Jeff Tilton
Jeff Tilton

Reputation: 1296

d3.js nested data update line plot in html table

I am trying to create a dashboard with d3. I am putting bullet plots and sparklines into a table. I have been able to put the bullet plots in without any trouble, but I can't get the paths for the line plots. My data is nested and the below appends an svg and path correctly, but the path is empty. I have tried multiple update patterns and I do not see why I can't get any data for the path with the below.

Any help or suggestions is appreciated. Thanks.

data = [{data:{data1:[{index:1, value:5},{index:2, value:9}]}}]
    
    
sparkScaleY = d3.scaleLinear()
  .domain([0,10])
  .range([10,0])
sparkScaleX = d3.scaleLinear()
  .domain([0,10])
  .range([0,15])
    
line = d3.line()
  .x(function(d) { return sparkScaleX (d.index); })
  .y(function(d) { return sparkScaleY (d.value); });
    
rows = d3.select("tbody")
 .selectAll("tr")
 .data(data)
 .enter()
 .append("tr")
    
spark = rows.append('td')
 .append('svg')
 .attr("class", "spark-svg")
 .attr("width", 15)
 .attr("height", 10)
 .append('path')
    
spark
  .attr("class", "spark-path")
    
function update(data){
    
  sparkSvg = d3.selectAll(".spark-svg")
     .data(data)
    
  sparkPath = sparkSvg.selectAll('.spark-path')
     .data(function(d){return [d.data.data1]})
     .enter()
     .append('path')
     .classed("new", true)
    
  sparkPath.attr('d', line)

  sparkPath
     .attr('d', line)
     .classed("new",false)
      
  sparkPath.exit().remove()

}
update(data)
<table>
<tbody>
</tbody>
</table>
<script src="https://d3js.org/d3.v5.min.js">

To clarify, the number of rows, columns, svgs, and paths remain constant between updates.

Upvotes: 1

Views: 332

Answers (1)

Andrew Reid
Andrew Reid

Reputation: 38181

If the table structure is constant (same number of rows, columns, svgs and sparklines(paths)), then we can simplify your code a bit. In your example you are updating, entering, and exiting in the update function. We don't need to do this if we aren't entering or exiting anything once the table is created.

You already add the rows, columns, svg, and path once prior to the update function, so all we need in the update function is an update selection - which let us simplify that a fair bit:

// Add rows once, same as before:
rows = d3.select("tbody")
  .selectAll("tr")
  .data(data)
  .enter()
  .append("tr")

// Add sparkline cell,svg, and path once, same as before:
var spark = rows.append('td')
  .append('svg')
  .attr("class", "spark-svg")
  .attr("width", 15)
  .attr("height", 10)
  .append('path')
  .attr("class", "spark-path")

// Only update, no need for entering/exiting if table structure doesn't change, only data:
function update(data){
  spark // use existing selection
    .data(data)  // assign new data
    .attr('d', function(d) {
      return line(d.data.data1);  // draw new data
  })
}

This looks like:

data = [{data:{data1:[{index:1, value:5},{index:4, value:9},{index:7,value:4},{index:10,value:8}]}}]

sparkScaleY = d3.scaleLinear()
  .domain([0,10])
  .range([10,0])
sparkScaleX = d3.scaleLinear()
  .domain([0,10])
  .range([0,15])

line = d3.line()
  .x(function(d) { return sparkScaleX (d.index); })
  .y(function(d) { return sparkScaleY (d.value); });

// Add rows once:
rows = d3.select("tbody")
  .selectAll("tr")
  .data(data)
  .enter()
  .append("tr")

// Add title cell:
rows.append("td")
  .text(function(d) { 
    return Object.keys(d.data)[0];
  })

// Add sparkline cell,svg, and path once:
var spark = rows.append('td')
  .append('svg')
  .attr("class", "spark-svg")
  .attr("width", 15)
  .attr("height", 10)
  .append('path')
  .attr("class", "spark-path")

// Only update, no need for entering/exiting if table structure doesn't change, only data:
function update(data){

  spark // use the existing selection
    .data(data)
    .attr('d', function(d) {
      return line(d.data.data1);
  })
}
update(data)
path {
  stroke-width: 2;
  stroke:black;
  fill: none;
}
<script src="https://d3js.org/d3.v5.min.js"></script><table>
<tbody>
</tbody>
</table>

And with some dynamic data

var data = generate();

sparkScaleY = d3.scaleLinear()
  .domain([0,10])
  .range([10,0])
sparkScaleX = d3.scaleLinear()
  .domain([0,10])
  .range([0,15])

line = d3.line()
  .x(function(d) { return sparkScaleX (d.index); })
  .y(function(d) { return sparkScaleY (d.value); });

// Add rows once:
rows = d3.select("tbody")
  .selectAll("tr")
  .data(data)
  .enter()
  .append("tr")

// Add title cell:
rows.append("td")
  .text(function(d) { 
    return d.name;
  })

// Add sparkline cell,svg, and path once:
var spark = rows.append('td')
  .append('svg')
  .attr("class", "spark-svg")
  .attr("width", 15)
  .attr("height", 10)
  .append('path')
  .attr("class", "spark-path")

var spark2 = rows.append('td')
  .append('svg')
  .attr("class", "spark-svg")
  .attr("width", 15)
  .attr("height", 10)
  .append('path')
  .attr("class", "spark-path2")
  .style("stroke","steelblue");

// Only update, no need for entering/exiting if table structure doesn't change, only data:
function update(data){

  spark
    .data(data)
    .transition()
    .attr('d', function(d) {
      return line(d.spark1);
  })
  spark2
    .data(data)
    .transition()
    .attr('d', function(d) {
      return line(d.spark2);
  })
  
}

update(generate());
d3.interval(function(elapsed) {
   update(generate());
}, 2000);



function generate() { 
  return d3.range(10).map(function(d) {
    return {
      name: "row"+d,
      spark1: d3.range(10).map(function(d) {
        return {index: d, value: Math.random()*10}
      }),   
      spark2: d3.range(10).map(function(d) {
        return {index: d, value: Math.random()*10}
      })       
    }
  })
}
path {
  stroke-width: 2;
  stroke:black;
  fill: none;
}
<script src="https://d3js.org/d3.v5.min.js"></script><table>
<tbody>
</tbody>
</table>

Upvotes: 2

Related Questions