def _createLabels(self, points, viewbox, logMode): for x, y in points: # fill the text xtext = self._getXText(x) ytext = self._getYText(y) text_item = TextItem() text_item.setHtml(("<div style='{}'> " + "<span><b>x=</b>{} " + "<span><b>y=</b>{}</span> " + "</div>").format( self._label_style, xtext, ytext)) # add text_item in the right position (take into account log mode) if logMode[0]: x = numpy.log10(x) if logMode[1]: y = numpy.log10(y) text_item.setPos(x, y) self._labels.append(text_item) viewbox.addItem(text_item, ignoreBounds=True) # Add "highlight" marker at each point highlight = PlotDataItem( numpy.array(points), pen=None, symbol="s", symbolBrush="35393C88", pxMode=True, symbolSize=self.trigger_point_size, ) # set log mode highlight.setLogMode(*logMode) # hack to make the CurvesPropertiesTool ignore the highlight points highlight._UImodifiable = False # Add it to the vbox and keep a reference viewbox.addItem(highlight, ignoreBounds=True) self._highlights.append(highlight)
def _createLabels(self, points): for x, y in points: # create label at x,y _x = self._getXValue(x) _y = self._getYValue(y) text_item = TextItem() text_item.setPos(x, y) text_item.setHtml(("<div style='{}'> " + "<span><b>x=</b>{} " + "<span><b>y=</b>{}</span> " + "</div>").format( self._label_style, _x, _y)) self._labels.append(text_item) self._plot_item.addItem(text_item, ignoreBounds=True) # Add "highlight" marker at each point self._highlights.setData(pos=points)
class MapViewWidget(DynImageView): sigShowSpectra = Signal(int) def __init__(self, *args, **kwargs): super(MapViewWidget, self).__init__(*args, **kwargs) # self.scene.sigMouseMoved.connect(self.showSpectra) self.scene.sigMouseClicked.connect(self.showSpectra) self.view.invertY(True) # add arrow # self.arrow = ArrowItem(angle=60, headLen=15, tipAngle=45, baseAngle=30, brush = (200, 80, 20)) # self.arrow.setPos(0, 0) self.cross = PlotDataItem([0], [0], symbolBrush=(200, 0, 0), symbolPen=(200, 0, 0), symbol='+', symbolSize=16) self.view.addItem(self.cross) self.cross.hide() #add txt self.txt = TextItem('', anchor=(0, 0)) self.addItem(self.txt) def setEnergy(self, lineobject): E = lineobject.value() # map E to index i = val2ind(E, self.wavenumbers) # print('E:', E, 'wav:', self.wavenumbers[i]) self.setCurrentIndex(i) def showSpectra(self, event): pos = event.pos() if self.view.sceneBoundingRect().contains( pos ): # Note, when axes are added, you must get the view with self.view.getViewBox() mousePoint = self.view.mapSceneToView(pos) x, y = int(mousePoint.x()), int(mousePoint.y()) y = self.row - y - 1 try: ind = self.rc2ind[(y, x)] self.sigShowSpectra.emit(ind) # print(x, y, ind, x + y * self.n_col) #update arrow self.cross.setData([x + 0.5], [self.row - y - 0.5]) self.cross.show() # update text self.txt.setHtml( f'<div style="text-align: center"><span style="color: #FFF; font-size: 8pt">X: {x}</div>\ <div style="text-align: center"><span style="color: #FFF; font-size: 8pt">Y: {y}</div>\ <div style="text-align: center"><span style="color: #FFF; font-size: 8pt">Point: #{ind}</div>' ) except Exception: self.cross.hide() def setHeader(self, header: NonDBHeader, field: str, *args, **kwargs): self.header = header self.field = field imageEvent = next(header.events(fields=['image'])) self.rc2ind = imageEvent['rc_index'] self.wavenumbers = imageEvent['wavenumbers'] # make lazy array from document data = None try: data = header.meta_array(field) self.row = data.shape[1] self.col = data.shape[2] self.txt.setPos(self.col, 0) except IndexError: msg.logMessage( 'Header object contained no frames with field ' '{field}' '.', msg.ERROR) if data is not None: # kwargs['transform'] = QTransform(1, 0, 0, -1, 0, data.shape[-2]) self.setImage(img=data, *args, **kwargs) self._data = data def updateImage(self, autoHistogramRange=True): super(MapViewWidget, self).updateImage(autoHistogramRange) self.ui.roiPlot.setVisible(False) def setImage(self, img, **kwargs): super(MapViewWidget, self).setImage(img, **kwargs) self.ui.roiPlot.setVisible(False) def makeMask(self, thresholds): peak1550 = val2ind(1550, self.wavenumbers) thr1550 = thresholds[0] mask = self._data[peak1550] > thr1550 mask = mask.astype(np.int) return mask
class DoubleCrossHair(QObject): """ Double cross hair implementation """ def __init__(self, x_1: ndarray, y_1: ndarray, x_2: ndarray, y_2: ndarray, plot_widget_1: PlotWidget, plot_widget_2: PlotWidget, labels: tuple, units=None, precision=0, width=1, x_labels=None, display=int, highlight_bars=True): """ Constructor / Instantiation of class Parameters ---------- x_1 : np.ndarray x-values y_1 : np.ndarray y-values x_2 : np.ndarray x-values y_2 : np.ndarray y-values plot_widget_1 : PlotWidget graphics view to place chart plot_widget_2 : PlotWidget graphics view to place chart labels : tuple labels for legend precision : int precision for rounding, default is zero width : int, float width of any bars x_labels : array-like array of dates, i.e. time-period or other labels display : type display dtype for labels highlight_bars : bool whether to highlight bars in plot """ super().__init__(parent=None) Assertor.assert_data_types( [x_1, y_1, x_2, y_2, plot_widget_1, plot_widget_2, labels, precision, width], [ndarray, ndarray, ndarray, ndarray, PlotWidget, PlotWidget, tuple, int, (int, float)]) self.x_1, self.y_1 = x_1, y_1 self.x_2, self.y_2 = x_2, y_2 self.plot_widget_1 = plot_widget_1 self.plot_widget_2 = plot_widget_2 self.precision = precision self.width = width self.x_time = x_labels self.display = display self.bar_item_1 = None self.bar_item_2 = None self.highlight_bars = highlight_bars pen = mkPen(color="#4c96d7", style=Qt.SolidLine, width=1) self.vertical_line_1 = InfiniteLine(angle=90, movable=False, pen=pen) self.vertical_line_2 = InfiniteLine(angle=90, movable=False, pen=pen) self.label_1 = TextItem() self.label_2 = TextItem() self.labels = labels self.units = units if units else tuple(["" for _ in range(10)]) if self.highlight_bars: self.plot_widget_1.addItem(self.vertical_line_1) self.plot_widget_2.addItem(self.vertical_line_2) self.view_box_1 = self.plot_widget_1.getViewBox() self.view_box_2 = self.plot_widget_2.getViewBox() self.configure_cross_hair() def configure_cross_hair(self): """ method for configuring cross hair """ place = percentile(insert(array(self.x_1), 0, 0), 2) self.label_1.setPos(place, int(abs(max(self.y_1, key=abs)) * 1.5)) self.label_2.setPos(place, int(abs(max(self.y_2, key=abs)) * 1.5)) self.plot_widget_1.addItem(self.label_1) self.plot_widget_2.addItem(self.label_2) def move_vertical_lines(self, pos): """ method for moving the vertical lines on the plot """ mouse_point_1 = self.view_box_1.mapSceneToView(pos) mouse_point_2 = self.view_box_2.mapSceneToView(pos) x_val_1 = int(round(mouse_point_1.x(), self.precision)) x_val_2 = int(round(mouse_point_2.x(), self.precision)) x_idx_1 = where(self.x_1 == x_val_1) x_idx_2 = where(self.x_2 == x_val_2) y_val_1 = self.display(self.y_1[x_idx_1]) if self.y_1[x_idx_1] else 0 y_val_2 = self.display(self.y_2[x_idx_2]) if self.y_2[x_idx_2] else 0 self.vertical_line_1.setPos(x_val_1) self.vertical_line_2.setPos(x_val_1) limits = min(self.x_1) <= x_val_1 <= max(self.x_1) if len(self.plot_widget_1.getViewBox().allChildren()) > 3 and limits \ and self.highlight_bars: self.highlight_bar_items(x_val_1, y_val_1, x_val_2, y_val_2) return x_val_1, y_val_1, x_val_2, y_val_2, limits def highlight_bar_items(self, x_val_1, y_val_1, x_val_2, y_val_2): """ method for highlighting bar items """ self.plot_widget_1.removeItem(self.bar_item_1) self.plot_widget_2.removeItem(self.bar_item_2) self.bar_item_1 = BarGraphItem(x=[x_val_1], height=y_val_1, width=self.width, brush="#69a8de") self.bar_item_2 = BarGraphItem(x=[x_val_2], height=y_val_2, width=self.width, brush="#69a8de") self.plot_widget_1.addItem(self.bar_item_1) self.plot_widget_2.addItem(self.bar_item_2) def mouse_moved(self, evt): """ method for moving the vertical lines based on mouse placement """ pos = evt[0] if self.plot_widget_1.sceneBoundingRect().contains(pos) or \ self.plot_widget_2.sceneBoundingRect().contains(pos): x_val_1, y_val_1, x_val_2, y_val_2, limits = self.move_vertical_lines(pos) x_label_idx = where(array(self.x_1) == x_val_1)[0] x_label_1 = self.x_time[x_label_idx.item()] + self.units[0] if \ self.x_time and x_label_idx.size != 0 else \ Amount(str(x_val_1)).amount + self.units[0] y_label_1 = Amount(str(y_val_1)).amount + self.units[1] x_label_2 = self.x_time[x_label_idx.item()] + self.units[0] if \ self.x_time and x_label_idx.size != 0 else \ Amount(str(x_val_2)).amount + self.units[2] y_label_2 = Amount(str(y_val_2)).amount + self.units[3] if limits: self.label_1.setHtml('<div style="text-align: center">' '<span style="font-size: 10pt">{}</span><br>' '<span style="font-size: 10pt">{}</span><br>' '<span style="font-size: 10pt">({})</span>' '</div>'.format(self.labels[0], x_label_1, y_label_1)) self.label_2.setHtml('<div style="text-align: center">' '<span style="font-size: 10pt">{}</span><br>' '<span style="font-size: 10pt">{}</span><br>' '<span style="font-size: 10pt">({})</span>' '</div>'.format(self.labels[1], x_label_2, y_label_2))
class baselinePlotWidget(SpectraPlotWidget): def __init__(self): super(baselinePlotWidget, self).__init__() self.line.setValue(800) self.txt = TextItem('', anchor=(0, 0)) self.cross = PlotDataItem([800], [0], symbolBrush=(255, 255, 0), symbolPen=(255, 255, 0), symbol='+',symbolSize=20) self.line.sigPositionChanged.connect(self.getMu) self._mu = None def plot(self, x, y, *args, **kwargs): # set up infinity line and get its position self.plotItem.plot(x, y, *args, **kwargs) self.addItem(self.line) self.addItem(self.cross) x_val = self.line.value() if x_val == 0: y_val = 0 else: idx = val2ind(x_val, self.wavenumbers) x_val = self.wavenumbers[idx] y_val = y[idx] if not self._meanSpec: txt_html = f'<div style="text-align: center"><span style="color: #FFF; font-size: 12pt">\ Spectrum #{self.spectrumInd}</div>' else: txt_html = f'<div style="text-align: center"><span style="color: #FFF; font-size: 12pt">\ {self._mean_title}</div>' txt_html += f'<div style="text-align: center"><span style="color: #FFF; font-size: 12pt">\ X = {x_val: .2f}, Y = {y_val: .4f}</div>' self.txt = TextItem(html=txt_html, anchor=(0, 0)) ymax = np.max(y) self._y = y r = self.txtPosRatio self.txt.setPos(r * x[-1] + (1 - r) * x[0], 0.95 * ymax) self.cross.setData([x_val], [y_val]) self.addItem(self.txt) def getMu(self): if self._mu is not None: x_val = self.line.value() if x_val == 0: y_val = 0 else: idx = val2ind(x_val, self._x) x_val = self._x[idx] y_val = self._mu[idx] txt_html = f'<div style="text-align: center"><span style="color: #FFF; font-size: 12pt">\ X = {x_val: .2f}, Y = {y_val: .4f}</div>' self.txt.setHtml(txt_html) self.cross.setData([x_val], [y_val]) def addDataCursor(self, x, y): self.addItem(self.line) self.addItem(self.cross) ymax = np.max(y) self.txt.setText('') r = self.txtPosRatio self.txt.setPos(r * x[-1] + (1 - r) * x[0], 0.95 * ymax) self.addItem(self.txt) self.getMu() def plotBase(self, dataGroup, plotType='raw'): """ make plots for Larch Group object :param dataGroup: Larch Group object :return: """ # add legend self.plotItem.addLegend(offset=(-1, -1)) x = self._x = dataGroup.energy # self._x, self._mu for getEnergy y = self._mu = dataGroup.specTrim n = len(x) # array length self._y = None # disable getEnergy func if plotType == 'raw': self.plotItem.plot(x, y, name='Raw', pen=mkPen('w', width=2)) elif plotType == 'rubber_base': self.plotItem.plot(x, y, name='Raw', pen=mkPen('w', width=2)) self.plotItem.plot(x, dataGroup.rubberBaseline, name='Rubberband baseline', pen=mkPen('g', style=Qt.DotLine, width=2)) self.plotItem.plot(dataGroup.wav_anchor, dataGroup.spec_anchor, symbol='o', symbolPen='r', symbolBrush=0.5) elif plotType == 'kohler_base': self.plotItem.plot(x, y, name='Raw', pen=mkPen('w', width=2)) self.plotItem.plot(x, dataGroup.kohlerBaseline, name='Kohler EMSC baseline', pen=mkPen('g', style=Qt.DotLine, width=2)) elif plotType == 'rubberband': y = self._mu = dataGroup.rubberDebased self.plotItem.plot(x, y, name='Rubberband debased', pen=mkPen('r', width=2)) elif plotType == 'kohler': y = self._mu = dataGroup.kohlerDebased self.plotItem.plot(x, y, name='Kohler EMSC debased', pen=mkPen('r', width=2)) elif plotType == 'deriv2_rubberband': y = self._mu = dataGroup.rubberDebased # for data cursor scale, offset = self.alignTwoCurve(dataGroup.rubberDebased[n//4:n*3//4], dataGroup.deriv2_rubber[n//4:n*3//4]) deriv2Scaled = dataGroup.deriv2_rubber * scale + offset ymin, ymax = np.min(y), np.max(y) self.getViewBox().setYRange(ymin, ymax, padding=0.1) self.plotItem.plot(x, y, name='Rubberband debased', pen=mkPen('r', width=2)) self.plotItem.plot(x, deriv2Scaled, name='2nd derivative (scaled, Rubberband)', pen=mkPen('g', width=2)) elif plotType == 'deriv2_kohler': y = self._mu = dataGroup.kohlerDebased scale, offset = self.alignTwoCurve(dataGroup.kohlerDebased[n//4:n*3//4], dataGroup.deriv2_kohler[n//4:n*3//4]) deriv2Scaled = dataGroup.deriv2_kohler * scale + offset ymin, ymax = np.min(y), np.max(y) self.getViewBox().setYRange(ymin, ymax, padding=0.1) self.plotItem.plot(x, y, name='Rubberband debased', pen=mkPen('r', width=2)) self.plotItem.plot(x, deriv2Scaled, name='2nd derivative (scaled, Kohler)', pen=mkPen('g', width=2)) # add infinityline, cross self.addDataCursor(x, y) def alignTwoCurve(self, y1, y2): """ Align the scale of y2 to that of y1 :param y1: the main curve :param y2: the curve to be aligned :return: scale: scale factor offset: y offset """ y1Range, y2Range = np.max(y1) - np.min(y1), np.max(y2) - np.min(y2) scale = y1Range / y2Range y = y2 * scale offset = np.max(y1) - np.max(y) return scale, offset
class SpectraPlotWidget(PlotWidget): sigEnergyChanged = Signal(object) def __init__(self, linePos=650, txtPosRatio=0.35, invertX=True, *args, **kwargs): """ A widget to display a 1D spectrum :param linePos: the initial position of the InfiniteLine :param txtPosRatio: a coefficient that determines the relative position of the textItem :param invertX: whether to invert X-axis """ super(SpectraPlotWidget, self).__init__(*args, **kwargs) self._data = None assert (txtPosRatio >= 0) and (txtPosRatio <= 1), 'Please set txtPosRatio value between 0 and 1.' self.txtPosRatio = txtPosRatio self.positionmap = dict() self.wavenumbers = None self._meanSpec = True # whether current spectrum is a mean spectrum self.line = InfiniteLine(movable=True) self.line.setPen((255, 255, 0, 200)) self.line.setValue(linePos) self.line.sigPositionChanged.connect(self.sigEnergyChanged) self.line.sigPositionChanged.connect(self.getEnergy) self.addItem(self.line) self.cross = PlotDataItem([linePos], [0], symbolBrush=(255, 0, 0), symbolPen=(255, 0, 0), symbol='+', symbolSize=20) self.cross.setZValue(100) self.addItem(self.cross) self.txt = TextItem() self.getViewBox().invertX(invertX) self.spectrumInd = 0 self.selectedPixels = None self._y = None def getEnergy(self, lineobject): if self._y is not None: x_val = lineobject.value() idx = val2ind(x_val, self.wavenumbers) x_val = self.wavenumbers[idx] y_val = self._y[idx] if not self._meanSpec: txt_html = toHtml(f'Spectrum #{self.spectrumInd}') else: txt_html = toHtml(f'{self._mean_title}') txt_html += toHtml(f'X = {x_val: .2f}, Y = {y_val: .4f}') self.txt.setHtml(txt_html) self.cross.setData([x_val], [y_val]) def setHeader(self, header: NonDBHeader, field: str, *args, **kwargs): self.header = header self.field = field # get wavenumbers spectraEvent = next(header.events(fields=['spectra'])) self.wavenumbers = spectraEvent['wavenumbers'] self.N_w = len(self.wavenumbers) self.rc2ind = spectraEvent['rc_index'] # make lazy array from document data = None try: data = header.meta_array(field) except IndexError: msg.logMessage(f'Header object contained no frames with field {field}.', msg.ERROR) if data is not None: # kwargs['transform'] = QTransform(1, 0, 0, -1, 0, data.shape[-2]) self._data = data def showSpectra(self, i=0): if (self._data is not None) and (i < len(self._data)): self.getViewBox().clear() self._meanSpec = False self.spectrumInd = i self.plot(self.wavenumbers, self._data[i]) def getSelectedPixels(self, selectedPixels): self.selectedPixels = selectedPixels # print(selectedPixels) def clearAll(self): # remove legend _legend = self.plotItem.legend if (_legend is not None) and (_legend.scene() is not None): _legend.scene().removeItem(_legend) self.getViewBox().clear() def showMeanSpectra(self): self._meanSpec = True self.getViewBox().clear() if self.selectedPixels is not None: n_spectra = len(self.selectedPixels) tmp = np.zeros((n_spectra, self.N_w)) for j in range(n_spectra): # j: jth selected pixel row_col = tuple(self.selectedPixels[j]) tmp[j, :] = self._data[self.rc2ind[row_col]] self._mean_title = f'ROI mean of {n_spectra} spectra' else: n_spectra = len(self._data) tmp = np.zeros((n_spectra, self.N_w)) for j in range(n_spectra): tmp[j, :] = self._data[j] self._mean_title = f'Total mean of {n_spectra} spectra' if n_spectra > 0: meanSpec = np.mean(tmp, axis=0) else: meanSpec = np.zeros_like(self.wavenumbers) + 1e-3 self.plot(self.wavenumbers, meanSpec) def plot(self, x, y, *args, **kwargs): # set up infinity line and get its position self.plotItem.plot(x, y, *args, **kwargs) self.addItem(self.line) self.addItem(self.cross) x_val = self.line.value() if x_val == 0: y_val = 0 else: idx = val2ind(x_val, self.wavenumbers) x_val = self.wavenumbers[idx] y_val = y[idx] if not self._meanSpec: txt_html = toHtml(f'Spectrum #{self.spectrumInd}') else: txt_html = toHtml(f'{self._mean_title}') txt_html += toHtml(f'X = {x_val: .2f}, Y = {y_val: .4f}') self.txt.setHtml(txt_html) ymax = np.max(y) self._y = y r = self.txtPosRatio self.txt.setPos(r * x[-1] + (1 - r) * x[0], ymax) self.cross.setData([x_val], [y_val]) self.addItem(self.txt)
class NaturalPlotView(GraphicsView): """Creates a simple about dialog. The about dialog contains general information about the application and shows the copyright notice. That's why the class has no attributes or return values. """ def __init__(self): super().__init__() # settings self.setBackground("#fff") self.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken) self.setAntialiasing(True) # create custom view box view_box = ViewBox() view_box.setMouseEnabled(False, False) view_box.setLimits(xMin=0, yMin=0, minXRange=10, minYRange=100) view_box.setRange(xRange=(0, 400), yRange=(0, 5000)) view_box.enableAutoRange() # create natural axis items self.x_axis = NaturalAxis("bottom") self.x_axis.setLabel(QApplication.translate("NaturalPlotView", "Fence length"), "m") self.y_axis = NaturalAxis("left") self.y_axis.setLabel(QApplication.translate("NaturalPlotView", "Number of plants")) # create fence information text self.fenceItem = TextItem(border=pyqtgraph.mkPen(width=2, color="#555"), fill=pyqtgraph.mkBrush((255, 255, 255, 200))) # create tube information text self.tubeItem = TextItem(border=pyqtgraph.mkPen(width=2, color="#555"), fill=pyqtgraph.mkBrush((255, 255, 255, 200)), anchor=(1,1)) # create plot item with custom view box and natural axis items self.plotItem = PlotItem(viewBox=view_box, axisItems={"bottom" : self.x_axis, "left" : self.y_axis}, enableMenu=False) self.plotItem.setContentsMargins(5, 5, 12, 5) self.plotItem.hideButtons() self.plotItem.hide() self.setCentralWidget(self.plotItem) # connect actions view_box.sigResized.connect(self.updateTubeLegendPosition) # translate the plot item self.retranslateUi() def retranslateUi(self): # title label titleStyle = "color: #111; font-size: 15px; font-weight: bold" titleLabel = "<span style='{style}'>{title}</span>".format(style=titleStyle, title="Ergebnis: Funktion(en) der Kostengleichheit") self.plotItem.setTitle(titleLabel) # axis items #self.x_axis.setLabel(QApplication.translate("NaturalPlotView", "Fence length"), "m") #self.y_axis.setLabel(QApplication.translate("NaturalPlotView", "Number of plants")) # fence hint self.fenceItem.setHtml("<p style='margin: 0; color: #555'><b>" + QApplication.translate("NaturalPlotView", "Pfeil über Funktion(en):") + "</b> " + QApplication.translate("NaturalPlotView", "Zaun günstiger") + "</p>") # tube hint self.tubeItem.setHtml("<p style='margin: 0; color: #555'><b>" + QApplication.translate("NaturalPlotView", "Pfeil unter Funktion(en):") + "</b> " + QApplication.translate("NaturalPlotView", "Wuchshülle günstiger") + "</p>") def addPlotItem(self, item, *args, **kwargs): # first show the plot item if not self.plotItem.isVisible(): self.plotItem.show() self.plotItem.addItem(item, *args, **kwargs) def removePlotItem(self, item): self.plotItem.removeItem(item) #TODO: def clear(self): self.plotItem.hide() self.plotItem.clear() def showDescription(self): viewBox = self.plotItem.getViewBox() self.fenceItem.setPos(15, 10) self.fenceItem.setParentItem(viewBox) rect = viewBox.screenGeometry() self.tubeItem.setPos(rect.width() - 15, rect.height() - 10) self.tubeItem.setParentItem(viewBox) def updateTubeLegendPosition(self): rect = self.plotItem.getViewBox().screenGeometry() self.tubeItem.setPos(rect.width() - 15, rect.height() - 10) def export(self, gfxfile): exporter = TestImageExporter(self.plotItem) exporter.parameters()["width"] = 2007.0 # 17 cm / 300 DPI # export the graphics to the selected file exporter.export(gfxfile)
class BarChartWithLine(Chart): """ Implementation of the Bar chart with line graphics """ def __init__(self, x: list, y: list, graphics_view: PlotWidget, table_view: QTableView, legend: str = "", width=0.5, reverse=True): """ Constructor / Instantiation of class Parameters ---------- x : list x-values y : list y-values graphics_view : PlotWidget graphics view to place chart table_view : QTableView table view to link graphics_view legend : HTML str legend width : int, float width of bars reverse : bool reverse selection in table """ Assertor.assert_data_types( [x, y, graphics_view, table_view, legend, width], [list, list, PlotWidget, QTableView, str, (int, float)]) super().__init__() self.x = x self.y = y self.graphics_view = graphics_view self.table_view = table_view self.label = TextItem() self.width = width self.reverse = reverse place = percentile(insert(array(self.x), 0, 0), 2) self.label.setPos(place, int(max(y) * 1.5)) self.label.setHtml(legend) self.graphics_view.addItem(self.label, ignore_bounds=True) self.bar_item = BarGraphItem(x=self.x, height=self.y, width=self.width, brush="#d2e5f5") self.graphics_view.addItem(self.bar_item) pen = mkPen(color="#d2e5f5", style=Qt.DotLine, width=2) self.graphics_view.plot(x=self.x, y=self.y, pen=pen, symbol='+', symbolSize=14) self.graphics_view.plotItem.vb.setLimits(xMin=min(self.x) - width, xMax=max(self.x) + width) self.graphics_view.setMenuEnabled(False) self.graphics_view.getViewBox().enableAutoRange() def table_view_mapping(self): """ method for mapping table rows to chart bars """ self.table_view.clicked.connect(self.row_clicked) def row_clicked(self, item): """ method for accessing row in table_view """ pen_1 = mkPen(color="#69a8de", style=Qt.DotLine, width=2) pen_2 = mkPen(color="#d2e5f5", style=Qt.DotLine, width=2) bar_item = BarGraphItem(x=self.x, height=self.y, width=self.width, brush="#d2e5f5") chart_item = PlotDataItem(x=self.x, y=self.y, pen=pen_1, symbol='+', symbolSize=14) clear_line = PlotDataItem(x=self.x, y=self.y, pen=pen_2, symbol='+', symbolSize=14) if item.row() < len(self.x): row = len(self.x) - 1 - item.row() if self.reverse else item.row() clicked_item = BarGraphItem(x=[self.x[row]], height=self.y[row], width=self.width, brush="#69a8de") self.graphics_view.addItem(bar_item) self.graphics_view.addItem(clicked_item) self.graphics_view.addItem(chart_item) else: self.graphics_view.addItem(bar_item) self.graphics_view.addItem(clear_line) @staticmethod def clear_graphics(graphics_view: PlotWidget, table_view: QTableView): """ static method for clearing content in all graphics Parameters ---------- graphics_view : PlotWidget graphics view to place chart table_view : QTableView table view to link graphics_view """ Assertor.assert_data_types([graphics_view, table_view], [PlotWidget, QTableView]) table_view.setModel(None) table_view.clearSpans() graphics_view.clear()
class ChangeBarChart(Chart): def __init__(self, x: list, y: list, graphics_view: PlotWidget, labels: str, units=None, x_labels=None, width=0.4): """ Constructor / Instantiation of class Parameters ---------- x : list x-values y : list y-values graphics_view : PlotWidget widget to add items labels : str legend labels units : tuple measurement units for tuple width : int, float width of any bars x_labels : array-like array of dates, i.e. time-period """ super().__init__() Assertor.assert_data_types([x, y, graphics_view, labels, units, x_labels, width], [list, list, PlotWidget, str, (type(None), tuple), (type(None), list), (float, int)]) self.x = asarray(arange(1, len(x) + 1, 1)) self.y = asarray([float(val.replace(" ", "").replace("%", "")) if val else 0 for val in y]) self.graphics_view = graphics_view self.labels = labels self.units = units if units else tuple(["" for _ in range(10)]) self.x_time = x_labels self.width = width self.bar_item_1 = BarGraphItem(x=self.x, height=self.y, width=self.width, brush="#a8ccec") self.graphics_view.addItem(self.bar_item_1) self.bar_item_2 = None self.label = TextItem() pen = mkPen(color="#4c96d7", style=Qt.SolidLine, width=1) self.vertical_line = InfiniteLine(angle=90, movable=False, pen=pen) self.graphics_view.addItem(self.vertical_line) self.view_box = self.graphics_view.getViewBox() self.configure_cross_hair() self.graphics_view.plotItem.vb.setLimits(xMin=0, xMax=max(self.x) + 1) self.graphics_view.setMenuEnabled(False) def configure_cross_hair(self): """ method for configuring cross hair """ place = percentile(array(insert(self.x, 0, 0)), 2) self.label.setPos(place, int(abs(max(self.y, key=abs)) * 1.5)) self.graphics_view.addItem(self.label) def move_vertical_lines(self, pos): """ method for moving the vertical lines on the plot """ mouse_point = self.view_box.mapSceneToView(pos) x_val = int(round(mouse_point.x())) x_idx = where(self.x == x_val) y_val = int(self.y[x_idx]) if self.y[x_idx] else 0 self.vertical_line.setPos(x_val) limits = min(self.x) <= x_val <= max(self.x) if len(self.graphics_view.getViewBox().allChildren()) > 3 and limits: self.highlight_bar_items(x_val, y_val) return x_val, y_val def highlight_bar_items(self, x_val, y_val): """ method for highlighting bar items """ self.graphics_view.removeItem(self.bar_item_2) self.bar_item_2 = BarGraphItem(x=[x_val], height=y_val, width=self.width, brush="#69a8de") self.graphics_view.addItem(self.bar_item_2) def mouse_moved(self, evt): """ method for moving the vertical lines based on mouse placement """ pos = evt[0] x_val, y_val = self.move_vertical_lines(pos) x_label_idx = where(array(self.x) == x_val)[0] x_label = self.x_time[x_label_idx.item()] if \ self.x_time and x_label_idx.size != 0 else \ Amount(str(x_val)).amount + self.units[0] y_label = str(y_val) + self.units[1] if min(self.x) <= x_val <= max(self.x): self.label.setHtml('<div style="text-align: center">' '<span style="font-size: 10pt">{}</span><br>' '<span style="font-size: 10pt">{}</span><br>' '<span style="font-size: 10pt">({})</span>' '</div>'.format(self.labels, x_label, y_label))
class RatioChart(Chart): """ Implementation of the RatioChart graphics used for visualizing the ratio between sqm-prices in area vs municipality. """ def __init__(self, x: list, y_1: list, y_2: list, graphics_view: PlotWidget, labels: str, units=None, x_labels=None, precision=0, width=0.4): """ Constructor / Instantiate the class Parameters ---------- x : list x-values y_1 : np.ndarray y-values y_2 : np.ndarray y-values graphics_view : PlotWidget widget to add items labels : str legend labels units : tuple measurement units for tuple x_labels : array-like array of x_labels precision : int precision for rounding, default is zero width : int, float width of any bars """ Assertor.assert_data_types([ x, y_1, y_2, graphics_view, labels, units, x_labels, precision, width ], [ list, list, list, PlotWidget, str, (type(None), tuple), (type(None), list), (float, int), (float, int) ]) super().__init__() self.y_1, self.x = self.create_bins(x, y_1, bins=x) self.y_2, self.x = self.create_bins(x, y_2, bins=x) self.x = self.x[:-1] self.labels = labels self.units = units if units else ("", "") self.precision = precision self.ratio = (self.y_1 / self.y_2) * 100 self.x_labels = x_labels if x_labels else self.ratio self.graphics_view = graphics_view self.view_box = self.graphics_view.getViewBox() self.width = width self.label = TextItem() self.bar_item_1 = BarGraphItem(x=self.x, height=self.ratio, width=self.width, brush="#a8ccec") self.graphics_view.addItem(self.bar_item_1) self.configure_cross_hair() self.bar_item_2 = None pen = mkPen(color="#4c96d7", style=Qt.SolidLine, width=1) self.vertical_line = InfiniteLine(angle=90, movable=False, pen=pen) self.graphics_view.addItem(self.vertical_line) self.graphics_view.plotItem.vb.setLimits(xMin=min(self.x) - width, xMax=max(self.x) + width) self.graphics_view.setMenuEnabled(False) def configure_cross_hair(self): """ method for configuring cross hair """ place = percentile(array(insert(self.x, 0, 0)), 2) self.label.setPos(place, int(abs(max(self.ratio, key=abs)) * 1.5)) self.graphics_view.addItem(self.label) def move_vertical_lines(self, pos): """ method for moving the vertical lines on the plot """ mouse_point = self.view_box.mapSceneToView(pos) x_val = int(round(mouse_point.x(), self.precision)) x_idx = where(self.x == x_val) y_val = self.ratio[x_idx] if self.ratio[x_idx] else 0 self.vertical_line.setPos(x_val) limits = min(self.x) <= x_val <= max(self.x) if len(self.graphics_view.getViewBox().allChildren()) > 3 and limits: self.highlight_bar_items(x_val, y_val) return x_val, y_val def highlight_bar_items(self, x_val, y_val): """ method for highlighting bar items """ self.graphics_view.removeItem(self.bar_item_2) self.bar_item_2 = BarGraphItem(x=[x_val], height=y_val, width=self.width, brush="#69a8de") self.graphics_view.addItem(self.bar_item_2) def mouse_moved(self, evt): """ method for moving the vertical lines based on mouse placement """ pos = evt[0] if self.graphics_view.sceneBoundingRect().contains(pos): x_val, y_val = self.move_vertical_lines(pos) percent = Percent( str(y_val / 100).replace("[", "").replace("]", "")) if min(self.x) <= x_val <= max(self.x): self.label.setHtml( '<div style="text-align: center">' '<span style="font-size: 10pt">{}</span><br>' '<span style="font-size: 10pt">{} {}</span><br>' '<span style="font-size: 10pt">({})</span>' '</div>'.format(self.labels, Amount(str(x_val)).amount, self.units[0], percent.value))