Nyxynyx Nyxynyx - 4 months ago 20
jQuery Question

d3.js chart not updated with new data

A series of rectangles are plotted using

d3.js
by fetching an array of 100 objects. Although the
console.log(data)
shows that new data are being retrieved, the new data points do not seem to be appending any rectangles.

Was
.enter().append()
used incorrectly?

function init() {
var width = 800,
height = 400,
margin = 50;

chart = d3.select('#chart')
.append('svg:svg')
.attr('width', width)
.attr('height', height);
}

function do_update() {
data = fetch_latest_top100();
console.log(data)

chart.selectAll('rect')
.data(data)
.enter()
.append('svg:rect')
.attr('x', function(d) { return x(d.x); })
.attr('y', function(d) { return y(d.y); })
.attr('height', function(d) { return d.value; })
.attr('width', function(d) { return 3 });
}


jsfiddle: http://jsfiddle.net/BU8cA/

Answer

The problem with your update method is that it ONLY handles new element, and does not update the existing elements to match their new data. After you have created the new rectangles with your enter() selection, you have to go back to your complete selection (new and changing rectangles) to set the attributes that are going to change.

The easiest way to do this is to store the selection in a variable at the point where you have applied the data function but before you call the enter method, like this:

   var rects = chart.selectAll('rect')
        .data(data);

   rects.enter()
        .append('svg:rect')
        .attr('width', function(d) { return 3 }); 
       // since the width is constant, you only need to set it once

   rects.exit().remove(); //if the number of data points might shrink

   rects.attr('x', function(d) { return x(d.x); })
        .attr('y', function(d) { return y(d.y); })
        .attr('height', function(d) { return d.value; });
   //note that this now includes the rectangles created with enter()
   //as well as the old ones you are resizing / repositioning

That should get the right data in, but you still have a layout problem (on the fiddle, anyway), in that your y values aren't being set correctly. The y value is the position of the top of the rectangle, so if you want bars that look like they are all sitting on the same line you need to adjust it based on the height of the bar. Mike Bostock's tutorial and demo here: http://bost.ocks.org/mike/bar/3/


Update for D3 version 4+

Elements appended to the "enter" selection are no longer automatically added to the main (update) selection. You now need to explicitly create a "merge" selection, by calling the merge method on the "enter" selection:

var rects = chart.selectAll('rect')
    .data(data);

rects.exit().remove(); //if the number of data points might shrink

rects.enter()
    .append('svg:rect')
    .attr('width', function(d) { return 3 }); 
   // since the width is constant, you only need to set it once

  .merge(rects) 
          //merge the newly added elements into the main selection
          //of existing elements to update
    .attr('x', function(d) { return x(d.x); })
    .attr('y', function(d) { return y(d.y); })
    .attr('height', function(d) { return d.value; });