Matplotlib: how to show a figure that has been closed - python

I have a function which returns a Figure created with pyplot. This function closes the figure before returning it. If I didn't close it, showing it would be very easy with just plt.show(), but let us assume I cannot do that.
I can easily save the returned Figure to a file, but I cannot find the way to display it (i.e.: have a popped window showing the figure).
from matplotlib import pyplot as plt
def new_figure():
fig = plt.figure()
plt.plot([0, 1], [2, 3])
plt.close(fig)
return fig
fig = new_figure()
fig.savefig('output.svg')
fig.show()
How could I show the figure?

When plt.close is called on a figure instance, what is actually destroyed is the graphical interface (the FigureManager) that is used to show the figure on-screen (see comment by JoeKington at Matplotlib: re-open a closed figure?). So the figure instance still exists and has not been destroyed. To show the figure on-screen again, we would have to reconstruct, in some way, an interface to replace the one that has been destroyed when calling plt.close(fig).
This can be done by simply creating a new figure with plt.figure(), "stealing" its manager, and use it to display the figure that we want to show on-screen. Alternatively, it is possible to reconstruct manually an interface to display the figure with a GUI Toolkit. I provide an example with PySide using the Qt4Agg backend. Moreover, there is a nice example that shows how this can be done with Tkinter (TkAgg) here : http://matplotlib.org/examples/user_interfaces/embedding_in_tk.html (I've tested this approach also and it works).
Dummy figure approach:
This solution is based on how to close a show() window but keep the figure alive? and Obtaining the figure manager via the OO interface in Matplotlib. The GUI toolkit that is used to construct the graphical interface for showing the figure on-screen depends on the backend that is used by matplotlib. If the backend used is TkAgg, TkInter will give some warning in Python 2.7 that can be ignored (see this post on python bug tracker).
import matplotlib.pyplot as plt
def new_figure():
fig = plt.figure()
plt.plot([0, 1], [2, 3])
plt.close(fig)
return fig
def show_figure(fig):
# create a dummy figure and use its
# manager to display "fig"
dummy = plt.figure()
new_manager = dummy.canvas.manager
new_manager.canvas.figure = fig
fig.set_canvas(new_manager.canvas)
if __name__ == '__main__':
fig = new_figure()
show_figure(fig)
plt.show()
Pyside approach:
This consists in reconstructing a GUI with a new canvas and toolbar to display the fig instance on-screen.
Important Note: The code below must be executed in a new dedicated Python console (press F6) if run from Spyder, since Spyder is also a Qt application that starts it's own QApplication (see PySide Qt script doesn't launch from Spyder but works from shell).
import matplotlib
matplotlib.use('Qt4Agg')
matplotlib.rcParams['backend.qt4']='PySide'
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT
import matplotlib.pyplot as plt
from PySide import QtGui
import sys
def new_figure():
fig = plt.figure()
plt.plot([0, 1], [2, 3])
plt.close(fig)
return fig
class myFigCanvas(QtGui.QWidget):
def __init__(self, fig, parent=None):
super(myFigCanvas, self).__init__(parent)
#---- create new canvas and toolbar --
canvas = FigureCanvasQTAgg(fig)
toolbar = NavigationToolbar2QT(canvas, self)
#---- setup layout of GUI ----
grid = QtGui.QGridLayout()
grid.addWidget(canvas, 0, 0)
grid.addWidget(toolbar, 1, 0)
self.setLayout(grid)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
fig = new_figure()
new_canvas = myFigCanvas(fig)
new_canvas.show()
sys.exit(app.exec_())
which results in:

Related

Matplotlib: how to make the background transparent on Windows

I need the whole plot window to be transparent so that a chrome window, for example, on my desktop could be seen through the plot, so that I can add points to it while seeing what's behind it.
https://stackoverflow.com/a/45505906/13650485
The answer I've listed above is EXACTLY what I want to do, except my interactive system doesn't work with TK. I'd like to use Qt5Agg. When I run the code above, the system won't accept it -- it says QT5 is currently running. If I run it without QT already loaded, it creates a blank transparent window (yay!) but if I move it or click on the icon it turns opaque black without any plot. If I change tk to Qt5 it complains on lift. If I remove the "win" code, it has no transparency(obviously). I've tried adding everything I can think of to make the canvas transparent and I can change the color but not make it transparent.
import matplotlib
# make sure Tk backend is used
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
# create a figure and some subplots
fig, ax = plt.subplots(figsize=(4,2))
ax.plot([2,3,5,1])
fig.tight_layout()
win = plt.gcf().canvas.manager.window
win.lift()
win.attributes("-topmost", True)
win.attributes("-transparentcolor", "white")
plt.show()
When I made the changes suggested by: eyllanesc
I found within a vanilla Spyder 4.1.3 | Python 3.7.7 64-bit | Qt 5.9.6 | PyQt5 5.9.2 | Windows 10
In order to import QtCore I had to first
conda install pyqt
not enough, so then conda install pyqt5
and also conda update --all
When I did that, the code ran without errors. This is a better first result!, but I still only get the frozen mpl.fig window. This time, however, it is white. . . The console returns, but the mpl window hangs. Run again, a new frozen window. Restart and run again: same result.
I hope that this is a simple error; please teach this newby.
#eyllanesc
Revised: Python screen tracing application – needs a mostly transparent plot window.
I need the whole plot window to be transparent so that a chrome window, for example, on my desktop could be seen through the plot, so that I can add plot (x, y) points to it while seeing what's behind it.
Adding the command win.setWindowFlags(QtCore.Qt.FramelessWindowHint) did indeed make the window transparent, but it made the tool bar transparent, got rid of the title bar, and removed the ability to move or resize the window. It also made it so that the graph area was not sensitive to the mouse unless I was over the line. I added the facecolor attribute to the subplots command so I could see what was going on. As long as I put a non-zero value for either the fig-alpha or the ax-alpha, the graph is sensitive to the mouse over the whole area.
I need to be able to move and resize the window and would like to have the toolbar be opaque or at least sensitive to the mouse over the whole toolbar. Can you help with this? Thanks for past help!
## Python Code Fragment by Helen for Windows 10
## to test sequence creating plot with transparent
## background (to be used to trace and record xy pairs)
from PyQt5 import QtCore
import matplotlib
matplotlib.use("Qt5Agg") #define backend, must be before pyplot is imported
import matplotlib.pyplot as plt
# create a figure and a subplot
fig,ax = plt.subplots(figsize=(4, 2),facecolor=(1.,1.,0.,0.1)) #facecolor of figure
fig.patch.set_alpha(0.1)
ax.patch.set_alpha(0.1)
# plot some fixed points
ax.plot([2, 3, 5, 1])
fig.tight_layout()
#make window transparent to the desktop
win = plt.gcf().canvas.manager.window
win.setAttribute(QtCore.Qt.WA_NoSystemBackground, True)
win.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
win.setStyleSheet("background:transparent")
win.setWindowFlags(QtCore.Qt.FramelessWindowHint)
win.setWindowTitle("My App")
plt.show()
You have to use the Qt flags, tested on Linux:
from PyQt5 import QtCore
import matplotlib
# make sure Tk backend is used
matplotlib.use("Qt5Agg")
import matplotlib.pyplot as plt
# create a figure and some subplots
fig, ax = plt.subplots(figsize=(4, 2))
fig.patch.set_alpha(0.0)
ax.patch.set_alpha(0.0)
ax.plot([2, 3, 5, 1])
fig.tight_layout()
win = plt.gcf().canvas.manager.window
win.setAttribute(QtCore.Qt.WA_NoSystemBackground, True)
win.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
win.setStyleSheet("background:transparent")
plt.show()

Unintended widget popup in a PyQt5 program and I don't know how to fix it

So I was able to successfully embed matplotlib into my PyQt5 program, except I am running into a problem where it seems the code I have is causing a popup of a matplot widget to open and close during the generation of the matplot for the widget. I was able to source the problem, but I am stuck on how I can go about to fix it.
def getHexabinData(self, shotsDf):
#returns the object type of the shot / makes hexabin
shotsHex = plt.hexbin(-shotsDf.LOC_X, shotsDf.LOC_Y,
extent=(-250, 250, 422.5, -47.5), cmap='Blues', gridsize=45, marginals=True, visible=False)
print('done')
#grabs object of hexabin of all shots
makeDf = shotsDf[shotsDf.SHOT_MADE_FLAG == 1]
#grabs the data frame of all the makes
makesHex = plt.hexbin(-makeDf.LOC_X, makeDf.LOC_Y,
extent=(-250, 250, 422.5, -47.5), cmap=plt.cm.Reds, gridsize=45, marginals=True, visible=False)
print('done')
plt.close()
#close the hexabin plot
pctsByHex = np.true_divide(makesHex.get_array(), shotsHex.get_array())
pctsByHex[np.isnan(pctsByHex)] = 0 # convert NAN values to 0
sizesByHex = len(shotsHex.get_array()) * [0]
sizesByHex = self.getSizeHexByZone(shotsDf, sizesByHex)
sizesByHex = sizesByHex * 120
#size 210 for figsize(12,11)
print('hexes done')
return shotsHex, pctsByHex, sizesByHex
And so, I've sourced the problem to be in the function above, which is a function of a separate class in a separate file that uses the following module instead of:
import matplotlib.pyplot as plt
#instead of these imported modules below for the pyqt5 program
from matplotlib.patches import Circle, Rectangle, Arc
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import (
FigureCanvasQTAgg as FigureCanvas,
NavigationToolbar2QT as NavigationToolbar)
Apologies if this question is way too specific of a problem. I've tried to do:
plt.close()
plt.hexabin(....visible=False)
but I still get this random "matplot" widget popup that opens and closes itself until the matplot widget shows the updated plot. Is there any fix to this or something I am not seeing?
Do not use import matplotlib.pyplot as plt when you integrate Matplotlib in PyQt. The pyplot module has its own event loop and maintains its own list of windoww. This clashes with PyQt as you are now are experiencing.
So remove the plt.close() statement. Instead just close the Qt window when needed.
A good example on how to integrate without using pyplot can be found here.

Jupyter Notebook: duplicated scatter plot using when using ipywidgets

I'm trying to control the display of a scatter plot with a checkbox. When I built it using the interact function it worked as expected. The plot was shown or hidden based on the value in the checkbox.
import matplotlib.pyplot as plt
from ipywidgets import interact, widgets
%matplotlib inline
def on_change(Display):
if Display == True:
plt.scatter(x,y)
plt.show()
return Display
interact(on_change, Display=False);
When I tried to do the same thing using the observe function every time I clicked on the checkbox I get an additional plot displayed below. What do I need to do to get it to redraw the same plot so it works like the example above?
I suppose something in the interact example is clearing the display but it's not clear how to do this manually.
import matplotlib.pyplot as plt
from ipywidgets import interact, widgets
%matplotlib inline
x = [1,2,3,4,5,6,7,8]
y = [5,2,4,2,1,4,5,2]
def on_change(change):
if change['new'] == True:
scat = plt.scatter(x,y)
plt.show()
cb = widgets.Checkbox(False, description = "Display")
cb.observe(on_change, names='value')
display(cb)
A couple of alterations I made to your example to hopefully demonstrate what you want. I have taken a more object-oriented route, not sure if you specifically wanted to avoid it but it helps achieve your desired outcome, it seems like you are moving towards a simple GUI here.
1) Include an Output widget (out) - basically a cell output which you can display like a normal widget. You can use a context manager block (with out:) when you want to print to that specific output widget. You can also clear the widget with out.clear_output()
2) Use the object oriented interface in matplotlib rather than using plt. I find this easier to control which plots are displayed and in which location at the right times.
temporarily suspend the interactive matplotlib with plt.ioff()
Create your figure and axis with fig, ax = plt.subplots(). NB figures can have multiple axes/subplots but we only need one.
'plot' the scatter data to your axis using ax.scatter(x,y), but this won't cause it to appear.
Explicitly display the figure with display(fig).
I'm assuming you want your figure to be replotted each time you check the box, so I have included it in the observe function. If your figure doesn't change, it would make sense to move it outside of the loop.
import matplotlib.pyplot as plt
from ipywidgets import interact, widgets
%matplotlib inline
out = widgets.Output()
x = [1,2,3,4,5,6,7,8]
y = [5,2,4,2,1,4,5,2]
def on_change(change):
if change['new'] == True:
with out:
plt.ioff()
fig,ax = plt.subplots()
ax.scatter(x,y)
display(fig)
else:
out.clear_output()
cb = widgets.Checkbox(False, description = "Display")
cb.observe(on_change, names='value')
display(cb)
display(out)

Plot not updaing correctly using draw() (duplicated axes) in wxPython

I am new to Python and wxPython I am trying to simulate particles in a box with random velocities in random directions.
I created a simple GUI in wxFormBuilder where I have a panel to show a plot of the paricles. The particles are set to a position and plotted onto the panel, then I start the simulation and update the x and y positons of the particles. When redrawing the positions The axes appear 'thicker' as before, it looks like if there are several axes ontop of eachother.
I cant find anything about this problem, I hope somebody could help me with this?
This is the code that creates the Plot:
import wx
import particles
import random
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import \
FigureCanvasWxAgg as FigCanvas, \
NavigationToolbar2WxAgg as NavigationToolbar
matplotlib.rcParams.update({'font.size': 8})
class MyFrameSub( particles.GUI_MainFrame ):
def __init__( self, parent ):
particles.GUI_MainFrame.__init__( self, parent )
def InitData(self):
self.npart = int(self.m_npart.GetValue())
self.nsteps = int(self.m_steps.GetValue())
self.ndt = float(self.m_dt.GetValue())
self.x= [random.random() for I in range(self.npart)]
self.y= [2*random.random()-1 for I in range(self.npart)]
self.vx= [self.ndt*(2*random.random()-1) for I in range(self.npart)]
self.vy= [self.ndt*(2*random.random()-1) for I in range(self.npart)]
return
def CreatePlot(self):
panelsize = self.m_PlotPanel.GetClientSize()
self.figure = Figure(figsize=(panelsize[0]/100.0,panelsize[1]/100.0), dpi=100, frameon=False)
self.canvas = FigCanvas(self.m_PlotPanel, wx.ID_ANY, self.figure)
self.axes = self.figure.add_subplot(111)
self.axes.axis((-1,1,-1,1))
self.partplot, = self.axes.plot(self.x, self.y, 'ro')
self.canvas.draw()
return
def UpdateData(self):
for i in range(self.nsteps):
for j in range(self.npart):
self.x[j]=self.x[j]+self.vx[j]
self.y[j]=self.y[j]+self.vy[j]
if abs(self.x[j])>1:
self.vx[j]=-self.vx[j]
if abs(self.y[j])>1:
self.vy[j]=-self.vy[j]
self.partplot.set_xdata(self.x)
self.partplot.set_ydata(self.y)
self.canvas.draw()
return
followed by the button definitions, it looks like this:
Before running the simulation: www.merlinvs.de/before.jpg
and after running the simulation: www.merlinvs.de/after.jpg
As you see the axes got ugly and I have no idea why.
Another question I was thinking about is the following:
When I run a loop that takes a while the UI is unresponsive, is it possible to have the UI active to cancel a loop if desired?
As for the unresponsive UI, I used greenlets for my Matplotlib stuff while updating it
from gevent.greenlet import Greenlet
from gevent import sleep
import matplotlib.plot as plt
# Plot stuff
def event_handler():
# Can update plot
sleep(1) # Simulate handling or number crunching (numpy WILL block)
g = Greenlet(event_handler)
g.start()
plt.plot(0,0) # Works immediately and updates
Some things of note is that for serious applications you need to add some protection against race coditions with the plot. Numpy and external science libraries typically cause the entire application to become unresponsive (in my experience) because they are blocking system calls outside of the greenlet context switcher's reach. For something simple though the above pattern works well.

Creating a matplotlib interactive plotting window for an existing figure

I am writing a program that fits curves to large sets of xy coordinate data. It is often helpful to watch the progress of the algorithm by plotting and displaying each iteration as the fitting progresses. I'm using matplotlib for plotting.
What I'd like to do is create the figure in the main thread, then pass it into a child thread that displays it. That way I have access to all the figure's methods and attributes in the main thread. I can plot by calling fig.gca().plot() and draw by calling fig.canvas.draw().
I can't figure out how to create an interactive plotting window that shows only the figure I pass to it. Right now I'm using matplotlib.pyplot.show(), which does display my figure, but it also displays any other figures that may have been defined in the program. Is there an object oriented way to create an interactive window for a specific figure? I am looking for a solution that does not rely on unsupported interfaces in matplotlib.
Here is a post that's similar, but it still doesn't answer my question: Interactive figure with OO Matplotlib
I've never understood why matplotlib always seems to use current objects (current figure, current axes, etc.) rather than specific objects (for example, why not have matplotlib.pyplot.show(fig) rather than just show()?) I think I'm missing something. If anyone could shed some light on why matplotlib is designed this way, or how I'm misunderstanding and/or misusing it, that would also be appreciated.
Here's my code:
import matplotlib.pyplot
import threading
import time
class Plotter():
def __init__(self,fig):
t = threading.Thread(target=self.PlottingThread,args=(fig,))
t.start()
def PlottingThread(self,fig):
#This line shows fig1 AND fig2 from below. I want it to show fig ONLY.
matplotlib.pyplot.show()
if __name__ == "__main__":
fig1 = matplotlib.pyplot.figure()
fig2 = matplotlib.pyplot.figure()
Plotter(fig1)
fig1.gca().clear()
fig1.gca().plot([1,2,3])
fig1.canvas.draw()
I think I got it:
import Tkinter
import threading
import matplotlib.backends.backend_tkagg
root = Tkinter.Tk()
class Plotter():
def __init__(self,fig):
t = threading.Thread(target=self.PlottingThread,args=(fig,))
t.start()
def PlottingThread(self,fig):
canvas = matplotlib.backends.backend_tkagg.FigureCanvasTkAgg(fig, master=root)
canvas.show()
canvas.get_tk_widget().pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1)
toolbar = matplotlib.backends.backend_tkagg.NavigationToolbar2TkAgg(canvas, root)
toolbar.update()
canvas._tkcanvas.pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1)
Tkinter.mainloop()
if __name__ == "__main__":
import time
fig1 = matplotlib.figure.Figure(figsize=(5,4), dpi=100)
fig1.gca().plot([1,2,3])
fig2 = matplotlib.figure.Figure(figsize=(5,4), dpi=100)
fig2.gca().plot([3,2,1])
#Shows fig1 and not fig2, just like it's supposed to
Plotter(fig1)
time.sleep(1)
#I can still plot to fig1 from my main thread
fig1.gca().clear()
fig1.gca().plot([5,2,7])
fig1.canvas.draw()
The only thing is if you try to create two instances of Plotter the whole thing crashes. That isn't too important for my application, but it probably means I'm using Tkinter wrong. Suggestions/corrections are welcome.

Categories