|
14 | 14 | import matplotlib.transforms as mtransforms |
15 | 15 | import matplotlib.gridspec as mgridspec |
16 | 16 | from .rctools import rc |
17 | | -from .utils import _warn_proplot, _notNone, _counter, units |
| 17 | +from .utils import _warn_proplot, _notNone, _counter, _setstate, units |
18 | 18 | from . import projs, axes |
19 | 19 | __all__ = [ |
20 | 20 | 'subplot_grid', 'close', 'show', 'subplots', 'Figure', |
@@ -726,19 +726,6 @@ def __exit__(self, *args): |
726 | 726 | label.set_visible(True) |
727 | 727 |
|
728 | 728 |
|
729 | | -class _unlocker(object): |
730 | | - """Suppress warning message when adding subplots, and cleanly reset |
731 | | - lock setting if exception raised.""" |
732 | | - def __init__(self, fig): |
733 | | - self._fig = fig |
734 | | - |
735 | | - def __enter__(self): |
736 | | - self._fig._locked = False |
737 | | - |
738 | | - def __exit__(self, *args): |
739 | | - self._fig._locked = True |
740 | | - |
741 | | - |
742 | 729 | class Figure(mfigure.Figure): |
743 | 730 | """The `~matplotlib.figure.Figure` class returned by `subplots`. At |
744 | 731 | draw-time, an improved tight layout algorithm is employed, and |
@@ -816,8 +803,10 @@ def __init__(self, |
816 | 803 | f'contrained_layout={constrained_layout}. ProPlot uses its ' |
817 | 804 | 'own tight layout algorithm, activated by default or with ' |
818 | 805 | 'tight=True.') |
| 806 | + self._authorized_add_subplot = False |
| 807 | + self._is_preprocessing = False |
| 808 | + self._is_resizing = False |
819 | 809 | super().__init__(**kwargs) |
820 | | - self._locked = False |
821 | 810 | self._pad = units(_notNone(pad, rc['subplots.pad'])) |
822 | 811 | self._axpad = units(_notNone(axpad, rc['subplots.axpad'])) |
823 | 812 | self._panelpad = units(_notNone(panelpad, rc['subplots.panelpad'])) |
@@ -880,7 +869,7 @@ def _add_axes_panel(self, ax, side, filled=False, **kwargs): |
880 | 869 | idx2 += 1 |
881 | 870 |
|
882 | 871 | # Draw and setup panel |
883 | | - with self._unlock(): |
| 872 | + with self._authorize_add_subplot(): |
884 | 873 | pax = self.add_subplot( |
885 | 874 | gridspec[idx1, idx2], |
886 | 875 | sharex=ax._sharex_level, sharey=ax._sharey_level, |
@@ -989,7 +978,7 @@ def _add_figure_panel(self, side, |
989 | 978 | side, iratio, width, space, space_orig, figure=True) |
990 | 979 |
|
991 | 980 | # Draw and setup panel |
992 | | - with self._unlock(): |
| 981 | + with self._authorize_add_subplot(): |
993 | 982 | pax = self.add_subplot(gridspec[idx1, idx2], |
994 | 983 | projection='xy') |
995 | 984 | getattr(self, '_' + s + 'panels').append(pax) |
@@ -1286,6 +1275,86 @@ def _align_labels(self, renderer): |
1286 | 1275 | 'transform': self.transFigure} |
1287 | 1276 | suptitle.update(kw) |
1288 | 1277 |
|
| 1278 | + def _authorize_add_subplot(self): |
| 1279 | + """Prevent warning message when adding subplots one-by-one. Used |
| 1280 | + internally.""" |
| 1281 | + return _setstate(self, _authorized_add_subplot=True) |
| 1282 | + |
| 1283 | + def _context_resizing(self): |
| 1284 | + """Ensure backend calls to `~matplotlib.figure.Figure.set_size_inches` |
| 1285 | + during pre-processing are not interpreted as *manual* resizing.""" |
| 1286 | + return _setstate(self, _is_resizing=True) |
| 1287 | + |
| 1288 | + def _context_preprocessing(self): |
| 1289 | + """Prevent re-running pre-processing steps due to draws triggered |
| 1290 | + by figure resizes during pre-processing.""" |
| 1291 | + return _setstate(self, _is_preprocessing=True) |
| 1292 | + |
| 1293 | + def _get_align_coord(self, side, axs): |
| 1294 | + """Returns figure coordinate for spanning labels and super title. The |
| 1295 | + `x` can be ``'x'`` or ``'y'``.""" |
| 1296 | + # Get position in figure relative coordinates |
| 1297 | + s = side[0] |
| 1298 | + x = ('y' if s in 'lr' else 'x') |
| 1299 | + extra = ('tb' if s in 'lr' else 'lr') |
| 1300 | + if self._include_panels: |
| 1301 | + axs = [iax for ax in axs for iax in ax._iter_panels(extra)] |
| 1302 | + ranges = np.array([ax._range_gridspec(x) for ax in axs]) |
| 1303 | + min_, max_ = ranges[:, 0].min(), ranges[:, 1].max() |
| 1304 | + axlo = axs[np.where(ranges[:, 0] == min_)[0][0]] |
| 1305 | + axhi = axs[np.where(ranges[:, 1] == max_)[0][0]] |
| 1306 | + lobox = axlo.get_subplotspec().get_position(self) |
| 1307 | + hibox = axhi.get_subplotspec().get_position(self) |
| 1308 | + if x == 'x': |
| 1309 | + pos = (lobox.x0 + hibox.x1) / 2 |
| 1310 | + else: |
| 1311 | + # 'lo' is actually on top, highest up in gridspec |
| 1312 | + pos = (lobox.y1 + hibox.y0) / 2 |
| 1313 | + # Return axis suitable for spanning position |
| 1314 | + spanax = axs[(np.argmin(ranges[:, 0]) + np.argmax(ranges[:, 1])) // 2] |
| 1315 | + spanax = spanax._panel_parent or spanax |
| 1316 | + return pos, spanax |
| 1317 | + |
| 1318 | + def _get_align_axes(self, side): |
| 1319 | + """Returns main axes along the left, right, bottom, or top sides |
| 1320 | + of the figure.""" |
| 1321 | + # Initial stuff |
| 1322 | + s = side[0] |
| 1323 | + idx = (0 if s in 'lt' else 1) |
| 1324 | + if s in 'lr': |
| 1325 | + x, y = 'x', 'y' |
| 1326 | + else: |
| 1327 | + x, y = 'y', 'x' |
| 1328 | + # Get edge index |
| 1329 | + axs = self._axes_main |
| 1330 | + if not axs: |
| 1331 | + return [] |
| 1332 | + ranges = np.array([ax._range_gridspec(x) for ax in axs]) |
| 1333 | + min_, max_ = ranges[:, 0].min(), ranges[:, 1].max() |
| 1334 | + edge = (min_ if s in 'lt' else max_) |
| 1335 | + # Return axes on edge sorted by order of appearance |
| 1336 | + axs = [ax for ax in self._axes_main if ax._range_gridspec(x)[ |
| 1337 | + idx] == edge] |
| 1338 | + ranges = [ax._range_gridspec(y)[0] for ax in axs] |
| 1339 | + return [ax for _, ax in sorted(zip(ranges, axs)) if ax.get_visible()] |
| 1340 | + |
| 1341 | + def _get_renderer(self): |
| 1342 | + """Get a renderer at all costs, even if it means generating a brand |
| 1343 | + new one! Used for updating the figure bounding box when it is accessed |
| 1344 | + and calculating centered-row legend bounding boxes. This is copied |
| 1345 | + from tight_layout.py in matplotlib.""" |
| 1346 | + if self._cachedRenderer: |
| 1347 | + renderer = self._cachedRenderer |
| 1348 | + else: |
| 1349 | + canvas = self.canvas |
| 1350 | + if canvas and hasattr(canvas, 'get_renderer'): |
| 1351 | + renderer = canvas.get_renderer() |
| 1352 | + else: |
| 1353 | + from matplotlib.backends.backend_agg import FigureCanvasAgg |
| 1354 | + canvas = FigureCanvasAgg(self) |
| 1355 | + renderer = canvas.get_renderer() |
| 1356 | + return renderer |
| 1357 | + |
1289 | 1358 | def _insert_row_column( |
1290 | 1359 | self, side, idx, |
1291 | 1360 | ratio, space, space_orig, figure=False): |
@@ -1388,76 +1457,6 @@ def _insert_row_column( |
1388 | 1457 |
|
1389 | 1458 | return gridspec |
1390 | 1459 |
|
1391 | | - def _get_align_coord(self, side, axs): |
1392 | | - """Returns figure coordinate for spanning labels and super title. The |
1393 | | - `x` can be ``'x'`` or ``'y'``.""" |
1394 | | - # Get position in figure relative coordinates |
1395 | | - s = side[0] |
1396 | | - x = ('y' if s in 'lr' else 'x') |
1397 | | - extra = ('tb' if s in 'lr' else 'lr') |
1398 | | - if self._include_panels: |
1399 | | - axs = [iax for ax in axs for iax in ax._iter_panels(extra)] |
1400 | | - ranges = np.array([ax._range_gridspec(x) for ax in axs]) |
1401 | | - min_, max_ = ranges[:, 0].min(), ranges[:, 1].max() |
1402 | | - axlo = axs[np.where(ranges[:, 0] == min_)[0][0]] |
1403 | | - axhi = axs[np.where(ranges[:, 1] == max_)[0][0]] |
1404 | | - lobox = axlo.get_subplotspec().get_position(self) |
1405 | | - hibox = axhi.get_subplotspec().get_position(self) |
1406 | | - if x == 'x': |
1407 | | - pos = (lobox.x0 + hibox.x1) / 2 |
1408 | | - else: |
1409 | | - # 'lo' is actually on top, highest up in gridspec |
1410 | | - pos = (lobox.y1 + hibox.y0) / 2 |
1411 | | - # Return axis suitable for spanning position |
1412 | | - spanax = axs[(np.argmin(ranges[:, 0]) + np.argmax(ranges[:, 1])) // 2] |
1413 | | - spanax = spanax._panel_parent or spanax |
1414 | | - return pos, spanax |
1415 | | - |
1416 | | - def _get_align_axes(self, side): |
1417 | | - """Returns main axes along the left, right, bottom, or top sides |
1418 | | - of the figure.""" |
1419 | | - # Initial stuff |
1420 | | - s = side[0] |
1421 | | - idx = (0 if s in 'lt' else 1) |
1422 | | - if s in 'lr': |
1423 | | - x, y = 'x', 'y' |
1424 | | - else: |
1425 | | - x, y = 'y', 'x' |
1426 | | - # Get edge index |
1427 | | - axs = self._axes_main |
1428 | | - if not axs: |
1429 | | - return [] |
1430 | | - ranges = np.array([ax._range_gridspec(x) for ax in axs]) |
1431 | | - min_, max_ = ranges[:, 0].min(), ranges[:, 1].max() |
1432 | | - edge = (min_ if s in 'lt' else max_) |
1433 | | - # Return axes on edge sorted by order of appearance |
1434 | | - axs = [ax for ax in self._axes_main if ax._range_gridspec(x)[ |
1435 | | - idx] == edge] |
1436 | | - ranges = [ax._range_gridspec(y)[0] for ax in axs] |
1437 | | - return [ax for _, ax in sorted(zip(ranges, axs)) if ax.get_visible()] |
1438 | | - |
1439 | | - def _get_renderer(self): |
1440 | | - """Get a renderer at all costs, even if it means generating a brand |
1441 | | - new one! Used for updating the figure bounding box when it is accessed |
1442 | | - and calculating centered-row legend bounding boxes. This is copied |
1443 | | - from tight_layout.py in matplotlib.""" |
1444 | | - if self._cachedRenderer: |
1445 | | - renderer = self._cachedRenderer |
1446 | | - else: |
1447 | | - canvas = self.canvas |
1448 | | - if canvas and hasattr(canvas, 'get_renderer'): |
1449 | | - renderer = canvas.get_renderer() |
1450 | | - else: |
1451 | | - from matplotlib.backends.backend_agg import FigureCanvasAgg |
1452 | | - canvas = FigureCanvasAgg(self) |
1453 | | - renderer = canvas.get_renderer() |
1454 | | - return renderer |
1455 | | - |
1456 | | - def _unlock(self): |
1457 | | - """Prevent warning message when adding subplots one-by-one. Used |
1458 | | - internally.""" |
1459 | | - return _unlocker(self) |
1460 | | - |
1461 | 1460 | def _update_figtitle(self, title, **kwargs): |
1462 | 1461 | """Assign figure "super title".""" |
1463 | 1462 | if title is not None and self._suptitle.get_text() != title: |
@@ -1493,10 +1492,11 @@ def _update_labels(self, ax, side, labels, **kwargs): |
1493 | 1492 | def add_subplot(self, *args, **kwargs): |
1494 | 1493 | """Issues warning for new users that try to call |
1495 | 1494 | `~matplotlib.figure.Figure.add_subplot` manually.""" |
1496 | | - if self._locked: |
| 1495 | + if not self._authorized_add_subplot: |
1497 | 1496 | _warn_proplot( |
1498 | 1497 | 'Using "fig.add_subplot()" with ProPlot figures may result in ' |
1499 | | - 'unexpected behavior. Use "proplot.subplots()" instead.') |
| 1498 | + 'unexpected behavior. Please use "proplot.subplots()" instead.' |
| 1499 | + ) |
1500 | 1500 | ax = super().add_subplot(*args, **kwargs) |
1501 | 1501 | return ax |
1502 | 1502 |
|
@@ -2138,7 +2138,7 @@ def subplots( |
2138 | 2138 | y0, y1 = yrange[idx, 0], yrange[idx, 1] |
2139 | 2139 | # Draw subplot |
2140 | 2140 | subplotspec = gridspec[y0:y1 + 1, x0:x1 + 1] |
2141 | | - with fig._unlock(): |
| 2141 | + with fig._authorize_add_subplot(): |
2142 | 2142 | axs[idx] = fig.add_subplot( |
2143 | 2143 | subplotspec, number=num, |
2144 | 2144 | spanx=spanx, spany=spany, alignx=alignx, aligny=aligny, |
|
0 commit comments