D3.JS 具有实时数据、平移和缩放的时间序列折线图

Posted

技术标签:

【中文标题】D3.JS 具有实时数据、平移和缩放的时间序列折线图【英文标题】:D3.JS time-series line chart with real-time data, panning and zooming 【发布时间】:2014-07-13 16:07:40 【问题描述】:

FIDDLE

我正在尝试在 d3 中创建一个实时(实时更新)时间序列图表,该图表也可以平移(在 X 中)和缩放。理想情况下,我想要的功能是,如果用户可以看到线的最右边部分,那么当将新数据添加到图表时,它将自动平移以包含新数据(不改变轴比例)。

我的 d3.json() 请求应该返回如下所示的 JSON 数组:

["timestamp":1399325270,"value":-0.0029460209892230222598710528,"timestamp":1399325271,"value":-0.0029460209892230222598710528,"timestamp":1399325279,"value":-0.0029460209892230222598710528,....]

当页面首次加载时,我提出请求并获取到现在为止的所有可用日期,然后绘制图表 - 很简单。以下代码执行此操作,它还允许平移(在 X 中)和缩放。

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];


在下面的代码块中,我发出一个 http 请求以获取所有新数据并将其添加到图表中。这工作正常。现在我只需要整理出新数据的平移逻辑——我想它会放在这里:

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);

        );

我正在尝试做的(我想这是我应该做的)是获取新数据的时间值范围(即 newData[] 数组的第一个值和最后一个值之间的差异,将其转换为像素,然后使用此数字平移线条。

这似乎有点工作,但仅限于第一次更新。另一个问题是,如果我在尝试更新数据时使用鼠标进行任何平移/缩放,则该行会消失,并且不一定会在下一次更新时返回。对于您可以在代码中发现的潜在错误和/或应该如何完成,我真的很感激一些反馈。谢谢。

更新 1:

好的,所以我已经弄清楚了自动平移的问题所在。我意识到平移向量需要具有来自某个来源的累积值,因此一旦我使 dx 累积 (dx = dx + (x(x2) - x(x1)); 之后,横向平移开始在添加新数据时起作用。

更新 2:

我现在包含了一个fiddle,它接近于我期望检索和绘制数据的实际方式。它似乎在某种程度上按我想要的方式工作,除了:

    X 轴刻度线不随新数据平移 当我进行手动平移时,第一次平移时的行为变得有点奇怪(向后跳了一点) 如果我在尝试添加新数据时进行平移/缩放 - 行会消失(“多线程”问题?:S)

【问题讨论】:

这是一个相当复杂的问题。没有小提琴,我想不可能有人可以帮助你。顺便说一句,你看过 NVD3 吗?也许它有一些开箱即用的东西...... 谢谢伙计,是的,我有,但我希望能够自定义更多内容并学习使用 D3。我希望至少能得到部分解决方案;例如,如何将新数据添加到绘图或线条(无论外观如何)等。 我能理解你,我也有同样的情况。 D3 很棒,但很难调试。最好使用版本控制 (Git) 并在推进时提交所有更改和小步骤,并在出现问题时提交回滚。很小很小的步骤……理解所有复制和粘贴的代码很重要。每个可视化都有自己的面孔。 你认为我应该每次都重画整行吗? :// 理论上,如果有密钥,就不需要重绘相同的数据。缩放、平移时……它只是改变大小和位置,也许它被剪掉了。 【参考方案1】:

您可以保留一个包含所有值的全局数组,而不是绘制整个数据并剪裁不必要的部分,每次您需要更新图表时只需对这些值进行切片。 然后您可以更轻松地重新计算您的 x 轴和值。缺点是你不能轻易过渡。

因此,您将有两个变量:

var globalOffset = 0;
var panOffset = 0;

globalOffset 将在您每次填充新值时更新,panOffset 将在您每次平移时更新。 然后,您只需在绘图之前对数据进行切片:

var offset = Math.max(0, globalOffset + panOffset);
var graphData = globalData.slice(offset, offset + 100);

查看fiddle获取完整解决方案。

【讨论】:

在 Firefox 和 Chrome 上都试过了,桌面和左右拖动图形都没有问题,当然只要有数据。 在 Chrome 上这对我来说真的很奇怪,线的右端锚定在右边缘,图形线向左延伸而不是平移:/

以上是关于D3.JS 具有实时数据、平移和缩放的时间序列折线图的主要内容,如果未能解决你的问题,请参考以下文章

在 D3.js 中缩放或平移时限制域

使用canvas和d3.js分层圆结构计算缩放和平移的方法

D3.js 缩放和平移可折叠树图

d3js中的缩放线

如何暂时禁用 d3.js 中的缩放

D3.JS“缩放”未定义