react-apexcharts icon indicating copy to clipboard operation
react-apexcharts copied to clipboard

Synced Charts are getting too many Options overwritten.

Open cdeutsch opened this issue 4 years ago • 12 comments

I have multiple <ApexChart>'s that share a group in order to synchronize the tooltips.

image

When I hover over "MAX(value)", they'll all get the same title

image

I tracked it down to multiple issues.

1. Something in apexcharts.js is modifying ApexCharts.props.options directly.

On the 2nd render of ApexCharts, this.chart.updateOptions(this.getConfig()) is being called because the prevOptions !== currentOptions https://github.com/apexcharts/react-apexcharts/blob/7345fdca622358f181aa64e1cdfc76f592f4bdf0/src/react-apexcharts.jsx#L116

When I pause the debugger, something has added xaxis.convertedCatToNumeric: false to my props.options

To get around this you should probably: A. Do a deep clone of options in getConfig(). B. Deep clone options in apexcharts.js. C. Stop modifying options in apexcharts.js

2. _updateOptions in apexcharts.js shouldn't update so many options for Synced Charts

https://github.com/apexcharts/apexcharts.js/blob/433fccce57a1dfd3ff1b67a731818189146ca7ca/src/modules/helpers/UpdateHelpers.js#L63

What's happening in my example, is you're taking the options for the bottom chart and applying it to all the charts (minus the series).

I'm not sure the reasoning for doing this, but options like title.text definitely should not be synced, and I imagine many others.

cdeutsch avatar May 27 '20 16:05 cdeutsch

I am having this issue too.

On the initial render everything looks great, but React re-renders when I scroll and then all my options are sync'd across the multiple graphs which means colours, axis labels and everything become the same.

cstrat avatar Apr 19 '21 00:04 cstrat

Ok I worked out how to fix this for my example, unsure if it is the same thing that is happening to you.

I wasn't storing the graph options in React State. I had a generator function outputting the options. Unsure why the behaviour didn't allow for this to work after the initial render... Anyway, moving this logic into a useEffect where I set an object to have the state output from the generator it stopped the issue completely.

cstrat avatar Apr 19 '21 03:04 cstrat

Ok I worked out how to fix this for my example, unsure if it is the same thing that is happening to you.

I wasn't storing the graph options in React State. I had a generator function outputting the options. Unsure why the behaviour didn't allow for this to work after the initial render... Anyway, moving this logic into a useEffect where I set an object to have the state output from the generator it stopped the issue completely.

@cstrat I tried your approach to pass the option state set in useEffect but still same issue after react re-renders. Am I missing something maybe? could you create a small codebox example if possible?

karimkozman avatar Feb 07 '22 14:02 karimkozman

I think that @cstrat likely made it so that Apex is mutating React state now, so the mutation persists between renders.

I can also confirm that under the hood Apex core mutates the options.xAxis.type to numeric. In react devtools, it "looks" like we are passing this prop even though we never did!

Screen Shot 2022-04-26 at 12 10 35 PM

It turns out Apex actually mutates the options, which triggers your wrapper to update and runs componentDidUpdate with the "numeric" axis, which blocks the page as all of our linked charts re-render, and makes them look corrupt since they are not intended to visualize on "numeric" scale.

On our end we pass a key prop to the charts which changes together with the dataset. We also copy pasted your source code into our repo and commented out the entire componentDidUpdate, basically forcing fresh unmount and mounts every time. We had to pass the key to force remounts on our end, and also disable componentDidUpdate in this library's code in order to prevent the updates triggered when Apex erroneously mutates the props.

This is related to https://github.com/apexcharts/apexcharts.js/issues/914 and https://github.com/apexcharts/apexcharts.js/issues/623

joshribakoff-sm avatar Apr 26 '22 19:04 joshribakoff-sm

Also confirming that simply deep cloning in getConfig() solves the problem as @cdeutsch suggests. This should be an easy PR

joshribakoff avatar Apr 27 '22 01:04 joshribakoff

Easy PR and 3 years late, the problem is still here. At this point, I think I am diching apexchart...

Andynopol avatar Dec 15 '22 19:12 Andynopol

The easiest solution I have managed to work is just simply extract all options into separate variable: var options = { colors: ['#fff'], chart: { id: 'earnings', group: 'results', type: 'area', foreColor: '#fff', zoom: { enabled: false, }, }, dataLabels: { enabled: false, }, stroke: { curve: 'smooth', width: 3, }, tooltip: { enabled: true, shared: false, theme: 'dark', x: { show: false, format: 'ddd' }, y: { formatter: (value) => ``${value} $``, }, }, grid: { show: false, xaxis: { lines: { show: false, }, }, yaxis: { lines: { show: false, }, }, }, xaxis: { type: 'datetime', axisBorder: { show: false, }, axisTicks: { show: false, }, labels: { show: false, }, }, yaxis: { show: false, opposite: true, }, };

And then make new state to hold it: const [chartOptions, setChartOptions] = useState(options);

Later on just pass it to your render Component:

<ApexCharts options={chartOptions} series={series} type='line' height={100} key={0} />

In my case it worked perfectly and I had to make this only on the one of my synced charts.

kamzak avatar Jan 04 '23 12:01 kamzak

@kamzak thanks for sharing, however lots of us actually do need the chart options to be dynamically updated. Im not sure why the state is necessary since you dont appear to ever update it. The bug occurs when updating the options (it wouldnt matter how the initial state is stored or whether it came from state vs props, that is not how react works)

joshribakoff avatar Jan 04 '23 15:01 joshribakoff

@kamzak your solution works for me, but apexcharts should fix this issue.

aniket-pande avatar May 25 '23 12:05 aniket-pande

The easiest solution I have managed to work is just simply extract all options into separate variable: var options = { colors: ['#fff'], chart: { id: 'earnings', group: 'results', type: 'area', foreColor: '#fff', zoom: { enabled: false, }, }, dataLabels: { enabled: false, }, stroke: { curve: 'smooth', width: 3, }, tooltip: { enabled: true, shared: false, theme: 'dark', x: { show: false, format: 'ddd' }, y: { formatter: (value) =>${value} $, }, }, grid: { show: false, xaxis: { lines: { show: false, }, }, yaxis: { lines: { show: false, }, }, }, xaxis: { type: 'datetime', axisBorder: { show: false, }, axisTicks: { show: false, }, labels: { show: false, }, }, yaxis: { show: false, opposite: true, }, };

And then make new state to hold it: const [chartOptions, setChartOptions] = useState(options);

Later on just pass it to your render Component:

<ApexCharts options={chartOptions} series={series} type='line' height={100} key={0} />

In my case it worked perfectly and I had to make this only on the one of my synced charts.

This one worked, but they neeed to fix this

Inna-7 avatar Mar 05 '24 12:03 Inna-7