Skip to main content

umber

Coverage

umber is a plotting library for Python with a rendering core written in Rust. It draws line, step, scatter, area, stacked area, bar, histogram, ECDF, KDE, box, violin, strip, swarm, event, bubble, error bar, band, stem, reference-line, heatmap, mesh, contour, 2-D histogram, hexbin and pie charts on numeric, log, category and time axes, alone or on a grid of panels with shared axes, with a second y axis where needed. It has text and arrow annotations, colormaps and colorbars, careful typography with bundled or your own fonts, three built-in themes, and PNG, SVG or PDF output.

简体中文

Twelve kinds of chart drawn by umber on one grid: lines and bands, grouped bars, points colored by value, a donut, a histogram with a density curve, violins with swarms, contours, a heatmap, a time series with an annotation, two y axes, a hexbin and grouped ECDFs

  • Fast on large data. Rendering runs in Rust: a 1,000,000-point line renders to PNG in about 77 ms and to PDF in about 36 ms on a standard GitHub Actions runner, 2.7× and 4.5× faster than matplotlib (see Performance).
  • Explicit. Every option is a typed value (Color, Size, Dash, TickFormat, …). There are no format strings, no positional style arguments and no silent conversions.
  • Fails fast, pointing at the problem. Invalid input raises at the call that caused it, with a message, a hint and the offending argument highlighted in the traceback.
  • Statically typed. Category and numeric axes are distinct types: calling bar on a numeric axes is reported by pyright and mypy before the code runs.
  • Fonts that travel. Text in the bundled fonts' coverage looks the same on every machine, and PDF output embeds the fonts it uses. Font files and installed families can be used too.
  • Ready for AI coding agents. The package ships an Agent Skill that teaches coding agents the umber API of the installed version (see Agent Skill).

Installation

pip install umber

umber requires Python 3.12 or newer and NumPy 2. Prebuilt wheels are published for Linux (x86_64 and aarch64), macOS (Apple silicon and Intel) and Windows (x64); free-threaded Python 3.14 and 3.15 or newer have wheels of their own. On other platforms pip builds from source, which needs Rust 1.88 or newer.

Quick start

import numpy as np
import umber as ub

t = np.linspace(0, 10, 1_000)
ub.line(t, np.sin(t), label="sin").title("A first chart").figure.save_png("chart.png")

The quick functions ub.line, ub.scatter, ub.bar, ub.hist and ub.pie create a figure with one axes and return the axes, so the rest of the chart is built by chaining.

Building a figure

import numpy as np
import umber as ub

rng = np.random.default_rng(7)
days = np.arange(60)
north = 20 + np.cumsum(rng.normal(0, 1, 60))
south = 18 + np.cumsum(rng.normal(0, 1, 60))

fig = ub.Figure(6, 4, theme=ub.Theme.GRAPHITE)  # width and height in inches
ax = fig.add_axes()
ax.title("Daily temperature").subtitle("Two stations, last 60 days")
ax.xlabel("Day").ylabel("°C")
ax.line(days, north, label="North")
ax.line(days, south, label="South", dash=ub.Dash.DASHED)

fig.save_png("temperature.png", dpi=200)
fig.save_svg("temperature.svg")
fig.save_pdf("temperature.pdf")

A line chart of daily temperature at two stations, in the Graphite theme

Data can be a list, a tuple, a NumPy array, or a pandas or polars Series. Missing values (NaN, or nulls in nullable pandas and polars columns) leave a gap.

Chart Method
Line ax.line(x, y)
Scatter ax.scatter(x, y, marker=ub.Marker.TRIANGLE, size=ub.Size.times(1.5))
Area ax.area(x, y)
Bar, grouped, stacked ax.bar(categories, heights), ax.grouped_bar(categories, series), ax.stacked_bar(categories, series); annotate=True writes the values on the bars
Histogram ax.hist(values, bins=30); style=ub.Hist.STEP, stat=ub.Stat.DENSITY or ub.Stat.FRACTION, cumulative=True
ECDF, KDE ax.ecdf(values), ax.kde(values)
Step ax.step(x, y, where=ub.Step.MID)
Stacked area ax.stacked_area(x, [ub.Series("A", a), ub.Series("B", b)])
Box, grouped ax.box(categories, groups), ax.grouped_box(categories, [ub.Distribution("A", groups)])
Violin, grouped ax.violin(categories, groups), ax.grouped_violin(categories, distributions)
Strip, swarm ax.strip(categories, groups), ax.swarm(categories, groups)
Events ax.events(rows, positions)
Bubbles ax.bubbles(x, y, sizes, label="Cities") (adds a size legend)
Error bars ax.y_error_bars(x, low, high), ax.x_error_bars(y, low, high)
Band ax.band(x, low, high)
Stem ax.stem(x, y)
Reference lines and spans ax.hline(y), ax.vline(x), ax.hspan(low, high), ax.vspan(low, high)
Heatmap ax.heatmap(x_categories, y_categories, values, annotate=True)
Mesh ax.mesh(x_edges, y_edges, values)
Contour ax.contour(x, y, z, style=ub.Contour.FILLED); Contour.LINES (default) or Contour.BOTH
2-D histogram, hexbin ax.hist2d(x, y, bins=40), ax.hexbin(x, y, gridsize=30)
Color-mapped points ax.scatter(x, y, color=ub.ColorBy(values))
Pie, donut ub.pie(categories, values, hole=0.5, annotate=True) or fig.add_pie_axes().pie(...)
Text, annotations ax.text("Note", (x, y)), ax.annotate("Peak", (x, y), offset=(24, 12))

Bar, box, violin, strip and swarm charts need a category axis. On a category x axis, fig.add_axes(x=ub.Scale.category()), they are vertical; on a category y axis, fig.add_axes(y=ub.Scale.category()), they are horizontal, and a scatter of values against categories is a dot plot. Events are drawn in rows on a category y axis. A heatmap needs category axes on both sides. Automatic category labels start at 0 and use the smallest step in 1, 2, 5, 10, 20, 50, ... that keeps neighbouring labels apart by the same minimum gap as numeric ticks; shared axes show the same labels. Category 0 is always labelled, so a first label wider than the available room still causes a tick-label error. Log axes use ub.Scale.log() and time axes ub.Scale.time(). Limits, ticks and tick label formats are set with xlim, ylim, xticks, xticks_labeled, ytick_format(ub.TickFormat.percent()) and their counterparts on the other axis.

Bars, histograms, areas and stems draw a baseline at zero (or at the lower edge when zero is out of range); other charts draw none. Override this for each axes with ax.baseline(shown=True) or ax.baseline(shown=False); ax.baseline(shown=ub.AUTO) restores the default.

Automatic limits contain whole bubble markers, outlines included; explicit limits clip them. Legend swatches show each mark's fill as drawn over the plot background.

A grouped bar chart in the Pigment theme A histogram with a gradient fill in the Pigment theme

Long-form data

Data in long form, one row per observation with a column naming its group, is drawn with by=. line, step, scatter, area, ecdf, kde, hist and bar take it: each group becomes one series, named after the group and colored in the order the groups first appear.

import pandas as pd
import umber as ub

df = pd.DataFrame(
    {
        "day": [1, 2, 3, 1, 2, 3],
        "temp": [12.0, 14.5, 13.0, 9.0, 10.5, 11.0],
        "station": ["North", "North", "North", "South", "South", "South"],
    }
)
ax = ub.Figure().add_axes()
ax.line(df["day"], df["temp"], by=df["station"])

Columns are passed as data, not as column names, and work the same from pandas, polars, NumPy or lists; umber does not read a column's name. With by=, every group takes its own label and color, so label= and color= are refused. Histograms need style=ub.Hist.STEP or style=ub.Hist.STACKED, and bar(categories, heights, by=groups) draws grouped bars.

Grids and shared axes

Figure(rows=2, cols=2) declares a grid. Its default width=ub.AUTO and height=ub.AUTO give each column 6 inches and each row 4 inches; an explicit size is for the whole figure. Add panels by their zero-based top-left cell:

import copy
import umber as ub

fig = ub.Figure(rows=2, cols=2, share_x=ub.Share.COL, legend=ub.Legend.MERGED)
fig.title("Station signals").subtitle("Shared time scales")
fig.add_axes(row=0, col=0).line([0, 1, 2], [1, 3, 2], label="North")
fig.add_axes(row=1, col=0).line([0, 1, 2], [2, 1, 3], label="South")
fig.add_axes(row=0, col=1, row_span=2).line([0, 1, 2], [1, 3, 2], label="North")
fig.save_png("subplots.png")

variant = copy.copy(fig)
variant.axes_at(row=0, col=1).title("Comparison")  # the original is unchanged

Share.NONE, Share.ROW, Share.COL and Share.ALL choose which panels share a scale, limits, ticks and tick format. Legend.PER_PANEL keeps a separate legend for each panel instead of the default merged legend. Empty cells remain empty; overlapping panels are refused.

Use fig.axes_at(row=..., col=..., x=..., y=...) to retrieve axes, including those in a copy. The scale kinds and log bases must match creation; for category axes, pass x=ub.Scale.category() to get Axes[str, float]. A spanning panel is found only at its top-left cell. A row or column can be omitted only when that dimension has size 1. Figure.axes has been removed in favor of this typed method.

fig.cells() yields immutable Cell positions in row-major order, occupied or not; fig.cells(n) yields the first n. For example, for cell, values in zip(fig.cells(), datasets): cell.add_axes().line(x, values) adds one panel per dataset. AUTO is a typed sentinel, not None, and only parameters that declare it accept it.

Colors and colorbars

Heatmaps, meshes, contours, 2-D histograms and hexbins color each cell, band or line by its value, and color=ub.ColorBy(values) does the same for the points of a scatter or bubble chart. The color-mapped marks of one axes share a single color scale, and a colorbar beside the plot explains it. From 10,000 cells, heatmaps, meshes and 2-D histograms draw their cells as one image with one pixel per cell instead of one shape per cell, which keeps large grids small and fast; render=ub.Render.VECTOR or render=ub.Render.RASTER chooses the form explicitly.

import numpy as np
import umber as ub

rng = np.random.default_rng(41)
x = rng.normal(size=20_000)
y = 0.6 * x + rng.normal(scale=0.7, size=x.size)

ax = ub.Figure(theme=ub.Theme.MIDNIGHT).add_axes()
ax.hexbin(x, y, gridsize=30, scale=ub.Scale.log()).colorbar("Count")
ax.figure.save_png("density.png")

cmap= chooses the colormap: each theme has its own sequential and diverging colormaps, and Cmap.VIRIDIS, Cmap.MAGMA, Cmap.CIVIDIS and Cmap.BLUE_RED are built in. Cmap.from_colors([...]) builds one from your colors, mixed in the perceptual Oklab space, and .reversed() turns one around. scale=ub.Scale.log() maps values on a log scale, and center=0 makes the mapping diverging and symmetric about that value. The color range runs from the smallest to the largest finite value; ax.clim(low, high) sets it, and values outside it take the end colors.

ax.colorbar(label, position=..., format=...) labels the colorbar, moves it below the plot with ColorbarPosition.BOTTOM or hides it with ColorbarPosition.NONE. On a grid, Figure(share_color=ub.Share.ROW) gives the panels of each row one color range and one colorbar at the right of the row; Share.COL puts one below each column, and Share.ALL one beside the whole grid. fig.colorbar(...) sets those shared colorbars.

An annotated correlation heatmap with a diverging colormap in the Graphite theme Hexagonal point counts on a log color scale in the Midnight theme

Contours

ax.contour(x, y, z) draws level lines of a field sampled on a grid, with z shaped (len(y), len(x)). style=ub.Contour.FILLED fills the bands between levels and Contour.BOTH draws both; levels= takes a count or the levels themselves, and annotate=True writes each level on its lines. Contours share the axes' color scale like the other color-mapped marks, and the colorbar shows the levels.

import numpy as np
import umber as ub

x = np.linspace(-3, 3, 121)
y = np.linspace(-2, 2, 81)
z = np.exp(-((x[None, :] - 1) ** 2) - (y[:, None] - 0.4) ** 2)

ax = ub.Figure(theme=ub.Theme.GRAPHITE).add_axes()
ax.contour(x, y, z, style=ub.Contour.BOTH, annotate=True).colorbar("km")
ax.figure.save_png("elevation.png")

Pie and donut charts

ub.pie(categories, values) draws a pie on its own panel; on a grid, fig.add_pie_axes(row=..., col=...) adds a pie panel. Slices start at twelve o'clock and run clockwise in input order. hole=0.55 makes a donut, annotate=True writes each share under its name, and names that do not fit inside a slice move outside with a leader line. A pie labels its slices directly; ax.legend(ub.LegendStyle.HEADER) uses a legend instead.

import umber as ub

channels = ["Search", "Direct", "Social", "Email", "Other"]
ax = ub.pie(channels, [41, 24, 17, 11, 7], hole=0.55, annotate=True).title("Visits by channel")
ax.figure.save_png("channels.png")

A filled elevation map with labelled level lines in the Graphite theme An annotated donut chart of visits by channel in the Pigment theme

Time axes

ub.Scale.time() makes a time axis. It takes datetime, date and numpy.datetime64 values, NumPy datetime64 arrays of any unit, and naive pandas and polars datetime columns:

import numpy as np
import umber as ub

hours = np.datetime64("2026-03-02T00", "h") + np.arange(7 * 24)
demand = 60 + 20 * np.sin((np.arange(7 * 24) - 6) / 24 * 2 * np.pi)

ax = ub.Figure(8, 4).add_axes(x=ub.Scale.time())
ax.band(hours, demand - 6, demand + 6, label="Range").line(hours, demand, label="Demand")
ax.vspan(np.datetime64("2026-03-05T01"), np.datetime64("2026-03-05T05"), label="Maintenance")
ax.figure.save_png("demand.png")

Ticks fall on calendar steps, such as months, days or hours, with concise labels; xtick_format(ub.TickFormat.iso()) writes them in ISO 8601 instead. Limits, ticks, reference lines and spans on a time axis take times. Times are kept to the microsecond, NaT leaves a gap, and a time zone is refused: convert to naive times first, for example with .tz_convert(None) in pandas. Bands and stacked areas take times on the x axis only.

A week of hourly demand with its range and a maintenance window, in the Pigment theme

A second y axis

ax.twin_y() adds a numeric y axis on the right that shares the panel's x axis, and returns the axes for it. The two y axes have their own scales, limits, ticks and labels; the marks of both share one palette and one legend. fig.twin_axes_at(row=..., col=...) retrieves the right axes later.

import umber as ub

months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"]
left = ub.Figure().add_axes(x=ub.Scale.category())
left.bar(months, [52, 48, 55, 61, 72, 84], label="Rainfall").ylabel("mm")
left.twin_y().line(months, [4, 5, 9, 13, 18, 22], label="Temperature").ylabel("°C")
left.figure.save_png("weather.png")

Text and annotations

ax.text(text, (x, y)) writes text at a data position, and ax.annotate(text, (x, y), offset=(dx, dy)) writes it a distance away in points, with an arrow to the position unless arrow=False. Positions use each axis' own values: numbers, times, or category names. Text and annotations never change the axis limits. "\n" breaks them into lines: the first line sits where a single line would, and the others follow below it.

annotate=True on bar, grouped_bar and stacked_bar writes each bar's value; automatic limits grow so that every value fits, and format= chooses how values are written. With explicit limits, a value that does not fit raises StyleError naming the bar.

import numpy as np
import umber as ub

days = np.datetime64("2026-03-01") + np.arange(8)
load = [21, 22, 24, 33, 25, 23, 15, 20]

ax = ub.Figure().add_axes(x=ub.Scale.time())
ax.line(days, load).hline(30.0, dash=ub.Dash.DASHED).ylim(10, 40)
ax.annotate("Release", (days[3], 33.0), offset=(28, 14))
ax.annotate("Outage", (days[6], 15.0), offset=(22, -20))
ax.text("Target", (days[0], 32.0), color=ub.Color.GRAY)

Monthly rainfall bars and a temperature line on a second y axis in the Midnight theme A time series with an annotated release peak, an outage dip and a dashed target line in the Pigment theme

Titles and legends

fig.title(text) and fig.subtitle(text) set text above the whole grid; ax.title(text) and ax.subtitle(text) belong to one panel. Each returns the same object for chaining, and an empty string removes the text. The figure title is 1.5 times the panel title size. "\n" breaks a title or subtitle into lines, each aligned like the title; other labels are one line, and text with a line break or another control character where it is not supported raises an error at the call. A title, subtitle or axis label wider than its room raises StyleError when the figure is rendered, with the width it needs; make the figure wider or lower the font scale.

Theme.with_title_align(ub.Align.LEFT) sets title alignment; CENTER and RIGHT are also available. Subtitles follow their title. On a multi-panel figure, the merged legend stays below the figure title and subtitle and follows their alignment, without a separate position setting. To place legends by panel, use Legend.PER_PANEL.

import umber as ub

theme = ub.Theme.PIGMENT.with_title_align(ub.Align.CENTER).with_legend_position(
    ub.LegendPosition.BOTTOM
)
fig = ub.Figure(theme=theme).title("Station signals")
ax = fig.add_axes().line([0, 1, 2], [1, 3, 2], label="North")
ax.legend(ub.LegendStyle.HEADER, position=ub.LegendPosition.RIGHT)
ax.legend(ub.LegendStyle.HEADER, position=ub.AUTO)  # back to the theme's BOTTOM

For single-panel figures and Legend.PER_PANEL, LegendPosition.TOP_LEFT (default) and TOP_RIGHT place a row below the panel title; RIGHT places a column beside the plot; BOTTOM places a row below the x axis title; BEST places the legend inside the plot where it covers the least data. Panel legend positions do not follow title alignment. Omitting position, or passing ub.AUTO, clears a previous override and uses the theme's position; it does not mean BEST. LegendStyle.DIRECT accepts only the default position=ub.AUTO, since its labels sit at line ends. LegendStyle.NONE hides the legend.

Titles, subtitles, axis labels, legends, colorbar labels, text, annotations, bar values and pie labels take size=. ub.Size.AUTO is the theme's size, ub.Size.times(1.5) scales it and follows the theme's font scale, and ub.Size.pt(14) is a fixed size in points. Tick labels are sized per axis with ax.xtick_labels(size=...) and ax.ytick_labels(size=...).

import umber as ub

ax = ub.line([0, 1, 2], [1, 3, 2]).title("Headline", size=ub.Size.pt(18))
ax.xlabel("Day", size=ub.Size.times(1.2)).ytick_labels(size=ub.Size.pt(7))

Themes

Three themes are built in: Theme.PIGMENT (warm paper, serif titles), Theme.GRAPHITE (neutral, sans-serif) and Theme.MIDNIGHT (dark, monospaced figures). A theme is an immutable value; derive a variant with its with_* methods:

import umber as ub

theme = ub.Theme.MIDNIGHT.with_fill_style(ub.FillStyle.SOLID).with_legend_style(
    ub.LegendStyle.HEADER
)
fig = ub.Figure(theme=theme)

Two sine waves in the Midnight theme

Fonts

Each theme has a title font and a body font. Five fonts are bundled: Font.SOURCE_SERIF_4, Font.IBM_PLEX_SANS, Font.GEIST, Font.INTER and Font.JETBRAINS_MONO. Font.from_file(regular, bold=...) reads TrueType or OpenType files, and Font.system(family) finds an installed family by name:

import umber as ub

serif = ub.Theme.PIGMENT.with_title_font(ub.Font.SOURCE_SERIF_4).with_body_font(ub.Font.INTER)
fig = ub.Figure(theme=serif)

# font = ub.Font.from_file("Brand-Regular.ttf", bold="Brand-Bold.ttf")
# font = ub.Font.system("Helvetica Neue")

Titles and direct legend labels are set in bold. A font without a bold face raises StyleError when such text is drawn, unless it was created with bold_fallback=True, which sets that text in the regular face; umber never synthesizes bold. A font is read once and kept in memory, so the figure does not change if the file does. Characters a font lacks fall back to the bundled fonts, then to system fonts.

Output

fig.save_png(path, dpi=...), fig.save_svg(path) and fig.save_pdf(path) write a file; to_png(), to_svg() and to_pdf() return the bytes or text instead. PNG output is antialiased at the chosen resolution. SVG text is drawn as outlines by default, so it looks the same everywhere; text=ub.SvgText.EDITABLE keeps it as text, which the viewer then draws with its own copy of the font. PDF output is vector, one page at the figure's size, with the fonts it uses embedded as subsets, so its text can be selected and searched; large cell grids stay images, as in SVG.

Jupyter and show()

A figure or axes at the end of a Jupyter cell displays the whole figure as a PNG. Use fig.show() or ax.show() to display it explicitly. Both render at 144 dpi and display at 72 CSS pixels per inch (a 6 × 4 in figure is 432 × 288 CSS pixels). Outside a Jupyter kernel, including terminal IPython, show() opens a PNG in the system's default viewer without waiting for it to close; the file is left in a fresh temporary directory. If no viewer can be started, UsageError names the saved file. Rendering errors propagate, and IPython is optional.

Errors

An error is raised by the call that caused it: umber.DataError for invalid data, umber.StyleError for an invalid option value, umber.UsageError for a call that is not allowed in the current state, and TypeError for a value of the wrong type. The three umber errors derive from umber.UmberError. The traceback points at the argument:

Traceback (most recent call last):
  File "example.py", line 7, in <module>
    ax.line(t, y, label="signal")
    ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
umber.DataError: Axes.line(): y[2] is inf; values must be finite.
  ax.line(t, y, label="signal")
             ^
hint: use NaN to leave a gap in the line.
TypeError: Axes.line(): color is str; color must be of type Color.
  ax.line([0, 1, 2], [1, 3, 2], color="red")
                                ^^^^^^^^^^^
hint: use Color.hex("#3366B8"), Color.rgb() or a named color such as Color.BLUE.

In a terminal that supports it, the hint and the highlight are shown in color; set NO_COLOR to turn color off or FORCE_COLOR to force it. The same notes appear in Jupyter.

Static typing

The package ships type information (py.typed) and is checked with pyright and mypy in strict mode. Figure().add_axes() is Axes[float, float]; add_axes(x=Scale.category()) is Axes[str, float], add_axes(y=Scale.category()) is Axes[float, str], two category axes are Axes[str, str] and add_axes(x=Scale.time()) is Axes[Time, float]. Methods that only make sense on one kind of axis are typed accordingly:

import umber as ub

ax = ub.Figure().add_axes()
ax.bar(["a", "b"], [1, 2])  # pyright and mypy: bar() needs Axes[str, float]

Performance

End-to-end Python timings next to matplotlib 3.11.2 (Agg, SVG and PDF backends, default style), from the call that builds the figure to the encoded bytes. Figures are 6 × 4 in, PNG at 200 dpi, with the same input data for both. Each value is the median of 8 runs after a warm-up, with each library in its own process. Measured with the umber 0.4.0 source on GitHub Actions ubuntu-latest (AMD EPYC 9V74, 4 vCPUs, Python 3.12, NumPy 2.5) by the Python bench workflow (this run); the script is scripts/bench/python_vs_matplotlib.py. GitHub assigns different CPU models to the same runner label, so absolute times vary between runs; the speed-ups, measured side by side on one machine, are the figures to compare.

Case umber matplotlib Speed-up
1,000,000-point line → PNG 77 ms 207 ms 2.7×
1,000,000-point line on a time axis → PNG 94 ms 221 ms 2.4×
1,000,000-point line → SVG 29 ms 65 ms 2.2×
1,000,000-point line → PDF 36 ms 164 ms 4.5×
100,000-point scatter → PNG 83 ms 290 ms 3.5×
Histogram of 1,000,000 values → PNG 32 ms 63 ms 2.0×
2-D histogram of 1,000,000 points, 100 × 100 bins → PNG 97 ms 168 ms 1.7×
Hexbin of 1,000,000 points, grid size 50 → PNG 122 ms 142 ms 1.2×
Heatmap of 1,000 × 1,000 cells → PNG 189 ms 253 ms 1.3×
Heatmap of 1,000 × 1,000 cells → SVG 171 ms 443 ms 2.6×
Heatmap of 1,000 × 1,000 cells → PDF 141 ms 178 ms 1.3×
4 × 5 grid, one 10,000-point line per panel → PNG 175 ms 550 ms 3.1×
1,000-point line with a second series, title, labels and legend → PNG 21 ms 81 ms 3.9×
The same 1,000-point line chart → PDF 3.6 ms 128 ms 36×
5-bar chart → PNG 12 ms 44 ms 3.8×
import in a fresh interpreter 70 ms 309 ms 4.4×

Pinned to one CPU, only the scatter plot is notably slower: 124 ms (2.3×). The 4 × 5 grid uses a 12 × 8 in figure. The script's docstring lists where the two libraries do different work, for example the hexbin grids, which are built differently. The PDF heatmap is larger than matplotlib's, 1.45 MB against 0.91 MB: umber computes the exact color of every cell, while matplotlib picks each cell's color from a 256-entry colormap table, which compresses better.

Lines with sorted numeric or time x values are downsampled when they have more than four points per pixel column of the plot (counted at a nominal 300 dpi), keeping the first, last, minimum and maximum point in each column, which is visually lossless. Large scatter plots are composited on up to 8 threads; the output is the same for any number of threads.

Agent Skill

The package includes an Agent Skill for coding agents: a guide to choosing charts and to umber's idioms, a translation table from matplotlib, the error classes and how to fix them, and an API reference generated from the installed version. Install it into a project for one or more agents:

python -m umber skill install --agent claude-code
python -m umber skill install --agent codex --agent cursor --user

--agent is one of claude-code, codex, cursor and pi; without --user the skill goes into the current project, with it into your home directory. python -m umber skill uninstall with the same options removes it. Install it again after upgrading umber, so that the reference matches the installed version.

Rust

The rendering core is published as the umber-core crate and can be used directly from Rust:

use umber_core::{AxisData, Figure, LineOptions, ScaleKind, Theme};

let mut figure = Figure::new(6.0, 4.0, Theme::pigment())?;
figure
    .add_axes(ScaleKind::Linear, ScaleKind::Linear)?
    .title("Signal")
    .line(AxisData::Numbers(vec![0.0, 1.0, 2.0, 3.0]), &[1.0, 3.0, 2.0, 4.0], LineOptions::default())?;
std::fs::write("signal.png", figure.to_png(144)?)?;

Status

umber is at version 0.x. The API is not yet stable and may change or be removed without a deprecation period. More examples are in examples/. Changes in each version are listed in the release notes.

License

Licensed under either of MIT or Apache-2.0, at your option (MIT OR Apache-2.0). Bundled fonts are under the SIL Open Font License 1.1; see crates/umber-core/fonts/LICENSES and crates/umber-core/fonts/MANIFEST.md. Wheels include statically linked Rust dependencies; their licenses are listed in THIRD-PARTY-LICENSES.txt.

Metadata

Release files for umber 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for umber 0.4.0
File Size Uploaded Provenance
umber-0.4.0.tar.gz 1.8 MB With provenance Details

Built distributions (wheels)

Table of built distributions (wheels) for umber 0.4.0
File
umber-0.4.0-cp315-abi3.abi3t-win_amd64.whl CPython 3.15 abi3t, abi3 Windows x86-64 Details
umber-0.4.0-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.15 abi3, abi3t Linux glibc 2.17+ x86-64 Details
umber-0.4.0-cp315-abi3.abi3t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.15 abi3t, abi3 Linux glibc 2.17+ ARM64 Details
umber-0.4.0-cp315-abi3.abi3t-macosx_11_0_arm64.whl CPython 3.15 abi3t, abi3 macOS 11.0+ ARM64 Details
umber-0.4.0-cp315-abi3.abi3t-macosx_10_12_x86_64.whl CPython 3.15 abi3, abi3t macOS 10.12+ x86-64 Details
umber-0.4.0-cp314-cp314t-win_amd64.whl CPython 3.14 CPython 3.14 free-threading Windows x86-64 Details
umber-0.4.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.14 CPython 3.14 free-threading Linux glibc 2.17+ x86-64 Details
umber-0.4.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.14 CPython 3.14 free-threading Linux glibc 2.17+ ARM64 Details
umber-0.4.0-cp314-cp314t-macosx_11_0_arm64.whl CPython 3.14 CPython 3.14 free-threading macOS 11.0+ ARM64 Details
umber-0.4.0-cp314-cp314t-macosx_10_12_x86_64.whl CPython 3.14 CPython 3.14 free-threading macOS 10.12+ x86-64 Details
umber-0.4.0-cp312-abi3-win_amd64.whl CPython 3.12 abi3 Windows x86-64 Details
umber-0.4.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.12 abi3 Linux glibc 2.17+ x86-64 Details
umber-0.4.0-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.12 abi3 Linux glibc 2.17+ ARM64 Details
umber-0.4.0-cp312-abi3-macosx_11_0_arm64.whl CPython 3.12 abi3 macOS 11.0+ ARM64 Details
umber-0.4.0-cp312-abi3-macosx_10_12_x86_64.whl CPython 3.12 abi3 macOS 10.12+ x86-64 Details

Total release size: 51.4 MB

Release files / umber-0.4.0.tar.gz

Download URL umber-0.4.0.tar.gz
Size 1.8 MB
Tags Source
SHA-256 checksum
How to use checksums
2ff1a2d0090941f9e63d171d27899768a603c00c088f2801ff3f90f3443cf498
BLAKE2b-256 checksum
How to use checksums
f616397e785c5c69ac1db352a4cd09c06c8a89312fa40789bcdb25767ec636c1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / umber-0.4.0-cp315-abi3.abi3t-win_amd64.whl

Download URL umber-0.4.0-cp315-abi3.abi3t-win_amd64.whl
Size 3.4 MB
Tags CPython 3.15 Windows x86-64 abi3 abi3t
SHA-256 checksum
How to use checksums
bf82529596f8f7237d0092d2c5038f2fea412fc85760bd476af499b75ec83bbb
BLAKE2b-256 checksum
How to use checksums
0d7366e45de21074b51fa60aa47c2bc2c20b738b77c859fd42458c56933136f2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / umber-0.4.0-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL umber-0.4.0-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 3.5 MB
Tags CPython 3.15 Linux glibc 2.17+ x86-64 abi3 abi3t
SHA-256 checksum
How to use checksums
3fe5e730d05bf3c7eae049c13d3343c98c9f03e19df548913a0f710fab20f230
BLAKE2b-256 checksum
How to use checksums
ab854182852a3eb5e53f486ffc4b8c50aca2f164a726788e505f3199f25fc36e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / umber-0.4.0-cp315-abi3.abi3t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL umber-0.4.0-cp315-abi3.abi3t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 3.3 MB
Tags CPython 3.15 Linux glibc 2.17+ ARM64 abi3 abi3t
SHA-256 checksum
How to use checksums
a1a02962026d824eb7210a3693eae9ad7f8f75567a121a480deee7928b7f5ac5
BLAKE2b-256 checksum
How to use checksums
9053dc685585e2fe005c173a0a7ea01c989e6e45637e155d6d407189b594cceb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / umber-0.4.0-cp315-abi3.abi3t-macosx_11_0_arm64.whl

Download URL umber-0.4.0-cp315-abi3.abi3t-macosx_11_0_arm64.whl
Size 3.1 MB
Tags CPython 3.15 abi3 abi3t macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
b90e2f7d9bd8ec402a0ed79a6f11d8541444cb596c57362fa8b5abb83557bbfb
BLAKE2b-256 checksum
How to use checksums
0cd8079517e9b6a98f2f8a7a53a6caecedc0ce28daee3ac6c5aa9c85676bf63f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / umber-0.4.0-cp315-abi3.abi3t-macosx_10_12_x86_64.whl

Download URL umber-0.4.0-cp315-abi3.abi3t-macosx_10_12_x86_64.whl
Size 3.3 MB
Tags CPython 3.15 abi3 abi3t macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
9d2d1c0cc1876e8623e6704c309155f85ebdddf62c35db3432cc52934ef8d002
BLAKE2b-256 checksum
How to use checksums
93ca360ec0101a6f496f60f77399c192fdda70a6c28737e25dc33459106f6c1d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / umber-0.4.0-cp314-cp314t-win_amd64.whl

Download URL umber-0.4.0-cp314-cp314t-win_amd64.whl
Size 3.4 MB
Tags CPython 3.14 CPython 3.14 free-threading Windows x86-64
SHA-256 checksum
How to use checksums
6edf371ad9b1e451c1c2277fb831265aba6db95bba37c901b6034bd17b16b902
BLAKE2b-256 checksum
How to use checksums
de01407ffb189592a626e5c7a9dfb4b7df2df3dd96eac1da596dbf8d52c51d25
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / umber-0.4.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL umber-0.4.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 3.5 MB
Tags CPython 3.14 CPython 3.14 free-threading Linux glibc 2.17+ x86-64
SHA-256 checksum
How to use checksums
179b51337289537b7ec05a62e59febc9d588aac2df563e4cb5445d5e2ac5df43
BLAKE2b-256 checksum
How to use checksums
422de7026c8c12350c96cacdef1b09eedc3a57405e67746cb0e83a6d0d940428
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / umber-0.4.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL umber-0.4.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 3.2 MB
Tags CPython 3.14 CPython 3.14 free-threading Linux glibc 2.17+ ARM64
SHA-256 checksum
How to use checksums
070e78c9e7509ff4ca2dd96d53dd845b4a427fe2e48f5c9584d6175b4714c12c
BLAKE2b-256 checksum
How to use checksums
f40a41ba88d43a4880b261ae184029d0b77a2b026363049448cc9bc7cdf18016
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / umber-0.4.0-cp314-cp314t-macosx_11_0_arm64.whl

Download URL umber-0.4.0-cp314-cp314t-macosx_11_0_arm64.whl
Size 3.1 MB
Tags CPython 3.14 CPython 3.14 free-threading macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
52364f45130c2ad01e26c7087152205022934634ddc1c2556185f99bf3db7f19
BLAKE2b-256 checksum
How to use checksums
eb8769c3e5469030739007ec87db1dd1a0bf2cc1b952e13e824492b1048232af
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / umber-0.4.0-cp314-cp314t-macosx_10_12_x86_64.whl

Download URL umber-0.4.0-cp314-cp314t-macosx_10_12_x86_64.whl
Size 3.3 MB
Tags CPython 3.14 CPython 3.14 free-threading macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
114282eb523f04987a973234f054eb01c340b7ff924c58eda787fec6584d52fe
BLAKE2b-256 checksum
How to use checksums
c178cb9ab0228bc123957cc2a86eb6bc54eb8435f77f2b8f94a3e85fcdc9c99d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / umber-0.4.0-cp312-abi3-win_amd64.whl

Download URL umber-0.4.0-cp312-abi3-win_amd64.whl
Size 3.4 MB
Tags CPython 3.12 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
20b95448332e8710fe35cdc9d6235e167d67285264df374f77bfccf2cdff00da
BLAKE2b-256 checksum
How to use checksums
74bd70db7dfad9a63db0b112b5264ef384cc4389d2518c64b3e7ad8540398b76
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / umber-0.4.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL umber-0.4.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 3.5 MB
Tags CPython 3.12 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
b712e80f365916241070266c496580a62c092d2974f5d86a5c7b744ef7fadac9
BLAKE2b-256 checksum
How to use checksums
d7cb2d3e293480d21f08a829b2f5688151ed3e53e52780915542575face5d0e1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / umber-0.4.0-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL umber-0.4.0-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 3.2 MB
Tags CPython 3.12 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
1d8811959a201a4838b1b4d4027ba2ea68c02847c64970e8ef2218c9658a2f62
BLAKE2b-256 checksum
How to use checksums
af0bc424106701e7ccc671dfb7cbfc8ddb23229714e146d6fa1d1e5514ee9b42
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / umber-0.4.0-cp312-abi3-macosx_11_0_arm64.whl

Download URL umber-0.4.0-cp312-abi3-macosx_11_0_arm64.whl
Size 3.1 MB
Tags CPython 3.12 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
edb5626b22dcd786b71949d7cb458d6ae210862b299cd1f108b9848ab5cb9276
BLAKE2b-256 checksum
How to use checksums
214d28fe671d52360a62567ab34bc8c8aa4c92e34b93100fd346866fc64fedcf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / umber-0.4.0-cp312-abi3-macosx_10_12_x86_64.whl

Download URL umber-0.4.0-cp312-abi3-macosx_10_12_x86_64.whl
Size 3.3 MB
Tags CPython 3.12 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
4d7ed53cd08dcaeb292ba17ec9ba42430893bbddbf3653bbb5c3bdbbfcee96b4
BLAKE2b-256 checksum
How to use checksums
4f4901ae90c936650a32301b705fcb17f1e7d323b6f3526e6ff01c25eae19313
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Security for umber 0.4.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 16 files in this release have been uploaded with provenance metadata.

Select from 16 files

Attestation for umber-0.4.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page