Skip to content

Releases: Ultraplot/UltraPlot

UltraPlot v2.5.0: Hawkeye, text-alignment, and latex fonts

Choose a tag to compare

@cvanelteren cvanelteren released this 22 Jul 23:15
c3d1e2d

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.

New Features

  • Hawkeye map insets (GeoAxes.hawkeye): Added a geographic callout inset
    that draws attention to a region of a map without inheriting the parent
    projection's aspect. Regular insets follow the parent projection, so Cartopy
    stretches them to whatever the projection dictates; a hawkeye instead lets you
    request a square — or circular — locator map, anchored anywhere on the parent
    axes, with an automatically drawn indicator box and optional connectors.
    Hawkeyes are excluded from automatic layout, so they can extend past the parent
    axes without reserving subplot space, and the returned object is an ordinary
    GeoAxes you can draw external geospatial data into.
hawkeye_preview
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")
  • Circular and aspect-aware insets: Generalized inset support so geographic
    insets can use a circular frame while still preserving projection scale. A new
    aspect-aware locator keeps a circular or square inset anchored to its
    lower-left corner after the box-aspect adjustment, so callout maps stay put
    under resizing. Rectangular frames preserve both the requested extent and the
    projection scale; circular frames expand the shorter projected dimension to
    keep the projection faithful (pass aspect='auto' to instead fit the exact
    extent with distortion).
circular_inset_preview
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")
  • Automatic text alignment (Axes.auto_align_text): Added a KD-tree-based
    relaxation solver that repositions text and annotations so they stop
    overlapping each other, the plotted data, and the axes edges, then pulls them
    back toward where you put them. Because it runs at draw time, the layout stays
    valid across resizing and changing data limits. Opt individual labels in with
    avoid_overlap=True, enable it globally with the new text.align rc setting,
    and tune it with text.align.pad, text.align.maxiter, and
    text.align.arrows (which draws a connector back to each displaced label).
text_align_preview
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)
  • Computer Modern math symbols (mathtext.cm_symbols): Added a middle ground
    between font-matched math and full text.usetex. With the setting on,
    ordinary letters and numbers keep the active document font, while \mathcal
    routes through cmsy10 and big operators (\sum, \prod, \int, \oint,
    \bigcup, \bigoplus) route through cmex10 for an authentic Computer Modern
    look — no external LaTeX required. Math is parsed when the figure is drawn,
    so set this globally rather than inside a context block.
cm_symbols_preview
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")
  • Geographic axes in mixed subplot layouts (abcanchor + geo aspect): Gave
    map users explicit control over the fixed-aspect-vs-slot trade-off that arises
    when a map shares a GridSpec with Cartesian axes. The new abcanchor option
    chooses whether an a-b-c label attaches to the visible map boundary
    ('axes', the default) or to the original GridSpec slot ('slot', useful for
    a regular label grid across mixed subplot types). Maps can also be stretched to
    fill their slot with aspect='auto', or made the figure's layout reference so
    the whole figure resizes around them.
geo_layout_preview
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')).

    snippet
    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.

Bug Fixes

  • Title centering: Fixed the horizontal centering of titles (#766).
  • ListedColormap deprecation: Fixed a ListedColormap N deprecation
    warning from newer matplotlib (#769).
  • Cross-product deprecation: Fixed a deprecation in the cross-product
    computation (#777).

Maintenance & Internals

  • Deprecation cleanup: Removed deprecated items and unused imports (#763).
  • Docs: Simplified the docs and expanded the insets, projections, fonts, and
    subplots guides with the new features above.
  • History hygiene: Reverted a set of misplaced hawkeye commits from main
    before re-landing the feature cleanly (#772).

Commits

  • 83b0e73c8 [Feature] Add Hawkeye option (#771)
  • ceab2bfc4 Add circular inset options
  • 9175fafab [Feature] Text alignment (#754)
  • fa26deece Route selected mathtext glyphs to Computer Modern (#744)
  • 384a6259f [Feature] Abc anchor and Geo aspect (#767)
  • 8b174295b Reorder visual hierarchy when side-cap labels are given (#765)
  • 1be6eeacd Refactor/alias kwargs (#775)
  • 7a5ddfb5e Fix centering of titles (#766)
  • 4ab628987 Fix ListedColormap N deprecation (#769)
  • c3d1e2d7b Deprecation fix for cross product computation. (#777)
  • 410de735d Remove unused imports (#763)
  • 50cbc622f Remove deprecated items
  • 6e39447fc Simplify docs
  • d26943592 Revert misplaced hawkeye commits from main (#772)

What's Changed

Read more

UltraPlot v2.4.1: figure state fixes and subplot manager groundwork

Choose a tag to compare

@cvanelteren cvanelteren released this 14 Jul 04:01
317db84

Maintenance release. Bug fixes and internal restructuring; no new public API.

Fixes

  • Figure.clear() no longer leaves stale state behind. A cleared figure kept handing out destroyed axes via subplotgrid and _iter_axes, held on to the old gridspec and subplot counter, leaked figure panels, and raised AttributeError from the next format(suptitle=...). clear() (and its clf() alias) now resets subplots, panels, layout flags, and the figure-level label artists, so a cleared figure is reusable. (#760)
  • Sensible defaults for missing font symbols. (#752)

Internal

  • Subplot creation, gridspec ownership, and projection parsing moved out of Figure into a dedicated SubplotManager, the first step toward Figure as a thin interface over focused collaborators (#677). Public API is unchanged, but the private Figure._subplot_dict, _subplot_counter, and _gridspec attributes are gone — use Figure.subplotgrid / Figure.gridspec instead. (#759, #698)
  • ultraplot.ui now derives projection keywords from SubplotManager, fixing uplt.subplot(proj=...) silently routing the projection to the figure. (#760)

Release plumbing

  • Zenodo archiving is handled by the Zenodo GitHub integration; the version and DOI are no longer hand-maintained in CITATION.cff. (#761)

What's Changed

Full Changelog: v2.4.0...v2.4.1

Taylor diagrams, inset colorbars, improved semantic legends

Choose a tag to compare

@cvanelteren cvanelteren released this 04 Jul 14:18
0dcfee9

UltraPlot v2.4.0

New Features

  • Taylor Diagram Projection (TaylorAxes): Added a brand new polar-style axes projection for Taylor diagrams (proj='taylor').
    • Implemented helper plotting methods: plot_corr and scatter_corr to plot points using correlation coefficient and standard-deviation coordinates.
    • Added documentation guide, examples gallery, and integration test coverage.
Code Snippet
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()
taylor_diagram_preview
  • Side-Attached Inset Colorbars: Enabled colorbars to attach to the sides of inset axes.

    • Side colorbar requests now map dynamically to side-appropriate default orientations relative to the inset.
    • Added support for stacking and aligned placement similar to standard axes colorbars.
    Code Snippet
    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:

    • Allowed axesec/axesedgecolor and axeslw/axeslinewidth aliases to control global axes boundary/frame styling.
    • Added the parameter mapping size/sizes aliases to scatter-plot s parameter for matching collection interfaces.

Bug Fixes

  • Frame Style Retention: Preserved explicit axes frame styling across layout reformatting passes.
  • Title Space Calculation: Fixed space reserving calculations for external container titles (ExternalAxesContainer) to prevent unwanted overlapping when using ABC-style sub-labels.
  • Legend Handle Formatting: Fixed single-point Line2D handlers in legends to correctly hide line connectors for marker-only plots.
  • Single Axis Title Sharing: Prevented alignment crashes on figures when sharing titles for single Cartesian/Geographic axis systems.

Maintenance & Internals

  • Actions updates: Bumped actions/checkout, actions/cache, and codecov/codecov-action in CI.
  • Metadata: Cleaned up styling/formatting in CITATION.cff.

What's Changed

Full Changelog: v2.3.0...v2.4.0

What's Changed

Full Changelog: v2.3.0...v2.4.0

UltraPlot v2.3.0: Enhanced semantic legends, improved polar labels placements, and geo label fixing

Choose a tag to compare

@cvanelteren cvanelteren released this 10 Jun 03:58
06e4552

This release introduces significant enhancements to the semantic legend system, improved geographic plotting formatting, and various bug fixes and performance improvements.

Enhanced Semantic Legends

image

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 Styles
import 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()

Geographic Plotting Improvements

r2

Fixed an issue where geographic grid label styling options (like labelsize) were silently ignored when formatting through SubplotGrid.format() or Figure.format().

Example: Geographic Formatting
import 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 Label Improvements

r5

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 Labels
import 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()

Bug Fixes and General Improvements

Various bug fixes including resolved int/list size errors in bar plots and consistent style application ordering.

Example: Bar Plot fix for pandas Series
import 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()

What's Changed

  • Example/semantic legend rm suffix (#735) by @lukas-schoen-qut
  • Add example of semantic plot to gallery (#734) by @lukas-schoen-qut
  • Unify semantic legend params. (#727) by @lukas-schoen-qut
  • Fix ordering of applying styles (#725) by @lukas-schoen-qut
  • Fix int/list has no size error, for bar plot of pd.Series (#732) by @lukas-schoen-qut
  • Change rectangle to non-square for geolegend (#730) by @lukas-schoen-qut
  • Fix duplicate import in colors.py (#728) by @lukas-schoen-qut
  • Fix geographic grid label styling in SubplotGrid/Figure.format (#724) by @lukas-schoen-qut
  • Add polar-aware thetalabel/rlabel support and remove generic x/y label handling from polar format by @lukas-schoen-qut

What's Changed

New Contributors

Full Changelog: v2.2.0...v2.3.0

UltraPlot 2.2.0: Precision Placement — colorbars that span, norms that flex, labels that stay

Choose a tag to compare

@cvanelteren cvanelteren released this 20 Apr 08:53
8bf6ccb

UltraPlot v2.2.0

What's New

Spanning colorbars across subplot slots

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.

release_v2 2 0_span_colorbar
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,
)

Flexible normalization inputs

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.

release_v2 2 0_norm_inputs
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)

Bug Fixes

Title border path effects properly cleared

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.

release_v2 2 0_titleborder
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)

Outer legends no longer hide shared tick labels

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.

release_v2 2 0_sharey_legend
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.]",
)

Other Changes

  • Zenodo publishing fix — corrected metadata for DOI generation (#686)
  • Figure initialization refactor — internal cleanup of figure setup (#687)
  • What's New page generation fix — documentation build improvements (#697)

Full Changelog: v2.1.9...v2.2.0

What's Changed

Full Changelog: v2.1.9...v2.2.0

UltraPlot v2.1.9: bugs, nans, and improved title sharing.

Choose a tag to compare

@cvanelteren cvanelteren released this 14 Apr 08:13
f8fd865

With v2.1.9 we add nan support for curved_quiver, and allow for using axes slicing to set titles.

Flexible title setting through axes slicing

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.

test
snippet
import ultraplot as uplt

fig, ax =uplt.subplots(ncols = 3, nrows = 2)
ax[0, :2].format(title = "Hello world!")
fig.show()

What's Changed

Full Changelog: v2.1.5...v2.1.9

What's Changed

Full Changelog: v2.1.8...v2.1.9

UltraPlot v2.1.5: Choropleth, Custom labels semantic plots, and bug fixes

Choose a tag to compare

@cvanelteren cvanelteren released this 30 Mar 23:45

UltraPlot v2.1.5

The biggest additions are richer semantic size legends and first-class
choropleth support for geographic axes, alongside typing, plotting, CI, and
documentation improvements.

Highlights

Custom labels for Axes.sizelegend

sizelegend can now describe marker magnitudes in domain language instead of
just echoing the raw numeric levels.

scatter_polished
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()

GeoAxes.choropleth for thematic maps

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.

choropleth_polished
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()

Other changes

  • Better static-analysis support for the lazy top-level API.
  • Numeric scatter plots with explicit numeric colors now respect cmap.
  • Shared boxplot tick labels no longer duplicate.
  • SubplotGrid single-item 2D slices now keep returning SubplotGrid.
  • Helper and release-metadata coverage expanded and the CI flow was tightened.

What's Changed

New Contributors

Full Changelog: v2.1.3...v2.1.5

What's Changed

New Contributors

Full Changelog: v2.1.3...v2.1.5

v2.1.3

Choose a tag to compare

@cvanelteren cvanelteren released this 11 Mar 01:53
69e0001

This is a small patch release focused on plotting and legend fixes.

Highlights

  • 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.

Other changes

  • Updated GitHub Actions dependencies in the workflow configuration.

Included pull requests

  • #605 Enable graph plotting on 3D axes
  • #610 Restore colorbar frame handling
  • #612 Preserve hatches in geometry legend proxies
  • #604 GitHub Actions dependency updates

Full Changelog: V2.1.2...v2.1.3

V2.1.2 Fix colorbar framing and extra legend entries on slicing

Choose a tag to compare

@cvanelteren cvanelteren released this 26 Feb 07:40
3378000

What's Changed

Full Changelog: V2.1.0...V2.1.2

V2.1.0: Tricontour fix projections

Choose a tag to compare

@cvanelteren cvanelteren released this 25 Feb 04:02
153df0d

This release hotfixes two bugs.

  1. It fixes a bug where the dpi would be changed by external packages that create figures using matplotlib axes
  2. It fixes a bug where the projection was assumed to be PlateCaree for tri-related functions

What's Changed

Full Changelog: v2.0.1...V2.1.0