class SweepDataPlot(GraphicsView): GREEN = [0, 204, 153] BLUE = [100, 171, 246] RED = [221, 61, 53] PURPLE = [175, 122, 197] ASH = [52, 73, 94] GRAY = [178, 186, 187] COLORS = [BLUE, RED, GREEN, PURPLE, ASH, GRAY] if sys.platform == 'darwin': LW = 3 else: LW = 1.5 def __init__(self): GraphicsView.__init__(self) # create layout self.layout = pg.GraphicsLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setSpacing(-1.) self.setBackground(None) self.setCentralItem(self.layout) # create axes and apply formatting axisItems = dict() for pos in ['bottom', 'left', 'top', 'right']: axisItems[pos] = AxisItem(orientation=pos, maxTickLength=-7) self.p = PlotItem(axisItems=axisItems) self.setTitle('Sweep data', fontScaling=1.3, color='k') self.layout.addItem(self.p) self.p.vb.setBackgroundColor('w') self.p.setContentsMargins(10, 10, 10, 10) for pos in ['bottom', 'left', 'top', 'right']: ax = self.p.getAxis(pos) ax.setZValue(0) # draw on top of patch ax.setVisible(True) # make all axes visible ax.setPen(width=self.LW * 2 / 3, color=0.5) # grey spines and ticks try: ax.setTextPen('k') # black text except AttributeError: pass ax.setStyle(autoExpandTextSpace=True, tickTextOffset=4) self.p.getAxis('top').setTicks([]) self.p.getAxis('top').setHeight(0) self.p.getAxis('right').setTicks([]) self.x_axis = self.p.getAxis('bottom') self.y_axis = self.p.getAxis('left') self.x_axis.setLabel('Voltage', units='V', color='k', size='12pt') self.y_axis.setLabel('Current', units='A', color='k', size='12pt') self.y_axis.setStyle(tickTextWidth=35) # set auto range and mouse panning / zooming self.p.enableAutoRange(x=True, y=True) self.p.setLimits(xMin=-1e20, xMax=1e20, yMin=-1e20, yMax=1e20) def suggestPadding(axis): length = self.p.vb.width() if axis == 0 else self.p.vb.height() if length > 0: if axis == 0: padding = 0 else: padding = np.clip(1. / (length**0.5), 0.02, 0.1) else: padding = 0.02 return padding self.p.vb.suggestPadding = suggestPadding # set default ranges to start self.p.setXRange(-10, 10) self.p.setYRange(-10, 10) # add legend self.legend = LegendItem(brush=fn.mkBrush(255, 255, 255, 150), labelTextColor='k', offset=(20, -20)) self.legend.setParentItem(self.p.vb) def clear(self): self.p.clear() # clear current plot self.legend.clear() # clear current legend def plot(self, sweep_data): self.clear() xdata = sweep_data.get_column(0) xdata_title = sweep_data.titles[0] ydata = sweep_data.values()[1:] # format plot according to sweep type unit = xdata_title.unit if xdata_title.has_unit() else 'a.u.' self.x_axis.setLabel(xdata_title.name, unit=unit) self.y_axis.setLabel('Current', unit='A') if sweep_data.params['sweep_type'] == 'transfer': self.setTitle('Transfer curve') self.p.setLogMode(x=False, y=True) self.legend.setOffset((20, -20)) # legend in bottom-left corner ydata = [np.abs(y) for y in ydata] elif sweep_data.params['sweep_type'] == 'output': self.setTitle('Output curve') self.p.setLogMode(x=False, y=False) self.legend.setOffset((-20, 20)) # legend in top-right corner ydata = [np.abs(y) for y in ydata] else: self.setTitle('Sweep curve') self.p.setLogMode(x=False, y=False) ydata = [np.abs(y) for y in ydata] # plot data self.lines = [] for y, c in zip(ydata, itertools.cycle(self.COLORS)): p = self.p.plot(xdata, y, pen=fn.mkPen(color=c, width=self.LW)) self.lines.append(p) # add legend for l, t in zip(self.lines, sweep_data.column_names[1:]): self.legend.addItem(l, str(t)) self.p.autoRange() def setTitle(self, text, fontScaling=None, color=None, font=None): # work around pyqtplot which forces the title to be HTML if text is None: self.p.setTitle(None) # clears title and hides title column else: self.p.setTitle( '') # makes title column visible, sets placeholder text self.p.titleLabel.item.setPlainText( text) # replace HTML with plain text if color is not None: color = fn.mkColor(color) self.p.titleLabel.item.setDefaultTextColor(color) if font is not None: self.p.titleLabel.item.setFont(font) if fontScaling is not None: font = self.p.titleLabel.item.font() defaultFontSize = QtWidgets.QLabel('test').font().pointSize() fontSize = round(defaultFontSize * fontScaling, 1) font.setPointSize(fontSize) self.p.titleLabel.item.setFont(font)
class ExtendedPlotItem(PlotItem): def __init__(self, *args, **kwargs): """ Create a new PlotItem, same as a base plotitem, but with a few extra pieces of functionality. - Keep track of images and allow proxying - Use a custom view box, such that we can do common tasks """ if 'viewBox' not in kwargs: vb = CustomViewBox() kwargs['viewBox'] = vb super().__init__(*args, **kwargs) # Keep track of context menus for items in this plot self.itemMenus = {} def addItem(self, item, *args, **kwargs): super().addItem(item, *args, **kwargs) if isinstance(item, ImageItem): # addItem does not keep track of images, let's add it ourselves self.dataItems.append(item) def plot(self, *args, **kargs): """ Reimplements PlotItem.plot to use ExtendedPlotDataItems. Add and return a new plot. See :func:`PlotDataItem.__init__ <pyqtgraph.PlotDataItem.__init__>` for data arguments Extra allowed arguments are: clear - clear all plots before displaying new data params - meta-parameters to associate with this data """ clear = kargs.get('clear', False) params = kargs.get('params', None) if clear: self.clear() item = ExtendedPlotDataItem(*args, **kargs) if params is None: params = {} self.addItem(item, params=params) return item def makeTracesDifferent(self, saturation=0.8, value=0.9, items=None): """ Color each of the traces in a plot a different color """ if items is None: items = self.listDataItems() items = [x for x in items if isinstance(x, PlotDataItem)] ntraces = len(items) for i, trace in enumerate(items): color = colorsys.hsv_to_rgb(i / ntraces, saturation, value) color = tuple(int(c * 255) for c in color) trace.setPen(*color) def addLegend(self, size=None, offset=(30, 30)): """ Reimplement addLegend to check if legend already exists. The default one should do this, but doesn't seem to work on our extended version for some reason? """ if self.legend is None: self.legend = LegendItem(size, offset) self.legend.setParentItem(self.vb) return self.legend
class Network(Base, metaclass=config.Singleton): """ Network performance measure """ def __init__(self, base): super(Network, self).__init__(base) self._out_val = None self._inc_val = None self._plot() self._clear() def _plot(self): """ Network performance graph """ setConfigOption('background', '#FF000000') _graph = PlotWidget() _graph.setMenuEnabled(enableMenu=False) _graph.setMouseEnabled(x=False, y=False) _graph.hideButtons() self._out_curve = _graph.getPlotItem().plot() self._inc_curve = _graph.getPlotItem().plot() self._legend = LegendItem(offset=(50, 10)) self._legend.setParentItem(_graph.getPlotItem()) self._legend.addItem(self._out_curve, self._lang.NetworkGraphOutgoing) self._legend.addItem(self._inc_curve, self._lang.NetworkGraphIncoming) self._base.net_perf_lyt.addWidget(_graph) self._menu_popup = QMenu() _action = QAction(QIcon('icon/reload.png'), self._lang.PopupReload, self._base.net_perf_box) _action.triggered.connect(self.reload) self._menu_popup.addAction(_action) self._base.net_perf_box.setContextMenuPolicy(Qt.CustomContextMenu) self._base.net_perf_box.customContextMenuRequested.connect( self._popup_show) def _set_value(self): """ Set send and receive value for the network performance curves """ self._out_curve.setData( self._out_val, pen=mkPen(width=self._sw_config.net_perf_out_wd, color=QColor(self._sw_config.net_perf_out_cl))) self._inc_curve.setData( self._inc_val, pen=mkPen(width=self._sw_config.net_perf_inc_wd, color=QColor(self._sw_config.net_perf_inc_cl))) async def _load(self): """ Load the network performance measure - coroutine """ if not self._sw_config.net_perf: return self._event.set() _old_out = 0 _old_inc = 0 while not self._event.is_set(): _rev_out = net_io_counters().bytes_sent _rev_int = net_io_counters().bytes_recv if _old_out and _old_inc: self._out_val.append(_rev_out - _old_out) self._inc_val.append(_rev_int - _old_inc) self._set_value() _old_out = _rev_out _old_inc = _rev_int await sleep(self._sw_config.net_perf_tmo) def _popup_show(self, coordinate): """ Right-click popup menu in the graph area """ self._menu_popup.exec_(self._base.net_perf_box.mapToGlobal(coordinate)) def _clear(self): """ Clear the entire graph """ self._out_val = deque([0] * self._sw_config.net_perf_len, maxlen=self._sw_config.net_perf_len) self._inc_val = deque([0] * self._sw_config.net_perf_len, maxlen=self._sw_config.net_perf_len) self._set_value()