From: Manuel M. <mm...@as...> - 2006-10-10 09:52:10
|
Hi, I just submitted a patch to sourceforge and also attached it to this email: The applied patch modifies the files axes.py and collections.py. I added a class StaredRegularPolyCollection() to collections.py to create star-like regular Polygons. These symbols can be used with the scatter() function. I modified the scatter function somewhat, but keeping the API compatible. It is now easily possible to create a greater variety of symbols: - scatter(x,y,marker="[marker caracter]") behaves as before - scatter (x,y,marker=None, verts=[list of verts]) behaves as before - [NEW] scatter(x,y,marker=(N,0,[angle])) produces a scatter plot with a N-sided regular solid polygone - [NEW] scatter(x,y,marker=(N,1,[angle])) produces a scatter plot with a N-sided star-like symbol (using the class StaredRegularPolyCollection) - [NEW] scatter(x,y,marker=(verts,0)) produces a scatter plot like scatter(x,y,marker=None,verts=verts) So, marker can now be: - a single character as before - a tuple (or list): (numsides, style, [optional angle]) - with style=[int] = 0: solid polygone = 1: stared polygone (define this as constants somewhere ?) - (verts, style); actually style is ignored for this option, but see below This approach allows for easy future extension, e.g. a style 2(=lines) may be added which allows for none-closed symbols which can be very useful to create "S"-shaped symbols. Another possibility would be to interpred marker=(0,0) to plot only single points instead of Polygons. This can be useful when plotting a large number of data-points, e.g. stars in an N-Body simulation of a galaxy collision. I also attached an example file, scatter_example.py, and its output, scatter_example.png. |
From: John H. <jdh...@ac...> - 2006-10-10 14:58:33
|
>>>>> "Manuel" == Manuel Metz <mm...@as...> writes: Manuel> Hi, I just submitted a patch to sourceforge and also Manuel> attached it to this email: Manuel> The applied patch modifies the files axes.py and Manuel> collections.py. Manuel> I added a class StaredRegularPolyCollection() to Manuel> collections.py to create star-like regular Polygons. This looks very useful -- thanks for the patch. To questions: What does the name "StaredRegularPolygon" mean? I am having trouble figuring out the Stared part. It would be nice to have this functionality in the line markers as well. Any interest in trying to port some of these changes to the matplotlib.lines? JDH |
From: Manuel M. <mm...@as...> - 2006-10-11 07:19:50
Attachments:
custom_symbol.a.patch
|
John Hunter wrote: >>>>>> "Manuel" == Manuel Metz <mm...@as...> writes: > > Manuel> Hi, I just submitted a patch to sourceforge and also > Manuel> attached it to this email: > > Manuel> The applied patch modifies the files axes.py and > Manuel> collections.py. > > Manuel> I added a class StaredRegularPolyCollection() to > Manuel> collections.py to create star-like regular Polygons. > > This looks very useful -- thanks for the patch. To questions: > > What does the name "StaredRegularPolygon" mean? I am having trouble > figuring out the Stared part. Argh - okay - this is a mistranslation from german to english - sorry. I wanted to say "starlike". So probably StarlikeRegularPolygon is a better name... If changed this and attach an updated patch (also updated on sourceforge). Btw.: there is also a minor change concerning the rescaling of custom verts. For the rescaling, it is assumed that the vertices are centred on the coordinate centre, and rescaling is done such that the largest distance from the centre is scaled to 1. > It would be nice to have this functionality in the line markers as > well. Any interest in trying to port some of these changes to the > matplotlib.lines? I will have a look into this. Manuel |
From: John H. <jdh...@ac...> - 2006-10-11 13:51:09
|
>>>>> "Manuel" == Manuel Metz <mm...@as...> writes: Manuel> John Hunter wrote: >>>>>>> "Manuel" == Manuel Metz <mm...@as...> writes: >> Manuel> Hi, I just submitted a patch to sourceforge and also Manuel> attached it to this email: >> Manuel> The applied patch modifies the files axes.py and Manuel> collections.py. >> Manuel> I added a class StaredRegularPolyCollection() to Manuel> collections.py to create star-like regular Polygons. >> This looks very useful -- thanks for the patch. To questions: >> >> What does the name "StaredRegularPolygon" mean? I am having >> trouble figuring out the Stared part. Manuel> Argh - okay - this is a mistranslation from german to Manuel> english - sorry. I wanted to say "starlike". So probably Manuel> StarlikeRegularPolygon is a better name... OK, I see. Perhaps we should just call it a StarPolygonCollection http://en.wikipedia.org/wiki/Star_polygon Also, in your patch, unless I am missing something, it looks like you could simply do something like scale = 0.5/math.sqrt(math.pi) r = scale*ones(self.numsides*2) rather than + r = 1.0/math.sqrt(math.pi) # unit area + r = asarray( [r]*(self.numsides*2) ) + for i in xrange(1,len(r),2): + r[i] *= 0.5 Ie, do everything in numerix, rather than in python. When you get all of this incorporated, if you could send one patch against svn that includes all of the changes I'll check it in (if noone else has any corrections or comments). Thanks again, JDH |
From: Manuel M. <mm...@as...> - 2006-10-12 07:24:41
Attachments:
custom_symbol.b.patch
|
John Hunter wrote: >>>>>> "Manuel" == Manuel Metz <mm...@as...> writes: > > Manuel> Argh - okay - this is a mistranslation from german to > Manuel> english - sorry. I wanted to say "starlike". So probably > Manuel> StarlikeRegularPolygon is a better name... > > OK, I see. Perhaps we should just call it a StarPolygonCollection > http://en.wikipedia.org/wiki/Star_polygon I've done that. > Also, in your patch, unless I am missing something, it looks like you > could simply do something like > > scale = 0.5/math.sqrt(math.pi) > r = scale*ones(self.numsides*2) > > rather than > > + r = 1.0/math.sqrt(math.pi) # unit area > + r = asarray( [r]*(self.numsides*2) ) > + for i in xrange(1,len(r),2): > + r[i] *= 0.5 > > Ie, do everything in numerix, rather than in python. There is a subtle but essential difference ;-) : for i in xrange(1,len(r), 2 ) ^^^ , i.e. every second value gets rescaled. But there is probably a more "pythonic" way to do that: r = 1.0/math.sqrt(math.pi) # unit area r = asarray( [r,0.5*r]*self.numsides ) I'm not aware of a better way to do this with numerix :-( The patch against the latest svn revision (2810) is attached. Manuel > When you get all of this incorporated, if you could send one patch > against svn that includes all of the changes I'll check it in (if > noone else has any corrections or comments). > > Thanks again, > JDH |
From: John H. <jdh...@ac...> - 2006-10-12 14:03:14
|
>>>>> "Manuel" == Manuel Metz <mm...@as...> writes: Manuel> There is a subtle but essential difference ;-) : for i in Manuel> xrange(1,len(r), 2 ) ^^^ , i.e. every second value gets Manuel> rescaled. But there is probably a more "pythonic" way to Manuel> do that: Manuel> r = 1.0/math.sqrt(math.pi) # unit area r = asarray( Manuel> [r,0.5*r]*self.numsides ) Manuel> I'm not aware of a better way to do this with numerix :-( Oops, sorry I missed that. I think what you want is then scale = 0.5/math.sqrt(math.pi) r = scale*ones(self.numsides*2) r[1::2] *= 0.5 Manuel> The patch against the latest svn revision (2810) is Manuel> attached. OK, if I could make a few more suggestions (I feel like a customer at a restaurant where every time the waiter brings me a cup of coffee I ask "just one more thing"...) + old_ymin,old_ymax = self.get_ylim() The matplotlib style guidelines are UpperCase : classes lower_underscore : functions and methods lower or lowerUpper : variables or attributes For shortish variable names, I prefer oldymin, oldymax = self.get_ylim() + if isinstance(marker, str) or isinstance(marker, unicode): + # the standard way to define symbols using a string character + sym = syms.get(marker) + if isinstance(marker, tuple) or isinstance(marker, list): + # accept marker to be: + # (numsides, style, [angle]) + if isinstance(marker[0], int) or isinstance(marker[0], long): + # (numsides, style, [angle]) Here you should use "duck typing" not "type checking" (google "duck typing"). matplotlib.cbook provides several duck typing functions, eg is_stringlike, iterable and is_numlike. Take a look at is_numlike def is_numlike(obj): try: obj+1 except TypeError: return False else: return True Ie, if it acts like a number (you can add one to it) then we'll treat it as a number. This allows users to provide other integer like classes which are not ints or longs. Everytime you use isinstance, take a 2nd look. There may be a better way. I'll await your updated patch :-) JDH |
From: Manuel M. <mm...@as...> - 2006-10-12 16:18:15
Attachments:
custom_symbol.c.patch
|
John Hunter wrote: >>>>>> "Manuel" == Manuel Metz <mm...@as...> writes: > > Manuel> There is a subtle but essential difference ;-) : for i in > Manuel> xrange(1,len(r), 2 ) ^^^ , i.e. every second value gets > Manuel> rescaled. But there is probably a more "pythonic" way to > Manuel> do that: > > Manuel> r = 1.0/math.sqrt(math.pi) # unit area r = asarray( > Manuel> [r,0.5*r]*self.numsides ) > > Manuel> I'm not aware of a better way to do this with numerix :-( > > Oops, sorry I missed that. I think what you want is then > > scale = 0.5/math.sqrt(math.pi) > r = scale*ones(self.numsides*2) > r[1::2] *= 0.5 > I've fixed that - and I've learned something ! > > OK, if I could make a few more suggestions (I feel like a customer at > a restaurant where every time the waiter brings me a cup of coffee I > ask "just one more thing"...) > > + old_ymin,old_ymax = self.get_ylim() > > The matplotlib style guidelines are > > UpperCase : classes > lower_underscore : functions and methods > lower or lowerUpper : variables or attributes > > For shortish variable names, I prefer > > oldymin, oldymax = self.get_ylim() Ah - there are three lines that I touched for only one reason: the line indention was done with a "tab" instead of "spaces". When I recognised this in my text editor, I changed it to space-indention. That's the only reason for these patched lines. > + if isinstance(marker, str) or isinstance(marker, unicode): > + # the standard way to define symbols using a string character > + sym = syms.get(marker) > > + if isinstance(marker, tuple) or isinstance(marker, list): > + # accept marker to be: > + # (numsides, style, [angle]) > > + if isinstance(marker[0], int) or isinstance(marker[0], long): > + # (numsides, style, [angle]) > > Here you should use "duck typing" not "type checking" (google "duck > typing"). matplotlib.cbook provides several duck typing functions, eg > is_stringlike, iterable and is_numlike. Take a look at is_numlike > > def is_numlike(obj): > try: obj+1 > except TypeError: return False > else: return True > > Ie, if it acts like a number (you can add one to it) then we'll treat > it as a number. This allows users to provide other integer like > classes which are not ints or longs. Everytime you use isinstance, > take a 2nd look. There may be a better way. > > I'll await your updated patch :-) I've fixed that too - and learned even more ;-) Thanks ! Patch against latest revision is attached. Manuel |
From: Manuel M. <mm...@as...> - 2006-10-12 16:22:38
Attachments:
custom_symbol.c.patch
|
John Hunter wrote: >>>>>> >>>>>> "Manuel" == Manuel Metz <mm...@as...> writes: > > > > Manuel> There is a subtle but essential difference ;-) : for i in > > Manuel> xrange(1,len(r), 2 ) ^^^ , i.e. every second value gets > > Manuel> rescaled. But there is probably a more "pythonic" way to > > Manuel> do that: > > > > Manuel> r = 1.0/math.sqrt(math.pi) # unit area r = asarray( > > Manuel> [r,0.5*r]*self.numsides ) > > > > Manuel> I'm not aware of a better way to do this with numerix :-( > > > > Oops, sorry I missed that. I think what you want is then > > > > scale = 0.5/math.sqrt(math.pi) > > r = scale*ones(self.numsides*2) > > r[1::2] *= 0.5 > > I've fixed that - and I've learned something ! > > > > OK, if I could make a few more suggestions (I feel like a customer at > > a restaurant where every time the waiter brings me a cup of coffee I > > ask "just one more thing"...) > > > > + old_ymin,old_ymax = self.get_ylim() > > > > The matplotlib style guidelines are > > > > UpperCase : classes > > lower_underscore : functions and methods > > lower or lowerUpper : variables or attributes > > > > For shortish variable names, I prefer > > > > oldymin, oldymax = self.get_ylim() Ah - there are three lines that I touched for only one reason: the line indention was done with a "tab" instead of "spaces". When I recognised this in my text editor, I changed it to space-indention. That's the only reason for these patched lines. > > + if isinstance(marker, str) or isinstance(marker, unicode): > > + # the standard way to define symbols using a string character > > + sym = syms.get(marker) > > > > + if isinstance(marker, tuple) or isinstance(marker, list): > > + # accept marker to be: > > + # (numsides, style, [angle]) > > > > + if isinstance(marker[0], int) or isinstance(marker[0], long): > > + # (numsides, style, [angle]) > > > > Here you should use "duck typing" not "type checking" (google "duck > > typing"). matplotlib.cbook provides several duck typing functions, eg > > is_stringlike, iterable and is_numlike. Take a look at is_numlike > > > > def is_numlike(obj): > > try: obj+1 > > except TypeError: return False > > else: return True > > > > Ie, if it acts like a number (you can add one to it) then we'll treat > > it as a number. This allows users to provide other integer like > > classes which are not ints or longs. Everytime you use isinstance, > > take a 2nd look. There may be a better way. > > > > I'll await your updated patch :-) I've fixed that too - and learned even more ;-) Thanks ! Patch against latest revision is attached. Manuel |
From: Manuel M. <mm...@as...> - 2006-10-13 07:16:09
Attachments:
axes.patch
|
Hallo, sorry to bother you again. I recognized that I introduced an error in my last patch :-( sym = None starlike = False # to be API compatible if sym is None and not (verts is None): ^^^^^^^^^^^ This, of cause, makes no sense. The correct line reads: if marker is None and not (verts is None): ^^^^^^^^^^^^^^ I've attached a patch... I apologize again ... Manuel Manuel Metz wrote: > John Hunter wrote: >>>>>>>>>>>>> "Manuel" == Manuel Metz <mm...@as...> writes: >>> Manuel> There is a subtle but essential difference ;-) : for i in >>> Manuel> xrange(1,len(r), 2 ) ^^^ , i.e. every second value gets >>> Manuel> rescaled. But there is probably a more "pythonic" way to >>> Manuel> do that: >>> >>> Manuel> r = 1.0/math.sqrt(math.pi) # unit area r = asarray( >>> Manuel> [r,0.5*r]*self.numsides ) >>> >>> Manuel> I'm not aware of a better way to do this with numerix :-( >>> >>> Oops, sorry I missed that. I think what you want is then >>> >>> scale = 0.5/math.sqrt(math.pi) >>> r = scale*ones(self.numsides*2) >>> r[1::2] *= 0.5 >>> > > I've fixed that - and I've learned something ! > >>> OK, if I could make a few more suggestions (I feel like a customer at >>> a restaurant where every time the waiter brings me a cup of coffee I >>> ask "just one more thing"...) >>> >>> + old_ymin,old_ymax = self.get_ylim() >>> >>> The matplotlib style guidelines are >>> >>> UpperCase : classes >>> lower_underscore : functions and methods >>> lower or lowerUpper : variables or attributes >>> >>> For shortish variable names, I prefer >>> >>> oldymin, oldymax = self.get_ylim() > > Ah - there are three lines that I touched for only one reason: the line > indention was done with a "tab" instead of "spaces". When I recognised > this in my text editor, I changed it to space-indention. That's the only > reason for these patched lines. > >>> + if isinstance(marker, str) or isinstance(marker, unicode): >>> + # the standard way to define symbols using a string > character >>> + sym = syms.get(marker) >>> >>> + if isinstance(marker, tuple) or isinstance(marker, list): >>> + # accept marker to be: >>> + # (numsides, style, [angle]) >>> >>> + if isinstance(marker[0], int) or isinstance(marker[0], > long): >>> + # (numsides, style, [angle]) >>> >>> Here you should use "duck typing" not "type checking" (google "duck >>> typing"). matplotlib.cbook provides several duck typing functions, eg >>> is_stringlike, iterable and is_numlike. Take a look at is_numlike >>> >>> def is_numlike(obj): >>> try: obj+1 >>> except TypeError: return False >>> else: return True >>> >>> Ie, if it acts like a number (you can add one to it) then we'll treat >>> it as a number. This allows users to provide other integer like >>> classes which are not ints or longs. Everytime you use isinstance, >>> take a 2nd look. There may be a better way. >>> >>> I'll await your updated patch :-) > > I've fixed that too - and learned even more ;-) Thanks ! > > Patch against latest revision is attached. > > Manuel > > > > ------------------------------------------------------------------------ > > Index: axes.py > =================================================================== > --- axes.py (revision 2811) > +++ axes.py (working copy) > @@ -14,8 +14,9 @@ > from artist import Artist, setp > from axis import XAxis, YAxis > from cbook import iterable, is_string_like, flatten, enumerate, \ > - allequal, dict_delall, popd, popall, silent_list > -from collections import RegularPolyCollection, PolyCollection, LineCollection, QuadMesh > + allequal, dict_delall, popd, popall, silent_list, is_numlike > +from collections import RegularPolyCollection, PolyCollection, LineCollection, QuadMesh, \ > + StarPolygonCollection > from colors import colorConverter, normalize, Colormap, \ > LinearSegmentedColormap, looks_like_color, is_color_like > import cm > @@ -1211,7 +1212,7 @@ > if xmax is None and hasattr(xmin,'__len__'): > xmin,xmax = xmin > > - old_xmin,old_xmax = self.get_xlim() > + old_xmin,old_xmax = self.get_xlim() > if xmin is None: xmin = old_xmin > if xmax is None: xmax = old_xmax > > @@ -1223,7 +1224,7 @@ > xmin -= 1e-38 > xmax += 1e-38 > > - self.viewLim.intervalx().set_bounds(xmin, xmax) > + self.viewLim.intervalx().set_bounds(xmin, xmax) > if emit: self._send_xlim_event() > return xmin, xmax > > @@ -1324,7 +1325,7 @@ > if ymax is None and hasattr(ymin,'__len__'): > ymin,ymax = ymin > > - old_ymin,old_ymax = self.get_ylim() > + old_ymin,old_ymax = self.get_ylim() > if ymin is None: ymin = old_ymin > if ymax is None: ymax = old_ymax > > @@ -3100,10 +3101,9 @@ > 'h' : hexagon > '8' : octagon > > + If marker is None and verts is not None, verts is a sequence > + of (x,y) vertices for a custom scatter symbol. > > - if marker is None and verts is not None, verts is a sequence > - of (x,y) vertices for a custom scatter symbol. The > - > s is a size argument in points squared. > > Any or all of x, y, s, and c may be masked arrays, in which > @@ -3171,26 +3171,74 @@ > if faceted: edgecolors = None > else: edgecolors = 'None' > > - sym = syms.get(marker) > - if sym is None and verts is None: > - raise ValueError('Unknown marker symbol to scatter') > - > + sym = None > + starlike = False > + > + # to be API compatible > + if sym is None and not (verts is None): > + marker = (verts, 0) > + verts = None > + > + if is_string_like(marker): > + # the standard way to define symbols using a string character > + sym = syms.get(marker) > + if sym is None and verts is None: > + raise ValueError('Unknown marker symbol to scatter') > + numsides, rotation = syms[marker] > + > + elif iterable(marker): > + # accept marker to be: > + # (numsides, style, [angle]) > + # or > + # (verts[], style, [angle]) > + > + if len(marker)<2 or len(marker)>3: > + raise ValueError('Cannot create markersymbol from marker') > + > + if is_numlike(marker[0]): > + # (numsides, style, [angle]) > + > + if len(marker)==2: > + numsides, rotation = marker[0], math.pi/4. > + elif len(marker)==3: > + numsides, rotation = marker[0], marker[2] > + sym = True > + > + if marker[1]==1: > + # starlike symbol, everthing else is interpreted as solid symbol > + starlike = True > + > + else: > + verts = asarray(marker[0]) > + > if sym is not None: > - numsides, rotation = syms[marker] > - collection = RegularPolyCollection( > - self.figure.dpi, > - numsides, rotation, scales, > - facecolors = colors, > - edgecolors = edgecolors, > - linewidths = linewidths, > - offsets = zip(x,y), > - transOffset = self.transData, > - ) > + if not starlike: > + collection = RegularPolyCollection( > + self.figure.dpi, > + numsides, rotation, scales, > + facecolors = colors, > + edgecolors = edgecolors, > + linewidths = linewidths, > + offsets = zip(x,y), > + transOffset = self.transData, > + ) > + else: > + collection = StarPolygonCollection( > + self.figure.dpi, > + numsides, rotation, scales, > + facecolors = colors, > + edgecolors = edgecolors, > + linewidths = linewidths, > + offsets = zip(x,y), > + transOffset = self.transData, > + ) > else: > - verts = asarray(verts) > - # hmm, the scaling is whacked -- how do we want to scale with custom verts? > + # rescale verts > + rescale = sqrt(max(verts[:,0]**2+verts[:,1]**2)) > + verts /= rescale > + > scales = asarray(scales) > - #scales = sqrt(scales * self.figure.dpi.get() / 72.) > + scales = sqrt(scales * self.figure.dpi.get() / 72.) > if len(scales)==1: > verts = [scales[0]*verts] > else: > Index: collections.py > =================================================================== > --- collections.py (revision 2811) > +++ collections.py (working copy) > @@ -15,7 +15,7 @@ > from cbook import is_string_like, iterable > from colors import colorConverter > from cm import ScalarMappable > -from numerix import arange, sin, cos, pi, asarray, sqrt, array, newaxis > +from numerix import arange, sin, cos, pi, asarray, sqrt, array, newaxis, ones > from transforms import identity_transform > > class Collection(Artist): > @@ -293,7 +293,7 @@ > * dpi is the figure dpi instance, and is required to do the > area scaling. > > - * numsides: the number of sides of the polygon > + * numsides: the number of sides of the polygon > > * sizes gives the area of the circle circumscribing the > regular polygon in points^2 > @@ -374,6 +374,43 @@ > raise NotImplementedError('Vertices in data coordinates are calculated\n' > + 'only with offsets and only if _transOffset == dataTrans.') > > +class StarPolygonCollection(RegularPolyCollection): > + def __init__(self, > + dpi, > + numsides, > + rotation = 0 , > + sizes = (1,), > + **kwargs): > + """ > + Draw a regular star like Polygone with numsides. > + > + * dpi is the figure dpi instance, and is required to do the > + area scaling. > + > + * numsides: the number of sides of the polygon > + > + * sizes gives the area of the circle circumscribing the > + regular polygon in points^2 > + > + * rotation is the rotation of the polygon in radians > + > + kwargs: See PatchCollection for more details > + > + * offsets are a sequence of x,y tuples that give the centers of > + the polygon in data coordinates > + > + * transOffset is the Transformation instance used to > + transform the centers onto the canvas. > + """ > + RegularPolyCollection.__init__(self, dpi, numsides, rotation, sizes, **kwargs) > + > + def _update_verts(self): > + scale = 1.0/math.sqrt(math.pi) > + r = scale*ones(self.numsides*2) > + r[1::2] *= 0.5 > + theta = (2.*math.pi/(2*self.numsides))*arange(2*self.numsides) + self.rotation > + self._verts = zip( r*sin(theta), r*cos(theta) ) > + > class LineCollection(Collection, ScalarMappable): > """ > All parameters must be sequences. The property of the ith line > > > ------------------------------------------------------------------------ > > ------------------------------------------------------------------------- > Using Tomcat but need to do more? Need to support web services, security? > Get stuff done quickly with pre-integrated technology to make your job easier > Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo > http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642 > > > ------------------------------------------------------------------------ > > _______________________________________________ > Matplotlib-devel mailing list > Mat...@li... > https://lists.sourceforge.net/lists/listinfo/matplotlib-devel -- --------------------------------------- Manuel Metz ............ Stw@AIfA Argelander Institut fuer Astronomie Auf dem Huegel 71 (room 3.06) D - 53121 Bonn E-Mail: mm...@as... Web: www.astro.uni-bonn.de/~mmetz Phone: (+49) 228 / 73-3660 Fax: (+49) 228 / 73-3672 --------------------------------------- |
From: John H. <jdh...@ac...> - 2006-10-13 13:47:32
|
>>>>> "Manuel" == Manuel Metz <mm...@as...> writes: Manuel> I've attached a patch... I apologize again ... No problem - -I just committed this change. JDH |