The legend for proportional circles sizes is drawn automatically.

Reprojectiong on the fly from WGS84 to the newly available AirOcean (Dymaxion projection) in D3v5.

Example data: number of political reports sent by Swiss embasies to Bern in all languages and in French specifically betwee 1920 annd 1960.

Basemaps are from here.

Running D3 version of this map is available here.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
.states1920 {
  stroke: white;
  stroke-width: 0.5;
  fill: lightgrey;
}
.states1945 {
  stroke: white;
  stroke-width: 2;
  stroke-opacity:0.5;
  fill: none;
}
.citiesall {
    fill:white;
    stroke:black;
    fill-opacity: 0.5;
    stroke-opacity: 0.5;
}
.citiesselected {
    fill:black;
    stroke:white;
}
.legend{
    font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
    font-size: 1.3em;
}
.legendtitle{
    font-variant: small-caps;
    font-weight: bold;
}
.legendscale{
    font-size: 0.8em;
}
</style>
<svg width="1500" height="900"></svg>
<script src="d3/d3.v5.min.js"></script>
<script src="d3/d3-geo.v1.min.js"></script>
<script src="d3/d3-geo-projection.v2.min.js"></script>
<script src="d3/d3-geo-polygon.min.js"></script>
<script>

var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height");

var region = "world"; // can be world or europe.
var legendtranslate = "translate(1000,800)"; // positioning the legend

if (region == "europe") {
    var projection = d3.geoPeirceQuincuncial()
            .scale(1300)
            .translate([0.35*width, -0.35*height])
            .precision(0.1);
    legendtranslate = "translate(100,350)";
} else {
    var projection = d3.geoAirocean()
        .scale(90)
        .translate([0.5*width,0.5*height])
        .precision(0.1);
}

var path = d3.geoPath()
  .projection(projection);

var scaler = 2;

Promise.all([
    d3.json('world_1920.geojson'),
    d3.json('world_1945.geojson'),
    d3.json('citiesallreports.geojson'),
    d3.json('citiesselected.geojson')
]).then(([states1920,states1945,citiesall,citiesselected]) => {
    svg.append("g")
            .attr("class", "states1920")
        .selectAll("path")
            .data(states1920.features)
            .enter().append("path")
            .attr("d", path)
    ; 
    svg.append("g")
            .attr("class", "states1945")
        .selectAll("path")
            .data(states1945.features)
            .enter().append("path")
            .attr("d", path)
    ;
    svg.append("g")
            .attr("class", "citiesall")
        .selectAll("circle")
            .data(citiesall.features.sort(function(a, b) { return b.properties.count - a.properties.count; }))
            .enter().append("circle")
            .attr("cx", function (d) { return projection(d.geometry.coordinates)[0]; })
            .attr("cy", function (d) { return projection(d.geometry.coordinates)[1]; })
            .attr("r", function (d) { return Math.sqrt(d.properties.count) /scaler; })
    ;
    svg.append("g")
            .attr("class", "citiesselected")
        .selectAll("circle")
            .data(citiesselected.features.sort(function(a, b) { return b.properties.count - a.properties.count; }))
            .enter().append("circle")
            .attr("cx", function (d) { return projection(d.geometry.coordinates)[0]; })
            .attr("cy", function (d) { return projection(d.geometry.coordinates)[1]; })
            .attr("r", function (d) { return Math.sqrt(d.properties.count) / scaler; })
    ;  

    // add a legend -----
    // add maximum circle sizes
    maxcitall = citiesall.features.sort(function(a, b) { return b.properties.count - a.properties.count; })[0].properties.count;
    maxcitsel = citiesselected.features.sort(function(a, b) { return b.properties.count - a.properties.count; })[0].properties.count;
    rmaxcitall = Math.sqrt(maxcitall)/scaler; // prepare circle radidus
    rmaxcitsel = Math.sqrt(maxcitsel)/scaler;
    var legend = svg.append("g").attr("class","legend").attr("transform",legendtranslate);
    legend.append("circle").attr("class","citiesall").attr("r",rmaxcitall); // the center of this one one serves as a reference point for the other elements.  This center is at position 0,0 with respect to the g element, which we have translated
    legend.append("circle").attr("class","citiesall").attr("r",Math.sqrt(1000)/scaler)
        .attr("cy",-(rmaxcitall-Math.sqrt(1000)/scaler))
    ;
    legend.append("text").text("number of sent reports per city")
        .attr("class","legendtitle")
        .attr("x",-rmaxcitall)
        .attr("y",-rmaxcitall-36)
    ;
    legend.append("text").text("All languages")
        .attr("x",-rmaxcitall)
        .attr("y",-rmaxcitall-12)
    ;
    legend.append("text").text("French")
        .attr("x",-(rmaxcitsel) + 200)
        .attr("y",-rmaxcitall-12)
    ;
    legend.append("circle").attr("class","citiesselected").attr("r",rmaxcitsel)
        .attr("cx",200) 
        .attr("cy",-(rmaxcitall-rmaxcitsel))
    ;
    legend.append("circle").attr("class","citiesselected").attr("r",Math.sqrt(1000)/scaler)
        .attr("cx",200) 
        .attr("cy",-(rmaxcitall-Math.sqrt(1000)/scaler))
    ; 

    legend.append("text").text("1000").attr("class","legendscale")
        .attr("x",rmaxcitall+35)
        .attr("y",-rmaxcitall+ Math.sqrt(1000)/scaler*2) // *2 because interested in diameter not radius, here
    ;
    legend.append("text").text(maxcitsel).attr("class","legendscale")
        .attr("x",rmaxcitall+35)
        .attr("y",-rmaxcitall+rmaxcitsel*2) 
    ;
    legend.append("text").text(maxcitall).attr("class","legendscale")
        .attr("x",rmaxcitall+35)
        .attr("y",rmaxcitall)
    ;
});
</script>

3 replies on “Dymaxion world map with proportional circles and automatic legend in D3.js v5”

Comments are closed.