Commit 7ab0c489671dd05814137586e1a010e5597db2f1
1 parent
be2eb14f
Exists in
master
add equidistant-emissions-map.js
Showing
1 changed file
with
258 additions
and
0 deletions
Show diff stats
... | ... | @@ -0,0 +1,258 @@ |
1 | +function draw_equidistant_emissions_map(containerSelector, csvUrl) { | |
2 | + let margin = {top: 48, right: 88, bottom: 68, left: 98}, | |
3 | + width = 960 - margin.left - margin.right, | |
4 | + height = 540 - margin.top - margin.bottom; | |
5 | + const baseAttendeeCircleRadius = 3*0.62; | |
6 | + const legendAmount = 5; | |
7 | + const baseAttendeeCircleRadiusRatio = 150.0*0.62; | |
8 | + const baseAttendeeCircleColorRatio = 1500.0; | |
9 | + | |
10 | + | |
11 | + let svg = null; | |
12 | + let countriesPath = null; | |
13 | + let geoPath = null; | |
14 | + let mapProjection = null; | |
15 | + | |
16 | + let coordinatesFromFile = null; | |
17 | + let geoJsonFromFile = null; | |
18 | + | |
19 | + let attendeeAmountPerCountry = []; | |
20 | + let countryCoordinates = []; | |
21 | + let maxAttendeeAmount = 0; | |
22 | + | |
23 | + let rotateForm = document.createElement("select"); | |
24 | + | |
25 | + let processGeoJson = function (geojson, firstRead = true) { | |
26 | + geoJsonFromFile = geojson; | |
27 | + countriesPath.selectAll("path") | |
28 | + .data(geojson.features) | |
29 | + .enter() | |
30 | + .append("path") | |
31 | + .attr("d", geoPath) | |
32 | + .style("fill", "#444444AA"); | |
33 | + if(firstRead) | |
34 | + { | |
35 | + // Prepare country data | |
36 | + geojson.features.forEach((element) => { | |
37 | + let countryFound = false; | |
38 | + coordinatesFromFile.forEach((countryCoord) => { | |
39 | + if (countryFound) { | |
40 | + return; | |
41 | + } | |
42 | + if (countryCoord.country === element.properties.iso_a2 || countryCoord.country === element.properties.wb_a2) { | |
43 | + countryCoordinates.push({ | |
44 | + name: element.properties.name_long, | |
45 | + latitude: countryCoord.latitude, | |
46 | + longitude: countryCoord.longitude | |
47 | + }); | |
48 | + countryFound = true; | |
49 | + } | |
50 | + }); | |
51 | + if (!countryFound) { | |
52 | + console.log("missing country:" + element.properties.name_long + " by the alpha2:" + element.properties.iso_a2 + " or " + element.properties.wb_a2); | |
53 | + } | |
54 | + }); | |
55 | + | |
56 | + // Sort country data | |
57 | + function compare( a, b ) { | |
58 | + if ( a.name < b.name ){ | |
59 | + return -1; | |
60 | + } | |
61 | + if ( a.name > b.name ){ | |
62 | + return 1; | |
63 | + } | |
64 | + return 0; | |
65 | + } | |
66 | + countryCoordinates.sort( compare ); | |
67 | + // Create Option Elements | |
68 | + countryCoordinates.forEach((element) =>{ | |
69 | + let option = document.createElement("option"); | |
70 | + option.text = element.name; | |
71 | + option.value = "[ " + -element.longitude + ", " + -element.latitude + "] "; | |
72 | + rotateForm.append(option); | |
73 | + }); | |
74 | + // Read actual data Sample | |
75 | + d3.csv(csvUrl, on_csv_datum) | |
76 | + .then(on_csv_ready); | |
77 | + } | |
78 | + }; | |
79 | + | |
80 | + | |
81 | + let processCountryCoords = function (countryCoords) { | |
82 | + coordinatesFromFile = countryCoords; | |
83 | + if (geoJsonFromFile) { | |
84 | + processGeoJson(geoJsonFromFile, false); | |
85 | + on_csv_ready(); | |
86 | + } else { | |
87 | + d3.json('worldmap.geo.json').then(processGeoJson); | |
88 | + | |
89 | + } | |
90 | + let selector = containerSelector.slice(1, containerSelector.length); | |
91 | + document.getElementById(selector).insertAdjacentElement("beforeend", rotateForm); | |
92 | + rotateForm.onchange = function (event) { | |
93 | + mapProjection = d3.geoAzimuthalEquidistant().scale(100).rotate(JSON.parse(rotateForm.value)).translate([width/2, height/2]); | |
94 | + geoPath.projection(mapProjection); | |
95 | + // console.log(rotateForm.value); | |
96 | + countriesPath.remove(); | |
97 | + countriesPath = svg.append("g"); | |
98 | + processCountryCoords(coordinatesFromFile, false); | |
99 | + }; | |
100 | + // d3.select("svg").on("mousedown", function(event) { | |
101 | + // console.log(mapProjection.invert(d3.pointer(event))); | |
102 | + // }); | |
103 | + }; | |
104 | + | |
105 | + | |
106 | + const on_csv_datum = function (datum) { | |
107 | + let trainAttendee = parseInt(datum["train trips_amount"]); | |
108 | + let planeAttendee = parseInt(datum["plane trips_amount"]); | |
109 | + if (trainAttendee === 0 && planeAttendee === 0) { | |
110 | + return; | |
111 | + } | |
112 | + let attendeeAmount = trainAttendee + planeAttendee; | |
113 | + let distance_km = datum.distance_km / attendeeAmount; | |
114 | + let co2_kg = parseFloat(datum.co2_kg); | |
115 | + if (co2_kg === "NaN" || distance_km === "NaN") { | |
116 | + return; | |
117 | + } | |
118 | + let countryFound = false; | |
119 | + let countryName = datum["country"].slice(1, datum["country"].length); | |
120 | + attendeeAmountPerCountry.forEach((element) =>{ | |
121 | + if (element.country === countryName) | |
122 | + { | |
123 | + element.attendeeAmount += attendeeAmount; | |
124 | + maxAttendeeAmount = Math.max(maxAttendeeAmount, element.attendeeAmount); | |
125 | + countryFound = true; | |
126 | + } | |
127 | + | |
128 | + }); | |
129 | + if ( ! countryFound ) { | |
130 | + attendeeAmountPerCountry.push({ | |
131 | + country: countryName, | |
132 | + attendeeAmount : attendeeAmount | |
133 | + }); | |
134 | + maxAttendeeAmount = Math.max(maxAttendeeAmount, attendeeAmount); | |
135 | + } | |
136 | + }; | |
137 | + | |
138 | + | |
139 | + const drawCircle = function (x, y, radius, color, className = "legend") | |
140 | + { | |
141 | + svg.append("circle") | |
142 | + .attr("class", className) | |
143 | + .attr("cx", x) | |
144 | + .attr("cy", y ) | |
145 | + .attr("r", radius) | |
146 | + .style("fill", color) | |
147 | + .style("stroke", "rgba(0,0,0,0.7)") | |
148 | + .style("stroke-width", "1"); | |
149 | + }; | |
150 | + | |
151 | + | |
152 | + const setupLegend = function() { | |
153 | + svg.append("rect") | |
154 | + .attr("class", "legend") | |
155 | + .attr("transform", "translate(" + 2 + "," + 2 + ")") | |
156 | + .attr("width", 150) | |
157 | + .attr("height", legendAmount * 34 + 15) | |
158 | + .style("fill", "#EEEEEEFF") | |
159 | + .style("stroke", "#000000") | |
160 | + .style("stroke-width", "2"); | |
161 | + svg.append("text") | |
162 | + .attr("class", "legend") | |
163 | + .attr("transform", | |
164 | + "translate(" + 60 + " ," + | |
165 | + (28) + ")") | |
166 | + .style("text-anchor", "left") | |
167 | + .text((1).toFixed(0) + " attendees"); | |
168 | + let x = 10 + 20; | |
169 | + let y = 25; | |
170 | + let radius = baseAttendeeCircleRadius + (baseAttendeeCircleRadiusRatio/maxAttendeeAmount); | |
171 | + let color = "rgba(" + (-(baseAttendeeCircleColorRatio/maxAttendeeAmount) + 255.0) + | |
172 | + ", " + (-(baseAttendeeCircleColorRatio/maxAttendeeAmount) + 255.0) + | |
173 | + ", 240, 0.7)"; | |
174 | + drawCircle(x, y, radius, color); | |
175 | + for (let i = 1; i < legendAmount; i++) | |
176 | + { | |
177 | + svg.append("text") | |
178 | + .attr("class", "legend") | |
179 | + .attr("transform", | |
180 | + "translate(" + 60 + " ," + | |
181 | + (28 + 34 * i) + ")") | |
182 | + .style("text-anchor", "left") | |
183 | + .text((Math.floor(maxAttendeeAmount * (i / legendAmount))).toFixed(0) + " attendees"); | |
184 | + let x = 10 + 20; | |
185 | + let y = 25 + 34 * i; | |
186 | + let radius = baseAttendeeCircleRadius + Math.sqrt(maxAttendeeAmount*(i/legendAmount))*(baseAttendeeCircleRadiusRatio/maxAttendeeAmount); | |
187 | + let color = "rgba(" + (-Math.sqrt(maxAttendeeAmount*(i/legendAmount))*(baseAttendeeCircleColorRatio/maxAttendeeAmount) + 255.0) + | |
188 | + ", " + (-Math.sqrt(maxAttendeeAmount*(i/legendAmount))*(baseAttendeeCircleColorRatio/maxAttendeeAmount) + 255.0) + | |
189 | + ", 240, 0.7)"; | |
190 | + drawCircle(x, y, radius, color); | |
191 | + } | |
192 | + | |
193 | + // todo: describe those in the legend | |
194 | + // svg.append("circle") | |
195 | + // .attr("cx", function (d) { return width /2; }) | |
196 | + // .attr("cy", function (d) { return height/2; }) | |
197 | + // .attr("r", function (d) { return 25; }) | |
198 | + // .style("fill", function(d) { return "rgba(255, 0, 0, 0.7)"; }); | |
199 | + // svg.append("circle") | |
200 | + // .attr("class", "attendee-dot") | |
201 | + // .attr("cx", function (d) { return width /2; }) | |
202 | + // .attr("cy", function (d) { return height/2; }) | |
203 | + // .attr("r", function (d) { return 3; }) | |
204 | + // .style("fill", function(d) { return "rgba(255, 0, 0, 1.0)"; }); | |
205 | + }; | |
206 | + | |
207 | + | |
208 | + const on_csv_ready = function () { | |
209 | + svg.selectAll("circle.attendee-dot").remove(); | |
210 | + svg.selectAll("rect.legend").remove(); | |
211 | + svg.selectAll("circle.legend").remove(); | |
212 | + svg.selectAll("text.legend").remove(); | |
213 | + | |
214 | + setupLegend(); | |
215 | + attendeeAmountPerCountry.forEach((element) => | |
216 | + { | |
217 | + countryCoordinates.forEach((coordinate) => { | |
218 | + if (element.country === coordinate.name) | |
219 | + { | |
220 | + let x = mapProjection([coordinate.longitude, coordinate.latitude])[0]; | |
221 | + let y = mapProjection([coordinate.longitude, coordinate.latitude])[1]; | |
222 | + let radius = baseAttendeeCircleRadius + Math.sqrt(element.attendeeAmount)*(baseAttendeeCircleRadiusRatio/maxAttendeeAmount); | |
223 | + let color = "rgba(" + (-Math.sqrt(element.attendeeAmount) * (baseAttendeeCircleColorRatio/maxAttendeeAmount) + 255.0) + | |
224 | + ", " + (-Math.sqrt(element.attendeeAmount) * (baseAttendeeCircleColorRatio/maxAttendeeAmount) + 255.0) + | |
225 | + ", 240, 0.7)"; | |
226 | + | |
227 | + drawCircle(x, y, radius, color, "attendee-dot"); | |
228 | + } | |
229 | + }) | |
230 | + }); | |
231 | + svg.append("circle") | |
232 | + .attr("class", "attendee-dot") | |
233 | + .attr("cx", width /2) | |
234 | + .attr("cy", height/2) | |
235 | + .attr("r", 3) | |
236 | + .style("fill", "rgba(255, 0, 0, 1.0)"); | |
237 | + }; | |
238 | + | |
239 | + | |
240 | + document.addEventListener("DOMContentLoaded", () => { | |
241 | + width = Math.max(880, $(containerSelector).parent().width()); | |
242 | + width = width - margin.left - margin.right; | |
243 | + svg = d3.select(containerSelector) | |
244 | + .append("svg") | |
245 | + .attr("width", width) | |
246 | + .attr("height", height); | |
247 | + svg.append("circle") | |
248 | + .attr("cx", width /2) | |
249 | + .attr("cy", height/2) | |
250 | + .attr("r", 25) | |
251 | + .style("fill", "rgba(255, 0, 0, 0.7)"); | |
252 | + geoPath = d3.geoPath(); | |
253 | + countriesPath = svg.append("g"); | |
254 | + mapProjection = d3.geoAzimuthalEquidistant().scale(100).rotate([-122, -13]).translate([width/2, height/2]); | |
255 | + geoPath.projection(mapProjection); | |
256 | + d3.csv('countries-coordinates.csv').then(processCountryCoords); | |
257 | + }); | |
258 | +} | |
0 | 259 | \ No newline at end of file | ... | ... |