Hey I have made this GUI, that shows matplotlib plots in a tkinter interface with a scrolled canvas. But when the plots are added to the canvas the scroll event for the mouse doesn't work anymore. Im totaly new to tkinter so consider this when you check out the code if there can be improvements also. So here it is:
from Tkinter import *
import matplotlib
#matplotlib.use('TkAgg')
from numpy import arange, sin, pi
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import numpy as np
import os
import matplotlib.mlab as mlab
class App:
def __init__(self, master, figureList=[]):
frame = Frame(master, bg='#00BFFF', borderwidth=1, relief=RAISED)
frame.pack(expand=YES, fill=BOTH)
master.title("Listed Plots")
#Fullscreen for windows
if os.name == 'nt':
master.wm_state('zoomed')
#Create a Canvas
canvas=Canvas(frame,bg='#00BFFF', relief=SUNKEN)
canvas.config(highlightthickness=0)
#Create a Scrollbar Horisontal
hbar=Scrollbar(frame,orient=HORIZONTAL)
hbar.pack(side=BOTTOM,fill=X)
hbar.config(command=canvas.xview)
#Create a Scrollbar Vertical
vbar=Scrollbar(frame,orient=VERTICAL)
vbar.pack(side=RIGHT,fill=Y)
vbar.config(command=canvas.yview)
canvas.config(xscrollcommand=hbar.set, yscrollcommand=vbar.set)
canvas.pack(side=TOP,expand=True,fill=BOTH)
#Create a Frame in the canvas
canvFrame = Frame(canvas, bg='#00BFFF')
canvFrame.pack()
Label(canvFrame, text="Matplots of data", bg='#B4EEB4').pack(side=TOP,fill=X)
plotNbr = 1
windowHeight = 10
for fig in figureList:
Label(canvFrame, text="#Plotnumber: " + str(plotNbr), bg='#FF7F24').pack(side=TOP,fill=X)
frm = Frame(canvFrame, bg='#9FB6CD')
frm.pack(side=TOP, fill=X)
self.canvasMPL2, self.canvasMPLToolBar2 = getCanvas(frm, fig)
self.canvasMPL2.pack(side=TOP)
self.canvasMPLToolBar2.pack(side=TOP)
#Create space for the plot and tool bar.
windowHeight = windowHeight + 680
plotNbr = plotNbr + 1
canvas.config(width=1200,height= windowHeight)
canvas.config(scrollregion=(0,0,1200, windowHeight))
canvas.create_window(0,0, anchor = NW, window = canvFrame, width = 1200, height = windowHeight)
canvas.focus_set() #Doesnt work with FigureCanvasTkAgg, this steals the focus
#canvas.focus_force()
#canvas.grab_set_global()
#scrollwheel settings
canvas.configure(yscrollincrement='25')
def rollWheel(event):
#print "Mousescroll"
direction = 0
if event.num == 5 or event.delta == -120:
direction = 1
if event.num == 4 or event.delta == 120:
direction = -1
event.widget.yview_scroll(direction, UNITS)
canvas.bind('<MouseWheel>', lambda event: rollWheel(event))
canvas.bind('<Button-4>', lambda event: rollWheel(event))
canvas.bind('<Button-5>', lambda event: rollWheel(event))
plottedFigures = [] #To store matplotlib figures
def addPlottedFig(figure):
'''
Matplotlib figures to be shown in the GUI
'''
plottedFigures.append(figure)
def getCanvas(masterWidget, figure=None):
'''
Returns canvas of plot and canvas of tool bar
'''
if(figure==None):
f = getHistoGramPlot() #For testing
canvas = FigureCanvasTkAgg(f, master=masterWidget)
else:
canvas = FigureCanvasTkAgg(figure, master=masterWidget)
canvas.show()
toolbar = NavigationToolbar2TkAgg( canvas, masterWidget )
toolbar.update()
#return the plot and the toolbar
return canvas.get_tk_widget(), canvas._tkcanvas
def initiate(FigureList=None):
'''
Start the GUI Application
'''
root = Tk()
app = App(root, FigureList)
root.mainloop()
def main():
'''
Shows 2 histograms plots in a tkinter GUI
'''
fig1 = getHistoGramPlot()
fig2 = getHistoGramPlot()
figList = [fig1, fig2]
initiate(figList)
def getHistoGramPlot():
'''
Return a figure of a histogram
'''
mu, sigma = 100, 15
x = mu + sigma * np.random.randn(10000)
fig = plt.figure(figsize=(12, 6), dpi=100)
ax = fig.add_subplot(111)
n, bins, patches = ax.hist(x, 50, normed=1, facecolor='green', alpha=0.6)
bincenters = 0.5*(bins[1:]+bins[:-1])
mu = np.median(x)
sigma = np.std(x)
y = mlab.normpdf( bincenters, mu, sigma)
l = ax.plot(bincenters, y, 'r--', linewidth=1)
ax.set_xlabel('Values')
ax.set_ylabel('Probability')
xlimTop = max(x)
xlimBottom = min(x)
ax.set_xlim(xlimBottom, xlimTop)
ax.grid(True)
return fig
if __name__ == '__main__':
main()
For a quick & dirty solution:
Make the main canvas an attribute of your App class
Make rollWheel a method of your App class
Create your mousewheel binding on the window rather than the canvas. This will
capture mousewheel events for all widgets inside the window.
Scroll the canvas on mousewheel events
from Tkinter import *
import matplotlib
#matplotlib.use('TkAgg')
from numpy import arange, sin, pi
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import numpy as np
import os
import matplotlib.mlab as mlab
class App:
def __init__(self, master, figureList=[]):
frame = Frame(master, bg='#00BFFF', borderwidth=1, relief=RAISED)
frame.pack(expand=YES, fill=BOTH)
master.title("Listed Plots")
#Fullscreen for windows
if os.name == 'nt':
master.wm_state('zoomed')
#Create a Canvas
self.canvas = canvas = Canvas(frame,bg='#00BFFF', relief=SUNKEN)
canvas.config(highlightthickness=0)
#Create a Scrollbar Horisontal
hbar=Scrollbar(frame,orient=HORIZONTAL)
hbar.pack(side=BOTTOM,fill=X)
hbar.config(command=canvas.xview)
#Create a Scrollbar Vertical
vbar=Scrollbar(frame,orient=VERTICAL)
vbar.pack(side=RIGHT,fill=Y)
vbar.config(command=canvas.yview)
canvas.config(xscrollcommand=hbar.set, yscrollcommand=vbar.set)
canvas.pack(side=TOP,expand=True,fill=BOTH)
#Create a Frame in the canvas
canvFrame = Frame(canvas, bg='#00BFFF')
canvFrame.pack()
Label(canvFrame, text="Matplots of data", bg='#B4EEB4').pack(side=TOP,fill=X)
plotNbr = 1
windowHeight = 10
for fig in figureList:
Label(canvFrame, text="#Plotnumber: " + str(plotNbr), bg='#FF7F24').pack(side=TOP,fill=X)
frm = Frame(canvFrame, bg='#9FB6CD')
frm.pack(side=TOP, fill=X)
self.canvasMPL2, self.canvasMPLToolBar2 = getCanvas(frm, fig)
self.canvasMPL2.pack(side=TOP)
self.canvasMPLToolBar2.pack(side=TOP)
#Create space for the plot and tool bar.
windowHeight = windowHeight + 680
plotNbr = plotNbr + 1
canvas.config(width=1200,height= windowHeight)
canvas.config(scrollregion=(0,0,1200, windowHeight))
canvas.create_window(0,0, anchor = NW, window = canvFrame, width = 1200, height = windowHeight)
canvas.focus_set() #Doesnt work with FigureCanvasTkAgg, this steals the focus
#canvas.focus_force()
#canvas.grab_set_global()
#scrollwheel settings
canvas.configure(yscrollincrement='25')
## def rollWheel(event):
## #print "Mousescroll"
## direction = 0
## if event.num == 5 or event.delta == -120:
## direction = 1
## if event.num == 4 or event.delta == 120:
## direction = -1
## event.widget.yview_scroll(direction, UNITS)
master.bind('<MouseWheel>', lambda event,s=self: self.rollWheel(event))
canvas.bind('<Button-4>', lambda event: rollWheel(event))
canvas.bind('<Button-5>', lambda event: rollWheel(event))
def rollWheel(self, event):
#print "Mousescroll"
direction = 0
if event.num == 5 or event.delta == -120:
direction = 1
if event.num == 4 or event.delta == 120:
direction = -1
## event.widget.yview_scroll(direction, UNITS)
self.canvas.yview_scroll(direction, UNITS)
plottedFigures = [] #To store matplotlib figures
def addPlottedFig(figure):
'''
Matplotlib figures to be shown in the GUI
'''
plottedFigures.append(figure)
def getCanvas(masterWidget, figure=None):
'''
Returns canvas of plot and canvas of tool bar
'''
if(figure==None):
f = getHistoGramPlot() #For testing
canvas = FigureCanvasTkAgg(f, master=masterWidget)
else:
canvas = FigureCanvasTkAgg(figure, master=masterWidget)
canvas.show()
toolbar = NavigationToolbar2TkAgg( canvas, masterWidget )
toolbar.update()
#return the plot and the toolbar
return canvas.get_tk_widget(), canvas._tkcanvas
def initiate(FigureList=None):
'''
Start the GUI Application
'''
root = Tk()
app = App(root, FigureList)
root.mainloop()
def main():
'''
Shows 2 histograms plots in a tkinter GUI
'''
fig1 = getHistoGramPlot()
fig2 = getHistoGramPlot()
figList = [fig1, fig2]
initiate(figList)
def getHistoGramPlot():
'''
Return a figure of a histogram
'''
mu, sigma = 100, 15
x = mu + sigma * np.random.randn(10000)
fig = plt.figure(figsize=(12, 6), dpi=100)
ax = fig.add_subplot(111)
n, bins, patches = ax.hist(x, 50, normed=1, facecolor='green', alpha=0.6)
bincenters = 0.5*(bins[1:]+bins[:-1])
mu = np.median(x)
sigma = np.std(x)
y = mlab.normpdf( bincenters, mu, sigma)
l = ax.plot(bincenters, y, 'r--', linewidth=1)
ax.set_xlabel('Values')
ax.set_ylabel('Probability')
xlimTop = max(x)
xlimBottom = min(x)
ax.set_xlim(xlimBottom, xlimTop)
ax.grid(True)
return fig
if __name__ == '__main__':
main()
The indentation for class App: code was dedented improperly. I assume that rollWheel is a function defined within __init__.
Make this change:
def rollWheel(event):
#print "Mousescroll"
direction = 0
if event.num == 5 or event.delta == -120:
direction = 1
if event.num == 4 or event.delta == 120:
direction = -1
# Tell the canvas to scroll directly
canvas.yview_scroll(direction, UNITS)
canvas.bind_all('<MouseWheel>', lambda event: rollWheel(event))
canvas.bind_all('<Button-4>', lambda event: rollWheel(event))
canvas.bind_all('<Button-5>', lambda event: rollWheel(event))
For more information on .bind_all and tkinter binding in general check out effbot's tutorial.
Related
I am currently working on a program that I am creating with Tkinter. Thereby large matrices (signals) are read in, which I represent as an image. In addition, I would like to display the signal at the point X (red vertical line) in the adjacent plot (interactive).
# Imports
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
import numpy as np
from tkinter import *
# Global: Selected points with cursor
points = []
# Cursor
class Cursor:
def __init__(self, ax):
self.ax = ax
self.background = None
self.horizontal_line = ax.axhline(color='r', lw=0.8, ls='--')
self.vertical_line = ax.axvline(color='r', lw=0.8, ls='--')
self._creating_background = False
ax.figure.canvas.mpl_connect('draw_event', self.on_draw)
def on_draw(self, event):
self.create_new_background()
def set_cross_hair_visible(self, visible):
need_redraw = self.horizontal_line.get_visible() != visible
self.horizontal_line.set_visible(visible)
self.vertical_line.set_visible(visible)
return need_redraw
def create_new_background(self):
if self._creating_background:
return
self._creating_background = True
self.set_cross_hair_visible(False)
self.ax.figure.canvas.draw_idle()
self.background = self.ax.figure.canvas.copy_from_bbox(self.ax.bbox)
self.set_cross_hair_visible(True)
self._creating_background = False
def on_mouse_move(self, event, mode: str, matrix=None):
if self.background is None:
self.create_new_background()
if not event.inaxes:
need_redraw = self.set_cross_hair_visible(False)
if need_redraw:
self.ax.figure.canvas.restore_region(self.background)
self.ax.figure.canvas.blit(self.ax.bbox)
else:
self.set_cross_hair_visible(True)
x, y = event.xdata, event.ydata
if mode == "both":
self.horizontal_line.set_ydata(y)
self.vertical_line.set_xdata(x)
self.ax.figure.canvas.restore_region(self.background)
self.ax.draw_artist(self.horizontal_line)
self.ax.draw_artist(self.vertical_line)
elif mode == "horizontal":
self.ax.cla()
self.ax.plot(matrix[:, int(x)], range(0, matrix.shape[0], 1))
self.ax.figure.canvas.draw_idle()
self.horizontal_line.set_ydata(y)
self.ax.figure.canvas.restore_region(self.background)
self.ax.draw_artist(self.horizontal_line)
self.ax.figure.canvas.blit(self.ax.bbox)
# Graphical User Interface
class ToolGUI:
def __init__(self, master):
self.master = master
# Matrix (Example)
self.matrix = np.random.rand(3000, 5000)
# Subplots
self.fig = plt.figure(constrained_layout=True)
self.spec = self.fig.add_gridspec(5, 6)
self.ax_main = self.fig.add_subplot(self.spec[:, :-1])
self.ax_main.imshow(self.matrix, cmap='gray', aspect='auto')
self.ax_right = self.fig.add_subplot(self.spec[:, -1:], sharey=self.ax_main)
self.ax_right.get_yaxis().set_visible(False)
# Canvas - Drawing Area
self.canvas = FigureCanvasTkAgg(self.fig, master=master)
self.canvas.get_tk_widget().grid(column=0, row=0, sticky=NSEW)
# Cursor with crosshair
self.cursor_main = Cursor(self.ax_main)
self.fig.canvas.mpl_connect('motion_notify_event', lambda event: self.cursor_main.on_mouse_move(event, mode="both"))
self.cursor_right = Cursor(self.ax_right)
self.fig.canvas.mpl_connect('motion_notify_event', lambda event: self.cursor_right.on_mouse_move(event, mode="horizontal", matrix=self.matrix))
# Update Canvas
self.canvas.draw() # Update canvas
# Create root window
root = Tk()
# Root window title
root.title("Tool")
# Create GUI
my_gui = ToolGUI(root)
# Execute Tkinter
root.mainloop()
This example is only a small part of my program. In my full program, for example, certain points are picked out manually. With the help of the interactive plot a more exact selection of such points is possible.
Unfortunately, the program runs very slowly due to the use of this interactive plot. Since I haven't been working with Python for too long, I would appreciate any suggestions for improvement!
Thanks in advance! - Stefan
I am building a GUI that takes in sensor data from the raspberry pi and displays it onto a window via matplotlib animation. The code works fine, except when being run on raspberry pi, the matplotlib animation takes some time to execute, which momentarily blocks the sensor reading GetCPM that I'm interested in. How can I make both these programs run simultaneously without one clogging the other, I've tried the multiprocessing library, but I can't seem to get it to work.
Note: The sensor data that I'm plotting does not have to have a high sample rate, its the sensor that I'm displaying on label that does.
Here is my code
import matplotlib
matplotlib.use("TkAgg")
import numpy as np
import tkinter as tk
from tkinter import ttk
from tkinter import *
import math
import datetime as dt
import time
from collections import Counter
import random as rn
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
import matplotlib.animation as animation
from matplotlib import style
import matplotlib.pyplot as plt
import threading as td
import multiprocessing as mp
from multiprocessing import Process, Queue
style.use('seaborn')
limit = np.array([5])
# Initialize Pressure Figure
fig1, ax1 = plt.subplots()
fig2, ax2 = plt.subplots()
x1, y1 = [], []
x2, y2 = [], []
TOT = []
CPM = 0
def GetValues(i, x, y, ax):
volts2 = rn.uniform(3,6)
x.append(dt.datetime.now().strftime('%H: %M: %S.%f'))
y.append(float(volts2))
x = x[-50:]
y = y[-50:]
ax.clear()
ax.plot(x, y, linewidth=1, color= 'k')
ax.fill_between(x, y, limit[0], where=(y > limit[0]), facecolor='forestgreen', alpha=0.7, interpolate=True)
ax.fill_between(x, y, limit[0], where=(y < limit[0]), facecolor='darkred', alpha=0.7, interpolate=True)
ax.set_xticklabels([])
def animate(i, x, y, ax):
volts = rn.uniform(2,8)
x.append(dt.datetime.now().strftime('%H: %M: %S.%f'))
y.append(float(volts))
x = x[-50:]
y = y[-50:]
ax.clear()
ax.plot(x, y, linewidth=1, color= 'k')
ax.fill_between(x, y, limit[0], where=(y > limit[0]), facecolor='forestgreen', alpha=0.7, interpolate=True)
ax.fill_between(x, y, limit[0], where=(y < limit[0]), facecolor='darkred', alpha=0.7, interpolate=True)
ax.set_xticklabels([])
def GetCPM():
global TOT, CPM
temp = 1
# Test Case
if temp == True:
TOT.append(True)
else:
TOT.append(False)
TOT = TOT[-2750:]
count = Counter(TOT)
CPM = count[True]
return CPM
class App(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# If you want to customize the icon of the tk window, only accepts .ico
#tk.Tk.iconbitmap(self, default="iconname.ico")
tk.Tk.wm_title(self, "Pressure")
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0,weight=1)
container.grid_columnconfigure(0,weight=1)
self.frames = {}
frame = GUI(container, self)
self.frames[GUI] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(GUI)
def show_frame(self,cont):
frame = self.frames[cont]
frame.tkraise()
class GUI(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
canvas1 = FigureCanvasTkAgg(fig1, self)
canvas1.get_tk_widget().pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True)
canvas2 = FigureCanvasTkAgg(fig2, self)
canvas2.get_tk_widget().pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True)
#Initialize label as self.
lbl = tk.Label(self, font = ('Sans Serif', 40, 'bold'),
background = 'purple',
foreground = 'White')
lbl.place(relx=0.5, rely=0.5, anchor=CENTER)
def update():
ll = GetCPM()
lbl.config(text = "CPM = {}".format(ll))
print(dt.datetime.now().strftime('%H: %M: %S.%f'))
lbl.after(20, update)
update()
# interval determines the speed at which data is recorded, 1000 = 1 second
if __name__ == '__main__':
app = App()
t1 = mp.Process(target=GetCPM)
t1.start()
t1.join()
ani_1 = animation.FuncAnimation(fig1, animate, interval = 500,
fargs=(x1, y1, ax1))
ani_2 = animation.FuncAnimation(fig2, GetValues, interval = 500,
fargs=(x2, y2, ax2))
app.mainloop()
I would like to make an equivalent of the function FuncAnimation from matplotlib.animation, in which I could control the current plotted data using the scrollbar.
Say you have a data array which contains data points to be plotted at each time i. When using FuncAnimation, you first need to define a function ( here animate(i) ) which will be called for each time i = 1 to len(data[:,0]) :
def animate(i):
ax.plot(data[i,:])
anim = FuncAnimation(fig, animate, interval=100, frames=len(data[:,0]))
plt.draw()
plt.show()
but you cannot control the time i, like with a play/stop functionality. What I would like to do is to call the function animate(i), with i being the position of the scrollbar.
I found this example ( using the events from matplotlib:
https://matplotlib.org/3.2.1/users/event_handling.html )
but the mpl_connect doesn't have a "scrollbar_event".
import tkinter
from random import randint
import matplotlib as plt
import numpy as np
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
# Implement the default Matplotlib key bindings.
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
root = tkinter.Tk()
root.wm_title("Embedding in Tk")
#create figure
fig = Figure(figsize=(5, 4), dpi=100)
ax = fig.add_axes([0,0,1,1])
ax.imshow(np.array([[0,10],[23,40]]))
#create canvas with figure
canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea.
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
def on_key_press(event):
ax.clear()
ax.imshow(np.array([[randint(0,30),randint(0,30)],[randint(0,30),randint(0,30)]]))
canvas.draw_idle()
key_press_handler(event, canvas)
print("you pressed {}".format(event.key))
#connect canvas to event function
canvas.mpl_connect("key_press_event", on_key_press)
def _quit():
root.quit() # stops mainloop
root.destroy() # this is necessary on Windows to prevent
# Fatal Python Error: PyEval_RestoreThread: NULL tstate
button = tkinter.Button(master=root, text="Quit", command=_quit)
button.pack(side=tkinter.BOTTOM)
tkinter.mainloop()
Actually the scroll functionality is given by matplotlib widgets !!
The example below works fine :
import matplotlib
import tkinter as Tk
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from myPytHeader import *
matplotlib.use('TkAgg')
root = Tk.Tk()
root.wm_title("Embedding in TK")
fig = plt.Figure(figsize=(8, 6))
canvas = FigureCanvasTkAgg(fig, root)
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
nDt = nbLines("grid.dat")
nDx = nbGridPoints("grid.dat")
grid = np.zeros( (nDt,nDx) ) ; loadData("grid.dat", grid)
valu = np.zeros( (nDt,nDx) ) ; loadData("valu.dat", valu)
ax=fig.add_subplot(111)
fig.subplots_adjust(bottom=0.25)
ax_time = fig.add_axes([0.12, 0.1, 0.78, 0.03])
s_time = Slider(ax_time, 'Time', 0, nDt, valinit=0, valstep=1)
def update(val):
frame = int(s_time.val)
ax.clear()
ax.set(xlim=(-0.05, 1.05), ylim=(-0.05, 1.25))
ax.grid()
ax.scatter(grid[frame,:], valu[frame,:], color='b', marker='.')
fig.canvas.draw_idle()
s_time.on_changed(update)
Tk.mainloop()
After all these years I've found solutions to my problems here, I am in debt !!!
Here is the final solution I came with.
I hope it can be useful to someone someday somehow !!
import matplotlib
import numpy as np
import tkinter as tk
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib.widgets import Slider
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
# load data
nDt = 1000
nDx = 400
grd = np.zeros( (nDt,nDx) )
val = np.zeros( (nDt,nDx) )
for t in np.arange(nDt):
for x in np.arange(nDx):
grd[t,x] = x / nDx
val[t,x] = (x / nDx) * (t/nDt) * np.sin(10 * 2*np.pi * (t-x)/nDt)
matplotlib.use('TkAgg')
root = tk.Tk()
root.wm_title("Embedding in TK")
fig = plt.Figure(figsize=(8, 6))
canvas = FigureCanvasTkAgg(fig, root)
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
ax=fig.add_subplot(111)
fig.subplots_adjust(bottom=0.25)
ax.set(xlim=(-0.05, 1.05), ylim=(-1.05, 1.05))
ax.grid()
scat = ax.scatter(grd[0,:], val[0,:], color='b', marker='.')
ax_time = fig.add_axes([0.12, 0.1, 0.78, 0.03])
s_time = Slider(ax_time, 'Time', 0, nDt, valinit=0, valstep=1)
i_anim = 0
i_relative = 0
i_current = 0
def updateGraph(i):
y_i = val[i,:]
scat.set_offsets(np.c_[grd[i,:], y_i])
def updateFromAnim(i):
global i_anim
global i_current
global i_relative
i_anim = i
i_current = i + i_relative
s_time.set_val(i_current)
updateGraph(i_current)
def updateFromScroll(val):
global i_anim
global i_current
global i_relative
i_relative = int(s_time.val) - i_anim
i_current = int(s_time.val)
updateGraph(i_current)
def onClick():
global anim_running
if anim_running:
anim.event_source.stop()
anim_running = False
else:
anim.event_source.start()
anim_running = True
start_button = tk.Button(root, text="START/STOP", command=onClick)
start_button.pack()
anim_running = True
anim = FuncAnimation(fig, updateFromAnim, interval=100, frames=nDt)
s_time.on_changed(updateFromScroll)
tk.mainloop()
When you run the code the error should be pretty obvious, just click your mouse in the black space and move it around. I'm not sure how the line segments are being created, I pulled it from the library and its kind of confusing. How can I get the line segments to plot along my scatter plot? When I looks at the segments array being generated I believe it’s creating pairs of x,x and y,y instead of x,y and x,y
import tkinter as tk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.collections import LineCollection
from matplotlib.colors import ListedColormap
import numpy as np
import random
class Mouse_Game:
'''Creates a matplotlib canvas object that plot mouse coordinates when animated.'''
def __init__(self, root, width=400, height=400):
self.frame = tk.Frame(master=root)
self.status = False
self.fig = plt.Figure(dpi=100, facecolor='black')
self.ax = self.fig.add_axes([0,0,1,1], fc='black')
self.width = width
self.height = height
self.ax.axis('off')
#set up the canvas object and bind commands
self.canvas = FigureCanvasTkAgg(self.fig, master=self.frame, resize_callback=self.configure) # A tk.DrawingArea.
self.canvas.draw()
self.canvas.get_tk_widget().pack(side='top', fill='both', expand=True)
self.fig.canvas.mpl_connect('button_press_event', self.animate)
self.fig.canvas.mpl_connect('motion_notify_event', self.motion)
def animate(self, event):
#Check if currently running
if not self.status:
self.status = True
self.capture = np.array([(event.xdata, event.ydata)]*40)
else:
self.status = False
#plot a line that follows the mouse around
while self.status:
self.ax.clear()
self.ax.set_xlim(0, self.width)
self.ax.set_ylim(0, self.height)
x = self.capture[:,0]
y = self.capture[:,1]
############################################################
points = self.capture.T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
###########################################################
lc = LineCollection(segments, cmap='magma')
lc.set_array(x)
lc.set_linewidth(2)
self.ax.add_collection(lc)
self.ax.scatter(x, y, marker='o')
self.fig.canvas.draw()
self.fig.canvas.flush_events()
def motion(self, event):
if self.status:
#Append mouse coordinates to array when the mouse moves
self.capture = self.capture[1:40]
self.capture = np.append(self.capture, [(event.xdata, event.ydata)], axis=0)
def configure(self, event):
#Used to adjust coordinate setting when the screen size changes
self.width, self.height = self.canvas.get_width_height()
def _quit():
#destroy window
root.quit()
root.destroy()
root = tk.Tk()
root.wm_title("Mouse Plot")
MG = Mouse_Game(root=root)
MG.frame.pack(expand=True, fill='both')
button = tk.Button(root, text="Quit", command=_quit)
button.pack(side='bottom', pady=10)
tk.mainloop()
I'm not sure why you transpose the array. If you leave the transposition out, it'll work just fine
points = self.capture.reshape(-1, 1, 2)
I would like to draw a matrix with imshow with tkinter in a gui. The problem is that after further updates, the gui crash. I don't manage to find answer on the web. You could help me please?
The code:
from numpy import *
from Tkinter import *
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
root = Tk()
f1 = Figure()
canvas = FigureCanvasTkAgg(f1, master=root)
canvas.show()
canvas.get_tk_widget().pack(fill="x")
a = f1.add_subplot(111)
a.get_axes().set_frame_on(True)
ini = [[i] * 100 for i in range(100)]
cax = a.matshow(ini)
while True:
mat = random.randint(0, 2**16-1, (1000, 1000))
cax.set_data(mat)
canvas.draw()
root.mainloop()
Thank you for your suggestion fhdrsdg but doing this way freeze the windows during the exection of redraw which is boring especially when this previous has a lot of stuff to do.
Here is my code:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from Tkinter import Button, Label, Text, Checkbutton, Radiobutton, Frame, Tk, Entry, INSERT, StringVar, IntVar, Toplevel, END
from ttk import Notebook, Combobox
from numpy import arange, zeros, array, uint16, empty, divide, random, ravel
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
from matplotlib import cm
from matplotlib.image import AxesImage
import time
from threading import Thread
import os
class Image_direct(Thread):
def __init__(self):
Thread.__init__(self)
self.encore = True
def run(self):
"""This loop can be long ~10s"""
while self.encore:
time.sleep(1)
app.cax.set_extent((0, 1023, 1023, 0))
mat = random.randint(0, 2**16-1, (1024, 1024)).astype("uint16")
app.update_camera(mat)
def stop(self):
self.encore = False
class Deu(Frame):
def __init__(self, master):
Frame.__init__(self, master)
self.creer_applets()
def creer_applets(self):
self.fen4 = Frame(fen)
self.fen4.pack(side="bottom", fill="both")
self.fen1 = Frame(fen, width=200)
self.fen1.pack(side="left", fill="both", expand=False)
self.note = Notebook(fen, width=1)
self.tab_mat = Frame(self.note)
self.note.add(self.tab_mat, text = "Caméra", compound="top")
self.note.pack(side="left", fill="both", expand=True)
self.fen3 = Frame(fen, width=250)
self.fen3.pack(side="left", fill="both", expand=False)
Button(self.fen4, text="Quit", command=self.aurevoir).pack(fill="x", side="top")
self.interp = StringVar()
self.interp.set("none")
liste_interp = ["none", "nearest", "bilinear", "bicubic", "spline16", "spline36", "hanning", "hamming", "hermite", "kaiser", "quadric", "catrom", "gaussian", "bessel", "mitchell", "sinc", "lanczos"]
self.choix_interp = Combobox(self.tab_mat, textvariable=self.interp, state="readonly", width=10)
self.choix_interp['values'] = liste_interp
self.cmap = StringVar()
self.cmap.set("jet")
palettes = sorted(m for m in cm.datad if not m.endswith("_r"))
self.choix_palette = Combobox(self.tab_mat, textvariable=self.cmap, state="readonly", width=10)
self.choix_palette['values'] = palettes
self.bouton_palette = Button(self.tab_mat, text="Changer la palette", command=self.changer_cmap)
self.f1 = Figure()
self.canvas1 = FigureCanvasTkAgg(self.f1, master=self.tab_mat)
self.canvas1.show()
self.canvas1.get_tk_widget().pack(fill="both", expand=1)
NavigationToolbar2TkAgg(self.canvas1, self.tab_mat)
self.a = self.f1.add_subplot(111)
self.bg = self.canvas1.copy_from_bbox(self.a.bbox)
self.a.get_axes().set_frame_on(True)
ini = random.randint(0, 2**16-1, (1024, 1024))
self.cax = self.a.matshow(ini, cmap=self.cmap.get(), interpolation=self.interp.get(), picker=True, alpha=1.0)
self.a.format_coord = lambda x, y: 'x=%d, y=%d, z=%d' % (x, y, ini[round(y), round(x)])
self.cbar = self.f1.colorbar(self.cax)
self.cbar.set_label("coups")
self.bouton_palette.pack(side="left")
self.choix_interp.pack(side="left")
self.choix_palette.pack(side="left")
Button(self.tab_mat, text=">", command=lambda: self.changer_cbar(1)).pack(side="right")
self.cbar_auto = IntVar()
self.chb3 = Checkbutton(self.tab_mat, text="Auto?", variable=self.cbar_auto, onvalue=1, offvalue=0, indicatoron=0, command=lambda: self.changer_cbar(0))
self.chb3.select()
self.chb3.pack(side="right")
Button(self.tab_mat, text="<", command=lambda: self.changer_cbar(-1)).pack(side="right")
self.bouton_direct_on = Button(self.fen3, width=20, text="Démarrer le direct", command=self.image_direct_on)
self.bouton_direct_on.place(x=0, y=400)
self.bouton_direct_off = Button(self.fen3, width=20, text="Arrêter le direct", command=self.image_direct_off)
self.bouton_direct_off.config(state="disabled")
self.bouton_direct_off.place(x=0, y=430)
def changer_cbar(self, sens):
if sens == -1:
self.cbar.set_clim(vmin=self.cax.get_array().min(), vmax=0.9*self.cbar.get_clim()[1])
elif sens == 0 and self.cbar_auto.get():
self.cbar.set_clim(vmin=self.cax.get_array().min(), vmax=self.cax.get_array().max())
elif sens == 1:
self.cbar.set_clim(vmin=self.cax.get_array().min(), vmax=2*self.cbar.get_clim()[1])
self.cax.set_clim(self.cbar.get_clim())
self.canvas1.restore_region(self.bg)
self.a.draw_artist(self.f1)
self.canvas1.blit(self.f1.bbox)
def changer_cmap(self):
self.cax.set_cmap(self.cmap.get())
self.cax.set_interpolation(self.interp.get())
self.canvas1.draw()
def update_camera(self, mat):
xmin = min([int(i) for i in app.a.get_xlim()])
xmax = max([int(i) for i in app.a.get_xlim()])
ymin = min([int(i) for i in app.a.get_ylim()])
ymax = max([int(i) for i in app.a.get_ylim()])
self.a.format_coord = lambda x, y: 'x=%d, y=%d, z=%d' % (x, y, mat[round(y), round(x)])
self.cax.set_data(mat)
self.changer_cbar(0)
def image_direct_on(self):
self.bouton_direct_off.config(state="normal")
self.bouton_direct_on.config(state="disabled")
self.dire = Image_direct()
self.dire.setDaemon(True)
self.dire.start()
def image_direct_off(self):
self.bouton_direct_off.config(state="disabled")
self.bouton_direct_on.config(state="normal")
self.dire.stop()
del self.dire
def aurevoir(self):
try:
self.dire.isAlive()
except:
pass
else:
self.dire.stop()
fen.quit()
fen.destroy()
if __name__ == '__main__':
fen = Tk()
fen.geometry("1300x750")
app = Deu(fen)
fen.mainloop()
What is strange is that:
the crash occurs when the cursor is on the image
under linux, there is no crash.
It can happen in few seconds or few minutes when the cursor is over the image or when I zoom/dezoom the image. Then the window becomes white, and a pop-up appears with the message "pythonw.exe not responding". Idle says nothing. I am in the complete fog :/
I think the problem is with running the while True loop.
Try to limit the time between each repetition using the after method.
The following code updates the canvas every 100ms
from numpy import *
from Tkinter import *
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
root = Tk()
f1 = Figure()
canvas = FigureCanvasTkAgg(f1, master=root)
canvas.show()
canvas.get_tk_widget().pack(fill="x")
a = f1.add_subplot(111)
a.get_axes().set_frame_on(True)
ini = [[i] * 100 for i in range(100)]
cax = a.matshow(ini)
def redraw():
mat = random.randint(0, 2**16-1, (1000, 1000))
cax.set_data(mat)
canvas.draw()
root.after(100, redraw)
redraw()
root.mainloop()