将新链接和节点数据绑定到强制布局和svg元素后,旧节点和链接将冻结。 enter()
选择的新节点和链接也不会连接到现有节点。
为什么会出现此问题?
我浏览了各种类似的问题,但没有人给出令人满意的为什么回答。请注意,我还仔细阅读了经常引用的"思考加入",输入/更新/退出选择文章。但是,有些东西没有点击这里。
start(graph);
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
window.setTimeout(function(){
graph.nodes.push({"name":"Westby","group":2})
graph.links.push({"source":5,"target":2,"value":1})
start(graph);
}, 2000);
function start(graph){
force
.nodes(graph.nodes)
.links(graph.links)
.start();
link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
node = svg.selectAll(".node")
.data(graph.nodes)
.call(force.drag)
.enter().append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function(d) { return color(d.group); })
.call(force.drag);
node.append("title")
.text(function(d) { return d.name; });
}
答案 0 :(得分:2)
您需要在修改布局后运行force.start()
。布局的所有配置都在force.start()
中完成。
nodes
和links
都不需要重新绑定。
我还将结构更改为一种模式,为您提供最大程度的控制和灵活性 使用此模式,您可以分别管理更新,输入和退出组件。
最后一个调整是使用
link.enter().insert("line", "circle.node")
而不是
link.enter().append("line")
这是为了确保链接在圆圈后面呈现。
force
//you only need to do this once///////////
.nodes(graph.nodes)
.links(graph.links)
//////////////////////////////////////////
.on("tick", function () {
link.attr("x1", function (d) { return d.source.x; })
.attr("y1", function (d) { return d.source.y; })
.attr("x2", function (d) { return d.target.x; })
.attr("y2", function (d) { return d.target.y; });
node.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; });
});
start(graph);
window.setTimeout(function () {
graph.nodes.push({ "name": "Westby", "group": 2 })
graph.links.push({ "source": 5, "target": 2, "value": 1 })
start(graph);
}, 2000);
function start(graph) {
//UPDATE pre-existing nodes to be re-cycled
link = svg.selectAll(".link")
.data(graph.links);
//ENTER new nodes to be created
link.enter().insert("line", "circle.node") //insert before node!
.attr("class", "link")
//UPDATE+ENTER .enter also merges update and enter, link is now both
link.style("stroke-width", function (d) { return Math.sqrt(d.value); });
//EXIT
link.exit().remove()
//UPDATE
node = svg.selectAll(".node")
.data(graph.nodes)
//ENTER
node.enter().append("circle")
.attr("class", "node")
.attr("r", 5)
.call(force.drag);
//UPDATE+ENTER .enter also merges update and enter, link is now both
node.style("fill", function (d) { return color(d.group); })
//EXIT
node.exit().remove();
node.append("title")
.text(function (d) { return d.name; });
force.start();
}
d3.layout.force
维护引用对nodes
和links
数组的闭包,因此您只需将布局绑定到数组引用一次。 />
正如您在阅读代码时所看到的那样......
d3.layout.force = function () {
var force = {},
//...
nodes = [], links = [], distances, strengths, charges;
//...
force.nodes = function (x) {
if (!arguments.length) return nodes;
nodes = x;
return force;
};
force.links = function (x) {
if (!arguments.length) return links;
links = x;
return force;
};
//...
};
所以我们有
force().nodes(nodesData);
force().links(linksData);
force().nodes() === nodesData // true
force().links() === linksData // true
此外,由于数据绑定在d3中的工作方式,选择结构中的每个DOM节点都引用了它的相应数据数组的一个元素。这存储在d3添加到DOM节点的__data__
成员上。
由于数组元素通常是复杂的对象,
__data__ === nodesData[i] // true
用于选择的第i个成员,并绑定数据。
selection.datum()
方法返回所选节点的__data__
成员的值(或选择中的第一个非空节点),该值是对数据数组元素的引用。这当然意味着对数据数组元素成员的任何修改都会自动反映在选择的数据绑定和引用节点的__data__
成员的任何内容中。
此时值得注意的是,
返回的对象update = selection.data(values)
是一个 new 数组,所以我们有
update.data() === values // false
update.data()[i] === values[i] // true
节点和链接 数据 存储为对象数组。数组元素的成员是通知可视化 - 节点或链接的类型,它们的标签,分组信息等所需的信息。
通过引用数据 强制布局 绑定到数据阵列:
force().nodes(nodesData);
force().links(linksData);
force().nodes() === nodesData // true
force().links() === linksData // true
选择 通过引用数据数组元素绑定到数据:
nodes = selection.data(nodesData); links.enter().append(nodeSelector)
links = selection.data(linksData); links.enter().append(linkSelector)
nodes === nodesData //false - nodes is a selection, nodesData is an array
nodes.data() === nodesData //false - nodes.data() returns a new array
nodes.data()[i] === nodesData[i] //true! - the elements of the data array are coppied to the new array that is returned by the selection
//similar for links
力布局和选择引用相同的数据(以不同的方式!),但 不引用彼此 。
部队布局的 角色 是使用 管理动画事件 (框架)&#39 ;每隔tick
计算节点位置的内部动态设置。每当发生 数据结构事件 时,需要通过调用force.start()
来通知强制布局(如果您想知道原因,请获取d3源和RTFC)。
选择的 角色 是通过将数据绑定到 来管理DOM元素 并提供API用于根据数据管理它们。每当发生 数据结构事件 时,需要通过更新/输入/追加周期通知选择。
在强制布局API提供的tick事件中,选择用于根据强制布局计算的新数据来驱动可视化(DOM元素)。
如果您使用浏览器开发人员工具查看force.nodes()返回的数组元素,您会看到在原始成员之上添加了很多状态,还有状态已关闭d3.force对象,如距离,强度和费用。所有这一切都必须在某个地方设置,而且不出意外,它已在force.start()
中完成。这就是为什么每次更改数据结构时都必须调用force.start()
的原因。
一般来说,这是最具防御性的模式......
//UPDATE
var update = baseSelection.selectAll(elementSelector)
.data(values, key),
//ENTER
enter = update.enter().append(appendElement)
.call(initStuff),
//enter() has side effect of adding enter nodes to the update selection
//so anything you do to update now will include the enter nodes
//UPDATE+ENTER
updateEnter = update
.call(stuffToDoEveryTimeTheDataChanges);
//EXIT
exit = update.exit().remove()
第一次通过update
将是一个与数据结构相同的空数组
在这种情况下,.selectAll()
返回零长度选择,并没有任何用处。
在后续更新中,.selectAll
不会为空,并将使用values
与keys
进行比较,以确定哪些节点正在更新,进入和退出节点。这就是为什么在数据加入之前需要选择的原因。
要理解的重要一点是它必须是.enter().append(...)
,因此您要在输入选择上附加元素。如果将它们附加到更新选择(数据连接返回的那个)上,那么您将重新输入相同的元素并查看与您获得的相似的行为。
输入选择是{ __data__: data }
形式的简单对象数组
更新和退出选择是对DOM元素的引用数组的数组。
d3中的数据方法对进入和退出选择保持关闭,这些选择由.enter()
上的.exit()
和update
方法访问。两者都返回对象,其中包括二维数组(d3中的所有选择都是组的数组,其中组是节点数组。)。
enter
成员也被赋予update
的引用,以便它可以合并这两者。之所以这样做,是因为在大多数情况下,对两个群体都做了同样的事情。