Skip to content

Commit 4f070ac

Browse files
ENH: Allow tuple for borderpad in AnchoredOffsetbox (#30359)
1 parent 5c0d055 commit 4f070ac

File tree

6 files changed

+88
-12
lines changed

6 files changed

+88
-12
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
``borderpad`` accepts a tuple for separate x/y padding
2+
-------------------------------------------------------
3+
4+
The ``borderpad`` parameter used for placing anchored artists (such as inset axes) now accepts a tuple of ``(x_pad, y_pad)``.
5+
6+
This allows for specifying separate padding values for the horizontal and
7+
vertical directions, providing finer control over placement. For example, when
8+
placing an inset in a corner, one might want horizontal padding to avoid
9+
overlapping with the main plot's axis labels, but no vertical padding to keep
10+
the inset flush with the plot area edge.
11+
12+
Example usage with :func:`~mpl_toolkits.axes_grid1.inset_locator.inset_axes`:
13+
14+
.. code-block:: python
15+
16+
ax_inset = inset_axes(
17+
ax, width="30%", height="30%", loc='upper left',
18+
borderpad=(4, 0))

lib/matplotlib/legend.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1140,9 +1140,10 @@ def _get_anchored_bbox(self, loc, bbox, parentbbox, renderer):
11401140
parentbbox : `~matplotlib.transforms.Bbox`
11411141
A parent box which will contain the bbox, in display coordinates.
11421142
"""
1143+
pad = self.borderaxespad * renderer.points_to_pixels(self._fontsize)
11431144
return offsetbox._get_anchored_bbox(
11441145
loc, bbox, parentbbox,
1145-
self.borderaxespad * renderer.points_to_pixels(self._fontsize))
1146+
pad, pad)
11461147

11471148
def _find_best_position(self, width, height, renderer):
11481149
"""Determine the best location to place the legend."""

lib/matplotlib/offsetbox.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -946,8 +946,13 @@ def __init__(self, loc, *,
946946
See the parameter *loc* of `.Legend` for details.
947947
pad : float, default: 0.4
948948
Padding around the child as fraction of the fontsize.
949-
borderpad : float, default: 0.5
949+
borderpad : float or (float, float), default: 0.5
950950
Padding between the offsetbox frame and the *bbox_to_anchor*.
951+
If a float, the same padding is used for both x and y.
952+
If a tuple of two floats, it specifies the (x, y) padding.
953+
954+
.. versionadded:: 3.11
955+
The *borderpad* parameter now accepts a tuple of (x, y) paddings.
951956
child : `.OffsetBox`
952957
The box that will be anchored.
953958
prop : `.FontProperties`
@@ -1054,12 +1059,22 @@ def set_bbox_to_anchor(self, bbox, transform=None):
10541059
@_compat_get_offset
10551060
def get_offset(self, bbox, renderer):
10561061
# docstring inherited
1057-
pad = (self.borderpad
1058-
* renderer.points_to_pixels(self.prop.get_size_in_points()))
1062+
fontsize_in_pixels = renderer.points_to_pixels(self.prop.get_size_in_points())
1063+
try:
1064+
borderpad_x, borderpad_y = self.borderpad
1065+
except TypeError:
1066+
borderpad_x = self.borderpad
1067+
borderpad_y = self.borderpad
1068+
pad_x_pixels = borderpad_x * fontsize_in_pixels
1069+
pad_y_pixels = borderpad_y * fontsize_in_pixels
10591070
bbox_to_anchor = self.get_bbox_to_anchor()
10601071
x0, y0 = _get_anchored_bbox(
1061-
self.loc, Bbox.from_bounds(0, 0, bbox.width, bbox.height),
1062-
bbox_to_anchor, pad)
1072+
self.loc,
1073+
Bbox.from_bounds(0, 0, bbox.width, bbox.height),
1074+
bbox_to_anchor,
1075+
pad_x_pixels,
1076+
pad_y_pixels
1077+
)
10631078
return x0 - bbox.x0, y0 - bbox.y0
10641079

10651080
def update_frame(self, bbox, fontsize=None):
@@ -1084,15 +1099,15 @@ def draw(self, renderer):
10841099
self.stale = False
10851100

10861101

1087-
def _get_anchored_bbox(loc, bbox, parentbbox, borderpad):
1102+
def _get_anchored_bbox(loc, bbox, parentbbox, pad_x, pad_y):
10881103
"""
10891104
Return the (x, y) position of the *bbox* anchored at the *parentbbox* with
1090-
the *loc* code with the *borderpad*.
1105+
the *loc* code with the *borderpad* and padding *pad_x*, *pad_y*.
10911106
"""
10921107
# This is only called internally and *loc* should already have been
10931108
# validated. If 0 (None), we just let ``bbox.anchored`` raise.
10941109
c = [None, "NE", "NW", "SW", "SE", "E", "W", "E", "S", "N", "C"][loc]
1095-
container = parentbbox.padded(-borderpad)
1110+
container = parentbbox.padded(-pad_x, -pad_y)
10961111
return bbox.anchored(c, container=container).p0
10971112

10981113

lib/matplotlib/offsetbox.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ class AnchoredOffsetbox(OffsetBox):
157157
loc: str,
158158
*,
159159
pad: float = ...,
160-
borderpad: float = ...,
160+
borderpad: float | tuple[float, float] = ...,
161161
child: OffsetBox | None = ...,
162162
prop: FontProperties | None = ...,
163163
frameon: bool = ...,
@@ -185,7 +185,7 @@ class AnchoredText(AnchoredOffsetbox):
185185
loc: str,
186186
*,
187187
pad: float = ...,
188-
borderpad: float = ...,
188+
borderpad: float | tuple[float, float] = ...,
189189
prop: dict[str, Any] | None = ...,
190190
**kwargs
191191
) -> None: ...

lib/matplotlib/tests/test_offsetbox.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,3 +470,40 @@ def test_draggable_in_subfigure():
470470
bbox = ann.get_window_extent()
471471
MouseEvent("button_press_event", fig.canvas, bbox.x1+2, bbox.y1+2)._process()
472472
assert not ann._draggable.got_artist
473+
474+
475+
def test_anchored_offsetbox_tuple_and_float_borderpad():
476+
"""
477+
Test AnchoredOffsetbox correctly handles both float and tuple for borderpad.
478+
"""
479+
480+
fig, ax = plt.subplots()
481+
482+
# Case 1: Establish a baseline with float value
483+
text_float = AnchoredText("float", loc='lower left', borderpad=5)
484+
ax.add_artist(text_float)
485+
486+
# Case 2: Test that a symmetric tuple gives the exact same result.
487+
text_tuple_equal = AnchoredText("tuple", loc='lower left', borderpad=(5, 5))
488+
ax.add_artist(text_tuple_equal)
489+
490+
# Case 3: Test that an asymmetric tuple with different values works as expected.
491+
text_tuple_asym = AnchoredText("tuple_asym", loc='lower left', borderpad=(10, 4))
492+
ax.add_artist(text_tuple_asym)
493+
494+
# Draw the canvas to calculate final positions
495+
fig.canvas.draw()
496+
497+
pos_float = text_float.get_window_extent()
498+
pos_tuple_equal = text_tuple_equal.get_window_extent()
499+
pos_tuple_asym = text_tuple_asym.get_window_extent()
500+
501+
# Assertion 1: Prove that borderpad=5 is identical to borderpad=(5, 5).
502+
assert pos_tuple_equal.x0 == pos_float.x0
503+
assert pos_tuple_equal.y0 == pos_float.y0
504+
505+
# Assertion 2: Prove that the asymmetric padding moved the box
506+
# further from the origin than the baseline in the x-direction and less far
507+
# in the y-direction.
508+
assert pos_tuple_asym.x0 > pos_float.x0
509+
assert pos_tuple_asym.y0 < pos_float.y0

lib/mpl_toolkits/axes_grid1/inset_locator.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,11 +341,16 @@ def inset_axes(parent_axes, width, height, loc='upper right',
341341
342342
%(Axes:kwdoc)s
343343
344-
borderpad : float, default: 0.5
344+
borderpad : float or (float, float), default: 0.5
345345
Padding between inset axes and the bbox_to_anchor.
346+
If a float, the same padding is used for both x and y.
347+
If a tuple of two floats, it specifies the (x, y) padding.
346348
The units are axes font size, i.e. for a default font size of 10 points
347349
*borderpad = 0.5* is equivalent to a padding of 5 points.
348350
351+
.. versionadded:: 3.11
352+
The *borderpad* parameter now accepts a tuple of (x, y) paddings.
353+
349354
Returns
350355
-------
351356
inset_axes : *axes_class*

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy