|
19 | 19 | from dask.dataframe import DataFrame as DaskDataFrame |
20 | 20 | from geopandas import GeoDataFrame |
21 | 21 | from matplotlib.axes import Axes |
| 22 | +from matplotlib.backend_bases import RendererBase |
22 | 23 | from matplotlib.colors import Colormap, LogNorm, Normalize |
23 | 24 | from matplotlib.figure import Figure |
24 | 25 | from mpl_toolkits.axes_grid1 import make_axes_locatable |
|
44 | 45 | CBAR_DEFAULT_FRACTION, |
45 | 46 | CBAR_DEFAULT_LOCATION, |
46 | 47 | CBAR_DEFAULT_PAD, |
47 | | - CBAR_STACK_PAD_INCHES, |
| 48 | + CBAR_STACK_GAP_INCHES, |
48 | 49 | ChannelLegendEntry, |
49 | 50 | CmapParams, |
50 | 51 | ColorbarSpec, |
@@ -1532,6 +1533,9 @@ def _draw_colorbar( |
1532 | 1533 | side_counts: dict[str, int], |
1533 | 1534 | clearance: dict[str, float], |
1534 | 1535 | axes_size_in: tuple[float, float], |
| 1536 | + prev_outer: dict[str, float], |
| 1537 | + renderer: RendererBase, |
| 1538 | + dpi: float, |
1535 | 1539 | ) -> None: |
1536 | 1540 | norm = spec.mappable.norm |
1537 | 1541 | if isinstance(norm, LogNorm): |
@@ -1566,19 +1570,18 @@ def _draw_colorbar( |
1566 | 1570 |
|
1567 | 1571 | # Append the colorbar axes through the panel's shared divider. This steals space from the |
1568 | 1572 | # panel (so the colorbar matches the equal-aspect plot's extent) and keeps it inside the |
1569 | | - # panel's grid cell, never overflowing into a neighbouring panel. The first colorbar on a |
1570 | | - # side is padded past the panel's own decorations (ticks/labels/title) plus the requested |
1571 | | - # pad; each subsequent one uses an absolute pad wide enough to clear the previous |
1572 | | - # colorbar's tick labels instead of overlapping them. |
1573 | | - # Sides with no decorations to clear (typically "right") keep the relative pad, which |
1574 | | - # matches the historical placement; sides whose ticks/labels/title must be cleared |
1575 | | - # (left/top/bottom), and stacked colorbars, use an absolute pad in inches. |
| 1573 | + # panel's grid cell, never overflowing into a neighbouring panel. |
| 1574 | + # Pad of the first colorbar on a side clears the panel's own decorations (ticks/labels/ |
| 1575 | + # title); a side with negligible clearance (typically "right") keeps the relative pad to |
| 1576 | + # match the historical placement. Each subsequent (stacked) colorbar is padded past the |
| 1577 | + # *measured* outer extent of the previous one (its tick labels AND axis label) so it never |
| 1578 | + # overlaps it. |
1576 | 1579 | n_on_side = side_counts.get(location, 0) |
1577 | 1580 | ref_in = axes_size_in[0] if location in {"left", "right"} else axes_size_in[1] |
1578 | 1581 | side_clearance = clearance.get(location, 0.0) |
1579 | 1582 | pad_spec: str | Fixed |
1580 | 1583 | if n_on_side: |
1581 | | - pad_spec = Fixed(CBAR_STACK_PAD_INCHES) |
| 1584 | + pad_spec = Fixed(prev_outer.get(location, 0.0) + CBAR_STACK_GAP_INCHES) |
1582 | 1585 | elif side_clearance > _CBAR_DECORATION_EPS_INCHES: |
1583 | 1586 | pad_spec = Fixed(side_clearance + max(pad, 0.0) * ref_in) |
1584 | 1587 | else: |
@@ -1615,6 +1618,21 @@ def _draw_colorbar( |
1615 | 1618 | with contextlib.suppress(Exception): |
1616 | 1619 | cb.solids.set_alpha(spec.alpha) |
1617 | 1620 |
|
| 1621 | + # Measure how far this colorbar's own ticks/labels/axis-label extend beyond its box on |
| 1622 | + # the outer side, so the next stacked colorbar on this side can be padded clear of them. |
| 1623 | + fig.canvas.draw() |
| 1624 | + cb_box = cax.get_window_extent(renderer) |
| 1625 | + cb_tight = cax.get_tightbbox(renderer) |
| 1626 | + if location == "right": |
| 1627 | + outer = (cb_tight.x1 - cb_box.x1) / dpi |
| 1628 | + elif location == "left": |
| 1629 | + outer = (cb_box.x0 - cb_tight.x0) / dpi |
| 1630 | + elif location == "top": |
| 1631 | + outer = (cb_tight.y1 - cb_box.y1) / dpi |
| 1632 | + else: # bottom |
| 1633 | + outer = (cb_box.y0 - cb_tight.y0) / dpi |
| 1634 | + prev_outer[location] = max(0.0, outer) |
| 1635 | + |
1618 | 1636 | # go through tree |
1619 | 1637 |
|
1620 | 1638 | for i, cs in enumerate(coordinate_systems): |
@@ -1829,8 +1847,9 @@ def _draw_colorbar( |
1829 | 1847 | # One divider per panel so multiple colorbars on the same panel stack via the divider. |
1830 | 1848 | divider = make_axes_locatable(axis) |
1831 | 1849 | side_counts: dict[str, int] = {} |
| 1850 | + prev_outer: dict[str, float] = {} |
1832 | 1851 | for spec in unique_specs: |
1833 | | - _draw_colorbar(spec, fig, divider, side_counts, clearance, axes_size_in) |
| 1852 | + _draw_colorbar(spec, fig, divider, side_counts, clearance, axes_size_in, prev_outer, renderer, dpi) |
1834 | 1853 |
|
1835 | 1854 | if fig_params.fig is not None and save is not None: |
1836 | 1855 | save_fig(fig_params.fig, path=save) |
|
0 commit comments