Wow, lots of food for thought. Thanks John! On Jul 19, 2007, at 12:18 PM, John Hunter wrote:
= Objects that talk to the backend "primitives" = Have just a few, fairly rich obects, that the backends need to understand. Clear candidates are a Path, Text and Image, but despite their names, don't confuse these with the eponymous matplotlib matplotlib Artists, which are higher level than what I'm thinking of here (eg matplotlib.text.Text does *a lot* of layout, and this would be offloaded ot the backend in this conception of the Text primitive).
This sounds like a great idea. I think you should consider making transformations a first-class citizen as well, to improve code readability. Some of us have to think really hard to translate "[[width, 0, 0], [0, -height, height], [0, 0, 1]]" into English! ;-)
= Where do the plot functions live? = In matplotlib, the plot functions are matplotlib.axes.Axes methods and I think there is consensus that this is a poor design. Where should these live, what should they create, etc?
I propose that the plot functions be replaced by plot classes that implement whatever the new Artist API ends up being. I'm not sure where I'd put them (grouped into modules by type?) or how they should be added to an Axes (add_artist()?).
= How much of an intermediate artist layer do we need? = Do we want to create high level objects like Circle, Rectangle and Line, each of which manage a Path object under the hood?
Yes, but I don't think they should be subclassed from Path. I found that rather confusing because I read that as "Rectangle is-a Path".
= Z-ordering, containers, etc = Peter has been doing a lot of nice work on z-order and layers for chaco, stuff that looks really useful for picking, interaction, etc... We should look at this approach, and think carefully about how this should be handled.
Is there somewhere in particular that I can look to see what Peter's been working on? Enthought's svn repositories?
I also plan to use the SWIG agg wrapper, so this gets rid of _backend_agg. If we can enhance the SWIG agg wrapper, we can also do images through there, getting rid of _image.cpp. Having a fully featured, python-exposed agg wrapper will be a plus in mpl and beyond. But with the agg license change, I'm open to discussion of other approaches.
Have you looked into Fredrik Lundh's aggdraw module? Apart from not yet supporting image drawing/blitting, I think it might do the trick. I've attached an aggdraw version your mpl1.py that I wrote as proof of concept. I opted to start hacking instead of installing the traits package, so there's just a basic demo of the renderer for now.
Since aggdraw can work natively with the Python Imaging Library we'd be able to support every raster format under the sun *and* save those images to Python strings. That would make the webapps people very happy.
I want to do away with *all* GUI extension code.
Hurrah!
This means someone needs to figure out how to get TkInter talking to a pythonbuffer object or a numpy array.
I think PIL's ImageTk module would do the trick for converting RGBA - > PIL Image -> Tk Bitmap/PhotoImage.
= Traits = I think we should make a major committment to traits and use them from the ground up. Even without the UI stuff, they add plenty to make them worthwhile, especially the validation and notification features.
I hate to be the first one to disagree, but here goes: traits give me the heebie-jeebies. I agree that matplotlib 1.0/2.0 needs to validate all user-settable parameters. However, I'm concerned about the development overhead that might result from making traits as a core dependency. Code readability is also a concern to me -- the experience of reading mpl1.py suggests to me that newcomers might find traits a bit too "voodoo". I'm confident that the same thing could be achieved using Python properties to validate attributes. Change notification is another matter, granted, but I think that a major rewrite will provide the opportunity to better design for those situations.
Ken
import aggdraw import Image import numpy as npy class Renderer: def __init__(self, width, height): self.width, self.height = width, height # almost all renderers assume 0,0 is left, upper, so we'll flip y here by default self.affinerenderer = npy.array( [[width, 0, 0], [0, -height, height], [0, 0, 1]], dtype=npy.float_) self.pathd = dict() # dict mapping path id -> path instance def add_path(self, pathid, path): self.pathd[pathid] = path def remove_path(self, pathid): if pathid in self.pathd: del self.pathd[pathid] def render_path(self, pathid): pass class RendererAggDrawPIL(Renderer): def __init__(self, width, height): Renderer.__init__(self, width, height) # XXX: this flips the origin without scaling to 0..1 self.affinerenderer = npy.array( [[1,0,0],[0,-1,height],[0,0,1]], dtype=npy.float_) self.image = Image.new('RGBA', (width, height), 'white') self.draw = aggdraw.Draw(self.image) def add_path(self, pathid, path): self.pathd[pathid] = path def render_path(self, pathid): path = self.pathd[pathid] self.draw.setantialias(path.antialiased) affine = npy.dot(self.affinerenderer, path.affine) a, b, tx = affine[0] c, d, ty = affine[1] self.draw.settransform((a, b, tx, c, d, ty)) if path.fillcolor is None: brush = None else: brush = aggdraw.Brush(path.fillcolor, int(255*path.alpha)) if path.strokecolor is None: pen = None else: pen = aggdraw.Pen(path.strokecolor, path.linewidth, int(255*path.alpha)) agg_path = aggdraw.Path() codes, verts = path.pathdata N = len(codes) MOVETO, LINETO, CLOSEPOLY = Path.MOVETO, Path.LINETO, Path.CLOSEPOLY for i in range(N): x, y = verts[i] code = codes[i] if code==MOVETO: agg_path.moveto(x, y) elif code==LINETO: agg_path.lineto(x, y) elif code==CLOSEPOLY: agg_path.close() self.draw.path((0,0), agg_path, pen, brush) def show(self): self.draw.flush() # we'll cheat a little and use PIL to display the image import os import sys if sys.platform == 'darwin': # Avoid PIL opening the image as a JPEG in Preview fname = os.tmpnam() + '.png' self.image.save(fname, 'PNG') os.system('open -a Preview %s' % fname) else: self.image.show() class Path: """ 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 = 'black' fillcolor = 'blue' alpha = 1.0 linewidth = 1.0 antialiased = True pathdata = [] affine = npy.array([[1,0,0],[0,1,0],[0,0,1]], npy.float_) def draw_rectangle(path, lbwh): l,b,w,h = lbwh 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 path.pathdata = codes, verts def draw_line(path, x, y, model=None): """ The model is a function taking Nx2->Nx2. This is where the nonlinear transformation can be used """ X = npy.array([x,y]).T numrows, numcols = X.shape codes = Path.LINETO*npy.ones(numrows, npy.uint8) codes[0] = Path.MOVETO if model is None: verts = X else: verts = model(X) path.pathdata = codes, verts path.fillcolor = None class Figure: def __init__(self): self.renderer = None self._pathid = 0 self.pathd = dict() def add_path(self, path): id_ = self._pathid self.pathd[id_] = path self._pathid += 1 return id_ def remove_path(self, pathid): if pathid in self.pathd: del self.pathd[pathid] if self.renderer is not None: self.renderer.remove_path(pathid) def draw(self): if self.renderer is None: raise RuntimeError('call set_renderer renderer first') for pathid in self.pathd: renderer.render_path(pathid) def set_renderer(self, renderer): self.renderer = renderer for pathid, path in self.pathd.items(): renderer.add_path(pathid, path) ############################################################################## x = npy.arange(32, 640, 32) y1 = npy.random.randint(0, 480, len(x)) fig = Figure() if 1: line1 = Path() draw_line(line1, x, y1) fig.add_path(line1) if 1: rect1 = Path() rect2 = Path() rect1.alpha = rect2.alpha = 0.5 rect2.fillcolor = 'red' draw_rectangle(rect1, [50,50,100,100]) draw_rectangle(rect2, [100,100,100,100]) fig.add_path(rect1) fig.add_path(rect2) renderer = RendererAggDrawPIL(640,480) fig.set_renderer(renderer) fig.draw() renderer.show()
------------------------------------------------------------------------- This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2005. http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________ Matplotlib-devel mailing list Matplotlib-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/matplotlib-devel