diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 7d31f01f05f9..df2ccd133110 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1321,6 +1321,7 @@ def tk_window_focus(): 'matplotlib.tests.test_artist', 'matplotlib.tests.test_axes', 'matplotlib.tests.test_axes_grid1', + 'matplotlib.tests.test_backend_bases', 'matplotlib.tests.test_backend_pdf', 'matplotlib.tests.test_backend_pgf', 'matplotlib.tests.test_backend_ps', diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index aa1b76635643..2245fbe4a66c 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -382,6 +382,23 @@ def _iter_collection_raw_paths(self, master_transform, paths, transform = Affine2D(all_transforms[i % Ntransforms]) yield path, transform + master_transform + def _iter_collection_uses_per_path(self, paths, all_transforms, + offsets, facecolors, edgecolors): + """ + Compute how many times each raw path object returned by + _iter_collection_raw_paths would be used when calling + _iter_collection. This is intended for the backend to decide + on the tradeoff between using the paths in-line and storing + them once and reusing. Rounds up in case the number of uses + is not the same for every path. + """ + Npaths = len(paths) + if Npaths == 0 or (len(facecolors) == 0 and len(edgecolors) == 0): + return 0 + Npath_ids = max(Npaths, len(all_transforms)) + N = max(Npath_ids, len(offsets)) + return (N + Npath_ids - 1) // Npath_ids + def _iter_collection(self, gc, master_transform, all_transforms, path_ids, offsets, offsetTrans, facecolors, edgecolors, linewidths, linestyles, diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 109f1ef92124..77b1732e2d0a 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -1617,12 +1617,24 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms, if not len(edgecolors): stroked = False else: - if np.all(edgecolors[:, 3] == edgecolors[0, 3]): + if np.all(np.asarray(linewidths) == 0.0): + stroked = False + elif np.all(edgecolors[:, 3] == edgecolors[0, 3]): stroked = edgecolors[0, 3] != 0.0 else: can_do_optimization = False - if not can_do_optimization: + # Is the optimization worth it? Rough calculation: + # cost of emitting a path in-line is len_path * uses_per_path + # cost of XObject is len_path + 5 for the definition, + # uses_per_path for the uses + len_path = len(paths[0].vertices) if len(paths) > 0 else 0 + uses_per_path = self._iter_collection_uses_per_path( + paths, all_transforms, offsets, facecolors, edgecolors) + should_do_optimization = \ + len_path + uses_per_path + 5 < len_path * uses_per_path + + if (not can_do_optimization) or (not should_do_optimization): return RendererBase.draw_path_collection( self, gc, master_transform, paths, all_transforms, offsets, offsetTrans, facecolors, edgecolors, @@ -1654,9 +1666,10 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms, def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None): - # For simple paths or small numbers of markers, don't bother - # making an XObject - if len(path) * len(marker_path) <= 10: + # Same logic as in draw_path_collection + len_marker_path = len(marker_path) + uses = len(path) + if len_marker_path * uses < len_marker_path + uses + 5: RendererBase.draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace) return diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index d2e8bc4c7e1e..58c2ca59f43c 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -637,6 +637,23 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms, offsets, offsetTrans, facecolors, edgecolors, linewidths, linestyles, antialiaseds, urls, offset_position): + # Is the optimization worth it? Rough calculation: + # cost of emitting a path in-line is + # (len_path + 2) * uses_per_path + # cost of definition+use is + # (len_path + 3) + 3 * uses_per_path + len_path = len(paths[0].vertices) if len(paths) > 0 else 0 + uses_per_path = self._iter_collection_uses_per_path( + paths, all_transforms, offsets, facecolors, edgecolors) + should_do_optimization = \ + len_path + 3 * uses_per_path + 3 < (len_path + 2) * uses_per_path + if not should_do_optimization: + return RendererBase.draw_path_collection( + self, gc, master_transform, paths, all_transforms, + offsets, offsetTrans, facecolors, edgecolors, + linewidths, linestyles, antialiaseds, urls, + offset_position) + write = self._pswriter.write path_codes = [] diff --git a/lib/matplotlib/backends/backend_svg.py b/lib/matplotlib/backends/backend_svg.py index 89e4d5986fb8..36deeeb92d7b 100644 --- a/lib/matplotlib/backends/backend_svg.py +++ b/lib/matplotlib/backends/backend_svg.py @@ -607,6 +607,23 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms, offsets, offsetTrans, facecolors, edgecolors, linewidths, linestyles, antialiaseds, urls, offset_position): + # Is the optimization worth it? Rough calculation: + # cost of emitting a path in-line is + # (len_path + 5) * uses_per_path + # cost of definition+use is + # (len_path + 3) + 9 * uses_per_path + len_path = len(paths[0].vertices) if len(paths) > 0 else 0 + uses_per_path = self._iter_collection_uses_per_path( + paths, all_transforms, offsets, facecolors, edgecolors) + should_do_optimization = \ + len_path + 9 * uses_per_path + 3 < (len_path + 5) * uses_per_path + if not should_do_optimization: + return RendererBase.draw_path_collection( + self, gc, master_transform, paths, all_transforms, + offsets, offsetTrans, facecolors, edgecolors, + linewidths, linestyles, antialiaseds, urls, + offset_position) + writer = self.writer path_codes = [] writer.start('defs') diff --git a/lib/matplotlib/tests/test_backend_bases.py b/lib/matplotlib/tests/test_backend_bases.py new file mode 100644 index 000000000000..643ebc89adeb --- /dev/null +++ b/lib/matplotlib/tests/test_backend_bases.py @@ -0,0 +1,45 @@ +from matplotlib.backend_bases import RendererBase +import matplotlib.transforms as transforms +import matplotlib.path as path +import numpy as np + + +def test_uses_per_path(): + id = transforms.Affine2D() + paths = [path.Path.unit_regular_polygon(i) for i in range(3, 7)] + tforms = [id.rotate(i) for i in range(1, 5)] + offsets = np.arange(20).reshape((10, 2)) + facecolors = ['red', 'green'] + edgecolors = ['red', 'green'] + + def check(master_transform, paths, all_transforms, + offsets, facecolors, edgecolors): + rb = RendererBase() + raw_paths = list(rb._iter_collection_raw_paths(master_transform, + paths, all_transforms)) + gc = rb.new_gc() + ids = [path_id for xo, yo, path_id, gc0, rgbFace in + rb._iter_collection(gc, master_transform, all_transforms, + range(len(raw_paths)), offsets, + transforms.IdentityTransform(), + facecolors, edgecolors, [], [], [False], + [], 'data')] + uses = rb._iter_collection_uses_per_path( + paths, all_transforms, offsets, facecolors, edgecolors) + seen = [0] * len(raw_paths) + for i in ids: + seen[i] += 1 + for n in seen: + assert n in (uses-1, uses) + + check(id, paths, tforms, offsets, facecolors, edgecolors) + check(id, paths[0:1], tforms, offsets, facecolors, edgecolors) + check(id, [], tforms, offsets, facecolors, edgecolors) + check(id, paths, tforms[0:1], offsets, facecolors, edgecolors) + check(id, paths, [], offsets, facecolors, edgecolors) + for n in range(0, offsets.shape[0]): + check(id, paths, tforms, offsets[0:n, :], facecolors, edgecolors) + check(id, paths, tforms, offsets, [], edgecolors) + check(id, paths, tforms, offsets, facecolors, []) + check(id, paths, tforms, offsets, [], []) + check(id, paths, tforms, offsets, facecolors[0:1], edgecolors) diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/mixedsubplot.pdf b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/mixedsubplot.pdf index 4e5d6a3ffbeb..ce5201010739 100644 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/mixedsubplot.pdf and b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/mixedsubplot.pdf differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/surface3d.pdf b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/surface3d.pdf index d11eb77e4764..127696d86156 100644 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/surface3d.pdf and b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/surface3d.pdf differ