Search code examples
javascriptchart.jschartjs-plugin-zoom

How to do automatic pan with Chart.js


I'm using chart.js to plot data in real time. Currently, as new data points come in and are drawn, the old ones remain which increases the amount of points chart.js has to plot every iteration. I'd like to have a maximum number of visible points, say 20 and after 20 points are on the graph, to pan every iteration. I'm using the zoom and pan plugin. Here's my code right now

if (ticks > 20) {
    flukeChart.pan({x: 1, y: 0}, 'active');
    flukeChart.update();
}

ticks is an integer that get's incremented every time new data comes in. Another thing to note, the X axis is timestamps. I'm using the timestamp from the incoming data source and not calculating the current time in JS. I have a feeling the delta in the .pan method shouldn't be 1 because the x axis isn't integers but timestamps.


Solution

  • You can get the pixel for the values of the timestamps. If you then subtract the first and second visable ones you can pan the chart with that amount of pixels.

    const options = {
      type: 'line',
      data: {
        datasets: [{
          label: '# of Votes',
          data: [],
          borderColor: 'pink'
        }]
      },
      options: {
        scales: {
          x: {
            type: 'time'
          }
        },
      }
    }
    
    const ctx = document.getElementById('chartJSContainer').getContext('2d');
    const chart = new Chart(ctx, options);
    let dataPointsCounter = -1;
    
    const interVal = setInterval(() => {
      chart.data.datasets[0].data.push({
        x: new Date(),
        y: Math.random()
      });
      dataPointsCounter++;
    
      if (dataPointsCounter > 20) {
        const diff = chart.scales.x.getPixelForValue(chart.data.datasets[0].data[dataPointsCounter - 21].x) - chart.scales.x.getPixelForValue(chart.data.datasets[0].data[dataPointsCounter - 20].x)
        chart.pan({
          x: diff
        }, undefined, 'default');
      }
      chart.update();
    }, 500)
    <body>
      <canvas id="chartJSContainer" width="600" height="400"></canvas>
      <script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.6.0/chart.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-zoom/1.1.1/chartjs-plugin-zoom.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
    </body>

    Edit:

    It is not exactly what you want but you can also use the streaming plugin that handles scolling and updating for you:

    const options = {
      type: 'line',
      data: {
        datasets: [{
          label: '# of Votes',
          data: [],
          borderColor: 'pink'
        }]
      },
      options: {
        scales: {
          x: {
            type: 'realtime',
            realtime: {
              duration: 7500,
              refresh: 1000,
              delay: 2000,
              onRefresh: (chart) => {
                chart.data.datasets[0].data.push({
                  x: new Date(),
                  y: Math.random()
                })
              }
            }
          }
        },
      }
    }
    
    const ctx = document.getElementById('chartJSContainer').getContext('2d');
    const chart = new Chart(ctx, options);
    <body>
      <canvas id="chartJSContainer" width="600" height="400"></canvas>
      <script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.6.0/chart.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-zoom/1.1.1/chartjs-plugin-zoom.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
    </body>