class CharPlotWidget(QChartView): def __init__(self, parent=None): super().__init__(parent=parent) self.setRenderHint(QPainter.Antialiasing) self._chart = self.chart() self._axis_x = QValueAxis() self._axis_y = QValueAxis() self.series = QLineSeries(self) self._chart.addSeries(self.series) self._chart.addAxis(self._axis_x, Qt.AlignBottom) self._chart.addAxis(self._axis_y, Qt.AlignLeft) self.series.attachAxis(self._axis_x) self.series.attachAxis(self._axis_y) self._axis_x.setTickCount(5) self._axis_x.setRange(0, 10) self._axis_y.setTickCount(5) self._axis_y.setRange(0, 10) self._chart.legend().hide() def plot(self, xs, ys): self.series.replace([QPointF(x, y) for x, y in zip(xs, ys)]) @property def axes_titles(self): return self._axis_x.titleText(), self._axis_y.titleText() @axes_titles.setter def axes_titles(self, value): x, y = value self._axis_x.setTitleText(x) self._axis_y.setTitleText(y) @property def title(self): return self._chart.title() @title.setter def title(self, value): self._chart.setTitle(value) @property def legend(self): return self._chart.legend()
def add_data(self, xdata, ydata, color=None, legend_text=None): curve = QLineSeries() pen = curve.pen() if color is not None: pen.setColor(color) pen.setWidthF(1.5) curve.setPen(pen) # curve.setPointsVisible(True) # curve.setUseOpenGL(True) self.total_samples = max(self.total_samples, len(xdata)) # Decimate xdecimated, ydecimated = self.decimate(xdata, ydata) # Data must be in ms since epoch # curve.append(self.series_to_polyline(xdecimated * 1000.0, ydecimated)) # self.reftime = datetime.datetime.fromtimestamp(xdecimated[0]) # if len(xdecimated) > 0: # xdecimated = xdecimated - xdecimated[0] xdecimated *= 1000 # No decimal expected points = [] for i, _ in enumerate(xdecimated): # TODO hack # curve.append(QPointF(xdecimated[i], ydecimated[i])) points.append(QPointF(xdecimated[i], ydecimated[i])) curve.replace(points) points.clear() if legend_text is not None: curve.setName(legend_text) # Needed for mouse events on series self.chart.setAcceptHoverEvents(True) self.xvalues[self.ncurves] = np.array(xdecimated) # connect signals / slots # curve.clicked.connect(self.lineseries_clicked) # curve.hovered.connect(self.lineseries_hovered) # Add series self.chart.addSeries(curve) self.ncurves += 1 self.update_axes()
class DemoWindow(QMainWindow): def __init__(self, parent=None): super(DemoWindow, self).__init__(parent=parent) self.plotChart = QChart() self.plotChart.legend().hide() self.plotView = QChartView(self.plotChart) self.setCentralWidget(self.plotView) self.plotCurve = QLineSeries() self.plotCurve.setUseOpenGL(True) self.plotCurve.pen().setColor(Qt.red) self.plotChart.addSeries(self.plotCurve) self.plotChart.createDefaultAxes() self.plotChart.axisX().setLabelFormat('%d') self.RecvData = array.array('f') # 存储接收到的传感器数据 self.RecvIndx = 0 self.tmrData = QTimer() # 模拟传感器传送过来数据 self.tmrData.setInterval(3) self.tmrData.timeout.connect(self.on_tmrData_timeout) self.tmrData.start() self.tmrPlot = QTimer() self.tmrPlot.setInterval(100) self.tmrPlot.timeout.connect(self.on_tmrPlot_timeout) self.tmrPlot.start() def on_tmrData_timeout(self): val = math.sin(2 * 3.14 / 500 * self.RecvIndx) self.RecvData.append(val) self.RecvIndx += 1 def on_tmrPlot_timeout(self): self.RecvData = self.RecvData[-1000:] plotData = [] for i, val in enumerate(self.RecvData): plotData.append(QPointF(i, val)) self.plotCurve.replace(plotData) self.plotChart.axisX().setMax(len(plotData)) self.plotChart.axisY().setRange(min(self.RecvData), max(self.RecvData))
def createLineChart(self, dataTable): self.chart.setTitle("Sensor1") series = QLineSeries(self.chart) series2 = QLineSeries(self.chart) series3 = QLineSeries(self.chart) # series2 = QLineSeries(self.chart) # series3 = QLineSeries(self.chart) series.setUseOpenGL(True) series2.setUseOpenGL(True) series3.setUseOpenGL(True) # series2.setUseOpenGL(True) # series3.setUseOpenGL(True) series.replace(self.series.pointsVector()) series2.replace(self.series2.pointsVector()) series3.replace(self.series3.pointsVector()) # series2.replace(self.series2.pointsVector()) # series3.replace(self.series2.pointsVector()) self.chart.removeAllSeries() self.chart.addSeries(series2) self.chart.addSeries(series) self.chart.addSeries(series3)
class PulseGen(QtWidgets.QMainWindow, Ui_MainWindow): GENERATOR_PORT = 5000 def __init__(self, parent=None): super(PulseGen, self).__init__(parent) self.setupUi(self) self.pulse_gen_display = PulseGenDisplay() self.display_data = [0] * 500 self.pulse_height_max = self.pulse_height_dial.maximum() self.pulse_height_min = self.pulse_height_dial.minimum() self.pulse_gen_display.pulse_height = self.pulse_height_max # set pulse height self.pulse_gen_display.pulse_height = self.pulse_height_max self.pulse_height_dial.setValue(self.pulse_gen_display.pulse_height) self.pulse_height_dial.actionTriggered.connect(self.avoid_wrapping) self.pulse_height_dial.valueChanged.connect(self.pulse_height_changed) self.pulse_height_text.setText(str( self.pulse_gen_display.pulse_height)) # don't allow editing self.pulse_height_text.textChanged.connect(self.no_editing) # pulse shape for pulse_form in self.pulse_gen_display.pulse_shape.keys(): self.pulse_shape_combo.addItem(pulse_form) self.pulse_shape_combo.currentIndexChanged.connect( self.pulse_shape_changed) current_pulse_form = self.pulse_shape_combo.currentText() self.pulse_gen_display.pulse_form = self.pulse_gen_display.pulse_shape[ current_pulse_form] print("Current pulse form: " + current_pulse_form + " code: " + str(self.pulse_gen_display.pulse_form)) for pulse_freq in self.pulse_gen_display.frequencies.keys(): self.gen_freq_combo.addItem(pulse_freq) current_pulse_freq = self.gen_freq_combo.currentText() self.pulse_gen_display.pulse_T = self.pulse_gen_display.frequencies[ current_pulse_freq] print("Current pulse T: " + str(self.pulse_gen_display.pulse_T)) self.gen_freq_combo.currentIndexChanged.connect( self.pulse_freq_changed) for display_freq in self.pulse_gen_display.frequencies.keys(): self.display_freq_combo.addItem(display_freq) self.display_freq_combo.currentIndexChanged.connect( self.display_freq_changed) current_display_freq = self.display_freq_combo.currentText() self.pulse_gen_display.display_T = self.pulse_gen_display.frequencies[ current_display_freq] print("Current display T: " + str(self.pulse_gen_display.display_T)) self.start_stop_pb.clicked.connect(self.start_stop_meas) self.connect_pb.clicked.connect(self.connect) self.gen_chart = QChart() self.gen_chart.legend().hide() self.x_axis = QValueAxis() self.x_axis.setMax(500 * self.pulse_gen_display.display_T) self.x_axis.setMin(0) self.y_axis = QValueAxis() self.y_axis.setMax(3.3) self.y_axis.setMin(0) self.gen_chart.addAxis(self.x_axis, Qt.AlignBottom) self.gen_chart.addAxis(self.y_axis, Qt.AlignLeft) self.generator_screen.setChart(self.gen_chart) # display the initial pulse self.pulse_gen_display.calc_pulse() self.pulse_gen_display.get_display_data() self.series = QLineSeries() for i in range(500): self.series.append( i * self.pulse_gen_display.display_T, self.pulse_gen_display.display_data[i] * self.pulse_gen_display.calib) self.gen_chart.addSeries(self.series) self.series.attachAxis(self.x_axis) self.series.attachAxis(self.y_axis) self.display_pulse() self.pulse_height_timer = QTimer() self.pulse_height_timer.setSingleShot(True) self.pulse_height_timer.timeout.connect(self.pulse_height_ready) self.genRunning = False self.actionQuit.triggered.connect(self.quit) self.client_socket = QTcpSocket(self) self.connected = False self.show() def no_editing(self): self.pulse_height_text.setText(str( self.pulse_gen_display.pulse_height)) def avoid_wrapping(self, val): if val == QtWidgets.QAbstractSlider.SliderMove: minDistance = 1 if self.pulse_height_dial.value() == self.pulse_height_max \ and self.pulse_height_dial.sliderPosition()<self.pulse_height_max-minDistance: self.pulse_height_dial.setSliderPosition(self.pulse_height_max) elif self.pulse_height_dial.value() == self.pulse_height_min \ and self.pulse_height_dial.sliderPosition()>self.pulse_height_min+minDistance: self.pulse_height_dial.setSliderPosition(self.pulse_height_min) def pulse_shape_changed(self): ''' if self.genRunning: print("Current shape index: ",self.pulse_shape_combo.currentIndex()) print("current pulse form: ",self.pulse_gen_display.pulse_form) if self.pulse_shape_combo.currentIndex() == self.pulse_gen_display.pulse_form: return QMessageBox.warning(self, 'Pulse generation is active', 'Pulse generator is running\nPlease stop pulse generation before changing parameters') self.pulse_shape_combo.setCurrentIndex(self.pulse_gen_display.pulse_form) return ''' print("pulse shape changed") current_pulse_form = self.pulse_shape_combo.currentText() self.pulse_gen_display.pulse_form = self.pulse_gen_display.pulse_shape[ current_pulse_form] print("Current pulse form: " + current_pulse_form + " code: " + str(self.pulse_gen_display.pulse_form)) self.display_pulse() if not self.connected: return else: pulse_form_msg = "pulse_shape=" + str( self.pulse_gen_display.pulse_form) + "\n" print("Sending: " + pulse_form_msg) self.client_socket.write(bytes(pulse_form_msg, encoding="ascii")) self.client_socket.waitForReadyRead() responseMsg = self.client_socket.readAll() print("reponse msg: ", bytes(responseMsg).decode()) def pulse_freq_changed(self): new_freq = self.gen_freq_combo.currentText() self.pulse_gen_display.pulse_T = self.pulse_gen_display.frequencies[ new_freq] print("Pulse frequency changed to " + new_freq + "T: " + str(self.pulse_gen_display.pulse_T)) self.display_pulse() ''' if self.genRunning: if self.pulse_gen_display.frequencies[self.gen_freq_combo.currentText()] == self.pulse_gen_display.pulse_T: return QMessageBox.warning(self, 'Pulse generation is active', 'Pulse generator is running\nPlease stop pulse generation before changing parameters') index = 0 for freq in self.pulse_gen_display.frequencies.keys(): print("freq: ",freq) print("val: ",self.pulse_gen_display.frequencies[freq]) if self.pulse_gen_display.frequencies[freq] == self.pulse_gen_display.pulse_T: self.gen_freq_combo.setCurrentIndex(index) break index += 1 return ''' if not self.connected: return else: pulse_T_msg = "pulse_T=" + str( self.pulse_gen_display.pulse_T) + "\n" print("Sending: " + pulse_T_msg) self.client_socket.write(bytes(pulse_T_msg, encoding="ascii")) self.client_socket.waitForReadyRead() responseMsg = self.client_socket.readAll() print("reponse msg: ", bytes(responseMsg).decode()) def display_freq_changed(self): new_freq = self.display_freq_combo.currentText() self.pulse_gen_display.display_T = self.pulse_gen_display.frequencies[ new_freq] self.display_pulse() def pulse_height_changed(self): self.pulse_gen_display.pulse_height = self.pulse_height_dial.value() self.pulse_height_text.setText(str( self.pulse_gen_display.pulse_height)) self.display_pulse() self.pulse_height_timer.start(500) def pulse_height_ready(self): print("Timeout") if not self.connected: return else: pulse_height_msg = "pulse_height=" + str( self.pulse_gen_display.pulse_height) + "\n" print("Sending: " + pulse_height_msg) self.client_socket.write(bytes(pulse_height_msg, encoding="ascii")) self.client_socket.waitForReadyRead() responseMsg = self.client_socket.readAll() print("reponse msg: ", bytes(responseMsg).decode()) def connect(self): if self.connected: self.client_socket.close() self.connected = False self.connect_pb.setChecked(False) self.connect_pb.setText("Connect to Pulse Generator") return server_ip = str(self.server_ip_text.text()) port = self.GENERATOR_PORT print("Server IP: ", server_ip) if server_ip.find('xxx') != -1: print("bad IP") QMessageBox.about( self, 'Bad Server IP', 'Please give a correct Server IP\n' 'IP is ' + server_ip) self.connect_pb.setChecked(False) return else: print("Connecting to " + server_ip + ":", port) self.client_socket.connectToHost(server_ip, port) self.client_socket.waitForConnected(1000) if self.client_socket.state() != QTcpSocket.ConnectedState: QMessageBox.warning( self, 'Connection failed', 'Please check IP address and port number\nIs the server running?' ) self.connect_pb.setChecked(False) return print("Connection established") self.connect_pb.setText("Connected") self.client_socket.waitForReadyRead() connectMsg = self.client_socket.readAll() msgstring = bytes(connectMsg).decode() print("Connection message:" + msgstring) print("Send generator settings") pulse_form_msg = "pulse_shape=" + str( self.pulse_gen_display.pulse_form) + "\n" print("Sending: " + pulse_form_msg) self.client_socket.write(bytes(pulse_form_msg, encoding="ascii")) self.client_socket.waitForReadyRead() responseMsg = self.client_socket.readAll() print("reponse msg: ", bytes(responseMsg).decode()) pulse_T_msg = "pulse_T=" + str(self.pulse_gen_display.pulse_T) + "\n" print("Sending: " + pulse_T_msg) self.client_socket.write(bytes(pulse_T_msg, encoding="ascii")) self.client_socket.waitForReadyRead() responseMsg = self.client_socket.readAll() print("reponse msg: ", bytes(responseMsg).decode()) pulse_height_msg = "pulse_height=" + str( self.pulse_gen_display.pulse_height) + "\n" print("Sending: " + pulse_height_msg) self.client_socket.write(bytes(pulse_height_msg, encoding="ascii")) self.client_socket.waitForReadyRead() responseMsg = self.client_socket.readAll() print("reponse msg: ", bytes(responseMsg).decode()) running_msg = 'running?=\n' print("Sending: " + running_msg) self.client_socket.write(bytes(running_msg, encoding="ascii")) self.client_socket.waitForReadyRead() responseMsg = self.client_socket.readAll() response = bytes(responseMsg).decode() print("reponse msg: ", response) if response == 'yes\n': print("task is running") elif response == 'no\n': print("task is not running") else: print("Don't know if task is running") self.connected = True def start_stop_meas(self): if not self.connected: QMessageBox.about( self, 'You must be connected to the generator server\n' 'please connect and try again') return if self.start_stop_pb.isChecked(): print("Start generation") # send current generator settings self.start_stop_pb.setText("Stop Pulse Generation") msg = 'start=\n' print("Sending: " + msg) self.client_socket.write(msg.encode()) self.client_socket.waitForReadyRead() responseMsg = self.client_socket.readAll() print("reponse msg: ", bytes(responseMsg).decode()) self.genRunning = True else: print("Stop generation") self.start_stop_pb.setText("Start Pulse Generation") msg = 'stop=\n' self.client_socket.write(msg.encode()) self.client_socket.waitForReadyRead() responseMsg = self.client_socket.readAll() print("reponse msg: ", bytes(responseMsg).decode()) self.genRunning = False def readTrace(self): print("Message from the generator") data = self.server_socket.readAll() msgstring = bytes(data).decode() print(msgstring) print(data, type(data)) def display_pulse(self): self.pulse_gen_display.calc_pulse() self.pulse_gen_display.get_display_data() self.x_axis.setMax(500 * self.pulse_gen_display.display_T) data_points = self.series.pointsVector() for i in range(500): data_points[i].setX(i * self.pulse_gen_display.display_T) data_points[i].setY(self.pulse_gen_display.display_data[i] * self.pulse_gen_display.calib) self.series.replace(data_points) def quit(self): app.exit()
class TelemetryDialog(QDialog): resized = QtCore.pyqtSignal() visibility = QtCore.pyqtSignal(bool) def __init__(self, winTitle="Network Telemetry", parent=None): super(TelemetryDialog, self).__init__(parent) self.visibility.connect(self.onVisibilityChanged) self.winTitle = winTitle self.updateLock = Lock() # Used to detect network change self.lastNetKey = "" self.lastSeen = None self.maxPoints = 20 self.maxRowPoints = 60 self.paused = False self.streamingSave = False self.streamingFile = None self.linesBeforeFlush = 10 self.currentLine = 0 # OK and Cancel buttons #buttons = QDialogButtonBox(QDialogButtonBox.Ok,Qt.Horizontal, self) #buttons.accepted.connect(self.accept) #buttons.move(170, 280) desktopSize = QApplication.desktop().screenGeometry() #self.mainWidth=1024 #self.mainHeight=768 #self.mainWidth = desktopSize.width() * 3 / 4 #self.mainHeight = desktopSize.height() * 3 / 4 self.setGeometry(self.geometry().x(), self.geometry().y(), desktopSize.width() / 2, desktopSize.height() / 2) self.setWindowTitle(winTitle) self.radar = RadarWidget(self) self.radar.setGeometry(self.geometry().width() / 2, 10, self.geometry().width() / 2 - 20, self.geometry().width() / 2 - 20) self.createTable() self.btnExport = QPushButton("Export Table", self) self.btnExport.clicked[bool].connect(self.onExportClicked) self.btnExport.setStyleSheet("background-color: rgba(2,128,192,255);") self.btnPause = QPushButton("Pause Table", self) self.btnPause.setCheckable(True) self.btnPause.clicked[bool].connect(self.onPauseClicked) self.btnPause.setStyleSheet("background-color: rgba(2,128,192,255);") self.btnStream = QPushButton("Streaming Save", self) self.btnStream.setCheckable(True) self.btnStream.clicked[bool].connect(self.onStreamClicked) self.btnStream.setStyleSheet("background-color: rgba(2,128,192,255);") self.createChart() self.setBlackoutColors() self.setMinimumWidth(600) self.setMinimumHeight(600) self.center() def createTable(self): # Set up location table self.locationTable = QTableWidget(self) self.locationTable.setColumnCount(8) self.locationTable.setGeometry(10, 10, self.geometry().width() / 2 - 20, self.geometry().height() / 2) self.locationTable.setShowGrid(True) self.locationTable.setHorizontalHeaderLabels([ 'macAddr', 'SSID', 'Strength', 'Timestamp', 'GPS', 'Latitude', 'Longitude', 'Altitude' ]) self.locationTable.resizeColumnsToContents() self.locationTable.setRowCount(0) self.locationTable.horizontalHeader().setSectionResizeMode( 1, QHeaderView.Stretch) self.ntRightClickMenu = QMenu(self) newAct = QAction('Copy', self) newAct.setStatusTip('Copy data to clipboard') newAct.triggered.connect(self.onCopy) self.ntRightClickMenu.addAction(newAct) self.locationTable.setContextMenuPolicy(Qt.CustomContextMenu) self.locationTable.customContextMenuRequested.connect( self.showNTContextMenu) def setBlackoutColors(self): self.locationTable.setStyleSheet( "QTableView {background-color: black;gridline-color: white;color: white} QTableCornerButton::section{background-color: white;}" ) headerStyle = "QHeaderView::section{background-color: white;border: 1px solid black;color: black;} QHeaderView::down-arrow,QHeaderView::up-arrow {background: none;}" self.locationTable.horizontalHeader().setStyleSheet(headerStyle) self.locationTable.verticalHeader().setStyleSheet(headerStyle) mainTitleBrush = QBrush(Qt.red) self.timeChart.setTitleBrush(mainTitleBrush) self.timeChart.setBackgroundBrush(QBrush(Qt.black)) self.timeChart.axisX().setLabelsColor(Qt.white) self.timeChart.axisY().setLabelsColor(Qt.white) titleBrush = QBrush(Qt.white) self.timeChart.axisX().setTitleBrush(titleBrush) self.timeChart.axisY().setTitleBrush(titleBrush) def resizeEvent(self, event): wDim = self.geometry().width() / 2 - 20 hDim = self.geometry().height() / 2 smallerDim = wDim if hDim < smallerDim: smallerDim = hDim # Radar self.radar.setGeometry(self.geometry().width() - smallerDim - 10, 10, smallerDim, smallerDim) # chart self.timePlot.setGeometry(10, 10, self.geometry().width() - smallerDim - 30, smallerDim) # Buttons self.btnPause.setGeometry(10, self.geometry().height() / 2 + 18, 110, 25) self.btnExport.setGeometry(150, self.geometry().height() / 2 + 18, 110, 25) self.btnStream.setGeometry(290, self.geometry().height() / 2 + 18, 110, 25) # Table self.locationTable.setGeometry(10, self.geometry().height() / 2 + 50, self.geometry().width() - 20, self.geometry().height() / 2 - 60) def center(self): # Get our geometry qr = self.frameGeometry() # Find the desktop center point cp = QDesktopWidget().availableGeometry().center() # Move our center point to the desktop center point qr.moveCenter(cp) # Move the top-left point of the application window to the top-left point of the qr rectangle, # basically centering the window self.move(qr.topLeft()) def showNTContextMenu(self, pos): curRow = self.locationTable.currentRow() if curRow == -1: return self.ntRightClickMenu.exec_(self.locationTable.mapToGlobal(pos)) def onCopy(self): self.updateLock.acquire() curRow = self.locationTable.currentRow() curCol = self.locationTable.currentColumn() if curRow == -1 or curCol == -1: self.updateLock.release() return curText = self.locationTable.item(curRow, curCol).text() clipboard = QApplication.clipboard() clipboard.setText(curText) self.updateLock.release() def onVisibilityChanged(self, visible): if not visible: self.paused = True self.btnPause.setStyleSheet("background-color: rgba(255,0,0,255);") # We're coming out of streaming self.streamingSave = False self.btnStream.setStyleSheet( "background-color: rgba(2,128,192,255);") self.btnStream.setChecked(False) if (self.streamingFile): self.streamingFile.close() self.streamingFile = None return else: self.paused = False self.btnPause.setStyleSheet( "background-color: rgba(2,128,192,255);") if self.locationTable.rowCount() > 1: self.locationTable.scrollToItem(self.locationTable.item(0, 0)) def hideEvent(self, event): self.visibility.emit(False) def showEvent(self, event): self.visibility.emit(True) def onPauseClicked(self, pressed): if self.btnPause.isChecked(): self.paused = True self.btnPause.setStyleSheet("background-color: rgba(255,0,0,255);") else: self.paused = False self.btnPause.setStyleSheet( "background-color: rgba(2,128,192,255);") def onStreamClicked(self, pressed): if not self.btnStream.isChecked(): # We're coming out of streaming self.streamingSave = False self.btnStream.setStyleSheet( "background-color: rgba(2,128,192,255);") if (self.streamingFile): self.streamingFile.close() self.streamingFile = None return self.btnStream.setStyleSheet("background-color: rgba(255,0,0,255);") self.streamingSave = True fileName = self.saveFileDialog() if not fileName: self.btnStream.setStyleSheet( "background-color: rgba(2,128,192,255);") self.btnStream.setChecked(False) return try: self.streamingFile = open( fileName, 'w', 1 ) # 1 says use line buffering, otherwise it fully buffers and doesn't write except: QMessageBox.question(self, 'Error', "Unable to write to " + fileName, QMessageBox.Ok) self.streamingFile = None self.streamingSave = False self.btnStream.setStyleSheet( "background-color: rgba(2,128,192,255);") self.btnStream.setChecked(False) return self.streamingFile.write( 'MAC Address,SSID,Strength,Timestamp,GPS,Latitude,Longitude,Altitude\n' ) def onExportClicked(self): fileName = self.saveFileDialog() if not fileName: return try: outputFile = open(fileName, 'w') except: QMessageBox.question(self, 'Error', "Unable to write to " + fileName, QMessageBox.Ok) return outputFile.write( 'MAC Address,SSID,Strength,Timestamp,GPS,Latitude,Longitude,Altitude\n' ) numItems = self.locationTable.rowCount() if numItems == 0: outputFile.close() return self.updateLock.acquire() for i in range(0, numItems): outputFile.write( self.locationTable.item(i, 0).text() + ',"' + self.locationTable.item(i, 1).text() + '",' + self.locationTable.item(i, 2).text() + ',' + self.locationTable.item(i, 3).text()) outputFile.write(',' + self.locationTable.item(i, 4).text() + ',' + self.locationTable.item(i, 5).text() + ',' + self.locationTable.item(i, 6).text() + ',' + self.locationTable.item(i, 7).text() + '\n') self.updateLock.release() outputFile.close() def saveFileDialog(self): options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog fileName, _ = QFileDialog.getSaveFileName( self, "QFileDialog.getSaveFileName()", "", "CSV Files (*.csv);;All Files (*)", options=options) if fileName: return fileName else: return None def createChart(self): self.timeChart = QChart() titleFont = QFont() titleFont.setPixelSize(18) titleBrush = QBrush(QColor(0, 0, 255)) self.timeChart.setTitleFont(titleFont) self.timeChart.setTitleBrush(titleBrush) self.timeChart.setTitle('Signal (Past ' + str(self.maxPoints) + ' Samples)') # self.timeChart.addSeries(testseries) # self.timeChart.createDefaultAxes() self.timeChart.legend().hide() # Axis examples: https://doc.qt.io/qt-5/qtcharts-multiaxis-example.html newAxis = QValueAxis() newAxis.setMin(0) newAxis.setMax(self.maxPoints) newAxis.setTickCount(11) newAxis.setLabelFormat("%d") newAxis.setTitleText("Sample") self.timeChart.addAxis(newAxis, Qt.AlignBottom) newAxis = QValueAxis() newAxis.setMin(-100) newAxis.setMax(-10) newAxis.setTickCount(9) newAxis.setLabelFormat("%d") newAxis.setTitleText("dBm") self.timeChart.addAxis(newAxis, Qt.AlignLeft) chartBorder = Qt.darkGray self.timePlot = QChartView(self.timeChart, self) self.timePlot.setBackgroundBrush(chartBorder) self.timePlot.setRenderHint(QPainter.Antialiasing) self.timeSeries = QLineSeries() pen = QPen(Qt.yellow) pen.setWidth(2) self.timeSeries.setPen(pen) self.timeChart.addSeries(self.timeSeries) self.timeSeries.attachAxis(self.timeChart.axisX()) self.timeSeries.attachAxis(self.timeChart.axisY()) def updateNetworkData(self, curNet): if not self.isVisible(): return # Signal is -NN dBm. Need to make it positive for the plot self.radar.updateData(curNet.signal * -1) if self.winTitle == "Client Telemetry": self.setWindowTitle(self.winTitle + " - [" + curNet.macAddr + "] " + curNet.ssid) else: self.setWindowTitle(self.winTitle + " - " + curNet.ssid) self.radar.draw() # Network changed. Clear our table and time data updateChartAndTable = False self.updateLock.acquire() if (curNet.getKey() != self.lastNetKey): self.lastNetKey = curNet.getKey() self.locationTable.setRowCount(0) self.timeSeries.clear() updateChartAndTable = True ssidTitle = curNet.ssid if len(ssidTitle) > 28: ssidTitle = ssidTitle[:28] ssidTitle = ssidTitle + '...' self.timeChart.setTitle(ssidTitle + ' Signal (Past ' + str(self.maxPoints) + ' Samples)') else: if self.lastSeen != curNet.lastSeen: updateChartAndTable = True if updateChartAndTable: # Update chart numPoints = len(self.timeSeries.pointsVector()) if numPoints >= self.maxPoints: self.timeSeries.remove(0) # Now we need to reset the x data to pull the series back counter = 0 for curPoint in self.timeSeries.pointsVector(): self.timeSeries.replace(counter, counter, curPoint.y()) counter += 1 if curNet.signal >= -100: self.timeSeries.append(numPoints, curNet.signal) else: self.timeSeries.append(numPoints, -100) # Update Table self.addTableData(curNet) # Limit points in each if self.locationTable.rowCount() > self.maxRowPoints: self.locationTable.setRowCount(self.maxRowPoints) self.updateLock.release() def addTableData(self, curNet): if self.paused: return # rowPosition = self.locationTable.rowCount() # Always insert at row(0) rowPosition = 0 self.locationTable.insertRow(rowPosition) #if (addedFirstRow): # self.locationTable.setRowCount(1) # ['macAddr','SSID', 'Strength', 'Timestamp','GPS', 'Latitude', 'Longitude', 'Altitude'] self.locationTable.setItem(rowPosition, 0, QTableWidgetItem(curNet.macAddr)) tmpssid = curNet.ssid if (len(tmpssid) == 0): tmpssid = '<Unknown>' newSSID = QTableWidgetItem(tmpssid) self.locationTable.setItem(rowPosition, 1, newSSID) self.locationTable.setItem(rowPosition, 2, IntTableWidgetItem(str(curNet.signal))) self.locationTable.setItem( rowPosition, 3, DateTableWidgetItem(curNet.lastSeen.strftime("%m/%d/%Y %H:%M:%S"))) if curNet.gps.isValid: self.locationTable.setItem(rowPosition, 4, QTableWidgetItem('Yes')) else: self.locationTable.setItem(rowPosition, 4, QTableWidgetItem('No')) self.locationTable.setItem( rowPosition, 5, FloatTableWidgetItem(str(curNet.gps.latitude))) self.locationTable.setItem( rowPosition, 6, FloatTableWidgetItem(str(curNet.gps.longitude))) self.locationTable.setItem( rowPosition, 7, FloatTableWidgetItem(str(curNet.gps.altitude))) #order = Qt.DescendingOrder #self.locationTable.sortItems(3, order ) # If we're in streaming mode, write the data out to disk as well if self.streamingFile: self.streamingFile.write( self.locationTable.item(rowPosition, 0).text() + ',"' + self.locationTable.item(rowPosition, 1).text() + '",' + self.locationTable.item(rowPosition, 2).text() + ',' + self.locationTable.item(rowPosition, 3).text() + ',' + self.locationTable.item(rowPosition, 4).text() + ',' + self.locationTable.item(rowPosition, 5).text() + ',' + self.locationTable.item(rowPosition, 6).text() + ',' + self.locationTable.item(rowPosition, 7).text() + '\n') if (self.currentLine > self.linesBeforeFlush): self.streamingFile.flush() self.currentLine += 1 numRows = self.locationTable.rowCount() if numRows > 1: self.locationTable.scrollToItem(self.locationTable.item(0, 0)) def onTableHeadingClicked(self, logical_index): header = self.locationTable.horizontalHeader() order = Qt.DescendingOrder # order = Qt.DescendingOrder if not header.isSortIndicatorShown(): header.setSortIndicatorShown(True) elif header.sortIndicatorSection() == logical_index: # apparently, the sort order on the header is already switched # when the section was clicked, so there is no need to reverse it order = header.sortIndicatorOrder() header.setSortIndicator(logical_index, order) self.locationTable.sortItems(logical_index, order) def updateData(self, newRadius): self.radar.updateData(newRadius) def showTelemetry(parent=None): dialog = TelemetryDialog(parent) result = dialog.exec_() return (result == QDialog.Accepted)
class ContentView(QWidget): def __init__(self): super().__init__() self.m_chart_1 = QChart() self.m_chart_2 = QChart() self.m_chart_3 = QChart() self.m_chart_4 = QChart() self.m_series_1 = QLineSeries() self.m_series_2 = QLineSeries() self.m_series_3 = QLineSeries() self.m_series_4 = QLineSeries() self.y_original = [] self.x_data = [] self.y_processed = [] self.sampling_rate = 0 self.pathForVocalMute = "" self.select_action_drop = QComboBox() self.echo_shift = 0.4 self.echo_alpha = 0.5 self.init_ui() def init_ui(self): main_layout = QVBoxLayout() # Drag&Drop area drag_drop = DragDropArea(parent=self) main_layout.addWidget(drag_drop) # Chart layout chart_layout_1 = QHBoxLayout() chart_layout_2 = QHBoxLayout() # Chart 1 chart_view_1 = QChartView(self.m_chart_1) chart_view_1.setMinimumSize(400, 300) self.m_chart_1.addSeries(self.m_series_1) pen = self.m_series_1.pen() pen.setColor(Qt.red) pen.setWidthF(.1) self.m_series_1.setPen(pen) self.m_series_1.setUseOpenGL(True) axis_x = QValueAxis() axis_x.setRange(0, 100) axis_x.setLabelFormat("%g") axis_x.setTitleText("Samples") axis_y = QValueAxis() axis_y.setRange(-10, 10) axis_y.setTitleText("Audio level") self.m_chart_1.setAxisX(axis_x, self.m_series_1) self.m_chart_1.setAxisY(axis_y, self.m_series_1) self.m_chart_1.setTitle("Original signal time domain") chart_layout_1.addWidget(chart_view_1) # Chart 2 chart_view_2 = QChartView(self.m_chart_2) chart_view_2.setMinimumSize(400, 300) self.m_chart_2.setTitle("Original signal frequency domain") pen = self.m_series_2.pen() pen.setColor(Qt.blue) pen.setWidthF(.1) self.m_series_2.setPen(pen) self.m_series_2.setUseOpenGL(True) self.m_chart_2.addSeries(self.m_series_2) chart_layout_1.addWidget(chart_view_2) # Chart 3 chart_view_3 = QChartView(self.m_chart_3) chart_view_3.setMinimumSize(400, 300) self.m_chart_3.addSeries(self.m_series_3) pen = self.m_series_3.pen() pen.setColor(Qt.green) pen.setWidthF(.1) self.m_series_3.setPen(pen) self.m_series_3.setUseOpenGL(True) axis_x = QValueAxis() axis_x.setRange(0, 100) axis_x.setLabelFormat("%g") axis_x.setTitleText("Samples") axis_y = QValueAxis() axis_y.setRange(-10, 10) axis_y.setTitleText("Audio level") self.m_chart_3.setAxisX(axis_x, self.m_series_3) self.m_chart_3.setAxisY(axis_y, self.m_series_3) self.m_chart_3.setTitle("Processed signal time domain") chart_layout_2.addWidget(chart_view_3) # Chart 4 chart_view_4 = QChartView(self.m_chart_4) chart_view_4.setMinimumSize(400, 300) self.m_chart_4.setTitle("Processed signal frequency domain") pen = self.m_series_4.pen() pen.setColor(Qt.magenta) pen.setWidthF(.1) self.m_series_4.setPen(pen) self.m_series_4.setUseOpenGL(True) self.m_chart_4.addSeries(self.m_series_4) chart_layout_2.addWidget(chart_view_4) main_layout.addLayout(chart_layout_1) main_layout.addLayout(chart_layout_2) # Action buttons player_layout = QHBoxLayout() self.select_action_drop.addItems([ "Add noise", "Filter", "Mute equipment", "Mute vocal", "Add echo", "Filter echo" ]) player_layout.addWidget(self.select_action_drop) noise_jc = QIcon('rate_ic.png') noise_btn = QPushButton('Process') noise_btn.setIcon(noise_jc) noise_btn.clicked.connect(self.on_action) player_layout.addWidget(noise_btn) play_jc = QIcon('play_ic.png') play_orig_btn = QPushButton('Play Original') play_orig_btn.setIcon(play_jc) play_orig_btn.clicked.connect(self.on_play_orig) player_layout.addWidget(play_orig_btn) play_jc = QIcon('play_ic.png') play_btn = QPushButton('Play Processed') play_btn.setIcon(play_jc) play_btn.clicked.connect(self.on_play) player_layout.addWidget(play_btn) stop_jc = QIcon('stop_ic.png') stop_btn = QPushButton('Stop') stop_btn.setIcon(stop_jc) stop_btn.clicked.connect(self.on_stop) player_layout.addWidget(stop_btn) main_layout.addLayout(player_layout) self.setLayout(main_layout) '''' Toolbar actions ''' def browse_file(self): path1 = QFileDialog.getOpenFileName(self, 'Open File', os.getenv('HOME'), '*.wav') print(path1[0]) rate, data = wavfile.read(path1[0]) self.sampling_rate = rate self.y_original = data[:, 0] self.show_original_data() def on_file_upload(self, file_url): print(file_url[7:]) self.pathForVocalMute = file_url[7:] rate, data = wavfile.read(file_url[7:]) self.sampling_rate = rate self.y_original = data[:, 0] self.show_original_data() def on_save(self): print("on_save") if len(self.y_processed) > 0: path = QFileDialog.getSaveFileName(self, 'Save File', os.getenv('HOME'), 'audio/wav') if path[0] != '': data2 = np.asarray([self.y_processed, self.y_processed]).transpose() wavfile.write(path[0], self.sampling_rate, data2) else: msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText("No path") msg.setInformativeText("You should define path to save file") msg.setWindowTitle("Error") msg.exec_() else: msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText("No data") msg.setInformativeText( "No data to save, you should upload and process sound file") msg.setWindowTitle("Error") msg.exec_() '''' Action selection ''' def on_action(self): if self.select_action_drop.currentText() == "Add noise": self.on_add_noise() elif self.select_action_drop.currentText() == "Filter": self.on_filter() elif self.select_action_drop.currentText() == "Mute equipment": self.on_mute_equipment() elif self.select_action_drop.currentText() == "Mute vocal": self.on_mute_voice() elif self.select_action_drop.currentText() == "Add echo": self.on_add_echo() elif self.select_action_drop.currentText() == "Filter echo": self.on_filter_echo() ''' Noise addition ''' def on_add_noise(self): if len(self.y_original) == 0: msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText("Upload sound file") msg.setInformativeText( "First you should add sound file to process") msg.setWindowTitle("Error") msg.exec_() return noise = np.random.normal(0, self.y_original.max() / 30, len(self.y_original)) arr1 = np.array(self.y_original) self.y_processed = arr1 + noise self.show_processed_data() def on_filter(self): print("on_filter") if len(self.y_original) == 0: msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText("Upload sound file") msg.setInformativeText( "First you should add sound file to process") msg.setWindowTitle("Error") msg.exec_() return filter1, filter2, limit1, limit2, extra, max_ripple, min_attenuation, ok = FilterSelectionDialog.show_dialog( parent=self) print(filter1, filter2, limit1, limit2, extra, max_ripple, min_attenuation, ok) if ok: if filter1 == "FIR filter": self.on_fir_filter(filter2, limit1, limit2, extra) elif filter1 == "IIR filter": self.on_iir_filter(filter2, limit1, limit2, extra, max_ripple, min_attenuation) def on_mute_equipment(self): print("on_mute_equipment") check_piano, check_organ, check_flute, check_french_horn, check_trumpet, check_violin, \ check_guitar_acoustic, check_guitar_bass, check_clarinet, \ check_saxophone, ok = MuteInstrumentsDialog.show_dialog(parent=self) print(check_piano, check_organ, check_flute, check_french_horn, check_trumpet, check_violin, check_guitar_acoustic, check_guitar_bass, check_clarinet, check_saxophone, ok) ''' Piano A0 (28 Hz) to C8 (4,186 Hz or 4.1 KHz) Organ C0 (16 Hz) to A9 (7,040 KHz) Concert Flute C4 (262 Hz) to B6 (1,976 Hz) French Horn A2 (110 Hz) to A5 (880 Hz) Trumpet E3 (165 Hz) to B5 (988 Hz) Violin G3 (196 Hz) - G7 (3,136 Hz) (G-D-E-A) (or C8 (4,186 Hz?) Guitar (Acoustic) E2 (82 Hz) to F6 (1,397 Hz) Guitar (Bass) 4 string E1 (41 Hz) to C4 (262 Hz) Clarinet E3 (165 Hz) to G6 (1,568 Hz) Saxaphone Eb 138-830 (880) ''' if ok: print(check_piano) limit1 = 0.1 limit2 = 0.2 if check_piano: pass elif check_organ: pass elif check_flute: limit1 = 262 / self.sampling_rate limit2 = 1976 / self.sampling_rate pass elif check_french_horn: limit1 = 110 / self.sampling_rate limit2 = 880 / self.sampling_rate pass elif check_trumpet: limit1 = 165 / self.sampling_rate limit2 = 988 / self.sampling_rate pass elif check_violin: limit1 = 196 / self.sampling_rate limit2 = 3136 / self.sampling_rate pass elif check_guitar_acoustic: limit1 = 82 / self.sampling_rate limit2 = 1397 / self.sampling_rate pass elif check_guitar_bass: limit1 = 41 / self.sampling_rate limit2 = 262 / self.sampling_rate pass elif check_clarinet: limit1 = 165 / self.sampling_rate limit2 = 1568 / self.sampling_rate pass elif check_saxophone: limit1 = 138 / self.sampling_rate limit2 = 880 / self.sampling_rate pass print(limit1, limit2) print([ 0.0, 0.0001, limit1 - 0.0001, limit1, limit2, limit2 + 0.0001, 0.9991, 1.0 ], [0, 1, 1, 0, 0, 1, 1, 0]) design_filter = signal.firwin2(1000000, [ 0.0, 0.0001, limit1 - 0.0001, limit1, limit2, limit2 + 0.0001, 0.9991, 1.0 ], [0, 1, 1, 0, 0, 1, 1, 0]) self.y_processed = signal.convolve(self.y_original, design_filter, mode='same') w1, h1 = signal.freqz(design_filter) result = FilterResponseDialog.show_dialog(parent=self, w1=w1, h1=h1) if result: self.show_processed_data() def on_mute_voice(self): y, sr = librosa.load(self.pathForVocalMute, sr=self.sampling_rate) S_full, phase = librosa.magphase(librosa.stft(y)) S_filter = librosa.decompose.nn_filter( S_full, aggregate=np.median, metric='cosine', width=int(librosa.time_to_frames(2, sr=sr))) S_filter = np.minimum(S_full, S_filter) margin_i, margin_v = 2, 10 power = 2 mask_i = librosa.util.softmask(S_filter, margin_i * (S_full - S_filter), power=power) mask_v = librosa.util.softmask(S_full - S_filter, margin_v * S_filter, power=power) S_foreground = mask_v * S_full S_background = mask_i * S_full self.y_processed = librosa.istft(S_background) self.show_processed_data() def on_add_echo(self): if len(self.y_original) == 0: msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText("Upload sound file") msg.setInformativeText( "First you should add sound file to process") msg.setWindowTitle("Error") msg.exec_() return num_shift = int(self.sampling_rate * self.echo_shift) zeros = np.zeros(num_shift) original = np.append(self.y_original, zeros) echo = np.append(zeros, self.y_original) * self.echo_alpha self.y_processed = original + echo np.delete( self.y_processed, np.arange( len(self.y_processed) - len(zeros), len(self.y_processed))) self.show_processed_data() def on_filter_echo(self): ceps = cepstrum.real_cepstrum(np.array(self.y_original)) index, result = CepstrumDialog.show_dialog(self, ceps) if result: print(index) b = np.array([1]) a = np.zeros(index + 1) a[0] = 1 a[len(a) - 1] = self.echo_alpha zi = signal.lfilter_zi(b, a) self.y_processed, _ = signal.lfilter(b, a, self.y_original, axis=0, zi=zi * self.y_original[0]) w1, h1 = signal.freqz(b, a) result = FilterResponseDialog.show_dialog(parent=self, w1=w1, h1=h1) if result: self.show_processed_data() ''' Filters ''' def on_fir_filter(self, filter_type, limit1, limit2, extra): if filter_type == "Low-pass": design_filter = signal.firwin(41, limit1, window=extra) elif filter_type == "High-pass": temp = np.zeros(41) temp[20] = 1 design_filter = temp - np.array( signal.firwin(41, limit1, window=extra)) elif filter_type == "Band-pass": temp = np.zeros(41) temp[20] = 1 design_filter = temp - np.array( signal.firwin(41, [limit1, limit2], window=extra)) elif filter_type == "Band-reject": design_filter = signal.firwin(41, [limit1, limit2], window=extra) self.y_processed = signal.convolve(self.y_original, design_filter, mode='same') w1, h1 = signal.freqz(design_filter) result = FilterResponseDialog.show_dialog(parent=self, w1=w1, h1=h1) if result: self.show_processed_data() def on_iir_filter(self, filter_type, limit1, limit2, extra, max_ripple, min_attenuation): if filter_type == "Low-pass": b, a = signal.iirfilter(4, limit1, rp=int(max_ripple), rs=int(min_attenuation), btype='lowpass', ftype=extra) elif filter_type == "High-pass": b, a = signal.iirfilter(4, limit1, rp=int(max_ripple), rs=int(min_attenuation), btype='highpass', ftype=extra) elif filter_type == "Band-pass": b, a = signal.iirfilter(4, [limit1, limit2], rp=int(max_ripple), rs=int(min_attenuation), btype='bandpass', ftype=extra) elif filter_type == "Band-reject": b, a = signal.iirfilter(4, [limit1, limit2], rp=int(max_ripple), rs=int(min_attenuation), btype='bandstop', ftype=extra) self.y_processed = signal.lfilter(b, a, self.y_original) w1, h1 = signal.freqz(b, a) result = FilterResponseDialog.show_dialog(parent=self, w1=w1, h1=h1) if result: self.show_processed_data() ''' Audio controls ''' def on_play(self): print("on_play") if len(self.y_processed) > 0: data2 = np.asarray(self.y_processed) sd.play(data2, self.sampling_rate) else: msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText("Upload sound file") msg.setInformativeText( "First you should upload and process sound file to play") msg.setWindowTitle("Error") msg.exec_() def on_stop(self): sd.stop() def on_play_orig(self): print("on_play_orig") if len(self.y_original) > 0: data = np.asarray(self.y_original) sd.play(data, self.sampling_rate) else: msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText("Upload sound file") msg.setInformativeText("First you should add sound file to play") msg.setWindowTitle("Error") msg.exec_() ''' Signal plots ''' def show_original_data(self): # Time domain y_data_scaled = np.interp( self.y_original, (self.y_original.min(), self.y_original.max()), (-10, +10)) sample_size = len(self.y_original) self.x_data = np.linspace(0., 100., sample_size) points_1 = [] for k in range(len(y_data_scaled)): points_1.append(QPointF(self.x_data[k], y_data_scaled[k])) self.m_series_1.replace(points_1) # Frequency domain y_freq_data = np.abs(fftpack.fft(self.y_original)) y_freq_data = np.interp(y_freq_data, (y_freq_data.min(), y_freq_data.max()), (0, +10)) x_freq_data = fftpack.fftfreq(len( self.y_original)) * self.sampling_rate axis_x = QValueAxis() axis_x.setRange(0, self.sampling_rate / 2) axis_x.setLabelFormat("%g") axis_x.setTitleText("Frequency [Hz]") axis_y = QValueAxis() axis_y.setRange(np.min(y_freq_data), np.max(y_freq_data)) axis_y.setTitleText("Magnitude") self.m_chart_2.setAxisX(axis_x, self.m_series_2) self.m_chart_2.setAxisY(axis_y, self.m_series_2) points_2 = [] for k in range(len(y_freq_data)): points_2.append(QPointF(x_freq_data[k], y_freq_data[k])) self.m_series_2.replace(points_2) self.m_series_3.clear() self.m_series_4.clear() def show_processed_data(self): # Time domain y_data_scaled = np.interp( self.y_processed, (self.y_processed.min(), self.y_processed.max()), (-10, +10)) points_3 = [] sample_size = len(self.y_processed) x_data = np.linspace(0., 100., sample_size) for k in range(len(y_data_scaled)): points_3.append(QPointF(x_data[k], y_data_scaled[k])) self.m_series_3.replace(points_3) # Frequency domain y_freq_data = np.abs(fftpack.fft(self.y_processed)) y_freq_data = np.interp(y_freq_data, (y_freq_data.min(), y_freq_data.max()), (0, +10)) x_freq_data = fftpack.fftfreq(len( self.y_processed)) * self.sampling_rate axis_x = QValueAxis() axis_x.setRange(0, self.sampling_rate / 2) axis_x.setLabelFormat("%g") axis_x.setTitleText("Frequency [Hz]") axis_y = QValueAxis() axis_y.setRange(np.min(y_freq_data), np.max(y_freq_data)) axis_y.setTitleText("Magnitude") self.m_chart_4.setAxisX(axis_x, self.m_series_4) self.m_chart_4.setAxisY(axis_y, self.m_series_4) points_4 = [] for k in range(len(y_freq_data)): points_4.append(QPointF(x_freq_data[k], y_freq_data[k])) self.m_series_4.replace(points_4)
class Chart(QChart): MIN_X = 0 MAX_X = 750 MIN_Y = -1 MAX_Y = 1 TICKS = 5 PENCOLOR = Qt.red PENWIDTH = 1 def __init__(self, parent=None): super(Chart, self).__init__(parent) self.parent = parent # we will draw lines self.series = QLineSeries(parent) # color and pen-width self.series.setPen(QPen(self.PENCOLOR, self.PENWIDTH)) self.addSeries(self.series) self.legend().hide() self.__construct_axises() def setRange_X_axis(self, min_X, max_X): self.MIN_X = min_X self.MAX_X = max_X self.axisX().setRange(self.MIN_X, self.MAX_X) def setRange_Y_axis(self, min_Y, max_Y): self.MIN_Y = min_Y self.MAX_Y = max_Y self.axisY().setRange(self.MIN_Y, self.MAX_Y) def add_point(self, x, y): self.series.append(x, y) def get_series(self) -> list: return self.series.pointsVector() def remove_point(self, index): self.series.remove(index) def remove_points(self, index, count): self.series.removePoints(index, count) def replace_point(self, index, x, y): self.series.replace(index, x, y) def replace_series(self, lst): self.series.replace(lst) def get_series_count(self): return self.series.count() def __construct_axises(self): self.createDefaultAxes() # X-Axis x_axis = self.axisX() x_axis.hide() x_axis.setRange(self.MIN_X, self.MAX_X) # Y-axis y_axis = self.axisY() y_axis.setRange(self.MIN_Y, self.MAX_Y) y_axis.setTickCount(self.TICKS)