How to update data in d3 chart with an onchange slider event? Code Answer

Hello Developer, Hope you guys are doing great. Today at Tutorial Guruji Official website, we are sharing the answer of How to update data in d3 chart with an onchange slider event? without wasting too much if your time.

The question is published on by Tutorial Guruji team.

Attached is my minimum working example in JS Fiddle. I am able to only show the initial data, but when I change the slider, the data does not update. Where am I going wrong in my code here? Apologies for the poor structure – I am still a beginner in D3.

https://jsfiddle.net/pv02z8em/1/

   chartGroup
    .selectAll('.line-series')
    .data(data, d=> d.x)
    .join(
      enter => {
        enter.append('path')
        .attr('class', d => `line-series x_${d.x}`)
        .attr("d", drawLine(data))
        .style('stroke', 'dodgerblue')
        .style('stroke-width', 2)
        .style('fill', 'none')
        .style('opacity', 1)
      },
      update => { update.transition().duration(500) },
      exit => { exit.remove() }
    )
}

Answer

You have several issues in that code:

  1. You are not selecting the slider by its ID. It should be d3.select('#slider-x-range');

  2. In the listener, you’re not calling buildLine(data);

  3. Remove everything inside buildLine that isn’t related to the path itself, otherwise you’ll create different SVGs every time the user moves the slider;

  4. Your join structure is currently appending several paths, one above the other. This is certainly not what you want. It could be just:

    let path = chartGroup.selectAll('.line-series')
        .data([data]);
    
    path = path.enter()
        .append('path')
        .attr('class', "line-series")
        .attr("d", d => drawLine(d))
        .style('stroke', 'dodgerblue')
        .style('stroke-width', 2)
        .style('fill', 'none')
        .style('opacity', 1)
        .merge(path)
    
    path.transition().duration(500).attr("d", d => drawLine(d))
    

Here is your code with these and other minor changes:

<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="css/normalize.css">
    <link rel="stylesheet" href="css/skeleton.css">
    <link rel="stylesheet" href="css/skeleton_override.css">

    <style>
      svg {
        display: inline-block;
        position: relative;
        vertical-align: top;
        overflow: hidden;

      }

      .x-axis,
      .y-axis {
        font: 16px sans-serif;
      }

      .axis-label {
        font: 18px sans-serif;
      }

      .chart-title {
        font: 24px sans-serif;
      }

      .x-axis .tick:first-of-type text {
        fill: none;
      }

      .body {
        display: flex;
      }

      .chart-group-container {
        margin: 10px 20px 20px 20px;
      }

      .controls-container {
        display: flex;
        flex-direction: column;
      }

      .controls-header {
        color: black;
        padding: 0.5rem 2rem;
        text-align: left;

      }

      .controls-body {
        overflow: auto;
        font-size: 0.8em;
        cursor: default;
      }

      .slidecontainer {
        text-align: left;
        margin: 10px;
        font-family: sans-serif;
        font-size: 14px;
      }

      #slider-x-range {
        vertical-align: bottom;
      }

    </style>
    <title>Document</title>
  </head>

  <body>
    <div class="chart-group-container">
      <div class="row">
        <div class="six columns">
          <div class="chart-container">
            <div class="viz">

            </div>
          </div>
        </div>
        <div class="six columns"></div>
        <div class="controls-container">
          <div class="controls-header">UI Controls</div>
          <div class="controls-body">
            <div class="slider-label">Adjust x axis</div>
            <div class="slidecontainer">
              <span>10</span>
              <input type="range" min="10" max="100" value="1" id="slider-x-range">
              <span>100</span>
            </div>

          </div>
        </div>
      </div>


      <script src="https://d3js.org/d3.v5.min.js"></script>
      <script>
        //let sinWave = Math.sin(x)

        let range = function(start, stop, step) {
          step = step || 1;
          let arr = []
          for (let i = start; i < stop; i += step) {
            arr.push(i);
          }
          return arr;
        }

        let generateSinWave = function(x) {
          let y = []
          x.forEach(function(i) {
            y.push(Math.sin(i))
          });
          return y;
        }

        const generateData = (n) => {
          x = range(0, n, 1)
          y = generateSinWave(x)

          let labels = ['x', 'y']

          let data = []
          for (let i = 0; i < x.length; i++) {
            data.push({
              x: x[i],
              y: y[i]
            })
          }
          return data;
        }

      </script>
      <script>
        let margin = {
            top: 50,
            right: 30,
            bottom: 30,
            left: 100
          },
          width = 800 - margin.left - margin.right
        height = 400 - margin.top - margin.bottom;

        let xScale = d3.scaleLinear()
          .range([0, width])

        let yScale = d3.scaleLinear()
          .range([height, 0])
          .nice()


        let drawLine = d3.line()
          .x(d => xScale(d.x))
          .y(d => yScale(d.y))
          .curve(d3.curveBasis);


        let svg = d3.select('.viz')
          .append('svg')
          .attr("viewBox", `0 0 ${width + margin.left + margin.right} ${height + margin.top + margin.bottom}`)
          .attr("preserveAspectRatio", "xMinYMin meet")
          //.attr('width', `${width + margin.left + margin.right}px`)
          //.attr('height', `${height + margin.top + margin.bottom}px`)
          //.classed("svg-content", true);
          .append('g')
          .attr('class', 'line-chart-container')
          .attr('transform', `translate(${margin.left}, ${margin.top})`);

        /* d3.select(".line-chart-container")
                  .attr("style", "outline: thin solid black;") 
                  .attr("margin-right", "102px") */



        const chartGroup = svg.append('g').attr('class', 'line-chart')

        // Draw x axis
        const xAxis = d3.axisBottom(xScale).tickSizeOuter(0);

        const xAxisDraw = svg
          .append('g')
          .attr('class', 'x-axis')
          //.style('font', '14px sans-serif')
          .attr('transform', `translate(0, ${height / 2})`)
          .call(xAxis);

        const yAxis = d3
          .axisLeft(yScale)
          .ticks(10)
        //.tickSizeInner(-width);

        const yAxisDraw = svg
          .append('g')
          .attr('class', 'y-axis')
          .call(yAxis);

        // x axis label
        svg.append('text')
          .attr('class', 'axis-label')
          .attr('text-anchor', 'end')
          .text('X axis')
          .attr('x', width)
          .attr('y', height - margin.bottom + 50)

        // y axis label
        svg.append('text')
          .attr('class', 'axis-label')
          .attr('text-anchor', 'end')
          .attr('transform', 'rotate(-90)')
          .attr('x', margin.top + 50 - (height / 2))
          .attr('y', margin.left - 160)
          .text('Y axis')

        // Draw Header
        const header = svg
          .append('g')
          .attr('class', 'chart-title')
          .attr('transform', `translate(${width / 2 - 75}, ${margin.top - 75})`)
          .append('text')

        header.append('tspan').text('Sine wave')

        function buildLine(data) {

          xScale.domain([d3.min(data, d => d.x), d3.max(data, d => d.x)])

          yScale.domain([d3.min(data, d => d.y), d3.max(data, d => d.y)])

          let path = chartGroup
            .selectAll('.line-series')
            .data([data]);

          path = path.enter().append('path')
            .attr('class', "line-series")
            .attr("d", d => drawLine(d))
            .style('stroke', 'dodgerblue')
            .style('stroke-width', 2)
            .style('fill', 'none')
            .style('opacity', 1)
            .merge(path)

          path.transition().duration(500).attr("d", d => drawLine(d))
        }

        let xRangeSlider = document.getElementById('slider-x-range');
        xRangeSlider.min = 10;
        xRangeSlider.max = 100;

        let data = generateData(xRangeSlider.value)

        buildLine(data)

        d3.select('#slider-x-range')
          .on("change", d => {
            data = generateData(xRangeSlider.value)
            buildLine(data)
          });

      </script>
  </body>

</html>

As you’ll find out, the transition is not what you probably expect: that’s an unrelated issue, the interpolation of the d property string (you can find more info here).

We are here to answer your question about How to update data in d3 chart with an onchange slider event? - If you find the proper solution, please don't forgot to share this with your team members.

Related Posts

Tutorial Guruji