{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "pie-chart", "title": "Pie Chart", "description": "A customizable pie/donut chart component with animated active sections and legends.", "dependencies": [ "recharts", "motion" ], "registryDependencies": [ "card", "chart" ], "files": [ { "path": "registry/new-york/pie-chart/pie-chart.tsx", "content": "\"use client\"\n\nimport { Pie, PieChart, Sector, Label } from \"recharts\"\n\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from \"@/components/ui/card\"\nimport {\n ChartConfig,\n ChartContainer,\n ChartStyle,\n ChartTooltip,\n ChartTooltipContent,\n} from \"@/components/ui/chart\"\nimport { useMemo, useState } from \"react\"\nimport { motion } from \"framer-motion\"\n\nexport interface PieChartData {\n category: string\n value: number\n percentage: number\n fill?: string\n [key: string]: unknown\n}\n\nexport interface PieChartProps {\n title?: string\n description?: string\n data: PieChartData[]\n chartConfig: ChartConfig\n className?: string\n dataKey?: string\n nameKey?: string\n legendKey?: string\n innerRadius?: number\n outerRadius?: number\n cornerRadius?: number\n paddingAngle?: number\n strokeWidth?: number\n stroke?: string\n animationDuration?: number\n enableAnimation?: boolean\n showLegend?: boolean\n showTooltip?: boolean\n showActiveSection?: boolean\n footerContent?: {\n mainText?: string\n subText?: string\n showTrending?: boolean\n trendingColor?: string\n trendingIcon?: React.ReactNode\n }\n}\n\nexport const PieChartComponent = ({\n title,\n description,\n data,\n chartConfig,\n className = \"\",\n dataKey = \"value\",\n nameKey = \"category\",\n legendKey = \"category\",\n innerRadius = 0,\n outerRadius = 100,\n strokeWidth = 0,\n cornerRadius = 0,\n paddingAngle = 0,\n stroke = \"var(--background)\",\n showLegend = true,\n showTooltip = true,\n showActiveSection = false,\n footerContent\n}: PieChartProps) => {\n const [hoverIndex, setHoverIndex] = useState(null)\n\n const activeIndex = useMemo(() => {\n return showActiveSection ? hoverIndex : null\n }, [hoverIndex, showActiveSection])\n\n const handleActiveSection = (_: unknown, index: number) => {\n if (showActiveSection) {\n setHoverIndex(index)\n }\n }\n\n const handleUnActiveSection = () => {\n if (showActiveSection) {\n setHoverIndex(null)\n }\n }\n\n const renderLegend = () => {\n if (!showLegend) return null;\n\n return (\n
\n {data.map((item, index) => {\n const legendValue = String(item[legendKey] || \"\")\n const color = item.fill || chartConfig[legendValue as keyof typeof chartConfig]?.color || `var(--chart-${index + 1})`\n\n return (\n
\n \n \n {chartConfig[legendValue as keyof typeof chartConfig]?.label || legendValue}\n \n
\n )\n })}\n
\n )\n }\n\n return (\n \n \n\n \n {title}\n {description && {description}}\n \n\n \n
\n \n \n {showTooltip && (\n }\n />\n )}\n\n {\n const typedProps = props as { outerRadius?: number;[key: string]: unknown }\n const { outerRadius = 100, ...otherProps } = typedProps\n return (\n \n \n \n \n )\n } : undefined}\n >\n {showActiveSection && innerRadius > 0 && activeIndex !== null && (\n {\n if (viewBox && \"cx\" in viewBox && \"cy\" in viewBox) {\n const activeData = data[activeIndex]\n if (!activeData) return null\n\n return (\n \n \n {String(activeData[dataKey] || '').toLocaleString()}\n \n \n {String(\n chartConfig[activeData[nameKey] as keyof typeof chartConfig]?.label\n ?? activeData[nameKey]\n ?? ''\n )}\n \n \n )\n }\n }}\n />\n )}\n \n \n \n
\n\n {renderLegend()}\n
\n\n {footerContent && (\n \n
\n \n {footerContent.mainText}\n \n {footerContent.showTrending && footerContent.trendingIcon && (\n \n {footerContent.trendingIcon}\n \n )}\n
\n\n
\n {footerContent.subText}\n
\n
\n )}\n
\n )\n}\n\n\n// ========================================================================================\n// Pie Chart Example\n// ========================================================================================\nexport const PieChartExample = () => {\n const rawData = [\n { intent: \"product_inquiry\", interactions: 1250, fill: \"var(--chart-1)\" },\n { intent: \"technical_support\", interactions: 892, fill: \"var(--chart-2)\" },\n { intent: \"pricing_question\", interactions: 654, fill: \"var(--chart-3)\" },\n { intent: \"booking_service\", interactions: 487, fill: \"var(--chart-4)\" },\n { intent: \"general_greeting\", interactions: 398, fill: \"var(--chart-5)\" },\n { intent: \"complaint_feedback\", interactions: 234, fill: \"var(--chart-6)\" },\n ]\n const total = rawData.reduce((sum, d) => sum + d.interactions, 0)\n const data = rawData.map(d => ({\n category: d.intent,\n value: d.interactions,\n percentage: Math.round((d.interactions / total) * 1000) / 10, // 1 chữ số thập phân\n fill: d.fill\n }))\n\n const chartConfig = {\n interactions: {\n label: \"Interactions\",\n },\n product_inquiry: {\n label: \"Product\",\n color: \"var(--chart-1)\",\n },\n technical_support: {\n label: \"Technical Support\",\n color: \"var(--chart-2)\",\n },\n pricing_question: {\n label: \"Pricing Questions\",\n color: \"var(--chart-3)\",\n },\n booking_service: {\n label: \"Booking Service\",\n color: \"var(--chart-4)\",\n },\n general_greeting: {\n label: \"General Greeting\",\n color: \"var(--chart-5)\",\n },\n complaint_feedback: {\n label: \"Complaints & Feedback\",\n color: \"var(--chart-6)\",\n },\n } satisfies ChartConfig\n\n return (\n \n )\n}", "type": "registry:component" } ], "type": "registry:block" }