39

UPDATE: I have posted and accepted a fully working solution in the answers section. Any code in this section is to be used as reference for comparison to your own NON-WORKING code, but is not to be used as the solution.


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();
});

UPDATE: 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

Jon
  • 3,154
  • 13
  • 53
  • 96

5 Answers5

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);
});
Jon
  • 3,154
  • 13
  • 53
  • 96
  • 2
    Isn't this rewriting everything? How can I avoid this? – Arkanoid Apr 23 '14 at 16:07
  • @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. – Jon Apr 30 '14 at 13:40
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;
Spike Williams
  • 35,795
  • 13
  • 48
  • 60
  • 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! – Jon May 14 '14 at 19:40
  • 1
    With d3v4, the second line should be `selection._groups[0][0].clientWidth` – Alex Dec 04 '17 at 17:31
8

This should work:

<svg 
    xmlns="http://www.w3.org/2000/svg"
    width="860"
    height="500"
    viewBox="0 0 860 500"
    preserveAspectRatio="xMinYMin meet">
Elijah
  • 4,609
  • 3
  • 28
  • 36
  • 1
    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. – Jon Jan 10 '13 at 20:39
  • 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. – Elijah Jan 10 '13 at 21:51
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;
Raj sree
  • 82
  • 6
1

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

Bimal Grg
  • 7,624
  • 2
  • 24
  • 21