Première question sur Stack Overflow, alors soyez indulgent avec moi ! Je suis nouveau dans le domaine de d3.js, mais j'ai été constamment étonné par ce que les autres sont capables d'accomplir avec lui... et presque aussi étonné par le peu de progrès que j'ai été capable de faire avec lui moi-même ! Il est clair que je n'ai pas compris quelque chose, alors j'espère que les bonnes âmes ici présentes pourront me montrer la lumière.
Mon intention est de créer une fonction javascript réutilisable qui fasse simplement ce qui suit :
- Crée un graphique vide dirigé par la force dans un élément DOM spécifié
- Permet d'ajouter et de supprimer des nœuds étiquetés et porteurs d'images dans ce graphique, en spécifiant les connexions entre eux.
J'ai pris http://bl.ocks.org/950642 comme point de départ, puisque c'est essentiellement le type de mise en page que je veux pouvoir créer :
Voici à quoi ressemble mon code :
<!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>
Chaque fois que j'ajoute un nouveau nœud, tous les nœuds existants sont réétiquetés ; ils s'empilent les uns sur les autres et les choses commencent à se gâter. Je comprends pourquoi : parce que lorsque j'appelle la fonction update()
lors de l'ajout d'un nouveau nœud, elle effectue un node.append(...)
à l'ensemble des données. Je n'arrive pas à trouver comment faire pour seulement le noeud que j'ajoute ... et je ne peux apparemment utiliser que node.enter()
pour créer un seul nouvel élément, ce qui ne fonctionne pas pour les éléments supplémentaires que je dois lier au nœud. Comment puis-je résoudre ce problème ?
Merci pour tout conseil que vous pourrez donner sur cette question !
Modifié parce que j'ai rapidement corrigé une source de plusieurs autres bugs qui ont été mentionnés précédemment