"""
Unified monitor & control widgets
Display sensor values, control control values, turns out it's all the same
"""
import numpy as np
import time
from PySide2 import QtWidgets, QtCore, QtGui
import PySide2
import pyqtgraph as pg
import os
import sys
import typing
from pvp.gui import styles, mono_font
from pvp.common import message, unit_conversion
from pvp.common.values import ValueName, Value
from pvp.common.message import ControlSetting
from pvp.common.loggers import init_logger
from pvp.common import unit_conversion, prefs
from pvp.gui.widgets.components import EditableLabel, DoubleSlider, QVLine
from pvp.gui.widgets.dialog import pop_dialog
from pvp.alarm import AlarmSeverity
[docs]class Display(QtWidgets.QWidget):
limits_changed = QtCore.Signal(tuple)
value_changed = QtCore.Signal(float)
[docs] def __init__(self,
value: Value,
update_period: float = styles.MONITOR_UPDATE_INTERVAL,
enum_name: ValueName = None,
button_orientation: str = "left",
control_type: typing.Union[None, str] = None,
style: str="dark",
*args, **kwargs):
"""
Args:
value (:class:`.Value`): Value Object to display
update_period (float): time to wait in between updating displayed value
enum_name (:class:`.ValueName`): Value name (not in Value objects)
button_orientation (str: 'left' or 'right'): whether the button should be on the left or right
control_type (None, str: 'slider', 'record'): whether a slider, a button to record recent values, or ``None`` control should be used with this object
style (str: 'light', 'dark', or a QtStylesheet string): _style for the display
*args, **kwargs: passed to :class:`PySide2.QtWidgets.QWidget`
Attributes:
self.name:
self.units:
self.abs_range:
self.safe_range:
self.decimals:
self.update_period:
self.enum_name:
self.orientation:
self.control:
self._style:
self.set_value:
self.sensor_value:
"""
super(Display, self).__init__(*args, **kwargs)
# unpack value object
self.name = value.name
self.units = value.units
self.abs_range = value.abs_range
if not value.safe_range:
self.safe_range = (0, 0)
else:
self.safe_range = value.safe_range
self.alarm_range = self.safe_range
self.decimals = value.decimals
self.logger = init_logger(__name__)
# store parameters
self.update_period = update_period
self.enum_name = enum_name
self.orientation = button_orientation
self.control = control_type
self._style = style
self._styles = {}
## initialize stateful attributes
# re: unit conversion for Pressure
self._convert_in = None
self._convert_out = None
# for drawing alarm state
self._alarm_state = AlarmSeverity.OFF # type: AlarmSeverity
# for setting control values by recording recent values
self._log_values = False
self._logged_values = []
# for storing set and sensed values
self.set_value = None
self.sensor_value = None
self._firstset = False # don't pop dialog boxes until the control is brought within the safe range one time
# for sensor value display
self._yrange = ()
# for confirming dangerous values
self._dialog_open = False
self._confirmed_unsafe = False
# for locking controls
self._locked = False
# become identifiable
if self.enum_name:
self.setObjectName(self.enum_name.name)
# create graphical objects
self.init_ui()
self.timed_update()
[docs] def init_ui(self):
self.layout = QtWidgets.QVBoxLayout()
self.layout.setContentsMargins(0,0,0,0)
self.setLayout(self.layout)
self.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Minimum)
self.setMinimumHeight(styles.DISPLAY_MIN_HEIGHT)
# choose stylesheets
if self._style == "dark":
self._styles['main'] = styles.DISPLAY_DARK
self._styles['label_value'] = styles.DISPLAY_VALUE_DARK
self._styles['label_name'] = styles.DISPLAY_NAME
self._styles['label_units'] = styles.DISPLAY_UNITS_DARK
self._styles['set_value_label'] = styles.CONTROL_VALUE_DARK
self._styles['slider_label'] = styles.CONTROL_LABEL
self._styles['line_color'] = styles.DIVIDER_COLOR
# self._styles['sensor_frame'] = styles.CONTROL_SENSOR_FRAME
elif self._style == "light":
self._styles['main'] = styles.DISPLAY_LIGHT
self._styles['label_value'] = styles.DISPLAY_VALUE_LIGHT
self._styles['label_name'] = styles.CONTROL_NAME
self._styles['label_units'] = styles.DISPLAY_UNITS_LIGHT
self._styles['set_value_label'] = styles.CONTROL_VALUE_LIGHT
self._styles['slider_label'] = styles.CONTROL_LABEL
self._styles['line_color'] = styles.DIVIDER_COLOR_DARK
# self._styles['sensor_frame'] = styles.CONTROL_SENSOR_FRAME
else:
raise NotImplementedError('Need to use "light" or "dark" for _style')
#self._styles['label_alarm'] = styles.DISPLAY_VALUE_ALARM
self.setProperty('widgetClass', 'Display')
self.setStyleSheet(self._styles['main'])
# self.setStyleSheet("border: 1px solid black")
# -----------------------
# first create all the widgets that we need
# widgets common to all display objects
self.init_ui_labels()
# a toggle button for controls is created whether we control or not
# (if self.control == None, button is invisible but keeps space for alignment purposes)
self.init_ui_toggle_button()
# graphical elements true of all controls
# if self.control:
self.init_ui_limits()
# graphical element specific to control type
if self.control == "slider":
self.init_ui_slider()
elif self.control == "record":
self.init_ui_record()
#----------------
# then lay them out and connect signals
self.init_ui_layout()
self.init_ui_signals()
#--------------
# and then connect internal signals
[docs] def init_ui_labels(self):
# label to display measured/sensed values
self.sensor_label = QtWidgets.QLabel()
self.sensor_label.setStyleSheet(self._styles['label_value'])
self.sensor_label.setFont(mono_font())
self.sensor_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignCenter)
self.sensor_label.setMargin(0)
self.sensor_label.setContentsMargins(0, 0, 0, 0)
# at minimum make space for 4 digits
self.sensor_label.setMinimumWidth(4 * styles.VALUE_SIZE * .6)
self.sensor_label.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Minimum)
# label display value name
self.name_label = QtWidgets.QLabel()
self.name_label.setStyleSheet(self._styles['label_name'])
self.name_label.setText(self.name)
self.name_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignBottom )
self.name_label.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
# label to display units
self.units_label = QtWidgets.QLabel()
self.units_label.setStyleSheet(self._styles['label_units'])
self.units_label.setText(self.units)
self.units_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTop)
self.units_label.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
[docs] def init_ui_limits(self):
"""
Create widgets to display sensed value alongside set value
"""
self.control_vline = QVLine(color=self._styles['line_color'])
# label to display and edit set value
self.set_value_label = EditableLabel()
self.set_value_label.setStyleSheet(self._styles['set_value_label'])
self.set_value_label.label.setFont(mono_font())
self.set_value_label.lineEdit.setFont(mono_font())
self.set_value_label.label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignBottom)
self.set_value_label.setMinimumWidth(4 * styles.VALUE_MINOR_SIZE * .6)
self.set_value_label.setContentsMargins(0, 0, 0, 0)
self.set_value_label.setSizePolicy(QtWidgets.QSizePolicy.Maximum,
QtWidgets.QSizePolicy.Maximum)
# bar graph that indicates current value and limits
self.sensor_plot = Limits_Plot(style=self._style)
if self.control is None:
label_size_policy = self.set_value_label.sizePolicy()
label_size_policy.setRetainSizeWhenHidden(True)
self.set_value_label.setSizePolicy(label_size_policy)
self.set_value_label.setHidden(True)
plot_size_policy = self.sensor_plot.sizePolicy()
plot_size_policy.setRetainSizeWhenHidden(True)
self.sensor_plot.setSizePolicy(plot_size_policy)
self.sensor_plot.setHidden(True)
[docs] def init_ui_slider(self):
# -------
# create toggle button
# ------
self.toggle_button.setArrowType(QtCore.Qt.LeftArrow)
# --------
# create (initially hidden) slider objects)
# ---------
# Min value - slider - max value
self.slider_layout = QtWidgets.QHBoxLayout()
self.slider_min = QtWidgets.QLabel()
self.slider_min.setText(unit_conversion.rounded_string(self.abs_range[0], self.decimals))
self.slider_min.setAlignment(QtCore.Qt.AlignLeft)
self.slider_min.setFont(mono_font())
self.slider_min.setStyleSheet(self._styles['slider_label'])
self.slider_min.setMargin(0)
self.slider_min.setSizePolicy(QtWidgets.QSizePolicy.Maximum,
QtWidgets.QSizePolicy.Maximum)
self.slider_max = QtWidgets.QLabel()
self.slider_max.setText(unit_conversion.rounded_string(self.abs_range[1], self.decimals))
self.slider_max.setAlignment(QtCore.Qt.AlignRight)
self.slider_max.setFont(mono_font())
self.slider_max.setStyleSheet(self._styles['slider_label'])
self.slider_max.setMargin(0)
self.slider_max.setSizePolicy(QtWidgets.QSizePolicy.Maximum,
QtWidgets.QSizePolicy.Maximum)
self.slider = DoubleSlider(decimals=self.decimals, orientation=QtCore.Qt.Orientation.Horizontal)
self.slider.setMinimum(self.abs_range[0])
self.slider.setMaximum(self.abs_range[1])
# self.slider.setContentsMargins(0,0,0,0)
self.slider.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Maximum)
self.slider_layout.addWidget(self.slider_min)
self.slider_layout.addWidget(self.slider)
self.slider_layout.addWidget(self.slider_max)
self.slider_frame = QtWidgets.QFrame()
self.slider_frame.setLayout(self.slider_layout)
self.slider_frame.setVisible(False)
[docs] def init_ui_record(self):
# load record icon
gui_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
rec_path = os.path.join(gui_dir, 'images', 'record.png')
image = QtGui.QPixmap(rec_path)
self.rec_icon = QtGui.QIcon(image)
self.toggle_button.setIcon(self.rec_icon)
[docs] def init_ui_layout(self):
"""
Basically two methods... lay objects out depending on whether we're oriented with our button to the left or right
"""
# main layout is the visible part -- widgets laid out horizontally
self.main_layout = QtWidgets.QHBoxLayout()
self.label_layout = QtWidgets.QGridLayout()
self.main_layout.setContentsMargins(0,0,0,0)
self.label_layout.setContentsMargins(10,0,10,0)
if self.orientation == "left":
self.label_layout.addWidget(self.sensor_label, 0, 0, 2, 1, alignment=QtCore.Qt.AlignRight)
self.label_layout.addWidget(self.name_label, 0, 1, 1, 1, alignment=QtCore.Qt.AlignRight)
self.label_layout.addWidget(self.units_label, 1, 1, 1, 1, alignment=QtCore.Qt.AlignRight)
# control objects
self.main_layout.addWidget(self.toggle_button)
# if self.control:
self.main_layout.addWidget(self.sensor_plot)
self.main_layout.addWidget(self.set_value_label)
self.main_layout.addWidget(self.control_vline)
# else:
# self.main_layout.addStretch()
self.main_layout.addLayout(self.label_layout)
elif self.orientation == "right":
# labels are always laid out the same becuse numbers have to be aligned right...
self.label_layout.addWidget(self.sensor_label, 0, 1, 2, 1, alignment=QtCore.Qt.AlignRight)
self.label_layout.addWidget(self.name_label, 0, 0, 1, 1, alignment=QtCore.Qt.AlignLeft)
self.label_layout.addWidget(self.units_label, 1, 0, 1, 1, alignment=QtCore.Qt.AlignLeft)
self.main_layout.addLayout(self.label_layout)
# control objects
# if self.control:
self.main_layout.addWidget(self.control_vline)
self.main_layout.addWidget(self.set_value_label)
self.main_layout.addWidget(self.sensor_plot)
# else:
# self.main_layout.addStretch()
self.main_layout.addWidget(self.toggle_button)
self.layout.addLayout(self.main_layout)
[docs] def init_ui_signals(self):
if self.control:
self.set_value_label.textChanged.connect(self._value_changed)
if self.control == "slider":
self.toggle_button.toggled.connect(self.toggle_control)
self.slider.doubleValueChanged.connect(self._value_changed)
elif self.control == "record":
self.toggle_button.toggled.connect(self.toggle_record)
[docs] @QtCore.Slot(bool)
def toggle_control(self, state):
if self.control != 'slider':
return
if state == True:
self.toggle_button.setArrowType(QtCore.Qt.DownArrow)
# self.layout.addWidget(self.slider_frame, 3, 0, 1, 3)
self.layout.addWidget(self.slider_frame)
self.slider_frame.setVisible(True)
# self.adjustSize()
else:
self.toggle_button.setArrowType(QtCore.Qt.LeftArrow)
self.layout.removeWidget(self.slider_frame)
self.slider_frame.setVisible(False)
self.adjustSize()
[docs] def toggle_record(self, state):
if self.control != 'record':
return
if state:
self._log_values = True
self._logged_values = []
self.toggle_button.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_DialogApplyButton))
self.sensor_label.setStyleSheet(styles.CONTROL_VALUE_REC)
self.name_label.setStyleSheet(styles.CONTROL_NAME_REC)
self.units_label.setStyleSheet(styles.CONTROL_UNITS_REC)
else:
if len(self._logged_values)>0:
# get the mean logged value
mean_val = np.mean(self._logged_values)
# convert to displayed range if necessary (_value_changed) expects it
if self._convert_in:
mean_val = self._convert_in(mean_val)
self._value_changed(mean_val)
self.toggle_button.setIcon(self.rec_icon)
# self.sensor_label.setStyleSheet(self._styles['label_value'])
self.name_label.setStyleSheet(self._styles['label_name'])
self.units_label.setStyleSheet(self._styles['label_units'])
# this resets the style to the alarm state
self.alarm_state = self.alarm_state
[docs] def _value_changed(self, new_value: float):
"""
"outward-directed" Control value changed by components
Args:
new_value (float):
emit (bool): whether to emit the `value_changed` signal (default True) -- in the case that our value is being changed by someone other than us
"""
# pdb.set_trace()
if isinstance(new_value, str):
new_value = float(new_value)
# stash unconverted value for use in dialog messages
orig_value = new_value
if self._convert_out:
# convert from display value to internal value
new_value = self._convert_out(new_value)
if (new_value > self.safe_range[1]) or (new_value < self.safe_range[0]):
if self._dialog_open: # pragma: no cover
# if we're already opening a dialogue, don't try to set value or emit
return
if (not self._confirmed_unsafe) and \
('pytest' not in sys.modules) and \
prefs.get_pref('ENABLE_WARNINGS') and \
self._firstset: # pragma: no cover
self._dialog_open = True
safe_range = self.safe_range
if self._convert_in:
safe_range = (self._convert_in(safe_range[0]), self._convert_in(safe_range[1]))
dialog = pop_dialog(
message = f'Confirm setting potentially unsafe {self.name} value',
sub_message= f'Values of {self.name} outside of {self.safe_range[0]}-{self.safe_range[1]} {self.units} are usually unsafe.\n\nAre you sure you want to set {self.name} to {orig_value} {self.units}',
buttons = QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Ok,
default_button = QtWidgets.QMessageBox.Cancel
)
ret = dialog.exec_()
self._dialog_open = False
if ret != QtWidgets.QMessageBox.Ok:
# if canceled, set value to current value
new_value = self.set_value
else:
# don't prompt again
self._confirmed_unsafe = True
else:
# reset _confirmed_unsafe if back in range
if self._confirmed_unsafe:
self._confirmed_unsafe = False
# set firstset flag if we're going in safe range for the first time
if not self._firstset:
self._firstset = True
changed = self.update_set_value(new_value)
if changed:
self.value_changed.emit(self.set_value)
[docs] def update_set_value(self, new_value: float):
"""inward value setting (set from elsewhere)"""
if isinstance(new_value, str):
new_value = float(new_value)
# don't convert value here,
# assume the only hPa values would come from the widget itself since it's display only
changed = False
if (new_value <= self.abs_range[1]) and (new_value >= self.abs_range[0]):
if (new_value != self.set_value):
self.set_value = new_value
changed = True
else:
self.logger.exception(f'Attempted to set {self.name} out of range ({self.abs_range}) with value {new_value}')
if changed:
self.redraw()
return changed
[docs] def update_sensor_value(self, new_value: float):
if new_value is None:
return
self.sensor_value = new_value
# store values to set value by averaging sensor values
if self._log_values:
self._logged_values.append(new_value)
if self._convert_in:
new_value = self._convert_in(new_value)
value_str = unit_conversion.rounded_string(new_value, self.decimals)
# self.sensor_label.setText(value_str)
if self.control:
self.sensor_plot.update_value(sensor_value = new_value)
[docs] def update_limits(self, control: ControlSetting):
"""
Update the alarm range and the GUI elements corresponding to it
Args:
control (:class:`~.ControlSetting`): control setting with min_value or max_value
"""
if self.control is None:
return
if control.min_value:
if self._convert_in:
self.sensor_plot.update_value(min=self._convert_in(control.min_value))
else:
self.sensor_plot.update_value(min=control.min_value)
self.alarm_range = (control.min_value, self.alarm_range[1])
if control.max_value:
if self._convert_in:
self.sensor_plot.update_value(max=self._convert_in(control.max_value))
else:
self.sensor_plot.update_value(max=control.max_value)
self.alarm_range = (self.alarm_range[0], control.max_value)
[docs] def redraw(self):
"""
Redraw all graphical elements to ensure internal model matches view
"""
# convert some guaranteed values
abs_range = self.abs_range
if self._convert_in:
abs_range = (self._convert_in(x) for x in abs_range)
# sensor value
if self.sensor_value:
sensor_value = self.sensor_value
if self._convert_in:
sensor_value = self._convert_in(sensor_value)
# don't update sensor value label, it should be on timed update
# just make sure the sensor bar is correct
if self.control:
self.sensor_plot.update_value(sensor_value=sensor_value)
# set value
if self.set_value:
set_value = self.set_value
if self._convert_in:
set_value = self._convert_in(set_value)
# update label and plot
if self.control:
set_value_str = unit_conversion.rounded_string(set_value, self.decimals)
self.set_value_label.setText(set_value_str)
self.sensor_plot.update_value(set_value=set_value)
if self.control == "slider":
try:
self.slider.blockSignals(True)
self.slider.setValue(set_value)
self.slider.setMinimum(abs_range[0])
self.slider.setMaximum(abs_range[1])
self.slider.setDecimals(self.decimals)
self.slider_min.setText(unit_conversion.rounded_string(abs_range[0], self.decimals))
self.slider_max.setText(unit_conversion.rounded_string(abs_range[1], self.decimals))
finally:
self.slider.blockSignals(False)
# alarm limits
if self.alarm_range:
alarm_range = self.alarm_range
if self._convert_in:
alarm_range = (self._convert_in(x) for x in alarm_range)
self.sensor_plot.update_value(min=alarm_range[0], max=alarm_range[1])
[docs] def timed_update(self):
# format value based on decimals
try:
if self.sensor_value:
if self._convert_in:
sensor_value = self._convert_in(self.sensor_value)
else:
sensor_value = self.sensor_value
value_str = unit_conversion.rounded_string(sensor_value, self.decimals)
self.sensor_label.setText(value_str)
except Exception as e:
self.logger.exception(f"{self.name} - error in timed update - {e}")
finally:
QtCore.QTimer.singleShot(round(self.update_period*1000), self.timed_update)
[docs] def set_units(self, units: str):
"""
Set pressure units to display as cmH2O or hPa
Args:
units ('cmH2O', 'hPa'):
"""
if self.name in (ValueName.PIP.name, ValueName.PEEP.name):
if units == 'cmH2O':
self.decimals = 1
self._convert_in = None
self._convert_out = None
self.sensor_plot._convert_in = None
self.sensor_plot._convert_out = None
elif units == 'hPa':
self.decimals = 0
self._convert_in = unit_conversion.cmH2O_to_hPa
self._convert_out = unit_conversion.hPa_to_cmH2O
self.sensor_plot._convert_in = unit_conversion.cmH2O_to_hPa
self.sensor_plot._convert_out = unit_conversion.hPa_to_cmH2O
else:
self.logger.exception(f'couldnt set units {units}')
return
self.units = units
self.units_label.setText(units)
self.redraw()
else:
self.logger.exception(f'error setting units {self.name} - {units}')
[docs] def set_locked(self, locked: bool):
if locked:
self.locked = True
if self.control:
if self.control == "slider":
self.toggle_control(False)
self.toggle_button.setEnabled(False)
self.set_value_label.setEditable(False)
# self.setStyleSheet()
else:
self.locked = False
if self.control:
self.toggle_button.setEnabled(True)
self.set_value_label.setEditable(True)
# ---------------------------------
# Properties
# ---------------------------------
@property
def is_set(self):
if self.set_value is None:
return False
else:
return True
@property
def alarm_state(self) -> AlarmSeverity:
return self._alarm_state
@alarm_state.setter
def alarm_state(self, alarm_state:AlarmSeverity):
if alarm_state == AlarmSeverity.OFF or alarm_state not in AlarmSeverity:
self.sensor_label.setStyleSheet(self._styles['label_value'])
else:
self.sensor_label.setStyleSheet(styles.DISPLAY_ALARM_STYLES[alarm_state])
self._alarm_state = alarm_state
[docs]class Limits_Plot(pg.PlotWidget):
"""
Widget to display current value in a bar graph along with alarm limits
"""
def __init__(self, style:str="light", *args, **kwargs):
self.set_value = None
self.sensor_value = None
self._minimum = None
self._maximum = None
self._style = style
self.yrange = (0, 1)
self._convert_in = None
self._convert_out = None
if self._style == "light":
super(Limits_Plot, self).__init__(background=styles.CONTROL_SENSOR_BACKGROUND_LIGHT, *args, **kwargs)
elif self._style == "dark":
super(Limits_Plot, self).__init__(background=styles.CONTROL_SENSOR_BACKGROUND_DARK, *args, **kwargs)
self.init_ui()
[docs] def init_ui(self):
# bar graph that's an indicator of current value
self.getPlotItem().hideAxis('bottom')
self.getPlotItem().hideAxis('left')
self.setRange(xRange=(-0.5, 0.5))
# bar itself
# if self._style == "light:"
self.sensor_bar = pg.BarGraphItem(x=np.array([0]), y1=np.array([0]), width=np.array([1]),
brush=styles.SENSOR_BAR_COLOR)
# error bars for limit indicators
self.top_limit = pg.InfiniteLine(movable=False, angle=0, pos=0,
pen={
'color': styles.SUBWAY_COLORS['red'],
'width': 2
},
label='{value:0.1f}',
labelOpts = {
'color': styles.SUBWAY_COLORS['red']
})
self.bottom_limit = pg.InfiniteLine(movable=False, angle=0, pos=0,
pen={
'color': styles.SUBWAY_COLORS['red'],
'width': 2
},
label='{value:0.1f}',
labelOpts={
'color': styles.SUBWAY_COLORS['red']
})
# the set value
if self._style == "light":
self.sensor_set = pg.InfiniteLine(movable=False, angle=0, pos=0,
pen={'color': styles.BACKGROUND_COLOR, 'width': 5})
elif self._style == "dark":
self.sensor_set = pg.InfiniteLine(movable=False, angle=0, pos=0,
pen={'color': styles.TEXT_COLOR, 'width': 5})
self.addItem(self.sensor_bar)
self.addItem(self.top_limit)
self.addItem(self.bottom_limit)
self.addItem(self.sensor_set)
self.setSizePolicy(QtWidgets.QSizePolicy.Maximum,
QtWidgets.QSizePolicy.Minimum)
self.plotItem.setSizePolicy(QtWidgets.QSizePolicy.Maximum,
QtWidgets.QSizePolicy.Minimum)
# self.set
self.setFixedWidth(styles.CONTROL_SENSOR_BAR_WIDTH)
# self.setFixedHeight(styles.CONTROL_SENSOR_BAR_WIDTH)
# self.enableAutoRange(y=True)
self.update_yrange()
[docs] def update_value(self,
min: float = None,
max: float = None,
sensor_value: float = None,
set_value: float = None):
"""
Move the lines! Pass any of the represented values
Args:
min (float): new alarm minimum
max (float): new alarm _maximum
sensor_value (float): new value for the sensor bar plot
set_value (float): new value for the set value line
"""
if min:
self.bottom_limit.setValue(float(min))
self._minimum = float(min)
if max:
self.top_limit.setValue(float(max))
self._maximum = float(max)
if sensor_value:
self.sensor_bar.setOpts(y1=np.array([float(sensor_value)]))
self.sensor_value = float(sensor_value)
if set_value:
self.sensor_set.setValue(float(set_value))
self.set_value = float(set_value)
self.update_yrange()
[docs] def update_yrange(self):
if self.set_value:
# put set_value in the middle, add space above and below to fit alarms
min_dist = 0
max_dist = 0
sensor_dist = 0
if self._minimum:
min_dist = np.abs(self.set_value-self._minimum)
if self._maximum:
max_dist = np.abs(self.set_value-self._maximum)
if self.sensor_value:
sensor_dist = np.abs(self.set_value-self.sensor_value)
dist = np.max([min_dist, max_dist])
self.setYRange(self.set_value-dist, self.set_value+dist)
else:
self.setYRange(0,0)