dress/resources/js/components/charts/knob-chart.tsx

130 lines
4.6 KiB
TypeScript

"use client"
import React, { useMemo } from "react"
import {
Label,
PolarGrid,
PolarRadiusAxis,
RadialBar,
RadialBarChart,
} from "recharts"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { ChartConfig, ChartContainer } from "@/components/ui/chart"
export const KnobChart = React.memo(function KnobChart({
title,
description,
percentage,
label,
footerText
}: {
title: string
description: string
percentage: number
label?: string
footerText?: string
}) {
const { chartData, chartConfig } = useMemo(() => {
return {
chartData: [
{
name: "value",
value: percentage,
fill: "hsl(var(--primary))",
},
],
chartConfig: {
value: {
label: label || "Value",
},
} satisfies ChartConfig,
}
}, [percentage, label])
return (
<Card className="flex flex-col">
<CardHeader className="items-center pb-0">
<CardTitle>{title}</CardTitle>
<CardDescription>{description}</CardDescription>
</CardHeader>
<CardContent className="flex-1 pb-0">
<ChartContainer
config={chartConfig}
className="mx-auto aspect-square max-h-[250px]"
>
<RadialBarChart
data={chartData}
startAngle={90}
endAngle={90 - (360 * (percentage / 100))}
innerRadius={80}
outerRadius={110}
>
<PolarGrid
gridType="circle"
radialLines={false}
stroke="none"
className="first:fill-muted last:fill-background"
polarRadius={[86, 74]}
/>
<RadialBar
dataKey="value"
background
cornerRadius={10}
/>
<PolarRadiusAxis
tick={false}
tickLine={false}
axisLine={false}
>
<Label
content={({ viewBox }) => {
if (viewBox && "cx" in viewBox && "cy" in viewBox) {
return (
<text
x={viewBox.cx}
y={viewBox.cy}
textAnchor="middle"
dominantBaseline="middle"
>
<tspan
x={viewBox.cx}
y={viewBox.cy}
className="fill-foreground text-4xl font-bold"
>
{percentage.toFixed(1)}%
</tspan>
<tspan
x={viewBox.cx}
y={(viewBox.cy || 0) + 24}
className="fill-muted-foreground"
>
{label || "Percentage"}
</tspan>
</text>
)
}
}}
/>
</PolarRadiusAxis>
</RadialBarChart>
</ChartContainer>
</CardContent>
{footerText && (
<CardFooter className="flex-col gap-2 text-sm">
<div className="leading-none text-muted-foreground">
{footerText}
</div>
</CardFooter>
)}
</Card>
)
})