Pyqtgraph multiple horizontal axes - python

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.

Related

What is the little box with an A in pyqtgraph/PyQt Viewbox with a little box and an A inside and how to get rid of it?

I am using pyqtgraph and trying to use a histogramLUTable inside of a viewbox to try and plot some data and I have this black box with a little A that is obstructing the graph origin/axis in the lower left hand corner and I can't seem to find what it is named and/or how to get rid of it. I attached a picture with a little red box around what I am talking about.
The image in question with little red box circling what I am referring to:
There is also a little black box on the lower right hadn side obfuscating the x axis but doesn't go into the level adjustment on the right hand side.
image showing issue with first code written :
For reference, my code so far:
import PyQt5
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pandas as pd
import pyqtgraph as pg
up = np.linspace(0,1,100)
down = np.flip(up)
nparray = np.hstack([up, down])
data = np.vstack([[nparray], [nparray],[nparray]])
data = np.transpose(data)
app = QtGui.QApplication([])
win = pg.GraphicsLayoutWidget(show=True, title="pyqtGraph attempt", size=[800,600])
view = pg.PlotItem()
win.addItem(view)
img = pg.ImageItem(data, border='w')
histogram = pg.HistogramLUTItem()
histogram.setImageItem(img)
win.addItem(histogram)
if __name__ == '__main__':
pg.mkQApp().exec_()
EDIT (again):
I took a different approach to try and mitigate the little black box by using a plotItem and Viewbox rather than the HistogramLUTable but when I go this route I get the same issue. Originally I said this gives me another black box in the lower right hand corner but if you look at my original screen shot the black box is there originally as well.
import PyQt5
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pandas as pd
import pyqtgraph as pg
up = np.linspace(0,1,100)
down = np.flip(up)
nparray = np.hstack([up, down])
data = np.vstack([[nparray], [nparray],[nparray]])
app = QtGui.QApplication([])
win = pg.GraphicsLayoutWidget(show=True, title="pyqtGraph attempt", size=[800,600])
view = pg.PlotItem()
view.hideButtons()
win.addItem(view)
img = pg.ImageItem()
view.addItem(img)
img.setImage(data)
if __name__ == '__main__':
pg.mkQApp().exec_()
image showing issue with second code written:
This element is a button that allows the plot to auto-scale, therefore it appears when you zoom. One possible solution is to hide it using hideButtons() method:
view = pg.PlotItem()
view.hideButtons()
win.addItem(view)
So after a lot of playing with the code the issue appeared to be using:
app = QtGui.QApplication([])
in conjunction with:
pg.mkQApp().exec_()
It causes issues, I assume from interfering with each other, because your trying to call two application execution methods but this is just an assumption.
Preferred solution if using pyqtgraph is to stick with removing
app = QtGui.QApplication([])
altogether and only including
if __name__ == '__main__': pg.mkQapp().exec_()
But an alternate also includes using
app = QtGui.QApplication([])
with
if __name__ == '__main__': app.exec_()
But it depends on how you are using pyqtgraph, if you are using it for it's graphing methods then you will probably need to use app.addItem(plotItem) and therefore you need to call app.exec_() however if you allow pyqtgraph to take care of generating the GUI you can let it take care of executing the application via pg.mkQapp().exec_()

Disable pyqtgraph plot window

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)

pyqtgraph : Multiple colors in PyQtGraph

Can I plot multiple channels with different colors in pyqtgraph with ArrayToQPath?
path = pg.arrayToQPath(xdata.flatten(), ydata.flatten(), conn.flatten())
item = QtGui.QGraphicsPathItem(path)
item.setPen(pg.mkPen('w'))
plt.addItem(item)
QGraphicsPathItem only supports drawing with a single color, so unfortunately it is necessary to create one item per color. For example, see examples/MultiPlotSpeedTest.py.
If this is not fast enough for you, consider using an OpenGL-based vis. library. VisPy has an example of this in examples/demo/gloo/realtime_signals.py.

How can I efficiently transfer data from a NumPy array to a QPolygonF when using PySide?

I want do draw polylines with many control points in a PyQt4 / PySide application. The point coordinates come from a NumPy array and must be put into a QPolygonF in order to be drawn with QPainter.drawPolyline(...).
With PyQt4, this can be done efficiently e.g. with something like this:
import numpy as np
from PyQt4.QtGui import *
n = 3
qpoints = QPolygonF(n)
vptr = qpoints.data()
vptr.setsize(8*2*n)
aa = np.ndarray( shape=(n,2), dtype=np.float64, buffer=buffer(vptr))
aa.setflags(write=True)
aa[:,0] = np.arange(n)
aa[:,1] = np.arange(n)
for i in range(n):
print qpoints.at(i)
This works, because, when using PyQt4, QPolygonF.data() returns something (a sip.voidptr object) which speaks the Python buffer protocol.
The problem now is that if I try to run the above code using PySide instead of PyQt4, QPolygonF.data() just returns a QPointF object (with the coordinates of the first point in the QPolygonF) and is thus useless.
So my question is: is there any known workaround to this? How can I, with PySide, put data into a QPolygonF without inserting QPointF objects, element-wise?
Here is an efficient way of writing a Numpy array into the memory block pointed by a QPolygonF object using PySide2:
https://github.com/PierreRaybaut/PythonQwt/blob/master/qwt/plot_curve.py#L63
(See function "array2d_to_qpolygonf")
This is as efficient as with PyQt4 or PyQt5.
This should work
from pylab import *
from PySide.QtGui import QPolygonF
from PySide.QtCore import QPointF
xy = resize(arange(10),(2,10)).T
qPlg = QPolygonF()
for p in xy:
qPlg.append(QPointF(*p))
Hope it helps!
In PyQt6, you can accelerate the code given by Pierre Raybaut (up to a factor three on my computer) by replacing
polyline = QPolygonF([QPointF(0, 0)] * size)
by
polyline = QPolygonF([QPointF(0, 0)])
polyline.fill(QPointF(0, 0),size)
This indeed removes the need of creating a long Python list.
However, if you want to add this polygon to a series with QChart.addSeries, the time of this operation is pretty long, but it still allows working fluently up to 1 million points with my Core i7 10th gen, on Ubuntu 22.04.

How to make savefig() save image for 'maximized' window instead of default size

I am using pylab in matplotlib to create a plot and save the plot to an image file. However, when I save the image using pylab.savefig( image_name ), I find that the SIZE image saved is the same as the image that is shown when I use pylab.show().
As it happens, I have a lot of data in the plot and when I am using pylab.show(), I have to maximize the window before I can see all of the plot correctly, and the xlabel tickers don't superimpose on each other.
Is there anyway that I can programmatically 'maximize' the window before saving the image to file? - at the moment, I am only getting the 'default' window size image, which results in the x axis labels being superimposed on one another.
There are two major options in matplotlib (pylab) to control the image size:
You can set the size of the resulting image in inches
You can define the DPI (dots per inch) for output file (basically, it is a resolution)
Normally, you would like to do both, because this way you will have full control over the resulting image size in pixels. For example, if you want to render exactly 800x600 image, you can use DPI=100, and set the size as 8 x 6 in inches:
import matplotlib.pyplot as plt
# plot whatever you need...
# now, before saving to file:
figure = plt.gcf() # get current figure
figure.set_size_inches(8, 6)
# when saving, specify the DPI
plt.savefig("myplot.png", dpi = 100)
One can use any DPI. In fact, you might want to play with various DPI and size values to get the result you like the most. Beware, however, that using very small DPI is not a good idea, because matplotlib may not find a good font to render legend and other text. For example, you cannot set the DPI=1, because there are no fonts with characters rendered with 1 pixel :)
From other comments I understood that other issue you have is proper text rendering. For this, you can also change the font size. For example, you may use 6 pixels per character, instead of 12 pixels per character used by default (effectively, making all text twice smaller).
import matplotlib
#...
matplotlib.rc('font', size=6)
Finally, some references to the original documentation:
http://matplotlib.sourceforge.net/api/pyplot_api.html#matplotlib.pyplot.savefig, http://matplotlib.sourceforge.net/api/pyplot_api.html#matplotlib.pyplot.gcf, http://matplotlib.sourceforge.net/api/figure_api.html#matplotlib.figure.Figure.set_size_inches, http://matplotlib.sourceforge.net/users/customizing.html#dynamic-rc-settings
P.S. Sorry, I didn't use pylab, but as far as I'm aware, all the code above will work same way in pylab - just replace plt in my code with the pylab (or whatever name you assigned when importing pylab). Same for matplotlib - use pylab instead.
You set the size on initialization:
fig2 = matplotlib.pyplot.figure(figsize=(8.0, 5.0)) # in inches!
Edit:
If the problem is with x-axis ticks - You can set them "manually":
fig2.add_subplot(111).set_xticks(arange(1,3,0.5)) # You can actually compute the interval You need - and substitute here
And so on with other aspects of Your plot. You can configure it all. Here's an example:
from numpy import arange
import matplotlib
# import matplotlib as mpl
import matplotlib.pyplot
# import matplotlib.pyplot as plt
x1 = [1,2,3]
y1 = [4,5,6]
x2 = [1,2,3]
y2 = [5,5,5]
# initialization
fig2 = matplotlib.pyplot.figure(figsize=(8.0, 5.0)) # The size of the figure is specified as (width, height) in inches
# lines:
l1 = fig2.add_subplot(111).plot(x1,y1, label=r"Text $formula$", "r-", lw=2)
l2 = fig2.add_subplot(111).plot(x2,y2, label=r"$legend2$" ,"g--", lw=3)
fig2.add_subplot(111).legend((l1,l2), loc=0)
# axes:
fig2.add_subplot(111).grid(True)
fig2.add_subplot(111).set_xticks(arange(1,3,0.5))
fig2.add_subplot(111).axis(xmin=3, xmax=6) # there're also ymin, ymax
fig2.add_subplot(111).axis([0,4,3,6]) # all!
fig2.add_subplot(111).set_xlim([0,4])
fig2.add_subplot(111).set_ylim([3,6])
# labels:
fig2.add_subplot(111).set_xlabel(r"x $2^2$", fontsize=15, color = "r")
fig2.add_subplot(111).set_ylabel(r"y $2^2$")
fig2.add_subplot(111).set_title(r"title $6^4$")
fig2.add_subplot(111).text(2, 5.5, r"an equation: $E=mc^2$", fontsize=15, color = "y")
fig2.add_subplot(111).text(3, 2, unicode('f\374r', 'latin-1'))
# saving:
fig2.savefig("fig2.png")
So - what exactly do You want to be configured?
I think you need to specify a different resolution when saving the figure to a file:
fig = matplotlib.pyplot.figure()
# generate your plot
fig.savefig("myfig.png",dpi=600)
Specifying a large dpi value should have a similar effect as maximizing the GUI window.
Check this:
How to maximize a plt.show() window using Python
The command is different depending on which backend you use. I find that this is the best way to make sure the saved pictures have the same scaling as what I view on my screen.
Since I use Canopy with the QT backend:
pylab.get_current_fig_manager().window.showMaximized()
I then call savefig() as required with an increased DPI per silvado's answer.
You can look in a saved figure it's size, like 1920x983 px (size when i saved a maximized window), then I set the dpi as 100 and the size as 19.20x9.83 and it worked fine. Saved exactly equal to the maximized figure.
import numpy as np
import matplotlib.pyplot as plt
x, y = np.genfromtxt('fname.dat', usecols=(0,1), unpack=True)
a = plt.figure(figsize=(19.20,9.83))
a = plt.plot(x, y, '-')
plt.savefig('file.png',format='png',dpi=100)
I had this exact problem and this worked:
plt.savefig(output_dir + '/xyz.png', bbox_inches='tight')
Here is the documentation:
[https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.savefig.html][1]
I did the same search time ago, it seems that he exact solution depends on the backend.
I have read a bunch of sources and probably the most useful was the answer by Pythonio here How to maximize a plt.show() window using Python
I adjusted the code and ended up with the function below.
It works decently for me on windows, I mostly use Qt, where I use it quite often, while it is minimally tested with other backends.
Basically it consists in identifying the backend and calling the appropriate function. Note that I added a pause afterwards because I was having issues with some windows getting maximized and others not, it seems this solved for me.
def maximize(backend=None,fullscreen=False):
"""Maximize window independently on backend.
Fullscreen sets fullscreen mode, that is same as maximized, but it doesn't have title bar (press key F to toggle full screen mode)."""
if backend is None:
backend=matplotlib.get_backend()
mng = plt.get_current_fig_manager()
if fullscreen:
mng.full_screen_toggle()
else:
if backend == 'wxAgg':
mng.frame.Maximize(True)
elif backend == 'Qt4Agg' or backend == 'Qt5Agg':
mng.window.showMaximized()
elif backend == 'TkAgg':
mng.window.state('zoomed') #works fine on Windows!
else:
print ("Unrecognized backend: ",backend) #not tested on different backends (only Qt)
plt.show()
plt.pause(0.1) #this is needed to make sure following processing gets applied (e.g. tight_layout)
Old question but to anyone in need, Here's what had worked for me a while ago:
You have to have a general idea of the aspect ratio that would maximise your plot fitting. This will take some trial and error to get right, but generally 1920x1080 would be a good aspect ratio for most modern monitors. I would still suggest playing around with the aspect ratios to best suit your plot.
Steps:
Before initiating the plot, set the size for the plot, use:
plt.figure(19.20, 10.80)
**notice how I have multiplied my aspect ratio by '0.01'.
At the end of the plot, when using plt.savefig, save it as follows:
plt.savefig('name.jpg', bbox_inches='tight', dpi=1000)
If I understand correctly what you want to do, you can create your figure and set the size of the window. Afterwards, you can save your graph with the matplotlib toolbox button. Here an example:
from pylab import get_current_fig_manager,show,plt,imshow
plt.Figure()
thismanager = get_current_fig_manager()
thismanager.window.wm_geometry("500x500+0+0")
#in this case 500 is the size (in pixel) of the figure window. In your case you want to maximise to the size of your screen or whatever
imshow(your_data)
show()

Categories

Resources