Skip to content
Merged
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
62 changes: 62 additions & 0 deletions ultraplot/axes/geo.py
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,8 @@ def _pad_ticks(ticks: np.ndarray, vmin: float, vmax: float) -> np.ndarray:
# giant lists of 10,000 gridline locations.
if len(ticks) == 0:
return ticks
if len(ticks) == 1:
return ticks
range_ = np.max(ticks) - np.min(ticks)
vmin = max(vmin, ticks[0] - range_)
vmax = min(vmax, ticks[-1] + range_)
Expand Down Expand Up @@ -1595,6 +1597,46 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
self._edge_lat_labels: list[mtext.Text] = []
super().__init__(*args, **kwargs)

def _sync_shared_tick_state(
self,
which: str,
*,
copy_major_locator: bool = False,
copy_minor_locator: bool = False,
copy_major_formatter: bool = False,
) -> None:
"""
Copy explicit tick-state changes from this axis to shared GeoAxes siblings.
"""
if which not in {"x", "y"}:
raise ValueError(f"Invalid axis: {which!r}")
if not any((copy_major_locator, copy_minor_locator, copy_major_formatter)):
return
if which == "x":
if self.figure._sharex < 2:
return
this_axis = self._lonaxis
siblings = list(self._shared_axes["x"].get_siblings(self))
else:
if self.figure._sharey < 2:
return
this_axis = self._lataxis
siblings = list(self._shared_axes["y"].get_siblings(self))
for sibling in siblings:
if sibling is self or not isinstance(sibling, GeoAxes):
continue
sibling_axis = sibling._lataxis if which == "y" else sibling._lonaxis
if copy_major_locator:
sibling_axis.set_major_locator(this_axis.get_major_locator())
if copy_minor_locator:
sibling_axis.set_minor_locator(this_axis.get_minor_locator())
if copy_major_formatter:
sibling_axis.set_major_formatter(this_axis.get_major_formatter())
if copy_major_locator or copy_major_formatter:
sibling._update_major_gridlines()
if copy_minor_locator:
sibling._update_minor_gridlines()

@docstring._snippet_manager
def hawkeye(
self,
Expand Down Expand Up @@ -3087,6 +3129,26 @@ def format(
labelpad=labelpad,
nsteps=nsteps,
)
self._sync_shared_tick_state(
"x",
copy_major_locator=_not_none(lonlocator=lonlocator, lonlines=lonlines)
is not None,
copy_minor_locator=_not_none(
lonminorlocator=lonminorlocator, lonminorlines=lonminorlines
)
is not None,
copy_major_formatter=lonformatter is not None,
)
self._sync_shared_tick_state(
"y",
copy_major_locator=_not_none(latlocator=latlocator, latlines=latlines)
is not None,
copy_minor_locator=_not_none(
latminorlocator=latminorlocator, latminorlines=latminorlines
)
is not None,
copy_major_formatter=latformatter is not None,
)
self._format_apply_ticklen(
lonlim=lonlim,
latlim=latlim,
Expand Down
65 changes: 65 additions & 0 deletions ultraplot/tests/test_geographic.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import numpy as np
import pytest
from matplotlib import ticker as mticker

import ultraplot as uplt

Expand Down Expand Up @@ -795,6 +796,70 @@ def test_copy_locator_props():
assert getattr(g1, prop) == getattr(g2, prop)


def test_format_shared_ticks_sync():
pytest.importorskip("cartopy")
fig, ax = uplt.subplots(ncols=2, proj="cyl", share="all")
ax.format(lonlim=(100, 105), latlim=(30, 35), labels=True)

before_lon = ax[0]._get_lonticklocs()
before_lat = ax[0]._get_latticklocs()

ax[1].format(lonlines=2, latlines=1)

after_left_lon = ax[0]._get_lonticklocs()
after_left_lat = ax[0]._get_latticklocs()
after_right_lon = ax[1]._get_lonticklocs()
after_right_lat = ax[1]._get_latticklocs()
left_gridliner = ax[0]._gridliner_adapters["major"].gridliner

assert np.asarray(after_left_lon).shape != np.asarray(
before_lon
).shape or not np.allclose(
after_left_lon,
before_lon,
)
assert np.asarray(after_left_lat).shape != np.asarray(
before_lat
).shape or not np.allclose(
after_left_lat,
before_lat,
)
assert np.allclose(after_left_lon, after_right_lon)
assert np.allclose(after_left_lat, after_right_lat)
assert np.allclose(left_gridliner.xlocator.tick_values(100, 105), after_left_lon)
assert np.allclose(left_gridliner.ylocator.tick_values(30, 35), after_left_lat)

ax[1].format(lonminorlines=0.5, latminorlines=0.5)
assert np.allclose(
ax[0]._lonaxis.get_minorticklocs(), ax[1]._lonaxis.get_minorticklocs()
)
assert np.allclose(
ax[0]._lataxis.get_minorticklocs(), ax[1]._lataxis.get_minorticklocs()
)

formatter = mticker.FormatStrFormatter("%.1f")
ax[1].format(lonformatter=formatter, latformatter=formatter)
lonformatter = ax[1]._lonaxis.get_major_formatter()
latformatter = ax[1]._lataxis.get_major_formatter()
assert ax[0]._lonaxis.get_major_formatter() is lonformatter
assert ax[0]._lataxis.get_major_formatter() is latformatter
assert left_gridliner.xformatter is lonformatter
assert left_gridliner.yformatter is latformatter
uplt.close(fig)


def test_sync_shared_tick_state_guards():
pytest.importorskip("cartopy")
fig, ax = uplt.subplots(ncols=2, proj="cyl")

ax[0]._sync_shared_tick_state("x")
ax[0]._sync_shared_tick_state("x", copy_major_locator=True)
ax[0]._sync_shared_tick_state("y", copy_major_locator=True)
with pytest.raises(ValueError, match="Invalid axis"):
ax[0]._sync_shared_tick_state("z", copy_major_locator=True)
uplt.close(fig)


def test_turn_off_tick_labels_basemap():
"""
Check if we can toggle the labels off for GeoAxes
Expand Down