nodos falconmasters curso básico javascript d3.js force-layout

javascript - falconmasters - jquery falcon master



Agregar nuevos nodos al diseño dirigido por Force (1)

Después de muchas horas de no poder hacer que funcionase, finalmente encontré una demo que no creo que esté vinculada a ninguna de la documentación: http://bl.ocks.org/1095795 :

Esta demo contenía las claves que finalmente me ayudaron a descifrar el problema.

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

Esto se debe a que si se construye una nueva matriz en su lugar, carecerá de los siguientes attributes :

  • índice: el índice basado en 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.
  • fixed - un 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 la llamada a force.nodes() , pero si no están presentes, se inicializarán aleatoriamente mediante force.start() en la primera llamada.

Si alguien tiene curiosidad, el código de trabajo se ve así:

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

Primera pregunta sobre Stack Overflow, así que ¡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.

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

  • Crea un gráfico dirigido por fuerza en blanco en un elemento DOM especificado
  • Le permite agregar y eliminar nodos etiquetados que llevan imágenes a ese gráfico, especificando las conexiones entre ellos

Tomé http://bl.ocks.org/950642 como punto de partida, ya que es esencialmente el tipo de diseño que quiero poder crear:

Así es como se ve mi código:

<!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 agrego un nuevo nodo, vuelve a etiquetar 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 de función update() al agregar un nuevo nodo, hace un node.append(...) a todo el conjunto de datos. No puedo entender cómo hacer esto solo para el nodo que estoy agregando ... y al parecer solo puedo usar node.enter() para crear un único elemento nuevo, así que eso no funciona para los elementos adicionales que Necesito ligado al nodo. ¿Cómo puedo arreglar esto?

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

Editado porque arreglé rápidamente una fuente de varios otros errores que fueron mencionados anteriormente