I am trying to update a stacked bar chart with transitions as the underlying data is changed. It calls the same "render" function each time and works well when no transitions are involved. However, I would like to animate the changes in values, transitioning from its current state to the next.
I have somewhat solved the problem, but feel like my solution is clunky - hoping there is a better way to do this for stacked bar charts.
My approach has been to do the following:
Using the above approach, if prevData has values, then use these to set the initial conditions. My problems is that finding and setting the initial conditions feels really clunky:
if (prevData.length > 0) {
//get the parent key so we know who's data we are now updating
var devKey = d3.select(this.parentNode).datum().key;
//find the data associated with its PREVIOUS value
var seriesData = seriesPrevData.find(function (s) { return (s.key == devKey); })
if (seriesData != null) {
//now find the date we are currently looking at
var day = seriesData.find(function (element) { return (element.data.Date.getTime() == d.data.Date.getTime()); });
if (day != null) {
//now set the value appropriately
//console.debug("prev height:" + devKey + ":" + day[1]);
return (y(day[0]) - y(day[1]));
}
}
}
All I'm doing, is finding the correct key array (created by d3.stack()), then trying to find the appropriate previous data entry (if it exists). However, searching parent nodes, and searching through arrays to find the required key and the appropriate data element feels very long-winded.
So, my question is, is there a better way to do this? or parts of this?
Sorry for the long post, I just spent a whole day working out my solution, frustrated by the fact that all I really needed, was the previous values of an item. Having to do all these "gymnastics" to get what I needed seems very "un" D3.js like :-)
Thanks
Following is a simple example for an animated bar chart. It'll iterate over two different versions of the dataset to show how one can handle changes in the underlying data very easily with d3. There is no need (in this example) for any manual data preparation for the transition/animation.
var data = [
[1, 2, 3, 4, 5],
[1, 6, 5, 3]
];
var c = d3.select('#canvas');
var currentDataIndex = -1;
function updateData() {
// change the current data
currentDataIndex = ++currentDataIndex % data.length;
console.info('updating data, index:', currentDataIndex);
var currentData = data[currentDataIndex];
// get our elements and bind the current data to it
var rects = c.selectAll('div.rect').data(currentData);
// remove old items
rects.exit()
.transition()
.style('opacity', 0)
.remove();
// add new items and define their appearance
rects.enter()
.append('div')
.attr('class', 'rect')
.style('width', '0px');
// change new and existing items
rects
// will transition from the previous width to the current one
// for new items, they will transition from 0px to the current value
.transition()
.duration('1000')
.ease('circle')
.style('width', function (d) { return d * 50 + 'px'; });
}
// initially set the data
updateData();
// keep changing the data every 2 seconds
window.setInterval(updateData, 2000);
div.rect {
height: 40px;
background-color: red;
}
div#canvas {
padding: 20px;
border: 1px solid #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="canvas">
</div>