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

Skip to content

Commit 4230879

Browse files
committed
Merge pull request #5617 from three-comrades/legend-tuple-handler-improve
Legend tuple handler improve
2 parents 8e72859 + 9f10618 commit 4230879

6 files changed

Lines changed: 125 additions & 7 deletions

File tree

doc/users/legend_guide.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,21 @@ following example demonstrates combining two legend keys on top of one another:
198198

199199
plt.legend([red_dot, (red_dot, white_cross)], ["Attr A", "Attr A+B"])
200200

201+
The :class:`~matplotlib.legend_handler.HandlerTuple` class can also be used to
202+
assign several legend keys to the same entry:
203+
204+
.. plot::
205+
:include-source:
206+
207+
import matplotlib.pyplot as plt
208+
from matplotlib.legend_handler import HandlerLine2D, HandlerTuple
209+
210+
p1, = plt.plot([1, 2.5, 3], 'r-d')
211+
p2, = plt.plot([3, 2, 1], 'k-o')
212+
213+
l = plt.legend([(p1, p2)], ['Two keys'], numpoints=1,
214+
handler_map={tuple: HandlerTuple(ndivide=None)})
215+
201216

202217
Implementing a custom legend handler
203218
------------------------------------
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Multiple legend keys for legend entries
2+
---------------------------------------
3+
4+
A legend entry can now contain more than one legend key. The extended
5+
``HandlerTuple`` class now accepts two parameters: ``ndivide`` divides the
6+
legend area in the specified number of sections; ``pad`` changes the padding
7+
between the legend keys.
8+
9+
.. plot:: mpl_examples/pylab_examples/legend_demo6.py
10+
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""
2+
Showcases legend entries with more than one legend key.
3+
"""
4+
import matplotlib.pyplot as plt
5+
from matplotlib.legend_handler import HandlerTuple
6+
7+
fig, (ax1, ax2) = plt.subplots(2, 1)
8+
9+
# First plot: two legend keys for a single entry
10+
p1 = ax1.scatter([1], [5], c='r', marker='s', s=100)
11+
p2 = ax1.scatter([3], [2], c='b', marker='o', s=100)
12+
# `plot` returns a list, but we want the handle - thus the comma on the left
13+
p3, = ax1.plot([1, 5], [4, 4], 'm-d')
14+
15+
# Assign two of the handles to the same legend entry by putting them in a tuple
16+
# and using a generic handler map (which would be used for any additional
17+
# tuples of handles like (p1, p3)).
18+
l = ax1.legend([(p1, p3), p2], ['two keys', 'one key'], scatterpoints=1,
19+
numpoints=1, handler_map={tuple: HandlerTuple(ndivide=None)})
20+
21+
# Second plot: plot two bar charts on top of each other and change the padding
22+
# between the legend keys
23+
x_left = [1, 2, 3]
24+
y_pos = [1, 3, 2]
25+
y_neg = [2, 1, 4]
26+
27+
rneg = ax2.bar(x_left, y_neg, width=0.5, color='w', hatch='///', label='-1')
28+
rpos = ax2.bar(x_left, y_pos, width=0.5, color='k', label='+1')
29+
30+
# Treat each legend entry differently by using specific `HandlerTuple`s
31+
l = ax2.legend([(rpos, rneg), (rneg, rpos)], ['pad!=0', 'pad=0'],
32+
handler_map={(rpos, rneg): HandlerTuple(ndivide=None),
33+
(rneg, rpos): HandlerTuple(ndivide=None, pad=0.)})
34+
35+
plt.show()

lib/matplotlib/legend_handler.py

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def legend_artist(self, legend, orig_handle, fontsize, handlebox):
2929

3030
from matplotlib.externals import six
3131
from matplotlib.externals.six.moves import zip
32+
from itertools import cycle
3233

3334
import numpy as np
3435

@@ -150,13 +151,14 @@ def get_xdata(self, legend, xdescent, ydescent, width, height, fontsize):
150151
if numpoints > 1:
151152
# we put some pad here to compensate the size of the
152153
# marker
153-
xdata = np.linspace(-xdescent + self._marker_pad * fontsize,
154-
width - self._marker_pad * fontsize,
154+
pad = self._marker_pad * fontsize
155+
xdata = np.linspace(-xdescent + pad,
156+
-xdescent + width - pad,
155157
numpoints)
156158
xdata_marker = xdata
157159
elif numpoints == 1:
158-
xdata = np.linspace(-xdescent, width, 2)
159-
xdata_marker = [0.5 * width - 0.5 * xdescent]
160+
xdata = np.linspace(-xdescent, -xdescent+width, 2)
161+
xdata_marker = [-xdescent + 0.5 * width]
160162

161163
return xdata, xdata_marker
162164

@@ -499,6 +501,7 @@ def create_artists(self, legend, orig_handle,
499501

500502
return artists
501503

504+
502505
class HandlerStem(HandlerNpointsYoffsets):
503506
"""
504507
Handler for Errorbars
@@ -565,21 +568,60 @@ def create_artists(self, legend, orig_handle,
565568

566569
class HandlerTuple(HandlerBase):
567570
"""
568-
Handler for Tuple
571+
Handler for Tuple.
572+
573+
Additional kwargs are passed through to `HandlerBase`.
574+
575+
Parameters
576+
----------
577+
578+
ndivide : int, optional
579+
The number of sections to divide the legend area into. If None,
580+
use the length of the input tuple. Default is 1.
581+
582+
583+
pad : float, optional
584+
If None, fall back to `legend.borderpad` as the default.
585+
In units of fraction of font size. Default is None.
586+
587+
588+
569589
"""
570-
def __init__(self, **kwargs):
590+
def __init__(self, ndivide=1, pad=None, **kwargs):
591+
592+
self._ndivide = ndivide
593+
self._pad = pad
571594
HandlerBase.__init__(self, **kwargs)
572595

573596
def create_artists(self, legend, orig_handle,
574597
xdescent, ydescent, width, height, fontsize,
575598
trans):
576599

577600
handler_map = legend.get_legend_handler_map()
601+
602+
if self._ndivide is None:
603+
ndivide = len(orig_handle)
604+
else:
605+
ndivide = self._ndivide
606+
607+
if self._pad is None:
608+
pad = legend.borderpad * fontsize
609+
else:
610+
pad = self._pad * fontsize
611+
612+
if ndivide > 1:
613+
width = (width - pad*(ndivide - 1)) / ndivide
614+
615+
xds = [xdescent - (width + pad) * i for i in range(ndivide)]
616+
xds_cycle = cycle(xds)
617+
578618
a_list = []
579619
for handle1 in orig_handle:
580620
handler = legend.get_legend_handler(handler_map, handle1)
581621
_a_list = handler.create_artists(legend, handle1,
582-
xdescent, ydescent, width, height,
622+
six.next(xds_cycle),
623+
ydescent,
624+
width, height,
583625
fontsize,
584626
trans)
585627
a_list.extend(_a_list)
30.9 KB
Loading

lib/matplotlib/tests/test_legend.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import matplotlib.patches as mpatches
1919
import matplotlib.transforms as mtrans
2020

21+
from matplotlib.legend_handler import HandlerTuple
2122

2223
@image_comparison(baseline_images=['legend_auto1'], remove_text=True)
2324
def test_legend_auto1():
@@ -77,6 +78,21 @@ def test_labels_first():
7778
ax.legend(loc=0, markerfirst=False)
7879

7980

81+
@image_comparison(baseline_images=['legend_multiple_keys'], extensions=['png'],
82+
remove_text=True)
83+
def test_multiple_keys():
84+
# test legend entries with multiple keys
85+
fig = plt.figure()
86+
ax = fig.add_subplot(111)
87+
p1, = ax.plot([1, 2, 3], '-o')
88+
p2, = ax.plot([2, 3, 4], '-x')
89+
p3, = ax.plot([3, 4, 5], '-d')
90+
ax.legend([(p1, p2), (p2, p1), p3], ['two keys', 'pad=0', 'one key'],
91+
numpoints=1,
92+
handler_map={(p1, p2): HandlerTuple(ndivide=None),
93+
(p2, p1): HandlerTuple(ndivide=None, pad=0)})
94+
95+
8096
@image_comparison(baseline_images=['rgba_alpha'],
8197
extensions=['png'], remove_text=True)
8298
def test_alpha_rgba():

0 commit comments

Comments
 (0)