Skip to content

ENH: Allow tuple for borderpad in AnchoredOffsetbox #30359

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions doc/users/next_whats_new/updated_borderpad_parameter.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
``borderpad`` accepts a tuple for separate x/y padding
-------------------------------------------------------

The ``borderpad`` parameter used for placing anchored artists (such as inset axes) now accepts a tuple of ``(x_pad, y_pad)``.

This allows for specifying separate padding values for the horizontal and
vertical directions, providing finer control over placement. For example, when
placing an inset in a corner, one might want horizontal padding to avoid
overlapping with the main plot's axis labels, but no vertical padding to keep
the inset flush with the plot area edge.

Example usage with :func:`~mpl_toolkits.axes_grid1.inset_locator.inset_axes`:

.. code-block:: python

ax_inset = inset_axes(
ax, width="30%", height="30%", loc='upper left',
borderpad=(4, 0))
3 changes: 2 additions & 1 deletion lib/matplotlib/legend.py
Original file line number Diff line number Diff line change
Expand Up @@ -1140,9 +1140,10 @@ def _get_anchored_bbox(self, loc, bbox, parentbbox, renderer):
parentbbox : `~matplotlib.transforms.Bbox`
A parent box which will contain the bbox, in display coordinates.
"""
pad = self.borderaxespad * renderer.points_to_pixels(self._fontsize)
return offsetbox._get_anchored_bbox(
loc, bbox, parentbbox,
self.borderaxespad * renderer.points_to_pixels(self._fontsize))
pad, pad)

def _find_best_position(self, width, height, renderer):
"""Determine the best location to place the legend."""
Expand Down
31 changes: 23 additions & 8 deletions lib/matplotlib/offsetbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -946,8 +946,13 @@ def __init__(self, loc, *,
See the parameter *loc* of `.Legend` for details.
pad : float, default: 0.4
Padding around the child as fraction of the fontsize.
borderpad : float, default: 0.5
borderpad : float or (float, float), default: 0.5
Padding between the offsetbox frame and the *bbox_to_anchor*.
If a float, the same padding is used for both x and y.
If a tuple of two floats, it specifies the (x, y) padding.

.. versionadded:: 3.11
The *borderpad* parameter now accepts a tuple of (x, y) paddings.
child : `.OffsetBox`
The box that will be anchored.
prop : `.FontProperties`
Expand Down Expand Up @@ -1054,12 +1059,22 @@ def set_bbox_to_anchor(self, bbox, transform=None):
@_compat_get_offset
def get_offset(self, bbox, renderer):
# docstring inherited
pad = (self.borderpad
* renderer.points_to_pixels(self.prop.get_size_in_points()))
fontsize_in_pixels = renderer.points_to_pixels(self.prop.get_size_in_points())
try:
borderpad_x, borderpad_y = self.borderpad
except TypeError:
borderpad_x = self.borderpad
borderpad_y = self.borderpad
pad_x_pixels = borderpad_x * fontsize_in_pixels
pad_y_pixels = borderpad_y * fontsize_in_pixels
bbox_to_anchor = self.get_bbox_to_anchor()
x0, y0 = _get_anchored_bbox(
self.loc, Bbox.from_bounds(0, 0, bbox.width, bbox.height),
bbox_to_anchor, pad)
self.loc,
Bbox.from_bounds(0, 0, bbox.width, bbox.height),
bbox_to_anchor,
pad_x_pixels,
pad_y_pixels
)
return x0 - bbox.x0, y0 - bbox.y0

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


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


Expand Down
4 changes: 2 additions & 2 deletions lib/matplotlib/offsetbox.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ class AnchoredOffsetbox(OffsetBox):
loc: str,
*,
pad: float = ...,
borderpad: float = ...,
borderpad: float | tuple[float, float] = ...,
child: OffsetBox | None = ...,
prop: FontProperties | None = ...,
frameon: bool = ...,
Expand Down Expand Up @@ -185,7 +185,7 @@ class AnchoredText(AnchoredOffsetbox):
loc: str,
*,
pad: float = ...,
borderpad: float = ...,
borderpad: float | tuple[float, float] = ...,
prop: dict[str, Any] | None = ...,
**kwargs
) -> None: ...
Expand Down
37 changes: 37 additions & 0 deletions lib/matplotlib/tests/test_offsetbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,3 +470,40 @@ def test_draggable_in_subfigure():
bbox = ann.get_window_extent()
MouseEvent("button_press_event", fig.canvas, bbox.x1+2, bbox.y1+2)._process()
assert not ann._draggable.got_artist


def test_anchored_offsetbox_tuple_and_float_borderpad():
"""
Test AnchoredOffsetbox correctly handles both float and tuple for borderpad.
"""

fig, ax = plt.subplots()

# Case 1: Establish a baseline with float value
text_float = AnchoredText("float", loc='lower left', borderpad=5)
ax.add_artist(text_float)

# Case 2: Test that a symmetric tuple gives the exact same result.
text_tuple_equal = AnchoredText("tuple", loc='lower left', borderpad=(5, 5))
ax.add_artist(text_tuple_equal)

# Case 3: Test that an asymmetric tuple with different values works as expected.
text_tuple_asym = AnchoredText("tuple_asym", loc='lower left', borderpad=(10, 4))
ax.add_artist(text_tuple_asym)

# Draw the canvas to calculate final positions
fig.canvas.draw()

pos_float = text_float.get_window_extent()
pos_tuple_equal = text_tuple_equal.get_window_extent()
pos_tuple_asym = text_tuple_asym.get_window_extent()

# Assertion 1: Prove that borderpad=5 is identical to borderpad=(5, 5).
assert pos_tuple_equal.x0 == pos_float.x0
assert pos_tuple_equal.y0 == pos_float.y0

# Assertion 2: Prove that the asymmetric padding moved the box
# further from the origin than the baseline in the x-direction and less far
# in the y-direction.
assert pos_tuple_asym.x0 > pos_float.x0
assert pos_tuple_asym.y0 < pos_float.y0
7 changes: 6 additions & 1 deletion lib/mpl_toolkits/axes_grid1/inset_locator.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,11 +341,16 @@ def inset_axes(parent_axes, width, height, loc='upper right',
%(Axes:kwdoc)s
borderpad : float, default: 0.5
borderpad : float or (float, float), default: 0.5
Padding between inset axes and the bbox_to_anchor.
If a float, the same padding is used for both x and y.
If a tuple of two floats, it specifies the (x, y) padding.
The units are axes font size, i.e. for a default font size of 10 points
*borderpad = 0.5* is equivalent to a padding of 5 points.
.. versionadded:: 3.11
The *borderpad* parameter now accepts a tuple of (x, y) paddings.
Returns
-------
inset_axes : *axes_class*
Expand Down
Loading
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