Skip to content

Commit 4925cc1

Browse files
authored
Fix inset colorbar frame reflow for refaspect (#593)
Makes the solver for colorbars consistent with inset colorbars by ensuring that the drawing pipeline is correctly run for the inset.
1 parent d479c71 commit 4925cc1

3 files changed

Lines changed: 114 additions & 8 deletions

File tree

ultraplot/axes/base.py

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3149,19 +3149,31 @@ def draw(self, renderer=None, *args, **kwargs):
31493149
self._colorbar_fill.update_ticks(manual_only=True) # only if needed
31503150
if self._inset_parent is not None and self._inset_zoom:
31513151
self.indicate_inset_zoom()
3152+
needs_inset_reflow = bool(getattr(self, "_inset_colorbar_needs_reflow", False))
3153+
has_inset_frame = bool(
3154+
getattr(self, "_inset_colorbar_frame", None) is not None
3155+
and getattr(self, "_inset_colorbar_obj", None)
3156+
)
31523157
super().draw(renderer, *args, **kwargs)
3153-
if getattr(self, "_inset_colorbar_obj", None) and getattr(
3154-
self, "_inset_colorbar_needs_reflow", False
3155-
):
3156-
self._inset_colorbar_needs_reflow = False
3158+
if has_inset_frame:
3159+
if not needs_inset_reflow:
3160+
needs_inset_reflow = _inset_colorbar_frame_needs_reflow(
3161+
self._inset_colorbar_obj,
3162+
labelloc=getattr(self, "_inset_colorbar_labelloc", None),
3163+
renderer=renderer,
3164+
)
3165+
if has_inset_frame and needs_inset_reflow:
31573166
_reflow_inset_colorbar_frame(
31583167
self._inset_colorbar_obj,
31593168
labelloc=getattr(self, "_inset_colorbar_labelloc", None),
31603169
ticklen=getattr(
31613170
self, "_inset_colorbar_ticklen", units(rc["tick.len"], "pt")
31623171
),
3172+
renderer=renderer,
31633173
)
3164-
self.figure.canvas.draw_idle()
3174+
self._inset_colorbar_needs_reflow = False
3175+
# Re-draw synchronously so the current render pass sees reflowed bounds.
3176+
super().draw(renderer, *args, **kwargs)
31653177

31663178
def get_tightbbox(self, renderer, *args, **kwargs):
31673179
# Perform extra post-processing steps
@@ -4581,11 +4593,71 @@ def _apply_inset_colorbar_layout(
45814593
frame.set_bounds(*bounds_frame)
45824594

45834595

4596+
def _inset_colorbar_frame_needs_reflow(colorbar, *, labelloc: str, renderer) -> bool:
4597+
cax = colorbar.ax
4598+
layout = getattr(cax, "_inset_colorbar_layout", None)
4599+
frame = getattr(cax, "_inset_colorbar_frame", None)
4600+
if not layout or frame is None:
4601+
return False
4602+
4603+
orientation = layout["orientation"]
4604+
loc = layout["loc"]
4605+
ticklocation = layout["ticklocation"]
4606+
labelloc_layout = labelloc if isinstance(labelloc, str) else ticklocation
4607+
bboxes = []
4608+
4609+
longaxis = _get_colorbar_long_axis(colorbar)
4610+
try:
4611+
bbox = longaxis.get_tightbbox(renderer)
4612+
except Exception:
4613+
bbox = None
4614+
if bbox is not None:
4615+
bboxes.append(bbox)
4616+
4617+
label_axis = _get_axis_for(
4618+
labelloc_layout, loc, orientation=orientation, ax=colorbar
4619+
)
4620+
if label_axis.label.get_text():
4621+
try:
4622+
bboxes.append(label_axis.label.get_window_extent(renderer=renderer))
4623+
except Exception:
4624+
pass
4625+
4626+
for artist in (
4627+
getattr(colorbar, "outline", None),
4628+
getattr(colorbar, "solids", None),
4629+
getattr(colorbar, "dividers", None),
4630+
):
4631+
if artist is None:
4632+
continue
4633+
try:
4634+
bboxes.append(artist.get_window_extent(renderer=renderer))
4635+
except Exception:
4636+
pass
4637+
4638+
if not bboxes:
4639+
return False
4640+
4641+
x0 = min(bbox.x0 for bbox in bboxes)
4642+
y0 = min(bbox.y0 for bbox in bboxes)
4643+
x1 = max(bbox.x1 for bbox in bboxes)
4644+
y1 = max(bbox.y1 for bbox in bboxes)
4645+
frame_bbox = frame.get_window_extent(renderer=renderer)
4646+
tol = 1.0
4647+
return (
4648+
frame_bbox.x0 > x0 + tol
4649+
or frame_bbox.y0 > y0 + tol
4650+
or frame_bbox.x1 < x1 - tol
4651+
or frame_bbox.y1 < y1 - tol
4652+
)
4653+
4654+
45844655
def _reflow_inset_colorbar_frame(
45854656
colorbar,
45864657
*,
45874658
labelloc: str,
45884659
ticklen: float,
4660+
renderer=None,
45894661
):
45904662
cax = colorbar.ax
45914663
layout = getattr(cax, "_inset_colorbar_layout", None)
@@ -4623,7 +4695,7 @@ def _reflow_inset_colorbar_frame(
46234695
cb_width = width
46244696
cb_height = length
46254697

4626-
renderer = cax.figure._get_renderer()
4698+
renderer = renderer or cax.figure._get_renderer()
46274699
if hasattr(colorbar, "update_ticks"):
46284700
colorbar.update_ticks(manual_only=True)
46294701
bboxes = []

ultraplot/colorbar.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,9 @@ def add(
347347
cax._inset_colorbar_obj = obj
348348
cax._inset_colorbar_labelloc = labelloc
349349
cax._inset_colorbar_ticklen = ticklen
350-
_register_inset_colorbar_reflow(ax.figure)
350+
has_frame = getattr(cax, "_inset_colorbar_frame", None) is not None
351+
if has_frame:
352+
_register_inset_colorbar_reflow(ax.figure)
351353
kw_outline = {"edgecolor": color, "linewidth": linewidth}
352354
if obj.outline is not None:
353355
obj.outline.update(kw_outline)
@@ -958,6 +960,7 @@ def _reflow_inset_colorbar_frame(
958960
*,
959961
labelloc: Optional[str],
960962
ticklen: float,
963+
renderer=None,
961964
):
962965
cax = colorbar.ax
963966
layout = getattr(cax, "_inset_colorbar_layout", None)
@@ -995,7 +998,7 @@ def _reflow_inset_colorbar_frame(
995998
cb_width = width
996999
cb_height = length
9971000

998-
renderer = cax.figure._get_renderer()
1001+
renderer = renderer or cax.figure._get_renderer()
9991002
if hasattr(colorbar, "update_ticks"):
10001003
colorbar.update_ticks(manual_only=True)
10011004
bboxes = []

ultraplot/tests/test_colorbar.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,37 @@ def test_inset_colorbar_frame_wraps_label(rng, orientation, labelloc):
9797
assert frame_bbox.y1 >= label_bbox.y1 - tol
9898

9999

100+
def test_inset_colorbar_frame_wraps_label_with_refaspect(rng):
101+
"""
102+
Inset colorbar frame should include the label when figure sizing is refaspect-driven.
103+
"""
104+
from ultraplot.axes.base import _get_axis_for
105+
106+
fig, ax = uplt.subplots(refaspect=2)
107+
data = rng.random((20, 30))
108+
m = ax.pcolormesh(data, vmin=0, vmax=1)
109+
cb = ax.colorbar(m, loc="ul", label="title", frameon=True)
110+
fig.canvas.draw()
111+
112+
frame = cb.ax._inset_colorbar_frame
113+
assert frame is not None
114+
115+
renderer = fig.canvas.get_renderer()
116+
frame_bbox = frame.get_window_extent(renderer)
117+
layout = cb.ax._inset_colorbar_layout
118+
labelloc = cb.ax._inset_colorbar_labelloc
119+
labelloc_layout = labelloc if isinstance(labelloc, str) else layout["ticklocation"]
120+
label_axis = _get_axis_for(
121+
labelloc_layout, layout["loc"], orientation=layout["orientation"], ax=cb
122+
)
123+
label_bbox = label_axis.label.get_window_extent(renderer)
124+
tol = 1.0
125+
assert frame_bbox.x0 <= label_bbox.x0 + tol
126+
assert frame_bbox.x1 >= label_bbox.x1 - tol
127+
assert frame_bbox.y0 <= label_bbox.y0 + tol
128+
assert frame_bbox.y1 >= label_bbox.y1 - tol
129+
130+
100131
from itertools import product
101132

102133

0 commit comments

Comments
 (0)