Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redrawing SVG on resize

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.

HTML

<div class="chart-container">
    <button class="user-option">Some User Options</button>
    <div class="svg-container"></div>
</div>

Javascript

$(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
            });
}
like image 460
lebolo Avatar asked Aug 28 '14 18:08

lebolo


3 Answers

@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/

like image 21
Quince Avatar answered Nov 01 '22 03:11

Quince


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/

like image 81
CupawnTae Avatar answered Nov 01 '22 03:11

CupawnTae


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 + ")");
like image 1
lebolo Avatar answered Nov 01 '22 04:11

lebolo