Douglas Mauch
Douglas Mauch

Reputation: 869

Using Javascript D3 library, how replace data in selection of rects?

I have an array of objects with a count and date object. Using them to create a selection of rects on a time scale. Works great. But I want to use a slider from jQuery UI to allow the user to narrow the results to a part of the full range and see the finer details better. But I have not been able to figure out how to dynamically add and remove data points from my rect selection. Here is the code. The part in question is at the very bottom in the slider slide event handler. I would really appreciate any help on how to properly manage the data.

  var data=env.client.data;
  var selected=env.client.selected;

  var max=_.max(data,function(element){
    return(element.count);
  }).count;

    var height=250;
    var width=960;

    var padding={
      bottom:25,
      left:30,
      right:10,
      top:10
    };

    var barwidth=(width-(padding.left+padding.right))/data.length;

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

    var scale={};

    scale.x=d3.time
              .scale
              .utc()
              .domain([d3.first(data).date,d3.last(data).date])
              .range([padding.left,width-padding.right]);

    scale.yy=d3.scale
               .linear()
               .domain([0,max||1])
               .range([height-padding.bottom,padding.top]);

    scale.yh=d3.scale
               .linear()
               .domain([0,max||1])
               .range([0,height-(padding.bottom+padding.top)]);

    var axis={};

    axis.x=d3.svg
             .axis()
             .scale(scale.x)
             .orient('bottom');

    axis.y=d3.svg
             .axis()
             .scale(scale.yy)
             .orient('left')
             .ticks(12);

    if(max<12) axis.y.ticks(max);

    var xa=chart.append('g')
                .attr('class','xAxis')
                .attr('transform','translate(0,'+(height-padding.bottom)+')')
                .call(axis.x);

    var ya=chart.append('g')
                .attr('class','yAxis')
                .attr('transform','translate('+(padding.left)+',0)')
                .call(axis.y);

    var rects=chart.selectAll('rect')
                   .data(data)
                   .enter()
                   .append('rect')
                   .attr('x',function(d){
                     return(scale.x(d.date));
                   })
                   .attr('y',function(d){
                     return(scale.yy(d.count));
                   })
                   .attr('height',function(d){
                     return(scale.yh(d.count));
                   })
                   .attr('fill',function(d){
                     return(d3.hsl(120-(120*(d.count/50)),1,0.1));
                   })
                   .attr('width',barwidth)
                   .attr('title',function(d){
                     return(d.tooltip);
                   })
                   .on('mouseover',function(d){
                     d3.select(this)
                       .attr('fill',d3.hsl(120-(120*(d.count/50)),1,0.5));
                   })
                   .on('mouseout',function(d){
                     d3.select(this)
                       .attr('fill',d3.hsl(120-(120*(d.count/50)),1,0.1));
                   })
                   .on('click',function(d){
                     var selected=moment(d.date.getTime()).utc();
                     env.client.range.selected=selected;
                     $('#client-datetime').val(selected.format('YYYY MMM DD, HH:mm'));
                     _.publish('client date changed');
                   });

    var be=d3.first(data).date.getTime();
    var ee=d3.last(data).date.getTime();

    $('#slider').slider({
      range:true,
      min:be,
      max:ee,
      values:[be,ee],
      slide:function(event,ui){
        var bd=new Date(ui.values[0]);
        var ed=new Date(ui.values[1]);
        var subdata=_.filter(data,function(element){
          return((element.date>=bd)&&(element.date<=ed));
        });
        var barwidth=(width-(padding.left+padding.right))/subdata.length;
        scale.x.domain([bd,ed]);
        xa.call(axis.x);
        rects.data(subdata)
             .remove();
        rects.enter()
             .append('rect')
             .attr('x',function(d){
               return(scale.x(d.date))
             })
             .attr('width',barwidth);
      }
    });

Upvotes: 2

Views: 2744

Answers (1)

meetamit
meetamit

Reputation: 25157

As far as I can tell, you're on the right track.

You obtained subdata, then you applied it to rects

    rects.data(subdata)
         .remove();// this is wrong!!

I'm not sure why you're calling remove(), but that's essentially removing all DOM elements that are in subdata. This should be taken out.

Next, (I'm pretty sure that) you need to reassign rects to the new binding. I.e.

    rects = rects.data(subdata);

On the next you append all the enter()'ing rects. But you're missing some of the settings ('y', 'fill', etc attributes). But you also need to update all the subdata members that were not added nor removed from the set. So it becomes:

    rects.enter()
         .append('rect')
         .attr('y',function(d) {
           return(scale.yy(d.count));
         })
         .attr('height',function(d){
           return(scale.yh(d.count));
         })
         .attr('fill',function(d){
           return(d3.hsl(120-(120*(d.count/50)),1,0.1));
         });

    rects// This happens for all updated AND entering rects
         .attr('x',function(d){
           return(scale.x(d.date))
         })
         .attr('width',barwidth);

Finally, you need to remove ones that are no longer in subdata:

    rects.exit().remove();

This logic is a lot like what you have higher up, in the init script (except the init version only operated on the enter()'ing set). So you should move it all into a function:

updateChart(newData) {
    rects = rects.data(subdata);
    rects.enter()
    // etc....

    rects.remove()
}

and call this function at init time and when new subdata is being applied.

Upvotes: 2

Related Questions