From: <jd...@us...> - 2007-07-22 16:53:43
|
Revision: 3604 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3604&view=rev Author: jdh2358 Date: 2007-07-22 09:53:37 -0700 (Sun, 22 Jul 2007) Log Message: ----------- added (broken) axis support Modified Paths: -------------- trunk/matplotlib/mpl1/mpl1.py Modified: trunk/matplotlib/mpl1/mpl1.py =================================================================== --- trunk/matplotlib/mpl1/mpl1.py 2007-07-21 23:30:03 UTC (rev 3603) +++ trunk/matplotlib/mpl1/mpl1.py 2007-07-22 16:53:37 UTC (rev 3604) @@ -8,17 +8,18 @@ wget http://peak.telecommunity.com/dist/ez_setup.py sudo python sez_setup.py - sudo easy_install -f http://code.enthought.com/enstaller/eggs/source/unstable "enthought.etsconfig < 3.0a" "enthought.util <3.0a" "enthought.debug <3.0a" - svn co https://svn.enthought.com/svn/enthought/branches/enthought.traits_2.0 enthought_traits - - cd enthought_traits/ - sudo python setup.py install - + sudo rm -rf /usr/local/lib/python2.5/site-packages/enthought* + sudo easy_install \ + -f http://code.enthought.com/enstaller/eggs/source/unstable \ + "enthought.resource <3.0a" "enthought.traits < 3.0a" + """ # see install instructions for enthrought traits2 in mtraits import enthought.traits.api as traits +from enthought.traits.api import HasTraits, Instance, Trait, Float, Int, \ + Array, Tuple from matplotlib import agg from matplotlib import colors as mcolors @@ -27,9 +28,10 @@ is_string_like = cbook.is_string_like +## begin core infrastructure -class Affine(traits.HasTraits): +class Affine(HasTraits): """ An affine 3x3 matrix that supports matrix multiplication with other Affine instances or numpy arrays. @@ -62,14 +64,14 @@ simply views into the data matrix, and are updated by reference """ # connect to the data_modified event if you want a callback - data = traits.Array('d', (3,3)) + data = Array('d', (3,3)) - translate = traits.Property(traits.Array('d', (2,))) - scale = traits.Property(traits.Array('d', (2,))) - vec6 = traits.Property(traits.Array('d', (6,))) + translate = traits.Property(Array('d', (2,))) + scale = traits.Property(Array('d', (2,))) + vec6 = traits.Property(Array('d', (6,))) - xlim = traits.Property(traits.Array('d', (2,))) - ylim = traits.Property(traits.Array('d', (2,))) + xlim = traits.Property(Array('d', (2,))) + ylim = traits.Property(Array('d', (2,))) #data_modified = traits.Event @@ -242,18 +244,18 @@ #return 'AFFINE:\n%s'%self.data -class Box(traits.HasTraits): +class Box(HasTraits): # left, bottom, width, height - bounds = traits.List - left = traits.Property(traits.Float) - bottom = traits.Property(traits.Float) - width = traits.Property(traits.Float) - height = traits.Property(traits.Float) + bounds = traits.Array('d', (4,)) + left = traits.Property(Float) + bottom = traits.Property(Float) + width = traits.Property(Float) + height = traits.Property(Float) - right = traits.Property(traits.Float) # read only - top = traits.Property(traits.Float) # read only + right = traits.Property(Float) # read only + top = traits.Property(Float) # read only - def ___bounds_default(self): + def _bounds_default(self): return [0.0, 0.0, 1.0, 1.0] def _get_left(self): @@ -295,7 +297,7 @@ return self.bottom + self.height def _bounds_changed(self, old, new): - print 'base bounds changed' + pass class ColorHandler(traits.TraitHandler): """ @@ -307,12 +309,12 @@ Eg class C(HasTraits): - fillcolor = traits.Trait('black', ColorHandler()) + facecolor = Trait('black', ColorHandler()) c = C() - c.fillcolor = 'red' - print c.fillcolor # prints red - print c.fillcolor_ # print (1,0,0,1) + c.facecolor = 'red' + print c.facecolor # prints red + print c.facecolor_ # print (1,0,0,1) """ is_mapped = True @@ -338,40 +340,98 @@ name like 'orange', a hex color like '#efefef', a grayscale intensity like '0.5', or an RGBA tuple (1,0,0,1)""" + + + + class MTraitsNamespace: - DPI = traits.Float(72.) + DPI = Float(72.) Alpha = traits.Range(0., 1., 0.) - Affine = traits.Trait(Affine()) + Affine = Trait(Affine()) AntiAliased = traits.true - Codes = traits.Array('b', value=npy.array([0,0], dtype=npy.uint8)) - Color = traits.Trait('black', ColorHandler()) - DPI = traits.Float(72.) - Interval = traits.Array('d', (2,), npy.array([0.0, 1.0], npy.float_)) - LineStyle = traits.Trait('-', '--', '-.', ':', 'steps', None) - LineWidth = traits.Float(1.0) - Marker = traits.Trait(None, '.', ',', 'o', '^', 'v', '<', '>', 's', + Color = Trait('black', ColorHandler()) + DPI = Float(72.) + Interval = Array('d', (2,), npy.array([0.0, 1.0], npy.float_)) + LineStyle = Trait('-', '--', '-.', ':', 'steps', None) + LineWidth = Float(1.0) + Marker = Trait(None, '.', ',', 'o', '^', 'v', '<', '>', 's', '+', 'x', 'd', 'D', '|', '_', 'h', 'H', 'p', '1', '2', '3', '4') - MarkerSize = traits.Float(6) - Verts = traits.Array('d', value=npy.array([[0,0],[0,0]], npy.float_)) - PathData = traits.Tuple(Codes(), Verts()) + MarkerSize = Float(6) Visible = traits.true mtraits = MTraitsNamespace() -class Renderer(traits.HasTraits): - dpi = mtraits.DPI - size = traits.Tuple(traits.Int(600), traits.Int(400)) +def Alias(name): + return Property(lambda obj: getattr(obj, name), + lambda obj, val: setattr(obj, name, val)) - adisplay = traits.Instance(Affine, ()) - def __init__(self, size=(600,400)): +class IDGenerator: + def __init__(self): + self._id = 0 - self.pathd = dict() # path id -> Path instance - self.markersd = dict() # path id -> Markers instance + def __call__(self): + _id = self._id + self._id += 1 + return _id + + + +## begin backend API +MOVETO, LINETO, CLOSEPOLY = range(3) +class PathPrimitive(HasTraits): + """ + The path is an object that talks to the backends, and is an + intermediary between the high level path artists like Line and + Polygon, and the backend renderer + """ + + + color = mtraits.Color('black') + facecolor = mtraits.Color('blue') + alpha = mtraits.Alpha(1.0) + linewidth = mtraits.LineWidth(1.0) + antialiased = mtraits.AntiAliased + pathdata =Tuple(Array('b'), Array('d')) + affine = Instance(Affine, ()) + + def _pathdata_default(self): + return (npy.array([0,0], dtype=npy.uint8), + npy.array([[0,0],[0,0]], npy.float_)) + + def _pathdata_changed(self, old, new): + codes, xy = new + + if len(xy.shape)!=2: + raise ValueError('xy in path data must be Nx2') + Ncodes = len(codes) + Nxy = xy.shape[0] + if Ncodes!=Nxy: + raise ValueError('codes and xy must have equal rows') + +class MarkerPrimitive(HasTraits): + locs = Array('d') + path = Instance(PathPrimitive, ()) # marker path in points + affine = Instance(Affine, ()) # transformation for the verts + + def _locs_default(self): + return npy.array([[0,0],[0,0]], npy.float_) + +class Renderer(HasTraits): + dpi = mtraits.DPI + size = traits.Tuple(Int(600), Int(400)) + + adisplay = Instance(Affine, ()) + pathd = traits.Dict(Int, PathPrimitive) + markerd = traits.Dict(Int, MarkerPrimitive) + + def __init__(self, size=(600,400)): + self.pathd = dict() + self.markerd = dict() self._size_changed(None, size) def _size_changed(self, old, new): @@ -383,37 +443,83 @@ - def add_path(self, pathid, path): - # todo, we could use a traits dict here - if not isinstance(path, Path): - raise TypeError('add_path takes an ID and a Path instance') - self.pathd[pathid] = path + def render_path(self, pathid): + pass + - def remove_path(self, pathid): - if pathid in self.pathd: - del self.pathd[pathid] + def new_path_primitive(self): + """ + return a PathPrimitive (or derived); these instances will be + added and removed later through add_path and remove path + """ + return PathPrimitive() - def add_markers(self, markersid, markers): - # todo, we could use a traits dict here - if not isinstance(markers, Markers): - raise TypeError('add_markers takes an ID and a Markers instance') - self.markersd[markersid] = markers + def new_marker_primitive(self): + """ + return a MarkerPrimitive (or derived); these instances will be + added and removed later through add_maker and remove_marker + """ + return MarkerPrimitive() + - def remove_markers(self, markersid): - if markersid in self.markersd: - del self.markersd[markersid] +## begin backend agg +class PathPrimitiveAgg(PathPrimitive): - def render_path(self, pathid): - pass - + def __init__(self): + self._pathdata_changed(None, self.pathdata) + self._facecolor_changed(None, self.facecolor) + self._color_changed(None, self.color) + + @staticmethod + def make_agg_path(pathdata): + agg_path = agg.path_storage() + codes, xy = pathdata + + Ncodes = len(codes) + + for i in range(Ncodes): + x, y = xy[i] + code = codes[i] + if code==MOVETO: + agg_path.move_to(x, y) + elif code==LINETO: + agg_path.line_to(x, y) + elif code==CLOSEPOLY: + agg_path.close_polygon() + return agg_path + + def _pathdata_changed(self, olddata, newdata): + self.agg_path = PathPrimitiveAgg.make_agg_path(newdata) + + + def _facecolor_changed(self, oldcolor, newcolor): + self.agg_facecolor = self.color_to_rgba8(self.facecolor_) + + def _color_changed(self, oldcolor, newcolor): + #print 'stroke color changed', newcolor + c = self.color_to_rgba8(self.color_) + self.agg_color = c + + def color_to_rgba8(self, color): + if color is None: return None + rgba = [int(255*c) for c in color] + return agg.rgba8(*rgba) + +class MarkerPrimitiveAgg(MarkerPrimitive): + path = Instance(PathPrimitiveAgg, ()) + + + class RendererAgg(Renderer): gray = agg.rgba8(128,128,128,255) white = agg.rgba8(255,255,255,255) blue = agg.rgba8(0,0,255,255) black = agg.rgba8(0,0,0,0) + pathd = traits.Dict(Int, PathPrimitiveAgg) + markerd = traits.Dict(Int, MarkerPrimitiveAgg) def _size_changed(self, old, new): Renderer._size_changed(self, old, new) @@ -440,9 +546,13 @@ self.scanlinebin = agg.scanline_bin() - def add_path(self, pathid, path): - self.pathd[pathid] = AggPath(path) + def new_path_primitive(self): + 'return a PathPrimitive (or derived)' + return PathPrimitiveAgg() + def new_marker_primitive(self): + 'return a MarkerPrimitive (or derived)' + return MarkerPrimitiveAgg() def render_path(self, pathid): @@ -465,23 +575,23 @@ aggaffine = agg.trans_affine(*affine.vec6) transpath = agg.conv_transform_path(path.agg_path, aggaffine) - if path.fillcolor is not None: - #print 'render path', path.fillcolor, path.agg_fillcolor + if path.facecolor is not None: + #print 'render path', path.facecolor, path.agg_facecolor self.rasterizer.add_path(transpath) - renderer.color_rgba8( path.agg_fillcolor ) + renderer.color_rgba8( path.agg_facecolor ) render_scanlines(self.rasterizer, scanline, renderer); - if path.strokecolor is not None: + if path.color is not None: stroke = agg.conv_stroke_transpath(transpath) stroke.width(path.linewidth) self.rasterizer.add_path(stroke) - renderer.color_rgba8( path.agg_strokecolor ) + renderer.color_rgba8( path.agg_color ) render_scanlines(self.rasterizer, scanline, renderer); - def render_markers(self, markerid): - markers = self.markersd[markerid] + def render_marker(self, markerid): + marker = self.markerd[markerid] - path = AggPath(markers.path) + path = marker.path if path.antialiased: renderer = self.renderer @@ -494,14 +604,14 @@ - affineverts = self.adisplay * markers.affine + affinelocs = self.adisplay * marker.affine - Nmarkers = markers.verts.shape[0] + Nmarkers = marker.locs.shape[0] Locs = npy.ones((3, Nmarkers)) - Locs[0] = markers.verts[:,0] - Locs[1] = markers.verts[:,1] + Locs[0] = marker.locs[:,0] + Locs[1] = marker.locs[:,1] - Locs = affineverts * Locs + Locs = affinelocs * Locs dpiscale = self.dpi/72. # for some reason this is broken @@ -509,32 +619,34 @@ # extension code using cached marker rasters as we now do in # _backend_agg - pathcodes, pathverts = markers.path.pathdata - pathx = dpiscale*pathverts[:,0] - pathy = dpiscale*pathverts[:,1] + pathcodes, pathxy = marker.path.pathdata + + pathx = dpiscale*pathxy[:,0] + pathy = dpiscale*pathxy[:,1] + Npath = len(pathcodes) XY = npy.ones((Npath, 2)) + for xv,yv,tmp in Locs.T: XY[:,0] = (pathx + xv).astype(int) + 0.5 XY[:,1] = (pathy + yv).astype(int) + 0.5 - pathdata = pathcodes, XY - aggpath = AggPath.make_agg_path(pathdata) + aggpath = PathPrimitiveAgg.make_agg_path(pathdata) - if path.fillcolor is not None: + if path.facecolor is not None: self.rasterizer.add_path(aggpath) - renderer.color_rgba8( path.agg_fillcolor ) + renderer.color_rgba8( path.agg_facecolor ) render_scanlines(self.rasterizer, scanline, renderer); - if path.strokecolor is not None: + if path.color is not None: stroke = agg.conv_stroke_path(aggpath) stroke.width(path.linewidth) self.rasterizer.add_path(stroke) - renderer.color_rgba8( path.agg_strokecolor ) + renderer.color_rgba8( path.agg_color ) render_scanlines(self.rasterizer, scanline, renderer); def show(self): @@ -552,7 +664,7 @@ pylab.show() -class Func(traits.HasTraits): +class Func(HasTraits): def __call__(self, X): 'transform the numpy array with shape N,2' return X @@ -598,111 +710,15 @@ raise NotImplementedError -mtraits.Model = traits.Instance(Func, ()) +mtraits.Model = Instance(Func, ()) -class Path(traits.HasTraits): - """ - The path is an object that talks to the backends, and is an - intermediary between the high level path artists like Line and - Polygon, and the backend renderer - """ - MOVETO, LINETO, CLOSEPOLY = range(3) - - strokecolor = mtraits.Color('black') - fillcolor = mtraits.Color('blue') - alpha = mtraits.Alpha(1.0) - linewidth = mtraits.LineWidth(1.0) - antialiased = mtraits.AntiAliased - pathdata = mtraits.PathData - affine = traits.Instance(Affine, ()) - def __init__(self): - # this is a quick workaround to deal with the problem that - # traits inited at the class level are shared between - # instances, which is not what I want - self.pathdata = (npy.array([0,0], npy.uint8), # codes - npy.array([[0,0], [0,0]])) # verts - -class AggPath(Path): - def __init__(self, path): - self.strokecolor = path.strokecolor - self.fillcolor = path.fillcolor - self.alpha = path.alpha - self.linewidth = path.linewidth - self.antialiased = path.antialiased - self.pathdata = path.pathdata - self.affine = path.affine - - - path.sync_trait('strokecolor', self, mutual=False) - path.sync_trait('fillcolor', self, mutual=False) - path.sync_trait('alpha', self, mutual=False) - path.sync_trait('linewidth', self, mutual=False) - path.sync_trait('antialiased', self, mutual=False) - path.sync_trait('pathdata', self, mutual=False) - - path.on_trait_change(self.affine.follow, 'vec6') - - - - # hmm, I would have thought these would be called by the attr - # setting above - self._pathdata_changed(None, self.pathdata) - self._fillcolor__changed(None, self.fillcolor_) - self._strokecolor__changed(None, self.strokecolor_) - - - @staticmethod - def make_agg_path(pathdata): - MOVETO, LINETO, CLOSEPOLY = Path.MOVETO, Path.LINETO, Path.CLOSEPOLY - agg_path = agg.path_storage() - codes, verts = pathdata - N = len(codes) - for i in range(N): - x, y = verts[i] - code = codes[i] - if code==MOVETO: - agg_path.move_to(x, y) - elif code==LINETO: - agg_path.line_to(x, y) - elif code==CLOSEPOLY: - agg_path.close_polygon() - return agg_path - - def _pathdata_changed(self, olddata, newdata): - self.agg_path = AggPath.make_agg_path(newdata) - - - def _fillcolor__changed(self, oldcolor, newcolor): - self.agg_fillcolor = self.color_to_rgba8(newcolor) - - def _strokecolor__changed(self, oldcolor, newcolor): - #print 'stroke color changed', newcolor - c = self.color_to_rgba8(newcolor) - self.agg_strokecolor = c - - - def color_to_rgba8(self, color): - if color is None: return None - rgba = [int(255*c) for c in color] - return agg.rgba8(*rgba) - - -mtraits.Path = traits.Instance(Path, ()) -class Markers(traits.HasTraits): - verts = mtraits.Verts # locations to draw the markers at - path = mtraits.Path # marker path in points - affine = traits.Instance(Affine, ()) # transformation for the verts - - - - -mtraits.Markers = traits.Instance(Markers, ()) +## begin Artist layer # coordinates: # # artist model : a possibly nonlinear transformation (Func instance) @@ -722,39 +738,20 @@ # 0 is left, top, which is the typical coordinate system of most # graphics formats - - - - -def Alias(name): - return Property(lambda obj: getattr(obj, name), - lambda obj, val: setattr(obj, name, val)) - - -class IDGenerator: - def __init__(self): - self._id = 0 - - def __call__(self): - _id = self._id - self._id += 1 - return _id - - primitiveID = IDGenerator() artistID = IDGenerator() -class Artist(traits.HasTraits): - zorder = traits.Float(1.0) + +class Artist(HasTraits): + zorder = Float(1.0) alpha = mtraits.Alpha() visible = mtraits.Visible() - adata = traits.Instance(Affine, ()) # the data affine - aview = traits.Instance(Affine, ()) # the view affine - - affine = traits.Instance(Affine, ()) # the product of the data and view affine + adata = Instance(Affine, ()) # the data affine + aview = Instance(Affine, ()) # the view affine + affine = Instance(Affine, ()) # the product of the data and view affine - renderer = traits.Trait(None, Renderer) + renderer = Trait(None, Renderer) # every artist defines a string which is the name of the attr that # containers should put it into when added. Eg, an Axes is an @@ -785,7 +782,7 @@ class ArtistContainer(Artist): - artistd = traits.Dict(traits.Int, Artist) + artistd = traits.Dict(Int, Artist) sequence = 'containers' def __init__(self): Artist.__init__(self) @@ -842,182 +839,280 @@ #print 'artist draw', self, artist, zorder artist.draw() -class Line(Artist): - linestyle = mtraits.LineStyle('-') - antialiased = mtraits.AntiAliased() - color = mtraits.Color('blue') - linewidth = mtraits.LineWidth(1.0) - marker = mtraits.Marker(None) - markerfacecolor = mtraits.Color('blue') - markeredgecolor = mtraits.Color('black') - markeredgewidth = mtraits.LineWidth(0.5) - markersize = mtraits.MarkerSize(6.0) - path = mtraits.Path - markers = mtraits.Markers - X = mtraits.Verts - model = mtraits.Model - zorder = traits.Float(2.0) - sequence = 'lines' +class Path(Artist): + """ + An interface class between the higher level artists and the path + primitive that needs to talk to the renderers + """ + _path = traits.Instance(PathPrimitive, ()) + antialiased = mtraits.AntiAliased() + color = mtraits.Color('blue') + facecolor = mtraits.Color('yellow') + linestyle = mtraits.LineStyle('-') + linewidth = mtraits.LineWidth(1.0) + model = mtraits.Model + pathdata = traits.Tuple(Array('b'), Array('d')) + sequence = 'paths' + zorder = Float(1.0) + # why have an extra layer separating the PathPrimitive from the + # Path artist? The reasons are severalfold, but it is still not + # clear if this is the better solution. Doing it this way enables + # the backends to create their own derived primitves (eg + # RendererAgg creates PathPrimitiveAgg, and in that class sets up + # trait listeners to create agg colors and agg paths when the + # PathPrimitive traits change. Another reason is that it allows + # us to handle nonlinear transformation (the "model") at the top + # layer w/o making the backends understand them. The current + # design is create a mapping between backend primitives and + # primitive artists (Path, Text, Image, etc...) and all of the + # higher level Artists (Line, Polygon, Axis) will use the + # primitive artitsts. So only a few artists will need to know how + # to talk to the backend. The alternative is to make the backends + # track and understand the primitive artists themselves. + def __init__(self): """ The model is a function taking Nx2->Nx2. This is where the nonlinear transformation can be used """ Artist.__init__(self) + self._pathid = primitiveID() - # this is potentially a big problem because you have to know - # which attrs may be shared and hence have to be initialized - # and which ones don't. Eg, if you comment out the self.path - # init, the code breaks + def _pathdata_default(self): + return (npy.array([0,0], dtype=npy.uint8), + npy.array([[0,0],[0,0]], npy.float_)) + + def _update_path(self): + 'sync the Path traits with the path primitive' + self.sync_trait('linewidth', self._path, mutual=False) + self.sync_trait('color', self._path, mutual=False) + self.sync_trait('facecolor', self._path, mutual=False) + self.sync_trait('antialiased', self._path, mutual=False) - - self.sync_trait('linewidth', self.path, 'linewidth', mutual=False) - self.sync_trait('color', self.path, 'strokecolor', mutual=False) - self.sync_trait('markerfacecolor', self.markers.path, 'fillcolor', mutual=False) - self.sync_trait('markeredgecolor', self.markers.path, 'strokecolor', mutual=False) - self.sync_trait('markeredgewidth', self.markers.path, 'linewidth', mutual=False) + # sync up the path affine + self._path.affine.follow(self.affine.vec6) + self.affine.on_trait_change(self._path.affine.follow, 'vec6') + self._update_pathdata() - # sync up the markers affine - self.markers.affine.follow(self.affine.vec6) - self.affine.on_trait_change(self.markers.affine.follow, 'vec6') + def _update_pathdata(self): + #print 'PATH: update pathdata' - # sync up the path affine - self.path.affine.follow(self.affine.vec6) - self.affine.on_trait_change(self.path.affine.follow, 'vec6') - + codes, xy = self.pathdata - self.path.fillcolor = None + #print ' PATH: shapes', codes.shape, xy.shape + if self.model is not None: + xy = self.model(xy) - self.pathid = primitiveID() - self.markerid = primitiveID() + pathdata = codes, xy - self.markerfuncd = { - 's': self._markers_square, - } - + self._path.pathdata = pathdata + def draw(self): - if self.renderer is None or not self.visible: return Artist.draw(self) - - if self.linestyle is not None: - self.renderer.render_path(self.pathid) - if self.marker is not None: - self.renderer.render_markers(self.markerid) + self.renderer.render_path(self._pathid) def _renderer_changed(self, old, new): if old is not None: - old.remove_path(self.pathid) - old.remove_markers(self.markerid) + del old.pathd[self._pathid] + + if new is None: return - if new is not None: - new.add_path(self.pathid, self.path) - new.add_markers(self.markerid, self.markers) + #print 'PATH renderer_changed; updating' + self._path = renderer.new_path_primitive() + new.pathd[self._pathid] = self._path + self._update_path() + + def _model_changed(self, old, new): + self._update_pathdata() + def _pathdata_changed(self, old, new): + #print 'PATH: pathdata changed' + self._update_pathdata() - def _model_changed(self, old, new): - self._update_data() - def _X_changed(self, old, new): - self._update_data() - - def _update_data(self): - N = self.X.shape[0] - codes = Path.LINETO*npy.ones(N, dtype=npy.uint8) - codes[0] = Path.MOVETO +class Marker(Artist): + """ + An interface class between the higher level artists and the marker + primitive that needs to talk to the renderers + """ + _marker = traits.Instance(MarkerPrimitive, ()) + locs = Array('d') + path = Instance(Path, ()) + model = mtraits.Model + sequence = 'markers' + size = Float(1.0) # size of the marker in points - # todo, having touble setting Model to default to Identity so - # allowing None as a hack workaround - #print 'X changed', self.model - if self.model is not None: - modelx = self.model(self.X) - else: - modelx = self.X - self.path.pathdata = codes, modelx - self.markers.verts = modelx + def __init__(self): + """ + The model is a function taking Nx2->Nx2. This is where the + nonlinear transformation can be used + """ + Artist.__init__(self) + self._markerid = primitiveID() - def _markersize_changed(self, oldX, newX): - self._refresh_markers() + def _locs_default(self): + return npy.array([[0,1],[0,1]], npy.float_) - def _marker_changed(self, oldX, newX): - self._refresh_markers() + + def _path_default(self): + bounds = npy.array([-0.5, -0.5, 1, 1])*self.size + return Rectangle().set(bounds=bounds) + def _path_changed(self, old, new): + if self.renderer is None: + # we can't sync up to the underlying path yet + return + print 'MARKER _path_changed', self.path._path.pathdata, self._marker.path.pathdata + old.sync_trait('_path', self._marker, 'path', remove=True) + new.sync_trait('_path', self._marker, 'path', mutual=False) - def _refresh_markers(self): - if self.marker is not None: - markerfunc = self.markerfuncd.get(self.marker) - if markerfunc is not None: markerfunc() + def _update_marker(self): + 'sync the Marker traits with the marker primitive' + if self.renderer is None: + # we can't sync up to the underlying path yet + return - def _markers_square(self): + # sync up the marker affine + self.path.sync_trait('_path', self._marker, 'path', mutual=False) + self._marker.affine.follow(self.affine.vec6) + self.affine.on_trait_change(self._marker.affine.follow, 'vec6') + self._update_locs() - verts = self.markersize*npy.array([[-0.5,-0.5], [-0.5,0.5], [0.5,0.5], [0.5,-0.5], [0,0]]) - codes = Path.LINETO*npy.ones(len(verts), dtype=npy.uint8) - codes[0] = Path.MOVETO - codes[-1] = Path.CLOSEPOLY + print 'MARKER _update_marker', self.path._path.pathdata, self._marker.path.pathdata + + def _update_locs(self): + print 'MARKER: update markerdata' + xy = self.locs + if self.model is not None: + xy = self.model(xy) + + self._marker.locs = xy - self.markers.path.pathdata = codes, verts + def draw(self): + if self.renderer is None or not self.visible: return + Artist.draw(self) + self.renderer.render_marker(self._markerid) + -mtraits.Line = traits.Instance(Line, ()) + def _renderer_changed(self, old, new): + # we must make sure the contained artist gets the callback + # first so we can update the path primitives properly + self.path._renderer_changed(old, new) + if old is not None: + del old.markerd[self._markerid] + + if new is None: return -class Rectangle(Artist, Box): - facecolor = mtraits.Color('yellow') - edgecolor = mtraits.Color('black') - edgewidth = mtraits.LineWidth(1.0) - path = mtraits.Path - zorder = traits.Float(1.0) - sequence = 'rectangles' + print 'MARKER renderer_changed; updating' + self._marker = renderer.new_marker_primitive() + new.markerd[self._markerid] = self._marker + self._update_marker() + + def _model_changed(self, old, new): + self._update_locs() - def __init__(self): - Artist.__init__(self) - - self.sync_trait('facecolor', self.path, 'fillcolor', mutual=False) - self.sync_trait('edgecolor', self.path, 'strokecolor', mutual=False) - self.sync_trait('edgewidth', self.path, 'linewidth', mutual=False) + def _locs_changed(self, old, new): + if len(new.shape)!=2: + raise ValueError('new must be nx2 array') + self._update_locs() - self.pathid = primitiveID() - # sync up the path affine - self.path.affine.follow(self.affine.vec6) - self.affine.on_trait_change(self.path.affine.follow, 'vec6') +class Line(Path): + + XY = Array('d') + sequence = 'lines' + + def _facecolor_default(self): + return None + + def _XY_default(self): + return npy.array([[0,1],[0,1]], npy.float_) + + def _XY_changed(self): + #print 'LINE: XY changed' + codes = npy.ones(len(self.XY), npy.uint8) + codes[0] = MOVETO + #print 'LINE shapes', codes.shape, self.XY.shape + self.pathdata = codes, self.XY + + # XXX: to we need to push pathdata changed here or will it + # happen automagically + + +class Polygon(Path): + zorder = Float(2.0) + vertices = Array('d') + sequence = 'polygons' + + def _vertices_default(self): + return npy.array([[-1,0], [0,1], [1,0], [0,0]], npy.float_) + + def _vertices_changed(self, old, new): + #print 'POLY: verts changed' + N = len(new) - def _hidebounds_changed(self, old, new): - Box._bounds_changed(self, old, new) - print 'rectangle bounds changed' + codes = LINETO*npy.ones(N, npy.uint8) + codes[0] = MOVETO + codes[-1] = CLOSEPOLY + pathdata = codes, new + self.pathdata = pathdata + self._pathdata_changed(None, pathdata) + +class Rectangle(Polygon, Box): + sequence = 'rectangles' + def __init__(self): + Polygon.__init__(self) + self._bounds_changed(None, self.bounds) + def _bounds_changed(self, old, new): Box._bounds_changed(self, old, new) - print 'rectangle bounds changed' + #print 'RECT: bounds changed' l,b,w,h = new t = b+h r = l+w - verts = npy.array([(l,b), (l,t), (r, t), (r, b), (0,0)], npy.float_) - codes = Path.LINETO*npy.ones(5, npy.uint8) - codes[0] = Path.MOVETO - codes[-1] = Path.CLOSEPOLY + self.vertices = npy.array([(l,b), (l,t), (r, t), (r, b), (0,0)], npy.float_) + #XXX: do we need to otify traits change + self._vertices_changed(None, self.vertices) - self.path.pathdata = codes, verts +class RegularPolygon(Polygon): + center = Tuple(Float, Float) + sides = Int(6) + radius = Float(1.0) + theta = Float(0.) # orientation in radians + sequence = 'rectangles' - def _renderer_changed(self, old, new): - if old is not None: - old.remove_path(self.pathid) + def __init__(self): + self._update_vertices() + + def _sides_changed(self, old, new): + self._update_verts() - if new is not None: - new.add_path(self.pathid, self.path) + def _theta_changed(self, old, new): + self._update_verts() - def draw(self): - if self.renderer is None or not self.visible: return - Artist.draw(self) - self.renderer.render_path(self.pathid) + def _radius_changed(self, old, new): + self._update_verts() + + def _update_verts(self): + theta = 2*npy.pi/self.sides*npy.arange(self.sides) + self.theta + x, y = self.center -mtraits.Rectangle = traits.Instance(Rectangle, ()) + xy = npy.zeros((self.sides,2)) + xy[:,0] = x + self.radius*npy.cos(theta) + xy[:,1] = y + self.radius*npy.sin(theta) + + self.vertices = xy + class Figure(ArtistContainer): - rectangle = traits.Instance(Rectangle, ()) + rectangle = Instance(Rectangle, ()) sequence = None # figure is top level container def __init__(self): ArtistContainer.__init__(self) @@ -1027,34 +1122,26 @@ self.add_artist(self.rectangle) class Axis(ArtistContainer): - zorder = traits.Float(1.5) - tickmarkers = mtraits.Markers - linepath = mtraits.Path - linecolor = mtraits.Color('black') - linewidth = mtraits.LineWidth(1.0) - ticklocs = traits.Array('d') - ticksize = traits.Float(5.0) - ticklinewidth = mtraits.LineWidth(1.0) - tickcolor = mtraits.Color('black') + zorder = Float(1.5) + tickmarker = Instance(Marker, ()) + line = Instance(Line, ()) + ticklocs = Array('d') + ticksize = Float(5.0) + - loc = traits.Float(0.) # the y location of the x-axis - tickoffset = traits.Float(0) # -1 for outer, -0.5 for centered, 0 for inner + loc = Float(0.) # the y location of the x-axis + tickoffset = Float(0) # -1 for outer, -0.5 for centered, 0 for inner sequence = 'axes' def __init__(self): ArtistContainer.__init__(self) - self.tickmarkersid = primitiveID() - self.linepathid = primitiveID() - self.affine.on_trait_change(self._update_blended_affine, 'vec6') - self.tickmarkers.path.antialiased = False - self.linepath.antialiased = False - - self.sync_trait('linewidth', self.linepath, mutual=False) - self.sync_trait('linecolor', self.linepath, 'strokecolor', mutual=False) - self.sync_trait('ticklinewidth', self.tickmarkers.path, 'linewidth', mutual=False) - self.sync_trait('tickcolor', self.tickmarkers.path, 'strokecolor', mutual=False) + self.tickmarker.antialiased = False + self.line.antialiased = False + self.add_artist(self.line, followdata=False) + self.add_artist(self.tickmarker, followdata=False) + # XXX, do we have to manually call these or will they get # calle dautomagically in init self._update_tick_path() @@ -1087,21 +1174,6 @@ def _update_linepath(self): raise NotImplementedError - def _renderer_changed(self, old, new): - if old is not None: - old.remove_markers(self.tickmarkersid) - old.remove_path(self.linepathid) - - if new is not None: - new.add_markers(self.tickmarkersid, self.tickmarkers) - new.add_path(self.linepathid, self.linepath) - - def draw(self): - if self.renderer is None or not self.visible: return - Artist.draw(self) - self.renderer.render_markers(self.tickmarkersid) - self.renderer.render_path(self.linepathid) - class XAxis(Axis): sequence = 'xaxes' def _update_blended_affine(self): @@ -1109,27 +1181,31 @@ sx, b, tx = self.adata.data[0] a = Affine() a.vec6 = sx, b, 0, 1, tx, self.loc - self.tickmarkers.affine.vec6 = (self.aview * a).vec6 + self.tickmarker.affine.vec6 = (self.aview * a).vec6 a = Affine() a.translate = 0, self.loc - self.linepath.affine.vec6 = (self.aview * a).vec6 + self.line.affine.vec6 = (self.aview * a).vec6 def _update_marker_locations(self): Nticks = len(self.ticklocs) - verts = self.loc*npy.ones((Nticks,2)) - verts[:,0] = self.ticklocs - self.tickmarkers.verts = verts + locs = self.loc*npy.ones((Nticks,2)) + locs[:,0] = self.ticklocs + self.tickmarker.locs = locs def _update_tick_path(self): - codes = Path.MOVETO, Path.LINETO + codes = MOVETO, LINETO verts = npy.array([[0., self.tickoffset], [0, self.tickoffset-1]])*self.ticksize - self.tickmarkers.path.pathdata = codes, verts + pathdata = codes, verts + self.tickmarker.path.pathdata = pathdata + def _update_linepath(self): - codes = Path.MOVETO, Path.LINETO + + codes = MOVETO, LINETO X = npy.array([[0, 1], [0, 0]], npy.float_).T - self.linepath.pathdata = codes, X + pathdata = codes, X + self.line.pathdata = pathdata class YAxis(Axis): sequence = 'yaxes' @@ -1139,28 +1215,31 @@ c, sy, ty = self.adata.data[1] a = Affine() a.vec6 = 1, 0, 0, sy, self.loc, ty - self.tickmarkers.affine.vec6 = (self.aview * a).vec6 + self.tickmarker.affine.vec6 = (self.aview * a).vec6 a = Affine() a.translate = self.loc, 0 - self.linepath.affine.vec6 = (self.aview * a).vec6 + self.line.affine.vec6 = (self.aview * a).vec6 def _update_marker_locations(self): Nticks = len(self.ticklocs) - verts = self.loc*npy.ones((Nticks,2)) - verts[:,1] = self.ticklocs - self.tickmarkers.verts = verts + locs = self.loc*npy.ones((Nticks,2)) + locs[:,1] = self.ticklocs + self.tickmarker.locs = locs def _update_tick_path(self): - codes = Path.MOVETO, Path.LINETO + codes = MOVETO, LINETO verts = npy.array([[self.tickoffset,0], [self.tickoffset+1,0]])*self.ticksize - self.tickmarkers.path.pathdata = codes, verts + pathdata = codes, verts + self.tickmarker.path.pathdata = pathdata def _update_linepath(self): - codes = Path.MOVETO, Path.LINETO + codes = MOVETO, LINETO X = npy.array([[0, 0], [0, 1]], npy.float_).T - self.linepath.pathdata = codes, X + pathdata = codes, X + self.line.pathdata = pathdata + class FigurePane(ArtistContainer, Box): """ The figure pane conceptually like the matplotlib Axes, but now @@ -1168,10 +1247,10 @@ Affine instances. It is a shell of it's former self: it has a rectangle and a default x and y axis instance """ - rectangle = traits.Instance(Rectangle, ()) + rectangle = Instance(Rectangle, ()) #gridabove = traits.false # TODO handle me - xaxis = traits.Instance(XAxis, ()) - yaxis = traits.Instance(YAxis, ()) + xaxis = Instance(XAxis, ()) + yaxis = Instance(YAxis, ()) sequence = 'panes' def __init__(self): @@ -1181,56 +1260,120 @@ self.rectangle.edgecolor = 'white' self.rectangle.linewidth = 0 - print 'setting rect bounds' self.rectangle.bounds = [0,0,1,1] - print 'set rect bounds' self.add_artist(self.rectangle, followdata=False) self.add_artist(self.xaxis) self.add_artist(self.yaxis) def _bounds_changed(self, old, new): Box._bounds_changed(self, old, new) - print 'pane bounds changed' l,b,w,h = self.bounds self.aview.scale = w, h self.aview.translate = l, b - +## begin examples + def classic(fig): - x = npy.arange(0, 10., 0.01) - y = npy.sin(2*npy.pi*x) pane = FigurePane().set(bounds=[0.1, 0.1, 0.8, 0.8]) fig.add_artist(pane, followdata=False, followview=False) - - - line1 = Line().set(X=npy.array([x,y]).T, - color='blue', linewidth=2.0, marker=None, + # update the view limits, all the affines should be automagically updated + x = npy.arange(0, 10., 0.01) + y = npy.sin(2*npy.pi*x) + y = npy.exp(-x/2.) + line1 = Line().set(XY=npy.array([x,y]).T, + color='blue', linewidth=2.0, ) pane.add_artist(line1) - - # update the view limits, all the affines should be automagically updated pane.adata.xlim = 0, 10 - pane.adata.ylim = -1.1, 1.1 + pane.adata.ylim = -0.1, 1.1 - pane.xaxis.ticklocs = npy.arange(0., 11., 1.) - pane.yaxis.ticklocs = npy.arange(-1.0, 1.1, 0.2) + # axis placement is still broken + xax, yax = pane.xaxis, pane.yaxis + xax.ticklocs = npy.arange(0., 11., 1) + xax.tickoffset = 0.5 + xax.loc = -0.05 + xax.line.color = 'black' + xax.tickmarker.path.color = 'black' + yax.ticklocs = npy.arange(-1.0, 1.1, 0.2) + yax.tickoffset = -0.5 + yax.loc = -0.05 + yax.line.color = 'black' + yax.tickmarker.path.color = 'black' - # add a right and top axis - xaxis2 = XAxis().set(loc=1, tickoffset=-1) - yaxis2 = YAxis().set(loc=1, tickoffset=-1) - xaxis2.ticklocs = npy.arange(0., 11., 0.5) - yaxis2.ticklocs = npy.arange(-1.0, 1.1, 0.1) + if 0: + x = npy.arange(0, 10., 0.1) + y = npy.sin(2*npy.pi*x) - pane.add_artist(xaxis2) - pane.add_artist(yaxis2) - # uncomment to change Axes wwidth - #pane.width = 0.8 + marker = Marker().set( + locs=npy.array([x,y]).T, color='ref', linewidth=1.0, + size=10) + + pane.add_artist(marker) + + + if 0: + xax, yax = pane.xaxis, pane.yaxis + xax.ticklocs = npy.arange(0., 11., 1) + xax.ticksize = 8 + xax.line.color = 'black' + xax.line.linewidth = 2.0 + xax.tickoffset = .5 + xax.tickmarker.path.color = 'black' + xax.tickmarker.path.linewidth = 2 + + xax.loc = 0.5 + xax.zorder = 10 + + yax.ticklocs = npy.arange(-1.0, 1.1, 0.2) + yax.line.color = 'black' + yax.line.linewidth = 2.0 + yax.tickmarker.path.color = 'black' + yax.ticksize = 8 + yax.tickoffset = -0.5 + yax.tickmarker.path.linewidth = 2 + yax.loc = 0.5 + yax.zorder = 10 + if 0: + # add a right and top axis; the markers are getting the loc + # but the line path isn't... It appears all the line paths + # are getting 0 + xaxis2 = XAxis() + xaxis2.loc = 0.6 + xaxis2.tickoffset = -1 + xaxis2.ticklocs = npy.arange(0., 10.1, 0.5) + yaxis2 = YAxis().set(loc=0.6, tickoffset=-1) + + yaxis2.tickmarker.path.color = 'green' + yaxis2.loc = 0.5 + yaxis2.ticksize = 10 + yaxis2.tickmarker.path.linewidth = 1 + yaxis2.line.color = 'green' + yaxis2.tickmarker.path.color = 'green' + yaxis2.ticklocs = npy.arange(-1.0, 1.1, 0.1) + + pane.add_artist(xaxis2) + pane.add_artist(yaxis2) + # uncomment to change Axes wwidth + #pane.width = 0.8 + + if 0: + # XXX: axes lines and tick markes are placed in vastly + # different locations depending on whether this is commented + # or uncommented, suggesting that the problem is caused by + # events not propogating unless lim are changed. If we set + # these lim to be the same as the lim above (note they are + # almost identical) then the graphs are the same regardless of + # whether the lim are set + pane.adata.xlim = 0.01, 10.01 + pane.adata.ylim = -0.101, 1.101 + + def make_subplot_ll(fig): x1 = npy.arange(0, 10., 0.05) x2 = npy.arange(0, 10., 0.1) @@ -1243,9 +1386,7 @@ line1 = Line().set(X=npy.array([x1,y1]).T, - color='blue', linewidth=2.0, marker='s', - markersize=5.0, markerfacecolor='green', - markeredgewidth=0.5) + color='blue', linewidth=2.0) pane.add_artist(line1) @@ -1257,14 +1398,14 @@ pane.xaxis.ticklocs = npy.arange(0., 11., 1.) pane.xaxis.loc = -0.1 pane.xaxis.tickoffset = -0.5 - pane.xaxis.linecolor = 'red' + pane.xaxis.line.color = 'red' - pane.yaxis.ticklocs = npy.arange(-1.0, 1.1, 0.2) + Pane.yaxis.ticklocs = npy.arange(-1.0, 1.1, 0.2) pane.yaxis.loc = -0.1 pane.xaxis.tickoffset = -0.5 - pane.yaxis.linecolor = 'blue' - pane.yaxis.tickcolor = 'blue' + pane.yaxis.line.color = 'blue' + pane.yaxis.tickmarker.color = 'blue' # uncomment to change Axes wwidth #pane.width = 0.8 @@ -1291,19 +1432,6 @@ -class TestContainer(ArtistContainer, Box): - rectangle = traits.Instance(Rectangle, ()) - sequence = 'panes' - - def __init__(self): - ArtistContainer.__init__(self) - self.rectangle.zorder = 0 - self.rectangle.facecolor = 'white' - - print 'setting rect bounds' - self.rectangle.bounds = [0,0,1,1] - print 'set rect bounds' - if __name__=='__main__': renderer = RendererAgg() This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |