Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d8bb1a5

Browse files
authored
ENH: rely on non-rectangular patch paths rather than bboxes for legend auto-placing (fix #9580) (#9598)
* use path rather than bbox for non rectangular patches * Add tests * Add a short breadcrumb note in api_changes
1 parent 7e32745 commit d8bb1a5

File tree

3 files changed

+69
-5
lines changed

3 files changed

+69
-5
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Change of ``legend(loc="best")`` behavior
2+
-----------------------------------------
3+
4+
The algorithm of the auto-legend locator has been tweaked to better handle
5+
non rectangular patches. Additional details on this change can be found in
6+
:ghissue:`9580` and :ghissue:`9598`.

lib/matplotlib/legend.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -865,12 +865,14 @@ def _auto_legend_data(self):
865865
bboxes.append(
866866
artist.get_bbox().transformed(artist.get_data_transform()))
867867
elif isinstance(artist, Patch):
868-
bboxes.append(
869-
artist.get_path().get_extents(artist.get_transform()))
868+
lines.append(
869+
artist.get_transform().transform_path(artist.get_path()))
870870
elif isinstance(artist, Collection):
871-
_, offset_trf, hoffsets, _ = artist._prepare_points()
872-
for offset in offset_trf.transform(hoffsets):
873-
offsets.append(offset)
871+
transform, transOffset, hoffsets, _ = artist._prepare_points()
872+
if len(hoffsets):
873+
for offset in transOffset.transform(hoffsets):
874+
offsets.append(offset)
875+
874876
return bboxes, lines, offsets
875877

876878
def get_children(self):

lib/matplotlib/tests/test_legend.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@
1010
from matplotlib.testing._markers import needs_usetex
1111
import matplotlib.pyplot as plt
1212
import matplotlib as mpl
13+
import matplotlib.patches as mpatches
1314
import matplotlib.transforms as mtransforms
1415
import matplotlib.collections as mcollections
1516
import matplotlib.lines as mlines
1617
from matplotlib.legend_handler import HandlerTuple
1718
import matplotlib.legend as mlegend
1819
from matplotlib import rc_context
1920
from matplotlib.font_manager import FontProperties
21+
from numpy.testing import assert_allclose
2022

2123

2224
def test_legend_ordereddict():
@@ -69,6 +71,60 @@ def test_legend_auto3():
6971
ax.legend(loc='best')
7072

7173

74+
def test_legend_auto4():
75+
"""
76+
Check that the legend location with automatic placement is the same,
77+
whatever the histogram type is. Related to issue #9580.
78+
"""
79+
# NB: barstacked is pointless with a single dataset.
80+
fig, axs = plt.subplots(ncols=3, figsize=(6.4, 2.4))
81+
leg_bboxes = []
82+
for ax, ht in zip(axs.flat, ('bar', 'step', 'stepfilled')):
83+
ax.set_title(ht)
84+
# A high bar on the left but an even higher one on the right.
85+
ax.hist([0] + 5*[9], bins=range(10), label="Legend", histtype=ht)
86+
leg = ax.legend(loc="best")
87+
fig.canvas.draw()
88+
leg_bboxes.append(
89+
leg.get_window_extent().transformed(ax.transAxes.inverted()))
90+
91+
# The histogram type "bar" is assumed to be the correct reference.
92+
assert_allclose(leg_bboxes[1].bounds, leg_bboxes[0].bounds)
93+
assert_allclose(leg_bboxes[2].bounds, leg_bboxes[0].bounds)
94+
95+
96+
def test_legend_auto5():
97+
"""
98+
Check that the automatic placement handle a rather complex
99+
case with non rectangular patch. Related to issue #9580.
100+
"""
101+
fig, axs = plt.subplots(ncols=2, figsize=(9.6, 4.8))
102+
103+
leg_bboxes = []
104+
for ax, loc in zip(axs.flat, ("center", "best")):
105+
# An Ellipse patch at the top, a U-shaped Polygon patch at the
106+
# bottom and a ring-like Wedge patch: the correct placement of
107+
# the legend should be in the center.
108+
for _patch in [
109+
mpatches.Ellipse(
110+
xy=(0.5, 0.9), width=0.8, height=0.2, fc="C1"),
111+
mpatches.Polygon(np.array([
112+
[0, 1], [0, 0], [1, 0], [1, 1], [0.9, 1.0], [0.9, 0.1],
113+
[0.1, 0.1], [0.1, 1.0], [0.1, 1.0]]), fc="C1"),
114+
mpatches.Wedge((0.5, 0.5), 0.5, 0, 360, width=0.05, fc="C0")
115+
]:
116+
ax.add_patch(_patch)
117+
118+
ax.plot([0.1, 0.9], [0.9, 0.9], label="A segment") # sthg to label
119+
120+
leg = ax.legend(loc=loc)
121+
fig.canvas.draw()
122+
leg_bboxes.append(
123+
leg.get_window_extent().transformed(ax.transAxes.inverted()))
124+
125+
assert_allclose(leg_bboxes[1].bounds, leg_bboxes[0].bounds)
126+
127+
72128
@image_comparison(['legend_various_labels'], remove_text=True)
73129
def test_various_labels():
74130
# tests all sorts of label types

0 commit comments

Comments
 (0)