简体   繁体   中英

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

  • I want to push new data point to point ie I would like to do the following

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

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

  • The line chart should therefore be translated of one point (like it is now), but not repeating the "old" values in the range;
  • I do not know why the y axis shows no labels, while the x axis is doing;

[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:

在此处输入图片说明

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM