Skip to content

Commit

Permalink
Store transformations on collections as an Nx3x3 array, rather than a…
Browse files Browse the repository at this point in the history
… list of Transform objects.

When the collection has the same styling and only varies by offsets, use draw_markers instead.
  • Loading branch information
mdboom committed Sep 30, 2013
1 parent d75d39f commit b8726d0
Show file tree
Hide file tree
Showing 9 changed files with 5,416 additions and 5,447 deletions.
9 changes: 5 additions & 4 deletions lib/matplotlib/backend_bases.py
Expand Up @@ -227,7 +227,7 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms,
path_ids = []
for path, transform in self._iter_collection_raw_paths(
master_transform, paths, all_transforms):
path_ids.append((path, transform))
path_ids.append((path, transforms.Affine2D(transform)))

for xo, yo, path_id, gc0, rgbFace in self._iter_collection(
gc, master_transform, all_transforms, path_ids, offsets,
Expand Down Expand Up @@ -316,7 +316,7 @@ def _iter_collection_raw_paths(self, master_transform, paths,
for i in xrange(N):
path = paths[i % Npaths]
if Ntransforms:
transform = all_transforms[i % Ntransforms]
transform = Affine2D(all_transforms[i % Ntransforms])
yield path, transform + master_transform

def _iter_collection(self, gc, master_transform, all_transforms,
Expand Down Expand Up @@ -380,8 +380,9 @@ def _iter_collection(self, gc, master_transform, all_transforms,
xo, yo = toffsets[i % Noffsets]
if offset_position == 'data':
if Ntransforms:
transform = (all_transforms[i % Ntransforms] +
master_transform)
transform = (
Affine2D(all_transforms[i % Ntransforms]) +
master_transform)
else:
transform = master_transform
xo, yo = transform.transform_point((xo, yo))
Expand Down
142 changes: 75 additions & 67 deletions lib/matplotlib/collections.py
Expand Up @@ -270,16 +270,41 @@ def draw(self, renderer):
if self.get_path_effects():
for pe in self.get_path_effects():
pe.draw_path_collection(renderer,
gc, transform.frozen(), paths, self.get_transforms(),
offsets, transOffset, self.get_facecolor(), self.get_edgecolor(),
self._linewidths, self._linestyles, self._antialiaseds, self._urls,
gc, transform.frozen(), paths,
self.get_transforms(), offsets, transOffset,
self.get_facecolor(), self.get_edgecolor(),
self._linewidths, self._linestyles,
self._antialiaseds, self._urls,
self._offset_position)
else:
renderer.draw_path_collection(
gc, transform.frozen(), paths, self.get_transforms(),
offsets, transOffset, self.get_facecolor(), self.get_edgecolor(),
self._linewidths, self._linestyles, self._antialiaseds, self._urls,
self._offset_position)
trans = self.get_transforms()
facecolors = self.get_facecolor()
edgecolors = self.get_edgecolor()
if (len(paths) == 1 and len(trans) <= 1 and
len(facecolors) == 1 and len(edgecolors) == 1 and
len(self._linewidths) == 1 and
self._linestyles == [(None, None)] and
len(self._antialiaseds) == 1 and len(self._urls) == 1 and
self.get_hatch() is None):
gc.set_foreground(tuple(edgecolors[0]))
gc.set_linewidth(self._linewidths[0])
gc.set_linestyle(self._linestyles[0])
gc.set_antialiased(self._antialiaseds[0])
gc.set_url(self._urls[0])
if len(trans):
transform = (transforms.Affine2D(trans[0]) +
transform)
renderer.draw_markers(
gc, paths[0], transform.frozen(),
mpath.Path(offsets), transOffset, tuple(facecolors[0]))
else:
renderer.draw_path_collection(
gc, transform.frozen(), paths,
self.get_transforms(), offsets, transOffset,
self.get_facecolor(), self.get_edgecolor(),
self._linewidths, self._linestyles,
self._antialiaseds, self._urls,
self._offset_position)

gc.restore()
renderer.close_group(self.__class__.__name__)
Expand Down Expand Up @@ -686,7 +711,31 @@ def update_from(self, other):
""")


class PathCollection(Collection):
class _CollectionWithSizes(Collection):
"""
Base class for collections that have an array of sizes.
"""
def get_sizes(self):
return self._sizes

def set_sizes(self, sizes, dpi=72.0):
if sizes is None:
self._sizes = np.array([])
self._transforms = np.empty((0, 3, 3))
else:
self._sizes = np.asarray(sizes)
self._transforms = np.zeros((len(self._sizes), 3, 3))
scale = np.sqrt(self._sizes) * dpi / 72.0
self._transforms[:, 0, 0] = scale
self._transforms[:, 1, 1] = scale
self._transforms[:, 2, 2] = 1.0

def draw(self, renderer):
self.set_sizes(self._sizes, self.figure.dpi)
Collection.draw(self, renderer)


class PathCollection(_CollectionWithSizes):
"""
This is the most basic :class:`Collection` subclass.
"""
Expand All @@ -701,28 +750,16 @@ def __init__(self, paths, sizes=None, **kwargs):

Collection.__init__(self, **kwargs)
self.set_paths(paths)
self._sizes = sizes
self.set_sizes(sizes)

def set_paths(self, paths):
self._paths = paths

def get_paths(self):
return self._paths

def get_sizes(self):
return self._sizes

@allow_rasterization
def draw(self, renderer):
if self._sizes is not None:
self._transforms = [
transforms.Affine2D().scale(
(np.sqrt(x) * self.figure.dpi / 72.0))
for x in self._sizes]
return Collection.draw(self, renderer)


class PolyCollection(Collection):
class PolyCollection(_CollectionWithSizes):
@docstring.dedent_interpd
def __init__(self, verts, sizes=None, closed=True, **kwargs):
"""
Expand All @@ -744,7 +781,7 @@ def __init__(self, verts, sizes=None, closed=True, **kwargs):
%(Collection)s
"""
Collection.__init__(self, **kwargs)
self._sizes = sizes
self.set_sizes(sizes)
self.set_verts(verts, closed)

def set_verts(self, verts, closed=True):
Expand Down Expand Up @@ -773,15 +810,6 @@ def set_verts(self, verts, closed=True):

set_paths = set_verts

@allow_rasterization
def draw(self, renderer):
if self._sizes is not None:
self._transforms = [
transforms.Affine2D().scale(
(np.sqrt(x) * self.figure.dpi / 72.0))
for x in self._sizes]
return Collection.draw(self, renderer)


class BrokenBarHCollection(PolyCollection):
"""
Expand Down Expand Up @@ -830,7 +858,7 @@ def span_where(x, ymin, ymax, where, **kwargs):
return collection


class RegularPolyCollection(Collection):
class RegularPolyCollection(_CollectionWithSizes):
"""Draw a collection of regular polygons with *numsides*."""
_path_generator = mpath.Path.unit_regular_polygon

Expand Down Expand Up @@ -871,29 +899,18 @@ def __init__(self,
)
"""
Collection.__init__(self, **kwargs)
self._sizes = sizes
self.set_sizes(sizes)
self._numsides = numsides
self._paths = [self._path_generator(numsides)]
self._rotation = rotation
self.set_transform(transforms.IdentityTransform())

@allow_rasterization
def draw(self, renderer):
self._transforms = [
transforms.Affine2D().rotate(-self._rotation).scale(
(np.sqrt(x) * self.figure.dpi / 72.0) / np.sqrt(np.pi))
for x in self._sizes]
return Collection.draw(self, renderer)

def get_numsides(self):
return self._numsides

def get_rotation(self):
return self._rotation

def get_sizes(self):
return self._sizes


class StarPolygonCollection(RegularPolyCollection):
"""
Expand Down Expand Up @@ -1339,7 +1356,7 @@ def get_color(self):
return self.get_colors()[0]


class CircleCollection(Collection):
class CircleCollection(_CollectionWithSizes):
"""
A collection of circles, drawn using splines.
"""
Expand All @@ -1352,24 +1369,10 @@ def __init__(self, sizes, **kwargs):
%(Collection)s
"""
Collection.__init__(self, **kwargs)
self._sizes = sizes
self.set_sizes(sizes)
self.set_transform(transforms.IdentityTransform())
self._paths = [mpath.Path.unit_circle()]

def get_sizes(self):
"return sizes of circles"
return self._sizes

@allow_rasterization
def draw(self, renderer):
# sizes is the area of the circle circumscribing the polygon
# in points^2
self._transforms = [
transforms.Affine2D().scale(
(np.sqrt(x) * self.figure.dpi / 72.0) / np.sqrt(np.pi))
for x in self._sizes]
return Collection.draw(self, renderer)


class EllipseCollection(Collection):
"""
Expand Down Expand Up @@ -1416,7 +1419,6 @@ def _set_transforms(self):
"""
Calculate transforms immediately before drawing.
"""
self._transforms = []
ax = self.axes
fig = self.figure

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

_affine = transforms.Affine2D
for x, y, a in zip(self._widths, self._heights, self._angles):
trans = _affine().scale(x * sc, y * sc).rotate(a)
self._transforms.append(trans)
self._transforms = np.zeros((len(self._widths), 3, 3))
widths = self._widths * sc
heights = self._heights * sc
sin_angle = np.cos(np.deg2rad(self._angles))
cos_angle = np.cos(np.deg2rad(self._angles))
self._transforms[:, 0, 0] = widths * cos_angle
self._transforms[:, 0, 1] = heights * -sin_angle
self._transforms[:, 1, 0] = widths * sin_angle
self._transforms[:, 1, 1] = heights * cos_angle
self._transforms[:, 2, 2] = 1.0

if self._units == 'xy':
m = ax.transData.get_affine().get_matrix().copy()
Expand Down
3 changes: 2 additions & 1 deletion lib/matplotlib/image.py
Expand Up @@ -1313,7 +1313,8 @@ def pil_to_array(pilImage):
is MxNx3. For RGBA images the return value is MxNx4
"""
def toarray(im, dtype=np.uint8):
"""Teturn a 1D array of dtype."""
"""Return a 1D array of dtype."""
# Pillow wants us to use "tobytes"
if hasattr(im, 'tobytes'):
x_str = im.tobytes('raw', im.mode)
else:
Expand Down
Binary file not shown.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit b8726d0

Please sign in to comment.