'Tkinter Moving Graph created through GUI - not from the start

Why isn't this graph moving? Or even being created? I've gotten graphs to move in tkinter before, but they would always open up immediately as the GUI opens, but I need the user to be able to choose their own dataset from a file within the GUI.

Thus, I have a function that chooses a file and creates the lists which matplotlib uses, but if I create the

import tkinter
import tkinter.ttk as ttk
from tkinter import filedialog
import time
import math
import serial


from numpy import arrange, sin, pi
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import numpy as np
import matplotlib.pyplot at plt
import matplotlib.animation as animation

global x
global y

def open_file():
    file = filedialog.askopenfile(mode = 'r', filetypes=[('CSV Files', '*.csv'), ('Text Files', '*.txt'), ('All Files', '*.*')])
    if file != None:
        unprocessed_content = file.read()
        unprocessed_content = unprocessed_content.split('\n')
        unprocessed_content.remove('')

        for line in unprocessed_content:
            x.append(float(line.split(',')[0]))
            y.append(float(line.split(',')[1]))

tkTop = tkinter.Tk()

fileOpener = tkinter.Button(tkTop, text = "Open File", command = open_file, height = 4, fg = 'black', bg = 'red', width = 8, bd = 5)
fileOpener.pack()

try:
    x #just to pass over this section and not start the graph if the user hasn't chosen a file yet
    fig = plt.Figure()

    x_current = x[:10]  #I want to only show part of the window at a time, so I thought I would do it in segments of 10 arbitrarily

    def animate(i):
        line.set_ydata(y[i:i+10])
        return line,

    canvas = FigureCanvasTkAgg(fig, master = tkTop)
    canvas.get_tk_widget().pack()

    ax = fig.add_subplot(111)
    line, = ax.plot(x, y[:10])
    ani = animation.FuncAnimation(fig, animate, np.arange(1,200), interval = 25, blit = False)    

except NameError:
    pass

tkTop.mainloop()

I think that's all the relevant code for my problem... so basically what happens is that I open up the GUI, and there's no graph (which is what I want) but then I click on the button to open a file and still nothing happens with the graph?

I kind of assumed that tkTop.mainloop() would run continuously again and again, so that it would continue to update the animation? Is that not right?



Solution 1:[1]

You may have problem for two reasons:


First

You do the worst thing - you use pass in except.

except NameError:
    pass

This way you don't see that you have other mistake which makes all problem.

You should at least display error

except NameError as ex:
    print('ex:', ex)

and you would know what makes all problem.


Second:

global is not for creating global variables. All variables created outside functions/classes are globla. And global is used inside function to inform function that it has to use external/global variable instead of local variable.

Problem is that your graph raise error because x,y don't exist. And you should create them by assigning default value

x = []
y = []

And now it should show graph


EDIT:

Minimal working code.

At start it displays empty plot and run animation (but it has nothing to display).

I added button to generate some fake data - so everyone can test it.

I didn't make one thing: restart animation after loading/generating new data and start at first frame i - so now it uses new y but old x (using old i in animate).

Frankly, it could be good to create animation only when data are loaded/generate, and replace previous animation.

import tkinter as tk
from tkinter import filedialog

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure

import matplotlib.pyplot as plt
import matplotlib.animation as animation

import csv

import random

# --- functions ---  # PEP8: `lower_case_names`

def open_file():
    global x
    global y

    file = filedialog.askopenfile(filetypes=[('CSV Files', '*.csv'),
                                             ('Text Files', '*.txt'),
                                             ('All Files', '*.*')])

    if file:
        print('read file')

        x = []
        y = []
        
        reader_csv = csv.reader(file)
        for row in reader_csv:
            x.append(float(row[0]))
            y.append(float(row[1]))

        if x:
            ax.set_xlim(0, 10)
        if y:
            ax.set_ylim(min(y), max(y))
        
def generate_data():
    global x
    global y
    
    print('generate data')
    
    # fake values
    x = list(range(250))
    y = [random.randint(0, 10) for _ in range(250)]

    # 
    if x:
        ax.set_xlim(0, 10)
    if y:
        ax.set_ylim(min(y), max(y))

def animate(i):
    print('i:', i, x[i:i+10], y[i:i+10])
    
    current_x = x[i:i+10]
    current_y = y[i:i+10]
    
    line.set_xdata(current_x)
    line.set_ydata(current_y)

    if current_x:    
        ax.set_xlim(min(current_x), max(current_x))

    #canvas.draw()
    
    return line,

# --- main ---

# default values at start
x = []
y = []

# - GUI -

tk_top = tk.Tk()

file_opener = tk.Button(tk_top, text="Open File", command=open_file)
file_opener.pack()

data_generator = tk.Button(tk_top, text="Generate Data", command=generate_data)
data_generator.pack()

# - empty plot -

try:
    fig = plt.Figure()

    canvas = FigureCanvasTkAgg(fig, master=tk_top)
    canvas.get_tk_widget().pack()

    ax = fig.add_subplot(111)
    line, = ax.plot([], [])
    
    ani = animation.FuncAnimation(fig, animate, 250, interval=250, blit=False)    

except NameError as ex:
    print(ex)

print('mainloop')
tk_top.mainloop()

enter image description here


EDIT:

Version which doesn't create animation at start (when there is no data) but it create animation after loading/generating data.

I also used this to create buttons STOP, START.

import tkinter as tk
from tkinter import filedialog

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure

import matplotlib.pyplot as plt
import matplotlib.animation as animation

import csv

import random

# --- functions ---  # PEP8: `lower_case_names`

def open_file():
    global x
    global y

    file = filedialog.askopenfile(filetypes=[('CSV Files', '*.csv'),
                                             ('Text Files', '*.txt'),
                                             ('All Files', '*.*')])

    if file:
        print('read file')

        x = []
        y = []
        
        reader_csv = csv.reader(file)
        for row in reader_csv:
            x.append(float(row[0]))
            y.append(float(row[1]))

        if x:
            ax.set_xlim(0, 10)
        if y:
            ax.set_ylim(min(y), max(y))
            
        # stop previous animation
        if ani:
            ani._stop()
        #del ani

        # create new animation    
        ani = animation.FuncAnimation(fig, animate, 250, interval=250, blit=False)    
        ani._start()
        
def generate_data():
    global x
    global y
    global ani
    
    print('generate data')
    
    # fake values
    x = list(range(250))
    y = [random.randint(0, 10) for _ in range(250)]

    # 
    if x:
        ax.set_xlim(0, 10)
    if y:
        ax.set_ylim(min(y), max(y))

    # stop previous animation
    if ani:
        ani._stop()
    #del ani

    # create new animation    
    ani = animation.FuncAnimation(fig, animate, 250, interval=250, blit=False)    
    ani._start()
    
def animate(i):
    print('i:', i, x[i:i+10], y[i:i+10])
    
    current_x = x[i:i+10]
    current_y = y[i:i+10]
    
    line.set_xdata(current_x)
    line.set_ydata(current_y)

    if current_x:    
        ax.set_xlim(min(current_x), max(current_x))

    #canvas.draw()
    
    return line,

def stop_animation():
    if ani:
        # matplotlib 3.4.0+   # https://matplotlib.org/3.4.0/gallery/animation/pause_resume.html
        #ani.pause() 
        # older matplotlib 
        ani.event_source.stop()
        if ani._blit:
            for artist in ani._drawn_artists:
                artist.set_animated(False)

def start_animation():
    if ani:
        # matplotlib 3.4.0+   # https://matplotlib.org/3.4.0/gallery/animation/pause_resume.html
        #ani.resume() 
        # older matplotlib 
        ani.event_source.start()
        if ani._blit:
            for artist in ani._drawn_artists:
                artist.set_animated(True)        
            
# --- main ---

# default values at start
x = []
y = []
ani = None

# - GUI -

tk_top = tk.Tk()

file_opener = tk.Button(tk_top, text="Open File", command=open_file)
file_opener.pack()

data_generator = tk.Button(tk_top, text="Generate Data", command=generate_data)
data_generator.pack()

# - empty plot -

try:
    fig = plt.Figure()

    canvas = FigureCanvasTkAgg(fig, master=tk_top)
    canvas.get_tk_widget().pack()

    ax = fig.add_subplot(111)
    line, = ax.plot([], [])
    
    # don't create animation at start
    #ani = animation.FuncAnimation(fig, animate, 250, interval=250, blit=False)    
    #ani.pause()  # it needs newer matplotlib
    
except NameError as ex:
    print(ex)
    
stop = tk.Button(tk_top, text="Stop", command=stop_animation)
stop.pack()

start = tk.Button(tk_top, text="Start", command=start_animation)
start.pack()

print('mainloop')
tk_top.mainloop()

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1