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

Skip to content

Commit da680dd

Browse files
Sanat GuptaSanat Gupta
authored andcommitted
Refactor: move legend property extraction to Legend._get_properties()
Address review feedback from @timhoffm: - Added Legend._get_properties() method that returns a dict of legend properties needed for recreation - figureoptions.py now calls old_legend._get_properties() instead of extracting properties inline - Updated tests to directly test _get_properties() method - Tests cover basic properties, mode/bbox_to_anchor, roundtrip recreation, and no-existing-legend edge case
1 parent 3a7f060 commit da680dd

3 files changed

Lines changed: 101 additions & 96 deletions

File tree

lib/matplotlib/backends/qt_editor/figureoptions.py

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -253,28 +253,7 @@ def apply_callback(data):
253253
old_legend = axes.get_legend()
254254
draggable = old_legend._draggable is not None
255255
ncols = old_legend._ncols
256-
# Preserve location and positioning
257-
legend_kwargs['loc'] = old_legend._loc
258-
if old_legend._bbox_to_anchor is not None:
259-
legend_kwargs['bbox_to_anchor'] = (
260-
old_legend._bbox_to_anchor.bounds)
261-
# Preserve styling
262-
legend_kwargs['fontsize'] = old_legend._fontsize
263-
legend_kwargs['frameon'] = old_legend.get_frame_on()
264-
legend_kwargs['shadow'] = old_legend.shadow
265-
legend_kwargs['framealpha'] = (
266-
old_legend.get_frame().get_alpha())
267-
legend_kwargs['title'] = (
268-
old_legend.get_title().get_text())
269-
# Preserve layout settings
270-
if old_legend._mode is not None:
271-
legend_kwargs['mode'] = old_legend._mode
272-
legend_kwargs['columnspacing'] = (
273-
old_legend.columnspacing)
274-
legend_kwargs['labelspacing'] = old_legend.labelspacing
275-
legend_kwargs['handlelength'] = old_legend.handlelength
276-
legend_kwargs['handletextpad'] = (
277-
old_legend.handletextpad)
256+
legend_kwargs = old_legend._get_properties()
278257
new_legend = axes.legend(ncols=ncols, **legend_kwargs)
279258
if new_legend:
280259
new_legend.set_draggable(draggable)

lib/matplotlib/legend.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,6 +1098,34 @@ def set_frame_on(self, b):
10981098

10991099
draw_frame = set_frame_on # Backcompat alias.
11001100

1101+
def _get_properties(self):
1102+
"""
1103+
Return a dictionary of legend properties for recreation.
1104+
1105+
This is used by the Qt figure options dialog to preserve legend
1106+
settings when regenerating the legend.
1107+
"""
1108+
props = {
1109+
'loc': self._loc,
1110+
'fontsize': self._fontsize,
1111+
'frameon': self.get_frame_on(),
1112+
'shadow': self.shadow,
1113+
'framealpha': self.get_frame().get_alpha(),
1114+
'title': self.get_title().get_text(),
1115+
'columnspacing': self.columnspacing,
1116+
'labelspacing': self.labelspacing,
1117+
'handlelength': self.handlelength,
1118+
'handletextpad': self.handletextpad,
1119+
'borderpad': self.borderpad,
1120+
'borderaxespad': self.borderaxespad,
1121+
'markerscale': self.markerscale,
1122+
}
1123+
if self._bbox_to_anchor is not None:
1124+
props['bbox_to_anchor'] = self._bbox_to_anchor.bounds
1125+
if self._mode is not None:
1126+
props['mode'] = self._mode
1127+
return props
1128+
11011129
def get_bbox_to_anchor(self):
11021130
"""Return the bbox that the legend will be anchored to."""
11031131
if self._bbox_to_anchor is None:
Lines changed: 72 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
1-
"""Tests for legend preservation in Qt figure options dialog."""
1+
"""Tests for legend property preservation."""
22
import matplotlib
33
matplotlib.use('Agg')
44
import matplotlib.pyplot as plt
55

66

7-
def test_legend_properties_preserved():
7+
def test_get_properties_basic():
88
"""
9-
Test that legend properties are preserved when the legend is
10-
regenerated via the Qt figure options dialog.
11-
12-
Regression test for https://github.com/matplotlib/matplotlib/issues/17775
9+
Test that Legend._get_properties() returns the correct properties.
1310
"""
1411
fig, ax = plt.subplots()
1512
ax.plot(range(5), label='a')
@@ -28,101 +25,102 @@ def test_legend_properties_preserved():
2825
handletextpad=1.2,
2926
)
3027

31-
old_legend = ax.get_legend()
32-
old_loc = old_legend._loc
33-
old_fontsize = old_legend._fontsize
34-
old_frameon = old_legend.get_frame_on()
35-
old_shadow = old_legend.shadow
36-
old_title = old_legend.get_title().get_text()
37-
old_ncols = old_legend._ncols
38-
old_columnspacing = old_legend.columnspacing
39-
old_labelspacing = old_legend.labelspacing
40-
old_handlelength = old_legend.handlelength
41-
old_handletextpad = old_legend.handletextpad
42-
43-
# Simulate what the patched figureoptions code does
44-
draggable = old_legend._draggable is not None
45-
ncols = old_legend._ncols
46-
legend_kwargs = {}
47-
legend_kwargs['loc'] = old_legend._loc
48-
if old_legend._bbox_to_anchor is not None:
49-
legend_kwargs['bbox_to_anchor'] = (
50-
old_legend._bbox_to_anchor.bounds)
51-
legend_kwargs['fontsize'] = old_legend._fontsize
52-
legend_kwargs['frameon'] = old_legend.get_frame_on()
53-
legend_kwargs['shadow'] = old_legend.shadow
54-
legend_kwargs['framealpha'] = old_legend.get_frame().get_alpha()
55-
legend_kwargs['title'] = old_legend.get_title().get_text()
56-
if old_legend._mode is not None:
57-
legend_kwargs['mode'] = old_legend._mode
58-
legend_kwargs['columnspacing'] = old_legend.columnspacing
59-
legend_kwargs['labelspacing'] = old_legend.labelspacing
60-
legend_kwargs['handlelength'] = old_legend.handlelength
61-
legend_kwargs['handletextpad'] = old_legend.handletextpad
62-
63-
new_legend = ax.legend(ncols=ncols, **legend_kwargs)
64-
if new_legend:
65-
new_legend.set_draggable(draggable)
66-
67-
assert new_legend._loc == old_loc
68-
assert new_legend._fontsize == old_fontsize
69-
assert new_legend.get_frame_on() == old_frameon
70-
assert new_legend.shadow == old_shadow
71-
assert new_legend.get_title().get_text() == old_title
72-
assert new_legend._ncols == old_ncols
73-
assert new_legend.columnspacing == old_columnspacing
74-
assert new_legend.labelspacing == old_labelspacing
75-
assert new_legend.handlelength == old_handlelength
76-
assert new_legend.handletextpad == old_handletextpad
28+
legend = ax.get_legend()
29+
props = legend._get_properties()
30+
31+
assert props['loc'] == legend._loc
32+
assert props['fontsize'] == 14
33+
assert props['frameon'] is False
34+
assert props['shadow'] is True
35+
assert props['title'] == 'My Legend'
36+
assert props['columnspacing'] == 3.0
37+
assert props['labelspacing'] == 1.5
38+
assert props['handlelength'] == 4.0
39+
assert props['handletextpad'] == 1.2
40+
assert 'bbox_to_anchor' not in props
41+
assert 'mode' not in props
7742

7843
plt.close(fig)
7944

8045

81-
def test_legend_regeneration_no_existing_legend():
46+
def test_get_properties_with_mode_and_bbox():
8247
"""
83-
Test that regenerating a legend when none exists still works.
48+
Test that _get_properties() includes mode and bbox_to_anchor
49+
when they are set.
8450
"""
8551
fig, ax = plt.subplots()
8652
ax.plot(range(5), label='a')
8753

88-
assert ax.get_legend() is None
54+
ax.legend(
55+
bbox_to_anchor=(0, 1.02, 1, 0.2),
56+
loc='lower left',
57+
mode='expand',
58+
ncols=2,
59+
)
8960

90-
new_legend = ax.legend(ncols=1)
91-
assert new_legend is not None
92-
assert len(new_legend.get_texts()) == 1
93-
assert new_legend.get_texts()[0].get_text() == 'a'
61+
legend = ax.get_legend()
62+
props = legend._get_properties()
63+
64+
assert props['mode'] == 'expand'
65+
assert 'bbox_to_anchor' in props
9466

9567
plt.close(fig)
9668

9769

98-
def test_legend_mode_preserved():
70+
def test_get_properties_roundtrip():
9971
"""
100-
Test that mode='expand' is preserved on legend regeneration.
72+
Test that properties from _get_properties() can be used to
73+
recreate a legend with the same settings.
10174
"""
10275
fig, ax = plt.subplots()
10376
ax.plot(range(5), label='a')
10477
ax.plot(range(3)[::-1], label='b')
10578

10679
ax.legend(
107-
loc='lower left',
108-
mode='expand',
80+
loc='upper right',
81+
fontsize=14,
82+
frameon=False,
83+
shadow=True,
84+
title='My Legend',
10985
ncols=2,
86+
columnspacing=3.0,
87+
labelspacing=1.5,
88+
handlelength=4.0,
89+
handletextpad=1.2,
11090
)
11191

11292
old_legend = ax.get_legend()
113-
old_mode = old_legend._mode
114-
old_loc = old_legend._loc
93+
props = old_legend._get_properties()
94+
ncols = old_legend._ncols
95+
96+
# Recreate legend using extracted properties
97+
new_legend = ax.legend(ncols=ncols, **props)
98+
99+
assert new_legend._fontsize == 14
100+
assert new_legend.get_frame_on() is False
101+
assert new_legend.shadow is True
102+
assert new_legend.get_title().get_text() == 'My Legend'
103+
assert new_legend._ncols == 2
104+
assert new_legend.columnspacing == 3.0
105+
assert new_legend.labelspacing == 1.5
106+
assert new_legend.handlelength == 4.0
107+
assert new_legend.handletextpad == 1.2
108+
109+
plt.close(fig)
110+
115111

116-
legend_kwargs = {}
117-
legend_kwargs['loc'] = old_legend._loc
118-
legend_kwargs['fontsize'] = old_legend._fontsize
119-
legend_kwargs['frameon'] = old_legend.get_frame_on()
120-
if old_legend._mode is not None:
121-
legend_kwargs['mode'] = old_legend._mode
112+
def test_legend_regeneration_no_existing_legend():
113+
"""
114+
Test that regenerating a legend when none exists still works.
115+
"""
116+
fig, ax = plt.subplots()
117+
ax.plot(range(5), label='a')
122118

123-
new_legend = ax.legend(ncols=old_legend._ncols, **legend_kwargs)
119+
assert ax.get_legend() is None
124120

125-
assert new_legend._mode == old_mode
126-
assert new_legend._loc == old_loc
121+
new_legend = ax.legend(ncols=1)
122+
assert new_legend is not None
123+
assert len(new_legend.get_texts()) == 1
124+
assert new_legend.get_texts()[0].get_text() == 'a'
127125

128126
plt.close(fig)

0 commit comments

Comments
 (0)