Given the following layout:
<g>
... // many nodes
<g>
<circle></circle>
<text></text>
</g>
...
</g>
How would a correct update pattern look like in d3 v4? What do I have to use as parameter in merge(?), how often do I have to call merge (only on node? node + circle + text ?)
I created an working example on fiddle: /
Code:
function update(items) {
node = nodeLayer.selectAll(".node")
.data(items, function(d) { return d.id; })
node = node.enter() // insert
.append("g")
.attr("class", "node");
node.append("circle") // insert
.attr("r", 2.5)
.attr('class', 'circle')
.merge(nodeLayer.selectAll('.node > circle')) // is this correct?! // merge
.attr('fill', 'red') // just for testing purposes
.exit().remove(); // exit
node.append("text") // insert
.attr("dy", 3)
.text(function(d) { return d.name; })
.merge(nodeLayer.selectAll('.node > text')) // is this correct?! // merge
.attr('fill', 'green') // just for testing purposes
.exit().remove();
node.merge(nodeLayer.selectAll('.node')) // is this correct?! // merge
.attr('class', 'anotherClass')
.exit().remove(); // does not work // exit
}
Could someone bring some clarity in terms of how to use enter(), merge(), exit() in groups?
I potentially like to do changes in every stage for every element.
Update: I simplified the example, I don't need links or a force-layout. My question is only about the update-pattern, not about forces. The updated jsfiddle does not have the force-layout.
Given the following layout:
<g>
... // many nodes
<g>
<circle></circle>
<text></text>
</g>
...
</g>
How would a correct update pattern look like in d3 v4? What do I have to use as parameter in merge(?), how often do I have to call merge (only on node? node + circle + text ?)
I created an working example on fiddle: https://jsfiddle/cvvfsg97/6/
Code:
function update(items) {
node = nodeLayer.selectAll(".node")
.data(items, function(d) { return d.id; })
node = node.enter() // insert
.append("g")
.attr("class", "node");
node.append("circle") // insert
.attr("r", 2.5)
.attr('class', 'circle')
.merge(nodeLayer.selectAll('.node > circle')) // is this correct?! // merge
.attr('fill', 'red') // just for testing purposes
.exit().remove(); // exit
node.append("text") // insert
.attr("dy", 3)
.text(function(d) { return d.name; })
.merge(nodeLayer.selectAll('.node > text')) // is this correct?! // merge
.attr('fill', 'green') // just for testing purposes
.exit().remove();
node.merge(nodeLayer.selectAll('.node')) // is this correct?! // merge
.attr('class', 'anotherClass')
.exit().remove(); // does not work // exit
}
Could someone bring some clarity in terms of how to use enter(), merge(), exit() in groups?
I potentially like to do changes in every stage for every element.
Update: I simplified the example, I don't need links or a force-layout. My question is only about the update-pattern, not about forces. The updated jsfiddle does not have the force-layout.
Share Improve this question edited Jan 13, 2017 at 3:08 Frame91 asked Jan 13, 2017 at 1:24 Frame91Frame91 3,8098 gold badges47 silver badges95 bronze badges 4- have you seen bl.ocks/mbostock/3808218? it's been updated with merge – softwarenewbie7331 Commented Jan 13, 2017 at 1:41
- 1 @Frame91, I'm glad you realised that you have 2 different questions here: One regarding the update pattern itself and other regarding how to update an force. They are substantially different questions. The problem right now is that any answer which addresses your general question (the update pattern) will fail to correctly address the force problem, as the two answers you have now. Thus, I suggest that you delete this question and post another one, regarding how to create an update pattern for groups containing texts and circles (and that will be easily answered). – Gerardo Furtado Commented Jan 13, 2017 at 2:57
- 1 I am not interested in the force-layout. I only care about the update pattern. I just used an existing example on jsfiddle which happens to have a force layout. sorry for the confusion ;) – Frame91 Commented Jan 13, 2017 at 3:01
- I also cannot delete this question as it has answers. However, nowhere did I ask about force-layouts, so I hope I still get a suitable answer in this thread. – Frame91 Commented Jan 13, 2017 at 3:28
3 Answers
Reset to default 9You are over plicating the pattern. Here's your update function written properly:
function update(items) {
var node = nodeLayer.selectAll(".node") // bind the data, this is update
.data(items, function(d) {
return d.id;
});
node.exit().remove(); // exit, remove the g
nodeEnter = node.enter() // enter, append the g
.append("g")
.attr("class", "node");
nodeEnter.append("circle") // enter, append the circle on the g
.attr("r", 2.5)
.attr('class', 'circle')
.attr('fill', 'red');
nodeEnter.append("text") // enter, append the text on the g
.attr("dy", 3)
.text(function(d) {
return d.name;
})
.attr('fill', 'green');
node = nodeEnter.merge(node); // enter + update on the g
node.attr('transform', function(d){ // enter + update, position the g
return 'translate(' + d.x + ',' + d.y + ')';
});
node.select("text") // enter + update on subselection
.text(function(d) {
return d.name;
});
}
Here it is running with multiple calls:
<!DOCTYPE html>
<html>
<head>
<script data-require="[email protected]" data-semver="4.0.0" src="https://d3js/d3.v4.min.js"></script>
</head>
<body>
<script>
var nodeLayer = d3.select('body')
.append('svg')
.attr('width',500)
.attr('height',500);
update([
{
id: 1,
name: 'A',
x: Math.random() * 500,
y: Math.random() * 500
},{
id: 2,
name: 'B',
x: Math.random() * 500,
y: Math.random() * 500
},{
id: 3,
name: 'C',
x: Math.random() * 500,
y: Math.random() * 500
}
]);
setTimeout(function(){
update([
{
id: 1,
name: 'A',
x: Math.random() * 500,
y: Math.random() * 500
},{
id: 4,
name: 'This is a new name...',
x: Math.random() * 500,
y: Math.random() * 500
},{
id: 3,
name: 'C',
x: Math.random() * 500,
y: Math.random() * 500
}
]);
}, 3000);
function update(items) {
var node = nodeLayer.selectAll(".node")
.data(items, function(d) {
return d.id;
});
node.exit().remove(); // exit, remove the g
nodeEnter = node.enter() // enter the g
.append("g")
.attr("class", "node");
nodeEnter.append("circle") // enter the circle on the g
.attr("r", 2.5)
.attr('class', 'circle')
.attr('fill', 'red');
nodeEnter.append("text") // enter the text on the g
.attr("dy", 3)
.attr('fill', 'green');
node = nodeEnter.merge(node); // enter + update
node.attr('transform', function(d){
return 'translate(' + d.x + ',' + d.y + ')';
});
node.select("text")
.text(function(d) {
return d.name;
});
}
</script>
</body>
</html>
I've done this recently in my code - I use select(subSelector) on the current selection that contains the elements. In your example I would change it as follows:
function update(items) {
var node = nodeLayer.selectAll(".node")
.data(items, function(d) { return d.id; })
var nodeEnter = node.enter()
.append("g")
.attr("class", "node");
nodeEnter.append("circle")
.attr("r", 2.5)
.attr('class', 'circle')
.merge(node.select('circle'))
.attr('fill', 'red');
nodeEnter.append("text") // insert
.attr("dy", 3)
.text(function(d) { return d.name; })
.merge(node.select('text'))
.attr('fill', 'green');
// You only need to call remove on the group, all the other exit().remove() calls are superfluous
node.exit().remove();
simulation
.nodes(items);
}
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
nodeLayer = svg.append('g'),
node;
var list = [];
var links = [];
var simulation = d3.forceSimulation(list)
.force("charge", d3.forceManyBody().strength(-1000))
.force("link", d3.forceLink(links).distance(200))
.force("center", d3.forceCenter(width / 2, height / 2))
.on("tick", ticked);
function createNodes(index) {
links.push({
'source': 0,
'target': index
})
list.push({
"id": index,
"name": "server " + index
});
return list;
}
var iteration = 0;
update(createNodes(iteration)); // just simulating updates
d3.interval(function(timer) {
iteration++;
update(createNodes(iteration));
}, 1000); //<-- this was mented out incorrectly just now
function update(items) {
var dataJoin = nodeLayer.selectAll(".node")
.data(items, function(d) {
return d.id;
});
node = dataJoin.enter() // insert
.append("g")
.attr("class", "node");
node.append("circle") // insert
.attr("r", 2.5)
.attr('class', 'circle')
.merge(dataJoin) // not the class, the actual selected group you called enter() on
.select('.circle')
.style('fill', 'red') // just for testing purposes
.exit().remove(); // exit
node.append("text") // insert
.attr("dy", 3)
.attr('class', 'text')
.text(function(d) {
return d.name;
})
.merge(dataJoin)
.select('.text')
.style('fill', 'green') // fill is a style
dataJoin.exit().remove();
simulation.nodes(list);
simulation.force("link").links(links);
simulation.alpha(1).restart();
}
function ticked() {
node.attr("transform", function(d) {
var a = d.x || 0;
var b = d.y || 0;
return "translate(" + a + ", " + b + ")";
});
}
<script src="https://cdnjs.cloudflare./ajax/libs/d3/4.4.1/d3.min.js"></script>
<svg height="300" width="300"></svg>
there are bunch of bugs,
first of all 'update'(misnomer) should be called with the list of all the data nodes not just the change.
you might want to explore https://bl.ocks/ and copy how people are doing force directed graphs. you need links to have forces.
the idea behind merge is to pare the new list with the old list, which means you need to use dataJoin or the group that has the data/id attached.
I am not an expert, do take a look at all the examples of force-directed graphs and see how they update/merge. (there's more than 1 way to update/restart the graph)