I'm using pyqtgraph and I'd like to add an item in the legend for InfiniteLines.
I've adapted the example code to demonstrate:
# -*- coding: utf-8 -*-
"""
Demonstrates basic use of LegendItem
"""
import initExample ## Add path to library (just for examples; you do not need this)
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
plt = pg.plot()
plt.setWindowTitle('pyqtgraph example: Legend')
plt.addLegend()
c1 = plt.plot([1,3,2,4], pen='r', name='red plot')
c2 = plt.plot([2,1,4,3], pen='g', fillLevel=0, fillBrush=(255,255,255,30), name='green plot')
c3 = plt.addLine(y=4, pen='y')
# TODO: add legend item indicating "maximum value"
## 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_()
What I get as a result is:
How do I add an appropriate legend item?
pyqtgraph automatically adds an item to the legend if it is created with the "name" parameter. The only adjustment needed in the above code would be as follows:
c3 = plt.plot (y=4, pen='y', name="maximum value")
as soon as you provide pyqtgraph with a name for the curve it will create the according legend item by itself.
It is important though to call plt.addLegend() BEFORE you create the curves.
For this example, you can create an empty PlotDataItem with the correct color and add it to the legend like this:
style = pg.PlotDataItem(pen='y')
plt.plotItem.legend.addItem(l, "maximum value")
Related
I am working on a project that requires me to log data over time, while also plotting the data on screen with a live line graph. I have gotten everything but the line graph to work this far and am unsure what I am doing incorrectly. This is the imports that I am currently using.
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
from matplotlib import pyplot as plt
from matplotlib import style
from tkinter import *
from PIL import Image
import numpy as np
import serial
from serial import Serial
import sqlite3
import time
from datetime import datetime
from array import *
import cv2
from pathlib import Path
from itertools import count
The data that is meant to be used for the Y axis plotting is stored in an array of data. Each index in this array is to hold the last read value from the sensors, i=0 is sensor 1 and so on.
A=[0,0,0,0,0,0,0,0]
This is the definition of the subplot that I am trying to draw to. I think I am setting this up correctly, however I am not getting the expected result so likely not.
fig1 = plt.Figure(dpi=100, facecolor="#f0f0f0")
a = fig1.add_subplot(111)
a.patch.set_facecolor("#f0f0f0")
a.set_xlabel('time (Sec)')
a.set_ylabel('pressure(kPa)')
a.set_ylim(0,100)
a.set_xlim(0,30)
graph1 = FigureCanvasTkAgg(fig1, master=root)
graph1.get_tk_widget().place(x=10, y=220, width=210, height=280)
graph1.draw();
I am currently just trying to get one of the lines to draw first before handling the, seemingly, minor issue that is overlapping multiple lines. This is the function that I am trying to use in order to draw said line.
def graph_plotdata():
global A
global a
line1 = []
time = []
time.append(next(index))
line1.append(A[0])
a.cla()
a.plot(time, line1)
graph1.draw()
I have tried several iterations of this code in order attempt to solve this problem. The closest I have to getting it to work is in the current state in which something is happening however instead of keeping my min and max limits on the graph it completely reformats my plot and plots an "invisible" line.
Before starting:
After starting:
I am not overwhelmingly experienced when is comes to python libraries so bare with me.
I use a dictionary to store the various lines and line plots and then update the plots using set_data(xdata, ydata). I'm not sure how your datastream works, so mine just updates when I push the update button and generates a random reading. You'll obviously want to change those parts to match your data input.
fig, ax = plt.subplots(1, 1)
plt.subplots_adjust(bottom = 0.20)
num_sensors = 10
latest_reading = [0]*num_sensors
lines = {index: [0] for index in range(num_sensors)}
times = [0]
line_plots = {index: ax.plot(lines[index])[0] for index in range(num_sensors)}
btn_ax = plt.axes([0.475, 0.05, 0.10, 0.05])
def update(event):
latest_reading = np.random.randint(0, 10, num_sensors)
times.append(times[-1] + 1)
for index in range(num_sensors):
lines[index].append(latest_reading[index])
line_plots[index].set_data(times, lines[index])
# Adjust limits
max_time_window = 20
ax.set_xlim(max(0, max(times)-max_time_window), max(times))
ax.set_ylim(0, max(lines))
plt.draw()
btn = mpl.widgets.Button(btn_ax, 'Update')
btn.on_clicked(update)
Thank you for the response.
I figured out the issue, it had nothing to do with my matplotlib/tkinter implementation. I just totally missed that I had a scope inheritance issue. The lists of 'time' and 'line1' are not persistent in the entire scope and therefore being rewritten to empty lists every time the 'graph_plotdata()' function is called.
my solution is as follows:
timet = []
line1 = []
"""----------Graph Updater-----------"""
def graph_plotdata():
global B
global a
global graph1
global timet
global line1
timet.append(next(index))
line1.append(B[0])
a.clear()
a.plot(timet, line1)
a.patch.set_facecolor("#f0f0f0")
a.set_xlabel('time (Sec)')
a.set_ylabel('pressure(kPa)')
a.set_ylim(0,30)
a.set_xlim(0,30)
graph1.draw()
Hopefully this helps people in the future running into a similar issue!
I am basically using the code from this example: https://github.com/pyqtgraph/pyqtgraph/blob/develop/examples/MultiplePlotAxes.py but I added a legend to it using pw.addLegend(). My problem is that the legend only shows the items from axis 1, how would I get it to show the data from all axes?
This is what I get:
Every line that isn't shown in the legend is linked to one of the axes on the right.
When You are adding viewBox into main plot scene, You are bypassing legend adding mechanism. That's why only line p1.plot([1, 2, 4, 8, 16, 32], name='White plot') adds legend. Therefor You have to add legend manually. It's not a big deal, since we have all the objects in hand.
When adding legend, it returns legend object which You can use to add additional curves.
Here is modified code of MultiplePlotAxes.py script adding legend for all curves:
# -*- coding: utf-8 -*-
"""
Demonstrates a way to put multiple axes around a single plot.
(This will eventually become a built-in feature of PlotItem)
"""
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
pg.mkQApp()
pw = pg.PlotWidget()
# Add legend item
legend = pw.addLegend()
pw.show()
pw.setWindowTitle('pyqtgraph example: MultiplePlotAxes')
p1 = pw.plotItem
p1.setLabels(left='axis 1')
## create a new ViewBox, link the right axis to its coordinate system
p2 = pg.ViewBox()
p1.showAxis('right')
a = p1.scene().addItem(p2)
p1.getAxis('right').linkToView(p2)
p2.setXLink(p1)
p1.getAxis('right').setLabel('axis2', color='#0000ff')
## create third ViewBox.
## this time we need to create a new axis as well.
p3 = pg.ViewBox()
ax3 = pg.AxisItem('right')
p1.layout.addItem(ax3, 2, 3)
p1.scene().addItem(p3)
ax3.linkToView(p3)
p3.setXLink(p1)
ax3.setZValue(-10000)
ax3.setLabel('axis 3', color='#ff0000')
## Handle view resizing
def updateViews():
## view has resized; update auxiliary views to match
global p1, p2, p3
p2.setGeometry(p1.vb.sceneBoundingRect())
p3.setGeometry(p1.vb.sceneBoundingRect())
## need to re-update linked axes since this was called
## incorrectly while views had different shapes.
## (probably this should be handled in ViewBox.resizeEvent)
p2.linkedViewChanged(p1.vb, p2.XAxis)
p3.linkedViewChanged(p1.vb, p3.XAxis)
updateViews()
p1.vb.sigResized.connect(updateViews)
p1.plot([1, 2, 4, 8, 16, 32], name='White plot')
# Create second curve
curve2 = pg.PlotCurveItem([10, 20, 40, 80, 40, 20], pen='b', name='Blue plot')
# Add curve2 into plot legend
legend.addItem(curve2, curve2.name())
# Add plot
plot2 = p2.addItem(curve2)
# Same deal for another curves ...
curve3 = pg.PlotCurveItem([3200, 1600, 800, 400, 200, 100], pen='r', name='Red plot')
legend.addItem(curve3, curve3.name())
p3.addItem(curve3)
## 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_()
This is a follow up to this question.
The question is how to adapt the x-axis scaling of multiple pyqtgraphs when one of the graphs is rescaled. My desired result is that the ticks of all graphs align perfectly, so that you can directly compare the content of both graphs.
The given answer archives that task, of rescaling the graphs on changes but when the content of the graphs differs the y-Axis description tends to be of different size which somehow messes everything up.
For example in the image below I adjusted the bottom plot to roughly cover the 0-100 range. Strangely the upper plot is showing a bigger interval.
Any ideas how to align the ticks while rescaling?
Source code of the example:
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
from pyqtgraph.dockarea import *
import sys
def onSigRangeChanged(r):
w1.sigRangeChanged.disconnect(onSigRangeChanged)
w2.sigRangeChanged.disconnect(onSigRangeChanged)
if w1==r:
w2.setRange(xRange=r.getAxis('bottom').range)
elif w2 == r:
w1.setRange(xRange=r.getAxis('bottom').range)
w1.sigRangeChanged.connect(onSigRangeChanged)
w2.sigRangeChanged.connect(onSigRangeChanged)
app = QtGui.QApplication(sys.argv)
win = QtGui.QMainWindow()
area = DockArea()
win.setCentralWidget(area)
win.resize(1000,500)
win.setWindowTitle('pyqtgraph example: dockarea')
d1 = Dock("Dock1")
d2 = Dock("Dock2")
area.addDock(d1, 'bottom')
area.addDock(d2, 'bottom', d1)
w1 = pg.PlotWidget(title="Dock 1 plot")
w1.plot(np.random.normal(size=100)*1e12)
w1.plotItem.showGrid(x=True,y=True,alpha=1)
d1.addWidget(w1)
w2 = pg.PlotWidget(title="Dock 2 plot")
w2.plot(np.random.normal(size=100))
w2.plotItem.showGrid(x=True,y=True,alpha=1)
d2.addWidget(w2)
w1.sigRangeChanged.connect(onSigRangeChanged)
w2.sigRangeChanged.connect(onSigRangeChanged)
win.show()
sys.exit(app.exec_())
I just found the answer in this question.
Instead of connecting to the sigRangeChanged event we can directly link the axes scales by w2.setXLink(w1). Of course we have to remove the connections to sigRangeChanged or the scales will shoot to the sky. Here is the example code:
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
from pyqtgraph.dockarea import *
import sys
app = QtGui.QApplication(sys.argv)
win = QtGui.QMainWindow()
area = DockArea()
win.setCentralWidget(area)
win.resize(1000,500)
win.setWindowTitle('pyqtgraph example: dockarea')
d1 = Dock("Dock1")
d2 = Dock("Dock2")
area.addDock(d1, 'bottom')
area.addDock(d2, 'bottom', d1)
w1 = pg.PlotWidget(title="Dock 1 plot")
w1.plot(np.random.normal(size=100)*1e12)
w1.plotItem.showGrid(x=True,y=True,alpha=1)
d1.addWidget(w1)
w2 = pg.PlotWidget(title="Dock 2 plot")
w2.plot(np.random.normal(size=100))
w2.plotItem.showGrid(x=True,y=True,alpha=1)
w2.setXLink(w1)
d2.addWidget(w2)
win.show()
sys.exit(app.exec_())
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.
I have a strange problem, with matplotlib. If I run this program, I'm able to open and close several time the same figure.
import numpy
from pylab import figure, show
X = numpy.random.rand(100, 1000)
xs = numpy.mean(X, axis=1)
ys = numpy.std(X, axis=1)
fig = figure()
ax = fig.add_subplot(111)
ax.set_title('click on point to plot time series')
line, = ax.plot(xs, ys, 'o', picker=5) # 5 points tolerance
def onpick(event):
figi = figure()
ax = figi.add_subplot(111)
ax.plot([1,2,3,4])
figi.show()
fig.canvas.mpl_connect('pick_event', onpick)
show()
On the contrary, if I use the same code of onpick function into my custom widget it opens the figure only the first time, into the other events it enters into the functions but doesn't display the figure:
from PyQt4 import QtGui, QtCore
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt4 import NavigationToolbar2QT as NavigationToolbar
import time
STEP = 0.000152
class MplCanvas(FigureCanvas):
def __init__(self):
# initialization of the canvas
FigureCanvas.__init__(self, Figure())
self.queue = []
self.I_data = np.array([])
self.T_data = np.array([])
self.LvsT = self.figure.add_subplot(111)
self.LvsT.set_xlabel('Time, s')
self.LvsT.set_ylabel('PMT Voltage, V')
self.LvsT.set_title("Light vs Time")
self.LvsT.grid(True)
self.old_size = self.LvsT.bbox.width, self.LvsT.bbox.height
self.LvsT_background = self.copy_from_bbox(self.LvsT.bbox)
self.LvsT_plot, = self.LvsT.plot(self.T_data,self.I_data)
#self.LvsT_plot2, = self.LvsT.plot(self.T_data2,self.I_data2)
self.mpl_connect('axes_enter_event', self.enter_axes)
self.mpl_connect('button_press_event', self.onpick)
self.count = 0
self.draw()
def enter_axes(self,event):
print "dentro"
def onpick(self,event):
print "click"
print 'you pressed', event.canvas
a = np.arange(10)
print a
print self.count
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(a)
fig.show()
def Start_Plot(self,q,Vmin,Vmax,ScanRate,Cycles):
self.queue = q
self.LvsT.clear()
self.LvsT.set_xlim(0,abs(Vmin-Vmax)/ScanRate*Cycles)
self.LvsT.set_ylim(-3, 3)
self.LvsT.set_autoscale_on(False)
self.LvsT.clear()
self.draw()
self.T_data = np.array([])
self.I_data = np.array([])
# call the update method (to speed-up visualization)
self.timerEvent(None)
# start timer, trigger event every 1000 millisecs (=1sec)
self.timerLvsT = self.startTimer(3)
def timerEvent(self, evt):
current_size = self.LvsT.bbox.width, self.LvsT.bbox.height
if self.old_size != current_size:
self.old_size = current_size
self.LvsT.clear()
self.LvsT.grid()
self.draw()
self.LvsT_background = self.copy_from_bbox(self.LvsT.bbox)
self.restore_region(self.LvsT_background, bbox=self.LvsT.bbox)
result = self.queue.get()
if result == 'STOP':
self.LvsT.draw_artist(self.LvsT_plot)
self.killTimer(self.timerLvsT)
print "Plot finito LvsT"
else:
# append new data to the datasets
self.T_data = np.append(self.T_data,result[0:len(result)/2])
self.I_data = np.append(self.I_data,result[len(result)/2:len(result)])
self.LvsT_plot.set_data(self.T_data,self.I_data)#L_data
#self.LvsT_plot2.set_data(self.T_data2,self.I_data2)#L_data
self.LvsT.draw_artist(self.LvsT_plot)
self.blit(self.LvsT.bbox)
class LvsT_MplWidget(QtGui.QWidget):
def __init__(self, parent = None):
QtGui.QWidget.__init__(self, parent)
self.canvas = MplCanvas()
self.vbl = QtGui.QVBoxLayout()
self.vbl.addWidget(self.canvas)
self.setLayout(self.vbl)
This widget is needed for an animation plot and when the experiment is finished if I click on the plot it should appear a figure, that appears only the first time.
Do you have any clue?
Thank you very much.
At the start of your code, enable interactive mode via
plt.ion()
I have new information about this that a google search turned up
This is from the writer of matplotlib. This came from http://old.nabble.com/calling-show%28%29-twice-in-a-row-td24276907.html
Hi Ondrej,
I'm not sure where to find a good
explanation of that, but let me give
you some hints. It is intended to use
show only once per program. Namely
'show' should be the last line in your
script. If you want interactive
plotting you may consider interactive
mode (pyplot.ion-ioff) like in the
example below.
Furthermore for dynamic plotting all
animation demos might be useful.
Maybe you want to have also a look at
http://matplotlib.sourceforge.net/users/shell.html
.
best regards Matthias
So it seems it is an undocumented "feature" (bug?).
Edit: here is his code block:
from pylab import *
t = linspace(0.0, pi, 100)
x = cos(t)
y = sin(t)
ion() # turn on interactive mode
figure(0)
subplot(111, autoscale_on=False, xlim=(-1.2, 1.2), ylim=(-.2, 1.2))
point = plot([x[0]], [y[0]], marker='o', mfc='r', ms=3)
for j in arange(len(t)):
# reset x/y-data of point
setp(point[0], data=(x[j], y[j]))
draw() # redraw current figure
ioff() # turn off interactive mode
show()
So maybe by using draw() you can get what you want. I haven't tested this code, I'd like to know its behavior.
I had the same issue with show() only working the first time. Are you still on version 0.99.3 or thereabouts? I was able to resolve my problem recently, if you're still interested in changing the behaviour of show(), try this:
I noticed this paragraph titled multiple calls to show supported on the what's new part of the matplotlib download site.
A long standing request is to support multiple calls to show(). This has been difficult because it is hard to get consistent behavior across operating systems, user interface toolkits and versions. Eric Firing has done a lot of work on rationalizing show across backends, with the desired behavior to make show raise all newly created figures and block execution until they are closed. Repeated calls to show should raise newly created figures since the last call. Eric has done a lot of testing on the user interface toolkits and versions and platforms he has access to, but it is not possible to test them all, so please report problems to the mailing list and bug tracker.
This was 'what's new' for version 1.0.1, at time of writing the version in synaptic was still on 0.99.3. I was able to download and build from source v1.0.1. The additional packages I also required to satisfy dependencies were libfreetype6-dev tk-dev tk8.5-dev tcl8.5-dev python-gtk2-dev; your mileage may vary.
Now that i have matplotlib.__version__ == 1.0.1 , the following code works how I would expect:
from matplotlib import pyplot as p
from scipy import eye
p.imshow(eye(3))
p.show()
print 'a'
p.imshow(eye(6))
p.show()
print 'b'
p.imshow(eye(9))
p.show()
print 'c'
def onpick(self,event):
print "click"
print 'you pressed', event.canvas
...
ax.plot(a)
fig.show() # <--- this blocks the entire loop
Try:
def onpick(self,event):
print "click"
print 'you pressed', event.canvas
...
ax.plot(a)
self.draw()
self.update()
My workaround to this problem is to never call close.
I'm pretty sure you can control the transparency of a widget in PyQt. You might try controlling the visibility using Qt instead of matplotlib. I'm sure someone else who knows more about matplotlib can give a better answer than that though :D
You can create a figure instance by:
fig = plt.figure(0)
And draw your stuff by manipulate this fig.
You can use fig.show() for anytime to show your figure.