Zoom and Pan in react-chartjs-2
Asked Answered
S

6

6

I have recently implemented chart display using react-chartjs-2 (https://github.com/jerairrest/react-chartjs-2)

I want to enable zooming and panning feature so that it will be more user-friendly in touch based screens. To implement this features, I installed react-hammerjs and chartjs-plugin-zoom.

import {Chart, Line} from 'react-chartjs-2';
import Hammer from 'react-hammerjs';
import zoom from 'chartjs-plugin-zoom'

And I registered the plugin

componentWillMount(){
    Chart.plugins.register(zoom)
}

And the render method goes as follows:

render(){
    return <Line data={data} options={options} />
}

Pan and Zoom options:

pan:{
    enabled=true,
    mode:'x'
},
zoom:{
    enabled:true,
    drag:true,
    mode:'xy'
}

I guess this is the correct method to implement. Unfortunately, the above implementation did not work. I will be really grateful if some of you guys already implemented Zooming and Panning using react-chartjs-2 plugin, please share if how you achieved these functionalities. Or you could point out the problem in my code above.

Stereoscope answered 21/9, 2017 at 14:4 Comment(0)
C
7

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
...
├─┬ [email protected]
│ └── [email protected]
...
  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.
Camper answered 23/5, 2020 at 21:55 Comment(1)
I tried to follow the steps and got below error: ./node_modules/chartjs-plugin-zoom/dist/chartjs-plugin-zoom.esm.js Module not found: Can't resolve 'chart.js/helpers' in 'E:\code\iTools\ClientApp\node_modules\chartjs-plugin-zoom\dist', where goes wrong?Vesuvian
P
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";
Pickard answered 26/2, 2019 at 11:54 Comment(0)
G
1

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);
  });
}, []);
Globulin answered 22/3, 2023 at 10:53 Comment(0)
S
0

you imported the wrong hammer it should be from "hammerjs";

Stump answered 9/10, 2017 at 8:0 Comment(1)
make sure you have npm install hammerjs and webpacked itStump
S
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'
      }
    }
  }
};
Sparteine answered 4/8, 2020 at 7:43 Comment(0)
A
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

Allottee answered 25/5, 2022 at 17:32 Comment(1)
Hey, I know it's been a while, but I come across the same thing. What I did was: const ZoomPlugin = dynamic(() => import('chartjs-plugin-zoom').then((mod) => Chart.register(mod.default)), { ssr: false }) And then added in my component <ZoomPlugin />. I'm not sure, but there could be a better way to do it through next.config.js, but couldn't make it work..Fourcycle

© 2022 - 2024 — McMap. All rights reserved.