Minimal call
import math
data = {"col": [f"c{i}" for i in range(8)]}
for r in range(5):
data[f"r{r}"] = [math.sin(i * 0.7 + r) for i in range(8)]
c = pt.chart()
c.add_heatmap(data=data, mapping=aes(x="col"),
values=[f"r{r}" for r in range(5)], cmap="viridis")
Worked example
import plotlet as pt
from plotlet import aes
df = pt.load_dataset("flights")
months = df["month"][:12]
years = sorted(set(df["year"]))
data = {"year": [str(y) for y in years]}
for m in months:
data[m] = [n for n, month in zip(df["passengers"], df["month"])
if month == m]
hm = pt.chart(title="airline passengers", data_width=420, data_height=240)
hm.add_heatmap(data=data, mapping=aes(x="year"), values=months,
cmap="viridis", legend={"label": "passengers"})
c = pt.grid([[hm, pt.legend()]]).gap(0)
Docstring
Heatmap artist — tidy/long input, continuous or categorical x.
Input is a **tidy table** (dict-of-columns or DataFrame), like `scatter`:
``c.add_heatmap(aes(x="x"), values=["a", "b"])``. Each table **row**
becomes one heatmap **column** (its x-position from the `x` column); each
**value column** becomes one heatmap **row** (a track). `values=`
picks/orders the tracks; by default every column except `x`/`sector` is a
track. A bare matrix is *not* accepted — reshape it into a table first.
The `x` column's dtype picks the x-axis: **numeric → continuous** (a
linear scale; cell edges inferred as neighbour midpoints, so uneven
spacing works), **string → categorical** band labels. The y-axis (the
tracks) is always categorical. A continuous-x heatmap `share_x`-aligns to
a scatter/line on the same numeric scale — the annotation-track shape.
Every cell feature is dtype-independent: NaN/None → `absent_fill`, cell
borders, annotations, discrete `palette=`, and circular warp via
`project=ctx.warp`.
Sectors: map `aes(sector=...)` (a column of per-column group tags) with a
panel-level ``c.sectors(Sectors(...), axis="x", column=...)``. The
framework's sector remap tags each column's x into its sector, and cell
edges are grouped per sector so gaps fall between sector groups.
`c.add_imshow(matrix, ...)` stays separate — a pure image blitter (uniform
pixels, no per-cell styling, no warp, no labels) for real image /
dense-array data. Reach for heatmap when you need labels, missing-value
handling, borders, or a non-affine coordinate; imshow when you just want
pixels from a matrix.
Rendering branches on size: below `imshow_max_rects` we emit one `<rect>`
per cell (vector-clean, zoomable). Above the threshold we encode the grid
as a base64 PNG inside one `<image>` — `_png_for_blocks` for a categorical
x (honours sector splits), a single imshow-style image for a continuous x
— except when a flat image can't represent the geometry (a warp, uneven
or sector-tagged continuous cells, y sector splits): `_use_rects` then
keeps the per-cell rects at any size.
For categorical-x clustering with visual gaps and ordering, call
``c.sectors({cluster: [members], ...}, axis="x" | "y")`` on the panel.
The category scale picks up the implied split positions and inserts a
gap between groups; ``_resolve_display`` (below) reorders the matrix at
draw time to match the sector cat order.