javascript - Why d3 updates entire data -


i have svg element data created way:

var chart = d3.select("#my-div").append("svg"); var chartdata = []; chartdata.push([{x: 1, y: 3}, {x: 2, y: 5}]); chartdata.push([{x: 1, y: 2}, {x: 2, y: 3}]);            .domain([1, 5]); var linefunc = d3.svg.line()     .x(function (d) {         return xrange(d.x);     })     .y(function (d) {         return yrange(d.y);     })     .interpolate('linear');  chart.append('g').classed('lines', true).selectall('path').data(chartdata).enter()     .append('path')     .attr('d', function(d) {         return linefunc(d);     })     .attr('stroke', 'black')     .attr('stroke-width', 1)     .attr('fill', 'none'); 

after trying update data , update chart:

chartdata[1].push({x: 5, y: 5}); chart.selectall('g.lines').selectall('path').data(chartdata)     .attr('d', function(d) {         console.log('updating:');         console.log(d);         return linefunc(d);     })     .attr('stroke', 'black')     .attr('stroke-width', 1)     .attr('fill', 'none'); 

but prints 'updating' twice (for both of chartdata elements), i've changed 1 (chartdata[1]). how prevent not update ones didn't change? have many functions, ineffiecient update of them when 1 has changed.

// edit @mef's answer

i changed data (i don't mind updating entire chartdata[x] data, want avoid updating entire chartdata):

chartdata.push({key: 'a', data: [{x: 1, y: 3}, {x: 2, y: 5}]}); chartdata.push({key: 'b', data: [{x: 1, y: 2}, {x: 2, y: 3}]}); 

and when adding data i've put .data(chartdata, function(d) {return d.key}) , when updating did same, still updates both.

i tried put .data(chartdata, function(d) {return 'a'}) or .data(chartdata, function(d) {return 'b'}) when updating data , updates one, data a key (whether function returns a or b).

so whole code looks this:

var chart = d3.select("#my-div").append("svg"); var chartdata = []; chartdata.push({key: 'a', data: [{x: 1, y: 3}, {x: 2, y: 5}]}); chartdata.push({key: 'b', data: [{x: 1, y: 2}, {x: 2, y: 3}]});  var xrange = d3.scale.linear().range([50, 780]).domain([1, 5]); var yrange = d3.scale.linear().range([380, 20]).domain([2, 9]);  var linefunc = d3.svg.line()     .x(function (d) {         return xrange(d.x);     })     .y(function (d) {         return yrange(d.y);     })     .interpolate('linear');  chart.append('g').classed('lines', true).selectall('path')     .data(chartdata, function(d) {return d.key}).enter()     .append('path')     .attr('d', function(d) {         return linefunc(d.data);     })     .attr('stroke', 'black')     .attr('stroke-width', 1)     .attr('fill', 'none'); 

updating data

chartdata[1].data.push({x: 5, y: 5}); chart.selectall('g.lines').selectall('path')     .data(chartdata, function(d) {return d.key})     .attr('d', function(d) {         console.log('updating:');         console.log(d);         return linefunc(d.data);     })     .attr('stroke', 'black')     .attr('stroke-width', 1)     .attr('fill', 'none'); 

ok, can done...

option 1 - use key

here lazy way it...

strategy

  1. make key function detect changes in data.
    reading node attribute string , comparing attribute generator function result, called on datum.
  2. detect phase of d3 data binding process (key on nodes or key on data) , use different key each:
    var k = array.isarray(this) ? lined(d, linefunc) : d3.select(this).attr("d");

  3. align formatting of 2 key values writing , reading dummy node during "data key" phase. (that's lazy part!)

  4. keep separate references update, exit , enter selections decouple behaviour.

code

var chart = d3.select("#my-div").append("svg")       .attr("height", 600)       .attr("width", 900); var chartdata = []; chartdata.push([{x: 1, y: 3}, {x: 2, y: 5}]); chartdata.push([{x: 1, y: 2}, {x: 2, y: 3}]);  var xrange = d3.scale.linear().range([50, 780]).domain([1, 5]); var yrange = d3.scale.linear().range([380, 20]).domain([2, 9]);  var linefunc = d3.svg.line()     .x(function (d) {       return xrange(d.x);     })     .y(function (d) {       return yrange(d.y);     })     .interpolate('linear');  chart.append('g').classed('lines', true).selectall('path')     .data(chartdata, key)     .enter().append('path')     .attr('d', function(d) {       return linefunc(d);     })     .attr('stroke', 'black')     .attr('stroke-width', 1)     .attr('fill', 'none'); //updating data  chartdata[1].push({x: 5, y: 5});  var update = chart.selectall('g.lines').selectall('path')       .data(chartdata, key); update.enter().append('path')       .attr('d', function (d) {         console.log('updating:');         console.log(d);         return linefunc(d);       })       .attr('stroke', 'black')       .attr('stroke-width', 1)       .attr('fill', 'none'); update.exit().remove();  function key(d, i, j) {   var k = array.isarray(this) ? lineattr(d, linefunc, "d") : d3.select(this).attr("d");   console.log((array.isarray(this) ? "data\t" : "node\t") + k)   return k;    function lineattr(d, linefunct, attribute) {     var l = d3.select("svg").selectall("g")       .append("path").style("display", "none")       .attr(attribute, linefunct(d))     d = l.attr(attribute);     l.remove();     return d;   } } 

output

node    m50,328.57142857142856l232.5,225.71428571428572           node    m50,380l232.5,328.57142857142856                          data    m50,328.57142857142856l232.5,225.71428571428572           data    m50,380l232.5,328.57142857142856l780,225.71428571428572  updating:                                                       array[3]0: object1: object2: objectlength: 3__proto__: array[0] 

option 2 - use filter

this more efficient applies if know number of points on lines change , number of lines fixed.

strategy

  1. join data without key function , filter comparing attribute string calculated bound data, current attribute string in dom element.
  2. as in option 1, use dummy node lazy (and cross-browser) way align formatting of node attribute , calculated attribute text.

code

//updating data  chartdata[1].push({x: 5, y: 5});  chart.selectall('g.lines').selectall('path')   .data(chartdata)   .filter(changed)   .attr('d', function (d) {     console.log('updating:');     console.log(d);     return linefunc(d);   })   .attr('stroke', 'black')   .attr('stroke-width', 1)   .attr('fill', 'none');  function changed(d) {   var s = d3.select(this);   console.log("data\t" + lineattr(s.datum(), linefunc, "d"));   console.log("node\t" + s.attr("d")); console.log("\n")   return lineattr(s.datum(), linefunc, "d") != s.attr("d");    function lineattr(d, linefunct, attribute) {     var l = d3.select("svg").selectall("g")       .append("path").style("display", "none")       .attr(attribute, linefunct(d))     d = l.attr(attribute);     l.remove();     return d;   } } 

output

data    m50,328.57142857142856l232.5,225.71428571428572 node    m50,328.57142857142856l232.5,225.71428571428572  data    m50,380l232.5,328.57142857142856l780,225.71428571428572 node    m50,380l232.5,328.57142857142856  updating: array[3] 

option 3 - best of both worlds

strategy

  1. use standard update/enter/exit pattern.
  2. filter update selection form "changed" selection before operating on it.

code

//updating data  alert("base"); chartdata[1].push({ x: 5, y: 5 }); updateviz(); alert("change"); chartdata.push([{x: 3, y: 1}, {x: 5, y: 2}]) updateviz(); alert("enter"); chartdata.shift(); updateviz(); alert("exit");  function updateviz() {   var update = chart.selectall('g.lines').selectall('path')       .data(chartdata),        enter = update.enter()         .append('path')         .attr('d', function (d) {           return linefunc(d);         })         .attr('stroke', 'black')         .attr('stroke-width', 1)         .attr('fill', 'none'),        changed = update.filter(changed)         .attr('d', function (d) {           console.log('updating:');           console.log(d);           return linefunc(d);         });    update.exit().remove();    function changed(d) {     var s = d3.select(this);     console.log("data\t" + lineattr(s.datum(), linefunc, "d"));     console.log("node\t" + s.attr("d")); console.log("\n")     return lineattr(s.datum(), linefunc, "d") != s.attr("d");      function lineattr(d, linefunct, attribute) {       var l = d3.select("svg").selectall("g")         .append("path").style("display", "none")         .attr(attribute, linefunct(d))       d = l.attr(attribute);       l.remove();       return d;     }   } } 

background

read this


Comments