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

Skip to content

Commit ab51610

Browse files
committed
Improve step performance
Do deprecation better Fix linestyle
1 parent f77c979 commit ab51610

File tree

6 files changed

+114
-22
lines changed

6 files changed

+114
-22
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
`StemContainer` now stores `LineCollection`
2+
-------------------------------------------
3+
4+
`StemContainer` objects can now store a `LineCollection` object instead of a
5+
list of `Line2D` objects for stem lines plotted using `ax.stem`. This gives a
6+
very large performance boost to displaying and moving `ax.stem` plots.
7+
8+
This will become the default behaviour in Matplotlib 3.3. To use it now, the
9+
``use_line_collection`` keyword argument to ~`.axes.stem` can be set to
10+
``True``.
11+
12+
Individual line segments can be extracted from the `LineCollection` using
13+
`LineCollection.get_segements()`. See the `LineCollection` documentation for
14+
other methods to retrieve the collection properties.

lib/matplotlib/axes/_axes.py

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2524,7 +2524,7 @@ def broken_barh(self, xranges, yrange, **kwargs):
25242524

25252525
@_preprocess_data(replace_all_args=True, label_namer=None)
25262526
def stem(self, *args, linefmt=None, markerfmt=None, basefmt=None,
2527-
bottom=0, label=None):
2527+
bottom=0, label=None, use_line_collection=False):
25282528
"""
25292529
Create a stem plot.
25302530
@@ -2583,6 +2583,12 @@ def stem(self, *args, linefmt=None, markerfmt=None, basefmt=None,
25832583
label : str, optional, default: None
25842584
The label to use for the stems in legends.
25852585
2586+
use_line_collection : bool, optional, default: False
2587+
If ``True``, store and plot the stem lines as a
2588+
~`.collections.LineCollection` instead of individual lines. This
2589+
significantly increases performance, and will become the default
2590+
option in Matplotlib 3.3. If ``False``, defaults to old behaviour.
2591+
25862592
25872593
Returns
25882594
-------
@@ -2614,6 +2620,9 @@ def stem(self, *args, linefmt=None, markerfmt=None, basefmt=None,
26142620
x = y
26152621
y = np.asarray(args[0], dtype=float)
26162622
args = args[1:]
2623+
self._process_unit_info(xdata=x, ydata=y)
2624+
x = self.convert_xunits(x)
2625+
y = self.convert_yunits(y)
26172626

26182627
# defaults for formats
26192628
if linefmt is None:
@@ -2662,24 +2671,40 @@ def stem(self, *args, linefmt=None, markerfmt=None, basefmt=None,
26622671
else:
26632672
basestyle, basemarker, basecolor = _process_plot_format(basefmt)
26642673

2674+
# New behaviour in 3.1 is to use a LineCollection for the stemlines
2675+
if use_line_collection:
2676+
stemlines = []
2677+
for thisx, thisy in zip(x, y):
2678+
stemlines.append(((thisx, bottom), (thisx, thisy)))
2679+
stemlines = mcoll.LineCollection(stemlines, linestyles=linestyle,
2680+
colors=linecolor,
2681+
label='_nolegend_')
2682+
self.add_collection(stemlines)
2683+
# Old behaviour is to plot each of the lines individually
2684+
else:
2685+
warnings.warn(
2686+
'In Matplotlib 3.3 individual lines on a stem plot will be '
2687+
'added as a LineCollection instead of individual lines.\n'
2688+
'This significantly improves the performance of a stem plot.\n'
2689+
'To remove this warning and switch to the new behaviour, '
2690+
'set the "use_line_collection" keyword argument to True.')
2691+
stemlines = []
2692+
for thisx, thisy in zip(x, y):
2693+
l, = self.plot([thisx, thisx], [bottom, thisy],
2694+
color=linecolor, linestyle=linestyle,
2695+
marker=linemarker, label="_nolegend_")
2696+
stemlines.append(l)
2697+
26652698
markerline, = self.plot(x, y, color=markercolor, linestyle=markerstyle,
26662699
marker=markermarker, label="_nolegend_")
26672700

2668-
stemlines = []
2669-
for thisx, thisy in zip(x, y):
2670-
l, = self.plot([thisx, thisx], [bottom, thisy],
2671-
color=linecolor, linestyle=linestyle,
2672-
marker=linemarker, label="_nolegend_")
2673-
stemlines.append(l)
2674-
26752701
baseline, = self.plot([np.min(x), np.max(x)], [bottom, bottom],
26762702
color=basecolor, linestyle=basestyle,
26772703
marker=basemarker, label="_nolegend_")
26782704

26792705
stem_container = StemContainer((markerline, stemlines, baseline),
26802706
label=label)
26812707
self.add_container(stem_container)
2682-
26832708
return stem_container
26842709

26852710
@_preprocess_data(replace_names=["x", "explode", "labels", "colors"],

lib/matplotlib/container.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,17 @@ class StemContainer(Container):
174174
175175
baseline : :class:`~matplotlib.lines.Line2D`
176176
The artist of the horizontal baseline.
177-
178177
"""
179-
180178
def __init__(self, markerline_stemlines_baseline, **kwargs):
179+
'''
180+
Parameters
181+
----------
182+
markerline_stemlines_baseline : tuple
183+
Tuple of ``(markerline, stemlines, baseline)``.
184+
``markerline`` contains the `LineCollection` of the markers,
185+
``stemlines`` is a `LineCollection` of the main lines,
186+
``baseline`` is the `Line2D` of the baseline.
187+
'''
181188
markerline, stemlines, baseline = markerline_stemlines_baseline
182189
self.markerline = markerline
183190
self.stemlines = stemlines

lib/matplotlib/legend_handler.py

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -589,8 +589,11 @@ def get_ydata(self, legend, xdescent, ydescent, width, height, fontsize):
589589
def create_artists(self, legend, orig_handle,
590590
xdescent, ydescent, width, height, fontsize,
591591
trans):
592-
593592
markerline, stemlines, baseline = orig_handle
593+
# Check to see if the stemcontainer is storing lines as a list or a
594+
# LineCollection. Eventually using a list will be removed, and this
595+
# logic can also be removed.
596+
using_linecoll = isinstance(stemlines, mcoll.LineCollection)
594597

595598
xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
596599
width, height, fontsize)
@@ -603,31 +606,52 @@ def create_artists(self, legend, orig_handle,
603606
else:
604607
bottom = self._bottom
605608

606-
leg_markerline = Line2D(xdata_marker, ydata[:len(xdata_marker)])
607-
self.update_prop(leg_markerline, markerline, legend)
608-
609609
leg_stemlines = []
610-
for thisx, thisy in zip(xdata_marker, ydata):
611-
l = Line2D([thisx, thisx], [bottom, thisy])
612-
leg_stemlines.append(l)
613610

614-
for lm, m in zip(leg_stemlines, stemlines):
615-
self.update_prop(lm, m, legend)
611+
if using_linecoll:
612+
# update_prop() usually takes two Line2D collections;
613+
# override temporarily to copy properties from a LineCollection
614+
orig_update_func = self._update_prop_func
615+
self._update_prop_func = self._copy_collection_props
616+
617+
for thisx, thisy in zip(xdata_marker, ydata):
618+
thisline = Line2D([thisx, thisx], [bottom, thisy])
619+
leg_stemlines.append(thisline)
620+
self.update_prop(thisline, stemlines, legend)
621+
622+
self._update_prop_func = orig_update_func
623+
else:
624+
for thisx, thisy in zip(xdata_marker, ydata):
625+
thisline = Line2D([thisx, thisx], [bottom, thisy])
626+
leg_stemlines.append(thisline)
627+
for lm, m in zip(leg_stemlines, stemlines):
628+
self.update_prop(lm, m, legend)
616629

617630
leg_baseline = Line2D([np.min(xdata), np.max(xdata)],
618631
[bottom, bottom])
619632

620633
self.update_prop(leg_baseline, baseline, legend)
621634

622-
artists = [leg_markerline]
623-
artists.extend(leg_stemlines)
635+
leg_markerline = Line2D(xdata_marker, ydata[:len(xdata_marker)])
636+
self.update_prop(leg_markerline, markerline, legend)
637+
638+
artists = leg_stemlines
624639
artists.append(leg_baseline)
640+
artists.append(leg_markerline)
625641

626642
for artist in artists:
627643
artist.set_transform(trans)
628644

629645
return artists
630646

647+
def _copy_collection_props(self, legend_handle, orig_handle):
648+
'''
649+
Method to copy properties from a LineCollection (orig_handle) to a
650+
Line2D (legend_handle).
651+
'''
652+
legend_handle.set_color(orig_handle.get_color()[0])
653+
legend_handle.set_linestyle(orig_handle.get_linestyle()[0])
654+
631655

632656
class HandlerTuple(HandlerBase):
633657
"""
Loading

lib/matplotlib/tests/test_axes.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2958,6 +2958,28 @@ def test_hist_stacked_weighted():
29582958
ax.hist((d1, d2), weights=(w1, w2), histtype="stepfilled", stacked=True)
29592959

29602960

2961+
@pytest.mark.parametrize("use_line_collection", [True, False],
2962+
ids=['w/ line collection', 'w/o line collection'])
2963+
@image_comparison(baseline_images=['stem'], extensions=['png'], style='mpl20',
2964+
remove_text=True)
2965+
def test_stem(use_line_collection):
2966+
x = np.linspace(0.1, 2 * np.pi, 100)
2967+
args = (x, np.cos(x))
2968+
# Label is a single space to force a legend to be drawn, but to avoid any
2969+
# text being drawn
2970+
kwargs = dict(linefmt='C2-.', markerfmt='k+', basefmt='C1-.',
2971+
label=' ', use_line_collection=use_line_collection)
2972+
2973+
fig, ax = plt.subplots()
2974+
if use_line_collection:
2975+
ax.stem(*args, **kwargs)
2976+
else:
2977+
with pytest.warns(UserWarning):
2978+
ax.stem(*args, **kwargs)
2979+
2980+
ax.legend()
2981+
2982+
29612983
def test_stem_args():
29622984
fig = plt.figure()
29632985
ax = fig.add_subplot(1, 1, 1)

0 commit comments

Comments
 (0)