How to update D3 contours with setInterval?

Viewed 254

I'm trying to update my d3js contours plot with new data. I'm using set interval method to create new random data and would like to generate new contours/animate current contours to new positions if that makes sense. Currently the code creates the contour plot for the fist data and doesn't update the contours with different data. Ideally, I would like to have a smooth transition of contours from one state to another according to the data I'm providing.

values = []

function solve() {
  var svg = d3.select('svg'),
    svgWidth = +svg.attr('width'),

    n = 31, // data width
    m = 31 // data height



  var thresholds = d3.range(0, 1, 0.1);
  var color = d3.scaleLinear()
    .domain(d3.extent(thresholds))
    .interpolate(function() {
      return d3.interpolateCool;
    });


  svg.selectAll('path')
    .data(d3.contours()
      .size([n, m])
      .thresholds(thresholds)(values) // this will create 6 step

    )
    .enter().append('path')
    .attr('d', d3.geoPath(d3.geoIdentity().scale(svgWidth / n)))
    .style('fill', function(d) {
      return color(d.value);
    })
    .style('stroke', function(d) {
      return color(d.value);
    })
    .style('stroke-width', 2.5)
    .style('stroke-dasharray', '0, 1000');



  // march the contours into existence
  /*  svg.selectAll('path')
     .transition()
     .duration(1000)
   
     .style('stroke-dasharray', '1000, 0'); */
  return

}



function updateData() {
  for (var v = 0; v < 31 * 31; v++) {
    values[v] = Math.floor(Math.random() * Math.floor(2))
  }
  return values
}

setInterval(function() {
  updateData()
  solve()

}, 10);
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-contour.v1.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>

<svg width="310" height="310" stroke-linejoin="bevel" stroke-linecap="round">
    </svg>

1 Answers

You're missing the idiomatic D3 "enter", "update" and "exit" selections. Here is your code refactored, the name of the selections are self-explanatory:

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" />
</head>

<body>
  <svg width="310" height="310" stroke-linejoin="bevel" stroke-linecap="round">
    </svg>

  <script src="https://d3js.org/d3.v4.min.js"></script>
  <script src="https://d3js.org/d3-contour.v1.min.js"></script>
  <script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>


  <script>
    values = []

    updateData()
    solve();

    function solve() {
      var svg = d3.select('svg'),
        svgWidth = +svg.attr('width'),

        n = 31, // data width
        m = 31 // data height



      var thresholds = d3.range(0, 1, 0.1);
      var color = d3.scaleLinear()
        .domain(d3.extent(thresholds))
        .interpolate(function() {
          return d3.interpolateCool;
        });


      let paths = svg.selectAll('path')
        .data(d3.contours()
          .size([n, m])
          .thresholds(thresholds)(values) // this will create 6 step

        );

      const pathsExit = paths.exit().remove();

      const pathsEnter = paths.enter().append('path')
        .attr('d', d3.geoPath(d3.geoIdentity().scale(svgWidth / n)))
        .style('fill', function(d) {
          return color(d.value);
        })
        .style('stroke', function(d) {
          return color(d.value);
        })
        .style('stroke-width', 2.5)
        .style('stroke-dasharray', '0, 1000');

      paths = pathsEnter.merge(paths);

      paths.transition()
        .duration(1000)
        .attr('d', d3.geoPath(d3.geoIdentity().scale(svgWidth / n)))


      // march the contours into existence
      /*  svg.selectAll('path')
         .transition()
         .duration(1000)
       
         .style('stroke-dasharray', '1000, 0'); */
      return

    }



    function updateData() {
      for (var v = 0; v < 31 * 31; v++) {
        values[v] = Math.floor(Math.random() * Math.floor(2))
      }
      return values
    }

    setInterval(function() {
      updateData()
      solve()

    }, 2000);
  </script>
</body>

</html>

However, pay attention to the fact that this will only interpolate the paths' d attribute. This is probably not what you want.

Related