Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

matplotlib forcing pan/zoom to constrain to x-axes

Matplotlib has a feature where if you hold down the "x" or "y" keys it constrains panning or zooming to the corresponding axis.

Is there a way to cause this to be the default? For some reason my CPU does not allow touchpad movement when a letter key is held down. And I only want the x-axis to be pan/zoomed, not the y-axis.


edit: found the pan/zoom function at https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/axes.py#L3001

which contains an internal function format_deltas. But I have no idea how to override the Axes class with a subclass when Axes objects are created automatically from Figure objects.

like image 865
Jason S Avatar asked May 23 '13 03:05

Jason S


People also ask

How do you limit the range of values on each of the axes MatPlotLib?

Matplotlib automatically arrives at the minimum and maximum values of variables to be displayed along x, y (and z axis in case of 3D plot) axes of a plot. However, it is possible to set the limits explicitly by using set_xlim() and set_ylim() functions.


2 Answers

It is possible to use your own axes class. In your case you can inherit from matplotlib.axes.Axes and change the drag_pan method to always act as though the 'x' key is being pressed. However the zooming doesn't seem to be defined in that class. The following will only allow x axis panning:

import matplotlib
import matplotlib.pyplot as plt    

class My_Axes(matplotlib.axes.Axes):
    name = "My_Axes"
    def drag_pan(self, button, key, x, y):
        matplotlib.axes.Axes.drag_pan(self, button, 'x', x, y) # pretend key=='x'

matplotlib.projections.register_projection(My_Axes)

figure = plt.figure()
ax = figure.add_subplot(111, projection="My_Axes")
ax.plot([0, 1, 2], [0, 1, 0])
plt.show()

For the zooming, you may have to look at the toolbar control itself. The NavigationToolbar2 class has the drag_zoom method which seems to be what's relevant here, but tracking down how that works is quickly complicated by the fact that the different backends all have their own versions (e.g. NavigationToolbar2TkAgg

edit

You can monkeypatch the desired behaviour in:

import types
def press_zoom(self, event):
    event.key='x'
    matplotlib.backends.backend_tkagg.NavigationToolbar2TkAgg.press_zoom(self,event)
figure.canvas.toolbar.press_zoom=types.MethodType(press_zoom, figure.canvas.toolbar)

You could do it properly and make a subclass of the toolbar, but you have to then create instances of Figure, FigureCanvas and your NavigationToolbar and put them in a Tk app or something. I don't think there's a really straightforward way to just use your own toolbar with the simple plotting interface.

like image 100
simonb Avatar answered Oct 01 '22 01:10

simonb


simonb's approach worked but I had to tweak it for the press_zoom behavior, so that it keeps the press_zoom method a feature of the class, not the instance, but I added a hook to fixup the per-instance behavior.

import types

def constrainXPanZoomBehavior(fig):
    # make sure all figures' toolbars of this class call a postPressZoomHandler()
    def overrideZoomMode(oldZoom, target):
        def newZoom(self, event):
            oldZoom(self, event)
            if hasattr(self, 'postPressZoomHandler'):
                self.postPressZoomHandler()
        return newZoom
    def overrideToolbarZoom(fig, methodname, functransform, *args):
        toolbar = fig.canvas.toolbar
        oldMethod = getattr(toolbar.__class__, methodname)
        newMethod = functransform(oldMethod, toolbar, *args)
        setattr(toolbar.__class__, methodname, newMethod)
    overrideToolbarZoom(fig, 'press_zoom', overrideZoomMode)

    # for this specific instance, override the zoom mode to 'x' always
    def postPressZoomHandler(self):
        self._zoom_mode = 'x'
    fig.canvas.toolbar.postPressZoomHandler = types.MethodType(postPressZoomHandler, fig.canvas.toolbar)
    return fig
like image 26
Jason S Avatar answered Oct 01 '22 02:10

Jason S