This release focuses on two user-facing improvements: a new ridgeline plot type and more flexible figure-level legend placement.
Under the hood, import-time work shifted from eager loading to lazy loading,
cutting startup overhead by about 98%.
Highlights
- Ridgeline (joyplot) support for stacked distribution comparisons.
- Figure-level legends now accept
ref=for span inference and consistent placement. - External context mode for integration-heavy workflows where UltraPlot should
defer on-the-fly guide creation. - New Copernicus journal width presets to standardize publication sizing.
- Faster startup via lazy-loading of top-level imports.
snippet
from pathlib import Path
import numpy as np
import ultraplot as uplt
outdir = Path("release_assets/v1.71.0")
outdir.mkdir(parents=True, exist_ok=True)Ridgeline plots
Ridgeline plots (joyplots) are now built-in. This example uses KDE ridges with
a colormap and overlap control.
snippet
rng = np.random.default_rng(12)
data = [rng.normal(loc=mu, scale=0.9, size=1200) for mu in range(5)]
labels = [f"Group {i + 1}" for i in range(len(data))]
fig, ax = uplt.subplots(refwidth="11cm", refaspect=1.6)
ax.ridgeline(
data,
labels=labels,
cmap="viridis",
overlap=0.65,
alpha=0.8,
linewidth=1.1,
)
ax.format(
xlabel="Value",
ylabel="Group",
title="Ridgeline plot with colormap",
)
fig.savefig(outdir / "ridgeline.png", dpi=200)Figure-level legend placement with ref=
Figure legends can now infer their span from a reference axes or axes group.
This removes the need to manually calculate span, rows, or cols for many
layouts.
snippet
x = np.linspace(0, 2 * np.pi, 256)
layout = [[1, 2, 3], [1, 4, 5]]
fig, axs = uplt.subplots(layout)
cycle = uplt.Cycle("bmh")
for idx, axi in enumerate(axs):
axi.plot(
x,
np.sin((idx + 1) * x),
color=cycle.get_next()["color"],
label=f"sin({idx+1}x)",
)
axs.format(xlabel="x", ylabel=r"sin($\alpha x)")
# Place legend of the first 2 axes on the bottom of the last plot
fig.legend(ax=axs[:2], ref=axs[-1], loc="bottom", ncols=2, frame=False)
# Place legend of the last 2 plots on the bottom of the first column
fig.legend(ax=axs[-2:], ref=axs[:, 1], loc="left", ncols=1, frame=False)
# Collect all labels in a singular legend
fig.legend(ax=axs, loc="bottom", frameon=0)
fig.savefig(outdir / "legend_ref.png", dpi=200)