GSoC 2008 project: Float canvas refactoring
* Mentor: Christopher Barker * Student: Matthias Kesternich
FloatCanvas implements a canvas where different kinds of objects can be drawn on. It's part of wxPython.
The reason for the refactoring is given by the original author itself: "Over the years I've worked on FloatCanvas, I've learned a lot of about structuring code, and programming in general. Also, folks have extended FloatCanvas use beyond my original ideas, and wxPython has added features. So, I think it's time for a re-factoring effort" (see  for more detail).
In addition to the neccessary refactoring FloatCanvas will also benefit from having code added which utilizes the wxPython Graphics Context. A Graphics Context (for short GC) is very similar to a DC in many ways. However, it's feature set is much wider as it supports things like anti-aliasing, alpha-blending amongst other things. These extended capabilities - which have become available rather recently - can be used to make FloatCanvas produce much higher quality output which is "polished" and up-to-par with today's requirements.
FloatCanvas will also benefit from having support for persistence added as it is common to save and reload a certain layout. Some people have requested SVG support. This is also something a persistence backend could take care of.
Additionally, there is a general restructuring neccessary. This involves splitting some huge monolithic files into smaller components. Also the handling of transformations such as scaling needs to be revisited, because it is not implemented in a satisfactory, flexible way right now. One example of the current systems shortcomings is the fact that non-linear transformations of coordinates are not possible right now.
Finally, the current FloatCanvasDemo found in the wxPython distribution will be updated to reflect the new features.
If there are more requests by users of FloatCanvas which fit into the scope and time of this project I am more than eager to work on them to make for the best possible user experience!
I have broken the task outlined in the abstract into several pieces. Those are given below:
2. Do general refactoring. FloatCanvas consists mostly of two rather big files right now. These could be broken down into smaller pieces to increase code readability a lot. (a few days)
3. Refactor scaling. This could be done by using established methods such as using the 2d equivalent of the homogenous 4x4 matrices used in 3d transformations (I have written a 3d engine so I am fairly familiar with those transformations). Those matrices can hold scale as well as rotation and position of objects. A new API could probably be added to FloatCanvas to support using general transformations. Of course the old API could be kept and made deprecated, perusing the new api internally. The matrix API would also benefit from numpy's matrix routines which should add some speed boost. In addition to a matrix API I could add a more flexibly approach where the user can supply a function mapping input coordinates to output coordinates. This is not very fast, so this should probably be optional. It allows for non-linear transforms, too. Along with this would go an implementation of the concept of a view/camera (projection), similar to the way transformations are done commonly in 3d. Something which is also worth considering is a "scene graph"-like structure where objects can have children which inherit the parent's transform. This needs more input from Christopher Barker and users though. (1-2 weeks)
4. Refactoring to allow a "GC" backend. This will go along with several performance tests so the performance after any change can be directly evaluated. This is important because it is expected that the GC backend will cause a serious slowdown compared with the current DC backend. If the GC is definately slower than the DC there will be a way to either switch totally back to the "DC" backend or to replace just the bottleneck operations of the GC. Furthermore options to disable/enable certain GC features will also be accessible. I will keep an eye on creating a preliminary infrastructure for supporting other backends in the future (SVG for example) and decoupling this from the rest. At the same time I'll try to avoid useless over-generalization for possible future non wx-backends. (2-3 weeks)
5. Adding persistence. How users want to do persistence exactly probably differs a lot. Some people may want to create simple pickles, others may want to serialize to/from databases (RDBMS / ZODB), XML, SVG, custom formats etc. So there should be some general interface for this. I will write a default backend using either pickles or an xml stream (depending on what users want). (1-2 weeks)
6. Update the current FloatCanvas demo to show off the new features and how to migrate away from the old API (if I'll introduce a new one)
(7. If there is any time left at this stage, I'll start working on an SVG persistence backend)
(written by Chris Barker -- April 30, 2008)
I see the various issues of the refactor breaking down into two catagories -- new features and code cleanup/reorganization. The distinctions aren't quite clear, as some code reorganization is required for the new features, but I hope the catagories will be usefull.
- More flexible projections. Currently FC only support simple linear scaling for a "projection". It would be nice to have full flexibilty in projections. This would be particularly good for mapping applications, but could be used for other stuff (like log scale plotting, etc). This will require that a projection function be generic -- it gets passed in coordinates, and returns projected coordinates.
Alpha blending, anti-aliasing -- this will require the use of GraphicsContext drawing.
- Persistance -- maybe SVG?
- Another set of coordinates -- "paper coords" (yow, that's a lot!):
- World coordinates -- the natural coordinates that the user's data lives in -- cartesian, lat.long, whatever.
- Projected coordinates -- coords after the projection is applied -- these should be orthogonal, y-up coords.
- Pixel coordinates -- actual pixel coords whenstuff is drawn, after zooming and shifting.
- Paper coordinates -- the coordinates used when "printing". Even if printing isn't enables, it's the coords that you can use to set things like font sizes and line thicknesses -- note that they will most likely be used for lengths, rather than locations. A scale will need to be set to tranlate from Paper to Projected coordinates. This will allow a user to set the scale, then zoom in and see an enlarged version of what the "printed" version will be. We'll also need a DPI setting to translate from paper coords to pixel coords.
- Better system for editing objects with a GUI -- this will require a bit of refactoring of GUIModes and the hit-testing.
Better MVC -- maybe a document object that the DrawObjects are on, rather than directly on the canvas
- Layers -- maybe "virtual", maybe separate buffers -- note this was way too slow a few years back, but maybe with alpha blending built in,it will be fast enough.
- Add a Graticule -- lines for the axis objects. lat-long lines, etc. This may require a way to draw things relative to the viewport like text at the edge of the window.
more modular -- DrawObjects in their own module, for instance
re-factor of hit-test code -- I think each DrawObject should keep a dict of events to bindings -- rather than that ll being in the Canvas.
- clean up of the Main Draw method -- it's pretty ugly!
- Graphics Context -- this is needed for alpha blending and anti-aliased drawing. There are concerns about performance, so we may need to have to optional, but I'd much rather just commit to it -- it would be cleaner. Ideas about that:
- use built in transforms -- it can only do linear transforms, so it can't completely replace projections, but it should be able to do the shifting and scaling from projected to pixel coords.
- Cache paths. It apparently is expensive to create new paths, so you don't want to do that with every draw. Paths would be in projected coords.
- change the bounding box testing -- rather simply loop through the objects and have them decide if they need to be drawn -- using a boundingbox test, or something else. This could be a bit slower, but it's more flexible. A grid object, for instance, has no boundingbox, it's always drawn, and scaled to fit the viewport.