1

I've run into an issue with ChartJS's Pan/Zoom plugin event onPan not firing when the Chart configuration is assigned from inside of a useEffect hook. I am listening to the pan & zoom event and firing an event bus accordingly to synchronize multiple charts' zoom/pan bounds with each other. The problem arises with only onPan as onZoom works perfectly fine.

Here's the general important code (this is all in a functional component):

useEffect(() => {
    if(applicableEffectCondition.isMet) {
      return; // to prevent infinite loop
    }

    // do code magic ...

    function dispatchInteractEvent(reference: MutableRefObject < any | undefined > ) {
        dispatch({
            type: "@@charts/INTERACT",
            payload: {
                sender: reference.current.id,
                x: {
                    min: reference.current.scales.x.min,
                    max: reference.current.scales.x.max,
                },
            }
        });
    }

    const options: ChartOptions = {
        // ...

        plugins: {

            zoom: {
                limits: {
                    // minimum X value & maximum X value
                    x: {
                        min: Math.min(...labelsNumber),
                        max: Math.max(...labelsNumber)
                    },
                },
                zoom: {
                    wheel: {
                        enabled: true,
                        speed: 0.1,
                    },
                    pinch: {
                        enabled: true
                    },
                    mode: 'x',
                    onZoom: () => {
                        alert("I am zooming!");
                        dispatchInteractEvent(chartRef);
                    }
                },
                pan: {
                    enabled: true,
                    mode: 'x',

                    rangeMin: {
                        y: allDataMin
                    },
                    rangeMax: {
                        y: allDataMax
                    },

                    onPan: () => {
                        alert("I am panning!");
                        dispatchInteractEvent(chartRef);
                    }
                }
                as any
            }
        },
    };

    setChartOptions(options); // use state

    const data: ChartData = {
        labels: labelsString,
        datasets: datasetState.getDatasets(),
    };

    setChartData(data); // use state
}, [applicableChannels]);

useBus(
    '@@charts/INTERACT',
    (data) => {
        const payload = data.payload;

        if (payload.sender === chartRef.current.id) {
            return;
        }

        // update chart scale/zoom/pan to be in sync with all others
        chartRef.current.options.scales.x.min = payload.x.min;
        chartRef.current.options.scales.x.max = payload.x.max;

        chartRef.current.update();
    },
    [],
);

return (

      <Chart type={"line"} ref={chartRef} options={chartOptions} data={chartData} />
);

As you can see in the configuration, I put two alerts to notify me when the events are fired. "I am zooming!" is fired perfectly fine, but "I am panning!" is never fired. Nor is any other pan-specific event.

This is a very odd issue. Any help would be appreciated! Thanks!

Jacob
  • 409
  • 6
  • 22
  • Hi Jacob. Unfortunately, the code you've provided is both too little and too much to help solve your problem. Please provide a [minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) that the community can use to diagnose your problem. Right now, there's too much irrelevant detail, but many relevant parts are also omitted. Try creating a working example on e.g. [codesandbox](https://codesandbox.io/s/recursing-shirley-s7pxc6) so people can see your problem in action and help you to fix it. Thanks! – thisisrandy Mar 07 '22 at 21:19

2 Answers2

1

The issue has been resolved.

When a chart is initialized, HammerJS is initialized and adds listeners for panning/zooming to the canvas. The issue happens when the chart is initialized with a configuration before the configuration is created (setting the configuration to an empty state), and when the real configuration is updated (with the zoom/pan plugin), the state of enabled in the pan object is not updated correctly, so panning becomes disabled.

The solution is to wait to display the chart React Element until the configuration is available to populate it.

This issue was really happening because the configuration was being set in a useEffect hook, making it available in a later event loop tick than the actual JSX element was being created in.

Jacob
  • 409
  • 6
  • 22
0

When you use useEffect(()=>{},[xxx]) hooks with [xxx] as the second parameter, you should know that only when xxx has changed it will run the function. In your case, I didn't see any "applicableChannels" in your code, so it's hard to know where the problem is.

greybeard
  • 2,249
  • 8
  • 30
  • 66
czhang xi
  • 51
  • 5