class RealtimePlotWidget(QWidget): COLORS = [ Qt.red, Qt.blue, Qt.green, Qt.magenta, Qt.cyan, Qt.darkRed, Qt.darkBlue, Qt.darkGreen, Qt.darkYellow, Qt.gray ] def __init__(self, parent=None): super(RealtimePlotWidget, self).__init__(parent) self._plot_widget = PlotWidget() self._plot_widget.setBackground((0, 0, 0)) self._plot_widget.addLegend() self._plot_widget.showButtons() self._plot_widget.enableAutoRange() self._plot_widget.showGrid(x=True, y=True, alpha=0.2) vbox = QVBoxLayout() vbox.addWidget(self._plot_widget) self.setLayout(vbox) self._color_index = 0 self._curves = {} def add_curve(self, curve_id, curve_name, data_x=[], data_y=[]): color = QColor(self.COLORS[self._color_index % len(self.COLORS)]) self._color_index += 1 pen = mkPen(color, width=1) plot = self._plot_widget.plot(name=curve_name, pen=pen) data_x = numpy.array(data_x) data_y = numpy.array(data_y) self._curves[curve_id] = {'x': data_x, 'y': data_y, 'plot': plot} def remove_curve(self, curve_id): curve_id = str(curve_id) if curve_id in self._curves: self._plot_widget.removeItem(self._curves[curve_id]['plot']) del self._curves[curve_id] def set_x_range(self, left, right): self._plot_widget.setRange(xRange=(left, right)) def update_values(self, curve_id, x, y): curve = self._curves[curve_id] curve['x'] = numpy.append(curve['x'], x) curve['y'] = numpy.append(curve['y'], y) def redraw(self): for curve in self._curves.values(): if len(curve['x']): curve['plot'].setData(curve['x'], curve['y']) def lazy_redraw(self, period): timestamp = time.time() if not hasattr(self, '_prev_lazy_redraw'): self._prev_lazy_redraw = 0.0 if timestamp - self._prev_lazy_redraw > period: self._prev_lazy_redraw = timestamp self.redraw()
class RealtimePlotWidget(QWidget): COLORS = [Qt.red, Qt.blue, Qt.green, Qt.magenta, Qt.cyan, Qt.darkRed, Qt.darkBlue, Qt.darkGreen, Qt.darkYellow, Qt.gray] def __init__(self, parent=None): super(RealtimePlotWidget, self).__init__(parent) self._plot_widget = PlotWidget() self._plot_widget.setBackground((0, 0, 0)) self._plot_widget.addLegend() self._plot_widget.showButtons() self._plot_widget.enableAutoRange() self._plot_widget.showGrid(x=True, y=True, alpha=0.2) vbox = QVBoxLayout() vbox.addWidget(self._plot_widget) self.setLayout(vbox) self._color_index = 0 self._curves = {} def add_curve(self, curve_id, curve_name, data_x=[], data_y=[]): color = QColor(self.COLORS[self._color_index % len(self.COLORS)]) self._color_index += 1 pen = mkPen(color, width=1) plot = self._plot_widget.plot(name=curve_name, pen=pen) data_x = numpy.array(data_x) data_y = numpy.array(data_y) self._curves[curve_id] = {'x': data_x, 'y': data_y, 'plot': plot} def remove_curve(self, curve_id): curve_id = str(curve_id) if curve_id in self._curves: self._plot_widget.removeItem(self._curves[curve_id]['plot']) del self._curves[curve_id] def set_x_range(self, left, right): self._plot_widget.setRange(xRange=(left, right)) def update_values(self, curve_id, x, y): curve = self._curves[curve_id] curve['x'] = numpy.append(curve['x'], x) curve['y'] = numpy.append(curve['y'], y) def redraw(self): for curve in self._curves.values(): if len(curve['x']): curve['plot'].setData(curve['x'], curve['y']) def lazy_redraw(self, period): timestamp = time.time() if not hasattr(self, '_prev_lazy_redraw'): self._prev_lazy_redraw = 0.0 if timestamp - self._prev_lazy_redraw > period: self._prev_lazy_redraw = timestamp self.redraw()
class Canvas2Dupgraded(PlotWidget, AnimatedWidget): def __init__(self, parent=None, data_dict=None): super(Canvas2Dupgraded, self).__init__() self.shareData(**data_dict) self.plotWidget = PlotWidget(self) self._i = self.current_state self.title = self.options['column'] self.graph_data = self.plot_data[self.title].tolist() self.internal_iterations = len(self.graph_data) self.createPlotCanvas() def createPlotCanvas(self): self.null_data = np.array([i for i in range(self.internal_iterations)]) self.plotWidget.setTitle(self.title) self.plotWidget.setGeometry(0, 0, self.geom[0] - 60, self.geom[1] - 60) self.plotWidget.setXRange(0, self.internal_iterations) self.plotWidget.setYRange(np.min(self.graph_data), np.max(self.graph_data), padding=0.1) self.plotWidget.enableAutoRange('xy', False) self.plotData = self.plotWidget.plot( self.graph_data[:self._i], pen=pg.mkPen(color=self.options['color'][0], width=self.options['marker_size']), name="data1", clear=True) def on_resize_geometry_reset(self, geom): """ when another widget is promoted, this window must resize too this means resetting the graph unfortunately """ self.plotWidget.setGeometry(0, 0, geom[0] - 60, geom[1] - 60) def set_i(self, value, trigger=False, record=False): self._i = value self._i %= self.internal_iterations self.plotData.setData(self.null_data[:self._i], self.graph_data[:self._i]) pg.QtGui.QApplication.processEvents()
def __init__(self, atri_plot: PlotWidget, vent_plot: PlotWidget, data_size: int): print("Graphs handler init") # noinspection PyArgumentList atri_plot.setRange(xRange=[-1, data_size], yRange=[-0.5, 5.5], padding=0) atri_plot.setLimits(xMin=-1, xMax=data_size, maxXRange=data_size + 1, yMin=-0.5, yMax=5.5) atri_plot.setMouseEnabled(x=True, y=False) atri_plot.enableAutoRange(x=False, y=True) atri_plot.setAutoVisible(x=False, y=True) atri_plot.showGrid(x=True, y=True) atri_plot.hideButtons() atri_plot.setMenuEnabled(False) atri_plot.setLabel('left', "Amplitude", units='V', **{'color': '#FFF', 'font-size': '10pt'}) atri_plot.setLabel('bottom', "Time", units='s', **{'color': '#FFF', 'font-size': '10pt'}) atri_plot.getAxis('bottom').setHeight(30) # noinspection PyArgumentList vent_plot.setRange(xRange=[-1, data_size], yRange=[-0.5, 5.5], padding=0) vent_plot.setLimits(xMin=-1, xMax=data_size, maxXRange=data_size + 1, yMin=-0.5, yMax=5.5) vent_plot.setMouseEnabled(x=True, y=False) vent_plot.enableAutoRange(x=False, y=True) vent_plot.setAutoVisible(x=False, y=True) vent_plot.showGrid(x=True, y=True) vent_plot.hideButtons() vent_plot.setMenuEnabled(False) vent_plot.setLabel('left', "Amplitude", units='V', **{'color': '#FFF', 'font-size': '10pt'}) vent_plot.setLabel('bottom', "Time", units='s', **{'color': '#FFF', 'font-size': '10pt'}) vent_plot.getAxis('bottom').setHeight(30) # Initialize graphs to 0 self._atri_data = np.zeros(data_size) self._vent_data = np.zeros(data_size) # Create new sense plots for the atrial and ventricular graphs, in blue self._atri_plot = atri_plot.plot(pen=(0, 229, 255)) self._vent_plot = vent_plot.plot(pen=(0, 229, 255)) self._plot_data()
class BusMonitorWidget(QGroupBox): DEFAULT_PLOT_X_RANGE = 120 BUS_LOAD_PLOT_MAX_SAMPLES = 5000 def __init__(self, parent, node, iface_name): super(BusMonitorWidget, self).__init__(parent) self.setTitle('CAN bus activity (%s)' % iface_name.split(os.path.sep)[-1]) self._node = node self._hook_handle = self._node.can_driver.add_io_hook(self._frame_hook) self._columns = [ BasicTable.Column('Dir', lambda e: (e[0].upper()), searchable=False), BasicTable.Column('Local Time', TimestampRenderer(), searchable=False), BasicTable.Column('CAN ID', lambda e: (('%0*X' % (8 if e[1].extended else 3, e[1].id)).rjust(8), colorize_can_id(e[1]))), BasicTable.Column('Data Hex', lambda e: (' '.join(['%02X' % x for x in e[1].data]).ljust(3 * e[1].MAX_DATA_LENGTH), colorize_transfer_id(e))), BasicTable.Column('Data ASCII', lambda e: (''.join([(chr(x) if 32 <= x <= 126 else '.') for x in e[1].data]), colorize_transfer_id(e))), BasicTable.Column('Src', lambda e: render_node_id_with_color(e[1], 'src')), BasicTable.Column('Dst', lambda e: render_node_id_with_color(e[1], 'dst')), BasicTable.Column('Data Type', lambda e: render_data_type_with_color(e[1]), resize_mode=QHeaderView.Stretch), ] self._log_widget = RealtimeLogWidget(self, columns=self._columns, font=get_monospace_font(), post_redraw_hook=self._redraw_hook) self._log_widget.on_selection_changed = self._update_measurement_display def flip_row_mark(row, col): if col == 0: item = self._log_widget.table.item(row, col) if item.icon().isNull(): item.setIcon(get_icon('circle')) flash(self, 'Row %d was marked, click again to unmark', row, duration=3) else: item.setIcon(QIcon()) self._log_widget.table.cellPressed.connect(flip_row_mark) self._stat_update_timer = QTimer(self) self._stat_update_timer.setSingleShot(False) self._stat_update_timer.timeout.connect(self._update_stat) self._stat_update_timer.start(500) self._traffic_stat = TrafficStatCounter() self._stat_frames_tx = QLabel('N/A', self) self._stat_frames_rx = QLabel('N/A', self) self._stat_traffic = QLabel('N/A', self) self._load_plot = PlotWidget(background=(0, 0, 0)) self._load_plot.setRange(xRange=(0, self.DEFAULT_PLOT_X_RANGE), padding=0) self._load_plot.setMaximumHeight(150) self._load_plot.setMinimumHeight(100) self._load_plot.setMinimumWidth(100) self._load_plot.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self._load_plot.showGrid(x=True, y=True, alpha=0.4) self._load_plot.setToolTip('Frames per second') self._load_plot.getPlotItem().getViewBox().setMouseEnabled(x=True, y=False) self._load_plot.enableAutoRange() self._bus_load_plot = self._load_plot.plot(name='Frames per second', pen=mkPen(QColor(Qt.lightGray), width=1)) self._bus_load_samples = [], [] self._started_at_mono = time.monotonic() layout = QVBoxLayout(self) layout.addWidget(self._log_widget, 1) stat_vars_layout = QGridLayout(self) stat_layout_next_row = 0 def add_stat_row(label, value): nonlocal stat_layout_next_row stat_vars_layout.addWidget(QLabel(label, self), stat_layout_next_row, 0) stat_vars_layout.addWidget(value, stat_layout_next_row, 1) value.setMinimumWidth(75) stat_layout_next_row += 1 add_stat_row('Frames transmitted:', self._stat_frames_tx) add_stat_row('Frames received:', self._stat_frames_rx) add_stat_row('Frames per second:', self._stat_traffic) stat_vars_layout.setRowStretch(stat_layout_next_row, 1) stat_layout = QHBoxLayout(self) stat_layout.addLayout(stat_vars_layout) stat_layout.addWidget(self._load_plot, 1) layout.addLayout(stat_layout, 0) self.setLayout(layout) def close(self): self._hook_handle.remove() def _update_stat(self): bus_load, ts_mono = self._traffic_stat.get_frames_per_second() self._stat_traffic.setText(str(int(bus_load + 0.5))) if len(self._bus_load_samples[0]) >= self.BUS_LOAD_PLOT_MAX_SAMPLES: self._bus_load_samples[0].pop(0) self._bus_load_samples[1].pop(0) self._bus_load_samples[1].append(bus_load) self._bus_load_samples[0].append(ts_mono - self._started_at_mono) self._bus_load_plot.setData(*self._bus_load_samples) (xmin, xmax), _ = self._load_plot.viewRange() diff = xmax - xmin xmax = self._bus_load_samples[0][-1] xmin = self._bus_load_samples[0][-1] - diff self._load_plot.setRange(xRange=(xmin, xmax), padding=0) def _redraw_hook(self): self._stat_frames_tx.setText(str(self._traffic_stat.tx)) self._stat_frames_rx.setText(str(self._traffic_stat.rx)) def _frame_hook(self, direction, frame): self._traffic_stat.add_frame(direction, frame) self._log_widget.add_item_async((direction, frame)) def _update_measurement_display(self, selected_rows_cols): if not selected_rows_cols: return min_row = min([row for row, _ in selected_rows_cols]) max_row = max([row for row, _ in selected_rows_cols]) def get_row_ts(row): return TimestampRenderer.parse_timestamp(self._log_widget.table.item(row, 1).text()) def get_load_str(num_frames, dt): if dt >= 1e-6: return 'average load %.1f FPS' % (num_frames / dt) return 'average load is unknown' if min_row == max_row: num_frames = min_row first_ts = get_row_ts(0) current_ts = get_row_ts(min_row) dt = current_ts - first_ts flash(self, '%d frames from beginning, %.3f sec since first frame, %s', num_frames, dt, get_load_str(num_frames, dt)) else: num_frames = max_row - min_row + 1 first_ts = get_row_ts(min_row) last_ts = get_row_ts(max_row) dt = last_ts - first_ts flash(self, '%d frames, timedelta %.6f sec, %s', num_frames, dt, get_load_str(num_frames, dt))
class Window(QWidget): def __init__(self, *args, **kwargs): super(Window, self).__init__(*args, **kwargs) self.title = "Motor Control" self.setWindowTitle(self.title) #Application Size self.left = 100 self.top = 100 self.width = 1000 self.height = 700 self.setGeometry(self.left, self.top, self.width, self.height) self.initUI() def initUI(self): self.setStyleSheet(qdarkstyle.load_stylesheet()) self.horizontalLayout = QHBoxLayout() self.verticalLayout = QVBoxLayout() self.verticalLayout.setSizeConstraint(QLayout.SetDefaultConstraint) self.verticalLayout.setSpacing(6) self.gridLayout = QGridLayout() self.imageLabel = QLabel() sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.imageLabel.sizePolicy().hasHeightForWidth()) self.imageLabel.setSizePolicy(sizePolicy) self.imageLabel.setMinimumSize(QSize(200, 130)) self.imageLabel.setMaximumSize(QSize(200, 130)) self.imageLabel.setPixmap( QPixmap("./Arduino/logo/CUAtHomeLogo-Horz.png").scaled( 200, 130, Qt.KeepAspectRatio, Qt.FastTransformation)) self.verticalLayout.addWidget(self.imageLabel) self.startbutton = QPushButton("Start", self) self.startbutton.setCheckable(False) self.startbutton.clicked.connect(self.startbutton_pushed) self.startbutton.setMaximumSize(QSize(100, 20)) self.gridLayout.addWidget(self.startbutton, 0, 0, 1, 1) self.stopbutton = QPushButton("Stop", self) self.stopbutton.setCheckable(False) self.stopbutton.clicked.connect(self.stopbutton_pushed) sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.stopbutton.sizePolicy().hasHeightForWidth()) self.stopbutton.setSizePolicy(sizePolicy) self.stopbutton.setMaximumSize(QSize(100, 20)) self.gridLayout.addWidget(self.stopbutton, 0, 1, 1, 1) self.clearbutton = QPushButton("Clear", self) self.clearbutton.setCheckable(False) self.clearbutton.clicked.connect(self.clearbutton_pushed) self.clearbutton.setMaximumSize(QSize(100, 20)) self.gridLayout.addWidget(self.clearbutton, 1, 0, 1, 1) self.savebutton = QPushButton("Save", self) self.savebutton.setCheckable(False) self.savebutton.clicked.connect(self.savebutton_pushed) sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.savebutton.sizePolicy().hasHeightForWidth()) self.savebutton.setSizePolicy(sizePolicy) self.savebutton.setMaximumSize(QSize(100, 20)) self.gridLayout.addWidget(self.savebutton, 1, 1, 1, 1) self.settings = QPushButton("Settings", self) self.settings.clicked.connect(self.settingsMenu) self.settings.setMaximumSize(QSize(300, 20)) self.gridLayout.addWidget(self.settings, 2, 0, 1, 2) self.checkBoxShowAll = QCheckBox("Show All Plots", self) self.checkBoxShowAll.setMaximumSize(QSize(100, 20)) self.checkBoxShowAll.setChecked(True) self.checkBoxShowAll.toggled.connect(self.visibilityAll) self.gridLayout.addWidget(self.checkBoxShowAll, 3, 0, 1, 1) self.checkBoxHideAll = QCheckBox("Hide All Plots", self) self.checkBoxHideAll.setChecked(False) self.checkBoxHideAll.toggled.connect(self.hideAll) sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.checkBoxHideAll.sizePolicy().hasHeightForWidth()) self.checkBoxHideAll.setSizePolicy(sizePolicy) self.checkBoxHideAll.setMaximumSize(QSize(100, 20)) self.gridLayout.addWidget(self.checkBoxHideAll, 3, 1, 1, 1) self.checkBoxPlot1 = QCheckBox("Plot 1", self) self.checkBoxPlot1.toggled.connect(self.visibility1) self.checkBoxPlot1.setMaximumSize(QSize(100, 20)) self.gridLayout.addWidget(self.checkBoxPlot1, 4, 0, 1, 1) self.checkBoxPlot2 = QCheckBox("Plot 2", self) self.checkBoxPlot2.toggled.connect(self.visibility2) sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.checkBoxPlot2.sizePolicy().hasHeightForWidth()) self.checkBoxPlot2.setSizePolicy(sizePolicy) self.checkBoxPlot2.setMaximumSize(QSize(100, 20)) self.gridLayout.addWidget(self.checkBoxPlot2, 4, 1, 1, 1) self.checkBoxShowAll.stateChanged.connect(self.checkbox_logic) self.checkBoxHideAll.stateChanged.connect(self.checkbox_logic) self.checkBoxPlot1.stateChanged.connect(self.checkbox_logic) self.checkBoxPlot2.stateChanged.connect(self.checkbox_logic) self.PowerScalingLabel = QLabel("Power Scaling (%)", self) self.PowerScalingLabel.setMinimumSize(QSize(100, 20)) self.PowerScalingLabel.setMaximumSize(QSize(100, 20)) self.gridLayout.addWidget(self.PowerScalingLabel, 7, 0, 1, 1) self.PowerScalingInput = QLineEdit("", self) sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.PowerScalingInput.sizePolicy().hasHeightForWidth()) self.PowerScalingInput.setSizePolicy(sizePolicy) self.PowerScalingInput.setMaximumSize(QSize(100, 20)) #self.PowerScalingInput.setValidator(QRegExpValidator(QRegExp("^[0-9][0-9]?$|^100$"))) #0-1 as a float FIX THIS self.gridLayout.addWidget(self.PowerScalingInput, 7, 1, 1, 1) self.FrequencyLabel = QLabel("Frequency (Hz)", self) self.FrequencyLabel.setMinimumSize(QSize(100, 20)) self.FrequencyLabel.setMaximumSize(QSize(100, 20)) self.gridLayout.addWidget(self.FrequencyLabel, 8, 0, 1, 1) self.FrequencyInput = QLineEdit("", self) sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.FrequencyInput.sizePolicy().hasHeightForWidth()) self.FrequencyInput.setSizePolicy(sizePolicy) self.FrequencyInput.setMaximumSize(QSize(100, 20)) self.FrequencyInput.setValidator(QDoubleValidator()) self.gridLayout.addWidget(self.FrequencyInput, 8, 1, 1, 1) PID_validator = QDoubleValidator( 0.0000, 50.000, 4, notation=QDoubleValidator.StandardNotation) self.PCheckBox = QCheckBox("P", self) self.PCheckBox.setMaximumSize(QSize(100, 20)) self.PCheckBox.setChecked(True) self.PCheckBox.toggled.connect(self.PCheckBoxLogic) self.gridLayout.addWidget(self.PCheckBox, 9, 0, 1, 1) self.PInput = QLineEdit("", self) sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.PInput.sizePolicy().hasHeightForWidth()) self.PInput.setSizePolicy(sizePolicy) self.PInput.setMaximumSize(QSize(100, 20)) self.PInput.setValidator(PID_validator) self.gridLayout.addWidget(self.PInput, 9, 1, 1, 1) self.ICheckBox = QCheckBox("I", self) self.ICheckBox.setMaximumSize(QSize(100, 20)) self.ICheckBox.setChecked(True) self.ICheckBox.toggled.connect(self.ICheckBoxLogic) self.gridLayout.addWidget(self.ICheckBox, 10, 0, 1, 1) self.IInput = QLineEdit("", self) sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.IInput.sizePolicy().hasHeightForWidth()) self.IInput.setSizePolicy(sizePolicy) self.IInput.setMaximumSize(QSize(100, 20)) self.IInput.setValidator(PID_validator) self.gridLayout.addWidget(self.IInput, 10, 1, 1, 1) self.DCheckBox = QCheckBox("D", self) self.DCheckBox.setMaximumSize(QSize(100, 20)) self.DCheckBox.setChecked(True) self.DCheckBox.toggled.connect(self.DCheckBoxLogic) self.gridLayout.addWidget(self.DCheckBox, 11, 0, 1, 1) self.DInput = QLineEdit("", self) sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.DInput.sizePolicy().hasHeightForWidth()) self.DInput.setSizePolicy(sizePolicy) self.DInput.setMaximumSize(QSize(100, 20)) self.DInput.setValidator(PID_validator) self.gridLayout.addWidget(self.DInput, 11, 1, 1, 1) self.LabType = QComboBox() self.LabType.addItems(["Position", "Speed"]) #self.LabType.activated.connect(self.getLabType) sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.LabType.sizePolicy().hasHeightForWidth()) self.LabType.setSizePolicy(sizePolicy) self.LabType.setMaximumSize(QSize(100, 20)) self.gridLayout.addWidget(self.LabType, 5, 1, 1, 1) self.LabLabel = QLabel("Lab Type") self.LabLabel.setMaximumSize(QSize(100, 20)) self.gridLayout.addWidget(self.LabLabel, 5, 0, 1, 1) self.inputForms = QComboBox() self.inputForms.addItems(["Sine", "Step"]) self.inputForms.activated.connect(self.getInput) sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.inputForms.sizePolicy().hasHeightForWidth()) self.inputForms.setSizePolicy(sizePolicy) self.inputForms.setMaximumSize(QSize(100, 20)) self.gridLayout.addWidget(self.inputForms, 6, 1, 1, 1) self.inputType = QLabel("Input Type") self.inputType.setMaximumSize(QSize(100, 20)) self.gridLayout.addWidget(self.inputType, 6, 0, 1, 1) self.verticalLayout.addLayout(self.gridLayout) spacerItem = QSpacerItem(20, 80, QSizePolicy.Minimum, QSizePolicy.Fixed) self.verticalLayout.addItem(spacerItem) #What is this? self.label = QLabel() self.label.setMaximumSize(QSize(200, 130)) self.label.setText("") self.verticalLayout.addWidget(self.label) self.horizontalLayout.addLayout(self.verticalLayout) self.rightVerticalLayout = QVBoxLayout() self.graphWidgetOutput = PlotWidget() self.graphWidgetInput = PlotWidget() #Adds grid lines self.graphWidgetOutput.showGrid(x=True, y=True, alpha=None) self.graphWidgetInput.showGrid(x=True, y=True, alpha=None) #self.graphWidget.setXRange(0, 100, padding=0) #Doesn't move with the plot. Can drag around #self.graphWidget.setLimits(xMin=0, xMax=100)#, yMin=c, yMax=d) #Doesn't move with the plot. Cannot drag around #self.graphWidget.setYRange(0, 4, padding=0) self.graphWidgetOutput.setYRange(-11, 11, padding=0) self.graphWidgetOutput.enableAutoRange() self.graphWidgetInput.setYRange(-11, 11, padding=0) self.graphWidgetInput.enableAutoRange() #Changes background color of graph self.graphWidgetOutput.setBackground((0, 0, 0)) self.graphWidgetInput.setBackground((0, 0, 0)) #Adds a legend after data starts to plot NOT before self.graphWidgetOutput.addLegend() #Adds title to graphs self.graphWidgetOutput.setTitle("Response", color="w", size="12pt") self.graphWidgetInput.setTitle("PWM Actuation Signal", color="w", size="12pt") self.rightVerticalLayout.addWidget(self.graphWidgetOutput) self.rightVerticalLayout.addWidget(self.graphWidgetInput) self.horizontalLayout.addLayout(self.rightVerticalLayout) self.setLayout(self.horizontalLayout) #Plot time update settings self.timer = QTimer() self.timer.setInterval( 50 ) #Changes the plot speed. Defaulted to 50. Can be placed in startbutton_pushed() method self.initialState() time.sleep(2) try: self.timer.timeout.connect(self.update) except: raise Exception("Not Connected") #self.show() #Checkbox logic def checkbox_logic(self, state): # checking if state is checked if state == Qt.Checked: if self.sender() == self.checkBoxShowAll: self.checkBoxHideAll.setChecked(False) self.checkBoxPlot1.setChecked(False) self.checkBoxPlot2.setChecked(False) #self.checkBoxShow.stateChanged.disconnect(self.uncheck) elif self.sender() == self.checkBoxHideAll: #self.checkBoxShow.stateChanged.connect(self.uncheck) self.checkBoxShowAll.setChecked(False) self.checkBoxPlot1.setChecked(False) self.checkBoxPlot2.setChecked(False) elif self.sender() == self.checkBoxPlot1: self.checkBoxShowAll.setChecked(False) self.checkBoxHideAll.setChecked(False) self.checkBoxPlot2.setChecked(False) elif self.sender() == self.checkBoxPlot2: self.checkBoxShowAll.setChecked(False) self.checkBoxHideAll.setChecked(False) self.checkBoxPlot1.setChecked(False) #Resets data arrays and establishes serial communcation. Disables itself after clicking def startbutton_pushed(self): self.initialState( ) #Reinitializes arrays in case you have to retake data self.size = self.serial_values[3] #Value from settings. Windows data #self.buffersize = self.serial_values[4] #Value from settings. Restricts buffer data ''' self.ser = serial.Serial(port = self.serial_values[0], baudrate = self.serial_values[1], timeout = self.serial_values[2]) self.ser.flushInput() self.ser.write(b'A') time.sleep(2) print("Recording Data") self.timer.start() #self.timer.setInterval(50) self.curve() self.startbutton.clicked.disconnect(self.startbutton_pushed) ''' self.serialInstance = SerialComm(self.serial_values[0], self.serial_values[1], self.serial_values[2]) self.serialInstance.serialOpen() time.sleep(2) print("Recording Data") self.timer.start() self.curve() self.startbutton.clicked.disconnect(self.startbutton_pushed) #Stops timer and ends serial communication def stopbutton_pushed(self): self.timer.stop() #self.ser.close() self.serialInstance.serialClose() ''' print("y1 zeros:", self.y1_zeros) print("y2 zeros:", self.y2_zeros) print("y1 full:", self.y1) print("y2 full:", self.y2) ''' #Resets both plotting windows and reenables Start Button def clearbutton_pushed(self): self.graphWidgetOutput.clear() self.graphWidgetInput.clear() self.graphWidgetOutput.enableAutoRange(axis=None, enable=True, x=None, y=None) self.startbutton.clicked.connect(self.startbutton_pushed) #Dumps data into a csv file to a selected path def savebutton_pushed(self): self.createCSV() path = QFileDialog.getSaveFileName(self, 'Save CSV', os.getenv('HOME'), 'CSV(*.csv)') if path[0] != '': with open(path[0], 'w', newline='') as csvfile: csvwriter = csv.writer(csvfile) csvwriter.writerow(self.header) csvwriter.writerows(self.data_set) #Creates csv data def createCSV(self): self.header = ['time', 'y1', 'y2'] self.data_set = zip(self.time, self.y1, self.y2) #Initilizes lists/arrays def initialState(self): self.buffersize = 500 #np array size that is used to plot data self.step = 0 #Used for repositioning data in plot window to the left #Data buffers. What is being plotted in the 2 windows self.time_zeros = np.zeros(self.buffersize + 1, float) self.y1_zeros = np.zeros(self.buffersize + 1, float) self.y2_zeros = np.zeros(self.buffersize + 1, float) self.y3_zeros = np.zeros(self.buffersize + 1, float) #Complete data. What will be written to the csv file self.time = list() self.y1 = list() self.y2 = list() self.y3 = list() self.getLabType() ''' def readValues(self): arduinoData = self.ser.readline().decode().replace('\r\n','').split(",") return arduinoData ''' #Initializes data# to have specific attributes def curve(self): pen1 = pg.mkPen(color=(255, 0, 0), width=1) pen2 = pg.mkPen(color=(0, 255, 0), width=1) pen3 = pg.mkPen(color=(0, 0, 255), width=1) self.data1 = self.graphWidgetOutput.plot(pen=pen1, name="Data 1") #Response self.data2 = self.graphWidgetOutput.plot(pen=pen2, name="Data 2") #Setpoint self.data3 = self.graphWidgetInput.plot( pen=pen3, name="Data 3") #PWM Actuation Signal #Connected to timer to update plot. Incoming data is in the form of timestamp,data1,data2... def update(self): #fulldata = self.readValues() #print(fulldata) fulldata = self.serialInstance.readValues() self.step = self.step + 1 time_index = int(self.time_zeros[self.buffersize]) self.time_zeros[time_index] = self.time_zeros[time_index + self.size] = float( fulldata[0]) self.time_zeros[self.buffersize] = time_index = (time_index + 1) % self.size self.time.append(fulldata[0]) i = int(self.y1_zeros[self.buffersize]) self.y1_zeros[i] = self.y1_zeros[i + self.size] = float(fulldata[1]) self.y1_zeros[self.buffersize] = i = (i + 1) % self.size self.y1.append(fulldata[1]) j = int(self.y2_zeros[self.buffersize]) self.y2_zeros[j] = self.y2_zeros[j + self.size] = float(fulldata[2]) self.y2_zeros[self.buffersize] = j = (j + 1) % self.size self.y2.append(fulldata[2]) k = int(self.y3_zeros[self.buffersize]) self.y3_zeros[k] = self.y3_zeros[k + self.size] = float(fulldata[3]) self.y3_zeros[self.buffersize] = k = (k + 1) % self.size self.y3.append(fulldata[3]) self.data1.setData(self.time_zeros[time_index:time_index + self.size], self.y1_zeros[i:i + self.size]) self.data1.setPos(self.step, 0) self.data2.setData(self.time_zeros[time_index:time_index + self.size], self.y2_zeros[j:j + self.size]) self.data2.setPos(self.step, 0) self.data3.setData(self.time_zeros[time_index:time_index + self.size], self.y3_zeros[k:k + self.size]) self.data3.setPos(self.step, 0) #Below 4 change visibility of data# in the curves() method def visibilityAll(self): showall = self.sender() if showall.isChecked() == True: self.data1.setVisible(True) self.data2.setVisible(True) def hideAll(self): disappearall = self.sender() if disappearall.isChecked() == True: self.data1.setVisible(False) self.data2.setVisible(False) def visibility1(self): test1 = self.sender() if test1.isChecked() == True: self.data1.setVisible(True) self.data2.setVisible(False) def visibility2(self): test2 = self.sender() if test2.isChecked() == True: self.data2.setVisible(True) self.data1.setVisible(False) #Class instance of settings menu. Creates a dialog (popup) def settingsMenu(self): self.settingsPopUp = Dialog1() self.settingsPopUp.show() #self.settingsPopUp.exec() self.serial_values = self.settingsPopUp.getDialogValues() def PCheckBoxLogic(self): test1 = self.sender() if test1.isChecked() == True: self.PInput.setEnabled(True) elif test1.isChecked() == False: self.PInput.setEnabled(False) def ICheckBoxLogic(self): test1 = self.sender() if test1.isChecked() == True: self.IInput.setEnabled(True) elif test1.isChecked() == False: self.IInput.setEnabled(False) def DCheckBoxLogic(self): test1 = self.sender() if test1.isChecked() == True: self.DInput.setEnabled(True) elif test1.isChecked() == False: self.DInput.setEnabled(False) def PIDInput(self): if self.PInput.text() == "" or self.PCheckBox.checkState() == False: self.Pvalue = 0 else: self.Pvalue = self.PInput.text() if self.IInput.text() == "" or self.ICheckBox.checkState() == False: self.Ivalue = 0 else: self.Ivalue = self.IInput.text() if self.DInput.text() == "" or self.DCheckBox.checkState() == False: self.Dvalue = 0 else: self.Dvalue = self.DInput.text() return ([self.Pvalue, self.Ivalue, self.Dvalue]) #Function that connects output pyqtgraph widget, and the combobox def getInput(self): self.inputType = str(self.inputForms.currentText()) pen_input = pg.mkPen(color=(255, 0, 0), width=1) if self.inputType == "Sine": print("Sine") self.graphWidgetInput.clear() self.x_input = np.arange(0, 10, 0.1) self.y_input = np.sin(self.x_input) self.data_input = self.graphWidgetInput.plot(self.x_input, self.y_input, pen=pen_input) self.data_input.setData(self.x_input, self.y_input) self.graphWidgetInput.setYRange(-2, 2, padding=0) elif self.inputType == "Step": print("Step") self.graphWidgetInput.clear() self.x_input = np.arange(0, 10, 0.1) self.y_input = np.heaviside(self.x_input, 1) self.data_input = self.graphWidgetInput.plot(self.x_input, self.y_input, pen=pen_input) self.data_input.setData(self.x_input, self.y_input) self.graphWidgetInput.setYRange(-2, 2, padding=0) def getLabType(self): self.inputType = str(self.LabType.currentText()) if self.inputType == "Position": print("Lab: Position") return ( self.graphWidgetOutput.setLabel( 'left', "<span style=\"color:white;font-size:16px\">θ (°)</span>" ), self.graphWidgetInput.setLabel( 'left', "<span style=\"color:white;font-size:16px\">Voltage</span>" ), self.graphWidgetOutput.setLabel( 'bottom', "<span style=\"color:white;font-size:16px\">Time (s)</span>" ), self.graphWidgetInput.setLabel( 'bottom', "<span style=\"color:white;font-size:16px\">Time (s)</span>" )) elif self.inputType == "Speed": print("Lab: Speed") return ( self.graphWidgetOutput.setLabel( 'left', "<span style=\"color:white;font-size:16px\">ω (°/s)</span>" ), self.graphWidgetInput.setLabel( 'left', "<span style=\"color:white;font-size:16px\">Voltage</span>" ), self.graphWidgetOutput.setLabel( 'bottom', "<span style=\"color:white;font-size:16px\">Time (s)</span>" ), self.graphWidgetInput.setLabel( 'bottom', "<span style=\"color:white;font-size:16px\">Time (s)</span>" ), )
def _load_analysis(self, x_axis_scale): this_question = db.question.find_one({'_id': self.index}) title = this_question['title'] content = this_question['content'] time_tuple = localtime(this_question['created_time']) question_created_time = "%d-%d-%d %d:%d:%d" % ( time_tuple.tm_year, time_tuple.tm_mon, time_tuple.tm_mday, time_tuple.tm_hour, time_tuple.tm_min, time_tuple.tm_sec) if content != '': self.textEdit.setText(title + '\n\n 提问时间:' + question_created_time + '\n 问题描述:\n' + content) else: self.textEdit.setText(title + '\n\n 提问时间:' + question_created_time) trend = db.question_trend.find_one({'_id': self.index}) time_trend = trend['time'] str_time = [] for num_time in time_trend: local_time = localtime(num_time) str_time.append("%d-%d\n%d:%d\n" % (local_time.tm_mon, local_time.tm_mday, local_time.tm_hour, local_time.tm_min)) ticks = [(i, j) for i, j in zip(time_trend, str_time)] rank_trend = trend['rank'] convert_rank = [51 - i for i in rank_trend] rank_ticks = [(i, j) for i, j in zip(convert_rank, rank_trend)] rankAxis = AxisItem(orientation='left') rankAxis.setTicks([ rank_ticks, ]) strAxis = AxisItem(orientation='bottom', maxTickLength=5) strAxis.setTicks([ ticks, ]) rank_plot = PlotWidget(axisItems={ 'bottom': strAxis, 'left': rankAxis }, background=None) rank_plot.setObjectName("tab") rank_plot.plot(x=time_trend, y=convert_rank, pen=(0, 255, 0), symbol='o') rank_plot.enableAutoRange('x', x_axis_scale) self._add_analysis_tab(rank_plot, '排名') heat_trend = trend['heat'] # strAxis.setStyle(autoExpandTextSpace=True) strAxis = AxisItem(orientation='bottom', maxTickLength=5) strAxis.setTicks([ ticks, ]) heat_plot = PlotWidget(axisItems={'bottom': strAxis}, background=None) heat_plot.setObjectName("tab") heat_plot.plot(x=time_trend, y=heat_trend, pen=(255, 0, 0), symbol='o') heat_plot.enableAutoRange('x', x_axis_scale) self._add_analysis_tab(heat_plot, '热度') answer_count_trend = trend['answer_count'] strAxis = AxisItem(orientation='bottom', maxTickLength=5) strAxis.setTicks([ ticks, ]) answer_count_plot = PlotWidget(axisItems={'bottom': strAxis}, background=None) answer_count_plot.setObjectName("tab") answer_count_plot.plot(x=time_trend, y=answer_count_trend, pen=(0, 0, 255), symbol='o') answer_count_plot.enableAutoRange('x', x_axis_scale) self._add_analysis_tab(answer_count_plot, '回答量') follower_count_trend = trend['follower_count'] strAxis = AxisItem(orientation='bottom', maxTickLength=5) strAxis.setTicks([ ticks, ]) follower_count_plot = PlotWidget(axisItems={'bottom': strAxis}, background=None) follower_count_plot.setObjectName("tab") follower_count_plot.plot(x=time_trend, y=follower_count_trend, pen=(19, 234, 201), symbolBrush=(19, 234, 201), symbol='o', symbolPen='w') follower_count_plot.enableAutoRange('x', x_axis_scale) self._add_analysis_tab(follower_count_plot, '关注数') visitor_count_trend = trend['visitor_count'] strAxis = AxisItem(orientation='bottom', maxTickLength=5) strAxis.setTicks([ ticks, ]) visitor_count_plot = PlotWidget(axisItems={'bottom': strAxis}, background=None) visitor_count_plot.setObjectName("tab") visitor_count_plot.plot(x=time_trend, y=visitor_count_trend, pen=(195, 46, 212), symbolBrush=(195, 46, 212), symbol='t', symbolPen='w') visitor_count_plot.enableAutoRange('x', x_axis_scale) self._add_analysis_tab(visitor_count_plot, '浏览量')
class Plotter(QWidget): MAX_DATA_POINTS_PER_CURVE = 200000 COLORS = [ Qt.red, Qt.green, Qt.blue, # RGB - http://ux.stackexchange.com/questions/79561 Qt.yellow, Qt.cyan, Qt.magenta, # Close to RGB Qt.darkRed, Qt.darkGreen, Qt.darkBlue, # Darker RGB Qt.darkYellow, Qt.darkCyan, Qt.darkMagenta, # Close to RGB Qt.gray, Qt.darkGray ] # Leftovers INITIAL_X_RANGE = 60 def __init__(self, parent=None): # Parent super(Plotter, self).__init__(parent) self.setWindowTitle('UAVCAN Plotter') self.setWindowIcon(APP_ICON) # Redraw timer self._update_timer = QTimer() self._update_timer.timeout.connect(self._update) self._update_timer.setSingleShot(False) self._update_timer.start(30) # PyQtGraph self._plot_widget = PlotWidget() self._plot_widget.setBackground((0, 0, 0)) self._legend = self._plot_widget.addLegend() self._plot_widget.setRange(xRange=(0, self.INITIAL_X_RANGE), padding=0) self._plot_widget.showButtons() self._plot_widget.enableAutoRange() self._plot_widget.showGrid(x=True, y=True, alpha=0.4) # Controls # https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html button_add_matcher = QtGui.QPushButton('New matcher', self) button_add_matcher.setIcon(QtGui.QIcon.fromTheme('list-add')) button_add_matcher.setToolTip('Add new curve matcher') button_add_matcher.clicked.connect(lambda: NewCurveMatcherWindow( self, lambda: sorted(self._active_messages), self. _add_curve_matcher).show()) button_clear_plots = QtGui.QPushButton('Clear plots', self) button_clear_plots.setIcon(QtGui.QIcon.fromTheme('edit-clear')) button_clear_plots.setToolTip('Clear the plotting area') button_clear_plots.clicked.connect(lambda: self._remove_all_curves()) def delete_all_matchers(): self._curve_matchers = [] for i in reversed(range(self._curve_matcher_container.count())): self._curve_matcher_container.itemAt(i).widget().deleteLater() self._remove_all_curves() button_delete_all_matchers = QtGui.QPushButton('Delete matchers', self) button_delete_all_matchers.setIcon( QtGui.QIcon.fromTheme('edit-delete')) button_delete_all_matchers.setToolTip('Delete all matchers') button_delete_all_matchers.clicked.connect(delete_all_matchers) self._autoscroll = QtGui.QCheckBox('Autoscroll', self) self._autoscroll.setChecked(True) self._max_x = self.INITIAL_X_RANGE # Layout control_panel = QHBoxLayout() control_panel.addWidget(button_add_matcher) control_panel.addWidget(button_clear_plots) control_panel.addWidget(self._autoscroll) control_panel.addStretch() control_panel.addWidget(button_delete_all_matchers) self._curve_matcher_container = QVBoxLayout() layout = QVBoxLayout() layout.addWidget(self._plot_widget, 1) layout.addLayout(control_panel) layout.addLayout(self._curve_matcher_container) self.setLayout(layout) # Logic self._color_index = 0 self._curves = {} self._message_queue = multiprocessing.Queue() self._active_messages = set() # set(data type name) self._curve_matchers = [] # Defaults self._add_curve_matcher( CurveMatcher('uavcan.protocol.debug.KeyValue', 'value', [('key', None)])) def _add_curve_matcher(self, matcher): self._curve_matchers.append(matcher) view = CurveMatcherView(matcher, self) def remove(): self._curve_matchers.remove(matcher) self._curve_matcher_container.removeWidget(view) view.setParent(None) view.deleteLater() view.on_remove = remove self._curve_matcher_container.addWidget(view) def _update(self): # Processing messages while True: try: m = self._message_queue.get_nowait() self._process_message(m) except queue.Empty: break # Updating curves for curve in self._curves.values(): if len(curve['x']): if len(curve['x']) > self.MAX_DATA_POINTS_PER_CURVE: curve['x'] = curve['x'][-self.MAX_DATA_POINTS_PER_CURVE:] curve['y'] = curve['y'][-self.MAX_DATA_POINTS_PER_CURVE:] assert len(curve['x']) == len(curve['y']) curve['plot'].setData(curve['x'], curve['y']) self._max_x = max(self._max_x, curve['x'][-1]) # Updating view range if self._autoscroll.checkState(): (xmin, xmax), _ = self._plot_widget.viewRange() diff = xmax - xmin xmax = self._max_x xmin = self._max_x - diff self._plot_widget.setRange(xRange=(xmin, xmax), padding=0) def _process_message(self, m): self._active_messages.add(m.data_type_name) for matcher in self._curve_matchers: if matcher.match(m): name, x, y = matcher.extract_curve_name_x_y(m) self._draw_curve(name, x, y) def _remove_all_curves(self): for curve in self._curves.values(): self._plot_widget.removeItem(curve['plot']) self._plot_widget.clear() self._curves = {} self._color_index = 0 self._legend.scene().removeItem(self._legend) self._legend = self._plot_widget.addLegend() def _draw_curve(self, name, x, y): if name not in self._curves: logging.info('Adding curve %r', name) color = self.COLORS[self._color_index % len(self.COLORS)] self._color_index += 1 pen = mkPen(QColor(color), width=1) plot = self._plot_widget.plot(name=name, pen=pen) self._curves[name] = { 'x': numpy.array([]), 'y': numpy.array([]), 'plot': plot } curve = self._curves[name] curve['x'] = numpy.append(curve['x'], [x] if isinstance(x, (float, int)) else x) curve['y'] = numpy.append(curve['y'], [y] if isinstance(y, (float, int)) else y) assert len(curve['x']) == len(curve['y']) def push_received_message(self, msg): self._message_queue.put_nowait(msg)
class BusMonitorWidget(QGroupBox): DEFAULT_PLOT_X_RANGE = 120 BUS_LOAD_PLOT_MAX_SAMPLES = 5000 def __init__(self, parent, node, iface_name): super(BusMonitorWidget, self).__init__(parent) self.setTitle('CAN bus activity (%s)' % iface_name.split(os.path.sep)[-1]) self._node = node self._hook_handle = self._node.can_driver.add_io_hook(self._frame_hook) self._columns = [ BasicTable.Column('Dir', lambda e: (e[0].upper()), searchable=False), BasicTable.Column('Local Time', TimestampRenderer(), searchable=False), BasicTable.Column( 'CAN ID', lambda e: (('%0*X' % (8 if e[1].extended else 3, e[1].id)).rjust(8), colorize_can_id(e[1]))), BasicTable.Column( 'Data Hex', lambda e: (' '.join(['%02X' % x for x in e[1].data]).ljust(3 * e[ 1].MAX_DATA_LENGTH), colorize_transfer_id(e))), BasicTable.Column( 'Data ASCII', lambda e: (''.join([(chr(x) if 32 <= x <= 126 else '.') for x in e[1].data]), colorize_transfer_id(e))), BasicTable.Column( 'Src', lambda e: render_node_id_with_color(e[1], 'src')), BasicTable.Column( 'Dst', lambda e: render_node_id_with_color(e[1], 'dst')), BasicTable.Column('Data Type', lambda e: render_data_type_with_color(e[1]), resize_mode=QHeaderView.Stretch), ] self._log_widget = RealtimeLogWidget( self, columns=self._columns, font=get_monospace_font(), post_redraw_hook=self._redraw_hook) self._log_widget.on_selection_changed = self._update_measurement_display def flip_row_mark(row, col): if col == 0: item = self._log_widget.table.item(row, col) if item.icon().isNull(): item.setIcon(get_icon('circle')) flash(self, 'Row %d was marked, click again to unmark', row, duration=3) else: item.setIcon(QIcon()) self._log_widget.table.cellPressed.connect(flip_row_mark) self._stat_update_timer = QTimer(self) self._stat_update_timer.setSingleShot(False) self._stat_update_timer.timeout.connect(self._update_stat) self._stat_update_timer.start(500) self._traffic_stat = TrafficStatCounter() self._stat_frames_tx = QLabel('N/A', self) self._stat_frames_rx = QLabel('N/A', self) self._stat_traffic = QLabel('N/A', self) self._load_plot = PlotWidget(background=(0, 0, 0)) self._load_plot.setRange(xRange=(0, self.DEFAULT_PLOT_X_RANGE), padding=0) self._load_plot.setMaximumHeight(150) self._load_plot.setMinimumHeight(100) self._load_plot.setMinimumWidth(100) self._load_plot.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self._load_plot.showGrid(x=True, y=True, alpha=0.4) self._load_plot.setToolTip('Frames per second') self._load_plot.getPlotItem().getViewBox().setMouseEnabled(x=True, y=False) self._load_plot.enableAutoRange() self._bus_load_plot = self._load_plot.plot(name='Frames per second', pen=mkPen(QColor( Qt.lightGray), width=1)) self._bus_load_samples = [], [] self._started_at_mono = time.monotonic() layout = QVBoxLayout(self) layout.addWidget(self._log_widget, 1) stat_vars_layout = QGridLayout(self) stat_layout_next_row = 0 def add_stat_row(label, value): nonlocal stat_layout_next_row stat_vars_layout.addWidget(QLabel(label, self), stat_layout_next_row, 0) stat_vars_layout.addWidget(value, stat_layout_next_row, 1) value.setMinimumWidth(75) stat_layout_next_row += 1 add_stat_row('Frames transmitted:', self._stat_frames_tx) add_stat_row('Frames received:', self._stat_frames_rx) add_stat_row('Frames per second:', self._stat_traffic) stat_vars_layout.setRowStretch(stat_layout_next_row, 1) stat_layout = QHBoxLayout(self) stat_layout.addLayout(stat_vars_layout) stat_layout.addWidget(self._load_plot, 1) layout.addLayout(stat_layout, 0) self.setLayout(layout) def close(self): self._hook_handle.remove() def _update_stat(self): bus_load, ts_mono = self._traffic_stat.get_frames_per_second() self._stat_traffic.setText(str(int(bus_load + 0.5))) if len(self._bus_load_samples[0]) >= self.BUS_LOAD_PLOT_MAX_SAMPLES: self._bus_load_samples[0].pop(0) self._bus_load_samples[1].pop(0) self._bus_load_samples[1].append(bus_load) self._bus_load_samples[0].append(ts_mono - self._started_at_mono) self._bus_load_plot.setData(*self._bus_load_samples) (xmin, xmax), _ = self._load_plot.viewRange() diff = xmax - xmin xmax = self._bus_load_samples[0][-1] xmin = self._bus_load_samples[0][-1] - diff self._load_plot.setRange(xRange=(xmin, xmax), padding=0) def _redraw_hook(self): self._stat_frames_tx.setText(str(self._traffic_stat.tx)) self._stat_frames_rx.setText(str(self._traffic_stat.rx)) def _frame_hook(self, direction, frame): self._traffic_stat.add_frame(direction, frame) self._log_widget.add_item_async((direction, frame)) def _update_measurement_display(self, selected_rows_cols): if not selected_rows_cols: return min_row = min([row for row, _ in selected_rows_cols]) max_row = max([row for row, _ in selected_rows_cols]) def get_row_ts(row): return TimestampRenderer.parse_timestamp( self._log_widget.table.item(row, 1).text()) def get_load_str(num_frames, dt): if dt >= 1e-6: return 'average load %.1f FPS' % (num_frames / dt) return 'average load is unknown' if min_row == max_row: num_frames = min_row first_ts = get_row_ts(0) current_ts = get_row_ts(min_row) dt = current_ts - first_ts flash(self, '%d frames from beginning, %.3f sec since first frame, %s', num_frames, dt, get_load_str(num_frames, dt)) else: num_frames = max_row - min_row + 1 first_ts = get_row_ts(min_row) last_ts = get_row_ts(max_row) dt = last_ts - first_ts flash(self, '%d frames, timedelta %.6f sec, %s', num_frames, dt, get_load_str(num_frames, dt))
class SmartScanGUI(QtGui.QWidget): # oh god i'm so sorry. don't listen to him; he's never sorry. def __init__(self): QtGui.QWidget.__init__(self) self.setLayout(QtGui.QHBoxLayout()) # a dict to store data self.dataMatrix = {} self.refData = {} ########################################## create a plot and associated widget ########################################################### self.plotWidget = PlotWidget() self.layout().addWidget(self.plotWidget,1) def xYPlot(plotWidget,x,y,yerr=None,xerr=None,color='w',name='Current'): thisPlot = plotWidget.plot(x,y,pen=mkPen(color,width=2)) plotWidget.addItem( ErrorBarItem( x=np.asarray(x), y=np.asarray(y), top=np.asarray(yerr) if yerr is not None else None, bottom=np.asarray(yerr) if yerr is not None else None, left=np.asarray(xerr) if xerr is not None else None, right=np.asarray(xerr) if xerr is not None else None, beam=.05, pen=mkPen(color) ) ) # method for updating plot with new data (plot has to be cleared first) def updatePlot(): self.plotWidget.clear() for name, rData in self.refData.iteritems(): xYPlot( self.plotWidget, rData['data'][0], rData['data'][1], yerr=rData['data'][2], color=rData['color'], name=name ) if len(self.dataMatrix.keys()) >= 1: for xVal, yValues in self.dataMatrix.items(): if xVal in self.xVals: oldMean = self.yVals[self.xVals.index(xVal)] thisMean = np.mean(yValues) newMean = (oldMean+thisMean)/2. self.yVals[self.xVals.index(xVal)] = newMean newErr = np.std(yValues)/np.sqrt(len(yValues)) self.errVals[self.xVals.index(xVal)] = newErr else: self.xVals.append(xVal) mean = np.mean(yValues) self.yVals.append(mean) err = np.std(yValues)/np.sqrt(len(yValues)) self.errVals.append(err) xYPlot(self.plotWidget,self.xVals,self.yVals,yerr=self.errVals) ############################################## configure a control panel layout ######################################################## cpLayout = QtGui.QVBoxLayout() self.layout().addLayout(cpLayout) # configure the output widget outputPane = QtGui.QTabWidget() cpLayout.addWidget(LabelWidget('output',outputPane)) @inlineCallbacks def onInit(): ############################################################# VOLTMETER OUTPUT ########################################################### # add volt meter to scan output if DEBUG: vmURL = TEST_VOLTMETER_SERVER else: vmURL = VOLTMETER_SERVER vmProtocol = yield getProtocol(vmURL) vmClient = VoltMeterClient(vmProtocol) vmWidget = VoltMeterOutputWidget(vmClient) outputPane.addTab(vmWidget,'voltmeter') ############################################################# BEGIN INPUTS ########################################################### # configure the input widget inputPane = QtGui.QTabWidget() inputPane.setTabPosition(inputPane.West) cpLayout.addWidget(LabelWidget('input',inputPane),1) # algorithm for scan inputs is: # 0. check to see if input is disabled # 1. create client for server from protocol object # 2. create combo widget to hold interval and list widgets # 3. create interval widget using client object, add to combo # 4. same for list widget # 5. add combo widget to base combo widget (resulting in 2-D tab widget) ############################################################# MANUAL INPUT ########################################################### class ManualInputWidget(QtGui.QWidget): def __init__(self,parentWidget): QtGui.QWidget.__init__(self) self.parentWidget = parentWidget self.done = False def initScan(self): return def checkIfDone(self): return def next(self): result, valid = QtGui.QInputDialog.getDouble( self.parentWidget, 'next x value', 'enter next x value', decimals=6 ) if valid: return result else: return None def cancel(self): return inputPane.addTab( ManualInputWidget(self), 'manual' ) ############################################################# MANUAL SCAN INPUT ########################################################### class ManualScanInputWidget(InputWidget): def __init__(self,parent): spinBoxProps = { 'rangeMin':-100000, 'rangeMax':100000, 'stepMin':.000001, 'stepMax':100000, 'startInit':0, 'stopInit':1, 'stepSizeInit':.1 } def setPosition(position): msgBox = QtGui.QMessageBox() msgBox.setText("next position:\t"+str(position)) msgBox.setStandardButtons(QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel) msgBox.setDefaultButton(QtGui.QMessageBox.Ok) ret = msgBox.exec_() if ret == QtGui.QMessageBox.Ok: return position elif ret == QtGui.QMessageBox.Cancel: return None InputWidget.__init__(self,lambda(x):None,setPosition,spinBoxProperties = spinBoxProps) inputPane.addTab( ManualScanInputWidget(self), 'manual scan' ) ############################################################# STEPPER MOTOR INPUTS ########################################################### # load in the stepper motor names from config.steppermotor import KDP, BBO, PDL, LID, POL # get stepper motor protocol (how to communicate to stepper motor server) smProtocol = yield getProtocol( TEST_STEPPER_MOTOR_SERVER if DEBUG else STEPPER_MOTOR_SERVER ) # define a chunked (cancellable) client for each stepper motor stepperMotorsClients = {} for stepperMotorName in (KDP,BBO,PDL,LID,POL): stepperMotorsClients[stepperMotorName] = ChunkedStepperMotorClient(smProtocol,stepperMotorName) # define an input widget for each stepper motor, each add to input pane for smID,smClient in stepperMotorsClients.items(): spinBoxProps = { 'rangeMin':-100000, 'rangeMax':100000, 'stepMin':1, 'stepMax':100000, 'startInit':0, 'stopInit':100, 'stepSizeInit':1 } thisInputWidget = InputWidget( smClient.getPosition, smClient.setPosition, cancelCommand = smClient.cancel, spinBoxProperties = spinBoxProps ) inputPane.addTab( thisInputWidget, smID ) ############################################################# WAVELENGTH SERVER INPUT ########################################################### ''' # add wavelength client to scan input wlProtocol = yield getProtocol( TEST_WAVELENGTH_SERVER if DEBUG else WAVELENGTH_SERVER ) wlClient = WavelengthClient(wlProtocol) spinBoxProps = { 'rangeMin':24100, 'rangeMax':25000, 'stepMin':.01, 'stepMax':900, 'startInit':24200, 'stopInit':24220, 'stepSizeInit':1 } wlInputWidget = InputWidget( wlClient.getWavelength, wlClient.setWavelength, cancelCommand = wlClient.cancelWavelengthSet, spinBoxProperties = spinBoxProps ) inputPane.addTab( wlInputWidget, 'wl' ) ''' ############################################################# POLARIZER SERVER INPUT ########################################################### ''' # get protocol polProtocol = yield getProtocol( TEST_POLARIZER_SERVER if DEBUG else POLARIZER_SERVER ) # define the client polClient = PolarizerClient(polProtocol) # set limits on spinboxes spinBoxProps = { 'rangeMin':-720, 'rangeMax':720, 'stepMin':.01, 'stepMax':720, 'startInit':0, 'stopInit':90, 'stepSizeInit':5 } polInputWidget = InputWidget( polClient.getAngle, polClient.setAngle, cancelCommand = polClient.cancelAngleSet, spinBoxProperties = spinBoxProps ) inputPane.addTab( polInputWidget, 'pol' ) ''' ############################################################# DDG INPUTS ########################################################### # load in the delay generator names from config.delaygenerator import MAV_PUMP_LAMP, MAV_PUMP_QSW, MAV_PROBE_LAMP, MAV_PROBE_QSW, MAV_NOZZLE # load in the delay generator limits from config.delaygenerator import MIN_DELAY, MAX_DELAY, DELAY_RES # get the delay generator protocol dgProtocol = yield getProtocol( TEST_DELAY_GENERATOR_SERVER if DEBUG else DELAY_GENERATOR_SERVER ) dgClient = DelayGeneratorClient(dgProtocol) # define an input widget for each delay generator, each add to input pane for dgName in (MAV_PUMP_LAMP, MAV_PUMP_QSW, MAV_PROBE_LAMP, MAV_PROBE_QSW, MAV_NOZZLE): spinBoxProps = { 'rangeMin':MIN_DELAY, 'rangeMax':MAX_DELAY, 'stepMin':DELAY_RES, 'stepMax':MAX_DELAY-MIN_DELAY, 'startInit':1, 'stopInit':1001, 'stepSizeInit':10 } # because there is one DG client for all DGs (unlike SM client), we use partial to map the client\ # commands to individual DGs so that we preserve as much of the same structure as possible. thisInputWidget = InputWidget( partial(dgClient.getDelay,dgName), partial(dgClient.setPartnerDelay,dgName), spinBoxProperties = spinBoxProps ) inputPane.addTab( thisInputWidget, dgName ) ############################################################# END INPUTS ########################################################### ############################################################# SCANNING ########################################################### #define fundamental scan logic: # 1: ask independent variable to change, wait # 2: measure dependent # 3: perform onStep task with x-y pair (e.g. update plot) class Scan: def __init__(self,inputWidget,outputWidget,onStepFunct,repeats=1,plotWidget=None): self.input = inputWidget self.output = outputWidget self.onStep = onStepFunct self.output.plot = plotWidget self.repeatTotal = repeats self.activeRepeat = 1 self.startScan() def startScan(self): if len(self.input.scanValues) == 0: self.input.initScan() self.output.initScan() self.paused = False self.done = False self.loop() @inlineCallbacks def loop(self): # pause if paused if self.paused: resumeCancelDialog = QtGui.QMessageBox() resumeCancelDialog.setText("the scan has been paused.") resumeCancelDialog.setInformativeText("do you want to cancel the scan?") resumeCancelDialog.setStandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) resumeCancelDialog.setDefaultButton(QtGui.QMessageBox.No) option = resumeCancelDialog.exec_() if option == QtGui.QMessageBox.Yes: #quit the scan self.cancel() return if option == QtGui.QMessageBox.No: #resume the scan self.resume() while self.paused: QtGui.QApplication.processEvents() sleep(.1) # check if done by asking input self.input.checkIfDone() inputDone = self.input.done # if done, finish to clean up if inputDone: self.finish() # if not done, continue onto next point & measure if not inputDone: inputData = yield self.input.next() if inputData == None: yield self.cancel() return else: self.output.setPlotterXVal(inputData) outputDataDefer = self.output.startAcquisition() outputData = yield outputDataDefer yield self.onStep(inputData,outputData) self.loop() def pause(self): self.paused = True def resume(self): self.paused = False def cancel(self): self.done = True self.input.cancel() self.output.cancel() scanToggleClicked() @inlineCallbacks def finish(self): if self.activeRepeat == self.repeatTotal: self.done = True yield self.input.cancel() self.output.cancel() scanToggleClicked() else: self.activeRepeat += 1 if self.activeRepeat % 2 == 0: self.input.initFlipScan() else: self.input.initScan() self.startScan() # define what to do after values are acquired at a position def onStepped(input,output): # unpack scan step data position, output = input, output if position == None or output == None: return else: # update data array if position not in self.dataMatrix.keys(): self.dataMatrix[position] = [] for value in output: self.dataMatrix[position].append(value) # update plot updatePlot() # define scanning start/pause/cancel logic def scanToggleClicked(): if self.scanning: #currently scanning so check if the scan is done, if not this was a pause if not self.thisScan.done: #pause the scan, pop resume/cancel dialog self.thisScan.pause() else: self.scanning = False scanToggleButton.setText("start") return if not self.scanning: #not currently scanning, so start the scan self.scanning = True #gather the agents (classes with specific methods) that progress scan and measure values inputAgent = inputPane.currentWidget() outputAgent = outputPane.currentWidget() updateAgent = onStepped #dump whatever data is in the matrix, prepare the plot for xVal in self.dataMatrix.keys(): del self.dataMatrix[xVal] # clear the lists that the plot uses to plot self.xVals = [] self.yVals = [] self.errVals = [] updatePlot() self.plotWidget.enableAutoRange() #define the scan, which automatically starts it numToRepeat = self.repeatSpinBox.value() self.thisScan = Scan(inputAgent, outputAgent, updateAgent, numToRepeat, self.plotWidget) #rename our button so users know about the other half of this function scanToggleButton.setText("pause/cancel") return self.scanning = False # set up the GUI to have the scan start/pause/cancel button and repeat spinbox scanPane = QtGui.QHBoxLayout() scanToggleButton = QtGui.QPushButton("start") scanToggleButton.clicked.connect(scanToggleClicked) scanPane.addWidget(scanToggleButton) self.repeatSpinBox = QtGui.QSpinBox() self.repeatSpinBox.setRange(1,10000) self.repeatSpinBox.setValue(1) scanPane.addWidget(self.repeatSpinBox) cpLayout.addWidget(LabelWidget('scan',scanPane)) ############################################################# LOAD FUNCTIONS ########################################################### refLayout = QtGui.QHBoxLayout() def onLoadClicked(): dir, filePrefix = filenameGen() dir = join(POOHDATAPATH,dir) refFileName = QtGui.QFileDialog.getOpenFileName(self,'select file', dir,"CSV Files (*.csv)") rData = np.loadtxt(open(refFileName[0],"rb"),delimiter=",") name = refFileName[0].rpartition('/')[2] color = QtGui.QColorDialog.getColor() if 'matrix' in refFileName[0]: xVals = rData[:,0] yVals = [] errVals = [] for rowNum in range(len(xVals)): thisYList = rData[rowNum,1:] yVals.append(np.mean(thisYList)) errVals.append(np.std(thisYList)/np.sqrt(len(thisYList))) self.refData[name] = { 'color': color, 'data': [xVals, yVals, errVals] } else: self.refData[name] = { 'color': color, 'data': [rData[:,0], rData[:,1], rData[:,2]] } updatePlot() loadButton = QtGui.QPushButton('load') loadButton.clicked.connect(onLoadClicked) refLayout.addWidget(SqueezeRow(loadButton)) def onClearClicked(): for refs in self.refData.keys(): del self.refData[refs] updatePlot() clearButton = QtGui.QPushButton('clear all') clearButton.clicked.connect(onClearClicked) refLayout.addWidget(SqueezeRow(clearButton)) cpLayout.addWidget(LabelWidget('reference',refLayout)) ############################################################# SAVE FUNCTIONS ########################################################### saveLayout = QtGui.QHBoxLayout() def onSaveRawClicked(): dataType = np.dtype(np.float32) orderedDataDict = OrderedDict(sorted(self.dataMatrix.items())) data = np.reshape(np.asarray(orderedDataDict.keys(),dtype=dataType),(len(orderedDataDict.keys()),1)) #just x values as a column yVals = np.asarray(orderedDataDict.values(),dtype=dataType) data = np.hstack((data,yVals)) saveFile(data,'matrix') saveRawButton = QtGui.QPushButton('save (raw)') saveRawButton.clicked.connect(onSaveRawClicked) saveLayout.addWidget(SqueezeRow(saveRawButton)) def onSaveStatsClicked(): xData = self.dataMatrix.keys() yData = [] errData = [] for rawValues in self.dataMatrix.values(): yData.append(np.mean(rawValues)) errData.append(np.std(rawValues)/np.sqrt(len(rawValues))) data = np.asarray([xData, yData, errData], dtype=np.dtype(np.float32)) saveFile(np.transpose(data),'stats') saveStatsButton = QtGui.QPushButton('save (stats)') saveStatsButton.clicked.connect(onSaveStatsClicked) saveLayout.addWidget(SqueezeRow(saveStatsButton)) def saveFile(dataToSave,prefix): dir, filePrefix = filenameGen() dir = join(POOHDATAPATH,dir) checkPath(dir) subDir = QtGui.QFileDialog.getExistingDirectory(self,'select folder', dir) desc, valid = QtGui.QInputDialog.getText(self, 'enter file description','description' ) if not valid: desc = None else: saveCSV(dataToSave, subDir=subDir, description=prefix+'_'+desc) cpLayout.addWidget(LabelWidget('save',saveLayout)) onInit() def closeEvent(self, event): if reactor.running: reactor.stop() event.accept()
class Plotter(QWidget): MAX_DATA_POINTS_PER_CURVE = 200000 COLORS = [Qt.red, Qt.green, Qt.blue, # RGB - http://ux.stackexchange.com/questions/79561 Qt.yellow, Qt.cyan, Qt.magenta, # Close to RGB Qt.darkRed, Qt.darkGreen, Qt.darkBlue, # Darker RGB Qt.darkYellow, Qt.darkCyan, Qt.darkMagenta, # Close to RGB Qt.gray, Qt.darkGray] # Leftovers INITIAL_X_RANGE = 60 def __init__(self, parent=None): # Parent super(Plotter, self).__init__(parent) self.setWindowTitle('UAVCAN Plotter') self.setWindowIcon(APP_ICON) # Redraw timer self._update_timer = QTimer() self._update_timer.timeout.connect(self._update) self._update_timer.setSingleShot(False) self._update_timer.start(30) # PyQtGraph self._plot_widget = PlotWidget() self._plot_widget.setBackground((0, 0, 0)) self._legend = self._plot_widget.addLegend() self._plot_widget.setRange(xRange=(0, self.INITIAL_X_RANGE), padding=0) self._plot_widget.showButtons() self._plot_widget.enableAutoRange() self._plot_widget.showGrid(x=True, y=True, alpha=0.4) # Controls # https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html button_add_matcher = QtGui.QPushButton('New matcher', self) button_add_matcher.setIcon(QtGui.QIcon.fromTheme('list-add')) button_add_matcher.setToolTip('Add new curve matcher') button_add_matcher.clicked.connect( lambda: NewCurveMatcherWindow(self, lambda: sorted(self._active_messages), self._add_curve_matcher).show()) button_clear_plots = QtGui.QPushButton('Clear plots', self) button_clear_plots.setIcon(QtGui.QIcon.fromTheme('edit-clear')) button_clear_plots.setToolTip('Clear the plotting area') button_clear_plots.clicked.connect(lambda: self._remove_all_curves()) def delete_all_matchers(): self._curve_matchers = [] for i in reversed(range(self._curve_matcher_container.count())): self._curve_matcher_container.itemAt(i).widget().deleteLater() self._remove_all_curves() button_delete_all_matchers = QtGui.QPushButton('Delete matchers', self) button_delete_all_matchers.setIcon(QtGui.QIcon.fromTheme('edit-delete')) button_delete_all_matchers.setToolTip('Delete all matchers') button_delete_all_matchers.clicked.connect(delete_all_matchers) self._autoscroll = QtGui.QCheckBox('Autoscroll', self) self._autoscroll.setChecked(True) self._max_x = self.INITIAL_X_RANGE # Layout control_panel = QHBoxLayout() control_panel.addWidget(button_add_matcher) control_panel.addWidget(button_clear_plots) control_panel.addWidget(self._autoscroll) control_panel.addStretch() control_panel.addWidget(button_delete_all_matchers) self._curve_matcher_container = QVBoxLayout() layout = QVBoxLayout() layout.addWidget(self._plot_widget, 1) layout.addLayout(control_panel) layout.addLayout(self._curve_matcher_container) self.setLayout(layout) # Logic self._color_index = 0 self._curves = {} self._message_queue = multiprocessing.Queue() self._active_messages = set() # set(data type name) self._curve_matchers = [] # Defaults self._add_curve_matcher(CurveMatcher('uavcan.protocol.debug.KeyValue', 'value', [('key', None)])) def _add_curve_matcher(self, matcher): self._curve_matchers.append(matcher) view = CurveMatcherView(matcher, self) def remove(): self._curve_matchers.remove(matcher) self._curve_matcher_container.removeWidget(view) view.setParent(None) view.deleteLater() view.on_remove = remove self._curve_matcher_container.addWidget(view) def _update(self): # Processing messages while True: try: m = self._message_queue.get_nowait() self._process_message(m) except queue.Empty: break # Updating curves for curve in self._curves.values(): if len(curve['x']): if len(curve['x']) > self.MAX_DATA_POINTS_PER_CURVE: curve['x'] = curve['x'][-self.MAX_DATA_POINTS_PER_CURVE:] curve['y'] = curve['y'][-self.MAX_DATA_POINTS_PER_CURVE:] assert len(curve['x']) == len(curve['y']) curve['plot'].setData(curve['x'], curve['y']) self._max_x = max(self._max_x, curve['x'][-1]) # Updating view range if self._autoscroll.checkState(): (xmin, xmax), _ = self._plot_widget.viewRange() diff = xmax - xmin xmax = self._max_x xmin = self._max_x - diff self._plot_widget.setRange(xRange=(xmin, xmax), padding=0) def _process_message(self, m): self._active_messages.add(m.data_type_name) for matcher in self._curve_matchers: if matcher.match(m): name, x, y = matcher.extract_curve_name_x_y(m) self._draw_curve(name, x, y) def _remove_all_curves(self): for curve in self._curves.values(): self._plot_widget.removeItem(curve['plot']) self._plot_widget.clear() self._curves = {} self._color_index = 0 self._legend.scene().removeItem(self._legend) self._legend = self._plot_widget.addLegend() def _draw_curve(self, name, x, y): if name not in self._curves: logging.info('Adding curve %r', name) color = self.COLORS[self._color_index % len(self.COLORS)] self._color_index += 1 pen = mkPen(QColor(color), width=1) plot = self._plot_widget.plot(name=name, pen=pen) self._curves[name] = {'x': numpy.array([]), 'y': numpy.array([]), 'plot': plot} curve = self._curves[name] curve['x'] = numpy.append(curve['x'], [x] if isinstance(x, (float, int)) else x) curve['y'] = numpy.append(curve['y'], [y] if isinstance(y, (float, int)) else y) assert len(curve['x']) == len(curve['y']) def push_received_message(self, msg): self._message_queue.put_nowait(msg)
class RealtimePlotWidget(QWidget): AUTO_RANGE_FRACTION = 0.99 COLORS = [ Qt.red, Qt.blue, Qt.green, Qt.magenta, Qt.cyan, Qt.darkRed, Qt.darkBlue, Qt.darkGreen, Qt.darkYellow, Qt.gray ] def __init__(self, display_measurements, parent): super(RealtimePlotWidget, self).__init__(parent) self.setAttribute( Qt.WA_DeleteOnClose) # This is required to stop background timers! self._plot_widget = PlotWidget() self._plot_widget.setBackground((0, 0, 0)) self._legend = self._plot_widget.addLegend() self._plot_widget.showButtons() self._plot_widget.showGrid(x=True, y=True, alpha=0.3) vbox = QVBoxLayout(self) vbox.addWidget(self._plot_widget) self.setLayout(vbox) self._last_update_ts = 0 self._reset_required = False self._update_timer = QTimer(self) self._update_timer.setSingleShot(False) self._update_timer.timeout.connect(self._update) self._update_timer.start(200) self._color_index = 0 self._curves = {} # Crosshair def _render_measurements(cur, ref): text = 'time %.6f sec, y %.6f' % cur if ref is None: return text dt = cur[0] - ref[0] dy = cur[1] - ref[1] if abs(dt) > 1e-12: freq = '%.6f' % abs(1 / dt) else: freq = 'inf' display_measurements(text + ';' + ' ' * 4 + 'dt %.6f sec, freq %s Hz, dy %.6f' % (dt, freq, dy)) display_measurements( 'Hover to sample Time/Y, click to set new reference') add_crosshair(self._plot_widget, _render_measurements) # Final reset self.reset() def _trigger_auto_reset_if_needed(self): ts = time.monotonic() dt = ts - self._last_update_ts self._last_update_ts = ts if dt > 2: self._reset_required = True def add_curve(self, curve_id, curve_name, data_x=[], data_y=[]): color = QColor(self.COLORS[self._color_index % len(self.COLORS)]) self._color_index += 1 pen = mkPen(color, width=1) plot = self._plot_widget.plot(name=curve_name, pen=pen) data_x = numpy.array(data_x) data_y = numpy.array(data_y) self._curves[curve_id] = {'data': (data_x, data_y), 'plot': plot} self._trigger_auto_reset_if_needed() def update_values(self, curve_id, x, y): curve = self._curves[curve_id] old_x, old_y = curve['data'] curve['data'] = numpy.append(old_x, x), numpy.append(old_y, y) self._trigger_auto_reset_if_needed() def reset(self): for curve in self._curves.keys(): self._plot_widget.removeItem(self._curves[curve]['plot']) self._curves = {} self._color_index = 0 self._plot_widget.enableAutoRange(enable=self.AUTO_RANGE_FRACTION, x=self.AUTO_RANGE_FRACTION, y=self.AUTO_RANGE_FRACTION) self._legend.scene().removeItem(self._legend) self._legend = self._plot_widget.addLegend() def _update(self): if self._reset_required: self.reset() self._reset_required = False for curve in self._curves.values(): if len(curve['data'][0]): curve['plot'].setData(*curve['data'])
class Canvas2Dupgraded(AbstractCanvas): def __init__(self, data_dict=None, parent=None): super().__init__(self) self.shareData(**data_dict) self._i = self.current_state self.triggered = True self.title = self.options['column'] self.synchronizedPlot = self.options['synchronizedPlot'] self.one_onePlot = self.options['one_one'] # Switch to using white background and black foreground pg.setConfigOption('background', 'w') pg.setConfigOption('foreground', 'k') self.plotWidget = PlotWidget(self) self.construct_triggered_plot() self.graph_data = self.plot_data[self.title].tolist() self.internal_iterations = len(self.graph_data) self.createPlotCanvas() def construct_triggered_plot(self): if self.trigger is not None and self.one_onePlot: self.triggered = False # shorter list self.plot_data = self.plot_data.iloc[self.trigger] """ this displays only datapoints that match exactly the view on the 3D animation i.e. one-one plot """ self.options['line_style'] = 'None' def createPlotCanvas(self): self.null_data = self.plot_data[self.options['xcolumn']].tolist() self.plotWidget.setTitle(self.title) self.plotWidget.setLabel('bottom', self.options['xcolumn']) self.plotWidget.setGeometry(0, 0, self.geom[0]-60, self.geom[1]-60) self.plotWidget.setXRange(0, self.internal_iterations) self.plotWidget.setYRange(np.min(self.graph_data), np.max(self.graph_data), padding=0.1) self.plotWidget.enableAutoRange('xy', True) if self.synchronizedPlot == False and not self.one_onePlot: self.plotData = self.plotWidget.plot(self.null_data, self.graph_data, pen=pg.mkPen(color=self.options['color'][0], width=self.options['marker_size']), name="data1", clear=True) else: self.plotData = self.plotWidget.plot(self.graph_data[:self._i], pen=pg.mkPen(color=self.options['color'][0], width=self.options['marker_size']), name="data1", clear=True) def on_resize_geometry_reset(self, geom): """ when another widget is promoted, this window must resize too this means resetting the graph unfortunately """ self.geom = geom self.plotWidget.setGeometry(0, 0, self.geom[0]-60, self.geom[1]-60) def set_i(self, value, trigger=False, record=False, reset=False): if self.synchronizedPlot == False and not self.one_onePlot: """ instant plot cannot obstruct one-one plot """ return if trigger and self.triggered: self._i = self.trigger[value] else: self._i = value self._i %= self.internal_iterations self.plotData.setData(self.null_data[:self._i], self.graph_data[:self._i]) pg.QtGui.QApplication.processEvents() self.plotWidget.setGeometry(0, 0, self.geom[0]-60, self.geom[1]-60)
class RealtimePlotWidget(QWidget): AUTO_RANGE_FRACTION = 0.99 COLORS = [Qt.red, Qt.blue, Qt.green, Qt.magenta, Qt.cyan, Qt.darkRed, Qt.darkBlue, Qt.darkGreen, Qt.darkYellow, Qt.gray] def __init__(self, display_measurements, parent): super(RealtimePlotWidget, self).__init__(parent) self.setAttribute(Qt.WA_DeleteOnClose) # This is required to stop background timers! self._plot_widget = PlotWidget() self._plot_widget.setBackground((0, 0, 0)) self._legend = self._plot_widget.addLegend() self._plot_widget.showButtons() self._plot_widget.showGrid(x=True, y=True, alpha=0.3) vbox = QVBoxLayout(self) vbox.addWidget(self._plot_widget) self.setLayout(vbox) self._last_update_ts = 0 self._reset_required = False self._update_timer = QTimer(self) self._update_timer.setSingleShot(False) self._update_timer.timeout.connect(self._update) self._update_timer.start(200) self._color_index = 0 self._curves = {} # Crosshair def _render_measurements(cur, ref): text = 'time %.6f sec, y %.6f' % cur if ref is None: return text dt = cur[0] - ref[0] dy = cur[1] - ref[1] if abs(dt) > 1e-12: freq = '%.6f' % abs(1 / dt) else: freq = 'inf' display_measurements(text + ';' + ' ' * 4 + 'dt %.6f sec, freq %s Hz, dy %.6f' % (dt, freq, dy)) display_measurements('Hover to sample Time/Y, click to set new reference') add_crosshair(self._plot_widget, _render_measurements) # Final reset self.reset() def _trigger_auto_reset_if_needed(self): ts = time.monotonic() dt = ts - self._last_update_ts self._last_update_ts = ts if dt > 2: self._reset_required = True def add_curve(self, curve_id, curve_name, data_x=[], data_y=[]): color = QColor(self.COLORS[self._color_index % len(self.COLORS)]) self._color_index += 1 pen = mkPen(color, width=1) plot = self._plot_widget.plot(name=curve_name, pen=pen) data_x = numpy.array(data_x) data_y = numpy.array(data_y) self._curves[curve_id] = {'data': (data_x, data_y), 'plot': plot} self._trigger_auto_reset_if_needed() def update_values(self, curve_id, x, y): curve = self._curves[curve_id] old_x, old_y = curve['data'] curve['data'] = numpy.append(old_x, x), numpy.append(old_y, y) self._trigger_auto_reset_if_needed() def reset(self): for curve in self._curves.keys(): self._plot_widget.removeItem(self._curves[curve]['plot']) self._curves = {} self._color_index = 0 self._plot_widget.enableAutoRange(enable=self.AUTO_RANGE_FRACTION, x=self.AUTO_RANGE_FRACTION, y=self.AUTO_RANGE_FRACTION) self._legend.scene().removeItem(self._legend) self._legend = self._plot_widget.addLegend() def _update(self): if self._reset_required: self.reset() self._reset_required = False for curve in self._curves.values(): if len(curve['data'][0]): curve['plot'].setData(*curve['data'])