|
30 | 30 | import matplotlib.mlab as mlab |
31 | 31 |
|
32 | 32 |
|
| 33 | +CIRCLE_AREA_FACTOR = 1.0 / np.sqrt(np.pi) |
| 34 | + |
| 35 | + |
33 | 36 | class Collection(artist.Artist, cm.ScalarMappable): |
34 | 37 | """ |
35 | 38 | Base class for Collections. Must be subclassed to be usable. |
@@ -271,25 +274,41 @@ def draw(self, renderer): |
271 | 274 | from matplotlib.patheffects import PathEffectRenderer |
272 | 275 | renderer = PathEffectRenderer(self.get_path_effects(), renderer) |
273 | 276 |
|
| 277 | + # If the collection is made up of a single shape/color/stroke, |
| 278 | + # it can be rendered once and blitted multiple times, using |
| 279 | + # `draw_markers` rather than `draw_path_collection`. This is |
| 280 | + # *much* faster for Agg, and results in smaller file sizes in |
| 281 | + # PDF/SVG/PS. |
| 282 | + |
274 | 283 | trans = self.get_transforms() |
275 | 284 | facecolors = self.get_facecolor() |
276 | 285 | edgecolors = self.get_edgecolor() |
| 286 | + do_single_path_optimization = False |
277 | 287 | if (len(paths) == 1 and len(trans) <= 1 and |
278 | 288 | len(facecolors) == 1 and len(edgecolors) == 1 and |
279 | 289 | len(self._linewidths) == 1 and |
280 | 290 | self._linestyles == [(None, None)] and |
281 | 291 | len(self._antialiaseds) == 1 and len(self._urls) == 1 and |
282 | 292 | self.get_hatch() is None): |
| 293 | + if len(trans): |
| 294 | + combined_transform = (transforms.Affine2D(trans[0]) + |
| 295 | + transform) |
| 296 | + else: |
| 297 | + combined_transform = transform |
| 298 | + extents = paths[0].get_extents(combined_transform) |
| 299 | + width, height = renderer.get_canvas_width_height() |
| 300 | + if (extents.width < width and |
| 301 | + extents.height < height): |
| 302 | + do_single_path_optimization = True |
| 303 | + |
| 304 | + if do_single_path_optimization: |
283 | 305 | gc.set_foreground(tuple(edgecolors[0])) |
284 | 306 | gc.set_linewidth(self._linewidths[0]) |
285 | 307 | gc.set_linestyle(self._linestyles[0]) |
286 | 308 | gc.set_antialiased(self._antialiaseds[0]) |
287 | 309 | gc.set_url(self._urls[0]) |
288 | | - if len(trans): |
289 | | - transform = (transforms.Affine2D(trans[0]) + |
290 | | - transform) |
291 | 310 | renderer.draw_markers( |
292 | | - gc, paths[0], transform.frozen(), |
| 311 | + gc, paths[0], combined_transform.frozen(), |
293 | 312 | mpath.Path(offsets), transOffset, tuple(facecolors[0])) |
294 | 313 | else: |
295 | 314 | renderer.draw_path_collection( |
@@ -713,6 +732,8 @@ class _CollectionWithSizes(Collection): |
713 | 732 | """ |
714 | 733 | Base class for collections that have an array of sizes. |
715 | 734 | """ |
| 735 | + _factor = 1.0 |
| 736 | + |
716 | 737 | def get_sizes(self): |
717 | 738 | """ |
718 | 739 | Returns the sizes of the elements in the collection. The |
@@ -744,7 +765,7 @@ def set_sizes(self, sizes, dpi=72.0): |
744 | 765 | else: |
745 | 766 | self._sizes = np.asarray(sizes) |
746 | 767 | self._transforms = np.zeros((len(self._sizes), 3, 3)) |
747 | | - scale = np.sqrt(self._sizes) * dpi / 72.0 |
| 768 | + scale = np.sqrt(self._sizes) * dpi / 72.0 * self._factor |
748 | 769 | self._transforms[:, 0, 0] = scale |
749 | 770 | self._transforms[:, 1, 1] = scale |
750 | 771 | self._transforms[:, 2, 2] = 1.0 |
@@ -882,6 +903,8 @@ class RegularPolyCollection(_CollectionWithSizes): |
882 | 903 | """Draw a collection of regular polygons with *numsides*.""" |
883 | 904 | _path_generator = mpath.Path.unit_regular_polygon |
884 | 905 |
|
| 906 | + _factor = CIRCLE_AREA_FACTOR |
| 907 | + |
885 | 908 | @docstring.dedent_interpd |
886 | 909 | def __init__(self, |
887 | 910 | numsides, |
@@ -1389,6 +1412,8 @@ class CircleCollection(_CollectionWithSizes): |
1389 | 1412 | """ |
1390 | 1413 | A collection of circles, drawn using splines. |
1391 | 1414 | """ |
| 1415 | + _factor = CIRCLE_AREA_FACTOR |
| 1416 | + |
1392 | 1417 | @docstring.dedent_interpd |
1393 | 1418 | def __init__(self, sizes, **kwargs): |
1394 | 1419 | """ |
|
0 commit comments