chart
Donut chart with center stats
Donut chart with center stats
Загрузка превью…
src/chart/c-chart-20.vue
<!-- Замена библиотеки: см. c-chart-19.vue / docs/PORTING.md §19/§27. -->
<script setup lang="ts">
import { DonutChart } from "vue-chrts"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { ChartContainer, ChartLegendContent, type ChartConfig } from "@/components/ui/chart"
const chartData = [
{ status: "completed", count: 186 },
{ status: "inProgress", count: 94 },
{ status: "pending", count: 62 },
{ status: "cancelled", count: 28 },
]
const totalTasks = chartData.reduce((sum, d) => sum + d.count, 0)
const completionRate = Math.round((chartData[0]!.count / totalTasks) * 100)
const chartConfig = {
count: { label: "Tasks" },
completed: { label: "Completed", color: "var(--chart-1)" },
inProgress: { label: "In Progress", color: "var(--chart-2)" },
pending: { label: "Pending", color: "var(--chart-3)" },
cancelled: { label: "Cancelled", color: "var(--chart-5)" },
} satisfies ChartConfig
const categories = {
completed: { name: "Completed", color: "var(--chart-1)" },
inProgress: { name: "In Progress", color: "var(--chart-2)" },
pending: { name: "Pending", color: "var(--chart-3)" },
cancelled: { name: "Cancelled", color: "var(--chart-5)" },
}
const donutData = chartData.map((d) => d.count)
const legendPayload = [
{ dataKey: "completed", value: "completed", color: "var(--chart-1)", status: "completed" },
{ dataKey: "inProgress", value: "inProgress", color: "var(--chart-2)", status: "inProgress" },
{ dataKey: "pending", value: "pending", color: "var(--chart-3)", status: "pending" },
{ dataKey: "cancelled", value: "cancelled", color: "var(--chart-5)", status: "cancelled" },
]
</script>
<template>
<Card class="w-full max-w-xs">
<CardHeader class="items-center pb-0">
<CardTitle>Task Status</CardTitle>
<CardDescription>Current sprint task breakdown</CardDescription>
</CardHeader>
<CardContent class="flex-1 pb-0">
<!--
`recharts`' `.recharts-legend-wrapper` is `position: absolute` inside
the SAME fixed-height chart box (verified via getComputedStyle on the
upstream render) — it overlays the plot area rather than adding to
layout height. `vue-chrts` has no equivalent composable legend slot,
so `ChartLegendContent` is placed here as an absolutely-positioned
sibling inside the same `ChartContainer` (already providing the
chart context it needs via `useChart`) instead of a second,
normal-flow `ChartContainer`, matching that overlay behavior instead
of adding extra page height.
-->
<ChartContainer :config="chartConfig" class="relative mx-auto aspect-square max-h-[280px]">
<DonutChart
:data="donutData"
:categories="categories"
:radius="95"
:arc-width="35"
:pad-angle="0.02"
:height="280"
:duration="0"
hide-tooltip
hide-legend
>
<div class="flex flex-col items-center">
<span class="fill-foreground text-3xl font-bold tabular-nums">{{ completionRate }}%</span>
<span class="fill-muted-foreground text-xs">Completed</span>
</div>
</DonutChart>
<ChartLegendContent :payload="legendPayload" name-key="status" class="absolute inset-x-0 bottom-0 -translate-y-2 pt-0" />
</ChartContainer>
</CardContent>
</Card>
</template>