Reputation: 3809
I'm drawing a scatterplot with d3.js. With the help of this question :
Get the size of the screen, current web page and browser window
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.
Upvotes: 207
Views: 212233
Reputation: 490
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()
Upvotes: 3
Reputation: 4686
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
Upvotes: 335
Reputation: 1503
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();
Upvotes: 1
Reputation: 9293
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);
Upvotes: 48
Reputation: 22767
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/
Upvotes: 33
Reputation: 274
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
Upvotes: 8
Reputation: 355
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.
Upvotes: 12