I am trying to add a new transition while a transition is running with a condition that if bar1 width matches bar2 then the bars change positions.
I have used transition().tween to see if the condition is met. When the 2nd transition starts, the 1st stops. I want the 1st transition to continue running until the end of its duration even though the 2nd one has started.
I have the code code but am unable to continue the 1st transition during the 2nd. Please help.
window.i1 = 0;
window.i2 = 0;
var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 500);
var bar1 = svg.append("rect")
.attr("fill", "green")
.attr("x", 20)
.attr("y", 40)
.attr("height", 20)
.attr("width", 40)
var bar2 = svg.append("rect")
.attr("fill", "blue")
.attr("x", 20)
.attr("y", 70)
.attr("height", 20)
.attr("width", 20)
update();
function update() {
bar1.transition()
.ease(d3.easeLinear)
.duration(2000)
.attr("width",100)
.tween("attr.fill", function() {
var node = this;
return function(t) {
window.bar1width = node.getAttribute("width");
var bl = check();
if(bl=="true"&&window.i1==0){
chnPos();
window.i1=window.i1+1;
}
}
})
bar2.transition()
.ease(d3.easeLinear)
.duration(2000)
.attr("width",120)
.tween("attr.fill", function() {
var node = this;
return function(t) {
window.bar2width = node.getAttribute("width");
var bl = check();
if(bl=="true"&&window.i2==0){
chnPos();
window.i2=window.i2+1;
}
}
})
}
function check() {
if (window.bar2width>=window.bar1width){
console.log(window.bar1width +' ' + window.bar2width);
return "true";
}
//console.log(true)
return "false";
}
function chnPos(){
bar1.transition()
.ease(d3.easeLinear)
.duration(500)
.attr("y",70)
bar2.transition()
.ease(d3.easeLinear)
.duration(500)
.attr("y",40)
}
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.3.0/d3.min.js"></script>
</head>
<body>
<script type="text/javascript" src="index.js"></script>
</body>
</html>
In d3v4+ you can have multiple concurrent transitions, but they need to have separate names:
selection.transition([name]) <>
Returns a new transition on the given selection with the specified name. If a name is not specified, null is used. The new transition is only exclusive with other transitions of the same name. (docs)
Let's add some names to the transitions, I use "grow" and "switch" below
window.i1 = 0;
window.i2 = 0;
var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 500);
var bar1 = svg.append("rect")
.attr("fill", "green")
.attr("x", 20)
.attr("y", 40)
.attr("height", 20)
.attr("width", 40)
var bar2 = svg.append("rect")
.attr("fill", "blue")
.attr("x", 20)
.attr("y", 70)
.attr("height", 20)
.attr("width", 20)
update();
function update() {
bar1.transition("grow")
.ease(d3.easeLinear)
.duration(2000)
.attr("width",100)
.tween("attr.fill", function() {
var node = this;
return function(t) {
window.bar1width = node.getAttribute("width");
var bl = check();
if(bl=="true"&&window.i1==0){
chnPos();
window.i1=window.i1+1;
}
}
})
bar2.transition("grow")
.ease(d3.easeLinear)
.duration(2000)
.attr("width",120)
.tween("attr.fill", function() {
var node = this;
return function(t) {
window.bar2width = node.getAttribute("width");
var bl = check();
if(bl=="true"&&window.i2==0){
chnPos();
window.i2=window.i2+1;
}
}
})
}
function check() {
if (window.bar2width>=window.bar1width){
//console.log(window.bar1width +' ' + window.bar2width);
return "true";
}
//console.log(true)
return "false";
}
function chnPos(){
bar1.transition("switch")
.ease(d3.easeLinear)
.duration(500)
.attr("y",70)
bar2.transition("switch")
.ease(d3.easeLinear)
.duration(500)
.attr("y",40)
}
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.3.0/d3.min.js"></script>
</head>
<body>
<script type="text/javascript" src="index.js"></script>
</body>
</html>
I'll just add that this can probably be simplified a fair bit - as the approach of creating a transition for each element individually introduces a lot of extra code. The complexity of your code also increases a fair bit for each additional bar. You should be able to use bound data and some sorting to re-order elements with transitions during the length transition. Perhaps something like (this is a rough snippet, there are surely better ways):
var data = [
{ start:200, current: 200, end: 40 },
{ start:120, current: 120, end: 240 },
{ start:10, current: 10, end: 260 }
];
var colors =["crimson","steelblue","lawngreen","orange"];
var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 500);
var bars = svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", 20)
.attr("y", function(d,i) { return i*30+20; })
.attr("width", function(d) { return d.start; })
.attr("height", 20)
.attr("fill",function(d,i) { return colors[i]; })
.on("click", order);
bars.transition("length")
.attr("width", function(d) { return d.end; })
.tween("attr.current", function(d,i) {
var bar = d3.select(this);
var that = this;
return function() {
d.current = +bar.attr("width");
bars = bars.sort(function(a,b) {
return b.current - a.current;
}).order();
// trigger new transition if needed:
var nodes = bars.nodes();
if(nodes[i] != that) {
for(var j = 0; j < nodes.length; j++) {
if(nodes[j] == that) { i=j; break;}
}
order();
}
}
})
.duration(4000);
function order(bar) {
bars.transition("order")
.attr("y", function(d,i) { return i*30+20; })
//.ease(d3.easeLinear)
}
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.3.0/d3.min.js"></script>
</head>
<body>
<script type="text/javascript" src="index.js"></script>
</body>
</html>
For a bit more explanation, I'll break down the second snippet's primary transition:
// Transition each bar's width/length:
bars.transition("length")
// set the final width value:
.attr("width", function(d) { return d.end; })
// Modify the datum throughout the transition
// This function is called once for each element
// This means we need to update d,i manually during the transition
.tween("attr.current", function(d,i) {
// Keep track of an individual bar being transitioned (element & selection):
var bar = d3.select(this);
var that = this;
// This function is invoked each tick for each bar:
return function() {
// Update a bar's datum to reflect current width:
d.current = +bar.attr("width");
// Sort the bars based on current width:
bars = bars.sort(function(a,b) {
return b.current - a.current;
})
.order(); // Pull up the longest bar so it is drawn last (if there is overlap, it will be on top)
// trigger new transition if needed:
// Has the bar being transitioned been moved in the selection?
// If so, nodes[i] will not equal the element being moved (that)
var nodes = bars.nodes();
if(nodes[i] != that) {
// If it has been moved, update i to reflect the element's new index
for(var j = 0; j < nodes.length; j++) {
if(nodes[j] == that) { i=j; break;}
}
// And apply the transition on the vertical spacing:
order();
}
}
})
.duration(4000);
Without the check to see if node order has changed, the second transition would be triggered repeatedly, replacing the previous second transition. The most visible consequence of this results from the default use of d3.easeCubic: the start of the transition is slow. If constantly restarting the second transition, the second transition will never move very fast until the first transition is complete. This could be an issue with the above snippet too, but only if there is a lot of place changes in rapid succession.
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