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

Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 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
32 changes: 32 additions & 0 deletions doc/api/next_api_changes/2018-06-01-AFV.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
Change of the (default) legend handler for Line2D instances
-----------------------------------------------------------

The default legend handler for Line2D instances (`.HandlerLine2D`) now
consistently exposes all the attributes and methods related to the line
marker (:ghissue:`11358`). This makes easy to change the marker features
after instantiating a legend.

.. code::

import matplotlib.pyplot as plt

fig, ax = plt.subplots()

ax.plot([1, 3, 2], marker="s", label="Line", color="pink", mec="red", ms=8)
leg = ax.legend()

leg.legendHandles[0].set_color("lightgray")
leg.legendHandles[0].set_mec("black") # marker edge color

The former legend handler for Line2D objects has been renamed
`.HandlerLine2DCompound`. To revert to the behavior that was used before
Matplotlib 3, one can use

.. code::

import matplotlib.legend as mlegend
from matplotlib.legend_handler import HandlerLine2DCompound
from matplotlib.lines import Line2D

mlegend.Legend.update_default_handler_map({Line2D: HandlerLine2DCompound()})

61 changes: 59 additions & 2 deletions lib/matplotlib/legend_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,10 @@ def get_ydata(self, legend, xdescent, ydescent, width, height, fontsize):
return ydata


class HandlerLine2D(HandlerNpoints):
class HandlerLine2DCompound(HandlerNpoints):
"""
Handler for `.Line2D` instances.
Original handler for `.Line2D` instances, that relies on combining
a line-only with a marker-only artist.
"""
def __init__(self, marker_pad=0.3, numpoints=None, **kw):
"""
Expand Down Expand Up @@ -255,6 +256,62 @@ def create_artists(self, legend, orig_handle,
return [legline, legline_marker]


class HandlerLine2D(HandlerNpoints):
"""
Handler for `.Line2D` instances.

A class similar to the original handler for `.Line2D` instances but
that uses a monolithic artist rather than using one artist for the
line and another one for the marker(s). NB: the former handler, in
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps use one of .. note:: or .. versionadded:: or .. seealso:: (http://www.sphinx-doc.org/en/stable/markup/para.html#paragraph-level-markup). The latter could also be a See also numpydoc section.

use before Matplotlib 3, is still available as `.HandlerLine2DCompound`.

"""
def __init__(self, marker_pad=0.3, numpoints=None, **kw):
"""
Parameters
----------
marker_pad : float
Padding between points in legend entry.

numpoints : int
Number of points to show in legend entry.

Notes
-----
Any other keyword arguments are given to `HandlerNpoints`.
"""
HandlerNpoints.__init__(self, marker_pad=marker_pad,
numpoints=numpoints, **kw)

def create_artists(self, legend, orig_handle,
xdescent, ydescent, width, height, fontsize,
trans):

xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
width, height, fontsize)

markevery = None
if self.get_numpoints(legend) == 1:
# Special case: one wants a single marker in the center
# and a line that extends on both sides. One will use a
# 3 points line, but only mark the #1 (i.e. middle) point.
xdata = np.linspace(xdata[0], xdata[-1], 3)
markevery = [1]

ydata = ((height - ydescent) / 2.) * np.ones(xdata.shape, float)
Copy link
Contributor

@anntzer anntzer Jun 7, 2018

Choose a reason for hiding this comment

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

np.full_like(xdata, (height - ydescent) / 2) (optionally)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll do that tomorrow ;). (TBH, this was just copy-pasted from the original handler and I did not really look about how to optimize the existing code.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@anntzer Well actually this will likely have to go in a future PR ^^ : np.full_like seems to have been introduced with Numpy 1.8 (release notes) and we still support Numpy 1.7.1, do we not?

Copy link
Contributor

Choose a reason for hiding this comment

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

Master requires 1.10 (see __version__numpy__ in __init__.py).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My bad, I looked at the official current doc 🐑... Should I update HandlerLineCollection, HandlerLine2DCompound, and HandlerErrorBar as well, or would it be better to do so in a separate PR?

Copy link
Contributor

Choose a reason for hiding this comment

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

A separate PR seems fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll open a separate PR to use np.full_like in the other relevant handler classes if this PR gets merged. (I am a bit afraid of the conflicts with the original LineHandler2D if I do it right now.)

legline = Line2D(xdata, ydata, markevery=markevery)

self.update_prop(legline, orig_handle, legend)

if legend.markerscale != 1:
newsz = legline.get_markersize() * legend.markerscale
legline.set_markersize(newsz)

legline.set_transform(trans)

return [legline]


class HandlerPatch(HandlerBase):
"""
Handler for `.Patch` instances.
Expand Down
7 changes: 6 additions & 1 deletion lib/matplotlib/lines.py
Original file line number Diff line number Diff line change
Expand Up @@ -801,8 +801,13 @@ def draw(self, renderer):
# subsample the markers if markevery is not None
markevery = self.get_markevery()
if markevery is not None:
try:
transform = self.axes.transAxes
except AttributeError:
# Typically in the case of a **figure** legend.
transform = self.get_transform()
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should just be set to None (or IdentityTransform)? I don't know if we really support float markeverys for figure-level artists (this PR only requires support for list-of-indexes markeverys), so setting it to None to throw something (in the isinstance(step, float) case of _mark_every_path) may be better (well, it probably used to throw here, so it's not worse), or if we want to support that case as well (which may be worth noting as a new, if obscure, feature), I think the figure-level artist's get_transform() already includes everything that's needed so we may not need to redo the transform another time? (in which case IdentityTransform may be correct?)

subsampled = _mark_every_path(markevery, tpath,
affine, self.axes.transAxes)
affine, transform)
else:
subsampled = tpath

Expand Down
6 changes: 5 additions & 1 deletion lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1329,9 +1329,13 @@ def test_markevery():
ax.legend()


@image_comparison(baseline_images=['markevery_line'],
@image_comparison(baseline_images=['markevery_line'], tol=0.005,
remove_text=True)
def test_markevery_line():
# TODO: a slight change in rendering between Inkscape versions may explain
# why one had to introduce a small non-zero tolerance for the SVG test
# to pass. One may try to remove this hack once Travis' Inkscape version
# is modern enough. FWIW, no failure with 0.92.3 on my computer (#11358).
x = np.linspace(0, 10, 100)
y = np.sin(x) * np.sqrt(x/10 + 0.5)

Expand Down
18 changes: 17 additions & 1 deletion lib/matplotlib/tests/test_legend.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import matplotlib as mpl
import matplotlib.transforms as mtransforms
import matplotlib.collections as mcollections
import matplotlib.lines as mlines
from matplotlib.legend_handler import HandlerTuple
import matplotlib.legend as mlegend
from matplotlib.cbook.deprecation import MatplotlibDeprecationWarning
Expand Down Expand Up @@ -55,9 +56,13 @@ def test_legend_auto2():
ax.legend([b1[0], b2[0]], ['up', 'down'], loc='best')


@image_comparison(baseline_images=['legend_auto3'])
@image_comparison(baseline_images=['legend_auto3'], tol=0.002)
def test_legend_auto3():
'Test automatic legend placement'
# TODO: a slight change in rendering between Inkscape versions may explain
# why one had to introduce a small non-zero tolerance for the SVG test
# to pass. One may try to remove this hack once Travis' Inkscape version
# is modern enough. FWIW, no failure with 0.92.3 on my computer (#11358).
fig = plt.figure()
ax = fig.add_subplot(111)
x = [0.9, 0.1, 0.1, 0.9, 0.9, 0.5]
Expand Down Expand Up @@ -539,3 +544,14 @@ def test_draggable():
with pytest.warns(MatplotlibDeprecationWarning):
legend.draggable()
assert not legend.get_draggable()


def test_handlerline2d():
'''Test consistency of the marker for the (monolithic) Line2D legend
handler (see #11357).
'''
fig, ax = plt.subplots()
ax.scatter([0, 1], [0, 1], marker="v")
handles = [mlines.Line2D([0], [0], marker="v")]
leg = ax.legend(handles, ["Aardvark"], numpoints=1)
assert handles[0].get_marker() == leg.legendHandles[0].get_marker()