2012-03-02 15 views
84

Primera pregunta sobre el desbordamiento de pila, ¡tengan paciencia! Soy nuevo en d3.js, pero me he sentido constantemente asombrado por lo que otros pueden lograr ... ¡y casi me asombra lo poco que he podido hacer con él! Claramente, no estoy asimilando algo, así que espero que las almas bondadosas aquí puedan mostrarme la luz.Agregar nuevos nodos al diseño dirigido a la fuerza

Mi intención es hacer una función de JavaScript, que simplemente hace lo siguiente reutilizable:

  • Crea un gráfico de fuerza dirigida en blanco en un elemento DOM especificado
  • Le permite añadir y eliminar la etiqueta, imagen- teniendo nodos para ese gráfico, especificando las conexiones entre ellas

que he tomado http://bl.ocks.org/950642 como punto de partida, ya que es esencialmente el tipo de diseño que quiero ser capaz de crear:

enter image description here

Esto es lo que se ve mi código como:

<!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> 

Cada vez que añadir un nuevo nodo, se re-etiquetas todos los nodos existentes; estos se amontonan uno encima del otro y las cosas empiezan a ponerse feas. Entiendo por qué esto es así: porque cuando llamo a la función update() cuando agrego un nuevo nodo, hace un node.append(...) a todo el conjunto de datos. No puedo entender cómo hacer esto para solo el nodo que estoy agregando ... y aparentemente solo puedo usar node.enter() para crear un único elemento nuevo, así que eso no funciona para los elementos adicionales que necesito enlazar al nodo. ¿Cómo puedo arreglar esto?

¡Gracias por cualquier orientación que pueda dar sobre este tema!

Editado porque rápidamente me fijo una fuente de varios otros insectos que anteriormente se mencionaron

Respuesta

147

Después de muchas horas de ser incapaz de conseguir este trabajo, que finalmente se topó con una demostración de que no creo que es vinculada cualquiera de la documentación: http://bl.ocks.org/1095795:

enter image description here

Esta demo contiene las claves que finalmente me ayudó a descifrar el problema.

La adición de múltiples objetos en un enter() se puede hacer asignando el enter() a una variable y luego anexándolo. Esto tiene sentido. La segunda parte crítica es que el nodo y las matrices de enlace deben basarse en el force(); de lo contrario, el gráfico y el modelo se desincronizarán a medida que se eliminen y añadan los nodos.

Esto es porque si una nueva matriz se construye en su lugar, carecerá de la siguiente attributes:

  • índice - el índice de base cero del nodo dentro de la matriz de nodos.
  • x - la coordenada x de la posición del nodo actual.
  • y - la coordenada y de la posición del nodo actual.
  • px - la coordenada x de la posición del nodo anterior.
  • py - la coordenada y de la posición del nodo anterior.
  • fijo - un valor booleano que indica si la posición del nodo está bloqueada.
  • peso: el peso del nodo; la cantidad de enlaces asociados.

Estos atributos no son estrictamente necesarios para que la llamada force.nodes(), pero si estos no están presentes, entonces serían azar inicializa force.start() en la primera llamada.

Si alguien tiene curiosidad, el código de trabajo se parece a esto:

<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í señor. Por cierto, esta es una muy buena respuesta! – Maziyar

+1

El uso de 'force.start()' en lugar de 'force.resume()' cuando se agregan nuevos datos fue la clave. ¡Muchas gracias! – Mouagip

+0

Esto es asombroso. Sea genial si escala automáticamente el nivel de zoom (¿tal vez reduciendo la carga hasta que todo encaja?) Para que todo encajara en el tamaño de la caja en la que estaba dibujando. –

Cuestiones relacionadas