Search code examples
csscanvassvgd3.js

d3.js Map (<svg>) Auto Fit into Parent Container and Resize with Window


I'm building a dashboard and using d3.js to add a world map that will plot tweets in real time based on geo location.

The world.json file referenced in the d3.json() line is downloadable HERE (it's called world-countries.json).

enter image description here

The map is on the page as an SVG container and is rendered using d3.

Below are the relevant code slices.

<div id="mapContainer">
    <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="500"></svg>
</div>

#mapContainer svg {
    display:block;
    margin:0 auto;
}
#mapContainer path {
  fill:#DDD;
  stroke:#FFF;
}

// generate US plot
function draw() {
    var map = d3.select("svg");
    var width = $("svg").parent().width();
    var height = $("svg").parent().height();
    
    var projection = d3.geo.equirectangular().scale(185).translate([width/2, height/2]);
    var path = d3.geo.path().projection(projection);
    d3.json('plugins/maps/world.json', function(collection) {
        map.selectAll('path').data(collection.features).enter()
            .append('path')
            .attr('d', path)
            .attr("width", width)
            .attr("height", height);
    });
}
draw();
latestLoop();

$(window).resize(function() {
    draw();
});

I have scaled the map to an acceptable size (for my particular browser size), but it still will not scale and center when I change the size of the window. If, however, I resize the window, then hit refresh, then the map will be centered once the page is reloaded. However, since the scale is static, it is not scaled properly.

enter image description here


Solution

  • COMPLETE SOLUTION:

    Here's the solution which will resize the map AFTER the user has released the edge of the window to resize it, and center it in the parent container.

    <div id="mapContainer"></div>
    

    function draw(ht) {
        $("#mapContainer").html("<svg id='map' xmlns='http://www.w3.org/2000/svg' width='100%' height='" + ht + "'></svg>");
        map = d3.select("svg");
        var width = $("svg").parent().width();
        var height = ht;
    
        // I discovered that the unscaled equirectangular map is 640x360. Thus, we
        // should scale our map accordingly. Depending on the width ratio of the new
        // container, the scale will be this ratio * 100. You could also use the height 
        // instead. The aspect ratio of an equirectangular map is 2:1, so that's why
        // our height is half of our width.
    
        projection = d3.geo.equirectangular().scale((width/640)*100).translate([width/2, height/2]);
        var path = d3.geo.path().projection(projection);
        d3.json('plugins/maps/world.json', function(collection) {
            map.selectAll('path').data(collection.features).enter()
                .append('path')
                .attr('d', path)
                .attr("width", width)
                .attr("height", width/2);
        });
    }
    draw($("#mapContainer").width()/2);
    

    $(window).resize(function() {
        if(this.resizeTO) clearTimeout(this.resizeTO);
        this.resizeTO = setTimeout(function() {
            $(this).trigger('resizeEnd');
        }, 500);
    });
    
    $(window).bind('resizeEnd', function() {
        var height = $("#mapContainer").width()/2;
        $("#mapContainer svg").css("height", height);
        draw(height);
    });