matplotlib show figure again - python

When using matplotlib:
from matplotlib import pyplot as plt
figure = plt.figure()
ax = figure.add_subplot(111)
ax.plot(x,y)
figure.show() # figure is shown in GUI
# How can I view the figure again after I closed the GUI window?
figure.show() # Exception in Tkinter callback... TclError: this isn't a Tk application
figure.show() # nothing happened
So my questions are:
How can I get the previous plot back if I have called figure.show()?
Is there a more convenient alternative to figure.add_suplot(111) if I have multiple figures and thus from pylab import *; plot(..); show() seems not a solution I'm looking for.
And what I eagerly want is
showfunc(stuff) # or
stuff.showfunc()
where stuff is an object containing all the plots arranged in one picture, and showfunc is STATELESS(I mean, each time I call it, I behaves as if it's first time called). Is this possible when working with matplotlib?

I can't find a satisfactory answer, so I handle this problem by writing a custom Figure class extending matplotlib.figure.Figure and providing a new show() method, which create a gtk.Window object each time called.
import gtk
import sys
import os
import threading
from matplotlib.figure import Figure as MPLFigure
from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NaviToolbar
class ThreadFigure(threading.Thread):
def __init__(self, figure, count):
threading.Thread.__init__(self)
self.figure = figure
self.count = count
def run(self):
window = gtk.Window()
# window.connect('destroy', gtk.main_quit)
window.set_default_size(640, 480)
window.set_icon_from_file(...) # provide an icon if you care about the looks
window.set_title('MPL Figure #{}'.format(self.count))
window.set_wmclass('MPL Figure', 'MPL Figure')
vbox = gtk.VBox()
window.add(vbox)
canvas = FigureCanvas(self.figure)
vbox.pack_start(canvas)
toolbar = NaviToolbar(canvas, window)
vbox.pack_start(toolbar, expand = False, fill = False)
window.show_all()
# gtk.main() ... should not be called, otherwise BLOCKING
class Figure(MPLFigure):
display_count = 0
def show(self):
Figure.display_count += 1
thrfig = ThreadFigure(self, Figure.display_count)
thrfig.start()
Make this file as start file of IPython. And
figure = Figure()
ax = figure.add_subplot(211)
... (same story as using standard `matplotlib.pyplot` )
figure.show()
# window closed accidentally or intentionally...
figure.show()
# as if `.show()` is never called
Works! I never touched GUI programming, and don't know if this would have any side-effect. Comment freely if you think something should be done that way.

Related

Close separately two windows in Python GTK (Glade)

I recently started developing some simple GUIs using Python GTK and Glade. I've created a GUI which consists of a main window with a button while the action of pressing the button is the popup of a second window with a matplotlib plot on it.
The problem I face is that when i close the second window then the first one is also closed and i would like to be able and terminate them separately.
Below is the python code and here is the glade file with GUI's layout.
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
import numpy as np
from matplotlib.figure import Figure
from matplotlib.axes import Subplot
from matplotlib.backends.backend_gtk3agg import (
FigureCanvasGTK3Agg as FigureCanvas)
class PlotApp:
def __init__(self):
gladefile = 'GTK-two-windows.glade'
self.builder = Gtk.Builder()
self.builder.add_from_file(gladefile)
main_win = self.builder.get_object("window1")
main_win.connect("delete-event", Gtk.main_quit)
button = self.builder.get_object('button')
button.connect('clicked', self.plot)
main_win.show_all()
def plot(self, widget):
window2 = self.builder.get_object("window2")
window2.connect("delete-event", Gtk.main_quit)
scrolledwindow = self.builder.get_object("scrolledwindow")
# ----- Start of Matplotlib specific code -----
figure = Figure(figsize=(8, 6), dpi=71)
axis = figure.add_subplot(111)
t = np.arange(0.0, 3.0, 0.01)
s = np.sin(2*np.pi*t)
axis.plot(t, s)
axis.set_xlabel('time [s]')
axis.set_ylabel('voltage [V]')
canvas = FigureCanvas(figure) # a Gtk.DrawingArea
canvas.set_size_request(800, 600)
scrolledwindow.add_with_viewport(canvas)
# ----- End of Matplotlib specific code -----
window2.show_all()
if __name__ == "__main__":
main = PlotApp()
Gtk.main()
The imports are from vext.gi, numpy and matplotlib python packages, the Glade version I use is 3.22.1 and my OS is Elementary Linux 5.1 Hera.
You need to change the calls:
.connect("delete-event", Gtk.main_quit)
So that they only call Gtk.main_quit if this is the last open window.
Replace that direct call to Gtk.main_quit with this:
def exit(window, event):
if not any(w.get_visible() for w in Gtk.Window.list_toplevels() if w is not window):
Gtk.main_quit()
This makes it so that closing a window will only exit the application if it is the last visible window.
If you only do:
window.hide()
return True
in your destroy-event handler, you will not have the application close ever.
Combining that which I didn't see as an issue as I couldn't run your program with the code I suggest would result in main_quit being called when all of the windows are closed.
After some research (Reopen window throws Gtk-CRITICAL **: gtk_widget_get_window: assertion 'GTK_IS_WIDGET (widget)' failed) and some experimenting I concluded that the second-popup window, which is triggered by the button of my main window, should not be closed with the event function Gtk.main_quit(), but it should be hidden using the event function hide(). As a result the first-main window will remain open.
However, since the matplotlib plot is plotted on a FigureCanvas which is contained on the child widget (scrollbar window) of my second-popup window, then it is required the delete-event of popup-window to destroy the scrollbar window in order to be able to replot and avoid errors about existing child widgets.
So in the code of my original question the following modifications add the required functionality:
[1] Delete the scrollbar window widget from glade file
[2] Add a scrollbar window on top of popup window:
scrolledwindow = Gtk.ScrolledWindow()
#----- Start of Matplotlib specific code -----
figure = Figure(figsize=(8, 6), dpi=71)
axis = figure.add_subplot(111)
t = np.arange(0.0, 3.0, 0.01)
s = np.sin(2*np.pi*t)
axis.plot(t, s)
axis.set_xlabel('time [s]')
axis.set_ylabel('voltage [V]')
canvas = FigureCanvas(figure) # a Gtk.DrawingArea
canvas.set_size_request(800, 600)
scrolledwindow.add_with_viewport(canvas)
#scrolledwindow.add(canvas)
# ----- End of Matplotlib specific code -----
window2.add(scrolledwindow)
window2.show_all()
[3] Change the event function of popup window:
window2.connect("delete-event", self.destroy)
where the destroy function is defined as:
def destroy(self, widget, event):
scrolledwindow = widget.get_child()
scrolledwindow.destroy()
widget.hide()
return True
Then no matter how many times I close the second window and press the button in main window, the plot is plotted on the (hidden) second window.

Tkinter textvariable not updated with MatplotLib

Can anyone help me here? The textvariable is not being updated when I add the line:
f = plt.figure(0, figsize=(20,9))
If I comment the line above, then I see the textvariable being updated in the Label, but as soon as I uncomment the line, the Label is not updated anymore.
Can anyone help me, please?
from tkinter import *
from tkinter import ttk
from tkinter import StringVar
import matplotlib
import matplotlib.artist as artists
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
import matplotlib.animation as animation
f = plt.figure(0, figsize=(20,9))
class make_window():
def __init__(self, *args, **kwargs):
self.win = Tk()
self.win.title("Test")
self.win.state("zoomed")
Frame = ttk.Frame(self.win)
Frame.pack()
labelVar = StringVar()
labelVar.set("Hi")
self.LabelUpdate = Label(Frame, textvariable = labelVar)
self.LabelUpdate.pack()
#################################################################################
window = make_window()
window.win.mainloop()
I have noticed a few issues that you need to address.
First the way you are importing (from tkinter import *) is going to cause problems and in fact here in this case you make the kind of mistake this import is known for. Later in your code you do Frame = ttk.Frame() and this one line is actually overriding the Frame import from from tkinter import *. Do not name your variables the same as built in methods. In order to prevent this kind of mistake you can simply do import tkinter as tk and always use the tk. prefix to prevent any overriding in your code.
Next I noticed your Label does pack to the screen but does not show any text oddly enough but when I comment out f = plt.figure(0, figsize=(20,9)) the Label starts to work as expected.
So there is some odd interaction here with matplotlib and this label.
When I change f = plt.figure(0, figsize=(20,9)) to f = Figure(figsize=(20,9)) the label also works again.
If Figure() is not exactly what you are trying to use here then please add some more context to your code as to what you are expecting to do with f.
Update:
I did also notice that when you move f to be after self.win = tk.Tk() the code works fine. My only guess as to why this is happening is possible how matplotlib does have its own tkinter code in it. So if you try to create a plt.figure before you create your tkinter instance then I imaging in the back end a tkinter instance is being ran by matplotlib and this is what is causing the odd behavior. I could be wrong but I cannot think of anything else. Maybe the Tkinter master Bryan Oakley can enlighten us as to what is causing this :D
import tkinter as tk
import tkinter.ttk as ttk
import matplotlib
import matplotlib.artist as artists
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
import matplotlib.animation as animation
class make_window():
def __init__(self, *args, **kwargs):
self.win = tk.Tk()
# moving the line to here should at least allow your code to work.
f = plt.figure(0, figsize=(20,9))
self.win.title("Test")
self.win.state("zoomed")
frame = ttk.Frame(self.win)
frame.pack()
labelVar = tk.StringVar()
labelVar.set("Hi")
self.LabelUpdate = tk.Label(frame, textvariable = labelVar)
self.LabelUpdate.pack()
window = make_window()
window.win.mainloop()

Embedding matplotlib into a tkinter application

I've got a program that on launch will open a matplotlib graph of bitcoin prices and will update every time the price changes. Then when I close it a fullscreen tkinter application opens with a background image and a updating clock in the corner. What I would like to happen is when I launch the program a fullscreen tkinter application opens with a background image, an updating clock in the corner, and the matplotlib graph in the middle. Basically I want to embed the matplotlib graph into my application. I've tried putting the graph code into my class and calling it but that just gives the results mentioned earlier. I tried replacing the plt.show() with this:
canvas = FigureCanvasTkAgg(fig, self)
canvas.show()
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)
but then the program wasn't launching at all. No matter where I put it in the script it would give errors like ''self' is not defined' when I directly replaced plt.show()with it and ''canvas' is not defined' when I tried to integrate it into the class. Here is the full code below:
from tkinter import *
from PIL import ImageTk, Image
import os
import time
import requests
import tkinter as tk
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
from matplotlib import style
from tkinter import ttk
import urllib
import json
import pandas as pd
import numpy as np
from PyQt5 import QtWidgets
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
class App(tk.Tk):
def __init__(self):
# call the __init__ method of Tk class to create the main window
tk.Tk.__init__(self)
# background image
img = ImageTk.PhotoImage(Image.open("0.png"))
panel = Label(self, image=img)
panel.pack()
# clock
self.label = tk.Label(self, text="", font=('comic',50,'bold'),
bg='#464545', fg='#1681BE')
self.label.place(height=204, width=484, x=1384, y=824)
self.update_clock()
# window geometry
w, h = self.winfo_screenwidth(), self.winfo_screenheight()
self.geometry("%dx%d+0+0" % (w, h))
self.overrideredirect(True)
self.mainloop()
def update_clock(self):
now = time.strftime('%H:%M:%S')
self.label.configure(text=now)
self.after(1000, self.update_clock)
def animate(self):
dataLink = 'https://btc-e.com/api/3/trades/btc_usd?limit=2000d'
data = urllib.request.urlopen(dataLink)
data = data.read().decode("utf-8")
data = json.loads(data)
data = data["btc_usd"]
data = pd.DataFrame(data)
buys = data[(data['type']=="ask")]
buys["datestamp"] = np.array(buys["timestamp"]).astype("datetime64[s]")
buyDates = (buys["datestamp"]).tolist()
plot(buyDates, buys["price"])
xlabel('time')
ylabel('Temperature')
title('Temperature Chart')
grid(True)
ani = animation.FuncAnimation(fig, animate, interval=1000)
plt.show()
app = App()
There are a few dependable's like '0.png' which is the background image and of course the many modules but any feedback would be much appreciated.
plt.show() will open a new dedicated GUI window to host the figure(s) currently present in the matplotlib state machine. If you don't want that, don't use plt.show().
To be on the save side, best don't use pyplot at all as even such innocent commands as plt.xticks() can cause a new window to open.
Also, get rid of from pylab import *. Pylab is a construct which includes several packages like numpy and pyplot in a single namespace. This may be desired for interactive notebook-like applications, but is usually a bad idea for scripts, as it makes debugging very cumbersome. Instead, try to fully stick to matplotlib API commands.
This linked question actually is an example of how to use FigureCanvasTkAgg and NavigationToolbar2Tk to embed a figure into tk.
A better start might be to look at the matplotlib example page where a minimal example for tk embedding is presented.

How to update a plot event driven?

How can I update a matplotlib plot event driven?
Background:
I wanna program a tool that displays me measured values I receive via a serial port.
These values will be send by a serial connected device when it has measured values and not when the host requests.
Current State:
It seems there is no way to update a plot manually.
When I call the plot()-function more than one time the new plots will be added. Similar to MATLAB or Octave, when you use the "hold on"-Option.
This ends after about 10 calls. Then it is freezed.
When I clear the figures before update, the whole plot disappears.
At least, when the plot is embedded in a window.
Neither draw() nor show() provide a remedy.
When I use a single plot figure, there is no easy way to update, because after the call of show(), the program flow sticks at this line, until the Window is closed. So the update has to be done in a separate thread.
Both problems will be solved, when I use an animation:
import sys
from PyQt4.QtGui import QApplication, QMainWindow, QDockWidget, QVBoxLayout,QTabWidget, QWidget
from PyQt4.QtCore import Qt
from matplotlib import pyplot, animation
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg
from random import randint
a = QApplication(sys.argv)
w = QMainWindow()
t = QTabWidget(w)
Tab1 = QWidget()
t.addTab(Tab1, '1st Plot')
t.resize(1280, 300)
x = [1, 2, 3]
Fig1 = pyplot.Figure();
Plot = Fig1.add_subplot(111);
Plot.plot(x)
Plot.grid();
layout = QVBoxLayout();
layout.addWidget(FigureCanvasQTAgg(Fig1));
Tab1.setLayout(layout);
def UpdateFunction(i):
x.append(randint(0, 10));
Plot.clear();
Plot.plot(x);
Plot.grid();
Anim = animation.FuncAnimation(Fig1, UpdateFunction, interval=500)
w.showMaximized()
sys.exit(a.exec_())
But such an animation is time triggered.
Is there any solution to update event triggered?
The event should be a finished UART read in.
Thank you very much.
Fabian
#tcaswell
Thank you!
This is my actual code:
import sys
from PyQt4.QtGui import QApplication, QMainWindow, QVBoxLayout,QTabWidget, QWidget
from PyQt4.QtCore import SIGNAL
from matplotlib import pyplot
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg
from random import randint
import time
from threading import Thread
a = QApplication(sys.argv)
w = QMainWindow()
t = QTabWidget(w)
Tab1 = QWidget()
t.addTab(Tab1, '1st Plot')
t.resize(1280, 300)
x = [];
x.append(randint(0, 10));
x.append(randint(0, 10));
x.append(randint(0, 10));
Fig1 = pyplot.Figure();
Plot = Fig1.add_subplot(111);
Line, = Plot.plot(x)
Plot.grid();
layout = QVBoxLayout();
layout.addWidget(FigureCanvasQTAgg(Fig1));
Tab1.setLayout(layout);
def triggerUpdate():
while True:
a.emit(SIGNAL('updatePlot()'));
time.sleep(2);
print('update triggered!\n')
def updateFunction():
x.append(randint(0, 10));
#Plot.clear();
Line.set_data(range(len(x)), x);
Fig1.canvas.draw_idle();
print('update done!\n')
a.connect(a, SIGNAL('updatePlot()'), updateFunction)
t = Thread(target=triggerUpdate);
t.start();
w.showMaximized()
sys.exit(a.exec_())
It seems to run, but the content of the plot gets not updated.
The Plot has no canvas. (Although it should have one, otherwise I don't now on what it is plotting when I command it.)
To be honest, I did not understand already the concept of the GUI and plot foo. The missing canvas is covered by its parenting figure. I really don't got it. When there is a figure, what can handle more than one plots, should't each plot have its own canvas?
But I think, here is the problem.
Thank you very much.

How to modify the navigation toolbar easily in a matplotlib figure window?

Is it possible to do something like the following to modify the navigation toolbar in matplotlib?
Generate a figure window, with: fig = figure()
Get a reference of the navigation tool-bar, with: tbar = fig.get_navigation_toolbar(),
or better yet, just by: tbar = fig.navtbar
Modify the tool-bar through the reference tbar, such as delete/add/edit a button with something like this:
tbar.add_button(<a Button object>);
tbar.remove_button(a reference to a button);
tbar.edit_button(a reference to a button);
Update the figure with: fig.canvas.draw()
Thank you very much.
The way I found to remove unwanted toolbar items is making a subclass, which is instantiated and used in a GTK application. As I manually create Figure, FigureCanvas and NavigationToolbar objects anyway, this was the easiest way.
class NavigationToolbar(NavigationToolbar2GTKAgg):
# only display the buttons we need
toolitems = [t for t in NavigationToolbar2GTKAgg.toolitems if
t[0] in ('Home', 'Pan', 'Zoom', 'Save')]
If you want to create custom buttons, you should take a look on the definition of NavigationToolbar2 in backend_bases. You can easily add your own entries to the toolitems list and define appropriate callback functions in your toolbar subclass.
With MPL 1.2.1 it is possible to get an handler of the navigation toolbar of a standard MPL figure through figure.canvas.toolbar. I'm not sure about previous versions.
At least with the QT backend it is possible to add arbitrary widgets to the navigation toolbar using the QT method .addWidget(). I suppose other backends will work using similar methods, but I haven't tested them.
Here it is a working example (using the QT backend) that adds a QLineEdit() to the navigation toolbar to change the title of a MPL figure (run from IPython (pylab) with run -i ..., then launch test()):
from PySide import QtGui, QtCore
def test():
plot([1,2,3], lw=2)
q = qt4_interface(gcf())
return q # WARNING: it's paramount to return the object otherwise, with
# no references, python deletes it and the GUI doesn't respond!
class qt4_interface:
def __init__(self,fig):
self.fig = fig
toolbar = fig.canvas.toolbar
self.line_edit = QtGui.QLineEdit()
toolbar.addWidget(self.line_edit)
self.line_edit.editingFinished.connect(self.do_something)
def do_something(self, *args):
self.fig.axes[0].set_title(self.line_edit.text())
self.fig.canvas.draw()
#f = open('l','a'); f.write('yes\n'); f.flush(); f.close()
The previous answers work but are very backend-specific. A slighly more elegant solution is to subclass NavigationToolbar2, as done in this other answer: Matplotlib/Tkinter - customizing toolbar tooltips
There the aim was to change the tooltips, but adding or removing a button is equally trivial.
In addition to torfbotl's solution above, you might have an extra button hanging at the end (the one with the green check mark).
That can be mitigated in the subclass constructor:
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
class PanOnlyToolbar(NavigationToolbar):
# only display the buttons we need
toolitems = [t for t in NavigationToolbar2GTKAgg.toolitems if
t[0] in ("Pan", )]
def __init__(self, *args, **kwargs):
super(PanOnlyToolbar, self).__init__(*args, **kwargs)
self.layout().takeAt(1) #or more than 1 if you have more buttons
Using PyQt5 and matplotlib version '3.0.2'
If you want to add some buttons just follow the doc given by the class NavigationToolbar2() that is initialised in NavigationToolbar2QT() wich is imported from matplotlib.backends.backend_qt5agg :
# list of toolitems to add to the toolbar, format is:
# (
# text, # the text of the button (often not visible to users)
# tooltip_text, # the tooltip shown on hover (where possible)
# image_file, # name of the image for the button (without the extension)
# name_of_method, # name of the method in NavigationToolbar2 to call
# )
So you need to redefine your class as previously said (you can also see under, the pre-defined buttons available atm). In my case I wanted to remove 2 buttons ('Save' and 'Subplots' that I commented) so that gave me :
class NavigationToolbar2QT(NavigationToolbar2QT):
# only display the buttons we need
NavigationToolbar2QT.toolitems = (
('Home', 'Reset original view', 'home', 'home'),
('Back', 'Back to previous view', 'back', 'back'),
('Forward', 'Forward to next view', 'forward', 'forward'),
(None, None, None, None),
('Pan', 'Pan axes with left mouse, zoom with right', 'move', 'pan'),
('Zoom', 'Zoom to rectangle', 'zoom_to_rect', 'zoom'),
# ('Subplots', 'Configure subplots', 'subplots', 'configure_subplots'),
(None, None, None, None),
# ('Save', 'Save the figure', 'filesave', 'save_figure'),
)
And calling the NavigationToolbar2QT (still in my case) :
figure = plt.figure()
canvas = FigureCanvas(figure)
toolbar = NavigationToolbar2QT(canvas, self)
I found that just that
fig = plt.figure()
toolbar = fig.canvas.manager.toolbar
tb=toolbar.toolitems
while len(tb)>0:
tb.pop(0)
worked to remove all tools, and popping individual tools worked too. That said,
toolbar.toolitems=[]
didnt work, so the code must have another reference to this array somewhere.

Categories

Resources