Commit 289db173f905e370cf9396476fdcd0d473048c33
1 parent
e0905f97
Exists in
master
feat: add the distance circle under the pointer
Co-authored by: @Adrenesis
Showing
3 changed files
with
93 additions
and
11 deletions
Show diff stats
flaskr/static/js/plots/emissions-equidistant-map.js
1 | // jQuery-free | 1 | // jQuery-free |
2 | function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissionsDataUrl) { | 2 | function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissionsDataUrl) { |
3 | + const EARTH_RADIUS = 6371000; // meters | ||
4 | + | ||
3 | let margin = {top: 48, right: 88, bottom: 68, left: 98}, | 5 | let margin = {top: 48, right: 88, bottom: 68, left: 98}, |
4 | width = 960 - margin.left - margin.right, | 6 | width = 960 - margin.left - margin.right, |
5 | height = 540 - margin.top - margin.bottom; | 7 | height = 540 - margin.top - margin.bottom; |
@@ -13,6 +15,7 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio | @@ -13,6 +15,7 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio | ||
13 | 15 | ||
14 | let svg = null; | 16 | let svg = null; |
15 | let cartaContainer = null; | 17 | let cartaContainer = null; |
18 | + let attendeesLayer = null; | ||
16 | 19 | ||
17 | let geoPath = d3.geoPath(); | 20 | let geoPath = d3.geoPath(); |
18 | let mapProjection = null; | 21 | let mapProjection = null; |
@@ -147,8 +150,8 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio | @@ -147,8 +150,8 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio | ||
147 | // }; | 150 | // }; |
148 | 151 | ||
149 | 152 | ||
150 | - const drawCircle = function (x, y, radius, color, className = "circle") { | ||
151 | - svg.append("circle") | 153 | + const drawCircle = function (into, x, y, radius, color, className = "circle") { |
154 | + into.append("circle") | ||
152 | .attr("class", className) | 155 | .attr("class", className) |
153 | .attr("cx", x) | 156 | .attr("cx", x) |
154 | .attr("cy", y) | 157 | .attr("cy", y) |
@@ -181,7 +184,7 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio | @@ -181,7 +184,7 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio | ||
181 | let color = "rgba(" + (-(baseAttendeeCircleColorRatio / maxAttendeeAmount) + 255.0) + | 184 | let color = "rgba(" + (-(baseAttendeeCircleColorRatio / maxAttendeeAmount) + 255.0) + |
182 | ", " + (-(baseAttendeeCircleColorRatio / maxAttendeeAmount) + 255.0) + | 185 | ", " + (-(baseAttendeeCircleColorRatio / maxAttendeeAmount) + 255.0) + |
183 | ", 240, 0.7)"; | 186 | ", 240, 0.7)"; |
184 | - drawCircle(x, y, radius, color); | 187 | + drawCircle(svg, x, y, radius, color); |
185 | for (let i = 1; i < legendAmount; i++) { | 188 | for (let i = 1; i < legendAmount; i++) { |
186 | svg.append("text") | 189 | svg.append("text") |
187 | .attr("class", "legend") | 190 | .attr("class", "legend") |
@@ -196,7 +199,7 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio | @@ -196,7 +199,7 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio | ||
196 | let color = "rgba(" + (-Math.sqrt(maxAttendeeAmount * (i / legendAmount)) * (baseAttendeeCircleColorRatio / maxAttendeeAmount) + 255.0) + | 199 | let color = "rgba(" + (-Math.sqrt(maxAttendeeAmount * (i / legendAmount)) * (baseAttendeeCircleColorRatio / maxAttendeeAmount) + 255.0) + |
197 | ", " + (-Math.sqrt(maxAttendeeAmount * (i / legendAmount)) * (baseAttendeeCircleColorRatio / maxAttendeeAmount) + 255.0) + | 200 | ", " + (-Math.sqrt(maxAttendeeAmount * (i / legendAmount)) * (baseAttendeeCircleColorRatio / maxAttendeeAmount) + 255.0) + |
198 | ", 240, 0.7)"; | 201 | ", 240, 0.7)"; |
199 | - drawCircle(x, y, radius, color, "legend"); | 202 | + drawCircle(svg, x, y, radius, color, "legend"); |
200 | } | 203 | } |
201 | 204 | ||
202 | // todo: describe those in the legend | 205 | // todo: describe those in the legend |
@@ -281,7 +284,7 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio | @@ -281,7 +284,7 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio | ||
281 | 284 | ||
282 | 285 | ||
283 | const redrawAttendees = () => { | 286 | const redrawAttendees = () => { |
284 | - svg.selectAll("circle.attendee-dot").remove(); | 287 | + attendeesLayer.selectAll("circle.attendee-dot").remove(); |
285 | 288 | ||
286 | emissionsData.forEach((datum) => { | 289 | emissionsData.forEach((datum) => { |
287 | // console.log("Emission datum", datum); | 290 | // console.log("Emission datum", datum); |
@@ -314,6 +317,7 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio | @@ -314,6 +317,7 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio | ||
314 | ) | 317 | ) |
315 | ); | 318 | ); |
316 | drawCircle( | 319 | drawCircle( |
320 | + attendeesLayer, | ||
317 | x, y, radius, | 321 | x, y, radius, |
318 | `rgba(${color}, ${color}, 240.0, 0.618)`, | 322 | `rgba(${color}, ${color}, 240.0, 0.618)`, |
319 | "attendee-dot" | 323 | "attendee-dot" |
@@ -323,12 +327,13 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio | @@ -323,12 +327,13 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio | ||
323 | 327 | ||
324 | 328 | ||
325 | const redrawWorldMap = () => { | 329 | const redrawWorldMap = () => { |
326 | - cartaContainer.selectAll("path").remove(); | 330 | + cartaContainer.selectAll("path.world-map").remove(); |
327 | cartaContainer.selectAll("path") | 331 | cartaContainer.selectAll("path") |
328 | .data(worldData.features) | 332 | .data(worldData.features) |
329 | .enter() | 333 | .enter() |
330 | .append("path") | 334 | .append("path") |
331 | .attr("d", geoPath) | 335 | .attr("d", geoPath) |
336 | + .classed("world-map", true) | ||
332 | .style("fill", "#d5d5d5"); | 337 | .style("fill", "#d5d5d5"); |
333 | }; | 338 | }; |
334 | 339 | ||
@@ -360,6 +365,65 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio | @@ -360,6 +365,65 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio | ||
360 | //setupLegend(); | 365 | //setupLegend(); |
361 | }; | 366 | }; |
362 | 367 | ||
368 | + const distanceCircles = {}; | ||
369 | + | ||
370 | + const redrawDistanceCircle = (circle_name, distance_meters) => { | ||
371 | + let distance_tooltip; | ||
372 | + let distance_tooltip_shadow; | ||
373 | + if ( ! distanceCircles.hasOwnProperty(circle_name)) { | ||
374 | + distance_tooltip_shadow = svg | ||
375 | + .append("text") | ||
376 | + .classed("pointer-tooltip-"+circle_name, true) | ||
377 | + .style("pointer-events", "none") | ||
378 | + .style("stroke", "#FFFFFF99") | ||
379 | + .style("stroke-width", "0.2em"); | ||
380 | + distance_tooltip = svg | ||
381 | + .append("text") | ||
382 | + .classed("pointer-tooltip-"+circle_name, true) | ||
383 | + .style("pointer-events", "none"); | ||
384 | + distanceCircles[circle_name] = { | ||
385 | + 'distance_tooltip': distance_tooltip, | ||
386 | + 'distance_tooltip_shadow': distance_tooltip_shadow, | ||
387 | + }; | ||
388 | + } else { | ||
389 | + distance_tooltip = distanceCircles[circle_name]['distance_tooltip']; | ||
390 | + distance_tooltip_shadow = distanceCircles[circle_name]['distance_tooltip_shadow']; | ||
391 | + } | ||
392 | + | ||
393 | + const gCircleRadius = (distance_meters / EARTH_RADIUS) * 360 / Math.TAU; | ||
394 | + const gCircle = d3.geoCircle(); | ||
395 | + gCircle | ||
396 | + .center([center_longitude, center_latitude]) | ||
397 | + .radius(gCircleRadius); | ||
398 | + | ||
399 | + svg.selectAll("path.pointer-circle-"+circle_name).remove(); | ||
400 | + svg | ||
401 | + .append("path") | ||
402 | + .attr("d", geoPath(gCircle())) | ||
403 | + .classed("pointer-circle-"+circle_name, true) | ||
404 | + // .style("fill", "#21d51d"); | ||
405 | + .style("fill", "#00000000") | ||
406 | + .style("stroke", "#0e5b0c") | ||
407 | + .style("stroke-dasharray", 3); | ||
408 | + | ||
409 | + const tooltip_pos = mapProjection([center_longitude+gCircleRadius, center_latitude]); | ||
410 | + distance_tooltip | ||
411 | + .attr("transform", `translate(${tooltip_pos[0]}, ${tooltip_pos[1]})`) | ||
412 | + .text( | ||
413 | + ((distance_meters*0.001)).toFixed(0) | ||
414 | + + | ||
415 | + "km" | ||
416 | + ); | ||
417 | + distance_tooltip_shadow | ||
418 | + .attr("transform", `translate(${tooltip_pos[0]}, ${tooltip_pos[1]})`) | ||
419 | + .text( | ||
420 | + ((distance_meters*0.001)).toFixed(0) | ||
421 | + + | ||
422 | + "km" | ||
423 | + ); | ||
424 | + | ||
425 | + }; | ||
426 | + | ||
363 | 427 | ||
364 | document.addEventListener("DOMContentLoaded", () => { | 428 | document.addEventListener("DOMContentLoaded", () => { |
365 | console.info("[Emissions Equidistant Map] Starting…"); | 429 | console.info("[Emissions Equidistant Map] Starting…"); |
@@ -369,7 +433,8 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio | @@ -369,7 +433,8 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio | ||
369 | .append("svg") | 433 | .append("svg") |
370 | .attr("width", width) | 434 | .attr("width", width) |
371 | .attr("height", height); | 435 | .attr("height", height); |
372 | - cartaContainer = svg.append("g"); | 436 | + cartaContainer = svg.append("g").classed("carta-layer", true); |
437 | + attendeesLayer = svg.append("g").classed("attendees-layer", true); | ||
373 | Promise.all([ | 438 | Promise.all([ |
374 | d3.csv(emissionsDataUrl), | 439 | d3.csv(emissionsDataUrl), |
375 | d3.json(worldDataUrl), | 440 | d3.json(worldDataUrl), |
@@ -388,5 +453,19 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio | @@ -388,5 +453,19 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio | ||
388 | const pointerLonLat = mapProjection.invert(d3.pointer(event)); | 453 | const pointerLonLat = mapProjection.invert(d3.pointer(event)); |
389 | recenterOnLatLon(pointerLonLat[1], pointerLonLat[0]); | 454 | recenterOnLatLon(pointerLonLat[1], pointerLonLat[0]); |
390 | }); | 455 | }); |
456 | + | ||
457 | + d3.select(containerSelector+" svg").on("mousemove", function(event) { | ||
458 | + if ( ! mapProjection) { | ||
459 | + console.warn("Too fast! Wait a little."); | ||
460 | + return; | ||
461 | + } | ||
462 | + const pointerLonLat = mapProjection.invert(d3.pointer(event)); | ||
463 | + const centerLonLat = [center_longitude, center_latitude]; | ||
464 | + // Great Circle Distance | ||
465 | + const gcd_radians = d3.geoDistance(pointerLonLat, centerLonLat); | ||
466 | + const gcd_meters = gcd_radians * EARTH_RADIUS; | ||
467 | + | ||
468 | + redrawDistanceCircle("pointer", gcd_meters) | ||
469 | + }); | ||
391 | }); | 470 | }); |
392 | } | 471 | } |
393 | \ No newline at end of file | 472 | \ No newline at end of file |
flaskr/static/js/plots/utils.js
1 | /** POLYFILLS **/ | 1 | /** POLYFILLS **/ |
2 | Math.log10 = Math.log10 || function(x) { return Math.log(x) * Math.LOG10E; }; | 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 | * Useful for axes' domains on plots. | 8 | * Useful for axes' domains on plots. |
6 | * @param value | 9 | * @param value |
flaskr/templates/estimation.html
@@ -267,19 +267,19 @@ var plots_config = { | @@ -267,19 +267,19 @@ var plots_config = { | ||
267 | }; | 267 | }; |
268 | 268 | ||
269 | {% if not estimation.is_many_to_many() %} | 269 | {% if not estimation.is_many_to_many() %} |
270 | -/** | 270 | + |
271 | draw_emissions_per_distance( | 271 | draw_emissions_per_distance( |
272 | "#emissions_per_distance_histogram", | 272 | "#emissions_per_distance_histogram", |
273 | "/estimation/{{ estimation.public_id }}.csv" | 273 | "/estimation/{{ estimation.public_id }}.csv" |
274 | ); | 274 | ); |
275 | -**/ | 275 | + |
276 | draw_sorted_emissions_inequality( | 276 | draw_sorted_emissions_inequality( |
277 | "#sorted_emissions_inequality", | 277 | "#sorted_emissions_inequality", |
278 | "/estimation/{{ estimation.public_id }}.csv" | 278 | "/estimation/{{ estimation.public_id }}.csv" |
279 | ); | 279 | ); |
280 | {% endif %} | 280 | {% endif %} |
281 | 281 | ||
282 | -/** | 282 | + |
283 | draw_emissions_equidistant_map( | 283 | draw_emissions_equidistant_map( |
284 | "#d3viz_emissions_equidistant_map", | 284 | "#d3viz_emissions_equidistant_map", |
285 | {#"/static/public/data/worldmap.geo.json",#} | 285 | {#"/static/public/data/worldmap.geo.json",#} |
@@ -288,7 +288,7 @@ draw_emissions_equidistant_map( | @@ -288,7 +288,7 @@ draw_emissions_equidistant_map( | ||
288 | "/estimation/{{ estimation.public_id }}.csv" | 288 | "/estimation/{{ estimation.public_id }}.csv" |
289 | {#"/estimation/{{ estimation.public_id }}/trips_to_destination_0.csv"#} | 289 | {#"/estimation/{{ estimation.public_id }}/trips_to_destination_0.csv"#} |
290 | ); | 290 | ); |
291 | -**/ | 291 | + |
292 | 292 | ||
293 | draw_travel_legs_worldmap( | 293 | draw_travel_legs_worldmap( |
294 | "#d3viz_travels", | 294 | "#d3viz_travels", |