Skip to content

Commit f7642c7

Browse files
committed
refactor: chart & colors
1 parent 4f3d6d2 commit f7642c7

File tree

3 files changed

+93
-84
lines changed

3 files changed

+93
-84
lines changed

src/Shared/Components/Charts/Chart.component.tsx

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
Tooltip,
1717
} from 'chart.js'
1818

19+
import { LEGENDS_LABEL_CONFIG } from './constants'
1920
import { ChartProps } from './types'
2021
import { getChartJSType, getDefaultOptions, transformDataForChart } from './utils'
2122

@@ -36,20 +37,17 @@ ChartJS.register(
3637
Filler,
3738
)
3839

40+
ChartJS.overrides.doughnut.plugins.legend.labels = {
41+
...ChartJS.overrides.doughnut.plugins.legend.labels,
42+
...LEGENDS_LABEL_CONFIG,
43+
}
44+
3945
const Chart = ({ id, type, labels, datasets, className, style }: ChartProps) => {
4046
const canvasRef = useRef<HTMLCanvasElement>(null)
4147
const chartRef = useRef<ChartJS | null>(null)
4248

4349
useEffect(() => {
44-
if (!canvasRef.current) return
45-
4650
const ctx = canvasRef.current.getContext('2d')
47-
if (!ctx) return
48-
49-
// Destroy existing chart if it exists
50-
if (chartRef.current) {
51-
chartRef.current.destroy()
52-
}
5351

5452
// Get Chart.js type and transform data
5553
const chartJSType = getChartJSType(type)
@@ -62,18 +60,11 @@ const Chart = ({ id, type, labels, datasets, className, style }: ChartProps) =>
6260
data: transformedData,
6361
options: defaultOptions,
6462
})
65-
}, [type, datasets, labels])
6663

67-
// Cleanup on unmount
68-
useEffect(
69-
() => () => {
70-
if (chartRef.current) {
71-
chartRef.current.destroy()
72-
chartRef.current = null
73-
}
74-
},
75-
[],
76-
)
64+
return () => {
65+
chartRef.current?.destroy()
66+
}
67+
}, [type, datasets, labels])
7768

7869
return (
7970
<div className="flex" style={style}>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export const LEGENDS_LABEL_CONFIG = {
2+
usePointStyle: true,
3+
pointStyle: 'rectRounded',
4+
pointStyleWidth: 0,
5+
font: {
6+
family: "'IBM Plex Sans', 'Open Sans', 'Roboto'",
7+
size: 13,
8+
lineHeight: '150%',
9+
weight: 400,
10+
},
11+
} as const

src/Shared/Components/Charts/utils.ts

Lines changed: 72 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { ChartData, ChartOptions, ChartType as ChartJSChartType } from 'chart.js'
1+
import { ChartData, ChartDataset, ChartOptions, ChartType as ChartJSChartType } from 'chart.js'
22

3+
import { LEGENDS_LABEL_CONFIG } from './constants'
34
import { ChartType, SimpleDataset } from './types'
45

56
const getCSSVariableValue = (variableName: string) => {
@@ -12,7 +13,7 @@ const getCSSVariableValue = (variableName: string) => {
1213
console.error(`CSS variable "${variableName}" not found`)
1314
}
1415

15-
return value ?? 'rgba(0, 0, 0, 0.1)'
16+
return value ?? 'transparent'
1617
}
1718

1819
// Map our chart types to Chart.js types
@@ -35,64 +36,75 @@ export const getDefaultOptions = (type: ChartType): ChartOptions => {
3536
const baseOptions: ChartOptions = {
3637
responsive: true,
3738
maintainAspectRatio: false,
39+
devicePixelRatio: 3,
3840
plugins: {
3941
legend: {
4042
position: 'bottom' as const,
43+
labels: LEGENDS_LABEL_CONFIG,
4144
},
4245
title: {
4346
display: false,
4447
},
4548
},
49+
elements: {
50+
line: {
51+
fill: true,
52+
tension: 0.4,
53+
},
54+
bar: {
55+
borderSkipped: 'start' as const,
56+
borderWidth: 2,
57+
borderColor: 'transparent',
58+
borderRadius: 4,
59+
},
60+
arc: {
61+
spacing: 2,
62+
},
63+
},
64+
}
65+
66+
const gridConfig = {
67+
color: getCSSVariableValue('--N50'),
4668
}
4769

4870
switch (type) {
4971
case 'area':
5072
return {
5173
...baseOptions,
52-
elements: {
53-
line: {
54-
fill: true,
74+
plugins: {
75+
...baseOptions.plugins,
76+
tooltip: {
77+
mode: 'index',
5578
},
5679
},
80+
interaction: {
81+
mode: 'nearest',
82+
axis: 'x',
83+
intersect: false,
84+
},
5785
scales: {
5886
y: {
87+
stacked: true,
5988
beginAtZero: true,
60-
grid: {
61-
color: getCSSVariableValue('--N50'),
62-
},
89+
grid: gridConfig,
6390
},
6491
x: {
65-
grid: {
66-
color: getCSSVariableValue('--N50'),
67-
},
92+
grid: gridConfig,
6893
},
6994
},
70-
}
95+
} as ChartOptions<'line'>
7196
case 'stackedBar':
7297
return {
7398
...baseOptions,
7499
scales: {
75100
x: {
76101
stacked: true,
77-
grid: {
78-
color: getCSSVariableValue('--N50'),
79-
},
102+
grid: gridConfig,
80103
},
81104
y: {
82105
stacked: true,
83106
beginAtZero: true,
84-
grid: {
85-
color: getCSSVariableValue('--N50'),
86-
},
87-
},
88-
},
89-
elements: {
90-
bar: {
91-
// Add gap between bars
92-
borderSkipped: 'start',
93-
borderWidth: 2,
94-
borderColor: 'transparent',
95-
borderRadius: 4,
107+
grid: gridConfig,
96108
},
97109
},
98110
}
@@ -104,24 +116,11 @@ export const getDefaultOptions = (type: ChartType): ChartOptions => {
104116
x: {
105117
stacked: true,
106118
beginAtZero: true,
107-
grid: {
108-
color: getCSSVariableValue('--N50'),
109-
},
119+
grid: gridConfig,
110120
},
111121
y: {
112122
stacked: true,
113-
grid: {
114-
color: getCSSVariableValue('--N50'),
115-
},
116-
},
117-
},
118-
elements: {
119-
bar: {
120-
// Add gap between bars
121-
borderSkipped: 'start',
122-
borderWidth: 2,
123-
borderColor: 'transparent',
124-
borderRadius: 4,
123+
grid: gridConfig,
125124
},
126125
},
127126
}
@@ -135,32 +134,39 @@ export const getDefaultOptions = (type: ChartType): ChartOptions => {
135134
align: 'center',
136135
},
137136
},
138-
elements: {
139-
arc: {
140-
spacing: 2,
141-
},
142-
},
143137
}
144138
default:
145139
return baseOptions
146140
}
147141
}
148142

149-
// Define color palette for consistent styling
150-
const getColorPalette = () => [
151-
'rgba(54, 162, 235, 0.8)', // Blue
152-
'rgba(255, 99, 132, 0.8)', // Red
153-
'rgba(255, 205, 86, 0.8)', // Yellow
154-
'rgba(75, 192, 192, 0.8)', // Green
155-
'rgba(153, 102, 255, 0.8)', // Purple
156-
'rgba(255, 159, 64, 0.8)', // Orange
157-
'rgba(199, 199, 199, 0.8)', // Grey
158-
'rgba(83, 102, 255, 0.8)', // Indigo
159-
]
143+
// Generates a palette of pastel HSL colors
144+
const generateColors = (count: number): string[] => {
145+
const colors: string[] = []
146+
for (let i = 0; i < count; i++) {
147+
const hue = (i * 360) / count
148+
const saturation = 50 // Pastel: 40-60%
149+
const lightness = 75 // Pastel: 80-90%
150+
colors.push(`hsl(${hue}, ${saturation}%, ${lightness}%)`)
151+
}
152+
return colors
153+
}
154+
155+
// Generates a slightly darker shade for a given HSL color string
156+
const generateCorrespondingBorderColor = (hsl: string): string => {
157+
// Parse hsl string: hsl(hue, saturation%, lightness%)
158+
const match = hsl.match(/hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/)
159+
if (!match) throw new Error('Invalid HSL color format')
160+
const hue = Number(match[1])
161+
const saturation = Number(match[2])
162+
let lightness = Number(match[3])
163+
lightness = Math.max(0, lightness - 15) // Clamp to 0
164+
return `hsl(${hue}, ${saturation}%, ${lightness}%)`
165+
}
160166

161167
// Transform simple data to Chart.js format with consistent styling
162168
export const transformDataForChart = (labels: string[], datasets: SimpleDataset[], type: ChartType): ChartData => {
163-
const colors = getColorPalette()
169+
const colors = generateColors(type === 'pie' ? datasets[0].data.length : datasets.length)
164170

165171
const transformedDatasets = datasets.map((dataset, index) => {
166172
const colorIndex = index % colors.length
@@ -175,20 +181,21 @@ export const transformDataForChart = (labels: string[], datasets: SimpleDataset[
175181
return {
176182
...baseDataset,
177183
fill: true,
178-
tension: 0.4,
179184
pointRadius: 0,
180-
backgroundColor: colors[colorIndex].replace('0.8', '0.2'),
181-
}
185+
pointHoverRadius: 10,
186+
pointHitRadius: 20,
187+
pointStyle: 'rectRounded',
188+
pointBorderWidth: 0,
189+
borderWidth: 2,
190+
borderColor: generateCorrespondingBorderColor(colors[colorIndex]),
191+
} as ChartDataset<'line'>
182192
case 'pie':
183193
return {
184194
...baseDataset,
185195
backgroundColor: colors.slice(0, dataset.data.length),
186196
}
187197
case 'stackedBar':
188198
case 'stackedBarHorizontal':
189-
return {
190-
...baseDataset,
191-
}
192199
default:
193200
return baseDataset
194201
}

0 commit comments

Comments
 (0)