Skip to content

Commit b8726d0

Browse files
committed
Store transformations on collections as an Nx3x3 array, rather than a list of Transform objects.
When the collection has the same styling and only varies by offsets, use draw_markers instead.
1 parent d75d39f commit b8726d0

File tree

9 files changed

+5416
-5447
lines changed

9 files changed

+5416
-5447
lines changed

Diff for: lib/matplotlib/backend_bases.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms,
227227
path_ids = []
228228
for path, transform in self._iter_collection_raw_paths(
229229
master_transform, paths, all_transforms):
230-
path_ids.append((path, transform))
230+
path_ids.append((path, transforms.Affine2D(transform)))
231231

232232
for xo, yo, path_id, gc0, rgbFace in self._iter_collection(
233233
gc, master_transform, all_transforms, path_ids, offsets,
@@ -316,7 +316,7 @@ def _iter_collection_raw_paths(self, master_transform, paths,
316316
for i in xrange(N):
317317
path = paths[i % Npaths]
318318
if Ntransforms:
319-
transform = all_transforms[i % Ntransforms]
319+
transform = Affine2D(all_transforms[i % Ntransforms])
320320
yield path, transform + master_transform
321321

322322
def _iter_collection(self, gc, master_transform, all_transforms,
@@ -380,8 +380,9 @@ def _iter_collection(self, gc, master_transform, all_transforms,
380380
xo, yo = toffsets[i % Noffsets]
381381
if offset_position == 'data':
382382
if Ntransforms:
383-
transform = (all_transforms[i % Ntransforms] +
384-
master_transform)
383+
transform = (
384+
Affine2D(all_transforms[i % Ntransforms]) +
385+
master_transform)
385386
else:
386387
transform = master_transform
387388
xo, yo = transform.transform_point((xo, yo))

Diff for: lib/matplotlib/collections.py

+75-67
Original file line numberDiff line numberDiff line change
@@ -270,16 +270,41 @@ def draw(self, renderer):
270270
if self.get_path_effects():
271271
for pe in self.get_path_effects():
272272
pe.draw_path_collection(renderer,
273-
gc, transform.frozen(), paths, self.get_transforms(),
274-
offsets, transOffset, self.get_facecolor(), self.get_edgecolor(),
275-
self._linewidths, self._linestyles, self._antialiaseds, self._urls,
273+
gc, transform.frozen(), paths,
274+
self.get_transforms(), offsets, transOffset,
275+
self.get_facecolor(), self.get_edgecolor(),
276+
self._linewidths, self._linestyles,
277+
self._antialiaseds, self._urls,
276278
self._offset_position)
277279
else:
278-
renderer.draw_path_collection(
279-
gc, transform.frozen(), paths, self.get_transforms(),
280-
offsets, transOffset, self.get_facecolor(), self.get_edgecolor(),
281-
self._linewidths, self._linestyles, self._antialiaseds, self._urls,
282-
self._offset_position)
280+
trans = self.get_transforms()
281+
facecolors = self.get_facecolor()
282+
edgecolors = self.get_edgecolor()
283+
if (len(paths) == 1 and len(trans) <= 1 and
284+
len(facecolors) == 1 and len(edgecolors) == 1 and
285+
len(self._linewidths) == 1 and
286+
self._linestyles == [(None, None)] and
287+
len(self._antialiaseds) == 1 and len(self._urls) == 1 and
288+
self.get_hatch() is None):
289+
gc.set_foreground(tuple(edgecolors[0]))
290+
gc.set_linewidth(self._linewidths[0])
291+
gc.set_linestyle(self._linestyles[0])
292+
gc.set_antialiased(self._antialiaseds[0])
293+
gc.set_url(self._urls[0])
294+
if len(trans):
295+
transform = (transforms.Affine2D(trans[0]) +
296+
transform)
297+
renderer.draw_markers(
298+
gc, paths[0], transform.frozen(),
299+
mpath.Path(offsets), transOffset, tuple(facecolors[0]))
300+
else:
301+
renderer.draw_path_collection(
302+
gc, transform.frozen(), paths,
303+
self.get_transforms(), offsets, transOffset,
304+
self.get_facecolor(), self.get_edgecolor(),
305+
self._linewidths, self._linestyles,
306+
self._antialiaseds, self._urls,
307+
self._offset_position)
283308

284309
gc.restore()
285310
renderer.close_group(self.__class__.__name__)
@@ -686,7 +711,31 @@ def update_from(self, other):
686711
""")
687712

688713

689-
class PathCollection(Collection):
714+
class _CollectionWithSizes(Collection):
715+
"""
716+
Base class for collections that have an array of sizes.
717+
"""
718+
def get_sizes(self):
719+
return self._sizes
720+
721+
def set_sizes(self, sizes, dpi=72.0):
722+
if sizes is None:
723+
self._sizes = np.array([])
724+
self._transforms = np.empty((0, 3, 3))
725+
else:
726+
self._sizes = np.asarray(sizes)
727+
self._transforms = np.zeros((len(self._sizes), 3, 3))
728+
scale = np.sqrt(self._sizes) * dpi / 72.0
729+
self._transforms[:, 0, 0] = scale
730+
self._transforms[:, 1, 1] = scale
731+
self._transforms[:, 2, 2] = 1.0
732+
733+
def draw(self, renderer):
734+
self.set_sizes(self._sizes, self.figure.dpi)
735+
Collection.draw(self, renderer)
736+
737+
738+
class PathCollection(_CollectionWithSizes):
690739
"""
691740
This is the most basic :class:`Collection` subclass.
692741
"""
@@ -701,28 +750,16 @@ def __init__(self, paths, sizes=None, **kwargs):
701750

702751
Collection.__init__(self, **kwargs)
703752
self.set_paths(paths)
704-
self._sizes = sizes
753+
self.set_sizes(sizes)
705754

706755
def set_paths(self, paths):
707756
self._paths = paths
708757

709758
def get_paths(self):
710759
return self._paths
711760

712-
def get_sizes(self):
713-
return self._sizes
714-
715-
@allow_rasterization
716-
def draw(self, renderer):
717-
if self._sizes is not None:
718-
self._transforms = [
719-
transforms.Affine2D().scale(
720-
(np.sqrt(x) * self.figure.dpi / 72.0))
721-
for x in self._sizes]
722-
return Collection.draw(self, renderer)
723761

724-
725-
class PolyCollection(Collection):
762+
class PolyCollection(_CollectionWithSizes):
726763
@docstring.dedent_interpd
727764
def __init__(self, verts, sizes=None, closed=True, **kwargs):
728765
"""
@@ -744,7 +781,7 @@ def __init__(self, verts, sizes=None, closed=True, **kwargs):
744781
%(Collection)s
745782
"""
746783
Collection.__init__(self, **kwargs)
747-
self._sizes = sizes
784+
self.set_sizes(sizes)
748785
self.set_verts(verts, closed)
749786

750787
def set_verts(self, verts, closed=True):
@@ -773,15 +810,6 @@ def set_verts(self, verts, closed=True):
773810

774811
set_paths = set_verts
775812

776-
@allow_rasterization
777-
def draw(self, renderer):
778-
if self._sizes is not None:
779-
self._transforms = [
780-
transforms.Affine2D().scale(
781-
(np.sqrt(x) * self.figure.dpi / 72.0))
782-
for x in self._sizes]
783-
return Collection.draw(self, renderer)
784-
785813

786814
class BrokenBarHCollection(PolyCollection):
787815
"""
@@ -830,7 +858,7 @@ def span_where(x, ymin, ymax, where, **kwargs):
830858
return collection
831859

832860

833-
class RegularPolyCollection(Collection):
861+
class RegularPolyCollection(_CollectionWithSizes):
834862
"""Draw a collection of regular polygons with *numsides*."""
835863
_path_generator = mpath.Path.unit_regular_polygon
836864

@@ -871,29 +899,18 @@ def __init__(self,
871899
)
872900
"""
873901
Collection.__init__(self, **kwargs)
874-
self._sizes = sizes
902+
self.set_sizes(sizes)
875903
self._numsides = numsides
876904
self._paths = [self._path_generator(numsides)]
877905
self._rotation = rotation
878906
self.set_transform(transforms.IdentityTransform())
879907

880-
@allow_rasterization
881-
def draw(self, renderer):
882-
self._transforms = [
883-
transforms.Affine2D().rotate(-self._rotation).scale(
884-
(np.sqrt(x) * self.figure.dpi / 72.0) / np.sqrt(np.pi))
885-
for x in self._sizes]
886-
return Collection.draw(self, renderer)
887-
888908
def get_numsides(self):
889909
return self._numsides
890910

891911
def get_rotation(self):
892912
return self._rotation
893913

894-
def get_sizes(self):
895-
return self._sizes
896-
897914

898915
class StarPolygonCollection(RegularPolyCollection):
899916
"""
@@ -1339,7 +1356,7 @@ def get_color(self):
13391356
return self.get_colors()[0]
13401357

13411358

1342-
class CircleCollection(Collection):
1359+
class CircleCollection(_CollectionWithSizes):
13431360
"""
13441361
A collection of circles, drawn using splines.
13451362
"""
@@ -1352,24 +1369,10 @@ def __init__(self, sizes, **kwargs):
13521369
%(Collection)s
13531370
"""
13541371
Collection.__init__(self, **kwargs)
1355-
self._sizes = sizes
1372+
self.set_sizes(sizes)
13561373
self.set_transform(transforms.IdentityTransform())
13571374
self._paths = [mpath.Path.unit_circle()]
13581375

1359-
def get_sizes(self):
1360-
"return sizes of circles"
1361-
return self._sizes
1362-
1363-
@allow_rasterization
1364-
def draw(self, renderer):
1365-
# sizes is the area of the circle circumscribing the polygon
1366-
# in points^2
1367-
self._transforms = [
1368-
transforms.Affine2D().scale(
1369-
(np.sqrt(x) * self.figure.dpi / 72.0) / np.sqrt(np.pi))
1370-
for x in self._sizes]
1371-
return Collection.draw(self, renderer)
1372-
13731376

13741377
class EllipseCollection(Collection):
13751378
"""
@@ -1416,7 +1419,6 @@ def _set_transforms(self):
14161419
"""
14171420
Calculate transforms immediately before drawing.
14181421
"""
1419-
self._transforms = []
14201422
ax = self.axes
14211423
fig = self.figure
14221424

@@ -1439,10 +1441,16 @@ def _set_transforms(self):
14391441
else:
14401442
raise ValueError('unrecognized units: %s' % self._units)
14411443

1442-
_affine = transforms.Affine2D
1443-
for x, y, a in zip(self._widths, self._heights, self._angles):
1444-
trans = _affine().scale(x * sc, y * sc).rotate(a)
1445-
self._transforms.append(trans)
1444+
self._transforms = np.zeros((len(self._widths), 3, 3))
1445+
widths = self._widths * sc
1446+
heights = self._heights * sc
1447+
sin_angle = np.cos(np.deg2rad(self._angles))
1448+
cos_angle = np.cos(np.deg2rad(self._angles))
1449+
self._transforms[:, 0, 0] = widths * cos_angle
1450+
self._transforms[:, 0, 1] = heights * -sin_angle
1451+
self._transforms[:, 1, 0] = widths * sin_angle
1452+
self._transforms[:, 1, 1] = heights * cos_angle
1453+
self._transforms[:, 2, 2] = 1.0
14461454

14471455
if self._units == 'xy':
14481456
m = ax.transData.get_affine().get_matrix().copy()

Diff for: lib/matplotlib/image.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1313,7 +1313,8 @@ def pil_to_array(pilImage):
13131313
is MxNx3. For RGBA images the return value is MxNx4
13141314
"""
13151315
def toarray(im, dtype=np.uint8):
1316-
"""Teturn a 1D array of dtype."""
1316+
"""Return a 1D array of dtype."""
1317+
# Pillow wants us to use "tobytes"
13171318
if hasattr(im, 'tobytes'):
13181319
x_str = im.tobytes('raw', im.mode)
13191320
else:
Binary file not shown.
Loading

0 commit comments

Comments
 (0)