sin tribu
sin tribu

Reputation: 318

general update pattern D3 - repainting unchanged nodes

I am making a d3 chart based off of time series data. I would like to plot moving averages based off a time window specified by the user in real time. All lines will share the same dataset. I am implementing the general update pattern (I believe), however, when I change the dataset, the update doesn't occur unless I call my update function explicitly. This works, however, a node is created for each element in the array (creating a new DOM element over the old one) as opposed to just adding the updated element. I feel I am misunderstanding something fundamental about D3, or perhaps about javascript itself. Here is a minimal working example of my code:

In brief, my chart has a setInterval method that adds to the dataset and updates all the lines on the chart. Each line shares the chart data, but will transform the data in different ways.

window.onload = function() {
  let mychart = chart( '#chart' );
  let props1 = {
    'className': 'line-1',
    'Color': 'red',
    'y': function( d ) { return d + 25; }
  }

  let line1 = line( mychart, props1 );

  mychart.addPlot( line1 );
  mychart.stream();
}

//need to find best way to share data across all indicators 
var chart = function(svg_id) {
  let data = [ 0, 1, 2, 3, 4, 5];
  let plots = [];
  let svg = ( function(  ) {
    return d3.select( svg_id + '-container' ).append( 'svg' )
      .attr( 'height', 500 )
      .attr( 'width', 1000 )
      .attr( 'id', svg_id.replace('#', '' ))
  })();

  let xscale = d3.scaleLinear()
                  .domain( [0, 100] )
                  .range( [10, 990] )

  let addPlot = function( plot ) {
    plots.push( plot );
  }

  let stream = function() {
    setInterval( function() {
      data.push( Math.random(0, 1) * 100);
      for( var i = 0; i < plots.length; i++ ) {
        plots[i].update();
      }
    }, 1000);
  }
  return {
    'svg': svg,
    'xscale': xscale,
    'data': data,
    'addPlot': addPlot,
    'stream': stream,
  }

}

var line = function( chart, props ) {
  let data = chart.data;
  let xscale = chart.xscale;
  let svg = chart.svg;

  let buildLine = d3.line()
    .x( function(d, i ) { return xscale( i ) + i*1; })
    .y( function(d) { return props.y( d ); })

  let update = function() {
    let line = svg.selectAll( props.className ).data( data );

    line.enter()
      .append( 'path' )
      .attr( 'stroke', props.Color )
      .attr( 'stroke-width', 2 )
      .attr( 'class', props.className )
      .attr( 'fill', 'none' )
      .merge( line )
        .attr( 'd', buildLine( data ))

    line.exit().remove();
  }


  return {
    'update': update,
  }
}

I found plenty online about nodes not being added, but nothing about d3 repainting on every iteration, and adding nodes every iteration. Thank you.

Upvotes: 0

Views: 64

Answers (2)

sin tribu
sin tribu

Reputation: 318

I solved this using this tutorial on d3 paths https://www.dashingd3js.com/svg-paths-and-d3js . Key point is selectAll is not appropriate for paths since a line is only one 'path' element, therefore append is all that is necessary. I am removing the line using jquery, so still a bit hacky as I'm sure D3 has a way to do this. But here is an updated init and update functions.

  let init = function() {
    svg.append( 'path' )
      .attr( 'd', buildLine( data ))
      .attr( 'stroke', props.Color )
      .attr( 'stroke-width', 2 )
      .attr( 'class', props.className )
      .attr( 'fill', 'none' )

  }



  let update = function( newdata ) {
    data.push( newdata );
    $( '.' + props.className ).remove();
    init();

Upvotes: 0

Sietse Brouwer
Sietse Brouwer

Reputation: 256

I'm not sure, but perhaps you can take a look at this: Key functions

As second argument of .data( data ) you can provide a function which uniquely identifies your data points. Like .data( data, function(d) { return d.someUniqueId; })

D3 can use this key to update DOM elements instead of regenerating them, as I understand it correctly from Mike Bostock's post.

Upvotes: 1

Related Questions