Zoom In and Out of the Graph Using the Mouse Wheel in Matplotlib

2024-06-11matplotlib,Python

Introduction

I have enabled zooming in and out of graphs displayed using Python’s matplotlib library by using the mouse wheel. Here are my notes. It would be convenient for tasks that require frequent zooming in and out of graphs.

What I Want to Implement

  1. Scroll the mouse wheel to zoom in and out on the x-axis.
  2. Hold down the Ctrl key and scroll the wheel to zoom in and out on the y-axis.
  3. The zooming center is at the cursor position.

Additionally, in a next article, the functionality to pan the display range has been implemented. Please take a look at this as well.

How to Handle Mouse and Keyboard Inputs

When performing custom mouse and keyboard operations on a graph, the operations triggered by the mouse or keyboard will be received as events. The mpl_connect() function can be used.

mpl_connect()

The usage of mpl_connect() is as follows:

import matplotlib.pyplot as plt
fig, ax = plt.subplots()
fig.canvas.mpl_connect([event-name], [callback-function])

The event name is specified as a string. The following events are defined:

EventDesctiption
'button_press_event'When a mouse button is clicked.
'button_release_event'When a mouse button is released.
'draw_event'When the graph is drawn.
'key_press_event'When the key is pressed.
'key_release_event'When the key is released.
'motion_notify_event'When the cursor moves within the graph.
'pick_event'When a data point is clicked.
'resize_event'When the size of the graph changes.
'scroll_event'When the mouse wheel is scrolled.
'figure_enter_event'When the cursor enters the graph.
'figure_leave_event'When the cursor leaves the graph.
'axes_enter_event'When the cursor enters a specific graph.
'axes_leave_event'When the cursor leaves a specific graph.
'close_event'When the graph is closed.

Example

For example, when the mouse wheel is scrolled, specify 'scroll_event'.

import matplotlib.pyplot as plt

def on_scroll(event):
    if event.button == 'up':
        print("wheel up")
    elif event.button == 'down':
        print("wheel down")

fig, ax = plt.subplots()
fig.canvas.mpl_connect('scroll_event', on_scroll)

The direction of the wheel rotation can be obtained with event.button. Upward direction is 'up', and downward direction is 'down'. In the example above, the direction of rotation is output to the console each time the wheel is scrolled.

Combining Ctrl Key and Mouse Operations

When combining keyboard and wheel operations, it is necessary to also receive keyboard operation events. For this, 'key_press_event' and 'key_release_event' must also be used. To keep the state of the Ctrl key being pressed, it needs to be shared using a global variable.

Example Code

import matplotlib.pyplot as plt

ctrl_flag = False

def on_key_press(event):
    global ctrl_flag

    if event.key == 'control':
        ctrl_flag = True

def on_key_release(event):
    global ctrl_flag

    if event.key == 'control':
        ctrl_flag = False

def on_scroll(event):
    global ctrl_flag

    if event.button == 'up':
        if ctrl_flag:
            print("wheel up(ctrl)")
        else:
            print("wheel up")
    elif event.button == 'down':
        if ctrl_flag:
            print("wheel down(ctrl)")
        else:
            print("wheel down")

fig, ax = plt.subplots()
fig.canvas.mpl_connect('key_press_event', on_key_press)
fig.canvas.mpl_connect('key_release_event', on_key_release)
fig.canvas.mpl_connect('scroll_event', on_scroll)

The pressed key can be obtained with event.key. When the Ctrl key is pressed, set a flag in the global variable, and when it is released, clear the flag.

Implementation of Zooming In and Out Using the Mouse Wheel

Applying the above, let’s implement it. Note that when creating multiple graphs, using global variables can be inconvenient for zooming in and out on specific graphs, so we will implement this using a class.

import matplotlib.pyplot as plt

class MplZoomHelper:
    def __init__(self, ax):
        self.ax = ax
        self.ctrl_flag = False
        self.zoom_rate = 0.9

        ax.figure.canvas.mpl_connect('key_press_event', self.on_key_press)
        ax.figure.canvas.mpl_connect('key_release_event', self.on_key_release)
        ax.figure.canvas.mpl_connect('scroll_event', self.on_scroll)

    def on_key_press(self, event):
        if event.inaxes is self.ax and event.key == 'control':
            self.ctrl_flag = True

    def on_key_release(self, event):
        if event.inaxes is self.ax and event.key == 'control':
            self.ctrl_flag = False

    def on_scroll(self, event):
        if event.inaxes is not self.ax:
            return

        if self.ctrl_flag:
            self.on_scroll_y(event)
        else:
            self.on_scroll_x(event)

        self.ax.figure.canvas.draw()

    def on_scroll_x(self, event):
        x_pos = event.xdata
        min_value, max_value = self.ax.get_xlim()

        if event.button == 'up':
            new_min_value = x_pos - (x_pos - min_value) * self.zoom_rate
            new_max_value = (max_value - x_pos) * self.zoom_rate + x_pos
        elif event.button == 'down':
            new_min_value = x_pos - (x_pos - min_value) / self.zoom_rate
            new_max_value = (max_value - x_pos) / self.zoom_rate + x_pos

        self.ax.set_xlim(new_min_value, new_max_value)

    def on_scroll_y(self, event):
        y_pos = event.ydata
        min_value, max_value = self.ax.get_ylim()

        if event.button == 'up':
            new_min_value = y_pos - (y_pos - min_value) * self.zoom_rate
            new_max_value = (max_value - y_pos) * self.zoom_rate + y_pos
        elif event.button == 'down':
            new_min_value = y_pos - (y_pos - min_value) / self.zoom_rate
            new_max_value = (max_value - y_pos) / self.zoom_rate + y_pos

        self.ax.set_ylim(new_min_value, new_max_value)

if __name__ == "__main__":
    fig, axes = plt.subplots(2, 2)
    axes[0, 0].plot([-1, 2, 3], [2, 4, 3])
    axes[1, 1].plot([0, 5, 9], [-2, 1, 10])

    mzh1 = MplZoomHelper(axes[0, 0])
    mzh2 = MplZoomHelper(axes[1, 1])
    plt.show()

Constructor

Using the constructor of the MplZoomHelper object, register the graph where you want to use the zoom functionality. The 3 member variables, individual graph (self.ax), the state of the Ctrl key (self.ctrl_flag), and the zoom ratio (self.zoom_rate) are initialized in the constructor. Then, register the callback function.

Since the constructor’s argument is an individual graph (Axes object), to register the callback function to receive events, obtain the Figure object from ax.figure.

on_key_press(), on_key_release()

Get and record the state of the Ctrl key. At this time, check event.inaxes to inspect if the event occurred on the registered graph (self.ax).

on_scroll()

Perform zooming in and out with the mouse wheel. Similar to on_key_press() and on_key_release(), check event.inaxes to see if the event occurred on the registered graph (self.ax). Only if the event occurred on the registered graph, perform zooming in and out.

The results of zooming in and out will not be reflected in the graph until it is redrawn. Therefore, execute self.ax.figure.canvas.draw() for that purpose.

on_scroll_x(), on_scroll_y()

The actual zooming takes place. Zooming is performed around the cursor position (event.xdata, event.ydata) with a ratio of self.zoom_rate.

Summary

This is how to implement zooming in and out with the mouse wheel for graphs displayed in matplotlib:

  1. Use mpl_connect() to register a callback function to receive 'scroll_event'.
  2. In the callback function, retrieve the direction of the wheel rotation.
  3. Depending on the direction of the wheel rotation, adjust the values at both ends of the graph.
  4. Redraw the graph to reflect the changes.

matplotlib,python

Posted by izadori