Zooming with the mouse

Hello all! I just recently downloaded Matplotlib and

    > love it so far :slight_smile:

Thanks!

    > A hopefully quick question. In all the demo examples, the
    > navigation bar has buttons for incremental +/- zoom in
    > the X/Y directions, but no way to directly choose a
    > rectange within the figure window to zoom to like in
    > matlab. It gets very tedious the way it is if a lot of
    > zooming and panning is involved.

    > I found that I could draw a rectange in the axes and
    > update its coordinates on an motion_notify_event. The
    > problem is I do not know how to link the
    > event.get_pointer() coordinates to the coordinates within
    > the axes. Therefore I cannot set the coordinates of the
    > rectangle correctly. The following code which is slightly
    > modified from embed_gtk2.py demonstrates what I am trying
    > to do...

Yes, this would be a very nive feature. When you implement it, please
send it back to the list. Here is an example that shows you how to
connect to the events and more importantly for you, convert to user
coords

"""
An example of how to interact with the plotting canvas by connecting
to move and click events
"""
from matplotlib.matlab import *

t = arange(0.0, 1.0, 0.01)
s = sin(2*pi*t)
ax = subplot(111)
ax.plot(t,s)

canvas = get_current_fig_manager().canvas

def on_move(widget, event):
    # get the x and y coords, flip y from top to bottom
    height = canvas.figure.bbox.y.interval()
    x, y = event.x, height-event.y

    if ax.in_axes(x, y):
        # transData transforms data coords to display coords. Use the
        # inverse method to transform back
        t = ax.xaxis.transData.inverse_positions(x)
        val = ax.yaxis.transData.inverse_positions(y)
        print t, val

def on_click(widget, event):
    # get the x and y coords, flip y from top to bottom
    height = canvas.figure.bbox.y.interval()
    x, y = event.x, height-event.y
    if event.button==1:
        if ax.in_axes(x, y):
            # transData transforms data coords to display coords. Use the
            # inverse method to transform back
            t = ax.xaxis.transData.inverse_positions(x)
            val = ax.yaxis.transData.inverse_positions(y)
        
            print t, val

#canvas.connect('motion_notify_event', on_move)
canvas.connect('button_press_event', on_click)

show()

Here's a very preliminary implementation. Its not really very elegant,
but it seems to work. A couple of questions. Is there a remove_line or
equivalent for axes? As of now, I make the rectangle which shows the
zoom window dissapear by setting its xdata and ydata to [], but it might
be elegant to just remove the line from the axis completely. Also I seem
to trigger a matplotlib bug if I toggle the zoom button and then start
the button press event outside the axes.

I have yet to delve into the matplotlib source itself and maybe I am not
the best person to do it. The best place to add this would be to extend
the axes class with a .zoom() method which would work like the
ZoomButtonPressed() function below (It basically makes the next button
press trigger the ZoomStart() function which in turn calls
ZoomContinue() on the "motion_notify_event"). ZoomStart() will have to
be modified to account for more than one axes belonging to a figure.
We'll have to check whether the user presses the mouse in the axes whos
.zoom() function has been called.

Thanks again for all the help :slight_smile: I think I am almost ready to get things
working with matplotlib.

···

On Fri, 16 Apr 2004, John Hunter wrote:

Yes, this would be a very nive feature. When you implement it, please
send it back to the list. Here is an example that shows you how to
connect to the events and more importantly for you, convert to user
coords

--
Srinath

from matplotlib.numerix import arange, sin, cos, pi

import matplotlib
matplotlib.use('GTK')
from matplotlib.backends.backend_gtk import FigureCanvasGTK

from matplotlib.axes import Subplot
from matplotlib.figure import Figure
import gtk

class MyApp:
    def __init__(self):
        self.win = gtk.Window()
        self.win.set_name("Embedding in GTK")
        self.win.connect("destroy", gtk.mainquit)
        self.win.set_border_width(5)

        self.vbox = gtk.VBox(spacing=3)
        self.win.add(self.vbox)
        self.vbox.show()

        self.fig = Figure(figsize=(5,4), dpi=100)
        self.ax = Subplot(self.fig, 111)

        self.ax.plot([0,1], [0,1])
        self.fig.add_axis(self.ax)

        self.canvas = FigureCanvasGTK(self.fig) # a gtk.DrawingArea
        self.canvas.show()
        self.canvas.connect('motion_notify_event', self.setAxisCursorLocation)
        self.vbox.pack_start(self.canvas)

        buttonZoom = gtk.ToggleButton('Toggle Zoom')
        buttonZoom.connect('button_press_event', self.ZoomButtonPressed)
        buttonZoom.show()
        self.vbox.pack_start(buttonZoom)

        self.win.show()

    def setAxisCursorLocation(self, widget, event):
        height = self.canvas.figure.bbox.y.interval()
        x, y = event.x, height-event.y

        t = self.ax.xaxis.transData.inverse_positions(x)
        val = self.ax.yaxis.transData.inverse_positions(y)

        self.cursorLocation = (t, val)

    def getAxisCursorLocation(self):
        if not hasattr(self, 'cursorLocation'):
            return None

        return self.cursorLocation

    def ZoomButtonPressed(self, widget, event):
        if not widget.get_active():
            self.zoomStartEventHandle = self.canvas.connect('button_press_event', self.ZoomStart)
        else:
            if hasattr(self, 'zoomStartEventHandle'):
                self.canvas.disconnect(self.zoomStartEventHandle)

    def ZoomStart(self, widget, event):
        self.zoomStartPosition = self.getAxisCursorLocation()
        x, y = self.zoomStartPosition
        if hasattr(self, 'zoomRectangle'):
            self.zoomRectangle.set_xdata([x,x,x,x,x])
            self.zoomRectangle.set_ydata([y,y,y,y,y])
        else:
            self.zoomRectangle = self.ax.plot([x,x,x,x,x], [y,y,y,y,y], 'k:')[0]

        self.zoomContinueEventHandle = self.canvas.connect_after('motion_notify_event', self.ZoomContinue)
        self.zoomStopEventHandle = self.canvas.connect('button_release_event', self.ZoomStop)

    def ZoomContinue(self, widget, event):
        xs, ys = self.zoomStartPosition
        x, y = self.getAxisCursorLocation()
        self.zoomRectangle.set_xdata([xs, x, x, xs, xs])
        self.zoomRectangle.set_ydata([ys, ys, y, y, ys])

        self.canvas.draw()

    def ZoomStop(self, widget, event):
        self.canvas.disconnect(self.zoomContinueEventHandle)
        self.canvas.disconnect(self.zoomStopEventHandle)

        self.zoomRectangle.set_xdata([])
        self.zoomRectangle.set_ydata([])

        xs, ys = self.zoomStartPosition
        x, y = self.getAxisCursorLocation()

        self.ax.set_xlim([min([xs,x]), max([xs,x])])
        self.ax.set_ylim([min([ys,y]), max([ys,y])])
        self.canvas.draw()

if __name__=='__main__':
    app = MyApp()
    gtk.mainloop()