I checked out this version of pyqtgraph
git clone https://github.com/3rdcycle/pyqtgraph.git
git checkout origin/date-axis-item
pip uninstall pyqtgraph
python setup.py install
I then run this program. It appears to run fine, except that my x-axes of timestamps goes in and out of view without me doing anything. Not sure if this is a bug in this program or in DateAxisItem. Also, the milliseconds are always a multiple of 100. So for example, I see 00:00:00:900, 00:00:01:200, but never 00:00:00:042?
# -*- coding: utf-8 -*-
"""
Created on Thu May 14 21:09:44 2015
#author: idf
"""
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
from PySide.QtCore import QTime, QTimer
from collections import deque
t = QTime()
t.start()
data = deque(maxlen=20)
class TimeAxisItem(pg.DateAxisItem):
def __init__(self, *args, **kwargs):
super(TimeAxisItem, self).__init__(*args, **kwargs)
def tickStrings(self, values, scale, spacing):
return [QTime().addMSecs(value).toString('hh:mm:ss.zzz') for value in values]
app = QtGui.QApplication([])
win = pg.GraphicsWindow(title="Basic time-plotting examples")
win.resize(1000,600)
plot = win.addPlot(title='Timed data', axisItems={'bottom': TimeAxisItem(orientation='bottom')})
curve = plot.plot()
def update():
global plot, curve, data
data.append({'x': t.elapsed(), 'y': np.random.randint(0, 100)})
x = [item['x'] for item in data]
y = [item['y'] for item in data]
curve.setData(x=x, y=y)
tmr = QTimer()
tmr.timeout.connect(update)
tmr.start(800)
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
I'm not sure what happens with your DateAxisItem. As you know it's not yet merged into the main development branch of PyQtGraph. However, for your particular application, it might be easier to start from scratch and define your own TimeAxisItem? If you start from the following code, what functionality would be missing?
class TimeAxisItem(AxisItem):
def __init__(self, orientation, **kwargs):
super().__init__(orientation, **kwargs)
def tickStrings(self, values, scale, spacing):
return [self.get_tick(v, spacing) for v in values]
def get_tick(self, ts, spacing):
dt = datetime.datetime.fromtimestamp(ts)
# Here you can decide on the accuracy of the time data
# displayed depending on the spacing.
if spacing > 60:
return "%02d:%02d" % (dt.hour, dt.minute)
else:
return "%02d:%02d:%02d" % (dt.hour, dt.minute, dt.second)
Related
Here if I run the code with components() line commented out I get a window with a purple torus as expected but if I just call the components() on the torus_entity (uncomment the line) the torus disappears.
I can print the components from the components() but trying to usem them returns an error saying it was deleted, for example: deleted torus_mesh.
The material variable which can be reused for various models also throws error saying the material was deleted if you try to use it on any line beyond the components() call.
Qt documentation: https://doc.qt.io/qt-5/qt3dcore-qentity.html#components
Documentation says components returns a list of QComponent nothing about the deletion, I don't know how to use this properly?
I'd like to go through the components and check them and change their properties, for example change position and/or scale through QTransform component.
I could go down the QT version to 5.12.12 but I don't know how to do this for PyQt5 so it builds against this QT version.
I've tried it in PyQt6, also in Qt5 (c++, but different version then the PyQt5 uses) and it works just fine in both, this might be a bug in PyQt5/Qt5? I wonder if there's a workaround for this (maybe making adjustments in some PyQt5 files?)?
import sys
from PyQt5.Qt3DCore import QEntity, QTransform, QAspectEngine
from PyQt5.QtCore import QPropertyAnimation, QVariant, QUrl
from PyQt5.QtGui import QVector3D, QQuaternion, QGuiApplication, QColor
from PyQt5.Qt3DRender import QCamera, QCameraLens, QRenderAspect, QDirectionalLight, QSceneLoader
from PyQt5.Qt3DInput import QInputAspect
from PyQt5.Qt3DExtras import QForwardRenderer, QMetalRoughMaterial, QCylinderMesh, QSphereMesh, QTorusMesh, \
Qt3DWindow, QOrbitCameraController, QPlaneMesh
def create_scene():
root_entity = QEntity()
d_light_entity = QEntity(root_entity)
direction_light = QDirectionalLight()
direction_light.setColor(QColor(255, 254, 245))
direction_light.setIntensity(2)
d_light_entity.addComponent(direction_light)
material = QMetalRoughMaterial(root_entity)
material.setBaseColor(QColor(160, 30, 160))
material.setRoughness(.5)
# Torus
torus_entity = QEntity(root_entity)
torus_mesh = QTorusMesh()
torus_mesh.setRadius(5)
torus_mesh.setMinorRadius(1)
torus_mesh.setRings(100)
torus_mesh.setSlices(20)
torus_transform = QTransform()
torus_transform.setScale3D(QVector3D(1.5, 1, 0.5))
torus_entity.addComponent(torus_mesh)
torus_entity.addComponent(torus_transform)
torus_entity.addComponent(material)
#components = torus_entity.components()
return root_entity
def main(argv):
app = QGuiApplication(argv)
view = Qt3DWindow()
view.defaultFrameGraph().setClearColor(QColor(39, 39, 39))
scene = create_scene()
camera = view.camera()
camera.lens().setPerspectiveProjection(45.0, 15.0/9.0, .1, 1000.0)
camera.setPosition(QVector3D(0, 0, 40.0))
camera.setViewCenter(QVector3D(0, 0, 0))
view.setRootEntity(scene)
view.show()
return sys.exit(app.exec())
if __name__ == '__main__':
main(sys.argv)
I am currently working on a project of simple spectrum analyzer that cooperates with PlutoSDR, which is programmable radio.
I created a simple GUI with QTDesigner, then pyuic'ed it into python code (my version is 3.8)
Here's my code: (I've cut out some irrelevant parts)
# Form implementation generated from reading ui file 'signal_analyzer.ui'
#
# Created by: PyQt5 UI code generator 5.15.6
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
import pyqtgraph
from PyQt5 import QtCore, QtGui, QtWidgets
from pyqtgraph.Qt import QtCore, QtGui
from pyqtgraph.widgets import PlotWidget
import sys
import numpy as np
import argparse
import adi
import time
import threading
import pyqtgraph as pg
np.seterr(divide='ignore')
class FakePluto:
# this is the class that simulates the radio if it's not connected to my computer
"""
perform some operations
"""
# output vector of data
return samples[:self.rx_buffer_size]
class SpectrumAnalyzerThread(threading.Thread):
def __init__(self, sdr, *args, **kwargs):
super().__init__(*args, **kwargs)
"""
set some initial parmeters
"""
self.sdr = sdr
elf.start_freq = int(70e6)
self.end_freq = int(150e6)
self.settings_changed = True
self.result = None
#property
def steps_per_scan(self):
return (self.end_freq - self.start_freq) // self.step_size
def setStop(self):
self.stop = True
print("stop")
def setStart(self):
self.stop = False
print("start")
def setStopFreq(self, win):
self.stop_freq = int(win.lineEdit.text)
self.start_freq += (self.stop_freq - self.start_freq) % self.step_size
def setStartFreq(self, win):
self.start_freq = int(win.lineEdit_2.text)
self.stop_freq += (self.stop_freq - self.start_freq) % self.step_size
def reconfigure(self, start_freq, end_freq, step_size):
self.start_freq = int(start_freq)
self.end_freq = int(end_freq)
self.step_size = int(step_size)
if (self.end_freq - self.start_freq) % self.step_size != 0:
raise Exception('range is not a multiple of the step size')
self.settings_changed = True
def run(self):
while not self.stop:
print("2", end = "\r")
if self.settings_changed:
self._current_freq = self.start_freq
self.result = np.zeros(self.steps_per_scan * self.samples_per_step)
self.sdr.gain_control_mode_chan0 = 'manual'
self.sdr.rx_hardwaregain_chan0 = self.gain
self.sdr.sample_rate = self.step_size
self.sdr.rx_rf_bandwidth = self.step_size
self.sdr.rx_buffer_size = self.samples_per_step
self.settings_changed = False
else:
if self._current_freq + self.step_size >= self.end_freq:
self._current_freq = self.start_freq
else:
self._current_freq += self.step_size
self.sdr.rx_lo = self._current_freq + self.step_size//2
self.sdr.rx() # skip one sample
rx_samples = self.sdr.rx()
psd = np.abs(np.fft.fftshift(np.fft.fft(rx_samples))) ** 2
assert len(psd) == self.samples_per_step
start_idx = (self._current_freq - self.start_freq) // self.step_size * self.samples_per_step
self.result[start_idx:start_idx + self.samples_per_step] = psd
#print(self.result.tolist())
class Ui_MainWindow(QtWidgets.QMainWindow):
def setupUi(self, MainWindow, analyzer_thread):
"""
define UI elements - this part was generated from .UI file
"""
self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit.setGeometry(QtCore.QRect(800, 140, 231, 25))
self.lineEdit.setObjectName("lineEdit")
self.runButton = QtWidgets.QPushButton(self.centralwidget)
self.runButton.setGeometry(QtCore.QRect(790, 310, 161, 51))
self.runButton.setObjectName("runButton")
self.stopButton = QtWidgets.QPushButton(self.centralwidget)
self.stopButton.setGeometry(QtCore.QRect(790, 380, 161, 51))
self.stopButton.setObjectName("stopButton")
self.retranslateUi(MainWindow)
# connect gui elements with functions
self.stopButton.clicked.connect(analyzer_thread.setStop) # type: ignore
self.runButton.clicked.connect(analyzer_thread.setStart) # type: ignore
self.lineEdit.textChanged[str].connect(analyzer_thread.setStartFreq)
#self.lineEdit_2.textChanged[str].connect(analyzer_thread.setStopFreq)
#self.sweepButton.clicked.connect(self.widget.singleSweep) # type: ignore
#self.lineEdit_3.modified.connect()
QtCore.QMetaObject.connectSlotsByName(self)
# function below is just for testing
def tomatoes(self, analyzer_thread):
print(analyzer_thread.start_freq)
def retranslateUi(self, MainWindow):
"""
UI retranslation
"""
def main():
# this part checks input script arguments, currently I'm using --simulate
parser = argparse.ArgumentParser(description='SDR Spectrum Analyzer for PlutoSDR')
parser.add_argument('pluto_uri', nargs='?', default=adi.Pluto._uri_auto,
help=f'URI of the PlutoSDR device (default: "{adi.Pluto._uri_auto}")')
parser.add_argument('--simulate', action='store_true',
help='Simulate by generating random noise instead of querying the Pluto device')
args = parser.parse_args()
if args.simulate:
sdr = FakePluto()
else:
sdr = adi.Pluto(args.pluto_uri)
# create and start the thread
analyzer_thread = SpectrumAnalyzerThread(sdr)
analyzer_thread.start()
app = QtGui.QApplication(sys.argv)
win = Ui_MainWindow()
win.show()
win.setupUi(win, analyzer_thread)
# this is the function that refreshes the plotWindow in GUI
def update():
if analyzer_thread.result is not None and not analyzer_thread.settings_changed:
print("1", end = "\r")
psd = analyzer_thread.result
num_bins = analyzer_thread.samples_per_step
if len(psd) % num_bins != 0:
raise Exception('num_bins is not a multiple of sample count')
binned = psd.reshape((-1, len(psd) // num_bins)).sum(axis=1)
binned_dB = 10 * np.log10(binned)
f = np.linspace(analyzer_thread.start_freq, analyzer_thread.end_freq, len(binned_dB))
#spectrum_trace.setData(f, binned_dB)
win.graphicsView.clear()
win.graphicsView.plot(f, binned_dB)
dispMax = str(np.amax(binned_dB))
win.currentMaxAmp.display(dispMax[0:5])
# update function is connected to the timer
timer = QtCore.QTimer()
timer.timeout.connect(update)
timer.start(1)
win.tomatoes(analyzer_thread)
app.exec()
analyzer_thread.stop = True
analyzer_thread.join()
if __name__ == '__main__':
main()
Now, I have two independent problems.
First
When I press "Stop" on the GUI, which sets analyzer_thread stop value to 'true', this works correctly (i. e. run function from analyzer_thread stops executing), though the update function is still running - the plot keeps refreshing but with the same values. Yet when I hit "start", the analyzer_thread doesn't start over. I have no idea what causes that.
Second
When I try to change stop frequency value, which should call analyzer_thread.setStopFreq I get this message:
Traceback (most recent call last):
File "main_window_ui.py", line 77, in setStopFreq
self.stop_freq = int(win.lineEdit.text)
AttributeError: 'str' object has no attribute 'lineEdit'
which I think is my mistake while connecting GUI objects to functions, but I couldn't figure out how to fix it. It seems there's a problem with function's arguments, yet when I call tomatoes function from main, it works despite having the same argument (analyzer_thread).
I know that my code is messy and sorry for that. The processing part was done by another person, my role is to make it work with GUI. I am attaching the main window view:
https://i.stack.imgur.com/vUZjj.png
I'll be thankful for any help :)
Your code wont resume running, because when you set self.stop to True once, whole ,,run'' loop terminates and self.run function ends its job, terminating thread. You should rather try something like:
while True:
if not self.stop:
#do your code
else:
time.sleep(0.001) #
And add other possibility to terminate thread completly.
Your functions should rather be named suspend/resume, not stop and start.
I am trying to make a print button for a GUI I am making. Right now when I click the print button it will store all the data to one cell for each variable I am trying to print. Right now I am trying to print the time, rpms, torque, and horsepower. It will give me each of those in their own separate cell, but it will not start a new row. With in this cell the format is: [1,2,3,4,5,...]. The brackets are in the cells as well. I would like for them to be separated into rows that way the user of the GUI can take the data for post processing. What I have mostly found on this matter is people are trying to get words into different cells and I know that characters act differently than numbers, pr that people want the values to be in a single cell, not the opposite like in my case.
What I have right now is that the print button will print a file with a unique time stamp. That way the users can keep this data for safe keeping.
Thank You in advance!
"""
SCSU DYNO GUI PROGRAM
created 11/10/2017
"""
import sys
import time
from time import gmtime, localtime, strftime
import csv
import numpy as np
import warnings
import serial
import serial.tools.list_ports
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QThread,QTimer, pyqtSignal
from PyQt5.QtWidgets import QMessageBox,QWidget, QApplication,QHBoxLayout
from PyQt5.QtGui import QColor
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
import random
from DynoTest1 import Ui_DynoTest1
__author__ = 'Matt Munn'
pg.setConfigOption('background', None)
pg.setConfigOption('foreground', 'k')
class GetData(QThread):
dataChanged = pyqtSignal(float, float, float, float, float, float, float, float)
#Distance = 0.5 #This is dependent on the lever arm.
def __init__(self, parent=None):
QThread.__init__(self, parent)
arduino_ports = [ # automatically searches for an Arduino and selects the port it's on
p.device
for p in serial.tools.list_ports.comports()
if 'Arduino' in p.description
]
if not arduino_ports:
raise IOError("No Arduino found - is it plugged in? If so, restart computer.")
if len(arduino_ports) > 1:
warnings.warn('Multiple Arduinos found - using the first')
self.Arduino = serial.Serial(arduino_ports[0], 9600, timeout=1)
def __del__(self): # part of the standard format of a QThread
self.wait()
def run(self): # also a required QThread function, the working part
self.Arduino.close()
self.Arduino.open()
self.Arduino.flush()
self.Arduino.reset_input_buffer()
start_time = time.time()
Distance = 1 #This is dependent on the lever arm.
Max_RPM = 0
Max_Horsepower = 0
Max_Torque = 0
#This is what does the work to get the data from the arduino and then converts it to the other needed values.
while True:
while self.Arduino.inWaiting() == 0:
pass
try:
data = self.Arduino.readline()
dataarray = data.decode().rstrip().split(',')
self.Arduino.reset_input_buffer()
Force = round(float(dataarray[0]), 3)
RPM = round(float(dataarray[1]), 0)
if Max_RPM < RPM:
Max_RPM = RPM
Torque = round(Force * Distance, 2)
if Max_Torque < Torque:
Max_Torque = Torque
Horsepower = round(Torque * RPM / 5252, 2)
if Max_Horsepower < Horsepower:
Max_Horsepower = Horsepower
Run_Time = round(time.time() - start_time, 1)
print(Force, 'Grams', ",", RPM, 'RPMs', ",", Torque, "ft-lbs", ",", Horsepower, "hp", Run_Time,
"Time Elasped")
self.dataChanged.emit(Force, RPM, Max_RPM, Torque, Max_Torque, Horsepower, Max_Horsepower, Run_Time)
except (KeyboardInterrupt, SystemExit, IndexError, ValueError):
pass
class GUI(QWidget, Ui_DynoTest1):
def __init__(self, parent=None, border = None):
# This is what is used to make the graph.
QWidget.__init__(self, parent)
self.setupUi(self)
self.thread = GetData(self)
self.thread.dataChanged.connect(self.onDataChanged)
self.thread.start()
self.rpm = []
self.torque = []
self.horse_power = []
self.time = []
self.counter = 0
layout = QHBoxLayout()
self.plot = pg.PlotWidget()
layout.addWidget(self.plot)
self.plot.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
self.graphicsView.setLayout(layout)
self.p1 = self.plot.plotItem
self.p1.setLabels(left='Torque (ft-lbs)', bottom= 'Time (sec)')
self.TorqueCurve = self.p1.plot()
self.TorqueCurve.setPen(pg.mkPen(QColor(0,0,0), width=2.5))
self.p2 = pg.ViewBox()
self.HorsePowerCurve = pg.PlotCurveItem()
self.HorsePowerCurve.setPen(pg.mkPen(QColor(0, 0, 255), width=2.5))
self.p2.addItem(self.HorsePowerCurve)
self.p1.scene().addItem(self.p2)
self.p1.showAxis('right')
self.p1.getAxis('right').setLabel('HorsePower', color='#0000ff')
self.p1.getAxis('right').linkToView(self.p2)
self.p1.vb.sigResized.connect(self.updateViews)
#This is where the buttons will be set up at.
self.pushButton_4.clicked.connect(self.Print_Out)
def Print_Out(self):
#This gives a unique time stamp for each file made.
outputFileName = "DynoData_#.csv"
outputFileName = outputFileName.replace("#", strftime("%Y-%m-%d_%H %M %S", localtime()))
with open(outputFileName, 'w',newline='') as outfile:
outfileWrite = csv.writer(outfile, delimiter=',')
#test = self.torque
#test2 = self.torque , self.rpm
outfileWrite.writerow([self.torque,self.horse_power,self.rpm,self.time])
def updateViews(self):
self.p2.setGeometry(self.p1.vb.sceneBoundingRect())
self.p2.linkedViewChanged(self.p1.vb, self.p2.XAxis)
def onDataChanged(self, Force, RPM, Max_RPM, Torque, Max_Torque, Horsepower, Max_Horsepower, Run_Time):
#These tell the program to display the values to the LCDs
self.lcdNumber.display(Max_RPM)
self.lcdNumber_2.display(Max_Torque)
self.lcdNumber_3.display(Max_Horsepower)
self.lcdNumber_4.display(RPM)
self.lcdNumber_5.display(Torque)
self.lcdNumber_6.display(Horsepower)
self.lcdNumber_7.display(Run_Time)
if self.counter < 50:
self.torque.append(Torque)
self.horse_power.append(Horsepower)
self.time.append(Run_Time)
self.rpm.append(RPM)
else:
self.torque = self.torque[1:] + [Torque]
self.horse_power = self.horse_power[1:] + [HorsePower]
self.time = self.time[1:] + [Run_Time]
self.rpm = self.rpm[1:] + [RPM]
self.HorsePowerCurve.setData(self.time, self.horse_power)
self.TorqueCurve.setData(np.array(self.time), self.torque)
self.updateViews()
#This is part of a standard closing script
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
Dyno = GUI()
Dyno.show()
sys.exit(app.exec_())
Since your data is in lists, you need to form rows of each instance of data items. One way is to transpose the lists with zip:
>>> hp = [1,2,3]
>>> rpm = [4,5,6]
>>> time = [7,8,9]
>>> for row in zip(hp,rpm,time):
... print(row)
...
(1, 4, 7)
(2, 5, 8)
(3, 6, 9)
Note how the first item in each list is printed, then the second, etc. You can use this to get your data into rows. Here's a standalone example:
import csv
class Test:
def __init__(self):
self.torque = [10,20,30]
self.horse_power = [100,200,300]
self.rpm = [1000,2000,3000]
self.time = [1,2,3]
def print(self):
with open('test.csv','w',newline='') as outfile:
outfileWrite = csv.writer(outfile)
outfileWrite.writerow('Torque HP RPM Time'.split())
outfileWrite.writerows(zip(self.torque,self.horse_power,self.rpm,self.time))
t = Test()
t.print()
Note writerows (plural). That expects a list of lists. writerow (singular) expects a single list and is used for a header. Output file is:
Torque,HP,RPM,Time
10,100,1000,1
20,200,2000,2
30,300,3000,3
First, calling a definition print in python is not recommended at all. Unless you do want to over-ride python 'print'. 'print_to_csv' for example.
As for the csv, you need to group your data into lists of [torque, horsepower, rpm, time] so your it will look like something like this,
data_lists = [
[torque(0), horsepower(0), rpm(0), time(0)],
[torque(1), horsepower(1), rpm(1), time(1)],
...
[torque(n), horsepower(n), rpm(n), time(n)]
]
then you will loop through this data list and print your rows one by one like so,
for d in data_datalist:
outfileWrite.writerow([d[0],d[1],d[2],d[3])
of course, many ways to structure your data, thats just an example.
Hope this helps.
I am having a problem getting matplotlib to work well with interactive plotting... what I see is that after displaying a few frames of my simulated data matplotlib hangs-and doesn't display any more.
Basically I've been playing around a bit with science simulations - and would like to be able to plot my results as they are being made - rather than at the end - using pylab.show().
I found a cookbook example from a while back that seems to do what I would want - in simple terms (although obv. the data is different). The cookbook is here...http://www.scipy.org/Cookbook/Matplotlib/Animations#head-2f6224cc0c133b6e35c95f4b74b1b6fc7d3edca4
I have searched around a little and I know that some people had these problems before - Matplotlib animation either freezes after a few frames or just doesn't work but it seems at the time there were no good solutions. I was wondering if someone has since found a good solution here.
I have tried a few 'backends' on matplotlib....TkAgg seems to work for a few frames.... qt4agg doesn't show the frames. I haven't yet got GTK to install properly.
I am running the most recent pythonxy(2.7.3).
Anyone have any advice?
import matplotlib
matplotlib.use('TkAgg') # 'Normal' Interactive backend. - works for several frames
#matplotlib.use('qt4agg') # 'QT' Interactive backend. - doesn't seem to work at all
#matplotlib.use('GTKAgg') # 'GTK' backend - can't seem to get this to work.... -
import matplotlib.pyplot as plt
import time
import numpy as np
plt.ion()
tstart = time.time() # for profiling
x = np.arange(0,2*np.pi,0.01) # x-array
line, = plt.plot(x,np.sin(x))
#plt.ioff()
for i in np.arange(1,200):
line.set_ydata(np.sin(x+i/10.0)) # update the data
line.axes.set_title('frame number {0}'.format(i))
plt.draw() # redraw the canvas
print 'FPS:' , 200/(time.time()-tstart)
EDIT:
edited code - to get rid of some style issues brought up.
Ok... So I have mangled together something that may sort of work for me....
Basically it is something like a watered down gui - but i'm hoping that it is a class i can import and basically forget about the details of (here's hoping).
I should say though - this is my first attempt at threading OR guis in python - so this code comes with a health warning.
** I'm not going to mark the question as answered though - because i'm sure someone more experienced will have a better solution.
'''
JP
Attempt to get multiple updating of matplotlibs working.
Uses WX to create an 'almost' gui with a mpl in the middle of it.
Data can be queued to this object - or you can directly plot to it.
Probably will have some limitations atm
- only really thinking about 2d plots for now -
but presumably can work around this for other implimentations.
- the working code seems to need to be put into another thread.
Tried to put the wx mainloop into another thread,
but it seemed unhappy. :(
Classes of Interest :
GraphData - A silly class that holds data to be plotted.
PlotFigure - Class of wx frame type.
Holds a mpl figure in it + queue to queue data to.
The frame will plot the data when it refreshes it's canvas
ThreadSimulation - This is not to do with the plotting
it is a test program.
Modified version of:
Copyright (C) 2003-2005 Jeremy O'Donoghue and others
License: This work is licensed under the PSF. A copy should be included
with this source code, and is also available at
http://www.python.org/psf/license.html
'''
import threading
import collections
import time
import numpy as np
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
from matplotlib.backends.backend_wx import NavigationToolbar2Wx
from matplotlib.figure import Figure
import wx
class GraphData(object):
'''
A silly class that holds data to be plotted.
'''
def __init__(self, xdatainit, ydatainit):
self.xdata = xdatainit
self.ydata = ydatainit
class PlotFigure(wx.Frame):
def __init__(self ):
'''
Initialises the frame.
'''
wx.Frame.__init__(self, None, -1, "Test embedded wxFigure")
self.timerid = wx.NewId()
self.fig = Figure((5,4), 75)
self.canvas = FigureCanvasWxAgg(self, -1, self.fig)
self.toolbar = NavigationToolbar2Wx(self.canvas)
self.toolbar.Realize()
# On Windows, default frame size behaviour is incorrect
# you don't need this under Linux
tw, th = self.toolbar.GetSizeTuple()
fw, fh = self.canvas.GetSizeTuple()
self.toolbar.SetSize(wx.Size(fw, th))
# Now put all into a sizer
sizer = wx.BoxSizer(wx.VERTICAL)
# This way of adding to sizer allows resizing
sizer.Add(self.canvas, 1, wx.LEFT|wx.TOP|wx.GROW)
# Best to allow the toolbar to resize!
sizer.Add(self.toolbar, 0, wx.GROW)
self.SetSizer(sizer)
self.Fit()
wx.EVT_TIMER(self, self.timerid, self.onTimer)
self.dataqueue = collections.deque()
# Add an axes and a line to the figure.
self.axes = self.fig.add_subplot(111)
self.line, = self.axes.plot([],[])
def GetToolBar(self):
'''
returns default toolbar.
'''
return self.toolbar
def onTimer(self, evt):
'''
Every timer period this is called.
Want to redraw the canvas.
'''
#print "onTimer"
if len(self.dataqueue) > 0 :
data = self.dataqueue.pop()
x = data.xdata
y = data.ydata
xmax = max(x)
xmin = min(x)
ymin = round(min(y), 0) - 1
ymax = round(max(y), 0) + 1
self.axes.set_xbound(lower=xmin, upper=xmax)
self.axes.set_ybound(lower=ymin, upper=ymax)
self.line.set_xdata(x)
self.line.set_ydata(y)
# Redraws the canvas - does this even if the data isn't updated...
self.canvas.draw()
def onEraseBackground(self, evt):
'''
this is supposed to prevent redraw flicker on some X servers...
'''
pass
class ThreadSimulation(threading.Thread):
'''
Simulation Thread - produces data to be displayed in the other thread.
'''
def __init__(self, nsimloops, datastep, pltframe, slowloop = 0):
threading.Thread.__init__(self)
self.nsimloops = nsimloops
self.datastep = datastep
self.pltframe = pltframe
self.slowloop=slowloop
def run(self):
'''
This is the simulation function.
'''
nsimloops = self.nsimloops
datastep = self.datastep
pltframe = self.pltframe
print 'Sim Thread: Starting.'
tstart = time.time() # for profiling
# Define Data to share between threads.
x = np.arange(0,2*np.pi,datastep) # x-array
y = np.sin(x )
# Queues up the data and removes previous versions.
pltframe.dataqueue.append(GraphData(x,y))
for i in range(len(pltframe.dataqueue)-1):
pltframe.dataqueue.popleft()
pltframe.dataqueue
for i in np.arange(1, nsimloops):
x = x + datastep
y = np.sin(x)
# Queues up the data and removes previous versions.
pltframe.dataqueue.append(GraphData(x,y))
for i in range(len(pltframe.dataqueue)-1):
pltframe.dataqueue.popleft()
#pltframe.dataqueue
if self.slowloop > 0 :
time.sleep(self.slowloop)
tstop= time.time()
print 'Sim Thread: Complete.'
print 'Av Loop Time:' , (tstop-tstart)/ nsimloops
if __name__ == '__main__':
# Create the wx application.
app = wx.PySimpleApp()
# Create a frame with a plot inside it.
pltframe = PlotFigure()
pltframe1 = PlotFigure()
# Initialise the timer - wxPython requires this to be connected to
# the receiving event handler
t = wx.Timer(pltframe, pltframe.timerid)
t.Start(100)
pltframe.Show()
pltframe1.Show()
npoints = 100
nsimloops = 20000
datastep = 2 * np.pi/ npoints
slowloop = .1
#Define and start application thread
thrd = ThreadSimulation(nsimloops, datastep, pltframe,slowloop)
thrd.setDaemon(True)
thrd.start()
pltframe1.axes.plot(np.random.rand(10),np.random.rand(10))
app.MainLoop()
I have a module to be used in iPython.
I'd like a user to enter everything needed to make a plot- x, y, label, linewidth, etc.
So the user might do something like this:
In[1] import this_script
In[2] x=range(0,10)
In[3] y=x
In[4] magically_exposed_function plot(x,y,'r+', linewidth=2)
This means that my function gets the string plot(x,y,'r+', linewidth=2). This can be parsed and
the values of x and y found in the iPython namespace using ip.user_ns, but I'm still stuck on
what to do with 'r+' and linewidth=2. Ideally I'd like to be able to:
a) import the entire iPython namespace so that I have the values of x and y available and
b) throw the entire string into plot()
As for b), having something like:
plot_string = x, y, 'r+', linewidth = 2
plot(plot_string)
would be ideal, but this does not work as shown above.
Is this possible to do either of these things? Is there a more graceful solution?
Could the user perhaps do plot(x,y), and my code could grab ahold of that plot and edit it?
Any advice on how to handle this situation would be greatly appreciated :)
Thanks!
--Erin
[EDIT] A demo of what I'd like to be able to do:
import matplotlib
import wx
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigCanv
from matplotlib.figure import Figure
import IPython.ipapi
ip = IPython.ipapi.get()
import sys
class WrapperExample(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, None, -1)
self.figure = Figure()
self.axes = self.figure.add_subplot(111)
self.axes.plot(*args, **kwargs)
self.canvas = FigCanv(self, -1, self.figure)
def run_me(*args, **kwargs):
""" Plot graph from iPython
Example:
In[1] import script
In[2] x=range(0,10)
In[3] y=x
In[4] run_me x y
"""
app = wx.PySimpleApp()
wrap = WrapperExample(*args, **kwargs)
wrap.Show()
app.MainLoop()
ip.expose_magic("run_me", run_me)
[EDIT] The following is how I ended up using the wrapper suggested below:
import wx
import matplotlib
from pylab import *
import IPython.ipapi
ip = IPython.ipapi.get()
class MainCanvas(wx.Frame):
def __init__(self, *args):
self.figure = plt.figure()
self.axes = self.figure.add_subplot(111)
self.axes.plot(*args)
show()
def run_this_plot(self, arg_s=''):
""" Run
Examples
In [1]: import demo
In [2]: rtp x y <z>
Where x, y, and z are numbers of any type
"""
args = []
for arg in arg_s.split():
try:
args.append(self.shell.user_ns[arg])
except KeyError:
raise ValueError("Invalid argument: %r" % arg)
mc = MainCanvas(*args)
# Activate the extension
ip.expose_magic("rtp", run_this_plot)
Parsing the actual string is better left to python. Maybe you want to create a wrapper:
real_plot = plot
def my_plot(*args, **kwargs):
x, y = args[0], args[1]
...your extra code here...
real_plot(*args, **kwargs)
plot = my_plot