使用d3.js保存和重新加载力布局

Dea*_*ean 15 d3.js force-layout

我试图找到正确的方法,以便能够保存一个力图的节点布局位置一旦确定,然后,以重新加载该布局,并从相同的稳定状态再次开始.

我试图通过克隆包含图表的DOM元素,删除它然后重新加载它来做到这一点.

我可以这样做,部分如下: -

_clone = $('#chart').clone(true,true);
$('#chart').remove();
Run Code Online (Sandbox Code Playgroud)

选择包含div,克隆它并将其删除,然后再添加

var _target = $('#p1content');
_target.append(_clone);
Run Code Online (Sandbox Code Playgroud)

选择div用于保存图表并重新加载的图表.重新加载的图表是固定的.

我不知道如何重新连接力以允许操纵继续进行.这可能吗?我想保留节点的固定位置.

另一种可能性,我可以重新加载节点位置并以低alpha启动力吗?

<!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>
Run Code Online (Sandbox Code Playgroud)

添加了这个我放在哪里//需要什么才能回收力量?
这似乎可以恢复已恢复的现有元素,并将Force从中断处通过强制节点等重新耦合到Timeout函数中

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; });

});
Run Code Online (Sandbox Code Playgroud)

one*_*ion 6

编辑:现在完整的解决方案!

此外,这种方法将适用于各种场景——既可以在单个页面上停止和重新启动布局,也可以在不同页面上保存和重新加载布局。

首先,在布局过程结束时保存原始 JSON 图,您可以使用以下命令进行监听:

force.on('tick', function(){
    ...
}).on('end', function(){
    // Run this when the layout has finished!
});
Run Code Online (Sandbox Code Playgroud)

现在保存很有价值,因为在布局期间,d3 已将 x、y 坐标(以及其他一些东西)添加到每个节点和边(但要不断更改,直到停止)。作为 JSON,该图很容易序列化,粘贴在 localStorage 中,拉出并再次解析:

localStorage.setItem(JSON.stringify(graph));
...
JSON.parse(localStorage.getItem('graph'));
Run Code Online (Sandbox Code Playgroud)

但是,一旦您将其从存储中取出,您不仅想要一个 JSON 对象,您还想将该保存的对象重新转换为 svg,并且理想情况下,为了简单起见,使用 d3.layout.force 已有的设备。事实上,您可以做到这一点——只需稍作改动

如果您将保存的图形重新粘贴回来,即只需运行

force
  .nodes(graph.nodes)
  .links(graph.links)
  .start();
Run Code Online (Sandbox Code Playgroud)

使用保存的图形,你会得到两个奇怪的行为。

奇怪的行为 1 和解决方案

根据良好的文档,在起始图中包含 x 和 y 坐标会覆盖布局过程的随机初始化——但仅覆盖初始化。所以你会得到节点应该在的地方,但是随着布局的变化,它们会漂浮到一个均匀分布的圆圈中。为了防止这种情况发生,请使用:

  for(n in graph.nodes){
    graph.nodes[n].fixed = 1
  }
Run Code Online (Sandbox Code Playgroud)

跑步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));

});
Run Code Online (Sandbox Code Playgroud)

然后,之前force.start()——

force.linkDistance(function (d) { return d.length })
Run Code Online (Sandbox Code Playgroud)

(可以在此处找到相关文档),最后,您的图表将看起来像它应该的样子。

总之,如果你要确保你的JSON图1)具有x,y坐标的节点上,2)具有设置为节点fixed=1,和3)force已linkDistance设置之前.start(),那么你可以运行完全相同的布局过程中,如果你是从头开始初始化,您将取回保存的图形。


小智 0

所以,除非我误读了内容:

https://github.com/mbostock/d3/wiki/Force-Layout#wiki-nodes

强制布局实际上将使用传递给节点/边缘函数的值指定的节点和边缘信息来初始化(或重新初始化,如果您再次调用恢复/启动)布局。

我通过使用您的图表进行了测试,然后在布局结束时恢复强制布局。它不会重新计算节点/边缘位置,因为它们已经保留在最初传入的数据集上。您还可以通过将 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);
});
Run Code Online (Sandbox Code Playgroud)