For some reason, D3 is not transitioning the div's width smoothing on exit.
In the GIF below, I switch between 2 datasets, from [20, 40]
to [20]
The expected behavior is that the blue div should shrink smoothly, but instead, it gets stuck.
what should I do differently?
Live example: http://jsbin.com/vikodufugo/1/edit?js,output
const render = (data) => {
const colors = ['red', 'blue', 'green', 'gold'];
const width = (data) => (d) => (d / d3.max(data)) * 100 + '%';
const selection = d3
.select('.chart')
.selectAll('div')
.data(data);
selection
.transition()
.duration(750)
.style('width', width(data))
selection
.enter()
.append('div')
.style('width', '0px')
.style('height', '100px')
.style('background-color', (d, i) => colors[i % colors.length])
.transition()
.duration(750)
.style('width', width(data))
selection
.exit()
.transition()
.duration(750)
// shouldn't it transition smoothly?
.style('width', '0px')
.remove()
}
...
onClick('#button-1', () => render([20, 40]))
onClick('#button-2', () => render([20]))
The problem here is just the interpolation used by the transition.
D3 can interpolate two strings without any problem, for instance 300px
to 20px
. However, in your case, the start string has a "%"
while the final string has "px"
. D3 cannot interpolate that, which is quite excusable: what's the transition from "%"
to "px"
?
Let's show it in the following snippets. Here, I'm using d3.interpolateString(a,b), which:
Returns an interpolator between the two strings a and b. The string interpolator finds numbers embedded in a and b, where each number is of the form understood by JavaScript.
In the first demo, both strings have "px"
:
var interpolator = d3.interpolateString("300px", "0px");
d3.range(0,1.1,.1).forEach(function(d){
console.log(interpolator(d))
});
<script src="https://d3js.org/d3.v4.min.js"></script>
As you can see, nice results.
Now look what happens if the first string has "%"
while the second one has "px"
:
var interpolator = d3.interpolateString("100%", "0px");
d3.range(0,1.1,.1).forEach(function(d){
console.log(interpolator(d))
});
<script src="https://d3js.org/d3.v4.min.js"></script>
As you can see, at the very first interpolation, the "%"
is converted to "px"
. That explains the behaviour you're seeing: the div goes from "100%"
to "100px"
suddenly, and from there to "0px"
during the transition time.
Solution:
Change your string to use %
in the exit selection:
selection
.exit()
.transition()
.duration(750)
.style('width', '0%')
//use % here -----^
.remove()
Here is your code with that change:
const render = (data) => {
const colors = ['red', 'blue', 'green', 'gold'];
const width = (data) => (d) => (d / d3.max(data)) * 100 + '%';
const selection = d3
.select('.chart')
.selectAll('div')
.data(data);
selection
.transition()
.duration(750)
.style('width', width(data))
selection
.enter()
.append('div')
.style('width', '0px')
.style('height', '100px')
.style('background-color', (d, i) => colors[i % colors.length])
.transition()
.duration(750)
.style('width', width(data))
selection
.exit()
.transition()
.duration(750)
// shouldn't it transition smoothly?
.style('width', '0%')
.remove()
}
const onClick = (selector, callback) => (
document.querySelector(selector).addEventListener('click', callback)
);
onClick('#button-1', () => render([20, 40]))
onClick('#button-2', () => render([20]))
render([20, 40]);
<script src="https://d3js.org/d3.v4.js"></script>
<div class="buttons">
<button id="button-1" value="0">Option 1</button>
<button id="button-2" value="1">Option 2</button>
</div>
<div class="chart"></div>