I'm trying to continuously update matlibplots in tkinter GUI while being able to click on buttons to pause/continue/stop updating the plots. I've tried using threads, but they don't seem to be executing parallelly (e.g. data thread is being executed but the plots don't get updated + clicking on buttons is ignored). Why doesn't it work?
# Import Modules
import tkinter as tk
from threading import *
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
from scipy.fft import fft
import numpy as np
import time
import random
# global variables
state = 1 # 0 starting state; 1 streaming; 2 pause; -1 end and save
x = [0]*12
y = [0]*12
# Thread buttons and plots separately
def threading():
state = 1
t_buttons = Thread(target = buttons)
t_plots = Thread(target = plots)
t_data = Thread(target = data)
t_buttons.start()
t_plots.start()
t_data.start()
def hex_to_dec(x, y):
for i in range(0, 12):
for j in range(0, len(y)):
x[i][j] = int(str(x[i][j]), 16)
y[i][j] = int(str(y[i][j]), 16)
def data():
fig1, axs1 = main_plot()
fig2, axs2 = FFT_plot()
# To be replaced with actual Arduino data
while(state!=-1):
for i in range(0, 12):
x[i] = [j for j in range(101)]
y[i] = [random.randint(0, 10) for j in range(-50, 51)]
for i in range(0, 12):
for j in range(0, len(y)):
x[i][j] = int(str(x[i][j]), 16)
y[i][j] = int(str(y[i][j]), 16)
# create buttons
def stream_clicked():
state = 1
print("clicked")
def pause_clicked():
state = 2
print("state")
def finish_clicked():
state = -1
def buttons():
continue_button = tk.Button(window, width = 30, text = "Stream data" ,
fg = "black", bg = '#98FB98', command = stream_clicked)
continue_button.place(x = window.winfo_screenwidth()*0.2, y = 0)
pause_button = tk.Button(window, width = 30, text = "Pause streaming data" ,
fg = "black", bg = '#FFA000', command = pause_clicked)
pause_button.place(x = window.winfo_screenwidth()*0.4, y = 0)
finish_button = tk.Button(window, width = 30, text = "End session and save",
fg = 'black', bg = '#FF4500', command = finish_clicked())
finish_button.place(x = window.winfo_screenwidth()*0.6, y = 0)
def plots():
fig1, axs1 = main_plot()
fig2, axs2 = FFT_plot()
if state==1:
print("update")
for i in range(0, 12):
axs1[i].plot(x[i], y[i], 'blue')
axs1[i].axes.get_yaxis().set_ticks([0], labels = ["channel " + str(i+1)])
axs1[i].grid(True)
axs1[i].margins(x = 0)
fig1.canvas.draw()
fig1.canvas.flush_events()
for i in range(0, 12):
axs1[i].clear()
for i in range(0, 12):
axs2.plot(x[i], fft(y[i]))
plt.title("FFT of all 12 channels", x = 0.5, y = 1)
fig2.canvas.draw()
fig2.canvas.flush_events()
axs2.clear()
def main_plot():
plt.ion()
fig1, axs1 = plt.subplots(12, figsize = (10, 9), sharex = True)
fig1.subplots_adjust(hspace = 0)
# Add fixed values for axis
canvas = FigureCanvasTkAgg(fig1, master = window)
canvas.draw()
canvas.get_tk_widget().pack()
canvas.get_tk_widget().place(x = 0, y = 35)
return fig1, axs1
def update_main_plot(fig1, axs1):
if state==1:
for i in range(0, 12):
axs1[i].plot(x[i], y[i], 'blue')
axs1[i].axes.get_yaxis().set_ticks([0], labels = ["channel " + str(i+1)])
axs1[i].grid(True)
axs1[i].margins(x = 0)
axs1[0].set_title("Plot recordings", x = 0.5, y = 1)
fig1.canvas.draw()
fig1.canvas.flush_events()
for i in range(0, 12):
axs1[i].clear()
def FFT_plot():
# Plot FFT figure
plt.ion()
fig2, axs2 = plt.subplots(1, figsize = (7, 9))
# Add fixed values for axis
canvas = FigureCanvasTkAgg(fig2, master = window)
canvas.draw()
canvas.get_tk_widget().pack()
canvas.get_tk_widget().place(x = window.winfo_screenwidth()*0.55, y = 35)
return fig2, axs2
def update_FFT_plot(fig2, axs2):
# Update FFT plot
for i in range(0, 12):
axs2.plot(x[i], fft(y[i]))
plt.title("FFT", x = 0.5, y = 1)
fig2.canvas.draw()
fig2.canvas.flush_events()
axs2.clear()
# create root window and set its properties
window = tk.Tk()
window.title("Data Displayer")
window.geometry("%dx%d" % (window.winfo_screenwidth(), window.winfo_screenheight()))
window.configure(background = 'white')
threading()
window.mainloop()
*** Sometimes it just doesn't work without any message and sometimes I also get "RuntimeError: main thread is not in main loop" ***
to be fair all functions in your code are very likely to cause a segmentation fault, and other functions that don't result in a segmentation fault simply don't work, it's hard to explain what's wrong.
define global variables as global if you are going to modify them
update GUI in your main thread by using the window.after method repeatedly.
only reading from your microcontroller should be done in separate thread.
creation of Tkinter objects should be done in the main thread, only updates are allowed in other threads, but it is not thread-safe, so while it may work it can lead to some weird behavior or errors sometimes.
calling matplotlib functions such as ion and flush_events causes errors because these are for matplotlib interactive canvas, not for tkinter canvas.
threading has a very tough learning curve so ask yourself "do i really need threads in here" and "is there any way to not use threads" before you attempt to use them as once you start using threads you are no longer using python's "safe code", despite all the efforts, threads are not safe to use for any task, it's up to you to make them safe, and to be honest threads are not needed here unless you are reading 1 GB/s from your microcontroller.
don't use numbers for states, it's not pythonic, and it confuses the readers, and it has no performance benefit over using Enums.
programs are built incrementally, not copy-paste from multiple working snippets, as it is harder to track where the error comes from when multiple parts of the code weren't verified to be working.
# Import Modules
import tkinter as tk
from threading import *
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
from scipy.fft import fft
import numpy as np
import time
import random
from enum import Enum,auto
UPDATE_INTERVAL_MS = 300
class States(Enum):
STREAM = auto()
PAUSE = auto()
SAVE = auto()
START = auto()
# global variables
state = States.START # check States Enum
x = [[0]]*12
y = [[0]]*12
# Thread buttons and plots separately
def threading():
global state
global window
state = States.STREAM
buttons()
plots()
data()
t_grab_data = Thread(target=grab_data_loop,daemon=True)
t_grab_data.start()
# t_buttons = Thread(target=buttons)
# t_plots = Thread(target=plots)
# t_data = Thread(target=data)
#
# t_buttons.start()
# t_plots.start()
# t_data.start()
def hex_to_dec(x, y):
for i in range(0, 12):
for j in range(0, len(y)):
x[i][j] = int(str(x[i][j]), 16)
y[i][j] = int(str(y[i][j]), 16)
def data():
global fig1,axs1,fig2,axs2
fig1, axs1 = main_plot()
fig2, axs2 = FFT_plot()
# To be replaced with actual Arduino data
window.after(UPDATE_INTERVAL_MS,draw_data_loop)
def grab_data_loop():
while state != States.SAVE:
for i in range(0, 12):
x[i] = [j for j in range(101)]
y[i] = [random.randint(0, 10) for j in range(-50, 51)]
for i in range(0, 12):
for j in range(0, len(y)):
x[i][j] = int(str(x[i][j]), 16)
y[i][j] = int(str(y[i][j]), 16)
time.sleep(0.1) # because we are not reading from a microcontroller
def draw_data_loop():
if state == States.STREAM:
update_main_plot(fig1, axs1)
update_FFT_plot(fig2, axs2)
window.after(UPDATE_INTERVAL_MS,draw_data_loop)
# create buttons
def stream_clicked():
global state
state = States.STREAM
print("clicked")
def pause_clicked():
global state
state = States.PAUSE
print("state")
def finish_clicked():
global state
state = States.SAVE
window.destroy()
def buttons():
continue_button = tk.Button(window, width=30, text="Stream data",
fg="black", bg='#98FB98', command=stream_clicked)
continue_button.place(x=window.winfo_screenwidth() * 0.2, y=0)
pause_button = tk.Button(window, width=30, text="Pause streaming data",
fg="black", bg='#FFA000', command=pause_clicked)
pause_button.place(x=window.winfo_screenwidth() * 0.4, y=0)
finish_button = tk.Button(window, width=30, text="End session and save",
fg='black', bg='#FF4500', command=finish_clicked)
finish_button.place(x=window.winfo_screenwidth() * 0.6, y=0)
def plots():
global state
fig1, axs1 = main_plot()
fig2, axs2 = FFT_plot()
if state == States.STREAM:
print("update")
for i in range(0, 12):
axs1[i].plot(x[i], y[i], 'blue')
axs1[i].axes.get_yaxis().set_ticks([0], labels=["channel " + str(i + 1)])
axs1[i].grid(True)
axs1[i].margins(x=0)
# fig1.canvas.draw()
# fig1.canvas.flush_events()
# for i in range(0, 12):
# axs1[i].clear()
for i in range(0, 12):
axs2.plot(x[i], np.abs(fft(y[i])))
plt.title("FFT of all 12 channels", x=0.5, y=1)
# fig2.canvas.draw()
# fig2.canvas.flush_events()
# axs2.clear()
def main_plot():
# plt.ion()
global canvas1
fig1, axs1 = plt.subplots(12, figsize=(10, 9), sharex=True)
fig1.subplots_adjust(hspace=0)
# Add fixed values for axis
canvas1 = FigureCanvasTkAgg(fig1, master=window)
# canvas.draw()
canvas1.get_tk_widget().pack()
canvas1.get_tk_widget().place(x=0, y=35)
return fig1, axs1
def update_main_plot(fig1, axs1):
if state == States.STREAM:
for i in range(0, 12):
axs1[i].clear()
for i in range(0, 12):
axs1[i].plot(x[i], y[i], 'blue')
axs1[i].axes.get_yaxis().set_ticks([0], labels=["channel " + str(i + 1)])
axs1[i].grid(True)
axs1[i].margins(x=0)
axs1[0].set_title("Plot recordings", x=0.5, y=1)
canvas1.draw()
# fig1.canvas.draw()
# fig1.canvas.flush_events()
def FFT_plot():
# Plot FFT figure
# plt.ion()
global canvas2
fig2, axs2 = plt.subplots(1, figsize=(7, 9))
# Add fixed values for axis
canvas2 = FigureCanvasTkAgg(fig2, master=window)
# canvas.draw()
canvas2.get_tk_widget().pack()
canvas2.get_tk_widget().place(x=window.winfo_screenwidth() * 0.55, y=35)
return fig2, axs2
def update_FFT_plot(fig2, axs2):
# Update FFT plot
if state == States.STREAM:
axs2.clear()
for i in range(0, 12):
axs2.plot(x[i], np.abs(fft(y[i])))
plt.title("FFT", x=0.5, y=1)
canvas2.draw()
# fig2.canvas.draw()
# fig2.canvas.flush_events()
# axs2.clear()
# create root window and set its properties
window = tk.Tk()
window.title("Data Displayer")
window.geometry("%dx%d" % (window.winfo_screenwidth(), window.winfo_screenheight()))
window.configure(background='white')
threading()
window.mainloop()
# put saving logic here
Related
Trying to embed matplotlib in tkinter GUI, however I get the error:
TypeError: init() got multiple values for argument 'master'
Could you tell me please how to handle it?
Here's the code:
interface - tkinter GUI
visualizer - selv updating graph that uses function live_plotter from pylive_mod file
interface:
# -*- coding: utf-8 -*-
"""
Created on Tue Oct 6 10:24:35 2020
#author: Dar0
"""
from tkinter import * #import tkinter module
from visualizer import main #import module 'visualizer' that shows the graph in real time
class Application(Frame):
''' Interface for visualizing graphs, indicators and text-box. '''
def __init__(self, master):
super(Application, self).__init__(master)
self.grid()
self.create_widgets()
def create_widgets(self):
# Label of the 1st graph
Label(self,
text='Hook Load / Elevator Height / Depth vs Time'
).grid(row = 0, column = 0, sticky = W)
# Graph 1 - Hook Load / Elevator Height / Depth vs Time
# button that displays the plot
plot_button = Button(self,
master = root,
command = main,
height = 2,
width = 10,
text = "Plot").grid(row = 1, column = 0, sticky = W)
# place the button
# in main window
# Label of the 2nd graph
Label(self,
text = 'Hook Load / Elevator Height vs Time'
).grid(row = 2, column = 0, sticky = W)
# Graph 2 - Hook Load / Elevator Height vs Time
#Label of the 3rd graph
Label(self,
text = 'Hook Load vs Time'
).grid(row = 4, column = 0, sticky = W)
#Graph 3 - Hook Load vs Time
#Label of the 1st indicator
Label(self,
text = '1st performance indicator'
).grid(row = 0, column = 1, sticky = W)
#1st performance indicator
#Label of 2nd performance indicator
Label(self,
text = '2nd performance indicator'
).grid(row = 2, column = 1, sticky = W)
#2nd performance indicator
#Label of 3rd performance indicator
Label(self,
text = '3rd performance indicator'
).grid(row = 4, column = 1, sticky = W)
#Text-box showing comments based on received data
self.text_box = Text(self, width = 50, height = 10, wrap = WORD)
self.text_box.grid(row = 6, column = 0, columnspan = 1)
self.text_box.delete(0.0, END)
self.text_box.insert(0.0, 'My message will be here.')
#Main part
root = Tk()
root.title('WiTSML Visualizer by Dar0')
app = Application(root)
root.mainloop()
visualizer:
#WiTSML visualizer
#Created by Dariusz Krol
#import matplotlib
#matplotlib.use('TkAgg')
#from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
#from matplotlib.figure import Figure
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import random
class Visualizer(object):
""" Includes all the methods needed to show streamed data. """
def __init__(self):
self.file_path = 'C:/Anaconda/_my_files/witsml_reader/modified_witsml.csv' #Defines which file is streamed
self.datetime_mod = []
self.bpos_mod = []
self.woh_mod = []
self.torq_mod = []
self.spp_mod = []
self.depth_mod = []
self.flow_in_mod = []
self.rpm_mod = []
def open_file(self):
self.df = pd.read_csv(self.file_path, low_memory = False, nrows = 300000) #Opens the STREAMED file (already modified so that data convert is not required)
self.df = self.df.drop(0)
self.df = pd.DataFrame(self.df)
return self.df
def convert_dataframe(self):
self.df = self.df.values.T.tolist() #Do transposition of the dataframe and convert to list
#Columns are as following:
# - DATETIME
# - BPOS
# - WOH
# - TORQ
# - SPP
# - DEPTH
# - FLOW_IN
# - RPM
self.datetime_value = self.df[0]
self.bpos_value = self.df[1]
self.woh_value = self.df[2]
self.torq_value = self.df[3]
self.spp_value = self.df[4]
self.depth_value = self.df[5]
self.flow_in_value = self.df[5]
self.rpm_value = self.df[7]
return self.datetime_value, self.bpos_value, self.woh_value, self.torq_value, self.spp_value, self.depth_value, self.flow_in_value, self.rpm_value
#print(self.bpos_value)
def deliver_values(self, no_dp, columns):
''' Method gets no_dp amount of data points from the original file. '''
self.no_dp = no_dp #defines how many data points will be presented in the graph
val_dict = {
'datetime': [self.datetime_value, self.datetime_mod],
'bpos': [self.bpos_value, self.bpos_mod],
'woh': [self.woh_value, self.woh_mod],
'torq': [self.torq_value, self.torq_mod],
'spp': [self.spp_value, self.spp_mod],
'depth': [self.depth_value, self.depth_mod],
'flow_in': [self.flow_in_value, self.flow_in_mod],
'rpm': [self.rpm_value, self.rpm_mod]
}
for item in columns:
if self.no_dp > len(val_dict[item][0]):
dp_range = len(val_dict[item][0])
else:
dp_range = self.no_dp
for i in range(dp_range):
val_dict[item][1].append(val_dict[item][0][i])
return self.datetime_mod, self.bpos_mod, self.woh_mod, self.torq_mod, self.spp_mod, self.depth_mod, self.flow_in_mod, self.rpm_mod
def show_graph2(self):
from pylive_mod import live_plotter
self.open_file()
self.convert_dataframe()
self.deliver_values(no_dp = 100000, columns = ['datetime', 'depth', 'bpos', 'woh'])
fst_p = 0
size = 300 # density of points in the graph (100 by default)
x_vec = self.datetime_mod[fst_p:size]
y_vec = self.depth_mod[fst_p:size]
y2_vec = self.bpos_mod[fst_p:size]
y3_vec = self.woh_mod[fst_p:size]
line1 = []
line2 = []
line3 = []
for i in range(self.no_dp):
#print(self.datetime_mod[i:6+i])
#print('Ostatni element y_vec: ', y_vec[-1])
#print(x_vec)
x_vec[-1] = self.datetime_mod[size+i]
y_vec[-1] = self.depth_mod[size+i]
y2_vec[-1] = self.bpos_mod[size+i]
y3_vec[-1] = self.woh_mod[size+i]
line1, line2, line3 = live_plotter(x_vec, y_vec, y2_vec, y3_vec, line1, line2, line3)
x_vec = np.append(x_vec[1:], 0.0)
y_vec = np.append(y_vec[1:], 0.0)
y2_vec = np.append(y2_vec[1:], 0.0)
y3_vec = np.append(y3_vec[1:], 0.0)
def main():
Graph = Visualizer()
Graph.open_file() #Opens the streamed file
Graph.convert_dataframe() #Converts dataframe to readable format
Graph.show_graph2()
#Show us the graph
#main()
pylive_mod (live_plotter):
def live_plotter(x_data, y1_data, y2_data, y3_data, line1, line2, line3, identifier='',pause_time=1):
if line1 == [] and line2 == [] and line3 == []:
# this is the call to matplotlib that allows dynamic plotting
plt.ion()
#fig = Figure(figsize = (5, 4), dpi = 100)
#host = fig.add_subplot()
fig, host = plt.subplots()
fig.set_figheight(7) #adjust figure's height
fig.set_figwidth(14) #adjust figure's width
fig.subplots_adjust(0.15)
#Line1
#line1 = host
ln1 = host
ln2 = host.twinx()
ln3 = host.twinx()
ln2.spines['right'].set_position(('axes', 1.))
ln3.spines['right'].set_position(('axes', 1.12))
make_patch_spines_invisible(ln2)
make_patch_spines_invisible(ln3)
ln2.spines['right'].set_visible(True)
ln3.spines['right'].set_visible(True)
ln1.set_xlabel('Date & Time') #main x axis
ln1.set_ylabel('Depth') #left y axis
ln2.set_ylabel('Elevator Height')
ln3.set_ylabel('Weight on Hook')
#
x_formatter = FixedFormatter([x_data])
x_locator = FixedLocator([x_data[5]])
ln1.xaxis.set_major_formatter(x_formatter)
ln1.xaxis.set_major_locator(x_locator)
#
ln1.locator_params(nbins = 5, axis = 'y')
ln1.tick_params(axis='x', rotation=90) #rotates x ticks 90 degrees down
ln2.axes.set_ylim(0, 30)
ln3.axes.set_ylim(200, 250)
line1, = ln1.plot(x_data, y1_data, color = 'black', linestyle = 'solid', alpha=0.8, label = 'Depth')
line2, = ln2.plot(x_data, y2_data, color = 'blue', linestyle = 'dashed', alpha=0.8, label = 'Elevator Height')
line3, = ln3.plot(x_data, y3_data, color = 'red', linestyle = 'solid', alpha=0.8, label = 'Weight on Hook')
# ----- embedding -----
canvas = FigureCanvasTkAgg(fig, master = root)
canvas.draw()
# placing the canvas on the Tkinter window
canvas.get_tk_widget().pack()
# creating the Matplotlib toolbar
toolbar = NavigationToolbar2Tk(canvas, root)
toolbar.update()
# placing the toolbar on the Tkinter window
canvas.get_tk_widget().pack()
#----- -----
plt.title('WiTSML Visualizer')
fig.tight_layout() #the graphs is not clipped on sides
#Shows legend
lines = [line1, line2, line3]
host.legend(lines, [l.get_label() for l in lines], loc = 'lower left')
#Shows grid
plt.grid(True)
#Shows the whole graph
plt.show()
# after the figure, axis, and line are created, we only need to update the y-data
mod_x_data = convert_x_data(x_data, 10)
line1.axes.set_xticklabels(mod_x_data)
line1.set_ydata(y1_data)
line2.set_ydata(y2_data)
line3.set_ydata(y3_data)
#Debugging
#rint('plt.lim: ', ln2.axes.get_ylim())
# adjust limits if new data goes beyond bounds
# limit for line 1
if np.min(y1_data)<=line1.axes.get_ylim()[0] or np.max(y1_data)>=line1.axes.get_ylim()[1]:
plt.ylim(0, 10)
line1.axes.set_ylim([np.min(y1_data)-np.std(y1_data),np.max(y1_data)+np.std(y1_data)])
# limit for line 2
if np.min(y2_data)<=line2.axes.get_ylim()[0] or np.max(y2_data)>=line2.axes.get_ylim()[1]:
plt.ylim([np.min(y2_data)-np.std(y2_data),np.max(y2_data)+np.std(y2_data)])
#plt.ylim(0, 25)
# limit for line 3
if np.min(y3_data)<=line3.axes.get_ylim()[0] or np.max(y3_data)>=line3.axes.get_ylim()[1]:
plt.ylim([np.min(y3_data)-np.std(y3_data),np.max(y3_data)+np.std(y3_data)])
#plt.ylim(0, 25)
# Adds lines to the legend
#host.legend(lines, [l.get_label() for l in lines])
# this pauses the data so the figure/axis can catch up - the amount of pause can be altered above
plt.pause(pause_time)
# return line so we can update it again in the next iteration
return line1, line2, line3
I have build simple tkinter GUI.
Now, I am trying to visualise 3 different graphs (by calling the same function with different variables) and place them in 3 different rows of the GUI.
When I do that I get 2 problems:
Every time I run the script (interface.py) I get 2 windows - both GUI and external graph's window. How to get rid of the second one?
I am not able to visualize all the 3 graphs. The script stops after showing the first one. I believe this is because of that the first graph works in a loop (iterates through plenty of data points). Is there any work around it?
Interface:
# -*- coding: utf-8 -*-
"""
Created on Tue Oct 6 10:24:35 2020
#author: Dar0
"""
from tkinter import * #import tkinter module
from visualizer import main #import module 'visualizer' that shows the graph in real time
class Application(Frame):
''' Interface for visualizing graphs, indicators and text-box. '''
def __init__(self, master):
super(Application, self).__init__(master)
self.grid()
self.create_widgets()
def create_widgets(self):
# Label of the 1st graph
Label(self,
text='Hook Load / Elevator Height / Depth vs Time'
).grid(row = 0, column = 0, sticky = W)
# Graph 1 - Hook Load / Elevator Height / Depth vs Time
# button that displays the plot
#plot_button = Button(self,2
# command = main,
# height = 2,
# width = 10,
# text = "Plot"
# ).grid(row = 1, column = 0, sticky = W)
self.graph_1 = main(root, 1, 0)
# place the button
# in main window
# Label of the 2nd graph
Label(self,
text = 'Hook Load / Elevator Height vs Time'
).grid(row = 3, column = 0, sticky = W)
# Graph 2 - Hook Load / Elevator Height vs Time
self.graph_2 = main(root, 4, 0)
#Label of the 3rd graph
Label(self,
text = 'Hook Load vs Time'
).grid(row = 6, column = 0, sticky = W)
#Graph 3 - Hook Load vs Time
#Label of the 1st indicator
Label(self,
text = '1st performance indicator'
).grid(row = 0, column = 1, sticky = W)
#1st performance indicator
#Label of 2nd performance indicator
Label(self,
text = '2nd performance indicator'
).grid(row = 3, column = 1, sticky = W)
#2nd performance indicator
#Label of 3rd performance indicator
Label(self,
text = '3rd performance indicator'
).grid(row = 6, column = 1, sticky = W)
#Text-box showing comments based on received data
self.text_box = Text(self, width = 50, height = 10, wrap = WORD)
self.text_box.grid(row = 9, column = 0, columnspan = 1)
self.text_box.delete(0.0, END)
self.text_box.insert(0.0, 'My message will be here.')
#Main part
root = Tk()
root.title('WiTSML Visualizer by Dar0')
app = Application(root)
root.mainloop()
Visualizer:
#WiTSML visualizer
#Created by Dariusz Krol
#import matplotlib
#matplotlib.use('TkAgg')
#from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
#from matplotlib.figure import Figure
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import random
class Visualizer(object):
""" Includes all the methods needed to show streamed data. """
def __init__(self):
self.file_path = 'C:/Anaconda/_my_files/witsml_reader/modified_witsml.csv' #Defines which file is streamed
self.datetime_mod = []
self.bpos_mod = []
self.woh_mod = []
self.torq_mod = []
self.spp_mod = []
self.depth_mod = []
self.flow_in_mod = []
self.rpm_mod = []
def open_file(self):
self.df = pd.read_csv(self.file_path, low_memory = False, nrows = 300000) #Opens the STREAMED file (already modified so that data convert is not required)
self.df = self.df.drop(0)
self.df = pd.DataFrame(self.df)
return self.df
def convert_dataframe(self):
self.df = self.df.values.T.tolist() #Do transposition of the dataframe and convert to list
#Columns are as following:
# - DATETIME
# - BPOS
# - WOH
# - TORQ
# - SPP
# - DEPTH
# - FLOW_IN
# - RPM
self.datetime_value = self.df[0]
self.bpos_value = self.df[1]
self.woh_value = self.df[2]
self.torq_value = self.df[3]
self.spp_value = self.df[4]
self.depth_value = self.df[5]
self.flow_in_value = self.df[5]
self.rpm_value = self.df[7]
return self.datetime_value, self.bpos_value, self.woh_value, self.torq_value, self.spp_value, self.depth_value, self.flow_in_value, self.rpm_value
#print(self.bpos_value)
def deliver_values(self, no_dp, columns):
''' Method gets no_dp amount of data points from the original file. '''
self.no_dp = no_dp #defines how many data points will be presented in the graph
val_dict = {
'datetime': [self.datetime_value, self.datetime_mod],
'bpos': [self.bpos_value, self.bpos_mod],
'woh': [self.woh_value, self.woh_mod],
'torq': [self.torq_value, self.torq_mod],
'spp': [self.spp_value, self.spp_mod],
'depth': [self.depth_value, self.depth_mod],
'flow_in': [self.flow_in_value, self.flow_in_mod],
'rpm': [self.rpm_value, self.rpm_mod]
}
for item in columns:
if self.no_dp > len(val_dict[item][0]):
dp_range = len(val_dict[item][0])
else:
dp_range = self.no_dp
for i in range(dp_range):
val_dict[item][1].append(val_dict[item][0][i])
return self.datetime_mod, self.bpos_mod, self.woh_mod, self.torq_mod, self.spp_mod, self.depth_mod, self.flow_in_mod, self.rpm_mod
def show_graph2(self, tr_val, row, column):
from pylive_mod import live_plotter, live_plotter2
self.open_file()
self.convert_dataframe()
self.deliver_values(no_dp = 100000, columns = ['datetime', 'depth', 'bpos', 'woh'])
fst_p = 0
size = 300 # density of points in the graph (100 by default)
x_vec = self.datetime_mod[fst_p:size]
y_vec = self.depth_mod[fst_p:size]
y2_vec = self.bpos_mod[fst_p:size]
y3_vec = self.woh_mod[fst_p:size]
line1 = []
line2 = []
line3 = []
for i in range(self.no_dp):
#print(self.datetime_mod[i:6+i])
#print('Ostatni element y_vec: ', y_vec[-1])
#print(x_vec)
x_vec[-1] = self.datetime_mod[size+i]
y_vec[-1] = self.depth_mod[size+i]
y2_vec[-1] = self.bpos_mod[size+i]
y3_vec[-1] = self.woh_mod[size+i]
line1, line2, line3 = live_plotter2(tr_val, row, column, x_vec, y_vec, y2_vec, y3_vec, line1, line2, line3)
x_vec = np.append(x_vec[1:], 0.0)
y_vec = np.append(y_vec[1:], 0.0)
y2_vec = np.append(y2_vec[1:], 0.0)
y3_vec = np.append(y3_vec[1:], 0.0)
def main(tr_val, row, column):
Graph = Visualizer()
Graph.open_file() #Opens the streamed file
Graph.convert_dataframe() #Converts dataframe to readable format
Graph.show_graph2(tr_val, row, column)
#Show us the graph
#main()
Function that creates the graph:
def live_plotter2(tr_val, row, column, x_data, y1_data, y2_data, y3_data, line1, line2, line3, identifier='',pause_time=1):
if line1 == [] and line2 == [] and line3 == []:
# this is the call to matplotlib that allows dynamic plotting
plt.ion()
fig = plt.figure(figsize = (5, 4), dpi = 100)
fig.subplots_adjust(0.15)
# -------------------- FIRST GRAPH --------------------
host = fig.add_subplot()
ln1 = host
ln2 = host.twinx()
ln3 = host.twinx()
ln2.spines['right'].set_position(('axes', 1.))
ln3.spines['right'].set_position(('axes', 1.12))
make_patch_spines_invisible(ln2)
make_patch_spines_invisible(ln3)
ln2.spines['right'].set_visible(True)
ln3.spines['right'].set_visible(True)
ln1.set_xlabel('Date & Time') #main x axis
ln1.set_ylabel('Depth') #left y axis
ln2.set_ylabel('Elevator Height')
ln3.set_ylabel('Weight on Hook')
#
x_formatter = FixedFormatter([x_data])
x_locator = FixedLocator([x_data[5]])
#ln1.xaxis.set_major_formatter(x_formatter)
ln1.xaxis.set_major_locator(x_locator)
#
ln1.locator_params(nbins = 5, axis = 'y')
ln1.tick_params(axis='x', rotation=90) #rotates x ticks 90 degrees down
ln2.axes.set_ylim(0, 30)
ln3.axes.set_ylim(200, 250)
line1, = ln1.plot(x_data, y1_data, color = 'black', linestyle = 'solid', alpha=0.8, label = 'Depth')
line2, = ln2.plot(x_data, y2_data, color = 'blue', linestyle = 'dashed', alpha=0.8, label = 'Elevator Height')
line3, = ln3.plot(x_data, y3_data, color = 'red', linestyle = 'solid', alpha=0.8, label = 'Weight on Hook')
fig.tight_layout() #the graphs is not clipped on sides
plt.title('WiTSML Visualizer')
plt.grid(True)
#Shows legend
lines = [line1, line2, line3]
host.legend(lines, [l.get_label() for l in lines], loc = 'lower left')
#Shows the whole graph
#plt.show()
#-------------------- Embedding --------------------
canvas = FigureCanvasTkAgg(fig, master=tr_val)
canvas.draw()
canvas.get_tk_widget().grid(row=row, column=column, ipadx=40, ipady=20)
# navigation toolbar
toolbarFrame = tk.Frame(master=tr_val)
toolbarFrame.grid(row=row,column=column)
toolbar = NavigationToolbar2Tk(canvas, toolbarFrame)
# after the figure, axis, and line are created, we only need to update the y-data
mod_x_data = convert_x_data(x_data, 20)
line1.axes.set_xticklabels(mod_x_data)
line1.set_ydata(y1_data)
line2.set_ydata(y2_data)
line3.set_ydata(y3_data)
#Debugging
#rint('plt.lim: ', ln2.axes.get_ylim())
# adjust limits if new data goes beyond bounds
# limit for line 1
if np.min(y1_data)<=line1.axes.get_ylim()[0] or np.max(y1_data)>=line1.axes.get_ylim()[1]:
plt.ylim(0, 10)
line1.axes.set_ylim([np.min(y1_data)-np.std(y1_data),np.max(y1_data)+np.std(y1_data)])
# limit for line 2
if np.min(y2_data)<=line2.axes.get_ylim()[0] or np.max(y2_data)>=line2.axes.get_ylim()[1]:
plt.ylim([np.min(y2_data)-np.std(y2_data),np.max(y2_data)+np.std(y2_data)])
#plt.ylim(0, 25)
# limit for line 3
if np.min(y3_data)<=line3.axes.get_ylim()[0] or np.max(y3_data)>=line3.axes.get_ylim()[1]:
plt.ylim([np.min(y3_data)-np.std(y3_data),np.max(y3_data)+np.std(y3_data)])
#plt.ylim(0, 25)
# Adds lines to the legend
#host.legend(lines, [l.get_label() for l in lines])
# this pauses the data so the figure/axis can catch up - the amount of pause can be altered above
plt.pause(pause_time)
# return line so we can update it again in the next iteration
return line1, line2, line3
The key is to not use pyplot when you want to plot within tkinter as shown in the official example. Use matplotlib.figure.Figure instead (see this for added info).
Below is a minimum sample that plots 3 independent graphs along a Text widget which I see in your code:
import pandas as pd
import numpy as np
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
class Graph(tk.Frame):
def __init__(self, master=None, title="", *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.fig = Figure(figsize=(4, 3))
ax = self.fig.add_subplot(111)
df = pd.DataFrame({"values": np.random.randint(0, 50, 10)}) #dummy data
df.plot(ax=ax)
self.canvas = FigureCanvasTkAgg(self.fig, master=self)
self.canvas.draw()
tk.Label(self, text=f"Graph {title}").grid(row=0)
self.canvas.get_tk_widget().grid(row=1, sticky="nesw")
toolbar_frame = tk.Frame(self)
toolbar_frame.grid(row=2, sticky="ew")
NavigationToolbar2Tk(self.canvas, toolbar_frame)
root = tk.Tk()
for num, i in enumerate(list("ABC")):
Graph(root, title=i, width=200).grid(row=num//2, column=num%2)
text_box = tk.Text(root, width=50, height=10, wrap=tk.WORD)
text_box.grid(row=1, column=1, sticky="nesw")
text_box.delete(0.0, "end")
text_box.insert(0.0, 'My message will be here.')
root.mainloop()
Result:
In the main loop, a 2 x 2 tkinter grid is cretaed with
one label in each cell of the first line.
In the second line, two Matplotlib figures are crated with a subplot
Two functions are in charge to dynamicly refresh the grid.
They are running each one in a thread.
The first line (two labels) is well refresh by the two functions.
But, in the second line nothing ... no plot !
from tkinter import *
import threading
import time
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib import style
def read_api1():
n = 0
while 1:
n = n + 1
texte1.config(text="fig1 " + str(n))
ax1.plot([1,2], [12,14])
time.sleep(2)
ax1.cla()
def read_api2():
m = 0
while 1:
m = m + 1
texte2.config(text="fig2 " + str(m))
ax2.plot([.1,.2,.3], [2,4,3])
time.sleep(1)
main = Tk()
style.use("ggplot")
texte1 = Label(main, text="fig1")
texte1.grid(row=0,column=0)
fig1 = Figure(figsize=(2, 2), dpi=112)
ax1 = fig1.add_subplot()
fig1.set_tight_layout(True)
graph = FigureCanvasTkAgg(fig1, master=main)
canvas = graph.get_tk_widget()
canvas.grid(row=1, column=0)
texte2 = Label(main, text="fig2")
texte2.grid(row=0,column=1)
fig2 = Figure(figsize=(2, 2), dpi=112)
ax2 = fig2.add_subplot()
fig2.set_tight_layout(True)
graph = FigureCanvasTkAgg(fig2, master=main)
canvas = graph.get_tk_widget()
canvas.grid(row=1, column=1)
t = threading.Thread(target=read_api1)
t.start()
t = threading.Thread(target=read_api2)
t.start()
main.mainloop()
Any help would be appreciated :)
EDIT:
Some more details #furas:
read_api2 is supposed to get data from a WEB API at specific time. So what you recommend (after method) should work.
read_api1 is supposed to acquire data from a serial port (GPIO UART). So the thread will be waiting for data beeing available for reading.
In that case, I don't see how to use the after method
In other words, the question is : how to refresh a matplotlib plot in a tkinter environnement based on asynchronous input ? The asynchronous serial data read cannot be in the mainloop, so I put it in the thread but even with graph.draw(), it does not work. Any suggestion ?
There are two problems:
it needs graph.draw() to update/redraw plot
usually GUIs don't like to run in thread and it seems graph.draw() doesn't work in thread (at least on my Linux).
You may have to use main.after(1000, main_api1) to run the same function after 1000ms (1s) without using thread and without blocking mainloop()
import tkinter as tk # PEP8: `import *` is not preferred
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib import style
import random
# --- functions ---
def read_api1():
global n
n = n + 1
texte1.config(text="fig1 " + str(n))
ax1.cla()
ax1.plot([1,2], [random.randint(0,10),random.randint(0,10)])
graph1.draw()
main.after(1000, read_api1)
def read_api2():
global m
m = m + 1
texte2.config(text="fig2 " + str(m))
ax2.cla()
ax2.plot([.1,.2,.3], [random.randint(0,10),random.randint(0,10),random.randint(0,10)])
graph2.draw()
main.after(1000, read_api2)
# --- main ---
m = 0
n = 0
main = tk.Tk()
style.use("ggplot")
texte1 = tk.Label(main, text="fig1")
texte1.grid(row=0, column=0)
fig1 = Figure(figsize=(2, 2), dpi=112)
ax1 = fig1.add_subplot()
fig1.set_tight_layout(True)
graph1 = FigureCanvasTkAgg(fig1, master=main)
canvas1 = graph1.get_tk_widget()
canvas1.grid(row=1, column=0)
#graph1.draw()
texte2 = tk.Label(main, text="fig2")
texte2.grid(row=0,column=1)
fig2 = Figure(figsize=(2, 2), dpi=112)
ax2 = fig2.add_subplot()
fig2.set_tight_layout(True)
graph2 = FigureCanvasTkAgg(fig2, master=main)
canvas2 = graph2.get_tk_widget()
canvas2.grid(row=1, column=1)
#graph2.draw()
read_api1()
read_api2()
main.mainloop()
EDIT: Example which runs two threads. Every threads generate data in different speed and use two queues to send data to main thread. And main thread use two after() to check two queues and update two plots.
import tkinter as tk # PEP8: `import *` is not preferred
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib import style
import random
import threading
import queue
import time
# --- functions ---
def WEB_API(queue):
# it will run in thread
print('WEB_API: start')
while web_api_running:
value = random.randint(1, 3)
time.sleep(.1)
print('WEB_API:', value)
queue.put(value)
def GPIO_API(queue):
# it will run in thread
print('GPIO_API: start')
while gpio_api_running:
value = random.randint(1, 3)
time.sleep(value)
print('GPIO_API:', value)
queue.put(value)
def read_api1():
global n
global data1
if not queue1.empty():
value = queue1.get()
# remove first item and add new item at the the end
data1 = data1[1:] + [value]
n += 1
texte1.config(text="fig1 " + str(n))
ax1.cla()
ax1.plot(range(10), data1)
graph1.draw()
main.after(100, read_api1)
def read_api2():
global m
global data2
if not queue2.empty():
value = queue2.get()
# remove first item and add new item at the the end
data2 = data2[1:] + [value]
m = m + 1
texte2.config(text="fig2 " + str(m))
ax2.cla()
ax2.plot([.1,.2,.3], data2)
graph2.draw()
main.after(100, read_api2)
# --- before GUI ---
# default data at start (to add new value at the end and remove first value)
data1 = [0,0,0,0,0,0,0,0,0,0]
data2 = [0,0,0]
m = 0
n = 0
# queues to communicate with threads
queue1 = queue.Queue()
queue2 = queue.Queue()
# global variables to control loops in thread
web_api_running = True
gpio_api_running = True
# start threads and send queues as arguments
thread1 = threading.Thread(target=WEB_API, args=(queue1,))
thread1.start()
thread2 = threading.Thread(target=GPIO_API, args=(queue2,))
thread2.start()
# --- GUI ---
main = tk.Tk()
style.use("ggplot")
texte1 = tk.Label(main, text="fig1")
texte1.grid(row=0, column=0)
fig1 = Figure(figsize=(2, 2), dpi=112)
ax1 = fig1.add_subplot()
fig1.set_tight_layout(True)
graph1 = FigureCanvasTkAgg(fig1, master=main)
canvas1 = graph1.get_tk_widget()
canvas1.grid(row=1, column=0)
texte2 = tk.Label(main, text="fig2")
texte2.grid(row=0,column=1)
fig2 = Figure(figsize=(2, 2), dpi=112)
ax2 = fig2.add_subplot()
fig2.set_tight_layout(True)
graph2 = FigureCanvasTkAgg(fig2, master=main)
canvas2 = graph2.get_tk_widget()
canvas2.grid(row=1, column=1)
# draw plots first time
ax1.plot(range(10), data1)
ax2.plot([.1,.2,.3], data2)
# run after which will update data and redraw plots
read_api1()
read_api2()
main.mainloop()
# --- after GUI ---
# stop loops in threads
web_api_running = False
gpio_api_running = False
# wait for end of theads
thread1.join()
thread2.join()
I found this cool animation for Matlibplot and want to add it to my plot widget in a Pyqt program. This is the method that shows regular plots successfully in my program
def plot(self):
ax = self.ui.figure.add_subplot(111)
ax.hold(False)
ax.plot([1,2,3],[4,5,6])
self.ui.canvas.draw()
I thought I could just add the single def from the animation code to my Form and call the animation the same as above, but alas no. Here is the animation code:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
N = 100
ON = 255
OFF = 0
vals = [ON, OFF]
# populate grid with random on/off - more off than on
grid = np.random.choice(vals, N*N, p=[0.2, 0.8]).reshape(N, N)
def update(data):
global grid
# copy grid since we require 8 neighbors for calculation
# and we go line by line
newGrid = grid.copy()
for i in range(N):
for j in range(N):
# compute 8-neghbor sum
# using toroidal boundary conditions - x and y wrap around
# so that the simulaton takes place on a toroidal surface.
total = (grid[i, (j-1)%N] + grid[i, (j+1)%N] +
grid[(i-1)%N, j] + grid[(i+1)%N, j] +
grid[(i-1)%N, (j-1)%N] + grid[(i-1)%N, (j+1)%N] +
grid[(i+1)%N, (j-1)%N] + grid[(i+1)%N, (j+1)%N])/255
# apply Conway's rules
if grid[i, j] == ON:
if (total < 2) or (total > 3):
newGrid[i, j] = OFF
else:
if total == 3:
newGrid[i, j] = ON
# update data
mat.set_data(newGrid)
grid = newGrid
return [mat]
# set up animation
fig, ax = plt.subplots()
mat = ax.matshow(grid)
ani = animation.FuncAnimation(fig, update, interval=50,
save_count=50)
plt.show()
You can use your found code, just change your method to:
def plot(self):
ax = self.ui.figure.add_subplot(111)
global mat
mat = ax.matshow(grid)
ani = animation.FuncAnimation(figure, update, interval=50, save_count=50)
self.ui.canvas.draw()
Note, that you don't have to use ax.hold(False) and it is likely that animation will work more slowly when using subplots(try increasing resolution [N] to see difference). I have such speed problems with 3D plots in my own project - very frustrating =D
I made small sample program using class instead of global variables, maybe it comes in handy:
import sys
import numpy as np
from PyQt4 import QtGui
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from functools import partial
class Game_of_life(QtGui.QWidget):
def __init__(self, N, ON, OFF):
super(Game_of_life, self).__init__()
self.N = N
self.ON = ON
self.OFF = OFF
self.vals = [ON, OFF]
self.grid = np.random.choice(self.vals, N*N, p=[0.2, 0.8]).reshape(N, N)
self.start()
def start(self):
self.setWindowTitle('Game of life')
gridLayout = QtGui.QGridLayout()
self.setLayout(gridLayout)
#Figure and subplot
figure = plt.figure()
canvas = FigureCanvas(figure)
ax = figure.add_subplot(111)
canvas.draw()
self.mat = ax.matshow(self.grid)
ani = animation.FuncAnimation(figure, self.update, interval=50, save_count=50)
#button
restart = QtGui.QPushButton("Restart game of life")
restart.clicked.connect(partial(self.restart_animation, ax=ax, figure=figure))
gridLayout.addWidget(canvas,0,0)
gridLayout.addWidget(restart, 1,0)
self.show()
def update(self, data):
newGrid = self.grid.copy()
for i in range(self.N):
for j in range(self.N):
total = (self.grid[i, (j-1)%self.N] + self.grid[i, (j+1)%self.N] +
self.grid[(i-1)%self.N, j] + self.grid[(i+1)%self.N, j] +
self.grid[(i-1)%self.N, (j-1)%self.N] + self.grid[(i-1)%self.N, (j+1)%self.N] +
self.grid[(i+1)%self.N, (j-1)%self.N] + self.grid[(i+1)%self.N, (j+1)%self.N])/255
if self.grid[i, j] == self.ON:
if (total < 2) or (total > 3):
newGrid[i, j] = self.OFF
else:
if total == 3:
newGrid[i, j] = self.ON
self.mat.set_data(newGrid)
self.grid = newGrid
#simply restarts game data
def restart_animation(self, ax, figure):
self.grid = np.random.choice(self.vals, self.N*self.N, p=[0.2, 0.8]).reshape(self.N, self.N)
self.mat = ax.matshow(self.grid)
def main():
app = QtGui.QApplication(sys.argv)
widget = Game_of_life(100, 255, 0)
#widget can be implement in other layout
sys.exit(app.exec_())
if __name__ == "__main__":
main()
I don't know if this is a bug with matplotlib/python but running the following from emacs removes my ability to kill the matplotlib process by clicking [x] on the figure window, it has no effect. Is there a command (I googled, no luck) for terminating a specific process that emacs has launched? I can kill the process by finding the buffer and doing C-x k but that is a bit of a hassle, any way to kill ALL running python processes?
#Simple circular box simulator, part of part_sim
#Restructure to import into gravity() or coloumb () or wind() or pressure()
#Or to use all forces: sim_full()
#Note: Implement crashing as backbone to all forces
import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial.distance import pdist, squareform
N = 100 #Number of particles
R = 10000 #Box width
pR= 5 #Particle radius
r = np.random.randint(0, R, (N,2)) #Position vector
v = np.random.randint(-R/100,R/100,(N,2)) #velocity vector
a = np.array([0,-10]) #Forces
v_limit = R/2 #Speedlimit
plt.ion()
line, = plt.plot([],'o')
line2, = plt.plot([],'o') #Track a particle
plt.axis([0,R+pR,0,R+pR]
while True:
v=v+a #Advance
r=r+v
#Collision tests
r_hit_x0 = np.where(r[:,0]<0) #Hit floor?
r_hit_x1 = np.where(r[:,0]>R) #Hit roof?
r_hit_LR = np.where(r[:,1]<0) #Left wall?
r_hit_RR = np.where(r[:,1]>R) #Right wall?
#Stop at walls
r[r_hit_x0,0] = 0
r[r_hit_x1,0] = R
r[r_hit_LR,1] = 0
r[r_hit_RR,1] = R
#Reverse velocities
v[r_hit_x0,0] = -0.9*v[r_hit_x0,0]
v[r_hit_x1,0] = -v[r_hit_x1,0]
v[r_hit_LR,1] = -0.95*v[r_hit_LR,1]
v[r_hit_RR,1] = -0.99*v[r_hit_RR,1]
#Collisions
D = squareform(pdist(r))
ind1, ind2 = np.where(D < pR)
unique = (ind1 < ind2)
ind1 = ind1[unique]
ind2 = ind2[unique]
for i1, i2 in zip(ind1, ind2):
eps = np.random.rand()
vtot= v[i1,:]+v[i2,:]
v[i1,:] = -(1-eps)*vtot
v[i2,:] = -eps*vtot
line.set_ydata(r[:,1])
line.set_xdata(r[:,0])
line2.set_ydata(r[:N/5,1])
line2.set_xdata(r[:N/5,0])
plt.draw()
C-c C-\ will kill the program with a SIGQUIT, but that is not a graceful way
to end the program.
Alternatively, if you change the backend to TkAgg, C-c
C-c will also terminate the program (again ungracefully), but trying to close the window will still not work.
import numpy as np
import matplotlib as mpl
mpl.use('TkAgg') # do this before importing pyplot
import matplotlib.pyplot as plt
A full, robust solution requires removing plt.ion(), using a GUI framework like Tk, pygtk, wxpython or pyqt, and embedding the GUI window in a matplotlib FigureCanvas.
Here is an example using Tk:
"""
http://stackoverflow.com/q/13660042/190597
Simple circular box simulator, part of part_sim
Restructure to import into gravity() or coloumb () or wind() or pressure()
Or to use all forces: sim_full()
Note: Implement crashing as backbone to all forces
"""
import tkinter as tk
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.figure as mplfig
import scipy.spatial.distance as dist
import matplotlib.backends.backend_tkagg as tkagg
class App(object):
def __init__(self, master):
self.master = master
self.fig = mplfig.Figure(figsize = (5, 4), dpi = 100)
self.ax = self.fig.add_subplot(111)
self.canvas = canvas = tkagg.FigureCanvasTkAgg(self.fig, master)
canvas.get_tk_widget().pack(side = tk.TOP, fill = tk.BOTH, expand = 1)
self.toolbar = toolbar = tkagg.NavigationToolbar2TkAgg(canvas, master)
self.button = button = tk.Button(master, text = 'Quit', command = master.quit)
button.pack(side = tk.BOTTOM)
toolbar.update()
self.update = self.animate().__next__
master.after(10, self.update)
canvas.show()
def animate(self):
N = 100 #Number of particles
R = 10000 #Box width
pR= 5 #Particle radius
r = np.random.randint(0, R, (N,2)) #Position vector
v = np.random.randint(-R/100,R/100,(N,2)) #velocity vector
a = np.array([0,-10]) #Forces
v_limit = R/2 #Speedlimit
line, = self.ax.plot([],'o')
line2, = self.ax.plot([],'o') #Track a particle
self.ax.set_xlim(0, R+pR)
self.ax.set_ylim(0, R+pR)
while True:
v=v+a #Advance
r=r+v
#Collision tests
r_hit_x0 = np.where(r[:,0]<0) #Hit floor?
r_hit_x1 = np.where(r[:,0]>R) #Hit roof?
r_hit_LR = np.where(r[:,1]<0) #Left wall?
r_hit_RR = np.where(r[:,1]>R) #Right wall?
#Stop at walls
r[r_hit_x0,0] = 0
r[r_hit_x1,0] = R
r[r_hit_LR,1] = 0
r[r_hit_RR,1] = R
#Reverse velocities
v[r_hit_x0,0] = -0.9*v[r_hit_x0,0]
v[r_hit_x1,0] = -v[r_hit_x1,0]
v[r_hit_LR,1] = -0.95*v[r_hit_LR,1]
v[r_hit_RR,1] = -0.99*v[r_hit_RR,1]
#Collisions
D = dist.squareform(dist.pdist(r))
ind1, ind2 = np.where(D < pR)
unique = (ind1 < ind2)
ind1 = ind1[unique]
ind2 = ind2[unique]
for i1, i2 in zip(ind1, ind2):
eps = np.random.rand()
vtot= v[i1,:]+v[i2,:]
v[i1,:] = -(1-eps)*vtot
v[i2,:] = -eps*vtot
line.set_ydata(r[:,1])
line.set_xdata(r[:,0])
line2.set_ydata(r[:N//5,1])
line2.set_xdata(r[:N//5,0])
self.canvas.draw()
self.master.after(1, self.update)
yield
def main():
root = tk.Tk()
app = App(root)
tk.mainloop()
if __name__ == '__main__':
main()