genwip
genwip

Reputation: 1037

How to create a responsive map using d3

I'm trying to make my map responsive, so it'd be resized according to the browser window size. This is what I have so far,

style.css

#mapbox {
border:2px solid #000;
width:960px;
height:550px;
background:#FFFFFF;
}

viz.js

var margin = {top: 10, left: 10, bottom: 10, right: 10},
width = parseInt(d3.select('#mapbox').style('width')), 
width = width - margin.left - margin.right, 
mapRatio = .5, 
height = width * mapRatio;
.
.
d3.select(window).on('resize', resize);
.
.
function resize(){  
    width = parseInt(d3.select('#mapbox').style('width'));
    width = width - margin.left - margin.right;
    height = width * mapRatio;


        projection.translate([width / 2, height / 2])
                .scale(width);

        svg.style('width', width + 'px')
            .style('height', height + 'px');

}

When I resize the window, its still keeping the same width and height for my svg container. I guess thats because, in resize function, I'm getting the width and height from the CSS which will be the same whether I resize the window right?

I'm stuck here, am I missing anything obvious? would be great if you have any pointers for me to get this working.

I'm pretty much following the following example,

Upvotes: 6

Views: 6323

Answers (3)

genwip
genwip

Reputation: 1037

Thanks for the pointers,

This is what worked for me, not sure whether this is a best practice or not though..

html

     <section id = "mapContainer">
        <svg id = "map" width="960" height="550"
            viewBox="0 0 960 550"   preserveAspectRatio="xMidYMid"> 
        </svg>
     </section>

Javascript

    var svgContainer = $("#map");
    var width = svgContainer.width();
    var height = svgContainer.height();
    var aspect = width/height;
    var container = svgContainer.parent();

    var svg = d3.select("svg");

    //responsive map
function resize(){
    // adjust things when the window size changes
    var targetWidth = container.width();

    svg.attr("width", targetWidth);
    svg.attr("height", Math.round(targetWidth/aspect));

    console.log ("width : " + targetWidth);
    console.log ("height : " + Math.round(targetWidth/aspect));

    // update projection
    projection.translate([width / 2, height / 2])
                .scale(width);

    // resize the map
        svg.select('.lga').attr('d', path);
}

Upvotes: 2

reblace
reblace

Reputation: 4195

There isn't really enough detail in your question to tell exactly what you're doing. There's a few common things that can break resize code like this...

It's possible that your mapbox isn't ever changing size. You could stick a console.log(...) with the width/height for each call to resize and that'll tell you exactly what's happening there.

Also, depending on how you specify the size of the SVG initially (Eg. using the width/height attributes vs using the height/width css settings) you can create a conflict where the attribute size specification takes precedence over the css.

If your width/height values are being parsed incorrectly and subsequently getting set incorrectly it'll just ignore those CSS settings and the size will never change.

Finally, if you go read the SO that Lars linked to, you'll see that an easier way to do svg scaling is to use the viewbox/preserveaspectration method. Here's a fiddle I made when I was getting this to work for my stuff: http://jsfiddle.net/reblace/Ku2UQ/2/

var svg = container.selectAll("svg").data([data]);
var svgEnter = svg.enter().append("svg");

// Append a clip path that will hide any data points outside of the xExtent and yExtent
svgEnter
    .attr("viewBox", "0 0 600 200") // This is the "ideal" size of the chart
    .attr("preserveAspectRatio", "xMinYMin");

...

var resize = function(w, h){
    svg.transition().duration(500)
        .attr("width", w)
        .attr("height", w / (width/height));        
};

function doResize() {
    var overflow = $("body").css("overflow");
    $("body").css("overflow", "hidden");

    var width = $("#center").innerWidth();
    var height = $(window).innerHeight() - 10;

    $("body").css("overflow", overflow);
    resize(width, height);
};

var resizeTimer;
$(window).resize(function() {
    clearTimeout(resizeTimer);
    resizeTimer = setTimeout(doResize, 250);
});

doResize();

This code does several things worth looking at, including filtering the resize event so that it doesn't call the resize code for every pixel size change to the window. Instead it basically buffers the event until you've stopped resizing the window for 250ms.

Then, it hides the scrollbars on the page so that you get an accurate window size if you're trying to scale the thing to fit on the page without scrollbars.

Finally, it scales the SVG element, which handles resizing the stuff and sets it to the correct size given the size of the parent container (scaling the height based on the original width/height ratio.

Upvotes: 0

tomtomtom
tomtomtom

Reputation: 1522

I set the svg like so to have a responsive resize:

var svg = d3.select("body")
        .append("div")
        .attr("id", "svg")
        .append("svg")
        .attr("viewBox", "0 0 " + width + " " + height )
        .attr("preserveAspectRatio", "xMinYMin");

And then set the css like so:

#svg {
    width:100%;
    height: auto;
}

Upvotes: 5

Related Questions