为什么d3会更新整个数据

时间:2015-06-03 10:38:45

标签: javascript d3.js

我有一个svg元素,其中包含以这种方式创建的数据:

var chart = d3.select("#my-div").append("svg");
var chartData = [];
chartData.push([{x: 1, y: 3}, {x: 2, y: 5}]);
chartData.push([{x: 1, y: 2}, {x: 2, y: 3}]);

          .domain([1, 5]);
var lineFunc = d3.svg.line()
    .x(function (d) {
        return xRange(d.x);
    })
    .y(function (d) {
        return yRange(d.y);
    })
    .interpolate('linear');

chart.append('g').classed('lines', true).selectAll('path').data(chartData).enter()
    .append('path')
    .attr('d', function(d) {
        return lineFunc(d);
    })
    .attr('stroke', 'black')
    .attr('stroke-width', 1)
    .attr('fill', 'none');

之后我尝试更新我的数据并更新图表:

chartData[1].push({x: 5, y: 5});
chart.selectAll('g.lines').selectAll('path').data(chartData)
    .attr('d', function(d) {
        console.log('updating:');
        console.log(d);
        return lineFunc(d);
    })
    .attr('stroke', 'black')
    .attr('stroke-width', 1)
    .attr('fill', 'none');

但它会两次打印“更新”(对于这两个chartData元素),但我只更改了一个(chartData[1])。如何防止它不更新我没有改变的?我将拥有许多功能,因此当只有一个功能发生变化时更新所有功能将是无效的。

//编辑@mef的回答

我将数据更改为(我不介意更新整个chartData [X]数据,我只是想避免更新整个chartData):

chartData.push({key: 'A', data: [{x: 1, y: 3}, {x: 2, y: 5}]});
chartData.push({key: 'B', data: [{x: 1, y: 2}, {x: 2, y: 3}]});

然后在添加数据时我放了.data(chartData, function(d) {return d.key}),在更新时我做了同样的事情,但它仍然更新了两者。

我还尝试在更新数据时添加.data(chartData, function(d) {return 'A'}).data(chartData, function(d) {return 'B'}),并且只更新一个,但始终使用A键更新数据(此函数是否返回AB)。

所以整个代码看起来像这样:

var chart = d3.select("#my-div").append("svg");
var chartData = [];
chartData.push({key: 'A', data: [{x: 1, y: 3}, {x: 2, y: 5}]});
chartData.push({key: 'B', data: [{x: 1, y: 2}, {x: 2, y: 3}]});

var xRange = d3.scale.linear().range([50, 780]).domain([1, 5]);
var yRange = d3.scale.linear().range([380, 20]).domain([2, 9]);

var lineFunc = d3.svg.line()
    .x(function (d) {
        return xRange(d.x);
    })
    .y(function (d) {
        return yRange(d.y);
    })
    .interpolate('linear');

chart.append('g').classed('lines', true).selectAll('path')
    .data(chartData, function(d) {return d.key}).enter()
    .append('path')
    .attr('d', function(d) {
        return lineFunc(d.data);
    })
    .attr('stroke', 'black')
    .attr('stroke-width', 1)
    .attr('fill', 'none');

更新数据

chartData[1].data.push({x: 5, y: 5});
chart.selectAll('g.lines').selectAll('path')
    .data(chartData, function(d) {return d.key})
    .attr('d', function(d) {
        console.log('updating:');
        console.log(d);
        return lineFunc(d.data);
    })
    .attr('stroke', 'black')
    .attr('stroke-width', 1)
    .attr('fill', 'none');

2 个答案:

答案 0 :(得分:2)

好的,可以做到......

选项1 - 使用密钥

这是一种懒惰的方式......

策略

  1. 创建一个能够检测数据变化的关键功能 通过读取节点属性字符串并将其与在基准上调用的属性生成器函数结果进行比较来完成此操作。
  2. 检测d3数据绑定过程的阶段(节点上的键或数据键)并为每个使用不同的键:
    var k = Array.isArray(this) ? lineD(d, lineFunc) : d3.select(this).attr("d");

  3. 通过在"数据键"期间从虚拟节点写入和读回来对齐两个键值的格式。相。 (这是懒惰的部分!)

  4. 为更新保留单独的引用,退出并输入选择以解除其行为。
  5. 代码

    var chart = d3.select("#my-div").append("svg")
          .attr("height", 600)
          .attr("width", 900);
    var chartData = [];
    chartData.push([{x: 1, y: 3}, {x: 2, y: 5}]);
    chartData.push([{x: 1, y: 2}, {x: 2, y: 3}]);
    
    var xRange = d3.scale.linear().range([50, 780]).domain([1, 5]);
    var yRange = d3.scale.linear().range([380, 20]).domain([2, 9]);
    
    var lineFunc = d3.svg.line()
        .x(function (d) {
          return xRange(d.x);
        })
        .y(function (d) {
          return yRange(d.y);
        })
        .interpolate('linear');
    
    chart.append('g').classed('lines', true).selectAll('path')
        .data(chartData, key)
        .enter().append('path')
        .attr('d', function(d) {
          return lineFunc(d);
        })
        .attr('stroke', 'black')
        .attr('stroke-width', 1)
        .attr('fill', 'none');
    //updating data
    
    chartData[1].push({x: 5, y: 5});
    
    var update = chart.selectAll('g.lines').selectAll('path')
          .data(chartData, key);
    update.enter().append('path')
          .attr('d', function (d) {
            console.log('updating:');
            console.log(d);
            return lineFunc(d);
          })
          .attr('stroke', 'black')
          .attr('stroke-width', 1)
          .attr('fill', 'none');
    update.exit().remove();
    
    function key(d, i, j) {
      var k = Array.isArray(this) ? lineAttr(d, lineFunc, "d") : d3.select(this).attr("d");
      console.log((Array.isArray(this) ? "data\t" : "node\t") + k)
      return k;
    
      function lineAttr(d, lineFunct, attribute) {
        var l = d3.select("svg").selectAll("g")
          .append("path").style("display", "none")
          .attr(attribute, lineFunct(d))
        d = l.attr(attribute);
        l.remove();
        return d;
      }
    }
    

    输出

    node    M50,328.57142857142856L232.5,225.71428571428572          
    node    M50,380L232.5,328.57142857142856                         
    data    M50,328.57142857142856L232.5,225.71428571428572          
    data    M50,380L232.5,328.57142857142856L780,225.71428571428572
    
    updating:                                                      
    Array[3]0: Object1: Object2: Objectlength: 3__proto__: Array[0]
    

    选项2 - 使用过滤器

    这样效率更高,但只有当你知道只有线上的点数会改变并且线数固定时才适用。

    策略

    1. 在没有功能的情况下加入数据并通过比较对其进行过滤 从绑定数据计算的属性字符串,到DOM元素中的当前属性字符串。
    2. 与选项1中一样,使用虚拟节点作为惰性(和跨浏览器)方式来对齐节点属性和计算属性文本的格式。
    3. 代码

      //updating data
      
      chartData[1].push({x: 5, y: 5});
      
      chart.selectAll('g.lines').selectAll('path')
        .data(chartData)
        .filter(changed)
        .attr('d', function (d) {
          console.log('updating:');
          console.log(d);
          return lineFunc(d);
        })
        .attr('stroke', 'black')
        .attr('stroke-width', 1)
        .attr('fill', 'none');
      
      function changed(d) {
        var s = d3.select(this);
        console.log("data\t" + lineAttr(s.datum(), lineFunc, "d"));
        console.log("node\t" + s.attr("d")); console.log("\n")
        return lineAttr(s.datum(), lineFunc, "d") != s.attr("d");
      
        function lineAttr(d, lineFunct, attribute) {
          var l = d3.select("svg").selectAll("g")
            .append("path").style("display", "none")
            .attr(attribute, lineFunct(d))
          d = l.attr(attribute);
          l.remove();
          return d;
        }
      }
      

      输出

      data    M50,328.57142857142856L232.5,225.71428571428572
      node    M50,328.57142857142856L232.5,225.71428571428572
      
      data    M50,380L232.5,328.57142857142856L780,225.71428571428572
      node    M50,380L232.5,328.57142857142856
      
      updating:
      Array[3]
      

      选项3 - 两全其美

      策略

      1. 使用标准更新/进入/退出模式。
      2. 过滤更新选择以形成"已更改"在操作之前进行选择。
      3. 代码

        //updating data
        
        alert("base");
        chartData[1].push({ x: 5, y: 5 });
        updateViz();
        alert("change");
        chartData.push([{x: 3, y: 1}, {x: 5, y: 2}])
        updateViz();
        alert("enter");
        chartData.shift();
        updateViz();
        alert("exit");
        
        function updateViz() {
          var update = chart.selectAll('g.lines').selectAll('path')
              .data(chartData),
        
              enter = update.enter()
                .append('path')
                .attr('d', function (d) {
                  return lineFunc(d);
                })
                .attr('stroke', 'black')
                .attr('stroke-width', 1)
                .attr('fill', 'none'),
        
              changed = update.filter(changed)
                .attr('d', function (d) {
                  console.log('updating:');
                  console.log(d);
                  return lineFunc(d);
                });
        
          update.exit().remove();
        
          function changed(d) {
            var s = d3.select(this);
            console.log("data\t" + lineAttr(s.datum(), lineFunc, "d"));
            console.log("node\t" + s.attr("d")); console.log("\n")
            return lineAttr(s.datum(), lineFunc, "d") != s.attr("d");
        
            function lineAttr(d, lineFunct, attribute) {
              var l = d3.select("svg").selectAll("g")
                .append("path").style("display", "none")
                .attr(attribute, lineFunct(d))
              d = l.attr(attribute);
              l.remove();
              return d;
            }
          }
        }
        

        背景

        阅读this

答案 1 :(得分:1)

您应该使用key function,以便允许d3查明记录是否已更改,并在更新数据集时进行相应匹配。

目前,您的数据元素是 javascript对象,并且d3不检查它们是否与先前版本相比已经更改(这将是棘手的)。

理想情况下,您应该为记录找到唯一标识符,并将其包含在数据集中。

然后您将替换.data(chartData)

.data(chartData, function(d) { return d.id })

如果您的数据集中没有属性可以用作唯一记录标识符,您仍然可以执行以下操作:

.data(chartData, function(d) {
    return d.map(function(coord) {
        return coord.x + '-' + coord.y
    }).join('-')
}

此处的关键是对象的所有坐标值的连接。