2017-09-28 6 views
1

Szenario:D3 v3 Kraft-Layout - anmutig hinzufügen/entfernen Knoten ohne Refresh

ich mit einem Standard-D3 v3 Kraft-Layout gestartet , die ich online von einem Beispiel nahm.

Ich möchte dies verbessern und mein Ziel ist:

  1. Wenn Skript lädt eine Funktion aufrufen Graph mit einigen Daten
  2. Klicken Sie auf eine Schaltfläche, um zu initialisieren und machen Sie die Grafik auf der Seite zu aktualisieren, hinzuzufügen/entfernen Knoten und Verbindungen anmutig - also ohne eine komplette mit Knoten ‚fliegenden‘-re ziehen in

ein Beispiel für die Art von Verhalten, das ich wünsche ist th. fantastische grafische Darstellung ist, wo die Schwelle Schieber ‚Pops‘ Links Ziehen in/out, ohne eine vollständige Wieder machen, so ist es einfach, zu sehen, was hinzugefügt/entfernt wurde: http://jsfiddle.net/simonraper/TdHgx/?utm_source=website&utm_medium=embed&utm_campaign=TdHgx

Problem:

  • I bin nicht sicher, wie Punkt Nummer 2 oben erreicht werden kann.
  • Ich kann mein Diagramm mit einem vollständigen Neuzeichnen nicht aktualisieren
  • Ich bin sicher, dass die Antwort etwas damit zu tun hat, wie ich das allgemeine Update-Muster D3 verwende. Hier

ist ein Beispiel dafür, was ich bisher: https://jsfiddle.net/thedev19/uvqosxrr/3/

-Code für jsfiddle:


html:

<body> 

    <button id="update-button1">Update Data - Remove</button> 
    <button id="update-button2">Update Data - Add</button> 

</body> 

js:

var width = 400, 
    height = 500; 

var force = d3.layout.force() 
    .size([width, height]) 
    .charge(-400) 
    .linkDistance(40) 
    .on("tick", tick); 

var drag = force.drag() 
    .on("dragstart", dragstart); 

//Set up the force layout 
var svg = d3.select("body").append("svg") 
    .attr("width", width) 
    .attr("height", height); 

var link = svg.selectAll(".link"), 
    node = svg.selectAll(".node"); 

    //we call this function when we first draw graph 
    var drawInit = function(graph){ 

    link = link.data(graph.links, function(d) { return d.id; }) 
     .enter().append("line") 
     .attr("class", "link"); 

    node = node.data(graph.nodes, function(d) { return d.id; }) 
     .enter().append("circle") 
     .attr("class", "node") 
     .attr("r", 12) 
     .on("dblclick", dblclick) 
     .call(drag); 

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

    //call this function whenever we want to update the graph 
var update = function(graph){ 

    link = link.data(graph.links, function(d) { return d.id; }); 

    link.exit().remove(); 

    link 
     .enter().append("line") 
     .attr("class", "link"); 

    node = node.data(graph.nodes, function(d) { return d.id; }); 

    //Remove nodes not in new data set 
    node.exit().remove(); 

    //For each datum in dataset that wasn't in old dataset append 
     circle 
    node.enter().append("circle") 
     .attr("class", "node") 
     .attr("r", 12) 
     .on("dblclick", dblclick) 
     .call(drag); 

    //Update the force layout graph 
    force 
     .nodes(graph.nodes) 
     .links(graph.links) 
     .start(); 
}; 

function tick() { 
    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; }); 
} 

function dblclick(d) { 
    console.log("double clicked on " + d.name); 
    d3.select(this).classed("fixed", d.fixed = false); 
} 

function dragstart(d) { 
    d3.select(this).classed("fixed", d.fixed = true); 
} 

//data1 is used for our initial drawing 
data1 = { 
    "nodes": [ 
    { 
     "id":0, 
     "name": 0, 
     "group": 1, 
     "size": 10 
    }, 
    { 
     "id":1, 
     "name": 1, 
     "group": 1, 
     "size": 10 
    }, 
    { 
     "id":2, 
     "name": 2, 
     "group": 1, 
     "size": 20 
    }, 
    { 
     "id":3, 
     "name": 3, 
     "group": 1, 
     "size": 30 
    }, 
    { 
     "id":4, 
     "name": 4, 
     "group": 1, 
     "size": 25 
    } 
], 
    "links": [ 
    { 
     "source": 1, "target": 0, "value":1, "id":0 
    }, 
    { 
     "source": 1, "target": 2, "value":1, "id":1 
    }, 
    { 
     "source": 1, "target": 3, "value":1, "id":2 
    }, 
    { 
     "source": 1, "target": 4, "value":1, "id":3 
    } 
] 

}; 

drawInit(data1); 

//When user clicks on button update force layout graph *gracefully* 
d3.select("#update-button1").on("click", function(e) { 

    var data2 = { 
     "nodes": [ 
      { 
       "id": 0, 
       "name": 0, 
       "group": 1, 
       "size": 10 
      }, 
      { 
       "id": 1, 
       "name": 1, 
       "group": 1, 
       "size": 10 
      }, 
      { 
       "id": 2, 
       "name": 2, 
       "group": 1, 
       "size": 20 
      }, 
      { 
       "id": 3, 
       "name": 3, 
       "group": 1, 
       "size": 30 
      } 
     ], 
     "links": [ 
      { 
       "source": 1, "target": 0, "value": 1, "id": 0 
      }, 
      { 
       "source": 1, "target": 2, "value": 1, "id": 1 
      }, 
      { 
       "source": 1, "target": 3, "value": 1, "id": 2 
      } 
     ] 
    }; 

     update(data2); 
}); 

//When user clicks on button update force layout graph *gracefully* 
d3.select("#update-button2").on("click", function(e) { 

    //this simulates removing a node 
    var data3 = { 
     "nodes": [ 
      { 
       "id": 0, 
       "name": 0, 
       "group": 1, 
       "size": 10 
      }, 
      { 
       "id": 1, 
       "name": 1, 
       "group": 1, 
       "size": 10 
      }, 
      { 
       "id": 2, 
       "name": 2, 
       "group": 1, 
       "size": 20 
      }, 
      { 
       "id": 3, 
       "name": 3, 
       "group": 1, 
       "size": 30 
      }, 
      { 
       "id": 4, 
       "name": 4, 
       "group": 1, 
       "size": 30 
      }, 
      { 
       "id": 5, 
       "name": 5, 
       "group": 1, 
       "size": 30 
      } 
     ], 
     "links": [ 
      { 
       "source": 1, "target": 0, "value": 1, "id": 0 
      }, 
      { 
       "source": 1, "target": 2, "value": 1, "id": 1 
      }, 
      { 
       "source": 1, "target": 3, "value": 1, "id": 2 
      }, 
      { 
       "source": 1, "target": 4, "value": 1, "id": 3 
      }, 
      { 
       "source": 1, "target": 5, "value": 1, "id": 4 
      } 
     ] 
    }; 

    update(data3); 
}); 

Antwort

0

fand ich die Lösung auf meine Frage. Im Wesentlichen ‚graziös‘ Aktualisierung muss man mutieren die Daten zunächst auf die Kraft Layout-Objekt „Knoten“/„Links“ Eigenschaften statt Neuzuweisung und Überschreiben sie zugewiesen zu erreichen. Hier

ist ein Beispiel, das ich erstellt: https://jsfiddle.net/thedev19/z3rwpcxp/27/

Man könnte noch Daten von einer externen Quelle laden, die hatten einige Knoten/Links hinzugefügt/entfernt und dann vergleichen die neu zur Verfügung gestellten Daten mit den aktuellen Daten zu dem zugewiesene Kraft Layout „Knoten“ und „Links“ Eigenschaften Knoten ‚/‚Links‘Daten zunächst auf die Kraft Layout Diagramm in seinen Eigenschaften zugewiesen“. Dann Knoten/Verbindungen in geeigneter Weise zu mutieren hinzufügen/entfernen‘.

-Code für jsfiddle:

<body> 

<p>Click and drag nodes to 'stick' them to a desired location</p> 
<p>Click button to see how we can 'gracefully' update the graph <br> 
    and add data without completely re-loading</p> 

    <button id="update1">Update - Click to add nodes</button> 
</body> 

js:

//Variables to set up SVG container 
var width = 400, 
    height = 350; 

//We initialise the force layout object here 
var force = d3.layout.force() 
    .size([width, height]) 
    .charge(-400) 
    .linkDistance(40) 
    .on("tick", tick); 

var svg = d3.select("body").append("svg") 
    .attr("width", width) 
    .attr("height", height); 

//Create a "g" container elements and create selections to reference throughout 
var link = svg.append("g").selectAll(".link"); 
var node = svg.append("g").selectAll(".node"); 

var update = function(){ 

    //Create an UPDATE selection by joining data with "link" element selection 
    link = link.data(graphData.links, function(d){ return d.id}); 

    //Access ENTER selection (hangs off UPDATE selection) 
    //This represents newly added data that dont have DOM elements 
    //so we create and add a "line" element for each of these data 
    link 
     .enter().append("line") 
     .attr("class", "link"); 

    //Access EXIT selection (hangs off UPDATE selection) 
    //This represents DOM elements for which there is now no corresponding data element 
    //so we remove these from DOM 
    link 
     .exit().remove(); 

    //same update pattern for nodes 
    node = node.data(graphData.nodes); 

    node. 
    enter().append("circle") 
     .attr("class", "node") 
     .attr("r", 12) 
     .on("dblclick", dblclick) 
     .call(drag); 

    node.exit().remove(); 

    force 
     .start(); 

}; 

function tick() { 
    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; }); 
} 

function dblclick(d) { 
    d3.select(this).classed("fixed", d.fixed = false); 
} 

function dragstart(d) { 
    d3.select(this).classed("fixed", d.fixed = true); 
} 

function init(){ 

    var data1 = { 
     "nodes": [ 
      { 
       "id":0, 
       "name": 0, 
       "group": 1, 
       "size": 10 
      }, 
      { 
       "id":1, 
       "name": 1, 
       "group": 1, 
       "size": 10 
      }, 
      { 
       "id":2, 
       "name": 2, 
       "group": 1, 
       "size": 20 
      }, 
      { 
       "id":3, 
       "name": 3, 
       "group": 1, 
       "size": 30 
      } 
     ], 
     "links": [ 
      { 
       "source": 0, "target": 1, "value":1, "id":0 
      }, 
      { 
       "source": 0, "target": 2, "value":1, "id":1 
      }, 
      { 
       "source": 0, "target": 3, "value":1, "id":2 
      } 
     ] 

    }; 

    graphData = data1; 
    drag = force.drag() 
     .on("dragstart", dragstart); 

    force.links(graphData.links); 
    force.nodes(graphData.nodes); 

    update() 
} 

init(); 

d3.select("#update1").on("click", function() { 

    //randomly select a (currently existing) node that our new node will link to 
    var sourceNodeId = Math.floor(Math.random() * (graphData.nodes.length-1)); 

    //if there are n nodes currently (before we add a new one, below) then 
    //the new target node will be the (n+1)th node with an id of n (zero-indexing) 
    var newNodeId = graphData.nodes.length; 

    // if there are currently n links (before we add a new one, below) then 
    // the new link will have an id of n (first link has an id of 0) 
    var linkId = graphData.links.length; 

    graphData.links.push({ 
     "source": sourceNodeId , "target": newNodeId, "value": 1, "id": linkId 
    }); 

    graphData.nodes.push({ 
     "id": newNodeId, 
     "name": newNodeId, 
     "group": 1, 
     "size": 30 
    }); 

    update() 

}); 

css:

.link { 
    stroke: #000; 
    stroke-width: 1.5px; 
} 

.node { 
    cursor: move; 
    fill: #ccc; 
    stroke: #000; 
    stroke-width: 1.5px; 
} 

.node.fixed { 
    fill: #f00; 
}