我正在尝试找到正确的方法,以便能够在确定之后保存一个力图节点布局位置,然后再重新加载该布局并从相同的已建立状态重新开始。
我试图通过克隆包含图表的DOM元素,删除它然后重新加载它来做到这一点。
我可以这样做,部分如下所示: -
_clone = $('#chart').clone(true,true);
$('#chart').remove();
选择包含div,克隆并删除它,然后再
var _target = $('#p1content');
_target.append(_clone);
选择用于保存图表的div
并重新加载。重新加载的图表是固定的。
我不知道如何重新连接力以允许操纵继续进行。这可能吗?我想保留节点的固定位置。
另一种可能性,我可以重新加载节点位置并以低α启动力吗?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>D3: Force layout</title>
<script src="./jquery-2.0.3.min.js" type="text/javascript"></script>
<script type="text/javascript" src="../d3.v3.js"></script>
<style type="text/css">
/* No style rules here yet */
</style>
</head>
<body>
<div data-role="content" id="p1content">
<div id="chart"></div>
</div>
<script type="text/javascript">
//Width and height
var w = 800;
var h = 600;
//Original data
var dataset = {
nodes: [
{ name: "Adam" },
{ name: "Bob" },
{ name: "Carrie" },
{ name: "Donovan" },
{ name: "Edward" },
{ name: "Felicity" },
{ name: "George" },
{ name: "Hannah" },
{ name: "Iris" },
{ name: "Jerry" }
],
edges: [
{ source: 0, target: 1 },
{ source: 0, target: 2 },
{ source: 0, target: 3 },
{ source: 0, target: 4 },
{ source: 1, target: 5 },
{ source: 2, target: 5 },
{ source: 2, target: 5 },
{ source: 3, target: 4 },
{ source: 5, target: 8 },
{ source: 5, target: 9 },
{ source: 6, target: 7 },
{ source: 7, target: 8 },
{ source: 8, target: 9 }
]
};
//Initialize a default force layout, using the nodes and edges in dataset
var force = d3.layout.force()
.nodes(dataset.nodes)
.links(dataset.edges)
.size([w, h])
.linkDistance([100])
.charge([-100])
.start();
var colors = d3.scale.category10();
//Create SVG element
var svg = d3.select("#chart")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create edges as lines
var edges = svg.selectAll("line")
.data(dataset.edges)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 1);
//Create nodes as circles
var nodes = svg.selectAll("circle")
.data(dataset.nodes)
.enter()
.append("circle")
.attr("r", 10)
.style("fill", function(d, i) {
return colors(i);
})
.call(force.drag);
//Every time the simulation "ticks", this will be called
force.on("tick", function() {
edges.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; });
nodes.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
// After 5 secs clone and remove DOM elements
setTimeout(function() {
_clone = $('#chart').clone(true,true);
$('#chart').remove();
}, 5000);
//After 10 secs reload DOM
setTimeout(function() {
var _target = $('#p1content');
_target.append(_clone);
// WHAT NEEDS TO GO HERE TO RECOUPLE THE FORCE?
}, 10000);
</script>
</body>
</html>
在我放置的地方添加了//需要什么才能回收力量?
这似乎可以恢复已恢复的现有元素,并将强制节点等通过强制节点等的力量重新连接到超时功能
force = d3.layout.force()
.nodes(dataset.nodes)
.links(dataset.edges)
.size([w, h])
.linkDistance([100])
.charge([-100])
.start();
colors = d3.scale.category10();
//Create SVG element
svg = d3.select("#chart");
//Create edges as lines
edges = svg.selectAll("line")
.data(dataset.edges);
//Create nodes as circles
nodes = svg.selectAll("circle")
.data(dataset.nodes)
.call(force.drag);
//Every time the simulation "ticks", this will be called
force.on("tick", function() {
edges.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; });
nodes.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
答案 0 :(得分:3)
编辑:现在全面解决方案!
此外,此方法适用于各种场景 - 在单个页面上停止和重新启动布局,以及在不同页面上保存和重新加载布局
首先,将原始JSON图保存在布局过程的 end 中,您可以使用它来监听:
force.on('tick', function(){
...
}).on('end', function(){
// Run this when the layout has finished!
});
现在保存很有价值,因为在布局期间,x,y坐标(以及其他一些东西)已经被d3添加到每个节点和边缘(但是一直在变化,直到停止为止)。作为JSON,图表很容易序列化,坚持使用localStorage,再次拔出并解析:
localStorage.setItem(JSON.stringify(graph));
...
localStorage.getItem(JSON.parse('graph'));
一旦你将它从存储中拉出来,你不仅仅想要一个JSON对象,你想把这个保存的对象变回一个svg,理想情况下,使用d3已经提供的设备.layout.force简单起见。事实上,你可以做到这一点 - 只需做一些小改动。
如果您将保存的图表重新插入,即只需运行
force
.nodes(graph.nodes)
.links(graph.links)
.start();
使用已保存的图表,您将获得两种奇怪的行为。
奇怪的行为1和解决方案
基于good documentation,在起始图中包含x和y坐标会覆盖布局过程的随机初始化 - 但只包括初始化。因此,您将获得应该所在的节点,但随着布局的顺序,它们将浮动到均匀分布的圆圈中。要防止这种情况发生,请使用:
for(n in graph.nodes){
graph.nodes[n].fixed = 1
}
在运行force.start()
之前。
奇怪的行为2和解决方案 现在你的节点和边缘都将在你想要的位置,但是你的边缘会 - 收缩?
发生了类似情况,但不幸的是,您无法使用完全相同的解决方案。边长保存在JSON对象中,并在布局的初始化中使用,但随后布局对它们施加了默认长度(20),除非您首先保存边缘JSON图中的长度 -
.on('end', function() {
links = svg.selectAll(".link")[0]
for(i in graph.links){
graph.links[i].length = links[i].getAttribute('length')
}
localStorage.setItem('graph', JSON.stringify(graph));
});
然后,在force.start()
-
force.linkDistance(function (d) { return d.length })
(可以找到here的文档),最后,您的图表看起来应该是这样的。
总之,如果确保您的JSON图1)在节点上有x,y坐标,2)节点设置为fixed=1
,而3)force
在{{{}之前设置了linkDistance 1}},然后你可以运行完全相同的布局过程,就像从头开始初始化一样,然后你就会收回你保存的图形。
答案 1 :(得分:0)
所以,除非我误读了以下内容:
https://github.com/mbostock/d3/wiki/Force-Layout#wiki-nodes
强制布局实际上会初始化(或者如果再次调用resume / start则重新初始化)布局,其中包含在传递给节点/边缘函数的值上指定的节点和边缘信息。
我通过使用您的图表然后在布局结束时测试了这一点,恢复了力布局。它不会重新计算节点/边缘位置,因为它们已经保留在数据集上 最初传入的。您还可以通过将x / y值添加到初始数据来测试它。
http://jsfiddle.net/jugglebird/Brb29/1/
force.on("end", function() {
// At this point dataset.nodes will include layout information
console.log("resuming");
force.resume(); // equivalent to force.alpha(.1);
});
答案 2 :(得分:0)
至关重要的是要记住,力布局将其结果存储在数据本身中。这样,当在tick处理程序函数内调整数据绑定的可视节点和边时,可以访问它们。
在进行计算时考虑所有力和约束,力布局会将结果存储到提供给force.nodes()
的节点数组中包含的节点中。在每个刻度结束时,当所有计算完成后,您的dataset.nodes
数组将更新为包含新位置,速度等的每个节点,从而表示力布局的当前状态。
然而,有一件事情缺少能够捕获布局的完整状态,即其当前值alpha
。
通过您喜欢的任何方式保存dataset
和alpha
,稍后您将能够将力布局恢复到捕获这些属性时的状态。根据您的需要,您可能会使用相当不稳定的存储,例如保留对这些属性的本地引用,或者JSON.stringify()
它们甚至能够以某种方式保留它们。
对于您自己的代码,可以按如下方式完成:
如果你需要像回调到第一次超时一样从DOM中完全删除SVG,那么将附加SVG以及节点和边的代码放入函数是很方便的,因为你需要两次打电话。
function initChart() {
svg = d3.select("#chart")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create edges as lines
edges = svg.selectAll("line")
.data(dataset.edges)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 1);
//Create nodes as circles
nodes = svg.selectAll("circle")
.data(dataset.nodes)
.enter()
.append("circle")
.attr("r", 10)
.style("fill", function(d, i) {
return colors(i);
})
.call(force.drag);
}
initChart(); // Append the SVG with nodes and edges.
但是,如果仅将其设置为display:none
就足够了,因为您可以保持所有引用的完整性。
要完全保存布局状态,您需要存储当前值alpha
。之后,您调用force.stop()
实际上立即停止力布局。请注意,您的dataset
已经设置了最新值。
var alpha; // This will save alpha when stopped.
// Stop and remove after 1 second.
setTimeout(function() {
alpha = force.alpha(); // Save alpha.
force.stop(); // Stop the force.
svg.remove(); // Dump the SVG.
}, 1000);
您可以随时将力布局恢复到已保存状态。在您的示例中,force
引用的强制布局未被销毁,因此它仍然包含对包含布局状态的dataset
的引用。但是根据force.nodes([nodes])
的API文档,在设置全新布局时,也会采用作为参数提供的节点上的值。然后,您可以通过将force.alpha(alpha)
设置为保存的值来恢复执行。请注意,在重新启动强制布局之前,通过另一次调用initChart()
来重建SVG。
// Restore to paused state and restart.
setTimeout(function() {
initChart(); // Append the SVG with nodes and edges.
force.alpha(alpha); // Restart the force with alpha.
}, 3000);
查看演示的完整代码段。我缩短了超时以强调效果。
//Width and height
var w = 800;
var h = 600;
//Original data
var dataset = {
nodes: [
{ name: "Adam" },
{ name: "Bob" },
{ name: "Carrie" },
{ name: "Donovan" },
{ name: "Edward" },
{ name: "Felicity" },
{ name: "George" },
{ name: "Hannah" },
{ name: "Iris" },
{ name: "Jerry" }
],
edges: [
{ source: 0, target: 1 },
{ source: 0, target: 2 },
{ source: 0, target: 3 },
{ source: 0, target: 4 },
{ source: 1, target: 5 },
{ source: 2, target: 5 },
{ source: 2, target: 5 },
{ source: 3, target: 4 },
{ source: 5, target: 8 },
{ source: 5, target: 9 },
{ source: 6, target: 7 },
{ source: 7, target: 8 },
{ source: 8, target: 9 }
]
};
//Initialize a default force layout, using the nodes and edges in dataset
var force = d3.layout.force()
.nodes(dataset.nodes)
.links(dataset.edges)
.size([w, h])
.linkDistance([100])
.charge([-100])
.start()
.on("tick", function() {
edges.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; });
nodes.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
var colors = d3.scale.category10();
//Create SVG element
var svg,
edges,
nodes;
function initChart() {
svg = d3.select("#chart")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create edges as lines
edges = svg.selectAll("line")
.data(dataset.edges)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 1);
//Create nodes as circles
nodes = svg.selectAll("circle")
.data(dataset.nodes)
.enter()
.append("circle")
.attr("r", 10)
.style("fill", function(d, i) {
return colors(i);
})
.call(force.drag);
}
initChart(); // Append the SVG with nodes and edges.
var alpha; // This will save alpha when stopped.
// Stop and remove after 1 second.
setTimeout(function() {
alpha = force.alpha(); // Save alpha.
force.stop(); // Stop the force.
svg.remove(); // Dump the SVG.
}, 1000);
// Restore to paused state and restart.
setTimeout(function() {
initChart(); // Append the SVG with nodes and edges.
force.alpha(alpha); // Restart the force with alpha.
}, 3000);
&#13;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>D3: Force layout</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<style type="text/css">
/* No style rules here yet */
</style>
</head>
<body>
<div data-role="content" id="p1content">
<div id="chart"></div>
</div>
</body>
</html>
&#13;