Hi I am having trouble working out which functions I need to use with Pyqtgraph.
Pyqtgraph automatically computes the axis and rescales upon zooming, and this is fine. However I have two axes, frequency and hour. Frequency can take any value between 0-100 and hour can take any value between 0-39. How can I limit the axis to these upper/lower bounds so that when the user zooms or pans they cannot go outside of these values?
I wish to add functionality such that the user can draw a rectangle over a selection of lines and the graph will refresh such that the lines within the rectangle keep their respective colour and any lines outside turn grey?
How can I add another graph to the same window which shows a zoomed in view of the region selected by the rectangle in 2. ?
My code is as follows, and currently zooms on the drawing of a user defined rectangle over the lines, (for 3 lines, my actual code will plot a lot more):
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
pg.setConfigOption('background', 'w')
pg.setConfigOption('foreground', 'k')
from random import randint
class CustomViewBox(pg.ViewBox):
def __init__(self, *args, **kwds):
pg.ViewBox.__init__(self, *args, **kwds)
self.setMouseMode(self.RectMode)
## reimplement right-click to zoom out
def mouseClickEvent(self, ev):
if ev.button() == QtCore.Qt.RightButton:
#self.autoRange()
self.setXRange(0,5)
self.setYRange(0,10)
def mouseDragEvent(self, ev):
if ev.button() == QtCore.Qt.RightButton:
ev.ignore()
else:
pg.ViewBox.mouseDragEvent(self, ev)
app = pg.mkQApp()
vb = CustomViewBox()
graph = pg.PlotWidget(viewBox=vb, enableMenu=False)
colour = []
for i in range(0,3):
colourvalue = [randint(0,255), randint(0,255), randint(0,255)]
tuple(colourvalue)
colour.append(colourvalue)
y_data = [
[['a',0],['b',1],['c',None],['d',6],['e',7]],
[['a',5],['b',2],['c',1],['d',None],['e',1]],
[['a',3],['b',None],['c',4],['d',9],['e',None]],
]
x_data = [0, 1, 2, 3, 4]
for i in range(3):
xv = []
yv = []
for j, v in enumerate(row[i][1] for row in y_data):
if v is not None:
xv.append(int(j))
yv.append(float(v))
graph.plot(xv, yv, pen = colour[i], name=y_data[0][i][0])
graph.show()
graph.setWindowTitle('Hourly Frequency Graph')
graph.setXRange(0,5)
graph.setYRange(0,10)
graph.setLabel('left', "Frequency", units='%')
graph.setLabel('bottom', "Hour")
graph.showGrid(x=True, y=True)
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
Thanks in advance for any help and advice!
I would also like to know why this code always give a segmentation fault: 11 when I close the window.
Related
I need to implement two graphs in Cartesian and polar coordinates. Everything is clear with Cartesian, but is it possible to make a polar coordinate system in pyqtgraph?
pyqtgraph does not provide by default the ability to make polar plots, I have requested the feature through the issue #452, in that discussion it is indicated that you can create that type plot easily by giving an example here.
The example is as follows:
import numpy as np
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
plot = pg.plot()
plot.setAspectLocked()
# Add polar grid lines
plot.addLine(x=0, pen=0.2)
plot.addLine(y=0, pen=0.2)
for r in range(2, 20, 2):
circle = pg.QtGui.QGraphicsEllipseItem(-r, -r, r * 2, r * 2)
circle.setPen(pg.mkPen(0.2))
plot.addItem(circle)
# make polar data
theta = np.linspace(0, 2 * np.pi, 100)
radius = np.random.normal(loc=10, size=100)
# Transform to cartesian and plot
x = radius * np.cos(theta)
y = radius * np.sin(theta)
plot.plot(x, y)
if __name__ == "__main__":
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, "PYQT_VERSION"):
QtGui.QApplication.instance().exec_()
Probably in future release pyqtgraph will offer that feature.
I can offer you to use the QPolarChart from PyQt5.QtChart. It's really easy. For example:
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtChart import QPolarChart, QChartView, QValueAxis, QScatterSeries
self.polar = QPolarChart()
chartView = QChartView(self.polar)
layout = QVBoxLayout()
layout.addWidget(chartView)
#Let's create container widget for our chart, for example QFrame
#Instead the MainWindow you should to substitute your own Widget or Main Form
self.MyFrame = QtWidgets.QFrame(MainWindow)
self.MyFrame.setGeometry(QtCore.QRect(0, 0, 1000, 1000))
self.MyFrame.setLayout(layout)
#setting axis
axisy = QValueAxis()
axisx = QValueAxis()
axisy.setRange(0,500)
axisy.setTickCount(4)
self.polar.setAxisY(axisy)
axisx.setRange(0,360)
axisx.setTickCount(5)
self.polar.setAxisX(axisx)
#Let's draw scatter series
self.polar_series = QScatterSeries()
self.polar_series.setMarkerSize(5.0)
self.polar_series.append(0, 0);
self.polar_series.append(360, 500);
#Why not draw archimedes spiral
for i in range(0,360,10):
self.polar_series.append(i, i)
self.polar.addSeries(self.polar_series)
I am trying to get my axis labels to all have decimal points, even if they happen to be integers. See the below picture.
You'll notice that on the x-axis, whenever the value is an integer, it no longer displays the decimal. I would like "1" to read "1.0".
I have a method that is setting my plot style. It reads something like this
Plotstyle1
def set_plotstyle(p1, style):
if style == 1:
axlabel_font = QtGui.QFont()
axlabel_font.setPixelSize(20)
p1.showAxis('right')
p1.showAxis('top')
p1.showLabel('right', show=False)
p1.showLabel('top', show=False)
p1.showGrid(x=False, y=False)
p1.setLogMode(x=False, y=False)
p1.getAxis('left').tickFont = axlabel_font
p1.getAxis('bottom').tickFont = axlabel_font
p1.getAxis('left').labelFont = axlabel_font
p1.getAxis('bottom').setHeight(70)
p1.getAxis('left').setWidth(100)
p1.getAxis('right').setWidth(60)
p1.getAxis('left').setStyle(tickTextOffset=15)
p1.getAxis('bottom').setStyle(tickTextOffset=15)
p1.getAxis('top').setStyle(showValues=False)
p1.getAxis('right').setStyle(showValues=False)
If possible, I would like to set this feature within this method. Thanks!
Please find example code below which displays the x and y values with a single decimal place. I modified the set_plotstyle method so that it also accepts the x and y values that are being plotted. This is not exactly what you asked for, but I hope that it helps.
The x and y values are converted to formatted strings that are assigned to the tick values using setTicks. The string format command for one decimal place is .1f. The idea and example code is based on this stackoverflow answer:
PyQtGraph - How to set intervals of axis
I reduced the font size in your set_plotstyle method so that the values do not overlap in the plot.
This is tested using Python 3.6 under Linux Mint using the Jupyter notebook. Use the %gui inline magic to display the plot if you also use Jupyter notebook.
import pyqtgraph as pg
import numpy as np
from pyqtgraph.Qt import QtCore, QtGui
def set_plotstyle(p1, x_vals, y_vals, style):
if style == 1:
axlabel_font = QtGui.QFont()
axlabel_font.setPixelSize(15)
p1.showAxis('right')
p1.showAxis('top')
p1.showLabel('right', show=False)
p1.showLabel('top', show=False)
p1.showGrid(x=False, y=False)
p1.setLogMode(x=False, y=False)
p1.getAxis('left').tickFont = axlabel_font
p1.getAxis('bottom').tickFont = axlabel_font
p1.getAxis('left').labelFont = axlabel_font
p1.getAxis('bottom').setHeight(70)
p1.getAxis('left').setWidth(100)
p1.getAxis('right').setWidth(60)
p1.getAxis('left').setStyle(tickTextOffset=15)
p1.getAxis('bottom').setStyle(tickTextOffset=15)
p1.getAxis('top').setStyle(showValues=False)
p1.getAxis('right').setStyle(showValues=False)
ax = p1.getAxis('bottom')
dx = [(value, '{:.1f}'.format(value)) for value in x_vals]
ax.setTicks([dx, []])
ay = p1.getAxis('left')
dy = [(value, '{:.1f}'.format(value)) for value in y_vals]
ay.setTicks([dy, []])
return p1
app = pg.mkQApp()
pw = pg.PlotWidget(title="Example")
x = np.arange(7)
y = x**2/150
pw.plot(x=x, y=y, symbol='o')
pw.show()
pw.setWindowTitle('Example')
set_plotstyle(pw, x, y, 1)
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
My program plots the positions of particles in my file for every time step. Unfortunately it gets slower and slower although I used matplotlib.animation. Where is the bottleneck?
My data file for two particles looks like the following:
# x y z
# t1 1 2 4
# 4 1 3
# t2 4 0 4
# 3 2 9
# t3 ...
My script:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import mpl_toolkits.mplot3d.axes3d as p3
import matplotlib.animation as animation
# Number of particles
numP = 2
# Dimensions
DIM = 3
timesteps = 2000
with open('//home//data.dat', 'r') as fp:
particleData = []
for line in fp:
line = line.split()
particleData.append(line)
x = [float(item[0]) for item in particleData]
y = [float(item[1]) for item in particleData]
z = [float(item[2]) for item in particleData]
# Attaching 3D axis to the figure
fig = plt.figure()
ax = p3.Axes3D(fig)
# Setting the axes properties
border = 1
ax.set_xlim3d([-border, border])
ax.set_ylim3d([-border, border])
ax.set_zlim3d([-border, border])
def animate(i):
global x, y, z, numP
#ax.clear()
ax.set_xlim3d([-border, border])
ax.set_ylim3d([-border, border])
ax.set_zlim3d([-border, border])
idx0 = i*numP
idx1 = numP*(i+1)
ax.scatter(x[idx0:idx1],y[idx0:idx1],z[idx0:idx1])
ani = animation.FuncAnimation(fig, animate, frames=timesteps, interval=1, blit=False, repeat=False)
plt.show()
I would suggest to use pyqtgraph in this case. Citation from the docs:
Its primary goals are 1) to provide fast, interactive graphics for
displaying data (plots, video, etc.) and 2) to provide tools to aid in
rapid application development (for example, property trees such as
used in Qt Designer).
You can check out some examples after the installation:
import pyqtgraph.examples
pyqtgraph.examples.run()
This small code snippet generates 1000 random points and displays them in a 3D scatter plot by constantly updating the opacity, similar to the 3D scatter plot example in pyqtgraph.examples:
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
import numpy as np
app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.show()
g = gl.GLGridItem()
w.addItem(g)
#generate random points from -10 to 10, z-axis positive
pos = np.random.randint(-10,10,size=(1000,3))
pos[:,2] = np.abs(pos[:,2])
sp2 = gl.GLScatterPlotItem(pos=pos)
w.addItem(sp2)
#generate a color opacity gradient
color = np.zeros((pos.shape[0],4), dtype=np.float32)
color[:,0] = 1
color[:,1] = 0
color[:,2] = 0.5
color[0:100,3] = np.arange(0,100)/100.
def update():
## update volume colors
global color
color = np.roll(color,1, axis=0)
sp2.setData(color=color)
t = QtCore.QTimer()
t.timeout.connect(update)
t.start(50)
## Start Qt event loop unless running in interactive mode.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
Small gif to give you an idea of the performance:
EDIT:
Displaying multiple points at every single time step is a little bit tricky since the gl.GLScatterPlotItem takes only (N,3)-arrays as point locations, see here. You could try to make a dictionary of ScatterPlotItems where each of them includes all time steps for a specific point. Then one would need to adapt the update function accordingly. You can find an example below where pos is an (100,10,3)-array representing 100 time steps for each point. I reduced the update time to 1000 ms for a slower animation.
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
import numpy as np
app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.show()
g = gl.GLGridItem()
w.addItem(g)
pos = np.random.randint(-10,10,size=(100,10,3))
pos[:,:,2] = np.abs(pos[:,:,2])
ScatterPlotItems = {}
for point in np.arange(10):
ScatterPlotItems[point] = gl.GLScatterPlotItem(pos=pos[:,point,:])
w.addItem(ScatterPlotItems[point])
color = np.zeros((pos.shape[0],10,4), dtype=np.float32)
color[:,:,0] = 1
color[:,:,1] = 0
color[:,:,2] = 0.5
color[0:5,:,3] = np.tile(np.arange(1,6)/5., (10,1)).T
def update():
## update volume colors
global color
for point in np.arange(10):
ScatterPlotItems[point].setData(color=color[:,point,:])
color = np.roll(color,1, axis=0)
t = QtCore.QTimer()
t.timeout.connect(update)
t.start(1000)
## Start Qt event loop unless running in interactive mode.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
Keep in mind that in this examples, all points are shown in the scatter plot, however, the color opacity (4th dimension in the color array) is updated in every time step to get an animation. You could also try to update the points instead of the color to get better performance...
I would guess your bottleneck is calling ax.scatter and ax.set_xlim3d and similar in every frame in the animation.
Ideally, you should make a call to scatter once, then use the object returned by scatter and its set_... properties in the animate function (more details here).
I can't figure out how to do it with scatter, but if you use ax.plot(x, y, z, 'o') instead, you can then follow the demo method here.
Using some random data for x, y, z. It would work like this
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import mpl_toolkits.mplot3d.axes3d as p3
import matplotlib.animation as animation
from numpy.random import random
# Number of particles
numP = 2
# Dimensions
DIM = 3
timesteps = 2000
x, y, z = random(timesteps), random(timesteps), random(timesteps)
# Attaching 3D axis to the figure
fig = plt.figure()
ax = p3.Axes3D(fig)
# Setting the axes properties
border = 1
ax.set_xlim3d([-border, border])
ax.set_ylim3d([-border, border])
ax.set_zlim3d([-border, border])
line = ax.plot(x[:1], y[:1], z[:1], 'o')[0]
def animate(i):
global x, y, z, numP
idx1 = numP*(i+1)
# join x and y into single 2 x N array
xy_data = np.c_[x[:idx1], y[:idx1]].T
line.set_data(xy_data)
line.set_3d_properties(z[:idx1])
ani = animation.FuncAnimation(fig, animate, frames=timesteps, interval=1, blit=False, repeat=False)
plt.show()
I am trying to animate a scatter plot (it needs to be a scatter plot as I want to vary the circle sizes). I have gotten the matplotlib documentation tutorial matplotlib documentation tutorial to work in my PyQT application, but would like to introduce blitting into the equation as my application will likely run on slower machines where the animation may not be as smooth.
I have had a look at many examples of animations with blitting, but none ever use a scatter plot (they use plot or lines) and so I am really struggling to figure out how to initialise the animation (the bits that don't get re-rendered every time) and the ones that do. I have tried quite a few things, and seem to be getting nowhere (and I am sure they would cause more confusion than help!). I assume that I have missed something fairly fundamental. Has anyone done this before? Could anyone help me out splitting the figure into the parts that need to be initiated and the ones that get updates?
The code below works, but does not blit. Appending
blit=True
to the end of the animation call yields the following error:
RuntimeError: The animation function must return a sequence of Artist objects.
Any help would be great.
Regards
FP
import numpy as np
from PyQt4 import QtGui, uic
import sys
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setupAnim()
self.show()
def setupAnim(self):
self.fig = plt.figure(figsize=(7, 7))
self.ax = self.fig.add_axes([0, 0, 1, 1], frameon=False)
self.ax.set_xlim(0, 1), self.ax.set_xticks([])
self.ax.set_ylim(0, 1), self.ax.set_yticks([])
# Create rain data
self.n_drops = 50
self.rain_drops = np.zeros(self.n_drops, dtype=[('position', float, 2),
('size', float, 1),
('growth', float, 1),
('color', float, 4)])
# Initialize the raindrops in random positions and with
# random growth rates.
self.rain_drops['position'] = np.random.uniform(0, 1, (self.n_drops, 2))
self.rain_drops['growth'] = np.random.uniform(50, 200, self.n_drops)
# Construct the scatter which we will update during animation
# as the raindrops develop.
self.scat = self.ax.scatter(self.rain_drops['position'][:, 0], self.rain_drops['position'][:, 1],
s=self.rain_drops['size'], lw=0.5, edgecolors=self.rain_drops['color'],
facecolors='none')
self.animation = FuncAnimation(self.fig, self.update, interval=10)
plt.show()
def update(self, frame_number):
# Get an index which we can use to re-spawn the oldest raindrop.
self.current_index = frame_number % self.n_drops
# Make all colors more transparent as time progresses.
self.rain_drops['color'][:, 3] -= 1.0/len(self.rain_drops)
self.rain_drops['color'][:, 3] = np.clip(self.rain_drops['color'][:, 3], 0, 1)
# Make all circles bigger.
self.rain_drops['size'] += self.rain_drops['growth']
# Pick a new position for oldest rain drop, resetting its size,
# color and growth factor.
self.rain_drops['position'][self.current_index] = np.random.uniform(0, 1, 2)
self.rain_drops['size'][self.current_index] = 5
self.rain_drops['color'][self.current_index] = (0, 0, 0, 1)
self.rain_drops['growth'][self.current_index] = np.random.uniform(50, 200)
# Update the scatter collection, with the new colors, sizes and positions.
self.scat.set_edgecolors(self.rain_drops['color'])
self.scat.set_sizes(self.rain_drops['size'])
self.scat.set_offsets(self.rain_drops['position'])
if __name__== '__main__':
app = QtGui.QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
You need to add return self.scat, at the end of the update method if you want to use FuncAnimation with blit=True. See also this nice StackOverflow post that presents an example of a scatter plot animation with matplotlib using blit.
As a side-note, if you wish to embed a mpl figure in a Qt application, it is better to avoid using the pyplot interface and to use instead the Object Oriented API of mpl as suggested in the matplotlib documentation.
This could be achieved, for example, as below, where mplWidget can be embedded as any other Qt widget in your main application. Note that I renamed the update method to update_plot to avoid conflict with the already existing method of the FigureCanvasQTAgg class.
import numpy as np
from PyQt4 import QtGui
import sys
import matplotlib as mpl
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
class mplWidget(FigureCanvasQTAgg):
def __init__(self):
super(mplWidget, self).__init__(mpl.figure.Figure(figsize=(7, 7)))
self.setupAnim()
self.show()
def setupAnim(self):
ax = self.figure.add_axes([0, 0, 1, 1], frameon=False)
ax.axis([0, 1, 0, 1])
ax.axis('off')
# Create rain data
self.n_drops = 50
self.rain_drops = np.zeros(self.n_drops, dtype=[('position', float, 2),
('size', float, 1),
('growth', float, 1),
('color', float, 4)
])
# Initialize the raindrops in random positions and with
# random growth rates.
self.rain_drops['position'] = np.random.uniform(0, 1, (self.n_drops, 2))
self.rain_drops['growth'] = np.random.uniform(50, 200, self.n_drops)
# Construct the scatter which we will update during animation
# as the raindrops develop.
self.scat = ax.scatter(self.rain_drops['position'][:, 0],
self.rain_drops['position'][:, 1],
s=self.rain_drops['size'],
lw=0.5, facecolors='none',
edgecolors=self.rain_drops['color'])
self.animation = FuncAnimation(self.figure, self.update_plot,
interval=10, blit=True)
def update_plot(self, frame_number):
# Get an index which we can use to re-spawn the oldest raindrop.
indx = frame_number % self.n_drops
# Make all colors more transparent as time progresses.
self.rain_drops['color'][:, 3] -= 1./len(self.rain_drops)
self.rain_drops['color'][:, 3] = np.clip(self.rain_drops['color'][:, 3], 0, 1)
# Make all circles bigger.
self.rain_drops['size'] += self.rain_drops['growth']
# Pick a new position for oldest rain drop, resetting its size,
# color and growth factor.
self.rain_drops['position'][indx] = np.random.uniform(0, 1, 2)
self.rain_drops['size'][indx] = 5
self.rain_drops['color'][indx] = (0, 0, 0, 1)
self.rain_drops['growth'][indx] = np.random.uniform(50, 200)
# Update the scatter collection, with the new colors,
# sizes and positions.
self.scat.set_edgecolors(self.rain_drops['color'])
self.scat.set_sizes(self.rain_drops['size'])
self.scat.set_offsets(self.rain_drops['position'])
return self.scat,
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = mplWidget()
sys.exit(app.exec_())
I'm testing an algorithm and I'd like to produce a sequence of figures displaying intermediate results using matplotlib.
I'm not needing animations, nor multiple figures all on the screens, nor subplots.
I'd just like to produce a sequence of figures (possibly using pyplot), and when I'm done, a single window is shown. Then I'd like to navigate in the sequence of figures using the arrows.
How can I do something like that?
I tried to search, but I can only find subplot or multiple figures on the screen.
Thanks
The most general approach is to create a sequence of axes in the same figure, and only display one at a time.
Here's an example of that (The left and right arrow keys control which plot is displayed):
import matplotlib.pyplot as plt
import numpy as np
def main():
x = np.linspace(0, 10, 100)
axes = AxesSequence()
for i, ax in zip(range(3), axes):
ax.plot(x, np.sin(i * x))
ax.set_title('Line {}'.format(i))
for i, ax in zip(range(5), axes):
ax.imshow(np.random.random((10,10)))
ax.set_title('Image {}'.format(i))
axes.show()
class AxesSequence(object):
"""Creates a series of axes in a figure where only one is displayed at any
given time. Which plot is displayed is controlled by the arrow keys."""
def __init__(self):
self.fig = plt.figure()
self.axes = []
self._i = 0 # Currently displayed axes index
self._n = 0 # Last created axes index
self.fig.canvas.mpl_connect('key_press_event', self.on_keypress)
def __iter__(self):
while True:
yield self.new()
def new(self):
# The label needs to be specified so that a new axes will be created
# instead of "add_axes" just returning the original one.
ax = self.fig.add_axes([0.15, 0.1, 0.8, 0.8],
visible=False, label=self._n)
self._n += 1
self.axes.append(ax)
return ax
def on_keypress(self, event):
if event.key == 'right':
self.next_plot()
elif event.key == 'left':
self.prev_plot()
else:
return
self.fig.canvas.draw()
def next_plot(self):
if self._i < len(self.axes):
self.axes[self._i].set_visible(False)
self.axes[self._i+1].set_visible(True)
self._i += 1
def prev_plot(self):
if self._i > 0:
self.axes[self._i].set_visible(False)
self.axes[self._i-1].set_visible(True)
self._i -= 1
def show(self):
self.axes[0].set_visible(True)
plt.show()
if __name__ == '__main__':
main()
If they're all the same type of plot, you could just update the data of the artists involved. This is especially easy if you have the same number of items in each plot. I'll leave out an example for the moment, but if the example above is too memory-hungry, just updating the data of the artists will be considerably lighter.