Skip to content

[Sprint] scatter plots are (reportedly) too slow #2156

New issue

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

Merged
merged 2 commits into from
Sep 30, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3269,12 +3269,14 @@ def scatter(self, x, y, s=20, c='b', marker='o', cmap=None, norm=None,
if not marker_obj.is_filled():
edgecolors = 'face'

offsets = np.dstack((x, y))

collection = mcoll.PathCollection(
(path,), scales,
facecolors=colors,
edgecolors=edgecolors,
linewidths=linewidths,
offsets=list(zip(x, y)),
offsets=offsets,
transOffset=kwargs.pop('transform', self.transData),
)
collection.set_transform(mtransforms.IdentityTransform())
Expand Down
9 changes: 5 additions & 4 deletions lib/matplotlib/backend_bases.py
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading