Commit 1d31d3ff0f2b49e150412e3fdee54b2d0347f1e6

Authored by Antoine Goutenoir
1 parent 928351df
Exists in master

feat: move the center in the equidistant graph

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",