diff --git a/src/visits/helpers/DefaultChart.tsx b/src/visits/helpers/DefaultChart.tsx index eb135203..16daadad 100644 --- a/src/visits/helpers/DefaultChart.tsx +++ b/src/visits/helpers/DefaultChart.tsx @@ -19,7 +19,6 @@ import { import { PieChartLegend } from './PieChartLegend'; export interface DefaultChartProps { - title: Function | string; stats: Stats; isBarChart?: boolean; max?: number; @@ -28,45 +27,56 @@ export interface DefaultChartProps { onClick?: (label: string) => void; } -const generateGraphData = ( - title: Function | string, +const generateChartDatasets = ( + isBarChart: boolean, + data: number[], + highlightedData: number[], + highlightedLabel?: string, +): ChartDataset[] => { + const mainDataset: ChartDataset = { + label: highlightedLabel ? 'Non-selected' : 'Visits', + data, + backgroundColor: isBarChart ? MAIN_COLOR_ALPHA : [ + '#97BBCD', + '#F7464A', + '#46BFBD', + '#FDB45C', + '#949FB1', + '#57A773', + '#414066', + '#08B2E3', + '#B6C454', + '#DCDCDC', + '#463730', + ], + borderColor: isBarChart ? MAIN_COLOR : isDarkThemeEnabled() ? PRIMARY_DARK_COLOR : PRIMARY_LIGHT_COLOR, + borderWidth: 2, + }; + + if (!isBarChart) { + return [ mainDataset ]; + } + + const highlightedDataset: ChartDataset = { + label: highlightedLabel ?? 'Selected', + data: highlightedData, + backgroundColor: HIGHLIGHTED_COLOR_ALPHA, + borderColor: HIGHLIGHTED_COLOR, + borderWidth: 2, + }; + + return [ mainDataset, highlightedDataset ]; +}; + +const generateChartData = ( isBarChart: boolean, labels: string[], data: number[], - highlightedData?: 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 : isDarkThemeEnabled() ? PRIMARY_DARK_COLOR : PRIMARY_LIGHT_COLOR, - borderWidth: 2, - }, - highlightedData && { - title, - label: highlightedLabel ?? 'Selected', - data: highlightedData, - backgroundColor: HIGHLIGHTED_COLOR_ALPHA, - borderColor: HIGHLIGHTED_COLOR, - borderWidth: 2, - }, - ].filter(Boolean) as ChartDataset[], + datasets: generateChartDatasets(isBarChart, data, highlightedData, highlightedLabel), }); const dropLabelIfHidden = (label: string) => label.startsWith('hidden') ? '' : label; @@ -93,7 +103,7 @@ const chartElementAtEvent = ( 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, + { isBarChart = false, stats, max, highlightedStats, highlightedLabel, onClick }: DefaultChartProps, ) => { const Component = isBarChart ? Bar : Doughnut; const [ chartRef, setChartRef ] = useState(); // Cannot use useRef here @@ -107,13 +117,14 @@ const DefaultChart = ( return acc; }, { ...stats }), ); - const highlightedData = statsAreDefined(highlightedStats) ? fillTheGaps(highlightedStats, labels) : undefined; + const highlightedData = fillTheGaps(highlightedStats ?? {}, labels); const options: ChartOptions = { plugins: { legend: { display: false }, tooltip: { intersect: !isBarChart, + mode: isBarChart ? 'y' : 'index', // Do not show tooltip on items with empty label when in a bar chart filter: ({ label }) => !isBarChart || label !== '', callbacks: { @@ -136,10 +147,10 @@ const DefaultChart = ( onHover: isBarChart ? pointerOnHover : undefined, indexAxis: isBarChart ? 'y' : 'x', }; - const graphData = generateGraphData(title, isBarChart, labels, data, highlightedData, highlightedLabel); + const chartData = generateChartData(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 + // Provide a key based on the height, so that every time the dataset changes, a new chart is rendered return (
@@ -148,7 +159,7 @@ const DefaultChart = ( setChartRef(element ?? undefined); }} key={height} - data={graphData} + data={chartData} options={options} height={height} getElementAtEvent={chartElementAtEvent(labels, onClick) as any} diff --git a/src/visits/helpers/GraphCard.tsx b/src/visits/helpers/GraphCard.tsx index c0709493..57388835 100644 --- a/src/visits/helpers/GraphCard.tsx +++ b/src/visits/helpers/GraphCard.tsx @@ -4,6 +4,7 @@ import DefaultChart, { DefaultChartProps } from './DefaultChart'; import './GraphCard.scss'; interface GraphCardProps extends DefaultChartProps { + title: Function | string; footer?: ReactNode; } @@ -11,7 +12,7 @@ const GraphCard = ({ title, footer, ...rest }: GraphCardProps) => ( {typeof title === 'function' ? title() : title} - + {footer && {footer}} diff --git a/src/visits/helpers/LineChartCard.tsx b/src/visits/helpers/LineChartCard.tsx index 58271723..8cca1e9f 100644 --- a/src/visits/helpers/LineChartCard.tsx +++ b/src/visits/helpers/LineChartCard.tsx @@ -1,4 +1,4 @@ -import { useState, useMemo } from 'react'; +import { useState, useMemo, FC } from 'react'; import { Card, CardHeader, @@ -183,14 +183,19 @@ const LineChartCard = ( () => fillTheGaps(groupVisitsByStep(step, reverse(highlightedVisits)), labels), [ highlightedVisits, step, labels ], ); + const generateChartDatasets = (): ChartDataset[] => { + const mainDataset = generateDataset(groupedVisits, 'Visits', MAIN_COLOR); - const data: ChartData = { - labels, - datasets: [ - generateDataset(groupedVisits, 'Visits', MAIN_COLOR), - highlightedVisits.length > 0 && generateDataset(groupedHighlighted, highlightedLabel, HIGHLIGHTED_COLOR), - ].filter(Boolean) as ChartDataset[], + if (highlightedVisits.length === 0) { + return [ mainDataset ]; + } + + const highlightedDataset = generateDataset(groupedHighlighted, highlightedLabel, HIGHLIGHTED_COLOR); + + return [ mainDataset, highlightedDataset ]; }; + const generateChartData = (): ChartData => ({ labels, datasets: generateChartDatasets() }); + const options: ChartOptions = { maintainAspectRatio: false, plugins: { @@ -213,8 +218,15 @@ const LineChartCard = ( title: { display: true, text: STEPS_MAP[step] }, }, }, - onHover: (pointerOnHover) as any, // TODO Types seem to be incorrectly defined in @types/chart.js + onHover: pointerOnHover, }; + const LineChart: FC = () => ( + + ); return ( @@ -241,11 +253,10 @@ const LineChartCard = (
- + {/* It's VERY IMPORTANT to render two different components here, as one has 1 dataset and the other has 2 */} + {/* Using the same component causes a crash when switching from 1 to 2 datasets, and then back to 1 dataset */} + {highlightedVisits.length > 0 && } + {highlightedVisits.length === 0 && } ); diff --git a/src/visits/helpers/SortableBarGraph.tsx b/src/visits/helpers/SortableBarGraph.tsx index 6e2efae5..9a4bd429 100644 --- a/src/visits/helpers/SortableBarGraph.tsx +++ b/src/visits/helpers/SortableBarGraph.tsx @@ -14,6 +14,7 @@ const pickKeyFromPair = ([ key ]: StatsRow) => key; const pickValueFromPair = ([ , value ]: StatsRow) => value; interface SortableBarGraphProps extends DefaultChartProps { + title: Function | string; sortingItems: Record; withPagination?: boolean; extraHeaderContent?: Function;