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
- make key function detect changes in data.
reading node attribute string , comparing attribute generator function result, called on datum. 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");align formatting of 2 key values writing , reading dummy node during "data key" phase. (that's lazy part!)
- 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
- join data without key function , filter comparing attribute string calculated bound data, current attribute string in dom element.
- 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
- use standard update/enter/exit pattern.
- 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
Post a Comment