-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Stem speedup2 #12380
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Stem speedup2 #12380
Changes from 3 commits
2098d1a
cdaadb8
00de51b
be0dc01
a986013
da04fa3
0bef714
6b19ba6
1f7228d
6d6c786
9aa02ec
7828b9c
2e98633
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
`StemContainer` now stores `LineCollection` | ||
------------------------------------------- | ||
|
||
`StemContainer` objects can 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. | ||
|
||
This will become the default behaviour in Matplotlib 3.3. To use it now, the | ||
``use_line_collection`` keyword argument to ~`.axes.stem` can be set to | ||
``True``. | ||
|
||
Individual line segments can be extracted from the `LineCollection` using | ||
`LineCollection.get_segements()`. See the `LineCollection` documentation for | ||
other methods to retrieve the collection properties. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2480,7 +2480,7 @@ def broken_barh(self, xranges, yrange, **kwargs): | |
|
||
@_preprocess_data() | ||
def stem(self, *args, linefmt=None, markerfmt=None, basefmt=None, bottom=0, | ||
label=None): | ||
label=None, use_line_collection=False): | ||
""" | ||
Create a stem plot. | ||
|
||
|
@@ -2539,6 +2539,12 @@ def stem(self, *args, linefmt=None, markerfmt=None, basefmt=None, bottom=0, | |
label : str, optional, default: None | ||
The label to use for the stems in legends. | ||
|
||
use_line_collection : bool, optional, default: False | ||
If ``True``, store and plot the stem lines as a | ||
`~.collections.LineCollection` instead of individual lines. This | ||
significantly increases performance, and will become the default | ||
option in Matplotlib 3.3. If ``False``, defaults to old behaviour. | ||
|
||
|
||
Returns | ||
------- | ||
|
@@ -2570,6 +2576,9 @@ def stem(self, *args, linefmt=None, markerfmt=None, basefmt=None, bottom=0, | |
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: | ||
|
@@ -2618,24 +2627,40 @@ def stem(self, *args, linefmt=None, markerfmt=None, basefmt=None, bottom=0, | |
else: | ||
basestyle, basemarker, basecolor = _process_plot_format(basefmt) | ||
|
||
# New behaviour in 3.1 is to use a LineCollection for the stemlines | ||
if use_line_collection: | ||
stemlines = [] | ||
for thisx, thisy in zip(x, y): | ||
stemlines.append(((thisx, bottom), (thisx, thisy))) | ||
dstansby marked this conversation as resolved.
Show resolved
Hide resolved
|
||
stemlines = mcoll.LineCollection(stemlines, linestyles=linestyle, | ||
colors=linecolor, | ||
label='_nolegend_') | ||
self.add_collection(stemlines) | ||
# Old behaviour is to plot each of the lines individually | ||
else: | ||
warnings.warn( | ||
dstansby marked this conversation as resolved.
Show resolved
Hide resolved
|
||
'In Matplotlib 3.3 individual lines on a stem plot will be ' | ||
'added as a LineCollection instead of individual lines.\n' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we have explicit carriage returns in our messages. Let the terminal take care of that... |
||
'This significantly improves the performance of a stem plot.\n' | ||
'To remove this warning and switch to the new behaviour, ' | ||
'set the "use_line_collection" keyword argument to True.') | ||
stemlines = [] | ||
for thisx, thisy in zip(x, y): | ||
dstansby marked this conversation as resolved.
Show resolved
Hide resolved
|
||
l, = self.plot([thisx, thisx], [bottom, thisy], | ||
color=linecolor, linestyle=linestyle, | ||
marker=linemarker, label="_nolegend_") | ||
stemlines.append(l) | ||
dstansby marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
markerline, = self.plot(x, y, color=markercolor, linestyle=markerstyle, | ||
marker=markermarker, label="_nolegend_") | ||
|
||
stemlines = [] | ||
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) | ||
|
||
baseline, = self.plot([np.min(x), np.max(x)], [bottom, bottom], | ||
color=basecolor, linestyle=basestyle, | ||
marker=basemarker, label="_nolegend_") | ||
|
||
stem_container = StemContainer((markerline, stemlines, baseline), | ||
label=label) | ||
self.add_container(stem_container) | ||
|
||
return stem_container | ||
|
||
@_preprocess_data(replace_names=["x", "explode", "labels", "colors"]) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -589,8 +589,11 @@ 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 | ||
# Check to see if the stemcontainer is storing lines as a list or a | ||
# LineCollection. Eventually using a list will be removed, and this | ||
# logic can also be removed. | ||
using_linecoll = isinstance(stemlines, mcoll.LineCollection) | ||
|
||
xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent, | ||
width, height, fontsize) | ||
|
@@ -609,23 +612,50 @@ def create_artists(self, legend, orig_handle, | |
leg_stemlines = [Line2D([x, x], [bottom, y]) | ||
for x, y in zip(xdata_marker, ydata)] | ||
|
||
for lm, m in zip(leg_stemlines, stemlines): | ||
self.update_prop(lm, m, legend) | ||
if using_linecoll: | ||
# update_prop() usually takes two Line2D collections; | ||
# override temporarily to copy properties from a LineCollection | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Found this confusing, and had to look elsewhere in code for explanation. Suggest: "change the function used by update_prop() from the default to one that handles 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) | ||
dstansby marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self.update_prop(thisline, stemlines, legend) | ||
|
||
self._update_prop_func = orig_update_func | ||
else: | ||
for thisx, thisy in zip(xdata_marker, ydata): | ||
thisline = Line2D([thisx, thisx], [bottom, thisy]) | ||
leg_stemlines.append(thisline) | ||
dstansby marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for lm, m in zip(leg_stemlines, stemlines): | ||
self.update_prop(lm, m, legend) | ||
|
||
leg_baseline = Line2D([np.min(xdata), np.max(xdata)], | ||
[bottom, bottom]) | ||
|
||
self.update_prop(leg_baseline, baseline, legend) | ||
|
||
F438 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): | ||
''' | ||
dstansby marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Method to copy properties from a LineCollection (orig_handle) to a | ||
Line2D (legend_handle). | ||
''' | ||
legend_handle.set_color(orig_handle.get_color()[0]) | ||
legend_handle.set_linestyle(orig_handle.get_linestyle()[0]) | ||
|
||
|
||
class HandlerTuple(HandlerBase): | ||
""" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3072,6 +3072,28 @@ def test_hist_stacked_weighted(): | |
ax.hist((d1, d2), weights=(w1, w2), histtype="stepfilled", stacked=True) | ||
|
||
|
||
@pytest.mark.parametrize("use_line_collection", [True, False], | ||
ids=['w/ line collection', 'w/o line collection']) | ||
@image_comparison(baseline_images=['stem'], extensions=['png'], style='mpl20', | ||
remove_text=True) | ||
def test_stem(use_line_collection): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps this may just as well use check_figures_equal to check that the result is the same regardless of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even then I'd rather check equivalency with e.g. manually creating the stem plot with individual calls to plot(), but it's just my preference. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it is worth having a visual test here, because it helps a lot when trying to figure out problems (especially with the legend) |
||
x = np.linspace(0.1, 2 * np.pi, 100) | ||
args = (x, np.cos(x)) | ||
# Label is a single space to force a legend to be drawn, but to avoid any | ||
# text being drawn | ||
kwargs = dict(linefmt='C2-.', markerfmt='k+', basefmt='C1-.', | ||
label=' ', use_line_collection=use_line_collection) | ||
|
||
fig, ax = plt.su 74 bplots() | ||
if use_line_collection: | ||
ax.stem(*args, **kwargs) | ||
else: | ||
with pytest.warns(UserWarning): | ||
ax.stem(*args, **kwargs) | ||
|
||
ax.legend() | ||
|
||
|
||
def test_stem_args(): | ||
fig = plt.figure() | ||
ax = fig.add_subplot(1, 1, 1) | ||
|
Uh oh!
There was an error while loading. Please reload this page.