Compare View

switch
from
...
to
 
Commits (5)
flaskr/static/js/plots/emissions-equidistant-map.js
1 1 // jQuery-free
2 2 function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissionsDataUrl) {
  3 + const EARTH_RADIUS = 6371000; // meters
  4 +
3 5 let margin = {top: 48, right: 88, bottom: 68, left: 98},
4 6 width = 960 - margin.left - margin.right,
5 7 height = 540 - margin.top - margin.bottom;
... ... @@ -13,12 +15,15 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio
13 15  
14 16 let svg = null;
15 17 let cartaContainer = null;
  18 + let attendeesLayer = null;
16 19  
17 20 let geoPath = d3.geoPath();
18 21 let mapProjection = null;
19 22 let center_latitude = 0.0;
20 23 let center_longitude = 0.0;
21 24  
  25 + let projectionScale = 79.4188;
  26 +
22 27 // Per city
23 28 let maxAttendeeAmount = 0;
24 29 let maxFootprint = 0;
... ... @@ -147,8 +152,8 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio
147 152 // };
148 153  
149 154  
150   - const drawCircle = function (x, y, radius, color, className = "circle") {
151   - svg.append("circle")
  155 + const drawCircle = function (into, x, y, radius, color, className = "circle") {
  156 + into.append("circle")
152 157 .attr("class", className)
153 158 .attr("cx", x)
154 159 .attr("cy", y)
... ... @@ -181,7 +186,7 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio
181 186 let color = "rgba(" + (-(baseAttendeeCircleColorRatio / maxAttendeeAmount) + 255.0) +
182 187 ", " + (-(baseAttendeeCircleColorRatio / maxAttendeeAmount) + 255.0) +
183 188 ", 240, 0.7)";
184   - drawCircle(x, y, radius, color);
  189 + drawCircle(svg, x, y, radius, color);
185 190 for (let i = 1; i < legendAmount; i++) {
186 191 svg.append("text")
187 192 .attr("class", "legend")
... ... @@ -196,7 +201,7 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio
196 201 let color = "rgba(" + (-Math.sqrt(maxAttendeeAmount * (i / legendAmount)) * (baseAttendeeCircleColorRatio / maxAttendeeAmount) + 255.0) +
197 202 ", " + (-Math.sqrt(maxAttendeeAmount * (i / legendAmount)) * (baseAttendeeCircleColorRatio / maxAttendeeAmount) + 255.0) +
198 203 ", 240, 0.7)";
199   - drawCircle(x, y, radius, color, "legend");
  204 + drawCircle(svg, x, y, radius, color, "legend");
200 205 }
201 206  
202 207 // todo: describe those in the legend
... ... @@ -281,7 +286,7 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio
281 286  
282 287  
283 288 const redrawAttendees = () => {
284   - svg.selectAll("circle.attendee-dot").remove();
  289 + attendeesLayer.selectAll("circle.attendee-dot").remove();
285 290  
286 291 emissionsData.forEach((datum) => {
287 292 // console.log("Emission datum", datum);
... ... @@ -314,6 +319,7 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio
314 319 )
315 320 );
316 321 drawCircle(
  322 + attendeesLayer,
317 323 x, y, radius,
318 324 `rgba(${color}, ${color}, 240.0, 0.618)`,
319 325 "attendee-dot"
... ... @@ -323,19 +329,20 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio
323 329  
324 330  
325 331 const redrawWorldMap = () => {
326   - cartaContainer.selectAll("path").remove();
  332 + cartaContainer.selectAll("path.world-map").remove();
327 333 cartaContainer.selectAll("path")
328 334 .data(worldData.features)
329 335 .enter()
330 336 .append("path")
331 337 .attr("d", geoPath)
  338 + .classed("world-map", true)
332 339 .style("fill", "#d5d5d5");
333 340 };
334 341  
335 342  
336 343 const rebuildProjection = () => {
337 344 mapProjection = d3.geoAzimuthalEquidistant()
338   - .scale(79.4188)
  345 + .scale(projectionScale)
339 346 .rotate([
340 347 // Don't ask me why
341 348 -1 * center_longitude,
... ... @@ -346,20 +353,82 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio
346 353 };
347 354  
348 355  
  356 + const redrawProjected = () => {
  357 + redrawWorldMap();
  358 + redrawDistanceCircles();
  359 + redrawAttendees();
  360 + redrawCentralCircle();
  361 + };
  362 +
349 363 const recenterOnLatLon = (latitude, longitude) => {
350 364 center_latitude = latitude;
351 365 center_longitude = longitude;
352 366  
353 367 rebuildProjection();
354 368 // Draw in order from back to front
355   - redrawWorldMap();
356   - redrawDistanceCircles();
357   - redrawAttendees();
358   - redrawCentralCircle();
359   -
  369 + redrawProjected()
360 370 //setupLegend();
361 371 };
362 372  
  373 + const distanceCircles = {};
  374 +
  375 + const redrawDistanceCircle = (circle_name, distance_meters) => {
  376 + let distance_tooltip;
  377 + let distance_tooltip_shadow;
  378 + if ( ! distanceCircles.hasOwnProperty(circle_name)) {
  379 + distance_tooltip_shadow = svg
  380 + .append("text")
  381 + .classed("pointer-tooltip-"+circle_name, true)
  382 + .style("pointer-events", "none")
  383 + .style("stroke", "#FFFFFF99")
  384 + .style("stroke-width", "0.2em");
  385 + distance_tooltip = svg
  386 + .append("text")
  387 + .classed("pointer-tooltip-"+circle_name, true)
  388 + .style("pointer-events", "none");
  389 + distanceCircles[circle_name] = {
  390 + 'distance_tooltip': distance_tooltip,
  391 + 'distance_tooltip_shadow': distance_tooltip_shadow,
  392 + };
  393 + } else {
  394 + distance_tooltip = distanceCircles[circle_name]['distance_tooltip'];
  395 + distance_tooltip_shadow = distanceCircles[circle_name]['distance_tooltip_shadow'];
  396 + }
  397 +
  398 + const gCircleRadius = (distance_meters / EARTH_RADIUS) * 360 / Math.TAU;
  399 + const gCircle = d3.geoCircle();
  400 + gCircle
  401 + .center([center_longitude, center_latitude])
  402 + .radius(gCircleRadius);
  403 +
  404 + svg.selectAll("path.pointer-circle-"+circle_name).remove();
  405 + svg
  406 + .append("path")
  407 + .attr("d", geoPath(gCircle()))
  408 + .classed("pointer-circle-"+circle_name, true)
  409 + // .style("fill", "#21d51d");
  410 + .style("fill", "#00000000")
  411 + .style("stroke", "#0e5b0c")
  412 + .style("stroke-dasharray", 3);
  413 +
  414 + const tooltip_pos = mapProjection([center_longitude+gCircleRadius, center_latitude]);
  415 + distance_tooltip
  416 + .attr("transform", `translate(${tooltip_pos[0]}, ${tooltip_pos[1]})`)
  417 + .text(
  418 + ((distance_meters*0.001)).toFixed(0)
  419 + +
  420 + "km"
  421 + );
  422 + distance_tooltip_shadow
  423 + .attr("transform", `translate(${tooltip_pos[0]}, ${tooltip_pos[1]})`)
  424 + .text(
  425 + ((distance_meters*0.001)).toFixed(0)
  426 + +
  427 + "km"
  428 + );
  429 +
  430 + };
  431 +
363 432  
364 433 document.addEventListener("DOMContentLoaded", () => {
365 434 console.info("[Emissions Equidistant Map] Starting…");
... ... @@ -369,7 +438,8 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio
369 438 .append("svg")
370 439 .attr("width", width)
371 440 .attr("height", height);
372   - cartaContainer = svg.append("g");
  441 + cartaContainer = svg.append("g").classed("carta-layer", true);
  442 + attendeesLayer = svg.append("g").classed("attendees-layer", true);
373 443 Promise.all([
374 444 d3.csv(emissionsDataUrl),
375 445 d3.json(worldDataUrl),
... ... @@ -384,9 +454,55 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio
384 454 console.info("[Emissions Equidistant Map] Done.-");
385 455 });
386 456  
  457 + // console.log(d3.select(containerSelector));
  458 + let buttonContainer = d3.select(containerSelector)
  459 + .append("div")
  460 + .style("position", 'absolute')
  461 + .style("top", d3.select(containerSelector).property("clientTop") + 12)
  462 + .style("left",
  463 + d3.select(containerSelector).property("clientLeft") +
  464 + svg.attr("width")
  465 + - 40);
  466 + let zoomOutButton = buttonContainer
  467 + .append("input")
  468 + .attr("value", "-")
  469 + .attr("type", "button")
  470 + .style("width", 22);
  471 + let zoomInButton = buttonContainer
  472 + .append("input")
  473 + .attr("value", "+")
  474 + .attr("type", "button")
  475 + .style("width", 22);
  476 +
  477 + zoomOutButton.on("click", () => {
  478 + projectionScale *= 0.9;
  479 + rebuildProjection();
  480 + redrawProjected();
  481 + });
  482 +
  483 + zoomInButton.on("click", () => {
  484 + projectionScale *= 1.10;
  485 + rebuildProjection();
  486 + redrawProjected();
  487 + });
  488 +
387 489 d3.select(containerSelector+" svg").on("mousedown", function(event) {
388 490 const pointerLonLat = mapProjection.invert(d3.pointer(event));
389 491 recenterOnLatLon(pointerLonLat[1], pointerLonLat[0]);
390 492 });
  493 +
  494 + d3.select(containerSelector+" svg").on("mousemove", function(event) {
  495 + if ( ! mapProjection) {
  496 + console.warn("Too fast! Wait a little.");
  497 + return;
  498 + }
  499 + const pointerLonLat = mapProjection.invert(d3.pointer(event));
  500 + const centerLonLat = [center_longitude, center_latitude];
  501 + // Great Circle Distance
  502 + const gcd_radians = d3.geoDistance(pointerLonLat, centerLonLat);
  503 + const gcd_meters = gcd_radians * EARTH_RADIUS;
  504 +
  505 + redrawDistanceCircle("pointer", gcd_meters)
  506 + });
391 507 });
392 508 }
393 509 \ No newline at end of file
... ...
flaskr/static/js/plots/emissions-per-distance.js
... ... @@ -8,10 +8,20 @@ function draw_emissions_per_distance(containerSelector, csvUrl) {
8 8  
9 9  
10 10 function getTicks(maxValue, interval, startValue = 0) {
  11 + // console.log("getTicks", maxValue, interval, startValue);
  12 + if (0 === interval) {
  13 + console.error("No interval for ticks.");
  14 + return [0];
  15 + }
  16 + if (Math.sign(maxValue-startValue) !== Math.sign(interval)) {
  17 + console.warn("Wrong interval sign for ticks. Corrected.");
  18 + interval *= -1;
  19 + }
11 20 let range = [];
12 21 for (let i = startValue; i <= maxValue; i += interval) {
13 22 range.push(i);
14 23 }
  24 + // console.log("range", range);
15 25 return range;
16 26 }
17 27  
... ... @@ -21,8 +31,30 @@ function draw_emissions_per_distance(containerSelector, csvUrl) {
21 31 return range;
22 32 }
23 33  
24   - function getLeftTicks(maxemissions) {
25   - return getTicks(maxemissions, Math.floor((maxemissions / 8) / 1000) * 1000);
  34 + function getLeftTicks(maximumValue) {
  35 + maximumValue = 3200+1;
  36 + if (0 > maximumValue) {
  37 + console.error("Only positive values are supported on left axis.");
  38 + }
  39 + if (0 === maximumValue) {
  40 + return [0];
  41 + }
  42 + let ticksAmount = 8.0; // MUST BE > 2
  43 + let magnitude = 100000.0;
  44 + let interval = 0;
  45 + while (interval * (ticksAmount) < maximumValue) {
  46 + interval = (
  47 + Math.floor(
  48 + (maximumValue / (ticksAmount-1)) / magnitude
  49 + )
  50 + *
  51 + magnitude
  52 + );
  53 + //console.log("interval + magnitude", interval, magnitude);
  54 + magnitude *= 0.1;
  55 + }
  56 +
  57 + return getTicks(maximumValue, interval);
26 58 }
27 59  
28 60 function getRightTicks(maxemissionsPercent) {
... ... @@ -134,6 +166,7 @@ function draw_emissions_per_distance(containerSelector, csvUrl) {
134 166 }
135 167  
136 168 document.addEventListener("DOMContentLoaded", () => {
  169 + console.info("[Emissions Per Distance] Starting…");
137 170 width = Math.max(880, $(containerSelector).parent().width());
138 171 width = width - margin.left - margin.right;
139 172 let maxemissions = 0;
... ... @@ -163,7 +196,13 @@ function draw_emissions_per_distance(containerSelector, csvUrl) {
163 196 let attendeeNumber = trainAttendee + planeAttendee;
164 197 let distance_km = datum.distance_km / attendeeNumber;
165 198 let co2_kg = parseFloat(datum.co2_kg);
166   - if (co2_kg === "NaN" || distance_km / sliceThickness > 37 || distance_km === "NaN") {
  199 + if (
  200 + (co2_kg === "NaN")
  201 + ||
  202 + (distance_km === "NaN")
  203 + // ||
  204 + // (distance_km / sliceThickness > 37)
  205 + ) {
167 206 return;
168 207 }
169 208 rows.push(datum);
... ... @@ -173,6 +212,7 @@ function draw_emissions_per_distance(containerSelector, csvUrl) {
173 212 };
174 213  
175 214 const on_csv_ready = function () {
  215 + console.info("[Emissions Per Distance] Generating…");
176 216 for (let i = 0; i <= maxDistance / sliceThickness; i++) {
177 217 emissionsPerGroup[i] = 0;
178 218 attendeeNumberPerGroup[i] = 0;
... ... @@ -192,7 +232,6 @@ function draw_emissions_per_distance(containerSelector, csvUrl) {
192 232 maxemissionsPercent = Math.max(maxemissionsPercent, element / emissionsSum * 100.0)
193 233 });
194 234 maxDistance += 2000;
195   - // console.log(maxDistance);
196 235  
197 236 // Title
198 237 svg.append("text")
... ... @@ -207,7 +246,9 @@ function draw_emissions_per_distance(containerSelector, csvUrl) {
207 246 .domain([0, maxDistance])
208 247 .range([0, width]);
209 248 let xAxis = d3.axisBottom(x)
210   - .tickValues(getBottomTicks(maxDistance));
  249 + .ticks(11)
  250 + // .tickValues(getBottomTicks(maxDistance))
  251 + ;
211 252 svg.append("g")
212 253 .attr("transform", "translate(0," + height + ")")
213 254 .attr("class", "x axis")
... ... @@ -224,7 +265,9 @@ function draw_emissions_per_distance(containerSelector, csvUrl) {
224 265 .domain([0, maxemissions])
225 266 .range([height, 0]);
226 267 let ylAxis = d3.axisLeft(yl)
227   - .tickValues(getLeftTicks(maxemissions));
  268 + .ticks(13)
  269 + // .tickValues(getLeftTicks(maxemissions))
  270 + ;
228 271 svg.append("g")
229 272 .attr("class", "yl axis")
230 273 .call(ylAxis);
... ... @@ -234,7 +277,9 @@ function draw_emissions_per_distance(containerSelector, csvUrl) {
234 277 .domain([0, maxemissionsPercent])
235 278 .range([height, 0]);
236 279 let yrAxis = d3.axisRight(yr)
237   - .tickValues(getRightTicks(maxemissionsPercent));
  280 + .ticks(20)
  281 + // .tickValues(getRightTicks(maxemissionsPercent))
  282 + ;
238 283 svg.append("g")
239 284 .attr("transform", "translate(" + width + ", 0)")
240 285 .attr("class", "yr axis")
... ... @@ -268,7 +313,6 @@ function draw_emissions_per_distance(containerSelector, csvUrl) {
268 313 .thresholds(x.ticks(Math.floor(maxDistance / sliceThickness))); // then the numbers of bins
269 314  
270 315 let histolol = histogram(0);
271   - // console.log(histolol);
272 316 let barSettings = [];
273 317 emissionsPerGroup.forEach((element, index) => {
274 318 barSettings[index] = {
... ... @@ -306,6 +350,7 @@ function draw_emissions_per_distance(containerSelector, csvUrl) {
306 350 .style("z-index", "500")
307 351 .style("fill", "#4444E5");
308 352 addVerticalLineAndListenCursor(x, attendeeNumberPerGroup, attendeeSum);
  353 + console.info("[Emissions Per Distance] Done.");
309 354 };
310 355  
311 356 d3.csv(csvUrl, on_csv_datum)
... ...
flaskr/static/js/plots/sorted-emissions-inequality.js
... ... @@ -193,13 +193,13 @@ function draw_sorted_emissions_inequality(containerSelector, csvUrl) {
193 193 .attr("class", "no-pointer-events")
194 194 .style("display", "none")
195 195 .style("position", "absolute")
196   - .style("z-index", "-50")
  196 + .style("z-index", "10")
197 197 .style("width", "0px")
198 198 .style("height", (height) + "px")
199 199 .style("top", (margin.top) + "px")
200 200 .style("bottom", "30px")
201 201 .style("left", "0px")
202   - .style("background", "rgba(60, 200, 60, 0.3)");
  202 + .style("background", "rgba(60, 200, 60, 0.2)");
203 203  
204 204 d3.select(containerSelector)
205 205 .on("mousemove", function (event) {
... ...
flaskr/static/js/plots/utils.js
1 1 /** POLYFILLS **/
2 2 Math.log10 = Math.log10 || function(x) { return Math.log(x) * Math.LOG10E; };
3 3  
  4 +Math.TAU = Math.TAU || Math.PI * 2;
  5 +
  6 +
4 7 /**
5 8 * Useful for axes' domains on plots.
6 9 * @param value
... ...
flaskr/templates/estimation.html
... ... @@ -267,19 +267,19 @@ var plots_config = {
267 267 };
268 268  
269 269 {% if not estimation.is_many_to_many() %}
270   -/**
  270 +
271 271 draw_emissions_per_distance(
272 272 "#emissions_per_distance_histogram",
273 273 "/estimation/{{ estimation.public_id }}.csv"
274 274 );
275   -**/
  275 +
276 276 draw_sorted_emissions_inequality(
277 277 "#sorted_emissions_inequality",
278 278 "/estimation/{{ estimation.public_id }}.csv"
279 279 );
280 280 {% endif %}
281 281  
282   -/**
  282 +
283 283 draw_emissions_equidistant_map(
284 284 "#d3viz_emissions_equidistant_map",
285 285 {#"/static/public/data/worldmap.geo.json",#}
... ... @@ -288,7 +288,7 @@ draw_emissions_equidistant_map(
288 288 "/estimation/{{ estimation.public_id }}.csv"
289 289 {#"/estimation/{{ estimation.public_id }}/trips_to_destination_0.csv"#}
290 290 );
291   -**/
  291 +
292 292  
293 293 draw_travel_legs_worldmap(
294 294 "#d3viz_travels",
... ...