On Wed, May 20, 2009 at 11:55 AM, Ryan May <rma...@gmail.com> wrote: > On Wed, May 20, 2009 at 11:38 AM, Ryan May <rma...@gmail.com> wrote: >> >> Hi, >> >> In looking over a test failure, I'm seeing some behavior that doesn't make >> sense to me. It looks like data passed to a line object is being improperly >> converted when units are involved. Here's a version of the code in the test >> script, but modified to use the units in basic_units.py (in the >> examples/units directory). You should be able to just drop this script into >> the examples/units directory and run it: > > It looks like revision 7020 broke this in the process of adding units > support for fill(). > > If I change the following lines (in the _xy_from_xy() function): > > if bx: > x = self.axes.convert_xunits(x) > if by: > y = self.axes.convert_yunits(y) > > back to: > > if bx or by: return x, y, False > > the example I posted works and the test failure I was seeing is gone. Of > course, this breaks fill() with unit-ed quantities. I'm getting a little > over my head here in terms of tracing the flow of units, so I'd love to hear > opinions on how to actually fix this. IMHO, we *really* need to standardize > on how units are handled. In some cases the axes method handles converting > units, but in this case, the Line2D object also registers for changes to > axis units so it can update itself.
The fundamental problem here is that some artists (Line2D) have support for storing original unitized data (_xorig, _yorig) and handling the conversion on unit change internally with the callback, and some artists (eg Patches) do not . axes._process_plot_var_arg subserves both plot (Line2D) and fill (Polygon), one of which is expecting possibly unitized data and one which is only capable of handling already converted data. Hence the fix one problem, create another bind we are now in. So yes, we need a standard. I think the resolution might be in having intermediate higher level container plot item objects (an ErrorBar, LintPlot, FilledRegion) which store the original data, manage the units, and pass converted data back to the primitives. This is obviously a major refactoring, and would require some thought, but may be the best way to go. Handling the conversions in the plotting functions (eg fill, errorbar) is probably not the right way because there is no obvious way to support unit changes (eg inches to cm) since the data is already converted, the artists already created. Having the artist primitives store the original, possibly unitized data, and register callbacks for unit changes can work, but the problem is how to create the artist primitives in such a way the unit data is passed through correctly. The problem here is that some operations don't make sense for certain unit types -- think addition with datetimes. Some functions, eg bar or errorbar, which need to do a lot of arithmetic on the input arrays, may want to do: xmid = 0.5*(x[1:] + x[:-1]) which would not work for x if x is datetime (can't add two dates). distance and scaling should always be well defined, so one should be able to do: xmid = x[1:] + 0.5*(x[1:]-x[:-1]) So one solution is to require all plotting functions to respect the "no addition" rule, ie define the set of operations that are allowed for plotting functions, and all artists to handle original unitized data with internal conversion. This is a fair amount of work at the plotting function layer, is invasive to the artist primitives, and requires the extra storage at the artist layer, but could work. The other solution, what I referred to as the intermediate plot item container, is to have a class ErrorBar, eg, which is like the errorbar method, but has an API like class ErrorBar: def __init__(self, all the errorbar args, possibly unitized): self.store_all_original_data_here() self.store_all_primitives_from_converted_data_here() def callback(): self.update_all_stored_primitives_newly_converted_original_data() self.connect_callback_to_unit_change(callback) This has the advantage that the plot item container class can always work with arrays of floats (removing the onerous restriction on what kind of binary relations are allowed) and removes the restrictions on creating artists which are unit aware. It also makes for a nicer API: eb = ErrorBar(something) eb.draw() # hmm, the cap widths are too small eb.capwidth = 12 eb.draw() ie, instead of getting back a bunch of artist primitives from errorbar which may be difficult to manipulate, you get back an ErrorBar object that knows how to update and plot itself. With traits or properties so that the eb.capwidth attr setting triggers a unitized updating of primitives, then everything is fairly transparent to the user. It would also make it easier support containers of artists for logical groupings during animation, zorder buffering/blitting, etc. JDH ------------------------------------------------------------------------------ Crystal Reports - New Free Runtime and 30 Day Trial Check out the new simplified licensing option that enables unlimited royalty-free distribution of the report engine for externally facing server and web deployment. http://p.sf.net/sfu/businessobjects _______________________________________________ Matplotlib-devel mailing list Matplotlib-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/matplotlib-devel