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

6

39

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

Surgery answered 10/1, 2013 at 18:47 Comment(0)
S
30

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);
});
Surgery answered 12/1, 2013 at 23:33 Comment(2)
Isn't this rewriting everything? How can I avoid this?Hifalutin
@arkanoid Maybe...this solution worked for me, so if that's ultimately what it's doing, then shrug. I didn't dig into how d3 is rendering things, so if my solution is actually just a rewrite, then I suppose that's how you do it.Surgery
B
9

The selection object is an multidimensional array, although in most cases it will probably have only one object in it. That object has a "clientWidth" field that tells you how wide its parent is.

So you can do this:

var selection = d3.select("#chart"); 
width = selection[0][0].clientWidth;
Bibliotherapy answered 6/5, 2014 at 21:23 Comment(2)
Since I haven't looked at it in a while, I'll take your word for it. A Fiddle might help for future people if you have time, but thanks for your answer!Surgery
With d3v4, the second line should be selection._groups[0][0].clientWidthFunda
W
8

This should work:

<svg 
    xmlns="http://www.w3.org/2000/svg"
    width="860"
    height="500"
    viewBox="0 0 860 500"
    preserveAspectRatio="xMinYMin meet">
Weal answered 10/1, 2013 at 20:12 Comment(2)
So this fills the container, but the scaling of the map is waaay zoomed in. Is there a way I can get it to scale based on the size of the container? I don't care if I have to use some jQuery or javascript, but I just don't know what values I would have to change.Surgery
You'd want to adjust the .scale() of the projection aside from adjusting the settings of the viewbox. If you're interested in showing more or less of the map from a geospatial perspective depending on how much screen real estate you have, then you're not going to want to rely on the viewBox method, but rather reproject or rotate based on changing width and height of the container.Weal
P
3

The best choice is to have a combined use of aspect ratio on normal definition of d3 graph's width and height. This has helped me in lot of my graph works.
Step 1 : Dynamically get the height of the div to which the graph has to be appended.
Step 2 : Declare width as aspect ratio with respect to the dynamically caught height.

var graph_div = document.getElementById(graph.divId);
graph.height = graph_div.clientHeight;
graph.width = (960/1200)*graph.height;
Plane answered 17/3, 2016 at 11:40 Comment(1)
But if you resize the screen, will it update those values?Ephialtes
G
2

In d3 v4, we could do this

const projection = d3.geo.equirectangular().fitSize([width, height], geojson);
const path = d3.geo.path().projection(projection);

fitSize is equivalent to

fitExtent([[0, 0], [width, height]], geojson)

fill free to add padding

Greff answered 9/4, 2018 at 6:2 Comment(0)
C
0

I had success by explicitly setting the div size...

<div id="ChartDiv" style="position:absolute; width:99.0%; height:99.0%;"></div>

And also setting the height attribute on the 'svg'...

const svg = d3.create("svg")
  .attr("width","100%")
  .attr("height","100%") // this was the secret sauce
  .attr("viewBox", [-width, -height, width*2, height*2]) // clipping [origin,size]

With this configuration, the drawing was automatically resized to fit completely within the browser window while also preserving the aspect ratio.

Chronicle answered 29/5 at 7:15 Comment(3)
It took way too long to find this simple solution.Chronicle
Similar: Make D3.js width and height 100%Chronicle
Setting the width attribute is optional and may be less important (for some uses) since displays are more likely to have landscape orientation.Chronicle

© 2022 - 2024 — McMap. All rights reserved.