Primera pregunta sobre Stack Overflow, ¡así que tengan paciencia conmigo! Soy nuevo en d3.js, pero siempre me ha sorprendido lo que otros pueden lograr con él ... ¡y casi igual de asombrado por los pocos avances que he podido hacer con él! Claramente no estoy asimilando algo, así que espero que las almas amables aquí puedan mostrarme la luz.
Mi intención es crear una función de JavaScript reutilizable que simplemente haga lo siguiente:
- Crea un gráfico dirigido a la fuerza en blanco en un elemento DOM especificado
- Le permite agregar y eliminar nodos etiquetados que portan imágenes a ese gráfico, especificando conexiones entre ellos
He tomado http://bl.ocks.org/950642 como punto de partida, ya que ese 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 apilan uno encima del otro y las cosas comienzan a ponerse feas. Entiendo por qué es esto: porque cuando llamo a la update()
función función al agregar un nuevo nodo, hace un node.append(...)
a todo el conjunto de datos. No puedo averiguar cómo hacer esto solo para el nodo que estoy agregando ... y aparentemente solo puedo usarlo node.enter()
para crear un solo elemento nuevo, por lo que eso no funciona para los elementos adicionales que necesito vinculados al nodo . ¿Cómo puedo arreglar esto?
¡Gracias por cualquier orientación que pueda brindar sobre este tema!
Editado porque arreglé rápidamente una fuente de varios otros errores que se mencionaron anteriormente
force.start()
lugar deforce.resume()
cuando se agregan nuevos datos fue la clave. ¡Muchas gracias!