I'm trying to redraw an SVG chart on window resize within d3, but without using the viewBox
and preserveAspectRatio
parameters (I didn't like how they treated text).
I'm also trying to stick with d3's databinding design via Bostock's suggestion when appending a single element (not based on backed data).
I have something that boils down to this (jsFiddle). However the SVG element's width/height never get updated.
<div class="chart-container">
<button class="user-option">Some User Options</button>
<div class="svg-container"></div>
</div>
$(window).on("resize", function () {
draw();
});
function draw() {
var container = d3.select('.chart-container');
var drawWidth = Math.round(0.80 * container.node().clientWidth);
var drawHeight = Math.round(drawWidth * (3 / 4)); // Fixing a 4/3 aspect ratio
/*
* Use an inner g element as the SVG canvas in order to set and forget margins.
* See http://bl.ocks.org/mbostock/3019563
*/
var margin = {
top: 10,
right: 30,
bottom: 50,
left: 40
};
var width = drawWidth - margin.left - margin.right;
var height = drawHeight - margin.top - margin.bottom;
var svg = container.select(".svg-container").selectAll('svg')
.data([0]) // Single data element to create SVG if it doesn't exist
.enter().append("svg")
.attr("width", drawWidth)
.attr("height", drawHeight)
.append("g")
.attr("class", "canvas")
// jsFiddle doesn't translate correctly, maybe because of frames???
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
console.debug(svg.node()); // null on resize???
// Add some random SVG element
svg.selectAll('rect')
.data([0]) // Single data element to create SVG if it doesn't exist
.enter().append('rect')
.attr({
x: 0,
y: 0,
width: drawWidth - margin.right - margin.left,
height: drawHeight - margin.bottom - margin.top
});
}
@Pablo Navarro's answer is close the only issue is that svg on the redraw will not be your svg element but an array, use the same technique of separating the two executions but specifically select your svg again to change the width and height
var svg = container.select(".svg-container").selectAll('svg')
.data([0]) // Single data element to create SVG if it doesn't exist
.enter().append("svg")
.append("g")
.attr("class", "canvas")
// jsFiddle doesn't translate correctly, maybe because of frames???
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
then
container.select(".svg-container").select('svg').attr("width", drawWidth).attr("height", drawHeight)
http://jsfiddle.net/LLvuq0Lm/9/
The root problem here is that your svg variable is not being assigned the <svg>
element selection, it's being assigned the end result of the chained method calls, which the first time round is the <g>
element, and after that is empty because there's no 'enter' selection.
So first thing to do is change to
var svg = container.select(".svg-container").selectAll('svg').data([0]);
// now you have a reference to the svg selection
On first execution, the update selection will be empty and the enter selection will have a single element. You then bind this to a new <svg>
element:
svg.enter()
.append("svg")
.append("g")
// note that from this point you're setting attributes on the <g>, not the <svg>
.attr("class", "canvas")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
This append operation also adds the element to the update selection stored in svg
.
On resize, the enter selection will be empty, but the update selection in svg
will already contain the existing <svg>
element. So either way, at this point you can set attributes on the svg
selection - it will work for both cases.
svg.attr("width", drawWidth)
.attr("height", drawHeight);
Updated fiddle http://jsfiddle.net/3dgn5pz8/5/
Thanks, I was able to get to the correct solution from all of your answers! However, neither answer was able to update the SVG on resize AND keep the svg
variable as the g
element (to stick with the margin convention). I ended up doing
// Create SVG element (if it doesn't exist)
var svg = container.select(".svg-container").selectAll('svg').data([null]);
svg.enter().append("svg");
svg.attr({width: drawWidth, height: drawHeight}); // apply to enter + update selections
// Create g element (if it doesn't exist) and remap the svg variable
svg = svg.selectAll(".canvas").data([null]);
svg.enter().append("g") // apply to enter selection only
.attr("class", "canvas")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With