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 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 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 _fill_chart(self, items: List[Run]): series = QLineSeries() series.setPointsVisible(True) series.setPointLabelsVisible(True) series.setPointLabelsFormat("@yPoint") series.hovered.connect(self.chart_view.show_series_tooltip) self.timestamp_by_run.clear() for run in items: date_value = calendar.timegm(run.date.timetuple()) * 1000 total_issues = run.get_total_issues() series.append(date_value, total_issues) self.timestamp_by_run[date_value] = run chart = QChart() chart.setTheme(QChart.ChartThemeDark) chart.setAnimationOptions(QChart.SeriesAnimations) chart.addSeries(series) chart.legend().hide() # No margin chart.layout().setContentsMargins(0, 0, 0, 0) chart.setBackgroundRoundness(0) axisX = QDateTimeAxis() axisX.setFormat("dd/MM/yyyy") axisX.setTitleText('Date') chart.addAxis(axisX, Qt.AlignBottom) series.attachAxis(axisX) axisY = QValueAxis() axisY.setLabelFormat('%d') axisY.setTitleText('Total issues') chart.addAxis(axisY, Qt.AlignLeft) series.attachAxis(axisY) self.chart_view.clear_all_tooltips() self.chart_view.setChart(chart)
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 Ui(mainwindow.Ui_MainWindow): def __init__(self, MainWindow): #super(Ui, self).__init__(M) super(Ui, self).setupUi(MainWindow) #uic.loadUi('UI/mainwindow.ui', self) self.MainWindow = MainWindow self.initialize() def close(self): self.MainWindow.close() def style(self): return self.MainWindow.style() def initialize(self): self.tabWidget.setCurrentIndex(0) self.actionExit.triggered.connect(self.close) self.action_Plot.setEnabled(False) self.actionNext.setIcon( self.style().standardIcon( QtWidgets.QStyle.SP_ArrowForward)) self.actionPrevious.setIcon( self.style().standardIcon( QtWidgets.QStyle.SP_ArrowBack)) self.action_Open.setIcon( self.style().standardIcon( QtWidgets.QStyle.SP_DialogOpenButton)) self.actionSave.setIcon( self.style().standardIcon( QtWidgets.QStyle.SP_DriveFDIcon)) self.actionSave.triggered.connect(self.save) self.action_Open.triggered.connect(self.getOpenFilename) self.actionNext.triggered.connect(self.nextPacket) self.actionPrevious.triggered.connect(self.previousPacket) self.actionAbout.triggered.connect(self.about) self.actionPrevious.setEnabled(False) self.actionNext.setEnabled(False) self.actionSave.setEnabled(False) self.action_Plot.setEnabled(False) self.actionPaste.triggered.connect(self.onPasteTriggered) # self.actionLog.triggered.connect(self.dockWidget_2.show) self.actionSet_IDB.triggered.connect(self.onSetIDBClicked) self.plotButton.clicked.connect(self.onPlotButtonClicked) self.exportButton.clicked.connect(self.onExportButtonClicked) self.action_Plot.triggered.connect(self.onPlotActionClicked) self.actionLoad_mongodb.triggered.connect(self.onLoadMongoDBTriggered) self.mdb=None self.current_row = 0 self.data=[] self.x=[] self.y=[] self.xlabel='x' self.ylabel='y' self.chart = QChart() self.chart.layout().setContentsMargins(0,0,0,0) self.chart.setBackgroundRoundness(0) self.savePlotButton.clicked.connect(self.savePlot) self.chartView = QChartView(self.chart) self.gridLayout.addWidget(self.chartView, 1, 0, 1, 15) # IDB location self.settings = QtCore.QSettings('FHNW', 'stix_parser') def onExportButtonClicked(self): if self.y: filename = str(QtWidgets.QFileDialog.getSaveFileName( None, "Save file", "", "*.csv")[0]) if filename: with open(filename,'w') as f: f.write('{},{}\n'.format(self.xlabel,self.ylabel)) for xx,yy in zip(self.x,self.y): f.write('{},{}\n'.format(xx,yy)) self.showMessage('The data has been written to {}'.format(filename)) else: msgBox = QtWidgets.QMessageBox() msgBox.setIcon(QtWidgets.QMessageBox.Information) msgBox.setText('Plot first!') msgBox.setWindowTitle("Warning") msgBox.setStandardButtons(QtWidgets.QMessageBox.Ok) msgBox.exec_() def savePlot(self): #if self.figure.get_axes(): if self.chart: filename = str(QtWidgets.QFileDialog.getSaveFileName( None, "Save file", "", "*.png *.jpg")[0]) if filename: if not filename.endswith(('.png','.jpg')): filename+='.png' #self.figure.savefig(filename) p=self.chartView.grab() p.save(filename) self.showMessage(('Saved to %s.' % filename)) else: msgBox = QtWidgets.QMessageBox() msgBox.setIcon(QtWidgets.QMessageBox.Information) msgBox.setText('The canvas is empty!') msgBox.setWindowTitle("STIX DATA VIEWER") msgBox.setStandardButtons(QtWidgets.QMessageBox.Ok) msgBox.exec_() def onPasteTriggered(self): pass def showMessageBox(self, message, content): msg = QtWidgets.QMessageBox() msg.setIcon(QtWidgets.QMessageBox.Critical) msg.setText("Error") msg.setInformativeText(message) msg.setWindowTitle("Error") msg.setDetailedText(content) msg.setStandardButtons(QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) retval = msg.exec_() def showMessage(self, msg): # if destination != 1: self.statusbar.showMessage(msg) # if destination !=0 : # self.listWidget_2.addItem(msg) def onSetIDBClicked(self): pass def save(self): pass def setListViewSelected(self, row): #index = self.model.createIndex(row, 0); # if index.isValid(): # self.model.selectionModel().select( index, QtGui.QItemSelectionModel.Select) pass def about(self): msgBox = QtWidgets.QMessageBox() msgBox.setIcon(QtWidgets.QMessageBox.Information) msgBox.setText("STIX raw data parser and viewer, [email protected]") msgBox.setWindowTitle("Stix data viewer") msgBox.setStandardButtons(QtWidgets.QMessageBox.Ok) msgBox.exec_() def nextPacket(self): self.current_row += 1 length=len(self.data) if self.current_row>=length: self.current_row=length-1 self.showMessage('No more packet!') self.showPacket(self.current_row) self.setListViewSelected(self.current_row) def previousPacket(self): self.current_row -= 1 if self.current_row <0: self.current_row=0 self.showMessage('Reach the first packet!') self.showPacket(self.current_row) self.setListViewSelected(self.current_row) def getOpenFilename(self): pass def openFile(self, filename): pass def onDataLoaded(self, data,clear=True): if not clear: self.data.append(data) else: self.data = data self.displayPackets(clear) if self.data: self.actionPrevious.setEnabled(True) self.actionNext.setEnabled(True) self.actionSave.setEnabled(True) self.action_Plot.setEnabled(True) def displayPackets(self,clear=True): if clear: self.packetTreeWidget.clear() t0=0 for p in self.data: if type(p) is not dict: continue header = p['header'] root = QtWidgets.QTreeWidgetItem(self.packetTreeWidget) if t0==0: t0=header['time'] root.setText(0, '{:.2f}'.format(header['time']-t0)) root.setText(1, ('TM({},{}) - {}').format(header['service_type'], header['service_subtype'], header['DESCR'])) self.total_packets = len(self.data) self.showMessage((('%d packets loaded') % (self.total_packets))) self.packetTreeWidget.currentItemChanged.connect(self.onPacketSelected) self.showPacket(0) def onLoadMongoDBTriggered(self): diag=QtWidgets.QDialog() diag_ui=mongo_dialog.Ui_Dialog() diag_ui.setupUi(diag) diag_ui.pushButton.setFocus(True) #self.settings = QtCore.QSettings('FHNW', 'stix_parser') self.mongo_server= self.settings.value('mongo_server', [], str) self.mongo_port= self.settings.value('mongo_port', [], str) self.mongo_user= self.settings.value('mongo_user', [], str) self.mongo_pwd= self.settings.value('mongo_pwd', [], str) if self.mongo_server: diag_ui.serverLineEdit.setText(self.mongo_server) if self.mongo_port: diag_ui.portLineEdit.setText(self.mongo_port) if self.mongo_user: diag_ui.userLineEdit.setText(self.mongo_user) if self.mongo_pwd: diag_ui.pwdLineEdit.setText(self.mongo_pwd) diag_ui.pushButton.clicked.connect(partial(self.loadRunsFromMongoDB,diag_ui)) diag_ui.buttonBox.accepted.connect(partial(self.loadDataFromMongoDB,diag_ui,diag)) diag.exec_() def loadRunsFromMongoDB(self,dui): server=dui.serverLineEdit.text() port=dui.portLineEdit.text() user=dui.userLineEdit.text() pwd=dui.pwdLineEdit.text() self.showMessage('saving setting...') if self.mongo_server!=server: self.settings.setValue('mongo_server', server) if self.mongo_port!=port: self.settings.setValue('mongo_port', port) if self.mongo_user!=user: self.settings.setValue('mongo_user', user) if self.mongo_pwd!=pwd: self.settings.setValue('mongo_pwd', pwd) self.showMessage('connecting Mongo database ...') self.mdb=mgdb.MongoDB(server,int(port),user,pwd) dui.treeWidget.clear() self.showMessage('Fetching data...') for run in self.mdb.get_runs(): root = QtWidgets.QTreeWidgetItem(dui.treeWidget) root.setText(0, str(run['_id'])) root.setText(1, run['file']) root.setText(2, run['date']) root.setText(3, str(run['start'])) root.setText(4, str(run['end'])) self.showMessage('Runs loaded!') def loadDataFromMongoDB(self,dui,diag): selected_runs=[] for item in dui.treeWidget.selectedItems(): selected_runs.append(item.text(0)) if not selected_runs: self.showMessage('Run not selected!') if selected_runs: diag.done(0) self.showMessage('Loading data ...!') data=self.mdb.get_packets(selected_runs[0]) if data: self.onDataLoaded(data,clear=True) else: self.showMessage('No packets found!') #close def onPacketSelected(self, cur, pre): self.current_row = self.packetTreeWidget.currentIndex().row() self.showMessage((('Packet #%d selected') % self.current_row)) self.showPacket(self.current_row) def showPacket(self, row): if not self.data: return header = self.data[row]['header'] self.showMessage( (('Packet %d / %d %s ') % (row, self.total_packets, header['DESCR']))) self.paramTreeWidget.clear() header_root = QtWidgets.QTreeWidgetItem(self.paramTreeWidget) header_root.setText(0, "Header") rows = len(header) for key, val in header.items(): root = QtWidgets.QTreeWidgetItem(header_root) root.setText(0, key) root.setText(1, str(val)) params = self.data[row]['parameters'] param_root = QtWidgets.QTreeWidgetItem(self.paramTreeWidget) param_root.setText(0, "Parameters") self.showParameterTree(params, param_root) self.paramTreeWidget.expandItem(param_root) self.paramTreeWidget.expandItem(header_root) def showParameterTree(self, params, parent): for p in params: root = QtWidgets.QTreeWidgetItem(parent) if not p: continue try: param_name=p['name'] desc='' scos_desc='' try: desc=param_desc.PCF[param_name] scos_desc=param_desc.SW[param_name] except KeyError: pass root.setToolTip(1,scos_desc) root.setText(0, param_name) root.setText(1, desc) root.setText(2, str(p['raw'])) root.setText(3, str(p['value'])) if 'child' in p: if p['child']: self.showParameterTree(p['child'], root) except KeyError: self.showMessage( ('[Error ]: keyError occurred when adding parameter')) self.paramTreeWidget.itemDoubleClicked.connect(self.onTreeItemClicked) def walk(self, name, params, header, ret_x, ret_y, xaxis=0, data_type=0): if not params: return timestamp = header['time'] #parameters=[p for p in params if p['name'] == name] for p in params: if type(p) is not dict: continue #for p in parameters: if name == p['name']: values = None #print('data type:{}'.format(data_type)) if data_type == 0: values = p['raw'] else: values = p['value'] try: yvalue = None if (type(values) is tuple) or (type(values) is list): yvalue = float(values[0]) else: yvalue = float(values) ret_y.append(yvalue) if xaxis == 1: ret_x.append(timestamp) else: self.showMessage((('Can not plot %s ') % str(yvalue))) except Exception as e: self.showMessage((('%s ') % str(e))) if 'child' in p: if p['child']: self.walk( name, p['child'], header, ret_x, ret_y, xaxis, data_type) def onPlotButtonClicked(self): if self.chart: self.chart.removeAllSeries() if not self.data: return self.showMessage('Preparing plot ...') name = self.paramNameEdit.text() packet_selection = self.comboBox.currentIndex() xaxis_type = self.xaxisComboBox.currentIndex() data_type = self.dataTypeComboBox.currentIndex() timestamp = [] self.y = [] packet_id = self.current_row params = self.data[packet_id]['parameters'] header = self.data[packet_id]['header'] current_spid=header['SPID'] if packet_selection == 0: self.walk( name, params, header, timestamp, self.y, xaxis_type, data_type) elif packet_selection == 1: for packet in self.data: header = packet['header'] if packet['header']['SPID'] != current_spid: continue #only look for parameters in the packets of the same type params = packet['parameters'] self.walk( name, params, header, timestamp, self.y, xaxis_type, data_type) self.x = [] if not self.y: self.showMessage('No data points') elif self.y: style = self.styleEdit.text() if not style: style = '-' title = '%s' % str(name) desc = self.descLabel.text() if desc: title += '- %s' % desc self.chart.setTitle(title) ylabel = 'Raw value' xlabel = name if data_type == 1: ylabel = 'Engineering value' if xaxis_type == 0: xlabel = "Packet #" self.x = range(0, len(self.y)) if xaxis_type == 1: self.x = [t - timestamp[0] for t in timestamp] xlabel = 'Time -T0 (s)' #if xaxis_type != 2: if True: series = QLineSeries() series2 = None # print(y) # print(x) for xx, yy in zip(self.x, self.y): series.append(xx, yy) if 'o' in style: series2 = QScatterSeries() for xx, yy in zip(self.x, self.y): series2.append(xx, yy) self.chart.addSeries(series2) self.chart.addSeries(series) self.showMessage('plotted!') #self.chart.createDefaultAxes() axisX = QValueAxis() axisX.setTitleText(xlabel) axisY = QValueAxis() axisY.setTitleText(ylabel) self.chart.setAxisX(axisX) self.chart.setAxisY(axisY) series.attachAxis(axisX) series.attachAxis(axisY) # histogram #else: # nbins = len(set(self.y)) # ycounts, xedges = np.histogram(self.y, bins=nbins) # series = QLineSeries() # for i in range(0, nbins): # meanx = (xedges[i] + xedges[i + 1]) / 2. # series.append(meanx, ycounts[i]) # # series.append(dataset) # self.chart.addSeries(series) # #self.chart.createDefaultAxes() # self.showMessage('Histogram plotted!') # axisX = QValueAxis() # axisX.setTitleText(name) # axisY = QValueAxis() # axisY.setTitleText("Counts") # self.chart.setAxisY(axisY) # self.chart.setAxisX(axisX) ## series.attachAxis(axisX) # series.attachAxis(axisY) # self.widget.setChart(self.chart) self.xlabel=xlabel self.ylabel=ylabel self.chartView.setRubberBand(QChartView.RectangleRubberBand) self.chartView.setRenderHint(QtGui.QPainter.Antialiasing) def plotParameter(self, name=None, desc=None): self.tabWidget.setCurrentIndex(1) if name: self.paramNameEdit.setText(name) if desc: self.descLabel.setText(desc) def onPlotActionClicked(self): self.tabWidget.setCurrentIndex(1) self.plotParameter() def onTreeItemClicked(self, it, col): #print(it, col, it.text(0)) self.plotParameter(it.text(0), it.text(1)) def error(self, msg, description=''): self.showMessage((('Error: %s - %s') % (msg, description))) def warning(self, msg, description=''): self.showMessage((('Warning: %s - %s') % (msg, description))) def info(self, msg, description=''): self.showMessage((('Info: %s - %s') % (msg, description)))
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))
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()
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 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 Ui(mainwindow.Ui_MainWindow): def __init__(self, MainWindow): super(Ui, self).setupUi(MainWindow) self.MainWindow = MainWindow self.socketPacketReceiver = None self.timmer_is_on = False self.hexParser = StixHexStringParser() self.socketPacketReceiver = StixSocketPacketReceiver() self.socketPacketServer = StixSocketPacketServer() self.dataReader = StixFileReader() slots = { 'info': self.onDataReaderInfo, 'warning': self.onDataReaderWarning, 'error': self.onDataReaderError, 'critical': self.onDataReaderCritical, 'dataLoaded': self.onDataReady, 'packetArrival': self.onPacketArrival, 'progress': self.onProgressUpdated } self.socketPacketReceiver.connectSignalSlots(slots) self.dataReader.connectSignalSlots(slots) self.hexParser.connectSignalSlots(slots) self.socketPacketServer.connectSignalSlots(slots) self.tabWidget.setCurrentIndex(0) self.actionExit.triggered.connect(self.close) self.actionPlot.setEnabled(False) self.actionNext.setIcon(self.style().standardIcon( QtWidgets.QStyle.SP_ArrowForward)) self.actionPrevious.setIcon(self.style().standardIcon( QtWidgets.QStyle.SP_ArrowBack)) self.actionOpen.setIcon(self.style().standardIcon( QtWidgets.QStyle.SP_DialogOpenButton)) self.actionSave.setIcon(self.style().standardIcon( QtWidgets.QStyle.SP_DriveFDIcon)) self.actionSave.triggered.connect(self.save) self.actionOpen.triggered.connect(self.getOpenFilename) self.filterPattern = None self.actionNext.triggered.connect(self.nextPacket) self.actionPrevious.triggered.connect(self.previousPacket) self.actionAbout.triggered.connect(self.about) self.actionPrevious.setEnabled(False) self.actionNext.setEnabled(False) self.actionSave.setEnabled(False) self.actionPlot.setEnabled(False) self.actionCopy.triggered.connect(self.onCopyTriggered) self.packetTreeWidget.currentItemChanged.connect(self.onPacketSelected) self.actionCopy.setEnabled(False) self.actionPaste.triggered.connect(self.onPasteTriggered) self.actionLog.triggered.connect(self.dockWidget.show) self.actionSetIDB.triggered.connect(self.onSetIDBClicked) self.plotButton.clicked.connect( partial(self.onPlotButtonClicked, None)) #self.progressBar = QtWidgets.QProgressBar() #self.statusbar.addPermanentWidget(self.progressBar) self.progressDiag = None #self.progressBar.hide() self.actionPacketServer.triggered.connect(self.startPacketServer) self.exportButton.clicked.connect(self.onExportButtonClicked) self.actionPlot.triggered.connect(self.onPlotActionClicked) self.actionLoadMongodb.triggered.connect(self.onLoadMongoDBTriggered) self.actionConnectTSC.triggered.connect(self.onConnectTSCTriggered) self.actionPacketFilter.triggered.connect(self.filter) self.actionPlugins.triggered.connect(self.onPluginTriggered) self.actionOnlineHelp.triggered.connect(self.onOnlineHelpTriggered) self.actionViewBinary.triggered.connect(self.onViewBinaryTriggered) self.actionTimestampConvertor.triggered.connect(self.onTimestampConvertorTriggered) #self.actionPythonConsole.triggered.connect(self.startPythonConsole) self.autoUpdateButton.clicked.connect( self.onPlotAutoUpdateButtonClicked) self.packetTreeWidget.customContextMenuRequested.connect( self.packetTreeContextMenuEvent) #self.statusListWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) #self.statusListWidget.customContextMenuRequested.connect(self.statusListContextMenuEvent) self.mdb = None self.current_row = 0 self.data = [] self.x = [] self.y = [] self.xlabel = 'x' self.ylabel = 'y' self.buttons_enabled = False self.chart = QChart() self.chart.layout().setContentsMargins(0, 0, 0, 0) self.chart.setBackgroundRoundness(0) self.savePlotButton.clicked.connect(self.savePlot) self.chartView = QChartView(self.chart) self.gridLayout.addWidget(self.chartView, 1, 0, 1, 15) self.selected_services = SELECTED_SERVICES self.selected_SPID = [] self.selected_tmtc = 3 # IDB location self.settings = QtCore.QSettings('FHNW', 'stix_parser') self.idb_filename = self.settings.value('idb_filename', [], str) if self.idb_filename: STIX_IDB.reload(self.idb_filename) if not STIX_IDB.is_connected(): self.showMessage('IDB has not been set!') else: idb_filename = STIX_IDB.get_idb_filename() self.showMessage('IDB loaded from : {} '.format(idb_filename), 1) if idb_filename != self.idb_filename: self.settings.setValue('idb_filename', idb_filename) self.idb_filename = idb_filename #def startPythonConsole(self): # console.start({'packets': self.data}) def close(self): self.MainWindow.close() def style(self): return self.MainWindow.style() def startPacketServer(self): host = 'localhost' port = 9096 self.socketPacketServer.connect(host, port) self.socketPacketServer.setData(self.current_row, self.data) self.socketPacketServer.start() abspath = os.path.dirname(os.path.abspath(__file__)) template = ( "import sys\nsys.path.append('{}')\nimport client_packet_request as req\n" "packets=req.request(query_str='len', host='{}',port={}, verbose_level=1)\n" "#a query_string can be \n" "# - a python slice notation, for example, ':' '0:-1', 3:-1\n" "# - 'len', to get the total number of packets,\n" "# - index , to get a packet of the given index" "#set verbose_level to 0, to suppress print output ").format( abspath, host, port) cb = QtWidgets.QApplication.clipboard() cb.clear(mode=cb.Clipboard) cb.setText(template, mode=cb.Clipboard) msg = QtWidgets.QMessageBox() msg.setIcon(QtWidgets.QMessageBox.Information) msg.setText( "Packet server started and a template to request packet has been copied to your clipboard!" ) retval = msg.exec_() def onPlotAutoUpdateButtonClicked(self): if not self.timmer_is_on: if not self.data: return num_packets = len(self.data) if num_packets > 200: packets = self.data[-200:-1] else: packets = self.data self.timer = QTimer() self.timer.timeout.connect( partial(self.onPlotButtonClicked, packets)) self.timer.start(2000) self.timmer_is_on = True self.autoUpdateButton.setText('Stop Auto Update') else: if self.timer: self.timer.stop() self.timmer_is_on = False self.autoUpdateButton.setText('Start Auto Update') #def statusListContextMenuEvent(self,pos): # menu = QtWidgets.QMenu() # clearLogAction= menu.addAction('Empty log') # clearLogAction.triggered.connect(self.clearLog) #def clearLog(self): # self.statusListWidget.clear() def packetTreeContextMenuEvent(self, pos): menu = QtWidgets.QMenu() filterAction = menu.addAction('Filter') menu.addSeparator() rawDataAction = menu.addAction('Raw binary data') menu.addSeparator() copyPacketAction = menu.addAction('Copy packet') menu.addSeparator() deleteAllAction = menu.addAction('Delete all packets') self.current_row = self.packetTreeWidget.currentIndex().row() rawDataAction.triggered.connect(self.onViewBinaryTriggered) filterAction.triggered.connect(self.filter) copyPacketAction.triggered.connect(self.onCopyTriggered) deleteAllAction.triggered.connect(self.onDeleteAllTriggered) action = menu.exec_(self.packetTreeWidget.viewport().mapToGlobal(pos)) def filter(self): text, okPressed = QtWidgets.QInputDialog.getText( None, "Packet filtering", "Filtering by SPID or description (! to exclude):", QtWidgets.QLineEdit.Normal, "") if okPressed: self.filterPattern = text self.addPacketsToView(self.data, True, show_stat=False) def onDeleteAllTriggered(self): self.data.clear() self.current_row = 0 self.packetTreeWidget.clear() self.paramTreeWidget.clear() #def onPacketTreeItemDoubleClicked(self): # self.onViewBinaryTriggered() def onViewBinaryTriggered(self): diag = QtWidgets.QDialog() diag_ui = raw_viewer.Ui_Dialog() diag_ui.setupUi(diag) if self.data: try: raw = self.data[self.current_row]['bin'] header = self.data[self.current_row]['header'] diag_ui.setPacketInfo('{}({},{}) {}'.format( header['TMTC'], header['service_type'], header['service_subtype'], header['descr'])) diag_ui.displayRaw(raw) except (IndexError, KeyError): diag_ui.setText('Raw data not available.') diag.exec_() def onOnlineHelpTriggered(self): webbrowser.open( 'https://github.com/i4Ds/STIX-python-data-parser', new=2) def onTimestampConvertorTriggered(self): diag = QtWidgets.QDialog() diag_ui = timestamp_convertor.Ui_Dialog() diag_ui.setupUi(diag) diag.exec_() def onPluginTriggered(self): self.plugin_location = self.settings.value('plugin_location', [], str) diag = QtWidgets.QDialog() diag_ui = plugin.Ui_Dialog() diag_ui.setupUi(diag) if self.plugin_location: diag_ui.setPluginLocation(self.plugin_location) diag_ui.setData(self.data, self.current_row) diag.exec_() location = diag_ui.getPluginLocation() if location != self.plugin_location: self.settings.setValue('plugin_location', location) def onPacketFilterTriggered(self): diag = QtWidgets.QDialog() diag_ui = packet_filter.Ui_Dialog() diag_ui.setupUi(diag) self.filterPattern = '' #empty search string diag_ui.setSelectedServices(self.selected_services) diag_ui.buttonBox.accepted.connect( partial(self.applyServiceFilter, diag_ui)) diag.exec_() def applyServiceFilter(self, diag_ui): self.selected_SPID = diag_ui.getSelectedSPID() self.selected_services = diag_ui.getSelectedServices() self.selected_tmtc = diag_ui.getTMTC() self.showMessage('Applying filter...') self.addPacketsToView(self.data, True, show_stat=False) def onExportButtonClicked(self): if self.y: filename = str( QtWidgets.QFileDialog.getSaveFileName( None, "Save data to file", "", "CSV(*.csv)")[0]) if filename: with open(filename, 'w') as f: f.write('{},{}\n'.format(self.xlabel, self.ylabel)) for xx, yy in zip(self.x, self.y): f.write('{},{}\n'.format(xx, yy)) self.showMessage( 'The data has been written to {}'.format(filename)) else: msgBox = QtWidgets.QMessageBox() msgBox.setIcon(QtWidgets.QMessageBox.Information) msgBox.setText('Plot first!') msgBox.setWindowTitle("Warning") msgBox.setStandardButtons(QtWidgets.QMessageBox.Ok) msgBox.exec_() def savePlot(self): # if self.figure.get_axes(): if self.chart: filetypes = "PNG (*.png);;JPEG (*.jpg)" filename = str( QtWidgets.QFileDialog.getSaveFileName( None, "Save plot to file", "", filetypes)[0]) if filename: if not filename.endswith(('.png', '.jpg')): filename += '.png' # self.figure.savefig(filename) p = self.chartView.grab() p.save(filename) self.showMessage(('Has been saved to %s.' % filename)) else: msgBox = QtWidgets.QMessageBox() msgBox.setIcon(QtWidgets.QMessageBox.Information) msgBox.setText('No figure to save') msgBox.setWindowTitle("STIX raw data viewer") msgBox.setStandardButtons(QtWidgets.QMessageBox.Ok) msgBox.exec_() def onCopyTriggered(self): packet_id = self.current_row try: packet = self.data[packet_id] ss = pprint.pformat(packet) cb = QtWidgets.QApplication.clipboard() cb.clear(mode=cb.Clipboard) cb.setText(ss, mode=cb.Clipboard) msg = QtWidgets.QMessageBox() msg.setIcon(QtWidgets.QMessageBox.Information) msg.setText( "The data of the selected packet has been copied to the clipboard." ) msg.setWindowTitle("Information") msg.setStandardButtons(QtWidgets.QMessageBox.Ok) retval = msg.exec_() except Exception as e: self.showMessage(str(e), 0) def onPasteTriggered(self): raw_hex = QtWidgets.QApplication.clipboard().text() if len(raw_hex) < 16: self.showMessage('No data in the clipboard.') return self.hexParser.setHex(raw_hex) self.hexParser.start() def showMessage(self, msg, where=0): if where != 1: self.statusbar.showMessage(msg) if where != 0: self.statusListWidget.addItem(msg) def onSetIDBClicked(self): self.idb_filename = QtWidgets.QFileDialog.getOpenFileName( None, 'Select file', '.', 'IDB file(*.db *.sqlite *.sqlite3)')[0] if not self.idb_filename: return STIX_IDB.reload(self.idb_filename) if STIX_IDB.is_connected(): #settings = QtCore.QSettings('FHNW', 'stix_parser') self.settings.setValue('idb_filename', self.idb_filename) self.showMessage( 'IDB location: {} '.format(STIX_IDB.get_idb_filename()), 1) def save(self): filetypes = 'python compressed pickle (*.pklz);; python pickle file (*.pkl);; binary data (*.dat)' self.output_filename = str( QtWidgets.QFileDialog.getSaveFileName(None, "Save packets to", "", filetypes)[0]) if not self.output_filename.endswith(('.pklz', '.pkl', '.dat')): msg = 'unsupported file format !' self.showMessage(msg) return msg = 'Writing data to file %s' % self.output_filename self.showMessage(msg) if self.output_filename.endswith(('.pklz', '.pkl')): stw = stix_writer.StixPickleWriter(self.output_filename) stw.register_run(str(self.input_filename)) stw.write_all(self.data) elif self.output_filename.endswith('.dat'): stw = stix_writer.StixBinaryWriter(self.output_filename) stw.write_all(self.data) num_ok = stw.get_num_sucess() msg = ( 'The binary data of {} packets written to file {}, total packets {}' .format(num_ok, self.output_filename, len(self.data))) self.showMessage(msg) msg = 'Packets have been written to %s' % self.output_filename self.showMessage(msg) def setListViewSelected(self, row): #index = self.model.createIndex(row, 0); # if index.isValid(): # self.model.selectionModel().select( index, QtGui.QItemSelectionModel.Select) pass def about(self): msgBox = QtWidgets.QMessageBox() msgBox.setIcon(QtWidgets.QMessageBox.Information) msgBox.setText("STIX raw data parser and viewer, [email protected]") msgBox.setWindowTitle("Stix data viewer") msgBox.setStandardButtons(QtWidgets.QMessageBox.Ok) msgBox.exec_() def nextPacket(self): self.current_row += 1 length = len(self.data) if self.current_row >= length: self.current_row = length - 1 self.showMessage('No more packet!') self.showPacket(self.current_row) self.setListViewSelected(self.current_row) def previousPacket(self): self.current_row -= 1 if self.current_row < 0: self.current_row = 0 self.showMessage('Reach the first packet!') self.showPacket(self.current_row) self.setListViewSelected(self.current_row) def getOpenFilename(self): location = self.settings.value('location', [], str) if not location: location = '.' filetypes = ( 'Supported file (*.dat *.bin *.binary *.pkl *.pklz *.xml *ascii *BDF *txt) ;; All(*)' ) self.input_filename = QtWidgets.QFileDialog.getOpenFileName( None, 'Select file', location, filetypes)[0] if not self.input_filename: return self.settings.setValue('location', os.path.abspath(self.input_filename)) diag = QtWidgets.QDialog() diag_ui = packet_filter.Ui_Dialog() diag_ui.setupUi(diag) diag_ui.setSelectedServices(SELECTED_SERVICES) diag_ui.buttonBox.accepted.connect( partial(self.onOpenFile, self.input_filename, diag_ui)) diag.exec_() def onOpenFile(self, input_filename, diag): self.selected_SPID = diag.getSelectedSPID() self.selected_services = diag.getSelectedServices() self.openFile(input_filename, self.selected_services, self.selected_SPID) def openFile(self, filename, selected_services=None, selected_SPID=None): msg = 'Loading file %s ...' % filename self.progressDiag = QtWidgets.QProgressDialog() #self.showMessage(msg) self.progressDiag.setLabelText(msg) self.progressDiag.setWindowTitle('Loading data') self.progressDiag.setCancelButtonText('Cancel') self.progressDiag.setRange(0, 100) self.progressDiag.setMinimumWidth(300) self.progressDiag.canceled.connect(self.stopParsing) self.filterPattern = '' self.dataReader.setPacketFilter(selected_services, selected_SPID) self.dataReader.setFilename(filename) self.dataReader.start() self.progressDiag.show() def stopParsing(self): if self.dataReader: self.dataReader.stopParsing() self.progressDiag.hide() def onProgressUpdated(self, progress): if not self.progressDiag: return self.progressDiag.setValue(progress) if progress >=99: self.progressDiag.hide() def onDataReaderCritical(self, msg): self.showMessage(msg, 1) def onDataReaderInfo(self, msg): self.showMessage(msg, 0) def onDataReaderWarning(self, msg): self.showMessage(msg, 1) def onDataReaderError(self, msg): self.showMessage(msg, 1) def onDataReady(self, data, clear=True, show_stat=True): #self.progressBar.hide() if not clear: self.data.extend(data) else: self.data = data if data: self.addPacketsToView(data, clear=clear, show_stat=show_stat) self.enableButtons() else: self.showMessage('No packet loaded') def enableButtons(self): if not self.buttons_enabled: self.actionPrevious.setEnabled(True) self.actionNext.setEnabled(True) self.actionSave.setEnabled(True) self.actionCopy.setEnabled(True) self.actionPlot.setEnabled(True) self.actionViewBinary.setEnabled(True) self.buttons_enabled = True def addPacketsToView(self, data, clear=True, show_stat=True): if clear: self.packetTreeWidget.clear() for p in data: if not isinstance(p, dict): continue header = p['header'] root = QtWidgets.QTreeWidgetItem(self.packetTreeWidget) colors = {2: '#FFA500', 1: '#000080', 3: '#FF0000', 4: '#800000'} tc_color = '#78281F' if header['TMTC'] == 'TC': root.setForeground(0, QtGui.QBrush(QtGui.QColor(tc_color))) root.setForeground(1, QtGui.QBrush(QtGui.QColor(tc_color))) else: if header['service_type'] == 5: if header['service_subtype'] in colors.keys(): root.setForeground( 0, QtGui.QBrush( QtGui.QColor( colors[header['service_subtype']]))) root.setForeground( 1, QtGui.QBrush( QtGui.QColor( colors[header['service_subtype']]))) timestamp_str = stix_datetime.format_datetime(header['unix_time']) root.setText(0, timestamp_str) description = '{}({},{}) - {}'.format( header['TMTC'], header['service_type'], header['service_subtype'], header['descr']) root.setText(1, description) hidden = False if self.selected_SPID: if header['TMTC'] == 'TC': hidden = True elif -int(header['SPID']) in self.selected_SPID or int( header['SPID']) not in self.selected_SPID: hidden = True else: if int(header['service_type']) not in self.selected_services: hidden = True TMTC = header['TMTC'] if TMTC == 'TM' and self.selected_tmtc in [2, 0]: hidden = True if TMTC == 'TC' and self.selected_tmtc in [1, 0]: hidden = True if self.filterPattern: to_exclude = False pattern = self.filterPattern.strip() if pattern.startswith('!'): to_exclude = True pattern = pattern[1:] try: spid = int(pattern) hidden = to_exclude == (header['SPID'] == spid) #XNOR operation except (TypeError, ValueError): hidden = to_exclude == (pattern in description) root.setHidden(hidden) if show_stat: total_packets = len(self.data) self.showMessage(('Total packet(s): %d' % total_packets)) def onConnectTSCTriggered(self): diag = QtWidgets.QDialog() diag_ui = tsc_connection.Ui_Dialog() diag_ui.setupUi(diag) self.tsc_host = self.settings.value('tsc_host', [], str) self.tsc_port = self.settings.value('tsc_port', [], str) if self.tsc_host: diag_ui.serverLineEdit.setText(self.tsc_host) if self.tsc_port: diag_ui.portLineEdit.setText(self.tsc_port) diag_ui.buttonBox.accepted.connect(partial(self.connectToTSC, diag_ui)) diag.exec_() def connectToTSC(self, dui): host = dui.serverLineEdit.text() port = dui.portLineEdit.text() self.showMessage('Connecting to TSC...') self.socketPacketReceiver.connect(host, int(port)) self.socketPacketReceiver.start() def onPacketArrival(self, packets): clear = False if packets: if len(self.data) > MAX_NUM_PACKET_IN_BUFFER: clear = True self.onDataReady(packets, clear=clear, show_stat=True) def onLoadMongoDBTriggered(self): diag = QtWidgets.QDialog() diag_ui = mongo_dialog.Ui_Dialog() diag_ui.setupUi(diag) #self.settings = QtCore.QSettings('FHNW', 'stix_parser') self.mongo_server = self.settings.value('mongo_server', [], str) self.mongo_port = self.settings.value('mongo_port', [], str) self.mongo_user = self.settings.value('mongo_user', [], str) self.mongo_pwd = self.settings.value('mongo_pwd', [], str) if self.mongo_server: diag_ui.serverLineEdit.setText(self.mongo_server) if self.mongo_port: diag_ui.portLineEdit.setText(self.mongo_port) if self.mongo_user: diag_ui.userLineEdit.setText(self.mongo_user) if self.mongo_pwd: diag_ui.pwdLineEdit.setText(self.mongo_pwd) diag_ui.pushButton.clicked.connect( partial(self.loadRunsFromMongoDB, diag_ui)) diag_ui.buttonBox.accepted.connect( partial(self.loadDataFromMongoDB, diag_ui, diag)) diag.exec_() def loadRunsFromMongoDB(self, dui): server = dui.serverLineEdit.text() port = dui.portLineEdit.text() user = dui.userLineEdit.text() pwd = dui.pwdLineEdit.text() self.showMessage('saving setting...') if self.mongo_server != server: self.settings.setValue('mongo_server', server) if self.mongo_port != port: self.settings.setValue('mongo_port', port) if self.mongo_user != user: self.settings.setValue('mongo_user', user) if self.mongo_pwd != pwd: self.settings.setValue('mongo_pwd', pwd) self.showMessage('connecting Mongo database ...') self.mdb = mgdb.MongoDB(server, int(port), user, pwd) if not self.mdb.is_connected(): self.showMessage('Failed to connect to MongoDB') return dui.treeWidget.clear() self.showMessage('Fetching data...') for run in self.mdb.select_all_runs(): root = QtWidgets.QTreeWidgetItem(dui.treeWidget) root.setText(0, str(run['_id'])) root.setText(1, run['filename']) root.setText(2, stix_datetime.format_datetime(run['date'])) root.setText(3, stix_datetime.format_datetime(run['data_start_unix_time'])) root.setText(4, stix_datetime.format_datetime(run['data_stop_unix_time'])) def loadDataFromMongoDB(self, dui, diag): self.showMessage('Loading packets ...') selected_runs = [] for item in dui.treeWidget.selectedItems(): selected_runs.append(item.text(0)) if not selected_runs: self.showMessage('Run not selected!') if selected_runs: diag.done(0) self.showMessage('Loading data ...!') data = list(self.mdb.select_packets_by_run(selected_runs[0])) if data: self.onDataReady(data, clear=True) else: self.showMessage('No packets found!') # close def onPacketSelected(self, cur, pre): self.current_row = self.packetTreeWidget.currentIndex().row() self.showMessage(('Packet #%d selected' % self.current_row)) self.showPacket(self.current_row) def showPacket(self, row): if not self.data: return header = self.data[row]['header'] total_packets = len(self.data) self.showMessage( ('Packet %d / %d %s ' % (row, total_packets, header['descr']))) self.paramTreeWidget.clear() header_root = QtWidgets.QTreeWidgetItem(self.paramTreeWidget) header_root.setText(0, "Header") rows = len(header) for key, val in header.items(): root = QtWidgets.QTreeWidgetItem(header_root) root.setText(0, key) root.setText(1, str(val)) params = self.data[row]['parameters'] param_root = QtWidgets.QTreeWidgetItem(self.paramTreeWidget) param_root.setText(0, "Parameters") self.showParameterTree(params, param_root) self.paramTreeWidget.expandItem(param_root) self.paramTreeWidget.expandItem(header_root) self.current_row = row def showParameterTree(self, params, parent, parent_id=[]): if not params: return for i, p in enumerate(params): root = QtWidgets.QTreeWidgetItem(parent) if not p: continue param = Parameter(p) param_name = param['name'] desc = param['desc'] current_ids = parent_id[:] current_ids.append(i) root.setText(0, param_name) root.setText(1, desc) root.setText(2, str(param['raw'])) tip='parameter'+''.join(['[{}]'.format(x) for x in current_ids]) root.setToolTip(0, tip) long_desc = STIX_IDB.get_scos_description(param_name) if long_desc: root.setToolTip(1, long_desc) try: root.setToolTip(2, hex(param['raw_int'])) except: pass unit=STIX_IDB.get_parameter_unit(param_name) eng=str(param['eng']) root.setText(3, eng) root.setText(4, unit) if 'NIXG' in param_name: root.setHidden(True) #groups should not be shown if param.children: self.showParameterTree(param['children'], root, current_ids) self.paramTreeWidget.itemDoubleClicked.connect(self.onTreeItemClicked) def walk(self, name, params, header, ret_x, ret_y, xaxis=0, data_type=0): if not params: return timestamp = header['unix_time'] for p in params: if not p: continue param = Parameter(p) if name == param.name: values = None if data_type == 0: values = param.raw else: values = param.eng try: yvalue = float(values) ret_y.append(yvalue) if xaxis == 1: ret_x.append(timestamp) else: self.showMessage(('Can not plot %s ' % str(yvalue))) except Exception as e: self.showMessage(('%s ' % str(e))) if param.children: self.walk(name, param.children, header, ret_x, ret_y, xaxis, data_type) def onPlotButtonClicked(self, packets=None): if self.chart: self.chart.removeAllSeries() if packets is None: packets = self.data if not packets: return self.showMessage('Preparing plot ...') name = self.paramNameEdit.text() packet_selection = self.comboBox.currentIndex() xaxis_type = self.xaxisComboBox.currentIndex() data_type = self.dataTypeComboBox.currentIndex() timestamp = [] self.y = [] params = self.paramNameEdit.text() header = packets[0]['header'] current_spid = 0 spid_text = self.spidLineEdit.text() if spid_text: current_spid = int(spid_text) selected_packets=[] if packet_selection == 0: selected_packets=[packets[self.current_row]] elif packet_selection == 1: selected_packets=packets for packet in selected_packets: header = packet['header'] if packet['header']['SPID'] != current_spid: continue params = packet['parameters'] self.walk(name, params, header, timestamp, self.y, xaxis_type, data_type) self.x = [] if not self.y: self.showMessage('No data points') elif self.y: style = self.styleEdit.text() if not style: style = '-' title = '%s' % str(name) desc = self.descLabel.text() if desc: title += '- %s' % desc self.chart.setTitle(title) ylabel = 'Raw value' xlabel = name if data_type == 1: ylabel = 'Engineering / Decompressed value' if xaxis_type == 0: if packet_selection == 1: xlabel = "Packet #" else: xlabel = "Repeat #" self.x = range(0, len(self.y)) if xaxis_type == 1: self.x = [t - timestamp[0] for t in timestamp] xlabel = 'Time -T0 (s)' if xaxis_type != 2: series = QLineSeries() series2 = None for xx, yy in zip(self.x, self.y): series.append(xx, yy) if 'o' in style: series2 = QScatterSeries() for xx, yy in zip(self.x, self.y): series2.append(xx, yy) self.chart.addSeries(series2) self.chart.addSeries(series) axisX = QValueAxis() axisX.setTitleText(xlabel) axisY = QValueAxis() axisY.setTitleText(ylabel) self.chart.setAxisX(axisX) self.chart.setAxisY(axisY) series.attachAxis(axisX) series.attachAxis(axisY) else: nbins = len(set(self.y)) ycounts, xedges = np.histogram(self.y, bins=nbins) series = QLineSeries() for i in range(0, nbins): meanx = (xedges[i] + xedges[i + 1]) / 2. series.append(meanx, ycounts[i]) self.chart.addSeries(series) axisX = QValueAxis() axisX.setTitleText(name) axisY = QValueAxis() axisY.setTitleText("Counts") self.chart.setAxisY(axisY) self.chart.setAxisX(axisX) series.attachAxis(axisX) series.attachAxis(axisY) self.xlabel = xlabel self.ylabel = ylabel self.chartView.setRubberBand(QChartView.RectangleRubberBand) self.chartView.setRenderHint(QtGui.QPainter.Antialiasing) msg = 'Number of data points: {}, Ymin: {}, Ymax: {}'.format( len(self.y), min(self.y), max(self.y)) self.showMessage(msg, 1) self.showMessage('The canvas updated!') def plotParameter(self, SPID=None, pname=None, desc=None): self.tabWidget.setCurrentIndex(1) if pname: self.paramNameEdit.setText(pname) if desc: self.descLabel.setText(desc) if SPID: self.spidLineEdit.setText(str(SPID)) def onPlotActionClicked(self): self.tabWidget.setCurrentIndex(1) self.plotParameter() def onTreeItemClicked(self, it, col): SPID = None try: SPID = self.data[self.current_row]['header']['SPID'] except IndexError: pass self.plotParameter(SPID, it.text(0), it.text(1))
class SENSOR_TEST(QWidget): bars = None series = None chart = None labelWidgets = [] comms = None commsStatus = False deviceID = "HARP" def __init__(self): QWidget.__init__(self) # ADD GUI WIDGETS self.setupLayout() # LAUNCH GUI self.show() self.resize(800,600) def setupLayout(self): parentLayout = QVBoxLayout() parentLayout.setContentsMargins(20,20,20,20) buttonLayout = QHBoxLayout() # CREATE SERIAL COMMUNICATION CONNECT BUTTON self.connectButton = QPushButton("CONNECT") self.connectButton.setFixedSize(150, 40) self.connectButton.setCheckable(True) self.connectButton.setStyleSheet("QPushButton {background-color: #66BB6A; color: black; border-radius: 20px}" "QPushButton:hover {background-color: #4CAF50; color: black; border-radius: 20px}" "QPushButton:checked {background-color: #EF5350; color: white; border-radius: 20px}" "QPushButton:checked:hover {background-color: #F44336; color: white; border-radius: 20px}") # CREATE SENSORS CALIBRATION BUTTON self.calibrateButton = QPushButton("CALIBRATE") self.calibrateButton.setFixedSize(150, 40) self.calibrateButton.setStyleSheet("QPushButton {background-color: #E0E0E0; color: black; border-radius: 20px}" "QPushButton:hover {background-color: #BDBDBD; color: black; border-radius: 20px}" "QPushButton:pressed {background-color: #D5D5D5; color: black; border-radius: 20px}") # ADD BUTTONS TO HORIZONTAL LAYOUT buttonLayout.addWidget(self.connectButton, alignment = Qt.AlignLeft) buttonLayout.addWidget(self.calibrateButton, alignment = Qt.AlignRight) # CREATE BAR CHART TO DISPLAY SENSOR READINGS self.bars = QBarSet('Sensor Readings') self.bars.append([0 for i in range(5)]) self.chart = QChart() self.chart.setBackgroundRoundness(20) self.chart.layout().setContentsMargins(0, 0, 0, 0) self.series = QBarSeries() self.series.append(self.bars) self.chart.addSeries(self.series) self.chart.setTitle('Sensor Readings') # CREATE X-AXIS AS SENSOR LABELS xAxis = QBarCategoryAxis() labels = ["Sensor {}".format(i+1) for i in range(5)] xAxis.append(labels) # CREATE Y-AXIS AND SENSOR READINGS yAxis = QValueAxis() yAxis.setRange(-100, 100) yAxis.setTickCount(11) yAxis.setTitleText("Pressure (%)") # ADD AXES TO CHART self.chart.addAxis(xAxis, Qt.AlignBottom) self.chart.addAxis(yAxis, Qt.AlignLeft) self.chart.legend().setVisible(False) # ATTACH AXES TO SERIES self.series.attachAxis(xAxis) self.series.attachAxis(yAxis) # ADD CHART TO A VIEW chartView = QChartView(self.chart) labelLayout = QFrame() labelLayout.setStyleSheet("background-color: white; border-radius: 20px") layout1 = QVBoxLayout() labelLayout.setLayout(layout1) layout2 = QHBoxLayout() layout3 = QHBoxLayout() for i in range(5): label = QLabel("Sensor {}".format(i+1)) label.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) label.setStyleSheet("font-weight: bold;") layout2.addWidget(label) value = QLabel("0 mV") value.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) self.labelWidgets.append(value) layout3.addWidget(value) layout1.addLayout(layout2, 1) layout1.addLayout(layout3, 1) parentLayout.addLayout(buttonLayout) parentLayout.addWidget(chartView, 5) parentLayout.addWidget(labelLayout, 1) # LINK WIDGETS self.connectButton.clicked.connect(self.serialToggle) self.calibrateButton.clicked.connect(self.sensorCalibrate) self.setLayout(parentLayout) def getSensorReadings(self): """ PURPOSE Requests sensor readings from ROV and updates GUI. INPUT NONE RETURNS NONE """ # SENSOR POLLING RATE (HZ) refreshRate = 100 # START QTIMER TO REPEATEDLY UPDATE SENSORS AT THE DESIRED POLLING RATE self.timer = QTimer() self.timer.setTimerType(Qt.PreciseTimer) self.timer.timeout.connect(self.getSensorReadings) self.timer.start(int(1000*1/refreshRate)) # STOP REQUESTING SENSOR VALUES IF ROV IS DISCONNECTED if self.commsStatus == False: self.timer.stop() else: # REQEST SINGLE READING sensorReadings = self.getSensors() # UPDATE VOLTAGE READINGS self.updateVoltageReadings(sensorReadings) # SCALE SENSOR READINGS scaledReadings = self.mapPressureReadings(sensorReadings) try: # UPDATE GUI self.series.remove(self.bars) self.bars = QBarSet("") self.bars.append([float(item) for item in scaledReadings]) self.series.append(self.bars) except: self.teensyDisconnect() def mapPressureReadings(self, values): mappedValues = [] for value in values: try: mappedValues.append((200/1023)*float(value) - 100) except: pass return mappedValues def updateVoltageReadings(self, values): voltageValues = [round((3300/1023)*float(i)) for i in values] for i, label in enumerate(self.labelWidgets): label.setText(str(voltageValues[i]) + " mV") def serialToggle(self, buttonState): """ PURPOSE Determines whether to connect or disconnect from the ROV serial interface. INPUT - buttonState = the state of the button (checked or unchecked). RETURNS NONE """ # CONNECT if buttonState: self.teensyConnect() # DISCONNECT else: self.teensyDisconnect() def sensorCalibrate(self): """ PURPOSE INPUT RETURNS """ self.calibrateSensors() ######################## ### SERIAL FUNCTIONS ### ######################## def teensyConnect(self): """ PURPOSE Attempts to connect to the ROV using the comms library. Changes the appearance of the connect buttons. If connection is successful, the ROV startup procedure is initiated. INPUT NONE RETURNS NONE """ # DISABLE BUTTONS TO AVOID DOUBLE CLICKS self.connectButton.setEnabled(False) # FIND ALL AVAILABLE COM PORTS availableComPorts, comPort, identity = self.findComPorts(115200, self.deviceID) print(availableComPorts, comPort, identity) # ATTEMPT CONNECTION TO ROV COM PORT status, message = self.serialConnect(comPort, 115200) print(status, message) # IF CONNECTION IS SUCCESSFUL if status == True: # MODIFY BUTTON STYLE self.connectButton.setText('DISCONNECT') # START READING SENSOR VALUES self.getSensorReadings() # IF CONNECTION IS UNSUCCESSFUL else: self.teensyDisconnect() # RE-ENABLE CONNECT BUTTONS self.connectButton.setEnabled(True) def teensyDisconnect(self): """ PURPOSE Disconnects from the ROV using the comms library. Changes the appearance of the connect buttons INPUT NONE RETURNS NONE """ # MODIFY BUTTON STYLE self.connectButton.setText('CONNECT') self.connectButton.setChecked(False) # CLOSE COM PORT if self.commsStatus: self.comms.close() self.commsStatus = False def findComPorts(self, baudRate, identity): """ PURPOSE Find all available COM ports and requests the devices identity. INPUT - menuObject = pointer to the drop down menu to display the available COM ports. - baudRate = baud rate of the serial interface. - identity = string containing the required device identity to connect to. RETURNS - availableComPorts = list of all the available COM ports. - rovComPort = the COM port that belongs to the device. - identity = the devices response from an identity request. """ # CREATE LIST OF ALL POSSIBLE COM PORTS ports = ['COM%s' % (i + 1) for i in range(256)] deviceIdentity = "" comPort = None availableComPorts = [] # CHECK WHICH COM PORTS ARE AVAILABLE for port in ports: try: comms = serial.Serial(port, baudRate, timeout = 1) availableComPorts.append(port) # REQUEST IDENTITY FROM COM PORT self.commsStatus = True deviceIdentity = self.getIdentity(comms, identity) comms.close() self.commsStatus = False # FIND WHICH COM PORT IS THE ROV if deviceIdentity == identity: comPort = port break # SKIP COM PORT IF UNAVAILABLE except (OSError, serial.SerialException): pass return availableComPorts, comPort, deviceIdentity def getIdentity(self, serialInterface, identity): """ PURPOSE Request identity from a defined COM port. INPUT - serialInterface = pointer to the serial interface object. - identity = the desired identity response from the device connected to the COM port. RETURNS - identity = the devices response. """ identity = "" startTime = datetime.now() elapsedTime = 0 # REPEATIDELY REQUEST IDENTIFICATION FROM DEVICE FOR UP TO 3 SECONDS while (identity == "") and (elapsedTime < 3): self.serialSend("?I", serialInterface) identity = self.serialReceive(serialInterface) elapsedTime = (datetime.now() - startTime).total_seconds() return identity def serialConnect(self, comPort, baudRate): """ PURPOSE Attempts to initialise a serial communication interface with a desired COM port. INPUT - rovComPort = the COM port of the ROV. - baudRate = the baud rate of the serial interface. RETURNS NONE """ self.commsStatus = False if comPort != None: try: self.comms = serial.Serial(comPort, baudRate, timeout = 1) message = "Connection to ROV successful." self.commsStatus = True except: message = "Failed to connect to {}.".format(comPort) else: message = "Failed to recognise device identity." return self.commsStatus, message def serialSend(self, command, serialInterface): """ PURPOSE Sends a string down the serial interface to the ROV. INPUT - command = the command to send. - serialInterface = pointer to the serial interface object. RETURNS NONE """ if self.commsStatus: try: serialInterface.write((command + '\n').encode('ascii')) except: self.teensyDisconnect() print("Failed to send command.") def serialReceive(self, serialInterface): """ PURPOSE Waits for data until a newline character is received. INPUT - serialInterface = pointer to the serial interface object. RETURNS NONE """ received = "" try: received = serialInterface.readline().decode('ascii').strip() except: self.teensyDisconnect() print("Failed to receive data.") return(received) def getSensors(self): """ PURPOSE Send request to device to return sensor readings. INPUT NONE RETURNS - results = array containing the sensor readings. """ results = [] # REQUEST SENSOR READINGS command = "?S" self.serialSend(command, self.comms) # READ RESPONSE INTO AN ARRAY results = self.serialReceive(self.comms).split(",") print(results) return results def calibrateSensors(self): """ """ # REQUEST SENSOR READINGS command = "?C" self.serialSend(command, self.comms)
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 Ui(mainwindow.Ui_MainWindow): def __init__(self, MainWindow): super(Ui, self).setupUi(MainWindow) self.MainWindow = MainWindow self.stix_tctm_parser = stix_parser.StixTCTMParser() self.initialize() def close(self): self.MainWindow.close() def style(self): return self.MainWindow.style() def initialize(self): self.tabWidget.setCurrentIndex(0) self.actionExit.triggered.connect(self.close) self.actionPlot.setEnabled(False) self.actionNext.setIcon(self.style().standardIcon( QtWidgets.QStyle.SP_ArrowForward)) self.actionPrevious.setIcon(self.style().standardIcon( QtWidgets.QStyle.SP_ArrowBack)) self.actionOpen.setIcon(self.style().standardIcon( QtWidgets.QStyle.SP_DialogOpenButton)) self.actionSave.setIcon(self.style().standardIcon( QtWidgets.QStyle.SP_DriveFDIcon)) self.actionSave.triggered.connect(self.save) self.actionOpen.triggered.connect(self.getOpenFilename) self.actionNext.triggered.connect(self.nextPacket) self.actionPrevious.triggered.connect(self.previousPacket) self.actionAbout.triggered.connect(self.about) self.actionPrevious.setEnabled(False) self.actionNext.setEnabled(False) self.actionSave.setEnabled(False) self.actionPlot.setEnabled(False) self.actionCopy.triggered.connect(self.onCopyTriggered) self.packetTreeWidget.currentItemChanged.connect(self.onPacketSelected) self.actionCopy.setEnabled(False) self.actionPaste.triggered.connect(self.onPasteTriggered) self.actionLog.triggered.connect(self.dockWidget.show) self.actionSetIDB.triggered.connect(self.onSetIDBClicked) self.plotButton.clicked.connect(self.onPlotButtonClicked) self.exportButton.clicked.connect(self.onExportButtonClicked) self.actionPlot.triggered.connect(self.onPlotActionClicked) self.actionLoadMongodb.triggered.connect(self.onLoadMongoDBTriggered) self.actionConnectTSC.triggered.connect(self.onConnectTSCTriggered) self.actionPacketFilter.triggered.connect(self.onPacketFilterTriggered) self.actionPlugins.triggered.connect(self.onPluginTriggered) self.actionOnlineHelp.triggered.connect(self.onOnlineHelpTriggered) self.actionViewBinary.triggered.connect(self.onViewBinaryTriggered) self.packetTreeWidget.customContextMenuRequested.connect( self.packetTreeContextMenuEvent) self.mdb = None self.current_row = 0 self.data = [] self.x = [] self.y = [] self.xlabel = 'x' self.ylabel = 'y' self.buttons_enabled = False self.chart = QChart() self.chart.layout().setContentsMargins(0, 0, 0, 0) self.chart.setBackgroundRoundness(0) self.savePlotButton.clicked.connect(self.savePlot) self.chartView = QChartView(self.chart) self.gridLayout.addWidget(self.chartView, 1, 0, 1, 15) #self.packetTreeWidget.itemDoubleClicked.connect(self.onPacketTreeItemDoubleClicked) self.selected_services = SELECTED_SERVICES self.selected_SPID = [] # IDB location self.settings = QtCore.QSettings('FHNW', 'stix_parser') self.idb_filename = self.settings.value('idb_filename', [], str) if self.idb_filename: idb._stix_idb.reload(self.idb_filename) if not idb._stix_idb.is_connected(): self.showMessage('IDB has not been set!') else: self.showMessage( 'IDB location: {} '.format(idb._stix_idb.get_idb_filename()), 1) def packetTreeContextMenuEvent(self, pos): menu = QtWidgets.QMenu() rawDataAction = menu.addAction('Show raw data') menu.addSeparator() filterAction = menu.addAction('Filter packets') copyPacketAction = menu.addAction('Copy packet') menu.addSeparator() deleteAllAction = menu.addAction('Delete all packets') self.current_row = self.packetTreeWidget.currentIndex().row() rawDataAction.triggered.connect(self.onViewBinaryTriggered) filterAction.triggered.connect(self.onPacketFilterTriggered) copyPacketAction.triggered.connect(self.onCopyTriggered) deleteAllAction.triggered.connect(self.onDeleteAllTriggered) action = menu.exec_(self.packetTreeWidget.viewport().mapToGlobal(pos)) def onDeleteAllTriggered(self): self.data.clear() self.packetTreeWidget.clear() self.paramTreeWidget.clear() #def onPacketTreeItemDoubleClicked(self): # self.onViewBinaryTriggered() def onViewBinaryTriggered(self): diag = QtWidgets.QDialog() diag_ui = raw_viewer.Ui_Dialog() diag_ui.setupUi(diag) if self.data: try: raw = self.data[self.current_row]['bin'] header = self.data[self.current_row]['header'] diag_ui.setPacketInfo('{}({},{}) {}'.format( header['TMTC'], header['service_type'], header['service_subtype'], header['DESCR'])) diag_ui.displayRaw(raw) except (IndexError, KeyError): diag_ui.setText('Raw data not available.') diag.exec_() def onOnlineHelpTriggered(self): webbrowser.open('https://github.com/i4Ds/STIX-dataviewer', new=2) def onPluginTriggered(self): self.plugin_location = self.settings.value('plugin_location', [], str) diag = QtWidgets.QDialog() diag_ui = plugin.Ui_Dialog() diag_ui.setupUi(diag) if self.plugin_location: diag_ui.setPluginLocation(self.plugin_location) diag_ui.setData(self.data, self.current_row) diag.exec_() location = diag_ui.getPluginLocation() if location != self.plugin_location: self.settings.setValue('plugin_location', location) def onPacketFilterTriggered(self): diag = QtWidgets.QDialog() diag_ui = packet_filter.Ui_Dialog() diag_ui.setupUi(diag) diag_ui.setSelectedServices(self.selected_services) diag_ui.buttonBox.accepted.connect( partial(self.applyServiceFilter, diag_ui)) diag.exec_() def applyServiceFilter(self, diag_ui): self.selected_SPID = diag_ui.getSelectedSPID() self.selected_services = diag_ui.getSelectedServices() self.showMessage('Applying filter...') self.addPacketsToView(self.data, True, show_stat=False) def onExportButtonClicked(self): if self.y: filename = str( QtWidgets.QFileDialog.getSaveFileName(None, "Save data to file", "", "CSV(*.csv)")[0]) if filename: with open(filename, 'w') as f: f.write('{},{}\n'.format(self.xlabel, self.ylabel)) for xx, yy in zip(self.x, self.y): f.write('{},{}\n'.format(xx, yy)) self.showMessage( 'The data has been written to {}'.format(filename)) else: msgBox = QtWidgets.QMessageBox() msgBox.setIcon(QtWidgets.QMessageBox.Information) msgBox.setText('Plot first!') msgBox.setWindowTitle("Warning") msgBox.setStandardButtons(QtWidgets.QMessageBox.Ok) msgBox.exec_() def savePlot(self): # if self.figure.get_axes(): if self.chart: filetypes = "PNG (*.png);;JPEG (*.jpg)" filename = str( QtWidgets.QFileDialog.getSaveFileName(None, "Save plot to file", "", filetypes)[0]) if filename: if not filename.endswith(('.png', '.jpg')): filename += '.png' # self.figure.savefig(filename) p = self.chartView.grab() p.save(filename) self.showMessage(('Saved to %s.' % filename)) else: msgBox = QtWidgets.QMessageBox() msgBox.setIcon(QtWidgets.QMessageBox.Information) msgBox.setText('No figure to save') msgBox.setWindowTitle("STIX raw data viewer") msgBox.setStandardButtons(QtWidgets.QMessageBox.Ok) msgBox.exec_() def onCopyTriggered(self): packet_id = self.current_row try: packet = self.data[packet_id] ss = pprint.pformat(packet) cb = QtWidgets.QApplication.clipboard() cb.clear(mode=cb.Clipboard) cb.setText(ss, mode=cb.Clipboard) msg = QtWidgets.QMessageBox() msg.setIcon(QtWidgets.QMessageBox.Information) msg.setText( "The data of the selected packet has been copied to the clipboard." ) msg.setWindowTitle("Information") msg.setStandardButtons(QtWidgets.QMessageBox.Ok) retval = msg.exec_() except Exception as e: self.showMessage(str(e), 0) def onPasteTriggered(self): raw_hex = QtWidgets.QApplication.clipboard().text() if len(raw_hex) < 16: self.showMessage('No data in the clipboard.') return data_hex = re.sub(r"\s+", "", raw_hex) try: data_binary = binascii.unhexlify(data_hex) packets = self.stix_tctm_parser.parse_binary(data_binary, 0, store_binary=True) if not packets: return self.showMessage('%d packets read from the clipboard' % len(packets)) self.onDataReady(packets, clear=False, show_stat=False) except Exception as e: self.showMessageBox(str(e), data_hex) def showMessageBox(self, message, content): msg = QtWidgets.QMessageBox() msg.setIcon(QtWidgets.QMessageBox.Critical) msg.setText("Error") msg.setInformativeText(message) msg.setWindowTitle("Error") msg.setDetailedText(content) msg.setStandardButtons(QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) retval = msg.exec_() def showMessage(self, msg, where=0): if where != 1: self.statusbar.showMessage(msg) if where != 0: self.statusListWidget.addItem(msg) # if destination !=0 : # self.listWidget_2.addItem(msg) def onSetIDBClicked(self): self.idb_filename = QtWidgets.QFileDialog.getOpenFileName( None, 'Select file', '.', 'IDB file(*.db *.sqlite *.sqlite3)')[0] if not self.idb_filename: return idb._stix_idb.reload(self.idb_filename) if idb._stix_idb.is_connected(): #settings = QtCore.QSettings('FHNW', 'stix_parser') self.settings.setValue('idb_filename', self.idb_filename) self.showMessage( 'IDB location: {} '.format(idb._stix_idb.get_idb_filename()), 1) def save(self): filetypes = 'python compressed pickle (*.pklz);; python pickle file (*.pkl);; binary data (*.dat)' self.output_filename = str( QtWidgets.QFileDialog.getSaveFileName(None, "Save packets to", "", filetypes)[0]) if not self.output_filename.endswith(('.pklz', '.pkl', '.dat')): msg = 'unsupported file format !' self.showMessage(msg) return msg = 'Writing data to file %s' % self.output_filename self.showMessage(msg) if self.output_filename.endswith(('.pklz', '.pkl')): stw = stix_writer.StixPickleWriter(self.output_filename) stw.register_run(str(self.input_filename)) stw.write_all(self.data) #stw.done() elif self.output_filename.endswith('.dat'): stw = stix_writer.StixBinaryWriter(self.output_filename) stw.write_all(self.data) num_ok = stw.get_num_sucess() msg = ( 'The binary data of {} packets written to file {}, total packets {}' .format(num_ok, self.output_filename, len(self.data))) self.showMessage(msg) def setListViewSelected(self, row): #index = self.model.createIndex(row, 0); # if index.isValid(): # self.model.selectionModel().select( index, QtGui.QItemSelectionModel.Select) pass def about(self): msgBox = QtWidgets.QMessageBox() msgBox.setIcon(QtWidgets.QMessageBox.Information) msgBox.setText("STIX raw data parser and viewer, [email protected]") msgBox.setWindowTitle("Stix data viewer") msgBox.setStandardButtons(QtWidgets.QMessageBox.Ok) msgBox.exec_() def nextPacket(self): self.current_row += 1 length = len(self.data) if self.current_row >= length: self.current_row = length - 1 self.showMessage('No more packet!') self.showPacket(self.current_row) self.setListViewSelected(self.current_row) def previousPacket(self): self.current_row -= 1 if self.current_row < 0: self.current_row = 0 self.showMessage('Reach the first packet!') self.showPacket(self.current_row) self.setListViewSelected(self.current_row) def getOpenFilename(self): filetypes = ( 'STIX raw data(*.dat *.bin *.binary);; python pickle files (*.pkl *pklz);;' 'ESA xml files (*xml);;' 'ESA ascii files(*.ascii);; CMDVS archive files (*.BDF);; All(*)') self.input_filename = QtWidgets.QFileDialog.getOpenFileName( None, 'Select file', '.', filetypes)[0] if not self.input_filename: return self.openFile(self.input_filename) def openFile(self, filename): self.packetTreeWidget.clear() msg = 'Loading file %s ...' % filename self.showMessage(msg) self.dataReader = StixFileReader(filename) #self.dataReader.dataLoaded.connect(self.onDataReady) self.dataReader.packetArrival.connect(self.onPacketArrival) self.dataReader.error.connect(self.onDataReaderError) self.dataReader.info.connect(self.onDataReaderInfo) self.dataReader.warn.connect(self.onDataReaderWarn) self.dataReader.start() def onDataReaderInfo(self, msg): self.showMessage(msg, 0) def onDataReaderWarn(self, msg): self.showMessage(msg, 1) def onDataReaderError(self, msg): self.showMessage(msg, 1) def onDataReady(self, data, clear=True, show_stat=True): if not clear: self.data.extend(data) else: self.data = data if data: self.addPacketsToView(data, clear=clear, show_stat=show_stat) self.enableButtons() def enableButtons(self): if not self.buttons_enabled: self.actionPrevious.setEnabled(True) self.actionNext.setEnabled(True) self.actionSave.setEnabled(True) self.actionCopy.setEnabled(True) self.actionPlot.setEnabled(True) self.actionViewBinary.setEnabled(True) self.buttons_enabled = True def addPacketsToView(self, data, clear=True, show_stat=True): if clear: self.packetTreeWidget.clear() #t0 = 0 for p in data: if type(p) is not dict: continue header = p['header'] root = QtWidgets.QTreeWidgetItem(self.packetTreeWidget) # if t0 == 0: # t0 = header['time'] timestamp_str = '' try: timestamp_str = header['utc'] except KeyError: timestamp_str = '{:.2f}'.format(header['time']) #- t0) root.setText(0, timestamp_str) root.setText( 1, '{}({},{}) - {}'.format(header['TMTC'], header['service_type'], header['service_subtype'], header['DESCR'])) if not self.selected_SPID: #not set then apply service if header['service_type'] not in self.selected_services: root.setHidden(True) else: if header['SPID'] not in self.selected_SPID: root.setHidden(True) if show_stat: total_packets = len(self.data) self.showMessage(('Total packet(s): %d' % total_packets)) def onConnectTSCTriggered(self): diag = QtWidgets.QDialog() diag_ui = tsc_connection.Ui_Dialog() diag_ui.setupUi(diag) self.tsc_host = self.settings.value('tsc_host', [], str) self.tsc_port = self.settings.value('tsc_port', [], str) if self.tsc_host: diag_ui.serverLineEdit.setText(self.tsc_host) if self.tsc_port: diag_ui.portLineEdit.setText(self.tsc_port) diag_ui.buttonBox.accepted.connect(partial(self.connectToTSC, diag_ui)) diag.exec_() def connectToTSC(self, dui): host = dui.serverLineEdit.text() port = dui.portLineEdit.text() self.showMessage('Connecting to TSC...') self.socketPacketReceiver = StixSocketPacketReceiver(host, int(port)) self.socketPacketReceiver.packetArrival.connect(self.onPacketArrival) self.socketPacketReceiver.error.connect(self.onDataReaderError) self.socketPacketReceiver.info.connect(self.onDataReaderInfo) self.socketPacketReceiver.warn.connect(self.onDataReaderWarn) self.socketPacketReceiver.start() def onPacketArrival(self, packets): if packets: self.onDataReady(packets, clear=False, show_stat=True) def onLoadMongoDBTriggered(self): diag = QtWidgets.QDialog() diag_ui = mongo_dialog.Ui_Dialog() diag_ui.setupUi(diag) #self.settings = QtCore.QSettings('FHNW', 'stix_parser') self.mongo_server = self.settings.value('mongo_server', [], str) self.mongo_port = self.settings.value('mongo_port', [], str) self.mongo_user = self.settings.value('mongo_user', [], str) self.mongo_pwd = self.settings.value('mongo_pwd', [], str) if self.mongo_server: diag_ui.serverLineEdit.setText(self.mongo_server) if self.mongo_port: diag_ui.portLineEdit.setText(self.mongo_port) if self.mongo_user: diag_ui.userLineEdit.setText(self.mongo_user) if self.mongo_pwd: diag_ui.pwdLineEdit.setText(self.mongo_pwd) diag_ui.pushButton.clicked.connect( partial(self.loadRunsFromMongoDB, diag_ui)) diag_ui.buttonBox.accepted.connect( partial(self.loadDataFromMongoDB, diag_ui, diag)) diag.exec_() def loadRunsFromMongoDB(self, dui): server = dui.serverLineEdit.text() port = dui.portLineEdit.text() user = dui.userLineEdit.text() pwd = dui.pwdLineEdit.text() self.showMessage('saving setting...') if self.mongo_server != server: self.settings.setValue('mongo_server', server) if self.mongo_port != port: self.settings.setValue('mongo_port', port) if self.mongo_user != user: self.settings.setValue('mongo_user', user) if self.mongo_pwd != pwd: self.settings.setValue('mongo_pwd', pwd) self.showMessage('connecting Mongo database ...') self.mdb = mgdb.MongoDB(server, int(port), user, pwd) if not self.mdb.is_connected(): return dui.treeWidget.clear() self.showMessage('Fetching data...') for run in self.mdb.get_runs(): root = QtWidgets.QTreeWidgetItem(dui.treeWidget) root.setText(0, str(run['_id'])) root.setText(1, run['filename']) root.setText(2, run['date']) root.setText(3, str(run['start'])) root.setText(4, str(run['end'])) def loadDataFromMongoDB(self, dui, diag): self.showMessage('Loading packets ...') selected_runs = [] for item in dui.treeWidget.selectedItems(): selected_runs.append(item.text(0)) if not selected_runs: self.showMessage('Run not selected!') if selected_runs: diag.done(0) self.showMessage('Loading data ...!') data = self.mdb.get_packets(selected_runs[0]) if data: self.onDataReady(data, clear=True) else: self.showMessage('No packets found!') # close def onPacketSelected(self, cur, pre): self.current_row = self.packetTreeWidget.currentIndex().row() self.showMessage(('Packet #%d selected' % self.current_row)) self.showPacket(self.current_row) def showPacket(self, row): if not self.data: return header = self.data[row]['header'] total_packets = len(self.data) self.showMessage( ('Packet %d / %d %s ' % (row, total_packets, header['DESCR']))) self.paramTreeWidget.clear() header_root = QtWidgets.QTreeWidgetItem(self.paramTreeWidget) header_root.setText(0, "Header") rows = len(header) for key, val in header.items(): root = QtWidgets.QTreeWidgetItem(header_root) root.setText(0, key) root.setText(1, str(val)) params = self.data[row]['parameters'] param_root = QtWidgets.QTreeWidgetItem(self.paramTreeWidget) param_root.setText(0, "Parameters") self.showParameterTree(params, param_root) self.paramTreeWidget.expandItem(param_root) self.paramTreeWidget.expandItem(header_root) self.current_row = row def showParameterTree(self, params, parent): if not params: return for p in params: root = QtWidgets.QTreeWidgetItem(parent) if not p: continue try: param_name = p['name'] desc = '' #description of parameter if 'desc' in p: desc = p['desc'] if not desc: desc = idb._stix_idb.get_PCF_description(param_name) scos_desc = idb._stix_idb.get_scos_description(param_name) if scos_desc: root.setToolTip(1, scos_desc) root.setText(0, param_name) root.setText(1, desc) root.setText(2, str(p['raw'])) try: root.setToolTip(2, hex(p['raw'][0])) except: pass root.setText(3, str(p['eng'])) if 'children' in p: if p['children']: self.showParameterTree(p['children'], root) except KeyError: self.showMessage( '[Error ]: keyError occurred when adding parameter') self.paramTreeWidget.itemDoubleClicked.connect(self.onTreeItemClicked) def walk(self, name, params, header, ret_x, ret_y, xaxis=0, data_type=0): if not params: return timestamp = header['time'] for p in params: if type(p) is not dict: continue if name == p['name']: values = None #print('data type:{}'.format(data_type)) if data_type == 0: values = p['raw'] else: values = p['eng'] try: yvalue = None if (type(values) is tuple) or (type(values) is list): yvalue = float(values[0]) else: yvalue = float(values) ret_y.append(yvalue) if xaxis == 1: ret_x.append(timestamp) else: self.showMessage(('Can not plot %s ' % str(yvalue))) except Exception as e: self.showMessage(('%s ' % str(e))) if 'children' in p: if p['children']: self.walk(name, p['children'], header, ret_x, ret_y, xaxis, data_type) def onPlotButtonClicked(self): if self.chart: self.chart.removeAllSeries() if not self.data: return self.showMessage('Preparing plot ...') name = self.paramNameEdit.text() packet_selection = self.comboBox.currentIndex() xaxis_type = self.xaxisComboBox.currentIndex() data_type = self.dataTypeComboBox.currentIndex() timestamp = [] self.y = [] packet_id = self.current_row params = self.data[packet_id]['parameters'] header = self.data[packet_id]['header'] current_spid = header['SPID'] if packet_selection == 0: self.walk(name, params, header, timestamp, self.y, xaxis_type, data_type) elif packet_selection == 1: for packet in self.data: header = packet['header'] if packet['header']['SPID'] != current_spid: continue params = packet['parameters'] self.walk(name, params, header, timestamp, self.y, xaxis_type, data_type) self.x = [] if not self.y: self.showMessage('No data points') elif self.y: style = self.styleEdit.text() if not style: style = '-' title = '%s' % str(name) desc = self.descLabel.text() if desc: title += '- %s' % desc self.chart.setTitle(title) ylabel = 'Raw value' xlabel = name if data_type == 1: ylabel = 'Engineering value' if xaxis_type == 0: if packet_selection == 1: xlabel = "Packet #" else: xlabel = "Repeat #" self.x = range(0, len(self.y)) if xaxis_type == 1: self.x = [t - timestamp[0] for t in timestamp] xlabel = 'Time -T0 (s)' if xaxis_type != 2: series = QLineSeries() series2 = None for xx, yy in zip(self.x, self.y): series.append(xx, yy) if 'o' in style: series2 = QScatterSeries() for xx, yy in zip(self.x, self.y): series2.append(xx, yy) self.chart.addSeries(series2) self.chart.addSeries(series) axisX = QValueAxis() axisX.setTitleText(xlabel) axisY = QValueAxis() axisY.setTitleText(ylabel) self.chart.setAxisX(axisX) self.chart.setAxisY(axisY) series.attachAxis(axisX) series.attachAxis(axisY) else: nbins = len(set(self.y)) ycounts, xedges = np.histogram(self.y, bins=nbins) series = QLineSeries() for i in range(0, nbins): meanx = (xedges[i] + xedges[i + 1]) / 2. series.append(meanx, ycounts[i]) self.chart.addSeries(series) axisX = QValueAxis() axisX.setTitleText(name) axisY = QValueAxis() axisY.setTitleText("Counts") self.chart.setAxisY(axisY) self.chart.setAxisX(axisX) series.attachAxis(axisX) series.attachAxis(axisY) self.xlabel = xlabel self.ylabel = ylabel self.chartView.setRubberBand(QChartView.RectangleRubberBand) self.chartView.setRenderHint(QtGui.QPainter.Antialiasing) msg = 'Y length: {}, min-Y: {}, max-Y: {}'.format( len(self.y), min(self.y), max(self.y)) self.showMessage(msg, 1) self.showMessage('The canvas updated!') def plotParameter(self, name=None, desc=None): self.tabWidget.setCurrentIndex(1) if name: self.paramNameEdit.setText(name) if desc: self.descLabel.setText(desc) def onPlotActionClicked(self): self.tabWidget.setCurrentIndex(1) self.plotParameter() def onTreeItemClicked(self, it, col): self.plotParameter(it.text(0), it.text(1))