To the best of my understanding it is not possible to include a transition on the entering elements in the standard enter/append/merge chain, since doing so would replace the entering element selection with a transition that cannot be merged with the update selection. (See here on the distinction between selections and transitions).
(Question edited in response to comment)
If the desired effect is sequenced transitions, one before and one after the merge, it can be accomplished as follows:
// Join data, store update selection
circ = svg.selectAll("circle")
.data(dataset);
// Add new circle and store entering circle selection
var newcirc = circ.enter().append("circle")
*attributes*
// Entering circle transition
newcirc
.transition()
.duration(1000)
*modify attributes*
.on("end", function () {
// Merge entering circle with existing circles, transition all
circ = newcirc.merge(circ)
.transition()
.duration(1000)
*modify attributes*
});
jsfiddle
I would like to know if there is a way to do this without breaking up the enter/append/merge chain.
There can be no doubt, that you have to have at least one break in your method chaining since you need to keep a reference to the update selection to be able to merge it into the enter selection later on. If you are fine with that, there is a way to keep the chain intact after that initial break.
I laid out the basic principal for this to work in my answer to "Can you chain a function after a transition without it being a part of the transition?". This uses transition.selection()
which allows you to break free from the current transition and get access to the underlying selection the transition was started on. Your code is more complicated, though, as the chained transition adds to the complexity.
The first part is to store the update selection like you did before:
// Join data, store update selection
const circUpd = svg.selectAll("circle")
.data(dataset);
The second, uninterrupted part goes like this:
const circ = circUpd // 2. Store merged selection from 1.
.enter().append("circle")
// .attr()...
.transition()
// .duration(1000)
// .attr()...
.on("end", function () {
circ.transition() // 3. Use merged selection from 2.
// .duration(1000)
// .attr()...
})
.selection().merge(circUpd); // 1. Merge stored update into enter selection.
This might need some further explanations for the numbered steps above:
The last line is the most important one—after kicking off the transition, the code uses .selection()
to get a hold of the selection the transition was based on, i.e. the enter selection, which in turn can be used to easily merge the stored update selection into it.
The merged selection comprising both the enter and the update selection is the result of the entire chain and is then stored in circ
.
This is the tricky part! It is important to understand, that the function provided to .on("end", function() {...})
is a callback which is not executed before the transition ends. Although this line comes before the merging of the selections, it is actually executed after that merge. By referring to circ
, however, it closes over—captures, if you will—the reference to circ
. That way, when the callback is actually executed, circ
will already refer to the previously merged selection.
Have a look at the following working snippet:
var w = 250;
var h = 250;
// Create SVG
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
// Create background rectangle
svg.append("rect")
.attr("x", "0")
.attr("y", "0")
.attr("width", w)
.attr("height", h)
.attr("fill", "aliceblue");
var dataset = [170, 220, 40, 120, 0, 300];
var xScale = d3.scaleBand()
.domain(d3.range(dataset.length))
.range([0, w]);
var yScale = d3.scaleLinear()
.domain([0, 300])
.range([75, 200])
var rad = xScale.bandwidth()/2
// Join data
var circ = svg.selectAll("circle")
.data(dataset);
// Create initial circles
circ.enter().append("circle")
.attr("cx", (d, i) => xScale(i)+rad)
.attr("cy", d => yScale(d))
.attr("r", rad)
.attr("fill", "blue");
// Trigger update on click
d3.select("h3")
.on("click", function () {
// Create new data value
var newvalue = Math.floor(Math.random()*300);
dataset.push(newvalue);
xScale.domain(d3.range(dataset.length));
rad = xScale.bandwidth()/2;
// Join data, store update selection
const circUpd = svg.selectAll("circle")
.data(dataset);
// Add new circle and store entering circle selection
const circ = circUpd // 2. Store merged selection from 1.
.enter().append("circle")
.attr("cx", "0")
.attr("cy", "25")
.attr("r", rad)
.attr("fill", "red")
.transition()
.duration(1000)
.attr("cx", (d, i) => xScale(i)+rad)
.on("end", function () {
circ.transition() // 3. Use merged selection from 2.
.duration(1000)
.attr("cx", (d, i) => xScale(i)+rad)
.attr("cy", d => yScale(d))
.attr("r", rad)
.attr("fill", "blue");
})
.selection().merge(circUpd); // 1. Merge stored update into enter selection.
});
<script src="https://d3js.org/d3.v4.js"></script>
<h3>Add a circle</h3>
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