The Pyvoid Chart Gallery: 31 Tufte-Informed Archetypes
Every chart in Pyvoid, why it exists, and when not to use it. A reference catalog of the 31 D3 archetypes that make up the BIM dashboards.
The Pyvoid Chart Gallery: 31 Tufte-Informed Archetypes
Pyvoid ships 31 chart archetypes in its design system - 21 from DDS v2 and 10 from Phase 9's Tufte-informed expansion. Every one was picked for a specific data shape and reading task. This page is the reference: pick by data shape, copy the recipe, ship.
The whole catalog lives behind one factory namespace: PyvoidCharts. Every archetype is keyboard-reachable, theme-aware, observer-cleanup-safe (REQ-DDS-030), and respects data-pyvoid-charts-v2 opt-out attributes. Source lives at Pyvoid.extension/lib/html/assets/js/pyvoid-charts.js and pyvoid-charts-archetypes.js; the contracts live at .agent/PATTERNS_CHARTS.md.
Pick by data shape
| Data shape | Archetype | When NOT |
|---|---|---|
| Single KPI in a table cell | createInlineSparkline | Frame it; that defeats the point |
| Target vs actual single value | createBullet | Less than 3 categories - use one number |
| Target vs actual, 2-8 entities | createBullet (multi-row) | More than 12 entities - use dot plot |
| 3-7 categories ranked | createSortedBar | More than 12 - use Cleveland dot plot |
| 8+ categories ranked | createDotPlot | Time-series data - use sparkline or line |
| Pre/post by category | createSlopeGraph | More than ~10 rows - labels saccade |
| Single time-series (small) | createSparkline | Card-body sparkline - that's createInlineSparkline |
| 2-3 time-series, same scale | createMultiLine | Different scales - use small multiples |
| 2 time-series, different scales | createSmallMultiplesGrid | Dual-axis - never |
| 5 series of same shape | createSmallMultiplesGrid (2x3) | More than 12 - use horizon |
| 20+ series, shared scale | createHorizon | Less than 6 - overlay or small multiples |
| Opened-vs-closed trajectory | createBurndown | Single-series count - sparkline |
| Distribution over time | createRidgeline | 1-2 categories - overlay or small multiples |
| Day-of-week activity | createCalendarHeatmap | Aggregate counts - bar chart |
| Continuous matrix (rows x cols) | createHeatmap (redesign: 'v9') | Rainbow palette - that's the anti-pattern |
| Two-metric trajectory | createConnectedScatter | One metric - line chart |
| Sequential flow with branches | createSankey | Linear delta - use waterfall |
| Linear delta (in/out flows) | createWaterfall | Branched flow - sankey |
| Hierarchical part-of-whole | createTreemap | Need to navigate hierarchy - hierarchyHeaders: true |
| Hierarchy navigation | createSunburst OR createTreemap w/ headers | Past 2 levels - use indented bars |
| Stacked counts over time | createStackedAreaChart (redesign: 'v9') | Reading individual series - small multiples |
| Multi-dimensional comparison | createRadar | More than 7 dimensions - Cleveland dot plot |
| Single-value KPI card | createGauge | Tier utilization - bullet chart |
| Funnel with stages | createFunnelChart | Width-encodes count - use sankey or waterfall |
Phase 9 archetypes (10 new)
The Phase 9 redesigns added 10 Tufte-informed factories. Each has a contract documented in .agent/PATTERNS_CHARTS.md.
createInlineSparkline
A true Tufte sparkline: ≈1em tall, no frame, endpoint dot, optional reference band. Sits inside table cells or running text.
When: A single value with recent context next to the number itself. ROI per week in a KPI card, recent trend cells in tool-usage tables.
When NOT: Card-body charts (use createSparkline). Frames defeat the archetype.
PyvoidCharts.createInlineSparkline(container, {
data: [3, 5, 4, 8, 12, 9, 14, 11, 16, 18],
variant: 'line', // or 'binary' for 0/1 streams
endpointDot: true,
redesign: 'v9',
});createBullet (multi-row)
Stephen Few bullet chart with a multi-row extension. All rows share one x-scale; per-row range bands, actual bars, target markers.
When: Target-vs-actual on 2-8 entities sharing a comparable scale. Tier utilization (Purchased/Active/Power), discipline adoption, sheet-issuance velocity by phase.
When NOT: A single KPI (use the single-object signature). Past 12 rows (use Cleveland dot plot - bullet height becomes too thin).
PyvoidCharts.createBullet(container, {
data: [
{ label: 'Tier 1', ranges: [60, 80, 100], target: 80, actual: 87 },
{ label: 'Tier 2', ranges: [60, 80, 100], target: 80, actual: 72 },
{ label: 'Tier 3', ranges: [60, 80, 100], target: 80, actual: 45 },
],
redesign: 'v9',
});createSmallMultiplesGrid
Trellis layout. Each panel renders the same shape with shared scales. Outer-edge axes only, panel labels typographically subordinate.
When: 4-9 series of the same shape that need to read together. Severity over time (2x2 or 2x3), discipline issuance (2x2), project-comparison panels.
When NOT: Two series in one panel (overlay). 12+ series (horizon).
PyvoidCharts.createSmallMultiplesGrid(container, {
panels: [
{ label: 'Critical', data: [3, 4, 5, 6, 7, 8, 9] },
{ label: 'High', data: [12, 14, 16, 18, 22, 19, 21] },
{ label: 'Medium', data: [20, 22, 24, 21, 19, 18, 20] },
{ label: 'Low', data: [4, 3, 5, 4, 3, 2, 1] },
],
layout: '2x2',
sharedScale: 'y',
redesign: 'v9',
});createSlopeGraph
Two-endpoint comparison. One line per series joining a start and end value. Direct labels at both endpoints. No legend.
When: Pre/post by category. Phase comparison, audit-pass-rate per discipline, on-time delivery before/after.
When NOT: Time-series with more than 2 points (line chart). More than ~10 rows (labels saccade).
PyvoidCharts.createSlopeGraph(container, {
data: [
{ label: 'SD', start: 80, end: 60 },
{ label: 'DD', start: 65, end: 45 },
{ label: 'CD', start: 70, end: 30 },
{ label: 'CA', start: 50, end: 20 },
],
leftLabel: 'Pre-audit',
rightLabel: 'Post-audit',
});createDotPlot (Cleveland)
One row per category with a dot at the value. Optional paired-dot variant for current/prior comparisons with a connector.
When: 8+ category rankings. Top tools by usage, warning types by count, project size.
When NOT: 3-5 categories (use sorted bar). Continuous distributions (ridgeline). Trend over time (sparkline).
PyvoidCharts.createDotPlot(container, {
data: [
{ label: 'Match Properties', value: 142 },
{ label: 'Smart Select', value: 88, prior: 76 },
// ...
],
sort: 'desc',
redesign: 'v9',
});createCalendarHeatmap (Phase 9 extension)
Year-grid daily cells with Phase 9 additions: startDate/endDate range mode, colorScale override, showMonthLabels/showDayLabels toggles, > 730 day auto-clip with one-shot console.info.
When: Day-of-week activity, weekly-cyclical patterns. Audit timestamps, warning-creation activity, tool-usage by hour.
When NOT: Aggregate counts (bar chart). Time-series with smooth-trend reading (line chart).
PyvoidCharts.createCalendarHeatmap(container, {
data: [{ date: '2026-01-15', value: 3 }, /* ... */],
startDate: new Date(2026, 0, 1),
endDate: new Date(2026, 11, 31),
});createSortedBar
Sorted horizontal bars with optional dashed target reference line. The Tufte-approved replacement for 3-7 category donuts.
When: 3-7 category comparison that previously reached for a donut. Team segment distribution, project breakdown.
When NOT: 12+ categories (dot plot). Categorical-by-time matrices (heatmap).
PyvoidCharts.createSortedBar(container, {
data: [
{ label: 'Architecture', value: 87 },
{ label: 'Structure', value: 72 },
{ label: 'MEP', value: 65 },
{ label: 'Civil', value: 52 },
],
sort: 'desc',
target: 80,
targetLabel: 'Goal',
redesign: 'v9',
});createHorizon
Space-efficient multi-series time chart. Each series in one narrow row with band-stacking; positive in accent, negative in error. Auto-decimates at >40 series with one console.warn.
When: 20+ time-series that need to read together. Per-workset warning counts, per-discipline issuance velocity, per-tier adoption.
When NOT: Fewer than 6 series (small multiples). Very different scales (small multiples - horizon assumes shared scale).
PyvoidCharts.createHorizon(container, {
series: [
{ label: 'Workset 1', data: [{ x: 0, y: 3 }, { x: 1, y: 7 }, /* ... */] },
// ... 20+ series
],
bandCount: 3,
maxSeries: 40,
});createBurndown
Opened/closed/net trajectories in one panel. Linear-interpolated zero-crossing markers highlight the moments where net flips sign.
When: "Are we keeping up?" reading. Warning resolution, sheet-issuance vs plan, audit failures opened vs closed per week.
When NOT: Single-series count (sparkline). More than ~3 series (small multiples).
PyvoidCharts.createBurndown(container, {
data: [
{ t: 0, opened: 30, closed: 10 },
{ t: 1, opened: 28, closed: 14 },
{ t: 2, opened: 25, closed: 22 },
{ t: 3, opened: 22, closed: 30 },
],
});createRidgeline
Distribution-over-time. Smoothed histogram per row with configurable overlap.
When: Severity-of-warnings over time, cycle time per phase. "Show me the distribution shape, not just the median."
When NOT: 1-2 categories (overlay or small multiples). No natural distribution per row (multi-line or horizon).
PyvoidCharts.createRidgeline(container, {
rows: [
{ label: 'Critical', values: [...] },
{ label: 'High', values: [...] },
],
overlap: 0.3,
});createConnectedScatter
Two co-evolving metrics over time. Path through points sorted by t, with endpoint emphasis and direction arrows.
When: Hours invested vs warnings resolved. Phase progress vs cycle time. "Is the trajectory bending the right way?"
When NOT: A single metric over time (line). Scatter without temporal ordering (regular scatter).
PyvoidCharts.createConnectedScatter(container, {
data: [
{ x: 5, y: 8, t: 0, label: 'M1' },
{ x: 12, y: 22, t: 1, label: 'M2' },
{ x: 78, y: 95, t: 5, label: 'M6' },
],
xLabel: 'Hours invested',
yLabel: 'Warnings resolved',
});DDS v2 archetypes (21 existing)
These ship from the Phase 8a baseline and are still recommended within their data shapes.
createGauge, createBarChart, createDonutChart, createScatterPlot, createSunburst, createLineChart, createProgressBar, createCardGrid
The Phase 5 (DDS v2) factories. createSunburst remains the "classic view" hierarchy navigation; the Phase 9 createTreemap({ hierarchyHeaders: true }) is the alternative, not a replacement.
createVerticalBar, createGroupedBar, createSparkline, createMultiLine, createRadar, createCalendarHeatmap (legacy), createWaterfall, createBullet (single-row), createTreemap, createSankey (legacy)
The Phase 6 archetypes. createBullet and createSankey were extended in Phase 9 (multi-row + hover-path/annotations); createTreemap was extended with hierarchyHeaders. Legacy single-object/manual-layout signatures remain backward-compatible.
createStackedAreaChart, createFunnelChart, createHeatmap, createPayoffTimeline
The Phase 7 (DDS v2) factories. createHeatmap and createStackedAreaChart got Phase 9 redesign: 'v9' paths (sequential palette + endpoint labels respectively). createFunnelChart remains; new authors should pick createSankey or createWaterfall per the funnel-anti-pattern critique.
The eight Phase 9 redesigns (R9-1 through R9-17)
Phase 9 critiqued and redesigned eight existing charts. Each opt-in lives at the call site with redesign: 'v9'. The v1 path stays live during the soak window so any regression falls through.
| ID | Before | After | Status |
|---|---|---|---|
| R9-1, R9-2 | roi-charts.js and team-charts.js donuts | Sorted horizontal bar with target reference | Shipped |
| R9-3 | tier_gauge.js concentric rings | 3-row bullet on shared 0-100% scale | Shipped |
| R9-3-mirror | trend-chart.js 5-line spaghetti | 2x2 small multiples per severity | Shipped |
| R9-4 | warning_funnel.js 6-stage funnel | Sankey of stage-to-stage flow | Shipped |
| R9-5 | roi-charts.js 200px framed sparkline | Inline ≈1em sparkline | Shipped |
| R9-6 | warning_visualizer/health_visualizer sunburst | Treemap with hierarchyHeaders: true (toggle) | Shipped (opt-in) |
| R9-16 | heatmap-renderer.js rainbow palette | Single-hue OKLCH sequential | Shipped (opt-in) |
| R9-17 | cumulative_roi.js stacked-area + legend | Stacked-area + endpoint labels | Shipped |
The Tufte rules behind the gallery
Six core principles, one anti-pattern lint pass, and a 230-line reference at .agent/PATTERNS_CHARTS.md.
- Maximize data-ink. Every pixel that does not encode data is suspect.
- Encode quantity in length, not area, angle, or volume. Length on a common baseline reads quickest.
- Show comparisons, not isolated values. Every chart should compare to a reference.
- Small multiples beat one busy panel. Repeating an archetype across panels with shared scales reads faster than overlay.
- No chartjunk, no 3D, no animation for animation's sake.
- Direct labeling beats legends. A legend forces eye-saccades from chart to legend and back.
The four tufte-* lint rules in scripts/design_system/lint_chart_tokens.py codify the most common violations: tufte-pie-slice-count, tufte-arc-area-encoding, tufte-3d-detection, tufte-legend-overcrowding. As of Phase 7 they fail CI by default; pass --informational to opt into advisory mode.
Recipes, not rules
Pick by data shape, copy the recipe, ship. Every archetype handles theme reactivity, observer cleanup, and v2 kill-switch automatically. The contract for adding a new archetype lives in .agent/PATTERNS_CHARTS.md under "How to Add a New Archetype" - checklist of 10 items.
If your data shape doesn't fit any archetype above, that's a research signal. Open a PRD-level discussion with the data shape, the dashboard surface, and a sketch - don't invent a one-off archetype inline.