API reference
pt.chart(data=None, mapping=None, **opts) returns a Chart.
Table-shaped marks (scatter, line, bar, hist, ecdf, …) take
long-form input only: pass a data table (any object supporting
data[col_name] — pandas / polars / dict-of-lists / dict-of-arrays)
and map columns to aesthetics with aes(...) —
aes(x="col", y="col", color="col") (fill= on fill-defaulted
artists). A bare string kwarg is always a literal (color="red");
only aes(...) refers to a column — no sniffing. Data and mapping set
on pt.chart(df, aes(...)) propagate to every mark, so per-call
values are optional once set at chart level. heatmap is also
table-shaped (df, aes(x=), values=, see below). The remaining shape
marks (imshow, contour, dendrogram, rect / polygon /
polyline, the reference lines and spans, annotate) take their
natural positional input — there's no aes(...) version for a 2-D
matrix or a single y-value. All methods return self, so they chain.
Frame options
Pass at construction or as chained setters:
title, subtitle (smaller line under the title), caption (small
right-aligned text below the chart — ggplot's labs(caption=)),
xlabel, ylabel, xlim=(a, b), ylim=(a, b), xscale,
yscale, gridlines=True/False (or "major"/"minor"/"both"; also
c.gridlines(which="minor") — minor lines use the axes' minor= tick
positions, or auto subdivisions when none are set), legend=True/False,
clip=True/False,
data_width, data_height, theme (see THEMES.md),
font (see Fonts).
c.legend(True, position=...) places the in-frame legend. Outside
tokens (reserve margin space beside the data area): "right" (default),
"left", "top", "bottom". Inside tokens (overlay the data area):
"top-right", "top-left", "bottom-right", "bottom-left",
"center". Outside positions emit no frame; inside positions get a
translucent background for readability over plot marks. ncols=N
(also on pt.legend(...)) wraps discrete entries into N columns,
filled down-then-across; "top" / "bottom" default to one
horizontal row until ncols= switches them to the grid. A
gradient-only chart (imshow / heatmap / hexbin) on "top" /
"bottom" gets a horizontal colorbar — strip running vmin-left →
vmax-right, ticks below. reverse=True (also on pt.legend(...))
flips the discrete entry order; entries=[{"label": ..., "color": ...}, ...] appends free-form manual rows not harvested from any
artist (optional alpha; rendered as standard rect swatches).
Per-artist legend={...} customizes that artist's entries wherever they
render (in-frame or pt.legend() panel): {"label": ..., "ticks": [...]}
on a continuous source retitles its gradient strip; {"glyph": "rect"}
swaps the series swatch for the standard rectangle — the readable choice
when the plot mark itself is tiny (e.g. scatter with size=1.5);
aesthetic keys (alpha, size, marker, markersize, linewidth,
linestyle) override that value in the legend key only — ggplot2's
override.aes. So scatter(..., alpha=0.2, legend={"alpha": 1}) plots
translucent points with an opaque legend key. Aesthetic guides
(scatter's graded size dots) keep their own glyphs.
Sizes accept bare pixels (400) or unit-suffixed strings ("4in",
"10cm", "100mm", "72pt"). The data region is the user-facing
primitive — the canvas grows to fit titles and tick labels. To target
a specific SVG canvas, chain .fit(canvas_width=…, canvas_height=…)
after composing.
clip=False lets artists bleed past the data area into the margin
space; default True clips at the data boundary so off-axis data can't
paint over tick labels.
c.spines(...) styles the frame border: top-level color= / width= /
linestyle= set the base style; top= / right= / bottom= / left= /
walls= (the inter-sector walls) each take a bool (visibility) or a
{color, width, linestyle, visible} dict. c.facecolor(color) fills the
data-area background. c.x_expand(lo, hi) / c.y_expand(...) override
the autoscale padding as fractions of the data span (one number pads both
ends). pt.chart(margin={"left": ..., "right": ..., "top": ..., "bottom": ...}) (all four sides) raises the per-side whitespace floor
around the data area.
c.aspect("equal") or c.aspect(r) locks the data-space aspect ratio
(matplotlib set_aspect, ggplot coord_fixed): one y data unit renders
at r× the pixel length of one x data unit. The lock survives share
classes and .fit().
Coordinates
c.coordinate(pt.CircularCoordinate(...)) switches a panel to a non-Cartesian
coord system. One coordinate per panel; for two coordinate systems, use two
panels (composed via pt.grid / | / /).
CircularCoordinate is the only coordinate shipped in core today (ring /
annulus geometry; set r_inner=0 for a full polar disc). Each coord opts
in its supporting artists via pt.declare_coord_support(name, [...]);
the renderer raises if you mix a coord with an artist not in that list.
See plotlet-cookbook/circle/ for a worked example and the
protocol notes at the top of
src/plotlet/render/_coord_circular.py.
Scales
xscale / yscale values:
| value | notes |
|---|---|
"linear" |
default |
"log" |
log10; respect positive data |
"category" |
for strings; auto-selected when data is string-valued. c.xscale("category", order=[...], padding=0) for explicit ordering. padding=0 makes bands contiguous (heatmap-track look). For grouped categories with visual gaps, use c.sectors({cluster: [members]}, axis="x") — see Sectors below. |
"symlog" |
accepts linthresh= (default 1.0) sizing the linear region around zero |
"power" |
accepts exponent= |
"sqrt" |
shorthand for "power" with exponent=0.5 |
"time" |
auto-selected for datetime.date / datetime.datetime values. Ticks snap to calendar boundaries (year / month / day / hour / minute / second) at a resolution matching the axis span; labels format accordingly. Force with c.xscale("time") when data is already epoch-seconds floats; xlim accepts datetime or epoch-seconds endpoints. |
Every non-category kind accepts reverse=True to invert the axis
(c.yscale("linear", reverse=True) — depth profiles, ranks). For a
category axis, reverse the order= list instead.
Tick overrides
c.xticks([0, 5, 10], ["A","B","C"], rotation=45, fontsize=12,
direction="out", marks=False) # `[]` to hide
c.yticks(...) # same shape
marks=False hides the tick marks but keeps labels; labels=False is
the counterpart — keep auto positions and marks, suppress the labels.
side="top" (x) / side="right" (y) moves the axis — spine, tick
marks, tick labels, and the axis title as a block — to the other edge
(plotly's side, ggplot2's position).
fontstyle="italic" / fontweight="bold" restyle the tick labels
(sector-name labels on that axis inherit the style) with the real
Italic/Bold faces of the active family — see Fonts.
decoration="underline" | "overline" | "line-through" adds the
matching stroke line.
format="{:.0%}" or format=lambda v: f"${v/1000:.0f}K" formats
auto-generated tick labels — string and callable both work; explicit
labels still override. Named formatters (pt.list_formatters()):
"money", "si", "percent", "scientific", "comma", and
"power10" (100000 → 10⁵, 0.002 → 2×10⁻³ — the natural log-axis
format, c.xticks(format="power10")). pt.register_formatter(name, fn)
adds your own.
minor=True adds auto-positioned minor ticks (5 per major-gap on linear
scales; sub-decade on log); minor=[v1, v2, …] for explicit positions.
Density overrides: step=0.25 forces a fixed spacing; count=4 requests
roughly that many major ticks (the nice-numbers algorithm still picks
the actual values).
Text in labels & annotations
All text (titles, labels, ticks, legend, text/annotate) accepts any
Unicode character the active font covers (Fonts; the default
DejaVu Sans covers the full scientific set) — just type it, no markup:
Greek (α μ Σ), super/subscripts (10⁻⁵, H₂O, μm², log₂ FC),
operators (× ± ≤ ≈ → √ ∫). Unicode super/subscripts exist only for
digits, + − ( ), and a few letters — no x^{n+1} markup (unbuilt).
pt.superscript("-2") / pt.subscript("2") convert plain strings so
you don't hunt for codepoints: "kg·m" + pt.superscript("-2") → kg·m⁻².
\n stacks lines: "Fold change\n(log₂)".
Fonts
Text renders as SVG path outlines from an explicitly named font file — never an OS font lookup — so output is byte-identical across machines and viewers need no font installed. Three tiers:
- Default — DejaVu Sans (bundled). Zero config. It stays the default for coverage: Greek, Mathematical Operators, arrows, and super/subscripts all render without tofu.
- Bundled families —
font="Arimo", the journal look: metric-compatible with Helvetica/Arial, and the aliases"Helvetica"/"Arial"select it. Arimo covers Greek and super/subscripts but few math operators or arrows; glyphs a face lacks render as.notdefboxes. - Escape hatch —
font="/path/to/MyFont.ttf"loads any TTF/OTF on disk. Only the author needs the file at render time; the output stays self-contained and carries the font's family name, never the file path. ⚠️ Embedding a proprietary font's outlines in published SVGs may be restricted by its EULA — check before shipping.
font= at construction or chained c.font(...); per-chart like
theme=, and the two compose (theme="dark", font="Arimo"). Unknown
font names raise — no silent fallback, no OS resolution.
Both bundled families ship all four faces (Regular / Bold / Italic /
BoldItalic), so per-text fontstyle="italic" / fontweight="bold"
(on tick labels and draw.text_path) render real drawn variants. A
path-loaded font is one file: italic falls back to a synthetic -12°
skew, and fontweight="bold" raises — pass the bold file's path as
font= instead.
Sectors
c.sectors(spec, axis="x" | "y", ...) partitions an axis into named
regions. Two kinds, picked from the spec shape:
Continuous — values are numeric lengths. Each artist's value column (named via
column=) is offset into a single global coordinate so the standard linear scale covers every sector.c.sectors({"warmup": 100, "training": 500, "cooldown": 50}, column="phase") c.add_scatter(df, aes(x="t", y="v")) # df has a 'phase' columnCategorical — values are lists of category labels. Sectors group the categorical axis members; the category scale inserts visual gaps between groups and reorders cells. This is the unification of heatmap row/column clustering with continuous-axis partitioning.
c.sectors({"clusterA": ["c1", "c2"], "clusterB": ["c3"]}, axis="x") # tidy input: the `col` column holds the x labels (one table row per # heatmap column); the remaining columns are the value tracks. c.add_heatmap(df, aes(x="col"))
| kwarg | notes |
|---|---|
axis="x" / "y" |
which axis to partition |
column= |
continuous only — the data row's sector tag column |
divider=True |
draw boundary divider lines |
label=True |
draw sector-name labels at sector centers |
gap=None |
inter-sector pixel pad (categorical); None → spec default (6 px) |
Typical usage picks one of gap (categorical default) or divider line
(continuous default) — both at once reads as redundant clutter. For
heatmap clusters with visible gap whitespace and no labels, pass
divider=False, label=False so sectors drive axis layout only:
c.sectors({"clusterA": ["c1", "c2"], "clusterB": ["c3"]},
axis="x", divider=False, label=False)
c.add_heatmap(df, aes(x="col")) # df["col"] = ["c1","c2","c3"]
Layout-level sugar fans out one declaration to every leaf:
pt.grid([[t1], [t2], [t3]]).share_x("col").sectors(PHASES, column="phase")
Mark methods
The tables below give a cross-artist at-a-glance comparison. For each
artist's full docstring (usage examples, special behaviors, coord-specific
kwargs like arc=False), read it directly via help(c.add_line) or c.add_line?
in Jupyter — Chart.__getattr__ surfaces each artist's module docstring
on the recorder.
Every data mark is long-form: a data table plus an aes(...)
mapping from aesthetics to column names. A bare string kwarg is always
a literal; only aes(...) maps a column. Call shapes:
df = {...} # data built outside calls
c = pt.chart(df, aes(x="t", y="v")) # chart-level mapping, inherited
c.add_line() # inherits df + mapping
c.add_line(aes(color="g")) # adds a column mapping
c.add_line(aes(x="t", y="v"), color="red") # literals stay bare
c.add_line(df2, aes(x="t", y="v")) # per-call data — overrides the chart's df
c.add_line(data=df2, mapping=aes(x="t", y="v")) # fully named spelling
The primary grouping aes is color= for stroke-defaulted artists
(line, scatter, regression, density_1d, ecdf, freqpoly, rug) and
fill= for fill-defaulted ones (bar, hist, area, boxplot, violin,
strip, swarm). Bare color="red" is a literal; aes(color="col")
maps the column — one series per unique level.
line and scatter accept additional column mappings:
aes(group=) (invisible split — no color/legend burn; e.g. one polyline per
subject within a cohort) and aes(alpha=) (opacity per level, linearly
interpolated through alphas=(min, max), default (0.3, 1.0)).
line additionally accepts linestyle= — bare it's a literal dash
spec ("--", ":", "-.", …); aes(linestyle="col") cycles dashes
per level. Not on scatter since there's no line to dash.
Chart-level mappings are set once on the constructor —
pt.chart(df, aes(x=, y=, color=, fill=, group=, linestyle=)) —
and are inherited by calls that don't override them (aes(alpha=) is
per-call only). Inheritance is per-key: a mark keeps every chart-level
mapping it doesn't name itself. Literal defaults (color="red",
palette=) stay as plain chart kwargs. Other artists (boxplot, bar,
hist, etc.) accept only the relevant subset for their geometry;
chart-level mappings they don't support pass through silently.
To keep an inherited mapping off one mark: pass a constant to override
it (c.add_line(color="#333") blocks the inherited color), or
inherit_aes=False to drop all chart-level aes on that call —
ggplot2's inherit.aes=FALSE. It affects mappings only; the chart's
data is still inherited if the mark brings none.
The tables list styling kwargs; the grouping/long-form pattern is universal and not repeated.
xy and 1-D distributions
| call | options |
|---|---|
.add_line(aes(x=, y=, color=, group=, linestyle=, alpha=), **opts) |
palette, alphas=(min, max), label, linewidth, marker, size (marker radius px), curve ("linear", "step-before", "step-after", "step-mid") — bare linestyle= ("-", "--", ":", "-.", …) → fixed dash; aes(linestyle=) → cycle dashes per level. estimator="mean"\|"median" collapses replicate rows per x with a CI band (ci="t"\|"boot"\|None, level, n_boot, seed, band_alpha) — seaborn lineplot |
.add_step(aes(x=, y=), where=, **opts) |
sugar over line(curve=…); where= is "pre" / "post" (default) / "mid" |
.add_scatter(aes(x=, y=, color=, group=, alpha=, size=, style=), **opts) |
palette, alphas=(min, max), label, marker, sizes=(min, max), size_legend={"breaks": [...], "labels": [...]}, cmap, vmin, vmax, norm — aes(color=) dispatches on dtype: numeric column → cmap, categorical → palette. Bare size=: number → fixed radius (px), list → per-point; aes(size=) → graded via sizes=(lo, hi) |
.add_regression(aes(x=, y=, color=), **opts) |
palette, level=0.95, alpha=0.2, linewidth=1.8 — OLS fit + Student-t band. order= fits a polynomial; robust=True a Huber IRLS fit with a bootstrap band (n_boot=200, seed=0); lowess=True a LOWESS smoother (frac=2/3, it=3), line only — no band |
.add_hist(aes(x=, fill=, weights=), **opts) |
color (stroke), palette, bins (count or explicit edges), binwidth, binrange=(lo, hi), weights (bare sequence or aes(weights=) column — sum per bin instead of count), density, cumulative, position="overlay"\|"stack"\|"fill"\|"dodge" (multi-group layout; histtype="bar" only), histtype ("bar" / "step" / "stepfilled"), orientation |
.add_density_1d(aes(x=, color=), **opts) |
palette, bw, n_grid=200, fill=True/False, alpha — Gaussian KDE |
.add_ecdf(aes(x=, color=), **opts) |
palette, complement=False (survival), linewidth |
.add_rug(aes(x=, color=), orientation="x", **opts) |
palette, length=0.04, alpha — tick marks at observations |
.add_freqpoly(aes(x=, color=), **opts) |
palette, bins, density — line version of hist |
.add_qq(aes(sample=, color=), **opts) |
dist= accepts "normal", any scipy.stats RV, or another sample; aes(color=) → one series + reference line per level (palette=) |
Categorical distributions
| call | options |
|---|---|
.add_boxplot(aes(x=, y=, fill=), **opts) |
color (stroke), palette, orientation, notch, width, whis=1.5, flier_size |
.add_violin(aes(x=, y=, fill=), **opts) |
color (stroke), palette, inner="box"\|"quartile"\|None, trim, bw_adjust, fill_alpha |
.add_swarm(aes(x=, y=, fill=), **opts) |
color (outline), palette, size, linewidth — collision-resolved jitter |
.add_strip(aes(x=, y=, fill=), **opts) |
color (outline), palette, size, jitter — raw jittered points |
.add_pointplot(aes(x=, y=, color=), **opts) |
estimator="mean", ci="t"\|"boot"\|None, level=0.95; aes(color=) → one series per level (palette=) |
Bars, areas, errorbars
| call | options |
|---|---|
.add_bar(aes(x=, y=, fill=), position=, **opts) |
color (stroke), palette, position="stack"\|"dodge"\|"fill" for multi-series, orientation, bottom, width, gap; yerr=/xerr= (same specs as errorbar) draw whiskers at bar/slot centers with ecolor, capsize — defaults position to "dodge" and requires one row per (category, group). stat="count" (drop the y mapping; seaborn countplot) or stat="mean" (mean per cell + CI error bars: ci="t"\|"boot"\|None, level, n_boot, seed; seaborn barplot) aggregate raw rows |
.add_area(aes(x=, y=, fill=), **opts) |
multi-series stacks when given a list-of-series or aes(fill=); palette, base, curve, alpha |
.add_fill_between(aes(x=, y1=, y2=), **opts) |
fill (interior color — not color=), alpha, curve, label |
.add_errorbar(aes(x=, y=, yerr=, xerr=), **opts) |
bare yerr=/xerr= take a scalar, sequence, or (lower, upper) tuple for asymmetric bars; aes(yerr=) names a column. aes(ymin=, ymax=) (and xmin=/xmax=) map columns for absolute bounds, mutually exclusive with the matching *err=; aes(color=) → grouped series (palette=), dodged on a categorical axis with bar-matching width/gap defaults |
2-D distributions
| call | options |
|---|---|
.add_hexbin(aes(x=, y=), **opts) |
gridsize=30, cmap, vmin, vmax — hexagonal bins colored by count |
.add_hist2d(aes(x=, y=), **opts) |
bins=30, binwidth, binrange (scalar or per-axis pair), cmap, vmin, vmax — rectangular bins colored by count, empty cells transparent |
.add_kde_2d(aes(x=, y=, color=), **opts) |
bw, n_grid=60, levels, cmap, fill=True (filled level regions), alpha — iso-density contours; aes(color=) → one single-colored density per level (palette=) |
.add_contour(grid, **opts) |
levels, extent=(x0, x1, y0, y1), cmap, fill=True (mpl contourf), alpha — pre-computed 2-D grid |
.add_ridge(aes(x=, y=, color=), **opts) |
overlap=1.4, bw, alpha — joyplot; aes(color=) → overlaid sub-densities per row (palette=) |
Images, matrices, reference, shapes, text
| call | options |
|---|---|
.add_imshow(data, **opts) |
cmap (~180 vendored, default "viridis"), vmin, vmax, extent, annot, fmt, annot_color, annot_fontsize |
.add_heatmap(df, aes(x=, sector=), values=, **opts) |
Tidy input: each table row → a heatmap column (x-position from the aes(x=) column — numeric → continuous axis, string → categorical), each value column → a track row (values= selects/orders them by name, default = all columns not mapped to x/sector). A numeric x is auto-sorted (row order carries no meaning); duplicate or NaN positions raise. Opts: cmap, vmin, vmax, norm, center, palette, absent_fill, legend, annot, fmt, annot_color, annot_fontsize, linewidth, linecolor, border. aes(sector=) + c.sectors(...) draws gaps; for categorical-x clusters call c.sectors({cluster: [members]}, axis=...) — see Sectors. A bare matrix is not accepted; reshape it into a table first. |
.add_dendrogram(data, **opts) |
orientation="top"\|"left"\|"right"\|"bottom", labels, method="single"\|"complete"\|"average"\|"ward"\|... (scipy), metric, linkage_matrix=<Z> (raw scipy Z, skip clustering math), tree=<SplitTree> (skip clustering entirely), clusters=[...] (parallel grouping vector for two-level cluster), parent=True\|<frac> (render centroid tree above per-block trees). Visual gap whitespace lives on the panel as c.sectors(...) — see Sectors. |
.add_axhline(y, **opts) / .add_axvline(x, **opts) |
color, linewidth, linestyle, alpha, axes-fraction span limits (xmin/xmax on axhline, ymin/ymax on axvline) |
.add_axline(xy1, xy2) / .add_axline(xy1, slope=) |
infinite line through two points or point + slope (matplotlib axline, ggplot geom_abline), clipped to the frame; color, linewidth, linestyle, alpha, label. slope= requires linear x and y scales |
.add_axhspan(ymin, ymax, **opts) / .add_axvspan(xmin, xmax, **opts) |
color, alpha, label |
.add_hlines(ys, xmins, xmaxs, **opts) / .add_vlines(xs, ymins, ymaxs, **opts) |
data-coordinate segments (scalars broadcast against sequences); unlike axhline/axvline they participate in autoscaling; color, linewidth, linestyle, alpha, label |
.add_rect(x, y, w, h, **opts) / .add_polygon(xs, ys, **opts) / .add_polyline(xs, ys, **opts) |
data-coord shapes — polygon is closed-and-fillable, polyline is open stroke-only. rect/polygon follow the bar convention: fill= fill color ("none" for unfilled), color= outline stroke |
.add_text(df, aes(x=, y=, label=), **opts) / .add_annotate(text, xy=, xytext=, **opts) |
ha, va, fontsize, arrow=True/False |
Notes
- Grouped series get auto-labels and tab10 colors; the palette is overridable per-artist via
palette=— a name string ("Set2"), a color list, or a{category: color}dict (see Palettes). color=is the stroke andfill=is the fill — independent aesthetics. So an outlined bar isaes(fill="col"), color="black"; previously inexpressible.- When
aes(color=)andaes(linestyle=)(oraes(alpha=)) map the same column, the existing color legend swatches inherit the dash / opacity — the canonical pattern for colorblind-safe or B&W-print redundancy. - Reference lines / spans default to black, are drawn outside the data color cycle, and don't participate in autoscaling.
- On
scatter,aes(size=)maps a numeric column to per-point radius (px, rescaled intosizes=(min, max)— default(2, 7));aes(style=)cycles markers per unique value (o,s,^,v,x,+).color,group,size,style,alphaall compose. .add_imshowemits one<rect>per cell for small grids (≤10000 cells, vector-clean at any zoom) and a base64 PNG above that. On.add_heatmap, a numericxgives a continuous axis thatshare_x-aligns with a scatter/line; a stringxgives categorical bands so a top/left dendrogram pairs cleanly viashare_x/share_y(orattach_above/attach_left, which auto-share).- On both,
annot=Trueoverlays each cell's value as a text label (correlation / confusion matrices).annot=<2D array>uses custom labels — numbers formatted viafmt, strings verbatim — so labels independent of cell values (e.g. significance asterisks over a correlation cmap) are a string-array away.fmt=".2g"is the format spec (passed toformat(value, fmt)); palette-mode heatmap labels skipfmtand render verbatim (identifiers/counts, not measurements).annot_color="auto"picks black or white per cell via luminance; pass any CSS color for uniform text. - Cluster gaps. Heatmap row/column clusters are declared via
c.sectors({cluster: [members]}, axis="x" | "y")on the panel — the category scale picks up the implied split positions and inserts a 6-px (default) gap at every block boundary, and the heatmap reorders cells at draw time to match the sector cat order. Pass the same grouping info as a parallel list to.add_dendrogram(clusters=[...])and it runs scipy per block for within-block leaf order plus once more on the per-block centroids for between-block order — a two-level cluster. The dendrogram exposes the resulting leaf order viaaxis_order, so the heatmap follows automatically when both share a category axis (attach_above/attach_left).parent=Trueon the dendrogram also renders the centroid tree above the per-block trees in the same panel. Seeplotlet-cookbook/heatmaps/for the worked examples.
Clustering helpers
Independent of any artist; the built-in dendrogram builds on these, and so can any third-party tree renderer.
| call | returns | notes |
|---|---|---|
pt.linkage(data, labels=, method=, metric=) |
SplitTree |
One scipy.linkage on data; wraps as a one-block SplitTree. |
pt.linkage_split(data, split=, labels=, method=, metric=) |
SplitTree |
Two-level cluster: scipy.linkage per group (within-block) plus scipy.linkage on the per-group centroids (between-block order). |
pt.SplitTree(blocks=, between_order=, between_Z=) |
dataclass | blocks: [(Z, labels), ...] + display order + the centroid linkage. Pass as dendrogram(tree=...) to skip redundant scipy work when the same cluster drives multiple charts. |
Deeper layout helpers — layout_tree(tree), layout_parent(tree), fit_parent(...), leaf_position(...), block_apex_centers(...), parent_leaf_px(...), build_tree(...), tree_frame_defaults(...) — exist for writing new tree-shaped artists; import via from plotlet.cluster import <helper>. See EXTENDING.md.
Color shortcuts
"C0","C1", … → tab10 cycle (wraps past"C9")- Named:
"blue","orange","green","red","purple","brown","pink","gray","olive","cyan"— tab10-flavored, not CSS - Single-letter:
"b","g","r","c","m","y","k","w" - Grayscale strings:
"0"(black) –"1"(white), e.g."0.5" (r, g, b)/(r, g, b, a)tuples of floats in [0, 1]- Any hex / CSS color string passes through
Palettes
pt.palette(name)→ list of hex colors. Qualitative names (pt.list_palettes():"Set1"–"Set3","Pastel1"/"Pastel2","Dark2","Accent","Paired","tab10"/"tab20"/"tab20b"/"tab20c","colorblind") return the full palette;ntruncates or cycles past the end."colorblind"is the Okabe–Ito colorblind-safe set (8 colors, black first) — safe under deuteranopia, protanopia, and tritanopia.pt.palette(name, n)also samples any continuous colormap (pt.list_colormaps()) atnevenly-spaced points — e.g.pt.palette("viridis", 12)for 12 hex colors, no matplotlib round-trip."_r"suffix reverses either kind.- Artists'
palette=accepts a name string directly:c.add_bar(aes(..., fill="group"), palette="Set2")— alongside the existing list and{category: color}dict forms.
Datasets
pt.load_dataset(name) → dict[col, list], the shape pt.chart() accepts directly; pt.list_datasets() for names. Bundled: "penguins" (scatter/grouping), "flights" (month × year heatmap), "anscombe" (regression/facets), "tips" (categorical: bar, box, violin). Numeric columns arrive as float/int with missing values as nan; sources and citations in NOTICE.md.
User-defined colormaps
pt.register_colormap("bwr2", ["#2166ac", "white", "#b2182b"])
c.add_heatmap(df, aes(x="col"), cmap="bwr2", center=0)
- Colors interpolate linearly in RGB, evenly spaced or at explicit
stops=(positions in [0, 1], first 0 and last 1, strictly increasing). - The reversed
name + "_r"variant registers alongside; the name then works everywhere a built-in does —cmap=kwargs, colorbars,pt.palette(name, n). - Anchoring the midpoint at a data value is the norm's job: pass
center=(orvmin=/vmax=) to the artist rather than encoding data values in stops. - Registration is per-process, like
register_theme— a serialized journal naming a user colormap needs the sameregister_colormapcall before re-rendering in a fresh process. Built-in names can't be shadowed.
Faceting
g = pt.facet(df, by="species", col_wrap=3)
g.add_scatter(aes(x="bill_length", y="bill_depth"))
g.show()
pt.facet(data, by=) returns a chart-shaped recorder — same mark and
frame methods — that splits data by group, replays your calls against
each subset, and lays the panels out in a shared-scale grid (share_x /
share_y on by default). by= wraps one variable into a near-square
grid (col_wrap= fixes the column count); row= / col= lay a
two-factor grid instead (the seaborn names; ggplot's facet_grid) —
pass one form or the other, not both. Panel titles default to the group
label; forwarded chart opts (theme=, data_width=, xlabel=, …)
apply to every panel. See help(pt.facet) for details.
Inset axes
c.add_line(df, aes(x="x", y="y"))
inset = c.inset(rect=(0.55, 0.55, 0.42, 0.4), xlim=(0, 1), ylim=(0.8, 1))
inset.add_line(df, aes(x="x", y="y"))
c.inset(rect=(x, y, w, h)) returns a fresh Chart sized as a fraction
of the parent's data area (origin at the bottom-left). It has its own
scales, ticks, and frame; record artists on it normally. The parent's
to_svg embeds the inset on top of the data layer.