Canvas Rendering Anomalies in Some Cases
When attempting to display an ECharts component after the page has finished rendering, the canvas may fail to render. Code example:
<div>
{dataType === DataTypeEnum.TABLE ? (
<div>
<Table columns={columns} dataSource={dataSource} loading={loading} />
</div>
) : (
<ReactEcharts
option={option}
style={{ height: 298 }}
/>
)}
</div>
When updating dataType to display the ECharts component, the chart does not appear. Inspecting the element in DevTools reveals that no corresponding canvas node exists in the DOM.
Same problem here. Version 3.0.3 definitely broke things! v3.0.2 works just fine!
@LPlusM @timashev Can help to debug this issue? release history here.
@hustcc Here is the DashboardChart component from my apps (React 18.3.1, lodash 4.17.21, ECharts 5.6, echarts-for-react 3.0.2 – works fine, 3.0.3 – renders nothing):
// sample:
<DashboardChart counts={{ new: 9, level1: 8, level2: 10, level3: 7, level4: 4, total: 38 }}/>
// DashboardChart.tsx
import { forwardRef, memo, useEffect, useMemo, useRef } from 'react';
import PropTypes, { type Validator } from 'prop-types';
import map from 'lodash/map';
import sum from 'lodash/sum';
import transform from 'lodash/transform';
import toSafeInteger from 'lodash/toSafeInteger';
import type { EChartsOption, ResizeOpts } from 'echarts';
import type EChartsReactCore from 'echarts-for-react/lib/core';
import useCombinedRefs from './useCombinedRefs';
import Chart from './Chart';
const ChartsStyles: EChartsOption = {
textStyle: {
fontFamily: 'Roboto'
}
} as const;
type MySkillsCounts = {
new: number;
level1: number;
level2: number;
level3: number;
level4: number;
total: number;
}
const MY_SKILLS_COUNT_KEYS = ['new', 'level1', 'level2', 'level3', 'level4'] as const;
const labels = [
'Newly inferred',
'Basic Understanding',
'Working Experience',
'Extensive Experience',
'Subject Matter Expert'
] as const;
const styles = [
{ color: '#b500b5' },
{ color: '#95d2ff' },
{ color: '#0294ff' },
{ color: '#0073e4' },
{ color: '#104b94' }
] as const;
const ECHART_RESIZE_DEFAULT: ResizeOpts = { width: 'auto', height: 'auto' } as const;
type DashboardChartProps = {
counts?: Partial<MySkillsCounts> | null;
}
const DashboardChartPropTypes = {
counts: PropTypes.object as Validator<MySkillsCounts>
};
const DashboardChart = forwardRef<EChartsReactCore, DashboardChartProps>(({
counts
}, ref) => {
const innerRef = useRef<EChartsReactCore>(null);
const chartRef = useCombinedRefs<EChartsReactCore>(ref, innerRef);
const [values, newCount, total] = useMemo(() => {
const vals = MY_SKILLS_COUNT_KEYS.map((key) => counts ? toSafeInteger(counts[key]) : 0);
return [vals, counts ? toSafeInteger(counts.new) : 0, sum(vals)];
}, [counts]);
useEffect(() => {
if (!innerRef.current) return;
const echartInstance = innerRef.current.getEchartsInstance();
echartInstance.setOption({
...ChartsStyles,
legend: {
bottom: '6.5%',
padding: [0, 16],
formatter: newCount >= 1 ? undefined : '{name}',
data: [
...transform(values, (result, value, idx) => {
if (idx >= 1 && value >= 1) result.push(labels[idx]);
}, [] as string[]),
...values[0] >= 1 ? [labels[0]] : []
],
selectedMode: false,
itemWidth: 16,
itemHeight: 16,
textStyle: {
fontWeight: 500,
fontSize: 15,
color: 'rgba(0, 0, 0, 0.69)'
}
},
series: {
silent: true,
name: 'skills',
type: 'pie',
clockwise: newCount >= 1,
radius: ['33%', '48%'],
center: ['50%', '25.5%'],
avoidLabelOverlap: false,
label: { show: false },
labelLine: { show: false },
data: [
...map(values, (value, idx) => value < 1 ? {} : {
value,
name: labels[idx],
itemStyle: styles[idx]
}),
total < 1 ? {
value: 1,
name: 'empty',
itemStyle: { color: 'rgba(32, 76, 190, 0.12)' }
} : {}
]
}
}, true);
echartInstance.resize(ECHART_RESIZE_DEFAULT);
}, [values, newCount, total]);
return <Chart ref={chartRef} option={ChartsStyles}/>;
});
DashboardChart.displayName = 'DashboardChart';
DashboardChart.propTypes = DashboardChartPropTypes;
export default memo(DashboardChart);
// Chart.tsx
import { forwardRef, memo } from 'react';
import PropTypes, { type Validator } from 'prop-types';
import ReactEChartsCore from 'echarts-for-react/lib/core';
import type { EChartsOption } from 'echarts';
import * as echarts from 'echarts/core';
import { LineChart, BarChart, PieChart, GraphChart } from 'echarts/charts';
import {
GridComponent, PolarComponent, GraphicComponent, TooltipComponent, TitleComponent,
MarkPointComponent, MarkLineComponent, LegendComponent
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
echarts.use([
TitleComponent, TooltipComponent, GraphicComponent, GridComponent, PolarComponent, MarkPointComponent,
MarkLineComponent, LegendComponent, LineChart, BarChart, PieChart, GraphChart, CanvasRenderer
]);
export type EventHandlers = Record<string, Function>;
type ChartProps = {
className?: string;
option?: EChartsOption;
onEvents?: EventHandlers;
};
const ChartPropTypes = {
className: PropTypes.string,
option: PropTypes.object as Validator<EChartsOption>,
onEvents: PropTypes.object as Validator<EventHandlers>
};
const Chart = forwardRef<ReactEChartsCore, ChartProps>(({
className,
option,
onEvents
}, ref) => option ? (
<ReactEChartsCore
ref={ref}
echarts={echarts}
option={option}
onEvents={onEvents}
className={className}
notMerge
lazyUpdate
theme="light"
/>
) : null);
Chart.displayName = 'Chart';
Chart.propTypes = ChartPropTypes;
export default memo(Chart);
// useCombinedRefs.ts
import { useCallback, type Ref, type RefCallback, type MutableRefObject } from 'react';
import forEach from 'lodash/forEach';
import isFunction from 'lodash/isFunction';
const useCombinedRefs = <T>(...refs: (Ref<T> | undefined)[]): RefCallback<T> =>
useCallback((element: T | null) => {
forEach(refs, (ref) => {
if (!ref) return;
if (isFunction(ref)) {
ref(element);
} else {
(ref as MutableRefObject<T | null>).current = element;
}
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, refs);
export default useCombinedRefs;
Although I haven't encountered rendering failures, I have experienced the issue of double rendering. Using the same version 3.0.3 of echarts-for-react and version 5.6.0 of ECharts, the same code has been running for at least six months without problems. However, recently, it suddenly started rendering twice during initialization. Additionally, when I log information in onChartReady, the log output only appears during the second rendering.
{data1.length === 0 && data2.length === 0 ? (
<Empty />
) : (
<ReactEcharts
option={options1}
style={{ height: 'calc(100% - 30px)' }}
onChartReady={(chart) => {
console.log('onChartReady', chart);
}}
/>
)}
@timashev @XijueYZ I have deprecated v3.0.3. Can help to debug which pr cause this bug.
- https://github.com/hustcc/echarts-for-react/pull/464
- https://github.com/hustcc/echarts-for-react/pull/520
- https://github.com/hustcc/echarts-for-react/pull/444
I have experienced the issue of double rendering.
Can you try disabling strict mode in react?
In dev env, strict mode of react compiler does double rendering, maybe disabling it when using canvas for dev env will solve your issue. There should be no issue on prod build with strict mode.
@hustcc Here is the DashboardChart component from my apps (React 18.3.1, lodash 4.17.21, ECharts 5.6, echarts-for-react 3.0.2 – works fine, 3.0.3 – renders nothing):
Hi @timashev, Your also the same issue disable strict mode you can see your chart
enable strict mode render nothing
Here link- try yourself - https://stackblitz.com/edit/react-ts-ikzdr1c6?file=DashboardChart.tsx,App.tsx,Chart.tsx,useCombinedRefs.ts,package.json,index.tsx
Actually, this is not an issue with echart but the upstream three.js or d3.js library used by them to create the canvas, rendering the canvas again before disposing it properly looses its context. If I get time, I will find the issue and tag it, so y'all can read it.
StrictMode has nothing to do with prod build of you react app.
tagging @LPlusM @XijueYZ so you can check in your code too.
@imsuvesh Alright. However v3.0.2 works even with <StrictMode>. So it is not purely echarts/three.js/d3.js issue, it has something to do with the changes introduced by this PR: https://github.com/hustcc/echarts-for-react/pull/464