6. Color & themes
Palettes for categories, colormaps for values, the cycle notation, and per-chart themes. Every snippet on this page is executed on every build — the figures are its output.
Palettes for categories
A categorical column mapped through
aes(...) pulls colors from the default cycle.
palette= overrides it: a named palette
(pt.list_palettes() — tab10, Set2, colorblind,
…) or an explicit {level: color} dict when specific
levels must keep specific colors.
import random
rng = random.Random(1)
data = {"grp": [], "value": [], "arm": []}
for i, g in enumerate(["ctrl", "low", "high"]):
for arm in ("A", "B"):
data["grp"] += [g] * 40
data["arm"] += [arm] * 40
data["value"] += [rng.gauss(5 + i + (arm == "B"), 1)
for _ in range(40)]
c = pt.chart(data, aes(x="grp", y="value", fill="arm"), legend=True,
data_width=380, data_height=200)
c.xscale("category", order=["ctrl", "low", "high"])
c.add_violin(palette={"A": "#3F97C5", "B": "#F99917"}, inner="box")
c.legend()
The color cycle
Literal colors accept hex strings, named colors,
and the cycle notation "C0"…"C9"
— slot n of the default tab10 cycle, the same in every
theme, so layered artists can coordinate colors without hardcoding hex. A bare
color= string is always a literal like these — only
aes(...) maps a column. The default cycle is also
available as pt.TAB10.
import math
xs = [i * 0.2 for i in range(40)]
waves = {"x": xs, "sin": [math.sin(x) for x in xs],
"cos": [math.cos(x) for x in xs]}
c = pt.chart(waves, aes(x="x"), data_width=380, data_height=170,
legend=True)
c.add_line(aes(y="sin"), color="C0", label="C0")
c.add_line(aes(y="cos"), color="C1", label="C1")
c.add_axhline(0, color="C3", linestyle="--", label="C3")
c.legend()
Colormaps for values
Continuous values map through cmap=
— the matplotlib names are all registered
(pt.list_colormaps()), each with a reversed
_r twin. For diverging data, center= pins the
midpoint (e.g. 0) to the colormap’s center. Register custom maps
with pt.register_colormap.
import math
data = {"col": [f"c{i}" for i in range(10)]}
for r in range(6):
data[f"r{r}"] = [math.sin(i * 0.6 + r) for i in range(10)]
values = [f"r{r}" for r in range(6)]
left = pt.chart(title="viridis", data_width=220, data_height=150)
left.add_heatmap(data=data, mapping=aes(x="col"), values=values,
cmap="viridis")
right = pt.chart(title="RdBu_r, center=0", data_width=220,
data_height=150)
right.add_heatmap(data=data, mapping=aes(x="col"), values=values,
cmap="RdBu_r", center=0)
c = left | right
Themes
Four built-ins — classic (the
default), dark, minimal,
void — set per chart, never globally: the same
script always produces the same figures. Themes restyle the chrome
— background, frame, gridlines, fonts — while series colors
and your data code don’t change. Custom themes are a
dict away (pt.register_theme,
docs/THEMES.md).
import math
xs = [i * 0.1 for i in range(64)]
waves = {"t": xs, "sin": [math.sin(x) for x in xs],
"cos": [math.cos(x) for x in xs]}
a = pt.chart(waves, aes(x="t", y="sin"), theme="classic",
title="classic", data_width=210, data_height=140,
xlabel="t")
a.add_line()
a.add_line(aes(y="cos"), linestyle="--")
b = pt.chart(waves, aes(x="t", y="sin"), theme="minimal",
title="minimal", data_width=210, data_height=140,
xlabel="t")
b.add_line()
b.add_line(aes(y="cos"), linestyle="--")
c = a | b
← Sectors & circular coordinates · Reproducibility & AI tooling →