Skip to content

Commit 4d0fc13

Browse files
authored
Merge pull request #5819 from bcgov/feat/5736
feat(5736): replace area graph implementation with Chart.js
2 parents 3b193e7 + 130803c commit 4d0fc13

File tree

6 files changed

+97
-47
lines changed

6 files changed

+97
-47
lines changed

app/app/layout.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@ import { MantineProvider } from '@mantine/core';
99
import { ModalsProvider } from '@mantine/modals';
1010
import { Notifications } from '@mantine/notifications';
1111
import { useQuery } from '@tanstack/react-query';
12+
import {
13+
Chart,
14+
CategoryScale,
15+
LinearScale,
16+
BarElement,
17+
Title,
18+
PointElement,
19+
LineElement,
20+
Tooltip,
21+
Legend,
22+
} from 'chart.js';
1223
import localFont from 'next/font/local';
1324
import { useEffect } from 'react';
1425
import Footer from '@/components/layouts/Footer';
@@ -19,6 +30,8 @@ import { useAppState } from '@/states/global';
1930
import { cn } from '@/utils/js';
2031
import { theme } from './mantine-theme';
2132

33+
Chart.register(CategoryScale, LinearScale, BarElement, Title, PointElement, LineElement, Tooltip, Legend);
34+
2235
const bcsans = localFont({
2336
src: [
2437
{

app/components/analytics/CombinedAreaGraph.tsx

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { LoadingOverlay } from '@mantine/core';
2-
import { AreaChart, Card, Title, Subtitle } from '@tremor/react';
2+
import { Card, Title, Subtitle } from '@tremor/react';
3+
import { ChartTypeRegistry, TooltipItem } from 'chart.js';
4+
import { useMemo } from 'react';
5+
import { Line } from 'react-chartjs-2';
6+
import { valueFormatter, getColor } from '@/components/analytics/helpers';
37
import ExportButton from '@/components/buttons/ExportButton';
48

5-
const valueFormatter = function (number: number) {
6-
return new Intl.NumberFormat('us').format(number).toString();
7-
};
8-
99
export type ChartDate = {
1010
date: string;
1111
};
@@ -29,6 +29,68 @@ export default function CombinedAreaGraph({
2929
isLoading?: boolean;
3030
exportApiEndpoint?: string /* temporary */;
3131
}) {
32+
const { data, options } = useMemo(() => {
33+
if (!chartData) return { data: { labels: [], datasets: [] }, options: {} };
34+
35+
const labels: string[] = [];
36+
const datasetMap: Record<string, number[]> = {};
37+
38+
for (const row of chartData) {
39+
for (const [key, value] of Object.entries(row)) {
40+
if (key === 'date') {
41+
labels.push(String(value));
42+
continue;
43+
}
44+
45+
if (!datasetMap[key]) {
46+
datasetMap[key] = [];
47+
}
48+
49+
datasetMap[key].push(Number(value));
50+
}
51+
}
52+
53+
const _data = {
54+
labels,
55+
datasets: Object.entries(datasetMap).map(([label, data], index) => ({
56+
label,
57+
data,
58+
borderColor: getColor(index),
59+
backgroundColor: getColor(index, 0.2),
60+
tension: 0.4,
61+
})),
62+
};
63+
64+
const _options = {
65+
responsive: true,
66+
maintainAspectRatio: true,
67+
interaction: {
68+
mode: 'index' as const,
69+
intersect: false,
70+
},
71+
plugins: {
72+
legend: {
73+
position: 'top' as const,
74+
},
75+
title: {
76+
display: false,
77+
text: '',
78+
},
79+
tooltip: {
80+
callbacks: {
81+
label: function (context: TooltipItem<keyof ChartTypeRegistry>) {
82+
const label = context.dataset.label || 'Unknown';
83+
const value = valueFormatter(context.raw as number);
84+
return `${label}: ${value}`;
85+
},
86+
},
87+
},
88+
},
89+
};
90+
91+
return { data: _data, options: _options };
92+
}, [chartData]);
93+
3294
return (
3395
<div className="flex flex-col items-end">
3496
<ExportButton onExport={onExport} downloadUrl={exportApiEndpoint} /* temporary */ className="m-2" />
@@ -42,15 +104,7 @@ export default function CombinedAreaGraph({
42104
overlayProps={{ radius: 'sm', blur: 2 }}
43105
loaderProps={{ color: 'pink', type: 'bars' }}
44106
/>
45-
<AreaChart
46-
className="h-72 mt-4"
47-
data={chartData}
48-
index="date"
49-
yAxisWidth={65}
50-
categories={categories}
51-
colors={colors}
52-
valueFormatter={valueFormatter}
53-
/>
107+
<Line className="max-h-[28rem] mt-4" data={data} options={options} />
54108
</div>
55109
</Card>
56110
</div>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export function valueFormatter(number: number) {
2+
return new Intl.NumberFormat('us').format(number).toString();
3+
}
4+
5+
export function getColor(index: number, alpha = 1) {
6+
const colors = [
7+
[75, 192, 192],
8+
[255, 99, 132],
9+
[54, 162, 235],
10+
[255, 206, 86],
11+
[153, 102, 255],
12+
[255, 159, 64],
13+
];
14+
const [r, g, b] = colors[index % colors.length];
15+
return `rgba(${r},${g},${b},${alpha})`;
16+
}

app/components/private-cloud/monthly-cost/MonthyCostChart.tsx

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,8 @@
11
import { Card, Title } from '@tremor/react';
2-
import {
3-
Chart as ChartJS,
4-
CategoryScale,
5-
LinearScale,
6-
BarElement,
7-
Title as ChartTitle,
8-
Tooltip,
9-
Legend,
10-
} from 'chart.js';
112
import { Bar } from 'react-chartjs-2';
123
import { MonthlyCost } from '@/types/private-cloud';
134
import { getMonthlyCostChartConfig } from './monthly-cost-chart-data';
145

15-
ChartJS.register(CategoryScale, LinearScale, BarElement, ChartTitle, Tooltip, Legend);
16-
176
export default function MonthlyCostChart({ data }: { data: Pick<MonthlyCost, 'days' | 'dayDetails'> }) {
187
const { options, data: chartData } = getMonthlyCostChartConfig({ data });
198

app/components/private-cloud/quarterly-cost/QuarterlyCostChart.tsx

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,8 @@
11
import { Card, Title } from '@tremor/react';
2-
import {
3-
Chart as ChartJS,
4-
CategoryScale,
5-
LinearScale,
6-
BarElement,
7-
Title as ChartTitle,
8-
Tooltip,
9-
Legend,
10-
} from 'chart.js';
112
import { Bar } from 'react-chartjs-2';
123
import { QuarterlyCost } from '@/types/private-cloud';
134
import { getQuarterlyCostChartConfig } from './quarterly-cost-chart-data';
145

15-
ChartJS.register(CategoryScale, LinearScale, BarElement, ChartTitle, Tooltip, Legend);
16-
176
export default function QuarterlyCostChart({ data }: { data: Pick<QuarterlyCost, 'months' | 'monthDetails'> }) {
187
const { options, data: chartData } = getQuarterlyCostChartConfig({ data });
198

app/components/private-cloud/yearly-cost/YearlyCostChart.tsx

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,8 @@
11
import { Card, Title } from '@tremor/react';
2-
import {
3-
Chart as ChartJS,
4-
CategoryScale,
5-
LinearScale,
6-
BarElement,
7-
Title as ChartTitle,
8-
Tooltip,
9-
Legend,
10-
} from 'chart.js';
112
import { Bar } from 'react-chartjs-2';
123
import { YearlyCost } from '@/types/private-cloud';
134
import { getYearlyCostChartConfig } from './yearly-cost-chart-data';
145

15-
ChartJS.register(CategoryScale, LinearScale, BarElement, ChartTitle, Tooltip, Legend);
16-
176
export default function YearlyCostChart({ data }: { data: Pick<YearlyCost, 'months' | 'monthDetails'> }) {
187
const { options, data: chartData } = getYearlyCostChartConfig({ data });
198

0 commit comments

Comments
 (0)