Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making an animating arc in d3 responsive?

I was looking at this animation in d3,

http://bl.ocks.org/mbostock/5100636

And I was wondering is there any way to make this responsive, so that the size changes with resizing of the browser window ? Or if it would be easier using raphael.js ?

Here's the code :

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>

var width = 960,
height = 500,
τ = 2 * Math.PI; // http://tauday.com/tau-manifesto

// An arc function with all values bound except the endAngle. So, to compute an
// SVG path string for a given angle, we pass an object with an endAngle
// property to the `arc` function, and it will return the corresponding string.
var arc = d3.svg.arc()
.innerRadius(180)
.outerRadius(240)
.startAngle(0);

// Create the SVG container, and apply a transform such that the origin is the
// center of the canvas. This way, we don't need to position arcs individually.
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")

// Add the background arc, from 0 to 100% (τ).
var background = svg.append("path")
.datum({endAngle: τ})
.style("fill", "#ddd")
.attr("d", arc);

// Add the foreground arc in orange, currently showing 12.7%.
var foreground = svg.append("path")
.datum({endAngle: .127 * τ})
.style("fill", "orange")
.attr("d", arc);

// Every so often, start a transition to a new random angle. Use transition.call
// (identical to selection.call) so that we can encapsulate the logic for
// tweening the arc in a separate function below.
setInterval(function() {
foreground.transition()
.duration(750)
.call(arcTween, Math.random() * τ);
}, 1500);

// Creates a tween on the specified transition's "d" attribute, transitioning
// any selected arcs from their current angle to the specified new angle.
function arcTween(transition, newAngle) {

// The function passed to attrTween is invoked for each selected element when
// the transition starts, and for each element returns the interpolator to use
// over the course of transition. This function is thus responsible for
// determining the starting angle of the transition (which is pulled from the
// element's bound datum, d.endAngle), and the ending angle (simply the
// newAngle argument to the enclosing function).
transition.attrTween("d", function(d) {

// To interpolate between the two angles, we use the default d3.interpolate.
// (Internally, this maps to d3.interpolateNumber, since both of the
// arguments to d3.interpolate are numbers.) The returned function takes a
// single argument t and returns a number between the starting angle and the
// ending angle. When t = 0, it returns d.endAngle; when t = 1, it returns
// newAngle; and for 0 < t < 1 it returns an angle in-between.
var interpolate = d3.interpolate(d.endAngle, newAngle);

// The return value of the attrTween is also a function: the function that
// we want to run for each tick of the transition. Because we used
// attrTween("d"), the return value of this last function will be set to the
// "d" attribute at every tick. (It's also possible to use transition.tween
// to run arbitrary code for every tick, say if you want to set multiple
// attributes from a single function.) The argument t ranges from 0, at the
// start of the transition, to 1, at the end.
return function(t) {

// Calculate the current arc angle based on the transition time, t. Since
// the t for the transition and the t for the interpolate both range from
// 0 to 1, we can pass t directly to the interpolator.
//
// Note that the interpolated angle is written into the element's bound
// data object! This is important: it means that if the transition were
// interrupted, the data bound to the element would still be consistent
// with its appearance. Whenever we start a new arc transition, the
// correct starting angle can be inferred from the data.
d.endAngle = interpolate(t);

// Lastly, compute the arc path given the updated data! In effect, this
// transition uses data-space interpolation: the data is interpolated
// (that is, the end angle) rather than the path string itself.
// Interpolating the angles in polar coordinates, rather than the raw path
// string, produces valid intermediate arcs during the transition.
return arc(d);
};
});
}

</script>
like image 252
lenny Avatar asked Jan 28 '14 16:01

lenny


1 Answers

You have two options for getting an SVG to adapt to window size.

The first option is to let the "Scalable" aspect of "Scalable Vector Graphics" do the work for you. Set the SVG size using relative units (percents or viewport units) or using CSS media queries to adapt to screen size. Then add a viewBox attribute to your SVG to make the image scale to fit whatever size box you put it in. The limitation is that everything scales equally, which can result in very large or very small text labels if the size of the graphic changes considerably. The benefit is that the re-size is completely independent of your code and any animations you have in the graphic.

Example of the concept applied to the arc tween demo:
http://fiddle.jshell.net/h8Mg9/

Key code:

var svg = d3.select("body").append("svg")
    .attr("height", "100%") //or use CSS
    .attr("width", "100%")
    .attr("viewBox", 
          "0 0 " + (margin.left + diameter + margin.right) +
          " " + (margin.top + diameter + margin.bottom) )
        //"0 0 160 120" -- defines relative units for drawing
        //(0,0) for top left corner coordinates, 
        //then width and height.
    .attr("preserveAspectRatio", "xMidYMid meet");
       //maintain aspect ratio from viewBox dimensions;
       //If they don't match svg dimensions, scale down to
       //fit the entire viewbox within the svg (meet);
       //center it vertically and horizontally (xMidYMid)

Note that the text is always sized proportional to the ring as if the ring was 100px in diameter. Also, the text transition is just a straight-line transition between old and new positions.

The second option is to listen for window resize events, query the svg size and then trigger a re-draw. All the size variables in your drawing function have to be appropriately scaled to the dimensions. Furthermore, you have to factor in the possibility of resize events happening during a transition. The custom arc tween actually makes that easier, since it calls the arc function at every tick of the transition; by changing the arc function's parameters, the tween results are automatically changed as well.

Example of this method on the arc tween demo:
http://fiddle.jshell.net/h8Mg9/2/

Key code:

function setSize() {
    var svgStyles = window.getComputedStyle(svg.node());
    diameter = Math.min(
                (parseInt(svgStyles["width"]) 
                    - margin.left - margin.right), 
                (parseInt(svgStyles["height"])
                    - margin.top - margin.bottom) );

    arc.outerRadius(diameter/2)
       .innerRadius(diameter/2 - ringThickness);


    vis.attr("transform",
          "translate(" + (margin.left + diameter/2) + ","
          + (margin.top + diameter/2) + ")"); 
    background.attr("d", arc);

    if(!transitioning) {
        //don't interrupt an ongoing transition --
        //it will automatically adjust
        //because we've modified the arc function;
        //note that we've created a custom tween
        //for the label, so it will adjust too.

        //Otherwise:
        foreground.attr("d", arc);
        label.attr("transform", function(d) {
            return "translate("+arc.centroid(d)+")" 
        });        
    }
    //Note that we're not transitioning the change
    //in diameter; it isn't necessary since there
    //will be multiple resize events creating a smooth
    //shift in size.
}
setSize(); //initialize

//adapt size to window changes:
window.addEventListener("resize", setSize, false)

Another benefit of the pie chart is that the size is really only set once, in the arc function's outerRadius. For a more complex layout redraw, you'll want to use scales to determine positions and sizing. The final examples of this answer on zooming show resizing with scales.

For combining transitions and resize in a scaled layout, you could use the same approach as in the pie chart (changing the arc function changes the results of the tween function), and have a custom tween function that queries the current state of the scale at each tick. However, in most cases it would probably be more efficient to simply interrupt the ongoing transition -- i.e., create a new transition which would end up combining the shift in dimension with the shift in size.

like image 91
AmeliaBR Avatar answered Sep 28 '22 08:09

AmeliaBR