Wednesday, April 25, 2018

d3 multiline update path and transition path does not work with nested data

Leave a Comment

I want to update the circles and the paths in this graph with a transition. However it does not work.

I am not very experienced with D3. How can I make my code better? Changing data structure is no problem. I want bind the data to graph with exit() remove() and enter() without deleting whole graph and add the data every time I update again. I do not know how to use enter() exit() and remove() for nested data. One time for the whole group and the other side updating the circle and paths. The ID should be fixed.

Here I have a little single line example from d3noob.


here is a JS Fiddle

var data = [{     id: 'p1',     points: [{       point: {         x: 10,         y: 10       }     }, {       point: {         x: 100,         y: 30       }     }]   },   {     id: 'p2',     points: [{         point: {           x: 30,           y: 100         }       }, {         point: {           x: 230,           y: 30         }       },       {         point: {           x: 50,           y: 200         }       },       {         point: {           x: 50,           y: 300         }       },     ]   } ];  var svg = d3.select("svg");  var line = d3.line()   .x((d) => d.point.x)   .y((d) => d.point.y);  function updateGraph() {   console.log('dataset contains', data.length, 'item(s)')   var allGroup = svg.selectAll(".pathGroup").data(data, function(d) {     return d.id   });   var g = allGroup.enter()     .append("g")     .attr("class", "pathGroup")   allGroup.exit().remove()    g.append("path")     .attr("class", "line")     .attr("stroke", "red")     .attr("stroke-width", "1px")     .transition()     .duration(500)     .attr("d", function(d) {       return line(d.points)     });    g.selectAll("path")   .transition()   .duration(500)   .attr("d", function(d) {     return line(d.points)   });    g.selectAll("circle")     .data(d => d.points)     .enter()     .append("circle")     .attr("r", 4)     .attr("fill", "teal")     .attr("cx", function(d) {       return d.point.x     })     .attr("cy", function(d) {       return d.point.y     })     .exit().remove()  } updateGraph()  document.getElementById('update').onclick = function(e) {    data = [{       id: 'p1',       points: [{         point: {           x: 10,           y: 10         }       }, {         point: {           x: 100,           y: 30         }       }]     },     {       id: 'p2',       points: [{           point: {             x: 30,             y: 100           }         }, {           point: {             x: 230,             y: 30           }         },         {           point: {             x: 50,             y: 300           }         },       ]     }   ];   updateGraph() }   $('#cb1').click(function() {   if ($(this).is(':checked')) {     data = [{         id: 'p1',         points: [{           point: {             x: 10,             y: 10           }         }, {           point: {             x: 100,             y: 30           }         }]       },       {         id: 'p2',         points: [{             point: {               x: 30,               y: 100             }           }, {             point: {               x: 230,               y: 30             }           },           {             point: {               x: 50,               y: 200             }           },           {             point: {               x: 50,               y: 300             }           },         ]       }     ];    } else {     data = [{       id: 'p1',       points: [{         point: {           x: 10,           y: 10         }       }, {         point: {           x: 100,           y: 30         }       }]     }];   }   updateGraph() }); 

2 Answers

Answers 1

Edited to change the data join to be be appropriate.

I think the issue has to do with variable data. So, I added data as an argument to the function and specified separate names for different data sets. When I specify different data names, I'm able to update the chart:

var first_data = [{     id: 'p1',     points: [{       point: {         x: 100,         y: 10       }     }, {       point: {         x: 100,         y: 30       }     }]   },   {     id: 'p2',     points: [{         point: {           x: 30,           y: 100         }       }, {         point: {           x: 230,           y: 30         }       },       {         point: {           x: 50,           y: 200         }       },       {         point: {           x: 50,           y: 300         }       },     ]   } ];  var svg = d3.select("svg");  var line = d3.line()   .x((d) => d.point.x)   .y((d) => d.point.y);  function updateGraph(data) {   console.log('dataset contains', data.length, 'item(s)')   var allGroup = svg.selectAll(".pathGroup")     .data(data, function(d) { return d; });    var g = allGroup.enter()     .append("g")     .attr("class", "pathGroup")    allGroup.exit().remove()    g.append("path")     .attr("class", "line")     .attr("stroke", "red")     .attr("stroke-width", "1px")     .transition()     .duration(500)     .attr("d", function(d) {         console.log("update path");       return line(d.points)     });    g.selectAll("path")   .transition()   .duration(500)   .attr("d", function(d) {     return line(d.points)   });    g.selectAll("circle")     .data(d => d.points)     .enter()     .append("circle")     .attr("r", 4)     .attr("fill", "teal")     .attr("cx", function(d) {       return d.point.x     })     .attr("cy", function(d) {       return d.point.y     })     .exit().remove()  } updateGraph(first_data)  document.getElementById('update').onclick = function(e) {    var update_data = [{       id: 'p1',       points: [{         point: {           x: 20,           y: 10         }       }, {         point: {           x: 100,           y: 30         }       }]     },     {       id: 'p2',       points: [{           point: {             x: 30,             y: 100           }         }, {           point: {             x: 230,             y: 30           }         },         {           point: {             x: 50,             y: 300           }         },       ]     }   ];    updateGraph(update_data) }   $('#cb1').click(function() {   if ($(this).is(':checked')) {    var click_data = [{         id: 'p1',         points: [{           point: {             x: 10,             y: 10           }         }, {           point: {             x: 100,             y: 30           }         }]       },       {         id: 'p2',         points: [{             point: {               x: 30,               y: 100             }           }, {             point: {               x: 230,               y: 30             }           },           {             point: {               x: 50,               y: 200             }           },           {             point: {               x: 50,               y: 300             }           },         ]       }     ];    } else {     var click_data = [{       id: 'p1',       points: [{         point: {           x: 10,           y: 10         }       }, {         point: {           x: 100,           y: 30         }       }]     }];   }   updateGraph(click_data) }); 

Fiddle: https://jsfiddle.net/wj266kmx/32/

Answers 2

I decided to rewrite all code and make an update pattern more suitable for d3.

var data_set = [     {line1_x: 0, line1_y: 4, line2_x: 0, line2_y: 8},     {line1_x: 2, line1_y: 6, line2_x: 2, line2_y: 12},     {line1_x: 4, line1_y: 16, line2_x: 4, line2_y: 10}  ];    var data_set2 = [     {line1_x: 0, line1_y: 6, line2_x: 0, line2_y: 12},     {line1_x: 2, line1_y: 22, line2_x: 2, line2_y: 31},     {line1_x: 4, line1_y: 15, line2_x: 4, line2_y: 20}  ];    var margin = {top: 20, right: 20, bottom: 20, left: 25},      width = 860 - margin.left - margin.right,      height = 400 - margin.top - margin.bottom;    var x = d3.scaleLinear().range([0,width]);  var y = d3.scaleLinear().range([height, 0]);  var z = d3.scaleOrdinal()      .range(["steelblue", "darkorange"]);    var xAxis = d3.axisBottom().scale(x);  var yAxis = d3.axisLeft().scale(y);    var line = d3.line()      .curve(d3.curveCardinal)      .x(function(d) { return x(d.xVal); })      .y(function(d) { return y(d.yVal); });    var svg = d3.select("svg")      .attr("width", width + margin.left + margin.right)      .attr("height", height + margin.top + margin.bottom)    .append("g")      .attr("transform",            "translate(" + margin.left + "," + margin.top + ")");    svg.append("g")    .attr("transform", "translate(0," + height + ")")    .attr("class","axis axis--x")    svg.append("g")    .attr("class","axis axis--y")    var durations = 0    d3.select("#selection").on("change", update)    update();    function update() {      var data = d3.select('#selection')      .property('value') == "First" ? data_set : data_set2      var keys = ["line1_y", "line2_y"];      var lineData = keys.map(function(id) {      return {        id: id,        values: data.map(function(d) {          return {xVal: d.line1_x, yVal: d[id]};        })      };    });      x.domain([0, d3.max(data, function(d) {       return d.line1_x;     })]).nice();      y.domain([      d3.min(lineData, function(c) {         return d3.min(c.values, function(d) {           return d.yVal - 5;         });       }),      d3.max(lineData, function(c) {         return d3.max(c.values, function(d) {           return d.yVal + 5;         });       })    ]);      svg.selectAll(".axis.axis--x").transition()      .duration(durations)      .call(xAxis);      svg.selectAll(".axis.axis--y").transition()      .duration(durations)       .call(yAxis);      var linePath = svg.selectAll(".linePath")      .data(lineData);      linePath = linePath      .enter()    .append("path")      .attr("class","linePath")      .style("stroke", function(d) { return z(d.id); })      .merge(linePath);      linePath.transition()      .duration(durations)      .attr("d", function(d) { return line(d.values); })      var layer = svg.selectAll("g.circle")      .data(lineData)      .enter().append("g").classed('circle', true);      layer.exit().remove();      var circle = svg.selectAll("g.circle").selectAll("circle")      .data(function(d) {        return d.values;      })      circle = circle      .enter()    .append("circle")       .attr("class", "circle" )      .style("fill", "black")      .attr("r", 3)      .merge(circle);      circle.transition()      .duration(durations)      .attr("cx", function(d) {        console.log(d)        return x(d.xVal)      })      .attr("cy", function(d) {         return y(d.yVal)      })      durations = 750;  }
body {    margin:auto;    width:900px;    padding:25px;  }    .linePath {    fill: none;    stroke: #555;    stroke-width: 1.5px;  }
<meta charset="utf-8">  <script src="https://d3js.org/d3.v4.min.js"></script>    <select id="selection">    <option value="First" selected>First</option>    <option value="Second">Second</option>  </select>    <svg></svg>

The more notable addition is the following code snippet:

  var lineData = keys.map(function(id) {     return {       id: id,       values: data.map(function(d) {         return {xVal: d.line1_x, yVal: d[id]};       })     };   }); 

Taken from Mike Bostocks chart which makes it easier to append the values to d3.line() when you're making a multiline chart

Hope this was the type of chart you were looking for.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment