Skip to content

Commit 499f22c

Browse files
committed
Fix recursive resize bugs in qt backend
1 parent 3a9f83c commit 499f22c

File tree

4 files changed

+46
-51
lines changed

4 files changed

+46
-51
lines changed

proplot/axes/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1051,7 +1051,7 @@ def colorbar(
10511051
subplotspec = gridspec[1]
10521052

10531053
# Draw colorbar axes
1054-
with self.figure._authorize_add_subplot():
1054+
with self.figure._context_authorize_add_subplot():
10551055
ax = self.figure.add_subplot(subplotspec, projection='cartesian') # noqa: E501
10561056
self.add_child_axes(ax)
10571057

proplot/axes/cartesian.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1124,7 +1124,7 @@ def altx(self, **kwargs):
11241124
# See https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/axes/_subplots.py # noqa
11251125
if self._altx_child or self._altx_parent:
11261126
raise RuntimeError('No more than *two* twin axes are allowed.')
1127-
with self.figure._authorize_add_subplot():
1127+
with self.figure._context_authorize_add_subplot():
11281128
ax = self._make_twin_axes(sharey=self, projection='cartesian')
11291129
ax.set_autoscaley_on(self.get_autoscaley_on())
11301130
ax.grid(False)
@@ -1145,7 +1145,7 @@ def alty(self, **kwargs):
11451145
# Docstring is programatically assigned below
11461146
if self._alty_child or self._alty_parent:
11471147
raise RuntimeError('No more than *two* twin axes are allowed.')
1148-
with self.figure._authorize_add_subplot():
1148+
with self.figure._context_authorize_add_subplot():
11491149
ax = self._make_twin_axes(sharex=self, projection='cartesian')
11501150
ax.set_autoscalex_on(self.get_autoscalex_on())
11511151
ax.grid(False)

proplot/figure.py

Lines changed: 42 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from .config import rc
1313
from .utils import units
1414
from .internals import ic # noqa: F401
15-
from .internals import warnings, _not_none, _set_state
15+
from .internals import warnings, _not_none, _dummy_context, _set_state
1616

1717
__all__ = ['Figure']
1818

@@ -77,11 +77,13 @@ def _preprocess(self, *args, **kwargs):
7777
self.draw()
7878

7979
# Bail out if we are already pre-processing
80-
# The return value for macosx _draw is the renderer, for qt draw is
80+
# NOTE: The _is_autoresizing check necessary when inserting new gridspec
81+
# rows or columns with the qt backend.
82+
# NOTE: Return value for macosx _draw is the renderer, for qt draw is
8183
# nothing, and for print_figure is some figure object, but this block
8284
# has never been invoked when calling print_figure.
8385
renderer = fig._get_renderer() # any renderer will do for now
84-
if fig._is_preprocessing:
86+
if fig._is_autoresizing or fig._is_preprocessing:
8587
if method == '_draw': # macosx backend
8688
return renderer
8789
else:
@@ -230,21 +232,20 @@ def __init__(
230232
**kwargs
231233
Passed to `matplotlib.figure.Figure`.
232234
""" # noqa
235+
# Initialize first, because need to provide fully initialized figure
236+
# as argument to gridspec, because matplotlib tight_layout does that
233237
tight_layout = kwargs.pop('tight_layout', None)
234238
constrained_layout = kwargs.pop('constrained_layout', None)
235239
if tight_layout or constrained_layout:
236240
warnings._warn_proplot(
237241
f'Ignoring tight_layout={tight_layout} and '
238242
f'contrained_layout={constrained_layout}. ProPlot uses its '
239243
'own tight layout algorithm, activated by default or with '
240-
'tight=True.'
244+
'plot.subplots(tight=True).'
241245
)
242-
243-
# Initialize first, because need to provide fully initialized figure
244-
# as argument to gridspec, because matplotlib tight_layout does that
245246
self._authorized_add_subplot = False
246247
self._is_preprocessing = False
247-
self._is_resizing = False
248+
self._is_autoresizing = False
248249
super().__init__(**kwargs)
249250

250251
# Axes sharing and spanning settings
@@ -330,7 +331,7 @@ def _add_axes_panel(self, ax, side, filled=False, **kwargs):
330331
idx2 += 1
331332

332333
# Draw and setup panel
333-
with self._authorize_add_subplot():
334+
with self._context_authorize_add_subplot():
334335
pax = self.add_subplot(gridspec[idx1, idx2], projection='cartesian') # noqa: E501
335336
pgrid.append(pax)
336337
pax._panel_side = side
@@ -439,7 +440,7 @@ def _add_figure_panel(
439440
)
440441

441442
# Draw and setup panel
442-
with self._authorize_add_subplot():
443+
with self._context_authorize_add_subplot():
443444
pax = self.add_subplot(gridspec[idx1, idx2], projection='cartesian') # noqa: E501
444445
pgrid = getattr(self, '_' + side + '_panels')
445446
pgrid.append(pax)
@@ -611,19 +612,19 @@ def _align_labels_figure(self, renderer):
611612
}
612613
suptitle.update(kw)
613614

614-
def _authorize_add_subplot(self):
615+
def _context_authorize_add_subplot(self):
615616
"""
616617
Prevent warning message when adding subplots one-by-one. Used
617618
internally.
618619
"""
619620
return _set_state(self, _authorized_add_subplot=True)
620621

621-
def _context_resizing(self):
622+
def _context_autoresizing(self):
622623
"""
623624
Ensure backend calls to `~matplotlib.figure.Figure.set_size_inches`
624625
during pre-processing are not interpreted as *manual* resizing.
625626
"""
626-
return _set_state(self, _is_resizing=True)
627+
return _set_state(self, _is_autoresizing=True)
627628

628629
def _context_preprocessing(self):
629630
"""
@@ -753,30 +754,24 @@ def _insert_row_column(
753754
spaces = subplots_kw[w + 'space']
754755
spaces_orig = subplots_orig_kw[w + 'space']
755756

756-
# Slot already exists
757+
# Adjust space, ratio, and panel indicator arrays
757758
slot_type = 'f' if figure else side[0]
758759
slot_exists = idx not in (-1, len(panels)) and panels[idx] == slot_type
759-
if slot_exists: # already exists!
760+
if slot_exists:
761+
# Slot already exists
760762
if spaces_orig[idx_space] is None:
761763
spaces_orig[idx_space] = units(space_orig)
762764
spaces[idx_space] = _not_none(spaces_orig[idx_space], space)
763765

764-
# Make room for new panel slot
765766
else:
766-
# Modify basic geometry
767+
# Modify basic geometry and insert new slot
767768
idx += idx_offset
768769
idx_space += idx_offset
769770
subplots_kw[ncols] += 1
770-
# Original space, ratio array, space array, panel toggles
771771
spaces_orig.insert(idx_space, space_orig)
772772
spaces.insert(idx_space, space)
773773
ratios.insert(idx, ratio)
774774
panels.insert(idx, slot_type)
775-
# Reference ax location array
776-
# TODO: For now do not need to increment, but need to double
777-
# check algorithm for fixing axes aspect!
778-
# ref = subplots_kw[x + 'ref']
779-
# ref[:] = [val + 1 if val >= idx else val for val in ref]
780775

781776
# Update figure
782777
figsize, gridspec_kw, _ = pgridspec._calc_geometry(**subplots_kw)
@@ -788,7 +783,9 @@ def _insert_row_column(
788783
else:
789784
# Make new gridspec
790785
gridspec = pgridspec.GridSpec(self, **gridspec_kw)
786+
self._gridspec_main.figure = None
791787
self._gridspec_main = gridspec
788+
792789
# Reassign subplotspecs to all axes and update positions
793790
for ax in self._iter_axes(hidden=True, children=True):
794791
# Get old index
@@ -800,26 +797,24 @@ def _insert_row_column(
800797
else:
801798
inserts = (idx, idx, None, None)
802799
subplotspec = ax.get_subplotspec()
803-
igridspec = subplotspec.get_gridspec()
804-
topmost = subplotspec.get_topmost_subplotspec()
800+
gridspec_ss = subplotspec.get_gridspec()
801+
subplotspec_top = subplotspec.get_topmost_subplotspec()
805802

806803
# Apply new subplotspec
807-
_, _, *coords = topmost.get_active_rows_columns()
804+
_, _, *coords = subplotspec_top.get_active_rows_columns()
808805
for i in range(4):
809806
if inserts[i] is not None and coords[i] >= inserts[i]:
810807
coords[i] += 1
811808
row1, row2, col1, col2 = coords
812809
subplotspec_new = gridspec[row1:row2 + 1, col1:col2 + 1]
813-
if topmost is subplotspec:
810+
if subplotspec_top is subplotspec:
814811
ax.set_subplotspec(subplotspec_new)
815-
elif topmost is igridspec._subplot_spec:
816-
igridspec._subplot_spec = subplotspec_new
812+
elif subplotspec_top is gridspec_ss._subplot_spec:
813+
gridspec_ss._subplot_spec = subplotspec_new
817814
else:
818815
raise ValueError(
819816
f'Unexpected GridSpecFromSubplotSpec nesting.'
820817
)
821-
822-
# Update parent or child position
823818
ax.update_params()
824819
ax.set_position(ax.figbox)
825820

@@ -1205,9 +1200,8 @@ def set_canvas(self, canvas):
12051200
# `~matplotlib.backend_bases.FigureCanvasBase.draw_idle` and
12061201
# `~matplotlib.backend_bases.FigureCanvasBase.print_figure`
12071202
# methods. The latter is called by save() and by the inline backend.
1208-
# See `_canvas_preprocessor` for details."""
1203+
# See `_canvas_preprocessor` for details.
12091204
# TODO: Concatenate docstrings.
1210-
# TODO: Figure out matplotlib>=3.3 bug with macos backend.
12111205
# NOTE: Cannot use draw_idle() because it causes complications for qt5
12121206
# backend (wrong figure size).
12131207
if callable(getattr(canvas, '_draw', None)): # for macos backend
@@ -1230,6 +1224,9 @@ def set_size_inches(self, w, h=None, forward=True, auto=False):
12301224
# renderer calls set_size_inches, size may be effectively the same, but
12311225
# slightly changed due to roundoff error! Therefore, always compare to
12321226
# *both* get_size_inches() and the truncated bbox dimensions times dpi.
1227+
# NOTE: If we fail to detect 'manual' resize as manual, not only will
1228+
# result be incorrect, but qt backend will crash because it detects a
1229+
# recursive size change, since preprocessor size will differ.
12331230
if h is None:
12341231
width, height = w
12351232
else:
@@ -1241,20 +1238,18 @@ def set_size_inches(self, w, h=None, forward=True, auto=False):
12411238
width_true, height_true = self.get_size_inches()
12421239
width_trunc = int(self.bbox.width) / self.dpi
12431240
height_trunc = int(self.bbox.height) / self.dpi
1244-
if auto: # internal resizing not associated with any draws
1245-
with self._context_resizing():
1246-
super().set_size_inches(width, height, forward=forward)
1247-
else: # manual resizing on behalf of user
1248-
if (
1249-
(
1250-
width not in (width_true, width_trunc)
1251-
or height not in (height_true, height_trunc)
1252-
)
1253-
and not self._is_resizing
1254-
and not self.canvas._is_idle_drawing # standard
1255-
and not getattr(self.canvas, '_draw_pending', None) # pyqt5
1256-
):
1257-
self._subplots_kw.update(width=width, height=height)
1241+
if (
1242+
(
1243+
width not in (width_true, width_trunc)
1244+
or height not in (height_true, height_trunc)
1245+
)
1246+
and not auto
1247+
and not self._is_autoresizing
1248+
and not getattr(self.canvas, '_is_idle_drawing', None) # standard
1249+
):
1250+
self._subplots_kw.update(width=width, height=height)
1251+
context = self._context_autoresizing if auto else _dummy_context
1252+
with context():
12581253
super().set_size_inches(width, height, forward=forward)
12591254

12601255
def get_alignx(self):

proplot/ui.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ def subplots(
486486
y0, y1 = yrange[idx, 0], yrange[idx, 1]
487487
# Draw subplot
488488
subplotspec = gridspec[y0:y1 + 1, x0:x1 + 1]
489-
with fig._authorize_add_subplot():
489+
with fig._context_authorize_add_subplot():
490490
axs[idx] = fig.add_subplot(
491491
subplotspec, number=num, main=True,
492492
**axes_kw[num]

0 commit comments

Comments
 (0)