Pie Chart
Pie and donut charts: separator, label, custom label, label list, legend, active slice, centre text and stacked rings.
Part-to-whole charts built on the Chart component (see that page for installation and the API). At most five slices, with a 2px surface gap.
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent,} from "@/components/ui/chart";import { Pie, PieChart } from "recharts";import { IconPlaceholder } from "@/edmi/icon-placeholder";
const data = [ { browser: "chrome", visitors: 275, fill: "var(--color-chrome)" }, { browser: "safari", visitors: 200, fill: "var(--color-safari)" }, { browser: "firefox", visitors: 187, fill: "var(--color-firefox)" }, { browser: "edge", visitors: 173, fill: "var(--color-edge)" }, { browser: "other", visitors: 90, fill: "var(--color-other)" },];
const config = { visitors: { label: "Visitors" }, chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" },} satisfies ChartConfig;
export default function Demo() { return ( <Card className="w-full max-w-sm gap-0 py-0"> <CardHeader className="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <CardTitle className="font-semibold text-base tracking-[-0.2px]"> Pie Chart </CardTitle> <CardDescription className="text-[13.5px]"> January - June 2024 </CardDescription> </CardHeader> <CardContent className="items-center px-5 pt-2.5 pb-0"> <ChartContainer config={config} className="aspect-auto h-[236px] w-full max-w-64 [&_.recharts-surface]:overflow-visible" > <PieChart> <ChartTooltip content={<ChartTooltipContent nameKey="browser" hideLabel />} /> <Pie data={data} dataKey="visitors" nameKey="browser" outerRadius={118} stroke="var(--card)" strokeWidth={2} /> </PieChart> </ChartContainer> </CardContent> <CardFooter className="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div className="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <IconPlaceholder lucide="TrendingUpIcon" tabler="IconTrendingUp" hugeicons="ChartUpIcon" phosphor="TrendUpIcon" remixicon="RiArrowUpLine" className="size-4" /> </div> <div className="text-muted-foreground"> Showing total visitors for the last 6 months </div> </CardFooter> </Card> );}<script setup lang="ts">import { Donut } from "@unovis/ts";import { VisDonut, VisSingleContainer } from "@unovis/vue";import { TrendingUpIcon } from "@lucide/vue";import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";import { type ChartConfig, ChartContainer, ChartLegendContent, ChartTooltip, ChartTooltipContent, componentToString,} from "@/components/ui/chart";
type Slice = { browser: string; visitors: number } & Record<string, string | number>;
const config = { chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" },} satisfies ChartConfig;
// Each slice also carries `[browser]: visitors` so the tooltip can look up its label.const data: Slice[] = [ { browser: "chrome", visitors: 275 }, { browser: "safari", visitors: 200 }, { browser: "firefox", visitors: 187 }, { browser: "edge", visitors: 173 }, { browser: "other", visitors: 90 },].map((d) => ({ ...d, [d.browser]: d.visitors }));
const value = (d: Slice) => d.visitors;const color = (d: Slice) => config[d.browser as keyof typeof config].color;const template = componentToString(config, ChartTooltipContent, { hideLabel: true });const triggers = { [Donut.selectors.segment]: template };</script>
<template> <Card class="w-full max-w-sm gap-0 py-0"> <CardHeader class="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <CardTitle class="font-semibold text-base tracking-[-0.2px]">Pie Chart</CardTitle> <CardDescription class="text-[13.5px]">January - June 2024</CardDescription> </CardHeader> <CardContent class="items-center px-5 pt-2.5 pb-0"> <ChartContainer :config="config" class="aspect-auto h-[236px] w-full max-w-64"> <VisSingleContainer :data="data" :margin="{ top: 8, bottom: 8 }"> <VisDonut :value="value" :color="color" :arc-width="0" :pad-angle="0.02" /> <ChartTooltip :triggers="triggers" /> </VisSingleContainer> </ChartContainer> </CardContent> <CardFooter class="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div class="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <TrendingUpIcon class="size-4" /> </div> <div class="text-muted-foreground">Showing total visitors for the last 6 months</div> </CardFooter> </Card></template><script lang="ts"> import { PieChart } from "layerchart"; import IconPlaceholder from "$lib/components/icon-placeholder/icon-placeholder.svelte"; import * as Card from "$lib/components/ui/card"; import * as Chart from "$lib/components/ui/chart";
const config = { chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" }, } satisfies Chart.ChartConfig;
const data = [ { browser: "chrome", visitors: 275, name: "Chrome", color: "var(--color-chrome)" }, { browser: "safari", visitors: 200, name: "Safari", color: "var(--color-safari)" }, { browser: "firefox", visitors: 187, name: "Firefox", color: "var(--color-firefox)" }, { browser: "edge", visitors: 173, name: "Edge", color: "var(--color-edge)" }, { browser: "other", visitors: 90, name: "Other", color: "var(--color-other)" }, ];</script>
<Card.Root class="w-full max-w-sm gap-0 py-0"> <Card.Header class="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <Card.Title class="font-semibold text-base tracking-[-0.2px]">Pie Chart</Card.Title> <Card.Description class="text-[13.5px]">January - June 2024</Card.Description> </Card.Header> <Card.Content class="items-center px-5 pt-2.5 pb-0"> <Chart.Container {config} class="aspect-auto h-[236px] w-full"> <PieChart {data} key="browser" label="name" value="visitors" c="color" padAngle={0.02} props={{ pie: { motion: "tween" } }} > {#snippet tooltip()} <Chart.Tooltip hideLabel nameKey="browser" /> {/snippet} </PieChart> </Chart.Container> </Card.Content> <Card.Footer class="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div class="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <IconPlaceholder lucide="TrendingUpIcon" tabler="IconTrendingUp" hugeicons="ChartUpIcon" phosphor="TrendUpIcon" remixicon="RiArrowUpLine" class="size-4" /> </div> <div class="text-muted-foreground">Showing total visitors for the last 6 months</div> </Card.Footer></Card.Root>Separator None
Section titled “Separator None”import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent,} from "@/components/ui/chart";import { Pie, PieChart } from "recharts";import { IconPlaceholder } from "@/edmi/icon-placeholder";
const data = [ { browser: "chrome", visitors: 275, fill: "var(--color-chrome)" }, { browser: "safari", visitors: 200, fill: "var(--color-safari)" }, { browser: "firefox", visitors: 187, fill: "var(--color-firefox)" }, { browser: "edge", visitors: 173, fill: "var(--color-edge)" }, { browser: "other", visitors: 90, fill: "var(--color-other)" },];
const config = { visitors: { label: "Visitors" }, chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" },} satisfies ChartConfig;
export default function Demo() { return ( <Card className="w-full max-w-sm gap-0 py-0"> <CardHeader className="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <CardTitle className="font-semibold text-base tracking-[-0.2px]"> Pie Chart - Separator None </CardTitle> <CardDescription className="text-[13.5px]"> January - June 2024 </CardDescription> </CardHeader> <CardContent className="items-center px-5 pt-2.5 pb-0"> <ChartContainer config={config} className="aspect-auto h-[236px] w-full max-w-64 [&_.recharts-surface]:overflow-visible" > <PieChart> <ChartTooltip content={<ChartTooltipContent nameKey="browser" hideLabel />} /> <Pie data={data} dataKey="visitors" nameKey="browser" outerRadius={118} stroke="var(--card)" strokeWidth={0} /> </PieChart> </ChartContainer> </CardContent> <CardFooter className="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div className="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <IconPlaceholder lucide="TrendingUpIcon" tabler="IconTrendingUp" hugeicons="ChartUpIcon" phosphor="TrendUpIcon" remixicon="RiArrowUpLine" className="size-4" /> </div> <div className="text-muted-foreground"> Showing total visitors for the last 6 months </div> </CardFooter> </Card> );}<script setup lang="ts">import { Donut } from "@unovis/ts";import { VisDonut, VisSingleContainer } from "@unovis/vue";import { TrendingUpIcon } from "@lucide/vue";import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";import { type ChartConfig, ChartContainer, ChartLegendContent, ChartTooltip, ChartTooltipContent, componentToString,} from "@/components/ui/chart";
type Slice = { browser: string; visitors: number } & Record<string, string | number>;
const config = { chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" },} satisfies ChartConfig;
// Each slice also carries `[browser]: visitors` so the tooltip can look up its label.const data: Slice[] = [ { browser: "chrome", visitors: 275 }, { browser: "safari", visitors: 200 }, { browser: "firefox", visitors: 187 }, { browser: "edge", visitors: 173 }, { browser: "other", visitors: 90 },].map((d) => ({ ...d, [d.browser]: d.visitors }));
const value = (d: Slice) => d.visitors;const color = (d: Slice) => config[d.browser as keyof typeof config].color;const template = componentToString(config, ChartTooltipContent, { hideLabel: true });const triggers = { [Donut.selectors.segment]: template };</script>
<template> <Card class="w-full max-w-sm gap-0 py-0"> <CardHeader class="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <CardTitle class="font-semibold text-base tracking-[-0.2px]">Pie Chart - Separator None</CardTitle> <CardDescription class="text-[13.5px]">January - June 2024</CardDescription> </CardHeader> <CardContent class="items-center px-5 pt-2.5 pb-0"> <ChartContainer :config="config" class="aspect-auto h-[236px] w-full max-w-64"> <VisSingleContainer :data="data" :margin="{ top: 8, bottom: 8 }"> <VisDonut :value="value" :color="color" :arc-width="0" :pad-angle="0" /> <ChartTooltip :triggers="triggers" /> </VisSingleContainer> </ChartContainer> </CardContent> <CardFooter class="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div class="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <TrendingUpIcon class="size-4" /> </div> <div class="text-muted-foreground">Showing total visitors for the last 6 months</div> </CardFooter> </Card></template><script lang="ts"> import { PieChart } from "layerchart"; import IconPlaceholder from "$lib/components/icon-placeholder/icon-placeholder.svelte"; import * as Card from "$lib/components/ui/card"; import * as Chart from "$lib/components/ui/chart";
const config = { chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" }, } satisfies Chart.ChartConfig;
const data = [ { browser: "chrome", visitors: 275, name: "Chrome", color: "var(--color-chrome)" }, { browser: "safari", visitors: 200, name: "Safari", color: "var(--color-safari)" }, { browser: "firefox", visitors: 187, name: "Firefox", color: "var(--color-firefox)" }, { browser: "edge", visitors: 173, name: "Edge", color: "var(--color-edge)" }, { browser: "other", visitors: 90, name: "Other", color: "var(--color-other)" }, ];</script>
<Card.Root class="w-full max-w-sm gap-0 py-0"> <Card.Header class="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <Card.Title class="font-semibold text-base tracking-[-0.2px]">Pie Chart - Separator None</Card.Title> <Card.Description class="text-[13.5px]">January - June 2024</Card.Description> </Card.Header> <Card.Content class="items-center px-5 pt-2.5 pb-0"> <Chart.Container {config} class="aspect-auto h-[236px] w-full"> <PieChart {data} key="browser" label="name" value="visitors" c="color" padAngle={0} props={{ pie: { motion: "tween" } }} > {#snippet tooltip()} <Chart.Tooltip hideLabel nameKey="browser" /> {/snippet} </PieChart> </Chart.Container> </Card.Content> <Card.Footer class="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div class="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <IconPlaceholder lucide="TrendingUpIcon" tabler="IconTrendingUp" hugeicons="ChartUpIcon" phosphor="TrendUpIcon" remixicon="RiArrowUpLine" class="size-4" /> </div> <div class="text-muted-foreground">Showing total visitors for the last 6 months</div> </Card.Footer></Card.Root>import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent,} from "@/components/ui/chart";import { Pie, PieChart } from "recharts";import { IconPlaceholder } from "@/edmi/icon-placeholder";
const data = [ { browser: "chrome", visitors: 275, fill: "var(--color-chrome)" }, { browser: "safari", visitors: 200, fill: "var(--color-safari)" }, { browser: "firefox", visitors: 187, fill: "var(--color-firefox)" }, { browser: "edge", visitors: 173, fill: "var(--color-edge)" }, { browser: "other", visitors: 90, fill: "var(--color-other)" },];
const config = { visitors: { label: "Visitors" }, chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" },} satisfies ChartConfig;
const RAD = Math.PI / 180;const pt = (cx: number, cy: number, r: number, mid: number) => ({ x: cx + r * Math.cos(-mid * RAD), y: cy + r * Math.sin(-mid * RAD),});
export default function Demo() { return ( <Card className="w-full max-w-sm gap-0 py-0"> <CardHeader className="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <CardTitle className="font-semibold text-base tracking-[-0.2px]"> Pie Chart - Label </CardTitle> <CardDescription className="text-[13.5px]"> January - June 2024 </CardDescription> </CardHeader> <CardContent className="items-center px-5 pt-2.5 pb-0"> <ChartContainer config={config} className="aspect-auto h-[236px] w-full max-w-64 [&_.recharts-surface]:overflow-visible" > <PieChart> <ChartTooltip content={<ChartTooltipContent nameKey="browser" hideLabel />} /> <Pie data={data} dataKey="visitors" nameKey="browser" outerRadius={90} stroke="var(--card)" strokeWidth={2} labelLine={false} label={(p) => { const a = pt(p.cx, p.cy, p.outerRadius, p.midAngle ?? 0); const b = pt(p.cx, p.cy, p.outerRadius + 14, p.midAngle ?? 0); const x = b.x + (b.x > p.cx ? 8 : -8); return ( <g> <path d={`M${a.x},${a.y}L${b.x},${b.y}`} stroke={p.fill} strokeWidth={1} fill="none" /> <text x={x} y={b.y + 4} textAnchor={b.x > p.cx ? "start" : "end"} className="fill-foreground text-xs tabular-nums" > {p.value} </text> </g> ); }} /> </PieChart> </ChartContainer> </CardContent> <CardFooter className="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div className="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <IconPlaceholder lucide="TrendingUpIcon" tabler="IconTrendingUp" hugeicons="ChartUpIcon" phosphor="TrendUpIcon" remixicon="RiArrowUpLine" className="size-4" /> </div> <div className="text-muted-foreground"> Showing total visitors for the last 6 months </div> </CardFooter> </Card> );}<script setup lang="ts">import { TrendingUpIcon } from "@lucide/vue";import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";import { type ChartConfig, ChartContainer } from "@/components/ui/chart";
const config = { chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" },} satisfies ChartConfig;
const data = [ { browser: "chrome", visitors: 275 }, { browser: "safari", visitors: 200 }, { browser: "firefox", visitors: 187 }, { browser: "edge", visitors: 173 }, { browser: "other", visitors: 90 },];
const CX = 128;const CY = 118;const RINGS = [[90,0]] as const; // [outer, inner]const TOTAL = 925;const p = (r: number, a: number) => [CX + r * Math.sin(a), CY - r * Math.cos(a)];const f = (n: number) => n.toFixed(1);
let acc = 0;const slices = data.map((d) => { const a0 = (acc / TOTAL) * 2 * Math.PI; acc += d.visitors; const a1 = (acc / TOTAL) * 2 * Math.PI; const large = a1 - a0 > Math.PI ? 1 : 0; const paths = RINGS.map(([ro, ri], ring) => { const grow = 0; const o = ro + grow; const [x0, y0] = p(o, a0); const [x1, y1] = p(o, a1); if (!ri) return `M${CX},${CY}L${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}Z`; const [x2, y2] = p(ri, a1); const [x3, y3] = p(ri, a0); return `M${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}L${f(x2)},${f(y2)}A${ri},${ri} 0 ${large} 0 ${f(x3)},${f(y3)}Z`; }); const mid = (a0 + a1) / 2; const at = (r: number) => ({ x: CX + r * Math.sin(mid), y: CY - r * Math.cos(mid) }); return { ...d, paths, at };});</script>
<template> <Card class="w-full max-w-sm gap-0 py-0"> <CardHeader class="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <CardTitle class="font-semibold text-base tracking-[-0.2px]">Pie Chart - Label</CardTitle> <CardDescription class="text-[13.5px]">January - June 2024</CardDescription> </CardHeader> <CardContent class="items-center px-5 pt-2.5 pb-0"> <ChartContainer :config="config" class="aspect-auto h-auto w-auto"> <svg width="256" height="236" viewBox="0 0 256 236" role="img" aria-label="Pie Chart - Label" class="block overflow-visible"> <template v-for="s in slices" :key="s.browser"> <path v-for="(d, ring) in s.paths" :key="ring" :d="d" :fill="`var(--color-${s.browser})`" stroke="var(--card)" stroke-width="2" stroke-linejoin="round" /> </template> <template v-for="s in slices" :key="'l' + s.browser"> <path :d="`M${f(s.at(90).x)},${f(s.at(90).y)}L${f(s.at(104).x)},${f(s.at(104).y)}`" :stroke="`var(--color-${s.browser})`" stroke-width="1" fill="none" /> <text :x="s.at(104).x + (s.at(104).x > CX ? 8 : -8)" :y="s.at(104).y + 4" :text-anchor="s.at(104).x > CX ? 'start' : 'end'" class="fill-foreground text-xs tabular-nums">{{ s.visitors }}</text> </template> </svg> </ChartContainer> </CardContent> <CardFooter class="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div class="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <TrendingUpIcon class="size-4" /> </div> <div class="text-muted-foreground">Showing total visitors for the last 6 months</div> </CardFooter> </Card></template><script lang="ts"> import IconPlaceholder from "$lib/components/icon-placeholder/icon-placeholder.svelte"; import * as Card from "$lib/components/ui/card"; import * as Chart from "$lib/components/ui/chart";
const config = { chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" }, } satisfies Chart.ChartConfig;
const data = [ { browser: "chrome", visitors: 275 }, { browser: "safari", visitors: 200 }, { browser: "firefox", visitors: 187 }, { browser: "edge", visitors: 173 }, { browser: "other", visitors: 90 }, ];
const CX = 128; const CY = 118; const RINGS = [[90,0]] as const; // [outer, inner] const TOTAL = 925; const p = (r: number, a: number) => [CX + r * Math.sin(a), CY - r * Math.cos(a)]; const f = (n: number) => n.toFixed(1);
let acc = 0; const slices = data.map((d) => { const a0 = (acc / TOTAL) * 2 * Math.PI; acc += d.visitors; const a1 = (acc / TOTAL) * 2 * Math.PI; const large = a1 - a0 > Math.PI ? 1 : 0; const paths = RINGS.map(([ro, ri], ring) => { const grow = 0; const o = ro + grow; const [x0, y0] = p(o, a0); const [x1, y1] = p(o, a1); if (!ri) return `M${CX},${CY}L${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}Z`; const [x2, y2] = p(ri, a1); const [x3, y3] = p(ri, a0); return `M${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}L${f(x2)},${f(y2)}A${ri},${ri} 0 ${large} 0 ${f(x3)},${f(y3)}Z`; }); const mid = (a0 + a1) / 2; const at = (r: number) => ({ x: CX + r * Math.sin(mid), y: CY - r * Math.cos(mid) }); return { ...d, paths, at }; });</script>
<Card.Root class="w-full max-w-sm gap-0 py-0"> <Card.Header class="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <Card.Title class="font-semibold text-base tracking-[-0.2px]">Pie Chart - Label</Card.Title> <Card.Description class="text-[13.5px]">January - June 2024</Card.Description> </Card.Header> <Card.Content class="items-center px-5 pt-2.5 pb-0"> <Chart.Container {config} class="aspect-auto h-auto w-auto flex-col items-center"> <svg width="256" height="236" viewBox="0 0 256 236" role="img" aria-label="Pie Chart - Label" class="block overflow-visible"> {#each slices as s (s.browser)} {#each s.paths as d, ring (ring)} <path {d} fill="var(--color-{s.browser})" stroke="var(--card)" stroke-width="2" stroke-linejoin="round" /> {/each} {/each} {#each slices as s (s.browser)} {@const a = s.at(90)} {@const b = s.at(104)} <path d="M{f(a.x)},{f(a.y)}L{f(b.x)},{f(b.y)}" stroke="var(--color-{s.browser})" stroke-width="1" fill="none" /> <text x={b.x + (b.x > CX ? 8 : -8)} y={b.y + 4} text-anchor={b.x > CX ? "start" : "end"} class="fill-foreground text-xs tabular-nums">{s.visitors}</text> {/each} </svg> </Chart.Container> </Card.Content> <Card.Footer class="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div class="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <IconPlaceholder lucide="TrendingUpIcon" tabler="IconTrendingUp" hugeicons="ChartUpIcon" phosphor="TrendUpIcon" remixicon="RiArrowUpLine" class="size-4" /> </div> <div class="text-muted-foreground">Showing total visitors for the last 6 months</div> </Card.Footer></Card.Root>Custom Label
Section titled “Custom Label”import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent,} from "@/components/ui/chart";import { Pie, PieChart } from "recharts";import { IconPlaceholder } from "@/edmi/icon-placeholder";
const data = [ { browser: "chrome", visitors: 275, fill: "var(--color-chrome)" }, { browser: "safari", visitors: 200, fill: "var(--color-safari)" }, { browser: "firefox", visitors: 187, fill: "var(--color-firefox)" }, { browser: "edge", visitors: 173, fill: "var(--color-edge)" }, { browser: "other", visitors: 90, fill: "var(--color-other)" },];
const config = { visitors: { label: "Visitors" }, chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" },} satisfies ChartConfig;
const RAD = Math.PI / 180;const pt = (cx: number, cy: number, r: number, mid: number) => ({ x: cx + r * Math.cos(-mid * RAD), y: cy + r * Math.sin(-mid * RAD),});
export default function Demo() { return ( <Card className="w-full max-w-sm gap-0 py-0"> <CardHeader className="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <CardTitle className="font-semibold text-base tracking-[-0.2px]"> Pie Chart - Custom Label </CardTitle> <CardDescription className="text-[13.5px]"> January - June 2024 </CardDescription> </CardHeader> <CardContent className="items-center px-5 pt-2.5 pb-0"> <ChartContainer config={config} className="aspect-auto h-[236px] w-full max-w-64 [&_.recharts-surface]:overflow-visible" > <PieChart> <ChartTooltip content={<ChartTooltipContent nameKey="browser" hideLabel />} /> <Pie data={data} dataKey="visitors" nameKey="browser" outerRadius={90} stroke="var(--card)" strokeWidth={2} labelLine={false} label={(p) => { const a = pt(p.cx, p.cy, p.outerRadius + 24, p.midAngle ?? 0); return ( <text x={a.x} y={a.y + 4} textAnchor="middle" className="fill-foreground text-xs tabular-nums" > {p.value} </text> ); }} /> </PieChart> </ChartContainer> </CardContent> <CardFooter className="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div className="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <IconPlaceholder lucide="TrendingUpIcon" tabler="IconTrendingUp" hugeicons="ChartUpIcon" phosphor="TrendUpIcon" remixicon="RiArrowUpLine" className="size-4" /> </div> <div className="text-muted-foreground"> Showing total visitors for the last 6 months </div> </CardFooter> </Card> );}<script setup lang="ts">import { TrendingUpIcon } from "@lucide/vue";import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";import { type ChartConfig, ChartContainer } from "@/components/ui/chart";
const config = { chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" },} satisfies ChartConfig;
const data = [ { browser: "chrome", visitors: 275 }, { browser: "safari", visitors: 200 }, { browser: "firefox", visitors: 187 }, { browser: "edge", visitors: 173 }, { browser: "other", visitors: 90 },];
const CX = 128;const CY = 118;const RINGS = [[90,0]] as const; // [outer, inner]const TOTAL = 925;const p = (r: number, a: number) => [CX + r * Math.sin(a), CY - r * Math.cos(a)];const f = (n: number) => n.toFixed(1);
let acc = 0;const slices = data.map((d) => { const a0 = (acc / TOTAL) * 2 * Math.PI; acc += d.visitors; const a1 = (acc / TOTAL) * 2 * Math.PI; const large = a1 - a0 > Math.PI ? 1 : 0; const paths = RINGS.map(([ro, ri], ring) => { const grow = 0; const o = ro + grow; const [x0, y0] = p(o, a0); const [x1, y1] = p(o, a1); if (!ri) return `M${CX},${CY}L${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}Z`; const [x2, y2] = p(ri, a1); const [x3, y3] = p(ri, a0); return `M${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}L${f(x2)},${f(y2)}A${ri},${ri} 0 ${large} 0 ${f(x3)},${f(y3)}Z`; }); const mid = (a0 + a1) / 2; const at = (r: number) => ({ x: CX + r * Math.sin(mid), y: CY - r * Math.cos(mid) }); return { ...d, paths, at };});</script>
<template> <Card class="w-full max-w-sm gap-0 py-0"> <CardHeader class="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <CardTitle class="font-semibold text-base tracking-[-0.2px]">Pie Chart - Custom Label</CardTitle> <CardDescription class="text-[13.5px]">January - June 2024</CardDescription> </CardHeader> <CardContent class="items-center px-5 pt-2.5 pb-0"> <ChartContainer :config="config" class="aspect-auto h-auto w-auto"> <svg width="256" height="236" viewBox="0 0 256 236" role="img" aria-label="Pie Chart - Custom Label" class="block overflow-visible"> <template v-for="s in slices" :key="s.browser"> <path v-for="(d, ring) in s.paths" :key="ring" :d="d" :fill="`var(--color-${s.browser})`" stroke="var(--card)" stroke-width="2" stroke-linejoin="round" /> </template> <text v-for="s in slices" :key="'l' + s.browser" :x="s.at(114).x" :y="s.at(114).y + 4" text-anchor="middle" class="fill-foreground text-xs tabular-nums">{{ s.visitors }}</text> </svg> </ChartContainer> </CardContent> <CardFooter class="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div class="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <TrendingUpIcon class="size-4" /> </div> <div class="text-muted-foreground">Showing total visitors for the last 6 months</div> </CardFooter> </Card></template><script lang="ts"> import IconPlaceholder from "$lib/components/icon-placeholder/icon-placeholder.svelte"; import * as Card from "$lib/components/ui/card"; import * as Chart from "$lib/components/ui/chart";
const config = { chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" }, } satisfies Chart.ChartConfig;
const data = [ { browser: "chrome", visitors: 275 }, { browser: "safari", visitors: 200 }, { browser: "firefox", visitors: 187 }, { browser: "edge", visitors: 173 }, { browser: "other", visitors: 90 }, ];
const CX = 128; const CY = 118; const RINGS = [[90,0]] as const; // [outer, inner] const TOTAL = 925; const p = (r: number, a: number) => [CX + r * Math.sin(a), CY - r * Math.cos(a)]; const f = (n: number) => n.toFixed(1);
let acc = 0; const slices = data.map((d) => { const a0 = (acc / TOTAL) * 2 * Math.PI; acc += d.visitors; const a1 = (acc / TOTAL) * 2 * Math.PI; const large = a1 - a0 > Math.PI ? 1 : 0; const paths = RINGS.map(([ro, ri], ring) => { const grow = 0; const o = ro + grow; const [x0, y0] = p(o, a0); const [x1, y1] = p(o, a1); if (!ri) return `M${CX},${CY}L${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}Z`; const [x2, y2] = p(ri, a1); const [x3, y3] = p(ri, a0); return `M${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}L${f(x2)},${f(y2)}A${ri},${ri} 0 ${large} 0 ${f(x3)},${f(y3)}Z`; }); const mid = (a0 + a1) / 2; const at = (r: number) => ({ x: CX + r * Math.sin(mid), y: CY - r * Math.cos(mid) }); return { ...d, paths, at }; });</script>
<Card.Root class="w-full max-w-sm gap-0 py-0"> <Card.Header class="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <Card.Title class="font-semibold text-base tracking-[-0.2px]">Pie Chart - Custom Label</Card.Title> <Card.Description class="text-[13.5px]">January - June 2024</Card.Description> </Card.Header> <Card.Content class="items-center px-5 pt-2.5 pb-0"> <Chart.Container {config} class="aspect-auto h-auto w-auto flex-col items-center"> <svg width="256" height="236" viewBox="0 0 256 236" role="img" aria-label="Pie Chart - Custom Label" class="block overflow-visible"> {#each slices as s (s.browser)} {#each s.paths as d, ring (ring)} <path {d} fill="var(--color-{s.browser})" stroke="var(--card)" stroke-width="2" stroke-linejoin="round" /> {/each} {/each} {#each slices as s (s.browser)} <text x={s.at(114).x} y={s.at(114).y + 4} text-anchor="middle" class="fill-foreground text-xs tabular-nums">{s.visitors}</text> {/each} </svg> </Chart.Container> </Card.Content> <Card.Footer class="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div class="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <IconPlaceholder lucide="TrendingUpIcon" tabler="IconTrendingUp" hugeicons="ChartUpIcon" phosphor="TrendUpIcon" remixicon="RiArrowUpLine" class="size-4" /> </div> <div class="text-muted-foreground">Showing total visitors for the last 6 months</div> </Card.Footer></Card.Root>Label List
Section titled “Label List”import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent,} from "@/components/ui/chart";import { LabelList, Pie, PieChart } from "recharts";import { IconPlaceholder } from "@/edmi/icon-placeholder";
const data = [ { browser: "chrome", visitors: 275, fill: "var(--color-chrome)" }, { browser: "safari", visitors: 200, fill: "var(--color-safari)" }, { browser: "firefox", visitors: 187, fill: "var(--color-firefox)" }, { browser: "edge", visitors: 173, fill: "var(--color-edge)" }, { browser: "other", visitors: 90, fill: "var(--color-other)" },];
const config = { visitors: { label: "Visitors" }, chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" },} satisfies ChartConfig;
export default function Demo() { return ( <Card className="w-full max-w-sm gap-0 py-0"> <CardHeader className="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <CardTitle className="font-semibold text-base tracking-[-0.2px]"> Pie Chart - Label List </CardTitle> <CardDescription className="text-[13.5px]"> January - June 2024 </CardDescription> </CardHeader> <CardContent className="items-center px-5 pt-2.5 pb-0"> <ChartContainer config={config} className="aspect-auto h-[236px] w-full max-w-64 [&_.recharts-surface]:overflow-visible" > <PieChart> <ChartTooltip content={<ChartTooltipContent nameKey="browser" hideLabel />} /> <Pie data={data} dataKey="visitors" nameKey="browser" outerRadius={118} stroke="var(--card)" strokeWidth={2} > <LabelList dataKey="browser" position="inside" stroke="rgba(0,0,0,.35)" strokeWidth={2} style={{ paintOrder: "stroke" }} className="fill-white font-medium text-xs" formatter={(value) => config[String(value) as keyof typeof config]?.label } /> </Pie> </PieChart> </ChartContainer> </CardContent> <CardFooter className="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div className="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <IconPlaceholder lucide="TrendingUpIcon" tabler="IconTrendingUp" hugeicons="ChartUpIcon" phosphor="TrendUpIcon" remixicon="RiArrowUpLine" className="size-4" /> </div> <div className="text-muted-foreground"> Showing total visitors for the last 6 months </div> </CardFooter> </Card> );}<script setup lang="ts">import { TrendingUpIcon } from "@lucide/vue";import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";import { type ChartConfig, ChartContainer } from "@/components/ui/chart";
const config = { chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" },} satisfies ChartConfig;
const data = [ { browser: "chrome", visitors: 275 }, { browser: "safari", visitors: 200 }, { browser: "firefox", visitors: 187 }, { browser: "edge", visitors: 173 }, { browser: "other", visitors: 90 },];
const CX = 128;const CY = 118;const RINGS = [[118,0]] as const; // [outer, inner]const TOTAL = 925;const p = (r: number, a: number) => [CX + r * Math.sin(a), CY - r * Math.cos(a)];const f = (n: number) => n.toFixed(1);
let acc = 0;const slices = data.map((d) => { const a0 = (acc / TOTAL) * 2 * Math.PI; acc += d.visitors; const a1 = (acc / TOTAL) * 2 * Math.PI; const large = a1 - a0 > Math.PI ? 1 : 0; const paths = RINGS.map(([ro, ri], ring) => { const grow = 0; const o = ro + grow; const [x0, y0] = p(o, a0); const [x1, y1] = p(o, a1); if (!ri) return `M${CX},${CY}L${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}Z`; const [x2, y2] = p(ri, a1); const [x3, y3] = p(ri, a0); return `M${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}L${f(x2)},${f(y2)}A${ri},${ri} 0 ${large} 0 ${f(x3)},${f(y3)}Z`; }); const mid = (a0 + a1) / 2; const at = (r: number) => ({ x: CX + r * Math.sin(mid), y: CY - r * Math.cos(mid) }); return { ...d, paths, at };});</script>
<template> <Card class="w-full max-w-sm gap-0 py-0"> <CardHeader class="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <CardTitle class="font-semibold text-base tracking-[-0.2px]">Pie Chart - Label List</CardTitle> <CardDescription class="text-[13.5px]">January - June 2024</CardDescription> </CardHeader> <CardContent class="items-center px-5 pt-2.5 pb-0"> <ChartContainer :config="config" class="aspect-auto h-auto w-auto"> <svg width="256" height="236" viewBox="0 0 256 236" role="img" aria-label="Pie Chart - Label List" class="block overflow-visible"> <template v-for="s in slices" :key="s.browser"> <path v-for="(d, ring) in s.paths" :key="ring" :d="d" :fill="`var(--color-${s.browser})`" stroke="var(--card)" stroke-width="2" stroke-linejoin="round" /> </template> <text v-for="s in slices" :key="'l' + s.browser" :x="s.at(71).x" :y="s.at(71).y + 4" text-anchor="middle" class="fill-white font-medium text-xs" style="paint-order: stroke; stroke: rgba(0, 0, 0, 0.35); stroke-width: 2px">{{ config[s.browser as keyof typeof config].label }}</text> </svg> </ChartContainer> </CardContent> <CardFooter class="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div class="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <TrendingUpIcon class="size-4" /> </div> <div class="text-muted-foreground">Showing total visitors for the last 6 months</div> </CardFooter> </Card></template><script lang="ts"> import IconPlaceholder from "$lib/components/icon-placeholder/icon-placeholder.svelte"; import * as Card from "$lib/components/ui/card"; import * as Chart from "$lib/components/ui/chart";
const config = { chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" }, } satisfies Chart.ChartConfig;
const data = [ { browser: "chrome", visitors: 275 }, { browser: "safari", visitors: 200 }, { browser: "firefox", visitors: 187 }, { browser: "edge", visitors: 173 }, { browser: "other", visitors: 90 }, ];
const CX = 128; const CY = 118; const RINGS = [[118,0]] as const; // [outer, inner] const TOTAL = 925; const p = (r: number, a: number) => [CX + r * Math.sin(a), CY - r * Math.cos(a)]; const f = (n: number) => n.toFixed(1);
let acc = 0; const slices = data.map((d) => { const a0 = (acc / TOTAL) * 2 * Math.PI; acc += d.visitors; const a1 = (acc / TOTAL) * 2 * Math.PI; const large = a1 - a0 > Math.PI ? 1 : 0; const paths = RINGS.map(([ro, ri], ring) => { const grow = 0; const o = ro + grow; const [x0, y0] = p(o, a0); const [x1, y1] = p(o, a1); if (!ri) return `M${CX},${CY}L${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}Z`; const [x2, y2] = p(ri, a1); const [x3, y3] = p(ri, a0); return `M${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}L${f(x2)},${f(y2)}A${ri},${ri} 0 ${large} 0 ${f(x3)},${f(y3)}Z`; }); const mid = (a0 + a1) / 2; const at = (r: number) => ({ x: CX + r * Math.sin(mid), y: CY - r * Math.cos(mid) }); return { ...d, paths, at }; });</script>
<Card.Root class="w-full max-w-sm gap-0 py-0"> <Card.Header class="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <Card.Title class="font-semibold text-base tracking-[-0.2px]">Pie Chart - Label List</Card.Title> <Card.Description class="text-[13.5px]">January - June 2024</Card.Description> </Card.Header> <Card.Content class="items-center px-5 pt-2.5 pb-0"> <Chart.Container {config} class="aspect-auto h-auto w-auto flex-col items-center"> <svg width="256" height="236" viewBox="0 0 256 236" role="img" aria-label="Pie Chart - Label List" class="block overflow-visible"> {#each slices as s (s.browser)} {#each s.paths as d, ring (ring)} <path {d} fill="var(--color-{s.browser})" stroke="var(--card)" stroke-width="2" stroke-linejoin="round" /> {/each} {/each} {#each slices as s (s.browser)} <text x={s.at(71).x} y={s.at(71).y + 4} text-anchor="middle" class="fill-white font-medium text-xs" style="paint-order: stroke; stroke: rgba(0, 0, 0, 0.35); stroke-width: 2px">{config[s.browser].label}</text> {/each} </svg> </Chart.Container> </Card.Content> <Card.Footer class="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div class="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <IconPlaceholder lucide="TrendingUpIcon" tabler="IconTrendingUp" hugeicons="ChartUpIcon" phosphor="TrendUpIcon" remixicon="RiArrowUpLine" class="size-4" /> </div> <div class="text-muted-foreground">Showing total visitors for the last 6 months</div> </Card.Footer></Card.Root>Legend
Section titled “Legend”import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";import { type ChartConfig, ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent,} from "@/components/ui/chart";import { Pie, PieChart } from "recharts";import { IconPlaceholder } from "@/edmi/icon-placeholder";
const data = [ { browser: "chrome", visitors: 275, fill: "var(--color-chrome)" }, { browser: "safari", visitors: 200, fill: "var(--color-safari)" }, { browser: "firefox", visitors: 187, fill: "var(--color-firefox)" }, { browser: "edge", visitors: 173, fill: "var(--color-edge)" }, { browser: "other", visitors: 90, fill: "var(--color-other)" },];
const config = { visitors: { label: "Visitors" }, chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" },} satisfies ChartConfig;
export default function Demo() { return ( <Card className="w-full max-w-sm gap-0 py-0"> <CardHeader className="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <CardTitle className="font-semibold text-base tracking-[-0.2px]"> Pie Chart - Legend </CardTitle> <CardDescription className="text-[13.5px]"> January - June 2024 </CardDescription> </CardHeader> <CardContent className="items-center px-5 pt-2.5 pb-0"> <ChartContainer config={config} className="aspect-auto h-[248px] w-full max-w-64 [&_.recharts-surface]:overflow-visible" > <PieChart> <ChartTooltip content={<ChartTooltipContent nameKey="browser" hideLabel />} /> <Pie data={data} dataKey="visitors" nameKey="browser" outerRadius={88} stroke="var(--card)" strokeWidth={2} /> <ChartLegend itemSorter={null} content={<ChartLegendContent nameKey="browser" />} /> </PieChart> </ChartContainer> </CardContent> <CardFooter className="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div className="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <IconPlaceholder lucide="TrendingUpIcon" tabler="IconTrendingUp" hugeicons="ChartUpIcon" phosphor="TrendUpIcon" remixicon="RiArrowUpLine" className="size-4" /> </div> <div className="text-muted-foreground"> Showing total visitors for the last 6 months </div> </CardFooter> </Card> );}<script setup lang="ts">import { Donut } from "@unovis/ts";import { VisDonut, VisSingleContainer } from "@unovis/vue";import { TrendingUpIcon } from "@lucide/vue";import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";import { type ChartConfig, ChartContainer, ChartLegendContent, ChartTooltip, ChartTooltipContent, componentToString,} from "@/components/ui/chart";
type Slice = { browser: string; visitors: number } & Record<string, string | number>;
const config = { chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" },} satisfies ChartConfig;
// Each slice also carries `[browser]: visitors` so the tooltip can look up its label.const data: Slice[] = [ { browser: "chrome", visitors: 275 }, { browser: "safari", visitors: 200 }, { browser: "firefox", visitors: 187 }, { browser: "edge", visitors: 173 }, { browser: "other", visitors: 90 },].map((d) => ({ ...d, [d.browser]: d.visitors }));
const value = (d: Slice) => d.visitors;const color = (d: Slice) => config[d.browser as keyof typeof config].color;const template = componentToString(config, ChartTooltipContent, { hideLabel: true });const triggers = { [Donut.selectors.segment]: template };</script>
<template> <Card class="w-full max-w-sm gap-0 py-0"> <CardHeader class="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <CardTitle class="font-semibold text-base tracking-[-0.2px]">Pie Chart - Legend</CardTitle> <CardDescription class="text-[13.5px]">January - June 2024</CardDescription> </CardHeader> <CardContent class="items-center px-5 pt-2.5 pb-0"> <ChartContainer :config="config" class="aspect-auto h-[266px] w-full max-w-64 [&_[data-vis-single-container]]:!h-[216px]"> <VisSingleContainer :data="data" :margin="{ top: 8, bottom: 8 }"> <VisDonut :value="value" :color="color" :arc-width="0" :pad-angle="0.02" /> <ChartTooltip :triggers="triggers" /> </VisSingleContainer> <ChartLegendContent /> </ChartContainer> </CardContent> <CardFooter class="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div class="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <TrendingUpIcon class="size-4" /> </div> <div class="text-muted-foreground">Showing total visitors for the last 6 months</div> </CardFooter> </Card></template><script lang="ts"> import { PieChart } from "layerchart"; import IconPlaceholder from "$lib/components/icon-placeholder/icon-placeholder.svelte"; import * as Card from "$lib/components/ui/card"; import * as Chart from "$lib/components/ui/chart";
const config = { chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" }, } satisfies Chart.ChartConfig;
const data = [ { browser: "chrome", visitors: 275, name: "Chrome", color: "var(--color-chrome)" }, { browser: "safari", visitors: 200, name: "Safari", color: "var(--color-safari)" }, { browser: "firefox", visitors: 187, name: "Firefox", color: "var(--color-firefox)" }, { browser: "edge", visitors: 173, name: "Edge", color: "var(--color-edge)" }, { browser: "other", visitors: 90, name: "Other", color: "var(--color-other)" }, ];</script>
<Card.Root class="w-full max-w-sm gap-0 py-0"> <Card.Header class="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <Card.Title class="font-semibold text-base tracking-[-0.2px]">Pie Chart - Legend</Card.Title> <Card.Description class="text-[13.5px]">January - June 2024</Card.Description> </Card.Header> <Card.Content class="items-center px-5 pt-2.5 pb-0"> <Chart.Container {config} class="aspect-auto h-[248px] w-full"> <PieChart {data} key="browser" label="name" value="visitors" c="color" padAngle={0.02} legend props={{ pie: { motion: "tween" } }} > {#snippet tooltip()} <Chart.Tooltip hideLabel nameKey="browser" /> {/snippet} </PieChart> </Chart.Container> </Card.Content> <Card.Footer class="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div class="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <IconPlaceholder lucide="TrendingUpIcon" tabler="IconTrendingUp" hugeicons="ChartUpIcon" phosphor="TrendUpIcon" remixicon="RiArrowUpLine" class="size-4" /> </div> <div class="text-muted-foreground">Showing total visitors for the last 6 months</div> </Card.Footer></Card.Root>import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent,} from "@/components/ui/chart";import { Pie, PieChart } from "recharts";import { IconPlaceholder } from "@/edmi/icon-placeholder";
const data = [ { browser: "chrome", visitors: 275, fill: "var(--color-chrome)" }, { browser: "safari", visitors: 200, fill: "var(--color-safari)" }, { browser: "firefox", visitors: 187, fill: "var(--color-firefox)" }, { browser: "edge", visitors: 173, fill: "var(--color-edge)" }, { browser: "other", visitors: 90, fill: "var(--color-other)" },];
const config = { visitors: { label: "Visitors" }, chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" },} satisfies ChartConfig;
export default function Demo() { return ( <Card className="w-full max-w-sm gap-0 py-0"> <CardHeader className="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <CardTitle className="font-semibold text-base tracking-[-0.2px]"> Pie Chart - Donut </CardTitle> <CardDescription className="text-[13.5px]"> January - June 2024 </CardDescription> </CardHeader> <CardContent className="items-center px-5 pt-2.5 pb-0"> <ChartContainer config={config} className="aspect-auto h-[236px] w-full max-w-64 [&_.recharts-surface]:overflow-visible" > <PieChart> <ChartTooltip content={<ChartTooltipContent nameKey="browser" hideLabel />} /> <Pie data={data} dataKey="visitors" nameKey="browser" innerRadius={62} outerRadius={100} stroke="var(--card)" strokeWidth={2} /> </PieChart> </ChartContainer> </CardContent> <CardFooter className="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div className="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <IconPlaceholder lucide="TrendingUpIcon" tabler="IconTrendingUp" hugeicons="ChartUpIcon" phosphor="TrendUpIcon" remixicon="RiArrowUpLine" className="size-4" /> </div> <div className="text-muted-foreground"> Showing total visitors for the last 6 months </div> </CardFooter> </Card> );}<script setup lang="ts">import { Donut } from "@unovis/ts";import { VisDonut, VisSingleContainer } from "@unovis/vue";import { TrendingUpIcon } from "@lucide/vue";import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";import { type ChartConfig, ChartContainer, ChartLegendContent, ChartTooltip, ChartTooltipContent, componentToString,} from "@/components/ui/chart";
type Slice = { browser: string; visitors: number } & Record<string, string | number>;
const config = { chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" },} satisfies ChartConfig;
// Each slice also carries `[browser]: visitors` so the tooltip can look up its label.const data: Slice[] = [ { browser: "chrome", visitors: 275 }, { browser: "safari", visitors: 200 }, { browser: "firefox", visitors: 187 }, { browser: "edge", visitors: 173 }, { browser: "other", visitors: 90 },].map((d) => ({ ...d, [d.browser]: d.visitors }));
const value = (d: Slice) => d.visitors;const color = (d: Slice) => config[d.browser as keyof typeof config].color;const template = componentToString(config, ChartTooltipContent, { hideLabel: true });const triggers = { [Donut.selectors.segment]: template };</script>
<template> <Card class="w-full max-w-sm gap-0 py-0"> <CardHeader class="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <CardTitle class="font-semibold text-base tracking-[-0.2px]">Pie Chart - Donut</CardTitle> <CardDescription class="text-[13.5px]">January - June 2024</CardDescription> </CardHeader> <CardContent class="items-center px-5 pt-2.5 pb-0"> <ChartContainer :config="config" class="aspect-auto h-[236px] w-full max-w-64"> <VisSingleContainer :data="data" :margin="{ top: 8, bottom: 8 }"> <VisDonut :value="value" :color="color" :arc-width="38" :pad-angle="0.02" /> <ChartTooltip :triggers="triggers" /> </VisSingleContainer> </ChartContainer> </CardContent> <CardFooter class="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div class="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <TrendingUpIcon class="size-4" /> </div> <div class="text-muted-foreground">Showing total visitors for the last 6 months</div> </CardFooter> </Card></template><script lang="ts"> import { PieChart } from "layerchart"; import IconPlaceholder from "$lib/components/icon-placeholder/icon-placeholder.svelte"; import * as Card from "$lib/components/ui/card"; import * as Chart from "$lib/components/ui/chart";
const config = { chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" }, } satisfies Chart.ChartConfig;
const data = [ { browser: "chrome", visitors: 275, name: "Chrome", color: "var(--color-chrome)" }, { browser: "safari", visitors: 200, name: "Safari", color: "var(--color-safari)" }, { browser: "firefox", visitors: 187, name: "Firefox", color: "var(--color-firefox)" }, { browser: "edge", visitors: 173, name: "Edge", color: "var(--color-edge)" }, { browser: "other", visitors: 90, name: "Other", color: "var(--color-other)" }, ];</script>
<Card.Root class="w-full max-w-sm gap-0 py-0"> <Card.Header class="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <Card.Title class="font-semibold text-base tracking-[-0.2px]">Pie Chart - Donut</Card.Title> <Card.Description class="text-[13.5px]">January - June 2024</Card.Description> </Card.Header> <Card.Content class="items-center px-5 pt-2.5 pb-0"> <Chart.Container {config} class="aspect-auto h-[236px] w-full"> <PieChart {data} key="browser" label="name" value="visitors" c="color" innerRadius={-38} padAngle={0.02} props={{ pie: { motion: "tween" } }} > {#snippet tooltip()} <Chart.Tooltip hideLabel nameKey="browser" /> {/snippet} </PieChart> </Chart.Container> </Card.Content> <Card.Footer class="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div class="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <IconPlaceholder lucide="TrendingUpIcon" tabler="IconTrendingUp" hugeicons="ChartUpIcon" phosphor="TrendUpIcon" remixicon="RiArrowUpLine" class="size-4" /> </div> <div class="text-muted-foreground">Showing total visitors for the last 6 months</div> </Card.Footer></Card.Root>Donut Active
Section titled “Donut Active”import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent,} from "@/components/ui/chart";import { Pie, PieChart, Sector } from "recharts";import { IconPlaceholder } from "@/edmi/icon-placeholder";
const data = [ { browser: "chrome", visitors: 275, fill: "var(--color-chrome)" }, { browser: "safari", visitors: 200, fill: "var(--color-safari)" }, { browser: "firefox", visitors: 187, fill: "var(--color-firefox)" }, { browser: "edge", visitors: 173, fill: "var(--color-edge)" }, { browser: "other", visitors: 90, fill: "var(--color-other)" },];
const config = { visitors: { label: "Visitors" }, chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" },} satisfies ChartConfig;
export default function Demo() { return ( <Card className="w-full max-w-sm gap-0 py-0"> <CardHeader className="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <CardTitle className="font-semibold text-base tracking-[-0.2px]"> Pie Chart - Donut Active </CardTitle> <CardDescription className="text-[13.5px]"> January - June 2024 </CardDescription> </CardHeader> <CardContent className="items-center px-5 pt-2.5 pb-0"> <ChartContainer config={config} className="aspect-auto h-[236px] w-full max-w-64 [&_.recharts-surface]:overflow-visible" > <PieChart> <ChartTooltip content={<ChartTooltipContent nameKey="browser" hideLabel />} /> <Pie data={data} dataKey="visitors" nameKey="browser" innerRadius={62} outerRadius={100} stroke="var(--card)" strokeWidth={2} shape={(p, index) => ( <Sector cx={p.cx} cy={p.cy} innerRadius={p.innerRadius} outerRadius={(p.outerRadius ?? 0) + (index === 0 ? 8 : 0)} startAngle={p.startAngle} endAngle={p.endAngle} fill={p.fill} stroke="var(--card)" strokeWidth={2} /> )} /> </PieChart> </ChartContainer> </CardContent> <CardFooter className="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div className="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <IconPlaceholder lucide="TrendingUpIcon" tabler="IconTrendingUp" hugeicons="ChartUpIcon" phosphor="TrendUpIcon" remixicon="RiArrowUpLine" className="size-4" /> </div> <div className="text-muted-foreground"> Showing total visitors for the last 6 months </div> </CardFooter> </Card> );}<script setup lang="ts">import { TrendingUpIcon } from "@lucide/vue";import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";import { type ChartConfig, ChartContainer } from "@/components/ui/chart";
const config = { chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" },} satisfies ChartConfig;
const data = [ { browser: "chrome", visitors: 275 }, { browser: "safari", visitors: 200 }, { browser: "firefox", visitors: 187 }, { browser: "edge", visitors: 173 }, { browser: "other", visitors: 90 },];
const CX = 128;const CY = 118;const RINGS = [[100,62]] as const; // [outer, inner]const TOTAL = 925;const p = (r: number, a: number) => [CX + r * Math.sin(a), CY - r * Math.cos(a)];const f = (n: number) => n.toFixed(1);
let acc = 0;const slices = data.map((d) => { const a0 = (acc / TOTAL) * 2 * Math.PI; acc += d.visitors; const a1 = (acc / TOTAL) * 2 * Math.PI; const large = a1 - a0 > Math.PI ? 1 : 0; const paths = RINGS.map(([ro, ri], ring) => { const grow = ring === 0 && d.browser === "chrome" ? 8 : 0; const o = ro + grow; const [x0, y0] = p(o, a0); const [x1, y1] = p(o, a1); if (!ri) return `M${CX},${CY}L${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}Z`; const [x2, y2] = p(ri, a1); const [x3, y3] = p(ri, a0); return `M${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}L${f(x2)},${f(y2)}A${ri},${ri} 0 ${large} 0 ${f(x3)},${f(y3)}Z`; }); const mid = (a0 + a1) / 2; const at = (r: number) => ({ x: CX + r * Math.sin(mid), y: CY - r * Math.cos(mid) }); return { ...d, paths, at };});</script>
<template> <Card class="w-full max-w-sm gap-0 py-0"> <CardHeader class="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <CardTitle class="font-semibold text-base tracking-[-0.2px]">Pie Chart - Donut Active</CardTitle> <CardDescription class="text-[13.5px]">January - June 2024</CardDescription> </CardHeader> <CardContent class="items-center px-5 pt-2.5 pb-0"> <ChartContainer :config="config" class="aspect-auto h-auto w-auto"> <svg width="256" height="236" viewBox="0 0 256 236" role="img" aria-label="Pie Chart - Donut Active" class="block overflow-visible"> <template v-for="s in slices" :key="s.browser"> <path v-for="(d, ring) in s.paths" :key="ring" :d="d" :fill="`var(--color-${s.browser})`" stroke="var(--card)" stroke-width="2" stroke-linejoin="round" /> </template>
</svg> </ChartContainer> </CardContent> <CardFooter class="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div class="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <TrendingUpIcon class="size-4" /> </div> <div class="text-muted-foreground">Showing total visitors for the last 6 months</div> </CardFooter> </Card></template><script lang="ts"> import IconPlaceholder from "$lib/components/icon-placeholder/icon-placeholder.svelte"; import * as Card from "$lib/components/ui/card"; import * as Chart from "$lib/components/ui/chart";
const config = { chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" }, } satisfies Chart.ChartConfig;
const data = [ { browser: "chrome", visitors: 275 }, { browser: "safari", visitors: 200 }, { browser: "firefox", visitors: 187 }, { browser: "edge", visitors: 173 }, { browser: "other", visitors: 90 }, ];
const CX = 128; const CY = 118; const RINGS = [[100,62]] as const; // [outer, inner] const TOTAL = 925; const p = (r: number, a: number) => [CX + r * Math.sin(a), CY - r * Math.cos(a)]; const f = (n: number) => n.toFixed(1);
let acc = 0; const slices = data.map((d) => { const a0 = (acc / TOTAL) * 2 * Math.PI; acc += d.visitors; const a1 = (acc / TOTAL) * 2 * Math.PI; const large = a1 - a0 > Math.PI ? 1 : 0; const paths = RINGS.map(([ro, ri], ring) => { const grow = ring === 0 && d.browser === "chrome" ? 8 : 0; const o = ro + grow; const [x0, y0] = p(o, a0); const [x1, y1] = p(o, a1); if (!ri) return `M${CX},${CY}L${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}Z`; const [x2, y2] = p(ri, a1); const [x3, y3] = p(ri, a0); return `M${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}L${f(x2)},${f(y2)}A${ri},${ri} 0 ${large} 0 ${f(x3)},${f(y3)}Z`; }); const mid = (a0 + a1) / 2; const at = (r: number) => ({ x: CX + r * Math.sin(mid), y: CY - r * Math.cos(mid) }); return { ...d, paths, at }; });</script>
<Card.Root class="w-full max-w-sm gap-0 py-0"> <Card.Header class="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <Card.Title class="font-semibold text-base tracking-[-0.2px]">Pie Chart - Donut Active</Card.Title> <Card.Description class="text-[13.5px]">January - June 2024</Card.Description> </Card.Header> <Card.Content class="items-center px-5 pt-2.5 pb-0"> <Chart.Container {config} class="aspect-auto h-auto w-auto flex-col items-center"> <svg width="256" height="236" viewBox="0 0 256 236" role="img" aria-label="Pie Chart - Donut Active" class="block overflow-visible"> {#each slices as s (s.browser)} {#each s.paths as d, ring (ring)} <path {d} fill="var(--color-{s.browser})" stroke="var(--card)" stroke-width="2" stroke-linejoin="round" /> {/each} {/each}
</svg> </Chart.Container> </Card.Content> <Card.Footer class="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div class="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <IconPlaceholder lucide="TrendingUpIcon" tabler="IconTrendingUp" hugeicons="ChartUpIcon" phosphor="TrendUpIcon" remixicon="RiArrowUpLine" class="size-4" /> </div> <div class="text-muted-foreground">Showing total visitors for the last 6 months</div> </Card.Footer></Card.Root>Donut with Text
Section titled “Donut with Text”import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent,} from "@/components/ui/chart";import { Label, Pie, PieChart } from "recharts";import { IconPlaceholder } from "@/edmi/icon-placeholder";
const data = [ { browser: "chrome", visitors: 275, fill: "var(--color-chrome)" }, { browser: "safari", visitors: 200, fill: "var(--color-safari)" }, { browser: "firefox", visitors: 187, fill: "var(--color-firefox)" }, { browser: "edge", visitors: 173, fill: "var(--color-edge)" }, { browser: "other", visitors: 90, fill: "var(--color-other)" },];
const total = data.reduce((sum, d) => sum + d.visitors, 0);
const config = { visitors: { label: "Visitors" }, chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" },} satisfies ChartConfig;
export default function Demo() { return ( <Card className="w-full max-w-sm gap-0 py-0"> <CardHeader className="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <CardTitle className="font-semibold text-base tracking-[-0.2px]"> Pie Chart - Donut with Text </CardTitle> <CardDescription className="text-[13.5px]"> January - June 2024 </CardDescription> </CardHeader> <CardContent className="items-center px-5 pt-2.5 pb-0"> <ChartContainer config={config} className="aspect-auto h-[236px] w-full max-w-64 [&_.recharts-surface]:overflow-visible" > <PieChart> <ChartTooltip content={<ChartTooltipContent nameKey="browser" hideLabel />} /> <Pie data={data} dataKey="visitors" nameKey="browser" innerRadius={62} outerRadius={100} stroke="var(--card)" strokeWidth={2} > <Label content={({ viewBox }) => { if (!viewBox || !("cx" in viewBox)) return null; return ( <text x={viewBox.cx} y={viewBox.cy} textAnchor="middle"> <tspan x={viewBox.cx} y={(viewBox.cy ?? 0) - 2} className="fill-foreground font-bold text-3xl tabular-nums tracking-[-1px]" > {total.toLocaleString()} </tspan> <tspan x={viewBox.cx} y={(viewBox.cy ?? 0) + 18} className="fill-muted-foreground" > Visitors </tspan> </text> ); }} /> </Pie> </PieChart> </ChartContainer> </CardContent> <CardFooter className="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div className="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <IconPlaceholder lucide="TrendingUpIcon" tabler="IconTrendingUp" hugeicons="ChartUpIcon" phosphor="TrendUpIcon" remixicon="RiArrowUpLine" className="size-4" /> </div> <div className="text-muted-foreground"> Showing total visitors for the last 6 months </div> </CardFooter> </Card> );}<script setup lang="ts">import { TrendingUpIcon } from "@lucide/vue";import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";import { type ChartConfig, ChartContainer } from "@/components/ui/chart";
const config = { chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" },} satisfies ChartConfig;
const data = [ { browser: "chrome", visitors: 275 }, { browser: "safari", visitors: 200 }, { browser: "firefox", visitors: 187 }, { browser: "edge", visitors: 173 }, { browser: "other", visitors: 90 },];
const CX = 128;const CY = 118;const RINGS = [[100,62]] as const; // [outer, inner]const TOTAL = 925;const p = (r: number, a: number) => [CX + r * Math.sin(a), CY - r * Math.cos(a)];const f = (n: number) => n.toFixed(1);
let acc = 0;const slices = data.map((d) => { const a0 = (acc / TOTAL) * 2 * Math.PI; acc += d.visitors; const a1 = (acc / TOTAL) * 2 * Math.PI; const large = a1 - a0 > Math.PI ? 1 : 0; const paths = RINGS.map(([ro, ri], ring) => { const grow = 0; const o = ro + grow; const [x0, y0] = p(o, a0); const [x1, y1] = p(o, a1); if (!ri) return `M${CX},${CY}L${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}Z`; const [x2, y2] = p(ri, a1); const [x3, y3] = p(ri, a0); return `M${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}L${f(x2)},${f(y2)}A${ri},${ri} 0 ${large} 0 ${f(x3)},${f(y3)}Z`; }); const mid = (a0 + a1) / 2; const at = (r: number) => ({ x: CX + r * Math.sin(mid), y: CY - r * Math.cos(mid) }); return { ...d, paths, at };});</script>
<template> <Card class="w-full max-w-sm gap-0 py-0"> <CardHeader class="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <CardTitle class="font-semibold text-base tracking-[-0.2px]">Pie Chart - Donut with Text</CardTitle> <CardDescription class="text-[13.5px]">January - June 2024</CardDescription> </CardHeader> <CardContent class="items-center px-5 pt-2.5 pb-0"> <ChartContainer :config="config" class="aspect-auto h-auto w-auto"> <svg width="256" height="236" viewBox="0 0 256 236" role="img" aria-label="Pie Chart - Donut with Text" class="block overflow-visible"> <template v-for="s in slices" :key="s.browser"> <path v-for="(d, ring) in s.paths" :key="ring" :d="d" :fill="`var(--color-${s.browser})`" stroke="var(--card)" stroke-width="2" stroke-linejoin="round" /> </template> <text x="128" y="116" text-anchor="middle" class="fill-foreground font-bold text-3xl tabular-nums tracking-[-1px]">925</text> <text x="128" y="136" text-anchor="middle" class="fill-muted-foreground">Visitors</text> </svg> </ChartContainer> </CardContent> <CardFooter class="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div class="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <TrendingUpIcon class="size-4" /> </div> <div class="text-muted-foreground">Showing total visitors for the last 6 months</div> </CardFooter> </Card></template><script lang="ts"> import IconPlaceholder from "$lib/components/icon-placeholder/icon-placeholder.svelte"; import * as Card from "$lib/components/ui/card"; import * as Chart from "$lib/components/ui/chart";
const config = { chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" }, } satisfies Chart.ChartConfig;
const data = [ { browser: "chrome", visitors: 275 }, { browser: "safari", visitors: 200 }, { browser: "firefox", visitors: 187 }, { browser: "edge", visitors: 173 }, { browser: "other", visitors: 90 }, ];
const CX = 128; const CY = 118; const RINGS = [[100,62]] as const; // [outer, inner] const TOTAL = 925; const p = (r: number, a: number) => [CX + r * Math.sin(a), CY - r * Math.cos(a)]; const f = (n: number) => n.toFixed(1);
let acc = 0; const slices = data.map((d) => { const a0 = (acc / TOTAL) * 2 * Math.PI; acc += d.visitors; const a1 = (acc / TOTAL) * 2 * Math.PI; const large = a1 - a0 > Math.PI ? 1 : 0; const paths = RINGS.map(([ro, ri], ring) => { const grow = 0; const o = ro + grow; const [x0, y0] = p(o, a0); const [x1, y1] = p(o, a1); if (!ri) return `M${CX},${CY}L${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}Z`; const [x2, y2] = p(ri, a1); const [x3, y3] = p(ri, a0); return `M${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}L${f(x2)},${f(y2)}A${ri},${ri} 0 ${large} 0 ${f(x3)},${f(y3)}Z`; }); const mid = (a0 + a1) / 2; const at = (r: number) => ({ x: CX + r * Math.sin(mid), y: CY - r * Math.cos(mid) }); return { ...d, paths, at }; });</script>
<Card.Root class="w-full max-w-sm gap-0 py-0"> <Card.Header class="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <Card.Title class="font-semibold text-base tracking-[-0.2px]">Pie Chart - Donut with Text</Card.Title> <Card.Description class="text-[13.5px]">January - June 2024</Card.Description> </Card.Header> <Card.Content class="items-center px-5 pt-2.5 pb-0"> <Chart.Container {config} class="aspect-auto h-auto w-auto flex-col items-center"> <svg width="256" height="236" viewBox="0 0 256 236" role="img" aria-label="Pie Chart - Donut with Text" class="block overflow-visible"> {#each slices as s (s.browser)} {#each s.paths as d, ring (ring)} <path {d} fill="var(--color-{s.browser})" stroke="var(--card)" stroke-width="2" stroke-linejoin="round" /> {/each} {/each} <text x="128" y="116" text-anchor="middle" class="fill-foreground font-bold text-3xl tabular-nums tracking-[-1px]">925</text> <text x="128" y="136" text-anchor="middle" class="fill-muted-foreground">Visitors</text> </svg> </Chart.Container> </Card.Content> <Card.Footer class="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div class="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <IconPlaceholder lucide="TrendingUpIcon" tabler="IconTrendingUp" hugeicons="ChartUpIcon" phosphor="TrendUpIcon" remixicon="RiArrowUpLine" class="size-4" /> </div> <div class="text-muted-foreground">Showing total visitors for the last 6 months</div> </Card.Footer></Card.Root>Stacked
Section titled “Stacked”import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent,} from "@/components/ui/chart";import { Pie, PieChart } from "recharts";import { IconPlaceholder } from "@/edmi/icon-placeholder";
const data = [ { browser: "chrome", visitors: 275, fill: "var(--color-chrome)" }, { browser: "safari", visitors: 200, fill: "var(--color-safari)" }, { browser: "firefox", visitors: 187, fill: "var(--color-firefox)" }, { browser: "edge", visitors: 173, fill: "var(--color-edge)" }, { browser: "other", visitors: 90, fill: "var(--color-other)" },];
const config = { visitors: { label: "Visitors" }, chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" },} satisfies ChartConfig;
export default function Demo() { return ( <Card className="w-full max-w-sm gap-0 py-0"> <CardHeader className="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <CardTitle className="font-semibold text-base tracking-[-0.2px]"> Pie Chart - Stacked </CardTitle> <CardDescription className="text-[13.5px]"> January - June 2024 </CardDescription> </CardHeader> <CardContent className="items-center px-5 pt-2.5 pb-0"> <ChartContainer config={config} className="aspect-auto h-[236px] w-full max-w-64 [&_.recharts-surface]:overflow-visible" > <PieChart> <ChartTooltip content={<ChartTooltipContent nameKey="browser" hideLabel />} /> <Pie data={data} dataKey="visitors" nameKey="browser" innerRadius={72} outerRadius={110} stroke="var(--card)" strokeWidth={2} /> <Pie data={data} dataKey="visitors" nameKey="browser" innerRadius={26} outerRadius={64} stroke="var(--card)" strokeWidth={2} /> </PieChart> </ChartContainer> </CardContent> <CardFooter className="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div className="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <IconPlaceholder lucide="TrendingUpIcon" tabler="IconTrendingUp" hugeicons="ChartUpIcon" phosphor="TrendUpIcon" remixicon="RiArrowUpLine" className="size-4" /> </div> <div className="text-muted-foreground"> Showing total visitors for the last 6 months </div> </CardFooter> </Card> );}<script setup lang="ts">import { TrendingUpIcon } from "@lucide/vue";import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";import { type ChartConfig, ChartContainer } from "@/components/ui/chart";
const config = { chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" },} satisfies ChartConfig;
const data = [ { browser: "chrome", visitors: 275 }, { browser: "safari", visitors: 200 }, { browser: "firefox", visitors: 187 }, { browser: "edge", visitors: 173 }, { browser: "other", visitors: 90 },];
const CX = 128;const CY = 118;const RINGS = [[110,72],[64,26]] as const; // [outer, inner]const TOTAL = 925;const p = (r: number, a: number) => [CX + r * Math.sin(a), CY - r * Math.cos(a)];const f = (n: number) => n.toFixed(1);
let acc = 0;const slices = data.map((d) => { const a0 = (acc / TOTAL) * 2 * Math.PI; acc += d.visitors; const a1 = (acc / TOTAL) * 2 * Math.PI; const large = a1 - a0 > Math.PI ? 1 : 0; const paths = RINGS.map(([ro, ri], ring) => { const grow = 0; const o = ro + grow; const [x0, y0] = p(o, a0); const [x1, y1] = p(o, a1); if (!ri) return `M${CX},${CY}L${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}Z`; const [x2, y2] = p(ri, a1); const [x3, y3] = p(ri, a0); return `M${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}L${f(x2)},${f(y2)}A${ri},${ri} 0 ${large} 0 ${f(x3)},${f(y3)}Z`; }); const mid = (a0 + a1) / 2; const at = (r: number) => ({ x: CX + r * Math.sin(mid), y: CY - r * Math.cos(mid) }); return { ...d, paths, at };});</script>
<template> <Card class="w-full max-w-sm gap-0 py-0"> <CardHeader class="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <CardTitle class="font-semibold text-base tracking-[-0.2px]">Pie Chart - Stacked</CardTitle> <CardDescription class="text-[13.5px]">January - June 2024</CardDescription> </CardHeader> <CardContent class="items-center px-5 pt-2.5 pb-0"> <ChartContainer :config="config" class="aspect-auto h-auto w-auto"> <svg width="256" height="236" viewBox="0 0 256 236" role="img" aria-label="Pie Chart - Stacked" class="block overflow-visible"> <template v-for="s in slices" :key="s.browser"> <path v-for="(d, ring) in s.paths" :key="ring" :d="d" :fill="`var(--color-${s.browser})`" stroke="var(--card)" stroke-width="2" stroke-linejoin="round" /> </template>
</svg> </ChartContainer> </CardContent> <CardFooter class="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div class="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <TrendingUpIcon class="size-4" /> </div> <div class="text-muted-foreground">Showing total visitors for the last 6 months</div> </CardFooter> </Card></template><script lang="ts"> import IconPlaceholder from "$lib/components/icon-placeholder/icon-placeholder.svelte"; import * as Card from "$lib/components/ui/card"; import * as Chart from "$lib/components/ui/chart";
const config = { chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" }, } satisfies Chart.ChartConfig;
const data = [ { browser: "chrome", visitors: 275 }, { browser: "safari", visitors: 200 }, { browser: "firefox", visitors: 187 }, { browser: "edge", visitors: 173 }, { browser: "other", visitors: 90 }, ];
const CX = 128; const CY = 118; const RINGS = [[110,72],[64,26]] as const; // [outer, inner] const TOTAL = 925; const p = (r: number, a: number) => [CX + r * Math.sin(a), CY - r * Math.cos(a)]; const f = (n: number) => n.toFixed(1);
let acc = 0; const slices = data.map((d) => { const a0 = (acc / TOTAL) * 2 * Math.PI; acc += d.visitors; const a1 = (acc / TOTAL) * 2 * Math.PI; const large = a1 - a0 > Math.PI ? 1 : 0; const paths = RINGS.map(([ro, ri], ring) => { const grow = 0; const o = ro + grow; const [x0, y0] = p(o, a0); const [x1, y1] = p(o, a1); if (!ri) return `M${CX},${CY}L${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}Z`; const [x2, y2] = p(ri, a1); const [x3, y3] = p(ri, a0); return `M${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}L${f(x2)},${f(y2)}A${ri},${ri} 0 ${large} 0 ${f(x3)},${f(y3)}Z`; }); const mid = (a0 + a1) / 2; const at = (r: number) => ({ x: CX + r * Math.sin(mid), y: CY - r * Math.cos(mid) }); return { ...d, paths, at }; });</script>
<Card.Root class="w-full max-w-sm gap-0 py-0"> <Card.Header class="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <Card.Title class="font-semibold text-base tracking-[-0.2px]">Pie Chart - Stacked</Card.Title> <Card.Description class="text-[13.5px]">January - June 2024</Card.Description> </Card.Header> <Card.Content class="items-center px-5 pt-2.5 pb-0"> <Chart.Container {config} class="aspect-auto h-auto w-auto flex-col items-center"> <svg width="256" height="236" viewBox="0 0 256 236" role="img" aria-label="Pie Chart - Stacked" class="block overflow-visible"> {#each slices as s (s.browser)} {#each s.paths as d, ring (ring)} <path {d} fill="var(--color-{s.browser})" stroke="var(--card)" stroke-width="2" stroke-linejoin="round" /> {/each} {/each}
</svg> </Chart.Container> </Card.Content> <Card.Footer class="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div class="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <IconPlaceholder lucide="TrendingUpIcon" tabler="IconTrendingUp" hugeicons="ChartUpIcon" phosphor="TrendUpIcon" remixicon="RiArrowUpLine" class="size-4" /> </div> <div class="text-muted-foreground">Showing total visitors for the last 6 months</div> </Card.Footer></Card.Root>Donut Legend
Section titled “Donut Legend”import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";import { type ChartConfig, ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent,} from "@/components/ui/chart";import { Pie, PieChart } from "recharts";import { IconPlaceholder } from "@/edmi/icon-placeholder";
const data = [ { browser: "chrome", visitors: 275, fill: "var(--color-chrome)" }, { browser: "safari", visitors: 200, fill: "var(--color-safari)" }, { browser: "firefox", visitors: 187, fill: "var(--color-firefox)" }, { browser: "edge", visitors: 173, fill: "var(--color-edge)" }, { browser: "other", visitors: 90, fill: "var(--color-other)" },];
const config = { visitors: { label: "Visitors" }, chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" },} satisfies ChartConfig;
export default function Demo() { return ( <Card className="w-full max-w-sm gap-0 py-0"> <CardHeader className="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <CardTitle className="font-semibold text-base tracking-[-0.2px]"> Pie Chart - Donut Legend </CardTitle> <CardDescription className="text-[13.5px]"> January - June 2024 </CardDescription> </CardHeader> <CardContent className="items-center px-5 pt-2.5 pb-0"> <ChartContainer config={config} className="aspect-auto h-[248px] w-full max-w-64 [&_.recharts-surface]:overflow-visible" > <PieChart> <ChartTooltip content={<ChartTooltipContent nameKey="browser" hideLabel />} /> <Pie data={data} dataKey="visitors" nameKey="browser" innerRadius={54} outerRadius={86} stroke="var(--card)" strokeWidth={2} /> <ChartLegend itemSorter={null} content={<ChartLegendContent nameKey="browser" />} /> </PieChart> </ChartContainer> </CardContent> <CardFooter className="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div className="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <IconPlaceholder lucide="TrendingUpIcon" tabler="IconTrendingUp" hugeicons="ChartUpIcon" phosphor="TrendUpIcon" remixicon="RiArrowUpLine" className="size-4" /> </div> <div className="text-muted-foreground"> Showing total visitors for the last 6 months </div> </CardFooter> </Card> );}<script setup lang="ts">import { Donut } from "@unovis/ts";import { VisDonut, VisSingleContainer } from "@unovis/vue";import { TrendingUpIcon } from "@lucide/vue";import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";import { type ChartConfig, ChartContainer, ChartLegendContent, ChartTooltip, ChartTooltipContent, componentToString,} from "@/components/ui/chart";
type Slice = { browser: string; visitors: number } & Record<string, string | number>;
const config = { chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" },} satisfies ChartConfig;
// Each slice also carries `[browser]: visitors` so the tooltip can look up its label.const data: Slice[] = [ { browser: "chrome", visitors: 275 }, { browser: "safari", visitors: 200 }, { browser: "firefox", visitors: 187 }, { browser: "edge", visitors: 173 }, { browser: "other", visitors: 90 },].map((d) => ({ ...d, [d.browser]: d.visitors }));
const value = (d: Slice) => d.visitors;const color = (d: Slice) => config[d.browser as keyof typeof config].color;const template = componentToString(config, ChartTooltipContent, { hideLabel: true });const triggers = { [Donut.selectors.segment]: template };</script>
<template> <Card class="w-full max-w-sm gap-0 py-0"> <CardHeader class="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <CardTitle class="font-semibold text-base tracking-[-0.2px]">Pie Chart - Donut Legend</CardTitle> <CardDescription class="text-[13.5px]">January - June 2024</CardDescription> </CardHeader> <CardContent class="items-center px-5 pt-2.5 pb-0"> <ChartContainer :config="config" class="aspect-auto h-[266px] w-full max-w-64 [&_[data-vis-single-container]]:!h-[216px]"> <VisSingleContainer :data="data" :margin="{ top: 8, bottom: 8 }"> <VisDonut :value="value" :color="color" :arc-width="32" :pad-angle="0.02" /> <ChartTooltip :triggers="triggers" /> </VisSingleContainer> <ChartLegendContent /> </ChartContainer> </CardContent> <CardFooter class="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div class="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <TrendingUpIcon class="size-4" /> </div> <div class="text-muted-foreground">Showing total visitors for the last 6 months</div> </CardFooter> </Card></template><script lang="ts"> import { PieChart } from "layerchart"; import IconPlaceholder from "$lib/components/icon-placeholder/icon-placeholder.svelte"; import * as Card from "$lib/components/ui/card"; import * as Chart from "$lib/components/ui/chart";
const config = { chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" }, } satisfies Chart.ChartConfig;
const data = [ { browser: "chrome", visitors: 275, name: "Chrome", color: "var(--color-chrome)" }, { browser: "safari", visitors: 200, name: "Safari", color: "var(--color-safari)" }, { browser: "firefox", visitors: 187, name: "Firefox", color: "var(--color-firefox)" }, { browser: "edge", visitors: 173, name: "Edge", color: "var(--color-edge)" }, { browser: "other", visitors: 90, name: "Other", color: "var(--color-other)" }, ];</script>
<Card.Root class="w-full max-w-sm gap-0 py-0"> <Card.Header class="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <Card.Title class="font-semibold text-base tracking-[-0.2px]">Pie Chart - Donut Legend</Card.Title> <Card.Description class="text-[13.5px]">January - June 2024</Card.Description> </Card.Header> <Card.Content class="items-center px-5 pt-2.5 pb-0"> <Chart.Container {config} class="aspect-auto h-[248px] w-full"> <PieChart {data} key="browser" label="name" value="visitors" c="color" innerRadius={-32} padAngle={0.02} legend props={{ pie: { motion: "tween" } }} > {#snippet tooltip()} <Chart.Tooltip hideLabel nameKey="browser" /> {/snippet} </PieChart> </Chart.Container> </Card.Content> <Card.Footer class="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div class="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <IconPlaceholder lucide="TrendingUpIcon" tabler="IconTrendingUp" hugeicons="ChartUpIcon" phosphor="TrendUpIcon" remixicon="RiArrowUpLine" class="size-4" /> </div> <div class="text-muted-foreground">Showing total visitors for the last 6 months</div> </Card.Footer></Card.Root>Donut Labels
Section titled “Donut Labels”import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent,} from "@/components/ui/chart";import { Pie, PieChart } from "recharts";import { IconPlaceholder } from "@/edmi/icon-placeholder";
const data = [ { browser: "chrome", visitors: 275, fill: "var(--color-chrome)" }, { browser: "safari", visitors: 200, fill: "var(--color-safari)" }, { browser: "firefox", visitors: 187, fill: "var(--color-firefox)" }, { browser: "edge", visitors: 173, fill: "var(--color-edge)" }, { browser: "other", visitors: 90, fill: "var(--color-other)" },];
const config = { visitors: { label: "Visitors" }, chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" },} satisfies ChartConfig;
const RAD = Math.PI / 180;const pt = (cx: number, cy: number, r: number, mid: number) => ({ x: cx + r * Math.cos(-mid * RAD), y: cy + r * Math.sin(-mid * RAD),});
export default function Demo() { return ( <Card className="w-full max-w-sm gap-0 py-0"> <CardHeader className="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <CardTitle className="font-semibold text-base tracking-[-0.2px]"> Pie Chart - Donut Labels </CardTitle> <CardDescription className="text-[13.5px]"> January - June 2024 </CardDescription> </CardHeader> <CardContent className="items-center px-5 pt-2.5 pb-0"> <ChartContainer config={config} className="aspect-auto h-[236px] w-full max-w-64 [&_.recharts-surface]:overflow-visible" > <PieChart> <ChartTooltip content={<ChartTooltipContent nameKey="browser" hideLabel />} /> <Pie data={data} dataKey="visitors" nameKey="browser" innerRadius={62} outerRadius={100} stroke="var(--card)" strokeWidth={2} labelLine={false} label={(p) => { const a = pt(p.cx, p.cy, p.outerRadius + 22, p.midAngle ?? 0); return ( <text x={a.x} y={a.y + 4} textAnchor="middle" className="fill-foreground text-xs tabular-nums" > {p.value} </text> ); }} /> </PieChart> </ChartContainer> </CardContent> <CardFooter className="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div className="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <IconPlaceholder lucide="TrendingUpIcon" tabler="IconTrendingUp" hugeicons="ChartUpIcon" phosphor="TrendUpIcon" remixicon="RiArrowUpLine" className="size-4" /> </div> <div className="text-muted-foreground"> Showing total visitors for the last 6 months </div> </CardFooter> </Card> );}<script setup lang="ts">import { TrendingUpIcon } from "@lucide/vue";import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";import { type ChartConfig, ChartContainer } from "@/components/ui/chart";
const config = { chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" },} satisfies ChartConfig;
const data = [ { browser: "chrome", visitors: 275 }, { browser: "safari", visitors: 200 }, { browser: "firefox", visitors: 187 }, { browser: "edge", visitors: 173 }, { browser: "other", visitors: 90 },];
const CX = 128;const CY = 118;const RINGS = [[100,62]] as const; // [outer, inner]const TOTAL = 925;const p = (r: number, a: number) => [CX + r * Math.sin(a), CY - r * Math.cos(a)];const f = (n: number) => n.toFixed(1);
let acc = 0;const slices = data.map((d) => { const a0 = (acc / TOTAL) * 2 * Math.PI; acc += d.visitors; const a1 = (acc / TOTAL) * 2 * Math.PI; const large = a1 - a0 > Math.PI ? 1 : 0; const paths = RINGS.map(([ro, ri], ring) => { const grow = 0; const o = ro + grow; const [x0, y0] = p(o, a0); const [x1, y1] = p(o, a1); if (!ri) return `M${CX},${CY}L${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}Z`; const [x2, y2] = p(ri, a1); const [x3, y3] = p(ri, a0); return `M${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}L${f(x2)},${f(y2)}A${ri},${ri} 0 ${large} 0 ${f(x3)},${f(y3)}Z`; }); const mid = (a0 + a1) / 2; const at = (r: number) => ({ x: CX + r * Math.sin(mid), y: CY - r * Math.cos(mid) }); return { ...d, paths, at };});</script>
<template> <Card class="w-full max-w-sm gap-0 py-0"> <CardHeader class="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <CardTitle class="font-semibold text-base tracking-[-0.2px]">Pie Chart - Donut Labels</CardTitle> <CardDescription class="text-[13.5px]">January - June 2024</CardDescription> </CardHeader> <CardContent class="items-center px-5 pt-2.5 pb-0"> <ChartContainer :config="config" class="aspect-auto h-auto w-auto"> <svg width="256" height="236" viewBox="0 0 256 236" role="img" aria-label="Pie Chart - Donut Labels" class="block overflow-visible"> <template v-for="s in slices" :key="s.browser"> <path v-for="(d, ring) in s.paths" :key="ring" :d="d" :fill="`var(--color-${s.browser})`" stroke="var(--card)" stroke-width="2" stroke-linejoin="round" /> </template> <text v-for="s in slices" :key="'l' + s.browser" :x="s.at(122).x" :y="s.at(122).y + 4" text-anchor="middle" class="fill-foreground text-xs tabular-nums">{{ s.visitors }}</text> </svg> </ChartContainer> </CardContent> <CardFooter class="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div class="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <TrendingUpIcon class="size-4" /> </div> <div class="text-muted-foreground">Showing total visitors for the last 6 months</div> </CardFooter> </Card></template><script lang="ts"> import IconPlaceholder from "$lib/components/icon-placeholder/icon-placeholder.svelte"; import * as Card from "$lib/components/ui/card"; import * as Chart from "$lib/components/ui/chart";
const config = { chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, edge: { label: "Edge", color: "var(--chart-4)" }, other: { label: "Other", color: "var(--chart-5)" }, } satisfies Chart.ChartConfig;
const data = [ { browser: "chrome", visitors: 275 }, { browser: "safari", visitors: 200 }, { browser: "firefox", visitors: 187 }, { browser: "edge", visitors: 173 }, { browser: "other", visitors: 90 }, ];
const CX = 128; const CY = 118; const RINGS = [[100,62]] as const; // [outer, inner] const TOTAL = 925; const p = (r: number, a: number) => [CX + r * Math.sin(a), CY - r * Math.cos(a)]; const f = (n: number) => n.toFixed(1);
let acc = 0; const slices = data.map((d) => { const a0 = (acc / TOTAL) * 2 * Math.PI; acc += d.visitors; const a1 = (acc / TOTAL) * 2 * Math.PI; const large = a1 - a0 > Math.PI ? 1 : 0; const paths = RINGS.map(([ro, ri], ring) => { const grow = 0; const o = ro + grow; const [x0, y0] = p(o, a0); const [x1, y1] = p(o, a1); if (!ri) return `M${CX},${CY}L${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}Z`; const [x2, y2] = p(ri, a1); const [x3, y3] = p(ri, a0); return `M${f(x0)},${f(y0)}A${o},${o} 0 ${large} 1 ${f(x1)},${f(y1)}L${f(x2)},${f(y2)}A${ri},${ri} 0 ${large} 0 ${f(x3)},${f(y3)}Z`; }); const mid = (a0 + a1) / 2; const at = (r: number) => ({ x: CX + r * Math.sin(mid), y: CY - r * Math.cos(mid) }); return { ...d, paths, at }; });</script>
<Card.Root class="w-full max-w-sm gap-0 py-0"> <Card.Header class="items-center justify-items-center gap-1 px-6 pt-5.5 pb-0 text-center"> <Card.Title class="font-semibold text-base tracking-[-0.2px]">Pie Chart - Donut Labels</Card.Title> <Card.Description class="text-[13.5px]">January - June 2024</Card.Description> </Card.Header> <Card.Content class="items-center px-5 pt-2.5 pb-0"> <Chart.Container {config} class="aspect-auto h-auto w-auto flex-col items-center"> <svg width="256" height="236" viewBox="0 0 256 236" role="img" aria-label="Pie Chart - Donut Labels" class="block overflow-visible"> {#each slices as s (s.browser)} {#each s.paths as d, ring (ring)} <path {d} fill="var(--color-{s.browser})" stroke="var(--card)" stroke-width="2" stroke-linejoin="round" /> {/each} {/each} {#each slices as s (s.browser)} <text x={s.at(122).x} y={s.at(122).y + 4} text-anchor="middle" class="fill-foreground text-xs tabular-nums">{s.visitors}</text> {/each} </svg> </Chart.Container> </Card.Content> <Card.Footer class="flex-col items-center gap-1 px-6 pt-3.5 pb-5.5 text-center text-[13.5px]"> <div class="flex items-center gap-1.5 font-medium"> Trending up by 5.2% this month <IconPlaceholder lucide="TrendingUpIcon" tabler="IconTrendingUp" hugeicons="ChartUpIcon" phosphor="TrendUpIcon" remixicon="RiArrowUpLine" class="size-4" /> </div> <div class="text-muted-foreground">Showing total visitors for the last 6 months</div> </Card.Footer></Card.Root>