❄️ Happy holidays from UltraPlot! #438
cvanelteren
announced in
Announcements
Replies: 2 comments
-
snippetimport cartopy.crs as ccrs
import cartopy.feature as cfeature
import numpy as np
import xarray as xr
import ultraplot as uplt
def main():
uplt.rc["formatter.log"] = True
layout = [[1, 1, 1], [2, 3, 4]]
fig, axs = uplt.subplots(
layout,
share=0,
figsize=(10, 8),
proj=["cyl", "cartesian", "cartesian", "cartesian"],
)
axs.format(abc=True, abcloc="ul")
# Curved quiver on a geo wind map.
u_url = (
"https://psl.noaa.gov/thredds/dodsC/Datasets/"
"ncep.reanalysis/surface/uwnd.sig995.2025.nc"
)
v_url = (
"https://psl.noaa.gov/thredds/dodsC/Datasets/"
"ncep.reanalysis/surface/vwnd.sig995.2025.nc"
)
ds_u = xr.open_dataset(u_url)
ds_v = xr.open_dataset(v_url)
ds = xr.merge([ds_u, ds_v])
ds = ds.assign_coords(lon=((ds.lon + 180) % 360) - 180).sortby("lon")
when = "2025-01-15T12:00"
lon_bounds = (-160, -60)
lat_bounds = (15, 60)
dsi = ds.sel(
time=when,
lat=slice(lat_bounds[1], lat_bounds[0]),
lon=slice(lon_bounds[0], lon_bounds[1]),
)
U = dsi["uwnd"].coarsen(lat=2, lon=2, boundary="trim").mean()
V = dsi["vwnd"].coarsen(lat=2, lon=2, boundary="trim").mean()
if U.lat.values[0] > U.lat.values[-1]:
U = U.sortby("lat")
V = V.sortby("lat")
speed = np.hypot(U, V)
lon1d = U.lon.values
lat1d = U.lat.values
U2d = U.values
V2d = V.values
def make_cell_center_seed_points(x1d, y1d, grains=8):
if x1d.size < 2 or y1d.size < 2:
eps = 1e-9
xs = np.linspace(
float(x1d.min()) + eps, float(x1d.max()) - eps, max(1, grains)
)
ys = np.linspace(
float(y1d.min()) + eps, float(y1d.max()) - eps, max(1, grains)
)
Xs, Ys = np.meshgrid(xs, ys)
return np.c_[Xs.ravel(), Ys.ravel()]
xcent = 0.5 * (x1d[:-1] + x1d[1:])
ycent = 0.5 * (y1d[:-1] + y1d[1:])
nx = int(np.clip(grains, 1, xcent.size))
ny = int(np.clip(grains, 1, ycent.size))
xi = np.linspace(0, xcent.size - 1, nx).astype(int)
yi = np.linspace(0, ycent.size - 1, ny).astype(int)
Xs, Ys = np.meshgrid(xcent[xi], ycent[yi])
return np.c_[Xs.ravel(), Ys.ravel()]
cq_seeds = make_cell_center_seed_points(lon1d, lat1d, grains=20)
ax_geo = axs[0]
ax_geo.set_extent(
[lon_bounds[0], lon_bounds[1], lat_bounds[0], lat_bounds[1]],
crs=ccrs.PlateCarree(),
)
cq = ax_geo.curved_quiver(
lon1d,
lat1d,
U2d,
V2d,
color=speed.values,
cmap="viko",
arrow_at_end=True,
arrowsize=2,
grains=40,
scale=20,
start_points=cq_seeds,
transform=ccrs.PlateCarree(),
linewidth=3.5,
)
# Add simple map context
ax_geo.format(
title="Near-surface winds",
lonlabels=True,
latlabels=True,
land=True,
coast=True,
ocean=True,
oceancolor="ocean blue",
landcolor="mushroom",
)
# Beeswarm plot.
rng = np.random.default_rng(2)
n_points, n_cats = 40, 4
levels = np.tile(np.arange(n_cats, dtype=float), (n_points, 1))
data = rng.normal(loc=np.arange(n_cats) * 1.5, scale=0.6, size=(n_points, n_cats))
feature_values = rng.standard_normal(size=(n_points, n_cats))
axs[1].beeswarm(
data,
levels=levels,
feature_values=feature_values,
ss=22,
orientation="vertical",
cmap="viko",
colorbar="lr",
colorbar_kw=dict(title="SHAP value"),
discrete=False,
)
axs[1].format(
title="Beeswarm (SHAP-style)",
xlabel="Group",
ylabel="Value",
xticks=np.arange(n_cats),
xticklabels=["A", "B", "C", "D"],
)
# Lollipop graph.
labels = ["Atlas", "Nova", "Sol", "Lumen", "Echo"]
x = np.arange(len(labels))
y = np.array([2.2, 5.4, 3.1, 6.2, 4.5])
cmap = uplt.Colormap("set3")
colors = cmap(np.linspace(0.1, 0.9, len(x)))
axs[2].lollipop(
x,
y,
width=0.6,
color=colors,
marker="o",
s=140,
edgecolor="k",
linewidth=1.5,
stemwidth=2.4,
stemcolor="gray6",
)
axs[2].format(
title="Lollipop",
xlabel="Series",
ylabel="Score",
xticks=x,
xticklabels=labels,
ygrid=True,
ygridcolor="gray9",
)
# CFTime axis with log-scaled values.
time = xr.cftime_range("2024-01-01", periods=49, freq="30D", calendar="360_day")
values = np.geomspace(1, 1e4, time.size)
axs[3].plot(time, values, lw=2)
axs[3].format(
title="CFTime + log y",
yscale="log",
xlabel="Date (360_day)",
ylabel="Magnitude",
)
fig.colorbar(cq.lines, ax=ax_geo, label="Near-surface wind speed (m/s)")
fig.format(suptitle="UltraPlot 2025 Showcase")
fig.savefig("showcase2025.png")
uplt.show()
if __name__ == "__main__":
main() |
Beta Was this translation helpful? Give feedback.
0 replies
-
|
UltraPlot has become part of my daily workflow. |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
It’s been a huge year of steady, focused development. Since January last year we went from 0 to 241 stars, crossed 134k+ downloads, and pushed test coverage to ~80%. Thank you to everyone who tried the library, filed issues, and helped shape the roadmap.
What shipped this year
curved_quiver,beeswarm,lollipop graphs,network plotting.Where we’re going in 2026
Thank you for a great year of building. Here’s to an even sharper UltraPlot in 2026. 🥂
Beta Was this translation helpful? Give feedback.
All reactions