FIDDLE <<< this has more modern code than in the question.
I am trying to create a real-time time series chart in d3 that can also be run (in X) and scaled up. Ideally, the functionality I want is that the right side of the line is visible to the user, and then when new data is added to the chart, it will automatically move sideways to include new data (without changing the axes).
My d3.json () requests should return JSON arrays that look like this:
[{"timestamp":1399325270,"value":-0.0029460209892230222598710528},{"timestamp":1399325271,"value":-0.0029460209892230222598710528},{"timestamp":1399325279,"value":-0.0029460209892230222598710528},....]
When the first page loads, I make a request and get the entire available date so far, and drawing a graph is easy. The following code does this; it also allows panning (in X) and zooming.
var globalData; var lastUpdateTime = "0"; var dataIntervals = 1; var margin = { top: 20, right: 20, bottom: 30, left: 50 }, width = document.getElementById("chartArea").offsetWidth - margin.left - margin.right, height = document.getElementById("chartArea").offsetHeight - margin.top - margin.bottom; var x = d3.time.scale() .range([0, width]); var y = d3.scale.linear() .range([height, 0]); var xAxis = d3.svg.axis() .scale(x) .orient("bottom") .ticks(10) .tickFormat(d3.time.format('%X')) .tickSize(1); //.tickPadding(8); var yAxis = d3.svg.axis() .scale(y) .orient("left"); var valueline = d3.svg.line() .x(function (d) { return x(d.timestamp); }) .y(function (d) { return y(d.value); }); var zoom = d3.behavior.zoom() .x(x) .y(y) .scaleExtent([1, 4]) .on("zoom", zoomed); var svg = d3.select("#chartArea") .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 + ")") .call(zoom); svg.append("rect") .attr("width", width) .attr("height", height) .attr("class", "plot"); // ???? var clip = svg.append("clipPath") .attr("id", "clip") .append("rect") .attr("x", 0) .attr("y", 0) .attr("width", width) .attr("height", height); var chartBody = svg.append("g") .attr("clip-path", "url(#clip)"); svg.append("g") // Add the X Axis .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis); svg.append("g") // Add the Y Axis .attr("class", "y axis") .call(yAxis); svg.append("text") .attr("transform", "rotate(-90)") .attr("y", 0 - margin.left) .attr("x", (0 - (height / 2))) .attr("dy", "1em") .style("text-anchor", "middle") .text("Return (%)"); // plot the original data by retrieving everything from time 0 d3.json("/performance/benchmark/date/0/interval/" + dataIntervals, function (error, data) { data.forEach(function (d) { lastUpdateTime = String(d.timestamp); // this will be called until the last element and so will have the value of the last element d.timestamp = new Date(d.timestamp); d.value = d.value * 100; }); globalData = data; x.domain(d3.extent(globalData, function (d) { return d.timestamp; })); y.domain(d3.extent(globalData, function (d) { return d.value; })); chartBody.append("path") // Add the valueline path .datum(globalData) .attr("class", "line") .attr("d", valueline); var inter = setInterval(function () { updateData(); }, 5000); }); var panMeasure = 0; var oldScale = 1; function zoomed() { //onsole.log(d3.event); d3.event.translate[1] = 0; svg.select(".x.axis").call(xAxis); if (Math.abs(oldScale - d3.event.scale) > 1e-5) { oldScale = d3.event.scale; svg.select(".y.axis").call(yAxis); } svg.select("path.line").attr("transform", "translate(" + d3.event.translate[0] + ",0)scale(" + d3.event.scale + ", 1)"); panMeasure = d3.event.translate[0]; }
In the next block of code, I make an http request to get all the new data and add it to the chart. It works great. Now I just need to understand the frontal logic for the new data, which, I think, will be here:
var dx = 0; function updateData() { var newData = []; d3.json("/performance/benchmark/date/" + lastUpdateTime + "/interval/" + dataIntervals, function (error, data) { data.forEach(function (d) { lastUpdateTime = String(d.timestamp); // must be called before its converted to Date() d.timestamp = new Date(d.timestamp); d.value = d.value * 100; globalData.push(d); newData.push(d); }); // panMeasure would be some measure of how much the user has panned (ie if the right-most part of the graph is still visible to the user. if (panMeasure <= 0) { // add the new data and pan x1 = newData[0].timestamp; x2 = newData[newData.length - 1].timestamp; dx = dx + (x(x1) - x(x2)); // dx needs to be cummulative d3.select("path") .datum(globalData) .attr("class", "line") .attr("d", valueline(globalData)) .transition() .ease("linear") .attr("transform", "translate(" + String(dx) + ")"); } else { // otherwise - just add the new data d3.select("path") .datum(globalData) .attr("class", "line") .attr("d", valueline(globalData)); } svg.select(".x.axis").call(xAxis); }); }
What I'm trying to do (I think that what I should do) gets a range of time values ββfor the new data (ie the difference between the first value and the last value of the newData [] array, convert this to pixels and then pan line using this number.
This seems to work, but only for the first update. Another problem is that if I try to update the data with the mouse, when I do the pan / zoom, the line disappears and does not necessarily return to the next update. I would really appreciate some feedback on potential errors that you might find in your code and / or how to do this. Thanks.
UPDATE 1:
Ok, so I realized what the problem is with auto panning. I realized that the translation vector should have a cumulative value from some source, so as soon as I made dx cumulative (dx = dx + (x(x2) - x(x1)); then the side panning started to work when new ones were added data.
UPDATE 2:
Now I have included FIDDLE , which is close to how I expect the data to be received and built. This seems to work to some degree the way I want, except:
- X-axis check marks are not panned by new data
- When I do a manual pan, the behavior gets a little weird on the first pan (bounces a little back)
- If I add pan / zoom when adding new data - the line disappears ("multithreading" :?)