class ChartWidget(QWidget): def __init__(self, number: int, parent=None): super(QWidget, self).__init__(parent) self.layout = QVBoxLayout(self) self.chart = QChart() self.chartView = QChartView(self.chart) self.chartView.setRenderHint(QPainter.Antialiasing) # 抗锯齿 self.layout.addWidget(self.chartView) self.channelNumber = number for index in range(self.channelNumber): tmp = QLineSeries() tmp.setName("channel "+str(index)) self.chart.addSeries(tmp) self.chart.createDefaultAxes() self.chart.axisX().setRange(0, 25) self.chart.axisX().setTitleText("AAA") self.chart.axisY().setRange(0, 25) self.chart.axisY().setTitleText("BBB") # 标题 self.chart.setTitle("XXX XXX XXX") # 指示颜色所代表的内容 # self.chart.legend().hide() # 动画效果 self.chart.setAnimationOptions(QChart.AllAnimations) def set_line(self, *lines): for index in range(len(lines)): self.chart.series()[index].clear() for v1, v2 in enumerate(lines[index]): self.chart.series()[index].append(v1, v2)
class ErrorLineChart(QFrame): def __init__(self, nseries=1, series_names=None): super().__init__() if nseries < 1: raise ValueError( 'The number of serieses must be larger than zero.') self.nseries = nseries self.series_names = series_names layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) self.setMinimumHeight(110) self.setMinimumWidth(400) self.serieses = [QLineSeries() for _ in range(self.nseries)] self.chart = QChart() if self.series_names is None: self.chart.legend().hide() for idx, series in enumerate(self.serieses): self.chart.addSeries(series) if self.series_names is not None: series.setName(self.series_names[idx]) self.chart.createDefaultAxes() self.chart.layout().setContentsMargins(0, 0, 0, 0) # self.chart.setTheme(QChart.ChartThemeDark) self.chart.axisY().setTickCount(3) chart_view = QChartView(self.chart) chart_view.setRenderHint(QPainter.Antialiasing) layout.addWidget(chart_view) self.x_max = 2 self.y_pts = list() def append_point(self, x, y, series_idx=0): self.serieses[series_idx].append(x, y) self.x_max = max(x, self.x_max) self.y_pts.append(y) if self.x_max > 100: self.chart.axisX().setRange(self.x_max - 100, self.x_max) y_max = max(self.y_pts[-100:]) self.serieses[series_idx].remove(self.x_max - 100, self.y_pts[self.x_max - 101]) else: self.chart.axisX().setRange(1, self.x_max) y_max = max(self.y_pts) self.chart.axisY().setRange(0, y_max + y_max / 5) def clear(self): self.chart.removeAllSeries() self.serieses = [QLineSeries() for _ in range(self.nseries)] for idx, series in enumerate(self.serieses): self.chart.addSeries(series) if self.series_names is not None: series.setName(self.series_names[idx]) self.chart.createDefaultAxes() self.chart.axisY().setTickCount(3) self.x_max = 2 self.y_pts = list()
def plot_candlechart(ohlc_data): app = ParaMakerApplication([]) #app.setStyleSheet("background-color:black;") series = QCandlestickSeries() series.setBodyOutlineVisible(False) series.setDecreasingColor(Qt.red) series.setIncreasingColor(Qt.green) rsi = qc.QLineSeries() # 5-days average data line rsi.append(QPointF(ohlc_data[300].timestamp, ohlc_data[300].closed)) rsi.append(QPointF(ohlc_data[700].timestamp, ohlc_data[700].closed)) #rsi.append(QPointF(ohlc_data[150].timestamp, ohlc_data[100].closed)) tm = [] # stores str type data # in a loop, series and rsi append corresponding data for candle in ohlc_data: series.append( QCandlestickSet(candle.opened, candle.high, candle.low, candle.closed)) #rsi.append(QPointF(num, m)) tm.append(str(candle.timestamp)) #rsi.append(str(candle.timestamp)) #rsi_values = calculate_rsi(14, ohlc_data) chart = QChart() chart.setBackgroundVisible(False) chart.setPlotAreaBackgroundVisible(False) chart.addSeries(series) # candle chart.addSeries(rsi) # rsi line #chart.axisX(rsi).setRange(ohlc_data[0].timestamp, ohlc_data[-1].timestamp) chart.createDefaultAxes() axisXRSI = QValueAxis() axisYRSI = QValueAxis() axisXRSI.setRange(ohlc_data[0].timestamp, ohlc_data[-1].timestamp) axisYRSI.setRange(ohlc_data[0].closed, ohlc_data[-1].closed) axisXRSI.setGridLineVisible(False) axisYRSI.setGridLineVisible(False) chart.setAxisX(axisXRSI, rsi) chart.setAxisY(axisYRSI, rsi) chart.legend().hide() chart.axisX(series).setCategories(tm) #chart.axisX(series).setGridLineVisible(False) #chart.axisY(series).setGridLineVisible(False) ###chart.axisX(rsi).setVisible(False) chartview = QChartView(chart) chartview.setRenderHint(QPainter.Antialiasing) ui = ParaMakerWindow() ui.setCentralWidget(chartview) sys.exit(app.exec_())
class QtChartCanvas(QWidget): def __init__(self, parent=None): super(QtChartCanvas, self).__init__(parent) self.plotChart = QChart() self.plotChart.legend().hide() self.verticalLayout = QtWidgets.QVBoxLayout(self) self.plotView = QChartView(self.plotChart) self.verticalLayout.addWidget(self.plotView) # self.setCentralWidget(self.plotView) self.plotCurve = QSplineSeries() self.plotCurve.setUseOpenGL(True) self.plotCurve.pen().setColor(Qt.red) self.plotChart.addSeries(self.plotCurve) self.plotChart.createDefaultAxes() # self.plotChart.axisX().setLabelFormat('%d') self.plotChart.axisX().hide() self.RecvData = [] # 存储接收到的传感器数据 self.RecvIndx = 0 self.setLockY = True self.isTop = False self.minY = 0 self.maxY = 100 def update_figure(self, payload): self.RecvData.append(payload["data"]) self.RecvData = self.RecvData[-20:] plotData = [] #print("call") if self.isTop: for i, val in enumerate(self.RecvData): plotData.append(QPoint(i, val)) self.plotCurve.replace(plotData) self.plotChart.axisX().setMax(len(plotData)) if not self.setLockY: self.plotChart.axisY().setRange(min(self.RecvData), max(self.RecvData)) else: self.plotChart.axisY().setRange(self.minY, self.maxY) def setYLimit(self, miny=0, maxy=100, lockY=True): self.setLockY = lockY if self.setLockY: self.maxY = maxy self.minY = miny # self.plotChart.axisY().setRange(miny,maxy) def changeTop(self, isTop): self.isTop = isTop
def main (): if len (sys.argv) < 3: print ("файл не задан") sys.exit () with open (sys.argv[1], 'rt') as file: TicksFile = file.read() TicksTuple = tuple(TicksFile.split('\n')[:-1]) Bars = (FormBarFrame(TicksTuple, 60)) with open(sys.argv[1][:-4] + '_' + sys.argv[2] + '.bars', 'wt') as file: for i in reversed(Bars): file.write("{0} {1} {2} {3} {4} {5} {6}\n".format(str(i[0]), i[1], i[2], i[3], i[4], i[5], i[6])) # Вывод графика app = QApplication(sys.argv) series = QCandlestickSeries() series.setDecreasingColor(Qt.red) series.setIncreasingColor(Qt.green) #ma5 = qc.QLineSeries() # 5-days average data line tm = [] # stores str type data # in a loop, series and ma5 append corresponding data #for num, o, h, l, c in Bars: for i in range(len(Bars)): series.append(QCandlestickSet(Bars[i][1], Bars[i][4], Bars[i][3], Bars[i][2])) #ma5.append(QPointF(num, m)) tm.append(str(Bars[i][0])) chart = QChart() chart.addSeries(series) # candle #chart.addSeries(ma5) # ma5 line chart.setAnimationOptions(QChart.SeriesAnimations) chart.createDefaultAxes() chart.legend().hide() chart.axisX(series).setCategories(tm) #chart.axisX(ma5).setVisible(False) chartview = QChartView(chart) ui = QMainWindow() ui.setGeometry(50, 50, 500, 300) ui.setCentralWidget(chartview) ui.show() sys.exit(app.exec_())
def test_generations(): range_a = float(str(form.input_a_test.text())) range_b = float(str(form.input_b_test.text())) precision = int(str(form.input_d_test.text())) generations = int(str(form.input_generations_test.text())) app.setOverrideCursor(QtCore.Qt.WaitCursor) start = time() result = test_generation(range_a, range_b, precision, generations) app.restoreOverrideCursor() chart = QChart() series = QLineSeries() form.test_table.setRowCount(0) form.test_table.insertRow(0) item = QtWidgets.QTableWidgetItem("iteracje") item.setTextAlignment(QtCore.Qt.AlignCenter) form.test_table.setItem(0, 0, item) item = QtWidgets.QTableWidgetItem("wystąpienia") item.setTextAlignment(QtCore.Qt.AlignCenter) form.test_table.setItem(0, 1, item) item = QtWidgets.QTableWidgetItem("%") item.setTextAlignment(QtCore.Qt.AlignCenter) form.test_table.setItem(0, 2, item) for i in range(0, generations): percent = sum(result[:i+1])/100000*100 series.append(i+1, percent) form.test_table.insertRow(i+1) form.test_table.setItem(i+1, 0, QtWidgets.QTableWidgetItem(str(i+1))) form.test_table.setItem(i+1, 1, QtWidgets.QTableWidgetItem(str(result[i]))) form.test_table.setItem(i+1, 2, QtWidgets.QTableWidgetItem(str(round(percent, 2)))) chart.addSeries(series) chart.setBackgroundBrush(QtGui.QColor(41, 43, 47)) chart.createDefaultAxes() chart.legend().hide() chart.setContentsMargins(-10, -10, -10, -10) chart.layout().setContentsMargins(0, 0, 0, 0) chart.axisX().setTickCount(10) chart.axisY().setRange(0, 100) chart.axisY().setTickCount(11) chart.axisX().setLabelsColor(QtGui.QColor("white")) chart.axisY().setLabelsColor(QtGui.QColor("white")) form.widget_test.setChart(chart)
def update_particles(particles): animation_chart = QChart() reals = QScatterSeries() pen_reals = reals.pen() pen_reals.setBrush(QtGui.QColor("white")) reals.setMarkerSize(5) reals.setColor(QtGui.QColor("red")) reals.setPen(pen_reals) for particle in particles: reals.append(particle, 0) animation_chart.addSeries(reals) animation_chart.setBackgroundBrush(QtGui.QColor(41, 43, 47)) animation_chart.createDefaultAxes() animation_chart.legend().hide() animation_chart.setContentsMargins(-10, -10, -10, -10) animation_chart.layout().setContentsMargins(0, 0, 0, 0) animation_chart.axisX().setTickCount(17) animation_chart.axisY().setTickCount(3) animation_chart.axisX().setLabelsColor(QtGui.QColor("white")) animation_chart.axisX().setGridLineColor(QtGui.QColor("grey")) animation_chart.axisX().setRange(-4, 12) animation_chart.axisY().setRange(-1, 1) animation_chart.axisY().setLabelsColor(QtGui.QColor("white")) animation_chart.axisY().setGridLineColor(QtGui.QColor("grey")) form.widget_animation.setChart(animation_chart)
def run_app(self): chart = QChart() chart.addSeries(self.series) # candle chart.createDefaultAxes() chart.legend().hide() chart.axisX(self.series).setCategories(self.tm) chart_view = QChartView(chart) self.win.setGeometry(50, 50, 800, 500) self.win.setCentralWidget(chart_view) self.win.show() sys.exit(self.app.exec_())
class DemoWindow(QMainWindow): def __init__(self, parent=None): super(DemoWindow, self).__init__(parent=parent) self.plotChart = QChart() self.plotChart.legend().hide() self.plotView = QChartView(self.plotChart) self.setCentralWidget(self.plotView) self.plotCurve = QLineSeries() self.plotCurve.setUseOpenGL(True) self.plotCurve.pen().setColor(Qt.red) self.plotChart.addSeries(self.plotCurve) self.plotChart.createDefaultAxes() self.plotChart.axisX().setLabelFormat('%d') self.RecvData = array.array('f') # 存储接收到的传感器数据 self.RecvIndx = 0 self.tmrData = QTimer() # 模拟传感器传送过来数据 self.tmrData.setInterval(3) self.tmrData.timeout.connect(self.on_tmrData_timeout) self.tmrData.start() self.tmrPlot = QTimer() self.tmrPlot.setInterval(100) self.tmrPlot.timeout.connect(self.on_tmrPlot_timeout) self.tmrPlot.start() def on_tmrData_timeout(self): val = math.sin(2 * 3.14 / 500 * self.RecvIndx) self.RecvData.append(val) self.RecvIndx += 1 def on_tmrPlot_timeout(self): self.RecvData = self.RecvData[-1000:] plotData = [] for i, val in enumerate(self.RecvData): plotData.append(QPointF(i, val)) self.plotCurve.replace(plotData) self.plotChart.axisX().setMax(len(plotData)) self.plotChart.axisY().setRange(min(self.RecvData), max(self.RecvData))
class ChartWidget(QWidget): def __init__(self, parent=None, ticker="BTCUSDT"): super().__init__(parent) uic.loadUi("resource/chart.ui", self) self.ticker = ticker self.viewLimit = 10 self.tm = [] self.priceData = QCandlestickSeries() self.priceData.setDecreasingColor(Qt.red) self.priceData.setIncreasingColor(Qt.green) self.priceChart = QChart() self.priceChart.addSeries(self.priceData) self.priceChart.legend().hide() axisX = QDateTimeAxis() axisX.setFormat("hh:mm:ss") axisX.setTickCount(4) dt = QDateTime.currentDateTime() axisX.setRange(dt, dt.addSecs(self.viewLimit)) axisY = QValueAxis() axisY.setVisible(False) self.priceChart.addAxis(axisX, Qt.AlignBottom) self.priceChart.addAxis(axisY, Qt.AlignRight) self.priceData.attachAxis(axisX) self.priceData.attachAxis(axisY) self.priceChart.layout().setContentsMargins(0, 0, 0, 0) self.priceView.setChart(self.priceChart) self.priceView.setRenderHints(QPainter.Antialiasing) self.pw = PriceWorker(ticker) self.pw.dataSent.connect(self.appendData) self.pw.start() def appendData(self, o, h, l, c): if len(self.tm) == self.viewLimit: self.priceData.remove(0) self.tm.remove(0) dt = QDateTime.currentDateTime() self.priceData.append(QCandlestickSet(o, h, l, c)) print(dt.toMSecsSinceEpoch()) print(type(dt.toMSecsSinceEpoch())) self.tm.append(dt.toMSecsSinceEpoch()) self.__updateAxis() def __updateAxis(self): pvs = self.tm dtStart = QDateTime.fromMSecsSinceEpoch(int(pvs[0])) if len(self.priceData) == self.viewLimit: dtLast = QDateTime.fromMSecsSinceEpoch(int(pvs[-1])) else: dtLast = dtStart.addSecs(self.viewLimit) ax = self.priceChart.axisX() ax.setRange(dtStart, dtLast)
def __init__(self, *args, **kwargs): super(Window, self).__init__(*args, **kwargs) self.resize(400, 300) # 抗锯齿 self.setRenderHint(QPainter.Antialiasing) # 图表 chart = QChart() self.setChart(chart) # 设置标题 chart.setTitle('Simple areachart example') # 添加Series chart.addSeries(self.getSeries()) # 创建默认轴线 chart.createDefaultAxes() # 设置xy轴的范围 chart.axisX().setRange(0, 20) chart.axisY().setRange(0, 10)
class VLineChartView(QChartView): def __init__(self): super(VLineChartView, self).__init__() self.stocks = read_tick_data() self.category = [ trade_date[4:] for trade_date in self.stocks['trade_date'] ] self.resize(800, 300) self.initChart() def initChart(self): self._chart = QChart(title='数量') self._chart.setAnimationOptions(QChart.SeriesAnimations) series = QStackedBarSeries() series.setName('数量') bar_red = QBarSet('red') bar_red.setColor(Qt.red) bar_green = QBarSet('green') bar_green.setColor(Qt.green) for _, stock in self.stocks.iterrows(): if stock['open'] < stock['close']: bar_red.append(stock['vol'] / 100) bar_green.append(0) else: bar_red.append(0) bar_green.append(stock['vol'] / 100) series.append(bar_red) series.append(bar_green) self._chart.addSeries(series) self._chart.createDefaultAxes() self._chart.setLocalizeNumbers(True) axis_x = self._chart.axisX() axis_y = self._chart.axisY() axis_x.setGridLineVisible(False) axis_y.setGridLineVisible(False) axis_y.setLabelFormat("%.2f") axis_x.setCategories(self.category) max_p = self.stocks[[ 'vol', ]].stack().max() / 100 + 10 min_p = self.stocks[[ 'vol', ]].stack().min() / 100 - 10 axis_y.setRange(min_p, max_p) # chart的图例 legend = self._chart.legend() legend.hide() # 设置图例由Series来决定样式 # legend.setMarkerShape(QLegend.MarkerShapeFromSeries) self.setChart(self._chart) self._chart.layout().setContentsMargins(0, 0, 0, 0) # self._chart.setMargins(QMargins(0, 0, 0, 0)) self._chart.setBackgroundRoundness(0)
class ChartWidget(QWidget): def __init__(self, parent=None, ticker="BTC"): super().__init__(parent) uic.loadUi("chart.ui", self) self.ticker = ticker self.viewLimit = 128 self.priceData = QLineSeries() self.priceChart = QChart() self.priceChart.addSeries(self.priceData) self.priceChart.legend().hide() axisX = QDateTimeAxis() axisX.setFormat("hh:mm:ss") axisX.setTickCount(4) dt = QDateTime.currentDateTime() axisX.setRange(dt, dt.addSecs(self.viewLimit)) axisY = QValueAxis() axisY.setVisible(False) self.priceChart.addAxis(axisX, Qt.AlignBottom) self.priceChart.addAxis(axisY, Qt.AlignRight) self.priceData.attachAxis(axisX) self.priceData.attachAxis(axisY) self.priceChart.layout().setContentsMargins(0, 0, 0, 0) self.priceView.setChart(self.priceChart) self.priceView.setRenderHints(QPainter.Antialiasing) # ----------------- 추 가 ------------------ self.pw = PriceWorker(ticker) self.pw.dataSent.connect(self.appendData) self.pw.start() # ------------------------------------------ def appendData(self, currPirce): if len(self.priceData) == self.viewLimit: self.priceData.remove(0) dt = QDateTime.currentDateTime() self.priceData.append(dt.toMSecsSinceEpoch(), currPirce) self.__updateAxis() def __updateAxis(self): pvs = self.priceData.pointsVector() dtStart = QDateTime.fromMSecsSinceEpoch(int(pvs[0].x())) if len(self.priceData) == self.viewLimit: dtLast = QDateTime.fromMSecsSinceEpoch(int(pvs[-1].x())) else: dtLast = dtStart.addSecs(self.viewLimit) ax = self.priceChart.axisX() ax.setRange(dtStart, dtLast) ay = self.priceChart.axisY() dataY = [v.y() for v in pvs] ay.setRange(min(dataY), max(dataY))
class Simulator(QtWidgets.QMainWindow, gui.Ui_MainWindow): def __init__(self): super().__init__() self.__car = car.motion() self.__car.param.fromJSON(car.defaultParams()) self.__canbus = None self.__translator = QTranslator(self) self.setupUi(self) self.positionChart = QChart() self.positionSeries = QLineSeries() self.speedChart = QChart() self.speedSeries = QLineSeries() self.fuelChart = QChart() self.fuelSeries = QLineSeries() self.engineChart = QChart() self.engineSeries = QLineSeries() self.positionChart.addSeries(self.positionSeries) self.speedChart.addSeries(self.speedSeries) self.fuelChart.addSeries(self.fuelSeries) self.engineChart.addSeries(self.engineSeries) self.positionChart.legend().hide() self.speedChart.legend().hide() self.fuelChart.legend().hide() self.engineChart.legend().hide() self.positionChart.createDefaultAxes() self.speedChart.createDefaultAxes() self.fuelChart.createDefaultAxes() self.engineChart.createDefaultAxes() self.positionChart.setTitle("Position") self.speedChart.setTitle("Speed") self.fuelChart.setTitle("Fuel") self.engineChart.setTitle("Engine") self.positionChart.setMargins(QMargins()) self.speedChart.setMargins(QMargins()) self.fuelChart.setMargins(QMargins()) self.engineChart.setMargins(QMargins()) self.positionChartW.setChart(self.positionChart) self.speedChartW.setChart(self.speedChart) self.fuelChartW.setChart(self.fuelChart) self.engineChartW.setChart(self.engineChart) self.populateFields() for file in os.listdir(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'lang')): if file.startswith('carSim_') and file.endswith('.qm'): self.langSelector.addItem(file[7:-3]) def changeEvent(self, event): if event.type() == QEvent.LanguageChange: self.retranslateUi(self) super().changeEvent(event) @pyqtSlot(str) def on_langSelector_currentTextChanged(self, lang): if lang: self.__translator.load(QLocale(lang), 'carSim', '_', 'lang', '.qm') QtWidgets.QApplication.instance().installTranslator(self.__translator) else: QtWidgets.QApplication.instance().removeTranslator(self.__translator) @pyqtSlot() def on_resetSimulationButton_clicked(self): self.__car = car.motion() self.__car.param.fromJSON(car.defaultParams()) self.positionSeries.clear() self.speedSeries.clear() self.fuelSeries.clear() self.engineSeries.clear() self.populateFields() @pyqtSlot() def on_makeStepButton_clicked(self): try: # self.__car.setThrottle(self.engineField.text()) self.__car.makeStep() # motionMessage=can.Message(arbitration_id=18,is_extended_id=False,data=self.__car.getCanBytes()[:]) motionMessage = car.canMsg( StdId=18, Data=self.__car.getCanBytes()[:]) print(motionMessage) if (self.__canbus is not None and self.__canbus.connected == True): self.__canbus.sendMsg(motionMessage) with open('log.dat', 'a') as outfile: outfile.write("%.1f\t%f\t%f\t%f\n" % (self.__car.getSimTime(), self.__car.getSimDistance(), self.__car.getSimSpeed(), self.__car.getSimFuel())) except Exception as e: QtWidgets.QMessageBox.warning( self, _translate("Dialog", "Error"), str(e)) self.populateFields() if float(self.timeField.text()) >= 240: if self.simulationStart.isChecked() == True: self.simulationStart.click() def populateFields(self): self.timeField.setText(f"{self.__car.getSimTime():.2f}") self.positionField.setText(f"{self.__car.getSimDistance():.2f}") self.speedField.setText(f"{self.__car.getSimSpeed():.2f}") self.fuelField.setText(f"{self.__car.getSimFuel():.2f}") if (self.__canbus is not None and self.__canbus.connected == True): self.engineField.setText(f"{self.__car.getThrottle():.2f}") xax = self.positionChart.axisX() if(self.__car.getSimTime()>self.positionChart.axisX().max()): self.positionChart.axisX().setMax(self.__car.getSimTime()) self.speedChart.axisX().setMax(self.__car.getSimTime()) self.fuelChart.axisX().setMax(self.__car.getSimTime()) self.engineChart.axisX().setMax(self.__car.getSimTime()) #elif(self.__car.getSimTime()<self.positionChart.axisX().min()): # self.positionChart.axisX().setMin(self.__car.getSimTime()) # self.speedChart.axisX().setMin(self.__car.getSimTime()) # self.fuelChart.axisX().setMin(self.__car.getSimTime()) # self.engineChart.axisX().setMin(self.__car.getSimTime()) if(self.__car.getSimDistance()>self.positionChart.axisY().max()): self.positionChart.axisY().setMax(self.__car.getSimDistance()) elif(self.__car.getSimSpeed()<self.speedChart.axisY().min()): self.positionChart.axisY().setMin(self.__car.getSimDistance()) if(self.__car.getSimSpeed()>self.speedChart.axisY().max()): self.speedChart.axisY().setMax(self.__car.getSimSpeed()) elif(self.__car.getSimSpeed()<self.speedChart.axisY().min()): self.speedChart.axisY().setMin(self.__car.getSimSpeed()) if(self.__car.getSimFuel()>self.fuelChart.axisY().max()): self.fuelChart.axisY().setMax(self.__car.getSimFuel()) elif(self.__car.getSimFuel()<self.fuelChart.axisY().min()): self.fuelChart.axisY().setMin(self.__car.getSimFuel()) self.positionSeries.append(self.__car.getSimTime(),self.__car.getSimDistance()) self.speedSeries.append(self.__car.getSimTime(),self.__car.getSimSpeed()) self.fuelSeries.append(self.__car.getSimTime(),self.__car.getSimFuel()) self.engineSeries.append(self.__car.getSimTime(),self.__car.getThrottle()) @pyqtSlot(str) def on_engineField_textEdited(self,newEngineValue): try: self.__car.setThrottle(newEngineValue) except: pass @pyqtSlot() def on_actionAbout_triggered(self): self.AboutDialog = QtWidgets.QDialog() self.AboutDialog.ui = gui.Ui_AboutDialog() self.AboutDialog.ui.setupUi(self.AboutDialog) self.AboutDialog.setAttribute(Qt.WA_DeleteOnClose) self.AboutDialog.exec_() @pyqtSlot() def on_actionExport_Settings_triggered(self): filename, _ = QtWidgets.QFileDialog.getSaveFileName( self, _translate("Dialog", "Save Config"), ".", filter=_translate("Dialog", "Config Files (*.json)")+";;"+_translate("Dialog", "All Files(*.*)")) if filename: fp = open(filename, 'w') self.__car.param.toJSON(file=fp) fp.close() @pyqtSlot() def on_actionImport_Settings_triggered(self): filename, _ = QtWidgets.QFileDialog.getOpenFileName( self, _translate("Dialog", "Open Config"), ".", filter=_translate("Dialog", "Config Files (*.json)")+";;"+_translate("Dialog", "All Files(*.*)")) if filename: fp = open(filename, 'r') self.__car.param.fromJSON(fp) fp.close() @pyqtSlot() def on_refreshAvailablePorts_clicked(self): ports = serial.tools.list_ports.comports() self.availablePorts.clear() for port, desc, hwid in sorted(ports): self.availablePorts.addItem(desc, port) @pyqtSlot(bool) def on_connectPort_clicked(self, state): if state: try: self.__canbus = car.myCan(self.availablePorts.currentData(), [ self.canReceived.appendPlainText], [ self.__car.setSwitchingPoint], loopTime=0.025) self.__watch = QTimer(self) self.__watch.setInterval(1000) self.__watch.timeout.connect(self.syncTime) self.__watch.start() except Exception as e: self.connectPort.setChecked(False) QtWidgets.QMessageBox.critical( self, _translate("Dialog", "Error"), str(e)) else: self.connectPort.setText( _translate("MainWindow", "Disconnect")) self.canInterfaceTypes.setEnabled(False) self.availablePorts.setEnabled(False) self.refreshAvailablePorts.setEnabled(False) else: self.connectPort.setText(_translate("MainWindow", "Connect")) self.__canbus = None del(self.__watch) self.canInterfaceTypes.setEnabled(True) self.availablePorts.setEnabled(True) self.refreshAvailablePorts.setEnabled(True) @pyqtSlot() def syncTime(self): self.__canbus.sendMsg(car.canMsg(StdId=0, Data=int( self.__car.getSimTime()*1000.0).to_bytes(4, 'little'))) @pyqtSlot(bool) def on_simulationStart_clicked(self, state): if state: self.__ticker = QTimer(self) self.__ticker.setInterval(100) self.__ticker.timeout.connect(self.on_makeStepButton_clicked) self.__ticker.start() self.__car.setThrottle(1) self.simulationStart.setText( _translate("MainWindow", "Stop Simulation!")) lapData = 1 with open('log.dat', 'a') as outfile: outfile.write("%.1f\t%f\t%f\t%f\n" % (self.__car.getSimTime(), self.__car.getSimDistance(), self.__car.getSimSpeed(), self.__car.getSimFuel())) else: self.__ticker.stop() self.simulationStart.setText( _translate("MainWindow", "Start Simulation!")) lapData = 0 lapMessage = car.canMsg(StdId=32, Data=[lapData]) print(lapMessage) if (self.__canbus is not None and self.__canbus.connected == True): self.__canbus.sendMsg(lapMessage)
class QtAgentGraph(QChartView): def __init__(self, spec): super().__init__(None) self.spec = spec self.chart = QChart() self.chart.setTitle(str(self.spec.variable)) self.chart.legend().hide() self.setMinimumWidth(400) self.setMinimumHeight(230) self.setChart(self.chart) self.setRenderHint(QPainter.Antialiasing) self.chart.createDefaultAxes() self.autoscale_y_axis = True if self.spec.min_y and self.spec.max_y: self.autoscale_y_axis = False self.chart.axes()[1].setRange(self.spec.min_y, self.spec.max_y) self.axis_x = QValueAxis() self.axis_y = QValueAxis() self.chart.addAxis(self.axis_x, Qt.AlignBottom) self.chart.addAxis(self.axis_y, Qt.AlignLeft) self._updates_per_second = 60 self._data = [] self._min = 0 self._max = 0 def clear(self): for chart in self.chart.series(): chart.clear() self._data = [] def update_data(self): for a in self.spec.agents: if not hasattr(a, "_agent_series"): a._agent_series = QLineSeries() a._agent_series.setColor( QColor(a.color[0], a.color[1], a.color[2])) a._agent_series_data = [getattr(a, self.spec.variable)] self.chart.addSeries(a._agent_series) a._agent_series.attachAxis(self.chart.axisX()) a._agent_series.attachAxis(self.chart.axisY()) else: a._agent_series_data.append(getattr(a, self.spec.variable)) def redraw(self): for a in self.spec.agents: if hasattr(a, "_agent_series") and len(a._agent_series_data) > 0: datapoint = sum(a._agent_series_data) / len( a._agent_series_data) a._agent_series.append( QPointF( a._agent_series.count() / self._updates_per_second, datapoint, )) self._min = min(self._min, datapoint) self._max = max(self._max, datapoint) a._agent_series.setColor( QColor(a.color[0], a.color[1], a.color[2])) a._agent_series_data = [] if len(self.spec.agents) > 0: first_agent = self.spec.agents[0] if hasattr(first_agent, "_agent_series"): first_series = first_agent._agent_series self.chart.axes()[0].setRange(0, (first_series.count() - 1) / self._updates_per_second) diff = self._max - self._min if self.autoscale_y_axis: if diff > 0: self.chart.axes()[1].setRange(self._min, self._max) else: self.chart.axes()[1].setRange(self._min - 0.5, self._max + 0.5)
class QtChartCanvas(QWidget): def __init__(self, parent=None): super(QtChartCanvas, self).__init__(parent) self.setStyleSheet("border:0;background-color:#263848") self.plotChart = QChart() self.plotChart.legend().hide() self.verticalLayout = QtWidgets.QVBoxLayout(self) self.plotView = QChartView(self.plotChart) self.plotView.setStyleSheet("border:0;background-color:#263848;") self.plotView.setBackgroundBrush(QBrush(QColor("#263848"))) self.plotChart.setBackgroundBrush(QBrush(QColor("#263848"))) #self.plotChart.setStyle() self.verticalLayout.addWidget(self.plotView) self.plotCurve = QSplineSeries() self.plotCurve.setColor(QColor("#AABFFF")) self.plotCurve.setUseOpenGL(True) self.plotCurve.pen().setColor(QColor("#FAF0FF")) self.plotChart.addSeries(self.plotCurve) # self.scatter=QScatterSeries() # self.scatter.setMarkerSize(8) # self.plotChart.addSeries(self.scatter) self.plotChart.createDefaultAxes() self.plotChart.axisY().setGridLineColor(QColor("#5D5C72")) self.plotChart.axisY().setLinePenColor(QColor("#9D9CA2")) self.plotChart.axisY().setLabelsColor(QColor("#F8F6F6")) self.plotChart.axisY().setRange(0, 100) self.plotChart.axisX().hide() self.RecvData = [] # 存储接收到的传感器数据 self.RecvIndx = 0 self.setLockY = True self.isTop = False self.minY = 0 self.maxY = 100 def update_figure(self, payload): data = payload["data"] self.RecvData.append(data) self.RecvData = self.RecvData[-20:] plotData = [] if self.isTop: for i, val in enumerate(self.RecvData): plotData.append(QPoint(i, val)) self.plotCurve.replace(plotData) #self.scatter.replace(plotData) self.plotChart.axisX().setMax(len(plotData)) if not self.setLockY: self.plotChart.axisY().setRange( min(self.RecvData) * 0.5, max(self.RecvData) * 1.3) else: self.plotChart.axisY().setRange(self.minY, self.maxY) def setYLimit(self, miny=0, maxy=100, lockY=True): self.setLockY = lockY if self.setLockY: self.maxY = maxy self.minY = miny def changeTop(self, isTop): self.isTop = isTop
def main(): import sys from PyQt5.QtChart import QChart, QChartView from PyQt5.QtCore import Qt from PyQt5.QtGui import QPainter from PyQt5.QtWidgets import QApplication, QMainWindow import numpy as np @pyqtSlot(QPointF, bool) def help(point, state): print('help') app = QApplication(sys.argv) size = 50 bdr = 2 opc = 0.3 special = False markers = [ XMarkerCircle(), XMarkerPlus(), XMarkerDash(), XMarkerSquare(), XMarkerDiamond(), XMarkerCross(), XMarkerTriangle() ] # Chart Setup chart = QChart() chart.axisX = QValueAxis() chart.axisY = QValueAxis() chart.axisX.setTickCount(11) chart.axisY.setTickCount(11) chart.legend().setVisible(True) chart.legend().setAlignment(Qt.AlignBottom) chartView = QChartView(chart) chartView.setRenderHint(QPainter.Antialiasing) class scatser(QScatterSeries): def __init__(self, parent=None): super(scatser, self).__init__(parent) self.hovered.connect(self.onHovered) self.pen = QPen(Qt.black) @pyqtSlot(QPointF, bool) def onHovered(self, point, state): if state: spoint = self.chart().mapToPosition(point) - QPointF( 0, size / 2) self.ttip = XToolTipLabel('Hello from:\n x: %.1f, y: %.1f' % (point.x(), point.y()), colour=self.color()) self.ttip_proxy = chart.scene().addWidget(self.ttip) w = self.ttip.width() h = self.ttip.height() self.ttip_proxy.setPos(spoint.x() - w / 2.0, spoint.y() - h) self.ttip.updateGeometry() else: self.ttip_proxy.deleteLater() mcount = 0 nm = len(markers) pos = [0.1, 0.3, 0.5, 0.7, 0.9] for i in pos: for j in pos: if mcount == nm: mcount = 0 #print(mcount) m = markers[mcount] m.setBrush(size, size, border=bdr, opacity=opc) ser = scatser() ser.append(i, j) if special: ser.setBorderColor(QColor(1, 1, 1, 1)) ser.setMarkerSize(size) #ser.setMarkerShape(1) ser.setBrush(m) else: xtypemarker(ser, size=size, width=bdr, fillalpha=50) chart.addSeries(ser) chart.setAxisX(chart.axisX, ser) chart.setAxisY(chart.axisY, ser) mcount += 1 chart.axisX.setMin(0) chart.axisY.setMin(0) chart.axisX.setMax(1) chart.axisY.setMax(1) window = QMainWindow() window.setCentralWidget(chartView) window.resize(800, 800) window.show() sys.exit(app.exec_())
class Display(QWidget): def __init__(self, trainer='MLP', debug=False): # QWidget Setup QWidget.__init__(self, flags=Qt.CustomizeWindowHint | Qt.WindowTitleHint) self.setWindowTitle("NeuroSky GUI") self.resize(1400, 1000) # Linker Params self._linker = Linker(debug=debug, trainer=trainer) self.TRAINER_FORWARD = self._linker.trainer.add_identifier('forward') self.TRAINER_BACKWARD = self._linker.trainer.add_identifier('backward') self.TRAINER_IDLE = self._linker.trainer.add_identifier('idle') # Indicators self._raw_data_indicator = self._create_indicator('Raw Data:') self._poor_level_indicator = self._create_indicator( 'Poor Level Signal:') self._sample_rate_indicator = self._create_indicator('Sample Rate:') self._prediction_indicator = self._create_indicator('Prediction:') self._training_status_indicator = self._create_indicator( 'Training Status:') self._forward_counter_indicator = self._create_indicator( 'Current Forward Count:') self._backward_counter_indicator = self._create_indicator( 'Current Backward Count:') self._idle_counter_indicator = self._create_indicator( 'Current Idle Count:') # Initializing layout self.main_page() # Series self._x_axis = 0 self._connect_data() def main_page(self): # type: (Display) -> None # Top Layout top_left_layout = QVBoxLayout() top_left_layout.addLayout(self._raw_data_indicator['layout']) top_left_layout.addLayout(self._poor_level_indicator['layout']) top_left_layout.addLayout(self._sample_rate_indicator['layout']) top_right_layout = QVBoxLayout() top_right_layout.addWidget(self._get_connector_chart(), alignment=Qt.AlignCenter) # top_right_layout.setStretchFactor(self._get_connector_chart(), 1) top_layout = QHBoxLayout() top_layout.addLayout(top_left_layout) top_layout.addLayout(top_right_layout) # Bottom Layout bottom_left_layout = QVBoxLayout() bottom_left_layout.addLayout(self._prediction_indicator['layout']) bottom_left_layout.addLayout(self._training_status_indicator['layout']) bottom_left_layout.addLayout(self._idle_counter_indicator['layout']) bottom_left_layout.addLayout(self._forward_counter_indicator['layout']) bottom_left_layout.addLayout( self._backward_counter_indicator['layout']) bottom_right_layout = QVBoxLayout() bottom_right_layout.addWidget(self._get_processor_chart(), alignment=Qt.AlignCenter) bottom_layout = QHBoxLayout() bottom_layout.addLayout(bottom_left_layout) bottom_layout.addLayout(bottom_right_layout) # Outer Layout outer_layout = QVBoxLayout() outer_layout.addLayout(top_layout) outer_layout.addLayout(bottom_layout) # Set Layout self.setLayout(outer_layout) def _get_connector_chart(self): # type: (Display) -> QChartView # Create pen pen = QLineSeries().pen() pen.setColor(Qt.blue) pen.setWidthF(1) # Series self._connector_series = QLineSeries() self._connector_series.setPen(pen) self._connector_series.useOpenGL() # Chart self._connector_chart = QChart() self._connector_chart.legend().hide() self._connector_chart.addSeries(self._connector_series) self._connector_chart.createDefaultAxes() self._connector_chart.axisX().setMax(100) self._connector_chart.axisX().setMin(0) self._connector_chart.axisY().setMax(500) self._connector_chart.axisY().setMin(-500) # Chart View view = QChartView(self._connector_chart) view.setRenderHint(QPainter.Antialiasing) view.setStyleSheet('margin: 0px; height: 250%; width: 400%;') return view def _get_processor_chart(self): # type: (Display) -> QChartView # Create pen pen = QLineSeries().pen() pen.setColor(Qt.red) pen.setWidthF(1) # Series self._processor_series = QLineSeries() self._processor_series.setPen(pen) self._processor_series.useOpenGL() # Chart self._processor_chart = QChart() self._processor_chart.legend().hide() self._processor_chart.addSeries(self._processor_series) self._processor_chart.createDefaultAxes() self._processor_chart.axisX().setMax(100) self._processor_chart.axisX().setMin(0) self._processor_chart.axisY().setMax(5000) self._processor_chart.axisY().setMin(0) self._processor_x_axis = QValueAxis() self._processor_x_axis.setLabelFormat('%i') self._processor_chart.setAxisX(self._processor_x_axis, self._processor_series) self._processor_y_axis = QLogValueAxis() self._processor_y_axis.setLabelFormat('%g') self._processor_y_axis.setBase(8) # Chart View view = QChartView(self._processor_chart) view.setRenderHint(QPainter.Antialiasing) view.setStyleSheet('margin: 0px; height: 250%; width: 400%;') return view def _connect_data(self): # type: (Display) -> None self._linker.raw.connect(self._add_connector_data) self._linker.poor_signal_level.connect( lambda level: self._poor_level_indicator['label'].setText( str(level))) self._linker.sampling_rate.connect( lambda rate: self._sample_rate_indicator['label'].setText(str(rate) )) self._linker.fft.connect(self._add_processor_data) self._linker.prediction.connect( lambda prediction: self._prediction_indicator['label'].setText( str(prediction))) self._linker.training_status.connect( lambda status: self._training_status_indicator['label'].setText( str(status))) self._linker.identifiers.connect(self._connect_identifiers) def keyPressEvent(self, event): # type: (Display, {key}) -> None key = event.key() if key == Qt.Key_Escape: self._linker.close() self.close() elif key == Qt.Key_W: # self._linker.connector.record( # './data/raw_data/' + self._linker.trainer.get_next_connector_label(self.TRAINER_FORWARD) # ) # self._linker.processor.record( # './data/processed_data/' + self._linker.trainer.get_next_processor_label(self.TRAINER_FORWARD) # ) self._linker.trainer.train(self.TRAINER_FORWARD) elif key == Qt.Key_S: # self._linker.connector.record( # './data/raw_data/' + self._linker.trainer.get_next_connector_label(self.TRAINER_BACKWARD) # ) # self._linker.processor.record( # './data/processed_data/' + self._linker.trainer.get_next_processor_label(self.TRAINER_BACKWARD) # ) self._linker.trainer.train(self.TRAINER_BACKWARD) elif key == Qt.Key_Space: # self._linker.connector.record( # './data/raw_data/' + self._linker.trainer.get_next_connector_label(self.TRAINER_IDLE) # ) # self._linker.processor.record( # './data/processed_data/' + self._linker.trainer.get_next_processor_label(self.TRAINER_IDLE) # ) self._linker.trainer.train(self.TRAINER_IDLE) else: print(key) @staticmethod def _create_indicator( label): # type: (Any) -> Dict[str, Union[QHBoxLayout, QLabel]] layout = QHBoxLayout() display_widget = QLabel(label) layout.addWidget(display_widget, alignment=Qt.AlignCenter) label_widget = QLabel('Initializing...') layout.addWidget(label_widget, alignment=Qt.AlignCenter) return { 'layout': layout, 'display': display_widget, 'label': label_widget } @pyqtSlot(int) def _add_connector_data(self, data): # type: (Display, Any) -> Optional[Any] self._raw_data_indicator['label'].setText(str(data)) self._connector_series.append(self._x_axis, data) if self._connector_series.count() >= 100: self._connector_series.clear() self._x_axis = 0 else: self._x_axis += 1 @pyqtSlot(np.ndarray) def _add_processor_data( self, data): # type: (Display, {__getitem__}) -> Optional[Any] self._processor_series.clear() x_axis = data[0] y_axis = data[1] for i in range(len(x_axis)): self._processor_series.append(x_axis[i], y_axis[i]) @pyqtSlot(list) def _connect_identifiers(self, identifiers): for identifier in identifiers: if identifier['name'] == self.TRAINER_IDLE: self._idle_counter_indicator['label'].setText( str(identifier['training_count'])) elif identifier['name'] == self.TRAINER_FORWARD: self._forward_counter_indicator['label'].setText( str(identifier['training_count'])) elif identifier['name'] == self.TRAINER_BACKWARD: self._backward_counter_indicator['label'].setText( str(identifier['training_count']))
class ChartView(QChartView): def __init__(self, *args, **kwargs): super(ChartView, self).__init__(*args, **kwargs) self.resize(800, 600) self.setRenderHint(QPainter.Antialiasing) # 抗锯齿 self.initChart() self.toolTipWidget = GraphicsProxyWidget(self._chart) # line self.lineItem = QGraphicsLineItem(self._chart) self.lineItem.setZValue(998) self.lineItem.hide() # 一些固定计算,减少mouseMoveEvent中的计算量 # 获取x和y轴的最小最大值 axisX, axisY = self._chart.axisX(), self._chart.axisY() self.min_x, self.max_x = axisX.min(), axisX.max() self.min_y, self.max_y = axisY.min(), axisY.max() # 坐标系中左上角顶点 self.point_top = self._chart.mapToPosition(QPointF(self.min_x, self.max_y)) # 坐标原点坐标 self.point_bottom = self._chart.mapToPosition(QPointF(self.min_x, self.min_y)) self.step_x = (self.max_x - self.min_x) / (axisX.tickCount() - 1) # self.step_y = (self.max_y - self.min_y) / (axisY.tickCount() - 1) def mouseMoveEvent(self, event): super(ChartView, self).mouseMoveEvent(event) # 把鼠标位置所在点转换为对应的xy值 x = self._chart.mapToValue(event.pos()).x() y = self._chart.mapToValue(event.pos()).y() index = round((x - self.min_x) / self.step_x) pos_x = self._chart.mapToPosition( QPointF(index * self.step_x + self.min_x, self.min_y)) # print(x, pos_x, index, index * self.step_x + self.min_x) # 得到在坐标系中的所有series的类型和点 points = [(serie, serie.at(index)) for serie in self._chart.series() if self.min_x <= x <= self.max_x and self.min_y <= y <= self.max_y] if points: # 永远在轴上的黑线条 self.lineItem.setLine(pos_x.x(), self.point_top.y(), pos_x.x(), self.point_bottom.y()) self.lineItem.show() self.toolTipWidget.show("", points, event.pos() + QPoint(20, 20)) else: self.toolTipWidget.hide() self.lineItem.hide() def onSeriesHoverd(self, point, state): if state: try: name = self.sender().name() except: name = "" QToolTip.showText(QCursor.pos(), "%s\nx: %s\ny: %s" % (name, point.x(), point.y())) def initChart(self): self._chart = QChart(title="Line Chart") self._chart.setAcceptHoverEvents(True) dataTable = [ [120, 132, 101, 134, 90, 230, 210], [220, 182, 191, 234, 290, 330, 310], [150, 232, 201, 154, 190, 330, 410], [320, 332, 301, 334, 390, 330, 320], [820, 932, 901, 934, 1290, 1330, 1320] ] for i, data_list in enumerate(dataTable): series = QLineSeries(self._chart) for j, v in enumerate(data_list): series.append(j, v) series.setName("Series " + str(i)) series.setPointsVisible(True) # 显示原点 series.hovered.connect(self.onSeriesHoverd) self._chart.addSeries(series) self._chart.createDefaultAxes() # 创建默认的轴 self._chart.axisX().setTickCount(7) # x轴设置7个刻度 self._chart.axisY().setTickCount(7) # y轴设置7个刻度 self._chart.axisY().setRange(0, 1500) # 设置y轴范围 self.setChart(self._chart)
class KLineChartView(QChartView): # QCandlestickSeries的hovered的信号响应后传递日期出去 candles_hovered = pyqtSignal(bool, str) def __init__(self, data: pd.DataFrame): super(KLineChartView, self).__init__() self.setRenderHint(QPainter.Antialiasing) # 抗锯齿 self._chart = QChart() self._series = QCandlestickSeries() self._stocks = data self._category = list() self._count = None self.init_chart() self._zero_value = (0, self._chart.axisY().min()) self._max_value = (len(self._chart.axisX().categories()), self._chart.axisY().max()) self._zero_point = self._chart.mapToPosition( QPointF(self._zero_value[0], self._zero_value[1])) self._max_point = self._chart.mapToPosition( QPointF(self._max_value[0], self._max_value[1])) # 计算x轴单个cate的宽度,用来处理横线不能画到边界 self._cate_width = (self._max_point.x() - self._zero_point.x()) / len( self._category) self._series.hovered.connect(self.on_series_hovered) def on_series_hovered(self, status, candles_set): trade_date = time.strftime('%Y%m%d', time.localtime(candles_set.timestamp())) self.candles_hovered.emit(status, trade_date) def set_name(self, name): self._series.setName(name) def clear_series_values(self): self._series.clear() self._chart.axisY().setRange(0, 10) self._chart.axisX().setCategories(list()) self._stocks = None def add_series_values(self, data: pd.DataFrame, is_init=False): self._stocks = data self._category = self._stocks['trade_date'] self._count = len(self._category) for _, stock in self._stocks.iterrows(): time_p = datetime.datetime.strptime(stock['trade_date'], '%Y%m%d') time_p = float(time.mktime(time_p.timetuple())) _set = QCandlestickSet(float(stock['open']), float(stock['high']), float(stock['low']), float(stock['close']), time_p, self._series) self._series.append(_set) if not is_init: self._stocks = data self._category = self._stocks['trade_date'] axis_x = self._chart.axisX() axis_y = self._chart.axisY() axis_x.setCategories(self._category) max_p = self._stocks[['high', 'low']].stack().max() min_p = self._stocks[['high', 'low']].stack().min() axis_y.setRange(min_p * 0.99, max_p * 1.01) self._zero_value = (0, self._chart.axisY().min()) self._max_value = (len(self._chart.axisX().categories()), self._chart.axisY().max()) # 计算x轴单个cate的宽度,用来处理横线不能画到边界 self._cate_width = (self._max_point.x() - self._zero_point.x()) / len(self._category) def resizeEvent(self, event): super(KLineChartView, self).resizeEvent(event) self._zero_point = self._chart.mapToPosition( QPointF(self._zero_value[0], self._zero_value[1])) self._max_point = self._chart.mapToPosition( QPointF(self._max_value[0], self._max_value[1])) self._cate_width = (self._max_point.x() - self._zero_point.x()) / len( self._category) def max_point(self): return QPointF(self._max_point.x() + self._cate_width / 2, self._max_point.y()) def min_point(self): return QPointF(self._zero_point.x() - self._cate_width / 2, self._zero_point.y()) def init_chart(self): self._chart.setAnimationOptions(QChart.SeriesAnimations) self._series.setIncreasingColor(QColor(Qt.red)) self._series.setDecreasingColor(QColor(Qt.green)) self._series.setName(self._stocks['name'].iloc[0]) self.add_series_values(self._stocks, True) self._chart.addSeries(self._series) self._chart.createDefaultAxes() self._chart.setLocalizeNumbers(True) axis_x = self._chart.axisX() axis_y = self._chart.axisY() axis_x.setGridLineVisible(False) axis_y.setGridLineVisible(False) axis_x.setCategories(self._category) axis_x.setLabelsVisible(False) axis_x.setVisible(False) max_p = self._stocks[['high', 'low']].stack().max() min_p = self._stocks[['high', 'low']].stack().min() axis_y.setRange(min_p * 0.99, max_p * 1.01) # chart的图例 legend = self._chart.legend() # 设置图例由Series来决定样式 legend.setMarkerShape(QLegend.MarkerShapeFromSeries) self.setChart(self._chart) # 设置外边界全部为0 self._chart.layout().setContentsMargins(0, 0, 0, 0) # 设置内边界的bottom为0 margins = self._chart.margins() self._chart.setMargins(QMargins(margins.left(), 0, margins.right(), 0)) # 设置背景区域无圆角 self._chart.setBackgroundRoundness(0)
class VLineChartView(QChartView): bar_hovered = pyqtSignal(bool, str) def __init__(self, data: pd.DataFrame): super(VLineChartView, self).__init__() self._stocks = data self._category = self._stocks['trade_date'] self._chart = QChart() self._chart.setAnimationOptions(QChart.SeriesAnimations) self._series = QStackedBarSeries() # 成交量以万股为单位 self._vol_multiple = 10000 self.init_chart() self._zero_value = (0, self._chart.axisY().min()) self._max_value = (len(self._chart.axisX().categories()), self._chart.axisY().max()) self._zero_point = self._chart.mapToPosition( QPointF(self._zero_value[0], self._zero_value[1])) self._max_point = self._chart.mapToPosition( QPointF(self._max_value[0], self._max_value[1])) # 计算x轴单个cate的宽度,用来处理横线不能画到边界 self._cate_width = (self._max_point.x() - self._zero_point.x()) / len( self._category) self._series.hovered.connect(self.on_series_hovered) x_index_list = np.percentile(range(len(self._category)), [0, 25, 50, 75, 100]) self._x_axis_list = [ QGraphicsSimpleTextItem(self._category[int(index)], self._chart) for index in x_index_list ] [axis.setText(axis.text()[4:]) for axis in self._x_axis_list[1:]] self._v_b = QGraphicsSimpleTextItem('B', self._chart) self._v_b.setZValue(100) def on_series_hovered(self, status, index): self.bar_hovered.emit(status, self._category[index]) def clear_series_value(self): self._series.clear() self._stocks = None self._chart.axisY().setRange(0, 10) self._chart.axisX().setCategories(list()) def add_series_values(self, data: pd.DataFrame, is_init=False): self._stocks = data bar_red = QBarSet('red') bar_red.setColor(Qt.red) bar_green = QBarSet('green') bar_green.setColor(Qt.green) for _, stock in self._stocks.iterrows(): if stock['open'] < stock['close']: bar_red.append(stock['vol'] / self._vol_multiple) bar_green.append(0) else: bar_red.append(0) bar_green.append(stock['vol'] / self._vol_multiple) self._series.append(bar_red) self._series.append(bar_green) if not is_init: self._stocks = data self._category = self._stocks['trade_date'] axis_x = self._chart.axisX() axis_y = self._chart.axisY() axis_x.setCategories(self._category) max_p = self._stocks[[ 'vol', ]].stack().max() min_p = self._stocks[[ 'vol', ]].stack().min() axis_y.setRange(min_p / self._vol_multiple * 0.9, max_p / self._vol_multiple * 1.1) self._zero_value = (0, self._chart.axisY().min()) self._max_value = (len(self._chart.axisX().categories()), self._chart.axisY().max()) # 计算x轴单个cate的宽度,用来处理横线不能画到边界 self._cate_width = (self._max_point.x() - self._zero_point.x()) / len(self._category) def resizeEvent(self, event): super(VLineChartView, self).resizeEvent(event) self._zero_point = self._chart.mapToPosition( QPointF(self._zero_value[0], self._zero_value[1])) self._max_point = self._chart.mapToPosition( QPointF(self._max_value[0], self._max_value[1])) self._cate_width = (self._max_point.x() - self._zero_point.x()) / len( self._category) # 绘制自定义X轴 self._x_axis_list[0].setPos(self._zero_point.x() - self._cate_width, self._zero_point.y() + 10) self._x_axis_list[1].setPos(self._max_point.x() * 0.25, self._zero_point.y() + 10) self._x_axis_list[2].setPos(self._max_point.x() * 0.5, self._zero_point.y() + 10) self._x_axis_list[3].setPos(self._max_point.x() * 0.75, self._zero_point.y() + 10) self._x_axis_list[4].setPos( self._max_point.x() - self._x_axis_list[-1].boundingRect().width(), self._zero_point.y() + 10) # 20180207 这个日期的柱形图上面画一个字母b vol = self._stocks[self._stocks['trade_date'] == '20180207']['vol'] / self._vol_multiple print('vol:', vol, ' trade_date:', '20180207') pos = self._chart.mapToPosition( QPointF(list(self._category).index('20180207'), vol)) pos = QPointF(pos.x() - self._cate_width / 2, pos.y() - self._v_b.boundingRect().height()) self._v_b.setPos(pos) def max_point(self): return QPointF(self._max_point.x() + self._cate_width / 2, self._max_point.y()) def min_point(self): return QPointF(self._zero_point.x() - self._cate_width / 2, self._zero_point.y()) def init_chart(self): self.add_series_values(self._stocks, True) self._chart.addSeries(self._series) self._chart.createDefaultAxes() self._chart.setLocalizeNumbers(True) axis_x = self._chart.axisX() axis_y = self._chart.axisY() axis_x.setGridLineVisible(False) axis_y.setGridLineVisible(False) axis_y.setLabelFormat("%.2f") axis_x.setCategories(self._category) axis_x.setLabelsVisible(False) max_p = self._stocks[[ 'vol', ]].stack().max() min_p = self._stocks[[ 'vol', ]].stack().min() axis_y.setRange(min_p / self._vol_multiple * 0.9, max_p / self._vol_multiple * 1.1) # chart的图例 legend = self._chart.legend() legend.hide() # 设置图例由Series来决定样式 # legend.setMarkerShape(QLegend.MarkerShapeFromSeries) self.setChart(self._chart) self._chart.layout().setContentsMargins(0, 0, 0, 0) # 设置内边界的bottom为0 # margins = self._chart.margins() # self._chart.setMargins(QMargins(margins.left(), 0, margins.right(), 0)) self._chart.setBackgroundRoundness(0)
class Graph(QChartView): def __init__(self, parent=None): super().__init__(parent=parent) self.setpoint_temperature = None self.chart = QChart() self.chart.legend().hide() self.setChart( self.chart ) self.setRenderHint(QPainter.Antialiasing) self.chart.setPlotAreaBackgroundBrush( QBrush(Qt.black) ) self.chart.setPlotAreaBackgroundVisible( True ) self.setpointTemperatureSeries = QLineSeries( self.chart ) pen = self.setpointTemperatureSeries.pen() pen.setWidthF(2.) pen.setColor( Qt.green ) self.setpointTemperatureSeries.setPen( pen ) #self.setpointTemperatureSeries.setUseOpenGL( True ) self.chart.addSeries( self.setpointTemperatureSeries ) self.temperatureSeries = QLineSeries( self.chart ) pen = self.temperatureSeries.pen() pen.setWidthF(2.) pen.setColor( Qt.red ) self.temperatureSeries.setPen( pen ) #self.temperatureSeries.setUseOpenGL( True ) self.chart.addSeries( self.temperatureSeries ) self.number_of_samples_to_keep = 2 * 5 * 60 self.xMin = QDateTime.currentDateTime().toMSecsSinceEpoch() self.xMax = QDateTime.currentDateTime().toMSecsSinceEpoch() self.yMin = 400 self.yMax = 0 #self.chart.createDefaultAxes() #x_axis = QValueAxis() x_axis = QDateTimeAxis() x_axis.setTitleText( "Time" ) x_axis.setFormat("HH:mm:ss") self.chart.addAxis( x_axis, Qt.AlignBottom ) self.temperatureSeries.attachAxis( x_axis ) self.setpointTemperatureSeries.attachAxis( x_axis ) startDate = QDateTime.currentDateTime().addSecs( -5 * 60 ) endDate = QDateTime.currentDateTime().addSecs( 5 * 60 ) #startDate = QDateTime(QDate(2017, 1, 9), QTime(17, 25, 0)) #endDate = QDateTime(QDate(2017, 1, 9), QTime(17, 50, 0)) #self.chart.axisX().setRange( startDate, endDate ) #self.chart.axisX().setRange( 0, 100 ) y_axis = QValueAxis() y_axis.setTitleText( "Temperature (K)" ) self.chart.addAxis( y_axis, Qt.AlignLeft ) self.temperatureSeries.attachAxis( y_axis ) self.setpointTemperatureSeries.attachAxis( y_axis ) self.chart.axisY().setRange( 0, 400 ) #self.chart.axisY().setRange( 260., 290. ) self.temperatureSeries.pointAdded.connect( self.Rescale_Axes ) #self.setpointTemperatureSeries.pointAdded.connect( self.Rescale_Axes ) self.setRubberBand( QChartView.HorizontalRubberBand ) # Customize chart title font = QFont() font.setPixelSize(24); self.chart.setTitleFont(font); self.chart.setTitleBrush(QBrush(Qt.white)); ## Customize chart background #backgroundGradient = QLinearGradient() #backgroundGradient.setStart(QPointF(0, 0)); #backgroundGradient.setFinalStop(QPointF(0, 1)); #backgroundGradient.setColorAt(0.0, QColor(0x000147)); #backgroundGradient.setColorAt(1.0, QColor(0x000117)); #backgroundGradient.setCoordinateMode(QGradient.ObjectBoundingMode); #self.chart.setBackgroundBrush(backgroundGradient); transparent_background = QBrush(QColor(0,0,0,0)) self.chart.setBackgroundBrush( transparent_background ) # Customize axis label font labelsFont = QFont() labelsFont.setPixelSize(16); x_axis.setLabelsFont(labelsFont) y_axis.setLabelsFont(labelsFont) x_axis.setTitleFont(labelsFont) y_axis.setTitleFont(labelsFont) # Customize axis colors axisPen = QPen(QColor(0xd18952)) axisPen.setWidth(2) x_axis.setLinePen(axisPen) y_axis.setLinePen(axisPen) # Customize axis label colors axisBrush = QBrush(Qt.white) x_axis.setLabelsBrush(axisBrush) y_axis.setLabelsBrush(axisBrush) x_axis.setTitleBrush(axisBrush) y_axis.setTitleBrush(axisBrush) def set_title(self, title): self.chart.setTitle(title) def add_new_data_point( self, x, y ): x_as_millisecs = x.toMSecsSinceEpoch() self.temperatureSeries.append( x_as_millisecs, y ) if( self.setpoint_temperature ): self.setpointTemperatureSeries.append( x_as_millisecs, self.setpoint_temperature ) num_of_datapoints = self.temperatureSeries.count() #if( num_of_datapoints > self.number_of_samples_to_keep ): # self.number_of_samples_to_keep. #print( x_as_millisecs, y ) #self.chart.scroll( x_as_millisecs - 5 * 60 * 1000, x_as_millisecs ) #self.temperatureSeries.append( x, float(y) ) #self.repaint() def Rescale_Axes( self, index ): x = self.temperatureSeries.at( index ).x() x_rescaled = False if( x < self.xMin ): self.xMin = x x_rescaled = True if( x > self.xMax ): self.xMax = x x_rescaled = True if( x_rescaled ): full_range = min( self.xMax - self.xMin, 5 * 60 * 1000 ) margin = full_range * 0.05 self.chart.axisX().setRange( QDateTime.fromMSecsSinceEpoch(self.xMax - full_range - margin), QDateTime.fromMSecsSinceEpoch(self.xMax + margin) ) y = self.temperatureSeries.at( index ).y() y_rescaled = False if( y < self.yMin ): self.yMin = y y_rescaled = True if( y > self.yMax ): self.yMax = y y_rescaled = True if( y_rescaled ): full_range = self.yMax - self.yMin margin = full_range * 0.05 self.chart.axisY().setRange( self.yMin - margin, self.yMax + margin )
class RTTView(QWidget): def __init__(self, parent=None): super(RTTView, self).__init__(parent) uic.loadUi('RTTView.ui', self) self.initSetting() self.initQwtPlot() self.rcvbuff = b'' self.tmrRTT = QtCore.QTimer() self.tmrRTT.setInterval(10) self.tmrRTT.timeout.connect(self.on_tmrRTT_timeout) self.tmrRTT.start() self.tmrRTT_Cnt = 0 def initSetting(self): if not os.path.exists('setting.ini'): open('setting.ini', 'w', encoding='utf-8') self.conf = configparser.ConfigParser() self.conf.read('setting.ini', encoding='utf-8') if not self.conf.has_section('J-Link'): self.conf.add_section('J-Link') self.conf.set('J-Link', 'dllpath', '') self.conf.set('J-Link', 'mcucore', 'Cortex-M0') self.conf.add_section('Memory') self.conf.set('Memory', 'rttaddr', '0x20000000') self.linDLL.setText(self.conf.get('J-Link', 'dllpath')) self.linRTT.setText(self.conf.get('Memory', 'rttaddr')) self.cmbCore.setCurrentIndex( self.cmbCore.findText(self.conf.get('J-Link', 'mcucore'))) def initQwtPlot(self): self.PlotData = [[0] * 1000 for i in range(N_CURVES)] self.PlotPoint = [[QtCore.QPointF(j, 0) for j in range(1000)] for i in range(N_CURVES)] self.PlotChart = QChart() self.ChartView = QChartView(self.PlotChart) self.ChartView.setVisible(False) self.vLayout.insertWidget(0, self.ChartView) self.PlotCurve = [QLineSeries() for i in range(N_CURVES)] @pyqtSlot() def on_btnOpen_clicked(self): if self.btnOpen.text() == '打开连接': try: self.jlink = ctypes.cdll.LoadLibrary(self.linDLL.text()) err_buf = (ctypes.c_char * 64)() self.jlink.JLINKARM_ExecCommand( f'Device = {self.cmbCore.currentText()}'.encode('latin-1'), err_buf, 64) self.jlink.JLINKARM_TIF_Select(1) self.jlink.JLINKARM_SetSpeed(4000) buff = ctypes.create_string_buffer(1024) Addr = int(self.linRTT.text(), 16) for i in range(128): self.jlink.JLINKARM_ReadMem(Addr + 1024 * i, 1024, buff) index = buff.raw.find(b'SEGGER RTT') if index != -1: self.RTTAddr = Addr + 1024 * i + index buff = ctypes.create_string_buffer( ctypes.sizeof(SEGGER_RTT_CB)) self.jlink.JLINKARM_ReadMem( self.RTTAddr, ctypes.sizeof(SEGGER_RTT_CB), buff) rtt_cb = SEGGER_RTT_CB.from_buffer(buff) self.aUpAddr = self.RTTAddr + 16 + 4 + 4 self.aDownAddr = self.aUpAddr + ctypes.sizeof( RingBuffer) * rtt_cb.MaxNumUpBuffers self.txtMain.append( f'\n_SEGGER_RTT @ 0x{self.RTTAddr:08X} with {rtt_cb.MaxNumUpBuffers} aUp and {rtt_cb.MaxNumDownBuffers} aDown\n' ) break else: raise Exception('Can not find _SEGGER_RTT') except Exception as e: self.txtMain.append(f'\n{str(e)}\n') else: self.linDLL.setEnabled(False) self.btnDLL.setEnabled(False) self.linRTT.setEnabled(False) self.cmbCore.setEnabled(False) self.btnOpen.setText('关闭连接') else: self.linDLL.setEnabled(True) self.btnDLL.setEnabled(True) self.linRTT.setEnabled(True) self.cmbCore.setEnabled(True) self.btnOpen.setText('打开连接') def aUpRead(self): buf = ctypes.create_string_buffer(ctypes.sizeof(RingBuffer)) self.jlink.JLINKARM_ReadMem(self.aUpAddr, ctypes.sizeof(RingBuffer), buf) aUp = RingBuffer.from_buffer(buf) if aUp.RdOff == aUp.WrOff: str = ctypes.create_string_buffer(0) elif aUp.RdOff < aUp.WrOff: cnt = aUp.WrOff - aUp.RdOff str = ctypes.create_string_buffer(cnt) self.jlink.JLINKARM_ReadMem( ctypes.cast(aUp.pBuffer, ctypes.c_void_p).value + aUp.RdOff, cnt, str) aUp.RdOff += cnt self.jlink.JLINKARM_WriteU32(self.aUpAddr + 4 * 4, aUp.RdOff) else: cnt = aUp.SizeOfBuffer - aUp.RdOff str = ctypes.create_string_buffer(cnt) self.jlink.JLINKARM_ReadMem( ctypes.cast(aUp.pBuffer, ctypes.c_void_p).value + aUp.RdOff, cnt, str) aUp.RdOff = 0 #这样下次再读就会进入执行上个条件 self.jlink.JLINKARM_WriteU32(self.aUpAddr + 4 * 4, aUp.RdOff) return str.raw def on_tmrRTT_timeout(self): if self.btnOpen.text() == '关闭连接': try: self.rcvbuff += self.aUpRead() if self.txtMain.isVisible(): if self.chkHEXShow.isChecked(): text = ''.join(f'{x:02X} ' for x in self.rcvbuff) else: text = self.rcvbuff.decode('latin') if len(self.txtMain.toPlainText()) > 25000: self.txtMain.clear() self.txtMain.moveCursor(QtGui.QTextCursor.End) self.txtMain.insertPlainText(text) self.rcvbuff = b'' else: if self.rcvbuff.rfind(b',') == -1: return d = self.rcvbuff[0:self.rcvbuff.rfind(b',')].split( b',') # [b'12', b'34'] or [b'12 34', b'56 78'] d = [[float(x) for x in X.strip().split()] for X in d] # [[12], [34]] or [[12, 34], [56, 78]] for arr in d: for i, x in enumerate(arr): if i == N_CURVES: break self.PlotData[i].pop(0) self.PlotData[i].append(x) self.PlotPoint[i].pop(0) self.PlotPoint[i].append(QtCore.QPointF(999, x)) self.rcvbuff = self.rcvbuff[self.rcvbuff.rfind(b',') + 1:] self.tmrRTT_Cnt += 1 if self.tmrRTT_Cnt % 4 == 0: if len(d[-1]) != len(self.PlotChart.series()): for series in self.PlotChart.series(): self.PlotChart.removeSeries(series) for i in range(min(len(d[-1]), N_CURVES)): self.PlotCurve[i].setName(f'Curve {i+1}') self.PlotChart.addSeries(self.PlotCurve[i]) self.PlotChart.createDefaultAxes() for i in range(len(self.PlotChart.series())): for j, point in enumerate(self.PlotPoint[i]): point.setX(j) self.PlotCurve[i].replace(self.PlotPoint[i]) miny = min([ min(d) for d in self.PlotData[:len(self.PlotChart.series())] ]) maxy = max([ max(d) for d in self.PlotData[:len(self.PlotChart.series())] ]) self.PlotChart.axisY().setRange(miny, maxy) self.PlotChart.axisX().setRange(0000, 1000) except Exception as e: self.rcvbuff = b'' print(str(e)) # 波形显示模式下 txtMain 不可见,因此错误信息不能显示在其上 def aDownWrite(self, bytes): buf = ctypes.create_string_buffer(ctypes.sizeof(RingBuffer)) self.jlink.JLINKARM_ReadMem(self.aDownAddr, ctypes.sizeof(RingBuffer), buf) aDown = RingBuffer.from_buffer(buf) if aDown.WrOff >= aDown.RdOff: if aDown.RdOff != 0: cnt = min(aDown.SizeOfBuffer - aDown.WrOff, len(bytes)) else: cnt = min( aDown.SizeOfBuffer - 1 - aDown.WrOff, len(bytes)) # 写入操作不能使得 aDown.WrOff == aDown.RdOff,以区分满和空 str = ctypes.create_string_buffer(bytes[:cnt]) self.jlink.JLINKARM_WriteMem( ctypes.cast(aDown.pBuffer, ctypes.c_void_p).value + aDown.WrOff, cnt, str) aDown.WrOff += cnt if aDown.WrOff == aDown.SizeOfBuffer: aDown.WrOff = 0 bytes = bytes[cnt:] if bytes and aDown.RdOff != 0 and aDown.RdOff != 1: # != 0 确保 aDown.WrOff 折返回 0,!= 1 确保有空间可写入 cnt = min(aDown.RdOff - 1 - aDown.WrOff, len(bytes)) # - 1 确保写入操作不导致WrOff与RdOff指向同一位置 str = ctypes.create_string_buffer(bytes[:cnt]) self.jlink.JLINKARM_WriteMem( ctypes.cast(aDown.pBuffer, ctypes.c_void_p).value + aDown.WrOff, cnt, str) aDown.WrOff += cnt self.jlink.JLINKARM_WriteU32(self.aDownAddr + 4 * 3, aDown.WrOff) @pyqtSlot() def on_btnSend_clicked(self): if self.btnOpen.text() == '关闭连接': text = self.txtSend.toPlainText() try: if self.chkHEXSend.isChecked(): text = ''.join([chr(int(x, 16)) for x in text.split()]) self.aDownWrite(text.encode('latin')) except Exception as e: self.txtMain.append(f'\n{str(e)}\n') @pyqtSlot() def on_btnDLL_clicked(self): dllpath, filter = QFileDialog.getOpenFileName( caption='JLink_x64.dll路径', filter='动态链接库文件 (*.dll)', directory=self.linDLL.text()) if dllpath != '': self.linDLL.setText(dllpath) @pyqtSlot(int) def on_chkWavShow_stateChanged(self, state): self.ChartView.setVisible(state == Qt.Checked) self.txtMain.setVisible(state == Qt.Unchecked) @pyqtSlot() def on_btnClear_clicked(self): self.txtMain.clear() def closeEvent(self, evt): self.conf.set('J-Link', 'dllpath', self.linDLL.text()) self.conf.set('J-Link', 'mcucore', self.cmbCore.currentText()) self.conf.set('Memory', 'rttaddr', self.linRTT.text()) self.conf.write(open('setting.ini', 'w', encoding='utf-8'))
class KLineChartView(QChartView): def __init__(self): super(KLineChartView, self).__init__() self.setRenderHint(QPainter.Antialiasing) # 抗锯齿 self._chart = QChart(title='蜡烛图悬浮提示') self.stocks = read_tick_data() self.category = [ trade_date[4:] for trade_date in self.stocks['trade_date'] ] self._count = len(self.category) self.resize(800, 300) self.init_chart() self.toolTipWidget = GraphicsProxyWidget(self._chart) # 鼠标跟踪的十字线 self.lineItem_h = QGraphicsLineItem(self._chart) self.lineItem_v = QGraphicsLineItem(self._chart) pen = QPen() pen.setStyle(Qt.DotLine) pen.setColor(QColor(Qt.gray)) pen.setWidth(2) self.lineItem_h.setPen(pen) self.lineItem_v.setPen(pen) self.lineItem_h.setZValue(100) self.lineItem_v.setZValue(100) self.lineItem_h.hide() self.lineItem_v.hide() # 坐标轴上最大最小的值 # x 轴是 self.min_x, self.max_x = 0, len(self._chart.axisX().categories()) self.min_y, self.max_y = self._chart.axisY().min(), self._chart.axisY( ).max() # y 轴最高点坐标 self.point_y_max = self._chart.mapToPosition( QPointF(self.min_x, self.max_y)) # x 轴最高点坐标 self.point_x_max = self._chart.mapToPosition( QPointF(self.max_x, self.min_y)) # self.point_x_min = self._chart.mapToPosition(QPointF(self.min_x, self.min_y)) # 计算x轴单个cate的宽度,用来处理横线不能画到边界 self.x_width = (self.point_x_max.x() - self.point_y_max.x()) / len( self.category) self.x_x_min = self.point_y_max.x() - self.x_width / 2 self.x_x_max = self.point_x_max.x() - self.x_width / 2 # 中间位置,用来判断TipWidget放在哪里 mid_date = self.stocks['trade_date'].iloc[len( self.stocks['trade_date']) // 2] self.mid_x = float( time.mktime( datetime.datetime.strptime(str(mid_date), '%Y%m%d').timetuple())) self.left_pos = self.point_y_max self.right_pos = self._chart.mapToPosition( QPointF(self.max_x, self.max_y)) def mouseMoveEvent(self, event): super(KLineChartView, self).mouseMoveEvent(event) pos = event.pos() if self.x_x_min < pos.x() < self.x_x_max \ and self.point_x_max.y() > pos.y() > self.point_y_max.y(): self.lineItem_h.setLine(self.x_x_min, pos.y(), self.x_x_max, pos.y()) self.lineItem_v.setLine(pos.x(), self.point_y_max.y(), pos.x(), self.point_x_max.y()) self.lineItem_h.show() self.lineItem_v.show() else: self.lineItem_h.hide() self.lineItem_v.hide() def resizeEvent(self, event): super(KLineChartView, self).resizeEvent(event) # y 轴最高点坐标 self.point_y_max = self._chart.mapToPosition( QPointF(self.min_x, self.max_y)) # x 轴最高点坐标 self.point_x_max = self._chart.mapToPosition( QPointF(self.max_x, self.min_y)) # 计算x轴单个cate的宽度,用来处理横线不能画到边界 self.x_width = (self.point_x_max.x() - self.point_y_max.x()) / len( self.category) self.x_x_min = self.point_y_max.x() - self.x_width / 2 self.x_x_max = self.point_x_max.x() - self.x_width / 2 self.left_pos = self.point_y_max self.right_pos = self._chart.mapToPosition( QPointF(self.max_x, self.max_y)) def init_chart(self): self._chart.setAnimationOptions(QChart.SeriesAnimations) series = QCandlestickSeries() series.setIncreasingColor(QColor(Qt.red)) series.setDecreasingColor(QColor(Qt.green)) series.setName(self.stocks['name'].iloc[0]) for _, stock in self.stocks.iterrows(): time_p = datetime.datetime.strptime(stock['trade_date'], '%Y%m%d') time_p = float(time.mktime(time_p.timetuple())) _set = QCandlestickSet(float(stock['open']), float(stock['high']), float(stock['low']), float(stock['close']), time_p, series) _set.hovered.connect(self.handleBarHoverd) # 鼠标悬停 series.append(_set) self._chart.addSeries(series) self._chart.createDefaultAxes() self._chart.setLocalizeNumbers(True) axis_x = self._chart.axisX() axis_y = self._chart.axisY() axis_x.setGridLineVisible(False) axis_y.setGridLineVisible(False) axis_x.setCategories(self.category) max_p = self.stocks[['high', 'low']].stack().max() + 10 min_p = self.stocks[['high', 'low']].stack().min() - 10 axis_y.setRange(min_p, max_p) # chart的图例 legend = self._chart.legend() # 设置图例由Series来决定样式 legend.setMarkerShape(QLegend.MarkerShapeFromSeries) self.setChart(self._chart) # 设置外边界全部为0 self._chart.layout().setContentsMargins(0, 0, 0, 0) # 设置内边界都为0 # self._chart.setMargins(QMargins(0, 0, 0, 0)) # 设置背景区域无圆角 self._chart.setBackgroundRoundness(0) def handleBarHoverd(self, status): """ 改变画笔的风格 """ bar = self.sender() # 信号发送者 pen = bar.pen() if not pen: return pen.setStyle(Qt.DotLine if status else Qt.SolidLine) bar.setPen(pen) if status: # 通过 bar 可以获取横轴坐标(timestamp)和纵轴坐标(high) # 然后将坐标值转换为位置,显示 TipWidget 的位置 right_pos = QPointF( self.right_pos.x() - self.toolTipWidget.width() - self.x_width, self.right_pos.y()) pos = self.left_pos if bar.timestamp() > self.mid_x else right_pos trade_date = time.strftime('%Y%m%d', time.localtime(bar.timestamp())) self.toolTipWidget.show(str(trade_date), str(bar.open()), str(bar.close()), str(bar.high()), str(bar.low()), pos) else: self.toolTipWidget.hide()
class MyWindow(QMainWindow): def __init__(self): super().__init__() # thread self.worker = Worker() self.worker.price.connect(self.get_price) self.worker.start() # window size self.setMinimumSize(600, 400) # data self.series = QLineSeries() # chart object self.chart = QChart() self.chart.legend().hide() self.chart.addSeries(self.series) # data feeding # axis axis_x = QDateTimeAxis() axis_x.setFormat("hh:mm:ss") dt = QDateTime.currentDateTime() axis_x.setRange(dt, dt.addSecs(128)) self.chart.addAxis(axis_x, Qt.AlignBottom) self.series.attachAxis(axis_x) axis_y = QValueAxis() axis_y.setLabelFormat("%i") self.chart.addAxis(axis_y, Qt.AlignLeft) self.series.attachAxis(axis_y) # margin self.chart.layout().setContentsMargins(0, 0, 0, 0) # displaying chart chart_view = QChartView(self.chart) chart_view.setRenderHint(QPainter.Antialiasing) self.setCentralWidget(chart_view) @pyqtSlot(float) def get_price(self, cur_price): if len(self.series) == 128: self.series.remove(0) # delete first data # append current price dt = QDateTime.currentDateTime() ts = dt.toMSecsSinceEpoch() self.series.append(ts, cur_price) print(ts, cur_price) # update asis data = self.series.pointsVector() first_ts = data[0].x() last_ts = data[-1].x() first_dt = QDateTime.fromMSecsSinceEpoch(first_ts) last_dt = QDateTime.fromMSecsSinceEpoch(last_ts) axis_x = self.chart.axisX() axis_x.setRange(first_dt, last_dt) axis_y = self.chart.axisY() axis_y.setRange(70000000, 71000000)
class RTTView(QWidget): def __init__(self, parent=None): super(RTTView, self).__init__(parent) uic.loadUi('RTTView.ui', self) self.initSetting() self.initQwtPlot() self.rcvbuff = '' self.daplink = None self.tmrRTT = QtCore.QTimer() self.tmrRTT.setInterval(10) self.tmrRTT.timeout.connect(self.on_tmrRTT_timeout) self.tmrRTT.start() self.tmrRTT_Cnt = 0 def initSetting(self): if not os.path.exists('setting.ini'): open('setting.ini', 'w', encoding='utf-8') self.conf = configparser.ConfigParser() self.conf.read('setting.ini', encoding='utf-8') if not self.conf.has_section('Memory'): self.conf.add_section('Memory') self.conf.set('Memory', 'StartAddr', '0x20000000') def initQwtPlot(self): self.PlotData = [[0] * 1000 for i in range(N_CURVES)] self.PlotPoint = [[QtCore.QPointF(j, 0) for j in range(1000)] for i in range(N_CURVES)] self.PlotChart = QChart() self.ChartView = QChartView(self.PlotChart) self.ChartView.setVisible(False) self.vLayout.insertWidget(0, self.ChartView) self.PlotCurve = [QLineSeries() for i in range(N_CURVES)] @pyqtSlot() def on_btnOpen_clicked(self): if self.btnOpen.text() == u'打开连接': try: self.daplink = self.daplinks[self.cmbDAP.currentText()] self.daplink.open() _dp = dap.DebugPort(self.daplink, None) _dp.init() _dp.power_up_debug() _ap = ap.AHB_AP(_dp, 0) _ap.init() self.dap = cortex_m.CortexM(None, _ap) Addr = int(self.conf.get('Memory', 'StartAddr'), 16) for i in range(256): buff = self.dap.read_memory_block8(Addr + 1024 * i, 1024) buff = ''.join([chr(x) for x in buff]) index = buff.find('SEGGER RTT') if index != -1: self.RTTAddr = Addr + 1024 * i + index buff = self.dap.read_memory_block8( self.RTTAddr, ctypes.sizeof(SEGGER_RTT_CB)) rtt_cb = SEGGER_RTT_CB.from_buffer(bytearray(buff)) self.aUpAddr = self.RTTAddr + 16 + 4 + 4 self.aDownAddr = self.aUpAddr + ctypes.sizeof( RingBuffer) * rtt_cb.MaxNumUpBuffers self.txtMain.append( '\n_SEGGER_RTT @ 0x%08X with %d aUp and %d aDown\n' % (self.RTTAddr, rtt_cb.MaxNumUpBuffers, rtt_cb.MaxNumDownBuffers)) break else: raise Exception('Can not find _SEGGER_RTT') except Exception as e: self.txtMain.append(f'\n{str(e)}\n') else: self.cmbDAP.setEnabled(False) self.btnOpen.setText(u'关闭连接') else: self.daplink.close() self.cmbDAP.setEnabled(True) self.btnOpen.setText(u'打开连接') def aUpRead(self): buf = self.dap.read_memory_block8(self.aUpAddr, ctypes.sizeof(RingBuffer)) aUp = RingBuffer.from_buffer(bytearray(buf)) if aUp.RdOff == aUp.WrOff: buf = [] elif aUp.RdOff < aUp.WrOff: cnt = aUp.WrOff - aUp.RdOff buf = self.dap.read_memory_block8( ctypes.cast(aUp.pBuffer, ctypes.c_void_p).value + aUp.RdOff, cnt) aUp.RdOff += cnt self.dap.write32(self.aUpAddr + 4 * 4, aUp.RdOff) else: cnt = aUp.SizeOfBuffer - aUp.RdOff buf = self.dap.read_memory_block8( ctypes.cast(aUp.pBuffer, ctypes.c_void_p).value + aUp.RdOff, cnt) aUp.RdOff = 0 #这样下次再读就会进入执行上个条件 self.dap.write32(self.aUpAddr + 4 * 4, aUp.RdOff) return ''.join([chr(x) for x in buf]) def on_tmrRTT_timeout(self): if self.btnOpen.text() == u'关闭连接': try: self.rcvbuff += self.aUpRead() if self.txtMain.isVisible(): if self.chkHEXShow.isChecked(): text = ''.join(f'{ord(x):02X} ' for x in self.rcvbuff) else: text = self.rcvbuff if len(self.txtMain.toPlainText()) > 25000: self.txtMain.clear() self.txtMain.moveCursor(QtGui.QTextCursor.End) self.txtMain.insertPlainText(text) self.rcvbuff = '' else: if self.rcvbuff.rfind(',') == -1: return d = self.rcvbuff[0:self.rcvbuff.rfind(',')].split( ',') # [b'12', b'34'] or [b'12 34', b'56 78'] d = [[float(x) for x in X.strip().split()] for X in d] # [[12], [34]] or [[12, 34], [56, 78]] for arr in d: for i, x in enumerate(arr): if i == N_CURVES: break self.PlotData[i].pop(0) self.PlotData[i].append(x) self.PlotPoint[i].pop(0) self.PlotPoint[i].append(QtCore.QPointF(999, x)) self.rcvbuff = self.rcvbuff[self.rcvbuff.rfind(',') + 1:] self.tmrRTT_Cnt += 1 if self.tmrRTT_Cnt % 4 == 0: if len(d[-1]) != len(self.PlotChart.series()): for series in self.PlotChart.series(): self.PlotChart.removeSeries(series) for i in range(min(len(d[-1]), N_CURVES)): self.PlotCurve[i].setName(f'Curve {i+1}') self.PlotChart.addSeries(self.PlotCurve[i]) self.PlotChart.createDefaultAxes() for i in range(len(self.PlotChart.series())): for j, point in enumerate(self.PlotPoint[i]): point.setX(j) self.PlotCurve[i].replace(self.PlotPoint[i]) miny = min([ min(d) for d in self.PlotData[:len(self.PlotChart.series())] ]) maxy = max([ max(d) for d in self.PlotData[:len(self.PlotChart.series())] ]) self.PlotChart.axisY().setRange(miny, maxy) self.PlotChart.axisX().setRange(0000, 1000) except Exception as e: self.rcvbuff = '' print(str(e)) # 波形显示模式下 txtMain 不可见,因此错误信息不能显示在其上 else: self.tmrRTT_Cnt += 1 if self.tmrRTT_Cnt % 20 == 0: self.detect_daplink() # 自动检测 DAPLink 的热插拔 def aDownWrite(self, text): buf = self.dap.read_memory_block8(self.aDownAddr, ctypes.sizeof(RingBuffer)) aDown = RingBuffer.from_buffer(bytearray(buf)) if aDown.WrOff >= aDown.RdOff: if aDown.RdOff != 0: cnt = min(aDown.SizeOfBuffer - aDown.WrOff, len(text)) else: cnt = min( aDown.SizeOfBuffer - 1 - aDown.WrOff, len(text)) # 写入操作不能使得 aDown.WrOff == aDown.RdOff,以区分满和空 self.dap.write_memory_block8( ctypes.cast(aDown.pBuffer, ctypes.c_void_p).value + aDown.WrOff, [ord(x) for x in text[:cnt]]) aDown.WrOff += cnt if aDown.WrOff == aDown.SizeOfBuffer: aDown.WrOff = 0 text = text[cnt:] if text and aDown.RdOff != 0 and aDown.RdOff != 1: # != 0 确保 aDown.WrOff 折返回 0,!= 1 确保有空间可写入 cnt = min(aDown.RdOff - 1 - aDown.WrOff, len(text)) # - 1 确保写入操作不导致WrOff与RdOff指向同一位置 self.dap.write_memory_block8( ctypes.cast(aDown.pBuffer, ctypes.c_void_p).value + aDown.WrOff, [ord(x) for x in text[:cnt]]) aDown.WrOff += cnt self.dap.write32(self.aDownAddr + 4 * 3, aDown.WrOff) @pyqtSlot() def on_btnSend_clicked(self): if self.btnOpen.text() == u'关闭连接': text = self.txtSend.toPlainText() try: if self.chkHEXSend.isChecked(): text = ''.join([chr(int(x, 16)) for x in text.split()]) self.aDownWrite(text) except Exception as e: self.txtMain.append(f'\n{str(e)}\n') def detect_daplink(self): daplinks = aggregator.DebugProbeAggregator.get_all_connected_probes() if len(daplinks) != self.cmbDAP.count(): self.cmbDAP.clear() for daplink in daplinks: self.cmbDAP.addItem(daplink.product_name) self.daplinks = collections.OrderedDict([ (daplink.product_name, daplink) for daplink in daplinks ]) if self.daplink and self.daplink.product_name in self.daplinks: self.cmbDAP.setCurrentIndex(self.daplinks.keys().index( self.daplink.product_name)) else: # daplink被拔掉 self.btnOpen.setText(u'打开连接') @pyqtSlot(int) def on_chkWavShow_stateChanged(self, state): self.ChartView.setVisible(state == Qt.Checked) self.txtMain.setVisible(state == Qt.Unchecked) @pyqtSlot() def on_btnClear_clicked(self): self.txtMain.clear() def closeEvent(self, evt): self.conf.write(open('setting.ini', 'w', encoding='utf-8'))
class ChartView(QChartView): def __init__(self, *args, **kwargs): super(ChartView, self).__init__(*args, **kwargs) self.resize(800, 600) self.setRenderHint(QPainter.Antialiasing) # 抗锯齿 # 自定义x轴label self.category = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] self.initChart() # 提示widget self.toolTipWidget = GraphicsProxyWidget(self._chart) # line self.lineItem = QGraphicsLineItem(self._chart) pen = QPen(Qt.gray) pen.setWidth(1) self.lineItem.setPen(pen) self.lineItem.setZValue(996) self.lineItem.hide() self.lineItemX = QGraphicsLineItem(self._chart) penx = QPen(Qt.gray) penx.setWidth(1) self.lineItemX.setPen(penx) self.lineItemX.setZValue(997) self.lineItemX.hide() # 一些固定计算,减少mouseMoveEvent中的计算量 # 获取x和y轴的最小最大值 axisX, axisY = self._chart.axisX(), self._chart.axisY() self.min_x, self.max_x = axisX.min(), axisX.max() self.min_y, self.max_y = axisY.min(), axisY.max() def resizeEvent(self, event): super(ChartView, self).resizeEvent(event) # 当窗口大小改变时需要重新计算 # 坐标系中左上角顶点 self.point_top = self._chart.mapToPosition( QPointF(self.min_x, self.max_y)) # 坐标右上点 self.point_right_top = self._chart.mapToPosition( QPointF(self.max_x, self.max_y)) # 坐标右下点 self.point_right_bottom = self._chart.mapToPosition( QPointF(self.max_x, self.min_y)) # 坐标原点坐标 self.point_bottom = self._chart.mapToPosition( QPointF(self.min_x, self.min_y)) self.step_x = (self.max_x - self.min_x) / \ (self._chart.axisX().tickCount() - 1) def mouseMoveEvent(self, event): super(ChartView, self).mouseMoveEvent(event) pos = event.pos() # 把鼠标位置所在点转换为对应的xy值 x = self._chart.mapToValue(pos).x() y = self._chart.mapToValue(pos).y() # 根据间隔来确定鼠标当前所在的索引 index = round((x - self.min_x) / self.step_x) # 得到在坐标系中的所有正常显示的series的类型和点 points = [ (serie, serie.at(index)) for serie in self._chart.series() if self.min_x <= x <= self.max_x and self.min_y <= y <= self.max_y ] if points: # 根据鼠标的点获取对应曲线的坐标点, # pos_x = self._chart.mapToPosition( # QPointF(index * self.step_x + self.min_x, self.min_y)) # 设置鼠标的垂直线 self.lineItem.setLine(pos.x(), self.point_top.y(), pos.x(), self.point_bottom.y()) self.lineItem.show() # 设置鼠标的水平线 self.lineItemX.setLine(self.point_top.x(), pos.y(), self.point_right_top.x(), pos.y()) self.lineItemX.show() try: title = self.category[index] except: title = "" t_width = self.toolTipWidget.width() t_height = self.toolTipWidget.height() # 如果鼠标位置离右侧的距离小于tip宽度 x = pos.x() - t_width if self.width() - \ pos.x() - 20 < t_width else pos.x() # 如果鼠标位置离底部的高度小于tip高度 y = pos.y() - t_height if self.height() - \ pos.y() - 20 < t_height else pos.y() self.toolTipWidget.show(title, points, QPoint(x, y)) else: self.toolTipWidget.hide() self.lineItem.hide() self.lineItemX.hide() def handleMarkerClicked(self): marker = self.sender() # 信号发送者 if not marker: return visible = not marker.series().isVisible() # 隐藏或显示series marker.series().setVisible(visible) marker.setVisible(True) # 要保证marker一直显示 # 透明度 alpha = 1.0 if visible else 0.4 # 设置label的透明度 brush = marker.labelBrush() color = brush.color() color.setAlphaF(alpha) brush.setColor(color) marker.setLabelBrush(brush) # 设置marker的透明度 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) def handleMarkerHovered(self, status): # 设置series的画笔宽度 marker = self.sender() # 信号发送者 if not marker: return series = marker.series() if not series: return pen = series.pen() if not pen: return pen.setWidth(pen.width() + (1 if status else -1)) series.setPen(pen) def handleSeriesHoverd(self, point, state): # 设置series的画笔宽度 series = self.sender() # 信号发送者 pen = series.pen() if not pen: return pen.setWidth(pen.width() + (1 if state else -1)) series.setPen(pen) def initChart(self): self._chart = QChart(title="折线图堆叠") self._chart.setAcceptHoverEvents(True) # Series动画 self._chart.setAnimationOptions(QChart.SeriesAnimations) dataTable = [["邮件营销", [120, 132, 101, 134, 90, 230, 210]], ["联盟广告", [220, 182, 191, 234, 290, 330, 310]], ["视频广告", [150, 232, 201, 154, 190, 330, 410]], ["直接访问", [320, 332, 301, 334, 390, 330, 320]], ["搜索引擎", [820, 932, 901, 934, 1290, 1330, 1320]]] for series_name, data_list in dataTable: series = QLineSeries(self._chart) for j, v in enumerate(data_list): series.append(j, v) series.setName(series_name) series.setPointsVisible(True) # 显示圆点 series.hovered.connect(self.handleSeriesHoverd) # 鼠标悬停 self._chart.addSeries(series) self._chart.createDefaultAxes() # 创建默认的轴 axisX = self._chart.axisX() # x轴 axisX.setTickCount(7) # x轴设置7个刻度 axisX.setGridLineVisible(False) # 隐藏从x轴往上的线条 axisY = self._chart.axisY() axisY.setTickCount(7) # y轴设置7个刻度 axisY.setRange(0, 1500) # 设置y轴范围 # 自定义x轴 axis_x = QCategoryAxis( self._chart, labelsPosition=QCategoryAxis.AxisLabelsPositionOnValue) axis_x.setTickCount(7) axis_x.setGridLineVisible(False) min_x = axisX.min() max_x = axisX.max() step = (max_x - min_x) / (7 - 1) # 7个tick for i in range(0, 7): axis_x.append(self.category[i], min_x + i * step) self._chart.setAxisX(axis_x, self._chart.series()[-1]) # self._chart.addAxis(axis_x, Qt.AlignBottom) # chart的图例 legend = self._chart.legend() # 设置图例由Series来决定样式 legend.setMarkerShape(QLegend.MarkerShapeFromSeries) # 遍历图例上的标记并绑定信号 for marker in legend.markers(): # 点击事件 marker.clicked.connect(self.handleMarkerClicked) # 鼠标悬停事件 marker.hovered.connect(self.handleMarkerHovered) self.setChart(self._chart)
class MainWindow(QMainWindow): BASE_DIR = os.path.dirname(os.path.abspath(__file__)) MAIN_UI_FILE = os.path.join(BASE_DIR, "main.ui") NEW_DISH_POPUP_UI_FILE = os.path.join(BASE_DIR, "new_dish_popup.ui") NEW_DISH_MULTI_POPUP_UI_FILE = os.path.join(BASE_DIR, "new_dish_multi_popup.ui") NEW_DISH_DATA_POPUP_UI_FILE = os.path.join(BASE_DIR, "new_dish_data_popup.ui") MODIFY_DISH_POPUP_UI_FILE = os.path.join(BASE_DIR, "modify_dish_popup.ui") DB_FILE = os.path.join(BASE_DIR, "restaurant.db") def __init__(self): super(MainWindow, self).__init__() # Initialize variable self.db_connection = None self.new_dish_popup = QWidget() self.new_dish_multi_popup = QWidget() self.new_dish_data_popup = QWidget() self.modify_dish_popup = QWidget() self.dish_table_model = QStandardItemModel(0, 6) self.dish_table_proxy = TableFilter() self.dish_data_table_model = QStandardItemModel(0, 6) self.dish_data_table_proxy = TableFilter() self.graph_chart = None self.graph_series = {} # Load UI designs uic.loadUi(self.MAIN_UI_FILE, self) uic.loadUi(self.NEW_DISH_POPUP_UI_FILE, self.new_dish_popup) uic.loadUi(self.NEW_DISH_MULTI_POPUP_UI_FILE, self.new_dish_multi_popup) uic.loadUi(self.NEW_DISH_DATA_POPUP_UI_FILE, self.new_dish_data_popup) uic.loadUi(self.MODIFY_DISH_POPUP_UI_FILE, self.modify_dish_popup) self.init_dish_table() self.init_dish_data_table() self.init_graph() # Connect to database self.init_db_connection() # MainWindow Bind action triggers self.action_new_dish.triggered.connect(self.show_new_dish_popup) self.action_new_dish_multi.triggered.connect( self.show_new_dish_multi_popup) self.action_new_data_multi.triggered.connect( lambda: self.modify_new_dish_data_popup_table(show=True)) self.tabWidget.currentChanged.connect(self.update_graph) # Dish Table filter bind self.dish_lineEdit.textChanged.connect( lambda text, col_idx=1: self.dish_table_proxy.set_col_regex_filter( col_idx, text)) self.lower_price_doubleSpinBox.valueChanged.connect( lambda value, col_idx=2: self.dish_table_proxy. set_col_number_filter(col_idx, value, -1)) self.higher_price_doubleSpinBox.valueChanged.connect( lambda value, col_idx=2: self.dish_table_proxy. set_col_number_filter(col_idx, -1, value)) self.lower_week_sell_spinBox.valueChanged.connect( lambda value, col_idx=3: self.dish_table_proxy. set_col_number_filter(col_idx, value, -1)) self.higher_week_sell_spinBox.valueChanged.connect( lambda value, col_idx=3: self.dish_table_proxy. set_col_number_filter(col_idx, -1, value)) # Dish Data Table filter bind self.lower_data_dateEdit.dateChanged.connect( lambda date, col_idx=1: self.dish_data_table_proxy. set_col_date_filter(col_idx, date, -1)) self.higher_data_dateEdit.dateChanged.connect( lambda date, col_idx=1: self.dish_data_table_proxy. set_col_date_filter(col_idx, -1, date)) self.data_lineEdit.textChanged.connect( lambda text, col_idx=2: self.dish_data_table_proxy. set_col_regex_filter(col_idx, text)) self.lower_data_doubleSpinBox.valueChanged.connect( lambda value, col_idx=3: self.dish_data_table_proxy. set_col_number_filter(col_idx, value, -1)) self.higher_data_doubleSpinBox.valueChanged.connect( lambda value, col_idx=3: self.dish_data_table_proxy. set_col_number_filter(col_idx, -1, value)) self.lower_data_spinBox.valueChanged.connect( lambda value, col_idx=4: self.dish_data_table_proxy. set_col_number_filter(col_idx, value, -1)) self.higher_data_spinBox.valueChanged.connect( lambda value, col_idx=4: self.dish_data_table_proxy. set_col_number_filter(col_idx, -1, value)) self.data_all_check_checkBox.stateChanged.connect( lambda state, col_idx=5: self.data_table_check_state( state, col_idx)) self.dish_data_table_model.itemChanged.connect(self.update_series) # Popup bind action triggers self.new_dish_popup.create_new_dish_btn.clicked.connect( self.create_new_dish) self.new_dish_multi_popup.pushButton_ok.clicked.connect( self.create_new_dish_multi) self.new_dish_data_popup.dateEdit.dateChanged.connect( self.modify_new_dish_data_popup_table) self.new_dish_data_popup.pushButton_ok.clicked.connect( self.create_new_dish_data) # Get current dishes self.load_dish_table() self.load_dish_data_table() self.new_dish_data_popup.dateEdit.setDate(QtCore.QDate.currentDate()) def init_dish_table(self): self.dish_tableView.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) # Set Header data and stretch for col, col_name in enumerate( ["ID", "菜品", "价格", "近7天总售出", "操作", "备注"]): self.dish_table_model.setHeaderData(col, Qt.Horizontal, col_name, Qt.DisplayRole) self.dish_table_proxy.setSourceModel(self.dish_table_model) self.dish_tableView.setModel(self.dish_table_proxy) self.dish_tableView.setColumnHidden(0, True) for (col, method) in [(1, "Regex"), (2, "Number"), (3, "Number"), (5, "Regex")]: self.dish_table_proxy.filter_method[col] = method def init_dish_data_table(self): self.data_tableView.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) for col, col_name in enumerate( ["Dish_ID", "日期", "菜品", "价格", "售出", "选择"]): self.dish_data_table_model.setHeaderData(col, Qt.Horizontal, col_name, Qt.DisplayRole) self.dish_data_table_proxy.setSourceModel(self.dish_data_table_model) self.data_tableView.setModel(self.dish_data_table_proxy) self.data_tableView.setColumnHidden(0, True) for (col, method) in [(1, "Date"), (2, "Regex"), (3, "Number"), (4, "Number")]: self.dish_data_table_proxy.filter_method[col] = method def init_graph(self): self.graph_chart = QChart(title="售出图") self.graph_chart.legend().setVisible(True) self.graph_chart.setAcceptHoverEvents(True) graph_view = QChartView(self.graph_chart) graph_view.setRenderHint(QPainter.Antialiasing) self.gridLayout_5.addWidget(graph_view) def init_db_connection(self): self.db_connection = sqlite3.connect(self.DB_FILE) cursor = self.db_connection.cursor() # check create table if not exist sql_create_dish_table = """ CREATE TABLE IF NOT EXISTS dish ( id integer PRIMARY KEY, name text NOT NULL, price numeric Not NULL, remarks text, UNIQUE (name, price) ); """ sql_create_dish_data_table = """ CREATE TABLE IF NOT EXISTS dish_data ( dish_id integer NOT NULL REFERENCES dish(id) ON DELETE CASCADE, date date, sell_num integer DEFAULT 0, PRIMARY KEY (dish_id, date), CONSTRAINT dish_fk FOREIGN KEY (dish_id) REFERENCES dish (id) ON DELETE CASCADE ); """ sql_trigger = """ CREATE TRIGGER IF NOT EXISTS place_holder_data AFTER INSERT ON dish BEGIN INSERT INTO dish_data (dish_id, date, sell_num) VALUES(new.id, null, 0); END; """ cursor.execute(sql_create_dish_table) cursor.execute(sql_create_dish_data_table) cursor.execute("PRAGMA FOREIGN_KEYS = on") cursor.execute(sql_trigger) cursor.close() def load_dish_table(self): today = datetime.today() sql_select_query = """ SELECT dish.id, dish.name, dish.price, COALESCE(SUM(dish_data.sell_num), 0), dish.remarks FROM dish LEFT JOIN dish_data ON dish.id = dish_data.dish_id WHERE dish_data.date IS NULL OR dish_data.date BETWEEN date('{}') and date('{}') GROUP BY dish.id ORDER BY dish.name, dish.price;""".format( (today - timedelta(days=7)).strftime("%Y-%m-%d"), today.strftime("%Y-%m-%d")) cursor = self.db_connection.cursor() cursor.execute(sql_select_query) records = cursor.fetchall() for row_idx, record in enumerate(records): self.dish_table_model.appendRow(create_dish_table_row(*record)) cursor.close() self.dish_tableView.setItemDelegateForColumn( 4, DishTableDelegateCell(self.show_modify_dish_popup, self.delete_dish, self.dish_tableView)) def load_dish_data_table(self): sql_select_query = """ SELECT dish_data.dish_id, dish_data.date, dish.name, dish.price, dish_data.sell_num FROM dish_data LEFT JOIN dish ON dish_data.dish_id = dish.id WHERE dish_data.date IS NOT NULL ORDER BY dish_data.date DESC, dish.name, dish.price, dish_data.sell_num;""" cursor = self.db_connection.cursor() cursor.execute(sql_select_query) records = cursor.fetchall() for row_idx, record in enumerate(records): self.dish_data_table_model.appendRow( create_dish_data_table_row(*record)) cursor.close() self.lower_data_dateEdit.setDate(QDate.currentDate().addDays(-7)) self.higher_data_dateEdit.setDate(QDate.currentDate()) self.data_tableView.setItemDelegateForColumn( 5, DishDataTableDelegateCell(self.data_tableView)) def data_table_check_state(self, state, col): for row in range(self.dish_data_table_proxy.rowCount()): index = self.dish_data_table_proxy.mapToSource( self.dish_data_table_proxy.index(row, col)) if index.isValid(): self.dish_data_table_model.setData(index, str(state), Qt.DisplayRole) def show_new_dish_popup(self): # Move popup to center point = self.rect().center() global_point = self.mapToGlobal(point) self.new_dish_popup.move( global_point - QtCore.QPoint(self.new_dish_popup.width() // 2, self.new_dish_popup.height() // 2)) self.new_dish_popup.show() def show_new_dish_multi_popup(self): file_name = QFileDialog().getOpenFileName(None, "选择文件", "", self.tr("CSV文件 (*.csv)"))[0] self.new_dish_multi_popup.tableWidget.setRowCount(0) if file_name: with open(file_name, "r") as file: csv_reader = csv.reader(file, delimiter=",") for idx, row_data in enumerate(csv_reader): if len(row_data) == 2: name, price = row_data remark = "" elif len(row_data) == 3: name, price, remark = row_data else: QMessageBox.warning( self, "格式错误", self.tr('格式为"菜品 价格"或者"菜品 价格 备注"\n第{}行输入有误'.format( idx))) return self.new_dish_multi_popup.tableWidget.insertRow( self.new_dish_multi_popup.tableWidget.rowCount()) self.new_dish_multi_popup.tableWidget.setItem( idx, 0, QTableWidgetItem(name)) price_type = str_type(price) if price_type == str or (isinstance( price_type, (float, int)) and float(price) < 0): QMessageBox.warning( self, "格式错误", self.tr('第{}行价格输入有误'.format(idx + 1))) return self.new_dish_multi_popup.tableWidget.setItem( idx, 1, QTableWidgetItem("{:.2f}".format(float(price)))) self.new_dish_multi_popup.tableWidget.setItem( idx, 2, QTableWidgetItem(remark)) self.new_dish_multi_popup.show() def modify_new_dish_data_popup_table(self, *args, show=False): sql_select_query = """ SELECT id, name, price, dish_data.sell_num FROM dish LEFT JOIN dish_data ON dish.id=dish_data.dish_id WHERE dish_data.date IS NULL OR dish_data.date = date('{}') GROUP BY id, name, price ORDER BY dish.name, dish.price;""".format( self.new_dish_data_popup.dateEdit.date().toString("yyyy-MM-dd")) cursor = self.db_connection.cursor() cursor.execute(sql_select_query) records = cursor.fetchall() self.new_dish_data_popup.tableWidget.setRowCount(len(records)) self.new_dish_data_popup.tableWidget.setColumnHidden(0, True) for row_idx, record in enumerate(records): dish_id, name, price, sell_num = record self.new_dish_data_popup.tableWidget.setItem( row_idx, 0, QTableWidgetItem(str(dish_id))) self.new_dish_data_popup.tableWidget.setItem( row_idx, 1, QTableWidgetItem(name)) self.new_dish_data_popup.tableWidget.setItem( row_idx, 2, QTableWidgetItem("{:.2f}".format(price))) spin_box = QSpinBox() spin_box.setMaximum(9999) spin_box.setValue(sell_num) self.new_dish_data_popup.tableWidget.setCellWidget( row_idx, 3, spin_box) cursor.close() if show: self.new_dish_data_popup.show() def create_new_dish(self): cursor = self.db_connection.cursor() sql_insert = """ INSERT INTO dish(name, price, remarks) VALUES(?,?,?)""" dish_name = self.new_dish_popup.dish_name.text() dish_price = self.new_dish_popup.dish_price.value() dish_remark = self.new_dish_popup.dish_remark.toPlainText() try: cursor.execute(sql_insert, (dish_name, dish_price, dish_remark)) new_dish_id = cursor.lastrowid cursor.close() self.db_connection.commit() # Update dish table and dish comboBox in UI self.dish_table_model.appendRow( create_dish_table_row(new_dish_id, dish_name, dish_price, 0, dish_remark)) self.new_dish_popup.hide() except sqlite3.Error: cursor.close() QMessageBox.warning(self, "菜品价格重复", self.tr('菜品价格组合重复,请检查')) def create_new_dish_multi(self): cursor = self.db_connection.cursor() sql_insert = """ INSERT INTO dish(name, price, remarks) VALUES (?, ?, ?)""" for row in range(self.new_dish_multi_popup.tableWidget.rowCount()): dish_name = self.new_dish_multi_popup.tableWidget.item(row, 0).text() dish_price = float( self.new_dish_multi_popup.tableWidget.item(row, 1).text()) dish_remark = self.new_dish_multi_popup.tableWidget.item(row, 2).text() try: cursor.execute(sql_insert, (dish_name, dish_price, dish_remark)) new_dish_id = cursor.lastrowid self.dish_table_model.appendRow( create_dish_table_row(new_dish_id, dish_name, dish_price, 0, dish_remark)) except sqlite3.Error: cursor.close() QMessageBox.warning( self, "菜品价格重复", self.tr('前{}行已插入。\n第{}行菜品价格组合重复,请检查'.format(row, row + 1))) return cursor.close() self.db_connection.commit() self.new_dish_multi_popup.hide() def create_new_dish_data(self): current_date = self.new_dish_data_popup.dateEdit.date().toString( "yyyy-MM-dd") table_filter = TableFilter() table_filter.setSourceModel(self.dish_data_table_model) table_filter.set_col_regex_filter(1, current_date) for row in range(table_filter.rowCount()): index = table_filter.mapToSource(table_filter.index(0, 1)) if index.isValid(): self.dish_data_table_model.removeRow(index.row()) del table_filter cursor = self.db_connection.cursor() sql_insert = """ INSERT OR REPLACE INTO dish_data(dish_id, date, sell_num) VALUES (?, ?, ?)""" for row in range(self.new_dish_data_popup.tableWidget.rowCount()): dish_id = int( self.new_dish_data_popup.tableWidget.item(row, 0).text()) name = self.new_dish_data_popup.tableWidget.item(row, 1).text() price = float( self.new_dish_data_popup.tableWidget.item(row, 2).text()) sell_num = self.new_dish_data_popup.tableWidget.cellWidget( row, 3).value() cursor.execute(sql_insert, (dish_id, current_date, sell_num)) self.dish_data_table_model.appendRow( create_dish_data_table_row(dish_id, current_date, name, price, sell_num)) cursor.close() self.db_connection.commit() self.new_dish_data_popup.hide() def delete_dish(self, dish_id): cursor = self.db_connection.cursor() sql_delete = """ DELETE FROM dish WHERE id=?""" cursor.execute(sql_delete, tuple([dish_id])) cursor.close() self.db_connection.commit() # Update dish table and dish comboBox in UI for row in self.dish_data_table_model.findItems(str(dish_id)): index = row.index() if index.isValid(): self.dish_data_table_model.removeRow(index.row()) for row in self.dish_table_model.findItems(str(dish_id)): index = row.index() if index.isValid(): self.dish_table_model.removeRow(index.row()) def show_modify_dish_popup(self, dish_id): point = self.rect().center() global_point = self.mapToGlobal(point) self.modify_dish_popup.move( global_point - QtCore.QPoint(self.modify_dish_popup.width() // 2, self.modify_dish_popup.height() // 2)) # Find the row and get necessary info index = self.dish_table_model.match(self.dish_table_model.index(0, 0), Qt.DisplayRole, str(dish_id)) if index: row_idx = index[0] dish_name = self.dish_table_model.data(row_idx.siblingAtColumn(1)) dish_price = self.dish_table_model.data(row_idx.siblingAtColumn(2)) dish_remark = self.dish_table_model.data( row_idx.siblingAtColumn(5)) self.modify_dish_popup.dish_name.setText(dish_name) self.modify_dish_popup.dish_price.setValue(float(dish_price)) self.modify_dish_popup.dish_remark.setText(dish_remark) try: self.modify_dish_popup.modify_dish_btn.clicked.disconnect() except TypeError: pass self.modify_dish_popup.modify_dish_btn.clicked.connect( lambda: self.modify_dish(row_idx, dish_id)) self.modify_dish_popup.show() def modify_dish(self, row, dish_id): cursor = self.db_connection.cursor() sql_update = """ UPDATE dish SET name = ?, price = ?, remarks = ? WHERE id=?""" dish_name = self.modify_dish_popup.dish_name.text() dish_price = self.modify_dish_popup.dish_price.value() dish_remark = self.modify_dish_popup.dish_remark.toPlainText() cursor.execute(sql_update, (dish_name, dish_price, dish_remark, dish_id)) cursor.close() self.db_connection.commit() self.modify_dish_popup.hide() # Update dish table and dish comboBox in UI old_name = self.dish_table_model.data(row.siblingAtColumn(1)) old_price = self.dish_table_model.data(row.siblingAtColumn(2)) sell_num = self.dish_table_model.data(row.siblingAtColumn(3)) row_idx = row.row() self.dish_table_model.removeRow(row_idx) self.dish_table_model.insertRow( row_idx, create_dish_table_row(dish_id, dish_name, dish_price, sell_num, dish_remark)) for row in self.dish_data_table_model.findItems(str(dish_id)): index = row.index() if index.isValid(): self.dish_data_table_model.setData(index.siblingAtColumn(2), dish_name) self.dish_data_table_model.setData(index.siblingAtColumn(3), "{:.2f}".format(dish_price)) old_key = old_name + '(' + old_price + ')' if old_key in self.graph_line_series: self.graph_line_series[dish_name + '(' + str(dish_price) + ')'] = self.graph_line_series[old_key] del self.graph_line_series[old_key] def update_series(self, item: QStandardItem): if item.column() == 5: # check for checkbox column item_idx = item.index() date = self.dish_data_table_model.data(item_idx.siblingAtColumn(1)) dish_name = self.dish_data_table_model.data( item_idx.siblingAtColumn(2)) dish_price = self.dish_data_table_model.data( item_idx.siblingAtColumn(3)) sell_num = self.dish_data_table_model.data( item_idx.siblingAtColumn(4)) set_name = dish_name + "(" + dish_price + ")" key = str( QDateTime(QDate.fromString(date, "yyyy-MM-dd")).toSecsSinceEpoch()) if key not in self.graph_series: self.graph_series[key] = {} if int(item.text()) == 0: if set_name in self.graph_series[key]: del self.graph_series[key][set_name] if not self.graph_series[key]: del self.graph_series[key] else: self.graph_series[key][set_name] = int(sell_num) def update_graph(self, index): if index == 2: self.graph_chart.removeAllSeries() axis_x = QBarCategoryAxis() axis_x.setTitleText("日期") if self.graph_chart.axisX(): self.graph_chart.removeAxis(self.graph_chart.axisX()) self.graph_chart.addAxis(axis_x, Qt.AlignBottom) axis_y = QValueAxis() axis_y.setLabelFormat("%i") axis_y.setTitleText("售出量") if self.graph_chart.axisY(): self.graph_chart.removeAxis(self.graph_chart.axisY()) self.graph_chart.addAxis(axis_y, Qt.AlignLeft) max_num = 0 total_date = 0 set_dict = {} for key, data in sorted(self.graph_series.items(), key=lambda i: int(i[0])): axis_x.append( QDateTime.fromSecsSinceEpoch( int(key)).toString("yyyy年MM月dd日")) for set_name, value in data.items(): if set_name not in set_dict: set_dict[set_name] = QBarSet(set_name) for _ in range(total_date): set_dict[set_name].append(0) set_dict[set_name].append(value) max_num = max(max_num, value) total_date += 1 for _, bar_set in set_dict.items(): if bar_set.count() < total_date: bar_set.append(0) bar_series = QBarSeries() for _, bar_set in set_dict.items(): bar_series.append(bar_set) bar_series.hovered.connect(self.graph_tooltip) axis_y.setMax(max_num + 1) axis_y.setMin(0) self.graph_chart.addSeries(bar_series) bar_series.attachAxis(axis_x) bar_series.attachAxis(axis_y) def graph_tooltip(self, status, index, bar_set: QBarSet): if status: QToolTip.showText( QCursor.pos(), "{}\n日期: {}\n售出: {}".format(bar_set.label(), self.graph_chart.axisX().at(index), int(bar_set.at(index))))
def __createChart(self): ##创建图表 chart = QChart() #创建 Chart ## chart.setTitle("简单函数曲线") chart.legend().setVisible(True) self.chartView.setChart(chart) #Chart添加到chartView pen=QPen() pen.setWidth(2) ##========LineSeries折线 和 ScatterSeries散点 seriesLine = QLineSeries() seriesLine.setName("LineSeries折线") seriesLine.setPointsVisible(False) #数据点不可见 pen.setColor(Qt.red) seriesLine.setPen(pen) seriesLine.hovered.connect(self.do_series_hovered) #信号 hovered seriesLine.clicked.connect(self.do_series_clicked) #信号 clicked chart.addSeries(seriesLine) #添加到chart seriesLinePoint = QScatterSeries() #散点序列 seriesLinePoint.setName("ScatterSeries散点") shape=QScatterSeries.MarkerShapeCircle #MarkerShapeRectangle seriesLinePoint.setMarkerShape(shape) #散点形状,只有2种 seriesLinePoint.setBorderColor(Qt.yellow) seriesLinePoint.setBrush(QBrush(Qt.red)) seriesLinePoint.setMarkerSize(10) #散点大小 seriesLinePoint.hovered.connect(self.do_series_hovered) #信号 hovered seriesLinePoint.clicked.connect(self.do_series_clicked) #信号 clicked chart.addSeries(seriesLinePoint) #添加到chart ##======== SplineSeries 曲线和 QScatterSeries 散点 seriesSpLine = QSplineSeries() seriesSpLine.setName("SplineSeries曲线") seriesSpLine.setPointsVisible(False) #数据点不可见 pen.setColor(Qt.blue) seriesSpLine.setPen(pen) seriesSpLine.hovered.connect(self.do_series_hovered) #信号 hovered seriesSpLine.clicked.connect(self.do_series_clicked) #信号 clicked seriesSpPoint = QScatterSeries() #散点序列 seriesSpPoint.setName("QScatterSeries") shape=QScatterSeries.MarkerShapeRectangle #MarkerShapeCircle seriesSpPoint.setMarkerShape(shape) #散点形状,只有2种 seriesSpPoint.setBorderColor(Qt.green) seriesSpPoint.setBrush(QBrush(Qt.blue)) seriesSpPoint.setMarkerSize(10) #散点大小 seriesSpPoint.hovered.connect(self.do_series_hovered) #信号 hovered seriesSpPoint.clicked.connect(self.do_series_clicked) #信号 clicked chart.addSeries(seriesSpLine) chart.addSeries(seriesSpPoint) ## 创建缺省坐标轴 chart.createDefaultAxes() #创建缺省坐标轴并与序列关联 chart.axisX().setTitleText("time(secs)") chart.axisX().setRange(0,10) chart.axisX().applyNiceNumbers() chart.axisY().setTitleText("value") chart.axisY().setRange(-2,2) chart.axisY().applyNiceNumbers() for marker in chart.legend().markers(): #QLegendMarker类型列表 marker.clicked.connect(self.do_LegendMarkerClicked)
class ChartView(QChartView): def __init__(self, file, parent=None): super(ChartView, self).__init__(parent) self._chart = QChart() self._chart.setAcceptHoverEvents(True) self.setChart(self._chart) self.initUi(file) def initUi(self, file): if isinstance(file, dict): return self.__analysis(file) if isinstance(file, str): if not os.path.isfile(file): return self.__analysis(json.loads(file)) with open(file, "rb") as fp: data = fp.read() encoding = chardet.detect(data) or {} data = data.decode(encoding.get("encoding") or "utf-8") self.__analysis(json.loads(data)) # def onSeriesHoverd(self, point, state): # print(point, state) def mouseMoveEvent(self, event): super(ChartView, self).mouseMoveEvent(event) # 获取x和y轴的最小最大值 axisX, axisY = self._chart.axisX(), self._chart.axisY() min_x, max_x = axisX.min(), axisX.max() min_y, max_y = axisY.min(), axisY.max() # 把鼠标位置所在点转换为对应的xy值 x = self._chart.mapToValue(event.pos()).x() y = self._chart.mapToValue(event.pos()).y() index = round(x) # 四舍五入 print(x, y, index) # 得到在坐标系中的所有series的类型和点 points = [(s.type(), s.at(index)) for s in self._chart.series() if min_x <= x <= max_x and min_y <= y <= max_y] print(points) def __getColor(self, color=None, default=Qt.white): ''' :param color: int|str|[r,g,b]|[r,g,b,a] ''' if not color: return QColor(default) if isinstance(color, QBrush): return color # 比如[r,g,b]或[r,g,b,a] if isinstance(color, list) and 3 <= len(color) <= 4: return QColor(*color) else: return QColor(color) def __getPen(self, pen=None, default=QPen(Qt.white, 1, Qt.SolidLine, Qt.SquareCap, Qt.BevelJoin)): ''' :param pen: pen json ''' if not pen or not isinstance(pen, dict): return default return QPen(self.__getColor(pen.get("color", None) or default.color()), pen.get("width", 1) or 1, pen.get("style", 0) or 0, pen.get("capStyle", 16) or 16, pen.get("joinStyle", 64) or 64) def __getAlignment(self, alignment): ''' :param alignment: left|top|right|bottom ''' try: return getattr(Qt, "Align" + alignment.capitalize()) except: return Qt.AlignTop # if alignment == "left": # return Qt.AlignLeft # if alignment == "right": # return Qt.AlignRight # if alignment == "bottom": # return Qt.AlignBottom # return Qt.AlignTop def __setTitle(self, title=None): ''' :param title: title json ''' if not title or not isinstance(title, dict): return # 设置标题 self._chart.setTitle(title.get("text", "") or "") # 设置标题颜色 self._chart.setTitleBrush( self.__getColor( title.get("color", self._chart.titleBrush()) or self._chart.titleBrush())) # 设置标题字体 font = QFont(title.get("font", "") or self._chart.titleFont()) pointSize = title.get("pointSize", -1) or -1 if pointSize > 0: font.setPointSize(pointSize) font.setWeight(title.get("weight", -1) or -1) font.setItalic(title.get("italic", False) or False) self._chart.setTitleFont(font) def __setAnimation(self, animation=None): ''' :param value: animation json ''' if not animation or not isinstance(animation, dict): return # 动画持续时间 self._chart.setAnimationDuration( animation.get("duration", 1000) or 1000) # 设置动画曲线 self._chart.setAnimationEasingCurve( EasingCurve.get(animation.get("curve", 10) or 10, None) or QEasingCurve.OutQuart) # 设置开启何种动画 self._chart.setAnimationOptions( AnimationOptions.get(animation.get("options", 0) or 0, None) or QChart.NoAnimation) def __setBackground(self, background=None): ''' :param background:background json ''' if not background or not isinstance(background, dict): return # 设置是否背景可用 self._chart.setBackgroundVisible( background.get("visible", True) or True) # 设置背景矩形的圆角 self._chart.setBackgroundRoundness(background.get("radius", 0) or 0) # 设置下拉阴影 self._chart.setDropShadowEnabled( background.get("dropShadow", True) or True) # 设置pen self._chart.setBackgroundPen( self.__getPen(background.get("pen", None), self._chart.backgroundPen())) # 设置背景 image = background.get("image", None) color = background.get("color", None) if image: self._chart.setBackgroundBrush(QBrush(QPixmap(image))) elif color: self._chart.setBackgroundBrush( self.__getColor(color, self._chart.backgroundBrush())) def __setMargins(self, margins=None): ''' :param margins: margins json ''' if not margins or not isinstance(margins, dict): return left = margins.get("left", 20) or 20 top = margins.get("top", 20) or 20 right = margins.get("right", 20) or 20 bottom = margins.get("bottom", 20) or 20 self._chart.setMargins(QMargins(left, top, right, bottom)) def __setLegend(self, legend=None): ''' :param legend: legend json ''' if not legend or not isinstance(legend, dict): return _legend = self._chart.legend() _legend.setAlignment(self.__getAlignment(legend.get("alignment", None))) _legend.setShowToolTips(legend.get("showToolTips", True) or True) def __getSerie(self, serie=None): if not serie or not isinstance(serie, dict): return None types = serie.get("type", "") or "" data = serie.get("data", []) or [] if not data or not isinstance(data, list): return None if types == "line": _series = QLineSeries(self._chart) else: return None # 设置series名字 _series.setName(serie.get("name", "") or "") # 添加数据到series中 for index, value in enumerate(data): # 保证vlaue必须是数字 _series.append(index, value if type(value) in (int, float) else 0) return _series def __setSeries(self, series=None): if not series or not isinstance(series, list): return for serie in series: _serie = self.__getSerie(serie) if _serie: # _serie.hovered.connect(self.onSeriesHoverd) self._chart.addSeries(_serie) # 创建默认的xy轴 self._chart.createDefaultAxes() def __setAxisX(self, axisx=None): if not axisx or not isinstance(axisx, dict): return series = self._chart.series() if not series: return types = axisx.get("type", None) data = axisx.get("data", []) or [] if not data or not isinstance(data, list): return None minx = self._chart.axisX().min() maxx = self._chart.axisX().max() if types == "category": xaxis = QCategoryAxis( self._chart, labelsPosition=QCategoryAxis.AxisLabelsPositionOnValue) # 隐藏网格 xaxis.setGridLineVisible(False) # 刻度条数 tickc_d = len(data) tickc = tickc_d if tickc_d > 1 else self._chart.axisX().tickCount() xaxis.setTickCount(tickc) # 强制x轴刻度与新刻度条数一致 self._chart.axisX().setTickCount(tickc) step = (maxx - minx) / (tickc - 1) for i in range(min(tickc_d, tickc)): xaxis.append(data[i], minx + i * step) self._chart.setAxisX(xaxis, series[-1]) def __analysis(self, datas): ''' analysis json data :param datas: json data ''' # 标题 self.__setTitle(datas.get("title", None)) # 抗锯齿 if (datas.get("antialiasing", False) or False): self.setRenderHint(QPainter.Antialiasing) # 主题 self._chart.setTheme(datas.get("theme", 0) or 0) # 动画 self.__setAnimation(datas.get("animation", None)) # 背景设置 self.__setBackground(datas.get("background", None)) # 边距设置 self.__setMargins(datas.get("margins", None)) # 设置图例 self.__setLegend(datas.get("legend", None)) # 设置series self.__setSeries(datas.get("series", None)) # 自定义的x轴 self.__setAxisX(datas.get("axisx", None))