如果我正确理解你的问题,你想知道 D3 如何将给定对象关联到给定 DOM 元素。
默认情况下,如果您没有设置关键功能(更多内容请参见下文),对象将按其顺序关联。根据API:
...data 中的第一个数据分配给第一个选定的元素,第二个数据分配给第二个选定的元素,依此类推。
让我们在下面的示例中看看这一点。第一个数组是这样的:
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>
正如您所看到的,单击按钮后的图表显然是不正确的。
为了避免这种情况,请确保 DOM 元素之前绑定到标签a
仍将绑定到该标签a
当数据数组发生变化时,我们必须使用一个关键函数:
可以指定一个关键函数来控制将哪个数据分配给哪个元素,以替换默认的按索引连接。此关键函数针对每个选定元素按顺序进行评估,传递当前数据 (d)、当前索引 (i) 和当前组(节点),并将其作为当前 DOM 元素 (nodes[i]) 。然后,还会针对 data 中的每个新数据评估 key 函数,传递当前数据 (d)、当前索引 (i) 和组的新数据,并将其作为组的父 DOM 元素。给定键的数据被分配给具有匹配键的元素。如果多个元素有相同的key,则将重复的元素放入退出选择中;如果多个数据具有相同的键,则将重复的数据放入输入选择中。
在你的情况下,这将是关键功能:
.data(data, d => d.label)
//key-------^
检查完全相同的代码,但具有关键功能:
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))
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<button>Click</button>
<br>
<svg></svg>
你可以看到,尽管order新数据数组中不同的对象的数量(c
是第三个,现在c
是第一个),这并不重要,因为 D3 将数据绑定到每个 DOM 元素,同时考虑到label
财产。