I have a d3 graph that leverages the Force Layout. When I have a group of nodes up front it lays out just fine. Specifically I mean the nodes stay nicely separated and the links are correct.
A demo of my issue is on jsFiddle here.
I've also included a code snippet below that appears to work like the jsFiddle.
However, if I start with one node and then add another down the line with the Add Person button, you'll notice the first node (even though it's referenced in the link) does not respond, nor can it be moved.
It seems like the latter is the real issue, in that it can't be moved.
What I've Tried
- Executing a
force.resume()instead of theforce...start()at the top when adding a new person to the graph. OUTCOME: this causes an error,Error: Invalid value for <g> attribute transform="translate(NaN,NaN)"to occur and I've not yet figured out why. - Adding a
force.resume()at the end of the method, and not adding theon('tick' ...again when adding a new person to the graph. I couple this by executing theforce...start()at the top regardless of theresume. OUTCOME: that causes the first node to bounce around again (promising), but the added node stays in the top left corner as if it's not connected.
var scope = {};
scope.nodes = [];
scope.links = [];
var width = 960,
height = 500;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.charge(-150)
.linkDistance(150)
.size([width, height]);
function renderGraph(resume) {
force.nodes(scope.nodes)
.links(scope.links)
.start();
var link = svg.selectAll(".link")
.data(scope.links)
.enter().append("line")
.attr("class", "link");
var node = svg.selectAll(".node")
.data(scope.nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("image")
.attr("xlink:href", function (d) {
return d.avatar || 'https://github.com/favicon.ico'
})
.attr("x", -56)
.attr("y", -8)
.attr("width", 64)
.attr("height", 64);
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function (d) {
return d._id === scope.user.profile._id ? 'You' : d.firstName + ' ' + d.lastName
});
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 + ")";
});
});
}
scope.user = {
profile: {
_id: 1,
firstName: 'Bob',
lastName: 'Smith'
}
};
scope.nodes.push(scope.user.profile);
renderGraph();
var b = document.getElementById("addButton");
b.onclick = addPerson;
function addPerson() {
scope.nodes.push({
_id: 2,
firstName: 'Jane',
lastName: 'Smith'
});
scope.links.push({
source: 0,
target: scope.nodes.length - 1
});
renderGraph();
}
.link {
stroke: #ccc;
}
.node text {
pointer-events: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<button id="addButton">Add Person</button>