class ChartWidget(QWidget): def __init__(self, parent=None, ticker="BTC"): super().__init__(parent) uic.loadUi("resource/chart.ui", self) self.ticker = ticker self.viewLimit = 128 self.priceData = QLineSeries() self.priceChart = QChart() self.priceChart.addSeries(self.priceData) self.priceChart.legend().hide() # ----------------- 추 가 ------------------ axisX = QDateTimeAxis() axisX.setFormat("hh:mm:ss") axisX.setTickCount(4) dt = QDateTime.currentDateTime() axisX.setRange(dt, dt.addSecs(self.viewLimit)) axisY = QValueAxis() axisY.setVisible(False) self.priceChart.addAxis(axisX, Qt.AlignBottom) self.priceChart.addAxis(axisY, Qt.AlignRight) self.priceData.attachAxis(axisX) self.priceData.attachAxis(axisY) self.priceChart.layout().setContentsMargins(0, 0, 0, 0) # ------------------------------------------ self.priceView.setChart(self.priceChart) self.priceView.setRenderHints(QPainter.Antialiasing)
def update_particles(particles): animation_chart = QChart() reals = QScatterSeries() pen_reals = reals.pen() pen_reals.setBrush(QtGui.QColor("white")) reals.setMarkerSize(5) reals.setColor(QtGui.QColor("red")) reals.setPen(pen_reals) for particle in particles: reals.append(particle, 0) animation_chart.addSeries(reals) animation_chart.setBackgroundBrush(QtGui.QColor(41, 43, 47)) animation_chart.createDefaultAxes() animation_chart.legend().hide() animation_chart.setContentsMargins(-10, -10, -10, -10) animation_chart.layout().setContentsMargins(0, 0, 0, 0) animation_chart.axisX().setTickCount(17) animation_chart.axisY().setTickCount(3) animation_chart.axisX().setLabelsColor(QtGui.QColor("white")) animation_chart.axisX().setGridLineColor(QtGui.QColor("grey")) animation_chart.axisX().setRange(-4, 12) animation_chart.axisY().setRange(-1, 1) animation_chart.axisY().setLabelsColor(QtGui.QColor("white")) animation_chart.axisY().setGridLineColor(QtGui.QColor("grey")) form.widget_animation.setChart(animation_chart)
class ErrorLineChart(QFrame): def __init__(self, nseries=1, series_names=None): super().__init__() if nseries < 1: raise ValueError( 'The number of serieses must be larger than zero.') self.nseries = nseries self.series_names = series_names layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) self.setMinimumHeight(110) self.setMinimumWidth(400) self.serieses = [QLineSeries() for _ in range(self.nseries)] self.chart = QChart() if self.series_names is None: self.chart.legend().hide() for idx, series in enumerate(self.serieses): self.chart.addSeries(series) if self.series_names is not None: series.setName(self.series_names[idx]) self.chart.createDefaultAxes() self.chart.layout().setContentsMargins(0, 0, 0, 0) # self.chart.setTheme(QChart.ChartThemeDark) self.chart.axisY().setTickCount(3) chart_view = QChartView(self.chart) chart_view.setRenderHint(QPainter.Antialiasing) layout.addWidget(chart_view) self.x_max = 2 self.y_pts = list() def append_point(self, x, y, series_idx=0): self.serieses[series_idx].append(x, y) self.x_max = max(x, self.x_max) self.y_pts.append(y) if self.x_max > 100: self.chart.axisX().setRange(self.x_max - 100, self.x_max) y_max = max(self.y_pts[-100:]) self.serieses[series_idx].remove(self.x_max - 100, self.y_pts[self.x_max - 101]) else: self.chart.axisX().setRange(1, self.x_max) y_max = max(self.y_pts) self.chart.axisY().setRange(0, y_max + y_max / 5) def clear(self): self.chart.removeAllSeries() self.serieses = [QLineSeries() for _ in range(self.nseries)] for idx, series in enumerate(self.serieses): self.chart.addSeries(series) if self.series_names is not None: series.setName(self.series_names[idx]) self.chart.createDefaultAxes() self.chart.axisY().setTickCount(3) self.x_max = 2 self.y_pts = list()
def 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 ChartWidget(QWidget): def __init__(self, parent=None, ticker="BTCUSDT"): super().__init__(parent) uic.loadUi("resource/chart.ui", self) self.ticker = ticker self.viewLimit = 10 self.tm = [] self.priceData = QCandlestickSeries() self.priceData.setDecreasingColor(Qt.red) self.priceData.setIncreasingColor(Qt.green) self.priceChart = QChart() self.priceChart.addSeries(self.priceData) self.priceChart.legend().hide() axisX = QDateTimeAxis() axisX.setFormat("hh:mm:ss") axisX.setTickCount(4) dt = QDateTime.currentDateTime() axisX.setRange(dt, dt.addSecs(self.viewLimit)) axisY = QValueAxis() axisY.setVisible(False) self.priceChart.addAxis(axisX, Qt.AlignBottom) self.priceChart.addAxis(axisY, Qt.AlignRight) self.priceData.attachAxis(axisX) self.priceData.attachAxis(axisY) self.priceChart.layout().setContentsMargins(0, 0, 0, 0) self.priceView.setChart(self.priceChart) self.priceView.setRenderHints(QPainter.Antialiasing) self.pw = PriceWorker(ticker) self.pw.dataSent.connect(self.appendData) self.pw.start() def appendData(self, o, h, l, c): if len(self.tm) == self.viewLimit: self.priceData.remove(0) self.tm.remove(0) dt = QDateTime.currentDateTime() self.priceData.append(QCandlestickSet(o, h, l, c)) print(dt.toMSecsSinceEpoch()) print(type(dt.toMSecsSinceEpoch())) self.tm.append(dt.toMSecsSinceEpoch()) self.__updateAxis() def __updateAxis(self): pvs = self.tm dtStart = QDateTime.fromMSecsSinceEpoch(int(pvs[0])) if len(self.priceData) == self.viewLimit: dtLast = QDateTime.fromMSecsSinceEpoch(int(pvs[-1])) else: dtLast = dtStart.addSecs(self.viewLimit) ax = self.priceChart.axisX() ax.setRange(dtStart, dtLast)
class VLineChartView(QChartView): def __init__(self): super(VLineChartView, self).__init__() self.stocks = read_tick_data() self.category = [ trade_date[4:] for trade_date in self.stocks['trade_date'] ] self.resize(800, 300) self.initChart() def initChart(self): self._chart = QChart(title='数量') self._chart.setAnimationOptions(QChart.SeriesAnimations) series = QStackedBarSeries() series.setName('数量') bar_red = QBarSet('red') bar_red.setColor(Qt.red) bar_green = QBarSet('green') bar_green.setColor(Qt.green) for _, stock in self.stocks.iterrows(): if stock['open'] < stock['close']: bar_red.append(stock['vol'] / 100) bar_green.append(0) else: bar_red.append(0) bar_green.append(stock['vol'] / 100) series.append(bar_red) series.append(bar_green) self._chart.addSeries(series) self._chart.createDefaultAxes() self._chart.setLocalizeNumbers(True) axis_x = self._chart.axisX() axis_y = self._chart.axisY() axis_x.setGridLineVisible(False) axis_y.setGridLineVisible(False) axis_y.setLabelFormat("%.2f") axis_x.setCategories(self.category) max_p = self.stocks[[ 'vol', ]].stack().max() / 100 + 10 min_p = self.stocks[[ 'vol', ]].stack().min() / 100 - 10 axis_y.setRange(min_p, max_p) # chart的图例 legend = self._chart.legend() legend.hide() # 设置图例由Series来决定样式 # legend.setMarkerShape(QLegend.MarkerShapeFromSeries) self.setChart(self._chart) self._chart.layout().setContentsMargins(0, 0, 0, 0) # self._chart.setMargins(QMargins(0, 0, 0, 0)) self._chart.setBackgroundRoundness(0)
class ChartWidget(QWidget): def __init__(self, parent=None, ticker="BTC"): super().__init__(parent) uic.loadUi("chart.ui", self) self.ticker = ticker self.viewLimit = 128 self.priceData = QLineSeries() self.priceChart = QChart() self.priceChart.addSeries(self.priceData) self.priceChart.legend().hide() axisX = QDateTimeAxis() axisX.setFormat("hh:mm:ss") axisX.setTickCount(4) dt = QDateTime.currentDateTime() axisX.setRange(dt, dt.addSecs(self.viewLimit)) axisY = QValueAxis() axisY.setVisible(False) self.priceChart.addAxis(axisX, Qt.AlignBottom) self.priceChart.addAxis(axisY, Qt.AlignRight) self.priceData.attachAxis(axisX) self.priceData.attachAxis(axisY) self.priceChart.layout().setContentsMargins(0, 0, 0, 0) self.priceView.setChart(self.priceChart) self.priceView.setRenderHints(QPainter.Antialiasing) # ----------------- 추 가 ------------------ self.pw = PriceWorker(ticker) self.pw.dataSent.connect(self.appendData) self.pw.start() # ------------------------------------------ def appendData(self, currPirce): if len(self.priceData) == self.viewLimit: self.priceData.remove(0) dt = QDateTime.currentDateTime() self.priceData.append(dt.toMSecsSinceEpoch(), currPirce) self.__updateAxis() def __updateAxis(self): pvs = self.priceData.pointsVector() dtStart = QDateTime.fromMSecsSinceEpoch(int(pvs[0].x())) if len(self.priceData) == self.viewLimit: dtLast = QDateTime.fromMSecsSinceEpoch(int(pvs[-1].x())) else: dtLast = dtStart.addSecs(self.viewLimit) ax = self.priceChart.axisX() ax.setRange(dtStart, dtLast) ay = self.priceChart.axisY() dataY = [v.y() for v in pvs] ay.setRange(min(dataY), max(dataY))
class 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 test_generations(): range_a = float(str(form.input_a_test.text())) range_b = float(str(form.input_b_test.text())) precision = int(str(form.input_d_test.text())) generations = int(str(form.input_generations_test.text())) app.setOverrideCursor(QtCore.Qt.WaitCursor) start = time() result = test_generation(range_a, range_b, precision, generations) app.restoreOverrideCursor() chart = QChart() series = QLineSeries() form.test_table.setRowCount(0) form.test_table.insertRow(0) item = QtWidgets.QTableWidgetItem("iteracje") item.setTextAlignment(QtCore.Qt.AlignCenter) form.test_table.setItem(0, 0, item) item = QtWidgets.QTableWidgetItem("wystąpienia") item.setTextAlignment(QtCore.Qt.AlignCenter) form.test_table.setItem(0, 1, item) item = QtWidgets.QTableWidgetItem("%") item.setTextAlignment(QtCore.Qt.AlignCenter) form.test_table.setItem(0, 2, item) for i in range(0, generations): percent = sum(result[:i+1])/100000*100 series.append(i+1, percent) form.test_table.insertRow(i+1) form.test_table.setItem(i+1, 0, QtWidgets.QTableWidgetItem(str(i+1))) form.test_table.setItem(i+1, 1, QtWidgets.QTableWidgetItem(str(result[i]))) form.test_table.setItem(i+1, 2, QtWidgets.QTableWidgetItem(str(round(percent, 2)))) chart.addSeries(series) chart.setBackgroundBrush(QtGui.QColor(41, 43, 47)) chart.createDefaultAxes() chart.legend().hide() chart.setContentsMargins(-10, -10, -10, -10) chart.layout().setContentsMargins(0, 0, 0, 0) chart.axisX().setTickCount(10) chart.axisY().setRange(0, 100) chart.axisY().setTickCount(11) chart.axisX().setLabelsColor(QtGui.QColor("white")) chart.axisY().setLabelsColor(QtGui.QColor("white")) form.widget_test.setChart(chart)
def make_cake( self ): score_1 = self.players[ 0 ].score score_2 = self.players[ 1 ].score series = QPieSeries() free_score = 120 - score_1 - score_2 series.append( f"{free_score}", free_score ) series.append( f"{score_1}", score_1 ) series.append( f"{score_2}", score_2 ) series.setPieStartAngle( 3 * score_2 ) series.setPieEndAngle( 3 * score_2 + 360 ) slices = series.slices() for slice in slices: slice.setLabelVisible( True ) if slice.angleSpan() < 45: slice.setLabelPosition( QPieSlice.LabelPosition.LabelInsideNormal ) elif slice.angleSpan() < 90: slice.setLabelPosition( QPieSlice.LabelPosition.LabelInsideTangential ) else: slice.setLabelPosition( QPieSlice.LabelPosition.LabelInsideHorizontal ) slice.setLabelColor( QColor( "#00000" ) ) slice.setLabelFont( QFont( "Fira Sans", 60, weight=QFont.Weight.Black ) ) slice.setBorderWidth( 0 ) slices[ 0 ].setLabelPosition( QPieSlice.LabelPosition.LabelInsideHorizontal ) if score_1 < 10: slices[ 1 ].setLabelFont( QFont( "Fira Sans", score_1 * 6, weight=QFont.Weight.Black ) ) if score_2 < 10: slices[ 2 ].setLabelFont( QFont( "Fira Sans", score_2 * 6, weight=QFont.Weight.Black ) ) slices[ 0 ].setLabelColor( QColor( "#FFFFFF" ) ) slices[ 0 ].setColor( remaining_points_color ) slices[ 1 ].setColor( player_1.color ) slices[ 2 ].setColor( player_2.color ) chart = QChart() chart.legend().hide() chart.addSeries( series ) chart.createDefaultAxes() chart.setBackgroundVisible( False ) chart.setContentsMargins( -120, -120, -120, -120 ) chart.layout().setContentsMargins( 0, 0, 0, 0 ) chart_view = QChartView( chart ) chart_view.setRenderHint( QPainter.Antialiasing ) return chart_view
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)
def __init__(self): super().__init__() # window size self.setMinimumSize(600, 400) df = pyupbit.get_ohlcv("KRW-BTC") # data series = QLineSeries() for index in df.index: close = df.loc[index, 'close'] dt = index.timestamp() * 1000 # msecs series.append(dt, close) # chart object chart = QChart() chart.legend().hide() chart.addSeries(series) # data feeding # axis axis_x = QDateTimeAxis() axis_x.setFormat("yyyy-MM-dd") chart.addAxis(axis_x, Qt.AlignBottom) series.attachAxis(axis_x) axis_y = QValueAxis() axis_y.setLabelFormat("%i") chart.addAxis(axis_y, Qt.AlignLeft) series.attachAxis(axis_y) # margin chart.layout().setContentsMargins(0, 0, 0, 0) # displaying chart chart_view = QChartView(chart) chart_view.setRenderHint(QPainter.Antialiasing) self.setCentralWidget(chart_view)
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))
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 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 MyWindow(QMainWindow): def __init__(self): super().__init__() # thread self.worker = Worker() self.worker.price.connect(self.get_price) self.worker.start() # window size self.setMinimumSize(600, 400) # data self.series = QLineSeries() # chart object self.chart = QChart() self.chart.legend().hide() self.chart.addSeries(self.series) # data feeding # axis axis_x = QDateTimeAxis() axis_x.setFormat("hh:mm:ss") dt = QDateTime.currentDateTime() axis_x.setRange(dt, dt.addSecs(128)) self.chart.addAxis(axis_x, Qt.AlignBottom) self.series.attachAxis(axis_x) axis_y = QValueAxis() axis_y.setLabelFormat("%i") self.chart.addAxis(axis_y, Qt.AlignLeft) self.series.attachAxis(axis_y) # margin self.chart.layout().setContentsMargins(0, 0, 0, 0) # displaying chart chart_view = QChartView(self.chart) chart_view.setRenderHint(QPainter.Antialiasing) self.setCentralWidget(chart_view) @pyqtSlot(float) def get_price(self, cur_price): if len(self.series) == 128: self.series.remove(0) # delete first data # append current price dt = QDateTime.currentDateTime() ts = dt.toMSecsSinceEpoch() self.series.append(ts, cur_price) print(ts, cur_price) # update asis data = self.series.pointsVector() first_ts = data[0].x() last_ts = data[-1].x() first_dt = QDateTime.fromMSecsSinceEpoch(first_ts) last_dt = QDateTime.fromMSecsSinceEpoch(last_ts) axis_x = self.chart.axisX() axis_x.setRange(first_dt, last_dt) axis_y = self.chart.axisY() axis_y.setRange(70000000, 71000000)
def bars_stacked(name, table_df, x_bottom_cols, y_left_cols, y_right_cols, legend_labels, tick_count): """ 柱形堆叠图 :param name: 图表名称 :param table_df: 用于画图的pandas DataFrame对象 :param x_bottom_cols: 下轴列索引列表 :param y_left_cols: 左轴列索引列表 :param y_right_cols: 右轴列索引列表 :param legend_labels: 图例名称标签列表 :param tick_count: 横轴刻度标签数 :return: QChart实例 """ """ 过滤轴 """ for y_left_col in y_left_cols: if is_datetime64_any_dtype(table_df[y_left_col]): # 如果是时间轴 y_left_cols.remove(y_left_col) for y_right_col in y_right_cols: if is_datetime64_any_dtype(table_df[y_right_col]): # 如果是时间轴 y_right_cols.remove(y_right_col) # 将x轴转为字符串 x_bottom = x_bottom_cols[0] # x轴列 if is_datetime64_any_dtype(table_df[x_bottom]): # 如果x轴是时间轴 table_df[x_bottom] = table_df[x_bottom].apply( lambda x: x.strftime('%Y-%m-%d')) else: # x轴非时间轴 table_df[x_bottom] = table_df[x_bottom].apply(lambda x: str(x)) font = QFont() # 轴文字风格 font.setPointSize(7) # 轴标签文字大小 chart = QChart() # 图表实例 chart.layout().setContentsMargins(0, 0, 0, 0) # chart的外边距 chart.setMargins(QMargins(15, 5, 15, 0)) # chart内边距 chart.setTitle(name) # chart名称 """ x轴 """ axis_x_bottom = QCategoryAxis( chart, labelsPosition=QCategoryAxis.AxisLabelsPositionOnValue) axis_x_bottom.setLabelsAngle(-90) # 逆时针旋转90度 axis_x_bottom.setLabelsFont(font) # 设置字体样式 axis_x_bottom.setGridLineVisible(False) # 竖向连接线不可见 # chart.addAxis(axis_x_bottom, Qt.AlignBottom) # 加入坐标x轴 has_x_labels = False # 收集x轴刻度的开关 chart.x_labels = list() # 绑定chart一个x轴的刻度列表 """ 左Y轴 """ axis_y_left = QValueAxis() axis_y_left.setLabelsFont(font) axis_y_left.setLabelFormat('%i') chart.addAxis(axis_y_left, Qt.AlignLeft) # 图表加入左轴 """ 右Y轴 """ axis_y_right = QValueAxis() axis_y_right.setLabelsFont(font) axis_y_right.setLabelFormat('%.2f') chart.addAxis(axis_y_right, Qt.AlignRight) # 记录各轴的最值 x_bottom_min, x_bottom_max = 0, table_df.shape[0] y_left_min, y_left_max = 0, 0 y_right_min, y_right_max = 0, 0 # 柱形图 left_bars = QBarSeries() """ 根据左轴画柱形 """ for y_left_col in y_left_cols: # 根据左轴画折线 # 计算做轴的最值用于设置范围 table_df[y_left_col] = table_df[y_left_col].apply( covert_float) # y轴列转为浮点数值 # 获取最值 y_min, y_max = table_df[y_left_col].min(), table_df[y_left_col].max() if y_min < y_left_min: y_left_min = y_min if y_max > y_left_max: y_left_max = y_max # 取得图表的源数据作柱形 left_bar_data = table_df.iloc[:, [x_bottom, y_left_col]] bar = QBarSet(legend_labels[y_left_col]) bar.setPen(QPen(Qt.transparent)) # 设置画笔轮廓线透明(数据量大会出现空白遮住柱形) for index, point_item in enumerate(left_bar_data.values.tolist()): bar.append(point_item[1]) if not has_x_labels: chart.x_labels.append(point_item[0]) has_x_labels = True # 关闭添加轴标签 left_bars.append(bar) # 柱形加入系列 left_bars.attachAxis(axis_y_left) # 左轴的范围 axis_y_left.setRange(y_left_min, y_left_max) """ 根据右轴画柱形 """ right_bars = QBarSeries() for y_right_col in y_right_cols: # 根据右轴画柱形 # 计算做轴的最值用于设置范围 table_df[y_right_col] = table_df[y_right_col].apply( covert_float) # y轴列转为浮点数值 # 获取最值 y_min, y_max = table_df[y_right_col].min(), table_df[y_right_col].max() if y_min < y_right_min: y_right_min = y_min if y_max > y_right_max: y_right_max = y_max # 取得图线的源数据作折线图 right_bar_data = table_df.iloc[:, [x_bottom, y_right_col]] bar = QBarSet(legend_labels[y_right_col]) bar.setPen(QPen(Qt.transparent)) for position_index, point_item in enumerate( right_bar_data.values.tolist()): bar.append(point_item[1]) # 取出源数据后一条线就2列数据 right_bars.append(bar) right_bars.attachAxis(axis_x_bottom) right_bars.attachAxis(axis_y_right) # 右轴范围 axis_y_right.setRange(y_right_min, y_right_max) chart.addSeries(left_bars) # 左轴的柱形图加入图表 # print(right_bars.count()) if right_bars.count() != 0: # 为空时加入会导致空位 chart.addSeries(right_bars) chart.setAxisX(axis_x_bottom, left_bars) # 关联设置x轴 # 横轴标签设置 x_bottom_interval = int(x_bottom_max / (tick_count - 1)) if x_bottom_interval == 0: for i in range(0, x_bottom_max): axis_x_bottom.append(chart.x_labels[i], i) else: for i in range(0, x_bottom_max, x_bottom_interval): axis_x_bottom.append(chart.x_labels[i], i) chart.legend().setAlignment(Qt.AlignBottom) return chart
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 MainWindow(QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.pixelRow = QLabel() self.pixelRow.setFixedSize(20, nbPixels) self.matrixDisplay = QTextEdit() self.matrixDisplay.setFixedSize(165, 180) self.valuesDisplay = QTextEdit() self.valuesDisplay.setFixedSize(50, nbPixels) # CHART self.serie = QLineSeries() self.chart = QChart() self.chart.addSeries(self.serie) self.chart.legend().hide() self.chart.layout().setContentsMargins(0, 0, 0, 0) self.xAxis = QValueAxis() self.xAxis.setTitleText("Height") self.xAxis.setLabelFormat("%d") self.xAxis.setRange(0, nbPixels) self.yAxis = QValueAxis() self.yAxis.setLabelFormat("%d") self.yAxis.setTitleText("Distance ") self.yAxis.setRange(0, couleurMax) self.chart.setAxisX(self.xAxis) self.chart.setAxisY(self.yAxis) self.serie.attachAxis(self.xAxis) self.serie.attachAxis(self.yAxis) self.chart.setTitle("Profile line") self.chartView = QChartView(self) self.chartView.setChart(self.chart) self.chartView.rotate(90) self.chartView.setGeometry(20, 195, 450, 400) #end CHART self.timer = QTimer(self) self.timer.setInterval(periode) self.timer.start self.timer.timeout.connect(self.refresh) self.startButton = QPushButton("START") self.startButton.setDefault(True) self.startButton.clicked.connect(self.startTimer) self.startButton.setFixedSize(100, 50) self.stopButton = QPushButton("STOP") self.stopButton.setDefault(True) self.stopButton.clicked.connect(self.stopTimer) self.stopButton.setFixedSize(100, 50) topLayout = QGridLayout() topLayout.addWidget(self.startButton, 0, 0) topLayout.addWidget(self.stopButton, 1, 0) topLayout.addWidget(self.matrixDisplay, 0, 1, 2, 1) mainLayout = QGridLayout() mainLayout.addLayout(topLayout, 0, 0) mainLayout.addWidget(self.pixelRow, 0, 1, 2, 1) mainLayout.addWidget(self.valuesDisplay, 0, 2, 2, 1) mainLayout.addWidget(self.chartView, 1, 0) mainwidget = QWidget() mainwidget.setLayout(mainLayout) self.setCentralWidget(mainwidget) self.setWindowTitle("Minecraft Depth Map") def startTimer(self): self.timer.start() def stopTimer(self): self.timer.stop() def refresh(self): self.serie.clear() self.valuesDisplay.clear() screen = app.primaryScreen() #grabWindow(wID, x, y, w, h) pix = QPixmap( screen.grabWindow(0, int((screenW * 3) / 4) - 10, int((screenH - nbPixels) / 2), 20, nbPixels)) self.pixelRow.setPixmap(pix) img = QImage(pix.toImage()) array = [0 for i in range(nbBlocsH)] for i in range(nbBlocsH): y = nbPixels - (i * (nbPixels / nbBlocsH) + (nbPixels / (2 * nbBlocsH))) colorvalue = 255 - QColor(img.pixel(10, y)).black() self.valuesDisplay.append(str(colorvalue)) self.valuesDisplay.append("\n") self.serie.append(y, colorvalue) #convert colors from 0->couleurMax to 0->nbBlocsD if colorvalue > couleurMax: colorvalue = nbBlocsD elif colorvalue < couleurMin: colorvalue = 0 else: colorvalue = int(colorvalue / (couleurMax / nbBlocsD)) array[i] = colorvalue self.convertToMatrix(array) def convertToMatrix(self, array): matrix = [[0 for j in range(nbBlocsD)] for i in range(nbBlocsH)] for i in range(nbBlocsH): if array[i] < nbBlocsD: matrix[i][array[i]] = 1 if i < (nbBlocsH - 1): if array[i + 1] > (array[i] + 1): for j in range(array[i] + 1, min(nbBlocsD, array[i + 1])): matrix[i][j] = 1 if i > 0: if array[i - 1] > (array[i] + 1): for j in range(array[i] + 1, min(nbBlocsD, array[i - 1])): matrix[i][j] = 1 #test1Scalable.convertToHexa(motifF,nbBlocsD,nbBlocsH) # es-ce bien la fonction a utiliser pour l'affichage ? self.displayMatrix(matrix) def displayMatrix(self, matrix): self.matrixDisplay.clear() for j in range(nbBlocsD - 1, -1, -1): line = "" for i in range(nbBlocsH): line = line + str(matrix[i][j]) + " " #self.matrixDisplay.append( line) #self.matrixDisplay.append( str(matrix[i][j]) + " ") #self.matrixDisplay.moveCursor( QTextCursor.EndOfLine ) self.matrixDisplay.append(line)
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)
def run_evolution(): range_a = float(str(form.input_a.text())) range_b = float(str(form.input_b.text())) precision = int(str(form.input_d.text())) generations_number = int(str(form.input_t.text())) app.setOverrideCursor(QtCore.Qt.WaitCursor) best_reals, best_binary, best_fxs, local_fxs, _, _ = evolution(range_a, range_b, precision, generations_number, form.checkBox.isChecked()) form.best_table.item(1,0).setText(str(best_reals[len(local_fxs)-1])) form.best_table.item(1,1).setText(''.join(map(str, best_binary[len(local_fxs)-1]))) form.best_table.item(1,2).setText(str(best_fxs[len(local_fxs)-1])) chart = QChart() bests = QLineSeries() pen_best = bests.pen() pen_best.setWidth(1) pen_best.setBrush(QtGui.QColor("red")) bests.setPen(pen_best) for i in range(0, len(local_fxs)): if len(local_fxs[i]) - 1 == 0: fxs = QScatterSeries() fxs.append(i + 0.99, local_fxs[i][0]) pen = fxs.pen() color = QtGui.QColor(random.randint(50,255), random.randint(50,255), random.randint(50,255)) fxs.setColor(color) pen.setColor(color) fxs.setPen(pen) fxs.setMarkerSize(5) else: fxs = QLineSeries() tick = 1 / (len(local_fxs[i]) - 1) for j in range(len(local_fxs[i])): fxs.append(i + j * tick, local_fxs[i][j]) pen = fxs.pen() pen.setWidth(1) pen.setBrush(QtGui.QColor(random.randint(50,255), random.randint(50,255), random.randint(50,255))) fxs.setPen(pen) bests.append(i+1, best_fxs[i]) chart.addSeries(fxs) chart.addSeries(bests) chart.setBackgroundBrush(QtGui.QColor(41, 43, 47)) chart.createDefaultAxes() chart.legend().hide() chart.setContentsMargins(-10, -10, -10, -10) chart.layout().setContentsMargins(0, 0, 0, 0) chart.axisX().setTickCount(11) chart.axisX().setLabelsColor(QtGui.QColor("white")) chart.axisX().setGridLineColor(QtGui.QColor("grey")) chart.axisX().setLabelFormat("%i") chart.axisY().setRange(-2,2) chart.axisY().setLabelsColor(QtGui.QColor("white")) chart.axisY().setGridLineColor(QtGui.QColor("grey")) form.widget.setChart(chart) with open('best_history.csv', 'w', newline='', encoding='utf8') as history_csvfile: history_writer = csv.writer( history_csvfile, delimiter=';', dialect=csv.excel) history_writer.writerow(['Parametry']) history_writer.writerow(['Precyzja: 10^-%d' % precision]) history_writer.writerow(['Iteracje: %d' % generations_number]) history_writer.writerow(['', 'real', 'bin', 'f(real)']) for index, generation in enumerate(range(generations_number)): history_writer.writerow([index, best_reals[generation], best_binary[generation], best_fxs[generation]]) app.restoreOverrideCursor()
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()
def lines_stacked(name, table_df, x_bottom_cols, y_left_cols, y_right_cols, legend_labels, tick_count): """ 折线堆叠图 :param name: 图表名称 :param table_df: 用于画图的pandas DataFrame对象 :param x_bottom_cols: 下轴列索引列表 :param y_left_cols: 左轴列索引列表 :param y_right_cols: 右轴列索引列表 :param legend_labels: 图例名称标签列表 :param tick_count: 横轴刻度标签数 :return: QChart实例 """ """ 过滤轴 """ for y_left_col in y_left_cols: if is_datetime64_any_dtype(table_df[y_left_col]): # 如果是时间轴 y_left_cols.remove(y_left_col) for y_right_col in y_right_cols: if is_datetime64_any_dtype(table_df[y_right_col]): # 如果是时间轴 y_right_cols.remove(y_right_col) # 将x轴转为字符串 x_bottom = x_bottom_cols[0] # x轴列 if is_datetime64_any_dtype(table_df[x_bottom]): # 如果x轴是时间轴 table_df[x_bottom] = table_df[x_bottom].apply( lambda x: x.strftime('%Y-%m-%d')) else: # x轴非时间轴 table_df[x_bottom] = table_df[x_bottom].apply(lambda x: str(x)) font = QFont() # 轴文字风格 font.setPointSize(7) # 轴标签文字大小 chart = QChart() # 图表实例 chart.layout().setContentsMargins(0, 0, 0, 0) # chart的外边距 chart.setMargins(QMargins(15, 5, 15, 0)) # chart内边距 chart.setTitle(name) # chart名称 """ x轴 """ axis_x_bottom = QCategoryAxis( chart, labelsPosition=QCategoryAxis.AxisLabelsPositionOnValue) axis_x_bottom.setLabelsAngle(-90) # 逆时针旋转90度 axis_x_bottom.setLabelsFont(font) # 设置字体样式 axis_x_bottom.setGridLineVisible(False) # 竖向连接线不可见 chart.addAxis(axis_x_bottom, Qt.AlignBottom) # 加入坐标x轴 has_x_labels = False # 收集x轴刻度的开关 chart.x_labels = list() # 绑定chart一个x轴的刻度列表 """ 左Y轴 """ axis_y_left = QValueAxis() axis_y_left.setLabelsFont(font) axis_y_left.setLabelFormat('%i') chart.addAxis(axis_y_left, Qt.AlignLeft) # 图表加入左轴 """ 右Y轴 """ axis_y_right = QValueAxis() axis_y_right.setLabelsFont(font) axis_y_right.setLabelFormat('%.2f') chart.addAxis(axis_y_right, Qt.AlignRight) # 记录各轴的最值 x_bottom_min, x_bottom_max = 0, table_df.shape[0] y_left_min, y_left_max = 0, 0 y_right_min, y_right_max = 0, 0 """ 根据左轴画折线 """ for y_left_col in y_left_cols: # 根据左轴画折线 # 计算做轴的最值用于设置范围 table_df[y_left_col] = table_df[y_left_col].apply( covert_float) # y轴列转为浮点数值 # 获取最值 y_min, y_max = table_df[y_left_col].min(), table_df[y_left_col].max() if y_min < y_left_min: y_left_min = y_min if y_max > y_left_max: y_left_max = y_max # 取得图线的源数据作折线图 left_line_data = table_df.iloc[:, [x_bottom, y_left_col]] series = QLineSeries() series.setName(legend_labels[y_left_col]) for position_index, point_item in enumerate( left_line_data.values.tolist()): series.append(position_index, point_item[1]) # 取出源数据后一条线就2列数据 # 收集坐标标签 if not has_x_labels: chart.x_labels.append(point_item[0]) chart.addSeries(series) # 折线入图 series.attachAxis(axis_x_bottom) series.attachAxis(axis_y_left) # 左轴范围 axis_y_left.setRange(y_left_min, y_left_max) """ 根据右轴画折线 """ for y_right_col in y_right_cols: # 根据右轴画折线 # 计算做轴的最值用于设置范围 table_df[y_right_col] = table_df[y_right_col].apply( covert_float) # y轴列转为浮点数值 # 获取最值 y_min, y_max = table_df[y_right_col].min(), table_df[y_right_col].max() if y_min < y_right_min: y_right_min = y_min if y_max > y_right_max: y_right_max = y_max # 取得图线的源数据作折线图 left_line_data = table_df.iloc[:, [x_bottom, y_right_col]] series = QLineSeries() series.setName(legend_labels[y_right_col]) for position_index, point_item in enumerate( left_line_data.values.tolist()): series.append(position_index, point_item[1]) # 取出源数据后一条线就2列数据 chart.addSeries(series) series.attachAxis(axis_x_bottom) series.attachAxis(axis_y_right) # 右轴范围 axis_y_right.setRange(y_right_min, y_right_max) # 设置下轴刻度标签 # print('x轴最大值', x_bottom_max) x_bottom_interval = int(x_bottom_max / (tick_count - 1)) if x_bottom_interval == 0: for i in range(0, x_bottom_max): axis_x_bottom.append(chart.x_labels[i], i) else: for i in range(0, x_bottom_max, x_bottom_interval): axis_x_bottom.append(chart.x_labels[i], i) chart.legend().setAlignment(Qt.AlignBottom) return chart
class 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 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)
def run_evolution(): range_a = float(str(form.input_a.text())) range_b = float(str(form.input_b.text())) precision = int(str(form.input_d.text())) particles_number = int(str(form.input_particles.text())) iterations = int(str(form.input_iterations.text())) c1_weight = float(str(form.input_c1.text())) c2_weight = float(str(form.input_c2.text())) c3_weight = float(str(form.input_c3.text())) neighborhood_distance = float(str(form.input_neighborhood.text())) app.setOverrideCursor(QtCore.Qt.WaitCursor) best_real, best_fx, best_fxs, avg_fxs, min_fxs, particles = evolution( range_a, range_b, precision, particles_number, iterations, c1_weight, c2_weight, c3_weight, neighborhood_distance) chart = QChart() bests = QLineSeries() avg = QLineSeries() mins = QLineSeries() pen_best = bests.pen() pen_best.setWidth(1) pen_best.setBrush(QtGui.QColor("red")) pen_avg = avg.pen() pen_avg.setWidth(1) pen_avg.setBrush(QtGui.QColor("green")) pen_min = mins.pen() pen_min.setWidth(1) pen_min.setBrush(QtGui.QColor("blue")) bests.setPen(pen_best) avg.setPen(pen_avg) mins.setPen(pen_min) form.best_table.item(1, 0).setText(str(best_real)) form.best_table.item(1, 1).setText(str(best_fx)) for i in range(len(best_fxs)): bests.append(i + 1, best_fxs[i]) avg.append(i + 1, avg_fxs[i]) mins.append(i + 1, min_fxs[i]) chart.addSeries(bests) chart.addSeries(avg) chart.addSeries(mins) chart.setBackgroundBrush(QtGui.QColor(41, 43, 47)) chart.createDefaultAxes() chart.legend().hide() chart.setContentsMargins(-10, -10, -10, -10) chart.layout().setContentsMargins(0, 0, 0, 0) chart.axisX().setTickCount(11) chart.axisX().setLabelsColor(QtGui.QColor("white")) chart.axisX().setGridLineColor(QtGui.QColor("grey")) chart.axisX().setLabelFormat("%i") chart.axisY().setRange(-2, 2) chart.axisY().setLabelsColor(QtGui.QColor("white")) chart.axisY().setGridLineColor(QtGui.QColor("grey")) form.widget.setChart(chart) draw_thread.particles_list = particles draw_thread.start() app.restoreOverrideCursor()
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)
class ChartWidget(QWidget): def __init__(self, parent=None, ticker="BTC"): super().__init__(parent) # chart.ui 파일을 읽어와서 디자인을 적용한다. uic.loadUi("resource/chart.ui", self) self.ticker = ticker self.viewLimit = 120 # 라인 차트로 그릴 데이터의 수를 미리 정의한다. self.priceData = QLineSeries() self.priceChart = QChart() self.priceChart.addSeries(self.priceData) self.priceChart.legend().hide() # 차트의 범례를 숨긴다. axisX = QDateTimeAxis() # PyChart에서 날짜 축을 관리하는 QDateTimeAxis 객체를 생성한다. axisX.setFormat("hh:mm:ss") # 시:분:초 형태로 차트에 표시한다. axisX.setTickCount(4) # 차트에 표시할 날짜의 개수를 4로 지정한다. dt = QDateTime.currentDateTime() # 현재 시간 정보를 QDateTime 객체로 얻어온다. # X축에 출력될 값의 범위를 현재 시간부터 viewLimit(120)초 이후까지 설정한다. # addSecs 메서드는 지정된 초 이후의 시간을 QDateTime으로 반환한다. axisX.setRange(dt, dt.addSecs(self.viewLimit)) axisY = QValueAxis() # 정수를 저장하는 축을 생성하고 축의 레이블을 차트에 표시하지 않는다. axisY.setVisible(False) self.priceChart.addAxis(axisX, Qt.AlignBottom) self.priceChart.addAxis(axisY, Qt.AlignRight) self.priceData.attachAxis(axisX) self.priceData.attachAxis(axisY) # 차트 객체 안에 여백을 최소화해서 차트를 크게 그린다. self.priceChart.layout().setContentsMargins(0, 0, 0, 0) self.priceView.setChart(self.priceChart) self.priceView.setRenderHints( QPainter.Antialiasing) # 차트에 anti-aliasing을 적용한다. # PriceWorker 객체 생성 및 dataSent 이벤트를 연결할 슬롯을 지정한다. self.pw = PriceWorker(ticker) self.pw.dataSent.connect(self.appendData) self.pw.start() # 차트에 그릴 데이터를 입력받는다. def appendData(self, currPrice): # 정해진 데이터 개수만큼 저장되어 있다면 오래된 0번 인덱스의 데이터를 삭제한다. # 삭제 로직이 없다면 저장되는 데이터의 개수가 무한히 증가할 것이다. if len(self.priceData) == self.viewLimit: self.priceData.remove(0) dt = QDateTime.currentDateTime() # append 메서드는 millisecond(ms)를 입력받기 때문에 MSecsSinceEpoch() 메서드로 QDateTime 객체를 millisecond로 변환한다. self.priceData.append(dt.toMSecsSinceEpoch(), currPrice) # 차트의 축 정보를 업데이트 한다. 실시간으로 추가되는 데이터의 위치를 지정한다. self.__updateAxis() def __updateAxis(self): # QLineSerires 객체에 저장된 데이터를 리스트로 얻어온다. # pvs에 저장된 리스트 안에는 QPointF 객체로 위치 정보가 저장되어 있다. pvs = self.priceData.pointsVector() # 가장 오래된 0번 인덱스의 객체를 하나 선택해서 x 좌표에 저장된 값을 가져온다. # ms로 변환해서 들어간 좌표 데이터를 fromMSecsSinceEpoch 메서드를 사용해서 QDateTime 객체로 변환한다. dtStart = QDateTime.fromMSecsSinceEpoch(int(pvs[0].x())) # 데이터가 꽉 차 있다면 최근 시간 정보가 들어 있는 마지막 객체를 선택한다. if len(self.priceData) == self.viewLimit: dtLast = QDateTime.fromMSecsSinceEpoch(int(pvs[-1].x())) # 데이터가 꽉 차 있지 않다면 시작 위치를 기준으로 viewLimit 초 이후까지 출력한다. # 항상 viewLimit 개의 데이터를 출력하는데 사용된다. else: dtLast = dtStart.addSecs(self.viewLimit) # 앞서 얻어온 위치 정보를 보여줄 수 있도록 X 축의 범위를 설정한다. ax = self.priceChart.axisX() ax.setRange(dtStart, dtLast) # QPointF 객체에서 y 좌표를 가져와서 최소값, 최대값으로 Y축에 표시될 범위를 지정한다. ay = self.priceChart.axisY() dataY = [v.y() for v in pvs] ay.setRange(min(dataY), max(dataY)) # QWidget에 정의된 메서드로 UI의 종료 버튼을 누르면 실행된다. # 자식 클래스에서 closeEvent를 재정의해서 종료되기 전 쓰레드를 종료한다. def closeEvent(self, event): self.pw.close()