D3.js - Tween Arc position, inner Radius and outer Radius - D3.js Arc

1.7k views Asked by At

I am trying to work out how to tween multiple arcs to new positions, while also changing the size of the arc in terms of its inner and outer radius.

Meaning it will move from its current position on the canvas to its new position while also morphing to its new shape based on changes in new data compared to the old data.

I have read many examples such as http://bl.ocks.org/mbostock/5100636 which are all great however I cannot adapt any example to my particular need. Most examples that Ive found do not involve moving the arc on the canvas.

here is how I am creating the arcs in the first place which is working fine, and also animating the arc from 0 to its end position according to the data.

// THIS IS CALLED ON LOAD OF THE GRAPH
function createLargeArcs(graph,data){
    var arc = d3.svg.arc()
                .innerRadius(function (data) { return (data.r * 5) + 5; })
                .outerRadius(function (data) { return data.r * 5 })
                .startAngle(0);


    var endAngleFunction = function (data) {
        return (data.percentage) * ((2 * Math.PI) / 180);
    };

    // a global variable
    arcGroup = graph.append("g")
        .attr("class", "arcsGroup");

    var arcs = arcGroup.selectAll("path")
                        .data(data)
                        .enter()
                        .append("svg:path")
                        .attr("transform", function (d) { return "translate(" + xScale(d.u) + "," + yScale(d.t) + ")"; })
                        .style("fill", "red");

    arcs.transition()
        .duration(750)
        .call(arcTween);

    // http://bl.ocks.org/mbostock/5100636
    function arcTween(transition, newAngle) {
        transition.attrTween("d", function (d) {
            var interpolate = d3.interpolate(0, endAngleFunction(d));
            return function (t) {
                d.endAngle = interpolate(t);
                return arc(d);
            };
        });
    }
  }
  • all of the above works just fine

But its the process of working out how to tween the arcs to their new positions and their new size based on new data values that I am struggling with.

so far I have a refresh function which accepts a new/updated data set and within the refresh function this is where I am targeting the tween of the arc elements. I have achieved the moving of all of the arcs to their new positions with the following :

// THIS IS CALLED LATER ON WHEN DATA CHANGES - ITS HERE I NEED TO IMPLEMENT
function refresh(data){

    var arcs = arcGroup.selectAll("path")
                        .data(newData)

    arcs.transition()
        .duration(1000)
        .attr("transform", function(d) { return "translate("+ xScale(d.users) + "," + yScale(d.traffic) + ")"; });
}

Any help would be much appreciated. Thanks

1

There are 1 answers

0
Leeroy On

Okay, so I finally worked out what seems the best approach for this use case. So as you can see from the above example I was able to create the arcs with no issue, but the task of transitioning position, radius and arc radians at the same time across multiple arcs (10 in this case) it started to get a little tricky.

Most of the examples such as http://bl.ocks.org/mbostock/5100636 demonstrate how to transition the arc radians based on changing data efficiently, however I could not find an example that achieved this and also moved the arc to a new position on the canvas while also transitioning the radius of the arc. Perhaps there is an example out there but I could not find it. So here is how I managed to achieve all 3 transitions.

    // A helper function to convert a percentage value to radians
    var endAngleFunction = function (d) {
        return (d['percentage-value']) * ((2 * Math.PI) / 180);
    };

    // create an arc object but without setting the "endAngle"
    var arc = d3.svg.arc()
                .innerRadius(function (data) { return (d.r * 5) + 5; }) // calculating the radius based on a value within the data (different for each arc)
                .outerRadius(function (data) { return d.r * 5 })
                .startAngle(0);

    // selecting all svg paths (arcs) within arcGroup, a global variable referencing the group each arc gets appended to when created
    var arcs = arcGroup.selectAll("path")
                        .data(data);

    // transition the position of the arc to its new position
    arcs.transition("position")
        .duration(1000)
        .attr("transform", function(d) { return "translate("+ xScale(d.u) + "," + yScale(d.t) + ")"; });

    // transition the arc radians to new value 
    arcs.transition("newArc")
        .duration(1000)
        .attr("endAngle", function(d) { return endAngleFunction(d); }) // binding the new end Angle to the arc for later reference to work out what previous end angle was
        .transition()
        .delay(500)
        .duration(1000)
        .call(arcTween);

    // A function to tween the arc radians from a value to a new value - inspired by http://bl.ocks.org/mbostock/5100636
    function arcTween(transition, newAngle) {
        transition.attrTween("d", function (d) {
            var interpolate = d3.interpolate(d3.select(this).attr("endAngle"), endAngleFunction(d));
            return function (t) {
                d.endAngle = interpolate(t);
                return arc(d);
            };
        });
    }

Note : The big thing here for me was working out how to get the previous angle and transition to the new angle, which I achieved by binding the end angle to the arc object on creation and/or modification so i could then pull it in later and use for the transition.

And this seemed to work for me just fine. However as I am new to D3 there could be a more efficient way of achieving this or even structuring the code or the order of how things are called. From here I will try tighten this up and work out a more efficient way.

I hope this might be of help to someone at some stage as god knows I really struggled with this for some time.

Thank you to all who helped on this.