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 have created a tkinter GUI that when a button is pushed a tk.toplevel is created with an interactive animated chart inside. When I run it for the first time and push the button to create the tk.toplevel with the chart, it works well, but when I close the tk.toplevel and push the button to open a new tk.toplevel, another extra chart appears. Apart from that, If I close the program and I want to run it again I have to close the terminal because it freezes.
I have tried removing plt.ion() and doing plt.show() but none of these options solve the problem. Also, I have seen the use of multiprocessing, but I don't really understand it well and I don't know why is it needed.
Why does it happen? How can I solve this problem?
Here is the code that I have used:
import tkinter as tk
import matplotlib
import matplotlib.figure as mf
import matplotlib.pyplot as plt
import matplotlib.animation as ani
from matplotlib.animation import FuncAnimation
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
class SampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
container = tk.Frame(self)
container.pack()
frame = MAININTERFACE(parent=container, controller=self)
frame.grid(row=0, column=0, sticky="nsew")
def graph(self):
grafica1=GRAPHICATION(controller=self)
grafica1.Graph()
class MAININTERFACE(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.button=tk.Button(self, text='GRAPH', command=lambda: self.controller.graph())
self.button.pack(pady=20)
class GRAPHICATION(tk.Frame):
def __init__(self,controller):
tk.Frame.__init__(self)
self.controller=controller
self.x=[]
self.y=[]
def animation_frame(self, i):
if i==0.0:
self.time=0
self.energy=0
else:
self.time=self.time+1
self.energy=self.energy+1
self.x.append(self.time)
self.y.append(self.energy)
self.line.set_data(self.x,self.y)
self.ax.axis([0,10,0,10])
def Graph(self):
self.graphtoplevel=tk.Toplevel(self.controller)
self.graphtoplevel.title('Toplevel')
self.fig, self.ax = plt.subplots()
self.graph=FigureCanvasTkAgg(self.fig, self.graphtoplevel)
self.image=self.graph.get_tk_widget()
plt.ion()
self.line, = self.ax.plot(self.x,self.y)
self.image.grid(row=0, column=0, sticky='nsew')
self.animation=FuncAnimation(self.fig,func=self.animation_frame,frames=np.arange(0,11,1),interval=500, repeat=False)
self.snap_cursor = SnaptoCursor(self.ax, self.x, self.y)
self.fig.canvas.mpl_connect('button_press_event', self.snap_cursor.mouse_click)
class SnaptoCursor(object):
"""
Like Cursor but the crosshair snaps to the nearest x, y point.
For simplicity, this assumes that *x* is sorted.
"""
def __init__(self, ax, x, y):
self.ax = ax
self.lx = ax.axhline(color='k', linewidth=0.5) # the horiz line
self.ly = ax.axvline(color='k', linewidth=0.5) # the vert line
self.x = x
self.y = y
# text location in axes coords
self.txt = ax.text(0.05, 0.9, '', transform=ax.transAxes)
def mouse_click(self, event):
if not event.inaxes:
return
x, y = event.xdata, event.ydata
indx = min(np.searchsorted(self.x, x), len(self.x) - 1)
x = self.x[indx]
y = self.y[indx]
# update the line positions
self.lx.set_ydata(y)
self.ly.set_xdata(x)
self.txt.set_text('x=%1.2f, y=%1.2f' % (x, y))
self.ax.figure.canvas.draw()
if __name__ == "__main__":
app = SampleApp()
app.geometry('500x200')
app.title('MAIN GUI')
app.mainloop()
You need to make changes to your def Graph(self) function, as there are lines that are creating a window with tk.Toplevel and then the plt is creating another window.
Make your changes as follows, the capitalized comments you must do, and the others are optional for setting of titles:
def Graph(self):
#REMOVE THESE LINES
#self.graphtoplevel=tk.Toplevel(self.controller)
#self.graphtoplevel.title('Toplevel')
#modify the window title to your liking by assigning num
self.fig, self.ax = plt.subplots(num="TopLevel")
#REMOVE THESE LINES
#self.graph=FigureCanvasTkAgg(self.fig, self.graphtoplevel)
#self.image=self.graph.get_tk_widget()
plt.ion()
self.line, = self.ax.plot(self.x,self.y)
#REMOVE THIS LINE
#self.image.grid(row=0, column=0, sticky='nsew')
self.animation=FuncAnimation(self.fig,func=self.animation_frame,frames=np.arange(0,11,1),interval=500, repeat=False)
self.snap_cursor = SnaptoCursor(self.ax, self.x, self.y)
self.fig.canvas.mpl_connect('button_press_event', self.snap_cursor.mouse_click)
# displaying a title on top of figure (or do not add if not needed)
plt.title("Specific Fig Desc Title")
#ADD THIS LINE TO SHOW WINDOW
plt.show()
Please see this for more on figure window title:
Set matplotlib default figure window title
I would like to display two matplotlib plots below each other in one tkinter window. I have needed two different codes so far. How can I combine the codes in one? I have created the desired result with powerpoint below. I have already tried it with subplot, unfortunately without success: plt.subplots(2,1, figsize=(20,10)) and plt.subplots(1,1, figsize=(20,10))
My first code:
# ___________________________________________________________________________
# Library
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import tkinter as tk
import numpy as np
# ___________________________________________________________________________
# Carwash Array
Carwash_km = np.array([1,4,500,1500,2800,2960,2700,5,2000,2000,3100,4000,4150,4150])
Carwash_cost = np.array([5,1000,1000,2100,3000,3150,3150,20,50,600,3500,3800,3960,3700])
# Fuel Array
Fuel_km = np.array([2,5,600,2600,3900,3970,3800,6,3000,3000,4200,5000,5260,5260])
Fuel_cost = np.array([6,2000,2000,3200,4000,4260,4260,30,60,700,4600,4900,4070,4800])
# Maintenance Array
Maintenance_km = np.array([0,3,400,400,1700,1850,1600,4,1000,1000,2000,3000,3040,3040])
Maintenance_cost = np.array([4,500,500,1000,2000,2040,2040,10,40,500,2400,2700,2850,2600])
# ___________________________________________________________________________
# Main
Vis = tk.Tk()
Vis.title("Main") # titel
# ___________________________________________________________________________
# Plot
fig, ax = plt.subplots(1, 1, figsize=(20,5), facecolor = "white")
Plot_Carwash_cost = plt.bar(Carwash_km, Carwash_cost,
bottom=0,
color="#BDD7EE",
ec="black",
width=100,
label="Carwash_cost")
Plot_Carwash_cost2 = plt.bar(Maintenance_km, -Maintenance_cost,
bottom=-0,
color="#C5E0B4",
ec="black",
width=100,
label="Maintenance_cost")
Plot_Fuel = plt.scatter(Fuel_km, Fuel_cost,
marker="^",
s=150,
color="#C00000",
edgecolors="black",
zorder=3,
label="Fuel_cost")
ax.spines["bottom"].set_position("zero")
ax.spines["top"].set_color("none")
ax.spines["right"].set_color("none")
ax.spines["left"].set_color("none")
ax.tick_params(axis="x", length=20)
_, xmax = ax.get_xlim()
ymin, ymax = ax.get_ylim()
ax.set_xlim(-15, xmax)
ax.set_ylim(ymin, ymax+10) # legend
ax.text(xmax, -5, "km", ha="right", va="top", size=14)
plt.legend(ncol=5, loc="upper left")
plt.tight_layout()
# ___________________________________________________________________________
# Canvas, Toolbar
canvas = FigureCanvasTkAgg(fig, master=Vis)
canvas.draw() # TK-Drawingarea
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)
canvas._tkcanvas.pack(side = tk.TOP, fill = tk.BOTH, expand = True)
toolbar = NavigationToolbar2Tk(canvas, Vis)
toolbar.update()
Vis.mainloop()
My second code:
# ___________________________________________________________________________
# Library
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import tkinter as tk
import numpy as np
# ___________________________________________________________________________
# 1 Array
Beg1 = np.array([1,4,500,1500,2800,2960,2700,5,2000,2000,3100,4000,4150,4150])
End1 = np.array([2,5,600,2600,3900,3970,3800,6,3000,3000,4200,5000,5260,5260])
# 2 Array
Beg2 = np.array([5,1000,1000,2100,3000,3150,3150,20,50,600,3500,3800,3960,3700])
End2 = np.array([6,2000,2000,3200,4000,4260,4260,30,60,700,4600,4900,4070,4800])
# 3 Array
Beg3 = np.array([0,3,400,400,1700,1850,1600,4,1000,1000,2000,3000,3040,3040])
End3 = np.array([4,500,500,1000,2000,2040,2040,10,40,500,2400,2700,2850,2600])
# ___________________________________________________________________________
# detail
Vis2 = tk.Tk()
Vis2.title("detail") # titel
# ___________________________________________________________________________
# Plot
fig, ax = plt.subplots(2, 1, figsize=(20,10), facecolor = "white")
Plot_1 = plt.barh(len(Beg1)+np.arange(len(Beg2)), End2-Beg2+500,
left=Beg2,
height=0.9,
color='red',
alpha=0.5)
Plot_2 = plt.barh(range(len(Beg1)), End1-Beg1,
left=Beg1,
height=0.9,
color='#BDD7EE')
Plot_3 = plt.barh(len(Beg1)+len(Beg2)+np.arange(len(Beg3)), End3-Beg3+500,
left=Beg3,
height=0.9,
color="#C5E0B4")
plt.tight_layout()
# ___________________________________________________________________________
# Canvas, Toolbar
canvas = FigureCanvasTkAgg(fig, master = Vis2)
canvas.draw()
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)
canvas._tkcanvas.pack(side = tk.TOP, fill = tk.BOTH, expand = True)
toolbar = NavigationToolbar2Tk(canvas, Vis2)
toolbar.update()
Vis2.mainloop()
The short answer is to create two Frame widget, and have your FigureCanvasTkAgg master set to each Frame.
...
frame1 = tk.Frame(Vis)
frame2 = tk.Frame(Vis)
frame1.pack()
frame2.pack()
...
canvas = FigureCanvasTkAgg(fig, master = frame1)
...
canvas2 = FigureCanvasTkAgg(fig, master = frame2)
...
Vis.mainloop()
A better way is to create a class which inherits from Frame and handles the creation of Figure and NavigationToolBar. Depending on your need, it could be something like this:
from matplotlib.figure import Figure #use Figure instead of pyplot if you are embedding into tkinter
class GraphFrame(tk.Frame):
def __init__(self, master=None, **kwargs):
super().__init__(master, **kwargs)
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
self.canvas = FigureCanvasTkAgg(self.fig, master=self)
self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)
self.toolbar = NavigationToolbar2Tk(self.canvas, self)
self.toolbar.update()
Then you can easily create new plotting areas under the same root window Vis:
Vis = tk.Tk()
graph1 = GraphFrame(Vis)
graph1.pack()
graph1.ax.bar(Carwash_km, Carwash_cost, ...)
graph1.ax.bar(Maintenance_km, -Maintenance_cost, ...)
graph1.ax.scatter(Fuel_km, Fuel_cost, ...)
graph2 = GraphFrame(Vis)
graph2.pack()
graph2.ax.barh(...)
graph2.ax.barh(...)
graph2.ax.barh(...)
Vis.mainloop()
#excuse me
#in
class GraphFrame(tk.Frame):
def __init__(self, master=None, **kwargs):
super().__init__(master, **kwargs)
self.fig = Figure()
I think the correct sintax is :
class GraphFrame(tk.Frame):
def __init__(self, master=None, **kwargs):
super().__init__(master, **kwargs)
self.fig = plt.figure()
Am I right?
Is there a matplotlib event that is similar the the Tkinter <B1-Motion> key? Such that a function is called only when the user is holding B1 down while moving the mouse?
I'm current looking into this documentation but don't see such an option, if not is there some simple way I can recreate this?
def notify_motion(event):
"""
Only called when button 1 is clicked and motion detected
"""
...
I have not idea whether you are still care about this issue, anyway there is a very simple solution using the button attribute of matplotlib.backend_bases.MouseEvent class, since event.button will be 1 if the cursor is being pressed and None if it is being released.
Following is my code to draw a line by holding and moving the cursor:
import matplotlib.pyplot as plt
import numpy as np
def moved_and_pressed(event):
if event.button==1:
x = np.append(line.get_xdata(), event.xdata)
y = np.append(line.get_ydata(), event.ydata)
line.set_data(x, y)
fig.canvas.draw()
fig, ax = plt.subplots(1,1, figsize=(5,3), dpi=100)
line, = ax.plot([], [], 'k')
ax.set_xlim(0,10); ax.set_ylim(0,10)
cid = fig.canvas.mpl_connect('motion_notify_event', moved_and_pressed)
plt.show()
Hope it helps.
canvas.mpl_connect doesn't provide a key_down+motion event. A possible hack is to store a property of whether the mouse button (or key) is hold down and use the motion_notify_event.
In this example, tho in wx, we can hold mouse down and draw a red rectangle over a plot:
import wx
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.patches import Rectangle
import matplotlib.cm as cm
import matplotlib.pyplot as plt
class Frame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent=parent)
self.boxSizer1 = wx.BoxSizer(orient=wx.VERTICAL)
self.SetSizer(self.boxSizer1)
self.figure = Figure()
self.canvas = FigureCanvas(self, -1, self.figure)
self.boxSizer1.Add(self.canvas, 1, wx.EXPAND)
self.x0 = None
self.y0 = None
self.x1 = None
self.y1 = None
self.axes = [self.figure.add_subplot(111), ]
self.axes[0].plot(range(100), range(100))
self.canvas.Bind(wx.EVT_ENTER_WINDOW, self.ChangeCursor)
self.canvas.mpl_connect('button_press_event', self.on_press)
self.canvas.mpl_connect('button_release_event', self.on_release)
self.canvas.mpl_connect('motion_notify_event', self.on_motion)
self.rect = Rectangle((0,0), 1, 1, fill=False, ec='r')
self.axes[0].add_patch(self.rect)
#store a property of whether the mouse key is pressed
self.pressed = False
def ChangeCursor(self, event):
'''Change cursor into crosshair type when enter the plot area'''
self.canvas.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
def on_press(self, event):
'''Draw ROI rectangle'''
self.pressed = True
self.x0 = int(event.xdata)
self.y0 = int(event.ydata)
def on_release(self, event):
'''When mouse is on plot and button is released, redraw ROI rectangle, update ROI values'''
self.pressed = False
self.redraw_rect(event)
def redraw_rect(self, event):
'''Draw the ROI rectangle overlay'''
try:
self.x1 = int(event.xdata)
self.y1 = int(event.ydata)
self.rect.set_xy((self.x0, self.y0))
self.rect.set_width(self.x1 - self.x0)
self.rect.set_height(self.y1 - self.y0)
except:
pass
self.canvas.draw()
def on_motion(self, event):
'''If the mouse is on plot and if the mouse button is pressed, redraw ROI rectangle'''
if self.pressed:
# redraw the rect
self.redraw_rect(event)
class App(wx.App):
def OnInit(self):
frame = Frame(None)
self.SetTopWindow(frame)
frame.Show(True)
return True
if __name__=='__main__':
app=App(0)
app.MainLoop()
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.