Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 23 additions & 4 deletions ultraplot/axes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3261,6 +3261,27 @@ def _is_panel_group_member(self, other: "Axes") -> bool:
# Not in the same panel group
return False

def _label_key(self, side: str) -> str:
"""
Map requested side name to the correct tick_params key across mpl versions.

This accounts for the API change around Matplotlib 3.10 where labeltop/labelbottom
became first-class tick parameter keys. For older versions, these map to
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

use_new = version.parse(str(_version_mpl)) >= version.parse("3.10")
if side == "labeltop":
return "labeltop" if use_new else "labelright"
if side == "labelbottom":
return "labelbottom" if use_new else "labelleft"
# "labelleft" and "labelright" are stable across versions
return side

def _is_ticklabel_on(self, side: str) -> bool:
"""
Check if tick labels are on for the specified sides.
Expand All @@ -3274,10 +3295,8 @@ def _is_ticklabel_on(self, side: str) -> bool:
label = "label1"
if side in ["labelright", "labeltop"]:
label = "label2"
for tick in axis.get_major_ticks():
if getattr(tick, label).get_visible():
return True
return False

return axis.get_tick_params().get(self._label_key(side), False)

@docstring._snippet_manager
def inset(self, *args, **kwargs):
Expand Down
111 changes: 6 additions & 105 deletions ultraplot/axes/cartesian.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,9 +386,7 @@ def _apply_axis_sharing(self):
# bottommost or to the *right* of the leftmost panel. But the sharing level
# used for the leftmost and bottommost is the *figure* sharing level.

# Get border axes once for efficiency
border_axes = self.figure._get_border_axes()

# Apply X axis sharing
self._apply_axis_sharing_for_axis("x", border_axes)

Expand All @@ -412,128 +410,31 @@ def _apply_axis_sharing_for_axis(
"""
if axis_name == "x":
axis = self.xaxis
shared_axis = self._sharex
panel_group = self._panel_sharex_group
shared_axis = self._sharex # do we share the xaxis?
panel_group = self._panel_sharex_group # do we have a panel?
sharing_level = self.figure._sharex
label_params = ["labeltop", "labelbottom"]
border_sides = ["top", "bottom"]
else: # axis_name == 'y'
axis = self.yaxis
shared_axis = self._sharey
panel_group = self._panel_sharey_group
sharing_level = self.figure._sharey
label_params = ["labelleft", "labelright"]
border_sides = ["left", "right"]

if shared_axis is None or not axis.get_visible():
if not axis.get_visible():
return

level = 3 if panel_group else sharing_level

# Handle axis label sharing (level > 0)
if level > 0:
# If we are a border axis, @shared_axis may be None
# We propagate this through the _determine_tick_label_visiblity() logic
if level > 0 and shared_axis:
shared_axis_obj = getattr(shared_axis, f"{axis_name}axis")
labels._transfer_label(axis.label, shared_axis_obj.label)
axis.label.set_visible(False)

# Handle tick label sharing (level > 2)
if level > 2:
label_visibility = self._determine_tick_label_visibility(
axis,
shared_axis,
axis_name,
label_params,
border_sides,
border_axes,
)
axis.set_tick_params(which="both", **label_visibility)
# Turn minor ticks off
axis.set_minor_formatter(mticker.NullFormatter())

def _determine_tick_label_visibility(
self,
axis: maxis.Axis,
shared_axis: maxis.Axis,
axis_name: str,
label_params: list[str],
border_sides: list[str],
border_axes: dict[str, list[plot.PlotAxes]],
) -> dict[str, bool]:
"""
Determine which tick labels should be visible based on sharing rules and borders.

Parameters
----------
axis : matplotlib axis
The current axis object
shared_axis : Axes
The axes this one shares with
axis_name : str
Either 'x' or 'y'
label_params : list
List of label parameter names (e.g., ['labeltop', 'labelbottom'])
border_sides : list
List of border side names (e.g., ['top', 'bottom'])
border_axes : dict
Dictionary from _get_border_axes()

Returns
-------
dict
Dictionary of label visibility parameters
"""
ticks = axis.get_tick_params()
shared_axis_obj = getattr(shared_axis, f"{axis_name}axis")
sharing_ticks = shared_axis_obj.get_tick_params()

label_visibility = {}

def _convert_label_param(label_param: str) -> str:
# Deal with logic not being consistent
# in prior mpl versions
if version.parse(str(_version_mpl)) <= version.parse("3.9"):
if label_param == "labeltop" and axis_name == "x":
label_param = "labelright"
elif label_param == "labelbottom" and axis_name == "x":
label_param = "labelleft"
return label_param

for label_param, border_side in zip(label_params, border_sides):
# Check if user has explicitly set label location via format()
label_visibility[label_param] = False
has_panel = False
for panel in self._panel_dict[border_side]:
# Check if the panel is a colorbar
colorbars = [
values
for key, values in self._colorbar_dict.items()
if border_side in key # key is tuple (side, top | center | lower)
]
if not panel in colorbars:
# Skip colorbar as their
# yaxis is not shared
has_panel = True
break
# When we have a panel, let the panel have
# the labels and turn-off for this axis + side.
if has_panel:
continue
is_border = self in border_axes.get(border_side, [])
is_panel = (
self in shared_axis._panel_dict[border_side]
and self == shared_axis._panel_dict[border_side][-1]
)
# Use automatic border detection logic
# if we are a panel we "push" the labels outwards
label_param_trans = _convert_label_param(label_param)
is_this_tick_on = ticks[label_param_trans]
is_parent_tick_on = sharing_ticks[label_param_trans]
if is_panel:
label_visibility[label_param] = is_parent_tick_on
elif is_border:
label_visibility[label_param] = is_this_tick_on
return label_visibility

def _add_alt(self, sx, **kwargs):
"""
Add an alternate axes.
Expand Down
74 changes: 31 additions & 43 deletions ultraplot/axes/geo.py
Original file line number Diff line number Diff line change
Expand Up @@ -652,27 +652,16 @@ def _apply_axis_sharing(self):
or to the *right* of the leftmost panel. But the sharing level used for
the leftmost and bottommost is the *figure* sharing level.
"""
# Handle X axis sharing
if self._sharex:
self._handle_axis_sharing(
source_axis=self._sharex._lonaxis,
target_axis=self._lonaxis,
)

# Handle Y axis sharing
if self._sharey:
self._handle_axis_sharing(
source_axis=self._sharey._lataxis,
target_axis=self._lataxis,
)
# Share interval x
if self._sharex and self.figure._sharex >= 2:
self._lonaxis.set_view_interval(*self._sharex._lonaxis.get_view_interval())
self._lonaxis.set_minor_locator(self._sharex._lonaxis.get_minor_locator())

# This block is apart of the draw sequence as the
# gridliner object is created late in the
# build chain.
if not self.stale:
return
if self.figure._get_sharing_level() == 0:
return
# Share interval y
if self._sharey and self.figure._sharey >= 2:
self._lataxis.set_view_interval(*self._sharey._lataxis.get_view_interval())
self._lataxis.set_minor_locator(self._sharey._lataxis.get_minor_locator())

def _get_gridliner_labels(
self,
Expand All @@ -691,38 +680,36 @@ def _toggle_gridliner_labels(
labelright=None,
geo=None,
):
# For BasemapAxes the gridlines are dicts with key as the coordinate and keys the line and label
# We override the dict here assuming the labels are mut excl due to the N S E W extra chars
"""
Toggle visibility of gridliner labels for each direction.

Parameters
----------
labeltop, labelbottom, labelleft, labelright : bool or None
Whether to show labels on each side. If None, do not change.
geo : optional
Not used in this method.
"""
# Ensure gridlines_major is fully initialized
if any(i is None for i in self.gridlines_major):
return

gridlabels = self._get_gridliner_labels(
bottom=labelbottom, top=labeltop, left=labelleft, right=labelright
)
bools = [labelbottom, labeltop, labelleft, labelright]
directions = "bottom top left right".split()
for direction, toggle in zip(directions, bools):

toggles = {
"bottom": labelbottom,
"top": labeltop,
"left": labelleft,
"right": labelright,
}

for direction, toggle in toggles.items():
if toggle is None:
continue
for label in gridlabels.get(direction, []):
label.set_visible(toggle)

def _handle_axis_sharing(
self,
source_axis: "GeoAxes",
target_axis: "GeoAxes",
):
"""
Helper method to handle axis sharing for both X and Y axes.

Args:
source_axis: The source axis to share from
target_axis: The target axis to apply sharing to
"""
# Copy view interval and minor locator from source to target

if self.figure._get_sharing_level() >= 2:
target_axis.set_view_interval(*source_axis.get_view_interval())
target_axis.set_minor_locator(source_axis.get_minor_locator())
label.set_visible(bool(toggle) or toggle in ("x", "y"))

@override
def draw(self, renderer=None, *args, **kwargs):
Expand Down Expand Up @@ -1441,6 +1428,7 @@ def _is_ticklabel_on(self, side: str) -> bool:
"""
# Deal with different cartopy versions
left_labels, right_labels, bottom_labels, top_labels = self._get_side_labels()

if self.gridlines_major is None:
return False
elif side == "labelleft":
Expand Down
10 changes: 10 additions & 0 deletions ultraplot/axes/polar.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
"""
import inspect

try:
from typing import override
except:
from typing_extensions import override

import matplotlib.projections.polar as mpolar
import numpy as np

Expand Down Expand Up @@ -138,6 +143,11 @@ def __init__(self, *args, **kwargs):
for axis in (self.xaxis, self.yaxis):
axis.set_tick_params(which="both", size=0)

@override
def _apply_axis_sharing(self):
# Not implemented. Silently pass
return

def _update_formatter(self, x, *, formatter=None, formatter_kw=None):
"""
Update the gridline label formatter.
Expand Down
Loading
Loading