views:

144

answers:

1

Suppose I have a figure canvas with 3 plots... 2 are images of the same dimensions plotted with imshow, and the other is some other kind of subplot. I'd like to be able to link the x and y axes of the imshow plots so that when I zoom in one (using the zoom tool provided by the NavigationToolbar), the other zooms to the same coordinates, and when I pan in one, the other pans as well.

Subplot methods such as scatter and histogram can be passed kwargs specifying an axes for sharex and sharey, but imshow has no such configuration.

I started hacking my way around this by subclassing NavigationToolbar2WxAgg (shown below)... but there are several problems here. 1) This will link the axes of all plots in a canvas since all I've done is get rid of the checks for a.in_axes() 2) This worked well for panning, but zooming caused all subplots to zoom from the same global point, rather than from the same point in each of their respective axes.

Can anyone suggest a workaround?

Much thanks! -Adam

from matplotlib.backends.backend_wxagg import NavigationToolbar2WxAgg
class MyNavToolbar(NavigationToolbar2WxAgg):
    def __init__(self, canvas, cpfig):
        NavigationToolbar2WxAgg.__init__(self, canvas)

    # overrided
    # As mentioned in the code below, the only difference here from overridden
    # method is that this one doesn't check a.in_axes(event) when deciding which
    # axes to start the pan in...
    def press_pan(self, event):
        'the press mouse button in pan/zoom mode callback'

        if event.button == 1:
            self._button_pressed=1
        elif  event.button == 3:
            self._button_pressed=3
        else:
            self._button_pressed=None
            return

        x, y = event.x, event.y

        # push the current view to define home if stack is empty
        if self._views.empty(): self.push_current()

        self._xypress=[]
        for i, a in enumerate(self.canvas.figure.get_axes()):
            # only difference from overridden method is that this one doesn't
            # check a.in_axes(event)
            if x is not None and y is not None and a.get_navigate():
                a.start_pan(x, y, event.button)
                self._xypress.append((a, i))
                self.canvas.mpl_disconnect(self._idDrag)
                self._idDrag=self.canvas.mpl_connect('motion_notify_event', self.drag_pan)

    # overrided
    def press_zoom(self, event):
        'the press mouse button in zoom to rect mode callback'
        if event.button == 1:
            self._button_pressed=1
        elif  event.button == 3:
            self._button_pressed=3
        else:
            self._button_pressed=None
            return

        x, y = event.x, event.y

        # push the current view to define home if stack is empty
        if self._views.empty(): self.push_current()

        self._xypress=[]
        for i, a in enumerate(self.canvas.figure.get_axes()):
            # only difference from overridden method is that this one doesn't
            # check a.in_axes(event) 
            if x is not None and y is not None and a.get_navigate() and a.can_zoom():
                self._xypress.append(( x, y, a, i, a.viewLim.frozen(), a.transData.frozen()))

        self.press(event)
+1  A: 

I didn't realize sharex could be passed to add_subplot...

f = plt.figure()
ax1 = f.add_subplot(211)
ax2 = f.add_subplot(212, sharex=ax1, sharey=ax1)
ax1.imshow(np.random.randn(10,10), interpolation='nearest')
ax2.imshow(np.random.randn(10,10), interpolation='nearest')
f.canvas.draw()

however, if you try to do the following to set your axis objects for displaying images.

ax1.axis('image')

you'll get:

ValueError: adjustable must be "datalim" for shared axes

The matplotlib guys tell me I can use

ax1.set_adjustable("box-forced")
ax2.set_adjustable("box-forced")

instead, but it doesn't work for me using matplotlib version 0.98.5.3 ... I assume it would with a newer version. Will update when I hear back.

Adam Fraser