D3 js - line not showing in real time line graph - javascript

I am creating an application which works with a bluetooth heart rate monitor, and I will be using D3.js to graph the heart rate in real time. Currently, I'm just creating a simple example which I can tweak later to work with real data.
Basically all I am trying to do at this stage is plot randomly generated data over the last minute. The data, readings, is an array of dummy readings of the form {timestamp: (the time which it was generated), hr:(num between 0 and 1)}. Initially readings will be empty but every second, a new random value with the current time will be pushed on.
For some reason, the graph isn't showing, however there are no errors being reported in my code. The x-axis seems to be correct and transitioning fine. Any advice or help would be much appreciated.
I am going to post my entire code as I am not sure of the origin of the issue.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.line {
fill: none;
stroke: blue;
stroke-width: 2px;
}
</style>
<svg width="600" height="400"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var n = 40,
random = d3.randomUniform(0, 1),
readings = [],
currentReadings = [],
duration = 1000,
now = new Date(Date.now() - duration);
startTime = new Date();
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 xScale = d3.scaleTime()
.domain([now - 60000, now])
.range([0, width]);
var yScale = d3.scaleLinear()
.domain([0, 1])
.range([height, 0]);
var line = d3.line()
.x(function(data) { return xScale(data.time); })
.y(function(data) { return yScale(data.hr); })
.curve(d3.curveBasis);
g.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var xAxisGroup = g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0, " + height + ")")
.call(xAxis = d3.axisBottom(xScale).
ticks(3));
var yAxisGroup = g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(yScale));
var path = g.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.datum(currentReadings)
.attr("class", "line")
.transition()
.duration(1000)
.ease(d3.easeLinear)
.on("start", tick);
function tick() {
now = new Date();
// update the x axis domain and slide left
xScale.domain([now - 60000, now]);
xAxisGroup.call(xAxis);
// generate new random reading
reading = {time: new Date(), hr: random()};
// Push the new reading onto readings and currentReadings
readings.push(reading);
currentReadings.push(reading);
// Remove readings not within graph period
for (var i=0; i<currentReadings.length; i++) {
if (now - currentReadings[i].time > 60000)
currentReadings.shift();
else
break;
}
// Redraw the line.
d3.select(this)
.datum(currentReadings)
.attr("d", line)
.attr("transform", null);
// Slide it to the left.
d3.active(this)
.attr("transform", "translate(" + xScale(0) + ",0)")
.transition()
.duration(duration)
.on("start", tick);
}
</script>

The key issue is that you are translating the graph too much. It is drawing, just not anywhere remotely visible. Why?
d3.active(this)
.attr("transform", "translate(" + xScale(0) + ",0)")
.transition()
.duration(duration)
.on("start", tick);
You are translating by xScale(0), which is some very high magnitude negative number:
var now = new Date();
var xScale = d3.scaleTime()
.domain([now - 60000, now])
.range([0, 100]);
console.log(xScale(0));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
So you'll never see your graph, it's well to the left. Instead, you could translate by the difference in x values between coordinates (as you update once per second, and show 60 values across your width, that could be: width/60). If the incoming points are inconsistent, then you'll need to translate by the width difference between the incoming points is in order to scroll the graph by an appropriate amount.
The snippet below shows your chart assuming a constant rate of data input (I've also applied a transition to the x axis so it scrolls and doesn't jump):
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.line {
fill: none;
stroke: blue;
stroke-width: 2px;
}
</style>
<svg width="600" height="400"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var n = 40,
random = d3.randomUniform(0, 1),
readings = [],
currentReadings = [],
duration = 1000,
now = new Date(Date.now() - duration);
startTime = new Date();
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 xScale = d3.scaleTime()
.domain([now - 60000, now])
.range([0, width]);
var yScale = d3.scaleLinear()
.domain([0, 1])
.range([height, 0]);
var line = d3.line()
.x(function(data) { return xScale(data.time); })
.y(function(data) { return yScale(data.hr); })
.curve(d3.curveBasis);
g.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var xAxisGroup = g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0, " + height + ")")
.call(xAxis = d3.axisBottom(xScale).
ticks(3));
var yAxisGroup = g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(yScale));
var path = g.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.datum(currentReadings)
.attr("class", "line")
.transition()
.duration(1000)
.ease(d3.easeLinear)
.on("start", tick);
function tick() {
now = new Date();
// update the x axis domain and slide left
xScale.domain([now - 60000, now]);
// transition the axis:
xAxisGroup.transition().duration(1000).ease(d3.easeLinear).call(xAxis);
// generate new random reading
reading = {time: new Date(), hr: random()};
// Push the new reading onto readings and currentReadings
readings.push(reading);
currentReadings.push(reading);
// Remove readings not within graph period
for (var i=0; i<currentReadings.length; i++) {
if (now - currentReadings[i].time > 60000)
currentReadings.shift();
else
break;
}
// Redraw the line.
d3.select(this)
.datum(currentReadings)
.attr("d", line(currentReadings))
.attr("transform", null);
// Slide it to the left.
d3.active(this)
.attr("transform", "translate(" + -width/60 + ",0)") // transition based on distance between each data point.
.transition()
.duration(duration)
.on("start", tick);
}
</script>
The jumpiness of the leading part of the chart is do to the easing and corrections to the easing made once the next point is generated

Related

Slight lag in translation animation using D3.interval()

I have created a time series animation. Essentially, I generate a random datum every 20ms and the time axis slides continuously leftwards on a one second interval. D3.interval() creates what I want, but the graph seems "choppy". I assume it has something to do with the web browser or D3? Here is my code (expand the snippet window for a better view):
////////////////////////////////////////////////////////////
////////////////////// Set-up /////////////////////////////
////////////////////////////////////////////////////////////
const margin = { left: 80, right: 80, top: 30, bottom: 165},
TIME_MEASUREMENT = 20,
TIME_INTERVAL = 1000,
TIME_FRAME = 10000;
let width = $("#chart").width() - margin.left - margin.right,
height = $("#chart").height() - margin.top - margin.bottom;
let date1 = 0,
date2 = TIME_FRAME;
let accuracy = 20,
precision = 20,
aF = 2, //accuracyFactor
pF = 1; //precisionFactor
let random = d3.randomUniform(-(pF * precision), pF * precision),
count = TIME_FRAME/TIME_MEASUREMENT + 1;
let data = d3.range(count).map((d,i) => random() + accuracy );
const yDomain = [0, 50],
yTickValues = [0, 10, 20, 30, 40, 50];
////////////////////////////////////////////////////////////
///////////////////////// SVG //////////////////////////////
////////////////////////////////////////////////////////////
const svg = d3.select("#chart")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
const g = svg.append("g")
.attr("transform", "translate(" + margin.left + ", " + margin.top + ")");
////////////////////////////////////////////////////////////
///////////////////// Axes & Scales ////////////////////////
////////////////////////////////////////////////////////////
//X axis - dynamic
let xAxisGroup = g.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0," + (height) + ")");
let xScale = d3.scaleTime();
const xAxis = d3.axisBottom(xScale)
.ticks(d3.timeSecond.every(1))
.tickSizeInner(15)
.tickFormat(d3.timeFormat("%M:%S"));
//Y axis - static
let yAxisGroup = g.append("g")
.attr("class", "y-axis");
let yScale = d3.scaleLinear()
.domain(yDomain)
.range([height, 0]);
const yAxis = d3.axisLeft()
.scale(yScale)
.tickValues(yTickValues)
.tickFormat(d3.format(".0f"));
yAxisGroup.call(yAxis);
//Data points - static
let dataScale = d3.scaleTime()
.domain([date1,date2])
.range([0,width]);
////////////////////////////////////////////////////////////
/////////////////////// Functions //////////////////////////
////////////////////////////////////////////////////////////
function drawData(){
xScale.domain([date1, date2])
.range([0, width]);
xAxisGroup
.transition()
.duration(TIME_MEASUREMENT)
.ease(d3.easeLinear)
.call(xAxis);
g.selectAll("circle")
.data(data)
.join(
function(enter){
enter.append("circle")
.attr("cx", (d,i) => dataScale(i*TIME_MEASUREMENT))
.attr("cy", (d,i) => yScale(d))
.attr("fill", "black")
.attr("r","3");
},
function(update){
update
.attr("cx", (d,i) => dataScale(i*TIME_MEASUREMENT))
.attr("cy", (d,i) => yScale(d))
.transition()
.ease(d3.easeLinear)
.attr("transform", "translate(" + dataScale(-TIME_MEASUREMENT) + ", 0)");
}
);
data.push(random() + accuracy);
data.shift();
date1 += TIME_INTERVAL/50;
date2 += TIME_INTERVAL/50;
};
////////////////////////////////////////////////////////////
///////////////////////// Main /////////////////////////////
////////////////////////////////////////////////////////////
d3.interval(drawData, TIME_MEASUREMENT);
x-axis,
.y-axis {
font-size: 0.8em;
stroke-width: 0.06em;
}
#chart {
width: 600px;
height: 500px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="//d3js.org/d3.v6.min.js"></script>
<div id="chart"></div>
I don't get this "choppy effect" when I use d3.timer or setInterval, but each second slides as if it were ~850ms. Thank you for your input!

D3 Transform Rescale X jumps to right

I'm creating a timeline line chart showing 5 or 6 different lines and want to be able to zoom in and scroll (once zoomed). I used some examples that use area charts but for some reason my line chart jumps to the right when I zoom the first time and I lose some of the data off to the right (I can no longer scroll to see it or see it when zoomed fully out). Also the lines appear over the y axis when I zoom or scroll.
I've copied it to JSFiddle (see here) with a dataset from 1 of the lines in my chart. Why is the line jumping to the right as soon as you use the zoom function? How can I stop the line from appearing over the y-axis?
Here is the JS of my version if you'd prefer to read it here:
function drawTimeline() {
var margin = {top: 10, right: 0, bottom: 50, left: 60}
var width = d3.select('#timeline').node().getBoundingClientRect().width/3*2;
var height = 300;
var svg = d3.select("#timeline").append("svg")
.attr("width", width)
.attr("height", height+margin.top+margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("width", width - margin.left - margin.right)
.call(d3.zoom()
// .extent()
.scaleExtent([1, 10])
.translateExtent([[0, -Infinity], [width - margin.left - margin.right, Infinity]])
.on("zoom", zoom)
);
var view = svg.append("rect")
.attr("class", "view")
.attr("width", width - margin.left - margin.right)
.attr("height", height)
.attr("fill", "white");
var x = d3.scaleTime()
.domain([
d3.min(poll_data[0].avgpolls, function(p) { return p.date; }),
d3.max(poll_data[0].avgpolls, function(p) { return p.date; })
])
.range([0, width - margin.left - margin.right]);
var y = d3.scaleLinear()
.domain([50, 0])
.range([0, height]);
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
var gX = svg.append("g")
.attr("class", "axis xaxis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
var gY = svg.append("g")
.attr("class", "axis yaxis")
.call(yAxis);
//All lines are drawn in the same way (x and y points)
var line = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.poll); });
//selectAll allows us to create and manipulate multiple groups at once
var party = svg.selectAll(".party")
.data(poll_data)
.enter()
.append("g")
.attr("class", "party");
//Add path to every country group at once
var pollPaths = party.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.avgpolls); })
.attr("fill", "none")
.attr("stroke-width", 2)
.style("stroke", function(d) { return returnPartyColour(d.party); });
function zoom() {
console.log("zooming: " + d3.event.transform);
var new_x = d3.event.transform.rescaleX(x);
gX.call(d3.axisBottom(new_x));
//view.attr("transform", d3.event.transform);
//Redraw lines
//pollPaths.select(".line").attr("d", function(d) { return line(d.avgpolls); });
var newline = d3.line()
.x(function(d) { return new_x(d.date); })
.y(function(d) { return y(d.poll); });
pollPaths.attr("d", function(d) { return line(d.avgpolls); });
}
}
The data is formatted like this in my version but not the JSFiddle:
poll_data = [
{
'party' : 'Party 1',
'avgpolls' : [
{'date' : new Date(year, month, day), 'poll' : 0, },
],
]
Thanks
Two things required to fix these issues.
The reason the line was jumping on zoom was because the zoom extent was not set. This was set and the value of translateExtent updated:
d3.zoom()
.scaleExtent([1, 10])
.translateExtent([[0, 0], [width - margin.left - margin.right, Infinity]])
.extent([[0, 0], [width - margin.left - margin.right, height]])
.on("zoom", zoom)
To prevent the paths from overflowing a clipping path is required. After creating the svg, before other elements are added, I added a clip-path as follows:
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
//Same dimensions as the area for the lines to appear
.attr("width", width - margin.left - margin.right)
.attr("height", height);
Then this had to be added to each path.
var pollPaths = party.append("path")
...
.attr("clip-path", "url(#clip)");
The original JSFiddle has been updated to reflect these changes.

D3.js - changing ordinal scales dynamically

I've got the following piece of code that takes a mouse movement on an SVG container and will shrink/grow the height/width of a visualization based on a mouse movement. When the user has a mouse movement in the x direction, the bars on the chart appear jittery: the x attribute will increase by, 2 or three and then revert to what it was previously: the rects on the page will scoot right a couple pixels, and then snap back to their original position.
Is there a bug with how I'm changing the ordinal scale? Or should I use a transform instead of manipulating the X value?
'use strict';
var d3 = require("d3");
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var chartData;
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 + ")");
let render = (e,data)=>{
width += d3.event ? d3.event.movementX : 0; //changing x axis here
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([data.height, 0]);
//
x.domain(data.map(function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
//
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10, "%")
.tickSize(1);
var yAxisEl = svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Frequency");
var bars = svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) {
console.log(x(d.letter));
var currX = x(d.letter); //sometimes gives wrong valuse
return currX;
})
.attr("width", function(){
return x.rangeBand();
})
.attr("y", function(d) { return y(d.frequency); })
.attr("height", function(d) { return Math.abs(height - y(d.frequency)); })
}
let rerender=(data)=>{
d3.select("svg").select("g").selectAll("*").remove();
render(null,data);
}
d3.tsv("data.tsv", type, function(error, data) {
if (error) throw error;
chartData = data;
chartData.height = height;
chartData.width = width;
render(error,chartData);
});
d3.selectAll('svg').on('mousemove',function(){
if(chartData){
chartData.height += d3.event.movementY;
rerender(chartData);
}
});
data
letter frequency
A .08167
B .01492
C .02782
D .04253
E .12702
F .02288
G .02015
I made the rerender function inside time out (hope this fixes the problem you referring)
var rerender = (data) => {
if (myVar){
clearTimeout(myVar);//clear timeout if called before 1 millisecond
}
myVar = setTimeout(function(){
d3.select("svg").select("g").selectAll("*").remove();
render(null, data);
}, 1);
}
working code here

In d3js, is it possible to transition a fixed range of values on x-axis?

I wish to create a line chart with transition. So for the first step, I wish to simply draw the axis and move the x-axis. The x-axis has 0-15 as values, and I want these values to keep on moving in a loop.. I took help from this code which I got through stackoverflow: http://jsfiddle.net/aggz2qbn/
Here is the code I have:
var t = 1, maxval;
var i, n = 40;
var duration = 750;
function refilter(){
var margin = {top: 10, right: 10, bottom: 20, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([1, 15])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, maxval])
.range([height, 0]);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// extra svg to clip the graph and x axis as they transition in and out
var graph = g.append("svg")
.attr("width", width)
.attr("height", height + margin.top + margin.bottom);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var axis = graph.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(x.axis=xAxis);
g.append("g")
.attr("class", "y axis")
.call(d3.svg.axis().scale(y).orient("left"));
tick();
function tick() {
t++;
if(t>=15)
t=1;
else if(t<=15)
{
x.domain([t,15]);
axis.transition()
.duration(500)
.ease("linear")
.call(xAxis)
.each("end", tick);
}
// slide the x-axis left
}
}
The values on x-axis do move, but not in a repetitive way, instead they simply stretch frm 1 to 15. Can anybody help me out?
Your tick function is setting the domain as:
[1,15]
then
[2,15]
then
[3,15]
etc...
This is more a zoom towards the end. What you want is a rolling effect (say with an N of 5 ticks):
[1,5]
then
[2,6]
then
[3,7]
etc...
So:
function tick() {
var curD = x.domain(); // get current domain
if (curD[1] >= 15){ // at 15 reset to 1,5
curD[0] = 0;
curD[1] = n-1;
}
x.domain([curD[0]+1,curD[1]+1]); // increase both sides by one
// slide the x-axis left
axis.transition()
.duration(1500)
.ease("linear")
.call(xAxis)
.each("end", tick);
}
Example here.
EDITS FOR COMMENT
Datetimes can be handled the same way, but do the math different. For instance, scrolling my months:
function tick() {
var curD = x.domain();
var newD = [curD[0].setMonth(curD[0].getMonth() + 1),
curD[1].setMonth(curD[1].getMonth() + 1)]
x.domain(newD);
// slide the x-axis left
axis.transition()
.duration(1500)
.ease("linear")
.call(xAxis)
.each("end", tick);
}
Updated example.

Drawing multiple real time lines

I want to draw multiple real time lines using JSON files. I am basically retrieving the JSON file from a website, getting the time data (duration in seconds), converting them into minutes and pushing them into the data array. This code checks the JSON file for every second.
I want to add as many line as possible. For example, I want to add the average of the elements in data array (average duration) and plot it on the same plane. I tried to add another "line" and "path" variable, however I wasn't able to plot it at the same time.
The data array is an empty array with 44 elements in the beginning, and everytime the code checks the JSON file it replaces those zeroes with the retrieved duration data.
Here is my code to draw only one line.
function graph() {
var n = 43,
duration = 1000,
now = new Date(Date.now() - duration),
count = 0,
data = d3.range(n).map(function() { return 0; });
var margin = {top: 10, right: 20, bottom: 30, left: 60},
width = 1200 - margin.left-margin.right,
height = 460 - margin.top - margin.bottom;
var x = d3.time.scale()
.domain([now - (n - 2) * duration, now - duration])
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var line = d3.svg.line()
.interpolate("basis")
.x(function(d, i) { return x(now - (n - 1 - i) * duration); })
.y(function(d, i) { return y(d); });
var line2 = d3.svg.line()
.interpolate("basis")
.x(function(d, i) { return x(now - (n - 1 - i) * duration); })
.y(function(d, i) { return y(d); });
var svg = d3.select("body").append("p").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.style("margin-left", -margin.left + "px")
.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);
var axis = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate( "+margin.left+"," + height + ")")
.call(x.axis = d3.svg.axis().scale(x).orient("bottom"));
var yaxis = svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + margin.left + ",0)")
.call(y.axis = d3.svg.axis().scale(y).orient("left"));
d3.select(".y.axis")
.append("text")
.text("Travel Time (min)")
.attr("text-anchor", "middle")
.attr("transform","rotate( -90, 200, 0)")
.attr("y",-250);
var path = svg.append("g")
.attr("clip-path", "url(#clip)")
.attr("transform", "translate(" + margin.left + ",0)")
.append("path")
.data([data])
.attr("class", "line");
tick();
function tick() {
d3.json("route.json",function(barzo){
var tempdata = barzo.route;
var len = tempdata.realTime;
var lastdata = parseInt(len)/60; //this is the time variable I use.
// update the domains
now = new Date();
x.domain([now - (n - 2) * duration, now - duration]);
y.domain([0, d3.max(data)+5]);
// push the time into the data
data.push(count);
count = lastdata;
// redraw the line
svg.select(".line")
.attr("d", line)
.attr("transform", null);
// slide the x-axis left
axis.transition()
.duration(duration)
.ease("linear")
.call(x.axis);
yaxis.transition()
.duration(duration/10)
.ease("linear")
.call(y.axis);
// slide the line left
path.transition()
.duration(duration)
.ease("linear")
.attr("transform", "translate(" + x(now - (n - 1) * duration) + ")")
.each("end", tick);
// pop the old data point off the front
data.shift();
});
}
};
First, I included another another data array (data2) to push the new data points for the new path:
var n = 43,
duration = 1000,
now = new Date(Date.now() - duration),
count = 0,
data = d3.range(n).map(function() { return 0; });
data2 = d3.range(n).map(function() { return 0; });
Then, I defined another path for the line that uses the points of data2 array.
var path2 = svg.append("g")
.attr("clip-path", "url(#clip)")
.attr("transform", "translate(" + margin.left + ",0)")
.append("path")
.data([data2])
.attr("class", "line2")
In the tick function, I needed to select both of those lines to update them (You can write a function to do the same thing for these steps instead of repeating the same code twice).
// redraw the line
svg.select(".line")
.attr("d", line)
.attr("transform", null);
svg.select(".line2")
.attr("d", line2)
.attr("transform", null);
The same thing for transition and data shift as well
// slide the line left
path.transition()
.duration(duration)
.ease("linear")
.attr("transform", "translate(" + x(now - (n - 1) * duration) + ")");
path2.transition()
.duration(duration)
.ease("linear")
.attr("transform", "translate(" + x(now - (n - 1) * duration) + ")")
.each("end", tick);
// pop the old data point off the front
data.shift();
data2.shift();

Categories