Resize svg when window is resized in d3.js

Javascriptd3.js

Javascript Problem Overview


I'm drawing a scatterplot with d3.js. With the help of this question :
https://stackoverflow.com/questions/3437786/how-to-get-web-page-size-browser-window-size-screen-size-in-a-cross-browser-wa

I'm using this answer :

var w = window,
    d = document,
    e = d.documentElement,
    g = d.getElementsByTagName('body')[0],
    x = w.innerWidth || e.clientWidth || g.clientWidth,
    y = w.innerHeight|| e.clientHeight|| g.clientHeight;

So I'm able to fit my plot to the user's window like this :

var svg = d3.select("body").append("svg")
		.attr("width", x)
		.attr("height", y)
	    .append("g");

Now I'd like that something takes care of resizing the plot when the user resize the window.

PS : I'm not using jQuery in my code.

Javascript Solutions


Solution 1 - Javascript

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")
   // Container class to make it responsive.
   .classed("svg-container", true) 
   .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)
   // Fill with a rectangle for visualization.
   .append("rect")
   .classed("rect", true)
   .attr("width", 600)
   .attr("height", 400);

.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;
}

svg .rect {
  fill: gold;
  stroke: steelblue;
  stroke-width: 5px;
}

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

<div id="chartId"></div>

Note: Everything in the SVG image will scale with the window width. This includes stroke width and font sizes (even those set with CSS). If this is not desired, there are more involved alternate solutions below.

More info / tutorials:

http://thenewcode.com/744/Make-SVG-Responsive

http://soqr.fr/testsvg/embed-svg-liquid-layout-responsive-web-design.php

Solution 2 - Javascript

Use window.onresize:

function updateWindow(){
    x = w.innerWidth || e.clientWidth || g.clientWidth;
    y = w.innerHeight|| e.clientHeight|| g.clientHeight;
    
    svg.attr("width", x).attr("height", y);
}
d3.select(window).on('resize.updatesvg', updateWindow);

http://jsfiddle.net/Zb85u/1/

Solution 3 - Javascript

UPDATE just use the new way from @cminatti


old answer for historic purposes

IMO it's better to use select() and on() since that way you can have multiple resize event handlers... just don't get too crazy

d3.select(window).on('resize', resize); 

function resize() {
    // update width
    width = parseInt(d3.select('#chart').style('width'), 10);
    width = width - margin.left - margin.right;

    // resize the chart
    x.range([0, width]);
    d3.select(chart.node().parentNode)
        .style('height', (y.rangeExtent()[1] + margin.top + margin.bottom) + 'px')
        .style('width', (width + margin.left + margin.right) + 'px');

    chart.selectAll('rect.background')
        .attr('width', width);

    chart.selectAll('rect.percent')
        .attr('width', function(d) { return x(d.percent); });

    // update median ticks
    var median = d3.median(chart.selectAll('.bar').data(), 
        function(d) { return d.percent; });

    chart.selectAll('line.median')
        .attr('x1', x(median))
        .attr('x2', x(median));


    // update axes
    chart.select('.x.axis.top').call(xAxis.orient('top'));
    chart.select('.x.axis.bottom').call(xAxis.orient('bottom'));

}

http://eyeseast.github.io/visible-data/2013/08/28/responsive-charts-with-d3/

Solution 4 - Javascript

It's kind of ugly if the resizing code is almost as long as the code for building the graph in first place. So instead of resizing every element of the existing chart, why not simply reloading it? Here is how it worked for me:

function data_display(data){
   e = document.getElementById('data-div');
   var w = e.clientWidth;
   // remove old svg if any -- otherwise resizing adds a second one
   d3.select('svg').remove();
   // create canvas
   var svg = d3.select('#data-div').append('svg')
                                   .attr('height', 100)
                                   .attr('width', w);
   // now add lots of beautiful elements to your graph
   // ...
}

data_display(my_data); // call on page load

window.addEventListener('resize', function(event){
    data_display(my_data); // just call it again...
}

The crucial line is d3.select('svg').remove();. Otherwise each resizing will add another SVG element below the previous one.

Solution 5 - Javascript

In force layouts simply setting the 'height' and 'width' attributes will not work to re-center/move the plot into the svg container. However, there's a very simple answer that works for Force Layouts found here. In summary:

Use same (any) eventing you like.

window.on('resize', resize);

Then assuming you have svg & force variables:

var svg = /* D3 Code */;
var force = /* D3 Code */;    

function resize(e){
    // get width/height with container selector (body also works)
    // or use other method of calculating desired values
    var width = $('#myselector').width(); 
    var height = $('#myselector').height(); 

    // set attrs and 'resume' force 
    svg.attr('width', width);
    svg.attr('height', height);
    force.size([width, height]).resume();
}

In this way, you don't re-render the graph entirely, we set the attributes and d3 re-calculates things as necessary. This at least works when you use a point of gravity. I'm not sure if that's a prerequisite for this solution. Can anyone confirm or deny ?

Cheers, g

Solution 6 - Javascript

If you want to bind custom logic to resize event, nowadays you may start using ResizeObserver browser API for the bounding box of an SVGElement.
This will also handle the case when container is resized because of the nearby elements size change.
There is a polyfill for broader browser support.

This is how it may work in UI component:

function redrawGraph(container, { width, height }) {
  d3
    .select(container)
    .select('svg')
    .attr('height', height)
    .attr('width', width)
    .select('rect')
    .attr('height', height)
    .attr('width', width);
}

// Setup observer in constructor
const resizeObserver = new ResizeObserver((entries, observer) => {
  for (const entry of entries) {
    // on resize logic specific to this component
    redrawGraph(entry.target, entry.contentRect);
  }
})

// Observe the container
const container = document.querySelector('.graph-container');
resizeObserver.observe(container)

.graph-container {
  height: 75vh;
  width: 75vw;
}

.graph-container svg rect {
  fill: gold;
  stroke: steelblue;
  stroke-width: 3px;
}

<script src="https://unpkg.com/[email protected]/dist/ResizeObserver.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

<figure class="graph-container">
  <svg width="100" height="100">
    <rect x="0" y="0" width="100" height="100" />
  </svg>
</figure>

// unobserve in component destroy method
this.resizeObserver.disconnect()

Solution 7 - Javascript

For those using force directed graphs in D3 v4/v5, the size method doesn't exist any more. Something like the following worked for me (based on this github issue):

simulation
    .force("center", d3.forceCenter(width / 2, height / 2))
    .force("x", d3.forceX(width / 2))
    .force("y", d3.forceY(height / 2))
    .alpha(0.1).restart();

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmthpvgView Question on Stackoverflow
Solution 1 - JavascriptcminattiView Answer on Stackoverflow
Solution 2 - JavascriptAdam PearceView Answer on Stackoverflow
Solution 3 - JavascriptslfView Answer on Stackoverflow
Solution 4 - JavascriptRaikView Answer on Stackoverflow
Solution 5 - JavascriptgavsView Answer on Stackoverflow
Solution 6 - JavascriptAnbu AgarwalView Answer on Stackoverflow
Solution 7 - JavascriptJustin LewisView Answer on Stackoverflow