import { useState } from 'react'; import { Doughnut, HorizontalBar } from 'react-chartjs-2'; import { keys, values } from 'ramda'; import classNames from 'classnames'; import Chart, { ChartData, ChartDataSets, ChartOptions } from 'chart.js'; import { fillTheGaps } from '../../utils/helpers/visits'; import { Stats } from '../types'; import { prettify } from '../../utils/helpers/numbers'; import { pointerOnHover, renderDoughnutChartLabel, renderNonDoughnutChartLabel } from '../../utils/helpers/charts'; import { HIGHLIGHTED_COLOR, HIGHLIGHTED_COLOR_ALPHA, MAIN_COLOR, MAIN_COLOR_ALPHA } from '../../utils/theme'; import './DefaultChart.scss'; export interface DefaultChartProps { title: Function | string; stats: Stats; isBarChart?: boolean; max?: number; highlightedStats?: Stats; highlightedLabel?: string; onClick?: (label: string) => void; } const generateGraphData = ( title: Function | string, isBarChart: boolean, labels: string[], data: number[], highlightedData?: number[], highlightedLabel?: string, ): ChartData => ({ labels, datasets: [ { title, label: highlightedData ? 'Non-selected' : 'Visits', data, backgroundColor: isBarChart ? MAIN_COLOR_ALPHA : [ '#97BBCD', '#F7464A', '#46BFBD', '#FDB45C', '#949FB1', '#57A773', '#414066', '#08B2E3', '#B6C454', '#DCDCDC', '#463730', ], borderColor: isBarChart ? MAIN_COLOR : 'white', borderWidth: 2, }, highlightedData && { title, label: highlightedLabel ?? 'Selected', data: highlightedData, backgroundColor: HIGHLIGHTED_COLOR_ALPHA, borderColor: HIGHLIGHTED_COLOR, borderWidth: 2, }, ].filter(Boolean) as ChartDataSets[], }); const dropLabelIfHidden = (label: string) => label.startsWith('hidden') ? '' : label; const determineHeight = (isBarChart: boolean, labels: string[]): number | undefined => { if (!isBarChart) { return 300; } return isBarChart && labels.length > 20 ? labels.length * 8 : undefined; }; const renderPieChartLegend = ({ config }: Chart) => { const { labels = [], datasets = [] } = config.data ?? {}; const { defaultColor } = config.options ?? {} as any; const [{ backgroundColor: colors }] = datasets; return ( ); }; const chartElementAtEvent = (onClick?: (label: string) => void) => ([ chart ]: [{ _index: number; _chart: Chart }]) => { if (!onClick || !chart) { return; } const { _index, _chart: { data } } = chart; const { labels } = data; onClick(labels?.[_index] as string); }; const statsAreDefined = (stats: Stats | undefined): stats is Stats => !!stats && Object.keys(stats).length > 0; const DefaultChart = ( { title, isBarChart = false, stats, max, highlightedStats, highlightedLabel, onClick }: DefaultChartProps, ) => { const Component = isBarChart ? HorizontalBar : Doughnut; const labels = keys(stats).map(dropLabelIfHidden); const data = values( !statsAreDefined(highlightedStats) ? stats : keys(highlightedStats).reduce((acc, highlightedKey) => { if (acc[highlightedKey]) { acc[highlightedKey] -= highlightedStats[highlightedKey]; } return acc; }, { ...stats }), ); const highlightedData = statsAreDefined(highlightedStats) ? fillTheGaps(highlightedStats, labels) : undefined; const [ chartRef, setChartRef ] = useState() const options: ChartOptions = { legend: { display: false }, legendCallback: !isBarChart && renderPieChartLegend as any, scales: !isBarChart ? undefined : { xAxes: [ { ticks: { beginAtZero: true, // @ts-expect-error precision: 0, callback: prettify, max, }, stacked: true, }, ], yAxes: [{ stacked: true }], }, tooltips: { intersect: !isBarChart, // Do not show tooltip on items with empty label when in a bar chart filter: ({ yLabel }) => !isBarChart || yLabel !== '', callbacks: { label: isBarChart ? renderNonDoughnutChartLabel('xLabel') : renderDoughnutChartLabel, }, }, onHover: !isBarChart ? undefined : (pointerOnHover) as any, // TODO Types seem to be incorrectly defined in @types/chart.js }; const graphData = generateGraphData(title, isBarChart, labels, data, highlightedData, highlightedLabel); const height = determineHeight(isBarChart, labels); // Provide a key based on the height, so that every time the dataset changes, a new graph is rendered return (
setChartRef(element ?? undefined)} key={height} data={graphData} options={options} height={height} getElementAtEvent={chartElementAtEvent(onClick)} />
{!isBarChart && (
{chartRef?.chartInstance.generateLegend()}
)}
); }; export default DefaultChart;