As a continuation of this question of mine:
pyqtgraph for plotting multiple data lists
I managed to use pyqtgraph to export my plot to a file. But i still get the window that pyqtgraph spawns in order to try to create the plot there. This window now shows nothing, it is empty and white. When i use regular python console, after a while this window disappears, but if i use Ipython, the window says "Not responding" and when i close it Ipython says "Kernel died, restarting".
Is there a way to completely disable this pyqtgraph window and only use the output file to create the plot, in order for it to work correctly without errors?
I used to do this with matplotlib (which had the same window popping up, but if you used command matplotlib.use('Agg'), to change the backend, then the window stopped popping.
Oh my... i just figured it out! My first answer on SO, don't be too harsh on me.
First, make sure you are creating your pyqtgraph graph in a constructor(init function) of a class. Call it there once and immediately hide it (that was the complicated part for me).
Here is an example code:
import numpy as np
import pyqtgraph as pg
import pyqtgraph.exporters
class MyPlotClass():
def __init__(self):
self.windowplt = pg.plot()
self.windowplt.win.hide()
def savePlots(self):
x = np.arange(0, 256)
y = np.arange(0, 256)
self.windowplt.plot(x, y)
exporter = pg.exporters.ImageExporter(self.windowplt.plotItem)
exporter.params.param('width').setValue(256, blockSignal=exporter.widthChanged)
exporter.params.param('height').setValue(256, blockSignal=exporter.heightChanged)
for i in np.arange(0,10):
exporter.export('./fileName' + str(i) + '.png')
print(i)
if __name__ == "__main__":
saveMyFiles = MyPlotClass()
saveMyFiles.savePlots()
Only one window WILL appear for a shot duration and hide itself immediately.
I know your Question is old, but it might help anyone in the future. I was searching for the solution for the whole day now.
As mentioned in your previous thread pyqtgraph for plotting multiple data lists the ImageExporter.py bug still exists. Insted of changing the code of the pyqtgraph library you can work around it by setting both width and height yourself (as in the code above).
exporter.params.param('width').setValue(256, blockSignal=exporter.widthChanged)
exporter.params.param('height').setValue(256, blockSignal=exporter.heightChanged)
Related
I have a python project that outputs several Matplotlib figures; each figure contains several charts. The problem that project launches about 15 figures (windows) every run, which I can not reduce.
Is it possible to concatenate all these figures (windows) to a single tabbed window so that each tab represents one figure?
Any help is much appreciated.
Thanks in advance
Workaround
Thanks to #mobiusklein comments below he suggested a workaround, to export the figures as myltipage pdf file as shown here.
Important note about the multipage pdf example mentioned above.
I tried it, but I got an error regarding the LaTeX use in matplotlib. Because fixing this error is beyond the scope of this question, so I suggest if it occurs to anyone, to set plt.rc('text', usetex=False) instead of usetex=True
I still hope if someone have other solution or workaround to post it for the benefit of others.
I wrote a simple wrapper for matplotlib that does something like you're describing. You need pyqt5 for it to work though.
Here is the code, you build a plotWindow object and feed it figure handles. It'll create a new tab for each figure.
import matplotlib
# prevent NoneType error for versions of matplotlib 3.1.0rc1+ by calling matplotlib.use()
# For more on why it's nececessary, see
# https://stackoverflow.com/questions/59656632/using-qt5agg-backend-with-matplotlib-3-1-2-get-backend-changes-behavior
matplotlib.use('qt5agg')
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from PyQt5.QtWidgets import QMainWindow, QApplication, QWidget, QTabWidget, QVBoxLayout
import matplotlib.pyplot as plt
import sys
class plotWindow():
def __init__(self, parent=None):
self.app = QApplication(sys.argv)
self.MainWindow = QMainWindow()
self.MainWindow.__init__()
self.MainWindow.setWindowTitle("plot window")
self.canvases = []
self.figure_handles = []
self.toolbar_handles = []
self.tab_handles = []
self.current_window = -1
self.tabs = QTabWidget()
self.MainWindow.setCentralWidget(self.tabs)
self.MainWindow.resize(1280, 900)
self.MainWindow.show()
def addPlot(self, title, figure):
new_tab = QWidget()
layout = QVBoxLayout()
new_tab.setLayout(layout)
figure.subplots_adjust(left=0.05, right=0.99, bottom=0.05, top=0.91, wspace=0.2, hspace=0.2)
new_canvas = FigureCanvas(figure)
new_toolbar = NavigationToolbar(new_canvas, new_tab)
layout.addWidget(new_canvas)
layout.addWidget(new_toolbar)
self.tabs.addTab(new_tab, title)
self.toolbar_handles.append(new_toolbar)
self.canvases.append(new_canvas)
self.figure_handles.append(figure)
self.tab_handles.append(new_tab)
def show(self):
self.app.exec_()
if __name__ == '__main__':
import numpy as np
pw = plotWindow()
x = np.arange(0, 10, 0.001)
f = plt.figure()
ysin = np.sin(x)
plt.plot(x, ysin, '--')
pw.addPlot("sin", f)
f = plt.figure()
ycos = np.cos(x)
plt.plot(x, ycos, '--')
pw.addPlot("cos", f)
pw.show()
This is also posted at: https://github.com/superjax/plotWindow
Hopefully this could be a good starting point for your application.
The backend you choose to use for matplotlib controls how each figure is displayed. Some backends just render figures to file, while others like the tk, qt, or gtk backends render figures in graphical windows. Those backends determine what functionality those GUI windows have.
The existing backends don't support the type of tabbed navigation you're looking for. Someone else here implemented this using Qt4.
You might also try writing your own report files with PDF or HTML which would let you more easily write more complex image arrangements with simpler libraries.
Something that is functionally similar might be implemented using the widgets. For example, provide a row of buttons, one button for each "tab", and repaint the graphical portion of the window in response to each button.
The buttons example as a workaround is creative. As another stated you can use PyQt to create a tabbed window.
It is for this reason I use PyQtGraph. PyQtGraph only uses PyQt as a backend and therefor "natively" supports both tabbed windows and "docks". Docks allow for movable tabs and splits as well as breaking off a tab or split to a new floating window.
In general, PyQtGraph's docks provide a method for organizing your graphs/plots/images that I haven't been able to get with other libraries.
Bokeh offers tabbed windows through their Panels and Tabs widgets.
I know it is not always feasible to move away from matplotlib but I felt like there was a lack of representation of libraries which have considered and implemented tools specifically for your use case.
I shared my code that allows docking and tabbing with drag-n-drop (qt docking system). It acts as a matplotlib backend, so it's easy to integrate.
https://github.com/peper0/mpldock
I recently released a python package which contains a class called DockablePlotWindow which provides a solution similar to superjax's answer but which provides a little more flexibility to organize how the plots are initially displayed.
I'm interested to continue improving this package so feel free to open pull requests or issues on github. You can find information about it here: https://github.com/nanthony21/mpl_qt_viz
and here:
https://nanthony21.github.io/mpl_qt_viz/
I would like to use pyqtgraph to plot optical spectra of some signal vs wavelength in nm. The harder part is that it would be useful to plot the energy of the corresponding wavelength along the top of the graph. See the bottom figure for an example.
My question is how to accomplish this in pyqtgraph. I've thought about trying to modify the two y-axis solution (such as here), but I don't think it's really appropriate. The axis should be linked, not free to move independently, so adding a new viewbox doesn't seem like the right path, unless it's to link everything.
I think I could do something by adding a new axisitem and connecting the appropriate resizing signals to force the new axis coordinates to work, but that feels rather dirty.
http://www.nature.com/nnano/journal/v10/n10/images/nnano.2015.178-f1.jpg
I found a quick work around which somewhat works for my purposes. I figure I'll post it here in case others are curious and it may be helpful for them. It involves subclassing AxisItem and specifying tickStrings. It doesn't work ideally, as it maintains the same tick positions as the main bottom axis, but it should be at least give me an idea for what I'm looking at.
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
class CustomAxis(pg.AxisItem):
def tickStrings(self, values, scale, spacing):
return ['{:.4f}'.format(1./i) for i in values]
pg.mkQApp()
pw = pg.PlotWidget()
pw.show()
pw.setWindowTitle('pyqtgraph example: MultipleXAxes')
p1 = pw.plotItem
p1.setLabels(left='axis 1')
# Get rid of the item at the grid position where the top should be
p1.layout.removeItem(p1.getAxis('top'))
# make our own, setting the parent and orientation
caxis = CustomAxis(orientation='top', parent=p1)
caxis.setLabel('inverted')
caxis.linkToView(p1.vb)
# set the new one for internal plotitem
p1.axes['top']['item'] = caxis
# and add it to the layout
p1.layout.addItem(caxis, 1, 1)
p1.plot(np.arange(1, 7), [1,2,4,8,16,32])
#p2.addItem(pg.PlotCurveItem(1./np.arange(1, 7), [1,2,4,8,16,32], pen='b'))
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
Obviously, the return value should be replaced by whatever function relates the two axis.
I'm trying to get real-time spectrum analyzer type plot in matplotlib. I've got some code working (with help from other posts on StackOverflow) as follows:
import time
import numpy as np
import matplotlib.pyplot as plt
plt.axis([0, 1000, 0, 1])
plt.ion()
plt.show()
i=0
np.zeros([1,500],'float')
lines=plt.plot(y[0])
while 1:
i=i+1
lines.pop(0).remove()
y = np.random.rand(1,100)
lines=plt.plot(y[0])
plt.draw()
The code works and I'm getting what I want, but there is a serious problem. The plot window would freeze after some time. I know the program is still running by inspecting the i variable (I'm running the code in Anaconda/Spyder so I can see the variables). However the plot window would show "Non responding" and if I terminate the python program in Spyder by ctrl+c, the plot window comes back to life and show the latest plot.
I'm out of wits here as how to further debug the issue. Anyone to help?
Thanks
I am not sure that adding plt.pause will entirely solve your issue. It may just take longer before the application crash. The memory used by your application seems to constantly increase over time (even after adding plt.pause). Below are two suggestions that may help you with your current issue:
Instead of removing/recreating the lines artists with each iteration with remove and plot, I would use the same artist throughout the whole animation and simply update its ydata.
I'll use explicit handlers for the axe and figure and call show and draw explicitly on the figure manager and canvas instead of going with implicit calls through pyplot, following the advices given in a post by tcaswell.
Following the above, the code would look something like this:
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.axis([0, 100, 0, 1])
y = np.random.rand(100)
lines = ax.plot(y)
fig.canvas.manager.show()
i=0
while 1:
i=i+1
y = np.random.rand(100)
lines[0].set_ydata(y)
fig.canvas.draw()
fig.canvas.flush_events()
I've run the above code for a good 10 minutes and the memory used by the application remained stable the whole time, while the memory used by your current code (without plt.pause) increased by about 30MiB over the same period.
To answer myself, I solved the issue by adding
plt.pause(0.01)
after the
plt.draw()
This probably allows the GUI to finish the drawing and clear the buffer somewhere (my guess) before the new data comes in.
I know I'm late to answer this question, but for your issue you could look into the "joystick" package. It is based on the line.set_data() and canvas.draw() methods, with optional axes re-scaling, hence most probably faster than removing a line and adding a new one. It also allows for interactive text logging or image plotting (in addition to graph plotting).
No need to do your own loops in a separate thread, the package takes care of it, just give the update frequency you wish. Plus the terminal remains available for more monitoring commands while live plotting, which is not possible with a "while True" loop.
See http://www.github.com/ceyzeriat/joystick/ or https://pypi.python.org/pypi/joystick (use pip install joystick to install)
try:
import joystick as jk
import numpy as np
import time
class test(jk.Joystick):
# initialize the infinite loop decorator
_infinite_loop = jk.deco_infinite_loop()
def _init(self, *args, **kwargs):
"""
Function called at initialization, see the doc
"""
self._t0 = time.time() # initialize time
self.xdata = np.array([self._t0]) # time x-axis
self.ydata = np.array([0.0]) # fake data y-axis
# create a graph frame
self.mygraph = self.add_frame(jk.Graph(name="test", size=(500, 500), pos=(50, 50), fmt="go-", xnpts=100, xnptsmax=1000, xylim=(None, None, 0, 1)))
#_infinite_loop(wait_time=0.2)
def _generate_data(self): # function looped every 0.2 second to read or produce data
"""
Loop starting with the simulation start, getting data and
pushing it to the graph every 0.2 seconds
"""
# concatenate data on the time x-axis
self.xdata = jk.core.add_datapoint(self.xdata, time.time(), xnptsmax=self.mygraph.xnptsmax)
# concatenate data on the fake data y-axis
self.ydata = jk.core.add_datapoint(self.ydata, np.random.random(), xnptsmax=self.mygraph.xnptsmax)
self.mygraph.set_xydata(t, self.ydata)
t = test()
t.start()
t.stop()
When plotting with Python ggplot, every single plot command causes a GUI pane to be displayed and suspend execution ("interactive plotting"). But I want to:
avoid/ turn off this GUI and save the plot object some where in runtime (I will be displaying it in some other C# forms control).
find a Python equivalent to dev.off() command in R language which turns off the GUI for plotting.
Example:
print ggplot(data, aes('Age', 'Weight')) + geom_point(colour='steelblue')
When I execute this, it opens up a new GUI (like below) displaying the plot.
You can do the following, which returns a matplotlib figure:
g = ggplot(...) + geom_xxx(...)
fig = g.draw()
ggplots __repr__() method (what is called by print(g) is basically self.draw() then use matplotlibs plt.show() to show the plot...
You can also use ggsave(g) to save the plot somewhere.
Since plotting is triggered by __repr__ method the obvious approach is to avoid situations when it is called. Since you want to use this plot in some other place there is no reason to call print or even executing statements which will be discarded like this:
ggplot(data, aes('Age', 'Weight')) + geom_point(colour='steelblue')
Instead you can simply assign it to the variable
p = ggplot(data, aes('Age', 'Weight')) + geom_point(colour='steelblue')
what is exactly the same thing one would do in R. Using graphic device to redirect output and discarding it doesn't really make sense.
If for some reason that's not enough you switch to non-interactive matplotlib backend:
import matplotlib
matplotlib.use('Agg')
from ggplot import *
ggplot(aes(x='date', y='beef'), data=meat)
<ggplot: (...)>
In the past I was able to do simple animations with matplotlib with a for loop, but this hasn't worked for some time now.
The standard answer is that you have to turn interactive mode on and/or force a redraw with matplotlib.pyplot.draw(). Here is my minimal working example:
import numpy as np
import matplotlib
matplotlib.use('Qt4Agg')
import matplotlib.pyplot as mplot
mplot.ion()
fig = mplot.figure(1)
ax = fig.add_subplot(111)
for ii in np.arange(0,10):
x = 200*np.random.rand(30)
ax.plot(x)
mplot.draw()
filename = ("img_%d.png" % ii)
mplot.savefig(filename)
When I run this in Interactive Python Editor, I get one figure at the very end with all the plots in it (this also happens with mplot.show())
When I run this in IPython 3.1 (with Python 3.3.5) from the command line, I get nothing at all.
The mplot.savefig(filename) line does seem to work, as the images are generated.
(It's possible this is a bug in the Qt4 backend.)
Try deleting the line matplotlib.use('Qt4Agg'). Works for me. Also works with matplotlib.use('TkAgg'). So it is a backend problem. There is another way to do animations.