I need to update an imshow() figure packed in tkinter. Here's a code example:
import matplotlib
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import tkinter as tk
import random
matplotlib.use('TkAgg')
def get_data():
return [[random.randint(1, 100), random.randint(1, 100)], [random.randint(1, 100), random.randint(1, 100)]]
class MainWindow:
def __init__(self, window):
self.window = window
self.figureCanvas = FigureCanvas(self.window)
self.button = tk.Button(window, text="Update", command=self._button_command)
self.button.pack()
self._pack_figure()
def _button_command(self):
self.figureCanvas.data = get_data()
self.figureCanvas.plot()
def _pack_figure(self):
canvas = FigureCanvasTkAgg(self.figureCanvas.figure, master=self.window)
canvas.get_tk_widget().pack()
canvas.draw()
class FigureCanvas:
data = []
def __init__(self, window):
self.window = window
self.figure = Figure(figsize=(6, 6))
self._create_plot_object()
def _create_plot_object(self):
self.axes = self.figure.add_subplot(111)
self.plot_object = self.axes.imshow([[0, 0], [0, 0]])
def update(self):
self.plot_object.set_data(self.data)
_window = tk.Tk()
_window.title("Snapshot")
start = MainWindow(_window)
_window.mainloop()
It plots the first image [[0, 0], [0, 0]] fine, but it doesn't re-draw after the image data has updated by plot_object.set_data(self.data)
Update
I have tried calling the following functions under FigureCanvas.update():
matplotlib.backend_bases.FigureCanvasBase(self.figure).draw_idle()
self.axes.redraw_in_frame()
A correct version of the code could look like this. I got rid of the false FigureCanvas and put everything in one class (sure you can use different classes, but don't name them confusingly). Also several other issues are fixed here (e.g. not calling the callback, supplying min and max color values).
import numpy as np
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import tkinter as tk
def get_data():
return np.random.randint(1,100,size=(2,2))
class MainWindow:
def __init__(self, window):
self.window = window
self.button = tk.Button(window, text="Update", command=self._button_command )
self.button.pack()
self._pack_figure()
self._create_plot_object()
def _button_command(self):
self.data = get_data()
self.plot()
def _pack_figure(self):
self.figure = Figure(figsize=(6, 6))
self.canvas = FigureCanvasTkAgg(self.figure, master=self.window)
self.canvas.get_tk_widget().pack()
def _create_plot_object(self):
axes = self.figure.add_subplot(111)
self.plot_object = axes.imshow([[0, 0], [0, 0]], vmin=0, vmax=100)
def plot(self):
self.plot_object.set_data(self.data)
self.canvas.draw_idle()
_window = tk.Tk()
_window.title("Snapshot")
start = MainWindow(_window)
_window.mainloop()
Related
I want to embed Matplotlib plot in my PyQt app using QWidget. This is the code of the widget script.
from PyQt5.QtWidgets import*
from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.figure import Figure
from entropia import entropy
import matplotlib.pyplot as plt
import numpy as np
import random
class MplWidget(QWidget):
def __init__(self, parent = None):
QWidget.__init__(self,parent)
self.canvas = FigureCanvas(Figure())
self.vertical_layout = QVBoxLayout()
self.vertical_layout.addWidget(self.canvas)
self.setLayout(self.vertical_layout)
def draw(self):
QWidget.update(self)
self.canvas.axes = self.canvas.figure.add_subplot(111)
fs = 500
f = random.randint(1, 100)
ts = 1/fs
length_of_signal = 100
t = np.linspace(0,1,length_of_signal)
cosinus_signal = np.cos(2*np.pi*f*t)
sinus_signal = np.sin(2*np.pi*f*t)
self.canvas.axes.clear()
self.canvas.axes.plot(t, cosinus_signal)
self.canvas.axes.plot(t, sinus_signal)
self.canvas.axes.legend(('cosinus', 'sinus'),loc='upper right')
self.canvas.axes.set_title('Cosinus - Sinus Signal')
self.canvas.draw()
I want the plot to be displayed after the pushbutton in another script is clicked. Unfortunately, this is not working. Button is connected to the function, though. If I do something like print(fs) in the "draw" method I see the variable in the python terminal when the button gets clicked.
This is how it looks when the button gets clicked:
When I move the whole thing to the init method the plot is displayed.
class MplWidget(QWidget):
def __init__(self, parent = None):
QWidget.__init__(self,parent)
self.canvas = FigureCanvas(Figure())
self.vertical_layout = QVBoxLayout()
self.vertical_layout.addWidget(self.canvas)
self.canvas.axes = self.canvas.figure.add_subplot(111)
self.setLayout(self.vertical_layout)
fs = 500
f = random.randint(1, 100)
ts = 1/fs
length_of_signal = 100
t = np.linspace(0,1,length_of_signal)
cosinus_signal = np.cos(2*np.pi*f*t)
sinus_signal = np.sin(2*np.pi*f*t)
self.canvas.axes.clear()
self.canvas.axes.plot(t, cosinus_signal)
self.canvas.axes.plot(t, sinus_signal)
self.canvas.axes.legend(('cosinus', 'sinus'),loc='upper right')
self.canvas.axes.set_title('Cosinus - Sinus Signal')
self.canvas.draw()
So, what can I do to display the plot only after calling it from another method?
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 know how to perform the following pseudocode in python when embedding a matplotlib figure inside of a wxPython FigureCanvasWxAgg instance:
the following items need to be used:
---- IMPORTS THAT CAN BE USED ----
import wx
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
-------------------------------------------------------
main_canvas;
shadow_canvas;
big_plot [a matplotlib figure instance with one big plot in it -- like the one you would make with figure.add_subplots(1,1,1)];
small_subplots [a matplotlib figure instance with, say, 2 subplots in it -- you would make with figure.add_subplots(2,1,i), where 1<=i<=2]
a function called SwapView(main_canvas,shadow_canvas,big_plot,small_subplots) that essentially swaps the figure that is currently in shadow_canvas with the one in main_canvas (so keep switching between the one with a big plot and the one with many small plots)
a function UpdateDisplay() that dynamically updates the display every time you call SwapView()
******* PSEUDOCODE *******
main_canvas.show()
shadow_canvas.hide()
main_canvas has big_plot initially
shadow_canvas has small_subplots initially
if big_plot in main_canvas:
SwapView(...) ---> should put big_plot in shadow_canvas and small_subplots in the main_canvas
else:
SwapView(...) ---> should put the small_subplots in shadow_canvas and the big_plot in main_canvas
UpdateDisplay()
******* END OF CODE *******
Here is my initial attempt at this code and unfortunately I can't find a way to find which figure is the one currently displayed.
#!/usr/bin/python
# -*- coding: utf-8 -*-
import numpy as np
import wx
import time
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
class myframe(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,parent = None, id = -1, title = 'LoadFigure()', size = (800,800))
self.figurePanel = FigurePanel(parent = self)
canvas1 = self.figurePanel.canvas
canvas2 = self.figurePanel.enlarged_canvas
fig1 = self.figurePanel.enlarged_figure
fig2 = self.figurePanel.figure
fig1.set_canvas(canvas1) #enlarged_fig resides in canvas1
fig2.set_canvas(canvas2) #fig resides in canvas2
#Show both canvases ---> canvas2 will override canvas1, but when canvas2 hides canvas1 should show
canvas2.Show()
canvas1.Show()
self.Show()
print "Starting to swap displays!"
time.sleep(1)
for i in range(10):
print "run: %d"%i
self.SwapView(big_plot = fig1,small_plots = fig2,main_canvas = canvas1,shadow_canvas = canvas2)
time.sleep(1)
def SwapView(self,big_plot,small_plots,main_canvas,shadow_canvas):
'''
Keep swapping the main_canvas with the shadow_canvas to show either fig1 or fig2.
Initially, big_plot has main_canvas and small_plots have shadow_canvas
'''
wx.Yield()
print list(main_canvas)
print list(big_plot.get_children())
time.sleep(2)
for child in big_plot.get_children():
if child == main_canvas:
print 'big_plot has main_canvas'
big_plot.set_canvas(shadow_canvas)
small_plots.set_canvas(main_canvas)
main_canvas.draw()
wx.Yield()
main_canvas.Show()
else:
print 'big_plot has shadow_canvas'
for child in small_plots.get_children():
if child == main_canvas:
print 'small_plots has main_canvas'
small_plots.set_canvas(shadow_canvas)
big_plot.set_canvas(main_canvas)
main_canvas.draw()
wx.Yield()
main_canvas.Show()
else:
print 'small_plots has shadow_canvas'
class FigurePanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.figPanel = self
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.figure = Figure(figsize = (8,6.1), dpi =60)
self.ax = self.figure.add_subplot(1,1,1)
self.ax.plot([1,2,3],[1,2,3])
self.enlarged_figure = Figure(figsize = (8,6.1), dpi = 60)
self.ax1 = self.enlarged_figure.add_subplot(2,1,1)
self.ax2 = self.enlarged_figure.add_subplot(2,1,2)
self.ax1.plot([1,2,3],[1,4,9])
self.ax2.plot([1,2,3],[1,4,9])
self.canvas = FigureCanvas(self, -1, self.figure)
self.enlarged_canvas = FigureCanvas(self,-1,self.enlarged_figure)
self.Layout()
self.Fit()
if __name__ == "__main__":
app = wx.App(False)
fr = myframe()
app.MainLoop()
For anyone that might need it, here's the solution that I came up with:
#!/usr/bin/python
# -*- coding: utf-8 -*-
import numpy as np
import wx
import time
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
class myframe(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,parent = None, id = -1, title = 'SWAP!', size = (480,390))
self.figurePanel = FigurePanel(parent = self)
self.canvas1 = self.figurePanel.canvas
self.canvas2 = self.figurePanel.enlarged_canvas
self.fig1 = self.figurePanel.enlarged_figure
self.fig2 = self.figurePanel.figure
self.fig1.set_canvas(self.canvas1) #enlarged_fig resides in canvas1
self.canvas1.Show()
self.Show()
self.canvas2.mpl_connect("button_release_event",self.OnLoadFigure) #Enable the detection of mouseclicks for the plots in the plotting window
print "Click anywhere on the figure to swap the plots!"
self.display = 1
def OnLoadFigure(self,event = None):
print "Tried to load figure"
if event != None:
self.display = self.SwapView(big_plot = self.fig1 ,small_plots = self.fig2 , display = self.display, main_canvas = self.canvas1 , shadow_canvas = 0)
def SwapView(self,big_plot = None,display = -1, small_plots = None,main_canvas = None,shadow_canvas = None):
'''
Keep swapping the main_canvas with the shadow_canvas to show either fig1 or fig2.
Initially, big_plot has main_canvas and small_plots have shadow_canvas
'''
wx.Yield()
print display
if display == 1: #Show the big plot
print 'big_plot showing'
big_plot.set_canvas(main_canvas)
main_canvas.Show()
time.sleep(0.01) #Fastest time you can pick
wx.Yield()
else:
print 'small_plots showing'
main_canvas.Hide()
wx.Yield()
self.Refresh(canvas = main_canvas)
display = not(display)
return display
def Refresh(self,canvas = None,figure = None):
wx.Yield()
if canvas != None:
print "draw"
canvas.draw()
self.Update()
self.figurePanel.Update()
wx.Yield()
class FigurePanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.figPanel = self
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.figure = Figure(figsize = (8,6.1), dpi =60)
self.ax = self.figure.add_subplot(1,1,1)
self.ax.plot([1,2,3],[1,2,3])
self.enlarged_figure = Figure(figsize = (8,6.1), dpi = 60)
self.ax1 = self.enlarged_figure.add_subplot(2,1,1)
self.ax2 = self.enlarged_figure.add_subplot(2,1,2)
self.ax1.plot([1,2,3],[1,4,9])
self.ax2.plot([1,2,3],[1,4,9])
self.canvas = FigureCanvas(self, -1, self.figure)
self.enlarged_canvas = FigureCanvas(self,-1,self.enlarged_figure)
self.Layout()
self.Fit()
if __name__ == "__main__":
app = wx.App(False)
fr = myframe()
app.MainLoop()
To make the display change, click on the figure.
I am using matplotlib and create a figure adding a plot. How can I replace the plot by a new one or a new grid of plots?
In my present code I create an axes with the menu dosomething() then add other red lines with the menu dosomethingelse() over the same axes.
Everytime I dosomething(), a new figure is appended bellow the current one, but I actually want to replace the current axes by a new one in the same figure. How can I do that?
import numpy as np
from Tkinter import *
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class Test1:
def __init__(self, windows, data, axes):
self.windows = windows
self.data = data
self.figure = axes.figure
self.axes = axes
self.im = self.axes.plot(data)
def dosomething():
global test1
global fig
fig = Figure(figsize=(12, 4))
axes = fig.add_subplot(111)
canvas = FigureCanvasTkAgg(fig, master=windows)
canvas.get_tk_widget().pack()
data=np.arange(100)
test1=Test1(windows, data, axes)
def dosomethingelse():
global test1
test1.axes.plot(np.arange(100)+10*(np.random.rand(100)-0.5),'-r')
test1.figure.canvas.show()
windows = Tk()
menubar = Menu(windows)
filemenu = Menu(menubar, tearoff=0)
filemenu.add_command(label="Do something", command=dosomething)
filemenu.add_command(label="Do somethingelse", command=dosomethingelse)
menubar.add_cascade(label="Tool", menu=filemenu)
windows.config(menu=menubar)
windows.mainloop()
This doesn't looks like a very standard way of doing tkinter. At least it's not very clear to me what you're trying to achieve. You have the following line in your dosomething:
test1=Test1(windows, data, axes)
which is what is producing your new window every time you run it. Also, there's no need for global variables, when you're inside a class. Just use self.variable = ..., and the variable will be available throughout your class and to objects that you pass the class to.
I haven't tried this, but perhaps something like this:
def dosomething():
try:
self.canvas.get_tk_widget().destroy()
except:
pass
fig = Figure(figsize=(12, 4))
axes = fig.add_subplot(111)
self.canvas = FigureCanvasTkAgg(fig, master=windows)
self.canvas.get_tk_widget().pack()
data=np.arange(100) # not sure what this is for
def dosomethingelse():
try:
self.canvas.get_tk_widget().destroy()
except:
pass
fig = Figure(figsize=(12, 4))
fig.plot(np.arange(100)+10*(np.random.rand(100)-0.5),'-r')
self.canvas = FigureCanvasTkAgg(fig, master=windows)
self.canvas.get_tk_widget().pack()
I am posting the full code which worked for me after the answer of #DrXorile
import numpy as np
from Tkinter import *
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class Test1:
def __init__(self, data):
self.data = data
def dosomething(self):
try:
self.canvas.get_tk_widget().destroy()
except:
pass
self.figure = Figure(figsize=(12, 4))
self.axes = self.figure.add_subplot(111)
self.im = self.axes.plot(data)
self.canvas = FigureCanvasTkAgg(self.figure, master=windows)
self.canvas.get_tk_widget().pack()
def dosomethingelse(self):
self.axes.plot(np.arange(100)+10*(np.random.rand(100)-0.5),'-r')
self.figure.canvas.show()
data=np.arange(100) # data to plot
test1=Test1(data)
windows = Tk()
menubar = Menu(windows)
filemenu = Menu(menubar, tearoff=0)
filemenu.add_command(label="Do something", command=test1.dosomething)
filemenu.add_command(label="Do somethingelse", command=test1.dosomethingelse)
menubar.add_cascade(label="Tool", menu=filemenu)
windows.config(menu=menubar)
windows.mainloop()
Being new to python, I've come upon the matplotlib draw() freezes window problem myself and found the solution on this site:
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import random
import numpy as np
import sys
import Tkinter as tk
import time
def function1(fig, ax):
ax.cla()
color_grade_classes = ['#80FF00','#FFFF00','#FF8000', '#FF0000']
varsi = random.randint(1, 100)
for colors, rows in zip(color_grade_classes, [3,2,1,0] ):
indexs = np.arange(5)
heights = [varsi,varsi/2,varsi/3,0,0]
ax.bar(indexs, heights, zs = rows, zdir='y', color=colors, alpha=0.8)
return fig
class App():
def __init__(self):
self.root = tk.Tk()
self.root.wm_title("Embedding in TK")
self.fig = plt.figure()
self.ax = self.fig.add_subplot(111, projection='3d')
self.ax.set_xlabel('X')
self.ax.set_ylabel('Y')
self.fig = function1(self.fig, self.ax)
self.canvas = FigureCanvasTkAgg(self.fig, master=self.root)
self.toolbar = NavigationToolbar2TkAgg( self.canvas, self.root )
self.toolbar.update()
self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
self.label = tk.Label(text="")
self.label.pack()
self.update_clock()
self.root.mainloop()
def update_clock(self):
self.fig = function1(self.fig,self.ax)
self.canvas.show()
self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
now = time.strftime("%H:%M:%S")
self.label.configure(text=now)
self.root.after(1000, self.update_clock)
app=App()
My problem is incorporating the following plotting code into it. It's not quite the same as the example given. Not sure how to split this up between the function definition and the class declaration. Can anyone help me on this?
t0 = time.time()
while time.time() - t0 <= 10:
data = np.random.random((32, 32))
plt.clf()
im = plt.imshow(data,cmap=cm.gist_gray, interpolation='none')
plt.ion()
cbar = plt.colorbar(im)
cbar.update_normal(im)
cbar.set_clim(0, np.amax(data))
plt.draw()
time.sleep(0.5)
plt.show(block=True)
this seems to work.
Basically the __init__ part initialises the plot and draws the first "frame". Then the function self.update_clockis called every 1000ms, and that function calls function1() which generates new data and redraws the plot.
I moved things around a bit because of the colorbar in your example, but the idea remains the same.
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import random
import numpy as np
import sys
import Tkinter as tk
import time
class App():
def __init__(self):
self.root = tk.Tk()
self.root.wm_title("Embedding in TK")
self.fig = plt.figure()
self.ax = self.fig.add_subplot(111)
self.ax.set_xlabel('X')
self.ax.set_ylabel('Y')
data = np.random.random((32, 32))
im = self.ax.imshow(data,cmap=cm.gist_gray, interpolation='none')
self.cbar = self.fig.colorbar(im)
self.cbar.update_normal(im)
self.cbar.set_clim(0, np.amax(data))
self.fig = self.function1(self.fig, self.ax)
self.canvas = FigureCanvasTkAgg(self.fig, master=self.root)
self.toolbar = NavigationToolbar2TkAgg( self.canvas, self.root )
self.toolbar.update()
self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
self.label = tk.Label(text="")
self.label.pack()
self.update_clock()
self.root.mainloop()
def update_clock(self):
self.fig = self.function1(self.fig,self.ax)
self.canvas.show()
self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
now = time.strftime("%H:%M:%S")
self.label.configure(text=now)
self.root.after(1000, self.update_clock)
def function1(self, fig, ax):
ax.cla()
data = np.random.random((32, 32))
im = ax.imshow(data,cmap=cm.gist_gray, interpolation='none')
self.cbar.update_normal(im)
self.cbar.set_clim(0, np.amax(data))
return fig
app=App()