[Chartjs]-Zoom and Pan in react-chartjs-2

6👍

In order to add Zoom and Pan capabilities to your chart components based on react-chartjs-2, you can follow the steps as shown below:

Step 1: you need to install chartjs-plugin-zoom

$ npm install chartjs-plugin-zoom

Step 2: Import chartjs-plugin-zoom in your chart component

import 'chartjs-plugin-zoom';

Step 3: Enable zoom and pan in the ChartJS component options

        zoom: {
          enabled: true,
          mode: 'x',
        },
        pan: {
          enabled: true,
          mode: 'x',
        },

That’s it. So now your chart component should look like this:

import React from 'react';
import { Line } from 'react-chartjs-2';
import 'chartjs-plugin-zoom';

export default function TimelineChart({ dailyDataSets }) {
  const lineChart = dailyDataSets[0] ? (
    <Line
      data={{
        labels: dailyDataSets.map(({ date }) => date),
        datasets: [
          {
            data: dailyDataSets.map((data) => data.attr1),
            label: 'First data set',
            borderColor: 'red',
            fill: true,
          },
          {
            data: dailyDataSets.map((data) => data.attr2),
            label: 'Second data set',
            borderColor: 'green',
            fill: true,
          },
        ],
      }}
      options={{
        title: { display: true, text: 'My Chart' },
        zoom: {
          enabled: true,
          mode: 'x',
        },
        pan: {
          enabled: true,
          mode: 'x',
        },
      }}
    />
  ) : null;

  return <div>{lineChart}</div>;
}

Notes:

  1. You don’t have to install hammerjs explicitly, as it will be automatically included by installing chartjs-plugin-zoom as its dependency, see below:
$ npm ls
...
├─┬ chartjs-plugin-zoom@0.7.7
│ └── hammerjs@2.0.8
...
  1. One way to zoom as an example (at least for Mac), you can move your mouse pointer into the chart area, and then scroll your mouse down or up. Once zoomed in, you can keep your mouse clicked while dragging left or right.

2👍

There’s a syntax error under pan object for enabled attribute.

You’ve mistakenly put = instead of :

Replace this:

pan:{
  enabled=true,
  ...
},

With:

pan:{
  enabled:true,
  ...
},

And also as @Jun Bin suggested:

Install hammerjs as:

npm install hammerjs --save

And in your component, import it as:

import Hammer from "hammerjs";

0👍

you imported the wrong hammer it should be from “hammerjs”;

0👍

You need to add import 'chartjs-plugin-zoom'; and then add zoom options into options.plugins.zoom, like:

const options = {
  plugins: {
    zoom: {
      pan: {
        enabled: true,
        mode: 'x',
      },
      zoom: {
        enabled: true,
        drag: true,
        mode: 'xy'
      }
    }
  }
};

0👍

I am trying to do this in a NextJS Project. But to no success so far.
I am using a timeseries plot with date-fns/locale for German and English and keep getting this error:

Cannot convert a Symbol value to a string
TypeError: Cannot convert a Symbol value to a string at TypedRegistry.register (webpack-internal:///./node_modules/chart.js/dist/chart.esm.js:4802:50) at Registry._exec (webpack-internal:///./node_modules/chart.js/dist/chart.esm.js:4927:21) at eval (webpack-internal:///./node_modules/chart.js/dist/chart.esm.js:4919:16) at each (webpack-internal:///./node_modules/chart.js/dist/chunks/helpers.segment.js:233:10) at eval (webpack-internal:///./node_modules/chart.js/dist/chart.esm.js:4917:70) at Array.forEach (<anonymous>) at Registry._each (webpack-internal:///./node_modules/chart.js/dist/chart.esm.js:4912:15) at Registry.add (webpack-internal:///./node_modules/chart.js/dist/chart.esm.js:4870:10) at Function.value [as register] (webpack-internal:///./node_modules/chart.js/dist/chart.esm.js:6192:16) at eval (webpack-internal:///./components/Charts/PortfolioPriceLineDual.jsx:39:45) at Module../components/Charts/PortfolioPriceLineDual.jsx (https://dev.domain.de/_next/static/chunks/components_Charts_PortfolioPriceLineDual_jsx.js:7758:1) at Module.options.factory (https://dev.domain.de/_next/static/chunks/webpack.js?ts=1653499440538:655:31) at __webpack_require__ (https://dev.domain.de/_next/static/chunks/webpack.js?ts=1653499440538:37:33) at Function.fn (https://dev.domain.de/_next/static/chunks/webpack.js?ts=1653499440538:310:21)

My Component:


import { Line } from 'react-chartjs-2'

import 'chartjs-adapter-date-fns'
import { de, enGB, ja } from 'date-fns/locale'

import dynamic from 'next/dynamic'
import 'chart.js/auto'
import { useRouter } from 'next/router'
import { Chart } from 'chart.js'

// import zoomPlugin from 'chartjs-plugin-zoom';
const zoomPlugin = dynamic(() => import('chartjs-plugin-zoom'), {
  ssr: false,
})
Chart.register(zoomPlugin);

const PortfolioPriceLineDual = ({
  title,
  data,
  unit,
  axesOptions,
  showLegend = true,
}) => {
  const totalDuration = 5000
  const delayBetweenPoints = totalDuration / data.datasets[0].data.length
  // const animation =
  const { locale } = useRouter()
  let format
  switch (locale) {
    case 'de-DE':
      format = de
      break
    case 'en-US':
      format = enGB

      break
    case 'ja-JP':
      format = ja

      break
    default:
      break
  }

  return (
    <Line
      data={data}
      options={{
        responsive: true,
        // maintainAspectRatio: true,
        // aspectRatio: 16 / 9,
        resizeDelay: 5,
        animation: {
          x: {
            type: 'number',
            easing: 'linear',
            duration: delayBetweenPoints,
            from: NaN, // the point is initially skipped
            delay: (ctx) => {
              if (ctx.type !== 'data' || ctx.xStarted) {
                return 0
              }
              ctx.xStarted = true
              return ctx.index * delayBetweenPoints
            },
          },
          y: {
            type: 'number',
            easing: 'linear',
            duration: delayBetweenPoints,
            from: (ctx) => {
              return ctx.index === 0
                ? ctx.chart.scales.y.getPixelForValue(100)
                : ctx.chart
                    .getDatasetMeta(ctx.datasetIndex)
                    .data[ctx.index - 1].getProps(['y'], true).y
            },
            delay: (ctx) => {
              if (ctx.type !== 'data' || ctx.yStarted) {
                return 0
              }
              ctx.yStarted = true
              return ctx.index * delayBetweenPoints
            },
          },
          y1: {
            type: 'number',
            easing: 'linear',
            duration: delayBetweenPoints,
            from: (ctx) => {
              return ctx.index === 0
                ? ctx.chart.scales.y.getPixelForValue(100)
                : ctx.chart
                    .getDatasetMeta(ctx.datasetIndex)
                    .data[ctx.index - 1].getProps(['y'], true).y
            },
            delay: (ctx) => {
              if (ctx.type !== 'data' || ctx.yStarted) {
                return 0
              }
              ctx.yStarted = true
              return ctx.index * delayBetweenPoints
            },
          },
        },
        interaction: {
          mode: 'index',
          intersect: false,
        },
        scales: {
          x: {
            type: 'time',

            time: {
              unit: 'year',
              displayFormats: {
                quarter: 'yyyy',
              },
              tooltipFormat: 'MMMM yyyy',
            },
            adapters: {
              date: {
                locale: format,
              },
            },
            ticks: {
              align: 'start',
              color: '#122a42',
              font: {
                size: 14,
                weight: 'bold',
              },
            },
            grid: {
              display: true,
              drawBorder: false,
              drawOnChartArea: true,
              drawTicks: true,
            },
          },
          y: {
            type: 'logarithmic',

            grid: {
              display: true,
              drawBorder: false,
              drawOnChartArea: true,
              drawTicks: true,
            },
            ticks: {
              color: '#122a42',
              align: 'end',
              font: {
                size: 10,
                weight: 'normal',
              },
              // Include a dollar sign in the ticks
              // stepSize: 1000,
              callback: function (value) {
                // callback: function (value, index, ticks) {
                return `${new Intl.NumberFormat(locale, axesOptions).format(
                  value
                )}`
              },
            },
          },
          y1: {
            type: 'linear',
            display: true,
            position: 'right',

            // grid line settings
            grid: {
              drawOnChartArea: false, // only want the grid lines for one axis to show up
            },
            ticks: {
              color: '#122a42',
              align: 'end',
              font: {
                size: 10,
                weight: 'normal',
              },
              // Include a dollar sign in the ticks
              // stepSize: 1000,
              callback: function (value) {
                // callback: function (value, index, ticks) {
                return `${new Intl.NumberFormat(locale, axesOptions).format(
                  value
                )}`
              },
            },
          },
        },
        zoom: {
          enabled: true,
          mode: 'x',
        },
        pan: {
          enabled: true,
          mode: 'x',
        },
        plugins: {
          zoom: {
            enabled: true,
            mode: 'x',
          },
          pan: {
            enabled: true,
            mode: 'x',
          },
          // zoom: {
          //   zoom: {
          //     wheel: {
          //       enabled: true,
          //     },
          //     pinch: {
          //       enabled: true,
          //     },
          //     mode: 'x',
          //   },
          // },
          title: {
            display: true,
            color: '#151C30',
            font: {
              size: 26,
              weight: 'bold',
              style: 'normal',
            },
            padding: {
              bottom: 10,
            },
            text: `${title}`,
          },
          tooltip: {
            enabled: true,
            backgroundColor: '#122a42',
            itemSort: function (a, b) {
              return b.raw - a.raw
            },
            callbacks: {
              label: function (context) {
                let label = context.dataset.label || ''

                if (label) {
                  label += ': '
                }
                if (context.parsed.y !== null) {
                  label += `${new Intl.NumberFormat(locale, axesOptions).format(
                    context.parsed.y
                  )} ${unit}`
                }
                return label
              },
            },
          },
          legend: {
            position: 'bottom',
            labels: {
              // This more specific font property overrides the global property
              color: '#151C30',
              font: {
                size: 12,
                weight: 'light',
              },
            },
          },
        },
      }}
    />
  )
}

export default PortfolioPriceLineDual

0👍

I was getting window is undefined error in next.js so I used solved it by using useEffect hook –

import { Chart } from "chart.js";

useEffect(() => {
if (typeof window !== "undefined")
  import("chartjs-plugin-zoom").then((plugin) => {
    Chart.register(plugin.default);
  });
}, []);

Leave a comment