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()
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 doing an EEG in python using the matplotlib library,
I generate random informations and I display them in a Tkinter window,
I want to update the animation 10 times per seconds,
So 10 updates should last 1 second, right ?
But instead the animation lasts between 1.1 and 1.3 seconds ...
I guess this is an optimization issue ?
I will be very grateful if you could help me !
How my EEG looks like :
my matplotlib's EEG at 17 seconds
my matplotlib's EEG at 29 seconds
Here's my code (main.py):
import tkinter as tk
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from Sources.Model.grapheEEG5 import grapheEEG5
from matplotlib.animation import FuncAnimation
HAUTEUR = 768
LARGEUR = 1366
root = tk.Tk()
root.title("Interface logicielle d'acquisition")
fenetre = tk.Canvas(root, height=HAUTEUR, width=LARGEUR)
fenetre.pack()
fig = plt.Figure(figsize = (10, 6), dpi = 100)
cadreMilieu = tk.Frame(fenetre, bg='white')
cadreMilieu.place(relx=0.1, rely=0.1, relwidth=0.8, relheight=0.8)
canvas = FigureCanvasTkAgg(fig, master = cadreMilieu).get_tk_widget().pack()
grapheDeroulant = grapheEEG5(8, fig, None, "EEG", "temps (en secondes)", "capteurs")
aniPoints = FuncAnimation(fig, grapheDeroulant.animation, cache_frame_data = False,
save_count = 0, frames=None, blit=False, interval=100, repeat=False)
root.mainloop()
And my class (grapheEEG5.py):
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import random as random
import time
class grapheEEG5:
#variables
axe = None
figure = None
toolbar = None
tailleDonnees = None
nbEEG = None
compteur = 0
#parametres
echelle = 1.0/10
periodeTrame = 0.1
boucle = 0
dureeSecondesFigure = 20
#courbes
ligneCurseur = None
ordonnees = None
ordonneesFantome = None
#coords curseur
cursorY = None
#coords vrais points
xVraiPoints = []
yVraiPoints = []
def __init__(self, nbEEG, figure, toolbar, titre, nomx, nomy, grid = None):
self.nbEEG = nbEEG
self.cursorY = [0, -self.nbEEG-1]
self.figure = figure
self.tailleDonnees = int(self.dureeSecondesFigure / self.periodeTrame)
print(self.tailleDonnees, "donnée(s)")
self.axe = self.figure.add_subplot(111)
self.axe.grid(ls="--", lw=0.5)
self.axe.set_title(titre)
self.axe.set_xlabel(nomx)
self.axe.set_ylabel(nomy)
self.axe.set_xlim(0, self.dureeSecondesFigure)
self.axe.xaxis.set_major_locator(plt.MaxNLocator(self.dureeSecondesFigure))
self.axe.set_ylim(-self.nbEEG-1,0)
self.axe.get_yaxis().set_visible(False)
for i in range(self.nbEEG):
self.xVraiPoints.append([None]*self.tailleDonnees)
self.yVraiPoints.append([None]*self.tailleDonnees)
self.ordonnees = []
self.ordonneesFantome = []
for i in range(self.nbEEG):
self.ordonnees.append(self.axe.plot([], [], lw=0.75, color="black", label="nouvelles valeurs")[0])
self.ordonneesFantome.append(self.axe.plot([], [], lw=0.75, color="grey", label="anciennes valeurs")[0])
self.ligneCurseur, = self.axe.plot([], [], color="red", label="curseur")
if toolbar != None:
self.toolbar = toolbar
self.toolbar.update()
for i in range(nbEEG):
self.axe.text(-30*self.periodeTrame, -i-1, "capteur " + str(i+1))
self.figure.canvas.draw()
def animation(self, i):
temps = self.compteur*self.periodeTrame
for _ in range(self.nbEEG):
self.xVraiPoints[_][self.compteur] = temps
indexEeg = -_-1
y = indexEeg + (random.randint(-3,3) * self.echelle)
self.yVraiPoints[_][self.compteur] = y
self.ordonnees[_].set_data(self.xVraiPoints[_], self.yVraiPoints[_])
cursorX = [temps, temps]
self.ligneCurseur.set_data(cursorX, self.cursorY)
self.compteur += 1
if self.compteur >= self.tailleDonnees:
self.compteur = 0
return self.ordonnees
I printed the time when i call my animations referenced in funcAnimation and it seems that it is never the exact time, for example if i put interval = 0.5
then my script will call it after 0.46 sec or 0.56, and this is this approximation that was creating delay. Now i am using a manual animation and i am calling it with a method that does not drift and it works perfectly.
I'm currently working on a visualisation of different sorting algorithms and am now attempting to embed the animations in a tkinker gui for ease of use. The animations are done with matplotlib and use bar charts. I'm currently struggling to get the graphs to be animated. I had it working without the gui but currently I get a static graph in the gui. Please could I have some advice on how to rectify this. Full code so far can be found at https://github.com/MGedney1/Sorting_Algorithm_Visualisation
Area of interest code:
import random
import time
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation as animation
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import tkinter as tk
from tkinter import Frame,Label,Entry,Button
def bubble_sort(lst): #Bubble Sort
index = len(lst) - 1
while index >= 0:
test_index = index
while test_index >= 0:
if lst[index] < lst[test_index]:
temp = lst[index]
lst[index] = lst[test_index]
lst[test_index] = temp
test_index -= 1
yield lst
index -= 1
class Window(Frame):
def __init__(self, master = None):
Frame.__init__(self,master)
self.master = master
self.set_up_window()
def update_fig(self): #Update fig function
for self.rect, val in zip(self.rects,self.unordered): #Setting height of the rectangles
self.rect.set_height(val)
self.iteration[0] += 1
text.set_text("# of operations: {}".format(iteration[0]))
def set_up_window(self):
n,self.unordered = create_array()
title = 'Test'
generator = bubble_sort(self.unordered)
self.fig,self.ax = plt.subplots() #Creating axis and figure
self.bar_rects = self.ax.bar(range(len(self.unordered)), self.unordered, align="edge") #Creating the rectangular bars
self.ax.set_xlim(0, n) #Axis limits
self.ax.set_ylim(0, int(1.07 * n))
self.text = self.ax.text(0.02, 0.95, "", transform=self.ax.transAxes) #Number of operations counter
self.iteration = [0]
self.anim = animation.FuncAnimation(self.fig, func=self.update_fig, frames=generator, interval=1,repeat=False) #Creating the animatio
self.canvas = FigureCanvasTkAgg(self.fig, master=root)
self.canvas.get_tk_widget().grid(column=0,row=1)
if __name__ == '__main__':
root = tk.Tk()
root.geometry("700x400")
app = Window(root)
tk.mainloop()
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()
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.