2012-03-02 18 views
84

Prima domanda su Stack Overflow, quindi portami con me! Sono nuovo di d3.js, ma sono stato costantemente stupito da ciò che gli altri sono in grado di realizzare con esso ... e quasi sorpreso da quanto poco ho potuto fare con me stesso! Chiaramente non sto facendo qualcosa, quindi spero che le anime gentili qui possano mostrarmi la luce.Aggiunta di nuovi nodi al layout Force-direct

La mia intenzione è quella di fare una funzione javascript che fa semplicemente il seguente riutilizzabile:

  • Crea un grafico forza-diretto in bianco in un elemento DOM specificato
  • Consente di aggiungere ed eliminare l'etichetta, immagine- cuscinetto nodi a tale grafico, specificando connessioni tra loro

ho preso http://bl.ocks.org/950642 come punto di partenza, dato che è essenzialmente il tipo di configurazione voglio essere in grado di creare:

enter image description here

Ecco ciò che il mio codice è simile:

<!DOCTYPE html> 
<html> 
<head> 
    <script type="text/javascript" src="jquery.min.js"></script> 
    <script type="text/javascript" src="underscore-min.js"></script> 
    <script type="text/javascript" src="d3.v2.min.js"></script> 
    <style type="text/css"> 
     .link { stroke: #ccc; } 
     .nodetext { pointer-events: none; font: 10px sans-serif; } 
     body { width:100%; height:100%; margin:none; padding:none; } 
     #graph { width:500px;height:500px; border:3px solid black;border-radius:12px; margin:auto; } 
    </style> 
</head> 
<body> 
<div id="graph"></div> 
</body> 
<script type="text/javascript"> 

function myGraph(el) { 

    // Initialise the graph object 
    var graph = this.graph = { 
     "nodes":[{"name":"Cause"},{"name":"Effect"}], 
     "links":[{"source":0,"target":1}] 
    }; 

    // Add and remove elements on the graph object 
    this.addNode = function (name) { 
     graph["nodes"].push({"name":name}); 
     update(); 
    } 

    this.removeNode = function (name) { 
     graph["nodes"] = _.filter(graph["nodes"], function(node) {return (node["name"] != name)}); 
     graph["links"] = _.filter(graph["links"], function(link) {return ((link["source"]["name"] != name)&&(link["target"]["name"] != name))}); 
     update(); 
    } 

    var findNode = function (name) { 
     for (var i in graph["nodes"]) if (graph["nodes"][i]["name"] === name) return graph["nodes"][i]; 
    } 

    this.addLink = function (source, target) { 
     graph["links"].push({"source":findNode(source),"target":findNode(target)}); 
     update(); 
    } 

    // set up the D3 visualisation in the specified element 
    var w = $(el).innerWidth(), 
     h = $(el).innerHeight(); 

    var vis = d3.select(el).append("svg:svg") 
     .attr("width", w) 
     .attr("height", h); 

    var force = d3.layout.force() 
     .nodes(graph.nodes) 
     .links(graph.links) 
     .gravity(.05) 
     .distance(100) 
     .charge(-100) 
     .size([w, h]); 

    var update = function() { 

     var link = vis.selectAll("line.link") 
      .data(graph.links); 

     link.enter().insert("line") 
      .attr("class", "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; }); 

     link.exit().remove(); 

     var node = vis.selectAll("g.node") 
      .data(graph.nodes); 

     node.enter().append("g") 
      .attr("class", "node") 
      .call(force.drag); 

     node.append("image") 
      .attr("class", "circle") 
      .attr("xlink:href", "https://d3nwyuy0nl342s.cloudfront.net/images/icons/public.png") 
      .attr("x", "-8px") 
      .attr("y", "-8px") 
      .attr("width", "16px") 
      .attr("height", "16px"); 

     node.append("text") 
      .attr("class", "nodetext") 
      .attr("dx", 12) 
      .attr("dy", ".35em") 
      .text(function(d) { return d.name }); 

     node.exit().remove(); 

     force.on("tick", function() { 
      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("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); 
     }); 

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

    // Make it all go 
    update(); 
} 

graph = new myGraph("#graph"); 

// These are the sort of commands I want to be able to give the object. 
graph.addNode("A"); 
graph.addNode("B"); 
graph.addLink("A", "B"); 

</script> 
</html> 

Ogni volta aggiungo un nuovo nodo, ri-label tutti i nodi esistenti; questi si accumulano l'uno sopra l'altro e le cose cominciano a diventare brutte. Capisco perché questo è: perché quando chiamo la funzione funzione update() all'aggiunta di un nuovo nodo, fa un node.append(...) all'intero set di dati. Non riesco a capire come fare questo per solo il nodo che sto aggiungendo ... e posso solo apparentemente usare node.enter() per creare un singolo nuovo elemento, in modo che non funzioni per gli elementi aggiuntivi che ho bisogno di rilegare al nodo. Come posso risolvere questo?

Grazie per tutte le indicazioni che puoi fornire in merito a questo problema!

cura perché ho risolto rapidamente una fonte di molti altri bug che sono stati precedentemente menzionati

risposta

147

Dopo molte lunghe ore di essere in grado di ottenere questo lavoro, ho finalmente imbattuto in un demo che non credo sia legata qualsiasi della documentazione: http://bl.ocks.org/1095795:

enter image description here

Questa demo conteneva le chiavi che finalmente mi ha aiutato a rompere il problema.

L'aggiunta di più oggetti su un enter() può essere effettuata assegnando il enter() a una variabile e quindi aggiungendo a quello. Questo ha senso. La seconda parte critica è che gli array di nodi e di collegamenti devono essere basati sullo force(), altrimenti il ​​grafico e il modello andranno fuori sincrono man mano che i nodi vengono eliminati e aggiunti.

Questo perché se un nuovo array viene costruito invece, sarà privo seguente attributes:

  • index - l'indice a base zero del nodo all'interno della matrice nodi.
  • x - la coordinata x della posizione corrente del nodo.
  • y - la coordinata y della posizione corrente del nodo.
  • px - la coordinata x della posizione del nodo precedente.
  • py - la coordinata y della posizione del nodo precedente.
  • corretto: un valore booleano che indica se la posizione del nodo è bloccata.
  • peso - il peso del nodo; il numero di collegamenti associati.

Questi attributi non sono strettamente necessari per la chiamata al force.nodes(), ma se queste non sono presenti, essi sarebbero casualmente inizializzato dal force.start() alla prima chiamata.

Se qualcuno è curioso, il codice di lavoro si presenta così:

<script type="text/javascript"> 

function myGraph(el) { 

    // Add and remove elements on the graph object 
    this.addNode = function (id) { 
     nodes.push({"id":id}); 
     update(); 
    } 

    this.removeNode = function (id) { 
     var i = 0; 
     var n = findNode(id); 
     while (i < links.length) { 
      if ((links[i]['source'] === n)||(links[i]['target'] == n)) links.splice(i,1); 
      else i++; 
     } 
     var index = findNodeIndex(id); 
     if(index !== undefined) { 
      nodes.splice(index, 1); 
      update(); 
     } 
    } 

    this.addLink = function (sourceId, targetId) { 
     var sourceNode = findNode(sourceId); 
     var targetNode = findNode(targetId); 

     if((sourceNode !== undefined) && (targetNode !== undefined)) { 
      links.push({"source": sourceNode, "target": targetNode}); 
      update(); 
     } 
    } 

    var findNode = function (id) { 
     for (var i=0; i < nodes.length; i++) { 
      if (nodes[i].id === id) 
       return nodes[i] 
     }; 
    } 

    var findNodeIndex = function (id) { 
     for (var i=0; i < nodes.length; i++) { 
      if (nodes[i].id === id) 
       return i 
     }; 
    } 

    // set up the D3 visualisation in the specified element 
    var w = $(el).innerWidth(), 
     h = $(el).innerHeight(); 

    var vis = this.vis = d3.select(el).append("svg:svg") 
     .attr("width", w) 
     .attr("height", h); 

    var force = d3.layout.force() 
     .gravity(.05) 
     .distance(100) 
     .charge(-100) 
     .size([w, h]); 

    var nodes = force.nodes(), 
     links = force.links(); 

    var update = function() { 

     var link = vis.selectAll("line.link") 
      .data(links, function(d) { return d.source.id + "-" + d.target.id; }); 

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

     link.exit().remove(); 

     var node = vis.selectAll("g.node") 
      .data(nodes, function(d) { return d.id;}); 

     var nodeEnter = node.enter().append("g") 
      .attr("class", "node") 
      .call(force.drag); 

     nodeEnter.append("image") 
      .attr("class", "circle") 
      .attr("xlink:href", "https://d3nwyuy0nl342s.cloudfront.net/images/icons/public.png") 
      .attr("x", "-8px") 
      .attr("y", "-8px") 
      .attr("width", "16px") 
      .attr("height", "16px"); 

     nodeEnter.append("text") 
      .attr("class", "nodetext") 
      .attr("dx", 12) 
      .attr("dy", ".35em") 
      .text(function(d) {return d.id}); 

     node.exit().remove(); 

     force.on("tick", function() { 
      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("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); 
     }); 

     // Restart the force layout. 
     force.start(); 
    } 

    // Make it all go 
    update(); 
} 

graph = new myGraph("#graph"); 

// You can do this from the console as much as you like... 
graph.addNode("Cause"); 
graph.addNode("Effect"); 
graph.addLink("Cause", "Effect"); 
graph.addNode("A"); 
graph.addNode("B"); 
graph.addLink("A", "B"); 

</script> 
+2

Sì, signore. Btw, questa è davvero una buona risposta! – Maziyar

+1

L'uso di 'force.start()' invece di 'force.resume()' quando si aggiungono nuovi dati è la chiave. Molte grazie! – Mouagip

+0

Questo è fantastico. Stai calmo se ha scalato automaticamente il livello di zoom (magari riducendo la carica finché tutto si adatta?), Quindi tutto si è adattato alle dimensioni della scatola che stava disegnando. –

Problemi correlati