I'm struggling with understanding the merge function in D3, despite reading through the D3 API countless times.
The API says: "This method is commonly used to merge the enter and update selections after a data-join. After modifying the entering and updating elements separately, you can merge the two selections and perform operations on both without duplicate code."
Here's an example of the supposedly straightforward use of it, in a force directed chart, where the ticked function is called with every tick:
var simulation = d3.forceSimulation(nodes) .force("charge", chargeForce) .force("center", centerForce) .on("tick", ticked); function ticked() { var u = d3.select("svg").selectAll("circle").data(nodes) u.enter().append("circle").attr("r",5) .merge(u) // What is the merge function doing here? .attr("cx", d => d.x) .attr("cy", d => d.y) u.exit().remove() // Why is it necessary to remove excess objects w/ the exit selection? }
I understand how data-binding works, and how enter() and exit() selections work. However, I've never had to use a "merge" before, and I don't understand it is doing here. If someone could briefly walk through what is going on in this function step-by-step, that would be extremely useful. I'm sure others have similar questions.
selectAll() function in D3. js is used to select all the element that matches the specified selector string. Parameters: This function accepts single parameter HTML tag as a parameter. Return Value: This function returns the selected elements.
d3. range returns an array of evenly-spaced numbers. In its simplest form, it returns the integers from zero to the specified end minus one. Array(10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
datum(data) will bind the entirety of data to every single element in the selection.
The documentation explains very well what that function does, so what it does is instead of you having to do this
u.attr("cx", d => d.x) .attr("cy", d => d.y); u.enter().append("circle").attr("r",5) .attr("cx", d => d.x) .attr("cy", d => d.y);
You can just call attr
once like
u.enter().append("circle").attr("r",5) .merge(u) // after this point, any updates will apply to both u and u.enter() selections .attr("cx", d => d.x) .attr("cy", d => d.y)
It will set attributes cx
and cy
on both u
-the update selection and u.enter()
-the enter selection
Why is it necessary to remove excess objects w/ the exit selection?
Because the exit selection contains any extra DOM elements that were not bound to the elements in the array you passed to data()
, you can do whatever you need on the exit colllection, for example setting the styles by calling u.exit().style(...)
, etc. instead of calling remove
to delete them from the DOM
You actually have two issues here:
merge()
method;Regarding #1 you already received an answer. Regarding #2, these are my two cents: that code does not make sense.
This is simple to understand: the ticked
function runs dozens of times per second. Why would you re-bind the data and reassign the update, enter and exit selections dozens of times per second, if the data doesn't change? (it's worth mentioning that the author of that code is a good programmer, something strange has happened here... after all, we all make mistakes)
The ticked
function just need to compute the positions of the elements, that's all.
Here is that same code you linked with the ticked
function simplified to just this:
function ticked() { u.attr('cx', function(d) { return d.x; }) .attr('cy', function(d) { return d.y; }) }
And here the running code:
var width = 600, height = 400; var colorScale = ['orange', 'lightblue', '#B19CD9']; var xCenter = [100, 300, 500] var numNodes = 100; var nodes = d3.range(numNodes).map(function(d, i) { return { radius: Math.random() * 25, category: i % 3 } }); var u = d3.select('svg g') .selectAll('circle') .data(nodes); var enter = u.enter() .append('circle') .attr('r', function(d) { return d.radius; }) .style('fill', function(d) { return colorScale[d.category]; }); u = enter.merge(u); u.exit().remove(); var simulation = d3.forceSimulation(nodes) .force('charge', d3.forceManyBody().strength(5)) .force('x', d3.forceX().x(function(d) { return xCenter[d.category]; })) .force('collision', d3.forceCollide().radius(function(d) { return d.radius; })) .on('tick', ticked); function ticked() { u.attr('cx', function(d) { return d.x; }) .attr('cy', function(d) { return d.y; }) }
<script src="https://d3js.org/d3.v4.min.js"></script> <div id="content"> <svg width="700" height="400"> <g transform="translate(50, 200)"></g> </svg> </div>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With