user2497586
user2497586

Reputation:

Animating histogram with D3

I have some code on this jsFiddle here that generates a histogram for a data array called "values". That's all well and good.

When I want to update this histogram with a new data array, called "newData", things go wrong. I am trying to adhere to the enter(), update(), exit() D3 strategy (which I am obviously extremely new with). An animation does indeed occur, but as you can see by the fiddle, it just squishes everything into the upper right hand corner. Can someone point out what I am doing wrong in this segment of the code (the update)?

//Animations
d3.select('#new')
  .on('click', function(d,i) {
  var newHist = d3.layout.histogram().bins(x.ticks(bins))(newData);

  var rect = svg.selectAll(".bar")
   .data(values, function(d) { return d; });

  // enter
  rect.enter().insert("g", "g")
    .attr("class", "bar")
    .attr("transform", function(d) { return "translate(" + x(d) + "," + y(d) + ")"; });

  rect.enter().append("rect")
    .attr("x", 1)
    .attr("width", w)
    .attr("height", function(d) { return y(d); });
  rect.enter().append("text")
    .attr("dy", ".75em")
    .attr("y", 6)
    .attr("x", x(histogram[0].dx) / 2)
    .attr("text-anchor", "middle")
    .text(function(d) { return formatCount(d); });

  // update    
  svg.selectAll('.bar')
    .data(newHist)
    .transition()
    .duration(3000)
    .attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
  svg.selectAll("rect")
    .data(newHist)
    .transition()
    .duration(3000)
    .attr("height", function(d) { return height - y(d.y); });
  svg.selectAll("text")
    .data(newHist)
    .transition()
    .duration(3000)
    .text(function(d) { return formatCount(d.y); });
    // exit
    rect.exit()
    .remove();
});

The entirety of the code is on the JSFiddle linked above. Thanks!

Upvotes: 1

Views: 1161

Answers (1)

stephenspann
stephenspann

Reputation: 1843

Looking at the code above and the fiddle, a few things jump out at me:

  • (Line 85) You are still binding the original data
  • (Lines 105, 115) You are binding the data multiple times
  • (Line 99) You are still referencing the original histogram variable without updating it with the new data
  • You are declaring multiple bind/add/update/remove patterns for a single set of (changing) data

You're on the right track, but you need to differentiate between things that need to be updated when the data changes, and things that should not be updated/declared when the data changes. You should only have to declare the d3 pattern (bind, add, update, remove) once... it will work for updated datasets.

So, declare as much as you can outside the makeHist(values) function, and only have code that needs the changed data inside the function (this includes modifying a previously declared scale's domain and range). Then, the on click function can simply call the makeHist function again with the new data.

Here's a rough outline:

// generate data

// declare everything that can be static
// (add svg to dom, declare axes, etc)

// function that handles everything that new data should modify
function makeHist(values) {

    // modify domains of axes, create histogram

    // bind data
    var rect = svg.selectAll('rect')
       .data(histogram);

    // add new elements
    rect.enter().append('rect');

    // update existing elements
    rect.transition()
        .duration(3000)
        .attr('transform', '...');

    // remove old elements
    rect.exit().remove();

}

// generate initial histogram
makeHist(initialValues);

// handle on click event
d3.select('#new')
  .on('click', function() {
    makeHist(newData);
});

Here's a mostly working updated fiddle, it needs a little bit of cleanup, though:

http://jsfiddle.net/spanndemic/rf4cw/

Spoiler Alert: the two datasets aren't all that different

Upvotes: 0

Related Questions