plot

Deterministic SVG/HTML chart rendering.

plot builds deterministic chart specifications in pure Dune and renders them to SVG or HTML — no external plotting library. It covers the common chart types (line, area, step, scatter, bar, grouped bars, histogram, and pie), multi-series overlays, grids, subplot figures, file output via fs.write_text, and a platform-native display window through show() where the VM supports it.

Chart types

Every builder returns a Chart you refine with chained methods (.title(...), .x_label(...), .grid(...), .legend(true), .size(w, h)) and then render with plot.svg(chart).

Line chart

The default for a trend over time. plot.line(xs, ys) (or plot.line(ys) for index-based x). Chain .grid(...) / .minor_grid(...) for a background mesh.

import plot;

months = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
users  = [120.0, 135.0, 148.0, 172.0, 168.0, 205.0, 233.0, 258.0];

chart = plot.line(months, users)
    .title("Monthly active users").x_label("month").y_label("users")
    .grid(7).minor_grid(2);
Monthly active users 1 8 0 258 Monthly active users month users

Multiple series

Overlay series by chaining add_line / add_scatter / add_bar on one chart. .label(...) names the most recently added series and legend(true) draws the key.

import plot;

chart = plot.line([30.0, 42.0, 39.0, 55.0, 61.0, 74.0]).label("revenue")
    .add_line([22.0, 28.0, 31.0, 36.0, 41.0, 48.0]).label("cost")
    .title("Revenue vs cost").x_label("quarter").y_label("$k")
    .legend(true).grid(6).minor_grid(2);
Revenue vs cost 1 6 0 74 Revenue vs cost quarter $k revenue cost

Area chart

plot.area(...) is a line filled down to the baseline — good for showing a cumulative quantity or emphasising volume under a curve.

import plot;

chart = plot.area([4.0, 9.0, 7.0, 14.0, 20.0, 18.0, 27.0])
    .title("Daily downloads").x_label("day").y_label("thousands")
    .grid(6).minor_grid(2);
Daily downloads 1 7 0 27 Daily downloads day thousands

Step chart

plot.step(...) holds each value constant until the next sample — the right shape for quantities that change in discrete jumps (counts, levels, states).

import plot;

hours    = [0.0, 3.0, 6.0, 9.0, 12.0, 15.0, 18.0, 21.0];
replicas = [2.0, 2.0, 4.0, 8.0, 8.0, 6.0, 3.0, 2.0];

chart = plot.step(hours, replicas)
    .title("Autoscaled replicas").x_label("hour").y_label("pods").grid(7);
Autoscaled replicas 0 21 0 8 Autoscaled replicas hour pods

Scatter plot

plot.scatter(...) draws points instead of a connecting line — use it to show the relationship between two variables.

import plot;

chart = plot.scatter(study_hours, exam_scores)
    .title("Study hours vs score").x_label("hours").y_label("score")
    .grid(6).minor_grid(2);
Study hours vs score 1 6.5 0 92 Study hours vs score hours score

Bar chart

plot.bar(...) for categorical comparisons. Bars are centred on their x value and the x-domain is padded so the outer bars stay inside the axes.

import plot;

chart = plot.bar([1.0, 2.0, 3.0, 4.0, 5.0], [42.0, 58.0, 33.0, 71.0, 25.0])
    .title("Units sold by product").x_label("product").y_label("units").grid(6);
Units sold by product 0.5 5.5 0 71 Units sold by product product units

Grouped bars

Add more than one bar series and they are drawn side by side within each slot, so you can compare categories across groups.

import plot;

qs = [1.0, 2.0, 3.0, 4.0];

chart = plot.bar(qs, [18.0, 24.0, 21.0, 30.0]).label("2024")
    .add_bar(qs, [22.0, 20.0, 27.0, 34.0]).label("2025")
    .title("Quarterly sales").x_label("quarter").y_label("units")
    .legend(true).grid(6);
Quarterly sales 0.5 4.5 0 34 Quarterly sales quarter units 2024 2025

Histogram

plot.histogram(values, bins) buckets raw samples into equal-width bars to show a distribution.

import plot;

samples = [ /* 30 response times in ms */ ];

chart = plot.histogram(samples, 8)
    .title("Response-time distribution").x_label("ms").y_label("count").grid(6);
Response-time distribution 12 30 0 8 Response-time distribution ms count

Pie chart

plot.pie(values) — or plot.pie(values, labels) — shows parts of a whole. Each wedge is sized by its share of the total and labelled with its percentage; pass labels and legend(true) to name the categories.

import plot;

chart = plot.pie([35.0, 25.0, 20.0, 15.0, 5.0],
    ["Rent", "Food", "Transport", "Savings", "Other"])
    .title("Monthly budget").legend(true);
Monthly budget 35% 25% 20% 15% 5% Monthly budget Rent Food Transport Savings Other

Subplots: one figure, many charts

plot.subplots(charts, cols) composes several charts into a single figure laid out as a grid of cols columns. Each chart keeps its own type, title, grid, and axes and is scaled into an equal cell. The result is a normal SVG, so it works with every output path — save_subplots_svg / save_subplots_html to a file, or show_subplots_native to open it in the native window.

import plot;

users    = [120.0, 135.0, 148.0, 172.0, 168.0, 205.0, 233.0, 258.0];
visits   = [4.0, 9.0, 7.0, 14.0, 20.0, 18.0, 27.0];
sold     = [42.0, 58.0, 33.0, 71.0, 25.0];
replicas = [2.0, 2.0, 4.0, 8.0, 8.0, 6.0, 3.0, 2.0];

figure = plot.subplots([
    plot.line(users).title("line").grid(4).size(300, 200),
    plot.area(visits).title("area").grid(4).size(300, 200),
    plot.scatter(study_hours, exam_scores).title("scatter").grid(4).size(300, 200),
    plot.bar(sold).title("bar").grid(4).size(300, 200),
    plot.step(replicas).title("step").grid(4).size(300, 200),
    plot.pie([35.0, 25.0, 20.0, 15.0, 5.0]).title("pie").size(300, 200),
], 3); // 3 columns -> a 3x2 grid, one SVG
line 1 8 0 258 line area 1 7 0 27 area scatter 1 6.5 0 92 scatter bar 0.5 5.5 0 71 bar step 1 8 0 8 step pie 35% 25% 20% 15% 5% pie

Auto-generated from stdlib/plot.dn by tools/gen_stdlib_docs.py.

record Chart

Methods:

  • static fn empty(): Chart
  • fn title(value: text): Chart — Return a copy of the chart with its title set. — e.g. plot.svg(plot.line([1.0, 2.0]).title("Sales")).contains("Sales") // 1
  • fn x_label(value: text): Chart — Return a copy with the x-axis label set. — e.g. plot.line([1.0, 2.0]).x_label("time")
  • fn y_label(value: text): Chart — Return a copy with the y-axis label set. — e.g. plot.line([1.0, 2.0]).y_label("value")
  • fn legend(enabled: bool): Chart — Return a copy with the legend shown or hidden. — e.g. plot.line([1.0, 2.0]).label("trend").legend(true)
  • fn size(width: int, height: int): Chart — Return a copy sized to width x height pixels (both must be positive). — e.g. plot.svg(plot.line([1.0, 2.0]).size(640, 480)).contains("width=\"640\"") // 1
  • fn grid(cells: int): Chart — Return a copy with a background grid of cells major divisions per axis — e.g. plot.svg(plot.line([1.0, 2.0]).grid(5)).contains("plot-grid") // 1
  • fn minor_grid(subdivisions: int): Chart — Return a copy that also draws subdivisions minor grid lines inside each — e.g. plot.svg(plot.line([1.0, 2.0]).grid(4).minor_grid(5)).contains("plot-grid-minor") // 1
  • fn label(value: text): Chart
  • fn add_line(xs: [real64], ys: [real64]): Chart
  • fn add_line(ys: [real64]): Chart
  • fn add_scatter(xs: [real64], ys: [real64]): Chart
  • fn add_scatter(ys: [real64]): Chart
  • fn add_bar(xs: [real64], ys: [real64]): Chart
  • fn add_bar(ys: [real64]): Chart
  • fn add_area(xs: [real64], ys: [real64]): Chart
  • fn add_area(ys: [real64]): Chart
  • fn add_step(xs: [real64], ys: [real64]): Chart
  • fn add_step(ys: [real64]): Chart

fn empty(): Chart

fn line(xs: [real64], ys: [real64]): Chart

A line chart from paired x/y data.

Example:

plot.svg(plot.line([0.0, 1.0, 2.0], [3.0, 7.0, 5.0])).contains("<svg")  // 1

fn line(ys: [real64]): Chart

A line chart from y values, with x taken as the index 0, 1, 2, ...

Example:

plot.svg(plot.line([1.0, 3.0, 2.0])).contains("<svg")  // 1

fn scatter(xs: [real64], ys: [real64]): Chart

A scatter chart from paired x/y data.

Example:

plot.svg(plot.scatter([1.0, 2.0], [3.0, 4.0])).contains("<circle")  // 1

fn scatter(ys: [real64]): Chart

A scatter chart from y values against their index.

Example:

plot.scatter([3.0, 1.0, 4.0])

fn bar(xs: [real64], ys: [real64]): Chart

A bar chart from paired x/y data.

Example:

plot.svg(plot.bar([1.0, 2.0], [3.0, 5.0])).contains("<rect")  // 1

fn bar(ys: [real64]): Chart

A bar chart from y values against their index.

Example:

plot.svg(plot.bar([3.0, 1.0, 2.0])).contains("<rect")  // 1

fn area(xs: [real64], ys: [real64]): Chart

An area chart: a line filled down to the baseline.

Example:

plot.svg(plot.area([0.0, 1.0, 2.0], [3.0, 7.0, 5.0])).contains("<polygon")  // 1

fn area(ys: [real64]): Chart

An area chart from y values against their index.

Example:

plot.svg(plot.area([3.0, 1.0, 2.0])).contains("<polygon")  // 1

fn step(xs: [real64], ys: [real64]): Chart

A step chart: values held constant between samples (like a staircase).

Example:

plot.svg(plot.step([0.0, 1.0, 2.0], [3.0, 7.0, 5.0])).contains("<polyline")  // 1

fn step(ys: [real64]): Chart

A step chart from y values against their index.

Example:

plot.svg(plot.step([3.0, 1.0, 2.0])).contains("<polyline")  // 1

fn pie(values: [real64]): Chart

A pie chart. Each value becomes a wedge sized by its share of the total and coloured from the palette; wedges are labelled with their percentage.

Example:

plot.svg(plot.pie([3.0, 5.0, 2.0])).contains("<path")  // 1

fn pie(values: [real64], labels: [text]): Chart

A pie chart with a category name for each wedge (shown in the legend).

Example:

plot.svg(plot.pie([3.0, 5.0], ["a", "b"]).legend(true)).contains("<path")  // 1

fn histogram(values: [real64], bins: int): Chart

A histogram: bucket values into bins equal-width bars.

Example:

plot.svg(plot.histogram([1.0, 2.0, 2.0, 3.0], 3)).contains("<svg")  // 1

fn histogram(data: stats.Histogram): Chart

Build a bar chart from a validated stats.Histogram. This preserves custom bin edges and lets data workflows compute/count once in stats, then render the same result in scripts and notebooks.

Example:

plot.svg(plot.histogram(stats.histogram([1.0, 2.0, 3.0], 2).value_or(stats.empty_histogram()))).contains("<svg")  // 1

fn use_backend(name: text): unit

Select the display backend for show. Supported MVP backends are:

  • "none": return a clear unsupported/headless error;
  • "svg": return the deterministic SVG text;
  • "html": return a deterministic HTML wrapper containing the SVG.
  • "native": open a platform-native plot window when the VM supports it.

fn backend(): text

The active backend used by show. Reads DUNE_PLOT_BACKEND if set, otherwise the value last passed to use_backend.

Example:

plot.use_backend("svg"); plot.backend()  // "svg"

fn available_backends(): [text]

The backend names show understands.

Example:

plot.available_backends().len()  // 4

fn svg(chart: Chart): text

Render chart to deterministic standalone SVG text.

Example:

plot.svg(plot.line([1.0, 2.0])).starts_with("<svg")  // 1

fn subplots(charts: [Chart], cols: int): text

Arrange several charts into one SVG as a grid of cols columns (rows are filled left-to-right, top-to-bottom). Each chart keeps its own size and is scaled into an equal cell taken from the first chart's dimensions.

Example:

plot.subplots([plot.line([1.0, 2.0]), plot.bar([3.0, 1.0])], 2).contains("<svg")  // 1

fn html(chart: Chart): text

Render chart to a deterministic standalone HTML document wrapping the SVG.

Example:

plot.html(plot.line([1.0, 2.0])).contains("<!doctype")  // 1

fn save_svg(chart: Chart, path: text): outcome.Outcome<text, text>

Write chart as SVG to path, returning an Outcome for the write.

Example:

plot.save_svg(plot.line([1.0, 2.0]), "chart.svg").is_done()  // 1

fn save_html(chart: Chart, path: text): outcome.Outcome<text, text>

Write chart as an HTML document to path, returning an Outcome.

Example:

plot.save_html(plot.line([1.0, 2.0]), "chart.html").is_done()  // 1

fn show_native(chart: Chart): outcome.Outcome<text, text>

Open chart in a native plot window where the VM supports it; returns an Outcome describing whether the window opened (a headless-safe error if not).

fn subplots_html(charts: [Chart], cols: int): text

Wrap a grid of charts in a standalone HTML document.

Example:

plot.subplots_html([plot.line([1.0, 2.0])], 1).contains("<!doctype")  // 1

fn save_subplots_svg(charts: [Chart], cols: int, path: text): outcome.Outcome<text, text>

Write a grid of charts as SVG to path.

Example:

plot.save_subplots_svg([plot.line([1.0, 2.0]), plot.bar([2.0, 1.0])], 2, "grid.svg").is_done()  // 1

fn save_subplots_html(charts: [Chart], cols: int, path: text): outcome.Outcome<text, text>

Write a grid of charts as an HTML document to path.

Example:

plot.save_subplots_html([plot.line([1.0, 2.0])], 1, "grid.html").is_done()  // 1

fn show_subplots_native(charts: [Chart], cols: int): outcome.Outcome<text, text>

Open a grid of charts in the native plot window where the VM supports it. The window renders the composed SVG, so subplots display exactly as they save.

fn show(chart: Chart): outcome.Outcome<text, text>