How to animate areas of a D3 area chart?

2019-02-13 09:58发布

问题:

How can d3 areas have their transitions animated? I've seen examples for lines but can't find anything on animating an area.

Eg area:

var area = d3.svg.area()
    .x(function(d) { return x(d.x); })
    .y0(height)
    .y1(function(d) { return y(d.y); });

Update: I've found an example for an area chart but I don't understand it. How is this function creating the area transition?

function transition() {
  d3.selectAll("path")
      .data(function() {
        var d = layers1;
        layers1 = layers0;
        return layers0 = d;
      })
    .transition()
      .duration(2500)
      .attr("d", area);
}

回答1:

The transition of areas works just like for other attributes. Only in case of areas, we are interpolating strings instead of interpolating numbers. When you call the area function with some data, then it produces a string which looks like M0,213L4,214L9,215 ... L130,255.7, which is a DSL used for the d attribute. When you change the data you pass to the area function, this string changes and D3 interpolates them.

Regarding the example you have found, the interesting bit which causes the transition is only this:

    .transition()
      .duration(2500)
      .attr("d", area);

The other part merely is a fancy way of alternatively returning layers1 and layers0 as the data for the area function on consecutive calls.

  d3.selectAll("path")
      .data(function() {
        var d = layers1;
        layers1 = layers0;
        return layers0 = d;
      })


回答2:

Bit late to the party, but:

I solved the problem by modifying the original 'area' function, passing two variables: the data, and the field I wish to chart:

var area = function(datum, field) {
    return d3.svg.area()
        .x(function(d) {
            return xScale(d.period_end);
        })
        .y0(height)
        .y1(function(d) {
            return yScale(d[field] || 0);
        })(datum);
    };

Then when you draw the path, just use basic transition. First time, passing no 'field', resulting in drawing zero values, and then - after transition() - passing the field I wanted:

areaChart.append('path')
    .attr('class', 'area')
    .attr('d', area(chartData))
    .attr('fill', function() {
        return chartColor;
    })
    .attr('opacity', 0.15)
    .transition().duration(chartSettings.duration)
    .attr('d', area(chartData, 'value'));

Works nicely without the need for sub functions. Exactly the same can of course be done for line charts.



回答3:

Thanks @neptunemo for your suggestion. However, your code is too specific for your problem. I would like to take a general case for better illustration of your idea:

Please see the full code from an example of d3noob: https://bl.ocks.org/d3noob/119a138ef9bd1d8f0a8d57ea72355252

Original code of area generator:

var area = d3.area()
    .x(function(d) { return x(d.date); })
    .y0(height)
    .y1(function(d) { return y(d.close); });

Modified code of area generator:

var area = function(datum, boolean) {
    return d3.area()
    .y0(height)
    .y1(function (d) { return y(d.close); })
    .x(function (d) { return boolean ? x(d.date) : 0; })
    (datum);
}
  • datum is to take the data,
  • boolean is to control the:
    • .x() (in case you want the animation along x-axis)
    • .y1() (in case you want the animation along y-axis)

By setting boolean to false, we're able to set .x() or .y1() to 0.

This will help us to set the initial state of area before triggering the transition process.

Modified code of area transition:

svg.append("path")
    .data([data])
    .attr("class", "area")
    .attr("d", d => area(d, false))
    .attr("fill", "lightsteelblue")
    .transition()
    .duration(2000)
    .attr("d", d => area(d,true));

Effects?

  • case of controlling .x()

  • case of controlling .y1()

Note: The issue I met is that I cannot synchronize the animation of line and area :(