loretoparisi
loretoparisi

Reputation: 16271

d3 Animated Line Chart with Path and SVG

I'm trying to adapt the animated line example here to a line chart

function displayGraph(id, data, width, height, interpolation, animate, updateDelay, transitionDelay) {
  // create an SVG element inside the #graph div that fills 100% of the div
  var graph = d3.select(id).append("svg:svg").attr("width", "100%").attr("height", "100%");

  // create a simple data array that we'll plot with a line (this array represents only the Y values, X will just be the index location)


  // X scale will fit values from 0-10 within pixels 0-100
  var x = d3.scale.linear().domain([0, 48]).range([-5, width]); // starting point is -5 so the first value doesn't show and slides off the edge as part of the transition
  // Y scale will fit values from 0-10 within pixels 0-100
  var y = d3.scale.linear().domain([0, 10]).range([0, height]);

  // create a line object that represents the SVN line we're creating
  var line = d3.svg.line()
    // assign the X function to plot our line as we wish
    .x(function(d, i) {
      // verbose logging to show what's actually being done
      //console.log('Plotting X value for data point: ' + d + ' using index: ' + i + ' to be at: ' + x(i) + ' using our xScale.');
      // return the X coordinate where we want to plot this datapoint
      return x(i);
    })
    .y(function(d) {
      // verbose logging to show what's actually being done
      //console.log('Plotting Y value for data point: ' + d + ' to be at: ' + y(d) + " using our yScale.");
      // return the Y coordinate where we want to plot this datapoint
      return y(d);
    })
    .interpolate(interpolation)

  // display the line by appending an svg:path element with the data line we created above
  graph.append("svg:path").attr("d", line(data));
  // or it can be done like this
  //graph.selectAll("path").data([data]).enter().append("svg:path").attr("d", line);


  function redrawWithAnimation() {
    // update with animation
    graph.selectAll("path")
      .data([data]) // set the new data
      .attr("transform", "translate(" + x(1) + ")") // set the transform to the right by x(1) pixels (6 for the scale we've set) to hide the new value
      .attr("d", line) // apply the new data values ... but the new value is hidden at this point off the right of the canvas
      .transition() // start a transition to bring the new value into view
      .ease("linear")
      .duration(transitionDelay) // for this demo we want a continual slide so set this to the same as the setInterval amount below
      .attr("transform", "translate(" + x(0) + ")"); // animate a slide to the left back to x(0) pixels to reveal the new value

    /* thanks to 'barrym' for examples of transform: https://gist.github.com/1137131 */
  }

  function redrawWithoutAnimation() {
    // static update without animation
    graph.selectAll("path")
      .data([data]) // set the new data
      .attr("d", line); // apply the new data values
  }
  setInterval(function() {
    if (animate) {
      redrawWithAnimation();
    } else {
      redrawWithoutAnimation();
    }
  }, updateDelay);
} //displayGraph

// input data
var data = [3, 6, 2, 7, 5, 2, 1, 3, 8, 9, 2, 5, 9, 3, 6, 3, 6, 2, 7, 5, 2, 1, 3, 8, 9, 2, 5, 9, 2, 7, 5, 2, 1, 3, 8, 9, 2, 5, 9, 3, 6, 2, 7, 5, 2, 1, 3, 8, 9, 2, 9];

// display
displayGraph("#graph1", data, 300, 30, "basis", true, 1000, 1000);

// update data
setInterval(function() {
  var v = data.shift(); // remove the first element of the array
  data.push(v); // add a new element to the array (we're just taking the number we just shifted off the front and appending to the end)
}, 1000);
path {
  stroke: steelblue;
  stroke-width: 1;
  fill: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="graph1" class="aGraph" style="width:300px; height:30px;"></div>

While in the example we draw a simple line, so the data is represented by an array of x values, being y the index, my dataset is like

var data=[ 
{"progress":42.3,"words":2116,"lr":0.288598,"loss":4.07032,"eta":"0h0m"}, {"progress":44,"words":2197,"lr":0.279892,"loss":4.06091,"eta":"0h0m"},{"progress":45.7,"words":2279,"lr":0.27161,"loss":4.053332,"eta":"0h0m"},{"progress":46.6,"words":2364,"lr":0.267103,"loss":4.052618,"eta":"0h0m"},{"progress":49.1,"words":2449,"lr":0.254353,"loss":4.055149,"eta":"0h0m"}, {"progress":50.9,"words":2532,"lr":0.245493,"loss":4.057263,"eta":"0h0m"},{"progress":52.7,"words":2617,"lr":0.236479,"loss":4.059458,"eta":"0h0m"},{"progress":57,"words":2833,"lr":0.215139,"loss":4.056543,"eta":"0h0m"},{"progress":58.8,"words":2920,"lr":0.205817,"loss":4.03259,"eta":"0h0m"},{"progress":61.5,"words":3046,"lr":0.192411,"loss":3.980249,"eta":"0h0m"},{"progress":64.2,"words":3175,"lr":0.178891,"loss":3.914494,"eta":"0h0m"},{"progress":66,"words":3262,"lr":0.170031,"loss":3.905593,"eta":"0h0m"},{"progress":67.8,"words":3345,"lr":0.161171,"loss":3.912257,"eta":"0h0m"},
 {"progress":69.4,"words":3425,"lr":0.152928,"loss":3.917797,"eta":"0h0m"},
{"progress":71,"words":3499,"lr":0.145031,"loss":3.922638,"eta":"0h0m"},{"progress":72.8,"words":3587,"lr":0.136055,"loss":3.927278,"eta":"0h0m"},
 {"progress":75.4,"words":3714,"lr":0.123112,"loss":3.932528,"eta":"0h0m"},{"progress":77.1,"words":3799,"lr":0.114638,"loss":3.919754,"eta":"0h0m"},{"progress":78.9,"words":3885,"lr":0.105701,"loss":3.877759,"eta":"0h0m"}
]

and I want to represent as y the lr or loss value, while having on the x the progress value of the data object, but I don't figure out how to keep the animation while plotting both the axis.

[UPDATE] A first attempt is the following

function displayGraph(id, data, width, height, interpolation, animate, updateDelay, transitionDelay) {

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

  var graph = d3.select(id)
    .append("svg:svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  var x = d3.scale.linear().domain([0, data.length]).range([-data.length, width]);
  var y = d3.scale.linear().domain([0, d3.max(data, function(d) {
    return d.lr
  })]).range([height, 0]);
  var y2 = d3.scale.linear().domain([0, d3.max(data, function(d) {
    return d.loss
  })]).range([height, 0]);


  var xAxis = d3.svg.axis().scale(x)
    .orient("bottom").ticks(10);
  var yAxis = d3.svg.axis().scale(y)
    .orient("left").ticks(10);

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

  graph.append("svg:path").attr("d", line(data)).attr('stroke', function(d) {
    return "blue"
  });
  graph.append("g") // Add the X Axis
    .attr('stroke', function(d) {
      return "steelblue"
    })
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);
  graph.append("g") // Add the Y Axis
    .attr('stroke', function(d) {
      return "steelblue"
    })
    .attr("class", "y axis")
    .call(yAxis);


  function redrawWithAnimation() {
    graph.selectAll("path")
      .data([data])
      .attr("transform", "translate(" + x(1) + ")")
      .attr("d", line)
      .transition()
      .ease("linear")
      .duration(transitionDelay)
      .attr("transform", "translate(" + x(0) + ")");
  }

  function redrawWithoutAnimation() {
    graph.selectAll("path")
      .data([data])
      .attr("d", line);
  }
  setInterval(function() {
    if (animate) {
      redrawWithAnimation();
    } else {
      redrawWithoutAnimation();
    }
  }, updateDelay);
} //displayGraph
var data = [];
var dataIn = [{
    "progress": 42.3,
    "words": 2116,
    "lr": 0.288598,
    "loss": 4.07032,
    "eta": "0h0m"
  }, {
    "progress": 44,
    "words": 2197,
    "lr": 0.279892,
    "loss": 4.06091,
    "eta": "0h0m"
  }, {
    "progress": 45.7,
    "words": 2279,
    "lr": 0.27161,
    "loss": 4.053332,
    "eta": "0h0m"
  }, {
    "progress": 46.6,
    "words": 2364,
    "lr": 0.267103,
    "loss": 4.052618,
    "eta": "0h0m"
  }, {
    "progress": 49.1,
    "words": 2449,
    "lr": 0.254353,
    "loss": 4.055149,
    "eta": "0h0m"
  }, {
    "progress": 50.9,
    "words": 2532,
    "lr": 0.245493,
    "loss": 4.057263,
    "eta": "0h0m"
  }, {
    "progress": 52.7,
    "words": 2617,
    "lr": 0.236479,
    "loss": 4.059458,
    "eta": "0h0m"
  }, {
    "progress": 57,
    "words": 2833,
    "lr": 0.215139,
    "loss": 4.056543,
    "eta": "0h0m"
  }, {
    "progress": 58.8,
    "words": 2920,
    "lr": 0.205817,
    "loss": 4.03259,
    "eta": "0h0m"
  }, {
    "progress": 61.5,
    "words": 3046,
    "lr": 0.192411,
    "loss": 3.980249,
    "eta": "0h0m"
  }, {
    "progress": 64.2,
    "words": 3175,
    "lr": 0.178891,
    "loss": 3.914494,
    "eta": "0h0m"
  }, {
    "progress": 66,
    "words": 3262,
    "lr": 0.170031,
    "loss": 3.905593,
    "eta": "0h0m"
  }, {
    "progress": 67.8,
    "words": 3345,
    "lr": 0.161171,
    "loss": 3.912257,
    "eta": "0h0m"
  },
  {
    "progress": 69.4,
    "words": 3425,
    "lr": 0.152928,
    "loss": 3.917797,
    "eta": "0h0m"
  },
  {
    "progress": 71,
    "words": 3499,
    "lr": 0.145031,
    "loss": 3.922638,
    "eta": "0h0m"
  }, {
    "progress": 72.8,
    "words": 3587,
    "lr": 0.136055,
    "loss": 3.927278,
    "eta": "0h0m"
  },
  {
    "progress": 75.4,
    "words": 3714,
    "lr": 0.123112,
    "loss": 3.932528,
    "eta": "0h0m"
  }, {
    "progress": 77.1,
    "words": 3799,
    "lr": 0.114638,
    "loss": 3.919754,
    "eta": "0h0m"
  }, {
    "progress": 78.9,
    "words": 3885,
    "lr": 0.105701,
    "loss": 3.877759,
    "eta": "0h0m"
  }
]
// display
displayGraph("#graph1", dataIn, 600, 200, "basis", true, 750, 1500); //linear

// update data
setInterval(function() {
  var v = dataIn.shift();
  if (v) dataIn.push(v);
}, 1000);
path {
  /*stroke: steelblue;*/
  stroke-width: 1;
  fill: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="graph1" class="aGraph" style="width:600px; height:200px;"></div>

Here there are several issues

so data will get a new value to simulate incoming data from a data source;

[ATTEMPT 3] In this last test I have followed the suggestion to add the graph to a group so that the translation will properly work and show the y axis label. I have also added a additional domain on data update. This has fixed the previous issue when redrawing the graph:

x.domain([0, 100]); // max(x) is 100
    y.domain([0, d3.max(data, function(d) {
      return d.lr;
    })]);

and I was able to add a new point to the data array at each update:

// update data
setInterval(function() {
  var v = dataIn.shift();
  if (v) data.push(v);
}, 1000);

The last issue I have is that the scale on the y axis seems to be still wrong, since I would like to be proportional to the y axis progress that should be the data.progress value, but I do not figure out how.

function displayGraph(id, data, width, height, interpolation, animate, updateDelay, transitionDelay) {

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


var svg = d3.select(id)
  .append("svg:svg")
  .attr("width", width + margin.left + margin.right)
  .attr("height", height + margin.top + margin.bottom)
var graph = svg.append('g')
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
  
  var x = d3.scale.linear().domain([0, 100]).range([0, width]); // max(x) is 100
  var y = d3.scale.linear().domain([0, 1]).range([height, 0]); // max(y) is 1   
  var line = d3.svg.line()
    .x(function(d, i) {
      return x(i);
    })
    .y(function(d) {
      return y(d.lr);
    })
    .interpolate(interpolation)

  var xAxis = d3.svg.axis().scale(x)
    .orient("bottom").ticks(10);
  var yAxis = d3.svg.axis().scale(y)
    .orient("left").ticks(10);

  graph.append("svg:path")
    .attr("d", line(data))
    .attr('stroke', function(d) {
      return "blue"
    });
  graph.append("g") // Add the X Axis
    .attr('stroke', function(d) {
      return "steelblue"
    })
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);
  graph.append("g") // Add the Y Axis
    .attr('stroke', function(d) {
      return "steelblue"
    })
    .attr("class", "y axis")
    .call(yAxis);


  function redrawWithAnimation() {
    //x.domain(d3.extent(data, function(d,i) { return i; }));
    x.domain([0, 100]); // max(x) is 100
    y.domain([0, d3.max(data, function(d) {
      return d.lr;
    })]);
    graph.selectAll("path")
      .data([data])
      .attr("transform", "translate(" + x(1) + ")")
      .attr("d", line)
      .transition()
      .ease("linear")
      .duration(transitionDelay)
      .attr("transform", "translate(" + x(0) + ")");

  }

  function redrawWithoutAnimation() {
    // static update without animation
    graph.selectAll("path")
      .data([data]) // set the new data
      .attr("d", line); // apply the new data values
  }
  setInterval(function() {
    if (animate) {
      redrawWithAnimation();
    } else {
      redrawWithoutAnimation();
    }
  }, updateDelay);
} //displayGraph
var data = [];
var dataIn = [{
    "progress": 42.3,
    "words": 2116,
    "lr": 0.288598,
    "loss": 4.07032,
    "eta": "0h0m"
  }, {
    "progress": 44,
    "words": 2197,
    "lr": 0.279892,
    "loss": 4.06091,
    "eta": "0h0m"
  }, {
    "progress": 45.7,
    "words": 2279,
    "lr": 0.27161,
    "loss": 4.053332,
    "eta": "0h0m"
  }, {
    "progress": 46.6,
    "words": 2364,
    "lr": 0.267103,
    "loss": 4.052618,
    "eta": "0h0m"
  }, {
    "progress": 49.1,
    "words": 2449,
    "lr": 0.254353,
    "loss": 4.055149,
    "eta": "0h0m"
  }, {
    "progress": 50.9,
    "words": 2532,
    "lr": 0.245493,
    "loss": 4.057263,
    "eta": "0h0m"
  }, {
    "progress": 52.7,
    "words": 2617,
    "lr": 0.236479,
    "loss": 4.059458,
    "eta": "0h0m"
  }, {
    "progress": 57,
    "words": 2833,
    "lr": 0.215139,
    "loss": 4.056543,
    "eta": "0h0m"
  }, {
    "progress": 58.8,
    "words": 2920,
    "lr": 0.205817,
    "loss": 4.03259,
    "eta": "0h0m"
  }, {
    "progress": 61.5,
    "words": 3046,
    "lr": 0.192411,
    "loss": 3.980249,
    "eta": "0h0m"
  }, {
    "progress": 64.2,
    "words": 3175,
    "lr": 0.178891,
    "loss": 3.914494,
    "eta": "0h0m"
  }, {
    "progress": 66,
    "words": 3262,
    "lr": 0.170031,
    "loss": 3.905593,
    "eta": "0h0m"
  }, {
    "progress": 67.8,
    "words": 3345,
    "lr": 0.161171,
    "loss": 3.912257,
    "eta": "0h0m"
  },
  {
    "progress": 69.4,
    "words": 3425,
    "lr": 0.152928,
    "loss": 3.917797,
    "eta": "0h0m"
  },
  {
    "progress": 71,
    "words": 3499,
    "lr": 0.145031,
    "loss": 3.922638,
    "eta": "0h0m"
  }, {
    "progress": 72.8,
    "words": 3587,
    "lr": 0.136055,
    "loss": 3.927278,
    "eta": "0h0m"
  },
  {
    "progress": 75.4,
    "words": 3714,
    "lr": 0.123112,
    "loss": 3.932528,
    "eta": "0h0m"
  }, {
    "progress": 77.1,
    "words": 3799,
    "lr": 0.114638,
    "loss": 3.919754,
    "eta": "0h0m"
  }, {
    "progress": 78.9,
    "words": 3885,
    "lr": 0.105701,
    "loss": 3.877759,
    "eta": "0h0m"
  }
]
// display
displayGraph("#graph1", data, 600, 200, "basis", true, 1000, 1000); //linear

// update data
setInterval(function() {
  var v = dataIn.shift();
  if (v) data.push(v);
}, 1000);
path {
  /*stroke: steelblue;*/
  stroke-width: 1;
  fill: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="graph1" class="aGraph" style="width:600px; height:200px;"></div>

The other issue is that given this code, the range of x does not extend with new values, so I get the y values out of the graph:

enter image description here

Upvotes: 0

Views: 694

Answers (1)

Razzildinho
Razzildinho

Reputation: 2584

The reason your y axis doesn't display is because it's outside the svg. If you add a group to the svg and translate that by the margin it will be visible.

var svg = d3.select(id)
  .append("svg:svg")
  .attr("width", width + margin.left + margin.right)
  .attr("height", height + margin.top + margin.bottom)
var graph = svg.append('g')
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

Can you clarify what is wrong with the way data is being added at the moment? Looks fine to me.

Upvotes: 1

Related Questions