Hi Ken -- sorry for the radio silence, I'm not intentionally ignoring
you. Real life has made some demands on my time of late, and probably
will until next week, but I was able to download, read through and
test your code.
I appreciate you making the time to take a look at my code in spite
of it.
But I don't think that closes the book on the subject -- for
example, I have realized I am introducing too much complexity and
trait forwarding, and I think I know a way to work around this, so
I will be hacking through my version one more time while at the
same time taking a closer look at yours.
I'd hardly expect it to close the book on the subject. Although it
functions as a proof-of-concept, my rendering model needs more work
before it can handle special cases like the blended affine
transformation to plot an axis. I've been thinking about how best to
accomplish this and will try to have something for you to look at by
early next week. Please drop me a line when you're satisfied with
your changes to mpl1.py, as I'm looking forward to seeing them.
I also would like to hear more about the "hard to optimize" part,
because that is not intuitively clear to me.
Moving to a rendering model when the primitives modify the CTM
instead of replacing it with a pre-calculated value removes the
dependency between primitives and their containers. A Primitive
object, like a Path, may be reused and shared between renderers. The
native version of a Primitive object is stored by the Primitive
object itself, so it can be reused by any Canvas that draws the
Primitive. This also means that you incur very little cost in
clearing a Renderer and then repopulating it with existing
Primitives. An Artist-level implementation of z-order could leverage
this to avoid having to sort primitives by z-order every time a
figure is rendered.
Since primitives modify graphics state instead of replacing it
wholesale the it's easier for the Canvas to push as few changes to
the backend as possible. For example, if none of the child
primitives of a ScaledView have their own transforms then the CTM
only gets modified once when the ScaledView is rendered. The same is
true for all of the other graphics state parameters, like linewidth
and strokecolor. This saves on unnecessary backend overhead (e.g.
creating the same agg.trans_affine repeatedly). It could also lead
to tighter PDF and EPS output.
Finally, the Renderer class is canvas-independent so it can be used
to draw on multiple canvases during the course of its lifetime. I
hope this will substantially simplify the process of saving a figure
that was drawn interactively. I'm also contemplating making
Renderers able to contain child Renderers so parts of a figure can be
selectively updated. For example, drawing the non-Axis children of
an Axes by using a subrenderer could further improve animation
performance.
I confess to being a meta-class ignoramus, so I am intrigued to study
how you handled the canvas primitive cache problem using meta classes.
I hate to disappoint you, but metaclasses aren't involved. Right now
the caching is implemented cooperatively by Canvas and
CanvasPrimitive to reduce duplicate code.
CanvasPrimitive.get_canvas_primitive() handles the caching logic and
calls CanvasPrimitive.create_canvas_primitive() to create a new
native object if necessary. Subclasses of CanvasPrimitiveoverride
create_canvas_primitive() to delegate the call to the appropriate
method of Canvas, e.g. Path.create_canvas_primitive() returns the
result of Canvas.create_canvas_path(). Subclasses of Canvas that do
not cache Primitives should not override the create_canvas_xxx()
methods.
How for example, if one modifies a Rectangle object which embodies a
path primitive, would the canvas get the notification to update it's
internal path representation (egg the agg_path_storage)?.
Native canvas objects are always created at render-time, so rectangle
would just update its associated Path instance when its bounds
changed. The next time the Path is drawn, Canvas.draw_path() would
call CanvasPrimitive.get_canvas_primitive() and end up with a new AGG
path. That being said, it might be more efficient if all Rectangles
share one Path that draws a unit square and change its size by
updating the CTM.
With traits, I use the trait event handling mechanism so that when
any of
the box properties (left, width, top, etc...) are modified, the
PathPrimitiveAgg would get a callback. So when the user does
rect.left = 0.1
the agg path knows to update itself. This is pretty cool.
Yes it is, and I agree that mpl1 should have an attribute-based API
for modifying plot object parameters.
vis-a-vis properties vs traits, I second Peter's comment that once
you've written 8,000 setters and getters and manually constructed
properties, you'll probably evolve toward something like traits, w/o
all the features. This is a library that has been bug and performance
vetted in production applications for years, so we should seriously
consider it as a properties alternative.
Traits does look excellent at what it does, but I'm still of the
opinion that the cost of adding a large external dependency that is
not available in Debian or Ubuntu outweighs the benefits of replacing
vanilla properties with traits.
I strongly encourage you to download Fernando's mplconfig sandbox
stuff and try the edit_traits demo in the presence of a wx enabled
traits.
I'll give it a try.
He is somewhat blown away by the fact that he got a sophisticated,
nested GUI dialog w/o writing *any* code. Since you are a
committed wx user, you might find this particularly appealing.
No one's committed me yet, mwahaha! Actually, that feature does
little to advance my primary use case for mpl, embedding plots in
applications. I my experience you have to put enough effort into
making the plots look good that you don't want users to be able to
edit them at runtime. That being said, I wouldn't be surprised to
learn that others' experiences differ.
But at the end of the day, I think the *notification* aspect of
traits is the killer feature.
I know, and I still think that for matplotlib it's a bad Software
Engineering move due to the implicit inter-object dependencies it
creates. I can't see why drawing plots should require that much
"spooky action at a distance". I could be wrong, but for now I'll
keep working to find a way that doesn't.
I think your approach of working on a display PDF renderer interface
is also a good one, for several reasons. It uses an established
specification instead of a home grown one, and it makes it easier for
us to consider things like integrating with Kiva or Chaco.
It's nice to hear you like it. Those are two of the specific goals I
have in mind. I also like the idea of having a generic retained
drawing system available in Python.
I am glad you interpreted my mpl1 sketch in the right vein, as a
lab in which
people can advocate ideas as working code.
Well, I'm glad you've taken my criticisms of mpl1.py in the spirit
they were intended in. I also appreciate you taking the time to
review the code I submitted instead of just telling me how very nice
and convenient traits are.
Hopefully next week we can come to some consensus and start merging
our two lines.
That sounds like a plan. I'll try to make some progress on the
blended affines for drawing axis tickmarks and the Artists layer.
Ken
···
Sent via BlackBerry from T-Mobile
-----Original Message-----
From: Ken McIvor <mcivor@...227...>
Date: Fri, 27 Jul 2007 17:35:05
To:John Hunter <jdh2358@...149...>
Cc:matplotlib development list <matplotlib-devel@lists.sourceforge.net>
Subject: Re: [matplotlib-devel] mpl1 draft
On Jul 25, 2007, at 12:09 PM, John Hunter wrote: