Thanks for the help in advance.
I'm trying to update a matplotlib imshow plot when a slider is moved (Python 3.7.4), but nothing is changing when the update function is called, despite calling canvas.draw which I thought would be all I needed to solve the problem. The code also needs to be embedded in Tkinter. This code will reproduce the problem:
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
class mainApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.image = np.zeros((10, 10))
fig = Figure()
self.mainAx = fig.add_subplot(111)
self.drawing = self.mainAx.imshow(self.image)
self.graphCanvas = FigureCanvasTkAgg(fig, master=self)
self.graphCanvas.draw()
self.graphCanvas.get_tk_widget().pack(side="top", fill="both", expand=True)
self.slider = tk.Scale(master=self, command=self.updateGraph, orient="horizontal")
self.slider.pack(fill="x")
def updateGraph(self, e):
self.image = np.zeros((10, 10))
self.image[self.slider.get()//10, self.slider.get()%10] = 1
self.drawing.set_data(self.image)
self.graphCanvas.draw_idle()
main = mainApp()
main.mainloop()
Calling mainAx.imshow(self.image) works but this is far slower and I would like this program to be as fast as possible. I think the issue lies with the draw_idle but I don't know what else I should be doing. It also doesn't work with the regular canvas.draw() function
Thanks,
Adam
change your update function to this.
def updateGraph(self, e):
self.image = np.zeros((10, 10))
self.image[self.slider.get()//10, self.slider.get()%10] = 1
self.drawing = self.mainAx.imshow(self.image)
self.graphCanvas.draw()
I would also modify your command to wait until a final value is chosen on the slider, unless you want to see it change, you can find how to do this here with an event binding or a delay. TkInter, slider: how to trigger the event only when the iteraction is complete?
Related
I have a splash window in a tkinter application, which is a child class of Toplevel. My target is to show a GIF moving, thus I am trying with a recurrent .after() function to update a Label. My code for the Splash window is:
import tkinter as tk
class Splash(tk.Toplevel):
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.overrideredirect(True)
self.parent = parent
frames = [tk.PhotoImage(file="myfile.gif",format = 'gif -index %i' %(i)) for i in range(89)] #it has 89 frames
def animate(ind, label):
frame = frames[ind]
ind += 1
if ind>88: #With this condition it will play gif infinitely
ind = 0
label.configure(image=frame)
label.update()
self.after(110, animate, ind, label)
label = tk.ttk.Label(self)
label.pack(expand=1)
self.after(0, animate, 0, label)
self.parent.overrideredirect(True)
self.update()
self.lift()
Now: I tried to also put a print function before the next after call, and I could see that it reached that step. However, if I place a print(str(ind)) at the beginning of the animate function, I see only the first index appearing.
How can I actually loop in a Toplevel window in the correct way?
EDIT:
To add context to this, I have the main class App where I do the following:
from ttkthemes import ThemedStyle, ThemedTk
import GUI.splash as sp
class App:
def __init__(self, ...):
self.interface = ThemedTk()
self.interface.iconify()
splash = sp.Splash(self.interface)
...
self.interface.after(3000, splash.destroy) #Thanks #CoolCloud
self.interface.deiconify()
self.interface.mainloop()
EDIT 2:
I have tried with a minimum reproducible example:
import tkinter as tk
class Splash(tk.Toplevel):
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.overrideredirect(True)
self.parent = parent
frames = [tk.PhotoImage(file="C:/temp/splash.gif",format = 'gif -index %i' %(i)) for i in range(89)] #it has 89 frames
def animate(ind, label):
frame = frames[ind]
ind += 1
if ind>88: #With this condition it will play gif infinitely
ind = 0
label.configure(image=frame)
label.update()
self.after(110, animate, ind, label)
label = tk.Label(self)
label.pack(expand=1)
self.after(0, animate, 0, label)
self.update()
self.lift()
class App:
def __init__(self):
self.interface = tk.Tk()
self.interface.iconify()
splash = Splash(self.interface)
self.interface.after(3000, splash.destroy)
self.interface.deiconify()
self.interface.mainloop()
if __name__ == "__main__":
App()
It looks like the actual problem is the size of my underlying window, or at least that's what I would think, since in my case I load multiple frames (one on top of the other one) and switch between them depending on an OptionMenu. Maybe this is too much, however it works much better than destroying and rebuilding everything.
Btw, keeping my app heavy as it is, is there a better way to produce a splash screen to load my interface and show a gif moving as in my minimal example? The gif can be downloaded here: https://images.app.goo.gl/Bpi6EvpJgodiQs2e7
I want to connect a QPushButton in my QMainWindow with a class that i created using Matplotlib so i can show a grid when i push the button. This is a part of the code:
class Ventana(QMainWindow):
def __init__(self):
super(QMainWindow, self).__init__()
self.channel = ViewWidget()
#I add a toolbar and i put the button in here
self.toolbar2.addWidget(self.btn_showgrid)
self.btn_showgrid.setEnabled(True)
self.connect(self.btn_showgrid, SIGNAL("clicked()"), self.showGrid)
def showGrid(self):
self.btn_showgrid.setEnabled(False)
self.channel.axes.grid(True)
class ViewWidget(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.mainWidget = QWidget()
self.setCentralWidget(self.mainWidget)
layout = QVBoxLayout()
self.mainWidget.setLayout(layout)
self.figure_canvas = FigureCanvas(Figure())
layout.addWidget(self.figure_canvas, 10)
self.axes = self.figure_canvas.figure.add_subplot(111)
x = np.arange(0,5,0.1)
y = np.sin(x)
self.axes.plot(x,y)
My method called showGrid set the button to "disable" (is what i want to when the button is pushed) but it does not shows the grid. What am i doing wrong?
Hope you can help me. Thanks for your answers
------------------------- EDIT --------------------------------
I´ve made a few changes. I created the QPushButton and i added it to the toolbar of the plot.
# create a simple widget to extend the navigation toolbar
self.btn_showgrid = QPushButton("Show Grid")
self.btn_showgrid.setEnabled(True)
self.btn_hidegrid = QPushButton("Hide Grid")
self.btn_hidegrid.setEnabled(False)
layout = QVBoxLayout()
self.mainWidget.setLayout(layout)
self.figure_canvas = FigureCanvas(Figure())
layout.addWidget(self.figure_canvas, 10)
self.axes = self.figure_canvas.figure.add_subplot(111)
self.axes.grid(False)
x = np.arange(0,5,0.1)
y = np.sin(x)
self.axes.plot(x,y)
I also put a line: self.axes.grid(False) as you can see above. And at last i created this method:
def showGrid(self):
self.btn_showgrid.setEnabled(False)
self.btn_hidegrid.setEnabled(True)
self.axes.grid(True)
self.axes.draw()
The problem now is that when i push the button, it only hides like it should, but the grid does no show. But if create a new plot in the same `QMainWindow, it works!!!!
I think i need to refresh the plot at the moment i make self.axes.grid(True), but the draw() does not work. How can i accomplish this? I mean, refresh the plot?
You need to tell the canvas to re-draw. Drawing can be expensive so updating the state of the artists does not trigger a re-draw (the pyplot API does do that but you should not use that here). I think
def showGrid(self):
self.btn_showgrid.setEnabled(False)
self.channel.axes.grid(True)
self.channel.canvas.draw_idle()
will do the trick. The call to draw_idle tells Qt to, the next time it re-paints window to also trigger an Agg redraw for mpl, and to please schedule a repaint 'soon'.
I'v got this matplotlib animation running in tkinter, it works fine but it just never stops looping, when i press 'X' the window closes but i have to force shut it with the task manager.
This is the example code of how i tried to set it up:
from matplotlib import pyplot as plt
from matplotlib import animation
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter as tk
from tkinter import *
class Grapher(tk.Tk): # inherit Tk()
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, "Quarantined-Grapher")
self.fig = plt.figure()
ax = plt.axes(xlim=(0,2), ylim=(0, 100))
N = 4 # amount of lines
self.lines = [plt.plot([], [])[0] for _ in range(N)]
# give the figure and the root(which is self) to the "canvas"
self.canvas = FigureCanvasTkAgg(self.fig, self)
self.canvas.show()
self.canvas.get_tk_widget().pack()
anim = animation.FuncAnimation(self.fig, self.animate, init_func=self.init,
frames=100, interval=1000, blit=True)
def init(self):
for line in self.lines:
line.set_data([], [])
return self.lines
def animate(self, i):
for j,line in enumerate(self.lines):
line.set_data([0, 2], [10 * j,i]) # some trick to animate fake data.
return self.lines
app = Grapher()
app.mainloop()
My guess is it might be the animation loop that never stops running, because only tkinter knows to stop?..
Note: I made a graph work before, but i was using the tkinter after() method clearing and recreating data points, but it used up to much resources that i had to remake it. This way is so that i don't have to delete/create 10-50K data points every second.
Answering the wrong question:
This is behaving as intended (looping indefinitely). If you would like to only run once use the repeat kwarg (some what arcane docs):
anim = animation.FuncAnimation(self.fig, self.animate, init_func=self.init,
frames=100, interval=1000, blit=True,
repeat=False)
# when the code works but it's meaningless to include it
### When I can't get this part to work and I'd need your code
How do you hide or show an Axes object (subplot) in matplotlib so you can toggle between different Axes in the same figure?
I'm using matplotlib to display graphics in a Tkinter GUI and I'd like to use radiobuttons to switch between different axes in the same figure.
Basically I'll have some radiobuttons linked to a IntVar():
graphic_version = tk.IntVar()
tk.Radiobutton(root, text='Option1', variable=graphic_version, value=1).pack()
tk.Radiobutton(root, text='Option2', variable=graphic_version, value=2).pack()
Then I'd trace the IntVar() with a custom methods updating my figure with the requested graphic:
choice.trace("w", lambda choice: myGraphic.showGraphic(version))
so that everytime the user clicks a radiobutton the figure is updated with a different version of the plot. Now the problem is I have no idea how to do the showGraphic properly. Lets say I use this class system to get 2 different versions of plotting the same data:
class Version1():
def __init__(self, ax, data):
self.ax = ax #This is a Axes object
self.data = self._formatDataV1(data)
self._draw()
self._setOptions()
self.hide()
def _formatDataV1(self, data):
#Here I manipulate the raw data to extract the info I need for this version
#Just a bunch of math algorithms it works fine
def _setOptions(self):
#Here I can overwrite or change settings specific for this version
def _draw(self):
self.ax.bar(self.data[0], self.data[1], width=1, color='red')
self._setOptions()
def hide(self):
###How do I remove the ax without affecting the figure?
def show(self):
###If I want to see this version again I don't want the cost of redrawing
class Version2():
def __init__(self, ax, data):
self.ax = ax #This is a Axes object
self.data = self._formatDataV1(data)
self._draw()
self._setOptions()
self.hide()
def _formatDataV2(self, data):
#The data is manipulated differently here to extract new information
def _setOptions(self):
#These options are specific to the version2 to get the display right
def _draw(self): #Drawing a different version of the graphic with differently formated data
self.ax.plot(self.data[0], self.data[1])
self._setOptions()
def hide(self):
###How do I remove the ax without affecting the figure?
def show(self):
###If I want to see this version again I don't want the cost of redrawing
class MyGraphic(tk.LabelFrame):
def __init__(self, root, data, **options):
#I use the labelframe only as a container to make things pretty
tk.LabelFrame.__init__(self, root, text="My 1337 graphic : ", **options)
self.data = data
self.fig = mpl.figure.Figure()
self.ax = self.fig.add_subplot(111)
self._drawCanvas() #This is just for Tkinter compatibility
self.my_versions = {}
self.my_versions.update({'v1' : Version1(self.ax, self.data)})
self.my_versions.update({'v2' : Version2(self.ax, self.data)})
def _drawCanvas(self):
self.canvas = FigureCanvasTkAgg(self.figure, master=self)
self.canvas.show()
self.canvas.get_tk_widget().grid()
self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
def _setOptions(self, **kwargs):
#Here I can set options common to both versions of the graphic
def showGraphic(self, graphic_version):
for i, item in enumerate(self.my_versions):
item.hide()
if graphic_version == 1:
self.my_versions['v1'].show()
elif graphic_version == 2:
self.my_versions['v2'].show()
self._setOptions()
Sorry for the lengthy post but I rather include too many details and edit out those who are not necessary when it's solved.
Basically I want to be able to hide and show different ax on the same figure depending on the choice made by my user. The missing parts of the puzzle are myGraphic.show() and myGraphic.hide().
I'm also a complete matplotlib newby I tried this design because it seemed clear and easy to implement additional versions when needed but design inputs are also really appreciated.
You can remove axes from a figure with figure.delaxes (and add with add_axes):
http://matplotlib.org/api/figure_api.html#matplotlib.figure.Figure.delaxes
I managed to solve it with figure.clear() and figure.add_axes(ax)
I'll try to edit a clean question-answer tomorrow when I have time with a minimal example of how to toggle different version of a plot on the same figure in Tkinter.
My program (developed with GTK using glade) receives some data and has the option to display a seperate window containing a matplotlib scatterplot that represents the data.
My problem is that if the user closes the graph window and reopens it, no graph is displayed. It is just a blank GTK Window. I'm sure there is a simple fix, but there aren't many resources available that are relevant to my issue (or GTK and matlplotlib integration for that matter).
I have created a Module for my scatterplot so I can easily reuse it. I am just trying to get it to work, so the code isn't structured perfectly.
##Scatterplot Module:
import gtk
import matplotlib
from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
from matplotlib.figure import Figure
class ScatterPlot:
def __init__(self):
self.window = gtk.Window()
self.window.connect("destroy", lambda x: self.destroy())
self.window.set_default_size(500,400)
self.is_hidden = False
self.figure = Figure(figsize = (5,4), dpi=100)
self.ax = self.figure
self.ax = self.ax.add_subplot(111)
self.canvas = FigureCanvas(self.figure)
self.window.add(self.canvas)
self.Xs = list()
self.Ys = list()
def set_axis(self, xLimit = (0,384) , yLimit = (0,100)):
self.ax.set_xlim(xLimit)
self.ax.set_ylim(yLimit)
def plot(self, xs, ys):
self.Xs.extend([xs])
self.Ys.extend([ys])
self.ax.plot(xs,ys,'bo')
def update(self):
self.window.add(self.canvas)
def set_title(self, title):
self.ax.set_title(title)
def show(self):
self.window.show_all()
self.is_hidden = False
def hide(self):
self.window.hide()
self.is_hidden = True
def destroy(self):
self.window.destroy()
I call the module like so:
class GUI:
def __init__(self):
self.scatterplot = scatterplot.ScatterPlot()
#When the user presses the "Graph" button it calls the following function
def graph():
self.scatterplot.plot(someDataX, someDataY)
self.scatterplot.set_axis()
self.scatterplot.set_title("Some Title")
self.scatterplot.show()
(This was just an example of what my code looks like.)
When the scatterplot is closed, I am calling self.window.destroy instead of self.window.hide. When reopening is attempted, I call the same graph() function but, as stated above, the GTK Window does not display the graph. (When I first open it, it displays perfectly)
My speculations:
Should I be calling .hide() instead of .destroy()?
Is there a piece of code in scatterplot's constructor that needs to be called again to create the plot?
Or should I just re-instantiate the plot every time graph() is called?
My Solution:
From:
class ScatterPlot:
def __init__(self):
#remove the following two lines
self.canvas = FigureCanvas(self.figure)
self.window.add(self.canvas)
Move the two lines of code to show()
def show(self):
self.canvas = FigureCanvas(self.figure)
self.window.add(self.canvas)
self.window.show_all()
self.is_hidden = False
Moving these two lines of code allows the graph to be displayed when re-opening the window.
Sidenote: Calling both .destroy() or .show() when closing the window will work. I'm not sure which one is better though.