forked from PinSpace-Org/GistPin
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCategoryPieChart.tsx
More file actions
101 lines (92 loc) 路 3.11 KB
/
Copy pathCategoryPieChart.tsx
File metadata and controls
101 lines (92 loc) 路 3.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
'use client';
import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js';
import type { TooltipItem } from 'chart.js';
import { Pie } from 'react-chartjs-2';
import { memo } from 'react';
import { useCategoryDataQuery } from '@/lib/analytics-queries';
import ExportButton from '@/components/ui/ExportButton';
import { exportRowsToCsv } from '@/lib/export';
import ChartSkeleton from '@/components/ui/ChartSkeleton';
ChartJS.register(ArcElement, Tooltip, Legend);
function CategoryPieChart() {
const { data: categories, isLoading, error } = useCategoryDataQuery();
if (isLoading || !categories) return <ChartSkeleton />;
if (error) return <p>Unable to load category distribution.</p>;
const total = categories.reduce((sum, c) => sum + c.count, 0);
const data = {
labels: categories.map((c) => c.label),
datasets: [
{
data: categories.map((c) => c.count),
backgroundColor: categories.map((c) => c.color),
borderColor: categories.map((c) => c.border),
borderWidth: 2,
hoverOffset: 16,
},
],
};
const options = {
responsive: true,
plugins: {
legend: {
position: 'bottom' as const,
labels: {
padding: 20,
usePointStyle: true,
pointStyleWidth: 10,
generateLabels: (chart: ChartJS) => {
const dataset = chart.data.datasets[0];
return (chart.data.labels as string[]).map((label, i) => {
const value = dataset.data[i] as number;
const pct = ((value / total) * 100).toFixed(1);
return {
text: `${label} (${pct}%)`,
fillStyle: (dataset.backgroundColor as string[])[i],
strokeStyle: (dataset.borderColor as string[])[i],
hidden: !chart.getDataVisibility(i),
index: i,
datasetIndex: 0,
lineWidth: 2,
pointStyle: 'circle' as const,
};
});
},
},
onClick: (_e: unknown, legendItem: { index?: number }, legend: { chart: ChartJS }) => {
const index = legendItem.index ?? 0;
legend.chart.toggleDataVisibility(index);
legend.chart.update();
},
},
tooltip: {
callbacks: {
label: (item: TooltipItem<'pie'>) => {
const value = item.raw as number;
const pct = ((value / total) * 100).toFixed(1);
return ` ${item.label}: ${value} gists (${pct}%)`;
},
},
},
},
};
return (
<div style={{ maxWidth: 480, margin: '0 auto' }}>
<ExportButton
onExport={(onProgress) =>
exportRowsToCsv({
filenamePrefix: 'category-distribution',
filters: { dataset: 'All categories' },
rows: categories.map((c) => ({
category: c.label,
count: c.count,
percentage: ((c.count / total) * 100).toFixed(1),
})),
onProgress,
})
}
/>
<Pie data={data} options={options} />
</div>
);
}
export default memo(CategoryPieChart);