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

Skip to content
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
4 changes: 3 additions & 1 deletion lib/matplotlib/backends/qt_editor/figureoptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,11 +248,13 @@ def apply_callback(data):
if generate_legend:
draggable = None
ncols = 1
legend_kwargs = {}
if axes.legend_ is not None:
old_legend = axes.get_legend()
draggable = old_legend._draggable is not None
ncols = old_legend._ncols
new_legend = axes.legend(ncols=ncols)
legend_kwargs = old_legend._get_properties()
new_legend = axes.legend(ncols=ncols, **legend_kwargs)
if new_legend:
new_legend.set_draggable(draggable)
Comment on lines 258 to 259
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to not include draggable in _get_properties()?


Expand Down
28 changes: 28 additions & 0 deletions lib/matplotlib/legend.py
Original file line number Diff line number Diff line change
Expand Up @@ -1098,6 +1098,34 @@ def set_frame_on(self, b):

draw_frame = set_frame_on # Backcompat alias.

def _get_properties(self):
"""
Return a dictionary of legend properties for recreation.

This is used by the Qt figure options dialog to preserve legend
settings when regenerating the legend.
"""
props = {
'loc': self._loc,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The list seems incomplete. On what basis did you select the properties?

'fontsize': self._fontsize,
'frameon': self.get_frame_on(),
'shadow': self.shadow,
'framealpha': self.get_frame().get_alpha(),
'title': self.get_title().get_text(),
'columnspacing': self.columnspacing,
'labelspacing': self.labelspacing,
'handlelength': self.handlelength,
'handletextpad': self.handletextpad,
'borderpad': self.borderpad,
'borderaxespad': self.borderaxespad,
'markerscale': self.markerscale,
}
if self._bbox_to_anchor is not None:
props['bbox_to_anchor'] = self._bbox_to_anchor.bounds
if self._mode is not None:
props['mode'] = self._mode
Comment on lines +1123 to +1126
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can these two not be always returned? If that is the case document that they are only conditionally present and explain why.

return props

def get_bbox_to_anchor(self):
"""Return the bbox that the legend will be anchored to."""
if self._bbox_to_anchor is None:
Expand Down
126 changes: 126 additions & 0 deletions lib/matplotlib/tests/test_figureoptions_legend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
"""Tests for legend property preservation."""
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt


def test_get_properties_basic():
"""
Test that Legend._get_properties() returns the correct properties.
"""
fig, ax = plt.subplots()
ax.plot(range(5), label='a')
ax.plot(range(3)[::-1], label='b')

ax.legend(
loc='upper right',
fontsize=14,
frameon=False,
shadow=True,
title='My Legend',
ncols=2,
columnspacing=3.0,
labelspacing=1.5,
handlelength=4.0,
handletextpad=1.2,
)
Comment on lines +15 to +26
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make it more compact and safe by not repeating the names and values

Suggested change
ax.legend(
loc='upper right',
fontsize=14,
frameon=False,
shadow=True,
title='My Legend',
ncols=2,
columnspacing=3.0,
labelspacing=1.5,
handlelength=4.0,
handletextpad=1.2,
)
initial_props = dict(
loc='upper right',
fontsize=14,
frameon=False,
shadow=True,
title='My Legend',
columnspacing=3.0,
labelspacing=1.5,
handlelength=4.0,
handletextpad=1.2,
)
legend = ax.legend(ncols=2, **props)

to be continued below


legend = ax.get_legend()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
legend = ax.get_legend()

props = legend._get_properties()

assert props['loc'] == legend._loc
assert props['fontsize'] == 14
assert props['frameon'] is False
assert props['shadow'] is True
assert props['title'] == 'My Legend'
assert props['columnspacing'] == 3.0
assert props['labelspacing'] == 1.5
assert props['handlelength'] == 4.0
assert props['handletextpad'] == 1.2
assert 'bbox_to_anchor' not in props
assert 'mode' not in props
Comment on lines +31 to +41
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assert props['loc'] == legend._loc
assert props['fontsize'] == 14
assert props['frameon'] is False
assert props['shadow'] is True
assert props['title'] == 'My Legend'
assert props['columnspacing'] == 3.0
assert props['labelspacing'] == 1.5
assert props['handlelength'] == 4.0
assert props['handletextpad'] == 1.2
assert 'bbox_to_anchor' not in props
assert 'mode' not in props
assert props == initial_props

Make sure you include all props returned by _get_properties to get full coverage and be able to just assert dict equality.


plt.close(fig)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
plt.close(fig)

Not needed. Closing is automatic in tests.



def test_get_properties_with_mode_and_bbox():
"""
Test that _get_properties() includes mode and bbox_to_anchor
when they are set.
"""
fig, ax = plt.subplots()
ax.plot(range(5), label='a')

ax.legend(
bbox_to_anchor=(0, 1.02, 1, 0.2),
loc='lower left',
mode='expand',
ncols=2,
)

legend = ax.get_legend()
props = legend._get_properties()

assert props['mode'] == 'expand'
assert 'bbox_to_anchor' in props

plt.close(fig)


def test_get_properties_roundtrip():
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this tests tell us more compared to test_get_properties_basic?

"""
Test that properties from _get_properties() can be used to
recreate a legend with the same settings.
"""
fig, ax = plt.subplots()
ax.plot(range(5), label='a')
ax.plot(range(3)[::-1], label='b')

ax.legend(
loc='upper right',
fontsize=14,
frameon=False,
shadow=True,
title='My Legend',
ncols=2,
columnspacing=3.0,
labelspacing=1.5,
handlelength=4.0,
handletextpad=1.2,
)

old_legend = ax.get_legend()
props = old_legend._get_properties()
ncols = old_legend._ncols

# Recreate legend using extracted properties
new_legend = ax.legend(ncols=ncols, **props)

assert new_legend._fontsize == 14
assert new_legend.get_frame_on() is False
assert new_legend.shadow is True
assert new_legend.get_title().get_text() == 'My Legend'
assert new_legend._ncols == 2
assert new_legend.columnspacing == 3.0
assert new_legend.labelspacing == 1.5
assert new_legend.handlelength == 4.0
assert new_legend.handletextpad == 1.2

plt.close(fig)


def test_legend_regeneration_no_existing_legend():
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I completely do not understand this test. There is no regeneration. You only create one legend. This test does not touch any library code modified in this PR.

"""
Test that regenerating a legend when none exists still works.
"""
fig, ax = plt.subplots()
ax.plot(range(5), label='a')

assert ax.get_legend() is None

new_legend = ax.legend(ncols=1)
assert new_legend is not None
assert len(new_legend.get_texts()) == 1
assert new_legend.get_texts()[0].get_text() == 'a'

plt.close(fig)
Loading