Files
buzz-sheet/components/public/comparison-chart.tsx
T

199 lines
6.4 KiB
TypeScript

"use client";
import {
Bar,
BarChart,
CartesianGrid,
LabelList,
Line,
LineChart,
XAxis,
YAxis,
} from "recharts";
import {
ChartContainer,
ChartLegend,
ChartLegendContent,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/components/ui/chart";
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import type { PreparedComparisonChart } from "@/lib/charts/comparison";
interface ComparisonChartProps {
prepared: PreparedComparisonChart;
}
function suffix(normalization: PreparedComparisonChart["config"]["normalization"]) {
return normalization === "percent-of-total" ? "%" : "";
}
export function ComparisonChart({ prepared }: ComparisonChartProps) {
const { config, rows } = prepared;
const chartConfig: ChartConfig = Object.fromEntries(
config.series.map((series) => [
series.field,
{ label: series.label, color: `var(--${series.color})` },
]),
);
const data = rows.map((row) => ({
category: row.category,
...row.values,
}));
const valueSuffix = suffix(config.normalization);
const chartHeight = Math.max(
config.presentation === "horizontal-bar" ? rows.length * 54 : 320,
280,
);
const common = {
data,
accessibilityLayer: true,
margin: { top: 20, right: 28, left: 8, bottom: 12 },
} as const;
return (
<div className="space-y-5">
<ChartContainer
config={chartConfig}
className="min-h-72 w-full aspect-auto"
style={{ height: chartHeight }}
aria-label={config.title ?? "กราฟเปรียบเทียบ"}
>
{config.presentation === "line" ? (
<LineChart {...common}>
<CartesianGrid vertical={false} />
<XAxis dataKey="category" tickLine={false} axisLine={false} minTickGap={16} />
<YAxis tickLine={false} axisLine={false} width={48} />
<ChartTooltip content={<ChartTooltipContent />} />
{config.showLegend ? (
<ChartLegend content={<ChartLegendContent />} />
) : null}
{config.series.map((series) => (
<Line
key={series.field}
dataKey={series.field}
type="monotone"
stroke={`var(--color-${series.field})`}
strokeWidth={2}
dot={{ r: 4, fill: `var(--color-${series.field})` }}
activeDot={{ r: 6 }}
>
{config.showValues ? (
<LabelList
position="top"
className="fill-foreground font-mono text-[10px]"
formatter={(value: unknown) => `${String(value)}${valueSuffix}`}
/>
) : null}
</Line>
))}
</LineChart>
) : config.presentation === "horizontal-bar" ? (
<BarChart {...common} layout="vertical">
<CartesianGrid horizontal={false} />
<XAxis type="number" tickLine={false} axisLine={false} />
<YAxis
type="category"
dataKey="category"
tickLine={false}
axisLine={false}
width={112}
/>
<ChartTooltip content={<ChartTooltipContent />} />
{config.showLegend ? (
<ChartLegend content={<ChartLegendContent />} />
) : null}
{config.series.map((series) => (
<Bar
key={series.field}
dataKey={series.field}
fill={`var(--color-${series.field})`}
radius={4}
>
{config.showValues ? (
<LabelList
position="right"
className="fill-foreground font-mono text-[10px]"
formatter={(value: unknown) => `${String(value)}${valueSuffix}`}
/>
) : null}
</Bar>
))}
</BarChart>
) : (
<BarChart {...common}>
<CartesianGrid vertical={false} />
<XAxis dataKey="category" tickLine={false} axisLine={false} minTickGap={16} />
<YAxis tickLine={false} axisLine={false} width={48} />
<ChartTooltip content={<ChartTooltipContent />} />
{config.showLegend ? (
<ChartLegend content={<ChartLegendContent />} />
) : null}
{config.series.map((series) => (
<Bar
key={series.field}
dataKey={series.field}
stackId={config.presentation === "stacked-bar" ? "total" : undefined}
fill={`var(--color-${series.field})`}
radius={config.presentation === "stacked-bar" ? 0 : 4}
>
{config.showValues ? (
<LabelList
position="top"
className="fill-foreground font-mono text-[10px]"
formatter={(value: unknown) => `${String(value)}${valueSuffix}`}
/>
) : null}
</Bar>
))}
</BarChart>
)}
</ChartContainer>
<Table>
<TableCaption className="sr-only">
ค่าที่ใช้ในกราฟ {config.title ?? "เปรียบเทียบ"}
</TableCaption>
<TableHeader>
<TableRow>
<TableHead scope="col">หมวดหมู่</TableHead>
{config.series.map((series) => (
<TableHead key={series.field} scope="col" className="text-right">
{series.label}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{rows.map((row) => (
<TableRow key={row.id}>
<TableHead scope="row">{row.category}</TableHead>
{config.series.map((series) => (
<TableCell key={series.field} className="text-right font-mono tabular-nums">
{row.exactValues[series.field]}
{config.normalization !== "raw" ? (
<span className="ml-2 text-muted-foreground">
({row.normalizedValues[series.field]}
{valueSuffix})
</span>
) : null}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</div>
);
}