Revision: 3928 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3928&view=rev Author: mdboom Date: 2007-10-08 11:10:11 -0700 (Mon, 08 Oct 2007)
Log Message: ----------- More work on collections. Modified Paths: -------------- branches/transforms/examples/collections_demo.py branches/transforms/lib/matplotlib/axes.py branches/transforms/lib/matplotlib/backends/backend_agg.py branches/transforms/lib/matplotlib/collections.py branches/transforms/lib/matplotlib/legend.py branches/transforms/lib/matplotlib/lines.py branches/transforms/lib/matplotlib/transforms.py branches/transforms/setupext.py branches/transforms/src/_backend_agg.cpp branches/transforms/src/_backend_agg.h Modified: branches/transforms/examples/collections_demo.py =================================================================== --- branches/transforms/examples/collections_demo.py 2007-10-08 12:45:23 UTC (rev 3927) +++ branches/transforms/examples/collections_demo.py 2007-10-08 18:10:11 UTC (rev 3928) @@ -45,6 +45,8 @@ a = fig.add_subplot(2,2,1) col = collections.LineCollection([spiral], offsets=xyo, transOffset=a.transData) +trans = transforms.Affine2D().scale(fig.dpi/72.0) +col.set_transform(trans) # the points to pixels transform # Note: the first argument to the collection initializer # must be a list of sequences of x,y tuples; we have only # one sequence, but we still have to put it in a list. @@ -59,9 +61,6 @@ # Make a transform for the line segments such that their size is # given in points: -trans = transforms.scale_transform(fig.dpi/transforms.Value(72.), - fig.dpi/transforms.Value(72.)) -col.set_transform(trans) # the points to pixels transform col.set_color(colors) a.autoscale_view() # See comment above, after a.add_collection. @@ -74,28 +73,25 @@ col = collections.PolyCollection([spiral], offsets=xyo, transOffset=a.transData) -a.add_collection(col, autolim=True) -trans = transforms.scale_transform(fig.dpi/transforms.Value(72.), - fig.dpi/transforms.Value(72.)) +trans = transforms.Affine2D().scale(fig.dpi/72.0) col.set_transform(trans) # the points to pixels transform +a.add_collection(col, autolim=True) col.set_color(colors) a.autoscale_view() a.set_title('PolyCollection using offsets') - # 7-sided regular polygons a = fig.add_subplot(2,2,3) col = collections.RegularPolyCollection(fig.dpi, 7, - sizes = N.fabs(xx)*10, offsets=xyo, + sizes = N.fabs(xx) / 10.0, offsets=xyo, transOffset=a.transData) -a.add_collection(col, autolim=True) -trans = transforms.scale_transform(fig.dpi/transforms.Value(72.), - fig.dpi/transforms.Value(72.)) +trans = transforms.Affine2D().scale(fig.dpi/72.0) col.set_transform(trans) # the points to pixels transform +a.add_collection(col, autolim=True) col.set_color(colors) a.autoscale_view() a.set_title('RegularPolyCollection using offsets') Modified: branches/transforms/lib/matplotlib/axes.py =================================================================== --- branches/transforms/lib/matplotlib/axes.py 2007-10-08 12:45:23 UTC (rev 3927) +++ branches/transforms/lib/matplotlib/axes.py 2007-10-08 18:10:11 UTC (rev 3928) @@ -1046,7 +1046,7 @@ self._set_artist_props(collection) collection.set_clip_path(self.axesPatch) if autolim: - self.update_datalim(collection.get_verts(self.transData)) + self.update_datalim(collection.get_datalim(self.transData)) collection._remove_method = lambda h: self.collections.remove(h) def add_line(self, line): @@ -1105,6 +1105,9 @@ # limits and set the bound to be the bounds of the xydata. # Otherwise, it will compute the bounds of it's current data # and the data in xydata + # MGDTODO: This isn't always the most efficient way to do this... in + # some cases things should use update_datalim_bounds + if not ma.isMaskedArray(xys): xys = npy.asarray(xys) self.update_datalim_numerix(xys[:, 0], xys[:, 1]) @@ -1119,6 +1122,10 @@ self.dataLim.update_from_data(x, y, self.ignore_existing_data_limits) self.ignore_existing_data_limits = False + def update_datalim_bounds(self, bounds): + # MGDTODO: Document me + self.dataLim.bounds = Bbox.union([self.dataLim, bounds]).bounds + def _get_verts_in_data_coords(self, trans, xys): if trans == self.transData: return xys @@ -4006,8 +4013,8 @@ shading='faceted --> edgecolors=None edgecolors also can be any mpl color or sequence of colors. - Optional kwargs control the PatchCollection properties: - %(PatchCollection)s + Optional kwargs control the Collection properties: + %(Collection)s """ if not self._hold: self.cla() @@ -4439,7 +4446,7 @@ * alpha=1.0 : the alpha blending value - Return value is a mcoll.PatchCollection + Return value is a mcoll.Collection object Grid Orientation @@ -4627,7 +4634,7 @@ * alpha=1.0 : the alpha blending value - Return value is a collections.PatchCollection + Return value is a collections.Collection object See pcolor for an explantion of the grid orientation and the Modified: branches/transforms/lib/matplotlib/backends/backend_agg.py =================================================================== --- branches/transforms/lib/matplotlib/backends/backend_agg.py 2007-10-08 12:45:23 UTC (rev 3927) +++ branches/transforms/lib/matplotlib/backends/backend_agg.py 2007-10-08 18:10:11 UTC (rev 3928) @@ -131,11 +131,15 @@ self._renderer.draw_markers(gc, marker_path, marker_trans.frozen(), path, trans.frozen(), rgbFace) def draw_path_collection(self, master_transform, clipbox, clippath, clippath_trans, - paths, transforms, facecolors, edgecolors, linewidths, linestyles, antialiaseds): + paths, transforms, offsets, transOffset, facecolors, edgecolors, + linewidths, linestyles, antialiaseds): assert master_transform.is_affine + if transOffset is not None: + transOffset = transOffset.frozen() self._renderer.draw_path_collection( master_transform.frozen(), clipbox, clippath, clippath_trans, - paths, transforms, facecolors, edgecolors, linewidths, linestyles, antialiaseds) + paths, transforms, offsets, transOffset, facecolors, edgecolors, linewidths, + linestyles, antialiaseds) def draw_mathtext(self, gc, x, y, s, prop, angle): """ Modified: branches/transforms/lib/matplotlib/collections.py =================================================================== --- branches/transforms/lib/matplotlib/collections.py 2007-10-08 12:45:23 UTC (rev 3927) +++ branches/transforms/lib/matplotlib/collections.py 2007-10-08 18:10:11 UTC (rev 3928) @@ -19,67 +19,20 @@ import matplotlib.nxutils as nxutils import matplotlib.path as path -# MGDTODO: Now that everything draws through draw_path_collection, -# perhaps more stuff could be moved into the Collection base class -# here +# MGDTODO: Move this stuff +from matplotlib.backends._backend_agg import get_path_collection_extents, \ + point_in_path_collection -class Collection(artist.Artist): +class Collection(artist.Artist, cm.ScalarMappable): """ + Base class for Collections. Must be subclassed to be usable. + All properties in a collection must be sequences or scalars; if scalars, they will be converted to sequences. The property of the ith element of the collection is the prop[i % len(props)]. - """ - - def __init__(self): - artist.Artist.__init__(self) - - - def get_verts(self): - 'return seq of (x,y) in collection' - raise NotImplementedError('Derived must override') - - def _get_value(self, val): - try: return (float(val), ) - except TypeError: - if cbook.iterable(val) and len(val): - try: float(val[0]) - except TypeError: pass # raise below - else: return val - - raise TypeError('val must be a float or nonzero sequence of floats') - - -# these are not available for the object inspector until after the -# class is built so we define an initial set here for the init -# function and they will be overridden after object defn -artist.kwdocd['PatchCollection'] = """\ - Valid PatchCollection kwargs are: - - edgecolors=None, - facecolors=None, - linewidths=None, - antialiaseds = None, - offsets = None, - transOffset = transforms.identity_transform(), - norm = None, # optional for cm.ScalarMappable - cmap = None, # ditto - - offsets and transOffset are used to translate the patch after - rendering (default no offsets) - - If any of edgecolors, facecolors, linewidths, antialiaseds are - None, they default to their patch.* rc params setting, in sequence - form. -""" - -class PatchCollection(Collection, cm.ScalarMappable): - """ - Base class for filled regions such as PolyCollection etc. - It must be subclassed to be usable. - kwargs are: edgecolors=None, @@ -103,30 +56,45 @@ draw time a call to scalar mappable will be made to set the face colors. """ + _offsets = npy.zeros((1, 2)) + _transOffset = transforms.IdentityTransform() + + _facecolors = [None] + _edgecolors = [None] + _lw = [1.0] + _ls = [None] + _aa = [True] + _pickradius = 5.0 + _transforms = [None] + zorder = 1 def __init__(self, edgecolors=None, facecolors=None, linewidths=None, + linestyles='solid', antialiaseds = None, offsets = None, transOffset = None, norm = None, # optional for ScalarMappable cmap = None, # ditto + pickradius = 5.0, + **kwargs ): """ Create a PatchCollection %(PatchCollection)s """ - Collection.__init__(self) + artist.Artist.__init__(self) cm.ScalarMappable.__init__(self, norm, cmap) if facecolors is None: facecolors = mpl.rcParams['patch.facecolor'] if edgecolors is None: edgecolors = mpl.rcParams['patch.edgecolor'] if linewidths is None: linewidths = (mpl.rcParams['patch.linewidth'],) if antialiaseds is None: antialiaseds = (mpl.rcParams['patch.antialiased'],) - + self.set_linestyles(linestyles) + self._facecolors = _colors.colorConverter.to_rgba_list(facecolors) if edgecolors == 'None': self._edgecolors = self._facecolors @@ -135,13 +103,80 @@ self._edgecolors = _colors.colorConverter.to_rgba_list(edgecolors) self._linewidths = self._get_value(linewidths) self._antialiaseds = self._get_value(antialiaseds) - #self._offsets = offsets - self._offsets = offsets - self._transOffset = transOffset - self._verts = [] - __init__.__doc__ = cbook.dedent(__init__.__doc__) % artist.kwdocd + self._uniform_offsets = None + self._offsets = npy.zeros((1, 2)) + if offsets is not None: + offsets = npy.asarray(offsets) + if len(offsets.shape) == 1: + offsets = offsets[npy.newaxis,:] # Make it Nx2. + if transOffset is not None: + Affine2D = transforms.Affine2D + self._offsets = offsets + self._transOffset = transOffset + else: + self._uniform_offsets = offsets + self._pickradius = pickradius + self.update(kwargs) + + def _get_value(self, val): + try: return (float(val), ) + except TypeError: + if cbook.iterable(val) and len(val): + try: float(val[0]) + except TypeError: pass # raise below + else: return val + + raise TypeError('val must be a float or nonzero sequence of floats') + + def get_paths(self): + raise NotImplementedError + + def get_transforms(self): + return self._transforms + + def get_datalim(self, transData): + result = transforms.Bbox.from_lbrt(*get_path_collection_extents( + self.get_transform().frozen(), + self.get_paths(), + self.get_transforms(), + self._offsets, + self._transOffset.frozen())) + result = result.transformed(transData.inverted()) + return result + + def draw(self, renderer): + if not self.get_visible(): return + renderer.open_group(self.__class__.__name__) + transform = self.get_transform() + transOffset = self._transOffset + offsets = self._offsets + + # MGDTODO: Transform the paths (since we don't keep track of segments anymore + if self.have_units(): + segments = [] + for segment in self._segments: + xs, ys = zip(*segment) + xs = self.convert_xunits(xs) + ys = self.convert_yunits(ys) + segments.append(zip(xs, ys)) +# if self._offsets is not None: +# xs = self.convert_xunits(self._offsets[:0]) +# ys = self.convert_yunits(self._offsets[:1]) +# offsets = zip(xs, ys) + + self.update_scalarmappable() + + #print 'calling renderer draw line collection' + clippath, clippath_trans = self.get_transformed_clip_path_and_affine() + + renderer.draw_path_collection( + transform, self.clipbox, clippath, clippath_trans, + self.get_paths(), self.get_transforms(), offsets, transOffset, + self._facecolors, self._edgecolors, self._lw, self._ls, self._aa) + renderer.close_group(self.__class__.__name__) + def contains(self, mouseevent): """ Test whether the mouse event occurred in the collection. @@ -149,25 +184,13 @@ Returns T/F, dict(ind=itemlist), where every item in itemlist contains the event. """ if callable(self._contains): return self._contains(self,mouseevent) - # TODO: Consider doing the test in data coordinates - # Patch transforms the mouse into data coordinates and does the - # test for membership there. This is more efficient though it - # may not match the visual appearance of the polygon on the - # screen. Regardless, patch and patch collection should use - # the same algorithm. Here's the code in patch: - # - # x, y = self.get_transform().inverse_xy_tup((mouseevent.x, mouseevent.y)) - # xyverts = self.get_verts() - # inside = nxutils.pnpoly(x, y, xyverts) - # - ind = [] - x, y = mouseevent.x, mouseevent.y - for i, thispoly in enumerate(self.get_transformed_patches()): - inside = nxutils.pnpoly(x, y, thispoly) - if inside: ind.append(i) + ind = point_in_path_collection( + mouseevent.x, mouseevent.y, self._pickradius, + self.get_transform(), self._paths, self._transforms, self._offsets, + self._offsetTrans, self._facecolors) return len(ind)>0,dict(ind=ind) - + # MGDTODO def get_transformed_patches(self): """ get a sequence of the polygons in the collection in display (transformed) space @@ -206,12 +229,9 @@ data.append(tverts) return data - def get_transoffset(self): - if self._transOffset is None: - self._transOffset = transforms.identity_transform() - return self._transOffset - - + def set_pickradius(self,pickradius): self.pickradius = 5 + def get_pickradius(self): return self.pickradius + def set_linewidth(self, lw): """ Set the linewidth(s) for the collection. lw can be a scalar or a @@ -224,6 +244,36 @@ def set_linewidths(self, lw): self.set_linewidth(lw) + def set_linestyles(self, ls): + """ + Set the linestyles(s) for the collection. + ACCEPTS: ['solid' | 'dashed', 'dashdot', 'dotted' | (offset, on-off-dash-seq) ] + """ + try: + if cbook.is_string_like(ls): + dashes = [backend_bases.GraphicsContextBase.dashd[ls]] + elif cbook.iterable(ls): + try: + dashes = [] + for x in ls: + if cbook.is_string_like(x): + dashes.append(backend_bases.GraphicsContextBase.dashd[ls]) + elif cbook.iterator(x) and len(x) == 2: + dashes.append(x) + else: + raise ValueError() + except ValueError: + if len(ls)==2: + dashes = ls + else: + raise ValueError() + else: + raise ValueError() + except ValueError: + raise ValueError('Do not know how to convert %s to dashes'%ls) + + self._ls = dashes + def set_color(self, c): """ Set both the edgecolor and the facecolor. @@ -277,6 +327,15 @@ if cbook.is_string_like(self._edgecolors) and self._edgecolors != 'None': self._edgecolors = [(r,g,b,alpha) for r,g,b,a in self._edgecolors] + def get_linewidth(self): + return self._lw + + def get_linestyle(self): + return self._ls + + def get_dashes(self): + return self._ls + def update_scalarmappable(self): """ If the scalar mappable array is not none, update facecolors @@ -289,7 +348,31 @@ self._facecolors = self.to_rgba(self._A, self._alpha) #print self._facecolors -class QuadMesh(PatchCollection): + +# these are not available for the object inspector until after the +# class is built so we define an initial set here for the init +# function and they will be overridden after object defn +artist.kwdocd['Collection'] = """\ + Valid Collection kwargs are: + + edgecolors=None, + facecolors=None, + linewidths=None, + antialiaseds = None, + offsets = None, + transOffset = transforms.identity_transform(), + norm = None, # optional for cm.ScalarMappable + cmap = None, # ditto + + offsets and transOffset are used to translate the patch after + rendering (default no offsets) + + If any of edgecolors, facecolors, linewidths, antialiaseds are + None, they default to their patch.* rc params setting, in sequence + form. +""" + +class QuadMesh(Collection): """ Class for the efficient drawing of a quadrilateral mesh. A quadrilateral mesh consists of a grid of vertices. The dimensions @@ -315,7 +398,7 @@ (0, 2) .. (0, meshWidth), (1, 0), (1, 1), and so on. """ def __init__(self, meshWidth, meshHeight, coordinates, showedges): - PatchCollection.__init__(self) + Collection.__init__(self) self._meshWidth = meshWidth self._meshHeight = meshHeight self._coordinates = coordinates @@ -329,7 +412,7 @@ # when creating/changing ****** Why not? speed? if not self.get_visible(): return transform = self.get_transform() - transoffset = self.get_transoffset() + transoffset = self._transOffset transform.freeze() transoffset.freeze() #print 'QuadMesh draw' @@ -342,63 +425,26 @@ transform.thaw() transoffset.thaw() -class PolyCollection(PatchCollection): +class PolyCollection(Collection): def __init__(self, verts, **kwargs): """ verts is a sequence of ( verts0, verts1, ...) where verts_i is a sequence of xy tuples of vertices, or an equivalent numpy array of shape (nv,2). - %(PatchCollection)s + %(Collection)s """ - PatchCollection.__init__(self,**kwargs) - self._verts = verts + Collection.__init__(self,**kwargs) + self.set_verts(verts) __init__.__doc__ = cbook.dedent(__init__.__doc__) % artist.kwdocd def set_verts(self, verts): '''This allows one to delay initialization of the vertices.''' - self._verts = verts + self._paths = [path.Path(v, closed=True) for v in verts] - def draw(self, renderer): - if not self.get_visible(): return - renderer.open_group('polycollection') - transform = self.get_transform() - transoffset = self.get_transoffset() - - - transform.freeze() - transoffset.freeze() - self.update_scalarmappable() - if cbook.is_string_like(self._edgecolors) and self._edgecolors[:2] == 'No': - self._linewidths = (0,) - #self._edgecolors = self._facecolors - renderer.draw_poly_collection( - self._verts, transform, self.clipbox, - self._facecolors, self._edgecolors, - self._linewidths, self._antialiaseds, - self._offsets, transoffset) - transform.thaw() - transoffset.thaw() - renderer.close_group('polycollection') - - - def get_verts(self, dataTrans=None): - '''Return vertices in data coordinates. - The calculation is incomplete in general; it is based - on the vertices or the offsets, whichever is using - dataTrans as its transformation, so it does not take - into account the combined effect of segments and offsets. - ''' - verts = [] - if self._offsets is None: - for seg in self._verts: - verts.extend(seg) - return [tuple(xy) for xy in verts] - if self.get_transoffset() == dataTrans: - return [tuple(xy) for xy in self._offsets] - raise NotImplementedError('Vertices in data coordinates are calculated\n' - + 'with offsets only if _transOffset == dataTrans.') - + def get_paths(self): + return self._paths + class BrokenBarHCollection(PolyCollection): """ A colleciton of horizontal bars spanning yrange with a sequence of @@ -409,7 +455,7 @@ xranges : sequence of (xmin, xwidth) yrange : ymin, ywidth - %(PatchCollection)s + %(Collection)s """ ymin, ywidth = yrange ymax = ymin + ywidth @@ -417,7 +463,7 @@ PolyCollection.__init__(self, verts, **kwargs) __init__.__doc__ = cbook.dedent(__init__.__doc__) % artist.kwdocd -class RegularPolyCollection(PatchCollection): +class RegularPolyCollection(Collection): def __init__(self, dpi, numsides, @@ -437,7 +483,7 @@ * rotation is the rotation of the polygon in radians - %(PatchCollection)s + %(Collection)s Example: see examples/dynamic_collection.py for complete example @@ -459,14 +505,18 @@ """ - PatchCollection.__init__(self,**kwargs) + Collection.__init__(self,**kwargs) self._sizes = sizes self._dpi = dpi - self.numsides = numsides - self.rotation = rotation - self._update_verts() + self._paths = [path.Path.unit_regular_polygon(numsides)] + self._transforms = [transforms.Affine2D().scale(x) for x in sizes] + __init__.__doc__ = cbook.dedent(__init__.__doc__) % artist.kwdocd + def get_paths(self): + return self._paths + + # MGDTODO def get_transformed_patches(self): # Shouldn't need all these calls to asarray; # the variables should be converted when stored. @@ -485,62 +535,6 @@ return polys - def _update_verts(self): - r = 1.0/math.sqrt(math.pi) # unit area - theta = (2*math.pi/self.numsides)*npy.arange(self.numsides) + self.rotation - self._verts = zip( r*npy.sin(theta), r*npy.cos(theta) ) - - def draw(self, renderer): - if not self.get_visible(): return - renderer.open_group('regpolycollection') - transform = self.get_transform() - transoffset = self.get_transoffset() - - transform.freeze() - transoffset.freeze() - self.update_scalarmappable() - self._update_verts() - scales = npy.sqrt(npy.asarray(self._sizes)*self._dpi.get()/72.0) - - - offsets = self._offsets - if self._offsets is not None: - xs, ys = zip(*offsets) - #print 'converting: units=%s, converter=%s'%(self.axes.xaxis.units, self.axes.xaxis.converter) - xs = self.convert_xunits(xs) - ys = self.convert_yunits(ys) - offsets = zip(xs, ys) - else: - offsets = None - - #print 'drawing offsets', offsets - #print 'drawing verts', self._verts - #print 'drawing scales', scales - if cbook.is_string_like(self._edgecolors) and self._edgecolors[:2] == 'No': - #self._edgecolors = self._facecolors - self._linewidths = (0,) - renderer.draw_regpoly_collection( - self.clipbox, - offsets, transoffset, - self._verts, scales, - self._facecolors, self._edgecolors, - self._linewidths, self._antialiaseds) - - transform.thaw() - transoffset.thaw() - renderer.close_group('regpolycollection') - - - def get_verts(self, dataTrans=None): - '''Return vertices in data coordinates. - The calculation is incomplete; it uses only - the offsets, and only if _transOffset is dataTrans. - ''' - if self.get_transoffset() == dataTrans: - return [tuple(xy) for xy in self._offsets] - raise NotImplementedError('Vertices in data coordinates are calculated\n' - + 'only with offsets and only if _transOffset == dataTrans.') - class StarPolygonCollection(RegularPolyCollection): def __init__(self, dpi, @@ -561,7 +555,7 @@ * rotation is the rotation of the polygon in radians - %(PatchCollection)s + %(Collection)s """ RegularPolyCollection.__init__(self, dpi, numsides, rotation, sizes, **kwargs) @@ -595,7 +589,7 @@ * rotation is the rotation of the polygon in radians - %(PatchCollection)s + %(Collection)s """ RegularPolyCollection.__init__(self, dpi, numsides, rotation, sizes, **kwargs) @@ -621,9 +615,8 @@ colors = None, antialiaseds = None, linestyles = 'solid', - # MGDTODO: Deal with offsets (mayb -# offsets = None, -# transOffset = None,#transforms.identity_transform(), + offsets = None, + transOffset = None, norm = None, cmap = None, pickradius = 5, @@ -668,168 +661,52 @@ matrix _A is not None (ie a call to set_array has been made), at draw time a call to scalar mappable will be made to set the colors. """ - Collection.__init__(self) - cm.ScalarMappable.__init__(self, norm, cmap) + if colors is None: colors = mpl.rcParams['lines.color'] + if linewidths is None: linewidths = (mpl.rcParams['lines.linewidth'],) + if antialiaseds is None: antialiaseds = (mpl.rcParams['lines.antialiased'],) + self.set_linestyles(linestyles) - if linewidths is None : - linewidths = (mpl.rcParams['lines.linewidth'], ) - if colors is None : - colors = (mpl.rcParams['lines.color'],) - if antialiaseds is None : - antialiaseds = (mpl.rcParams['lines.antialiased'], ) + Collection.__init__( + self, + edgecolors=colors, + linewidths=linewidths, + linestyles=linestyles, + antialiaseds=antialiaseds, + offsets=offsets, + transOffset=transOffset, + norm=norm, + cmap=cmap, + pickradius=pickradius, + **kwargs) - self._colors = _colors.colorConverter.to_rgba_list(colors) - self._aa = self._get_value(antialiaseds) - self._lw = self._get_value(linewidths) - self.set_linestyles(linestyles) - self._uniform_offsets = None - # MGDTODO: Deal with offsets -# if offsets is not None: -# offsets = npy.asarray(offsets) -# if len(offsets.shape) == 1: -# offsets = offsets[npy.newaxis,:] # Make it Nx2. -# if transOffset is None: -# if offsets is not None: -# self._uniform_offsets = offsets -# offsets = None -# transOffset = transforms.IdentityTransform() -# self._offsets = offsets -# self._transOffset = transOffset + self._facecolors = [None] self.set_segments(segments) - self.pickradius = pickradius - self.update(kwargs) - def contains(self, mouseevent): - """ - Test whether the mouse event occurred in the collection. - - Returns T/F, dict(ind=itemlist), where every item in itemlist contains the event. - """ - import matplotlib.lines as ML - if callable(self._contains): return self._contains(self,mouseevent) - - # TODO: add offset processing; adjusting the mouse for each offset - # will be somewhat cheaper than adjusting the segments. - if self._offsets != None: - raise NotImplementedError, "LineCollection does not yet support picking with offsets" - - mx,my = mouseevent.x,mouseevent.y - transform = self.get_transform() - - # MGDTODO: Numpify - ind = [] - for this in xrange(len(self._segments)): - xy = transform.transform_point(self._segments[this]) - this_ind = ML.segment_hits(mx,my,xy[:,0],xy[:,1],self.pickradius) - ind.extend([(this,k) for k in this_ind]) - return len(ind)>0,dict(ind=ind) - - def set_pickradius(self,pickradius): self.pickradius = 5 - def get_pickradius(self): return self.pickradius - - # MGDTODO: Support offsets (maybe) -# def get_transoffset(self): -# if self._transOffset is None: -# self._transOffset = transforms.identity_transform() -# return self._transOffset - + def get_paths(self): + return self._paths + def set_segments(self, segments): if segments is None: return + segments = [npy.asarray(seg, npy.float_) for seg in segments] + if self._uniform_offsets is not None: + segments = self._add_offsets(segments) self._paths = [path.Path(seg, closed=False) for seg in segments] -# if self._uniform_offsets is not None: -# self._add_offsets() - set_verts = set_segments # for compatibility with PolyCollection - # MGDTODO: Support offsets (maybe) -# def _add_offsets(self): -# segs = self._segments -# offsets = self._uniform_offsets -# Nsegs = len(segs) -# Noffs = offsets.shape[0] -# if Noffs == 1: -# for i in range(Nsegs): -# segs[i] = segs[i] + i * offsets -# else: -# for i in range(Nsegs): -# io = i%Noffs -# segs[i] = segs[i] + offsets[io:io+1] + def _add_offsets(self, segs): + offsets = self._uniform_offsets + Nsegs = len(segs) + Noffs = offsets.shape[0] + if Noffs == 1: + for i in range(Nsegs): + segs[i] = segs[i] + i * offsets + else: + for i in range(Nsegs): + io = i%Noffs + segs[i] = segs[i] + offsets[io:io+1] + return segs - def draw(self, renderer): - if not self.get_visible(): return - renderer.open_group('linecollection') - transform = self.get_transform() - # MGDTODO: Deal with offsets (maybe) -# transoffset = self.get_transoffset() - - # segments = self._segments - # MGDTODO: Deal with offsets (maybe) - # offsets = self._offsets - - # MGDTODO: Transform the paths (since we don't keep track of segments anymore - if self.have_units(): - segments = [] - for segment in self._segments: - xs, ys = zip(*segment) - xs = self.convert_xunits(xs) - ys = self.convert_yunits(ys) - segments.append(zip(xs, ys)) -# if self._offsets is not None: -# xs = self.convert_xunits(self._offsets[:0]) -# ys = self.convert_yunits(self._offsets[:1]) -# offsets = zip(xs, ys) - - self.update_scalarmappable() - #print 'calling renderer draw line collection' - clippath, clippath_trans = self.get_transformed_clip_path_and_affine() - renderer.draw_path_collection( - transform, self.clipbox, clippath, clippath_trans, - self._paths, [None], [None], - self._colors, self._lw, self._ls, self._aa) - renderer.close_group('linecollection') - - def set_linewidth(self, lw): - """ - Set the linewidth(s) for the collection. lw can be a scalar or a - sequence; if it is a sequence the patches will cycle through the - sequence - - ACCEPTS: float or sequence of floats - """ - - self._lw = self._get_value(lw) - - def set_linestyles(self, ls): - """ - Set the linestyles(s) for the collection. - ACCEPTS: ['solid' | 'dashed', 'dashdot', 'dotted' | (offset, on-off-dash-seq) ] - """ - try: - if cbook.is_string_like(ls): - dashes = [backend_bases.GraphicsContextBase.dashd[ls]] - elif cbook.iterable(ls): - try: - dashes = [] - for x in ls: - if cbook.is_string_like(x): - dashes.append(backend_bases.GraphicsContextBase.dashd[ls]) - elif cbook.iterator(x) and len(x) == 2: - dashes.append(x) - else: - raise ValueError() - except ValueError: - if len(ls)==2: - dashes = ls - else: - raise ValueError() - else: - raise ValueError() - except ValueError: - raise ValueError('Do not know how to convert %s to dashes'%ls) - - self._ls = dashes - def set_color(self, c): """ Set the color(s) of the line collection. c can be a @@ -839,7 +716,7 @@ ACCEPTS: matplotlib color arg or sequence of rgba tuples """ - self._colors = _colors.colorConverter.to_rgba_list(c) + self._edgecolors = _colors.colorConverter.to_rgba_list(c) def color(self, c): """ @@ -853,48 +730,12 @@ warnings.warn('LineCollection.color deprecated; use set_color instead') return self.set_color(c) - def set_alpha(self, alpha): - """ - Set the alpha tranpancies of the collection. Alpha can be a - float, in which case it is applied to the entire collection, - or a sequence of floats - - ACCEPTS: float or sequence of floats - """ - - try: float(alpha) - except TypeError: raise TypeError('alpha must be a float') - else: - artist.Artist.set_alpha(self, alpha) - self._colors = [(r,g,b,alpha) for r,g,b,a in self._colors] - - def get_linewidth(self): - return self._lw - - def get_linestyle(self): - return self._ls - - def get_dashes(self): - return self._ls - def get_color(self): - return self._colors + return self._edgecolors get_colors = get_color # for compatibility with old versions - def update_scalarmappable(self): - """ - If the scalar mappable array is not none, update colors - from scalar data - """ - if self._A is None: return - if len(self._A.shape)>1: - raise ValueError('LineCollections can only map rank 1 arrays') - self._colors = self.to_rgba(self._A, self._alpha) - - -artist.kwdocd['Collection'] = artist.kwdoc(Collection) -artist.kwdocd['PatchCollection'] = patchstr = artist.kwdoc(PatchCollection) +artist.kwdocd['Collection'] = patchstr = artist.kwdoc(Collection) for k in ('QuadMesh', 'PolyCollection', 'BrokenBarHCollection', 'RegularPolyCollection', 'StarPolygonCollection'): artist.kwdocd[k] = patchstr Modified: branches/transforms/lib/matplotlib/legend.py =================================================================== --- branches/transforms/lib/matplotlib/legend.py 2007-10-08 12:45:23 UTC (rev 3927) +++ branches/transforms/lib/matplotlib/legend.py 2007-10-08 18:10:11 UTC (rev 3928) @@ -32,7 +32,7 @@ from lines import Line2D from mlab import segments_intersect from patches import Patch, Rectangle, RegularPolygon, Shadow, bbox_artist, draw_bbox -from collections import LineCollection, RegularPolyCollection, PatchCollection +from collections import LineCollection, RegularPolyCollection from text import Text from transforms import Affine2D, Bbox, BboxTransform Modified: branches/transforms/lib/matplotlib/lines.py =================================================================== --- branches/transforms/lib/matplotlib/lines.py 2007-10-08 12:45:23 UTC (rev 3927) +++ branches/transforms/lib/matplotlib/lines.py 2007-10-08 18:10:11 UTC (rev 3928) @@ -349,24 +349,14 @@ self._picker = p def get_window_extent(self, renderer): - # MGDTODO: Numpify - xy = self.get_transform().transform(self._xy) + bbox = Bbox() + bbox.update_from_data(self.get_transform().transform(self._xy)) - x = xy[:, 0] - y = xy[:, 1] - left = x.min() - bottom = y.min() - width = x.max() - left - height = y.max() - bottom - # correct for marker size, if any if self._marker is not None: ms = self._markersize / 72.0 * self.figure.dpi - left -= ms/2 - bottom -= ms/2 - width += ms - height += ms - return Bbox.from_lbwh(left, bottom, width, height) + bbox = Bbox(bbox.get_points() + [[-ms/2.0, ms/2.0]]) + return bbox def set_axes(self, ax): Modified: branches/transforms/lib/matplotlib/transforms.py =================================================================== --- branches/transforms/lib/matplotlib/transforms.py 2007-10-08 12:45:23 UTC (rev 3927) +++ branches/transforms/lib/matplotlib/transforms.py 2007-10-08 18:10:11 UTC (rev 3928) @@ -395,6 +395,8 @@ when False, include the existing bounds of the Bbox. when None, use the last value passed to Bbox.ignore(). """ + # MGDTODO: It may be more efficient for some callers to use update_from_data_xy instead + if ignore is None: ignore = self._ignore @@ -430,6 +432,18 @@ npy.float_) self._minpos = npy.minimum(minpos, self._minpos) self.invalidate() + + def update_from_data_xy(self, xy, ignore=None): + """ + Update the bounds of the Bbox based on the passed in data. + + xy: a numpy array of 2D points + ignore: + when True, ignore the existing bounds of the Bbox. + when False, include the existing bounds of the Bbox. + when None, use the last value passed to Bbox.ignore(). + """ + return self.update_from_data(xy[:, 0], xy[:, 1], ignore) def _set_xmin(self, val): self._points[0, 0] = val Modified: branches/transforms/setupext.py =================================================================== --- branches/transforms/setupext.py 2007-10-08 12:45:23 UTC (rev 3927) +++ branches/transforms/setupext.py 2007-10-08 18:10:11 UTC (rev 3928) @@ -352,6 +352,7 @@ try_pkgconfig(module, 'libpng', 'png') module.libraries.append('z') add_base_flags(module) + add_numpy_flags(module) module.include_dirs.extend(['src','swig', '%s/include'%AGG_VERSION, '.']) # put these later for correct link order @@ -823,7 +824,7 @@ # add agg flags before pygtk because agg only supports freetype1 # and pygtk includes freetype2. This is a bit fragile. - + add_tk_flags(module) # do this first add_agg_flags(module) add_ft2font_flags(module) Modified: branches/transforms/src/_backend_agg.cpp =================================================================== --- branches/transforms/src/_backend_agg.cpp 2007-10-08 12:45:23 UTC (rev 3927) +++ branches/transforms/src/_backend_agg.cpp 2007-10-08 18:10:11 UTC (rev 3928) @@ -336,9 +336,11 @@ if (bbox_obj.ptr() != Py_None) { PyArrayObject* bbox = (PyArrayObject*) PyArray_FromObject(bbox_obj.ptr(), PyArray_DOUBLE, 2, 2); - if (!bbox || bbox->nd != 2 || bbox->dimensions[0] != 2 || bbox->dimensions[1] != 2) + if (!bbox || bbox->nd != 2 || bbox->dimensions[0] != 2 || bbox->dimensions[1] != 2) { + Py_XDECREF(bbox); throw Py::TypeError ("Expected a Bbox object."); + } *l = *(double*)PyArray_GETPTR2(bbox, 0, 0); double _b = *(double*)PyArray_GETPTR2(bbox, 0, 1); @@ -346,8 +348,11 @@ double _t = *(double*)PyArray_GETPTR2(bbox, 1, 1); *b = height - _t; *t = height - _b; + + Py_XDECREF(bbox); return true; } + return false; } @@ -920,7 +925,7 @@ Py::Object RendererAgg::draw_path_collection(const Py::Tuple& args) { _VERBOSE("RendererAgg::draw_path_collection"); - args.verify_length(11); + args.verify_length(13); //segments, trans, clipbox, colors, linewidths, antialiaseds agg::trans_affine master_transform = py_to_agg_transformation_matrix(args[0]); @@ -929,23 +934,30 @@ agg::trans_affine clippath_trans = py_to_agg_transformation_matrix(args[3], false); Py::SeqBase<Py::Object> paths = args[4]; Py::SeqBase<Py::Object> transforms_obj = args[5]; - Py::SeqBase<Py::Object> facecolors_obj = args[6]; - Py::SeqBase<Py::Object> edgecolors_obj = args[7]; - Py::SeqBase<Py::Float> linewidths = args[8]; - Py::SeqBase<Py::Object> linestyles_obj = args[9]; - Py::SeqBase<Py::Int> antialiaseds = args[10]; + Py::Object offsets_obj = args[6]; + agg::trans_affine offset_trans = py_to_agg_transformation_matrix(args[7], false); + Py::SeqBase<Py::Object> facecolors_obj = args[8]; + Py::SeqBase<Py::Object> edgecolors_obj = args[9]; + Py::SeqBase<Py::Float> linewidths = args[10]; + Py::SeqBase<Py::Object> linestyles_obj = args[11]; + Py::SeqBase<Py::Int> antialiaseds = args[12]; GCAgg gc(dpi, false); + PyArrayObject* offsets = (PyArrayObject*)PyArray_FromObject(offsets_obj.ptr(), PyArray_DOUBLE, 2, 2); + if (!offsets || offsets->dimensions[1] != 2) + throw Py::ValueError("Offsets array must be Nx2"); + size_t Npaths = paths.length(); - size_t Ntransforms = std::min(transforms_obj.length(), Npaths); - size_t Nfacecolors = std::min(facecolors_obj.length(), Npaths); - size_t Nedgecolors = std::min(edgecolors_obj.length(), Npaths); + size_t Noffsets = offsets->dimensions[0]; + size_t N = std::max(Npaths, Noffsets); + size_t Ntransforms = std::min(transforms_obj.length(), N); + size_t Nfacecolors = std::min(facecolors_obj.length(), N); + size_t Nedgecolors = std::min(edgecolors_obj.length(), N); size_t Nlinewidths = linewidths.length(); - size_t Nlinestyles = std::min(linestyles_obj.length(), Npaths); + size_t Nlinestyles = std::min(linestyles_obj.length(), N); size_t Naa = antialiaseds.length(); - size_t N = Npaths; size_t i = 0; // Convert all of the transforms up front @@ -983,7 +995,7 @@ } } - // Convert all of the edges up front + // Convert all of the edgecolors up front typedef std::vector<agg::rgba> edgecolors_t; edgecolors_t edgecolors; edgecolors.reserve(Nedgecolors); @@ -1016,18 +1028,23 @@ bool has_clippath = render_clippath(clippath, clippath_trans); for (i = 0; i < N; ++i) { - facepair_t& face = facecolors[i % Nfacecolors]; - gc.color = edgecolors[i % Nedgecolors]; - gc.linewidth = double(Py::Float(linewidths[i % Nlinewidths])) * dpi/72.0; - gc.dashes = dashes[i % Nlinestyles].second; - gc.dashOffset = dashes[i % Nlinestyles].first; - gc.isaa = bool(Py::Int(antialiaseds[i % Naa])); + double xo = *(double*)PyArray_GETPTR2(offsets, i % Noffsets, 0); + double yo = *(double*)PyArray_GETPTR2(offsets, i % Noffsets, 1); + offset_trans.transform(&xo, &yo); + agg::trans_affine_translation transOffset(xo, yo); agg::trans_affine& trans = transforms[i % Ntransforms]; - PathIterator path(paths[i]); + facepair_t& face = facecolors[i % Nfacecolors]; + gc.color = edgecolors[i % Nedgecolors]; + gc.linewidth = double(Py::Float(linewidths[i % Nlinewidths])) * dpi/72.0; + gc.dashes = dashes[i % Nlinestyles].second; + gc.dashOffset = dashes[i % Nlinestyles].first; + gc.isaa = bool(Py::Int(antialiaseds[i % Naa])); + PathIterator path(paths[i % Npaths]); bool snap = (path.total_vertices() == 2); - _draw_path(path, trans, snap, has_clippath, face, gc); + _draw_path(path, trans * transOffset, snap, has_clippath, face, gc); } + Py_XDECREF(offsets); return Py::Object(); } @@ -1482,11 +1499,9 @@ void get_path_extents(PathIterator& path, agg::trans_affine& trans, double* x0, double* y0, double* x1, double* y1) { - typedef agg::conv_transform<PathIterator> transformed_path_t; - typedef agg::conv_curve<transformed_path_t> curve_t; + typedef agg::conv_curve<PathIterator> curve_t; - transformed_path_t trans_path(path, trans); - curve_t curved_path(trans_path); + curve_t curved_path(path); double x, y; curved_path.rewind(0); @@ -1505,6 +1520,9 @@ if (x > *x1) *x1 = x; if (y > *y1) *y1 = y; } + + trans.transform(x0, y0); + trans.transform(x1, y1); } Py::Object _backend_agg_module::get_path_extents(const Py::Tuple& args) { @@ -1524,6 +1542,133 @@ return result; } +struct PathCollectionExtents { + double x0, y0, x1, y1; +}; + +Py::Object _backend_agg_module::get_path_collection_extents(const Py::Tuple& args) { + args.verify_length(5); + + //segments, trans, clipbox, colors, linewidths, antialiaseds + agg::trans_affine master_transform = py_to_agg_transformation_matrix(args[0]); + Py::SeqBase<Py::Object> paths = args[1]; + Py::SeqBase<Py::Object> transforms_obj = args[2]; + Py::SeqBase<Py::Object> offsets = args[3]; + agg::trans_affine offset_trans = py_to_agg_transformation_matrix(args[4], false); + + size_t Npaths = paths.length(); + size_t Noffsets = offsets.length(); + size_t N = std::max(Npaths, Noffsets); + size_t Ntransforms = std::min(transforms_obj.length(), N); + size_t i; + + // Convert all of the transforms up front + typedef std::vector<agg::trans_affine> transforms_t; + transforms_t transforms; + transforms.reserve(Ntransforms); + for (i = 0; i < Ntransforms; ++i) { + agg::trans_affine trans = py_to_agg_transformation_matrix + (transforms_obj[i], false); + trans *= master_transform; + transforms.push_back(trans); + } + + typedef std::vector<PathCollectionExtents> path_extents_t; + path_extents_t path_extents; + path_extents.resize(Npaths); + + // Get each of the path extents first + i = 0; + for (path_extents_t::iterator p = path_extents.begin(); + p != path_extents.end(); ++p, ++i) { + PathIterator path(paths[i]); + agg::trans_affine& trans = transforms[i % Ntransforms]; + ::get_path_extents(path, trans, &p->x0, &p->y0, &p->x1, &p->y1); + } + + // The offset each of those and collect the mins/maxs + double x0 = std::numeric_limits<double>::infinity(); + double y0 = std::numeric_limits<double>::infinity(); + double x1 = -std::numeric_limits<double>::infinity(); + double y1 = -std::numeric_limits<double>::infinity(); + for (i = 0; i < N; ++i) { + Py::SeqBase<Py::Float> offset = Py::SeqBase<Py::Float>(offsets[i % Noffsets]); + double xo = Py::Float(offset[0]); + double yo = Py::Float(offset[1]); + offset_trans.transform(&xo, &yo); + PathCollectionExtents& ext = path_extents[i % Npaths]; + + x0 = std::min(x0, ext.x0 + xo); + y0 = std::min(y0, ext.y0 + yo); + x1 = std::max(x1, ext.x1 + xo); + y1 = std::max(y1, ext.y1 + yo); + } + + Py::Tuple result(4); + result[0] = Py::Float(x0); + result[1] = Py::Float(y0); + result[2] = Py::Float(x1); + result[3] = Py::Float(y1); + return result; +} + +Py::Object _backend_agg_module::point_in_path_collection(const Py::Tuple& args) { + args.verify_length(9); + + //segments, trans, clipbox, colors, linewidths, antialiaseds + double x = Py::Float(args[0]); + double y = Py::Float(args[1]); + double radius = Py::Float(args[2]); + agg::trans_affine master_transform = py_to_agg_transformation_matrix(args[3]); + Py::SeqBase<Py::Object> paths = args[4]; + Py::SeqBase<Py::Object> transforms_obj = args[5]; + Py::SeqBase<Py::Object> offsets = args[6]; + agg::trans_affine offset_trans = py_to_agg_transformation_matrix(args[7], false); + Py::SeqBase<Py::Object> facecolors = args[8]; + + size_t Npaths = paths.length(); + size_t Noffsets = offsets.length(); + size_t N = std::max(Npaths, Noffsets); + size_t Ntransforms = std::min(transforms_obj.length(), N); + size_t Ncolors = facecolors.length(); + size_t i; + + // Convert all of the transforms up front + typedef std::vector<agg::trans_affine> transforms_t; + transforms_t transforms; + transforms.reserve(Ntransforms); + for (i = 0; i < Ntransforms; ++i) { + agg::trans_affine trans = py_to_agg_transformation_matrix + (transforms_obj[i], false); + trans *= master_transform; + transforms.push_back(trans); + } + + Py::List result; + + for (i = 0; i < N; ++i) { + PathIterator path(paths[i % Npaths]); + + Py::SeqBase<Py::Float> offset = Py::SeqBase<Py::Float>(offsets[i % Noffsets]); + double xo = Py::Float(offset[0]); + double yo = Py::Float(offset[1]); + offset_trans.transform(&xo, &yo); + agg::trans_affine_translation transOffset(xo, yo); + agg::trans_affine trans = transforms[i % Ntransforms] * transOffset; + + const Py::Object& facecolor_obj = facecolors[i & Ncolors]; + if (facecolor_obj.ptr() == Py_None) { + if (::point_on_path(x, y, radius, path, trans)) + result.append(Py::Int((int)i)); + } else { + if (::point_in_path(x, y, path, trans)) + result.append(Py::Int((int)i)); + } + } + + return result; +} + /* ------------ module methods ------------- */ Py::Object _backend_agg_module::new_renderer (const Py::Tuple &args, const Py::Dict &kws) @@ -1563,7 +1708,7 @@ add_varargs_method("draw_path", &RendererAgg::draw_path, "draw_path(gc, path, transform, rgbFace)\n"); add_varargs_method("draw_path_collection", &RendererAgg::draw_path_collection, - "draw_path_collection(master_transform, cliprect, clippath, clippath_trans, paths, transforms, facecolors, edgecolors, linewidths, linestyles, antialiaseds)\n"); + "draw_path_collection(master_transform, cliprect, clippath, clippath_trans, paths, transforms, offsets, offsetTrans, facecolors, edgecolors, linewidths, linestyles, antialiaseds)\n"); add_varargs_method("draw_markers", &RendererAgg::draw_markers, "draw_markers(gc, marker_path, marker_trans, path, rgbFace)\n"); add_varargs_method("draw_text_image", &RendererAgg::draw_text_image, Modified: branches/transforms/src/_backend_agg.h =================================================================== --- branches/transforms/src/_backend_agg.h 2007-10-08 12:45:23 UTC (rev 3927) +++ branches/transforms/src/_backend_agg.h 2007-10-08 18:10:11 UTC (rev 3928) @@ -244,6 +244,11 @@ "point_on_path(x, y, r, path, trans)"); add_varargs_method("get_path_extents", &_backend_agg_module::get_path_extents, "get_path_extents(path, trans)"); + add_varargs_method("get_path_collection_extents", &_backend_agg_module::get_path_collection_extents, + "get_path_collection_extents(trans, paths, transforms, offsets, offsetTrans)"); + add_varargs_method("point_in_path_collection", &_backend_agg_module::point_in_path_collection, + "point_in_path_collection(x, y, r, trans, paths, transforms, offsets, offsetTrans, colors)"); + initialize( "The agg rendering backend" ); } @@ -255,6 +260,8 @@ Py::Object point_in_path(const Py::Tuple& args); Py::Object point_on_path(const Py::Tuple& args); Py::Object get_path_extents(const Py::Tuple& args); + Py::Object get_path_collection_extents(const Py::Tuple& args); + Py::Object point_in_path_collection(const Py::Tuple& args); }; This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. ------------------------------------------------------------------------- This SF.net email is sponsored by: Splunk Inc. Still grepping through log files to find problems? Stop. Now Search log events and configuration files using AJAX and a browser. Download your FREE copy of Splunk now >> http://get.splunk.com/ _______________________________________________ Matplotlib-checkins mailing list Matplotlib-checkins@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins