0

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:

  1. Load the data
  2. Load the initial conditions (req. for transitions)
  3. Load the final conditions (within a transition)
  4. Copy the current data into another array: prevData
  5. Reload data after interval

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?

  1. Find the previously bound data values associated with this element or the current values before it is changed within a function.
  2. Better way to find the current key being updated rather than using: d3.select(this.parentNode)... ? I've tried passing key values but don't seem to be getting it right. The best I have achieved, is passing a key function to the parent, and looking for it the way described above.

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

Sabur Aziz
  • 236
  • 1
  • 2
  • 9
  • I am not sure I understood your problem. Especially why you do some explicit current/previous data preparations is unclear to me. Basically one should pipe in the data into d3 and can then use the `enter` and `exit` methods to handle additional or removed data. I fail to see the need to manually prep the data here. – pintxo Jan 15 '17 at 21:08
  • the data needs to be prepped to handle animations. Because D3 needs a start and end state in order to do a tween, when data reloads, i need to set the initial state of the bars to what they were previously before allowing the binding to set the final (updated) state. is there another way to perform transitions? – Sabur Aziz Jan 15 '17 at 22:52

1 Answers1

1

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>
pintxo
  • 2,085
  • 14
  • 27
  • Hi, I understand what your answer is conveying and it highlights a lack of proper understanding on my part in terms of how D3 works (as much as I should have already known!). However, I cant seem to apply your logic to a ** STACKED ** bar chart. It seems when I try to follow your pattern, no data loads :-( could the fact that a stacked bar chart binds to the dataset twice (i believe it uses a form of nested binds)? – Sabur Aziz Jan 20 '17 at 21:05
  • Part of the problem seems to arise when I try to include the exit and enter functions seperately. As your example, i do the following (pseudo code): 1. var m = d3.selectAll().data(); m.enter(); For some reason, this seems to break things. if I instead just do: var m = d3.selectAll().data().enter(); this works OK. I am not sure what the differences in the above code are?? Again, I think this is due to a lack of my understanding. – Sabur Aziz Jan 20 '17 at 21:12
  • Have you looked at: https://medium.com/fattura-con-billy/animated-stacked-bar-charts-with-d3-js-2ef928163e59#.xausgxgvn – pintxo Jan 22 '17 at 14:41
  • Hi, yes, I have looked at the above link. It doesn't really help. I am able to generate the graph no problems. My problem arises from wanting to animate it between two different datasets in a smooth transition. – Sabur Aziz Jan 25 '17 at 07:42
  • Can you put the full code + example data in a fiddle? – pintxo Jan 25 '17 at 15:19
  • Although this answer didn't directly solve the problem, it did point me in the correct path. It made me aware of some fundamental gaps in my D3 knowledge and helped me to re-work my code appropriately. The useful part of pintxo's answer was the update/enter/exit pattern that I was not using correctly. – Sabur Aziz Jan 27 '17 at 10:21