{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "area-chart", "title": "Area Chart", "description": "A customizable area chart component with multiple variants (default, stacked, gradient, interactive).", "dependencies": [ "recharts", "lucide-react" ], "registryDependencies": [ "card", "chart" ], "files": [ { "path": "registry/new-york/area-chart/area-chart.tsx", "content": "\"use client\"\n\nimport { useMemo } from \"react\"\nimport {\n Area,\n AreaChart,\n CartesianGrid,\n XAxis,\n YAxis,\n} from \"recharts\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from \"@/components/ui/card\"\nimport {\n ChartContainer,\n ChartLegend,\n ChartLegendContent,\n ChartTooltip,\n ChartTooltipContent,\n type ChartConfig,\n} from \"@/components/ui/chart\"\nimport { TrendingUp } from \"lucide-react\"\n\nexport type AreaChartVariant =\n | 'default'\n | 'linear'\n | 'step'\n | 'stacked'\n | 'stacked-expanded'\n | 'gradient'\n | 'legend'\n | 'interactive'\n\nexport type AreaCurveType = 'monotone' | 'linear' | 'step' | 'stepBefore' | 'stepAfter' | 'natural' | 'basis'\n\nexport interface AreaConfig {\n dataKey: string\n fill?: string\n stroke?: string\n stackId?: string\n type?: AreaCurveType\n fillOpacity?: number\n}\n\nexport interface AreaChartComponentProps {\n title?: string\n description?: string\n data: Array>\n chartConfig: ChartConfig\n className?: string\n\n variant?: AreaChartVariant\n\n areas?: AreaConfig[]\n\n xAxisKey?: string\n xAxisFormatter?: (value: string) => string\n yAxisConfig?: {\n domain?: [number, number] | 'auto'\n tickFormatter?: (value: number) => string\n hide?: boolean\n }\n\n showGrid?: boolean\n showTooltip?: boolean\n showLegend?: boolean\n showDots?: boolean\n showActiveDot?: boolean\n\n useGradient?: boolean\n gradientOpacity?: { start: number; end: number }\n\n margin?: { top?: number; right?: number; bottom?: number; left?: number }\n\n footerContent?: {\n mainText?: string\n subText?: string\n showTrending?: boolean\n trendingIcon?: React.ReactNode\n trendingColor?: string\n }\n}\n\nconst getCurveType = (variant: AreaChartVariant): AreaCurveType => {\n switch (variant) {\n case 'linear':\n return 'linear'\n case 'step':\n return 'step'\n default:\n return 'monotone'\n }\n}\n\nconst isStackedVariant = (variant: AreaChartVariant): boolean => {\n return variant === 'stacked' || variant === 'stacked-expanded'\n}\n\nexport const AreaChartComponent = ({\n title,\n description,\n data,\n chartConfig,\n className = \"\",\n variant = 'default',\n areas,\n xAxisKey = \"month\",\n xAxisFormatter = (value) => typeof value === 'string' ? value.slice(0, 3) : String(value),\n yAxisConfig,\n showGrid = true,\n showTooltip = true,\n showLegend,\n showDots,\n showActiveDot = true,\n useGradient,\n gradientOpacity = { start: 0.8, end: 0.1 },\n margin = { left: 12, right: 12 },\n footerContent,\n}: AreaChartComponentProps) => {\n // Determine curve type based on variant\n const curveType = getCurveType(variant)\n\n // Determine if stacked\n const isStacked = isStackedVariant(variant)\n\n // Auto determine showLegend based on variant\n const shouldShowLegend = showLegend ?? (variant === 'legend' || variant === 'stacked' || variant === 'stacked-expanded')\n\n // Auto determine showDots based on variant\n const shouldShowDots = showDots ?? (variant === 'interactive')\n\n // Auto determine showActiveDot (dot only on hover)\n const shouldShowActiveDot = showActiveDot ?? true // Default true: show dot on hover\n\n // Auto determine gradient based on variant\n const shouldUseGradient = useGradient ?? (variant === 'gradient')\n\n // Auto-generate areas from chartConfig if not provided\n const chartAreas = useMemo(() => {\n if (areas) return areas\n\n return Object.keys(chartConfig).map((key, index) => ({\n dataKey: key,\n fill: shouldUseGradient ? `url(#fill${key})` : chartConfig[key].color || `var(--chart-${index + 1})`,\n stroke: chartConfig[key].color || `var(--chart-${index + 1})`,\n stackId: isStacked ? 'stack' : undefined,\n type: curveType,\n fillOpacity: shouldUseGradient ? 1 : 0.4,\n }))\n }, [areas, chartConfig, isStacked, curveType, shouldUseGradient])\n\n // Prepare data for stacked-expanded (normalize to 100%)\n const normalizedData = useMemo(() => {\n if (variant !== 'stacked-expanded') return data\n\n return data.map(item => {\n const newItem = { ...item }\n const dataKeys = chartAreas.map(a => a.dataKey)\n const total = dataKeys.reduce((sum, key) => sum + (Number(item[key]) || 0), 0)\n\n if (total > 0) {\n dataKeys.forEach(key => {\n newItem[key] = ((Number(item[key]) || 0) / total) * 100\n })\n }\n return newItem\n })\n }, [data, variant, chartAreas])\n\n // Render gradients if needed\n const renderGradients = () => {\n if (!shouldUseGradient) return null\n\n return (\n \n {chartAreas.map((area) => (\n \n \n \n \n ))}\n \n )\n }\n\n // Render Y Axis\n const renderYAxis = () => {\n if (yAxisConfig?.hide) return null\n\n const tickFormatter = variant === 'stacked-expanded'\n ? (value: number) => `${value}%`\n : yAxisConfig?.tickFormatter\n\n if (!tickFormatter && variant !== 'stacked-expanded') return null\n\n return (\n \n )\n }\n\n return (\n \n {(title || description) && (\n \n {title && {title}}\n {description && {description}}\n \n )}\n\n \n
\n \n \n {renderGradients()}\n\n {showGrid && }\n\n \n\n {renderYAxis()}\n\n {showTooltip && (\n \n }\n />\n )}\n\n {shouldShowLegend && } />}\n\n {chartAreas.map((area) => (\n \n ))}\n \n \n
\n
\n\n {footerContent && (\n \n
\n {footerContent.mainText}\n {footerContent.showTrending && footerContent.trendingIcon}\n
\n {footerContent.subText && (\n
\n {footerContent.subText}\n
\n )}\n
\n )}\n
\n )\n}\n\n// ========================================================================================\n// Area Chart Example\n// ========================================================================================\nexport const AreaChartExample = () => {\n const chartData = [\n { month: \"January\", desktop: 186, mobile: 80 },\n { month: \"February\", desktop: 305, mobile: 200 },\n { month: \"March\", desktop: 237, mobile: 120 },\n { month: \"April\", desktop: 73, mobile: 190 },\n { month: \"May\", desktop: 209, mobile: 130 },\n { month: \"June\", desktop: 214, mobile: 140 },\n ]\n\n const chartConfig = {\n desktop: {\n label: \"Desktop\",\n color: \"var(--chart-1)\",\n },\n mobile: {\n label: \"Mobile\",\n color: \"var(--chart-2)\",\n },\n } satisfies ChartConfig\n\n return (\n typeof value === 'string' ? value.slice(0, 3) : String(value)}\n yAxisConfig={{\n domain: [0, 400],\n tickFormatter: (value) => String(value),\n }}\n footerContent={{\n mainText: \"Trending up by 5.2% this month\",\n subText: \"January - June 2024\",\n showTrending: true,\n trendingIcon: ,\n trendingColor: \"text-emerald-600\"\n }}\n />\n )\n}\n", "type": "registry:component" } ], "type": "registry:block" }