104 lines
4.3 KiB
TypeScript
104 lines
4.3 KiB
TypeScript
"use client";
|
|
|
|
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts";
|
|
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import {
|
|
ChartContainer,
|
|
ChartLegend,
|
|
ChartLegendContent,
|
|
ChartTooltip,
|
|
ChartTooltipContent,
|
|
type ChartConfig,
|
|
} from "@/components/ui/chart";
|
|
import type { PublicGuide } from "@/lib/guides/queries";
|
|
|
|
const chartConfig = {
|
|
overall: { label: "Overall - ดาเมจทั้งทีม", color: "var(--chart-1)" },
|
|
personal: { label: "Personal - ดาเมจส่วนตัว", color: "var(--chart-2)" },
|
|
} satisfies ChartConfig;
|
|
|
|
function percentage(value: string | null) {
|
|
if (!value?.trim()) return null;
|
|
const number = Number(value);
|
|
// Zero is displayed as an unavailable metric in the weapon cards.
|
|
return Number.isFinite(number) && number !== 0 ? number : null;
|
|
}
|
|
|
|
export function WeaponChart({ rows }: { rows: PublicGuide["weaponGroups"][number]["rows"] }) {
|
|
const data = rows.map((row, index) => ({
|
|
name: `${index + 1}. ${row.weapon.name} R${row.refinement}`,
|
|
imageUrl: row.weapon.imageUrl,
|
|
overall: percentage(row.overallPercent),
|
|
personal: percentage(row.personalPercent),
|
|
}));
|
|
const hasOverall = data.some((row) => row.overall !== null);
|
|
const hasPersonal = data.some((row) => row.personal !== null);
|
|
const singleMetric = !hasOverall || !hasPersonal;
|
|
|
|
if (!hasOverall && !hasPersonal) return null;
|
|
|
|
return (
|
|
<Card className="min-w-0" size={singleMetric ? "sm" : "default"}>
|
|
<CardHeader>
|
|
<CardTitle>เปรียบเทียบดาเมจอาวุธ</CardTitle>
|
|
<CardDescription>เปอร์เซ็นต์ดาเมจของอาวุธทั้งหมดใน Condition นี้</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="min-w-0">
|
|
<ChartContainer
|
|
config={chartConfig}
|
|
className="min-w-0 aspect-auto w-full"
|
|
style={{
|
|
height: singleMetric ? 300 : 400,
|
|
}}
|
|
aria-label="กราฟเปรียบเทียบเปอร์เซ็นต์ดาเมจอาวุธ"
|
|
>
|
|
<BarChart accessibilityLayer data={data} margin={{ right: 16 }}>
|
|
<CartesianGrid vertical={false} />
|
|
<YAxis type="number" tickLine={false} axisLine={false} tickFormatter={(value) => `${value}%`} />
|
|
<XAxis
|
|
dataKey="name"
|
|
type="category"
|
|
height={60}
|
|
tickLine={false}
|
|
axisLine={false}
|
|
interval={0}
|
|
tick={({ x, y, payload, width }) => {
|
|
const weapon = data.find((row) => row.name === payload.value);
|
|
if (!weapon) return <g />;
|
|
const size = Math.max(8, Math.min(40, Number(width) / data.length - 4 || 32));
|
|
return (
|
|
<g transform={`translate(${x},${y})`}>
|
|
<title>{weapon.name}</title>
|
|
<image
|
|
href={weapon.imageUrl}
|
|
x={-size / 2}
|
|
y={4}
|
|
width={size}
|
|
height={size}
|
|
preserveAspectRatio="xMidYMid meet"
|
|
role="img"
|
|
aria-label={weapon.name}
|
|
/>
|
|
</g>
|
|
);
|
|
}}
|
|
/>
|
|
<ChartTooltip content={
|
|
<ChartTooltipContent formatter={(value, name) => (
|
|
<div className="flex flex-1 items-center justify-between gap-4">
|
|
<span className="text-muted-foreground">{chartConfig[name as keyof typeof chartConfig]?.label ?? name}</span>
|
|
<span className="font-mono font-medium">{value}%</span>
|
|
</div>
|
|
)} />
|
|
} />
|
|
<ChartLegend content={<ChartLegendContent className="flex-wrap" />} />
|
|
{hasPersonal ? <Bar dataKey="personal" fill="var(--color-personal)" radius={4} maxBarSize={32} /> : null}
|
|
{hasOverall ? <Bar dataKey="overall" fill="var(--color-overall)" radius={4} maxBarSize={32} /> : null}
|
|
</BarChart>
|
|
</ChartContainer>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|