比较/差异新数据与d3.js更新的先前数据

时间:2014-05-01 14:01:00

标签: javascript d3.js

我想表示当前数据集与之前数据集之间的差异,由客户端计算。

想象一下,我已经有三个圈子,绑定到数据[1, 2, 3]。现在我想更新数据并根据新值和旧值之间的差异做些什么?

var new_data = [2, 2, 2]; // This is the new data I'd like to compare with the old

svg.selectAll("circle").data(new_data)
    .transition().duration(2000)
.attr("fill", "red") // e.g. I'd like to colour the circles red if the change
                     // is negative, blue if positive, black if no change.
.attr("r", function(d) { return d * 10; });

Here's a JSFiddle将上面的代码设置为一个示例。

2 个答案:

答案 0 :(得分:13)

您有两种方法可以保存附加到元素的旧数据,以便在新数据加入后识别更改。

如您所建议的,第一个选项是使用数据属性。 This SO Q&A describes that approach。需要考虑的事项:

  • 您的所有数据值都将被强制转换为字符串
  • 您需要为数据的每个方面单独的方法调用/属性
  • 你正在操纵DOM,所以如果你有很多元素或每个数据很多,它可能会减慢速度。
  • 数据现在是DOM的一部分,因此可以与图像一起保存或由其他脚本访问

第二个选项是将数据存储为元素的DOM对象的Javascript属性,方法与d3 stores the active data as the __data__ property相同。我在this forum post中讨论过这种方法。

一般方法:

selection = selection.property(" __oldData__", function(d){ return d; } ); 
                        //store the old data as a property of the node
                    .data(newData, dataKeyFunction);  
                        //over-write the default data property with new data
                        //and store the new data-joined selection in your variable

selection.enter() /*etc*/;  

selection.attr("fill",  function(d) {
                 // Within any d3 callback function,
                 // you can now compare `d` (the new data object)
                 // with `this.__oldData__` (the old data object).
                 // Just remember to check whether `this.__oldData__` exists
                 // to account for the just-entered elements.

                if (this.__oldData__) { //old data exists

                  var dif = d.value - this.__oldData__.value; 
                  return (dif) ? //is dif non-zero?
                         ( (dif > 0)? "blue" : "red" ) :
                         "black" ; 
                } else {
                  return "green"; //value for new data
                }

            });

selection.property("__oldData__", null); 
          //delete the old data once it's no longer needed
          //(not required, but a good idea if it's using up a lot of memory)

你当然可以使用旧数据属性的任何名称,只是惯例会抛出很多" _"它周围的字符,以避免弄乱任何浏览器的本机DOM属性。

答案 1 :(得分:9)

从D3 v4 开始,您可以使用local variables的内置支持。内部实现与AmeliaBR的answer建议基本相同,但它使您无需自己存储旧数据。使用d3.local()时,您可以set作用于特定DOM节点的值,因此名称为 local 变量。在下面的代码段中,这是通过行

为每个圆圈完成的
.each(function(d) { previousData.set(this, d) });  // Store previous data locally...

您稍后可以retrieve为其存储的任何特定节点提供该值:

.attr("fill", function(d) {
  var diff = previousData.get(this) - d;  // Retrieve previously stored data.
  return diff  < 0 ? "red" : diff > 0 ? "blue" : "black";
}) 

此完整代码可能如下所示:

var old_data = [1, 2, 3]; // When the data gets updated I'd like to 'remember' these values

// Create a local variable for storing previous data.
var previousData = d3.local();

var svg = d3.select("body").append("svg")
    .attr("width", 500)
    .attr("height", 200);

var p = d3.select("body")
  .append("p")
    .text("Old data. Click on the circles to update the data.");

var circle = svg.selectAll("circle")
  .data(old_data)
  .enter().append("circle")
    .attr("fill", "black")
    .attr("r", function(d) { return d * 10; })
    .attr("cx", function(d){ return d * 40; })
    .attr("cy", function(d){ return d * 40; })
    .each(function(d) { previousData.set(this, d) });  // Store previous data locally on each node

svg.on("click", function(d) {
  p.text("Updated data.");
  var new_data = [2, 2, 2]; // This is the new data I'd like to compare with the old
    
  circle.data(new_data)
    .transition().duration(2000)
      .attr("fill", function(d) {
        var diff = previousData.get(this) - d;  // Retrieve previously stored data.
        return diff  < 0 ? "red" : diff > 0 ? "blue" : "black";
      }) 
      .attr("r", function(d) { return d * 10; });
});
<script src="https://d3js.org/d3.v4.js"></script>