Plots with two scales for the y axis

Hello, I tried to improve the way of plotting two scales

    > (cf examples/two_scales.py). The attached patch contains
    > the following changes:

Hi Baptiste -- I applied your patch and am very impressed. It takes a
lot of hacking through matplotlib internals to get everything working
right together with transform, axis limits and the like. Your
approach is also a significant improvement over what we currently
have.

Here is what I think would be ideal, and I wanted to sketch some of
these ideas in hopes that you might have some ideas on how to apply
them. Basically, the idea is that we want one axes to be able to
share the x or y limits with another axes in a more general way. Your
approach works fine as long as the two axes are overlayed. It would
be nice if we could do something like

    # separate axes
    ax1 = subplot(211)
    plot([1,2,3])

    ax2 = subplot(212, twinx=ax1)
    plot([4,5,6])

To do overlayed axes, you could do

    # overalyed axes
    ax1 = subplot(111)
    plot([1,2,3])

    ax2 = subplot(111, twinx=ax1, yticks='right')
    plot([4,5,6])

I think this would be a nice consistent interface and in both cases
ax2 would share the xlim with ax1. As far as I can see, the only
thing getting in the way of extending your approach to handle this
case are the tick labels, since they would be in the wrong place for
ax2 in the separate axes case. For the separate axes case, you would
typically want tick labeling on the lower axes, but it would be ideal
to be able to control that as well.

I've been meaning to decouple the axis line and tick locations from
the main axes frame so that you could have offset labels and ticks.
Perhaps this would be a good time to make both changes together.

The other problem in the current implementation (and in your patch)
stems from the fact that for event handling, only one axes gets the
event. So in your two_scales.py example, if you pan/zoom the xlimits
behave correctly but only one axes gets the pan/zoom event for y. It
would be nice to pass these events on to all the axes the user is over
to handle the case of overlapping axes.

Just some thoughts on what I think the proper behavior should be. If
you have any ideas on how to handle these, let me know.

I just applied your two scales patch to CVS as an improved interim
solution.

Thanks!
JDH

John Hunter a écrit :

Here is what I think would be ideal, and I wanted to sketch some of
these ideas in hopes that you might have some ideas on how to apply
them. Basically, the idea is that we want one axes to be able to
share the x or y limits with another axes in a more general way. Your
approach works fine as long as the two axes are overlayed. It would
be nice if we could do something like

    # separate axes
    ax1 = subplot(211)
    plot([1,2,3])

    ax2 = subplot(212, twinx=ax1)
    plot([4,5,6])

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.

To do overlayed axes, you could do

    # overalyed axes
    ax1 = subplot(111)
    plot([1,2,3])

    ax2 = subplot(111, twinx=ax1, yticks='right')
    plot([4,5,6])

OK, you also have to set the frame to off, we'll see the details later. Maybe we can keep just the twin function in pylab.py as a shortcut.

I think this would be a nice consistent interface and in both cases
ax2 would share the xlim with ax1. As far as I can see, the only
thing getting in the way of extending your approach to handle this
case are the tick labels, since they would be in the wrong place for
ax2 in the separate axes case. For the separate axes case, you would
typically want tick labeling on the lower axes, but it would be ideal
to be able to control that as well.

I've been meaning to decouple the axis line and tick locations from
the main axes frame so that you could have offset labels and ticks.
Perhaps this would be a good time to make both changes together.

That one is more tricky. All of matplotlib drawing model is based on the premise that one objet draws at one given place. This has many advantages. It allows the object to draw itself, which is good design. It is also necessary for the cases where the final size matters (fonts, linewidth). I don't think we want to change that.

< I stopped here, thought about it for some time, went back later >

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.

The other problem in the current implementation (and in your patch)
stems from the fact that for event handling, only one axes gets the
event. So in your two_scales.py example, if you pan/zoom the xlimits
behave correctly but only one axes gets the pan/zoom event for y. It
would be nice to pass these events on to all the axes the user is over
to handle the case of overlapping axes.

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[0], or maybe the most recently used axes (more difficult, but maybe more consistent...we'll see at implementation time).

Just some thoughts on what I think the proper behavior should be. If
you have any ideas on how to handle these, let me know.

well, we need to discuss that a little bit more, so we'll get a clearer view of where we go :slight_smile:

Cheers,
Baptiste