Roland
Roland

Reputation: 9701

animate d3.js line chart path exit

I'm creating a simple line chart with d3 and I was trying to animate the line (right now there is a single line) when doing data updates.

// https://bl.ocks.org/mbostock/3883245
// http://bl.ocks.org/d3noob/7030f35b72de721622b8
function LineChart(options) {
    // Mike Bostock margin conventions.
    // See http://bl.ocks.org/mbostock/3019563 for more info.
    const margin = this.margin = {
        top: 20,
        right: 20,
        bottom: 30,
        left: 40
    };

    // Used by some of the functions that get a different context when called by d3.
    const thisRef = this;

    this.width = options.width - margin.left - margin.right;
    this.height = options.height - margin.top - margin.bottom;

    this.x = d3.time.scale()
        .range([0, this.width]);
    this.y = d3.scale.linear()
        .range([this.height, 0]);

    this.xAxis = d3.svg.axis()
        .scale(this.x)
        .orient('bottom');
    this.yAxis = d3.svg.axis()
        .scale(this.y)
        .orient('left');

    this.line = d3.svg.line()
        // https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes#line_interpolate
        .interpolate(options.interpolate)
        .x(function(d) {
            return thisRef.x(d.date);
        })
        .y(function(d) {
            return thisRef.y(d.value);
        });

    // Create an SVG element (appended to `#chart`):
    // 1. set size;
    // 2. add a `g` element (as in a group) - the `svg` variable here is a `g` element now;
    // 3. set the transform on `<g>` (not on `<svg>`).
    this.svg = d3.select(options.target)
        .append('svg')
        .attr('width', this.width + margin.left + margin.right)
        .attr('height', this.height + margin.top + margin.bottom)
        .append('g')
        .attr('transform', `translate(${margin.left}, ${margin.top})`);

    // Add x axis.
    this.svg.append('g')
        .attr('class', 'x axis')
        .attr('transform', `translate(0, ${this.height})`)

    // Add y axis (with a label).
    this.svg.append('g')
        .attr('class', 'y axis')
        // Just for the title (ticks are automatic).
        .append('text')
        // Rotate the text.
        .attr('transform', 'rotate(-90)')
        .attr('y', 6)
        .attr('dy', '.71em')
        .style('text-anchor', 'end')
        .text('Price ($)');
}

LineChart.prototype.update = function update(data) {
    const thisRef = this;

    this.x.domain(d3.extent(data, function(d) {
        return d.date;
    }));
    this.y.domain(d3.extent(data, function(d) {
       return d.value;
    }));

    this.svg.select('.x.axis')
        .transition()
        .call(this.xAxis);
    this.svg.select('.y.axis')
        .transition()
        .call(this.yAxis)

    const lines = this.svg.selectAll('.line')
        .data(data, function(d) {
            return d.label;
        });

    lines.enter()
        .append('path')
        .attr('class', 'line');

    lines.transition()
        .duration(1500)
        .attr('d', this.line(data));

    lines.exit()
        .remove();
}

You can see a running example here.

In the example there is a 3000 ms delay after which you will see the update and the animation.

But I have a slight problem, a part of the line is being thrown out without any animation.

I have a hard time figuring out what is going on as I am not very experienced with d3, some help would be appreciated :)

Upvotes: 1

Views: 702

Answers (1)

Nixie
Nixie

Reputation: 637

D3 animates DOM elements. There is only one DOM element (a line) in your example, and D3 thinks that you changing that one line (for 1500ms). It does not know what to do with the points thrown away (or with points added). SVG path segments are just not a unit of animation. The 'enter' and 'exit' selections are not changed when you change the data (and 'exit' selection is empty).

You have two options:

  1. Before removing piece of data, create an extra line (DOM element) with missing points, and animate it separately.
  2. Create segments of that line (thus, making multiple lines, one line per each 2 points), and animate them (see http://jsfiddle.net/L1tLot0v/15/). Unfortunately, then the interpolation will not be that cool.

    var segmentedData = segments(data);
    const lines = this.svg.selectAll('.line')
        .data(segmentedData, function(d) {
            return d[0].date;
    });
    
    var lineFunc = this.line;
    lines.enter()
        .append('path')
        .attr('class', 'line')
        .style("opacity", 0.0)
        .attr('d', function(d) {
          return lineFunc(d)})
        .transition()
        .duration(1000)
        .style("opacity", 1.0);
    
    lines.exit()
      .transition()
      .duration(1000)
      .style("opacity", 0.0)
      .remove();
    

Upvotes: 1

Related Questions