diff --git a/doc/api/api_changes/2017-10-24-DS.rst b/doc/api/api_changes/2017-10-24-DS.rst new file mode 100644 index 000000000000..c1c3c6ec9c55 --- /dev/null +++ b/doc/api/api_changes/2017-10-24-DS.rst @@ -0,0 +1,10 @@ +`StemContainer` now stores `LineCollection` +------------------------------------------- + +`StemContainer` objects now store a `LineCollection` object instead of a list +of `Line2D` objects for stem lines plotted using `ax.stem`. This gives a very +large performance boost to displaying and moving `ax.stem` plots. + +Line segments can be extracted from the `LineCollection` using +`LineCollection.get_segements()`. See the `LineCollection` documentation for +other methods to retrieve the collection properties. diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 9fa6e6650b00..67d5d5007aa1 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2370,6 +2370,9 @@ def stem(self, *args, linefmt=None, markerfmt=None, basefmt=None, x = y y = np.asarray(args[0], dtype=float) args = args[1:] + self._process_unit_info(xdata=x, ydata=y) + x = self.convert_xunits(x) + y = self.convert_yunits(y) # defaults for formats if linefmt is None: @@ -2421,12 +2424,12 @@ def stem(self, *args, linefmt=None, markerfmt=None, basefmt=None, markerline, = self.plot(x, y, color=markercolor, linestyle=markerstyle, marker=markermarker, label="_nolegend_") - stemlines = [] + lines = [] for thisx, thisy in zip(x, y): - l, = self.plot([thisx, thisx], [bottom, thisy], - color=linecolor, linestyle=linestyle, - marker=linemarker, label="_nolegend_") - stemlines.append(l) + lines.append(((thisx, bottom), (thisx, thisy))) + stemlines = mcoll.LineCollection(lines, linestyles=linestyle, + colors=linecolor, label='_nolegend_') + self.add_collection(stemlines) baseline, = self.plot([np.min(x), np.max(x)], [bottom, bottom], color=basecolor, linestyle=basestyle, diff --git a/lib/matplotlib/container.py b/lib/matplotlib/container.py index 552d6da5a761..0f71c606fe5c 100644 --- a/lib/matplotlib/container.py +++ b/lib/matplotlib/container.py @@ -174,10 +174,17 @@ class StemContainer(Container): baseline : :class:`~matplotlib.lines.Line2D` The artist of the horizontal baseline. - """ - def __init__(self, markerline_stemlines_baseline, **kwargs): + ''' + Parameters + ---------- + markerline_stemlines_baseline : tuple + Tuple of ``(markerline, stemlines, baseline)``. + ``markerline`` contains the `LineCollection` of the markers, + ``stemlines`` is a `LineCollection` of the main lines, + ``baseline`` is the `Line2D` of the baseline. + ''' markerline, stemlines, baseline = markerline_stemlines_baseline self.markerline = markerline self.stemlines = stemlines diff --git a/lib/matplotlib/legend_handler.py b/lib/matplotlib/legend_handler.py index ad574dcf33d0..44f1afc0b236 100644 --- a/lib/matplotlib/legend_handler.py +++ b/lib/matplotlib/legend_handler.py @@ -589,7 +589,6 @@ def get_ydata(self, legend, xdescent, ydescent, width, height, fontsize): def create_artists(self, legend, orig_handle, xdescent, ydescent, width, height, fontsize, trans): - markerline, stemlines, baseline = orig_handle xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent, @@ -603,31 +602,44 @@ def create_artists(self, legend, orig_handle, else: bottom = self._bottom - leg_markerline = Line2D(xdata_marker, ydata[:len(xdata_marker)]) - self.update_prop(leg_markerline, markerline, legend) - leg_stemlines = [] - for thisx, thisy in zip(xdata_marker, ydata): - l = Line2D([thisx, thisx], [bottom, thisy]) - leg_stemlines.append(l) - for lm, m in zip(leg_stemlines, stemlines): - self.update_prop(lm, m, legend) + # update_prop() usually takes two Line2D collections; + # override temporarily to copy properties from a LineCollection + orig_update_func = self._update_prop_func + self._update_prop_func = self._copy_collection_props + + for thisx, thisy in zip(xdata_marker, ydata): + thisline = Line2D([thisx, thisx], [bottom, thisy]) + leg_stemlines.append(thisline) + self.update_prop(thisline, stemlines, legend) + # Reset update_prop_func + self._update_prop_func = orig_update_func leg_baseline = Line2D([np.min(xdata), np.max(xdata)], [bottom, bottom]) self.update_prop(leg_baseline, baseline, legend) - artists = [leg_markerline] - artists.extend(leg_stemlines) + leg_markerline = Line2D(xdata_marker, ydata[:len(xdata_marker)]) + self.update_prop(leg_markerline, markerline, legend) + + artists = leg_stemlines artists.append(leg_baseline) + artists.append(leg_markerline) for artist in artists: artist.set_transform(trans) return artists + def _copy_collection_props(self, legend_handle, orig_handle): + ''' + Method to copy properties from a LineCollection (orig_handle) to a + Line2D (legend_handle). + ''' + legend_handle._color = orig_handle.get_color()[0] + class HandlerTuple(HandlerBase): """ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/stem.png b/lib/matplotlib/tests/baseline_images/test_axes/stem.png new file mode 100644 index 000000000000..407c977f5b72 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/stem.png differ diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 12197bd8e55f..8a6515dbe045 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -2876,6 +2876,17 @@ def test_hist_stacked_weighted(): ax.hist((d1, d2), weights=(w1, w2), histtype="stepfilled", stacked=True) +@image_comparison(baseline_images=['stem'], extensions=['png'], style='mpl20', + remove_text=True) +def test_stem(): + x = np.linspace(0.1, 2 * np.pi, 100) + + fig, ax = plt.subplots() + ax.stem(x, np.cos(x), linefmt='C2-', markerfmt='k+', basefmt='C1-.', + label='Stem') + ax.legend() + + def test_stem_args(): fig = plt.figure() ax = fig.add_subplot(1, 1, 1)