Commit 1d31d3ff0f2b49e150412e3fdee54b2d0347f1e6
1 parent
928351df
Exists in
master
feat: move the center in the equidistant graph
Showing
2 changed files
with
314 additions
and
171 deletions
Show diff stats
flaskr/static/js/plots/emissions-equidistant-map.js
1 | -function draw_emissions_equidistant_map(containerSelector, csvUrl) { | 1 | +// jQuery-free |
2 | +function draw_emissions_equidistant_map(containerSelector, worldDataUrl, countriesDataUrl, emissionsDataUrl) { | ||
2 | let margin = {top: 48, right: 88, bottom: 68, left: 98}, | 3 | let margin = {top: 48, right: 88, bottom: 68, left: 98}, |
3 | width = 960 - margin.left - margin.right, | 4 | width = 960 - margin.left - margin.right, |
4 | height = 540 - margin.top - margin.bottom; | 5 | height = 540 - margin.top - margin.bottom; |
5 | - const baseAttendeeCircleRadius = 3*0.62; | 6 | + const baseAttendeeCircleRadius = 2; |
6 | const legendAmount = 5; | 7 | const legendAmount = 5; |
7 | - const baseAttendeeCircleRadiusRatio = 150.0*0.62; | 8 | + const baseAttendeeCircleRadiusRatio = 10.0; |
8 | const baseAttendeeCircleColorRatio = 1500.0; | 9 | const baseAttendeeCircleColorRatio = 1500.0; |
9 | 10 | ||
11 | + let emissionsData = null; | ||
12 | + let worldData = null; | ||
10 | 13 | ||
11 | let svg = null; | 14 | let svg = null; |
12 | - let countriesPath = null; | ||
13 | - let geoPath = null; | ||
14 | - let mapProjection = null; | 15 | + let cartaContainer = null; |
15 | 16 | ||
16 | - let coordinatesFromFile = null; | ||
17 | - let geoJsonFromFile = null; | 17 | + let geoPath = d3.geoPath(); |
18 | + let mapProjection = null; | ||
19 | + let center_latitude = 0.0; | ||
20 | + let center_longitude = 0.0; | ||
18 | 21 | ||
19 | - let attendeeAmountPerCountry = []; | ||
20 | - let countryCoordinates = []; | 22 | + // Per city |
21 | let maxAttendeeAmount = 0; | 23 | let maxAttendeeAmount = 0; |
24 | + let maxFootprint = 0; | ||
22 | 25 | ||
23 | - let rotateForm = document.createElement("select"); | 26 | + // let coordinatesFromFile = null; |
27 | + // let geoJsonFromFile = null; | ||
24 | 28 | ||
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 | - }; | 29 | + // let attendeeAmountPerCountry = []; |
30 | + // let countryCoordinates = []; | ||
79 | 31 | ||
80 | 32 | ||
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); | 33 | + // let rotateForm = document.createElement("select"); |
88 | 34 | ||
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 | - }; | 35 | + // let processGeoJson = function (geojson, firstRead = true) { |
36 | + // geoJsonFromFile = geojson; | ||
37 | + // cartaContainer.selectAll("path") | ||
38 | + // .data(geojson.features) | ||
39 | + // .enter() | ||
40 | + // .append("path") | ||
41 | + // .attr("d", geoPath) | ||
42 | + // .style("fill", "#444444AA"); | ||
43 | + // if (firstRead) { | ||
44 | + // // Prepare country data | ||
45 | + // geojson.features.forEach((element) => { | ||
46 | + // let countryFound = false; | ||
47 | + // coordinatesFromFile.forEach((countryCoord) => { | ||
48 | + // if (countryFound) { | ||
49 | + // return; | ||
50 | + // } | ||
51 | + // if (countryCoord.country === element.properties.iso_a2 || countryCoord.country === element.properties.wb_a2) { | ||
52 | + // countryCoordinates.push({ | ||
53 | + // name: element.properties.name_long, | ||
54 | + // latitude: countryCoord.latitude, | ||
55 | + // longitude: countryCoord.longitude | ||
56 | + // }); | ||
57 | + // countryFound = true; | ||
58 | + // } | ||
59 | + // }); | ||
60 | + // if (!countryFound) { | ||
61 | + // console.log("missing country:" + element.properties.name_long + " by the alpha2:" + element.properties.iso_a2 + " or " + element.properties.wb_a2); | ||
62 | + // } | ||
63 | + // }); | ||
64 | + // | ||
65 | + // // Sort country data | ||
66 | + // function compare(a, b) { | ||
67 | + // if (a.name < b.name) { | ||
68 | + // return -1; | ||
69 | + // } | ||
70 | + // if (a.name > b.name) { | ||
71 | + // return 1; | ||
72 | + // } | ||
73 | + // return 0; | ||
74 | + // } | ||
75 | + // | ||
76 | + // countryCoordinates.sort(compare); | ||
77 | + // // Create Option Elements | ||
78 | + // countryCoordinates.forEach((element) => { | ||
79 | + // let option = document.createElement("option"); | ||
80 | + // option.text = element.name; | ||
81 | + // option.value = JSON.stringify([ | ||
82 | + // element.latitude, | ||
83 | + // element.longitude, | ||
84 | + // ]); | ||
85 | + // rotateForm.append(option); | ||
86 | + // }); | ||
87 | + // // Read actual data Sample | ||
88 | + // d3.csv(emissionsDataUrl, onEmissionsDatum) | ||
89 | + // .then(onEmissionsReady); | ||
90 | + // } | ||
91 | + // }; | ||
104 | 92 | ||
105 | 93 | ||
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 | - } | 94 | + // let processCountryCoords = function (countryCoords) { |
95 | + // coordinatesFromFile = countryCoords; | ||
96 | + // if (geoJsonFromFile) { | ||
97 | + // processGeoJson(geoJsonFromFile, false); | ||
98 | + // onEmissionsReady(); | ||
99 | + // } else { | ||
100 | + // d3.json(worldDataUrl).then(processGeoJson); | ||
101 | + // } | ||
102 | + // let selector = containerSelector.slice(1, containerSelector.length); | ||
103 | + // document.getElementById(selector).insertAdjacentElement("beforeend", rotateForm); | ||
104 | + // rotateForm.onchange = function (event) { | ||
105 | + // const [latitude, longitude] = JSON.parse(rotateForm.value); | ||
106 | + // recenterOnLatLon(latitude, longitude); | ||
107 | + // // console.log(rotateForm.value); | ||
108 | + // cartaContainer.remove(); | ||
109 | + // cartaContainer = svg.append("g"); | ||
110 | + // processCountryCoords(coordinatesFromFile, false); | ||
111 | + // }; | ||
112 | + // // d3.select("svg").on("mousedown", function(event) { | ||
113 | + // // console.log(mapProjection.invert(d3.pointer(event))); | ||
114 | + // // }); | ||
115 | + // }; | ||
127 | 116 | ||
128 | - }); | ||
129 | - if ( ! countryFound ) { | ||
130 | - attendeeAmountPerCountry.push({ | ||
131 | - country: countryName, | ||
132 | - attendeeAmount : attendeeAmount | ||
133 | - }); | ||
134 | - maxAttendeeAmount = Math.max(maxAttendeeAmount, attendeeAmount); | ||
135 | - } | ||
136 | - }; | 117 | + |
118 | + // const onEmissionsDatum = function (datum) { | ||
119 | + // let trainAttendee = parseInt(datum["train trips_amount"]); | ||
120 | + // let planeAttendee = parseInt(datum["plane trips_amount"]); | ||
121 | + // if (trainAttendee === 0 && planeAttendee === 0) { | ||
122 | + // return; | ||
123 | + // } | ||
124 | + // let attendeeAmount = trainAttendee + planeAttendee; | ||
125 | + // let distance_km = datum.distance_km / attendeeAmount; | ||
126 | + // let co2_kg = parseFloat(datum.co2_kg); | ||
127 | + // if (co2_kg === "NaN" || distance_km === "NaN") { | ||
128 | + // return; | ||
129 | + // } | ||
130 | + // let countryFound = false; | ||
131 | + // let countryName = datum["country"].slice(1, datum["country"].length); | ||
132 | + // attendeeAmountPerCountry.forEach((element) => { | ||
133 | + // if (element.country === countryName) { | ||
134 | + // element.attendeeAmount += attendeeAmount; | ||
135 | + // maxAttendeeAmount = Math.max(maxAttendeeAmount, element.attendeeAmount); | ||
136 | + // countryFound = true; | ||
137 | + // } | ||
138 | + // | ||
139 | + // }); | ||
140 | + // if (!countryFound) { | ||
141 | + // attendeeAmountPerCountry.push({ | ||
142 | + // country: countryName, | ||
143 | + // attendeeAmount: attendeeAmount | ||
144 | + // }); | ||
145 | + // maxAttendeeAmount = Math.max(maxAttendeeAmount, attendeeAmount); | ||
146 | + // } | ||
147 | + // }; | ||
137 | 148 | ||
138 | 149 | ||
139 | - const drawCircle = function (x, y, radius, color, className = "legend") | ||
140 | - { | 150 | + const drawCircle = function (x, y, radius, color, className = "circle") { |
141 | svg.append("circle") | 151 | svg.append("circle") |
142 | .attr("class", className) | 152 | .attr("class", className) |
143 | .attr("cx", x) | 153 | .attr("cx", x) |
144 | - .attr("cy", y ) | 154 | + .attr("cy", y) |
145 | .attr("r", radius) | 155 | .attr("r", radius) |
146 | .style("fill", color) | 156 | .style("fill", color) |
147 | .style("stroke", "rgba(0,0,0,0.7)") | 157 | .style("stroke", "rgba(0,0,0,0.7)") |
@@ -149,11 +159,11 @@ function draw_emissions_equidistant_map(containerSelector, csvUrl) { | @@ -149,11 +159,11 @@ function draw_emissions_equidistant_map(containerSelector, csvUrl) { | ||
149 | }; | 159 | }; |
150 | 160 | ||
151 | 161 | ||
152 | - const setupLegend = function() { | 162 | + const setupLegend = function () { |
153 | svg.append("rect") | 163 | svg.append("rect") |
154 | .attr("class", "legend") | 164 | .attr("class", "legend") |
155 | .attr("transform", "translate(" + 2 + "," + 2 + ")") | 165 | .attr("transform", "translate(" + 2 + "," + 2 + ")") |
156 | - .attr("width", 150) | 166 | + .attr("width", 155) |
157 | .attr("height", legendAmount * 34 + 15) | 167 | .attr("height", legendAmount * 34 + 15) |
158 | .style("fill", "#EEEEEEFF") | 168 | .style("fill", "#EEEEEEFF") |
159 | .style("stroke", "#000000") | 169 | .style("stroke", "#000000") |
@@ -161,33 +171,32 @@ function draw_emissions_equidistant_map(containerSelector, csvUrl) { | @@ -161,33 +171,32 @@ function draw_emissions_equidistant_map(containerSelector, csvUrl) { | ||
161 | svg.append("text") | 171 | svg.append("text") |
162 | .attr("class", "legend") | 172 | .attr("class", "legend") |
163 | .attr("transform", | 173 | .attr("transform", |
164 | - "translate(" + 60 + " ," + | 174 | + "translate(" + 50 + " ," + |
165 | (28) + ")") | 175 | (28) + ")") |
166 | .style("text-anchor", "left") | 176 | .style("text-anchor", "left") |
167 | .text((1).toFixed(0) + " attendees"); | 177 | .text((1).toFixed(0) + " attendees"); |
168 | let x = 10 + 20; | 178 | let x = 10 + 20; |
169 | let y = 25; | 179 | let y = 25; |
170 | - let radius = baseAttendeeCircleRadius + (baseAttendeeCircleRadiusRatio/maxAttendeeAmount); | ||
171 | - let color = "rgba(" + (-(baseAttendeeCircleColorRatio/maxAttendeeAmount) + 255.0) + | ||
172 | - ", " + (-(baseAttendeeCircleColorRatio/maxAttendeeAmount) + 255.0) + | 180 | + let radius = baseAttendeeCircleRadius + (baseAttendeeCircleRadiusRatio / maxAttendeeAmount); |
181 | + let color = "rgba(" + (-(baseAttendeeCircleColorRatio / maxAttendeeAmount) + 255.0) + | ||
182 | + ", " + (-(baseAttendeeCircleColorRatio / maxAttendeeAmount) + 255.0) + | ||
173 | ", 240, 0.7)"; | 183 | ", 240, 0.7)"; |
174 | drawCircle(x, y, radius, color); | 184 | drawCircle(x, y, radius, color); |
175 | - for (let i = 1; i < legendAmount; i++) | ||
176 | - { | 185 | + for (let i = 1; i < legendAmount; i++) { |
177 | svg.append("text") | 186 | svg.append("text") |
178 | .attr("class", "legend") | 187 | .attr("class", "legend") |
179 | .attr("transform", | 188 | .attr("transform", |
180 | - "translate(" + 60 + " ," + | 189 | + "translate(" + 50 + " ," + |
181 | (28 + 34 * i) + ")") | 190 | (28 + 34 * i) + ")") |
182 | .style("text-anchor", "left") | 191 | .style("text-anchor", "left") |
183 | .text((Math.floor(maxAttendeeAmount * (i / legendAmount))).toFixed(0) + " attendees"); | 192 | .text((Math.floor(maxAttendeeAmount * (i / legendAmount))).toFixed(0) + " attendees"); |
184 | let x = 10 + 20; | 193 | let x = 10 + 20; |
185 | let y = 25 + 34 * i; | 194 | 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) + | 195 | + let radius = baseAttendeeCircleRadius + Math.sqrt(maxAttendeeAmount * (i / legendAmount)) * (baseAttendeeCircleRadiusRatio / maxAttendeeAmount); |
196 | + let color = "rgba(" + (-Math.sqrt(maxAttendeeAmount * (i / legendAmount)) * (baseAttendeeCircleColorRatio / maxAttendeeAmount) + 255.0) + | ||
197 | + ", " + (-Math.sqrt(maxAttendeeAmount * (i / legendAmount)) * (baseAttendeeCircleColorRatio / maxAttendeeAmount) + 255.0) + | ||
189 | ", 240, 0.7)"; | 198 | ", 240, 0.7)"; |
190 | - drawCircle(x, y, radius, color); | 199 | + drawCircle(x, y, radius, color, "legend"); |
191 | } | 200 | } |
192 | 201 | ||
193 | // todo: describe those in the legend | 202 | // todo: describe those in the legend |
@@ -205,54 +214,176 @@ function draw_emissions_equidistant_map(containerSelector, csvUrl) { | @@ -205,54 +214,176 @@ function draw_emissions_equidistant_map(containerSelector, csvUrl) { | ||
205 | }; | 214 | }; |
206 | 215 | ||
207 | 216 | ||
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 | - }) | 217 | + // const onEmissionsReady = function () { |
218 | + // svg.selectAll("circle.attendee-dot").remove(); | ||
219 | + // svg.selectAll("rect.legend").remove(); | ||
220 | + // svg.selectAll("circle.legend").remove(); | ||
221 | + // svg.selectAll("text.legend").remove(); | ||
222 | + // | ||
223 | + // setupLegend(); | ||
224 | + // attendeeAmountPerCountry.forEach((element) => { | ||
225 | + // countryCoordinates.forEach((coordinate) => { | ||
226 | + // if (element.country === coordinate.name) { | ||
227 | + // let x = mapProjection([coordinate.longitude, coordinate.latitude])[0]; | ||
228 | + // let y = mapProjection([coordinate.longitude, coordinate.latitude])[1]; | ||
229 | + // let radius = baseAttendeeCircleRadius + Math.sqrt(element.attendeeAmount) * (baseAttendeeCircleRadiusRatio / maxAttendeeAmount); | ||
230 | + // let color = "rgba(" + (-Math.sqrt(element.attendeeAmount) * (baseAttendeeCircleColorRatio / maxAttendeeAmount) + 255.0) + | ||
231 | + // ", " + (-Math.sqrt(element.attendeeAmount) * (baseAttendeeCircleColorRatio / maxAttendeeAmount) + 255.0) + | ||
232 | + // ", 240, 0.7)"; | ||
233 | + // | ||
234 | + // drawCircle(x, y, radius, color, "attendee-dot"); | ||
235 | + // } | ||
236 | + // }) | ||
237 | + // }); | ||
238 | + // svg.append("circle") | ||
239 | + // .attr("class", "attendee-dot") | ||
240 | + // .attr("cx", width / 2) | ||
241 | + // .attr("cy", height / 2) | ||
242 | + // .attr("r", 3) | ||
243 | + // .style("fill", "rgba(255, 0, 0, 1.0)"); | ||
244 | + // }; | ||
245 | + | ||
246 | + | ||
247 | + const crunchEmissionsData = () => { | ||
248 | + emissionsData.forEach((datum, idx) => { | ||
249 | + let trainAttendeesAmount = parseInt(datum["train trips_amount"]); | ||
250 | + let planeAttendeesAmount = parseInt(datum["plane trips_amount"]); | ||
251 | + if (trainAttendeesAmount === 0 && planeAttendeesAmount === 0) { | ||
252 | + return; | ||
253 | + } | ||
254 | + let attendeesAmount = trainAttendeesAmount + planeAttendeesAmount; | ||
255 | + maxAttendeeAmount = Math.max(maxAttendeeAmount, attendeesAmount); | ||
256 | + emissionsData[idx].attendeeAmount = attendeesAmount; | ||
257 | + | ||
258 | + maxFootprint = Math.max(maxFootprint, datum.co2_kg); | ||
230 | }); | 259 | }); |
260 | + }; | ||
261 | + | ||
262 | + | ||
263 | + const redrawCentralCircle = () => { | ||
264 | + svg.selectAll("circle.central-dot").remove(); | ||
265 | + | ||
231 | svg.append("circle") | 266 | 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)"); | 267 | + .attr("cx", width / 2) |
268 | + .attr("cy", height / 2) | ||
269 | + .attr("r", 2) | ||
270 | + .classed("central-dot", true) | ||
271 | + .style("fill", "rgba(255, 0, 0, 0.777)"); | ||
272 | + }; | ||
273 | + | ||
274 | + | ||
275 | + const redrawDistanceCircles = () => { | ||
276 | + // TODO: draw a few circles and a label with the distance for each | ||
277 | + // โฆ | ||
278 | + // or not. | ||
279 | + // We might instead draw the circle under the mouse | ||
280 | + }; | ||
281 | + | ||
282 | + | ||
283 | + const redrawAttendees = () => { | ||
284 | + svg.selectAll("circle.attendee-dot").remove(); | ||
285 | + | ||
286 | + emissionsData.forEach((datum) => { | ||
287 | + // console.log("Emission datum", datum); | ||
288 | + let x = mapProjection([datum.longitude, datum.latitude])[0]; | ||
289 | + let y = mapProjection([datum.longitude, datum.latitude])[1]; | ||
290 | + let radius = ( | ||
291 | + baseAttendeeCircleRadius | ||
292 | + + | ||
293 | + ( | ||
294 | + baseAttendeeCircleRadiusRatio | ||
295 | + * | ||
296 | + Math.sqrt( | ||
297 | + datum.attendeeAmount | ||
298 | + / | ||
299 | + maxAttendeeAmount | ||
300 | + ) | ||
301 | + ) | ||
302 | + ); | ||
303 | + let color = ( | ||
304 | + 255.0 | ||
305 | + - | ||
306 | + ( | ||
307 | + baseAttendeeCircleColorRatio | ||
308 | + * | ||
309 | + Math.sqrt( | ||
310 | + datum.co2_kg | ||
311 | + / | ||
312 | + maxFootprint | ||
313 | + ) | ||
314 | + ) | ||
315 | + ); | ||
316 | + drawCircle( | ||
317 | + x, y, radius, | ||
318 | + `rgba(${color}, ${color}, 240.0, 0.618)`, | ||
319 | + "attendee-dot" | ||
320 | + ); | ||
321 | + }); | ||
322 | + }; | ||
323 | + | ||
324 | + | ||
325 | + const redrawWorldMap = () => { | ||
326 | + cartaContainer.selectAll("path").remove(); | ||
327 | + cartaContainer.selectAll("path") | ||
328 | + .data(worldData.features) | ||
329 | + .enter() | ||
330 | + .append("path") | ||
331 | + .attr("d", geoPath) | ||
332 | + .style("fill", "#d5d5d5"); | ||
333 | + }; | ||
334 | + | ||
335 | + | ||
336 | + const rebuildProjection = () => { | ||
337 | + mapProjection = d3.geoAzimuthalEquidistant() | ||
338 | + .scale(79.4188) | ||
339 | + .rotate([ | ||
340 | + // Don't ask me why | ||
341 | + -1 * center_longitude, | ||
342 | + -1 * center_latitude, | ||
343 | + ]) | ||
344 | + .translate([width / 2, height / 2]); | ||
345 | + geoPath.projection(mapProjection); | ||
346 | + }; | ||
347 | + | ||
348 | + | ||
349 | + const recenterOnLatLon = (latitude, longitude) => { | ||
350 | + center_latitude = latitude; | ||
351 | + center_longitude = longitude; | ||
352 | + | ||
353 | + rebuildProjection(); | ||
354 | + // Draw in order from back to front | ||
355 | + redrawWorldMap(); | ||
356 | + redrawDistanceCircles(); | ||
357 | + redrawAttendees(); | ||
358 | + redrawCentralCircle(); | ||
359 | + | ||
360 | + //setupLegend(); | ||
237 | }; | 361 | }; |
238 | 362 | ||
239 | 363 | ||
240 | document.addEventListener("DOMContentLoaded", () => { | 364 | document.addEventListener("DOMContentLoaded", () => { |
241 | - width = Math.max(880, $(containerSelector).parent().width()); | 365 | + width = document.querySelector(containerSelector).parentElement.offsetWidth; |
242 | width = width - margin.left - margin.right; | 366 | width = width - margin.left - margin.right; |
243 | svg = d3.select(containerSelector) | 367 | svg = d3.select(containerSelector) |
244 | .append("svg") | 368 | .append("svg") |
245 | .attr("width", width) | 369 | .attr("width", width) |
246 | .attr("height", height); | 370 | .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); | 371 | + cartaContainer = svg.append("g"); |
372 | + Promise.all([ | ||
373 | + d3.csv(emissionsDataUrl), | ||
374 | + d3.json(worldDataUrl), | ||
375 | + ]).then((allTheData) => { | ||
376 | + [emissionsData, worldData] = allTheData; | ||
377 | + crunchEmissionsData(); | ||
378 | + recenterOnLatLon( | ||
379 | + parseFloat(emissionsData[0].latitude), | ||
380 | + parseFloat(emissionsData[0].longitude) | ||
381 | + ); | ||
382 | + }); | ||
383 | + | ||
384 | + d3.select(containerSelector+" svg").on("mousedown", function(event) { | ||
385 | + const pointerLonLat = mapProjection.invert(d3.pointer(event)); | ||
386 | + recenterOnLatLon(pointerLonLat[1], pointerLonLat[0]); | ||
387 | + }); | ||
257 | }); | 388 | }); |
258 | } | 389 | } |
259 | \ No newline at end of file | 390 | \ No newline at end of file |
flaskr/templates/estimation.html
@@ -212,6 +212,8 @@ | @@ -212,6 +212,8 @@ | ||
212 | 212 | ||
213 | <hr> | 213 | <hr> |
214 | 214 | ||
215 | + <div id="d3viz_emissions_equidistant_map" class="plot-container-noborder"></div> | ||
216 | + | ||
215 | <div id="d3viz_travels" class="plot-container-noborder"></div> | 217 | <div id="d3viz_travels" class="plot-container-noborder"></div> |
216 | 218 | ||
217 | </div> | 219 | </div> |
@@ -254,6 +256,7 @@ | @@ -254,6 +256,7 @@ | ||
254 | <script src="/static/js/vendor/d3-geo-projection.v2.min.js"></script> | 256 | <script src="/static/js/vendor/d3-geo-projection.v2.min.js"></script> |
255 | <script src="/static/js/plots/utils.js"></script> | 257 | <script src="/static/js/plots/utils.js"></script> |
256 | <script src="/static/js/plots/emissions-per-distance.js"></script> | 258 | <script src="/static/js/plots/emissions-per-distance.js"></script> |
259 | +<script src="/static/js/plots/emissions-equidistant-map.js"></script> | ||
257 | <script src="/static/js/plots/sorted-emissions-inequality.js"></script> | 260 | <script src="/static/js/plots/sorted-emissions-inequality.js"></script> |
258 | <script src="/static/js/plots/travel-legs-worldmap.js"></script> | 261 | <script src="/static/js/plots/travel-legs-worldmap.js"></script> |
259 | 262 | ||
@@ -275,6 +278,15 @@ draw_sorted_emissions_inequality( | @@ -275,6 +278,15 @@ draw_sorted_emissions_inequality( | ||
275 | {% endif %} | 278 | {% endif %} |
276 | 279 | ||
277 | 280 | ||
281 | +draw_emissions_equidistant_map( | ||
282 | + "#d3viz_emissions_equidistant_map", | ||
283 | + {#"/static/public/data/worldmap.geo.json",#} | ||
284 | + "/static/public/data/world-earth.geojson", | ||
285 | + "/static/public/data/countries-coordinates.csv", | ||
286 | + "/estimation/{{ estimation.public_id }}.csv" | ||
287 | + {#"/estimation/{{ estimation.public_id }}/trips_to_destination_0.csv"#} | ||
288 | +); | ||
289 | + | ||
278 | draw_travel_legs_worldmap( | 290 | draw_travel_legs_worldmap( |
279 | "#d3viz_travels", | 291 | "#d3viz_travels", |
280 | "/static/public/data/world-earth.geojson", | 292 | "/static/public/data/world-earth.geojson", |