class DynamicSpline(QChart): def __init__(self): super().__init__() self.m_step = 0 self.m_x = 2 self.m_y = 1 # Инициализировать изображение self.series = QSplineSeries(self) red_pen = QPen(Qt.red) red_pen.setWidth(2) self.series.setPen(red_pen) self.axisX = QValueAxis() self.axisY = QValueAxis() self.series.append(self.m_x, self.m_y) self.addSeries(self.series) self.addAxis(self.axisX, Qt.AlignBottom) self.addAxis(self.axisY, Qt.AlignLeft) self.series.attachAxis(self.axisX) self.series.attachAxis(self.axisY) self.axisX.setTickCount(5) self.axisX.setRange(0, 10) self.axisY.setRange(0, 100) self.timer = QTimer(self) self.timer.setInterval(1000) self.timer.timeout.connect(self.handleTimeout) self.timer.start() def handleTimeout(self): x = self.plotArea().width() / self.axisX.tickCount() y = (self.axisX.max() - self.axisX.min()) / self.axisX.tickCount() self.m_x += y # В PyQt 5.11.3 и выше, QRandomGenerator.global() был переименован в global_() try: self.m_y=db_wrk() except : self.m_y = QRandomGenerator.global_().bounded(50) self.series.append(self.m_x, self.m_y) self.scroll(x, 0) if self.m_x >= 10: self.timer.stop() def db_wrk(): return m_y
class Chart(QChart): def __init__(self): super(QChart, self).__init__() self.m_timer = QTimer() self.m_series = None self.m_titles = [] self.m_axis = QValueAxis() self.m_step = None self.m_x = 5 self.m_y = 1 random.seed(QTime.currentTime().msec()) self.m_timer.timeout.connect(self.handleTimeout) self.m_timer.setInterval(1000) self.m_series = QSplineSeries(self) green = QPen(Qt.green) green.setWidth(3) self.m_series.setPen(green) self.m_series.append(self.m_x, self.m_y) self.addSeries(self.m_series) self.createDefaultAxes() self.setAxisX(self.m_axis, self.m_series) self.m_axis.setTickCount(5) self.axisX().setRange(0, 10) self.axisY().setRange(-5, 10) self.m_timer.start() @pyqtSlot() def handleTimeout(self): x = self.plotArea().width() / self.m_axis.tickCount() y = (self.m_axis.max() - self.m_axis.min()) / self.m_axis.tickCount() self.m_x += y self.m_y = random.randint(0, 5) - 2.5 self.m_series.append(self.m_x, self.m_y) self.scroll(x, 0) if self.m_x is 100: self.m_timer.stop()
class DynamicSpline(QChart): def __init__(self): super().__init__() self.m_step = 0 self.m_x = 5 self.m_y = 1 # 初始化图像 self.series = QSplineSeries(self) green_pen = QPen(Qt.red) green_pen.setWidth(3) self.series.setPen(green_pen) self.axisX = QValueAxis() self.axisY = QValueAxis() self.series.append(self.m_x, self.m_y) self.addSeries(self.series) self.addAxis(self.axisX, Qt.AlignBottom) self.addAxis(self.axisY, Qt.AlignLeft) self.series.attachAxis(self.axisX) self.series.attachAxis(self.axisY) self.axisX.setTickCount(5) self.axisX.setRange(0, 10) self.axisY.setRange(-5, 10) self.timer = QTimer(self) self.timer.setInterval(1000) self.timer.timeout.connect(self.handleTimeout) self.timer.start() def handleTimeout(self): x = self.plotArea().width() / self.axisX.tickCount() y = (self.axisX.max() - self.axisX.min()) / self.axisX.tickCount() self.m_x += y # 在PyQt5.11.3及以上版本中,QRandomGenerator.global()被重命名为global_() self.m_y = QRandomGenerator.global_().bounded(5) - 2.5 self.series.append(self.m_x, self.m_y) self.scroll(x, 0) if self.m_x >= 100: self.timer.stop()
class XChartProbit(QChart): def __init__(self, parent=None): super(QChart, self).__init__(parent) # Class Vars self.activeDistr = 'lognorm' self.knowndistr = distr._distrnames() self.data = dict() # Axis Setup self.axisX = QValueAxis() self.axisY = QValueAxis() self.axisX.setLabelsVisible(False) self.axisX.setTickCount(2) self.axisX.setTitleText("Series Fractional Probability") self.axisY.setTitleText("Value") self.setAxesMinMax(-3, 3, 0.01, 1.5) self.axisX.setMinorGridLineVisible(False) self.axisX.setGridLineVisible(False) # define the default grid colour to grey self.setGridColor(110, 110, 110) self.setActiveProbit(self.activeDistr) self.plotAreaChanged.connect(self.onPlotSizeChanged) # method needed for axes change to redraw grid lines def addLinearReg(self, seriesname): x = self.data[seriesname]['X'], y = self.data[seriesname][seriesname] # adds a linear regression line for a data set x,y slope, intercept, r_value, p_value, std_err = linregress(x, y) xmin = distr.distrppf(self.activeDistr, 0.01) xmax = distr.distrppf(self.activeDistr, 0.99) ymin = slope * xmin + intercept ymax = slope * xmax + intercept data = dict() data['X'] = [xmin, xmax] data['LinearReg'] = [ymin, ymax] lines = XLineSeries(data, xkey='X', openGL=True) self.addSeries(lines[0]) self.setAxes(lines[0]) def loadSeries(self, arr, name): # takes a list/array arr y = array(arr).copy() y.sort() self.data[name] = y self.redrawChart() def plotSeries(self, name): nsamp = len(self.data[name]) # add data to temport dictionary tdict = dict() if self.activeScale == 'log10': tdict[name] = log10(self.data[name]) elif self.activeScale == 'linear': tdict[name] = self.data[name] tdict['X'] = distr.distrppf(self.activeProbit, [ percentileofscore(self.data[name], self.data[name][i]) / 100.00001 for i in range(0, nsamp) ]) series = XScatterSeries(tdict, xkey='X', openGL=True) self.addSeries(series[0]) self.setAxes(series[0]) def _replotData(self): for key in self.data.keys(): self.pl def axesMinMax(self): # returns a length 4 list of the axes min and max values [x1,x2,y1,y2] return [ self.axisX.min(), self.axisX.max(), self.axisY.min(), self.axisY.max() ] def redrawChart(self): self.removeAllSeries() self._removeHorizontalGridLabels() self.resetAxes() self._drawVerticalGridLines() if self.activeScale == 'log10': self.axisY.setLabelsVisible(False) self.axisY.setTickCount(1) self.setTitle("Log Probit Plot") self.axisY.setMinorGridLineVisible(False) self.axisY.setGridLineVisible(False) self._drawHorizontalGridLine() self._drawHorizontalLabels() self._drawHorizontalGridlLabels() elif self.activeScale == 'linear': self.axisY.setLabelsVisible(True) self.axisY.setTickCount(10) self.setTitle("Probit Plot") self.axisY.setMinorGridLineVisible(True) self.axisY.setGridLineVisible(True) for serkey in self.data.keys(): self.plotSeries(serkey) def resetAxes(self): ymins = [] ymaxs = [] for key in self.data.keys(): ymins.append(min(self.data[key])) ymaxs.append(max(self.data[key])) try: ymin = min(ymins) ymax = max(ymaxs) except ValueError: ymin = 1.1 ymax = 2 xmin = distr.distrppf(self.activeProbit, 0.001) xmax = distr.distrppf(self.activeProbit, 0.999) if self.activeScale == 'linear': yscal = 0.1 * (ymax - ymin) self.setAxesMinMax(xmin, xmax, ymin - yscal, ymax + yscal) elif self.activeScale == 'log10': yscal = 0.1 * (log10(ymax) - log10(ymin)) self.setAxesMinMax(xmin, xmax, log10(ymin), log10(ymax)) #self.setAxesMinMax(xmin,xmax,log10(ymin)-yscal,log10(ymax)+yscal*0.1) def setGridColor(self, r, g, b): # sets the colour of the background grid self.gridcolor = QColor(r, g, b) def setActiveProbit(self, type): if type in self.knowndistr: self.activeDistr = type if type == 'norm': self.activeProbit = 'norm' self.activeScale = 'linear' elif type == 'lognorm': self.activeProbit = 'norm' self.activeScale = 'log10' #self.redrawChart() def setActiveScale(self, newscale): self.activeScale = newscale def setAxes(self, series): # assigns a series to the chart default axes self.setAxisX(self.axisX, series) self.setAxisY(self.axisY, series) def setAxesMinMax(self, x1, x2, y1, y2): # sets the min max values in X and Y self.axisX.setMin(x1) self.axisX.setMax(x2) self.axisY.setMin(y1) self.axisY.setMax(y2) def _drawHorizontalLabels(self): xmin = self.axisX.min() xmax = self.axisX.max() axisScale = 1 / (xmax - xmin ) # scaler for plotted axis (reduces to 0-1.0) # calculate probit values to scale from grid lines insert min and max values to scale correctly vlabx = distr.distrppf(self.activeProbit, self.vgridx) vlabx = insert(vlabx, 0, xmin) vlabx = insert(vlabx, len(vlabx), xmax) vlabx = ( vlabx - xmin ) * axisScale #scale the probit value to ratios of the Xaxis length paw = self.plotArea().width() pah = self.plotArea().height() #find the plot width and height # find plot bottom left corner X and Y pblx = self.plotArea().bottomLeft().x() pbly = self.plotArea().bottomLeft().y() # offset from axix by 10 pixels -> may need to automate this offset in future pbly_lab = pbly + 10 # calculate the position on the chart in x plane with which to place each label. pblx = [pblx + int(paw * x) for x in vlabx[1:-1]] try: self.hlabels except AttributeError: self.hlabels = [] for i, labx in enumerate( pblx): #run through labels and create and position them # label text based on P scale ltext = 'P' + '%02d' % round(100.0 * (1.0 - self.vgridx[i])) self.hlabels.append(self.scene().addText(ltext)) for i, labx in enumerate( pblx): #run through labels and create and position them self.hlabels[i].setPos( labx - 0.5 * self.hlabels[i].boundingRect().width(), pbly) #centre on tick marks def _drawVerticalGridLines(self): self.vgridx = arange(0.05, 1.0, 0.05) self.vgridx = insert(self.vgridx, 0, [0.01, 0.02]) self.vgridx = insert(self.vgridx, len(self.vgridx), [0.98, 0.99]) vgridy = [self.axisY.min(), self.axisY.max()] self.vgridseries = [] for val in self.vgridx: line = 'P' + '%02d' % round(100.0 * (1.0 - val)) tdict = { 'X': [distr.distrppf(self.activeProbit, val)] * 2, line: vgridy } self.vgridseries = self.vgridseries + XLineSeries( tdict, xkey='X', openGL=True) for i, line in enumerate(self.vgridseries): pen = line.pen() pen.setColor(self.gridcolor) pen.setWidthF(0.4), line.setPen(pen) line.setPointLabelsVisible(True) self.addSeries(line) self.setAxes(line) self.legend().markers(line)[0].setVisible(False) def _drawHorizontalGridLine(self): # calculate xmin and xmax points for lines to completely cross graph hgridx = [ distr.distrppf(self.activeProbit, 0.0001) - 1, distr.distrppf(self.activeProbit, 0.9999) + 1 ] # calculate log scale for lines y values self.hgridy = self._logrange(10**self.axisY.min(), 10**self.axisY.max(), base=10) self.hgridseries = [] # create a line series for each lines and add to list for val in self.hgridy: line = '%d' % val tdict = {'X': hgridx, line: [log10(val)] * 2} self.hgridseries = self.hgridseries + XLineSeries( tdict, xkey='X', openGL=True) # add each of the series to the grid with special formatting for i, line in enumerate(self.hgridseries): pen = line.pen() pen.setColor(self.gridcolor) pen.setWidthF(0.4), line.setPen(pen) self.addSeries(line) self.setAxes(line) self.legend().markers(line)[0].setVisible(False) def _drawHorizontalGridlLabels(self): ymin = self.axisY.min() ymax = self.axisY.max() axisScale = 1 / (ymax - ymin ) # scaler for plotted axis (reduces to 0-1.0) # calculate base10 values to scale from grid lines insert min and max values to scale correctly vlaby = log10(self.hgridy) vlaby = insert(vlaby, 0, ymin) vlaby = insert(vlaby, len(vlaby), ymax) vlaby = ( vlaby - ymin ) * axisScale # scale the probit value to ratios of the Xaxis length paw = self.plotArea().width() pah = self.plotArea().height() # find the plot width and height # find plot bottom left corner X and Y pblx = self.plotArea().bottomLeft().x() pbly = self.plotArea().bottomLeft().y() # offset from axix by 10 pixels -> may need to automate this offset in future pblx_lab = pblx - 10 # calculate the position on the chart in y plane with which to place each label. pbly = [pbly - int(pah * y) for y in vlaby[1:-1]] self.vlabels = [] for i, labx in enumerate( pbly): # run through labels and create and position them # label text based on P scale ltext = str(self.hgridy[i]) self.vlabels.append(self.scene().addText(ltext)) for i, laby in enumerate( pbly): #run through labels and create and position them # label text based on P scale self.vlabels[i].setPos( pblx - self.vlabels[i].boundingRect().width() - 10, laby - 0.5 * self.vlabels[i].boundingRect().height()) #centre on tick marks def _removeHorizontalGridLine(self): for ser in self.hgridseries: self.removeSeries(ser) def _removeVerticalGridLine(self): for ser in self.vgridseries: self.removeSeries(ser) def _removeHorizontalGridLabels(self): try: for lab in self.vlabels: self.scene().removeItem(lab) except AttributeError: pass def _logrange(self, min, max, base=10): if min <= 0: min += max / (base**10) y = 1 bpow = base if min < base: while min < bpow: y -= 1 bpow = pow(base, y) else: while min > bpow: y += 1 bpow = pow(base, y) out = array([]) while bpow < max: y += 1 bpown = pow(base, y) out = append(out, arange(bpow, bpown, bpow)) bpow = bpown i = 0 j = 0 for ind, val in enumerate(out): if val <= min: i = ind if val <= max: j = ind return out[i:j + 1] @pyqtSlot() def onPlotSizeChanged(self): #reset position of labels self.redrawChart()
class VCTemporalSeries(VCCommons): def __init__(self): VCCommons.__init__(self) self.__chart = QChart() #After setChart you must call it with chart() self.customContextMenuRequested.connect( self.on_customContextMenuRequested) self._allowHideSeries = True #Axis cration self.axisX = QDateTimeAxis() self.axisX.setTickCount(8) self.axisX.setFormat("yyyy-MM") self.maxx = None self.maxy = None self.minx = None self.miny = None self.__ohclduration = eOHCLDuration.Day self.axisY = QValueAxis() self.axisY.setLabelFormat("%i") self.setRenderHint(QPainter.Antialiasing) self.series = [] self.popup = MyPopup(self) def appendCandlestickSeries(self, name): ls = QCandlestickSeries() ls.setName(name) ls.setIncreasingColor(QColor(Qt.green)) ls.setDecreasingColor(QColor(Qt.red)) self.series.append(ls) return ls def appendCandlestickSeriesData(self, ls, dtaware, ope, hig, clo, low): x = dtaware2epochms(dtaware) ls.append( QCandlestickSet(float(ope), float(hig), float(clo), float(low), x)) if self.maxy == None: self.maxy = float(hig) self.miny = float(low) self.maxx = x self.minx = x if hig > self.maxy: self.maxy = float(hig) if low < self.miny: self.miny = float(low) if x > self.maxx: self.maxx = x if x < self.minx: self.minx = x def setOHCLDuration(self, ohclduration): self.__ohclduration = ohclduration def appendScatterSeries(self, name): ls = QScatterSeries() ls.setName(name) self.series.append(ls) return ls def appendScatterSeriesData(self, ls, x, y): self.appendTemporalSeriesData(ls, x, y) def setAxisFormat(self, axis, min, max, type, zone=None): """ type=0 #Value type=1 # Datetime if zone=None remains in UTC, zone is a zone object. """ if type == 0: if max - min <= Decimal(0.01): axis.setLabelFormat("%.4f") elif max - min <= Decimal(100): axis.setLabelFormat("%.2f") else: axis.setLabelFormat("%i") elif type == 1: max = epochms2dtaware(max) #UTC aware min = epochms2dtaware(min) if max - min < timedelta(days=1): axis.setFormat("hh:mm") else: axis.setFormat("yyyy-MM-dd") def setAllowHideSeries(self, boolean): self._allowHideSeries = boolean def appendTemporalSeries(self, name): ls = QLineSeries() ls.setName(name) self.series.append(ls) return ls def appendTemporalSeriesData(self, ls, x, y): """ x is a datetime zone aware """ x = dtaware2epochms(x) x = float(x) y = float(y) ls.append(x, y) if self.maxy == None: #Gives first maxy and miny self.maxy = y * 1.01 self.miny = y * 0.99 self.maxx = x * 1.01 self.minx = x * 0.99 if y > self.maxy: self.maxy = y if y < self.miny: self.miny = y if x > self.maxx: self.maxx = x if x < self.minx: self.minx = x def mouseMoveEvent(self, event): ##Sets the place of the popup in the windows to avoid getout of the screen ##frmshow can be a frmShowCasilla or a frmShowFicha def placePopUp(): resultado = QPoint(event.x() + 15, event.y() + 15) if event.x() > self.width() - self.popup.width() - 15: resultado.setX(event.x() - self.popup.width() - 15) if event.y() > self.height() - self.popup.height() - 15: resultado.setY(event.y() - self.popup.height() - 15) return resultado def showCurrentPosition(): if hasattr(self, "qgstiCurrentX") == False: self.qgstiCurrentX = QGraphicsSimpleTextItem(self.chart()) self.qgstiCurrentY = QGraphicsSimpleTextItem(self.chart()) self.qgstiCurrentX.setPos(event.pos().x(), maxY - 10) self.qgstiCurrentY.setPos(self.chart().size().width() - 47, event.pos().y()) self.qgstiCurrentX.setText(str(epochms2dtaware(xVal).date())) self.qgstiCurrentY.setText(str(round(yVal, 2))) # --------------------------------------- QChartView.mouseMoveEvent(self, event) xVal = self.chart().mapToValue(event.pos()).x() yVal = self.chart().mapToValue(event.pos()).y() maxX = self.axisX.max().toMSecsSinceEpoch() minX = self.axisX.min().toMSecsSinceEpoch() maxY = self.axisY.max() minY = self.axisY.min() if xVal <= maxX and xVal >= minX and yVal <= maxY and yVal >= minY: self.popup.move(self.mapToGlobal(placePopUp())) self.popup.refresh(self, xVal, yVal) showCurrentPosition() self.popup.show() else: self.popup.hide() ## Return the value of the serie in x def series_value(self, serie, x): for point in serie.pointsVector(): if point.x() >= x: return point.y() @pyqtSlot() def on_marker_clicked(self): marker = QObject.sender( self ) #Busca el objeto que ha hecho la signal en el slot en el que está conectado, ya que estaban conectados varios objetos a una misma señal marker.series().setVisible(not marker.series().isVisible()) marker.setVisible(True) if marker.series().isVisible(): alpha = 1 else: alpha = 0.5 lbrush = marker.labelBrush() color = lbrush.color() color.setAlphaF(alpha) lbrush.setColor(color) marker.setLabelBrush(lbrush) brush = marker.brush() color = brush.color() color.setAlphaF(alpha) brush.setColor(color) marker.setBrush(brush) pen = marker.pen() color = pen.color() color.setAlphaF(alpha) pen.setColor(color) marker.setPen(pen) ## Used to display chart. You cannot use it twice. close the view widget and create another one def display(self): if self.__chart != None: del self.__chart self.__chart = QChart() self.setChart(self.__chart) if self._animations == True: self.chart().setAnimationOptions(QChart.AllAnimations) else: self.chart().setAnimationOptions(QChart.NoAnimation) self.chart().layout().setContentsMargins(0, 0, 0, 0) self._display_set_title() self.setAxisFormat(self.axisX, self.minx, self.maxx, 1) self.setAxisFormat(self.axisY, self.miny, self.maxy, 0) self.chart().addAxis(self.axisY, Qt.AlignLeft) self.chart().addAxis(self.axisX, Qt.AlignBottom) for s in self.series: self.chart().addSeries(s) s.attachAxis(self.axisX) s.attachAxis(self.axisY) self.axisY.setRange(self.miny, self.maxy) #Legend positions if len(self.chart().legend().markers()) > 6: self.chart().legend().setAlignment(Qt.AlignLeft) else: self.chart().legend().setAlignment(Qt.AlignTop) if self._allowHideSeries == True: for marker in self.chart().legend().markers(): try: marker.clicked.disconnect() except: pass marker.clicked.connect(self.on_marker_clicked) self.repaint() ## Returns a qmenu to be used in other qmenus def qmenu(self, title="Chart options"): menu = QMenu(self) menu.setTitle(self.tr(title)) menu.addAction(self.actionSave) return menu def on_customContextMenuRequested(self, pos): self.qmenu().exec_(self.mapToGlobal(pos))
class VCScatterAlone(VCCommons): def __init__(self): VCCommons.__init__(self) self.clear() self.popuplock = QMutex() self._x_format = "float" self._x_decimals = 2 self._x_title = "" self._y_format = "float" self._y_decimals = 2 self._y_title = "" ## To clean pie, removes serie and everithing is like create an empty pie def clear(self): self._chart = QChart() self.setChart(self._chart) self.setRenderHint(QPainter.Antialiasing) self._allowHideSeries = True self.axisX = QValueAxis() self.maxx = None self.minx = None self.axisY = QValueAxis() self.maxy = None self.miny = None self.series = [] self.chart().legend().hide() self.popup = MyPopup(self) def appendScatterSeries(self, name, list_x, list_y): ls = QScatterSeries() ls.setName(name) self.series.append(ls) for i in range(len(list_x)): x = float(list_x[i]) y = float(list_y[i]) ls.append(x, y) if self.maxy == None: #Gives first maxy and miny self.maxy = y * 1.01 self.miny = y * 0.99 self.maxx = x * 1.01 self.minx = x * 0.99 if y > self.maxy: self.maxy = y if y < self.miny: self.miny = y if x > self.maxx: self.maxx = x if x < self.minx: self.minx = x def setXFormat(self, stringtype, title="", decimals=2): self._x_format = stringtype self._x_decimals = decimals self._x_title = title def _applyXFormat(self): # self.axisX.setTickCount(8) self.axisX.setTitleText(self._x_title) if self._x_format == "int": self.axisX.setLabelFormat("%i") elif self._x_format in ["float", "Decimal"]: self.axisX.setLabelFormat("%.{}f".format(self._x_decimals)) def setYFormat(self, stringtype, title="", decimals=2): self._y_format = stringtype self._y_decimals = decimals self._y_title = title def _applyYFormat(self): self.axisY.setTitleText(self._y_title) if self._y_format == "int": self.axisY.setLabelFormat("%i") elif self._y_format in ["float", "Decimal"]: self.axisY.setLabelFormat("%.{}f".format(self._y_decimals)) def setAllowHideSeries(self, boolean): self._allowHideSeries = boolean def mouseMoveEvent(self, event): ##Sets the place of the popup in the windows to avoid getout of the screen ##frmshow can be a frmShowCasilla or a frmShowFicha def placePopUp(): resultado = QPoint(event.x() + 15, event.y() + 15) if event.x() > self.width() - self.popup.width() - 15: resultado.setX(event.x() - self.popup.width() - 15) if event.y() > self.height() - self.popup.height() - 15: resultado.setY(event.y() - self.popup.height() - 15) return resultado # --------------------------------------- if self.popuplock.tryLock() == False: event.reject() return QChartView.mouseMoveEvent(self, event) xVal = self.chart().mapToValue(event.pos()).x() yVal = self.chart().mapToValue(event.pos()).y() maxX = self.axisX.max() minX = self.axisX.min() maxY = self.axisY.max() minY = self.axisY.min() if xVal <= maxX and xVal >= minX and yVal <= maxY and yVal >= minY: self.popup.move(self.mapToGlobal(placePopUp())) self.popup.refresh(self, xVal, yVal) self.popuplock.unlock() @pyqtSlot() def on_marker_clicked(self): marker = QObject.sender( self ) #Busca el objeto que ha hecho la signal en el slot en el que está conectado, ya que estaban conectados varios objetos a una misma señal marker.series().setVisible(not marker.series().isVisible()) marker.setVisible(True) if marker.series().isVisible(): alpha = 1 else: alpha = 0.5 lbrush = marker.labelBrush() color = lbrush.color() color.setAlphaF(alpha) lbrush.setColor(color) marker.setLabelBrush(lbrush) brush = marker.brush() color = brush.color() color.setAlphaF(alpha) brush.setColor(color) marker.setBrush(brush) pen = marker.pen() color = pen.color() color.setAlphaF(alpha) pen.setColor(color) marker.setPen(pen) ## Used to display chart. You cannot use it twice. close the view widget and create another one def display(self): if self._chart != None: del self._chart self._chart = QChart() self.setChart(self._chart) if self._animations == True: self.chart().setAnimationOptions(QChart.AllAnimations) else: self.chart().setAnimationOptions(QChart.NoAnimation) self.chart().layout().setContentsMargins(0, 0, 0, 0) self._display_set_title() self._applyXFormat() self._applyYFormat() self.chart().addAxis(self.axisY, Qt.AlignLeft) self.chart().addAxis(self.axisX, Qt.AlignBottom) for s in self.series: self.chart().addSeries(s) s.attachAxis(self.axisX) s.attachAxis(self.axisY) self.axisY.setRange(self.miny, self.maxy) #Legend positions if len(self.chart().legend().markers()) > 6: self.chart().legend().setAlignment(Qt.AlignLeft) else: self.chart().legend().setAlignment(Qt.AlignTop) if self._allowHideSeries == True: for marker in self.chart().legend().markers(): try: marker.clicked.disconnect() except: pass marker.clicked.connect(self.on_marker_clicked) self.repaint() ## Returns a qmenu to be used in other qmenus def qmenu(self, title="Chart options"): menu = QMenu(self) menu.setTitle(self.tr(title)) menu.addAction(self.actionCopyToClipboard) menu.addSeparator() menu.addAction(self.actionSave) return menu ## If you use VCPieAlone you can add a context menu setting boolean to True def setCustomContextMenu(self, boolean): self.customContextMenuRequested.connect( self.on_customContextMenuRequested) def on_customContextMenuRequested(self, pos): self.qmenu().exec_(self.mapToGlobal(pos))
class VCTemporalSeriesAlone(VCCommons): def __init__(self): VCCommons.__init__(self) self.clear() self.popuplock = QMutex() self._x_format = "date" self._x_timezone = "UTC" self._x_decimals = 0 #Needed in popup self._x_tickcount = 8 self._x_title = "" self._y_format = "float" self._y_decimals = 2 self._y_title = "" ## To clean pie, removes serie and everithing is like create an empty pie def clear(self): self._chart = QChart() self.setChart(self._chart) self.setRenderHint(QPainter.Antialiasing) self._allowHideSeries = True self.axisX = QDateTimeAxis() self.maxx = None self.minx = None self.axisY = QValueAxis() self.maxy = None self.miny = None self.series = [] self.chart().legend().hide() self.popup = MyPopup(self) def appendCandlestickSeries(self, name): ls = QCandlestickSeries() ls.setName(name) ls.setIncreasingColor(QColor(Qt.green)) ls.setDecreasingColor(QColor(Qt.red)) self.series.append(ls) return ls def appendCandlestickSeriesData(self, ls, dtaware, ope, hig, clo, low): x = dtaware2epochms(dtaware) ls.append( QCandlestickSet(float(ope), float(hig), float(clo), float(low), x)) if self.maxy == None: self.maxy = float(hig) self.miny = float(low) self.maxx = x self.minx = x if hig > self.maxy: self.maxy = float(hig) if low < self.miny: self.miny = float(low) if x > self.maxx: self.maxx = x if x < self.minx: self.minx = x ## Used to draw poinnts in a temporal series def appendScatterSeries(self, name): ls = QScatterSeries() ls.setName(name) self.series.append(ls) return ls def appendScatterSeriesData(self, ls, x, y): self.appendTemporalSeriesData(ls, x, y) ## @param stringtype is one of the types in casts.value2object. Can be auto too to auto select best datetime format def setXFormat(self, stringtype, title="", zone_name="UTC", tickcount=8): self._x_format = stringtype self._x_zone_name = zone_name self._x_tickcount = tickcount self._x_title = title def _applyXFormat(self): self.axisX.setTickCount(8) self.axisX.setTitleText(self._x_title) if self._x_format == "time": self.axisX.setFormat("hh:mm") else: self.axisX.setFormat("yyyy-MM-dd") def setYFormat(self, stringtype, title="", decimals=2): self._y_format = stringtype self._y_decimals = decimals self._y_title = title def _applyYFormat(self): self.axisY.setTitleText(self._y_title) if self._y_format == "int": self.axisY.setLabelFormat("%i") elif self._y_format in ["float", "Decimal"]: self.axisY.setLabelFormat("%.{}f".format(self._y_decimals)) def setAllowHideSeries(self, boolean): self._allowHideSeries = boolean def appendTemporalSeries(self, name): ls = QLineSeries() ls.setName(name) self.series.append(ls) return ls def appendTemporalSeriesData(self, ls, x, y): """ x is a datetime zone aware """ x = dtaware2epochms(x) y = float(y) ls.append(x, y) if self.maxy == None: #Gives first maxy and miny self.maxy = y * 1.01 self.miny = y * 0.99 self.maxx = x * 1.01 self.minx = x * 0.99 if y > self.maxy: self.maxy = y if y < self.miny: self.miny = y if x > self.maxx: self.maxx = x if x < self.minx: self.minx = x def mouseMoveEvent(self, event): ##Sets the place of the popup in the windows to avoid getout of the screen ##frmshow can be a frmShowCasilla or a frmShowFicha def placePopUp(): resultado = QPoint(event.x() + 15, event.y() + 15) if event.x() > self.width() - self.popup.width() - 15: resultado.setX(event.x() - self.popup.width() - 15) if event.y() > self.height() - self.popup.height() - 15: resultado.setY(event.y() - self.popup.height() - 15) return resultado # --------------------------------------- if self.popuplock.tryLock() == False: event.reject() return QChartView.mouseMoveEvent(self, event) xVal = self.chart().mapToValue(event.pos()).x() yVal = self.chart().mapToValue(event.pos()).y() maxX = self.axisX.max().toMSecsSinceEpoch() minX = self.axisX.min().toMSecsSinceEpoch() maxY = self.axisY.max() minY = self.axisY.min() if xVal <= maxX and xVal >= minX and yVal <= maxY and yVal >= minY: self.popup.move(self.mapToGlobal(placePopUp())) self.popup.refresh(self, xVal, yVal) self.popuplock.unlock() ## Return the value of the serie in x def series_value(self, serie, x): if serie.__class__.__name__ == "QLineSeries": for point in serie.pointsVector(): if point.x() >= x: return point.y() elif serie.__class__.__name__ == "QCandlestickSeries": for qohcl in serie.sets(): if qohcl.timestamp() >= x: return qohcl.close ## Returns values from QSeries objects in an ordereddict d[x]=y, key is a dtaware def series_dictionary(self, serie): d = OrderedDict() if serie.__class__.__name__ == "QLineSeries": for point in serie.pointsVector(): d[epochms2dtaware(point.x())] = point.y() elif serie.__class__.__name__ == "QCandlestickSeries": for qohcl in serie.sets(): d[epochms2dtaware(qohcl.timestamp())] = qohcl.close return d @pyqtSlot() def on_marker_clicked(self): marker = QObject.sender( self ) #Busca el objeto que ha hecho la signal en el slot en el que está conectado, ya que estaban conectados varios objetos a una misma señal marker.series().setVisible(not marker.series().isVisible()) marker.setVisible(True) if marker.series().isVisible(): alpha = 1 else: alpha = 0.5 lbrush = marker.labelBrush() color = lbrush.color() color.setAlphaF(alpha) lbrush.setColor(color) marker.setLabelBrush(lbrush) brush = marker.brush() color = brush.color() color.setAlphaF(alpha) brush.setColor(color) marker.setBrush(brush) pen = marker.pen() color = pen.color() color.setAlphaF(alpha) pen.setColor(color) marker.setPen(pen) ## Used to display chart. You cannot use it twice. close the view widget and create another one def display(self): if self._chart != None: del self._chart self._chart = QChart() self.setChart(self._chart) if self._animations == True: self.chart().setAnimationOptions(QChart.AllAnimations) else: self.chart().setAnimationOptions(QChart.NoAnimation) self.chart().layout().setContentsMargins(0, 0, 0, 0) self._display_set_title() self._applyXFormat() self._applyYFormat() self.chart().addAxis(self.axisY, Qt.AlignLeft) self.chart().addAxis(self.axisX, Qt.AlignBottom) for s in self.series: self.chart().addSeries(s) s.attachAxis(self.axisX) s.attachAxis(self.axisY) self.axisY.setRange(self.miny, self.maxy) #Legend positions if len(self.chart().legend().markers()) > 6: self.chart().legend().setAlignment(Qt.AlignLeft) else: self.chart().legend().setAlignment(Qt.AlignTop) if self._allowHideSeries == True: for marker in self.chart().legend().markers(): try: marker.clicked.disconnect() except: pass marker.clicked.connect(self.on_marker_clicked) self.repaint() ## Returns a qmenu to be used in other qmenus def qmenu(self, title="Chart options"): menu = QMenu(self) menu.setTitle(self.tr(title)) menu.addAction(self.actionCopyToClipboard) menu.addSeparator() menu.addAction(self.actionSave) return menu ## If you use VCPieAlone you can add a context menu setting boolean to True def setCustomContextMenu(self, boolean): self.customContextMenuRequested.connect( self.on_customContextMenuRequested) def on_customContextMenuRequested(self, pos): self.qmenu().exec_(self.mapToGlobal(pos))