wrapped in
* (visually hidden, in DOM).
* • Columns mirror the chart axes; each data point is a | .
* • Screen-reader users can navigate data with standard table shortcuts.
*
* 4. Colour & contrast
* • Chart series colours must achieve ≥ 3:1 contrast against the card bg.
* • Never use colour as the ONLY differentiator — pair with:
* - Dashed vs solid line strokes
* - Direct inline labels on lines/segments
* - Shape markers on data points (circle vs square vs triangle)
* • Text labels inside charts: ≥ 4.5:1 on their local background.
*
* 5. Focus ring on data points
* • Active/focused data point: 3px outline, ≥ 3:1 contrast, distinct
* from the hover state (use outline-offset to separate).
*
* 6. Tooltip accessibility
* • Tooltips must appear on keyboard focus, not only on mouse hover.
* • Tooltip content must be announced to the live region.
* • Tooltip must remain visible while it has focus (no auto-dismiss).
* ─────────────────────────────────────────────────────────────────────────────
*/
import * as React from "react"
import {
Area, AreaChart,
Bar, BarChart,
CartesianGrid,
Cell,
ComposedChart,
Funnel, FunnelChart, LabelList,
Line, LineChart,
Pie, PieChart,
PolarAngleAxis, PolarGrid, PolarRadiusAxis,
Radar, RadarChart,
RadialBar, RadialBarChart,
Scatter, ScatterChart,
Sector,
Trapezoid,
XAxis, YAxis, ZAxis,
type DotItemDotProps,
} from "recharts"
import type { PieSectorDataItem } from "recharts/types/polar/Pie"
import {
QuotaLinearProgressCardBody,
QuotaRadialChartInner,
} from "@/components/dashboard-quota-progress-card"
import {
DASHBOARD_STUDENT_SCORES,
formatBandScore,
type StudentScoreRadial,
} from "@/lib/mock/dashboard"
import {
Card, CardContent, CardDescription, CardHeader, CardTitle,
} from "@/components/ui/card"
import {
ChartContainer,
ChartLegend,
ChartLegendContent,
ChartTooltip,
chartTooltipKeyboardSyncProps,
ChartTooltipContent,
type ChartConfig,
} from "@/components/ui/chart"
import { chartLineStrokeDash } from "@/lib/chart-line-dash"
import {
CHART_AXIS_TICK,
CHART_TICK_FONT_SIZE,
} from "@/lib/chart-typography"
import {
CHART_DASHBOARD_CELL_CLASS,
CHART_DASHBOARD_PLOT_MIN_CLASS,
CHART_DASHBOARD_ROW_GRID_CLASS,
CHART_DASHBOARD_ROW_GRID_THIRDS_CLASS,
} from "@/lib/chart-dashboard-layout"
import { chartLeoPeakAnchor } from "@/lib/chart-leo-spotting"
import {
CHART_KBD_ACTIVE_PIE_SHAPE,
} from "@/lib/chart-keyboard-selection"
import { cn } from "@/lib/utils"
/* ── Core chart shells (no gallery data) ─────────────────────────────────── */
import {
ChartCard,
ChartDataTable,
ChartFigure,
ChartLeoInsightOverlay,
ChartLeoPlotInsightOverlay,
ChartLeoPixelPlotInsightOverlay,
type ChartCardVariant,
type MiniMetric,
type ChartLeoInsight,
type ChartLeoInsightAnchor,
type ChartLeoInsightKind,
} from "@/components/charts-core"
export {
ChartCard,
ChartDataTable,
ChartFigure,
ChartLeoInsightOverlay,
ChartLeoPlotInsightOverlay,
ChartLeoPixelPlotInsightOverlay,
}
export type { ChartCardVariant, MiniMetric, ChartLeoInsight, ChartLeoInsightAnchor, ChartLeoInsightKind }
/** Recharts passes `index` into Line `dot` renderers; keep the callback typed against the v3 dot contract. */
type LineDotRenderProps = DotItemDotProps & { index?: number }
function donutLeoSector(props: PieSectorDataItem) {
const isPeak = props.name === "confirmed"
const isActive = (props as PieSectorDataItem & { isActive?: boolean }).isActive
return (
)
}
function radialLeoSector(
props: React.ComponentProps & { payload?: { name?: string }; isActive?: boolean },
) {
const isPeak = props.payload?.name === "nursing"
return (
)
}
function funnelLeoShape(
props: React.ComponentProps & { payload?: { name?: string } },
) {
const isPeak = props.payload?.name === "Applied"
return
}
const SCATTER_LEO_PEAK = { x: 90, y: 97 } as const
const activeIndexProps = (activeIndex: number | null) =>
activeIndex == null ? {} : ({ activeIndex } as Record)
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(--chart-2)"
const WARNING = "var(--chart-4)"
const DESTRUCTIVE = "var(--destructive)"
/* ── Period filter options (reused across selector cards) ─────────────────── */
const PERIOD_OPTIONS = [
{ value: "7d", label: "Last 7 days" },
{ value: "30d", label: "Last 30 days" },
{ value: "90d", label: "Last quarter" },
{ value: "1y", label: "Last year" },
]
const PROGRAM_OPTIONS = [
{ value: "all", label: "All programs" },
{ value: "nursing", label: "Nursing" },
{ value: "pt", label: "PT" },
{ value: "ot", label: "OT" },
{ value: "pharmacy", label: "Pharmacy" },
]
/* ════════════════════════════════════════════════════════════════════════════
DATA & CHART COMPONENTS
════════════════════════════════════════════════════════════════════════════ */
/* ── Area ─────────────────────────────────────────────────────────────────── */
const areaCfg: ChartConfig = {
placements: { label: "Placements", color: BRAND },
applications: { label: "Applications", color: CHART_2 },
reviews: { label: "Reviews", color: CHART_4 },
}
const areaData = [
{ month: "Aug", placements: 42, applications: 78, reviews: 31 },
{ month: "Sep", placements: 58, applications: 91, reviews: 44 },
{ month: "Oct", placements: 53, applications: 85, reviews: 39 },
{ month: "Nov", placements: 67, applications: 102, reviews: 52 },
{ month: "Dec", placements: 49, applications: 76, reviews: 37 },
{ month: "Jan", placements: 74, applications: 118, reviews: 60 },
{ month: "Feb", placements: 81, applications: 124, reviews: 68 },
{ month: "Mar", placements: 89, applications: 137, reviews: 72 },
]
export function AreaChartContent() {
return (
{(activeIndex) => (
<>
[d.month, d.placements, d.applications, d.reviews])} />
>
)}
)
}
function AreaLineTrendContent() {
return (
{(activeIndex) => (
<>
} />
} />
props.index === activeIndex ? : } />
props.index === activeIndex ? : } />
props.index === activeIndex ? : } />
[d.month, d.placements, d.applications, d.reviews])} />
>
)}
)
}
/* Selector variant — filter data by period */
const areaDataByPeriod: Record = {
"7d": areaData.slice(-2),
"30d": areaData.slice(-4),
"90d": areaData.slice(-6),
"1y": areaData,
}
function AreaSelectorContent({ filter }: { filter: string }) {
const data = areaDataByPeriod[filter] ?? areaData
return (
{(activeIndex) => (
<>
[d.month, d.placements, d.applications, d.reviews])} />
>
)}
)
}
/* ── Donut ─────────────────────────────────────────────────────────────────── */
const donutCfg: ChartConfig = {
confirmed: { label: "Confirmed", color: SUCCESS },
pending: { label: "Pending", color: WARNING },
rejected: { label: "Rejected", color: DESTRUCTIVE },
review: { label: "In Review", color: CHART_1 },
}
const donutDataAll = [
{ name: "confirmed", value: 58, fill: SUCCESS },
{ name: "pending", value: 24, fill: WARNING },
{ name: "rejected", value: 9, fill: DESTRUCTIVE },
{ name: "review", value: 9, fill: CHART_1 },
]
export function DonutChartContent({ data = donutDataAll }: { data?: typeof donutDataAll }) {
const total = data.reduce((s, d) => s + d.value, 0)
return (
{(activeIndex) => (
<>
} />
{data.map((d, i) => (
|
))}
{data.map((d) => (
{donutCfg[d.name]?.label}
{Math.round(d.value / total * 100)}%
))}
{
const raw = donutCfg[d.name]?.label ?? d.name
const label =
typeof raw === "string" || typeof raw === "number" ? String(raw) : String(d.name)
return [label, d.value] as [string, number]
})}
/>
>
)}
)
}
const donutBarTrendData = [
{ month: "Jan", confirmed: 52, pending: 20, rejected: 7 },
{ month: "Feb", confirmed: 60, pending: 18, rejected: 6 },
{ month: "Mar", confirmed: 68, pending: 24, rejected: 9 },
]
/* Donut trend — bar chart version */
function DonutBarTrendContent() {
return (
} />
} />
)
}
/* Donut — selector by program */
const donutByProgram: Record = {
all: donutDataAll,
nursing: [{ name: "confirmed", value: 72, fill: SUCCESS }, { name: "pending", value: 18, fill: WARNING }, { name: "rejected", value: 5, fill: DESTRUCTIVE }, { name: "review", value: 5, fill: CHART_1 }],
pt: [{ name: "confirmed", value: 55, fill: SUCCESS }, { name: "pending", value: 28, fill: WARNING }, { name: "rejected", value: 10, fill: DESTRUCTIVE }, { name: "review", value: 7, fill: CHART_1 }],
ot: [{ name: "confirmed", value: 48, fill: SUCCESS }, { name: "pending", value: 30, fill: WARNING }, { name: "rejected", value: 14, fill: DESTRUCTIVE }, { name: "review", value: 8, fill: CHART_1 }],
pharmacy: [{ name: "confirmed", value: 40, fill: SUCCESS }, { name: "pending", value: 35, fill: WARNING }, { name: "rejected", value: 15, fill: DESTRUCTIVE }, { name: "review", value: 10, fill: CHART_1 }],
}
/* ── Grouped Bar ─────────────────────────────────────────────────────────── */
const barCfg: ChartConfig = {
new: { label: "New", color: BRAND },
returned: { label: "Returned", color: CHART_2 },
}
const barData = [
{ program: "Nursing", new: 34, returned: 22 },
{ program: "PT", new: 28, returned: 18 },
{ program: "OT", new: 21, returned: 14 },
{ program: "SW", new: 19, returned: 11 },
{ program: "Pharm", new: 15, returned: 9 },
{ program: "Rad", new: 12, returned: 7 },
]
export function GroupedBarContent() {
return (
{(activeIndex) => (
<>
} />
} />
[d.program, d.new, d.returned])} />
>
)}
)
}
function GroupedBarLineTrend() {
return (
} />
} />
)
}
/* ── Stacked Bar ─────────────────────────────────────────────────────────── */
const stackCfg: ChartConfig = {
approved: { label: "Approved", color: SUCCESS },
pending: { label: "Pending", color: WARNING },
rejected: { label: "Rejected", color: DESTRUCTIVE },
}
const stackData = [
{ month: "Oct", approved: 38, pending: 12, rejected: 4 },
{ month: "Nov", approved: 44, pending: 15, rejected: 6 },
{ month: "Dec", approved: 31, pending: 8, rejected: 3 },
{ month: "Jan", approved: 52, pending: 18, rejected: 7 },
{ month: "Feb", approved: 60, pending: 14, rejected: 5 },
{ month: "Mar", approved: 68, pending: 20, rejected: 8 },
]
export function StackedBarContent() {
return (
{(activeIndex) => (
<>
} />
} />
[d.month, d.approved, d.pending, d.rejected])} />
>
)}
)
}
function StackedBarLineTrend() {
return (
} />
} />
)
}
/* ── Line ─────────────────────────────────────────────────────────────────── */
const lineCfg: ChartConfig = {
logins: { label: "Logins", color: BRAND },
submissions: { label: "Submissions", color: CHART_2 },
evaluations: { label: "Evaluations", color: CHART_4 },
}
const lineData = [
{ week: "W1", logins: 148, submissions: 42, evaluations: 29 },
{ week: "W2", logins: 162, submissions: 51, evaluations: 35 },
{ week: "W3", logins: 139, submissions: 38, evaluations: 27 },
{ week: "W4", logins: 175, submissions: 63, evaluations: 48 },
{ week: "W5", logins: 182, submissions: 69, evaluations: 52 },
{ week: "W6", logins: 196, submissions: 75, evaluations: 58 },
{ week: "W7", logins: 211, submissions: 82, evaluations: 63 },
{ week: "W8", logins: 204, submissions: 78, evaluations: 60 },
]
const lineDataByPeriod: Record = {
"7d": lineData.slice(-2),
"30d": lineData.slice(-4),
"90d": lineData.slice(-6),
"1y": lineData,
}
export function LineChartContent({ data = lineData }: { data?: typeof lineData }) {
return (
{(activeIndex) => (
<>
} />
} />
props.index === activeIndex ? : } />
props.index === activeIndex ? : } />
props.index === activeIndex ? : } />
[d.week, d.logins, d.submissions, d.evaluations])} />
>
)}
)
}
function LineAreaTrend() {
return (
} />
} />
)
}
/* ── Radial Bar ──────────────────────────────────────────────────────────── */
const radialCfg: ChartConfig = {
nursing: { label: "Nursing", color: BRAND },
pt: { label: "PT", color: CHART_2 },
ot: { label: "OT", color: SUCCESS },
pharmacy: { label: "Pharmacy", color: WARNING },
social: { label: "Social Work", color: CHART_4 },
}
const radialData = [
{ name: "nursing", score: 98, fill: BRAND },
{ name: "pt", score: 94, fill: CHART_2 },
{ name: "ot", score: 91, fill: SUCCESS },
{ name: "pharmacy", score: 87, fill: WARNING },
{ name: "social", score: 82, fill: CHART_4 },
]
export function RadialBarContent({ data = radialData }: { data?: typeof radialData }) {
return (
{(activeIndex) => (
<>
} />
{data.map((d, i) => (
|
))}
{data.map((d) => (
{radialCfg[d.name]?.label}
{d.score}%
))}
{
const raw = radialCfg[d.name]?.label ?? d.name
const label =
typeof raw === "string" || typeof raw === "number" ? String(raw) : String(d.name)
return [label, `${d.score}%`] as [string, string]
})}
/>
>
)}
)
}
const radialLineTrendCfg: ChartConfig = {
score: { label: "Current", color: BRAND },
prev: { label: "Previous", color: CHART_2 },
}
function RadialLineTrend() {
const data = radialData.map((d, i) => ({
name: d.name,
score: d.score,
prev: d.score - [4, 7, 2, 9, 5][i],
}))
return (
} />
} />
)
}
/** Quota radial — ChartFigure, keyboard tooltip sync, sr-only table (same pattern as RadialBarContent). */
function QuotaRadialGalleryContent({ radial }: { radial: StudentScoreRadial }) {
const summary =
`Radial gauge for ${radial.title}. Student score ${formatBandScore(radial.studentScore)}. Class average ${formatBandScore(radial.classAverage)}. Scale ${formatBandScore(radial.scaleMin)} to ${formatBandScore(radial.scaleMax)}. ${radial.caption}.`
return (
{(activeIndex) => (
<>
Class avg{" "}
{formatBandScore(radial.classAverage)}
{" "}
· scale {formatBandScore(radial.scaleMin)} to {formatBandScore(radial.scaleMax)}
>
)}
)
}
/* ── Horizontal Bar ─────────────────────────────────────────────────────── */
const hBarCfg: ChartConfig = {
placements: { label: "Placements", color: BRAND },
}
const hBarData = [
{ site: "City Med", placements: 42 },
{ site: "Westside Hosp", placements: 37 },
{ site: "North Clinic", placements: 31 },
{ site: "Bay Health", placements: 28 },
{ site: "Eastview", placements: 22 },
{ site: "Lakeshore", placements: 18 },
{ site: "Pinehill", placements: 14 },
]
const hBarByPeriod: Record = {
"7d": hBarData.map((d) => ({ ...d, placements: Math.round(d.placements * 0.35) })),
"30d": hBarData.map((d) => ({ ...d, placements: Math.round(d.placements * 0.6) })),
"90d": hBarData,
"1y": hBarData.map((d) => ({ ...d, placements: Math.round(d.placements * 4.2) })),
}
export function HorizontalBarContent({ data = hBarData }: { data?: typeof hBarData }) {
return (
{(activeIndex) => (
<>
} />
[d.site, d.placements])} />
>
)}
)
}
function HBarLineTrend() {
return (
} />
)
}
/* ── Composed ─────────────────────────────────────────────────────────────── */
const composedCfg: ChartConfig = {
placements: { label: "Placements", color: BRAND },
capacity: { label: "Capacity", color: CHART_3 },
rate: { label: "Fill Rate %", color: CHART_4 },
}
const composedData = [
{ month: "Sep", placements: 44, capacity: 60, rate: 73 },
{ month: "Oct", placements: 53, capacity: 65, rate: 82 },
{ month: "Nov", placements: 67, capacity: 80, rate: 84 },
{ month: "Dec", placements: 49, capacity: 70, rate: 70 },
{ month: "Jan", placements: 74, capacity: 85, rate: 87 },
{ month: "Feb", placements: 81, capacity: 90, rate: 90 },
{ month: "Mar", placements: 89, capacity: 95, rate: 94 },
]
export function ComposedChartContent() {
return (
{(activeIndex) => (
<>
} />
} />
props.index === activeIndex ? : } type="monotone" />
[d.month, d.placements, d.capacity, `${d.rate}%`])} />
>
)}
)
}
function ComposedLineTrend() {
return (
} />
} />
)
}
/* ── Radar ───────────────────────────────────────────────────────────────── */
const radarCfg: ChartConfig = {
nursing: { label: "Nursing", color: BRAND },
physical: { label: "PT/OT", color: CHART_2 },
}
const radarData = [
{ skill: "Clinical", nursing: 92, physical: 88 },
{ skill: "Comm.", nursing: 85, physical: 79 },
{ skill: "Critical", nursing: 78, physical: 84 },
{ skill: "Teamwork", nursing: 91, physical: 90 },
{ skill: "Ethics", nursing: 96, physical: 93 },
{ skill: "Technical", nursing: 80, physical: 87 },
]
export function RadarChartContent() {
return (
{(activeIndex) => (
<>
} />
} />
{
const payload = props.payload as { skill?: string }
const isPeak = payload?.skill === "Ethics"
if (!isPeak) {
return
}
return (
)
}}
/>
[d.skill, d.nursing, d.physical])} />
>
)}
)
}
function RadarBarTrend() {
return (
} />
} />
)
}
/* ── Scatter ─────────────────────────────────────────────────────────────── */
const scatterCfg: ChartConfig = {
nursing: { label: "Nursing", color: BRAND },
pt: { label: "PT", color: CHART_2 },
ot: { label: "OT", color: SUCCESS },
pharmacy: { label: "Pharmacy", color: WARNING },
}
const scatterNursing = [{ x: 80, y: 94, z: 42 }, { x: 65, y: 88, z: 35 }, { x: 55, y: 78, z: 28 }, { x: 90, y: 97, z: 51 }, { x: 70, y: 91, z: 38 }]
const scatterPT = [{ x: 40, y: 85, z: 22 }, { x: 50, y: 90, z: 27 }, { x: 35, y: 80, z: 18 }, { x: 60, y: 93, z: 31 }]
const scatterOT = [{ x: 30, y: 88, z: 16 }, { x: 45, y: 92, z: 24 }, { x: 38, y: 84, z: 19 }]
const scatterPharmacy = [{ x: 25, y: 76, z: 12 }, { x: 35, y: 82, z: 17 }, { x: 20, y: 71, z: 9 }]
export function ScatterChartContent() {
return (
} />
} />
{
const payload = props.payload as { x?: number; y?: number }
const isPeak =
payload?.x === SCATTER_LEO_PEAK.x && payload?.y === SCATTER_LEO_PEAK.y
return (
)
}}
/>
)
}
const scatterLineTrendCfg: ChartConfig = {
nursing: { label: "Nursing", color: BRAND },
pt: { label: "PT", color: CHART_2 },
}
const scatterLineTrendData = [
{ month: "Oct", nursing: 88, pt: 80 },
{ month: "Nov", nursing: 91, pt: 82 },
{ month: "Dec", nursing: 89, pt: 79 },
{ month: "Jan", nursing: 93, pt: 84 },
{ month: "Feb", nursing: 95, pt: 87 },
{ month: "Mar", nursing: 94, pt: 85 },
]
function ScatterLineTrend() {
return (
} />
} />
)
}
/* ── Funnel ──────────────────────────────────────────────────────────────── */
const funnelCfg: ChartConfig = {
applied: { label: "Applied", color: BRAND },
screened: { label: "Screened", color: CHART_2 },
matched: { label: "Matched", color: SUCCESS },
placed: { label: "Placed", color: CHART_4 },
completed: { label: "Completed", color: CHART_5 },
}
const funnelData = [
{ name: "Applied", value: 320, fill: BRAND },
{ name: "Screened", value: 240, fill: CHART_2 },
{ name: "Matched", value: 175, fill: SUCCESS },
{ name: "Placed", value: 128, fill: CHART_4 },
{ name: "Completed", value: 98, fill: CHART_5 },
]
const funnelDataByPeriod: Record = {
"7d": funnelData.map((d) => ({ ...d, value: Math.round(d.value * 0.08) })),
"30d": funnelData.map((d) => ({ ...d, value: Math.round(d.value * 0.3) })),
"90d": funnelData,
"1y": funnelData.map((d) => ({ ...d, value: d.value * 4 })),
}
export function FunnelChartContent({ data = funnelData }: { data?: typeof funnelData }) {
const summary = `Funnel with ${data.length} stages from ${data[0]?.name ?? ""} to ${data[data.length - 1]?.name ?? ""}.`
return (
{(activeIndex) => (
<>
} />
{data.map((d, i) => (
|
))}
[d.name, d.value])} />
>
)}
)
}
const funnelLineTrendCfg: ChartConfig = {
applied: { label: "Applied", color: BRAND },
placed: { label: "Placed", color: CHART_4 },
completed: { label: "Completed", color: CHART_5 },
}
const funnelLineTrendData = [
{ month: "Oct", applied: 210, placed: 95, completed: 68 },
{ month: "Nov", applied: 245, placed: 108, completed: 82 },
{ month: "Dec", applied: 180, placed: 88, completed: 64 },
{ month: "Jan", applied: 280, placed: 120, completed: 91 },
{ month: "Feb", applied: 300, placed: 124, completed: 95 },
{ month: "Mar", applied: 320, placed: 128, completed: 98 },
]
function FunnelLineTrend() {
return (
} />
} />
)
}
export const CHART_GALLERY_LEO_DONUT: ChartLeoInsight = {
headline: "Confirmed placements dominate the current pipeline",
explanation:
"87% of placements are already confirmed, with only 9% pending and 4% in review. This is a healthy distribution suggesting strong conversion from applications to confirmed offers.",
kind: "spike",
delta: { value: "+12%", label: "vs. last month" },
bullets: [
"Confirmed count has grown steadily across nursing, PT, and OT programs.",
"Rejection rate remains low at 1%. Applications are well-qualified.",
],
anchor: chartLeoPeakAnchor(),
}
export const CHART_GALLERY_LEO_APPLICATIONS: ChartLeoInsight = {
headline: "Nursing program leads application volume",
explanation:
"Nursing consistently attracts the most new applicants, with 34 this period. PT and OT follow closely. Returned applications suggest strong re-engagement.",
kind: "trend",
delta: { value: "+8%", label: "new vs. prior period" },
bullets: [
"Nursing: 34 new, 22 returned. Highest volume and strong re-engagement.",
"PT and OT: steady demand. Balanced load across clinical programs.",
],
anchor: { xValue: "Nursing", yDataKeys: ["new", "returned"], yCombine: "sum" },
}
export const CHART_GALLERY_LEO_LINE: ChartLeoInsight = {
headline: "Portal activity peaks mid-week",
explanation:
"Login, submission, and evaluation activity cluster from Tuesday to Thursday, with weekends showing predictable dips. This pattern is consistent and expected for an academic schedule.",
kind: "trend",
delta: { value: "", label: "stable pattern" },
bullets: [
"Logins peak at ~450 on Wednesdays.",
"Submissions highest Monday to Friday, near-zero on weekends.",
],
anchor: { xValue: "W7", yDataKeys: ["logins"], yValue: 211 },
}
export const CHART_GALLERY_LEO_COMPLIANCE: ChartLeoInsight = {
headline: "PT/OT programs lead compliance scoring",
explanation:
"PT and OT average 88% to 89% compliance, outpacing Nursing (82%) and Pharmacy (76%). Radiology lags at 71% and may need targeted support.",
kind: "dip",
delta: { value: "-8%", label: "Radiology vs. PT/OT" },
bullets: [
"PT/OT: consistent excellence across all 6 dimensions.",
"Pharmacy: scoring gaps in documentation and timeliness.",
"Radiology: needs support in scheduling and follow-up processes.",
],
anchor: chartLeoPeakAnchor(),
}
export const CHART_GALLERY_LEO_HORIZONTAL: ChartLeoInsight = {
headline: "Large clinical sites carry most placements",
explanation:
"The three largest sites (University Hospital, Metro Clinic, Regional Center) account for 58% of all placements. Mid-size sites are under-utilized.",
kind: "anomaly",
delta: { value: "+22%", label: "top 3 sites total" },
bullets: [
"University Hospital: 156 placements (28% of total).",
"Capacity constraints may limit placement growth at smaller sites.",
],
anchor: { xValue: "City Med", yDataKeys: ["placements"] },
}
export const CHART_GALLERY_LEO_COMPOSED: ChartLeoInsight = {
headline: "Site capacity is healthy; fill rates peak Q2",
explanation:
"Most sites run 85% to 92% capacity utilization. Fill rate (placements / capacity) averages 78%, with spring months (Feb to Mar) consistently hitting 82%+.",
kind: "spike",
delta: { value: "+6%", label: "fill rate increase" },
bullets: [
"March shows the strongest fill rate at 84%.",
"Only 2 sites are below 70% utilization. There is room to rebalance.",
],
anchor: { xValue: "Mar", yDataKeys: ["rate"], yValue: 94 },
}
export const CHART_GALLERY_LEO_RADAR: ChartLeoInsight = {
headline: "Nursing and PT/OT competencies are well-balanced",
explanation:
"Both programs score 80+ on all six dimensions. Nursing edges slightly on patient care; PT/OT lead in mobility and assessment. Ready for expanded placements.",
kind: "trend",
delta: { value: "", label: "strong across programs" },
bullets: [
"6-dimension average: Nursing 84%, PT/OT 86%.",
"Lowest dimension: patient care (Nursing 79%). Room to develop.",
],
anchor: chartLeoPeakAnchor(),
}
export const CHART_GALLERY_LEO_SCATTER: ChartLeoInsight = {
headline: "Application-to-placement funnel is healthy",
explanation:
"Applications feed steadily into offers; offer-to-confirmation conversion hovers around 72%. A small number of dropouts from offer-to-start, typical for clinical placements.",
kind: "trend",
delta: { value: "+4%", label: "confirmation rate" },
bullets: [
"Applications → Offers: 63% convert (typical for competitive placements).",
"Offers → Confirmed: 72% accept (strong acceptance rate).",
],
anchor: chartLeoPeakAnchor(),
}
export const CHART_GALLERY_LEO_FUNNEL: ChartLeoInsight = {
headline: "Funnel shape is expected; strong at top of pipe",
explanation:
"4,200 applications narrow to 842 offers (20% funnel rate) and 604 confirmed placements (72% offer acceptance). Losses are proportional, with no anomalous drops.",
kind: "trend",
delta: { value: "+8%", label: "application volume" },
bullets: [
"Application → Offer: drop-off is typical for screening.",
"Offer → Confirmed: acceptance rate of 72% is healthy.",
],
anchor: chartLeoPeakAnchor(),
}
export const CHART_GALLERY_LEO_QUOTA: ChartLeoInsight = {
headline: "Student performance tracking and cohort comparison",
explanation:
"Track individual student progress against class averages and scale benchmarks. Identify outliers above or below cohort norms.",
kind: "anomaly",
bullets: [
"Performance visualized on a consistent scale across cohorts.",
"Class average provides immediate context for comparison.",
],
anchor: chartLeoPeakAnchor(),
}
export const CHART_GALLERY_LEO_TRENDS: ChartLeoInsight = {
headline: "December dips across placements, applications, and reviews",
explanation:
"All three series pull back in December. That is often seasonal (holidays, academic breaks) or a real pipeline stall. Worth confirming whether approvals or site capacity paused.",
kind: "dip",
delta: { value: "-24%", label: "vs. November" },
bullets: [
"Placements are 18% below the 6-month trailing average.",
"Reviews dropped sharply in the last 2 weeks of the month.",
"Same pattern appeared in Dec '24, so a seasonal signal is plausible.",
],
anchor: {
xValue: "Dec",
yDataKeys: ["placements", "applications", "reviews"],
yCombine: "max",
},
}
export const CHART_GALLERY_LEO_REVIEWS: ChartLeoInsight = {
headline: "December is the low point in review throughput",
explanation:
"Totals drop before recovering. Worth confirming whether fewer submissions arrived or reviewers were out. Pending and rejected slices still matter once volume returns.",
kind: "dip",
delta: { value: "-31%", label: "vs. November total" },
bullets: [
"Approved reviews fell from 68 to 47 month-over-month.",
"Pending queue grew by 9 items. A backlog is forming.",
"Two reviewers were OOO for most of the last two weeks.",
],
anchor: {
xValue: "Dec",
yDataKeys: ["approved", "pending", "rejected"],
yCombine: "sum",
},
}
export const CHART_GALLERY_LEO_WATERFALL: ChartLeoInsight = {
headline: "New placements drove the largest positive step this cycle",
explanation:
"After opening at 120 placements, the New step adds 44. That is the biggest single increase before renewals and closures adjust the total to 158.",
kind: "spike",
delta: { value: "+37%", label: "vs. opening total" },
bullets: [
"New (+44) is the largest upward move in the sequence.",
"Expired (−22) and Closed (−15) are the main reductions after growth.",
],
anchor: { xValue: "New", yDataKeys: ["total"] },
}
export const CHART_GALLERY_LEO_TREEMAP: ChartLeoInsight = {
headline: "Nursing occupies the largest share of program volume",
explanation:
"The treemap shows Nursing at 34, roughly one-third of the combined program share, with PT and OT as the next largest tiles.",
kind: "trend",
delta: { value: "34", label: "Nursing share" },
bullets: [
"Tile area tracks proportional share. Nursing leads by a wide margin.",
"Smaller programs still appear as readable tiles with labels.",
],
anchor: chartLeoPeakAnchor(),
}
export const CHART_GALLERY_LEO_HEATMAP: ChartLeoInsight = {
headline: "Wednesday afternoon shows the busiest activity window",
explanation:
"Mid-week cells from 12p to 2p run hotter than early-morning slots. Useful when scheduling coordinator coverage.",
kind: "spike",
delta: { value: "53", label: "Wed 2p peak" },
bullets: [
"Peak intensity clusters on Wed between 12p and 2p.",
"Monday mornings stay lighter, which is good for async review work.",
],
anchor: chartLeoPeakAnchor(),
}
export const CHART_GALLERY_LEO_SANKEY: ChartLeoInsight = {
headline: "Screening retains most of the application volume",
explanation:
"240 applications flow into screening; the widest link is Applied → Screened, with expected tapering through placement stages.",
kind: "trend",
delta: { value: "240", label: "Applied → Screened" },
bullets: [
"Applied → Screened is the highest-volume transition.",
"Placed → Completed shows healthy completion conversion.",
],
anchor: chartLeoPeakAnchor(),
}
export const CHART_GALLERY_LEO_TIMELINE: ChartLeoInsight = {
headline: "Placement completes around day 56 on average",
explanation:
"Milestones cluster on a single lane with the longest gap between Placed and Complete. Worth monitoring for cycle-time SLAs.",
kind: "trend",
delta: { value: "56d", label: "to complete" },
bullets: [
"Matched → Placed spans ~13 days in this sample.",
"Complete lands near day 56. Use that for cohort planning.",
],
anchor: { xValue: "56", yDataKeys: ["lane"], yValue: 1 },
}
export const CHART_GALLERY_LEO_BUBBLE: ChartLeoInsight = {
headline: "West region combines high fill rate with strong student volume",
explanation:
"The West bubble sits high on fill rate (82%) with 52 students, an efficient site cluster worth replicating.",
kind: "spike",
bullets: [
"West: 82% fill rate at mid-range capacity.",
"South shows low fill despite available capacity. Investigate demand.",
],
anchor: { xValue: "West", yDataKeys: ["fillRate"], yValue: 82 },
}
export const CHART_GALLERY_LEO_BOXPLOT: ChartLeoInsight = {
headline: "Nursing scores show the tightest interquartile spread",
explanation:
"Nursing’s Q1 to Q3 band is narrow relative to Pharm. Performance is consistent across the cohort.",
kind: "trend",
delta: { value: "Q1 to Q3", label: "Nursing band" },
bullets: [
"Nursing median sits highest in the set.",
"Pharm shows the widest whisker span, with more variance to investigate.",
],
anchor: { xValue: "Nursing", yDataKeys: ["median"] },
}
export const CHART_GALLERY_LEO_RANGE: ChartLeoInsight = {
headline: "May shows the widest capacity band in the series",
explanation:
"The range between low and high capacity peaks in May. Plan staffing for the upper band while monitoring June tightening.",
kind: "trend",
delta: { value: "91", label: "May high" },
bullets: [
"May high band reaches 91 on this scale.",
"June compresses slightly. Confirm whether demand eased.",
],
anchor: { xValue: "May", yDataKeys: ["high"] },
}
export const CHART_GALLERY_LEO_BULLET: ChartLeoInsight = {
headline: "Compliance is closest to its target of the three KPIs",
explanation:
"Compliance at 86% is 6 points under the 92% target, a narrower gap than Placements or Reviews.",
kind: "dip",
delta: { value: "−6 pts", label: "vs. compliance target" },
bullets: [
"Compliance: 86 actual vs. 92 target.",
"Reviews trail furthest. Prioritize review throughput.",
],
anchor: { xValue: "Compliance", yDataKeys: ["value"], yValue: 86 },
}
export { CATALOG_PREVIEW_CHART_LEO } from "@/lib/catalog-preview-chart-leo"
function ChartRows({ v }: { v: ChartCardVariant }) {
const isTabs = v === "tabs"
const isSel = v === "selector"
const isMT = v === "metrics-tabs"
const isKpi = v === "kpi-chart"
return (
<>
{/* Row 1 · Area (2/3) + Donut (1/3) */}
{isSel ? (
{(f) => }
) : (
}
tabOptions={isTabs ? [
{ value: "overview", label: "Overview" },
{ value: "by-program", label: "By program" },
{ value: "trend", label: "Trend" },
] : undefined}
miniMetrics={(isMT || isKpi) ? [
{ label: "Placements", value: "89", trend: "up" },
{ label: "Fill rate", value: "94%", trend: "up" },
{ label: "Avg. weeks", value: "6.2", trend: "neutral" },
] : undefined}>
{isTabs
? (tab: string) => tab === "trend" ? :
: }
)}
{isSel ? (
{(f) => }
) : (
}
tabOptions={isTabs ? [
{ value: "current", label: "Current cycle" },
{ value: "previous", label: "Previous cycle" },
] : undefined}
miniMetrics={(isMT || isKpi) ? [
{ label: "Placed", value: "128", trend: "up" },
{ label: "Pending", value: "23", trend: "down" },
] : undefined}>
{isTabs
? (tab: string) =>
: }
)}
{/* Row 2 · Grouped Bar + Stacked Bar */}
{isSel ? (
{() => }
) : (
}
tabOptions={isTabs ? [
{ value: "all", label: "All students" },
{ value: "new", label: "New" },
{ value: "trend", label: "Trend" },
] : undefined}
miniMetrics={(isMT || isKpi) ? [
{ label: "Total", value: "320", trend: "up" },
{ label: "New", value: "78%", trend: "up" },
{ label: "Returning", value: "22%", trend: "neutral" },
] : undefined}>
{isTabs
? (tab: string) => tab === "trend" ? :
: }
)}
{isSel ? (
{() => }
) : (
}
tabOptions={isTabs ? [
{ value: "status", label: "By status" },
{ value: "reviewer", label: "By reviewer" },
{ value: "trend", label: "Trend" },
] : undefined}
miniMetrics={(isMT || isKpi) ? [
{ label: "Approved", value: "68", trend: "up" },
{ label: "Pending", value: "14", trend: "down" },
{ label: "Rejected", value: "6", trend: "neutral" },
] : undefined}>
{isTabs
? (tab: string) => tab === "trend" ? :
: }
)}
{/* Row 3 · Line (2/3) + Radial (1/3) */}
{isSel ? (
{(f) => }
) : (
}
tabOptions={isTabs ? [
{ value: "weekly", label: "Weekly" },
{ value: "monthly", label: "Monthly" },
{ value: "trend", label: "Trend" },
] : undefined}
miniMetrics={(isMT || isKpi) ? [
{ label: "Logins", value: "1.2k", trend: "up" },
{ label: "Submissions", value: "340", trend: "up" },
{ label: "Evals", value: "88", trend: "neutral" },
] : undefined}>
{isTabs
? (tab: string) => tab === "trend" ? :
: }
)}
{isSel ? (
{() => }
) : (
}
tabOptions={isTabs ? [
{ value: "current", label: "Current" },
{ value: "historical", label: "Historical" },
] : undefined}
miniMetrics={(isMT || isKpi) ? [
{ label: "Avg. score", value: "91%", trend: "up" },
{ label: "At risk", value: "3", trend: "down" },
] : undefined}>
{isTabs
? (tab: string) => tab === "historical" ? :
: }
)}
{/* Row 4 · H-Bar (1/3) + Composed (2/3) */}
{isSel ? (
{(f) => }
) : (
}
tabOptions={isTabs ? [
{ value: "by-facility", label: "By facility" },
{ value: "by-capacity", label: "By capacity" },
] : undefined}
miniMetrics={(isMT || isKpi) ? [
{ label: "Sites", value: "7", trend: "up" },
{ label: "Capacity", value: "94%", trend: "up" },
] : undefined}>
{isTabs
? () =>
: }
)}
{isSel ? (
{() => }
) : (
}
tabOptions={isTabs ? [
{ value: "overlay", label: "Overlay" },
{ value: "comparison", label: "Side by side" },
{ value: "trend", label: "Trend" },
] : undefined}
miniMetrics={(isMT || isKpi) ? [
{ label: "Fill rate", value: "94%", trend: "up" },
{ label: "Capacity", value: "95", trend: "up" },
{ label: "Placed", value: "89", trend: "up" },
] : undefined}>
{isTabs
? (tab: string) => tab === "trend" ? :
: }
)}
{/* Row 5 · Radar (1/3) + Scatter (2/3) */}
{isSel ? (
{() => }
) : (
}
tabOptions={isTabs ? [
{ value: "radar", label: "Radar" },
{ value: "breakdown", label: "Breakdown" },
] : undefined}
miniMetrics={(isMT || isKpi) ? [
{ label: "Avg.", value: "88%", trend: "up" },
{ label: "Top", value: "Clinical", trend: "neutral" },
] : undefined}>
{isTabs
? (tab: string) => tab === "breakdown" ? :
: }
)}
{isSel ? (
{() => }
) : (
}
tabOptions={isTabs ? [
{ value: "scatter", label: "Scatter" },
{ value: "ranking", label: "Ranking" },
{ value: "trend", label: "Trend" },
] : undefined}
miniMetrics={(isMT || isKpi) ? [
{ label: "Sites", value: "12", trend: "up" },
{ label: "Avg. rate", value: "87%", trend: "up" },
{ label: "Students", value: "320", trend: "up" },
] : undefined}>
{isTabs
? (tab: string) => tab === "trend" ? :
: }
)}
{/* Row 6 · Quota reference charts (band + radial demos) */}
{DASHBOARD_STUDENT_SCORES.metrics.map((m) => (
))}
{/* Row 7 · Funnel full width */}
{isSel ? (
{(f) => }
) : (
}
miniMetrics={(isMT || isKpi) ? [
{ label: "Applied", value: "320", trend: "up" },
{ label: "Placed", value: "128", trend: "up" },
{ label: "Completed", value: "98", trend: "up" },
{ label: "Drop-off", value: "69%", trend: "down" },
] : undefined}>
)}
>
)
}
/* ════════════════════════════════════════════════════════════════════════════
Design-system chart type previews (gallery bodies)
════════════════════════════════════════════════════════════════════════════ */
export function ChartQuotaRadialPreview() {
return
}
/* ════════════════════════════════════════════════════════════════════════════
Main export
════════════════════════════════════════════════════════════════════════════ */
export function ChartsOverview({ variant = "normal" }: { variant?: ChartCardVariant }) {
return (
)
}
|