Search code examples
pythonmatplotlibtkintercursor

Vertical cursor following mouse on multiple subplots


I am coding for personal use a small script which is intended to get in shape some data records to be able to analyze them. The idea is to choose which data I want to see then check them and they will be displayed on multiple sublots. I am using Python with tkinter and matplotlib.

The problem I have curently is that I want to have a vertical cursor following the mouse movement and I want this cursor to be the same on all displayed subplots. I'm trying to use "MultiCursor" from matplotlib but it doesn't work and I have no error. I think the problem is that I give as parameter to MultiCursor a list of subplots, but in my code I don't see how to do it by an other way.

Any idea or remark would be appreciated.

Minimal Reproductible Code:

import tkinter as tk
from matplotlib.widgets import MultiCursor
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

w_main = tk.Tk()
w_main.geometry("1080x720")
f_leftFrame = tk.Frame(w_main)
f_leftFrame.pack(side=tk.LEFT, anchor='nw', fill=tk.BOTH)
f_graphFrame = tk.Frame(w_main)
f_graphFrame.pack(fill=tk.BOTH, expand=1)
fig = Figure(figsize=(5, 5), dpi=100)
graph = FigureCanvasTkAgg(fig, master=f_graphFrame)
graph.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)

time = [0, 0.150, 0.3, 0.450, 0.6, 0.750, 0.9]
record1 = [0, 5, 3, 6, 2, 8, 9]
record2 = [1, 6, 8, 5, 7, 2, 5]
record3 = [9, 7, 5, 3, 0, 0, 2]
labels = ["record1", "record2", "record3"]
dic_state_cb = {}
test_list = []

def curveBuilder():
    dic_state_cb = {}
    sel_curves = []
    global test_list
    for i in labels:
        dic_state_cb["state_{}".format(i)] = globals()["state_{}".format(i)].get()
        if dic_state_cb["state_{}".format(i)] == 1:
            sel_curves.append(i)
    for i in range(len(labels)):
        try:
            globals()["ax{}".format(i)].remove()
            test_list = []
        except:
            pass
    for i in range(len(sel_curves)):
        globals()["ax{}".format(i)] = fig.add_subplot(int("{}1{}".format(len(sel_curves), i+1)))
        test_list.append(globals()["ax{}".format(i)])
    x = time
    if len(sel_curves) > 0:
        for i in range(len(sel_curves)):
            curve = globals()[sel_curves[i]]
            test_list[i].plot(x, curve)
            test_list[i].set_ylabel(sel_curves[i])
            if i < len(sel_curves) - 1:
                x_axis = test_list[i].axes.get_xaxis()
                x_axis.set_visible(False)
        multi = MultiCursor(graph, test_list, color="'r'", lw=2.0, vertOn=True)
    graph.draw()

for i in enumerate(labels):
    globals()["state_{}".format(i[1])] = tk.IntVar()
    cb = tk.Checkbutton(f_leftFrame, text=i[1], variable=globals()["state_{}".format(i[1])], command=curveBuilder,
                        anchor=tk.W)
    cb.pack(fill=tk.BOTH)

tk.mainloop()

Solution

  • Finally I have just found the answer by myself. The problem comes from the variable which contains the MultiCursor call's result which was local to the function in my code. If I declare it as a global variable, then it works fine.

    I am not sure to understand why but it solved the problem. I hope someone who knows why it hapens will let here the answer.

    Here the working code:

    import tkinter as tk
    from matplotlib.widgets import MultiCursor
    from matplotlib.figure import Figure
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
    
    w_main = tk.Tk()
    w_main.geometry("1080x720")
    f_leftFrame = tk.Frame(w_main)
    f_leftFrame.pack(side=tk.LEFT, anchor='nw', fill=tk.BOTH)
    f_graphFrame = tk.Frame(w_main)
    f_graphFrame.pack(fill=tk.BOTH, expand=1)
    fig = Figure(figsize=(5, 5), dpi=100)
    graph = FigureCanvasTkAgg(fig, master=f_graphFrame)
    graph.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)
    
    time = [0, 0.150, 0.3, 0.450, 0.6, 0.750, 0.9]
    record1 = [0, 5, 3, 6, 2, 8, 9]
    record2 = [1, 6, 8, 5, 7, 2, 5]
    record3 = [9, 7, 5, 3, 0, 0, 2]
    labels = ["record1", "record2", "record3"]
    dic_state_cb = {}
    test_list = []
    
    def curveBuilder():
        dic_state_cb = {}
        sel_curves = []
        global test_list
        for i in labels:
            dic_state_cb["state_{}".format(i)] = globals()["state_{}".format(i)].get()
            if dic_state_cb["state_{}".format(i)] == 1:
                sel_curves.append(i)
        for i in range(len(labels)):
            try:
                globals()["ax{}".format(i)].remove()
                test_list = []
            except:
                pass
        for i in range(len(sel_curves)):
            globals()["ax{}".format(i)] = fig.add_subplot(int("{}1{}".format(len(sel_curves), i+1)))
            test_list.append(globals()["ax{}".format(i)])
        x = time
        if len(sel_curves) > 0:
            for i in range(len(sel_curves)):
                curve = globals()[sel_curves[i]]
                test_list[i].plot(x, curve)
                test_list[i].set_ylabel(sel_curves[i])
                if i < len(sel_curves) - 1:
                    x_axis = test_list[i].axes.get_xaxis()
                    x_axis.set_visible(False)
            global multi
            multi = MultiCursor(graph, test_list, color='r', lw=2.0, vertOn=True)
        graph.draw()
    
    for i in enumerate(labels):
        globals()["state_{}".format(i[1])] = tk.IntVar()
        cb = tk.Checkbutton(f_leftFrame, text=i[1], variable=globals()["state_{}".format(i[1])], command=curveBuilder,
                            anchor=tk.W)
        cb.pack(fill=tk.BOTH)
    
    tk.mainloop()