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 python
buffer 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
[email protected]
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel