Skip to content

Commit 0790137

Browse files
committed
Merge pull request matplotlib#3303 from fcolas/master
ENH : Adding legend handler to PolyCollection and labels to stackplot
2 parents 0f049f7 + ae9304d commit 0790137

File tree

7 files changed

+77
-7
lines changed

7 files changed

+77
-7
lines changed

doc/users/whats_new.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@ All selectors now implement ``set_active`` and ``get_active`` methods (also
3939
called when accessing the ``active`` property) to properly update and query
4040
whether they are active.
4141

42+
43+
New plotting features
44+
---------------------
45+
46+
Support for legend for PolyCollection and stackplot
47+
```````````````````````````````````````````````````
48+
Added a `legend_handler` for :class:`~matplotlib.collections.PolyCollection` as well as a `labels` argument to
49+
:func:`~matplotlib.axes.Axes.stackplot`.
50+
51+
4252
.. _whats-new-1-4:
4353

4454
new in matplotlib-1.4
@@ -280,6 +290,7 @@ polar-plot r-tick locations
280290
Added the ability to control the angular position of the r-tick labels
281291
on a polar plot via :func:`~matplotlib.Axes.axes.set_rlabel_position`.
282292

293+
283294
Date handling
284295
-------------
285296

lib/matplotlib/collections.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,10 @@ def update_scalarmappable(self):
658658
elif self._is_stroked:
659659
self._edgecolors = self.to_rgba(self._A, self._alpha)
660660

661+
def get_fill(self):
662+
'return whether fill is set'
663+
return self._is_filled
664+
661665
def update_from(self, other):
662666
'copy properties from other to self'
663667

lib/matplotlib/legend.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
from matplotlib.lines import Line2D
4040
from matplotlib.patches import Patch, Rectangle, Shadow, FancyBboxPatch
4141
from matplotlib.collections import LineCollection, RegularPolyCollection, \
42-
CircleCollection, PathCollection
42+
CircleCollection, PathCollection, PolyCollection
4343
from matplotlib.transforms import Bbox, BboxBase, TransformedBbox
4444
from matplotlib.transforms import BboxTransformTo, BboxTransformFrom
4545

@@ -487,7 +487,8 @@ def _approx_text_height(self, renderer=None):
487487
BarContainer: legend_handler.HandlerPatch(
488488
update_func=legend_handler.update_from_first_child),
489489
tuple: legend_handler.HandlerTuple(),
490-
PathCollection: legend_handler.HandlerPathCollection()
490+
PathCollection: legend_handler.HandlerPathCollection(),
491+
PolyCollection: legend_handler.HandlerPolyCollection()
491492
}
492493

493494
# (get|set|update)_default_handler_maps are public interfaces to

lib/matplotlib/legend_handler.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def legend_artist(self, legend, orig_handle, fontsize, handlebox):
3535
from matplotlib.lines import Line2D
3636
from matplotlib.patches import Rectangle
3737
import matplotlib.collections as mcoll
38+
import matplotlib.colors as mcolors
3839

3940

4041
def update_from_first_child(tgt, src):
@@ -581,3 +582,38 @@ def create_artists(self, legend, orig_handle,
581582
a_list.extend(_a_list)
582583

583584
return a_list
585+
586+
587+
class HandlerPolyCollection(HandlerBase):
588+
"""
589+
Handler for PolyCollection used in fill_between and stackplot.
590+
"""
591+
def _update_prop(self, legend_handle, orig_handle):
592+
def first_color(colors):
593+
colors = mcolors.colorConverter.to_rgba_array(colors)
594+
if len(colors):
595+
return colors[0]
596+
else:
597+
return "none"
598+
def get_first(prop_array):
599+
if len(prop_array):
600+
return prop_array[0]
601+
else:
602+
return None
603+
legend_handle.set_edgecolor(first_color(orig_handle.get_edgecolor()))
604+
legend_handle.set_facecolor(first_color(orig_handle.get_facecolor()))
605+
legend_handle.set_fill(orig_handle.get_fill())
606+
legend_handle.set_hatch(orig_handle.get_hatch())
607+
legend_handle.set_linewidth(get_first(orig_handle.get_linewidths()))
608+
legend_handle.set_linestyle(get_first(orig_handle.get_linestyles()))
609+
legend_handle.set_transform(get_first(orig_handle.get_transforms()))
610+
legend_handle.set_figure(orig_handle.get_figure())
611+
legend_handle.set_alpha(orig_handle.get_alpha())
612+
613+
def create_artists(self, legend, orig_handle,
614+
xdescent, ydescent, width, height, fontsize, trans):
615+
p = Rectangle(xy=(-xdescent, -ydescent),
616+
width=width, height=height)
617+
self.update_prop(p, orig_handle, legend)
618+
p.set_transform(trans)
619+
return [p]

lib/matplotlib/stackplot.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ def stackplot(axes, x, *args, **kwargs):
4141
can be found at http://www.leebyron.com/else/streamgraph/.
4242
4343
44+
*labels* : A list or tuple of labels to assign to each data series.
45+
46+
4447
*colors* : A list or tuple of colors. These will be cycled through and
4548
used to colour the stacked areas.
4649
All other keyword arguments are passed to
@@ -49,18 +52,15 @@ def stackplot(axes, x, *args, **kwargs):
4952
Returns *r* : A list of
5053
:class:`~matplotlib.collections.PolyCollection`, one for each
5154
element in the stacked area plot.
52-
53-
Note that :class:`~matplotlib.legend.Legend` does not support
54-
:class:`~matplotlib.collections.PolyCollection` objects. To create a
55-
legend on a stackplot, use a proxy artist:
56-
http://matplotlib.org/users/legend_guide.html#using-proxy-artist
5755
"""
5856

5957
if len(args) == 1:
6058
y = np.atleast_2d(*args)
6159
elif len(args) > 1:
6260
y = np.row_stack(args)
6361

62+
labels = iter(kwargs.pop('labels', []))
63+
6464
colors = kwargs.pop('colors', None)
6565
if colors is not None:
6666
axes.set_color_cycle(colors)
@@ -104,12 +104,14 @@ def stackplot(axes, x, *args, **kwargs):
104104
# Color between x = 0 and the first array.
105105
r.append(axes.fill_between(x, first_line, stack[0, :],
106106
facecolor=six.next(axes._get_lines.color_cycle),
107+
label= six.next(labels, None),
107108
**kwargs))
108109

109110
# Color between array i-1 and array i
110111
for i in xrange(len(y) - 1):
111112
color = six.next(axes._get_lines.color_cycle)
112113
r.append(axes.fill_between(x, stack[i, :], stack[i + 1, :],
113114
facecolor= color,
115+
label= six.next(labels, None),
114116
**kwargs))
115117
return r
Loading

lib/matplotlib/tests/test_legend.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,22 @@ def test_legend_handler_map(self):
218218
handles_labels.assert_called_with({'1': 2})
219219

220220

221+
@image_comparison(baseline_images=['legend_stackplot'], extensions=['png'])
222+
def test_legend_stackplot():
223+
'''test legend for PolyCollection using stackplot'''
224+
# related to #1341, #1943, and PR #3303
225+
fig = plt.figure()
226+
ax = fig.add_subplot(111)
227+
x = np.linspace(0, 10, 10)
228+
y1 = 1.0 * x
229+
y2 = 2.0 * x + 1
230+
y3 = 3.0 * x + 2
231+
ax.stackplot(x, y1, y2, y3, labels=['y1', 'y2', 'y3'])
232+
ax.set_xlim((0, 10))
233+
ax.set_ylim((0, 70))
234+
ax.legend(loc=0)
235+
236+
221237
if __name__ == '__main__':
222238
import nose
223239
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)

0 commit comments

Comments
 (0)