Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit dfccf07

Browse filesBrowse files
committed
refactor: prevent unnecessary redrawing
1 parent 09f959f commit dfccf07
Copy full SHA for dfccf07

File tree

2 files changed

+208
-178
lines changed
Filter options

2 files changed

+208
-178
lines changed

‎packages/coreui-react-chartjs/src/CChart.tsx

Copy file name to clipboardExpand all lines: packages/coreui-react-chartjs/src/CChart.tsx
+183-153Lines changed: 183 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,21 @@ import React, {
55
useEffect,
66
useImperativeHandle,
77
useMemo,
8-
useState,
98
useRef,
109
} from 'react'
1110
import classNames from 'classnames'
1211

13-
import Chart, { ChartData, ChartOptions, ChartType, InteractionItem, Plugin } from 'chart.js/auto'
12+
import { Chart as ChartJS, registerables } from 'chart.js'
13+
import type {
14+
ChartData,
15+
ChartOptions,
16+
ChartType,
17+
ChartTypeRegistry,
18+
InteractionItem,
19+
Plugin,
20+
ScatterDataPoint,
21+
BubbleDataPoint,
22+
} from 'chart.js'
1423
import { customTooltips as cuiCustomTooltips } from '@coreui/chartjs'
1524

1625
import assign from 'lodash/assign'
@@ -92,7 +101,7 @@ export interface CChartProps extends HTMLAttributes<HTMLCanvasElement | HTMLDivE
92101
*
93102
* @type {'line' | 'bar' | 'radar' | 'doughnut' | 'polarArea' | 'bubble' | 'pie' | 'scatter'}
94103
*/
95-
type: ChartType
104+
type?: ChartType
96105
/**
97106
* Width attribute applied to the rendered canvas.
98107
*
@@ -107,187 +116,208 @@ export interface CChartProps extends HTMLAttributes<HTMLCanvasElement | HTMLDivE
107116
wrapper?: boolean
108117
}
109118

110-
export const CChart = forwardRef<Chart | undefined, CChartProps>((props, ref) => {
111-
const {
112-
className,
113-
customTooltips = true,
114-
data,
115-
id,
116-
fallbackContent,
117-
getDatasetAtEvent,
118-
getElementAtEvent,
119-
getElementsAtEvent,
120-
height = 150,
121-
options,
122-
plugins = [],
123-
redraw = false,
124-
type,
125-
width = 300,
126-
wrapper = true,
127-
...rest
128-
} = props
129-
130-
const canvasRef = useRef<HTMLCanvasElement>(null)
131-
132-
const computedData = useMemo(() => {
133-
if (typeof data === 'function') {
134-
return canvasRef.current ? data(canvasRef.current) : { datasets: [] }
135-
} else return merge({}, data)
136-
}, [data, canvasRef.current])
137-
138-
const computedOptions = useMemo(() => {
139-
return customTooltips
140-
? merge({}, options, {
141-
plugins: {
142-
tooltip: {
143-
enabled: false,
144-
mode: 'index',
145-
position: 'nearest',
146-
external: cuiCustomTooltips,
147-
},
148-
},
149-
})
150-
: options
151-
}, [data, canvasRef.current, options])
119+
export const CChart = forwardRef<ChartJS | undefined, CChartProps>(
120+
(
121+
{
122+
className,
123+
customTooltips = true,
124+
data,
125+
id,
126+
fallbackContent,
127+
getDatasetAtEvent,
128+
getElementAtEvent,
129+
getElementsAtEvent,
130+
height = 150,
131+
options,
132+
plugins = [],
133+
redraw = false,
134+
type = 'bar',
135+
width = 300,
136+
wrapper = true,
137+
...rest
138+
},
139+
ref,
140+
) => {
141+
ChartJS.register(...registerables)
142+
143+
const canvasRef = useRef<HTMLCanvasElement>(null)
144+
const chartRef = useRef<
145+
| ChartJS<
146+
keyof ChartTypeRegistry,
147+
(number | ScatterDataPoint | BubbleDataPoint | null)[],
148+
unknown
149+
>
150+
| undefined
151+
>()
152+
153+
useImperativeHandle<ChartJS | undefined, ChartJS | undefined>(ref, () => chartRef.current, [
154+
chartRef,
155+
])
156+
157+
const computedData = useMemo(() => {
158+
if (typeof data === 'function') {
159+
return canvasRef.current ? data(canvasRef.current) : { datasets: [] }
160+
}
152161

153-
const [chart, setChart] = useState<Chart>()
162+
return merge({}, data)
163+
}, [canvasRef.current, JSON.stringify(data)])
154164

155-
useImperativeHandle<Chart | undefined, Chart | undefined>(ref, () => chart, [chart])
165+
const computedOptions = useMemo(() => {
166+
return customTooltips
167+
? merge({}, options, {
168+
plugins: {
169+
tooltip: {
170+
enabled: false,
171+
mode: 'index',
172+
position: 'nearest',
173+
external: cuiCustomTooltips,
174+
},
175+
},
176+
})
177+
: options
178+
}, [canvasRef.current, JSON.stringify(options)])
156179

157-
const renderChart = () => {
158-
if (!canvasRef.current) return
180+
const renderChart = () => {
181+
if (!canvasRef.current) return
159182

160-
setChart(
161-
new Chart(canvasRef.current, {
183+
chartRef.current = new ChartJS(canvasRef.current, {
162184
type,
163185
data: computedData,
164186
options: computedOptions,
165187
plugins,
166-
}),
167-
)
168-
}
188+
})
189+
}
169190

170-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
171-
const handleOnClick = (e: any) => {
172-
if (!chart) return
191+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
192+
const handleOnClick = (e: any) => {
193+
if (!chartRef.current) return
173194

174-
getDatasetAtEvent &&
175-
getDatasetAtEvent(
176-
chart.getElementsAtEventForMode(e, 'dataset', { intersect: true }, false),
177-
e,
178-
)
179-
getElementAtEvent &&
180-
getElementAtEvent(
181-
chart.getElementsAtEventForMode(e, 'nearest', { intersect: true }, false),
182-
e,
183-
)
184-
getElementsAtEvent &&
185-
getElementsAtEvent(chart.getElementsAtEventForMode(e, 'index', { intersect: true }, false), e)
186-
}
195+
getDatasetAtEvent &&
196+
getDatasetAtEvent(
197+
chartRef.current.getElementsAtEventForMode(e, 'dataset', { intersect: true }, false),
198+
e,
199+
)
200+
getElementAtEvent &&
201+
getElementAtEvent(
202+
chartRef.current.getElementsAtEventForMode(e, 'nearest', { intersect: true }, false),
203+
e,
204+
)
205+
getElementsAtEvent &&
206+
getElementsAtEvent(
207+
chartRef.current.getElementsAtEventForMode(e, 'index', { intersect: true }, false),
208+
e,
209+
)
210+
}
187211

188-
const updateChart = () => {
189-
if (!chart) return
212+
const updateChart = () => {
213+
if (!chartRef.current) return
190214

191-
if (options) {
192-
chart.options = { ...computedOptions }
193-
}
215+
if (options) {
216+
chartRef.current.options = { ...computedOptions }
217+
}
194218

195-
if (!chart.config.data) {
196-
chart.config.data = computedData
197-
chart.update()
198-
return
199-
}
219+
if (!chartRef.current.config.data) {
220+
chartRef.current.config.data = computedData
221+
chartRef.current.update()
222+
return
223+
}
200224

201-
const { datasets: newDataSets = [], ...newChartData } = computedData
202-
const { datasets: currentDataSets = [] } = chart.config.data
225+
const { datasets: newDataSets = [], ...newChartData } = computedData
226+
const { datasets: currentDataSets = [] } = chartRef.current.config.data
203227

204-
// copy values
205-
assign(chart.config.data, newChartData)
206-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
207-
chart.config.data.datasets = newDataSets.map((newDataSet: any) => {
208-
// given the new set, find it's current match
209-
const currentDataSet = find(
210-
currentDataSets,
211-
(d) => d.label === newDataSet.label && d.type === newDataSet.type,
212-
)
228+
// copy values
229+
assign(chartRef.current.config.data, newChartData)
230+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
231+
chartRef.current.config.data.datasets = newDataSets.map((newDataSet: any) => {
232+
// given the new set, find it's current match
233+
const currentDataSet = find(
234+
currentDataSets,
235+
(d) => d.label === newDataSet.label && d.type === newDataSet.type,
236+
)
213237

214-
// There is no original to update, so simply add new one
215-
if (!currentDataSet || !newDataSet.data) return newDataSet
238+
// There is no original to update, so simply add new one
239+
if (!currentDataSet || !newDataSet.data) return newDataSet
216240

217-
if (!currentDataSet.data) {
218-
currentDataSet.data = []
219-
} else {
220-
currentDataSet.data.length = newDataSet.data.length
221-
}
241+
if (!currentDataSet.data) {
242+
currentDataSet.data = []
243+
} else {
244+
currentDataSet.data.length = newDataSet.data.length
245+
}
246+
247+
// copy in values
248+
assign(currentDataSet.data, newDataSet.data)
249+
250+
// apply dataset changes, but keep copied data
251+
return {
252+
...currentDataSet,
253+
...newDataSet,
254+
data: currentDataSet.data,
255+
}
256+
})
222257

223-
// copy in values
224-
assign(currentDataSet.data, newDataSet.data)
258+
chartRef.current.update()
259+
}
225260

226-
// apply dataset changes, but keep copied data
227-
return {
228-
...currentDataSet,
229-
...newDataSet,
230-
data: currentDataSet.data,
261+
const destroyChart = () => {
262+
if (chartRef.current) {
263+
chartRef.current.destroy()
264+
chartRef.current = undefined
231265
}
232-
})
266+
}
233267

234-
chart.update()
235-
}
268+
useEffect(() => {
269+
renderChart()
236270

237-
const destroyChart = () => {
238-
if (chart) chart.destroy()
239-
}
271+
return () => destroyChart()
272+
}, [])
240273

241-
useEffect(() => {
242-
renderChart()
274+
useEffect(() => {
275+
if (!chartRef.current) return
243276

244-
return () => destroyChart()
245-
}, [])
277+
if (redraw) {
278+
destroyChart()
279+
setTimeout(() => {
280+
renderChart()
281+
}, 0)
282+
} else {
283+
updateChart()
284+
}
285+
}, [JSON.stringify(data), computedData])
246286

247-
useEffect(() => {
248-
if (redraw) {
249-
destroyChart()
250-
setTimeout(() => {
251-
renderChart()
252-
}, 0)
253-
} else {
254-
updateChart()
287+
const canvas = (ref: React.Ref<HTMLCanvasElement>) => {
288+
return (
289+
<canvas
290+
{...(!wrapper && className && { className: className })}
291+
data-testid="canvas"
292+
height={height}
293+
id={id}
294+
onClick={(e: React.MouseEvent<HTMLCanvasElement>) => {
295+
handleOnClick(e)
296+
}}
297+
ref={ref}
298+
role="img"
299+
width={width}
300+
{...rest}
301+
>
302+
{fallbackContent}
303+
</canvas>
304+
)
255305
}
256-
}, [props, computedData])
257-
258-
const canvas = (ref: React.Ref<HTMLCanvasElement>) => {
259-
return (
260-
<canvas
261-
{...(!wrapper && className && { className: className })}
262-
data-testid="canvas"
263-
height={height}
264-
id={id}
265-
onClick={(e: React.MouseEvent<HTMLCanvasElement>) => {
266-
handleOnClick(e)
267-
}}
268-
ref={ref}
269-
role="img"
270-
width={width}
271-
{...rest}
272-
>
273-
{fallbackContent}
274-
</canvas>
275-
)
276-
}
277306

278-
return wrapper ? (
279-
<div className={classNames('chart-wrapper', className)} {...rest}>
280-
{canvas(canvasRef)}
281-
</div>
282-
) : (
283-
canvas(canvasRef)
284-
)
285-
})
307+
return wrapper ? (
308+
<div className={classNames('chart-wrapper', className)} {...rest}>
309+
{canvas(canvasRef)}
310+
</div>
311+
) : (
312+
canvas(canvasRef)
313+
)
314+
},
315+
)
286316

287317
CChart.propTypes = {
288318
className: PropTypes.string,
289319
customTooltips: PropTypes.bool,
290-
data: PropTypes.any.isRequired, // TODO: check
320+
data: PropTypes.any.isRequired, // TODO: improve this type
291321
fallbackContent: PropTypes.node,
292322
getDatasetAtEvent: PropTypes.func,
293323
getElementAtEvent: PropTypes.func,

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.