| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| Downsample line plots | ||
| --------------------- | ||
|
|
||
| A ``downsample`` parameter now exists for the method :func:`plot`. This | ||
| allows line plots to be intelligently downsampled so that rendering and | ||
| interaction is faster. The downsampling algorithm will render an image | ||
| very similar to a non-downsampled image. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| """ | ||
| Demo of downsampling line plots. | ||
|
|
||
| This method of downsampling will greatly speed up interaction | ||
| and rendering time, without changing the actual rasterization too much. | ||
| Try interacting with the two figures (panning, zooming, etc.) to see | ||
| the difference. | ||
|
|
||
| """ | ||
| import numpy as np | ||
| import matplotlib.pyplot as plt | ||
|
|
||
| # Fixing random state for reproducibility | ||
| np.random.seed(8675309) | ||
|
|
||
| # make up some data | ||
| y = np.random.normal(loc=0.5, scale=0.4, size=1000000) | ||
| x = np.arange(len(y)) | ||
|
|
||
| # plot without downsampling | ||
| plt.figure(1) | ||
| plt.plot(x, y) | ||
|
|
||
| # plot with downsampling | ||
| plt.figure(2) | ||
| plt.plot(x, y, downsample=True) | ||
|
|
||
| plt.show() | ||
|
|
||
| # interact with both figures to compare snapiness | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| Expand Up | @@ -310,6 +310,7 @@ def __init__(self, xdata, ydata, | |
| pickradius=5, | ||
| drawstyle=None, | ||
| markevery=None, | ||
| downsample=None, | ||
| **kwargs | ||
| ): | ||
| """ | ||
| Expand Down Expand Up | @@ -408,6 +409,8 @@ def __init__(self, xdata, ydata, | |
| self.set_antialiased(antialiased) | ||
| self.set_markersize(markersize) | ||
|
|
||
| self._downsample = downsample | ||
|
|
||
| self._markeredgecolor = None | ||
| self._markeredgewidth = None | ||
| self._markerfacecolor = None | ||
| Expand Down Expand Up | @@ -583,6 +586,10 @@ def set_markevery(self, every): | |
| markers is always determined from the display-coordinates | ||
| axes-bounding-box-diagonal regardless of the actual axes data limits. | ||
|
|
||
| See also | ||
| -------- | ||
| downsample : for downsampling the line segments that are plotted. | ||
|
|
||
| """ | ||
| if self._markevery != every: | ||
| self.stale = True | ||
| Expand All | @@ -592,6 +599,59 @@ def get_markevery(self): | |
| """return the markevery setting""" | ||
| return self._markevery | ||
|
|
||
| @property | ||
| def downsample(self): | ||
| """Set the downsample property to subsample the plotted line segments. | ||
|
Comment thread
Copy link
Copy Markdown
Member
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. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityThe current docstring is a mixture of our old and new style of docstring. Can you please stick to the new style? (I'm adding a couple notes to explain what I mean).
Sorry, something went wrong.
All reactions
|
||
|
|
||
| If True, then only a subset of line segments will be plotted. The | ||
| subset is carefully chosen so that the rendering of the downsampled | ||
| plot looks very similar to the rending of the non-downsampled plot. In | ||
| fact, if there is no anti-aliasing and plotting uses a solid line only, | ||
| then the renderings will be identical. | ||
|
|
||
| Recommended *only* when plotting with a solid line (e.g. no dashes, | ||
| no markers). | ||
|
|
||
| Parameters | ||
| ---------- | ||
| downsample : True | False | ||
| Whether or not to downsample. | ||
|
|
||
| - downsample=True, a downsampled set of points will be plotted. | ||
| - downsample=False, every point will be plotted. | ||
|
|
||
| Notes | ||
| ----- | ||
| If the x-values of the line are monotonic, then the downsampling | ||
| algorithm will plot at most 4 times the width of the parent | ||
| :class:`matplotlib.axes.Axes` in pixels of vertices. If the x-values | ||
| are not monotonic, the plot will still render correctly, but you are | ||
| not guaranteed any faster rendering. | ||
|
|
||
| If plotting with a non-solid line format (e.g. '--'), then | ||
| the downsampled plot could render quite differently from the | ||
| non-downsampled plot. | ||
|
|
||
| Markers will not be downsampled. | ||
|
|
||
| If the line is anti-aliased, then the downsampled plot will not | ||
| render *exactly* the same as a non-dowsampled plot, | ||
| but will be very similar. | ||
|
|
||
| See also | ||
| -------- | ||
| markevery : for downsampling the markers that are plotted. | ||
|
|
||
| """ | ||
| return self._downsample | ||
|
|
||
| @downsample.setter | ||
| def downsample(self, downsample): | ||
| """Sets the downsample property.""" | ||
| if self._downsample != downsample: | ||
| self.stale = True | ||
| self._downsample = downsample | ||
|
|
||
| def set_picker(self, p): | ||
| """Sets the event picker details for the line. | ||
|
|
||
| Expand Down Expand Up | @@ -747,6 +807,57 @@ def _is_sorted(self, x): | |
| # We don't handle the monotonically decreasing case. | ||
| return _path.is_sorted(x) | ||
|
|
||
| def _downsample_path(self, tpath, affine): | ||
| """ | ||
| Helper function to compute the downsampled path. | ||
| """ | ||
| # pull out the two bits of data we want from the path | ||
| codes, verts = tpath.codes, tpath.vertices | ||
|
|
||
| def _slice_or_none(in_v, slc): | ||
| """ | ||
| Helper function to cope with `codes` being an ndarray or `None` | ||
| """ | ||
| if in_v is None: | ||
| return None | ||
| return in_v[slc] | ||
|
|
||
| # Convert vertices from data space to pixel space. | ||
| # Any non-affine axis transformation has already been applied | ||
| # to tpath, so we just need to apply affine part. | ||
| verts_trans = affine.transform_path(tpath).vertices | ||
|
|
||
| # Find where the pixel column of the x data changes | ||
| split_indices = np.diff(np.floor(verts_trans[:, 0]).astype(int)) != 0 | ||
| split_indices = np.where(split_indices)[0] + 1 | ||
|
|
||
|
Comment thread
Copy link
Copy Markdown
Member
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. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityMaybe shorten the code and reduce the number of operations inside the loop (untested!): for i, y_pixel_col in enumerate(np.split(verts_trans[:, 1], split_indices)):
keep_inds[i, 1:3] = np.argmin(y_pixel_col), np.argmax(y_pixel_col)
starts = np.hstack((0, split_indices))
ends = np.hstack((split_indices, verts_trans.shape[0]))
keep_inds[:, :3] += starts[:, np.newaxis]
keep_inds[:, 3] = ends - 1
Sorry, something went wrong.
All reactions
Copy link
Copy Markdown
Contributor
Author
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. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityGood idea. Done. Your code worked without modification for me.
Sorry, something went wrong.
All reactions
|
||
| # Don't waste time downsampling if it won't give us any benefit | ||
| # 4.0 was chosen somewhat arbitrarily. | ||
| if split_indices.size >= verts_trans.shape[0] / 4.0: | ||
| return tpath | ||
|
|
||
| keep_inds = np.zeros((split_indices.size + 1, 4), dtype=int) | ||
| for i, y_pixel_col in enumerate(np.split(verts_trans[:, 1], | ||
| split_indices)): | ||
| try: | ||
| keep_inds[i, 1:3] = (np.nanargmin(y_pixel_col), | ||
| np.nanargmax(y_pixel_col)) | ||
| except ValueError: | ||
| # np.nanarg* raises a ValueError if all elements are NaN. | ||
| # If either np.nanargmin or np.nanargmax raise this error, | ||
| # then all y_pixel_col is NaN. In this case, the argmin | ||
| # and argmax are both undefined, just keep the first value. | ||
| keep_inds[i, 1:3] = 0 | ||
|
|
||
| starts = np.hstack((0, split_indices)) | ||
| ends = np.hstack((split_indices, verts_trans.shape[0])) | ||
| keep_inds[:, :3] += starts[:, np.newaxis] | ||
| keep_inds[:, 3] = ends - 1 | ||
|
|
||
| keep_inds = keep_inds.flatten() | ||
|
|
||
| return Path(verts[keep_inds], _slice_or_none(codes, keep_inds)) | ||
|
|
||
| @allow_rasterization | ||
| def draw(self, renderer): | ||
| """draw the Line with `renderer` unless visibility is False""" | ||
| Expand Down Expand Up | @@ -775,6 +886,8 @@ def draw(self, renderer): | |
| if funcname != '_draw_nothing': | ||
| tpath, affine = transf_path.get_transformed_path_and_affine() | ||
| if len(tpath.vertices): | ||
| if self._downsample: | ||
| tpath = self._downsample_path(tpath, affine) | ||
| line_func = getattr(self, funcname) | ||
| gc = renderer.new_gc() | ||
| self._set_gc_clip(gc) | ||
| Expand Down | ||
| Back | FazBrowse Home | New Git URL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityCan you add a sphinx-gallery like docstring to explain why this example has been added to the gallery?
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.