score:3

Accepted answer

There are a few ways to go through this. You could easily use an enter/update/exit cycle, though this is a little complex when compared to typical use of the cycle because of the nested elements and the need to set keys to ensure smooth transitions between chart states.

In this situation, it may be easier to simply use an array to hold bars that are to be filtered out, hide those bars, update the scales to not use those keys' values, and update the remaining bars.

This requires an onclick event for each legend item. When clicked, in our clicked function we manage the array of filtered out (filtered) items like so, where d is the datum associated with the legend rectangle:

// add the clicked key if not included:
if (filtered.indexOf(d) == -1) {
 filtered.push(d); 
  // if all bars are un-checked, reset:
  if(filtered.length == keys.length) filtered = [];
}
// otherwise remove it:
else {
  filtered.splice(filtered.indexOf(d), 1);
}

Then we can update the scales (we need the all the keys that are not in the filtered array for the domain of the x1 scale, hence the newKeys variable):

var newKeys = [];
    keys.forEach(function(d) {
      if (filtered.indexOf(d) == -1 ) {
        newKeys.push(d);
      }
    })
    x1.domain(newKeys).rangeRound([0, x0.bandwidth()]);
    y.domain([0, d3.max(data, function(d) { return d3.max(keys, function(key) { if (filtered.indexOf(key) == -1) return d[key]; }); })]).nice();

Then we can select our rectangles, filter by whether they should be hidden or shown, and update accordingly:

var bars = svg.selectAll(".bar").selectAll("rect")
  .data(function(d) { return keys.map(function(key) { return {key: key, value: d[key]}; }); })

// filter out bars:
bars.filter(function(d) {
  return filtered.indexOf(d.key) > -1;
})
.transition()
.attr("x", function(d) {
  return (+d3.select(this).attr("x")) + (+d3.select(this).attr("width"))/2;  
})
.attr("height",0)
.attr("width",0)     
.attr("y", function(d) { return height; })
.duration(500);

// update persistent bars:
bars.filter(function(d) {
    return filtered.indexOf(d.key) == -1;
  })
  .transition()
  .attr("x", function(d) { return x1(d.key); })
  .attr("y", function(d) { return y(d.value); })
  .attr("height", function(d) { return height - y(d.value); })
  .attr("width", x1.bandwidth())
  .attr("fill", function(d) { return z(d.key); })
  .duration(500);

This solution could be made a little bit more "d3-ish" with the enter/update/exit cycle, but as our elements are relatively fixed in number, this is not as useful as in many other situations.

Here is the above code in action:

https://bl.ocks.org/andrew-reid/64a6c1892d1893009d2b99b8abee75a7

And as noted in the comments, you also need to update the axis, not just the scale. To do so, I added a class to the y scale to allow easy selection when updating the chart:

       svg.select(".y")
        .transition()
        .call(d3.axisLeft(y).ticks(null, "s"))
        .duration(500);

Related Query

More Query from same tag