-
Notifications
You must be signed in to change notification settings - Fork 6
/
iNat_obs_species_counts_packed_circles.html
192 lines (180 loc) · 9.58 KB
/
iNat_obs_species_counts_packed_circles.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="description" content="iNaturalist API Get Observation Leaf Taxa Counts" />
<title>iNaturalist API Get Observation Leaf Taxa Counts</title>
<style>
body { font:10pt Sans-Serif; }
circle { fill:green; opacity:0.5; stroke:white; }
text { font-family:sans-serif; fill:white; font-size:10px; text-anchor:middle; }
a { text-decoration:none; }
h1 { margin:0px; padding:0px; }
p { margin:5px 0px 0px 0px; padding:0px; }
</style>
<script src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body>
<div id="header"></div>
<div><svg id="main" width="650" height="650"><g></g></svg></div>
<script>
var ary_it = [
{icon_taxa:"Mammalia",disp_name:"Mammals",icon:"🐒",color:"mediumblue",count:0},
{icon_taxa:"Aves",disp_name:"Birds",icon:"🦅",color:"blue",count:0},
{icon_taxa:"Reptilia",disp_name:"Reptiles",icon:"🐍",color:"darkblue",count:0},
{icon_taxa:"Amphibia",disp_name:"Amphibians",icon:"🐸",color:"navy",count:0},
{icon_taxa:"Actinopterygii",disp_name:"Ray-Finned Fish",icon:"🐠",color:"midnightblue",count:0},
{icon_taxa:"Mollusca",disp_name:"Mollusks",icon:"🐌",color:"orangered",count:0},
{icon_taxa:"Insecta",disp_name:"Insects",icon:"🐞",color:"red",count:0},
{icon_taxa:"Arachnida",disp_name:"Arachnids",icon:"🕷",color:"crimson",count:0},
{icon_taxa:"Animalia",disp_name:"Other Animals",icon:"🦀",color:"royalblue",count:0},
{icon_taxa:"Plantae",disp_name:"Plants",icon:"🌱",color:"green",count:0},
{icon_taxa:"Fungi",disp_name:"Fungi",icon:"🍄",color:"magenta",count:0},
{icon_taxa:"Chromista",disp_name:"Chromista",color:"saddlebrown",count:0},
{icon_taxa:"Protozoa",disp_name:"Protozoa",color:"purple",count:0},
{icon_taxa:"unknown",disp_name:"Unknown",color:"gray",count:0},
{icon_taxa:"All",disp_name:"All",icon:"🌐",color:"none",count:0},
];
function furl(url,txt=url) { return '<a href="'+url+'">'+txt+'</a>'; };
function famp(str) { return str.replace(/&/g,'&'); };
function faddelem(etype,eparent=null,eclass=null,eid=null,ehtml=null,etext=null) {
var eobj = document.createElement(etype);
if (eclass!==null) { eobj.classList = eclass };
if (eid!==null) { eobj.id = eid };
if (ehtml!==null) { eobj.innerHTML = ehtml };
if (etext!==null) { eobj.innerText = etext };
if (eparent!==null) { eparent.appendChild(eobj); };
return eobj;
};
function ffontsize(r) { return r/5+'px'; }; // font size for labels
//return a color based on iconic taxon
function fitcolor(it) {
if (it==='All') { return 'none'; };
if (p_color) { return p_color; };
var obj = ary_it.find(o=>o.icon_taxa===it);
return obj.color||'gray';
};
//return increment or return the count for a given iconic taxon
function fitcount(it,incr=null) {
var obj = ary_it.find(o=>o.icon_taxa===it);
if (incr) {
obj.count = obj.count+incr;
//if incrementing any taxon, also increment the All taxon
var obja = ary_it.find(o=>o.icon_taxa==='All');
obja.count = obja.count+incr;
};
return obj.count;
};
function fvisualize(data) {
if (!data || data.results.length===0) {
faddelem('p',header,null,null,'No records were returned for this set of parameters.')
return;
};
var taxa = data.results;
//put taxon records into an array
var arytaxa = [];
for (var i=0; i<taxa.length; i++) {
arytaxa.push({
taxon_name: taxa[i].taxon.name,
common_name: taxa[i].taxon.preferred_common_name,
iconic_taxon: taxa[i].taxon.iconic_taxon_name,
count: taxa[i].count
});
fitcount(taxa[i].taxon.iconic_taxon_name,taxa[i].count);
};
//data is already sorted by count desc at taxon level; here, sort by count desc at iconic taxon level
if (['1','2'].includes(p_style)) {
arytaxa.sort(function(a, b) {
if(fitcount(a.iconic_taxon) < fitcount(b.iconic_taxon)) { return 1; }
if(fitcount(a.iconic_taxon) > fitcount(b.iconic_taxon)) { return -1; }
return 0;
});
};
//put taxon records into a hierarchical structure, with All at top level, taxon records at the bottom level, and, in some cases, iconic taxon in a middle level
var hierarchy = {name:'All',group:'All',children:[]};
var children = [];
for (var j=0; j<arytaxa.length; j++) {
var label = (p_label==='common_name') ? (arytaxa[j].common_name || arytaxa[j].taxon_name) : arytaxa[j].taxon_name
if (p_style==='2') {
children.push({name:label,group:arytaxa[j].iconic_taxon,count:arytaxa[j].count});
if (j+1==arytaxa.length||arytaxa[j].iconic_taxon!=arytaxa[j+1].iconic_taxon) {
hierarchy.children.push({name:arytaxa[j].iconic_taxon,group:arytaxa[j].iconic_taxon,children:children});
children = [];
};
}
else { hierarchy.children.push({name:label,group:arytaxa[j].iconic_taxon,count:arytaxa[j].count}); };
};
//create the packed circles visualization
//adapted from:
//https://www.d3indepth.com/layouts/#pack-layout
//https://bl.ocks.org/d3indepth/e9e845cb419049497369af4347e4348a
var packLayout = d3.pack()
.size([p_size,p_size])
.padding(0);
var rootNode = d3.hierarchy(hierarchy);
rootNode.sum(function(d) { return d.count; });
packLayout(rootNode);
var nodes = d3.select('svg g')
.selectAll('g')
.data(rootNode.descendants())
.enter()
.append('g')
.attr('transform', function(d) {return 'translate(' + [d.x, d.y] + ')'});
nodes
.append('circle')
.attr('r', function(d) { return d.r; })
.style('fill', function(d) { return fitcolor(d.data.group); })
.style('opacity',g_opacity);
nodes
.append('text')
.style('font-size', function(d) { return ffontsize(d.r); })
.text(function(d) { return d.children === undefined ? d.data.name : ''; });
};
//get parameters from URL
let winurlstr = window.location.href;
let winurlsearchstr = window.location.search;
let winurlexsearchstr = winurlstr.replace(winurlsearchstr,'');
var winurlparams = new URLSearchParams(winurlsearchstr.substring(1));
var p_size = winurlparams.get('size'); // this will be set to fill the window later, if a value was not specified
var p_style = winurlparams.get('style') || 0;
var p_color = winurlparams.get('color');
var p_label = winurlparams.get('label') || 'name';
winurlparams.delete('size');
winurlparams.delete('style');
winurlparams.delete('color');
winurlparams.delete('label');
var header = document.getElementById('header');
var apibase = 'https://api.inaturalist.org/v1/observations/species_counts';
var apiurl = apibase+((winurlparams!='')?('?'+winurlparams):'');
var apirefurl = 'http://api.inaturalist.org/v1/docs/#!/Observations/get_observations_species_counts';
var apirefname = 'iNaturalist Obs Species Counts';
var apiref = furl(apirefurl,apirefname);
faddelem('h1',header,null,null,apirefname+' ⇒ Packed Circles');
var g_opacity = (p_style==='2')?0.55:1;
if (window.location.search==='') {
faddelem('p',header,null,null,'This page displays the response from the '+apiref+' API endpoint in a packed circles visualization (using the D3.js library). To use this page, add at least one query parameter to the URL. See '+furl(apirefurl)+' for available query parameters.');
faddelem('p',header,null,null,'Suppose the address of this page is '+winurlexsearchstr+', <br />and you want to visualize the results of '+furl(famp(apibase+'?introduced=true&place_id=49610&quality_grade=research'))+', <br />then you would open '+furl(famp(winurlexsearchstr+'?introduced=true&place_id=49610&quality_grade=research'))+' in your browser.');
faddelem('p',header,null,null,'Several page-specific parameters are also available:<br />"&size=" defines a custom visualization size in pixels<br />"&color=" defines a custom color for the circles<br />"&label=common_name" will use common names for the labels<br />"&style=1" orders the circles by iconic taxon before visualizing<br />"&style=2" groups packed circles into iconic taxon groups');
faddelem('p',header,null,null,'Note that a single request to this API endpoint will return no more than 500 records. Although it is possible to make multiple requests, a visualization with more than 500 circles is very busy. So this page will stick with the endpoint limit and visualize only up to 500 circles.');
faddelem('p',header,null,null,'The idea for this kind of visualization came from Alexis Garretson. Her R code to produce a similar visualization is available here: '+furl('https://alexis-catherine.github.io/visualization/inaturalist-invasive-bubble-plot/')+'.');
}
else {
faddelem('p',header,null,null,'This is the base query: '+furl(famp(apiurl))+'. (This page will accept most parameters from the '+apiref+' API endpoint.)');
p_size = p_size || ((window.innerWidth>(window.innerHeight-header.offsetHeight)?(window.innerHeight-header.offsetHeight):window.innerWidth)-20); // fill the window, if size was not specified
var main = document.getElementById('main');
main.style.width = p_size+'px';
main.style.height = p_size+'px';
fetch(apiurl)
.then((response) => {
if (!response.ok) { throw new Error(response.status+' ('+response.statusText+') returned from '+response.url); };
return response.json();
})
.then((data) => { fvisualize(data); })
.catch((err) => {
console.error(err.message);
faddelem('p',header,null,null,'There was a problem retrieving data. Error '+err.message+'.')
});
};
</script>
</body>
</html>