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

时间:2013-08-13 10:05:51

标签: d3.js force-layout

我正在尝试找到正确的方法,以便能够在确定之后保存一个力图节点布局位置,然后再重新加载该布局并从相同的已建立状态重新开始。

我试图通过克隆包含图表的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; });

});

3 个答案:

答案 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

通过您喜欢的任何方式保存datasetalpha,稍后您将能够将力布局恢复到捕获这些属性时的状态。根据您的需要,您可能会使用相当不稳定的存储,例如保留对这些属性的本地引用,或者JSON.stringify()它们甚至能够以某种方式保留它们。

对于您自己的代码,可以按如下方式完成:

  1. 如果你需要像回调到第一次超时一样从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就足够了,因为您可以保持所有引用的完整性。

  2. 要完全保存布局状态,您需要存储当前值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);
    
  3. 您可以随时将力布局恢复到已保存状态。在您的示例中,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);
    
  4. 查看演示的完整代码段。我缩短了超时以强调效果。

    &#13;
    &#13;
            //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;
    &#13;
    &#13;