What's the best way to make a d3.js visualisation layout responsive?
There's another way to do this that doesn't require redrawing the graph, and it involves modifying the viewBox and preserveAspectRatio attributes on the <svg>
element:
<svg id="chart" width="960" height="500" viewBox="0 0 960 500" preserveAspectRatio="xMidYMid meet"></svg>
Update 11/24/15: most modern browsers can infer the aspect ratio of SVG elements from the viewBox
, so you may not need to keep the chart's size up to date. If you need to support older browsers, you can resize your element when the window resizes like so:
var aspect = width / height, chart = d3.select('#chart');d3.select(window) .on("resize", function() { var targetWidth = chart.node().getBoundingClientRect().width; chart.attr("width", targetWidth); chart.attr("height", targetWidth / aspect); });
And the svg contents will be scaled automatically. You can see a working example of this (with some modifications) here: just resize the window or the bottom right pane to see how it reacts.
Look for 'responsive SVG' it is pretty simple to make a SVG responsive and you don't have to worry about sizes any more.
Here is how I did it:
d3.select("div#chartId") .append("div") .classed("svg-container", true) //container class to make it responsive .append("svg") //responsive SVG needs these 2 attributes and no width and height attr .attr("preserveAspectRatio", "xMinYMin meet") .attr("viewBox", "0 0 600 400") //class to make it responsive .classed("svg-content-responsive", true);
The CSS code:
.svg-container { display: inline-block; position: relative; width: 100%; padding-bottom: 100%; /* aspect ratio */ vertical-align: top; overflow: hidden;}.svg-content-responsive { display: inline-block; position: absolute; top: 10px; left: 0;}
More info / tutorials:
http://demosthenes.info/blog/744/Make-SVG-Responsive
http://soqr.fr/testsvg/embed-svg-liquid-layout-responsive-web-design.php
I've coded up a small gist to solve this.
The general solution pattern is this:
- Breakout the script into computation and drawing functions.
- Ensure the drawing function draws dynamically and is driven ofvisualisation width and height variables (The best way to do this isto use the d3.scale api)
- Bind/chain the drawing to a referenceelement in the markup. (I used jquery for this, so imported it).
- Remember to remove it if it's already drawn. Get the dimensions fromthe referenced element using jquery.
- Bind/chain the draw function tothe window resize function. Introduce a debounce (timeout) to thischain to ensure we only redraw after a timeout.
I also added the minified d3.js script for speed.The gist is here: https://gist.github.com/2414111
jquery reference back code:
$(reference).empty()var width = $(reference).width();
Debounce code:
var debounce = function(fn, timeout) { var timeoutID = -1; return function() { if (timeoutID > -1) { window.clearTimeout(timeoutID); } timeoutID = window.setTimeout(fn, timeout); }};var debounced_draw = debounce(function() { draw_histogram(div_name, pos_data, neg_data); }, 125); $(window).resize(debounced_draw);
Enjoy!