chart
Active segment donut with center label
Active segment donut with center label
Загрузка превью…
src/chart/c-chart-22.vue
<!--
Замена библиотеки: см. c-chart-19.vue / docs/PORTING.md §19/§27.
`activeShape` (увеличение сегмента по hover) не переносится — состояние
не проверяется статичным скриншотом.
-->
<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 = [
{ plan: "free", users: 12800 },
{ plan: "starter", users: 5400 },
{ plan: "pro", users: 3600 },
{ plan: "enterprise", users: 1200 },
]
const totalUsers = chartData.reduce((sum, d) => sum + d.users, 0)
const paidUsers = totalUsers - chartData[0]!.users
const conversionRate = ((paidUsers / totalUsers) * 100).toFixed(1)
const chartConfig = {
users: { label: "Users" },
free: { label: "Free", color: "var(--chart-5)" },
starter: { label: "Starter", color: "var(--chart-3)" },
pro: { label: "Pro", color: "var(--chart-2)" },
enterprise: { label: "Enterprise", color: "var(--chart-1)" },
} satisfies ChartConfig
const categories = {
free: { name: "Free", color: "var(--chart-5)" },
starter: { name: "Starter", color: "var(--chart-3)" },
pro: { name: "Pro", color: "var(--chart-2)" },
enterprise: { name: "Enterprise", color: "var(--chart-1)" },
}
const donutData = chartData.map((d) => d.users)
const legendPayload = [
{ dataKey: "free", value: "free", color: "var(--chart-5)", plan: "free" },
{ dataKey: "starter", value: "starter", color: "var(--chart-3)", plan: "starter" },
{ dataKey: "pro", value: "pro", color: "var(--chart-2)", plan: "pro" },
{ dataKey: "enterprise", value: "enterprise", color: "var(--chart-1)", plan: "enterprise" },
]
</script>
<template>
<Card class="w-full max-w-xs">
<CardHeader class="items-center pb-0">
<CardTitle>Conversion Funnel</CardTitle>
<CardDescription>{{ conversionRate }}% of users are on paid plans</CardDescription>
</CardHeader>
<CardContent class="flex-1 pb-0">
<!--
`recharts`' `.recharts-legend-wrapper` is `position: absolute` inside
the SAME fixed-height chart box — see c-chart-20.vue for the full
explanation. Placed as an absolute sibling in the same
`ChartContainer` instead of a second, normal-flow one.
-->
<ChartContainer :config="chartConfig" class="relative mx-auto aspect-square max-h-[300px]">
<DonutChart
:data="donutData"
:categories="categories"
:radius="100"
:arc-width="45"
:pad-angle="0.02"
:height="300"
:duration="0"
hide-tooltip
hide-legend
>
<div class="flex flex-col items-center">
<span class="fill-foreground text-2xl font-bold tabular-nums">{{ paidUsers.toLocaleString() }}</span>
<span class="fill-muted-foreground text-xs">Paid Users</span>
</div>
</DonutChart>
<ChartLegendContent :payload="legendPayload" name-key="plan" class="absolute inset-x-0 bottom-0 -translate-y-2 pt-0" />
</ChartContainer>
</CardContent>
</Card>
</template>