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 | 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,6 +15,7 @@ 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; |
... | ... | @@ -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 | 155 | .attr("class", className) |
153 | 156 | .attr("cx", x) |
154 | 157 | .attr("cy", y) |
... | ... | @@ -181,7 +184,7 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio |
181 | 184 | let color = "rgba(" + (-(baseAttendeeCircleColorRatio / maxAttendeeAmount) + 255.0) + |
182 | 185 | ", " + (-(baseAttendeeCircleColorRatio / maxAttendeeAmount) + 255.0) + |
183 | 186 | ", 240, 0.7)"; |
184 | - drawCircle(x, y, radius, color); | |
187 | + drawCircle(svg, x, y, radius, color); | |
185 | 188 | for (let i = 1; i < legendAmount; i++) { |
186 | 189 | svg.append("text") |
187 | 190 | .attr("class", "legend") |
... | ... | @@ -196,7 +199,7 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio |
196 | 199 | let color = "rgba(" + (-Math.sqrt(maxAttendeeAmount * (i / legendAmount)) * (baseAttendeeCircleColorRatio / maxAttendeeAmount) + 255.0) + |
197 | 200 | ", " + (-Math.sqrt(maxAttendeeAmount * (i / legendAmount)) * (baseAttendeeCircleColorRatio / maxAttendeeAmount) + 255.0) + |
198 | 201 | ", 240, 0.7)"; |
199 | - drawCircle(x, y, radius, color, "legend"); | |
202 | + drawCircle(svg, x, y, radius, color, "legend"); | |
200 | 203 | } |
201 | 204 | |
202 | 205 | // todo: describe those in the legend |
... | ... | @@ -281,7 +284,7 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio |
281 | 284 | |
282 | 285 | |
283 | 286 | const redrawAttendees = () => { |
284 | - svg.selectAll("circle.attendee-dot").remove(); | |
287 | + attendeesLayer.selectAll("circle.attendee-dot").remove(); | |
285 | 288 | |
286 | 289 | emissionsData.forEach((datum) => { |
287 | 290 | // console.log("Emission datum", datum); |
... | ... | @@ -314,6 +317,7 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio |
314 | 317 | ) |
315 | 318 | ); |
316 | 319 | drawCircle( |
320 | + attendeesLayer, | |
317 | 321 | x, y, radius, |
318 | 322 | `rgba(${color}, ${color}, 240.0, 0.618)`, |
319 | 323 | "attendee-dot" |
... | ... | @@ -323,12 +327,13 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio |
323 | 327 | |
324 | 328 | |
325 | 329 | const redrawWorldMap = () => { |
326 | - cartaContainer.selectAll("path").remove(); | |
330 | + cartaContainer.selectAll("path.world-map").remove(); | |
327 | 331 | cartaContainer.selectAll("path") |
328 | 332 | .data(worldData.features) |
329 | 333 | .enter() |
330 | 334 | .append("path") |
331 | 335 | .attr("d", geoPath) |
336 | + .classed("world-map", true) | |
332 | 337 | .style("fill", "#d5d5d5"); |
333 | 338 | }; |
334 | 339 | |
... | ... | @@ -360,6 +365,65 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio |
360 | 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 | 428 | document.addEventListener("DOMContentLoaded", () => { |
365 | 429 | console.info("[Emissions Equidistant Map] Startingโฆ"); |
... | ... | @@ -369,7 +433,8 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio |
369 | 433 | .append("svg") |
370 | 434 | .attr("width", width) |
371 | 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 | 438 | Promise.all([ |
374 | 439 | d3.csv(emissionsDataUrl), |
375 | 440 | d3.json(worldDataUrl), |
... | ... | @@ -388,5 +453,19 @@ function draw_emissions_equidistant_map(containerSelector, worldDataUrl, emissio |
388 | 453 | const pointerLonLat = mapProjection.invert(d3.pointer(event)); |
389 | 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 | 472 | \ No newline at end of file | ... | ... |
flaskr/static/js/plots/utils.js
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", | ... | ... |