> That sounds like a good syntax. Its not a problem the
> share the x/ylims. As long as you can get the lazy
> values at Axes creation, you can set the transforms
> correctly. The question is whether or not to share the
> Axis instance. If you do, you will have trouble drawing,
> if you don't, you have trouble when you want to change
> the attributes (see below). I got away with this only
> because I have to draw only one "physical" axis.
After reading your email and studying your patch more, I now see how
to generalize this to the case of non-overlapping axes which share
either the x or y axis, eg ganged plots.
I have added some changes to CVS to support sharex and sharey kwargs
(I thought this was a better name than twinx and twiny). I had to
make some minor changes to axis to support sharing tick locators and
formatters, but nothing substantial. You can now instantiate an axes
and ditto for sharey. The view limits, transform function, and tick
Locator and Formatter are shared. This allows you to pan and zoom on
one axes and have the others follow, which is very nice. There is a
new example showing how to use this example/shared_axis_demo.py.
I was able to remove the TwinAxes class altogether and use the shared
kwargs in its stead, which is cleaner. I preserved the "twin"
convenience function (naming it twinx) and all it does is pass the
proper kwargs and make the calls to tick_right.
examples/two_scales.py is updated
> I'm starting to wonder which properties of Axis we
> really want to share between axes: limits, scale
> (linear/log), fmtdata, label, tick positions (thus a
> common "tick factory"), tick label texts, viewLim,
> dataLim (we need to think about update_datalim).
> and which we do *not* want to share: all graphic
> objects, visibility of labels (ticks ?), style of ticks
> and tick labels (you may want smaller fonts in an
> inset), type of axis (XAxis, YAxis, ThetaAxis, Color,
> ..., for ex. we may want to plot z(x,y) as pcolor and
> z(x) as line with shared z lims)
> Maybe we want to split an axis into 2 objects ? I need
> to think more about this.
I don't think the datalim need to be shared because they are
responsible only for autoscaling. The new approach allows the
different axes to have different properties on their labels, eg font
size as you mentioned. While you cannot turn off labels selectively
on one axes with
because this changes the Formatter which is shared, you can achieve
the same effect by setting the visibility property, which is not
set( ax2.get_xticklabels(), visible=False)
examples/ganged_plots.py, examples/two_scales.py, and
examples/shared_axis_demo.py show off the new features.
Let me know if this design covers the use cases you can think of.
> This is uncorrelated, and probably easier than the one
> above. We would need to modify event.inaxes to be a list
> (in backend_bases), and act upon all those axes in
> pan/zoom. When only one axes can be used (for ex. the
> coordinates of the mouse in the status bar), we would
> use event.inaxes, or maybe the most recently used
> axes (more difficult, but maybe more consistent...we'll
> see at implementation time).
Yes, this needs to be fixed and it is not very hard to do. There are
three event variables affected, inaxes, xdata and ydata, the latter
two give the data coords of the point in the axes the mouse is over.
As you know, the problem is that the current implementation only
registers the first axes and then breaks out of the loop. It would be
easy to fix this but I'm worried about two things: backward
compatibility (not a huge problem since only power users are using
this feature) and ease of use. It is really a corner case to be over
multiple axes, and I am hesitant to force the newbie to deal with this
in the typical case when there are no overlapping axes.
One possibility is to leave inaxes, xdata and ydata alone which
satisfies both problems above. And then to add a new attribute, axseq
which is a list of (ax, xdata, ydata ) tuples to handle the
overlapping axes case. Internally, we could use axseq so that pan/zoom
will be handled properly in the two_scales case. The draw back here
is that having more than one obvious way to do it may also confuse
people down the road. For clarity, the two interfaces I'm discussing are
def callback1(event): # current impl.
if event.inaxes is not None:
do_something with event.inaxes, event.xdata, event.ydata
def callback2(event): # candidate impl.
for ax, xdata, ydata in event.axseq:
do_something with ax, xdata, ydata
I'm weakly inclined to break backward compatibility and go with the
cleaner, comprehensive design in callback2, which on the face of it
doesn't look too hard to use.