class MainGroupCti(GroupCti): """ Read only config Tree Item that only stores None. To be used as a high level group (e.g. the inspector group) Is the same as a groupCti but drawn as light text on a dark grey back ground """ _backgroundBrush = QtGui.QBrush( QtGui.QColor("#606060")) # create only once _foregroundBrush = QtGui.QBrush(QtGui.QColor(Qt.white)) # create only once _font = QtGui.QFont() _font.setWeight(QtGui.QFont.Bold) def __init__(self, nodeName, defaultData=None): """ Constructor. For the parameters see the AbstractCti constructor documentation. """ super(MainGroupCti, self).__init__(nodeName, defaultData, expanded=True) # always expand @property def font(self): """ Returns a font for displaying this item's text in the tree. """ return self._font @property def backgroundBrush(self): """ Returns a (dark gray) brush for drawing the background role in the tree. """ return self._backgroundBrush @property def foregroundBrush(self): """ Returns a (white) brush for drawing the foreground role in the tree. """ return self._foregroundBrush
def __init__(self, store, parent=None): """ Constructor. :param store: Underlying data store, must descent from BaseRegistry :param parent: Parent widget """ super(BaseRegistryModel, self).__init__(store, parent) check_class(store, BaseRegistry) self.regularBrush = QtGui.QBrush(QCOLOR_REGULAR) self.notImportedBrush = QtGui.QBrush(QCOLOR_NOT_IMPORTED) self.errorBrush = QtGui.QBrush(QCOLOR_ERROR)
def __init__(self, registry, attrNames = ('fullName', ), parent=None): """ Constructor. :param registry: Underlying registry. Must descent from ClassRegistry :param attrNames: List of attributes that will be displayed (def. only the fullName). :param parent: Parent widget """ super(RegistryTableModel, self).__init__(parent) check_class(registry, ClassRegistry) self.registry = registry self.attrNames = attrNames self.regularBrush = QtGui.QBrush(QCOLOR_REGULAR) self.notImportedBrush = QtGui.QBrush(QCOLOR_NOT_IMPORTED) self.errorBrush = QtGui.QBrush(QCOLOR_ERROR)
def createPlotDataItem(self): """ Creates a PyQtGraph PlotDataItem from the config values """ antialias = self.antiAliasCti.configValue color = self.penColor if self.lineCti.configValue: pen = QtGui.QPen() pen.setCosmetic(True) pen.setColor(color) pen.setWidthF(self.lineWidthCti.configValue) pen.setStyle(self.lineStyleCti.configValue) shadowCti = self.lineCti.findByNodePath('shadow') shadowPen = shadowCti.createPen(altStyle=pen.style(), altWidth=2.0 * pen.widthF()) else: pen = None shadowPen = None drawSymbols = self.symbolCti.configValue symbolShape = self.symbolShapeCti.configValue if drawSymbols else None symbolSize = self.symbolSizeCti.configValue if drawSymbols else 0.0 symbolPen = None # otherwise the symbols will also have dotted/solid line. symbolBrush = QtGui.QBrush(color) if drawSymbols else None plotDataItem = pg.PlotDataItem(antialias=antialias, pen=pen, shadowPen=shadowPen, symbol=symbolShape, symbolSize=symbolSize, symbolPen=symbolPen, symbolBrush=symbolBrush) return plotDataItem
def _drawContents(self, reason=None, initiator=None): """ Draws the plot contents from the sliced array of the collected repo tree item. The reason parameter is used to determine if the axes will be reset (the initiator parameter is ignored). See AbstractInspector.updateContents for their description. """ self.slicedArray = self.collector.getSlicedArray() if not self._hasValidData(): self._clearContents() raise InvalidDataError( "No data available or it does not contain real numbers") # -- Valid plot data from here on -- # PyQtGraph doesn't handle masked arrays so we convert the masked values to Nans (missing # data values are replaced by NaNs). The PyQtGraph line plot omits the Nans, which is great. self.slicedArray.replaceMaskedValueWithNan( ) # will convert data to float if int self.plotItem.clear() # Reset the axes ranges (via the config) if (reason == UpdateReason.RTI_CHANGED or reason == UpdateReason.COLLECTOR_COMBO_BOX): # self.config.yAxisRangeCti.setAutoRangeOn() doesn't work as refreshBlocked is True # TODO: can refreshBlocked maybe only block the signals to prevent loops? self.config.xAxisRangeCti.autoRangeCti.data = True self.config.yAxisRangeCti.autoRangeCti.data = True self.titleLabel.setText( self.configValue('title').format(**self.collector.rtiInfo)) connected = np.isfinite(self.slicedArray.data) if is_an_array(self.slicedArray.mask): connected = np.logical_and(connected, ~self.slicedArray.mask) else: connected = np.zeros_like( self.slicedArray.data) if self.slicedArray.mask else connected plotDataItem = self.config.plotDataItemCti.createPlotDataItem() plotDataItem.setData(self.slicedArray.data, connect=connected) self.plotItem.addItem(plotDataItem) if self.config.probeCti.configValue: self.probeLabel.setVisible(True) self.plotItem.addItem(self.crossLineVerShadow, ignoreBounds=True) self.plotItem.addItem(self.crossLineVertical, ignoreBounds=True) self.plotItem.addItem(self.probeDataItem, ignoreBounds=True) self.probeDataItem.setSymbolBrush( QtGui.QBrush(self.config.plotDataItemCti.penColor)) self.probeDataItem.setSymbolSize(10) else: self.probeLabel.setVisible(False) # Update the config tree from the (possibly) new state of the PgLinePlot1d inspector, # e.g. the axis range may have changed while drawing. self.config.updateTarget()
def mouseMoved(self, viewPos): """ Updates the probe text with the values under the cursor. Draws a vertical line and a symbol at the position of the probe. """ try: check_class(viewPos, QtCore.QPointF) show_data_point = False # shows the data point as a circle in the cross hair plots self.crossPlotRow, self.crossPlotCol = None, None self.probeLabel.setText( "<span style='color: #808080'>No data at cursor</span>") self.crossLineHorizontal.setVisible(False) self.crossLineVertical.setVisible(False) self.crossLineHorShadow.setVisible(False) self.crossLineVerShadow.setVisible(False) self.horCrossPlotItem.clear() self.verCrossPlotItem.clear() if self.slicedArray is not None and self.viewBox.sceneBoundingRect( ).contains(viewPos): # Calculate the row and column at the cursor. scenePos = self.viewBox.mapSceneToView(viewPos) row, col = round(scenePos.y()), round(scenePos.x()) row, col = int(row), int(col) # Needed in Python 2 nRows, nCols = self.slicedArray.shape if (0 <= row < nRows) and (0 <= col < nCols): self.viewBox.setCursor(Qt.CrossCursor) self.crossPlotRow, self.crossPlotCol = row, col index = tuple([row, col]) valueStr = to_string(self.slicedArray.data[index], masked=self.slicedArray.maskAt(index), maskFormat='<masked>') txt = "({}, {}) = ({:d}, {:d}) {} {} = {}".format( self.collector.rtiInfo['x-dim'], self.collector.rtiInfo['y-dim'], col, row, RIGHT_ARROW, self.collector.rtiInfo['name'], valueStr) self.probeLabel.setText(txt) # Show cross section at the cursor pos in the line plots if self.config.horCrossPlotCti.configValue: self.crossLineHorShadow.setVisible(True) self.crossLineHorizontal.setVisible(True) self.crossLineHorShadow.setPos(row) self.crossLineHorizontal.setPos(row) # Line plot of cross section row. # First determine which points are connected or separated by masks/nans. rowData = self.slicedArray.data[row, :] connected = np.isfinite(rowData) if is_an_array(self.slicedArray.mask): connected = np.logical_and( connected, ~self.slicedArray.mask[row, :]) else: connected = (np.zeros_like(rowData) if self.slicedArray.mask else connected) # Replace mask by Nans. Only doing when not showing lines to hack around PyQtGraph issue 1057 # See comment in PgLinePlot1d._drawContents for a more detailed explanation # TODO: reuse imageItem data array when this hack is no longer necessary if not self.config.crossPenCti.lineCti.configValue: rowData = replaceMaskedValueWithFloat( rowData, np.logical_not(connected), np.nan, copyOnReplace=True) # Replace infinite value with nans because PyQtGraph can't handle them rowData = replaceMaskedValueWithFloat( rowData, np.isinf(rowData), np.nan, copyOnReplace=True) horPlotDataItem = self.config.crossPenCti.createPlotDataItem( ) # TODO: try to use connect='finite' when the hack above is no longer necessary. In that case # test with array_masked test data horPlotDataItem.setData(rowData, connect=connected) self.horCrossPlotItem.addItem(horPlotDataItem) # Vertical line in hor-cross plot crossLineShadow90 = pg.InfiniteLine( angle=90, movable=False, pen=self.crossShadowPen) crossLineShadow90.setPos(col) self.horCrossPlotItem.addItem(crossLineShadow90, ignoreBounds=True) crossLine90 = pg.InfiniteLine(angle=90, movable=False, pen=self.crossPen) crossLine90.setPos(col) self.horCrossPlotItem.addItem(crossLine90, ignoreBounds=True) if show_data_point: crossPoint90 = pg.PlotDataItem( symbolPen=self.crossPen) crossPoint90.setSymbolBrush( QtGui.QBrush(self.config.crossPenCti.penColor)) crossPoint90.setSymbolSize(10) crossPoint90.setData((col, ), (rowData[col], )) self.horCrossPlotItem.addItem(crossPoint90, ignoreBounds=True) self.config.horCrossPlotRangeCti.updateTarget( ) # update auto range del rowData # defensive programming if self.config.verCrossPlotCti.configValue: self.crossLineVerShadow.setVisible(True) self.crossLineVertical.setVisible(True) self.crossLineVerShadow.setPos(col) self.crossLineVertical.setPos(col) # Line plot of cross section row. # First determine which points are connected or separated by masks/nans. colData = self.slicedArray.data[:, col] connected = np.isfinite(colData) if is_an_array(self.slicedArray.mask): connected = np.logical_and( connected, ~self.slicedArray.mask[:, col]) else: connected = (np.zeros_like(colData) if self.slicedArray.mask else connected) # Replace mask by Nans. Only doing when not showing lines to hack around PyQtGraph issue 1057 # See comment in PgLinePlot1d._drawContents for a more detailed explanation if not self.config.crossPenCti.lineCti.configValue: colData = replaceMaskedValueWithFloat( colData, np.logical_not(connected), np.nan, copyOnReplace=True) # Replace infinite value with nans because PyQtGraph can't handle them colData = replaceMaskedValueWithFloat( colData, np.isinf(colData), np.nan, copyOnReplace=True) verPlotDataItem = self.config.crossPenCti.createPlotDataItem( ) verPlotDataItem.setData(colData, np.arange(nRows), connect=connected) self.verCrossPlotItem.addItem(verPlotDataItem) # Horizontal line in ver-cross plot crossLineShadow0 = pg.InfiniteLine( angle=0, movable=False, pen=self.crossShadowPen) crossLineShadow0.setPos(row) self.verCrossPlotItem.addItem(crossLineShadow0, ignoreBounds=True) crossLine0 = pg.InfiniteLine(angle=0, movable=False, pen=self.crossPen) crossLine0.setPos(row) self.verCrossPlotItem.addItem(crossLine0, ignoreBounds=True) if show_data_point: crossPoint0 = pg.PlotDataItem( symbolPen=self.crossPen) crossPoint0.setSymbolBrush( QtGui.QBrush(self.config.crossPenCti.penColor)) crossPoint0.setSymbolSize(10) crossPoint0.setData((colData[row], ), (row, )) self.verCrossPlotItem.addItem(crossPoint0, ignoreBounds=True) self.config.verCrossPlotRangeCti.updateTarget( ) # update auto range del colData # defensive programming except Exception as ex: # In contrast to _drawContents, this function is a slot and thus must not throw # exceptions. The exception is logged. Perhaps we should clear the cross plots, but # this could, in turn, raise exceptions. if DEBUGGING: raise else: logger.exception(ex)
def _drawContents(self, reason=None, initiator=None): """ Draws the plot contents from the sliced array of the collected repo tree item. The reason parameter is used to determine if the axes will be reset (the initiator parameter is ignored). See AbstractInspector.updateContents for their description. """ # If auto-reset is true, reset config complete or partially, depending on the mode. if self._resetRequired(reason, initiator): self.resetConfig() self.slicedArray = self.collector.getSlicedArray() slicedArray = self.collector.getSlicedArray() if slicedArray is None: self._clearContents() raise InvalidDataError() # Don't show message, to common. elif not array_has_real_numbers(slicedArray.data): self._clearContents() raise InvalidDataError("Selected item contains {} data.".format( array_kind_label(slicedArray.data))) else: self.slicedArray = slicedArray # -- Valid plot data from here on -- numElem = np.prod(self.slicedArray.data.shape) if numElem == 0: self.sigShowMessage.emit( "Current slice is empty.") # Not expected to happen. elif numElem == 1: self.sigShowMessage.emit( "Current slice contains only a single data point.") # PyQtGraph doesn't handle masked arrays so we convert the masked values to Nans (missing # data values are replaced by NaNs). The PyQtGraph line plot omits the Nans, which is great. # Update: in newer version of Qt the Nans are no longer printed, see PyQtGraph issue 1057, # https://github.com/pyqtgraph/pyqtgraph/issues/1057 # When showing lines we therefore don't replace the Nans and let the setData connect parameter be responsible # for omitting the masked data. When showing only symbols the masked values are replaced. When both symbols # wnd lines are shown the resulting plot is incorrect as the masked values are not replaced and thus displayed # as point. This is unfortunate but can't be helped until the issue is resolved in PyQtGraph. if not self.config.plotDataItemCti.lineCti.configValue: self.slicedArray.replaceMaskedValueWithNan( ) # will convert data to float if int self.plotItem.clear() self.titleLabel.setText( self.configValue('title').format(**self.collector.rtiInfo)) connected = np.isfinite(self.slicedArray.data) if is_an_array(self.slicedArray.mask): connected = np.logical_and(connected, ~self.slicedArray.mask) else: connected = np.zeros_like( self.slicedArray.data) if self.slicedArray.mask else connected plotDataItem = self.config.plotDataItemCti.createPlotDataItem() plotDataItem.setData(self.slicedArray.data, connect=connected) if plotDataItem.opts['pen'] is None and plotDataItem.opts[ 'symbol'] is None: self.sigShowMessage.emit( "The 'line' and 'symbol' config options are both unchecked!") self.plotItem.addItem(plotDataItem) if self.config.probeCti.configValue: self.probeLabel.setVisible(True) self.plotItem.addItem(self.crossLineVerShadow, ignoreBounds=True) self.plotItem.addItem(self.crossLineVertical, ignoreBounds=True) self.plotItem.addItem(self.probeDataItem, ignoreBounds=True) self.probeDataItem.setSymbolBrush( QtGui.QBrush(self.config.plotDataItemCti.penColor)) self.probeDataItem.setSymbolSize(10) else: self.probeLabel.setVisible(False) self.plotItem.setRectangleZoomOn(self.config.zoomModeCti.configValue) self.config.updateTarget()