p

primaviz

@preview

A charting library with 50+ chart types, 7 themes, and zero dependencies — built entirely with native primitives

v0.12.1
MIT

Primaviz

License: MIT Typst version 0.13.0 or higher 50+ chart types Pure Typst

A charting library for Typst built entirely with native primitives (rect, circle, line, polygon, place). No external dependencies required.

All 50+ chart types across 8 pages — see examples/showcase.typ for the source:

Showcase: bar, horizontal bar, grouped bar, stacked bar, line, multi-line, area, and stacked area charts Showcase: pie, donut, radar, scatter, multi-scatter, bubble, gauge, progress-bar, and circular-progress charts Showcase: ring progress, dual-axis, histogram, waterfall, funnel, box-plot, heatmap, and correlation-matrix charts Showcase: calendar heatmap, progress bars, sparklines, treemap, sankey, lollipop, horizontal lollipop, and diverging bar charts Showcase: slope, bullet, grouped-stacked bar, gantt, waffle, bump, dumbbell, and radial-bar charts Showcase: sunburst, metric cards, violin plot, timeline, parliament, chord diagram, and dashboard components (card, table, alert, badge) Showcase: golden-ratio φ-scaling demo with bar and line charts at 0.5×, 1×, and 2× base size Showcase: full-page word cloud of data visualization vocabulary

Examples

File Description
examples/demos/ 21 per-chart demo files, each a 2×2 grid (light/dark + variations)
examples/showcase.typ Compact 8-page showcase of all chart types (dark theme)
examples/demo.typ Comprehensive demo with all features, themes, and data loading

Shared datasets in data/ used by both demo and showcase:

typst compile --root . examples/showcase.typ          # Compile the showcase
typst compile --root . examples/demo.typ              # Compile the comprehensive demo
typst compile --root . examples/demos/demo-bar.typ    # Compile one per-chart demo

Features

  • 50+ chart types for data visualization
  • JSON data input — load data directly from JSON files
  • Theme system — golden-ratio proportional scaling from two seeds (base-size, base-gap), preset themes, custom overrides, and with-theme() for document-wide defaults
  • Nice ticks — D3-style axis tick algorithm produces round, evenly-spaced values; auto-detects decimal precision from step size
  • Smart label placement — automatic fit detection, font shrinking, and spreading of overlapping labels
  • Layout primitives — shared utilities for label density, font scaling, and label placement
  • Annotations — overlay reference lines, bands, labels, content, points, error bars, and rectangles on any Cartesian chart
  • Error bars — native errors: parameter on bar and line charts (symmetric or asymmetric)
  • Outliers — native outliers: field on box-plot boxes
  • Line interpolation — line-interpolation: ("linear", "smooth", "catmull-rom") and smooth-radius: (0 to 5, default 0: a "smooth" curve passes through the data points; 1 to 5 draw a moving-average trend; markers, values and error bars always sit on the data) on line and multi-line charts
  • Relative widths — use width: 100% for responsive charts inside containers and grids
  • Dashboard primitives — card, compact-table, alert, badge, separator, and dashboard-layout for report layouts
  • Customizable — colors, sizes, labels, legends
  • Pure Typst — no packages or external tools needed

Chart Types

Bar Charts

  • bar-chart - Vertical bar chart
  • horizontal-bar-chart - Horizontal bar chart
  • grouped-bar-chart - Side-by-side grouped bars
  • stacked-bar-chart - Stacked bar segments
  • grouped-stacked-bar-chart - Groups of stacked segments side by side
  • lollipop-chart - Vertical stem + dot (cleaner bar alternative)
  • horizontal-lollipop-chart - Horizontal stem + dot
  • diverging-bar-chart - Left/right bars from center axis

Line & Area Charts

  • line-chart - Single line with points
  • multi-line-chart - Multiple series comparison
  • dual-axis-chart - Two independent Y-axes
  • area-chart - Filled area under line
  • stacked-area-chart - Stacked area series

Circular Charts

  • pie-chart - Pie chart with legend
  • pie-chart (donut mode) - Donut/ring chart
  • radar-chart - Spider/radar chart

Scatter & Bubble

  • scatter-plot - X/Y point plotting
  • multi-scatter-plot - Multi-series scatter
  • bubble-chart - Scatter with size dimension, smart label deconfliction (inside/outside with leaders)
  • multi-bubble-chart - Multi-series bubble chart

Gauges & Progress

  • gauge-chart - Semi-circular dial gauge
  • progress-bar - Horizontal progress bar
  • circular-progress - Ring progress indicator
  • ring-progress - Concentric fitness rings (Apple Watch style)
  • progress-bars - Multiple comparison bars, one colour by default (color: auto gives each bar its own palette colour)

Sparklines (inline)

  • sparkline - Tiny line chart for tables and text
  • sparkbar - Tiny bar chart
  • sparkdot - Tiny dot chart

Heatmaps

  • heatmap - Grid heatmap with color scale
  • calendar-heatmap - GitHub-style activity grid
  • correlation-matrix - Symmetric correlation display

Statistical

  • histogram - Auto-binned frequency distribution
  • waterfall-chart - Bridge/waterfall chart with pos/neg/total segments
  • funnel-chart - Conversion funnel with percentages, auto external labels for narrow sections (one colour; color-by-stage: true for a colour per stage)
  • box-plot - Box-and-whisker distribution plot
  • treemap - Nested rectangles for hierarchical data
  • slope-chart - Two-period comparison with connecting lines
  • bullet-chart - Compact gauge with qualitative ranges and target
  • bullet-charts - Multiple bullet charts stacked vertically

Proportional & Hierarchical

  • waffle-chart - 10×10 grid of colored squares for proportions (category names sit beside their squares; a legend when they do not fit)
  • sunburst-chart - Multi-level hierarchical pie with nested rings
  • parliament-chart - Semicircle dot layout for seat visualization; each party takes one wedge (fill-order: "rows" fills the concentric rows instead)

Comparison & Ranking

  • bump-chart - Multi-period ranking chart
  • dumbbell-chart - Before/after dot comparisons with connecting lines
  • radial-bar-chart - Circular bars radiating from center

Distribution

  • violin-plot - Kernel density estimation with mirrored polygon

Flow & Timeline

  • sankey-chart - Flow diagram with curved bands between nodes
  • gantt-chart - Timeline bar chart for project scheduling (one colour, or one per group; color-by-task: true for a colour per task)
  • timeline-chart - Vertical event timeline with alternating layout
  • chord-diagram - Circular flow diagram with chord bands

Dashboard

  • metric-card - KPI tile with value, delta, and sparkline
  • metric-row - Horizontal row of metric cards
  • card - Themed container with optional title and description
  • compact-table - Dense data table with header styling and highlight column
  • alert - Info/warning/error/success notification block with left border accent
  • badge - Inline colored pill (default/secondary/destructive/outline/success)
  • separator - Themed horizontal rule
  • dashboard-layout - Grid layout helper for multi-row dashboard pages
  • word-cloud - Weighted text layout sized by importance; shrinks to keep every word, one hue pale to full by weight (color: auto gives each word its own palette colour)

Annotations

Overlay extra elements on any cartesian chart (bar, line, area, scatter, bubble, box-plot, histogram, violin, waterfall, dual-axis, ...) via the annotations: parameter. All coordinates are in data space.

  • h-line — horizontal reference line (target, average, threshold)
  • v-line — vertical reference line
  • h-band — horizontal shaded region (goal zone, range)
  • label — text label at a data point
  • content — place arbitrary Typst content at (x, y); anchor: controls positioning (top-left, top, center, bottom-right, etc.)
  • point — circle marker at (x, y) with configurable radius, fill, stroke
  • errorbar — vertical (default) or horizontal (orientation: "h") error bar with caps
  • rect — rectangle in data coordinates with fill, stroke, opacity
#bar-chart(data, annotations: (
  (type: "h-line", value: 160, dash: "dashed", label: "target"),
  (type: "content", x: 2, y: 165, body: [★ best], anchor: "bottom"),
  (type: "errorbar", x: 0, low: 115, high: 125),
))

Error bars (native)

Bar and line charts accept an errors: parameter. Axis range auto-extends to cover the error extremes.

#bar-chart(data, errors: (10, 12, 8, 15))                       // symmetric
#line-chart(data, errors: ((0.5, 0.7), (0.3, 0.4), (1, 2)))     // asymmetric (low, high)

Box-plot outliers

Each box can carry an optional outliers: array. Outlier values are drawn as small dots and extend the y-axis range.

#box-plot((
  labels: ("A", "B"),
  boxes: (
    (min: 20, q1: 35, median: 50, q3: 65, max: 80, outliers: (95, 5)),
    (min: 15, q1: 30, median: 45, q3: 60, max: 75),
  ),
))

Installation

#import "@preview/primaviz:0.12.1": *

Usage

#import "@preview/primaviz:0.12.1": *

// Load data from JSON
#let data = json("mydata.json")

// Create a bar chart
#bar-chart(
  (
    labels: ("A", "B", "C", "D"),
    values: (25, 40, 30, 45),
  ),
  width: 300pt,
  height: 200pt,
  title: "My Chart",
)

// Create a pie chart
#pie-chart(
  (
    labels: ("Red", "Blue", "Green"),
    values: (30, 45, 25),
  ),
  size: 150pt,
  donut: true,
)

// Create a radar chart
#radar-chart(
  (
    labels: ("STR", "DEX", "CON", "INT", "WIS", "CHA"),
    series: (
      (name: "Fighter", values: (18, 12, 16, 10, 13, 8)),
      (name: "Wizard", values: (8, 14, 12, 18, 15, 11)),
    ),
  ),
  size: 200pt,
  title: "Character Comparison",
)

Theming

Every chart function accepts an optional theme parameter. Themes control colors, font sizes, grid lines, backgrounds, and other visual properties.

Using a preset theme

#import "@preview/primaviz:0.12.1": *

#bar-chart(data, theme: themes.dark)

Document-wide theme

Use with-theme() to set a default theme for all charts in a block — no need to pass theme: to every chart. Explicit per-chart theme: parameters still override.

// Block wrapper
#with-theme(themes.dark)[
  #bar-chart(data)       // uses dark theme
  #pie-chart(data2)      // uses dark theme
  #line-chart(data3, theme: themes.minimal) // explicit override wins
]

// Or as a show rule for the entire document
#show: with-theme.with(themes.dark)

Scaling with seeds

All font sizes and spacing are derived from two seed values via golden-ratio (φ = 1.618) powers. Change the seeds to scale everything proportionally:

#bar-chart(data, theme: (base-size: 10pt, base-gap: 8pt))  // larger text and spacing
#bar-chart(data, theme: (base-size: 5pt, base-gap: 3pt))   // compact

Custom overrides

Pass a dictionary with only the keys you want to change. Unspecified keys fall back to the active theme (global or default). Partial overrides merge onto the global theme set by with-theme(), so theme: (show-grid: true) inside a with-theme(themes.dark) block gives you dark + grid:

#bar-chart(data, theme: (show-grid: true, palette: (red, blue, green)))
#bar-chart(data, theme: (tick-digits: 2))  // force 2 decimal places on axis ticks
#bar-chart(data, theme: (category-label-min-size: 7pt))  // smallest type for crowded category labels (6pt, or 5pt on horizontal bar rows, unless set)

Category labels are all kept whenever that is possible at a legible size: flat, else tilted 45 degrees or upright (whichever keeps the larger type, down to category-label-min-size), and only then thinned evenly (first and last always shown). Horizontal bar and lollipop charts grow their rows with height: auto; with a fixed height they shrink the row labels to the floor before thinning.

Theme from JSON

Build a theme from a JSON tokens file (e.g., exported from a CSS design system):

#let tokens = json("tokens.json")
#let my-theme = theme-from-json(tokens.light)
#let my-dark = theme-from-json(tokens.dark)

#show: with-theme.with(my-theme)

Expected JSON format: palette (array of hex strings), text-color, text-color-light, text-color-inverse, background (hex or null), border-color, border-radius (number in pt).

Extracting themes from CSS

A pair of helper scripts (Python and TypeScript) convert CSS design-token files (Tailwind, shadcn, etc.) into primaviz themes. They live in the repo but are not shipped with the package — see CONTRIBUTING.md for usage.

Custom theme keys

Themes support passthrough of custom keys not in the default theme. This lets you extend the theme system for your own components:

#let my-theme = (
  palette: (red, blue, green),
  card-fill: rgb("#f5f5f5"),  // custom key — preserved and accessible
)
#show: with-theme.with(my-theme)

Available presets

Preset Description
themes.default Tableau 10 color palette, no grid, standard font sizes
themes.minimal Lighter axis strokes, grid enabled, regular-weight titles
themes.dark Dark background (#1a1a2e), vibrant neon palette (cyan, pink, purple, ...)
themes.presentation Larger font sizes across the board for slides and projectors
themes.print Grayscale palette with grid lines, optimized for black-and-white printing
themes.accessible Okabe-Ito colorblind-safe palette
themes.compact Smaller fonts, tighter padding for dense dashboard layouts

Data Formats

Simple data (labels + values)

(
  labels: ("Jan", "Feb", "Mar"),
  values: (100, 150, 120),
)

Multi-series data

(
  labels: ("Q1", "Q2", "Q3"),
  series: (
    (name: "Product A", values: (100, 120, 140)),
    (name: "Product B", values: (80, 90, 110)),
  ),
)

Content labels

Anywhere a label or series name is accepted, content works as well as a string — math, markup, or both:

#stacked-bar-chart((
  labels: ($alpha$, $beta$),
  series: (
    (name: $x$, values: (90, 0)),
    (name: [*y*], values: (10, 100)),
  ),
))

Scatter/bubble data

(
  x: (1, 2, 3, 4, 5),
  y: (10, 25, 15, 30, 20),
  size: (5, 10, 8, 15, 12),  // for bubble chart
)

Heatmap data

(
  rows: ("Row1", "Row2", "Row3"),
  cols: ("Col1", "Col2", "Col3"),
  values: (
    (1, 2, 3),
    (4, 5, 6),
    (7, 8, 9),
  ),
)

Color Palette

The default theme uses Tableau 10 colors. You can access colors from any theme via the get-color function:

#import "@preview/primaviz:0.12.1": get-color, themes

// Default palette
#get-color(themes.default, 0)  // blue
#get-color(themes.default, 1)  // orange
#get-color(themes.default, 2)  // red

// Or use a theme preset
#get-color(themes.dark, 0)  // cyan

Contributing

For local dev setup, project structure, and helper scripts, see CONTRIBUTING.md.

License

MIT

Version History

0.12.10.13.0
620de0a4d739...
0.12.00.13.0
451429decf96...
0.11.00.13.0
df6907395333...
0.10.00.13.0
6f60b6d7b380...
0.9.10.13.0
786c736b02cd...
0.9.00.13.0
d3f3079338eb...
0.8.00.13.0
1eb1dbdca391...
0.7.00.13.0
388c15a39809...
0.6.00.12.0
b8d823676b99...
0.5.30.12.0
a313c05b9887...
0.4.10.12.0
7e31af14e8e3...
0.1.10.12.0
08ce04fb6130...