Skip to content
BetaPrivate beta - free access.Join the waitlist
Pyvoid
Article· Updated June 15, 2026·9 min read

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.

By Tyler Putnam|
referencepyvoiddocumentation

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 shapeArchetypeWhen NOT
Single KPI in a table cellcreateInlineSparklineFrame it; that defeats the point
Target vs actual single valuecreateBulletLess than 3 categories - use one number
Target vs actual, 2-8 entitiescreateBullet (multi-row)More than 12 entities - use dot plot
3-7 categories rankedcreateSortedBarMore than 12 - use Cleveland dot plot
8+ categories rankedcreateDotPlotTime-series data - use sparkline or line
Pre/post by categorycreateSlopeGraphMore than ~10 rows - labels saccade
Single time-series (small)createSparklineCard-body sparkline - that's createInlineSparkline
2-3 time-series, same scalecreateMultiLineDifferent scales - use small multiples
2 time-series, different scalescreateSmallMultiplesGridDual-axis - never
5 series of same shapecreateSmallMultiplesGrid (2x3)More than 12 - use horizon
20+ series, shared scalecreateHorizonLess than 6 - overlay or small multiples
Opened-vs-closed trajectorycreateBurndownSingle-series count - sparkline
Distribution over timecreateRidgeline1-2 categories - overlay or small multiples
Day-of-week activitycreateCalendarHeatmapAggregate counts - bar chart
Continuous matrix (rows x cols)createHeatmap (redesign: 'v9')Rainbow palette - that's the anti-pattern
Two-metric trajectorycreateConnectedScatterOne metric - line chart
Sequential flow with branchescreateSankeyLinear delta - use waterfall
Linear delta (in/out flows)createWaterfallBranched flow - sankey
Hierarchical part-of-wholecreateTreemapNeed to navigate hierarchy - hierarchyHeaders: true
Hierarchy navigationcreateSunburst OR createTreemap w/ headersPast 2 levels - use indented bars
Stacked counts over timecreateStackedAreaChart (redesign: 'v9')Reading individual series - small multiples
Multi-dimensional comparisoncreateRadarMore than 7 dimensions - Cleveland dot plot
Single-value KPI cardcreateGaugeTier utilization - bullet chart
Funnel with stagescreateFunnelChartWidth-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',
});

Source · PRD §4.5.5

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.

IDBeforeAfterStatus
R9-1, R9-2roi-charts.js and team-charts.js donutsSorted horizontal bar with target referenceShipped
R9-3tier_gauge.js concentric rings3-row bullet on shared 0-100% scaleShipped
R9-3-mirrortrend-chart.js 5-line spaghetti2x2 small multiples per severityShipped
R9-4warning_funnel.js 6-stage funnelSankey of stage-to-stage flowShipped
R9-5roi-charts.js 200px framed sparklineInline ≈1em sparklineShipped
R9-6warning_visualizer/health_visualizer sunburstTreemap with hierarchyHeaders: true (toggle)Shipped (opt-in)
R9-16heatmap-renderer.js rainbow paletteSingle-hue OKLCH sequentialShipped (opt-in)
R9-17cumulative_roi.js stacked-area + legendStacked-area + endpoint labelsShipped

Six core principles, one anti-pattern lint pass, and a 230-line reference at .agent/PATTERNS_CHARTS.md.

  1. Maximize data-ink. Every pixel that does not encode data is suspect.
  2. Encode quantity in length, not area, angle, or volume. Length on a common baseline reads quickest.
  3. Show comparisons, not isolated values. Every chart should compare to a reference.
  4. Small multiples beat one busy panel. Repeating an archetype across panels with shared scales reads faster than overlay.
  5. No chartjunk, no 3D, no animation for animation's sake.
  6. 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.

The Pyvoid Chart Gallery: 31 Tufte-Informed Archetypes | Pyvoid