Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
2bdd15b
add seaborn context processing
cvanelteren Nov 12, 2025
5cae6f0
rm debug
cvanelteren Nov 12, 2025
0786d28
add unittest
cvanelteren Nov 12, 2025
61ae661
resolve iterable
cvanelteren Nov 12, 2025
6dab0f2
relax legend filter
cvanelteren Nov 12, 2025
2887b3f
add seaborn import
cvanelteren Nov 12, 2025
d542103
add more unittests
cvanelteren Nov 12, 2025
4f9c13b
add ctx texts
cvanelteren Nov 12, 2025
7ea041c
implement mark external and context managing
cvanelteren Nov 12, 2025
c12de2b
fix test
cvanelteren Nov 12, 2025
80fef46
refactor classes for clarity
cvanelteren Nov 12, 2025
7d93bb1
update tests
cvanelteren Nov 12, 2025
288e8bb
more fixes
cvanelteren Nov 12, 2025
8ecccdd
more tests
cvanelteren Nov 12, 2025
aaa1cbc
minor fix
cvanelteren Nov 12, 2025
219e611
minor fix
cvanelteren Nov 12, 2025
3b5c90d
fix for mpl 3.9
cvanelteren Nov 13, 2025
9435fda
remove stack frame
cvanelteren Nov 13, 2025
4c44e82
adjust and remove unecessary tests
cvanelteren Nov 13, 2025
c8635c8
more fixes
cvanelteren Nov 13, 2025
3f969bf
add external to pass test
cvanelteren Nov 13, 2025
1b1ddc4
restore test
cvanelteren Nov 16, 2025
82fbb5d
rm dup
cvanelteren Nov 16, 2025
d5b2aa9
finalize docstring
cvanelteren Nov 16, 2025
8d2824c
remove fallback
cvanelteren Nov 17, 2025
8ee3fe3
Merge branch 'main' into add-seaborn-ctx
cvanelteren Nov 18, 2025
e6ba821
Apply suggestion from @beckermr
beckermr Nov 18, 2025
5923cdb
Apply suggestion from @beckermr
beckermr Nov 18, 2025
5287cdb
fix bar and test
cvanelteren Nov 19, 2025
d5df6a2
Merge branch 'main' into add-seaborn-ctx
cvanelteren Nov 19, 2025
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
85 changes: 76 additions & 9 deletions ultraplot/axes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
import copy
import inspect
import re
import sys
import types
from numbers import Integral, Number
from typing import Union, Iterable, MutableMapping, Optional, Tuple
from collections.abc import Iterable as IterableType
from numbers import Integral, Number
from typing import Iterable, MutableMapping, Optional, Tuple, Union

try:
# From python 3.12
Expand All @@ -34,12 +35,11 @@
from matplotlib import cbook
from packaging import version

from .. import legend as plegend
from .. import colors as pcolors
from .. import constructor
from .. import legend as plegend
from .. import ticker as pticker
from ..config import rc
from ..internals import ic # noqa: F401
from ..internals import (
_kwargs_to_args,
_not_none,
Expand All @@ -51,6 +51,7 @@
_version_mpl,
docstring,
guides,
ic, # noqa: F401
labels,
rcsetup,
warnings,
Expand Down Expand Up @@ -700,7 +701,52 @@ def __call__(self, ax, renderer): # noqa: U100
return bbox


class Axes(maxes.Axes):
class _ExternalModeMixin:
"""
Mixin providing explicit external-mode control and a context manager.
"""

def set_external(self, value=True):
"""
Set explicit external-mode override for this axes.

value:
- True: force external behavior (defer on-the-fly guides, etc.)
- False: force UltraPlot behavior
"""
if value not in (True, False):
raise ValueError("set_external expects True or False")
setattr(self, "_integration_external", value)
return self

class _ExternalContext:
def __init__(self, ax, value=True):
self._ax = ax
self._value = True if value is None else value
self._prev = getattr(ax, "_integration_external", None)

def __enter__(self):
self._ax._integration_external = self._value
return self._ax

def __exit__(self, exc_type, exc, tb):
self._ax._integration_external = self._prev

def external(self, value=True):
"""
Context manager toggling external mode during the block.
"""
return _ExternalModeMixin._ExternalContext(self, value)

def _in_external_context(self):
"""
Return True if UltraPlot helper behaviors should be suppressed.
"""
mode = getattr(self, "_integration_external", None)
return mode is True


class Axes(_ExternalModeMixin, maxes.Axes):
"""
The lowest-level `~matplotlib.axes.Axes` subclass used by ultraplot.
Implements basic universal features.
Expand Down Expand Up @@ -822,6 +868,7 @@ def __init__(self, *args, **kwargs):
self._panel_sharey_group = False # see _apply_auto_share
self._panel_side = None
self._tight_bbox = None # bounding boxes are saved
self._integration_external = None # explicit external-mode override (None=auto)
self.xaxis.isDefault_minloc = True # ensure enabled at start (needed for dual)
self.yaxis.isDefault_minloc = True

Expand Down Expand Up @@ -1739,14 +1786,20 @@ def _get_legend_handles(self, handler_map=None):
handler_map_full = plegend.Legend.get_default_handler_map()
handler_map_full = handler_map_full.copy()
handler_map_full.update(handler_map or {})
# Prefer synthetic tagging to exclude helper artists; see _ultraplot_synthetic flag on artists.
for ax in axs:
for attr in ("lines", "patches", "collections", "containers"):
for handle in getattr(ax, attr, []): # guard against API changes
label = handle.get_label()
handler = plegend.Legend.get_legend_handler(
handler_map_full, handle
) # noqa: E501
if handler and label and label[0] != "_":
if (
handler
and label
and label[0] != "_"
and not getattr(handle, "_ultraplot_synthetic", False)
):
handles.append(handle)
return handles

Expand Down Expand Up @@ -1897,11 +1950,17 @@ def _update_guide(
if legend:
align = legend_kw.pop("align", None)
queue = legend_kw.pop("queue", queue_legend)
self.legend(objs, loc=legend, align=align, queue=queue, **legend_kw)
# Avoid immediate legend creation in external context
if not self._in_external_context():
self.legend(objs, loc=legend, align=align, queue=queue, **legend_kw)
if colorbar:
align = colorbar_kw.pop("align", None)
queue = colorbar_kw.pop("queue", queue_colorbar)
self.colorbar(objs, loc=colorbar, align=align, queue=queue, **colorbar_kw)
# Avoid immediate colorbar creation in external context
if not self._in_external_context():
self.colorbar(
objs, loc=colorbar, align=align, queue=queue, **colorbar_kw
)

@staticmethod
def _parse_frame(guide, fancybox=None, shadow=None, **kwargs):
Expand Down Expand Up @@ -2423,6 +2482,8 @@ def _legend_label(*objs): # noqa: E301
labs = []
for obj in objs:
if hasattr(obj, "get_label"): # e.g. silent list
if getattr(obj, "_ultraplot_synthetic", False):
continue
lab = obj.get_label()
if lab is not None and not str(lab).startswith("_"):
labs.append(lab)
Expand Down Expand Up @@ -2453,10 +2514,15 @@ def _legend_tuple(*objs): # noqa: E306
if hs:
handles.extend(hs)
elif obj: # fallback to first element
handles.append(obj[0])
# Skip synthetic helpers and fill_between collections
if not getattr(obj[0], "_ultraplot_synthetic", False):
handles.append(obj[0])
else:
handles.append(obj)
elif hasattr(obj, "get_label"):
# Skip synthetic helpers and fill_between collections
if getattr(obj, "_ultraplot_synthetic", False):
continue
handles.append(obj)
else:
warnings._warn_ultraplot(f"Ignoring invalid legend handle {obj!r}.")
Expand Down Expand Up @@ -3332,6 +3398,7 @@ def _label_key(self, side: str) -> str:
labelright/labelleft respectively.
"""
from packaging import version

from ..internals import _version_mpl

# TODO: internal deprecation warning when we drop 3.9, we need to remove this
Expand Down
Loading