1

I have a question, which may not be even related to the modules.

In the code below, there is a function update, which will create a canvas by matplotlib and assign it to the related frame from tkinter. Then it create an event handler Cursor, which should print the position of the mouse into the console. But it does not. However, everything works just fine, if you delete method update and use the lines for creating figure, cursor and connection just in the body of the module.

What am I missing? I guess it is a basic knowledge of Python, the visibility and passing the right instance, I don't know.

import matplotlib.pyplot as plt
from tkinter import *
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

class Cursor (object):
    def __init__ (self, fig):
        self.fig = fig
        print ("initializing")

    def mouse_move (self, event):
        if not event.inaxes:
            return 
        x, y = event.xdata, event.ydata
        print (x, y)

    def connect (self):
        print ("Connecting")
        self.conmove = self.fig.canvas.mpl_connect ('motion_notify_event', self.mouse_move)

def pyplot_f ():
    fig = plt.figure(figsize=(6,4), dpi=100)
    axes = fig.add_subplot (111)
    axes.plot([1,2,3,4], [1,2,3,4])

    Canvas = FigureCanvasTkAgg (fig, master=frame_output)

    canvas = Canvas.get_tk_widget()
    canvas.grid(row=0,column=0, padx=5, pady=5, sticky="nesw")

    return fig

w_width = 1000
w_height = 600
root = Tk()
root.resizable(0,0)

frame_output = Frame (root, bg="", relief=SUNKEN, width=w_width*0.8, height=w_height*0.9)
frame_output.grid(row=0, column=0, padx=20, pady=20, sticky=W+N+E+S)
frame_input = Frame (root, bg="", relief=RAISED,width=w_width*0.2, height=w_height*0.9)
frame_input.grid(row=0, column=1, padx=20, pady=20, sticky=W+N+E+S)

def update ():
    fig = pyplot_f()
    cursor = Cursor(fig)
    cursor.connect()

def on_closing():
    print ("Exiting")
    root.quit()

update()
root.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()

1 Answer 1

2

Your problem appears to be one about variable scope and lifetime.

When your update() function ends, the variables fig and cursor declared within it go out of scope. The figure and cursor objects created within your update() have no further references pointing to them, and so they end up being garbage-collected. Your update() function is successfully creating the figure and the cursor, but it isn't preventing them from being deleted again.

When you move the three lines within update() to the body of the module, the variables fig and cursor then remain in scope and don't get garbage-collected until the program ends. Hence your figure and cursor are created and not immediately garbage-collected.

The simplest way to fix this is for the update() function to return the cursor, and then keep that in module scope:

def update ():
    fig = pyplot_f()
    cursor = Cursor(fig)
    cursor.connect()
    return cursor

# ...

cursor = update()

This prevents the cursor from being garbage-collected, and as the cursor has a reference to the figure, the figure won't get garbage-collected either.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks! However it wont solve the problem. I have a button wich should call an update function, which will create a new layout, and thus I need to recreate cursor. In the case with button the cursor will be destroyed
Ok, the problem is indeed in the scope. I found a way around it. I changed class a little, now it links to the figure in the method connect. init method stays empty (pass). And I create an instance of Cursor in the main module, thus it is always exist and does not collected by GC.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.