D_S_X
D_S_X

Reputation: 1549

Smooth transition on updating paths in d3 v5

I'm trying to build a d3 line chart which updates the chart data on each click. Here's my progress so far:

var n = 10,
  random = d3.randomNormal(0, .2),
  data = d3.range(n).map(random);

var svg = d3.select("svg"),
  margin = {
    top: 20,
    right: 20,
    bottom: 20,
    left: 40
  },
  width = +svg.attr("width") - margin.left - margin.right,
  height = +svg.attr("height") - margin.top - margin.bottom,
  g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

var x = d3.scaleLinear()
  .domain([0, n - 1])
  .range([0, width]);

var y = d3.scaleLinear()
  .domain([-1, 1])
  .range([height, 0]);

var line = d3.line()
  .x(function(d, i) {
    return x(i);
  })
  .y(function(d, i) {
    return y(d);
  });

g.append("defs").append("clipPath")
  .attr("id", "clip")
  .append("rect")
  .attr("width", width)
  .attr("height", height);

g.append("g")
  .attr("class", "axis axis--x")
  .attr("transform", "translate(0," + y(0) + ")")
  .call(d3.axisBottom(x));

g.append("g")
  .attr("class", "axis axis--y")
  .call(d3.axisLeft(y));

g.append("g")
  .attr("clip-path", "url(#clip)")
  .append("path")
  .datum(data)
  .attr("class", "line")
  .attr('d', line)

function tick() {

  // Push a new data point onto the back.
  data.push(random());

  // Redraw the line.
  d3.select(".line")
    .attr("d", line)
    .attr("transform", null)
    .transition()
    .duration(300)

  // Slide it to the left.
  d3.select(".line")
    .attr("transform", "translate(" + x(-1) + ",0)")
    .transition()
    .duration(300)

  // Pop the old data point off the front.
  data.shift();

}

d3.select("#translate")
  .on("click", tick)
.line {
  fill: none;
  stroke: #000;
  stroke-width: 1.5px;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<button id="translate">Translate</button>
<svg width="960" height="500"></svg>

Currently, the chart updates instantly without any transition (even though i have added transition in update function). Whats am i doing wrong here?

I want to achieve something like this https://bl.ocks.org/mbostock/1642874, but on "click" rather than on "start" event.

Upvotes: 3

Views: 4548

Answers (1)

Andrew Reid
Andrew Reid

Reputation: 38151

You aren't transitioning anything:

d3.select(".line")
  .attr("...")
  .transition()
  .duration()

This doesn't specify that you are transitioning anything. The first two lines return the selection, the second two lines return a transition - you need to use .attr() on a transition to actually transition something. If .attr() is used on a selection, it simply updates that attribute. So, you need to use this pattern:

d3.select(".line")
  .transition()
  .duration()
  .attr("...")  // attribute to be transitioned

Or:

d3.select(".line")
  .transition()
  .attr("...")  // attribute to be transitioned
  .duration()

Also, you don't need to transition twice, add the new data and then shift the graph:

// Redraw the line.
d3.select(".line")
  .attr("d", line)
  .attr("transform", null)
  .transition()
  .attr("transform", "translate(" + x(-1) + ",0)")
  .duration(300)

Snippet:

var n = 10,
            random = d3.randomNormal(0, .2),
            data = d3.range(n).map(random);

        var svg = d3.select("svg"),
            margin = {
                top: 20,
                right: 20,
                bottom: 20,
                left: 40
            },
            width = +svg.attr("width") - margin.left - margin.right,
            height = +svg.attr("height") - margin.top - margin.bottom,
            g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

        var x = d3.scaleLinear()
            .domain([0, n - 1])
            .range([0, width]);

        var y = d3.scaleLinear()
            .domain([-1, 1])
            .range([height, 0]);

        var line = d3.line()
            .x(function(d, i) {
                return x(i);
            })
            .y(function(d, i) {
                return y(d);
            });

        g.append("defs").append("clipPath")
            .attr("id", "clip")
            .append("rect")
            .attr("width", width)
            .attr("height", height);

        g.append("g")
            .attr("class", "axis axis--x")
            .attr("transform", "translate(0," + y(0) + ")")
            .call(d3.axisBottom(x));

        g.append("g")
            .attr("class", "axis axis--y")
            .call(d3.axisLeft(y));

        g.append("g")
            .attr("clip-path", "url(#clip)")
            .append("path")
            .datum(data)
            .attr("class", "line")
            .attr('d', line)

        function tick() {

            // Push a new data point onto the back.
            data.push(random());

            // Redraw the line.
            d3.select(".line")
                .attr("d", line)
                .attr("transform", null)
				.transition()
                .attr("transform", "translate(" + x(-1) + ",0)")
                .duration(300)
				
            // Pop the old data point off the front.
            data.shift();

        }

        d3.select("#translate")
            .on("click", tick)
.line {
            fill: none;
            stroke: #000;
            stroke-width: 1.5px;
        }
<script src="https://d3js.org/d3.v5.min.js"></script>
   <button id="translate">Translate</button>
<svg width="960" height="500"></svg>

Upvotes: 7

Related Questions