Alex
Alex

Reputation: 2959

Why the line in this D3 chart isn't properly displayed when updated?

I'm a beginner with D3.js and I want to display a dynamic line chart where the line is always growing with random fluctuations.

I don't need an X axis but I'd like to get a dynamic Y axis based on the last point inserted in the line.

var n = 40,
    random = function(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; },
    data = d3.range(n).map(random);

var margin = {top: 20, right: 20, bottom: 20, left: 40},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

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

var y = d3.scale.linear()
    .domain([0, 100])
    .range([height, 0]);

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

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("defs").append("clipPath")
    .attr("id", "clip")
  .append("rect")
    .attr("width", width)
    .attr("height", height);
svg.append("g")
    .attr("class", "y axis")
    .call(d3.svg.axis().scale(y).orient("left"));
var path = svg.append("g")
    .attr("clip-path", "url(#clip)")
  .append("path")
    .datum(data)
    .attr("class", "line")
    .attr("d", line);

var min = 0, max = min + 40;
tick();

//Update the chart
function tick() {
  // push a new data point onto the back
  var r = random(min, max);
  data.push(r);
  min += 10;
  max += 10;

  // update Y Axis
  var y = d3.scale.linear().domain([r - 20,r + 20]).range([height, 0]);
  var yAxis = d3.svg.axis().scale(y).orient("left");
  svg.selectAll(".y.axis").call(yAxis);

  // redraw the line, and slide it to the left
  path
      .attr("d", line)
      .attr("transform", null)
    .transition()
      .duration(500)
      .ease("linear")
      .attr("transform", "translate(" + x(0) + ",0)")
      .each("end", tick);
  // pop the old data point off the front
  data.shift();
}

JSFiddle : https://jsfiddle.net/ugj8g9wu/

If I didn't increase the min / max and don't update the Y Axis everything is ok.

But with the code above, my line quickly go above the the Y axis, which doesn't make any sens since the randomized value is include in the domain of the Y axis...

Could you tell me what's going on and why my line isn't properly displayed?

Upvotes: 2

Views: 130

Answers (1)

Gildor
Gildor

Reputation: 2574

The issue is a bit hidden. In tick(), you made a new y to handle the new domain and range, but you only updated yAxis with this y. What about the line which is still referencing the original y? It also needs update! You can either add code to update the line:

// update Y Axis
var y = d3.scale.linear().domain([r - 20,r + 20]).range([height, 0]);
var yAxis = d3.svg.axis().scale(y).orient("left");
svg.selectAll(".y.axis").call(yAxis);

// NEW CODE
line.y(function(d, i) { return y(d); });

Or (better I think), instead of creating a new y every tick, you can modify the existing one, saving all the efforts to assign it to everywhere else using it. Just change this line:

var y = d3.scale.linear().domain([minY, maxY]).range([height, 0]);

into:

y.domain([minY, maxY]);

Then you'll be able to see the newest point coming in the right.

But there's one more problem with the code: you are increasing the value too quickly so that it's hard to see old points on the chart, so I tuned the arguments a bit to make it look better. Ideally, the minY and maxY should be calculated according to the values in data, not guessing magic boundarys. :)

Fiddle: https://jsfiddle.net/gbwycmrd/

Upvotes: 1

Related Questions