def create_piechart(self, series, animation=True): if animation: animation_type = QChart.AllAnimations else: animation_type = QChart.NoAnimation font_title = QFont() font_title.setBold(True) font_title.setPointSize(13) # define the chart properties chart = QChart() chart.addSeries(series) chart.createDefaultAxes() chart.setAnimationOptions(animation_type) chart.setBackgroundVisible(False) chart.setMargins(QMargins()) chart.setTitle("Prediction Distribution") chart.setTitleFont(font_title) font_legend = QFont() font_legend.setBold(True) # define legend properties chart.legend().show() chart.legend().setVisible(True) chart.legend().setAlignment(Qt.AlignBottom) chart.legend().setFont(font_legend) self.ui.piechart.setChart(chart) self.ui.piechart.setRenderHint(QPainter.Antialiasing) QApplication.processEvents()
def build_plot(self, data, title, is_legend_visible=False, series_name=None): axis_x, axis_y = self.make_axis() chart = QChart() if self.no_margins: chart.setMargins(QMargins(0, 0, 0, 0)) self.clean() if data != None: self.lineSeries = self.fill_series(data) self.lineSeries.setName(series_name) chart.addSeries(self.lineSeries) chart.legend().setVisible(is_legend_visible) chart.setTitle(title) chart.addAxis(axis_x, Qt.AlignBottom) chart.addAxis(axis_y, Qt.AlignLeft) self.lineSeries.attachAxis(axis_x) self.lineSeries.attachAxis(axis_y) self.setChart(chart)
def create_barchart(self, series, max_val, animation=True): if animation: animation_type = QChart.AllAnimations else: animation_type = QChart.NoAnimation chart = QChart() chart.addSeries(series) chart.setBackgroundVisible(False) chart.setMargins(QMargins()) chart.setAnimationOptions(animation_type) labels = ('Human', 'Bot') axisX = QBarCategoryAxis() axisX.append(labels) axisY = QValueAxis() axisY.setTitleText("Percentage (%)") axisY.setRange(0, max_val) chart.addAxis(axisX, Qt.AlignBottom) chart.addAxis(axisY, Qt.AlignLeft) font = QFont() font.setPointSize(9) chart.legend().setVisible(True) chart.legend().setFont(font) chart.legend().setAlignment(Qt.AlignBottom) self.ui.barchart.setChart(chart) self.ui.barchart.setRenderHint(QPainter.Antialiasing)
def build_multiple_plot(self, first_data, second_data, title): axis_x, axis_y = self.make_axis() chart = QChart() if self.no_margins: chart.setMargins(QMargins(0, 0, 0, 0)) self.clean() first_lineseries = self.fill_series(first_data) second_lineseries = self.fill_series(second_data) chart.addSeries(first_lineseries) chart.addSeries(second_lineseries) chart.addAxis(axis_x, Qt.AlignBottom) chart.addAxis(axis_y, Qt.AlignLeft) chart.legend().setVisible(False) chart.setTitle(title) first_lineseries.attachAxis(axis_x) first_lineseries.attachAxis(axis_y) second_lineseries.attachAxis(axis_x) second_lineseries.attachAxis(axis_y) first_lineseries.setPointsVisible(True) second_lineseries.setPointsVisible(True) self.setChart(chart)
def load_glycation(self, filename: Optional[str] = None) -> None: """ Load glycation data from a CSV file and display it in the corresponding chart view. :param str filename: directly load this file :return: nothing, sets self.se_glycation :rtype: None """ # load and clean glycation data if filename is None: filename, self.last_path = get_filename( self, "open", self.tr("Load glycation data ..."), self.last_path, FileTypes(["csv"])) if filename is None: return logging.info( self.tr("Loading glycation data in '{}'").format(filename)) try: self.glycation = read_clean_datasets(filename) except (OSError, ValueError) as e: logging.error(str(e)) QMessageBox.critical(self, self.tr("Error"), str(e)) return # extract x- and y-values from series x_values = [str(i) for i in self.glycation.index] y_values = [a.nominal_value for a in self.glycation] # assemble the chart bar_set = QBarSet("glycation abundance") bar_set.append(y_values) bar_set.setColor(QColor("#a1dab4")) bar_set.hovered.connect(self.update_glycation_label) bar_series = QBarSeries() bar_series.append(bar_set) x_axis = QBarCategoryAxis() x_axis.append(x_values) x_axis.setTitleText(self.tr("count")) y_axis = QValueAxis() y_axis.setRange(0, 100) y_axis.setTitleText(self.tr("abundance")) y_axis.setLabelFormat("%d") chart = QChart() chart.addSeries(bar_series) chart.setAxisX(x_axis, bar_series) chart.setAxisY(y_axis, bar_series) chart.legend().setVisible(False) chart.setBackgroundRoundness(0) chart.layout().setContentsMargins(0, 0, 0, 0) chart.setMargins(QMargins(5, 5, 5, 5)) self.cvGlycation.setChart(chart)
class pieChartView(QChartView): def __init__(self, *args, **kwargs): super(pieChartView, self).__init__(*args, **kwargs) self.initChart() self.refresh() def initChart(self): self._chart = QChart() # 调整边距 self._chart.layout().setContentsMargins(0, 0, 0, 0) # 外界 self._chart.setMargins(QMargins(3, 0, 3, 0)) # 内界 self._chart.setBackgroundRoundness(0) self._chart.setBackgroundVisible(False) # 设置主题 self._chart.setTheme(QChart.ChartThemeBlueIcy) # 抗锯齿 self.setRenderHint(QPainter.Antialiasing) # 开启动画效果 self._chart.setAnimationOptions(QChart.SeriesAnimations) self._series = QPieSeries() self._series.setPieSize(0.8) self._chart.addSeries(self._series) self.setChart(self._chart) def refresh(self): # 提示widget self.toolTipWidget = GraphicsProxyWidget(self._chart) def mouseMoveEvent(self, event): super(pieChartView, self).mouseMoveEvent(event) pos = event.pos() x = pos.x() y = pos.y() # 得到在坐标系中的所有区域 self.min_x, self.max_x = self._chart.geometry().width( ) * 0.2, self._chart.geometry().width() * 0.8 self.min_y, self.max_y = self._chart.geometry().height( ) * 0.2, self._chart.geometry().height() * 0.9 serie = self._chart.series()[0] slices = [(slice, slice.value()) for slice in serie.slices()] if self.min_x <= x <= self.max_x and self.min_y <= y <= self.max_y: t_width = self.toolTipWidget.width() t_height = self.toolTipWidget.height() title = "数据组成" # 如果鼠标位置离右侧的距离小于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, slices, QPoint(x, y)) else: self.toolTipWidget.hide()
def agg_glycoforms(self) -> None: """ Display glycoform data in the corresponding chart view. :return: nothing :rtype: None """ # aggregate "other" abundances if self.cbAggGlycoforms.isChecked(): agg_abundance = ( self.glycoforms.iloc[self.sbAggGlycoforms.value():].sum()) self.glycoforms_agg = ( self.glycoforms.iloc[:self.sbAggGlycoforms.value()].append( pd.Series(agg_abundance, index=[self.tr("other")]))) else: self.glycoforms_agg = self.glycoforms # extract x- and y-values from series x_values = [str(i) for i in self.glycoforms_agg.index] y_values = [a.nominal_value for a in self.glycoforms_agg] # assemble the chart bar_set = QBarSet("glycoform abundance") bar_set.append(y_values) bar_set.setColor(QColor("#2c7fb8")) bar_set.hovered.connect(self.update_glycoform_label) bar_series = QBarSeries() bar_series.append(bar_set) x_axis = QBarCategoryAxis() x_axis.append(x_values) x_axis.setTitleVisible(False) x_axis.setLabelsAngle(270) range_max = max(self.glycoforms_agg).nominal_value range_max = math.ceil(range_max / 20) * 20 tick_count = range_max // 20 + 1 y_axis = QValueAxis() y_axis.setRange(0, range_max) y_axis.setTickCount(tick_count) y_axis.setTitleText(self.tr("abundance")) y_axis.setLabelFormat("%d") chart = QChart() chart.addSeries(bar_series) chart.setAxisX(x_axis, bar_series) chart.setAxisY(y_axis, bar_series) chart.legend().setVisible(False) chart.setBackgroundRoundness(0) chart.layout().setContentsMargins(0, 0, 0, 0) chart.setMargins(QMargins(5, 5, 5, 5)) self.cvGlycoforms.setChart(chart)
def crearGraficoBarras(self): paises = [ "EEUU", "China", "Japon", "Alemania", "Reino Unido", "Resto del mundo" ] valores = [24.32, 14.85, 8.91, 12.54, 7.85, 31.53] colores = [ Qt.blue, Qt.red, Qt.darkYellow, Qt.gray, Qt.black, Qt.darkCyan ] grafico = QChart() grafico.setMargins(QMargins(30, 30, 30, 30)) grafico.setTheme(QChart.ChartThemeLight) grafico.setTitle("% Distribución del PIB global") grafico.setAnimationOptions(QChart.SeriesAnimations) for i in range(len(paises)): series = QBarSeries() barSet = QBarSet(paises[i]) barSet.setColor(colores[i]) barSet.setLabelColor(Qt.yellow) barSet.append(valores[i]) series.append(barSet) series.setLabelsVisible(True) series.setLabelsAngle(-90) # series.setLabelsPrecision(2) series.setLabelsFormat("@value %") series.setLabelsPosition(QAbstractBarSeries.LabelsCenter) grafico.addSeries(series) axisX = QBarCategoryAxis() axisX.append(paises) axisY = QValueAxis() axisY.setRange(0, 31.53) axisY.setTickCount(10) axisY.setLabelFormat("%.2f %") grafico.createDefaultAxes() grafico.setAxisX(axisX, None) grafico.setAxisY(axisY, None) grafico.legend().setVisible(True) grafico.legend().setAlignment(Qt.AlignBottom) return grafico
def create_histogram(self, series, max_val, animation=True): if animation: animation_type = QChart.AllAnimations else: animation_type = QChart.NoAnimation font_title = QFont() font_title.setBold(True) font_title.setPointSize(13) chart = QChart() chart.addSeries(series) chart.setTitle('Histogram of Bot Scores') chart.setTitleFont(font_title) chart.setBackgroundVisible(False) chart.setMargins(QMargins()) chart.setAnimationOptions(animation_type) scores = ('0', '0.1', '0.2', '0.3', '0.4', '0.5', '0.6', '0.7', '0.8', '0.9') axisX = QBarCategoryAxis() axisX.setTitleText("Bot Score") axisX.append(scores) axisY = QValueAxis() axisY.setTitleText("Number Of Tweets") axisY.setRange(0, max_val) chart.addAxis(axisX, Qt.AlignBottom) chart.addAxis(axisY, Qt.AlignLeft) chart.legend().setVisible(False) chart.legend().setAlignment(Qt.AlignBottom) self.ui.histogram.setChart(chart) self.ui.histogram.setRenderHint(QPainter.Antialiasing) QApplication.processEvents()
class MonitorTab(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(1550, 750) self.gridLayout_2 = QtWidgets.QGridLayout() self.gridLayout_2.setObjectName("gridLayout_2") self.gridLayout = QtWidgets.QGridLayout() self.gridLayout.setObjectName("gridLayout") self.chart4 = QChart() self.chart4.setMargins(QMargins(0, 0, 0, 0)) self.chart4.setBackgroundVisible(False) self.chart4.legend().setVisible(False) self.chartView4 = QChartView(self.chart4) self.gridLayout.addWidget(self.chartView4, 7, 0, 1, 1) self.chart3 = QChart() self.chart3.setMargins(QMargins(0, 0, 0, 0)) self.chart3.setBackgroundVisible(False) self.chart3.legend().setVisible(False) self.chartView3 = QChartView(self.chart3) self.gridLayout.addWidget(self.chartView3, 5, 0, 1, 1) self.chart2 = QChart() self.chart2.setMargins(QMargins(0, 0, 0, 0)) self.chart2.setBackgroundVisible(False) self.chart2.legend().setVisible(False) self.chartView2 = QChartView(self.chart2) self.gridLayout.addWidget(self.chartView2, 3, 0, 1, 1) self.chart1 = QChart() self.chart1.setMargins(QMargins(0, 0, 0, 0)) self.chart1.setBackgroundVisible(False) self.chartView = QChartView(self.chart1) self.chart1.legend().setVisible(False) self.gridLayout.addWidget(self.chartView, 1, 0, 1, 1) self.gridLayout_2.addLayout(self.gridLayout, 0, 0, 1, 1) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "MyForm"))
def agg_results(self) -> None: """ Display results in the corresponding chart view. :return: nothing :rtype: None """ # aggregate "other" abundances if self.cbAggResults.isChecked(): agg_abundance = ( self.results.iloc[self.sbAggResults.value():].sum()) agg_abundance["glycoform"] = self.tr("other") self.results_agg = ( self.results.iloc[:self.sbAggResults.value()].append( agg_abundance, ignore_index=True)) else: self.results_agg = self.results # extract x- and y-values from series x_values = list(self.results_agg["glycoform"].str.split(" or").str[0]) y_values_obs = list(self.results_agg["abundance"]) y_values_cor = list(self.results_agg["corr_abundance"]) # assemble the chart bar_set_obs = QBarSet(self.tr("observed")) bar_set_obs.append(y_values_obs) bar_set_obs.setColor(QColor("#225ea8")) bar_set_obs.hovered.connect(self.update_results_label) bar_set_cor = QBarSet(self.tr("corrected")) bar_set_cor.append(y_values_cor) bar_set_cor.setColor(QColor("#41b6c4")) bar_set_cor.hovered.connect(self.update_results_label) bar_series = QBarSeries() bar_series.append([bar_set_obs, bar_set_cor]) x_axis = QBarCategoryAxis() x_axis.append(x_values) x_axis.setTitleVisible(False) x_axis.setLabelsAngle(270) range_min = min( self.results_agg[["abundance", "corr_abundance"]].min().min(), 0) range_min = math.floor(range_min / 20) * 20 range_max = (self.results_agg[["abundance", "corr_abundance"]].max().max()) range_max = math.ceil(range_max / 20) * 20 tick_count = (range_max - range_min) // 20 + 1 y_axis = QValueAxis() y_axis.setRange(range_min, range_max) y_axis.setTickCount(tick_count) y_axis.setTitleText(self.tr("abundance")) y_axis.setLabelFormat("%d") chart = QChart() chart.addSeries(bar_series) chart.setAxisX(x_axis, bar_series) chart.setAxisY(y_axis, bar_series) chart.legend().setVisible(False) chart.setBackgroundRoundness(0) chart.layout().setContentsMargins(0, 0, 0, 0) chart.setMargins(QMargins(5, 5, 5, 5)) self.cvResults.setChart(chart)
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 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 QIndex_gasList(QDialog): username = '' person_id = '' url = '' gas = [] def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_Index_gasList() self.ui.setupUi(self) self.model = QStringListModel(self) self.ui.listView.setModel(self.model) self.ui.listView.setEditTriggers(QAbstractItemView.NoEditTriggers) #self.ui.listView.setEditTriggers(QAbstractItemView.DoubleClicked | QAbstractItemView.SelectedClicked) self.__createChart() def setInfo(self, username, person_id, url): self.username = username self.person_id = person_id self.url = url #开始查询该用户的所有气体文件,用list变量gas保存 list_gasInfo = requests.get(self.url + '/person/getGasInfo/' + str(self.person_id)) self.gas = eval(list_gasInfo.text) #开始将数据添加进list中 for i in self.gas: self.addItem(i["save_time"]) def addItem(self, name): #向list添加数据 #在尾部插入一空行 lastRow = self.model.rowCount() self.model.insertRow(lastRow) #给该空行设置显示 index = self.model.index(lastRow, 0) self.model.setData(index, name, Qt.DisplayRole) #选中该行 self.ui.listView.setCurrentIndex(index) def on_listView_clicked(self, index): #如果某条数据信息被点击,则向绘图框发送信息 self.showGas(self.gas[self.ui.listView.currentIndex().row()]) self.__prepareData() def showGas(self, gasData): #接收gas数据,并在页面右侧进行显示 #首先向五个Edit设置数据 sensor = self.ui.sensorBox.currentIndex() self.ui.timeEdit.setText(gasData["gas_time"]) self.ui.maxEdit.setText(str(eval(gasData["gas_max"])[sensor])) self.ui.minEdit.setText(str(eval(gasData["gas_min"])[sensor])) self.ui.oriEdit.setText(str(eval(gasData["gas_ori"])[sensor])) self.ui.extrEdit.setText(str(eval(gasData["gas_extrTime"])[sensor])) def on_sensorBox_currentIndexChanged(self): gasData = self.gas[self.ui.listView.currentIndex().row()] sensor = self.ui.sensorBox.currentIndex() self.ui.timeEdit.setText(gasData["gas_time"]) self.ui.maxEdit.setText(str(eval(gasData["gas_max"])[sensor])) self.ui.minEdit.setText(str(eval(gasData["gas_min"])[sensor])) self.ui.oriEdit.setText(str(eval(gasData["gas_ori"])[sensor])) self.ui.extrEdit.setText(str(eval(gasData["gas_extrTime"])[sensor])) def __createChart(self): self.__chart = QChart() self.ui.chartView.setChart(self.__chart) self.__chart.legend().setVisible(False) #隐藏图例 self.__chart.setMargins(QMargins(0, 0, 0, 0)) #把间距设置到最小 #初始化线条数组 series = [QLineSeries() for _ in range(15)] color = [ '#FF88C2', '#FF8888', '#FFA488', '#FFBB66', '#FFDD55', '#FFFF77', '#DDFF77', '#BBFF66', '#66FF66', '#77FFCC', '#77FFEE', '#66FFFF', '#77DDFF', '#99BBFF', '#9999FF' ] #设置线条颜色形状 pen = QPen() pen.setStyle(Qt.SolidLine) pen.setWidth(2) for i in range(15): pen.setColor(QColor(color[i])) series[i].setPen(pen) #向表格添加线条 for i in range(15): self.__chart.addSeries(series[i]) #设置坐标轴 axisX = QValueAxis() #self.__curAxis=axisX #当前坐标轴 axisX.setRange(0, 200) #设置坐标轴范围 axisX.setLabelFormat("%d") #标签格式 axisX.setTickCount(5) #主分隔个数 axisX.setMinorTickCount(4) axisX.setGridLineVisible(True) axisX.setMinorGridLineVisible(False) axisY = QValueAxis() axisY.setRange(0, 1024) axisY.setLabelFormat("%d") #标签格式 axisY.setTickCount(5) axisY.setMinorTickCount(4) axisY.setGridLineVisible(True) axisY.setMinorGridLineVisible(False) self.__chart.addAxis(axisX, Qt.AlignBottom) #坐标轴添加到图表,并指定方向 self.__chart.addAxis(axisY, Qt.AlignLeft) for i in range(15): series[i].attachAxis(axisX) series[i].attachAxis(axisY) def __prepareData(self): ##为序列设置数据点 chart = self.ui.chartView.chart() #获取chartView中的QChart对象 series = [QLineSeries() for _ in range(15)] for i in range(15): series[i] = chart.series()[i] series[i].clear() gasData = self.gas[self.ui.listView.currentIndex().row()] sensors = ['' for _ in range(15)] for i in range(15): sensors[i] = eval(gasData['gas_sensors' + str(i + 1)]) for j in range(len(sensors[i])): series[i].append(j, sensors[i][j]) @pyqtSlot(bool) def on_refreshButton_clicked(self): #刷新列表 #先清空所有的list self.model.removeRows(0, self.model.rowCount()) #开始查询该用户的所有气体文件,用list变量gas保存 list_gasInfo = requests.get(self.url + '/person/getGasInfo/' + str(self.person_id)) self.gas = eval(list_gasInfo.text) #开始将数据添加进list中 for i in self.gas: self.addItem(i["save_time"]) @pyqtSlot(bool) def on_deleteButton_clicked(self): #删除某条气体数据 #如果此时listview没有数据,则跳过 if self.model.rowCount() == 0: pass else: #弹出确认删除的窗口 result = QMessageBox.question( self, '删除', '是否要删除' + self.gas[self.ui.listView.currentIndex().row()]["save_time"], QMessageBox.Yes | QMessageBox.Cancel, QMessageBox.NoButton) if result == QMessageBox.Yes: delete_gasInfo = requests.get( self.url + '/person/deleteGas/' + str(self.gas[ self.ui.listView.currentIndex().row()]["gas_id"])) #这个地方的删除实际上没有删除数据库中的表,是把gas的is_delete写为了True,显示的时候不做显示 #先清空所有的list self.model.removeRows(0, self.model.rowCount()) #开始查询该用户的所有气体文件,用list变量gas保存 list_gasInfo = requests.get(self.url + '/person/getGasInfo/' + str(self.person_id)) self.gas = eval(list_gasInfo.text) #开始将数据添加进list中 for i in self.gas: self.addItem(i["save_time"])
class barChartView(QChartView): def __init__(self, xAxis=[], *args, **kwargs): super(barChartView, self).__init__(*args, **kwargs) self.initChart(xAxis) # line 宽度需要调整 self.lineItem = QGraphicsLineItem(self._chart) pen = QPen(Qt.gray) self.lineItem.setPen(pen) self.lineItem.setZValue(998) self.lineItem.hide() self.cal() # 一些固定计算,减少mouseMoveEvent中的计算量 def cal(self): # 提示widget self.toolTipWidget = GraphicsProxyWidget(self._chart) # 获取x和y轴的最小最大值 axisX, axisY = self._chart.axisX(), self._chart.axisY() self.category_len = len(axisX.categories()) self.min_x, self.max_x = -0.5, self.category_len - 0.5 self.min_y, self.max_y = axisY.min(), axisY.max() # 坐标系中左上角顶点 self.point_top = self._chart.mapToPosition( QPointF(self.min_x, self.max_y)) def setCat(self, data): self.categories = data #初始化 def initChart(self, xAxis): self._chart = QChart() # 调整边距 self._chart.layout().setContentsMargins(0, 0, 0, 0) # 外界 self._chart.setMargins(QMargins(3, 0, 3, 0)) # 内界 self._chart.setBackgroundRoundness(0) self._chart.setBackgroundVisible(False) # 设置主题 self._chart.setTheme(QChart.ChartThemeBlueIcy) # 抗锯齿 self.setRenderHint(QPainter.Antialiasing) # 开启动画效果 self._chart.setAnimationOptions(QChart.SeriesAnimations) self.categories = xAxis self._series = QBarSeries(self._chart) self._chart.addSeries(self._series) self._chart.createDefaultAxes() # 创建默认的轴 self._axis_x = QBarCategoryAxis(self._chart) self._axis_x.append(self.categories) self._axis_y = QValueAxis(self._chart) self._axis_y.setTitleText("任务数") self._axis_y.setRange(0, 10) self._chart.setAxisX(self._axis_x, self._series) self._chart.setAxisY(self._axis_y, self._series) # chart的图例 legend = self._chart.legend() legend.setVisible(True) self.setChart(self._chart) def mouseMoveEvent(self, event): super(barChartView, self).mouseMoveEvent(event) pos = event.pos() # 把鼠标位置所在点转换为对应的xy值 x = self._chart.mapToValue(pos).x() y = self._chart.mapToValue(pos).y() index = round(x) # 得到在坐标系中的所有bar的类型和点 serie = self._chart.series()[0] bars = [ (bar, bar.at(index)) for bar in serie.barSets() if self.min_x <= x <= self.max_x and self.min_y <= y <= self.max_y ] # print(bars) if bars: right_top = self._chart.mapToPosition( QPointF(self.max_x, self.max_y)) # 等分距离比例 step_x = round( (right_top.x() - self.point_top.x()) / self.category_len) posx = self._chart.mapToPosition(QPointF(x, self.min_y)) self.lineItem.setLine(posx.x(), self.point_top.y(), posx.x(), posx.y()) self.lineItem.show() try: title = self.categories[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, bars, QPoint(x, y)) else: self.toolTipWidget.hide() self.lineItem.hide()
class Statistics(QWidget): """A statistics widget that displays information about studied time, shows grown plants, etc...""" def __init__(self, history, *args, **kwargs): super().__init__(*args, **kwargs) self.history = history chart = self.generate_chart() self.SPACING = 10 self.MIN_WIDTH = 400 self.MIN_HEIGHT = 200 chart.setMinimumWidth(self.MIN_WIDTH) chart.setMinimumHeight(self.MIN_HEIGHT) image_layout = QVBoxLayout() self.plant_study = None # the study record the plant is a part of self.plant: Optional[Plant] = None # the plant being displayed self.plant_date_label = QLabel(self) self.plant_date_label.setAlignment(Qt.AlignLeft) self.plant_duration_label = QLabel(self) self.plant_duration_label.setAlignment(Qt.AlignRight) label_layout = QHBoxLayout() label_layout.addWidget(self.plant_date_label) label_layout.addWidget(self.plant_duration_label) self.canvas = Canvas(self) self.canvas.setMinimumWidth(self.MIN_HEIGHT) self.canvas.setMinimumHeight(self.MIN_HEIGHT) stacked_layout = QVBoxLayout() stacked_layout.addLayout(label_layout) stacked_layout.addWidget(self.canvas) image_control = QHBoxLayout() text_color = self.palette().text().color() self.age_slider = QSlider(Qt.Horizontal, minimum=0, maximum=1000, value=1000, valueChanged=self.slider_value_changed) self.left_button = QPushButton(self, clicked=self.left, icon=qtawesome.icon('fa5s.angle-left', color=text_color)) self.right_button = QPushButton(self, clicked=self.right, icon=qtawesome.icon('fa5s.angle-right', color=text_color)) self.save_button = QPushButton(self, clicked=self.save, icon=qtawesome.icon('fa5s.download', color=text_color)) image_control.addWidget(self.left_button) image_control.addWidget(self.right_button) image_control.addSpacing(self.SPACING) image_control.addWidget(self.age_slider) image_control.addSpacing(self.SPACING) image_control.addWidget(self.save_button) image_layout.addLayout(stacked_layout) image_layout.addLayout(image_control) image_layout.setContentsMargins(self.SPACING, self.SPACING, self.SPACING, self.SPACING) separator = QFrame() separator.setStyleSheet(f"background-color: {self.palette().text().color().name()}") separator.setFixedWidth(1) main_layout = QGridLayout() main_layout.setHorizontalSpacing(self.SPACING * 2) main_layout.setColumnStretch(0, 1) main_layout.setColumnStretch(2, 0) main_layout.addWidget(chart, 0, 0) main_layout.addWidget(separator, 0, 1) main_layout.addLayout(image_layout, 0, 2) self.setLayout(main_layout) self.move() # go to the most recent plant self.refresh() def generate_chart(self): """Generate the bar graph for the widget.""" self.tags = [QBarSet(tag) for tag in ["Study"]] series = QStackedBarSeries() for set in self.tags: series.append(set) self.chart = QChart() self.chart.addSeries(series) self.chart.setTitle("Total time studied (minutes per day)") days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] axis = QBarCategoryAxis() axis.append(days) self.chart.createDefaultAxes() self.chart.setAxisX(axis, series) self.chart.legend().setAlignment(Qt.AlignBottom) self.chart.legend().setVisible(False) self.chart.setTheme(QChart.ChartThemeQt) self.chart.setBackgroundVisible(False) self.chart.setBackgroundRoundness(0) self.chart.setMargins(QMargins(0, 0, 0, 0)) self.chart.setTitleBrush(QBrush(self.palette().text().color())) yAxis = self.chart.axes(Qt.Vertical)[0] yAxis.setGridLineVisible(False) yAxis.setLabelFormat("%d") yAxis.setLinePenColor(self.palette().text().color()) yAxis.setLabelsColor(self.palette().text().color()) xAxis = self.chart.axes(Qt.Horizontal)[0] xAxis.setGridLineVisible(False) xAxis.setLinePenColor(self.palette().text().color()) xAxis.setLabelsColor(self.palette().text().color()) chartView = QChartView(self.chart) chartView.setRenderHint(QPainter.Antialiasing) return chartView def slider_value_changed(self): """Called when the slider value has changed. Sets the age of the plant and updates it.""" if self.plant is not None: # makes it a linear function from 0 to whatever the duration was, so the plant appears to grow normally self.plant.set_age( self.plant.inverse_age_coefficient_function(self.age_slider.value() / self.age_slider.maximum() * self.plant.age_coefficient_function( self.plant_study["duration"]))) self.canvas.update() def refresh(self): """Refresh the labels.""" # clear tag values for tag in self.tags: tag.remove(0, tag.count()) study_minutes = [0] * 7 for study in self.history.get_studies(): # TODO: don't just crash study_minutes[study["date"].weekday()] += study["duration"] for minutes in study_minutes: self.tags[0] << minutes # manually set the range of the y axis, because it doesn't for some reason yAxis = self.chart.axes(Qt.Vertical)[0] yAxis.setRange(0, max(study_minutes)) def left(self): """Move to the left (older) plant.""" self.move(-1) def right(self): """Move to the right (newer) plant.""" self.move(1) def move(self, delta: int = 0): """Move to the left/right plant by delta. If no plant is currently being displayed or delta is 0, pick the latest one.""" studies = self.history.get_studies() # if there are no plants to display, don't do anything if len(studies) == 0: return # if no plant is being displayed or 0 is provided, pick the last one if self.plant is None or delta == 0: index = -1 # if one is, find it and move by delta else: current_index = self.history.get_studies().index(self.plant_study) index = max(min(current_index + delta, len(studies) - 1), 0) # TODO: check for correct formatting, don't just crash if it's wrong self.plant = pickle.loads(studies[index]["plant"]) self.plant_study = studies[index] # TODO: check for correct formatting, don't just crash if it's wrong self.plant_date_label.setText(self.plant_study["date"].strftime("%-d/%-m/%Y")) self.plant_duration_label.setText(f"{int(self.plant_study['duration'])} minutes") self.canvas.set_drawable(self.plant) self.slider_value_changed() # it didn't, but the code should act as if it did (update plant) def save(self): """Save the current state of the plant to a file.""" if self.plant is not None: name, _ = QFileDialog.getSaveFileName(self, 'Save File', "", "SVG files (*.svg)") if name == "": return if not name.endswith(".svg"): name += ".svg" self.plant.save(name, 1000, 1000)
class QmyMainWindow(QMainWindow): def __init__(self, parent=None): super().__init__(parent) #调用父类构造函数,创建窗体 self.ui=Ui_MainWindow() #创建UI对象 self.ui.setupUi(self) #构造UI界面 self.setWindowTitle("Demo12_2, QChart绘制折线图") self.setCentralWidget(self.ui.splitter) self.__chart=None #图表 self.__curSeries=None #当前序列 self.__curAxis=None #当前坐标轴 self.__createChart() self.__prepareData() self.__updateFromChart() ## ==============自定义功能函数======================== def __createChart(self): self.__chart = QChart() self.__chart.setTitle("简单函数曲线") self.ui.chartView.setChart(self.__chart) self.ui.chartView.setRenderHint(QPainter.Antialiasing) series0 = QLineSeries() series0.setName("Sin曲线") series1 = QLineSeries() series1.setName("Cos曲线") self.__curSeries=series0 #当前序列 pen=QPen(Qt.red) pen.setStyle(Qt.DotLine) #SolidLine, DashLine, DotLine, DashDotLine pen.setWidth(2) series0.setPen(pen) #序列的线条设置 pen.setStyle(Qt.SolidLine) #SolidLine, DashLine, DotLine, DashDotLine pen.setColor(Qt.blue) series1.setPen(pen) #序列的线条设置 self.__chart.addSeries(series0) self.__chart.addSeries(series1) axisX = QValueAxis() self.__curAxis=axisX #当前坐标轴 axisX.setRange(0, 10) #设置坐标轴范围 axisX.setLabelFormat("%.1f") #标签格式 axisX.setTickCount(11) #主分隔个数 axisX.setMinorTickCount(4) axisX.setTitleText("time(secs)") #标题 axisX.setGridLineVisible(True) axisX.setMinorGridLineVisible(False) axisY = QValueAxis() axisY.setRange(-2, 2) axisY.setLabelFormat("%.2f") #标签格式 axisY.setTickCount(5) axisY.setMinorTickCount(4) axisY.setTitleText("value") axisY.setGridLineVisible(True) axisY.setMinorGridLineVisible(False) ## self.__chart.setAxisX(axisX, series0) #添加X坐标轴 ## self.__chart.setAxisX(axisX, series1) #添加X坐标轴 ## self.__chart.setAxisY(axisY, series0) #添加Y坐标轴 ## self.__chart.setAxisY(axisY, series1) #添加Y坐标轴 ##另一种实现设置坐标轴的方法 self.__chart.addAxis(axisX,Qt.AlignBottom) #坐标轴添加到图表,并指定方向 self.__chart.addAxis(axisY,Qt.AlignLeft) series0.attachAxis(axisX) #序列 series0 附加坐标轴 series0.attachAxis(axisY) series1.attachAxis(axisX) #序列 series1 附加坐标轴 series1.attachAxis(axisY) def __prepareData(self): ##为序列设置数据点 chart=self.ui.chartView.chart() #获取chartView中的QChart对象 series0=chart.series()[0] #获取第1个序列,QLineSeries series0.clear() series1=chart.series()[1] #获取第2个序列,QLineSeries series1.clear() t,y1,y2=0.0,0.0,0.0 intv=0.1 pointCount=100 for i in range(pointCount): rd=random.randint(-5,5) #随机数,-5~+5 y1=math.sin(t)+rd/50.0 series0.append(t,y1) #序列添加数据点 rd=random.randint(-5,5) #随机数,-5~+5 y2=math.cos(t)+rd/50.0 series1.append(t,y2) #序列添加数据点 t=t+intv def __updateFromChart(self): self.ui.editTitle.setText(self.__chart.title()) #图表标题 mg=self.__chart.margins() #边距, QMargins self.ui.spinMarginLeft.setValue(mg.left()) self.ui.spinMarginRight.setValue(mg.right()) self.ui.spinMarginTop.setValue(mg.top()) self.ui.spinMarginBottom.setValue(mg.bottom()) ## ==============event处理函数========================== ## ==========由connectSlotsByName()自动连接的槽函数============ ##========工具栏上的几个按钮的Actions============== @pyqtSlot() ## "刷新绘图" 工具栏按钮 def on_actDraw_triggered(self): self.__prepareData() ##=====ToolBox 第1组:==="图表设置" 分组里的功能================ ##=======1.1 标题======== @pyqtSlot() ##设置标题文字 def on_btnTitleSetText_clicked(self): text=self.ui.editTitle.text() self.__chart.setTitle(text) @pyqtSlot() ##设置标题文字颜色 def on_btnTitleColor_clicked(self): color=self.__chart.titleBrush().color() color=QColorDialog.getColor(color) if color.isValid(): self.__chart.setTitleBrush(QBrush(color)) @pyqtSlot() ##设置标题字体 def on_btnTitleFont_clicked(self): iniFont=self.__chart.titleFont() #QFont font,ok=QFontDialog.getFont(iniFont) if ok: self.__chart.setTitleFont(font) ##=======1.2 图例========== @pyqtSlot(bool) ##图例是否可见 def on_groupBox_Legend_clicked(self,checked): self.__chart.legend().setVisible(checked) @pyqtSlot() ##图例的位置, 上 def on_radioButton_clicked(self): self.__chart.legend().setAlignment(Qt.AlignTop) @pyqtSlot() ##图例的位置,下 def on_radioButton_2_clicked(self): self.__chart.legend().setAlignment(Qt.AlignBottom) @pyqtSlot() ##图例的位置,左 def on_radioButton_3_clicked(self): self.__chart.legend().setAlignment(Qt.AlignLeft) @pyqtSlot() ##图例的位置,右 def on_radioButton_4_clicked(self): self.__chart.legend().setAlignment(Qt.AlignRight) @pyqtSlot() ##图例的文字颜色 def on_btnLegendlabelColor_clicked(self): color=self.__chart.legend().labelColor() color=QColorDialog.getColor(color) if color.isValid(): self.__chart.legend().setLabelColor(color) @pyqtSlot() ##图例的字体 def on_btnLegendFont_clicked(self): iniFont=self.__chart.legend().font() font,ok=QFontDialog.getFont(iniFont) if ok: self.__chart.legend().setFont(font) ##=======1.3 边距======== @pyqtSlot() ##设置图表的4个边距 def on_btnSetMargin_clicked(self): mgs=QMargins() mgs.setLeft(self.ui.spinMarginLeft.value()) mgs.setRight(self.ui.spinMarginRight.value()) mgs.setTop(self.ui.spinMarginTop.value()) mgs.setBottom(self.ui.spinMarginBottom.value()) self.__chart.setMargins(mgs) ##=======1.4 动画效果======== @pyqtSlot(int) ##动画效果 def on_comboAnimation_currentIndexChanged(self,index): animation=QChart.AnimationOptions(index) self.__chart.setAnimationOptions(animation) @pyqtSlot(int) ##图表的主题 def on_comboTheme_currentIndexChanged(self,index): self.__chart.setTheme(QChart.ChartTheme(index)) ##=====ToolBox 第2组:==="曲线设置" 分组里的功能================ ##=======2.1 选择操作序列======== @pyqtSlot() ##获取当前数据序列,sin def on_radioSeries0_clicked(self): if self.ui.radioSeries0.isChecked(): self.__curSeries=self.__chart.series()[0] else: self.__curSeries=self.__chart.series()[1] ##获取序列的属性值,并显示到界面上 self.ui.editSeriesName.setText(self.__curSeries.name()) self.ui.groupBox_Series.setChecked(self.__curSeries.isVisible()) #序列是否显示 self.ui.chkBoxPointVisible.setChecked(self.__curSeries.pointsVisible()) #数据点是否显示 self.ui.chkkBoxUseOpenGL.setChecked(self.__curSeries.useOpenGL()) #使用openGL self.ui.sliderOpacity.setValue(self.__curSeries.opacity()*10) #透明度 visible=self.__curSeries.pointLabelsVisible() #数据点标签可见性 self.ui.groupBox_PointLabel.setChecked(visible) @pyqtSlot() ##获取当前数据序列,cos def on_radioSeries1_clicked(self): self.on_radioSeries0_clicked() ##======2.2 序列曲线 设置======== @pyqtSlot(bool) ##序列是否可见 def on_groupBox_Series_clicked(self,checked): self.__curSeries.setVisible(checked) @pyqtSlot() ##设置序列名称 def on_btnSeriesName_clicked(self): seriesName=self.ui.editSeriesName.text() self.__curSeries.setName(seriesName) if self.ui.radioSeries0.isChecked(): self.ui.radioSeries0.setText(seriesName) else: self.ui.radioSeries1.setText(seriesName) @pyqtSlot() ##序列的曲线颜色 def on_btnSeriesColor_clicked(self): color=self.__curSeries.color() color=QColorDialog.getColor(color) if color.isValid(): self.__curSeries.setColor(color) @pyqtSlot() ##序列曲线的Pen设置 def on_btnSeriesPen_clicked(self): iniPen=self.__curSeries.pen() pen,ok=QmyDialogPen.staticGetPen(iniPen) if ok: self.__curSeries.setPen(pen) @pyqtSlot(bool) ##序列的数据点是否可见,数据点形状是固定的 def on_chkBoxPointVisible_clicked(self,checked): self.__curSeries.setPointsVisible(checked) @pyqtSlot(bool) ##使用openGL加速后,不能设置线型,不能显示数据点 def on_chkkBoxUseOpenGL_clicked(self,checked): self.__curSeries.setUseOpenGL(checked) @pyqtSlot(int) ##序列的透明度 def on_sliderOpacity_sliderMoved(self,position): self.__curSeries.setOpacity(position/10.0) ##=======2.3 数据点标签 ======== @pyqtSlot(bool) ##数据点标签 groupBox def on_groupBox_PointLabel_clicked(self,checked): self.__curSeries.setPointLabelsVisible(checked) @pyqtSlot() ##序列数据点标签颜色 def on_btnSeriesLabColor_clicked(self): color=self.__curSeries.pointLabelsColor() color=QColorDialog.getColor(color) if color.isValid(): self.__curSeries.setPointLabelsColor(color) @pyqtSlot() ##序列数据点标签字体 def on_btnSeriesLabFont_clicked(self): font=self.__curSeries.pointLabelsFont() #QFont font,ok=QFontDialog.getFont(font) if ok: self.__curSeries.setPointLabelsFont(font) @pyqtSlot() ##序列数据点标签的显示格式 def on_radioSeriesLabFormat0_clicked(self): self.__curSeries.setPointLabelsFormat("@yPoint") @pyqtSlot() ##序列数据点标签的显示格式 def on_radioSeriesLabFormat1_clicked(self): self.__curSeries.setPointLabelsFormat("(@xPoint,@yPoint)") ##=====ToolBox 第3组:==="坐标轴设置" 分组里的功能================ ##=======3.1 选择操作的坐标轴对象======= @pyqtSlot() ##选择坐标轴X def on_radioAxisX_clicked(self): if (self.ui.radioAxisX.isChecked()): self.__curAxis=self.ui.chartView.chart().axisX() #QValueAxis else: self.__curAxis=self.ui.chartView.chart().axisY() ##获取坐标轴的各种属性,显示到界面上 self.ui.groupBox_Axis.setChecked(self.__curAxis.isVisible()) #坐标轴可见性 self.ui.chkBoxAxisReverse.setChecked(self.__curAxis.isReverse()) self.ui.spinAxisMin.setValue(self.__curAxis.min()) self.ui.spinAxisMax.setValue(self.__curAxis.max()) self.ui.editAxisTitle.setText(self.__curAxis.titleText()) #轴标题 self.ui.groupBox_AxisTitle.setChecked(self.__curAxis.isTitleVisible()) #轴标题可见 self.ui.editAxisLabelFormat.setText(self.__curAxis.labelFormat()) #标签格式 self.ui.groupBox_AxisLabel.setChecked(self.__curAxis.labelsVisible()) #标签可见 self.ui.groupBox_GridLine.setChecked(self.__curAxis.isGridLineVisible()) #网格线 self.ui.groupBox_Ticks.setChecked(self.__curAxis.isLineVisible()) #主刻度线 self.ui.spinTickCount.setValue(self.__curAxis.tickCount()) #主刻度个数 self.ui.spinMinorTickCount.setValue(self.__curAxis.minorTickCount()) #次刻度个数 self.ui.groupBox_MinorGrid.setChecked(self.__curAxis.isMinorGridLineVisible()) #次网格线可见 @pyqtSlot() ##选择坐标轴Y def on_radioAxisY_clicked(self): self.on_radioAxisX_clicked() ##======3.2 坐标轴可见性和范围======= @pyqtSlot(bool) ##坐标轴可见性 def on_groupBox_Axis_clicked(self,checked): self.__curAxis.setVisible(checked) @pyqtSlot(bool) ##坐标反向 def on_chkBoxAxisReverse_clicked(self,checked): self.__curAxis.setReverse(checked) @pyqtSlot() ##设置坐标范围 def on_btnSetAxisRange_clicked(self): minV=self.ui.spinAxisMin.value() maxV=self.ui.spinAxisMax.value() self.__curAxis.setRange(minV,maxV) ##======3.3 轴标题======= @pyqtSlot(bool) ##坐标轴标题可见性 def on_groupBox_AxisTitle_clicked(self,checked): self.__curAxis.setTitleVisible(checked) @pyqtSlot() ##设置轴标题 def on_btnAxisSetTitle_clicked(self): self.__curAxis.setTitleText(self.ui.editAxisTitle.text()) @pyqtSlot() ##设置轴标题的颜色 def on_btnAxisTitleColor_clicked(self): color=self.__curAxis.titleBrush().color() color=QColorDialog.getColor(color) if color.isValid(): self.__curAxis.setTitleBrush(QBrush(color)) @pyqtSlot() ##设置轴标题的字体 def on_btnAxisTitleFont_clicked(self): iniFont=self.__curAxis.titleFont() #QFont font,ok=QFontDialog.getFont(iniFont) if ok: self.__curAxis.setTitleFont(font) ##======3.4 轴刻度标签======= @pyqtSlot(bool) ##可见性 def on_groupBox_AxisLabel_clicked(self,checked): self.__curAxis.setLabelsVisible(checked) @pyqtSlot() ##设置标签格式 def on_btnAxisLabelFormat_clicked(self): strFormat=self.ui.editAxisLabelFormat.text() self.__curAxis.setLabelFormat(strFormat) @pyqtSlot() ##设置标签文字颜色 def on_btnAxisLabelColor_clicked(self): color=self.__curAxis.labelsColor() color=QColorDialog.getColor(color) if color.isValid(): self.__curAxis.setLabelsColor(color) @pyqtSlot() ##设置标签字体 def on_btnAxisLabelFont_clicked(self): iniFont=self.__curAxis.labelsFont() #QFont font,ok=QFontDialog.getFont(iniFont) if ok: self.__curAxis.setLabelsFont(font) ##======3.5 轴线和主刻度========= @pyqtSlot(bool) ##可见性 def on_groupBox_Ticks_clicked(self,checked): self.__curAxis.setLineVisible(checked) @pyqtSlot(int) ##主刻度个数 def on_spinTickCount_valueChanged(self,arg1): self.__curAxis.setTickCount(arg1) @pyqtSlot() ##设置线条Pen def on_btnAxisLinePen_clicked(self): iniPen=self.__curAxis.linePen() pen,ok=QmyDialogPen.staticGetPen(iniPen) if ok: self.__curAxis.setLinePen(pen) @pyqtSlot() ##设置线条颜色 def on_btnAxisLinePenColor_clicked(self): color=self.__curAxis.linePenColor() color=QColorDialog.getColor(color) if color.isValid(): self.__curAxis.setLinePenColor(color) ##======3.6 主网格线========= @pyqtSlot(bool) ##可见性 def on_groupBox_GridLine_clicked(self,checked): self.__curAxis.setGridLineVisible(checked) @pyqtSlot() ##设置线条Pen def on_btnGridLinePen_clicked(self): iniPen=self.__curAxis.gridLinePen() pen,ok=QmyDialogPen.staticGetPen(iniPen) if ok: self.__curAxis.setGridLinePen(pen) @pyqtSlot() ##设置线条颜色 def on_btnGridLineColor_clicked(self): color=self.__curAxis.gridLineColor() color=QColorDialog.getColor(color) if color.isValid(): self.__curAxis.setGridLineColor(color) ##======3.7 次网格线======= @pyqtSlot(bool) ##可见性 def on_groupBox_MinorGrid_clicked(self,checked): self.__curAxis.setMinorGridLineVisible(checked) @pyqtSlot(int) ##次刻度个数 def on_spinMinorTickCount_valueChanged(self,arg1): self.__curAxis.setMinorTickCount(arg1) @pyqtSlot() ##设置线条Pen def on_btnMinorPen_clicked(self): iniPen=self.__curAxis.minorGridLinePen() pen,ok=QmyDialogPen.staticGetPen(iniPen) #使用类函数调用方法 if ok: self.__curAxis.setMinorGridLinePen(pen) @pyqtSlot() ##设置线条颜色 def on_btnMinorColor_clicked(self): color=self.__curAxis.minorGridLineColor() color=QColorDialog.getColor(color) if color.isValid(): self.__curAxis.setMinorGridLineColor(color)
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))
def bars_stacked(name, table_df, x_bottom_cols, y_left_cols, y_right_cols, legend_labels, tick_count): """ 柱形堆叠图 :param name: 图表名称 :param table_df: 用于画图的pandas DataFrame对象 :param x_bottom_cols: 下轴列索引列表 :param y_left_cols: 左轴列索引列表 :param y_right_cols: 右轴列索引列表 :param legend_labels: 图例名称标签列表 :param tick_count: 横轴刻度标签数 :return: QChart实例 """ """ 过滤轴 """ for y_left_col in y_left_cols: if is_datetime64_any_dtype(table_df[y_left_col]): # 如果是时间轴 y_left_cols.remove(y_left_col) for y_right_col in y_right_cols: if is_datetime64_any_dtype(table_df[y_right_col]): # 如果是时间轴 y_right_cols.remove(y_right_col) # 将x轴转为字符串 x_bottom = x_bottom_cols[0] # x轴列 if is_datetime64_any_dtype(table_df[x_bottom]): # 如果x轴是时间轴 table_df[x_bottom] = table_df[x_bottom].apply( lambda x: x.strftime('%Y-%m-%d')) else: # x轴非时间轴 table_df[x_bottom] = table_df[x_bottom].apply(lambda x: str(x)) font = QFont() # 轴文字风格 font.setPointSize(7) # 轴标签文字大小 chart = QChart() # 图表实例 chart.layout().setContentsMargins(0, 0, 0, 0) # chart的外边距 chart.setMargins(QMargins(15, 5, 15, 0)) # chart内边距 chart.setTitle(name) # chart名称 """ x轴 """ axis_x_bottom = QCategoryAxis( chart, labelsPosition=QCategoryAxis.AxisLabelsPositionOnValue) axis_x_bottom.setLabelsAngle(-90) # 逆时针旋转90度 axis_x_bottom.setLabelsFont(font) # 设置字体样式 axis_x_bottom.setGridLineVisible(False) # 竖向连接线不可见 # chart.addAxis(axis_x_bottom, Qt.AlignBottom) # 加入坐标x轴 has_x_labels = False # 收集x轴刻度的开关 chart.x_labels = list() # 绑定chart一个x轴的刻度列表 """ 左Y轴 """ axis_y_left = QValueAxis() axis_y_left.setLabelsFont(font) axis_y_left.setLabelFormat('%i') chart.addAxis(axis_y_left, Qt.AlignLeft) # 图表加入左轴 """ 右Y轴 """ axis_y_right = QValueAxis() axis_y_right.setLabelsFont(font) axis_y_right.setLabelFormat('%.2f') chart.addAxis(axis_y_right, Qt.AlignRight) # 记录各轴的最值 x_bottom_min, x_bottom_max = 0, table_df.shape[0] y_left_min, y_left_max = 0, 0 y_right_min, y_right_max = 0, 0 # 柱形图 left_bars = QBarSeries() """ 根据左轴画柱形 """ for y_left_col in y_left_cols: # 根据左轴画折线 # 计算做轴的最值用于设置范围 table_df[y_left_col] = table_df[y_left_col].apply( covert_float) # y轴列转为浮点数值 # 获取最值 y_min, y_max = table_df[y_left_col].min(), table_df[y_left_col].max() if y_min < y_left_min: y_left_min = y_min if y_max > y_left_max: y_left_max = y_max # 取得图表的源数据作柱形 left_bar_data = table_df.iloc[:, [x_bottom, y_left_col]] bar = QBarSet(legend_labels[y_left_col]) bar.setPen(QPen(Qt.transparent)) # 设置画笔轮廓线透明(数据量大会出现空白遮住柱形) for index, point_item in enumerate(left_bar_data.values.tolist()): bar.append(point_item[1]) if not has_x_labels: chart.x_labels.append(point_item[0]) has_x_labels = True # 关闭添加轴标签 left_bars.append(bar) # 柱形加入系列 left_bars.attachAxis(axis_y_left) # 左轴的范围 axis_y_left.setRange(y_left_min, y_left_max) """ 根据右轴画柱形 """ right_bars = QBarSeries() for y_right_col in y_right_cols: # 根据右轴画柱形 # 计算做轴的最值用于设置范围 table_df[y_right_col] = table_df[y_right_col].apply( covert_float) # y轴列转为浮点数值 # 获取最值 y_min, y_max = table_df[y_right_col].min(), table_df[y_right_col].max() if y_min < y_right_min: y_right_min = y_min if y_max > y_right_max: y_right_max = y_max # 取得图线的源数据作折线图 right_bar_data = table_df.iloc[:, [x_bottom, y_right_col]] bar = QBarSet(legend_labels[y_right_col]) bar.setPen(QPen(Qt.transparent)) for position_index, point_item in enumerate( right_bar_data.values.tolist()): bar.append(point_item[1]) # 取出源数据后一条线就2列数据 right_bars.append(bar) right_bars.attachAxis(axis_x_bottom) right_bars.attachAxis(axis_y_right) # 右轴范围 axis_y_right.setRange(y_right_min, y_right_max) chart.addSeries(left_bars) # 左轴的柱形图加入图表 # print(right_bars.count()) if right_bars.count() != 0: # 为空时加入会导致空位 chart.addSeries(right_bars) chart.setAxisX(axis_x_bottom, left_bars) # 关联设置x轴 # 横轴标签设置 x_bottom_interval = int(x_bottom_max / (tick_count - 1)) if x_bottom_interval == 0: for i in range(0, x_bottom_max): axis_x_bottom.append(chart.x_labels[i], i) else: for i in range(0, x_bottom_max, x_bottom_interval): axis_x_bottom.append(chart.x_labels[i], i) chart.legend().setAlignment(Qt.AlignBottom) return chart
def lines_stacked(name, table_df, x_bottom_cols, y_left_cols, y_right_cols, legend_labels, tick_count): """ 折线堆叠图 :param name: 图表名称 :param table_df: 用于画图的pandas DataFrame对象 :param x_bottom_cols: 下轴列索引列表 :param y_left_cols: 左轴列索引列表 :param y_right_cols: 右轴列索引列表 :param legend_labels: 图例名称标签列表 :param tick_count: 横轴刻度标签数 :return: QChart实例 """ """ 过滤轴 """ for y_left_col in y_left_cols: if is_datetime64_any_dtype(table_df[y_left_col]): # 如果是时间轴 y_left_cols.remove(y_left_col) for y_right_col in y_right_cols: if is_datetime64_any_dtype(table_df[y_right_col]): # 如果是时间轴 y_right_cols.remove(y_right_col) # 将x轴转为字符串 x_bottom = x_bottom_cols[0] # x轴列 if is_datetime64_any_dtype(table_df[x_bottom]): # 如果x轴是时间轴 table_df[x_bottom] = table_df[x_bottom].apply( lambda x: x.strftime('%Y-%m-%d')) else: # x轴非时间轴 table_df[x_bottom] = table_df[x_bottom].apply(lambda x: str(x)) font = QFont() # 轴文字风格 font.setPointSize(7) # 轴标签文字大小 chart = QChart() # 图表实例 chart.layout().setContentsMargins(0, 0, 0, 0) # chart的外边距 chart.setMargins(QMargins(15, 5, 15, 0)) # chart内边距 chart.setTitle(name) # chart名称 """ x轴 """ axis_x_bottom = QCategoryAxis( chart, labelsPosition=QCategoryAxis.AxisLabelsPositionOnValue) axis_x_bottom.setLabelsAngle(-90) # 逆时针旋转90度 axis_x_bottom.setLabelsFont(font) # 设置字体样式 axis_x_bottom.setGridLineVisible(False) # 竖向连接线不可见 chart.addAxis(axis_x_bottom, Qt.AlignBottom) # 加入坐标x轴 has_x_labels = False # 收集x轴刻度的开关 chart.x_labels = list() # 绑定chart一个x轴的刻度列表 """ 左Y轴 """ axis_y_left = QValueAxis() axis_y_left.setLabelsFont(font) axis_y_left.setLabelFormat('%i') chart.addAxis(axis_y_left, Qt.AlignLeft) # 图表加入左轴 """ 右Y轴 """ axis_y_right = QValueAxis() axis_y_right.setLabelsFont(font) axis_y_right.setLabelFormat('%.2f') chart.addAxis(axis_y_right, Qt.AlignRight) # 记录各轴的最值 x_bottom_min, x_bottom_max = 0, table_df.shape[0] y_left_min, y_left_max = 0, 0 y_right_min, y_right_max = 0, 0 """ 根据左轴画折线 """ for y_left_col in y_left_cols: # 根据左轴画折线 # 计算做轴的最值用于设置范围 table_df[y_left_col] = table_df[y_left_col].apply( covert_float) # y轴列转为浮点数值 # 获取最值 y_min, y_max = table_df[y_left_col].min(), table_df[y_left_col].max() if y_min < y_left_min: y_left_min = y_min if y_max > y_left_max: y_left_max = y_max # 取得图线的源数据作折线图 left_line_data = table_df.iloc[:, [x_bottom, y_left_col]] series = QLineSeries() series.setName(legend_labels[y_left_col]) for position_index, point_item in enumerate( left_line_data.values.tolist()): series.append(position_index, point_item[1]) # 取出源数据后一条线就2列数据 # 收集坐标标签 if not has_x_labels: chart.x_labels.append(point_item[0]) chart.addSeries(series) # 折线入图 series.attachAxis(axis_x_bottom) series.attachAxis(axis_y_left) # 左轴范围 axis_y_left.setRange(y_left_min, y_left_max) """ 根据右轴画折线 """ for y_right_col in y_right_cols: # 根据右轴画折线 # 计算做轴的最值用于设置范围 table_df[y_right_col] = table_df[y_right_col].apply( covert_float) # y轴列转为浮点数值 # 获取最值 y_min, y_max = table_df[y_right_col].min(), table_df[y_right_col].max() if y_min < y_right_min: y_right_min = y_min if y_max > y_right_max: y_right_max = y_max # 取得图线的源数据作折线图 left_line_data = table_df.iloc[:, [x_bottom, y_right_col]] series = QLineSeries() series.setName(legend_labels[y_right_col]) for position_index, point_item in enumerate( left_line_data.values.tolist()): series.append(position_index, point_item[1]) # 取出源数据后一条线就2列数据 chart.addSeries(series) series.attachAxis(axis_x_bottom) series.attachAxis(axis_y_right) # 右轴范围 axis_y_right.setRange(y_right_min, y_right_max) # 设置下轴刻度标签 # print('x轴最大值', x_bottom_max) x_bottom_interval = int(x_bottom_max / (tick_count - 1)) if x_bottom_interval == 0: for i in range(0, x_bottom_max): axis_x_bottom.append(chart.x_labels[i], i) else: for i in range(0, x_bottom_max, x_bottom_interval): axis_x_bottom.append(chart.x_labels[i], i) chart.legend().setAlignment(Qt.AlignBottom) return chart
class MainWindow(QMainWindow, Ui_MainWindow): """ Main application window """ WIDTH = 150 HEIGHT = 50 MARGIN = 5 max_ping = 0 max_loss = 0 def __init__(self, host, history, history_size, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.host = host self.history = history self.history_size = history_size self.setupUi(self) self.setWindowFlag(Qt.WindowStaysOnTopHint) self.position_to_dock() self.series_delay = QLineSeries() self.series_loss = QLineSeries() self.axis_X = QDateTimeAxis() # pylint: disable=invalid-name self.axis_X.setTickCount(3) self.axis_X.setFormat("HH:mm") self.axis_X.setTitleText("Time") self.chart = QChart() self.chart.addSeries(self.series_delay) self.chart.addSeries(self.series_loss) self.chart.setTitle(f"Connection to {self.host}") self.init_series(self.series_delay, "Delay ms") self.init_series(self.series_loss, "Loss %") self.chart.legend().setVisible(False) self.chart.legend().setAlignment(Qt.AlignBottom) self.chart.layout().setContentsMargins(0, 0, 0, 0) self.chart.setBackgroundRoundness(0) self.chart.setMargins(QMargins(0, 0, 0, 0)) self.chartWidget.setChart(self.chart) self.chartWidget.setRenderHint(QPainter.Antialiasing) def init_series(self, series, label): """ Series settings """ self.chart.setAxisX(self.axis_X, series) axis_Y = QValueAxis() # pylint: disable=invalid-name axis_Y.setLabelFormat("%i") axis_Y.setTitleText(label) axis_Y.setRange(0, 100) self.chart.addAxis(axis_Y, Qt.AlignLeft) self.chart.setAxisY(axis_Y, series) def add_series(self, ping, loss): """ Append series data """ self.max_ping = max(ping or 0, self.max_ping) self.max_loss = max(loss, self.max_loss) if self.series_delay.count() > self.history_size: self.series_delay.remove(0) self.series_delay.append( QDateTime.currentDateTime().toMSecsSinceEpoch(), ping or 0) if self.series_loss.count() > self.history_size: self.series_loss.remove(0) self.series_loss.append( QDateTime.currentDateTime().toMSecsSinceEpoch(), loss) self.axis_X.setRange( QDateTime.currentDateTime().addSecs(-self.history), QDateTime.currentDateTime()) self.chart.axisY().setRange(0, self.max_ping + self.MARGIN) def position_to_dock(self): """ Adjust main window position according to it's size and desktop """ desktop_geometry = QDesktopWidget().availableGeometry() self.setGeometry( desktop_geometry.width() - self.width() - self.MARGIN, desktop_geometry.height() - self.height() - self.MARGIN, self.width(), self.height()) def set_labels(self, mean_=None, curr=None, loss=None): """ Update window text """ mean_text = curr_text = loss_text = "No connection" if mean_ is not None: mean_text = f"Mean ping: {mean_}ms" if curr is not None: curr_text = f"Last ping: {curr}ms" if loss is not None: loss_text = f"Ping loss: {loss}%" self.meanLabel.setText(mean_text) self.currLabel.setText(curr_text) self.lossLabel.setText(loss_text) def toggle(self, reason): """ Toggle window visibility """ if reason == QSystemTrayIcon.ActivationReason.DoubleClick: # pylint: disable=no-member self.setVisible(not self.isVisible()) if self.isVisible(): self.activateWindow()