chartlet
chartlet compiles a small JSON specification into a finished, accessible chart or software diagram at build time. You get plain SVG, or an HTML figure with caption, source and data table. Nothing runs in the browser: no chart JavaScript, no hydration, no layout shift.
Website and documentation: casoon.github.io/chartlet
- Accessible by default: every chart carries a title and a generated description of the data, and the HTML output always includes the complete data as a table.
- Deterministic: the same specification produces the same bytes, so charts can be reviewed, diffed and cached like any other build artifact.
- Honest about problems: invalid input is rejected with a code, a path and a fix; layout compromises such as shortened labels are reported as warnings instead of happening silently.
- Diagrams laid out for you: sequence, flow, state, architecture, class, tree and network diagrams come from a description of what is connected; chartlet places every box and routes every edge, in portrait or landscape, and lists the whole structure in the data table.
- Models, not only pictures: one architecture model yields several views, deployments per
environment and perspectives such as ownership, and
chartlet inspectpoints out what its documentation leaves open.
Status: before 1.0. Until 1.0, a minor release may still change the specification; a patch release never does. Flow, state and class diagrams, topic maps and knowledge landscapes are experimental: their layout may change between minor releases.
Quick start
Install the CLI from crates.io (Rust 1.88 or newer), or build it from a clone with
cargo install --path .:
Save a minimal specification as spec.json:
Render it, or keep a live preview open while you edit:
Website and documentation
The project website has the documentation, every example
chart with its specification next to the rendered output, and the support matrix for embedding
contexts, browsers and screen readers. It is built with Astro and renders every chart with
@casoon/chartlet at build time; its source lives in site/, the documentation in
docs/.
Chart types
| Chart | Specification | Example |
|---|---|---|
| Bar, vertical | "type": "bar" with data |
monthly-revenue |
| Bar, horizontal, with negative values | "orientation": "horizontal" |
quarterly-change |
| Grouped bar, up to four series | categories and series instead of data |
budget-vs-actual |
| Bars colored by group, one legend entry per group | "group" on every data point |
benefit-and-harm |
| Grouped bars with error bars | lower and upper arrays on each series |
satisfaction-by-quarter |
| Bars with error bars (confidence intervals) | lower and upper on every data point |
satisfaction-scores |
| Small multiples with a shared value axis | "type": "multiples" with titled panes |
emission-pathways |
| Small multiples of bars: one panel per measure over shared categories, each with its own value axis | "type": "multiples" with categories and a values array per pane |
framework-benchmarks |
| Small multiples with a finding under each panel, one column on phones | note and noteEmphasis per pane, "mobile": { "columns": 1 } |
warming-causes |
| Line with gaps for missing values | "type": "line", null values |
monthly-trend |
| Line chart with error bars | lower and upper on every data point of a line chart |
monthly-satisfaction |
| Time series on a calendar axis | "type": "time" with panes and layers |
daily-orders |
| Time series, dark theme, declared colors | "theme": "dark", color per layer |
revenue-vs-forecast |
| Time series with uncertainty band, modeled (hatched, dashed) | lower/upper per point, "modeled": true per layer |
temperature-projection |
| Reference lines: threshold and date marker | "mark": "annotation" with value or time and label |
annual-mean-threshold |
| Time series with area, gaps, line patterns and zoom | "mark": "area", null values, dash, "stroke": "bold", zoomSteps by time |
sensor-readings |
| Stacked areas, named where they end | "stack": "normal" on a pane of area layers, "legend": "end" |
generation-mix |
| Readings as dots around a trend line | "mark": "point" next to a line layer |
resting-heart-rate |
| Zones and point markers | "mark": "band" with from/to or bottom/top; "mark": "annotation" with time, value and shape |
release-incidents |
| Candlesticks with a volume pane on one time axis, weekends closed up | "mark": "ohlc" with data; up to four panes with heightRatio; "gaps": "collapse" |
share-price |
| Warming stripes on a diverging scale | "type": "stripes" with stripes |
warming-stripes |
| Calendar heatmap, by month or by week | "type": "calendar" with calendar |
daily-anomaly-calendar |
| Range bars with central value, modeled hatched | "type": "rangebar" with ranges |
warming-contributions |
| Range bars in groups on a logarithmic axis | group per range, "valueAxis": { "scale": "log" } |
soil-animals |
| Forest plot: squares by weight, a summary diamond, the line of no effect | "type": "rangebar" with weight, summary, references |
trial-effects |
| Timeline: phases, milestones, today's marker and dependencies (Gantt, roadmap) | "type": "timeline" with items, markers |
product-roadmap |
| Waterfall: a total made up of rises, falls and subtotals | "type": "waterfall" with waterfall.steps |
revenue-to-profit |
| Waffle chart: shares of a whole as squares, with the rest | "type": "waffle" with waffle.parts and total |
energy-sources |
| Parliament chart: seats by party, the majority line, a coalition ringed | "type": "parliament" with parliament.parties, majority, coalition |
election-result |
| Nested treemap: sectors and departments | children on treemap.items |
budget-by-sector |
| Treemap: many parts of a whole as rectangles by value, in groups | "type": "treemap" with treemap.items |
budget-by-department |
| Sankey diagram: flows between stages as bands | "type": "sankey" with sankey.links |
energy-flow |
| Sankey diagram with a flow back (recycling) as a loop | "back": true on a Sankey link |
water-cycle |
| Sankey diagram with fixed columns, order and colors (voter movement) | sankey.nodes with column, color, "order": "listed" |
voter-movement |
| Scatter plot: points in groups with threshold lines and names (volcano, Manhattan) | "type": "scatter" with scatter.points, lines |
gene-expression |
| Scatter plot with a regression line and logarithmic axes | "regression": true, "xScale": "log" on a scatter |
allometry |
| Manhattan plot: scatter with named x ticks and fixed ranges | scatter.xTicks, xRange |
genome-scan |
| Impact and effort matrix: scatter in four named quadrants | scatter.quadrants |
feature-priorities |
| Kaplan-Meier curves: survival by group, censoring marks, confidence band, number at risk | "type": "survival" with survival.groups |
overall-survival |
| Box plot from observations, with a box given by its five numbers | "type": "boxplot" with boxes |
response-times |
| Grouped box plots: boxes colored by group with a legend | group on every box |
latency-by-region |
| Violins and strips from the same observations | "boxDisplay": "violin" on a boxplot |
response-distribution |
| Topic map: separate landmasses, area by value (experimental) | "type": "topicmap" with topics |
topicmap-sample |
| Knowledge landscape: one land, position by kinship (experimental) | "type": "atlas" with realms |
knowledge-landscape |
| Mobile variant for narrow containers, any type | "mobile": { "width": 360 } |
mobile-revenue |
Each example has its rendered .svg and .html next to it; the SVG files are also the reference
output of the test suite. In a line chart, null leaves a visible gap instead of a line across a
missing observation; in a grouped bar chart it leaves out the bar, and the data table says
“Missing”. Time series take Unix seconds or ISO 8601 dates on a calendar axis with a fixed time
zone, up to four panes on one time axis and up to six layers per pane. See
Chart types and the time series guide
for every field.
Software diagrams
Seven diagram types describe software and structure rather than data. The specification says only what exists and what is connected; chartlet lays the diagram out — layers along the flow, edges routed around the boxes, the main path kept straight, cycles drawn back against the flow — and writes the same accessible output as for a chart.
| Type | Elements | Examples |
|---|---|---|
sequence |
participants (service, actor, database, queue, external), messages (call, reply, async), activation bars, fragments (alt, opt, loop, par, critical, break), numbered messages |
cache-lookup, async-export, sign-in-sequence |
flow |
steps (process, start, end, decision, io, subprocess, store, external), labelled edges, lanes, groups, a main path; presets for study selection (PRISMA) and chains of courts |
release-flow, order-flow, contract-review, order-fulfilment, study-selection, court-instances |
state |
states, choices, composite and final states, an initial state, transitions event [guard] / action |
ticket-states |
architecture |
components (person, frontend, service, database, queue, storage, cache, security, external, node, artifact), connections with a technology, a direction and an optional step number, nested boundaries; theme: "blueprint" |
shop-architecture, checkout-calls, shop-deployment, tenant-architecture |
classes |
classes, interfaces, abstract classes, enums and entities with attribute and method compartments, relations with their marks, cardinalities and navigability, nested packages | order-model, shop-schema, shop-modules, invoice-model |
tree |
a root and the nodes below it with optional link labels such as shares; couples for family trees; a radial mind map; a dendrogram from a distance matrix | ownership-structure, family-tree, product-strategy, climate-similarity |
network |
nodes as dots on a circle or in rings around a center, groups, weighted and directed edges | co-authors, service-dependencies |
Modeling an architecture:
- Views: one model, several diagrams. A view lists the components it shows, or selects them
by
tags; a dynamic view numbersstepsalong existing connections.--viewrenders one,--all-viewsevery view on one HTML page — shop-views. - Deployments: a view with an
environmentplacesinstancesof components on nestedhosts, with instance connections listed or derived from the model — api-environments, booking-deployment. - Perspectives: stated categories such as the owning team or technical debt, shown as a
colored, named stripe with a legend, per view or with
--perspective— service-perspectives. - Interfaces and symbols: a component
providesinterfaces drawn as balls, a connection withtoInterfacerequires one; 16 genericsymbols mark gateways, caches, brokers and the like — payment-interfaces, shop-symbols. - Levels: the MCP starters
context,containersanddeploymentbegin a model at one level of abstraction — booking-context, booking-containers.
In every diagram that has them:
- Notes explain an element, a frame or the whole diagram without adding an edge (flow, class and architecture) — payment-notes, subscription-model.
- Roles
critical,plannedanddeprecatedlook alike across flow, state, architecture, class, tree and network diagrams, with a sign that does not rest on color — billing-migration, payment-redesign. - Layout hints:
mainPath(as a list or from one step to another),sameRank, the sides an edge leaves and reaches (fromSide,toSide),spacing,weightfor line width andsharedTrunksfor fan-outs on one branching line — data-tier, request-volume, service-fan-out. - Values and parts fill flow and tree boxes by value or with a stripe of shares — department-budgets, claim-handling.
- Portrait or landscape:
orientationruns a diagram down or right;autopicks what fits the canvas, and a mobile variant decides again. Below 480 pixels diagrams turn compact. - Text alternative: the description lists every message, step, transition or component in reading order, and names the shape of the graph where it says more; the table holds the whole structure.
- Focus: in the HTML output, a click on a node or a choice in the "Focus" list keeps the node and its neighbours and fades the rest — without a script.
- Hooks: with
hooks: true, nodes, frames, lanes and architecture connections carry their specification id asdata-chartlet-*attributes, so a page finds them by id, not by position.
See Chart types for every field.
Interaction without JavaScript
The HTML output can add native controls and CSS-based interaction. No chart JavaScript is shipped.
- Series filter: a grouped chart gets a checkbox per series that hides its marks with CSS
:has(); the data table and description always show the full data. - Zoom steps: two to four
zoomStepspre-compute narrower views of the same chart, switched with radio buttons. - Tooltips: every bar, point and diagram element carries a native
<title>; the description and data table remain the assistive-technology alternative.
The pure SVG profile stays a single static chart. For what CSS cannot do, the optional browser
module @casoon/chartlet/interactive adds a crosshair that reads out values by pointer and
keyboard, series toggles on time charts, scroll stations and playback; it reads charts rendered
with hooks: true and lays nothing out again. See Interaction.
Specification
schema/chartlet.schema.json is the complete contract and can be
used for editor validation. Every field, per chart type, and every limit is listed in the
specification reference.
Command line
|
|
Option of render |
Effect |
|---|---|
--format svg|html|png |
Standalone SVG (default); an HTML <figure> with caption, SVG, source and data table, both layouts behind a container query when the specification has mobile; or a PNG (cargo install chartlet --features png, --scale 0.25–4). |
--variant mobile|print|social |
The phone layout alone, literal colors for PDF and print, or a 1200 × 630 Open Graph image. |
--view <key>, --all-views, --perspective <key> |
One view of an architecture model, every view on one HTML page, or a perspective to show. |
--table details|visible |
The HTML data table in a closed <details> (default) or always shown. |
--id-prefix <prefix> |
Stable IDs, for the same chart twice on one page. |
--hooks, --styles external |
data-* hooks for scripts and tests; a chart without its shared stylesheet, which chartlet stylesheet prints once per page. |
--manifest <path> |
A provenance manifest: version, hashes of specification and output, warnings; no timestamp. |
--strict, --diagnostics json |
Fail on any warning; errors and warnings as one JSON document. |
--watch, --preview [--port <port>] |
Render again on every change of the file; also serve the latest chart with its warnings on a local page that reloads. |
chartlet inspect checks an architecture model, a flow chart or a class diagram for gaps in its
documentation without rendering it — unlabelled connections and decision branches, components in
no view, unreachable steps — each with a JSON Pointer and a hint. See
CLI options.
Accessibility
- The SVG is exposed as a single image (
role="img"). Its accessible name is the title, and its description is generated from the data, for example: “Bar chart with 4 categories and 2 series (Budget, Actual). Highest: 160 (Budget in April). Lowest: 120 (Budget in January). 1 value is missing.” - The HTML output adds a
<figure>with caption and a real<table>containing every value, including values whose visual label had to be left out. The caption is the visible title: the SVG inside the figure draws none, so the title is shown and announced once, while the SVG keeps it as its accessible name. - Series colors stay distinguishable for the common forms of color-vision deficiency and have at least 4.5:1 contrast against white — and against the dark theme's background, where the lowest series color reaches 7.65:1. The legend lists series in the same order as the bars.
- Text is never removed silently: shortened labels and omitted value labels produce warnings, and the full text stays in the specification and the data table.
Testing with VoiceOver and NVDA is still pending. Until then, treat the output as designed for accessibility, not as verified.
Warnings and errors
Errors stop rendering and name a code, the location in the specification as a JSON Pointer
(/ for the whole document) and a way to fix it:
chartlet: series_length_mismatch at /series/0/values: expected 2 values, one per category; use null for a missing value
Warnings go to standard error and the chart is still produced: shortened text
(text_truncated), omitted value labels, thinned category labels, dense charts, overlapping
annotation labels, a diagram that needed a larger canvas (canvas_too_small) and more. Every
code is listed in Warnings and errors.
Astro and JavaScript
The npm package @casoon/chartlet renders charts while Astro
builds the site and ships no JavaScript to the browser. It carries the renderer as WebAssembly,
so the package is all you install:
---
import Chart from '@casoon/chartlet/astro';
import revenue from '../data/monthly-revenue.json';
---
<Chart id="monthly-revenue" spec={revenue} />
renderChart and inspectChart do the same outside Astro — in Node.js, Cloudflare Workers and
Vite, see JavaScript runtimes — and @casoon/chartlet/builder
assembles architecture and flow specifications in code, with ids checked at the call. To render
with an installed CLI instead, set CHARTLET_BIN to its path.
AI agents
@casoon/chartlet-mcp is an MCP server for AI assistants:
the assistant inspects the data and drafts a specification, chartlet validates and compiles it.
No model runs in the server and the data is never changed. chartlet_inspect_data suggests the
types that fit a table, chartlet_chart_starter and chartlet_diagram_starter hand over a valid
starting specification per type (for architecture at the context, container or deployment level),
chartlet_validate_spec, chartlet_render, chartlet_explain and chartlet_inspect check,
compile, describe and inspect it. See AI agents.
Rust
use ;
let spec = read_to_string?;
let output = render_json?;
for warning in &output.warnings
write?;
render_with_metrics accepts your own TextMetrics implementation if your pages use a font whose
widths differ noticeably from the built-in profile; inspect_json runs the inspections, and
chartlet::qr draws a QR code as SVG. See Rust.
When chartlet is not the right tool
- You need continuous zooming, panning, cross-filtering or live updates. (A crosshair with values,
series toggles, scroll stations and step-by-step playback come from the optional
@casoon/chartlet/interactivemodule, which only reads the static output.) - The data changes at runtime rather than at build time.
- You need geographic maps, 3D charts or chart types beyond the ones listed above, or diagrams you place by hand, with more than about 40 boxes.
- You want to explore data rather than publish a finished chart.