Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6a2024c
Fix references in documentation for clarity
gepcel Dec 10, 2025
b85930d
keep apply_norm
cvanelteren Dec 10, 2025
d221b79
Add container class to wrap external axes objects
cvanelteren Dec 10, 2025
19b6691
remove v1
cvanelteren Dec 11, 2025
ede7a12
Merge branch 'main' into relax-projection
cvanelteren Dec 11, 2025
2e66139
fix test_geographic_multiple_projections
cvanelteren Dec 11, 2025
5afc083
Merge branch 'main' into relax-projection
cvanelteren Dec 11, 2025
b3aeb43
fixes
cvanelteren Dec 11, 2025
e502992
add container tests
cvanelteren Dec 11, 2025
7e0f112
fix merge issue
cvanelteren Dec 11, 2025
09c59b5
correct rebase
cvanelteren Dec 11, 2025
72a3cde
fix mpl39 issue
cvanelteren Dec 11, 2025
24e8502
fix double draw in repl
cvanelteren Dec 11, 2025
2bda4cc
Merge branch 'main' into relax-projection
cvanelteren Dec 12, 2025
4cb9c97
Improve external axes container layout for native appearance
cvanelteren Dec 14, 2025
e63ac50
Handle non-numeric padding conversion
cvanelteren Jan 1, 2026
6bcea81
Merge branch 'main' into pr-422
cvanelteren Jan 1, 2026
499880f
Merge branch 'main' into relax-projection
cvanelteren Jan 1, 2026
beb55b9
adjust test
cvanelteren Jan 1, 2026
8ee1d12
Add coverage for external axes container
cvanelteren Jan 1, 2026
eb8cd57
Expand external container test coverage
cvanelteren Jan 1, 2026
78a4020
this works
cvanelteren Jan 2, 2026
8ca6bc7
Adjust mpltern default shrink
cvanelteren Jan 2, 2026
9278d3a
Add mpltern container shrink tests
cvanelteren Jan 3, 2026
4aa34fc
Document external axes containers
cvanelteren Jan 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ docs/_build
docs/_static/ultraplotrc
docs/_static/rctable.rst
docs/_static/*
*.html

# Development subfolders
dev
Expand All @@ -33,6 +34,8 @@ sources
*.pyc
.*.pyc
__pycache__
*.ipynb


# OS files
.DS_Store
Expand Down
42 changes: 42 additions & 0 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,48 @@ plotting packages.
Since these features are optional,
UltraPlot can be used without installing any of these packages.

External axes containers (mpltern, others)
------------------------------------------

UltraPlot can wrap third-party Matplotlib projections (e.g., ``mpltern``'s
``"ternary"`` projection) in a lightweight container. The container keeps
UltraPlot's figure/labeling behaviors while delegating plotting calls to the
external axes.

Basic usage mirrors standard subplots:

.. code-block:: python

import mpltern
import ultraplot as uplt

fig, axs = uplt.subplots(ncols=2, projection="ternary")
axs.format(title="Ternary example", abc=True, abcloc="left")
axs[0].plot([0.1, 0.7, 0.2], [0.2, 0.2, 0.6], [0.7, 0.1, 0.2])
axs[1].scatter([0.2, 0.3], [0.5, 0.4], [0.3, 0.3])

Controlling the external content size
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Use ``external_shrink_factor`` (or the rc setting ``external.shrink``) to
shrink the *external* axes inside the container, creating margin space for
titles and annotations without resizing the subplot itself:

.. code-block:: python

uplt.rc["external.shrink"] = 0.8
fig, axs = uplt.subplots(projection="ternary")
axs.format(external_shrink_factor=0.7)

Notes and performance
~~~~~~~~~~~~~~~~~~~~~

* Titles and a-b-c labels are rendered by the container, not the external axes,
so they behave like normal UltraPlot subplots.
* For mpltern with ``external_shrink_factor < 1``, UltraPlot skips the costly
tight-bbox fitting pass and relies on the shrink factor for layout. This
keeps rendering fast and stable.

.. _usage_features:

Additional features
Expand Down
7 changes: 7 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,10 @@ include-package-data = true
[tool.setuptools_scm]
write_to = "ultraplot/_version.py"
write_to_template = "__version__ = '{version}'\n"


[tool.ruff]
ignore = ["I001", "I002", "I003", "I004"]

[tool.basedpyright]
exclude = ["**/*.ipynb"]
9 changes: 7 additions & 2 deletions ultraplot/axes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@
from ..internals import context
from .base import Axes # noqa: F401
from .cartesian import CartesianAxes
from .geo import GeoAxes # noqa: F401
from .geo import _BasemapAxes, _CartopyAxes
from .container import ExternalAxesContainer # noqa: F401
from .geo import (
GeoAxes, # noqa: F401
_BasemapAxes,
_CartopyAxes,
)
from .plot import PlotAxes # noqa: F401
from .polar import PolarAxes
from .shared import _SharedAxes # noqa: F401
Expand All @@ -22,6 +26,7 @@
"PolarAxes",
"GeoAxes",
"ThreeAxes",
"ExternalAxesContainer",
]

# Register projections with package prefix to avoid conflicts
Expand Down
28 changes: 26 additions & 2 deletions ultraplot/axes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -963,7 +963,18 @@ def _add_inset_axes(
zoom = ax._inset_zoom = _not_none(zoom, zoom_default)
if zoom:
zoom_kw = zoom_kw or {}
ax.indicate_inset_zoom(**zoom_kw)
# Check if the inset axes is an Ultraplot axes class.
# Ultraplot axes have a custom indicate_inset_zoom that can be
# called on the inset itself (uses self._inset_parent internally).
# Non-Ultraplot axes (e.g., raw matplotlib/cartopy) require calling
# matplotlib's indicate_inset_zoom on the parent with the inset as first argument.
if isinstance(ax, Axes):
# Ultraplot axes: call on inset (uses self._inset_parent internally)
ax.indicate_inset_zoom(**zoom_kw)
else:
# Non-Ultraplot axes: call matplotlib's parent class method
# with inset as first argument (matplotlib API)
maxes.Axes.indicate_inset_zoom(self, ax, **zoom_kw)
return ax

def _add_queued_guides(self):
Expand Down Expand Up @@ -2662,7 +2673,20 @@ def _range_subplotspec(self, s):
if not isinstance(self, maxes.SubplotBase):
raise RuntimeError("Axes must be a subplot.")
ss = self.get_subplotspec().get_topmost_subplotspec()
row1, row2, col1, col2 = ss._get_rows_columns()

# Check if this is an ultraplot SubplotSpec with _get_rows_columns method
if not hasattr(ss, "_get_rows_columns"):
# Fall back to standard matplotlib SubplotSpec attributes
# This can happen when axes are created directly without ultraplot's gridspec
if hasattr(ss, "rowspan") and hasattr(ss, "colspan"):
row1, row2 = ss.rowspan.start, ss.rowspan.stop - 1
col1, col2 = ss.colspan.start, ss.colspan.stop - 1
else:
# Unable to determine range, return default
row1, row2, col1, col2 = 0, 0, 0, 0
else:
row1, row2, col1, col2 = ss._get_rows_columns()

if s == "x":
return (col1, col2)
else:
Expand Down
Loading