15

フォース ダイアグラム ノード レイアウトの位置が確定したら保存し、後でそのレイアウトをリロードして、同じ確定状態からやり直すことができる正しい方法を見つけようとしています。

ダイアグラムを含む 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>

これを追加しました // 力を取り戻すためにここに行く必要があるのは何ですか?
これは、復元された既存の要素を拾い上げるように機能しているようであり、強制ノードなどを 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; });

});
4

3 に答える 3

6

編集:今すぐ完全な解決策を!

さらに、このアプローチはさまざまなシナリオで機能します。つまり、1 つのページでレイアウトを停止して再開する場合と、別のページでレイアウトを保存して再ロードする場合の両方です。

まず、レイアウト プロセスの最後に元の JSON グラフを保存します。これを使用してリッスンできます。

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

レイアウト中に d3 によって x、y 座標 (およびその他の要素) が各ノードとエッジに追加されているため (ただし、停止するまで変更し続けます)、保存することは重要です。JSON であるため、グラフは簡単にシリアル化でき、localStorage に貼り付け、取り出して再度解析できます。

localStorage.setItem(JSON.stringify(graph));
...
JSON.parse(localStorage.getItem('graph'));

ただし、ストレージから取り出したら、JSON オブジェクトだけではなく、保存したオブジェクトを svg に戻し、理想的には単純化のために d3.layout.force で既に利用可能な装置を使用します。実際、いくつかの小さな変更を加えることで、これを行うことができます。

保存したグラフをすぐに貼り付ける場合、つまり実行するだけです

force
  .nodes(graph.nodes)
  .links(graph.links)
  .start();

保存されたグラフを使用すると、 2 つの奇妙な動作が発生します。

奇妙な動作 1、および解決策

優れたドキュメントに基づいて、開始グラフに x 座標と y 座標を含めると、レイアウト プロセスのランダムな初期化が上書きされますが、初期化のみが行われます。したがって、ノードがあるべき場所に配置されますが、レイアウトが刻々と変化するにつれて、ノードは均一に分散された円に浮き上がります。これが起こらないようにするには、次を使用します。

  for(n in graph.nodes){
    graph.nodes[n].fixed = 1
  }

実行する前にforce.start()

奇妙な動作 2、および解決策 これで、ノードとエッジの両方が目的の場所に配置されますが、エッジは縮小しますか?

似たようなことが起こっていますが、残念ながら、まったく同じ解決策を使用することはできません。エッジの長さは JSON オブジェクトに保存され、レイアウトの初期化に使用されましたが、最初にエッジの長さを JSON グラフに保存しない限り、レイアウトはそれらすべてにデフォルトの長さ (20) を課します --

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

(ドキュメンテーションはここにあります) そして最終的に、グラフは想定どおりに表示されます。

要約すると、JSON グラフの 1) ノードに x、y 座標があり、2) に設定されたノードがfixed=1あり、3) のforce前に linkDistance が設定されていることを確認すると、ちょうど同じレイアウト プロセス.start()を実行できます。最初から初期化すると、保存したグラフが返されます。

于 2015-12-27T18:43:57.030 に答える
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);
});
于 2014-02-06T19:46:51.773 に答える
0

力のレイアウトはその結果をデータ自体に保存することに注意してください。そうすれば、ティック ハンドラー関数内で、データがバインドされたビジュアル ノードとエッジを調整するときにアクセスできます。

すべての力と制約を考慮して計算を行うと、力レイアウトは に提供されたノードの配列に含まれるノードに結果を格納しforce.nodes()ます。各ティックの終わりに、すべての計算が完了すると、dataset.nodes配列は新しい位置、速度などを含む各ノードで更新され、フォース レイアウトの現在の状態を表します。

ただし、レイアウトの完全な状態をキャプチャできるようにするために欠けていることが 1 つあります。それは、現在の の値ですalpha

と の両方を保存するdatasetalpha、任意の方法で、これらのプロパティがキャプチャされた時点の状態に強制レイアウトを後で復元できます。必要に応じて、これらのプロパティへのローカル参照を保持したり、JSON.stringify()何らかの形で永続化できるようにするなど、かなり揮発性のストレージを使用する場合があります。

独自のコードの場合、これは次のように実行できます。

  1. 最初のタイムアウトへのコールバックで行われるように、DOM から SVG を完全に削除する必要がある場合は、SVG とノードとエッジを追加するコードを関数に入れると便利です。これは、2 回呼び出す必要があるためです。 .

    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、レイアウトの状態を含むことへの参照がまだあります。しかし、 の API ドキュメントによるとforce.nodes([nodes])、パラメータとして指定されたノードに存在する値は、まったく新しいレイアウトを設定するときにも採用されます。force.alpha(alpha)その後、保存された値に設定することで、その実行を再開できます。強制レイアウトを再開する前に、SVG が への別の呼び出しによって再構築されることに注意してくださいinitChart()

    // 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);
<!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>

于 2015-12-27T22:50:34.480 に答える