83 lines
4.8 KiB
TypeScript
83 lines
4.8 KiB
TypeScript
"use client";
|
|
|
|
import { Bar, BarChart, CartesianGrid, LabelList, ReferenceLine, XAxis, YAxis } from "recharts";
|
|
import Image from "@/components/ui/resilient-image";
|
|
import { Badge } from "@/components/ui/badge";
|
|
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: "ดาเมจทั้งทีม", color: "var(--chart-1)" },
|
|
personal: { label: "ดาเมจส่วนตัว", color: "var(--chart-2)" },
|
|
} satisfies ChartConfig;
|
|
|
|
function percentage(value: string | null) {
|
|
if (!value?.trim()) return null;
|
|
const number = Number(value);
|
|
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}`,
|
|
weapon: row.weapon,
|
|
refinement: row.refinement,
|
|
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 <p className="text-sm text-muted-foreground">ไม่มีข้อมูลเปรียบเทียบสำหรับกราฟนี้</p>;
|
|
|
|
return <Card className="min-w-0" size="sm">
|
|
<CardHeader>
|
|
<CardTitle>เปรียบเทียบดาเมจอาวุธ</CardTitle>
|
|
<CardDescription>เปอร์เซ็นต์ดาเมจในเงื่อนไขนี้ - เส้น 100% คือค่าฐาน</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="min-w-0">
|
|
<ChartContainer config={chartConfig} className="aspect-auto min-w-0 w-full"
|
|
style={{ height: 420 }}
|
|
aria-label="กราฟเปรียบเทียบเปอร์เซ็นต์ดาเมจอาวุธ">
|
|
<BarChart accessibilityLayer data={data} layout="horizontal" barGap={4} margin={{ top: 28, right: 12, left: 0 }}>
|
|
<CartesianGrid vertical={false} />
|
|
<YAxis type="number" tickLine={false} axisLine={false}
|
|
domain={[(minimum: number) => Math.min(0, minimum), (maximum: number) => Math.max(110, maximum)]}
|
|
tickFormatter={(value) => `${value}%`} />
|
|
<XAxis dataKey="name" type="category" tickLine={false} axisLine={false} height={96} interval={0}
|
|
tick={({ x, y, width, payload }) => {
|
|
const row = data.find((item) => item.name === payload.value);
|
|
if (!row) return <g />;
|
|
const scale = Math.min(1, Math.max(0, Number(width) / data.length - 4) / 56);
|
|
return <g transform={`translate(${x},${Number(y) + 8}) scale(${scale})`}>
|
|
<foreignObject x={-28} y={0} width={56} height={80}>
|
|
<div className="flex flex-col items-center gap-1" title={row.name}>
|
|
<Image src={row.weapon.imageUrl} alt={row.weapon.name} width={56} height={56} className="size-14 object-contain" />
|
|
<Badge variant="secondary">R{row.refinement}</Badge>
|
|
</div>
|
|
</foreignObject>
|
|
</g>;
|
|
}} />
|
|
<ReferenceLine y={100} stroke="var(--muted-foreground)" strokeDasharray="4 4" />
|
|
<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={singleMetric ? 48 : 40} isAnimationActive={false}>
|
|
<LabelList position="top" formatter={(value) => typeof value === "number" ? `${value.toLocaleString("th-TH", { maximumFractionDigits: 2 })}%` : ""} className="fill-foreground text-xs" />
|
|
</Bar>}
|
|
{hasOverall && <Bar dataKey="overall" fill="var(--color-overall)" radius={4} maxBarSize={singleMetric ? 48 : 40} isAnimationActive={false}>
|
|
<LabelList position="top" formatter={(value) => typeof value === "number" ? `${value.toLocaleString("th-TH", { maximumFractionDigits: 2 })}%` : ""} className="fill-foreground text-xs" />
|
|
</Bar>}
|
|
</BarChart>
|
|
</ChartContainer>
|
|
</CardContent>
|
|
</Card>;
|
|
}
|