Péter Leéh
Péter Leéh

Reputation: 2119

How can I add functionality to a user defined button on matplotlib toolbar?

I have a class which sets up a matplotlib figure with the given data and by default it records clicks on the plot. It records every mouseclick, so zooming always add a new point. To prevent that I want to create a toggle button on the mpl toolbar to enable and disable click recording. Here is the code:

import numpy as np
import matplotlib
matplotlib.rcParams["toolbar"] = "toolmanager"
import matplotlib.pyplot as plt
from matplotlib.backend_tools import ToolToggleBase
from matplotlib.backend_bases import MouseButton

class EditPeak(object):
    def __init__(self, x, y, x_extremal=None, y_extremal=None):
        # setting up the plot
        self.x = x
        self.y = y
        self.cid = None
        self.figure = plt.figure()
        self.press()
        plt.plot(self.x, self.y, 'r')
        self.x_extremal = x_extremal
        self.y_extremal = y_extremal
        if not len(self.x_extremal) == len(self.y_extremal):
            raise ValueError('Data shapes are different.')
        self.lins = plt.plot(self.x_extremal, self.y_extremal, 'ko', markersize=6, zorder=99)
        plt.grid(alpha=0.7)
        # adding the button
        tm = self.figure.canvas.manager.toolmanager
        tm.add_tool('Toggle recording', SelectButton)
        self.figure.canvas.manager.toolbar.add_tool(tm.get_tool('Toggle recording'), "toolgroup")
        plt.show()


    def on_clicked(self, event):
            """ Function to record and discard points on plot."""
            ix, iy = event.xdata, event.ydata
            if event.button is MouseButton.RIGHT:
                ix, iy, idx = get_closest(ix, self.x_extremal, self.y_extremal)
                self.x_extremal = np.delete(self.x_extremal, idx)
                self.y_extremal = np.delete(self.y_extremal, idx)
            elif event.button is MouseButton.LEFT:
                ix, iy, idx = get_closest(ix, self.x, self.y)
                self.x_extremal = np.append(self.x_extremal, ix)
                self.y_extremal = np.append(self.y_extremal, iy)
            else:
                pass
            plt.cla()
            plt.plot(self.x, self.y, 'r')
            self.lins = plt.plot(self.x_extremal, self.y_extremal, 'ko', markersize=6, zorder=99)
            plt.grid(alpha=0.7)
            plt.draw()
            return

    def press(self):
            self.cid = self.figure.canvas.mpl_connect('button_press_event', self.on_clicked)

    def release(self):
            self.figure.canvas.mpl_disconnect(self.cid)

Where get_closest is the following:

def get_closest(x_value, x_array, y_array):
    """Finds the closest point in a graph to a given x_value, where distance is 
       measured with respect to x.
     """
    idx = (np.abs(x_array - x_value)).argmin()
    value = x_array[idx]
    return value, y_array[idx], idx

This is the button on the toolbar.

class SelectButton(ToolToggleBase):
    default_toggled = True

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def enable(self, event):
        pass

    def disable(self, event):
        pass

I would like to activate a function when it's triggered, otherwise disable. My problem is that the enable and disable functions are defined in EditPeak class and I couldn't link them to the SelectButton class.

The easiest way you can try it (even though it doesn't really make sense with random values):

x = np.random.normal(0,1,100)
y = np.random.normal(0,1,100)
EditPeak(x, y, x[::2], y[::2])

Left click will add a new point to the closest x value in the given graph, right clicks will delete the closest.

My goal is to call EditPeak.press when the toggle button is on, and call EditPeak.release otherwise. Any improvement in the code (including style, structure) is appreciated.

Upvotes: 3

Views: 1249

Answers (1)

Jan Kuiken
Jan Kuiken

Reputation: 2010

Right after:

tm.add_tool('Toggle recording', SelectButton)

you can do something like:

self.my_select_button = tm.get_tool('Toggle recording')

Now the EditPeak instance has a reference to the SelectButton instance created by add_tool that can be used in EditPeak.on_clicked. (The remainder is left as an exercise for the reader :-)

Upvotes: 1

Related Questions