D3如何改变检测工作?

时间:2017-05-10 10:48:39

标签: d3.js

如果我有一个JSON对象数组。 D3如何确定enter()集合中的哪些内容?

如果我有一个对象数组,如下:

var data = [
  {label:'a', value:1},
  {label:'b', value:3},
  {label:'c', value:2}
]

然后我将它绑定到选择:

var bars = vis.selectAll("rect.bar")
  .data(data)

enter集现在包含所有这些数据。如果我现在更改数据:

var data = [
  {label:'a', value:99},
  {label:'c', value:2}
  {label:'b', value:3},
]

D3如何判断哪些值是更新,移动还是新的?它是在对象身份上完成的吗?有没有办法让它使用标签字段来填充输入集?

1 个答案:

答案 0 :(得分:6)

如果我正确理解您的问题,您想知道D3如何将给定对象与给定DOM元素相关联。

默认情况下,如果您没有设置关键功能(下面有更多内容),则对象会按其顺序关联。根据API:

  

...数据中的第一个数据分配给第一个选定元素,第二个数据分配给第二个选定元素,依此类推。

让我们在下面的例子中看到这一点。第一个数组是:

var data = [{
    label: 'a',
    value: 1
}, {
    label: 'b',
    value: 3
}, {
    label: 'c',
    value: 2
}];

第二个数组也有3个对象。但请注意更改:第一个是标签c,第三个是标签a。他们被交换了:

var data2 = [{
    label: 'c',
    value: 99
}, {
    label: 'b',
    value: 2
}, {
    label: 'a',
    value: 3
}];

因此,第一个数据中相对于a的条形将在第二个数据中获得c的值。查看演示,单击按钮:



var svg = d3.select("svg");

var data = [{
  label: 'a',
  value: 1
}, {
  label: 'b',
  value: 3
}, {
  label: 'c',
  value: 2
}];

var data2 = [{
  label: 'c',
  value: 99
}, {
  label: 'b',
  value: 2
}, {
  label: 'a',
  value: 3
}];

var xScale = d3.scaleLinear()
  .domain([0, d3.max(data, d => d.value)])
  .range([0, 260]);

var yScale = d3.scaleBand()
  .domain(data.map(d => d.label))
  .range([10, 140])
  .padding(0.3);

var rects = svg.selectAll("foo")
  .data(data)
  .enter()
  .append("rect");

rects.attr("x", 40)
  .attr("y", d => yScale(d.label))
  .attr("height", yScale.bandwidth)
  .attr("width", d => xScale(d.value))
  .attr("fill", "teal");
  
d3.axisLeft(yScale)(svg.append("g").attr("transform", "translate(40,0)"))

d3.select("button").on("click", function() {

  xScale.domain([0, d3.max(data2, d => d.value)]);

  rects.data(data2);

  rects.transition()
    .duration(1000)
    .attr("width", d => xScale(d.value))

})

<script src="https://d3js.org/d3.v4.min.js"></script>
<button>Click</button>
<br>
<svg></svg>
&#13;
&#13;
&#13;

如您所见,单击按钮后的图表显然不正确。

为避免这种情况,确保先前绑定到标签a的DOM元素在数据数组更改时仍将绑定到该标签a,我们必须使用键函数:< / p>

  

可以指定一个键函数来控制将哪个数据分配给哪个元素,替换默认的join-by-index。为每个选定的元素评估此关键函数,按顺序传递当前数据(d),当前索引(i)和当前组(节点),将其作为当前DOM元素(nodes [i]) 。然后还为数据中的每个新数据评估关键函数,传递当前数据(d),当前索引(i)和组的新数据,并将其作为组的父DOM元素。给定键的数据被分配给具有匹配键的元素。如果多个元素具有相同的键,则将重复的元素放入出口选择中;如果多个数据具有相同的密钥,则将重复数据放入回车选择中。

在您的情况下,这将是关键功能:

.data(data, d => d.label)
//key-------^

检查完全相同的代码,但使用关键功能:

&#13;
&#13;
var svg = d3.select("svg");

var data = [{
  label: 'a',
  value: 1
}, {
  label: 'b',
  value: 3
}, {
  label: 'c',
  value: 2
}];

var data2 = [{
  label: 'c',
  value: 99
}, {
  label: 'b',
  value: 2
}, {
  label: 'a',
  value: 3
}];

var xScale = d3.scaleLinear()
  .domain([0, d3.max(data, d => d.value)])
  .range([0, 260]);

var yScale = d3.scaleBand()
  .domain(data.map(d => d.label))
  .range([10, 140])
  .padding(0.3);

var rects = svg.selectAll("foo")
  .data(data, d => d.label)
  .enter()
  .append("rect");

rects.attr("x", 40)
  .attr("y", d => yScale(d.label))
  .attr("height", yScale.bandwidth)
  .attr("width", d => xScale(d.value))
  .attr("fill", "teal");
  
d3.axisLeft(yScale)(svg.append("g").attr("transform", "translate(40,0)"))

d3.select("button").on("click", function() {

  xScale.domain([0, d3.max(data2, d => d.value)]);

  rects.data(data2, d => d.label);

  rects.transition()
    .duration(1000)
    .attr("width", d => xScale(d.value))

})
&#13;
<script src="https://d3js.org/d3.v4.min.js"></script>
<button>Click</button>
<br>
<svg></svg>
&#13;
&#13;
&#13;

你可以看到,尽管新数据数组中对象的顺序不同(c是第三个,现在c是第一个) ,这并不重要,因为D3考虑到label属性将数据绑定到每个DOM元素。