"use client" /** * Chart primitive previews — re-export canonical gallery bodies from charts-overview * so the design-system Chart page stays in sync with the dashboard gallery. */ import * as React from "react" import { Bar, BarChart, CartesianGrid, Cell, ComposedChart, Customized, ErrorBar, ReferenceLine, Sankey, Scatter, ScatterChart, Text, Treemap, XAxis, YAxis, ZAxis, } from "recharts" import { Tabs, TabsContent, TabsList, TabsTrigger, } from "@/components/ui/tabs" import { AreaChartContent as ChartAreaPreview, ChartCard, ChartDataTable, ChartFigure, CHART_GALLERY_LEO_APPLICATIONS, CHART_GALLERY_LEO_BOXPLOT, CHART_GALLERY_LEO_BUBBLE, CHART_GALLERY_LEO_COMPLIANCE, CHART_GALLERY_LEO_COMPOSED, CHART_GALLERY_LEO_BULLET, CHART_GALLERY_LEO_DONUT, CHART_GALLERY_LEO_FUNNEL, CHART_GALLERY_LEO_HEATMAP, CHART_GALLERY_LEO_HORIZONTAL, CHART_GALLERY_LEO_LINE, CHART_GALLERY_LEO_QUOTA, CHART_GALLERY_LEO_RADAR, CHART_GALLERY_LEO_RANGE, CHART_GALLERY_LEO_REVIEWS, CHART_GALLERY_LEO_SANKEY, CHART_GALLERY_LEO_SCATTER, CHART_GALLERY_LEO_TIMELINE, CHART_GALLERY_LEO_TREEMAP, CHART_GALLERY_LEO_TRENDS, CHART_GALLERY_LEO_WATERFALL, ComposedChartContent as ChartComposedPreview, DonutChartContent as ChartDonutPreview, FunnelChartContent as ChartFunnelPreview, GroupedBarContent as ChartGroupedBarPreview, HorizontalBarContent as ChartHorizontalBarPreview, LineChartContent as ChartLinePreview, RadarChartContent as ChartRadarPreview, RadialBarContent as ChartRadialBarPreview, ScatterChartContent as ChartScatterPreview, StackedBarContent as ChartStackedBarPreview, ChartQuotaRadialPreview, type ChartLeoInsight, } from "@/components/charts-overview" import { buildChartHeatmapPoints, ChartHeatmap, } from "@/components/chart-heatmap" import { ChartLeoPlotInsightOverlay } from "@/components/chart-leo-spotting" import { ChartContainer, chartTooltipKeyboardSyncProps, ChartTooltip, ChartTooltipContent, type ChartConfig, } from "@/components/ui/chart" import { Button } from "@/components/ui/button" import { FilterChipGroup } from "@/components/ui/filter-chip-group" import { cn } from "@/lib/utils" import { CHART_AXIS_TICK, CHART_TICK_FONT_SIZE } from "@/lib/chart-typography" export { ChartAreaPreview, ChartComposedPreview, ChartDonutPreview, ChartFunnelPreview, ChartGroupedBarPreview, ChartHorizontalBarPreview, ChartLinePreview, ChartRadarPreview, ChartRadialBarPreview, ChartScatterPreview, ChartStackedBarPreview, ChartQuotaRadialPreview, } const BRAND = "var(--brand-color)" const CHART_1 = "var(--color-chart-1)" const CHART_2 = "var(--color-chart-2)" const CHART_3 = "var(--color-chart-3)" const CHART_4 = "var(--color-chart-4)" const CHART_5 = "var(--color-chart-5)" const SUCCESS = "var(--success)" const WARNING = "var(--warning)" const DESTRUCTIVE = "var(--destructive)" const activeIndexProps = (activeIndex: number | null) => activeIndex == null ? {} : ({ activeIndex } as Record) function GalleryChartCardShell({ title, description, leoInsight, children, }: { title: string description: string leoInsight: ChartLeoInsight children: React.ReactNode }) { return ( {children} ) } function ChartCardPreviewFrame({ title, description, summary, dataLength, leoInsight, leoPlot, children, table, }: { title: string description: string summary: string dataLength: number leoInsight: ChartLeoInsight leoPlot?: React.ReactNode children: (activeIndex: number | null) => React.ReactNode table: React.ReactNode }) { return ( {(activeIndex) => ( <>
{children(activeIndex)} {leoPlot}
{table} )}
) } const heatmapRows = ["Mon", "Tue", "Wed", "Thu", "Fri"] as const const heatmapCols = ["8a", "10a", "12p", "2p", "4p", "6p"] as const const heatmapMatrix = [ [12, 18, 32, 28, 20, 14], [16, 24, 36, 42, 33, 18], [22, 31, 48, 53, 41, 25], [18, 29, 44, 50, 39, 21], [10, 16, 25, 30, 24, 12], ] as const const heatmapPeak = { day: "Wed", time: "2p", activity: 53 } as const const heatmapConfig: ChartConfig = { activity: { label: "Activity", color: BRAND }, } const heatmapPoints = buildChartHeatmapPoints(heatmapRows, heatmapCols, heatmapMatrix) function heatmapCellIndex(day: string, time: string) { const row = heatmapRows.indexOf(day as (typeof heatmapRows)[number]) const col = heatmapCols.indexOf(time as (typeof heatmapCols)[number]) return row >= 0 && col >= 0 ? row * heatmapCols.length + col : -1 } function HeatmapPreview() { const leoPeakIndex = heatmapCellIndex(heatmapPeak.day, heatmapPeak.time) return ( [d.row, d.col, d.value])} /> } > {(activeIndex) => ( )} ) } const treemapConfig: ChartConfig = { nursing: { label: "Nursing", color: CHART_1 }, pt: { label: "PT", color: CHART_2 }, ot: { label: "OT", color: CHART_3 }, pharm: { label: "Pharm", color: CHART_4 }, social: { label: "Social", color: CHART_5 }, radiology: { label: "Radiology", color: BRAND }, } const treemapLeaves = [ { name: "Nursing", size: 34, fill: CHART_1 }, { name: "PT", size: 22, fill: CHART_2 }, { name: "OT", size: 18, fill: CHART_3 }, { name: "Pharm", size: 14, fill: CHART_4 }, { name: "Social", size: 8, fill: CHART_5 }, { name: "Radiology", size: 4, fill: BRAND }, ] as const const treemapTotal = treemapLeaves.reduce((sum, leaf) => sum + leaf.size, 0) type TreemapCellProps = { x?: number y?: number width?: number height?: number name?: string depth?: number index?: number fill?: string size?: number tooltipIndex?: string | null } /** Treemap nodes use string paths (`children[0]`) — map ChartFigure numeric index for keyboard tooltip sync. */ function treemapTooltipKeyboardSyncProps(keyboardActiveIndex: number | null) { const hasKbd = typeof keyboardActiveIndex === "number" && keyboardActiveIndex >= 0 && keyboardActiveIndex < treemapLeaves.length return { key: hasKbd ? `kbd-${keyboardActiveIndex}` : "kbd-off", props: hasKbd ? ({ defaultIndex: `children[${keyboardActiveIndex}]` } as unknown as { defaultIndex?: number }) : {}, } } function TreemapTileCell({ x = 0, y = 0, width = 0, height = 0, name, depth, index, fill, size, tooltipIndex, activeIndex, }: TreemapCellProps & { activeIndex: number | null }) { if (depth === 0 || width < 2 || height < 2) return null const leafIndex = depth === 1 ? index : undefined const isActive = leafIndex != null && activeIndex === leafIndex const isLeoPeak = leafIndex === 0 const showLabels = width >= 44 && height >= 36 const showValue = width >= 36 && height >= 28 return ( {showLabels && name ? ( <> {name} {showValue && size != null ? ( {size} ) : null} ) : null} ) } function TreemapShareLegend({ activeIndex, }: { activeIndex: number | null }) { return ( ) } function TreemapPreview() { return ( } table={ [ leaf.name, leaf.size, `${Math.round((leaf.size / treemapTotal) * 100)}%`, ])} /> } > {(activeIndex) => ( <> ( )} > } /> )} ) } const waterfallConfig: ChartConfig = { base: { label: "Base", color: "transparent" }, value: { label: "Value", color: BRAND }, } const waterfallSteps = [ { label: "Start", delta: 120 }, { label: "New", delta: 44 }, { label: "Expired", delta: -22 }, { label: "Renewed", delta: 31 }, { label: "Closed", delta: -15 }, ] as const type WaterfallRow = { label: string kind: "start" | "delta" | "end" base: number value: number delta: number total: number fill: string } function buildWaterfallData(steps: readonly { label: string; delta: number }[]): WaterfallRow[] { const rows: WaterfallRow[] = [] let total = steps[0]?.delta ?? 0 rows.push({ label: steps[0]?.label ?? "Start", kind: "start", base: 0, value: total, delta: total, total, fill: CHART_2, }) for (const step of steps.slice(1)) { const previous = total total += step.delta if (step.delta >= 0) { rows.push({ label: step.label, kind: "delta", base: previous, value: step.delta, delta: step.delta, total, fill: BRAND, }) } else { rows.push({ label: step.label, kind: "delta", base: total, value: Math.abs(step.delta), delta: step.delta, total, fill: DESTRUCTIVE, }) } } rows.push({ label: "End", kind: "end", base: 0, value: total, delta: 0, total, fill: CHART_3, }) return rows } const waterfallData = buildWaterfallData(waterfallSteps) type WaterfallConnectorLayerProps = { xAxisMap?: Record number; bandwidth?: () => number }> yAxisMap?: Record number }> } function WaterfallConnectorLayer({ xAxisMap, yAxisMap }: WaterfallConnectorLayerProps) { const xAxis = xAxisMap?.[Object.keys(xAxisMap)[0] ?? ""] const yAxis = yAxisMap?.[Object.keys(yAxisMap)[0] ?? ""] if (!xAxis?.scale || !yAxis?.scale) return null const band = xAxis.bandwidth?.() ?? 0 return ( ) } function WaterfallPreview() { return ( } table={ [d.label, d.delta, d.total])} />} > {(activeIndex) => ( } /> {waterfallData.map((entry) => ( ))} )} ) } const bubbleConfig: ChartConfig = { students: { label: "Students", color: BRAND }, } const bubbleData = [ { region: "North", capacity: 20, fillRate: 68, students: 34 }, { region: "West", capacity: 42, fillRate: 82, students: 52 }, { region: "Central", capacity: 58, fillRate: 48, students: 28 }, { region: "East", capacity: 76, fillRate: 74, students: 44 }, { region: "South", capacity: 88, fillRate: 38, students: 24 }, ] function BubblePreview() { return ( } table={ [d.region, d.capacity, `${d.fillRate}%`, d.students])} />} > {(activeIndex) => ( } /> )} ) } const rangeConfig: ChartConfig = { low: { label: "Low", color: "transparent" }, range: { label: "Range", color: BRAND }, } const rangeData = [ { month: "Jan", low: 44, high: 68, range: 24 }, { month: "Feb", low: 50, high: 74, range: 24 }, { month: "Mar", low: 56, high: 83, range: 27 }, { month: "Apr", low: 48, high: 76, range: 28 }, { month: "May", low: 62, high: 91, range: 29 }, { month: "Jun", low: 70, high: 96, range: 26 }, ] function RangePreview() { return ( } table={ [d.month, d.low, d.high])} />} > {(activeIndex) => ( } /> )} ) } const boxplotConfig: ChartConfig = { q1: { label: "Q1", color: CHART_2 }, iqr: { label: "IQR", color: BRAND }, } const boxplotData = [ { program: "Nursing", min: 62, q1: 72, median: 80, q3: 88, max: 96, iqr: 16 }, { program: "PT", min: 58, q1: 68, median: 76, q3: 84, max: 91, iqr: 16 }, { program: "OT", min: 64, q1: 74, median: 82, q3: 89, max: 94, iqr: 15 }, { program: "Pharm", min: 55, q1: 65, median: 73, q3: 82, max: 90, iqr: 17 }, ] function BoxplotPreview() { return ( } table={ [d.program, d.min, d.q1, d.median, d.q3, d.max])} />} > {(activeIndex) => ( } /> [entry.q1 - entry.min, entry.max - entry.q3]} width={8} stroke="var(--foreground)" /> )} ) } const bulletConfig: ChartConfig = { value: { label: "Actual", color: BRAND }, } const bulletData = [ { metric: "Compliance", value: 86, target: 92 }, { metric: "Placements", value: 74, target: 80 }, { metric: "Reviews", value: 63, target: 70 }, ] function BulletPreview() { return ( } table={ [d.metric, `${d.value}%`, `${d.target}%`])} />} > {(activeIndex) => ( } /> {bulletData.map((item) => ( ))} )} ) } const timelineConfig: ChartConfig = { milestone: { label: "Milestone", color: BRAND }, } const timelineData = [ { milestone: "Applied", day: 1, lane: 1 }, { milestone: "Screened", day: 7, lane: 1 }, { milestone: "Matched", day: 18, lane: 1 }, { milestone: "Placed", day: 31, lane: 1 }, { milestone: "Complete", day: 56, lane: 1 }, ] function TimelinePreview() { return ( } table={ [d.milestone, d.day])} />} > {(activeIndex) => ( } /> )} ) } const sankeyConfig: ChartConfig = { applied: { label: "Applied", color: CHART_1 }, screened: { label: "Screened", color: CHART_2 }, matched: { label: "Matched", color: CHART_3 }, placed: { label: "Placed", color: CHART_4 }, completed: { label: "Completed", color: CHART_5 }, } const sankeyData = { nodes: [ { name: "Applied", fill: CHART_1 }, { name: "Screened", fill: CHART_2 }, { name: "Matched", fill: CHART_3 }, { name: "Placed", fill: CHART_4 }, { name: "Completed", fill: CHART_5 }, ], links: [ { source: 0, target: 1, value: 240 }, { source: 1, target: 2, value: 175 }, { source: 2, target: 3, value: 128 }, { source: 3, target: 4, value: 98 }, ], } type SankeyNodeShapeProps = { x?: number y?: number width?: number height?: number index?: number payload?: { name?: string; fill?: string } } function SankeyNodeShape({ x = 0, y = 0, width = 0, height = 0, index = 0, payload, }: SankeyNodeShapeProps) { const isFirst = index === 0 const labelX = isFirst ? x - 8 : x + width + 8 const textAnchor = isFirst ? "end" : "start" return ( {payload?.name} ) } type SankeyLinkShapeProps = { sourceX?: number sourceY?: number targetX?: number targetY?: number sourceControlX?: number targetControlX?: number linkWidth?: number payload?: { value?: number source?: { name?: string } target?: { name?: string } } } function SankeyLinkShape({ sourceX = 0, sourceY = 0, targetX = 0, targetY = 0, sourceControlX = 0, targetControlX = 0, linkWidth = 0, payload, }: SankeyLinkShapeProps) { const isLeoPeak = payload?.source?.name === "Applied" && payload?.target?.name === "Screened" const strokeWidth = Math.max(linkWidth, 2) return ( ) } function SankeyStageLegend() { return (
    {sankeyData.nodes.map((node) => (
  • {node.name}
  • ))}
) } function SankeyFlowSummary() { return (

Application flow from Applied through Screened, Matched, Placed, to Completed. {sankeyData.links.map((link) => { const source = sankeyData.nodes[link.source]?.name ?? "" const target = sankeyData.nodes[link.target]?.name ?? "" return ` ${source} to ${target}: ${link.value}.` })}

) } function SankeyPreview() { return ( } table={ [ sankeyData.nodes[d.source]?.name ?? "", sankeyData.nodes[d.target]?.name ?? "", d.value, ])} /> } > {() => ( <> } link={} > { const payload = item.payload as { source?: { name?: string } target?: { name?: string } } const source = payload?.source?.name ?? "Source" const target = payload?.target?.name ?? "Target" return ( {source} → {target}: {value} ) }} /> } /> )} ) } const BAR_VARIANT_TABS = [ { id: "grouped", label: "Grouped", description: "Side-by-side bars for comparing related series per category.", content: ( ), }, { id: "stacked", label: "Stacked", description: "Stacked segments for part-to-whole category totals.", content: ( ), }, { id: "horizontal", label: "Horizontal", description: "Horizontal bars when category labels need more reading room.", content: ( ), }, ] as const function ChartSubTabs({ tabs, defaultValue, }: { tabs: readonly TTab[] defaultValue: TTab["id"] }) { const [activeValue, setActiveValue] = React.useState(defaultValue) const active = tabs.find((tab) => tab.id === activeValue) ?? tabs[0] return ( setActiveValue(value as TTab["id"])} className="flex-col gap-3" > {tabs.map((tab) => ( {tab.label} ))}

{active.description}

{tabs.map((tab) => ( {tab.content} ))}
) } function BarChartVariantPreview() { return } const TREND_VARIANT_TABS = [ { id: "area", label: "Area", description: "Filled trend lines for cumulative volume and trend bands.", content: ( ), }, { id: "line", label: "Line", description: "Multi-series trend line with dash variants.", content: ( ), }, ] as const function TrendChartFamilyPreview() { return } const DISTRIBUTION_VARIANT_TABS = [ { id: "donut", label: "Donut", description: "Part-to-whole distribution with legend and percent labels.", content: ( ), }, { id: "radial", label: "Radial", description: "Radial bar comparison across programs or score bands.", content: ( ), }, { id: "gauge", label: "Gauge", description: "Single-score radial gauge for quota, progress, or achievement.", content: ( ), }, ] as const function DistributionChartFamilyPreview() { return } const RELATIONSHIP_VARIANT_TABS = [ { id: "scatter", label: "Scatter", description: "Point cloud for relationships between two numeric measures.", content: ( ), }, { id: "bubble", label: "Bubble", description: "Scatter variant with point size encoding a third measure.", content: , }, { id: "radar", label: "Radar", description: "Multi-axis profile comparison across competency dimensions.", content: ( ), }, ] as const function RelationshipChartFamilyPreview() { return } const COMPOSITION_VARIANT_TABS = [ { id: "composed", label: "Composed", description: "Combined bar and line series for capacity, volume, and rate overlays.", content: ( ), }, ] as const function CompositionChartFamilyPreview() { return } const RANGE_VARIANT_TABS = [ { id: "range", label: "Range", description: "Low/high value bands for capacity, forecasts, or confidence intervals.", content: , }, { id: "bullet", label: "Bullet", description: "Actual value against target in a dense KPI comparison.", content: , }, ] as const function RangeChartFamilyPreview() { return } const STATISTICAL_VARIANT_TABS = [ { id: "boxplot", label: "Boxplot", description: "Distribution summary with min, quartiles, median, and max.", content: , }, { id: "heatmap", label: "Heatmap", description: "Intensity matrix for time, cohort, or schedule density.", content: , }, ] as const function StatisticalChartFamilyPreview() { return } const HIERARCHY_VARIANT_TABS = [ { id: "treemap", label: "Treemap", description: "Proportional category breakdown using a space-filling hierarchy.", content: , }, ] as const function HierarchyChartFamilyPreview() { return } const FLOW_VARIANT_TABS = [ { id: "funnel", label: "Funnel", description: "Pipeline progression from one stage to the next.", content: ( ), }, { id: "sankey", label: "Sankey", description: "Flow volume between stages or categories.", content: , }, { id: "waterfall", label: "Waterfall", description: "Positive and negative deltas that explain how a total changes.", content: , }, { id: "timeline", label: "Timeline", description: "Ordered events or milestones along a process.", content: , }, ] as const function FlowChartFamilyPreview() { return } const CHART_TABS = [ { id: "trends", label: "Trends", description: "Use trend charts for time series, movement, and volume over time.", content: , }, { id: "bars", label: "Bars", description: "Bar chart family with grouped, stacked, and horizontal variants.", content: , }, { id: "distribution", label: "Distribution", description: "Use distribution charts for part-to-whole, score, and progress views.", content: , }, { id: "relationship", label: "Relationship", description: "Use relationship charts to compare dimensions or reveal correlation.", content: , }, { id: "composition", label: "Composition", description: "Use composition charts when multiple measures share one story.", content: , }, { id: "range", label: "Range", description: "Use range charts for uncertainty, targets, and value bands.", content: , }, { id: "statistical", label: "Statistical", description: "Use statistical charts for distribution, intensity, and variance.", content: , }, { id: "hierarchy", label: "Hierarchy", description: "Use hierarchy charts for proportional category breakdowns.", content: , }, { id: "flow", label: "Flow", description: "Use flow charts for stage conversion and pipeline drop-off.", content: , }, ] as const export function ChartTabbedPreview() { const [activeTab, setActiveTab] = React.useState<(typeof CHART_TABS)[number]["id"]>("trends") const active = CHART_TABS.find((tab) => tab.id === activeTab) ?? CHART_TABS[0] return (
({ value: tab.id, label: tab.label }))} variant="muted" size="sm" aria-label="Chart families" />

{active.description}

{active.content}
) }