简体   繁体   中英

How to properly remove graph from Tkinter frame

I have several graphs that I want to display on a button press. To do that, I make the plt.Figure and FigureCanvasTkAgg variables global, and just erase the axis each time on button press with ax.clear() . After that, i added a feature using Figure.canvas.mpl_connect : when I press on the graph area, the second point is highlighted with a vertical line. When I print some simple output (in my case, print('Vertical Line Constructed') in the select_trade_with_mouse function), it turns out that the event is created for each graph that was constructed and erased: when I generate a graph and press on graph (that generates event), then the print('Vertical Line Constructed') is executed just once. If I generate the second graph and click on graph, then the print('Vertical Line Constructed') is executed twice. Looks like the old graphs aren't destroyed, and the click on graph generates an event for each graph in memory, even if it is not displayed. I tried plt.cla() , plt.clf() and plt.close() , but none of them completely removes the graph.

My question is, how can I properly remove the old graphs? So that a click on graph will generate only one event?

My code:

import pandas as pd
import tkinter as tk
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg


def select_trade_with_mouse(event, ax, graph, data):
    global vertical_line_trading
    print('Vertical Line Constructed')
    if vertical_line_trading:
        vertical_line_trading.remove()
        del vertical_line_trading
    vertical_line_trading = ax.axvline(data, color='b')
    graph.draw()

def construct_graph(change_by = 0):  
    global data
    global graph_index
    print('Graph Constructed')
    graph_index = graph_index + change_by
    ax.clear()
    #plt.close('all')
    line = data[graph_index].plot(x='x', y='y', linestyle='-', ax=ax, linewidth=1, color='y')
    click_id = figure.canvas.mpl_connect('button_press_event', lambda event: select_trade_with_mouse(event, ax, graph, data[graph_index].loc[1, 'x']))    
    graph.draw()


if __name__ == '__main__':

    vertical_line_trading = None
    graph_index = 0

    ## Some random data
    data = []
    for i in range(10):
        data.append(pd.DataFrame({'x': [1+i, 2+i, 3+2*i], 'y': [1+2*i, 2+i, 3+i]}))

    root = tk.Tk()
    main_frame = tk.Frame(root, height=600, width=1200)
    graph_frame = tk.Frame(main_frame, height=500, width=1200)

    ## Graph
    figure = plt.Figure(figsize=(10,10), dpi=100)
    ax = figure.add_subplot(111)
    graph = FigureCanvasTkAgg(figure, graph_frame)
    graph.get_tk_widget().pack(side=tk.LEFT)

    ## Buttons to switch between graphs
    prev_graph_button = tk.Button(graph_frame, command = lambda: construct_graph(-1), height=10, width=10, text='Prev\nGraph')
    next_graph_button = tk.Button(graph_frame, command = lambda: construct_graph(1), height=10, width=10, text='Next\nGraph')
    prev_graph_button.pack(side=tk.LEFT)
    next_graph_button.pack(side=tk.LEFT)

    for frame in [main_frame, graph_frame]:
        frame.pack(side=tk.BOTTOM, fill='both')
        frame.pack_propagate(0)

    root.mainloop()

You don't have to remove graph. You have to use mpl_connect() only once at start, and not again and again when you clear graph.

Or you have to keep click_id in global variable and use mpl_disconnect(click_id) before click_id = mpl_connect()

I create global variable click_id = None at start and later in construct_graph() I remove previous click_id if it is not None . This way graph has assigned only one function

import pandas as pd
import tkinter as tk
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg


def select_trade_with_mouse(event, ax, graph, data):
    global vertical_line_trading
    print('Vertical Line Constructed')
    if vertical_line_trading:
        vertical_line_trading.remove()
        del vertical_line_trading
    vertical_line_trading = ax.axvline(data, color='b')
    graph.draw()

def construct_graph(change_by = 0):  
    global data
    global graph_index
    global click_id 

    print('Graph Constructed')
    graph_index = graph_index + change_by
    ax.clear()
    #plt.close('all')
    line = data[graph_index].plot(x='x', y='y', linestyle='-', ax=ax, linewidth=1, color='y')

    # if function exist then remove it
    if click_id: # if click_id is not None:
        figure.canvas.mpl_disconnect(click_id)

    click_id = figure.canvas.mpl_connect('button_press_event', lambda event: select_trade_with_mouse(event, ax, graph, data[graph_index].loc[1, 'x']))   

    graph.draw()


if __name__ == '__main__':

    click_id = None # create global varaible with default value at start

    vertical_line_trading = None
    graph_index = 0

    ## Some random data
    data = []
    for i in range(10):
        data.append(pd.DataFrame({'x': [1+i, 2+i, 3+2*i], 'y': [1+2*i, 2+i, 3+i]}))

    root = tk.Tk()
    main_frame = tk.Frame(root, height=600, width=1200)
    graph_frame = tk.Frame(main_frame, height=500, width=1200)

    ## Graph
    figure = plt.Figure(figsize=(10,10), dpi=100)
    ax = figure.add_subplot(111)
    graph = FigureCanvasTkAgg(figure, graph_frame)
    graph.get_tk_widget().pack(side=tk.LEFT)

    ## Buttons to switch between graphs
    prev_graph_button = tk.Button(graph_frame, command = lambda: construct_graph(-1), height=10, width=10, text='Prev\nGraph')
    next_graph_button = tk.Button(graph_frame, command = lambda: construct_graph(1), height=10, width=10, text='Next\nGraph')
    prev_graph_button.pack(side=tk.LEFT)
    next_graph_button.pack(side=tk.LEFT)

    for frame in [main_frame, graph_frame]:
        frame.pack(side=tk.BOTTOM, fill='both')
        frame.pack_propagate(0)

    root.mainloop()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM