import { ActiveElement, BarController, BarElement, CategoryScale, Chart, ChartData, ChartEvent, ChartOptions, ChartType, Decimation, Filler, Legend, LinearScale, LineController, LineElement, PointElement, SubTitle, TimeScale, TimeSeriesScale, Title, Tooltip, } from 'chart.js'; import * as chartjsAdapter from 'chartjs-adapter-date-fns'; import annotationPlugin from 'chartjs-plugin-annotation'; import React, { useCallback, useEffect, useRef } from 'react'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import AppReducer from 'types/reducer/app'; import { hasData } from './hasData'; import { legend } from './Plugin'; import { emptyGraph } from './Plugin/EmptyGraph'; import { LegendsContainer } from './styles'; import { useXAxisTimeUnit } from './xAxisConfig'; import { getToolTipValue, getYAxisFormattedValue } from './yAxisConfig'; Chart.register( LineElement, PointElement, LineController, CategoryScale, LinearScale, TimeScale, TimeSeriesScale, Decimation, Filler, Legend, Title, Tooltip, SubTitle, BarController, BarElement, annotationPlugin, ); function Graph({ animate = true, data, type, title, isStacked, onClickHandler, name, yAxisUnit = 'short', forceReRender, staticLine, containerHeight, }: GraphProps): JSX.Element { const { isDarkMode } = useSelector((state) => state.app); const chartRef = useRef(null); const currentTheme = isDarkMode ? 'dark' : 'light'; const xAxisTimeUnit = useXAxisTimeUnit(data); // Computes the relevant time unit for x axis by analyzing the time stamp data const lineChartRef = useRef(); const getGridColor = useCallback(() => { if (currentTheme === undefined) { return 'rgba(231,233,237,0.1)'; } if (currentTheme === 'dark') { return 'rgba(231,233,237,0.1)'; } return 'rgba(231,233,237,0.8)'; }, [currentTheme]); // eslint-disable-next-line sonarjs/cognitive-complexity const buildChart = useCallback(() => { if (lineChartRef.current !== undefined) { lineChartRef.current.destroy(); } if (chartRef.current !== null) { const options: ChartOptions = { animation: { duration: animate ? 200 : 0, }, responsive: true, maintainAspectRatio: false, interaction: { mode: 'index', intersect: false, }, plugins: { annotation: staticLine ? { annotations: [ { type: 'line', yMin: staticLine.yMin, yMax: staticLine.yMax, borderColor: staticLine.borderColor, borderWidth: staticLine.borderWidth, label: { content: staticLine.lineText, enabled: true, font: { size: 10, }, borderWidth: 0, position: 'start', backgroundColor: 'transparent', color: staticLine.textColor, }, }, ], } : undefined, title: { display: title !== undefined, text: title, }, legend: { display: false, }, tooltip: { callbacks: { label(context) { let label = context.dataset.label || ''; if (label) { label += ': '; } if (context.parsed.y !== null) { label += getToolTipValue(context.parsed.y.toString(), yAxisUnit); } return label; }, }, }, }, layout: { padding: 0, }, scales: { x: { grid: { display: true, color: getGridColor(), drawTicks: true, }, adapters: { date: chartjsAdapter, }, time: { unit: xAxisTimeUnit?.unitName || 'minute', stepSize: xAxisTimeUnit?.stepSize || 1, displayFormats: { millisecond: 'HH:mm:ss', second: 'HH:mm:ss', minute: 'HH:mm', hour: 'MM/dd HH:mm', day: 'MM/dd', week: 'MM/dd', month: 'yy-MM', year: 'yy', }, }, type: 'time', }, y: { display: true, grid: { display: true, color: getGridColor(), }, ticks: { // Include a dollar sign in the ticks callback(value) { return getYAxisFormattedValue(value.toString(), yAxisUnit); }, }, }, stacked: { display: isStacked === undefined ? false : 'auto', }, }, elements: { line: { tension: 0, cubicInterpolationMode: 'monotone', }, }, onClick: (event, element, chart) => { if (onClickHandler) { onClickHandler(event, element, chart, data); } }, }; const chartHasData = hasData(data); const chartPlugins = []; if (!chartHasData) chartPlugins.push(emptyGraph); chartPlugins.push(legend(name, data.datasets.length > 3)); lineChartRef.current = new Chart(chartRef.current, { type, data, options, plugins: chartPlugins, }); } }, [ animate, title, getGridColor, xAxisTimeUnit?.unitName, xAxisTimeUnit?.stepSize, isStacked, type, data, name, yAxisUnit, onClickHandler, staticLine, ]); useEffect(() => { buildChart(); }, [buildChart, forceReRender]); return (
); } interface GraphProps { animate?: boolean; type: ChartType; data: Chart['data']; title?: string; isStacked?: boolean; onClickHandler?: GraphOnClickHandler; name: string; yAxisUnit?: string; forceReRender?: boolean | null | number; staticLine?: StaticLineProps | undefined; containerHeight?: string | number; } export interface StaticLineProps { yMin: number | undefined; yMax: number | undefined; borderColor: string; borderWidth: number; lineText: string; textColor: string; } export type GraphOnClickHandler = ( event: ChartEvent, elements: ActiveElement[], chart: Chart, data: ChartData, ) => void; Graph.defaultProps = { animate: undefined, title: undefined, isStacked: undefined, onClickHandler: undefined, yAxisUnit: undefined, forceReRender: undefined, staticLine: undefined, containerHeight: '85%', }; export default Graph;