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 3 let margin = {top: 48, right: 88, bottom: 68, left: 98},
3 4 width = 960 - margin.left - margin.right,
4 5 height = 540 - margin.top - margin.bottom;
5   - const baseAttendeeCircleRadius = 3*0.62;
  6 + const baseAttendeeCircleRadius = 2;
6 7 const legendAmount = 5;
7   - const baseAttendeeCircleRadiusRatio = 150.0*0.62;
  8 + const baseAttendeeCircleRadiusRatio = 10.0;
8 9 const baseAttendeeCircleColorRatio = 1500.0;
9 10  
  11 + let emissionsData = null;
  12 + let worldData = null;
10 13  
11 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 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 151 svg.append("circle")
142 152 .attr("class", className)
143 153 .attr("cx", x)
144   - .attr("cy", y )
  154 + .attr("cy", y)
145 155 .attr("r", radius)
146 156 .style("fill", color)
147 157 .style("stroke", "rgba(0,0,0,0.7)")
... ... @@ -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 163 svg.append("rect")
154 164 .attr("class", "legend")
155 165 .attr("transform", "translate(" + 2 + "," + 2 + ")")
156   - .attr("width", 150)
  166 + .attr("width", 155)
157 167 .attr("height", legendAmount * 34 + 15)
158 168 .style("fill", "#EEEEEEFF")
159 169 .style("stroke", "#000000")
... ... @@ -161,33 +171,32 @@ function draw_emissions_equidistant_map(containerSelector, csvUrl) {
161 171 svg.append("text")
162 172 .attr("class", "legend")
163 173 .attr("transform",
164   - "translate(" + 60 + " ," +
  174 + "translate(" + 50 + " ," +
165 175 (28) + ")")
166 176 .style("text-anchor", "left")
167 177 .text((1).toFixed(0) + " attendees");
168 178 let x = 10 + 20;
169 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 183 ", 240, 0.7)";
174 184 drawCircle(x, y, radius, color);
175   - for (let i = 1; i < legendAmount; i++)
176   - {
  185 + for (let i = 1; i < legendAmount; i++) {
177 186 svg.append("text")
178 187 .attr("class", "legend")
179 188 .attr("transform",
180   - "translate(" + 60 + " ," +
  189 + "translate(" + 50 + " ," +
181 190 (28 + 34 * i) + ")")
182 191 .style("text-anchor", "left")
183 192 .text((Math.floor(maxAttendeeAmount * (i / legendAmount))).toFixed(0) + " attendees");
184 193 let x = 10 + 20;
185 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 198 ", 240, 0.7)";
190   - drawCircle(x, y, radius, color);
  199 + drawCircle(x, y, radius, color, "legend");
191 200 }
192 201  
193 202 // todo: describe those in the legend
... ... @@ -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 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 364 document.addEventListener("DOMContentLoaded", () => {
241   - width = Math.max(880, $(containerSelector).parent().width());
  365 + width = document.querySelector(containerSelector).parentElement.offsetWidth;
242 366 width = width - margin.left - margin.right;
243 367 svg = d3.select(containerSelector)
244 368 .append("svg")
245 369 .attr("width", width)
246 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 390 \ No newline at end of file
... ...
flaskr/templates/estimation.html
... ... @@ -212,6 +212,8 @@
212 212  
213 213 <hr>
214 214  
  215 + <div id="d3viz_emissions_equidistant_map" class="plot-container-noborder"></div>
  216 +
215 217 <div id="d3viz_travels" class="plot-container-noborder"></div>
216 218  
217 219 </div>
... ... @@ -254,6 +256,7 @@
254 256 <script src="/static/js/vendor/d3-geo-projection.v2.min.js"></script>
255 257 <script src="/static/js/plots/utils.js"></script>
256 258 <script src="/static/js/plots/emissions-per-distance.js"></script>
  259 +<script src="/static/js/plots/emissions-equidistant-map.js"></script>
257 260 <script src="/static/js/plots/sorted-emissions-inequality.js"></script>
258 261 <script src="/static/js/plots/travel-legs-worldmap.js"></script>
259 262  
... ... @@ -275,6 +278,15 @@ draw_sorted_emissions_inequality(
275 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 290 draw_travel_legs_worldmap(
279 291 "#d3viz_travels",
280 292 "/static/public/data/world-earth.geojson",
... ...