Commit 7ab0c489671dd05814137586e1a010e5597db2f1

Authored by Adrenesis
1 parent be2eb14f
Exists in master

add equidistant-emissions-map.js

Showing 1 changed file with 258 additions and 0 deletions   Show diff stats
flaskr/static/js/plots/equidistant-emissions-map.js 0 → 100644
@@ -0,0 +1,258 @@ @@ -0,0 +1,258 @@
  1 +function draw_equidistant_emissions_map(containerSelector, csvUrl) {
  2 + let margin = {top: 48, right: 88, bottom: 68, left: 98},
  3 + width = 960 - margin.left - margin.right,
  4 + height = 540 - margin.top - margin.bottom;
  5 + const baseAttendeeCircleRadius = 3*0.62;
  6 + const legendAmount = 5;
  7 + const baseAttendeeCircleRadiusRatio = 150.0*0.62;
  8 + const baseAttendeeCircleColorRatio = 1500.0;
  9 +
  10 +
  11 + let svg = null;
  12 + let countriesPath = null;
  13 + let geoPath = null;
  14 + let mapProjection = null;
  15 +
  16 + let coordinatesFromFile = null;
  17 + let geoJsonFromFile = null;
  18 +
  19 + let attendeeAmountPerCountry = [];
  20 + let countryCoordinates = [];
  21 + let maxAttendeeAmount = 0;
  22 +
  23 + let rotateForm = document.createElement("select");
  24 +
  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 + };
  79 +
  80 +
  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);
  88 +
  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 + };
  104 +
  105 +
  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 + }
  127 +
  128 + });
  129 + if ( ! countryFound ) {
  130 + attendeeAmountPerCountry.push({
  131 + country: countryName,
  132 + attendeeAmount : attendeeAmount
  133 + });
  134 + maxAttendeeAmount = Math.max(maxAttendeeAmount, attendeeAmount);
  135 + }
  136 + };
  137 +
  138 +
  139 + const drawCircle = function (x, y, radius, color, className = "legend")
  140 + {
  141 + svg.append("circle")
  142 + .attr("class", className)
  143 + .attr("cx", x)
  144 + .attr("cy", y )
  145 + .attr("r", radius)
  146 + .style("fill", color)
  147 + .style("stroke", "rgba(0,0,0,0.7)")
  148 + .style("stroke-width", "1");
  149 + };
  150 +
  151 +
  152 + const setupLegend = function() {
  153 + svg.append("rect")
  154 + .attr("class", "legend")
  155 + .attr("transform", "translate(" + 2 + "," + 2 + ")")
  156 + .attr("width", 150)
  157 + .attr("height", legendAmount * 34 + 15)
  158 + .style("fill", "#EEEEEEFF")
  159 + .style("stroke", "#000000")
  160 + .style("stroke-width", "2");
  161 + svg.append("text")
  162 + .attr("class", "legend")
  163 + .attr("transform",
  164 + "translate(" + 60 + " ," +
  165 + (28) + ")")
  166 + .style("text-anchor", "left")
  167 + .text((1).toFixed(0) + " attendees");
  168 + let x = 10 + 20;
  169 + let y = 25;
  170 + let radius = baseAttendeeCircleRadius + (baseAttendeeCircleRadiusRatio/maxAttendeeAmount);
  171 + let color = "rgba(" + (-(baseAttendeeCircleColorRatio/maxAttendeeAmount) + 255.0) +
  172 + ", " + (-(baseAttendeeCircleColorRatio/maxAttendeeAmount) + 255.0) +
  173 + ", 240, 0.7)";
  174 + drawCircle(x, y, radius, color);
  175 + for (let i = 1; i < legendAmount; i++)
  176 + {
  177 + svg.append("text")
  178 + .attr("class", "legend")
  179 + .attr("transform",
  180 + "translate(" + 60 + " ," +
  181 + (28 + 34 * i) + ")")
  182 + .style("text-anchor", "left")
  183 + .text((Math.floor(maxAttendeeAmount * (i / legendAmount))).toFixed(0) + " attendees");
  184 + let x = 10 + 20;
  185 + 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) +
  189 + ", 240, 0.7)";
  190 + drawCircle(x, y, radius, color);
  191 + }
  192 +
  193 + // todo: describe those in the legend
  194 + // svg.append("circle")
  195 + // .attr("cx", function (d) { return width /2; })
  196 + // .attr("cy", function (d) { return height/2; })
  197 + // .attr("r", function (d) { return 25; })
  198 + // .style("fill", function(d) { return "rgba(255, 0, 0, 0.7)"; });
  199 + // svg.append("circle")
  200 + // .attr("class", "attendee-dot")
  201 + // .attr("cx", function (d) { return width /2; })
  202 + // .attr("cy", function (d) { return height/2; })
  203 + // .attr("r", function (d) { return 3; })
  204 + // .style("fill", function(d) { return "rgba(255, 0, 0, 1.0)"; });
  205 + };
  206 +
  207 +
  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 + })
  230 + });
  231 + 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)");
  237 + };
  238 +
  239 +
  240 + document.addEventListener("DOMContentLoaded", () => {
  241 + width = Math.max(880, $(containerSelector).parent().width());
  242 + width = width - margin.left - margin.right;
  243 + svg = d3.select(containerSelector)
  244 + .append("svg")
  245 + .attr("width", width)
  246 + .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);
  257 + });
  258 +}
0 \ No newline at end of file 259 \ No newline at end of file