Hello, I tried to improve the way of plotting two scales (cf examples/two_scales.py). The attached patch contains the following changes: in axis.py: allow right side label, so both y axes can get one. axis instances get a new property: label_position, which is left or right for yaxis, top or bottom for xaxis. in axes.py: a new Axes subclass (TwinAxes) that shares the x axis with another. Transforms are set accordingly (lazy values for x lims are shared). This avoids having to change the xlim or xlabel (and so on) 2 times. in pylab.py: a new command (twin) that returns the twin of an axes instance (the current one by default), thus making it into a two scales plot. How to use: see the new examples/two_scales.py for the typical use case. Cheers, BC
Hello, I got confused with my two patches. The pylab.py chunk from the other patch actually belongs here. Sorry for that, I'll send a corrected version tomorrow, I need some sleep :) Cheers, BC
Hello, here is the corrected patch, sorry for the noise. Cheers, BC
>>>>> "Baptiste" == Baptiste Carvello <bap...@al...> writes: Baptiste> Hello, I tried to improve the way of plotting two scales Baptiste> (cf examples/two_scales.py). The attached patch contains Baptiste> 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 =E9crit : > 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 >=20 > # separate axes > ax1 =3D subplot(211) > plot([1,2,3]) >=20 > ax2 =3D subplot(212, twinx=3Dax1) > plot([4,5,6]) >=20 That sounds like a good syntax. Its not a problem the share the x/ylims. As long as you can get the lazy=20 values at Axes creation, you can set the transforms correctly. The=20 question is whether or not to share the Axis instance. If you do, you=20 will have trouble drawing, if you don't, you have trouble when you want=20 to change the attributes (see below). I got away with this only because=20 I have to draw only one "physical" axis. > To do overlayed axes, you could do >=20 > # overalyed axes > ax1 =3D subplot(111) > plot([1,2,3]) >=20 > ax2 =3D subplot(111, twinx=3Dax1, yticks=3D'right') > plot([4,5,6]) >=20 OK, you also have to set the frame to off, we'll see the details later.=20 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. >=20 > 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. >=20 That one is more tricky. All of matplotlib drawing model is based on the=20 premise that one objet draws at one given place. This has many=20 advantages. It allows the object to draw itself, which is good design.=20 It is also necessary for the cases where the final size matters (fonts,=20 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=20 between axes: limits, scale (linear/log), fmtdata, label, tick positions=20 (thus a common "tick factory"), tick label texts, viewLim, dataLim (we=20 need to think about update_datalim). and which we do *not* want to share: all graphic objects, visibility of=20 labels (ticks ?), style of ticks and tick labels (you may want smaller=20 fonts in an inset), type of axis (XAxis, YAxis, ThetaAxis, Color, ...,=20 for ex. we may want to plot z(x,y) as pcolor and z(x) as line with=20 shared z lims) Maybe we want to split an axis into 2 objects ? I need to think more=20 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. >=20 This is uncorrelated, and probably easier than the one above. We would=20 need to modify event.inaxes to be a list (in backend_bases), and act=20 upon all those axes in pan/zoom. When only one axes can be used (for ex.=20 the coordinates of the mouse in the status bar), we would use=20 event.inaxes[0], or maybe the most recently used axes (more difficult,=20 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. =20 >=20 well, we need to discuss that a little bit more, so we'll get a clearer=20 view of where we go :-) Cheers, Baptiste
>>>>> "Baptiste" == Baptiste Carvello <bap...@al...> writes: >> Baptiste> That sounds like a good syntax. Its not a problem the Baptiste> share the x/ylims. As long as you can get the lazy Baptiste> values at Axes creation, you can set the transforms Baptiste> correctly. The question is whether or not to share the Baptiste> Axis instance. If you do, you will have trouble drawing, Baptiste> if you don't, you have trouble when you want to change Baptiste> the attributes (see below). I got away with this only Baptiste> because I have to draw only one "physical" axis. Hi Baptiste, 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 with, eg subplot(212, sharex=ax1) 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 Baptiste> I'm starting to wonder which properties of Axis we Baptiste> really want to share between axes: limits, scale Baptiste> (linear/log), fmtdata, label, tick positions (thus a Baptiste> common "tick factory"), tick label texts, viewLim, Baptiste> dataLim (we need to think about update_datalim). Baptiste> and which we do *not* want to share: all graphic Baptiste> objects, visibility of labels (ticks ?), style of ticks Baptiste> and tick labels (you may want smaller fonts in an Baptiste> inset), type of axis (XAxis, YAxis, ThetaAxis, Color, Baptiste> ..., for ex. we may want to plot z(x,y) as pcolor and Baptiste> z(x) as line with shared z lims) Baptiste> Maybe we want to split an axis into 2 objects ? I need Baptiste> 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 set(ax2, xticklabels=[]) because this changes the Formatter which is shared, you can achieve the same effect by setting the visibility property, which is not shared 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. Baptiste> This is uncorrelated, and probably easier than the one Baptiste> above. We would need to modify event.inaxes to be a list Baptiste> (in backend_bases), and act upon all those axes in Baptiste> pan/zoom. When only one axes can be used (for ex. the Baptiste> coordinates of the mouse in the status bar), we would Baptiste> use event.inaxes[0], or maybe the most recently used Baptiste> axes (more difficult, but maybe more consistent...we'll Baptiste> 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. Suggestions? JDH
John Hunter a =E9crit : > 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 > with, eg >=20 > subplot(212, sharex=3Dax1) >=20 > 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. >=20 Hi John, I like this implementation a lot. Cool stuff ! > 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 >=20 you also need to call tick_left on the original axis, but that is a=20 minor correction. > I don't think the datalim need to be shared because they are > responsible only for autoscaling. > There is still a problem because autoscaling is disabled on the second=20 axes. I have an idea on how to solve this, I'll try it and send a patch=20 later in the week. > 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. >=20 This sounds reasonable to me. I don't think it is confusing. If people=20 know what to do with multiple axes, they'll go with axseq, if not=20 inaxes, xdata and ydata will provide the most reasonable choice for them. I'll play a little bit with this, and see if it breaks anything I use. Cheers, BC