import os
from qtpy.QtGui import QColor
from qtpy.QtWidgets import *
from qtpy.QtCore import Property, Signal, Slot, QTime, QTimer, Qt
from collections import deque
import pyqtgraph as pg
import numpy as np
from qtpyvcp import hal
from qtpyvcp.widgets import HALWidget, VCPWidget
IN_DESIGNER = os.getenv('DESIGNER', False)
class TimeAxisItem(pg.AxisItem):
"""Internal timestamp for x-axis"""
def __init__(self, *args, **kwargs):
super(TimeAxisItem, self).__init__(*args, **kwargs)
def tickStrings(self, values, scale, spacing):
"""Function overloading the weak default version to provide timestamp"""
return [QTime().currentTime().addMSecs(int(value)).toString('mm:ss') for value in values]
[docs]class HalPlot(QWidget, HALWidget, VCPWidget):
"""HAL Plot
Plots HAL pin values over time, similar to HAL scope.
Up to four HAL pin values can be plotted.
.. table:: Generated HAL Pins
================================== =========== =========
HAL Pin Name Type Direction
================================== =========== =========
qtpyvcp.pinBaseName.seriesXname.in float in
================================== =========== =========
both pinBaseName and seriesXname can be set in the property editor in QtDesigner.
"""
def __init__(self, parent=None):
super(HalPlot, self).__init__(parent)
# HAL sampling frequency parameters
self._frequency = 1 # Hz
self._timeWindow = 600 # seconds
# Internal timestamp for x-axis - data values are ms from when "timestamp" was started
self.timestamp = QTime()
self.timestamp.start()
self._legend = False
self._yAxisLabel = 'y label'
self._yAxisUnits = 'y units'
self._minY = 0
self._maxY = 1
self._s1enable = True
self._s1name = "Series 1"
self._s1colour = QColor('red')
self._s1width = 1
self._s1style = Qt.SolidLine
self._s1_pin = None
self._s2enable = False
self._s2name = "Series 2"
self._s2colour = QColor('blue')
self._s2width = 1
self._s2style = Qt.SolidLine
self._s2_pin = None
self._s3enable = False
self._s3name = "Series 3"
self._s3colour = QColor('green')
self._s3width = 1
self._s3style = Qt.SolidLine
self._s3_pin = None
self._s4enable = False
self._s4name = "Series 4"
self._s4colour = QColor('yellow')
self._s4width = 1
self._s4style = Qt.SolidLine
self._s4_pin = None
# PyQtGraph stuff
self.graph = pg.GraphicsLayoutWidget()
self.yAxis = pg.AxisItem(orientation='left')
self.yAxis.setLabel(self._yAxisLabel, units=self._yAxisUnits)
self.yAxis.setGrid(125)
self.plot = self.graph.addPlot(axisItems={'bottom': TimeAxisItem(orientation='bottom'), 'left': self.yAxis})
self.plot.setYRange(self._minY, self._maxY, padding=0.0)
self.legend = self.plot.addLegend()
self.p1 = pg.PlotCurveItem(name=self._s1name)
self.p2 = pg.PlotCurveItem(name=self._s2name)
self.p3 = pg.PlotCurveItem(name=self._s3name)
self.p4 = pg.PlotCurveItem(name=self._s4name)
self.setSeries()
self.setData()
self.Vlayout = QVBoxLayout(self)
self.Vlayout.addWidget(self.graph)
# HAL stuff
self._typ = "float"
self._fmt = "%s"
if IN_DESIGNER:
return
# QTimer
self.updatetimer = QTimer(self)
self.updatetimer.timeout.connect(self.updateplot)
self.updatetimer.start(self._refreshRate)
def setSeries(self):
# first remove the legend as it does not update correnctly
try:
self.legend.scene().removeItem(self.legend)
except:
pass
# remove all plot items
self.plot.clear()
# add the legend and plot itmes
if self._legend:
self.legend = self.plot.addLegend()
if self._s1enable:
self.p1 = pg.PlotCurveItem(name=self._s1name)
self.plot.addItem(self.p1)
self.p1.setPen(QColor(self._s1colour), width=self._s1width, style=self._s1style)
if self._s2enable:
self.p2 = pg.PlotCurveItem(name=self._s2name)
self.plot.addItem(self.p2)
self.p2.setPen(QColor(self._s2colour), width=self._s2width, style=self._s2style)
if self._s3enable:
self.p3 = pg.PlotCurveItem(name=self._s3name)
self.plot.addItem(self.p3)
self.p3.setPen(QColor(self._s3colour), width=self._s3width, style=self._s3style)
if self._s4enable:
self.p4 = pg.PlotCurveItem(name=self._s4name)
self.plot.addItem(self.p4)
self.p4.setPen(QColor(self._s4colour), width=self._s4width, style=self._s4style)
def setData(self):
# Data stuff
self._period = 1.0/self._frequency
self._refreshRate = int(self._period * 1000) # sample period in milliseconds
self._timeWindowMS = self._timeWindow * 1000 # time window in milliseconds
self._bufsize = int(self._timeWindowMS / self._refreshRate)
# Data containers: collections.deque is list-like container with fast appends and pops on either end
self.x = np.linspace(-self.timeWindow, 0.0, self._bufsize)
self.now = self.timestamp.elapsed()
self.x_data = deque(np.linspace(self.now-self._timeWindowMS, self.now, self._bufsize),self._bufsize)
self.s1 = np.zeros(self._bufsize, dtype=float)
self.s1_data = deque([0.0] * self._bufsize, self._bufsize)
self.s2 = np.zeros(self._bufsize, dtype=float)
self.s2_data = deque([0.0] * self._bufsize, self._bufsize)
self.s3 = np.zeros(self._bufsize, dtype=float)
self.s3_data = deque([0.0] * self._bufsize, self._bufsize)
self.s4 = np.zeros(self._bufsize, dtype=float)
self.s4_data = deque([0.0] * self._bufsize, self._bufsize)
def updateplot(self):
self.x_data.append(self.timestamp.elapsed())
self.x[:] = self.x_data
if self._s1enable:
self.s1_data.append(self._s1_pin.value)
self.s1[:] = self.s1_data
self.p1.setData(self.x, self.s1)
if self._s2enable:
self.s2_data.append(self._s2_pin.value)
self.s2[:] = self.s2_data
self.p2.setData(self.x, self.s2)
if self._s3enable:
self.s3_data.append(self._s3_pin.value)
self.s3[:] = self.s3_data
self.p3.setData(self.x, self.s3)
if self._s4enable:
self.s4_data.append(self._s4_pin.value)
self.s4[:] = self.s4_data
self.p4.setData(self.x, self.s4)
def setyAxis(self):
self.yAxis.setLabel(self._yAxisLabel, units=self._yAxisUnits)
def setYRange(self):
self.plot.setYRange(self._minY, self._maxY, padding = 0.0)
@Property(int)
def frequency(self):
return self._frequency
@frequency.setter
def frequency(self, frequency):
self._frequency = frequency
return self.setData()
@Property(int)
def timeWindow(self):
return self._timeWindow
@timeWindow.setter
def timeWindow(self, timeWindow):
self._timeWindow = timeWindow
return self.setData()
@Property(str)
def yAxisLabel(self):
return self._yAxisLabel
@yAxisLabel.setter
def yAxisLabel(self, yAxisLabel):
self._yAxisLabel = yAxisLabel
return self.setyAxis()
@Property(str)
def yAxisUnits(self):
return self._yAxisUnits
@yAxisUnits.setter
def yAxisUnits(self, yAxisUnits):
self._yAxisUnits = yAxisUnits
return self.setyAxis()
@Property(float)
def minYRange(self):
return self._minY
@minYRange.setter
def minYRange(self, minY):
self._minY = minY
return self.setYRange()
@Property(float)
def maxYRange(self):
return self._maxY
@maxYRange.setter
def maxYRange(self, maxY):
self._maxY = maxY
return self.setYRange()
# Legend propterties
@Property(bool)
def legendenable(self):
return self._legend
@legendenable.setter
def legendenable(self, legendenable):
self._legend = legendenable
self.setSeries()
# Series 1 properties
@Property(bool)
def series1enable(self):
return self._s1enable
@series1enable.setter
def series1enable(self, series1enable):
self._s1enable = series1enable
self.setSeries()
@Property(str)
def series1name(self):
return self._s1name
@series1name.setter
def series1name(self, series1name):
self._s1name = series1name
self.setSeries()
@Property(QColor)
def series1colour(self):
return self._s1colour
@series1colour.setter
def series1colour(self, series1colour):
self._s1colour = series1colour
self.setSeries()
@Property(int)
def series1width(self):
return self._s1width
@series1width.setter
def series1width(self, series1width):
self._s1width = series1width
self.setSeries()
@Property(Qt.PenStyle)
def series1style(self):
return self._s1style
@series1style.setter
def series1style(self, series1style):
self._s1style = series1style
self.setSeries()
# Series 2 properties
@Property(bool)
def series2enable(self):
return self._s2enable
@series2enable.setter
def series2enable(self, series2enable):
self._s2enable = series2enable
self.setSeries()
@Property(str)
def series2name(self):
return self._s2name
@series2name.setter
def series2name(self, series2name):
self._s2name = series2name
self.setSeries()
@Property(QColor)
def series2colour(self):
return self._s2colour
@series2colour.setter
def series2colour(self, series2colour):
self._s2colour = series2colour
self.setSeries()
@Property(int)
def series2width(self):
return self._s2width
@series2width.setter
def series2width(self, series2width):
self._s2width = series2width
self.setSeries()
@Property(Qt.PenStyle)
def series2style(self):
return self._s2style
@series2style.setter
def series2style(self, series2style):
self._s2style = series2style
self.setSeries()
# Series 3 properties
@Property(bool)
def series3enable(self):
return self._s3enable
@series3enable.setter
def series3enable(self, series3enable):
self._s3enable = series3enable
self.setSeries()
@Property(str)
def series3name(self):
return self._s3name
@series3name.setter
def series3name(self, series3name):
self._s3name = series3name
self.setSeries()
@Property(QColor)
def series3colour(self):
return self._s3colour
@series3colour.setter
def series3colour(self, series3colour):
self._s3colour = series3colour
self.setSeries()
@Property(int)
def series3width(self):
return self._s3width
@series3width.setter
def series3width(self, series3width):
self._s3width = series3width
self.setSeries()
@Property(Qt.PenStyle)
def series3style(self):
return self._s3style
@series3style.setter
def series3style(self, series3style):
self._s3style = series3style
self.setSeries()
# Series 4 properties
@Property(bool)
def series4enable(self):
return self._s4enable
@series4enable.setter
def series4enable(self, series4enable):
self._s4enable = series4enable
self.setSeries()
@Property(str)
def series4name(self):
return self._s4name
@series4name.setter
def series4name(self, series4name):
self._s4name = series4name
self.setSeries()
@Property(QColor)
def series4colour(self):
return self._s4colour
@series4colour.setter
def series4colour(self, series4colour):
self._s4colour = series4colour
self.setSeries()
@Property(int)
def series4width(self):
return self._s4width
@series4width.setter
def series4width(self, series4width):
self._s4width = series4width
self.setSeries()
@Property(Qt.PenStyle)
def series4style(self):
return self._s4style
@series4style.setter
def series4style(self, series4style):
self._s4style = series4style
self.setSeries()
def initialize(self):
comp = hal.getComponent()
obj_name = self.getPinBaseName()
# add HAL pins
if self._s1enable:
self._s1_pin = comp.addPin(obj_name + "." + self._s1name.replace(' ', ''), self._typ, "in")
if self._s2enable:
self._s2_pin = comp.addPin(obj_name + "." + self._s2name.replace(' ', ''), self._typ, "in")
if self._s3enable:
self._s3_pin = comp.addPin(obj_name + "." + self._s3name.replace(' ', ''), self._typ, "in")
if self._s4enable:
self._s4_pin = comp.addPin(obj_name + "." + self._s4name.replace(' ', ''), self._typ, "in")