| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
UltraPlot 2.6.0 adds kernel density overlays to histograms, makes sticky axis
edges configurable, expands geographic legends, and improves interoperability
with Seaborn. This release also fixes shared geographic tick configuration,
refreshes the documentation experience, and hardens tag-based package releases.
snippet
import numpy as np
import ultraplot as uplt
rng = np.random.default_rng(51423)
data = rng.normal(size=(500, 3)) + np.arange(3)
fig, ax = uplt.subplots(refwidth=4)
ax.hist(
data,
bins=20,
kde=True,
kde_kw={"bw_method": "silverman", "linewidth": 2},
labels=("A", "B", "C"),
legend="ur",
)
ax.format(xlabel="value", ylabel="count")Configurable sticky edges: The new axes.sticky_edges rc setting and
per-axes use_sticky_edges property control whether lines, fills, and similar
artists meet the axes bounds without automatic padding. This keeps the useful
default while making it easy to restore margins globally or for one axes
(#796).
import ultraplot as uplt
uplt.rc["axes.sticky_edges"] = False
fig, axs = uplt.subplots(ncols=2)
axs[0].plot([0, 1], [0, 1])
# Override the global setting for an individual axes.
axs[1].use_sticky_edges = True
axs[1].plot([0, 1], [0, 1])Line entries in geographic legends: Geographic legends now accept line
symbols alongside the existing point and area symbols (#783).
Better Seaborn legend compatibility: UltraLegend now implements
remove(), and UltraPlot supplies the compatibility hooks expected by
seaborn.move_legend. Legends created by Seaborn inside ax.external() can
therefore be moved or removed normally (#793).
import seaborn as sns
import ultraplot as uplt
fig, ax = uplt.subplots()
with ax.external():
sns.histplot(data, ax=ax, kde=True, legend=True)
sns.move_legend(ax, "upper right")Full Changelog: v2.5.0...v2.6.0
In UltraPlot 2.5.0, we introduce the Hawkeye feature for GeoAxes, a new automatic text-alignment feature, and the ability to change latex fonts with more control.
snippet
import ultraplot as uplt
singapore = (103.8198, 1.3521)
fig, ax = uplt.subplots(proj="robin", refwidth=4)
ax.format(land=True, landcolor="gray8", oceancolor="blue9")
ax.plot(*singapore, marker="o", color="red", ms=5, transform="cyl")
ax.text(106, 4, "Singapore", color="red", size=7, transform="map")
# A circular locator map anchored to the upper-right corner
inax = ax.hawkeye(
(0.97, 0.97),
size=0.23,
anchor="ur",
proj="merc",
extent=(103.76, 103.90, 1.27, 1.41),
shape="circle",
target="circle",
connector="line",
color="red",
indicator_kw={"linewidth": 1.5},
)
inax.format(land=True, landcolor="gray9", oceancolor="blue9")
inax.plot(*singapore, marker="o", color="red", ms=5, transform="cyl")
snippet
import ultraplot as uplt
fig, ax = uplt.subplots(proj="robin", refwidth=4)
ax.format(land=True, landcolor="gray8", oceancolor="blue9")
# `shape` controls the inset frame; `target` controls the indicator on the parent
inax = ax.hawkeye(
(0.97, 0.97), size=0.23, anchor="ur", proj="merc",
extent=(103.76, 103.90, 1.27, 1.41),
shape="circle", target="circle", connectors="line",
)
inax.format(land=True, landcolor="gray9", oceancolor="blue9")
snippet
import ultraplot as uplt
fig, ax = uplt.subplots()
ax.scatter(x, y)
for xi, yi, name in zip(x, y, names):
ax.text(xi, yi, name)
# Relax the labels apart; `arrows=True` connects moved labels to their points
ax.auto_align_text(arrows=True)
snippet
import ultraplot as uplt
expr = r"$\mathcal{ABCXYZ}\quad\sum_{i=0}^{n}\quad\prod_{j=1}^{m}\quad\int_a^b\quad\oint_C$"
uplt.rc["mathtext.cm_symbols"] = True
fig, ax = uplt.subplots(refwidth=6, refheight=1.1)
ax.text(0.02, 0.5, expr, transform="axes", va="center", fontsize=24)
ax.format(title="Computer Modern math symbols", titleloc="left")
snippet
import ultraplot as uplt
layout = [[1, 1, 1, 2, 2, 2], [3, 3, 4, 4, 5, 5]]
fig, axs = uplt.subplots(layout, refwidth=2.4, proj={4: "cyl"}, share=False)
axs[3].format(
lonlim=(0, 1),
latlim=(0, 1),
abcanchor="slot", # align the map's a-b-c label with the Cartesian slots
)
fig.format(abc="A.", abcloc="left")Shared row/column label spacing: When figure-level row or column labels and
a shared spanning axis label sit on the same side, the row/column labels are
now placed nearer the axes and the spanning label outside them. The gap is
controlled by the new leftlabel.sharedpad, rightlabel.sharedpad,
bottomlabel.sharedpad, and toplabel.sharedpad settings, which can also be
passed to format (e.g. fig.format(leftlabelsharedpad='2em')).
import ultraplot as uplt
fig, axs = uplt.subplots(ncols=2, nrows=2, share=True, span=True)
fig.format(
leftlabels=("Row A", "Row B"),
ylabel="shared y label",
leftlabelsharedpad="2em", # gap between the row labels and the spanning label
)Keyword-alias cleanup (_alias_kwargs): Extracted the keyword/alias
resolution helpers out of the internals grab-bag into a dedicated
internals/kwargs.py, and added an @_alias_kwargs decorator that folds
synonym keywords into their canonical names with the same precedence and
conflict warning as the old _not_none boilerplate. Figure.__init__ is the
first adopter. As a user-visible upshot, the shared style docstrings (line,
patch, pcolor/contour, text) now lead each numpydoc field with the canonical
parameter name instead of a pile of aliases, making the parameter tables much
easier to scan.
Maintenance release. Bug fixes and internal restructuring; no new public API.
Fixes
Internal
Release plumbing
Full Changelog: v2.4.0...v2.4.1
models = ("Control", "Physics A", "Physics B", "Ensemble")
correlation = np.array([0.73, 0.84, 0.91, 0.96])
stddev = np.array([0.82, 1.18, 1.05, 0.93])
colors = ("blue7", "orange7", "green7", "violet7")
fig, ax = uplt.subplots(proj="taylor", refwidth=4.2)
ax.format(
title="Model skill summary",
xlabel="Standard deviation",
ylabel="",
corrlabel="Correlation",
rlim=(0, 1.5),
rlines=0.25,
corrlines=(1, 0.95, 0.9, 0.8, 0.6, 0.4, 0.2, 0),
)
# Centered RMS-difference contours around the reference point at (corr=1, std=1).
theta = np.linspace(0, np.pi / 2, 160)
radius = np.linspace(0, 1.5, 160)
theta_grid, radius_grid = np.meshgrid(theta, radius)
rms = np.sqrt(1 + radius_grid**2 - 2 * radius_grid * np.cos(theta_grid))
contours = ax.contour(
theta_grid,
radius_grid,
rms,
levels=(0.25, 0.5, 0.75, 1.0, 1.25),
cmap="tokyo",
lw=0.9,
ls="--",
)
ax.clabel(contours, levels=(0.5, 1.0), inline=True, fontsize=8, fmt="%.1f")
ax.plot_corr(1, 1, marker="*", markersize=12, color="red7", label="Reference")
for name, corr, std, color in zip(models, correlation, stddev, colors):
ax.scatter_corr(
corr,
std,
s=75,
color=color,
edgecolor="white",
lw=0.8,
zorder=4,
label=name,
)
ax.legend(loc="b", ncols=3, frame=False)
fig.show()
Side-Attached Inset Colorbars: Enabled colorbars to attach to the sides of inset axes.
import ultraplot as uplt
fig, ax = uplt.subplots()
inset = ax.inset([0.5, 0.5, 0.4, 0.4])
# Attach colorbar directly to the side of the inset axes rather than standard subplot panels
inset.colorbar(mappable, loc="right", label="Value")Axes Styling Enhancements:
Full Changelog: v2.3.0...v2.4.0
Full Changelog: v2.3.0...v2.4.0
This release introduces significant enhancements to the semantic legend system, improved geographic plotting formatting, and various bug fixes and performance improvements.
The semantic legend system has been unified and expanded. You can now create legends from semantic mappings with even more control over marker styles, including custom paths, CapStyle, JoinStyle, and arbitrary transforms.
Example: Custom Marker Stylesimport matplotlib.transforms as mtransforms
import numpy as np
from matplotlib.markers import CapStyle, JoinStyle, MarkerStyle
from matplotlib.path import Path
import ultraplot as uplt
star = Path.unit_regular_star(6)
circle = Path.unit_circle()
star_path = Path.unit_regular_star(5)
cut_star = Path(
vertices=np.concatenate([circle.vertices, star.vertices[::-1, ...]]),
codes=np.concatenate([circle.codes, star.codes]),
)
fig, ax = uplt.subplots()
# upper left legend with custom mark
ax.catlegend(
["star", "cus_star"],
marker=[star_path, cut_star],
markersize=10,
add=True,
loc="ul",
title="Paths",
ncols=1,
)
# upper right legend with advanced CapStyle and JoinStyle
ax.catlegend(
["butt / round", "round / miter", "projecting / bevel"],
marker="1",
markersize=10,
markeredgecolor=list("gbr"),
markeredgewidth=4,
markerfacecoloralt="none",
marker_capstyle=[
CapStyle.butt,
CapStyle.round,
CapStyle.projecting,
],
marker_joinstyle=[
JoinStyle.round,
JoinStyle.miter,
JoinStyle.bevel,
],
marker_transform=[mtransforms.Affine2D().rotate_deg(x) for x in [0, 30, 60]],
title="Cap & Join Style",
add=True,
loc="ur",
ncols=1,
)
# center geolegend with different styles
ax.geolegend(
["rect", "tri", "hex", "AU"],
facecolor=["tab:red", "r", "k", "tab:blue"],
ec=["k", "g", "orange", "bright pink"],
loc="c",
title="geolegend",
ew=[0.5, 2, 1, 0.5],
markersize=10,
ncols=4,
handletextpad=0.1,
columnspacing=0.7,
)
# lower left legend with TeX symbols and rotation transform
ax.catlegend(
["\\infty", "\\sum", "\\int"],
marker=[r"$\infty$", r"$\sum$", r"$\int$"],
s=[6, 18, 9], # ms/markersize=[6,8,10]
title="TeX symbols\nwith rotation",
marker_transform=[mtransforms.Affine2D().rotate_deg(x) for x in [30, 90, 45]],
add=True,
loc="ll",
ncols=1,
)
# lower right legend with different fill style
ax.catlegend(
["top", "bottom", "left", "right"],
marker="o",
markersize=10,
mfc=["r", "g", "b", "c"],
markerfacecoloralt="lightsteelblue",
markeredgecolor=["k", "r", "y", "b"],
fillstyle=["top", "bottom", "left", "right"],
title="Half filled",
add=True,
loc="lr",
ncols=1,
)
ax.axis("off")
fig.show()
Fixed an issue where geographic grid label styling options (like labelsize) were silently ignored when formatting through SubplotGrid.format() or Figure.format().
Example: Geographic Formattingimport ultraplot as uplt
import cartopy.crs as ccrs
fig, axs = uplt.subplots(proj="merc", ncols=2)
# styling labelsize now works correctly through Figure.format
fig.format(
labels=True,
labelsize=14,
labelweight="bold",
grid=True,
coast=True
)
fig.show()
Polar axes now support curved polar-aware axis labels via thetalabel and rlabel. These labels follow the outer theta arc or a radial spoke, respect sector and annular layouts, and stay correctly offset under theta transforms and redraws. This work also finishes the removal of generic x/y label handling from polar formatting.
Example: Polar Axis Labelsimport ultraplot as uplt
fig, ax = uplt.subplots(proj="polar")
ax.format(
thetalim=(0, 120),
rlim=(0.3, 1.0),
thetalabel="Azimuth",
rlabel="Radius",
thetalabelloc=60,
rlabelloc="left",
)
fig.show()Various bug fixes including resolved int/list size errors in bar plots and consistent style application ordering.
Example: Bar Plot fix for pandas Seriesimport ultraplot as uplt
import pandas as pd
import numpy as np
data = pd.Series(np.random.rand(5), index=list("abcde"))
fig, ax = uplt.subplots()
ax.bar(data, color="blue7") # Previously might trigger size error
ax.format(title="Fixed Pandas Series Bar Plot")
fig.show()Full Changelog: v2.2.0...v2.3.0
Colorbars can now span a specific range of columns or rows using the span parameter, rather than stretching across the entire figure edge. This gives much finer control over colorbar placement in multi-panel figures.
Example
import ultraplot as uplt
import numpy as np
rng = np.random.default_rng(42)
data = rng.random((20, 20))
fig, axs = uplt.subplots(nrows=2, ncols=3, share=False)
for ax in axs:
m = ax.pcolormesh(data, cmap="batlow")
# A single colorbar spanning only the first two columns
fig.colorbar(m, loc="bottom", span=(1, 2), label="Shared metric")
axs.format(
suptitle="Spanning colorbar across selected columns",
abc="[a.]",
grid=False,
)Norms can now be specified as strings alongside vmin/vmax kwargs, or as compact tuple/list specs like ('linear', 0.1, 0.9). Previously, passing a string norm with explicit vmin/vmax raised an error.
Example
import ultraplot as uplt
import numpy as np
rng = np.random.default_rng(0)
data = rng.random((30, 30))
fig, axs = uplt.subplots(ncols=3, share=False)
# String norm with explicit vmin/vmax kwargs
axs[0].pcolormesh(data, norm="linear", vmin=0.2, vmax=0.8, cmap="fire")
axs[0].format(title="String + vmin/vmax")
# Tuple form bundles everything together
axs[1].pcolormesh(data, norm=("linear", 0.2, 0.8), cmap="fire")
axs[1].format(title="Tuple form")
# Works with log norms too
axs[2].pcolormesh(data + 0.01, norm=("log", 0.01, 1), cmap="fire")
axs[2].format(title="Log tuple form")
axs.format(suptitle="Flexible norm specifications", abc="[a.]", grid=False)Disabling titleborder=False now correctly removes the stroke effect from title text. Previously, calling ax.format(titleborder=False) after a title border had been applied would leave the border visible.
Example
import ultraplot as uplt
import numpy as np
rng = np.random.default_rng(0)
fig, axs = uplt.subplots(ncols=2)
for ax in axs:
ax.pcolormesh(rng.random((20, 20)), cmap="batlow")
# Left: border on (default for inset titles)
axs[0].format(title="With border", titleloc="upper left", titleborder=True)
# Right: border explicitly off — now correctly removed
axs[1].format(title="Without border", titleloc="upper left", titleborder=False)
axs.format(suptitle="Title border toggle fix", grid=False)Adding an outer legend (loc='r') no longer suppresses y-tick labels on neighboring axes when using sharey='labs'. The hidden panel backing the legend was incorrectly being counted as a sharing participant.
Example
import ultraplot as uplt
import numpy as np
x = np.linspace(0, 4 * np.pi, 200)
fig, axs = uplt.subplots(ncols=3, sharey="labs")
for i, ax in enumerate(axs):
for j in range(3):
ax.plot(x, np.sin(x + j) * (i + 1), label=f"Wave {j+1}")
# Outer legend on the middle panel — y-tick labels stay visible on all axes
axs[1].legend(loc="r")
axs.format(
suptitle="Outer legend with shared y-labels",
xlabel="Phase",
ylabel="Amplitude",
abc="[a.]",
)Full Changelog: v2.1.9...v2.2.0
Full Changelog: v2.1.9...v2.2.0
With v2.1.9 we add nan support for curved_quiver, and allow for using axes slicing to set titles.
We intend to enhance capabilities to offer strong and emphatic controls to the user. The format method gives a succinct localized entry point to format matplotlib axes. We extend the functionality that we added to colorbars and legend by now allowing titles to be spannend across subgroupings.
snippet
import ultraplot as uplt
fig, ax =uplt.subplots(ncols = 3, nrows = 2)
ax[0, :2].format(title = "Hello world!")
fig.show()Full Changelog: v2.1.5...v2.1.9
Full Changelog: v2.1.8...v2.1.9
The biggest additions are richer semantic size legends and first-class
choropleth support for geographic axes, alongside typing, plotting, CI, and
documentation improvements.
sizelegend can now describe marker magnitudes in domain language instead of
just echoing the raw numeric levels.
Snippet
import numpy as np
import ultraplot as uplt
np.random.seed(42)
cities = [
"Tokyo",
"Delhi",
"Shanghai",
"Sao Paulo",
"Mumbai",
"Cairo",
"Beijing",
"Dhaka",
"Osaka",
"Lagos",
"Istanbul",
"London",
]
population = np.array(
[37.4, 32.9, 29.2, 22.4, 21.7, 21.3, 20.9, 23.2, 19.1, 16.6, 15.8, 9.5]
)
gdp_pc = np.array([42, 8, 23, 12, 7, 4, 22, 3, 38, 3, 14, 55])
growth = np.array([0.2, 2.8, 0.5, 0.7, 1.1, 1.9, 0.4, 3.1, 0.1, 3.5, 1.4, 0.8])
fig, ax = uplt.subplots(refwidth=4.5, refaspect=1.1)
ax.scatter(
gdp_pc,
growth,
s=population * 12,
c="cherry red",
edgecolor="gray8",
linewidth=0.5,
alpha=0.85,
absolute_size=True,
)
for i, city in enumerate(cities):
offset = (5, 5)
if city == "Osaka":
offset = (5, -10)
elif city == "Beijing":
offset = (-5, 8)
ax.annotate(
city,
(gdp_pc[i], growth[i]),
fontsize=6,
textcoords="offset points",
xytext=offset,
color="gray8",
)
ax.sizelegend(
[10 * 12, 20 * 12, 35 * 12],
labels={10 * 12: "10M", 20 * 12: "20M", 35 * 12: "35M"},
title="Population",
loc="ur",
frameon=False,
color="gray6",
edgecolor="gray8",
)
ax.format(
title="Megacities: Wealth vs Growth",
xlabel="GDP per capita (k USD)",
ylabel="Annual growth rate (%)",
xgrid=True,
ygrid=True,
xlim=(-2, 62),
ylim=(-0.3, 4.2),
)
fig.show()You can now color countries and polygon features directly from numeric values
while keeping the same UltraPlot formatting and colorbar workflow used on
cartesian plots.
Snippet
import numpy as np
import ultraplot as uplt
values = {
"United States of America": 83.6,
"Canada": 81.7,
"Mexico": 75.1,
"Brazil": 75.9,
"Argentina": 76.7,
"United Kingdom": 81.0,
"France": 82.5,
"Germany": 80.9,
"Italy": 83.5,
"Spain": 83.4,
"Norway": 83.2,
"Sweden": 83.0,
"Russia": 73.2,
"China": 78.2,
"Japan": 84.8,
"South Korea": 83.7,
"India": 70.8,
"Australia": 83.3,
"New Zealand": 82.1,
"South Africa": 64.9,
"Nigeria": 53.9,
"Egypt": 72.1,
"Saudi Arabia": 76.5,
"Turkey": 76.0,
"Indonesia": 71.9,
"Thailand": 78.7,
}
fig, ax = uplt.subplots(proj="merc", proj_kw={"lon0": 10}, refwidth=5.5)
m = ax.choropleth(
values,
country=True,
cmap="Glacial",
vmin=50,
vmax=88,
edgecolor="none",
linewidth=0,
colorbar="b",
colorbar_kw={"label": "Life expectancy (years)", "length": 0.7},
missing_kw={"facecolor": "gray8", "hatch": "///", "edgecolor": "gray5"},
)
ax.format(
title="Global Life Expectancy (2023)",
land=True,
landcolor="gray2",
ocean=True,
oceancolor="gray1",
coast=True,
coastcolor="gray4",
coastlinewidth=0.3,
borders=True,
borderscolor="gray4",
borderslinewidth=0.2,
longrid=False,
latgrid=False,
)
fig.show()Full Changelog: v2.1.3...v2.1.5
Full Changelog: v2.1.3...v2.1.5
This is a small patch release focused on plotting and legend fixes.
Restored frame / frameon handling for colorbars.
Outer colorbars now again respect frame as a backwards-compatible alias for outline visibility, and inset colorbars no longer fail during layout reflow when frame=False.
Preserved hatching in geometry legend proxies.
Legends generated from geographic geometry artists now carry hatch styling through to the legend handle, alongside facecolor, edgecolor, linewidth, and alpha.
Enabled graph plotting on 3D axes.
This restores graph plotting support for 3D plots.
Full Changelog: V2.1.2...v2.1.3
Full Changelog: V2.1.0...V2.1.2
| Back | FazBrowse Home | New Git URL |