Unsolved Continuously Updating BarPlot (QtCharts)
-
Hi to everybody, i'm new in this forum and i'm relatively new to Qt for Python.
Up untill now i have been always capable of solving the problems googling or using the official documentation, but now i'm facing an obstacle than I cannot solve by myself.
I'm using a
QTimer()
with a 20Hz update clock.
At every timeout, some operations are executed. Among them i need to plot a barplot that takes the values from the Numpy array (converted to a list)self.z_coeff
.I designed the interface with Qt Designer and I draw a QWidget for the barplot (object name:
zernike_bar_plot
).The
.ui
is converted to python code using thepyside2-uic
command, and what i'm doing in the separate file that I use to add all the functionality is pretty standard:class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() self.ui = Ui_MainWindow() self.ui.setupUi(self)
When the program starts i draw the bar chart for the first time using this function:
def set_zernike_plot(self): self.zernike_barset = QtCharts.QBarSet('zernike_bp') self.zernike_barset.append(self.z_coeff.tolist()) self.zernike_barset.setColor(QColor(0, 230, 0)) self.zernike_series = QtCharts.QBarSeries() self.zernike_series.append(self.zernike_barset) self.zernike_series.setBarWidth(1) self.zernike_plot_chart = QtCharts.QChart() self.zernike_plot_chart.addSeries(self.zernike_series) self.zernike_plot_chart.createDefaultAxes() self.zernike_plot_chart.legend().hide() self.zernike_plot_chart.axisX(self.zernike_series).setVisible(False) self.zernike_plot_chart.axisY(self.zernike_series).setVisible(False) self.zernike_chartview = QtCharts.QChartView(self.zernike_plot_chart) self.ui.zernike_bar_plot.setContentsMargins(0, 0, 0, 0) self.zernike_layout = QHBoxLayout(self.ui.zernike_bar_plot) self.zernike_layout.setContentsMargins(0, 0, 0, 0) self.zernike_layout.addWidget(self.zernike_chartview)
Here the first problem: I'm not able to limit the y axis to be in some range, say
[-1,1]
.Then what I want is to update this plot with che continuously changing values of
self.z_coeff
.
For this purpose, i connected the QTimer timeout to another function:def update_zernike_coefficient_plot(self): self.zernike_plot_chart.removeSeries(self.zernike_series) self.zernike_barset = QtCharts.QBarSet('zernike_set') self.zernike_barset.append(self.z_coeff.tolist()) self.zernike_barset.setColor(QColor(0, 230, 0)) self.zernike_series = QtCharts.QBarSeries() self.zernike_series.append(self.zernike_barset) self.zernike_series.setBarWidth(1) self.zernike_plot_chart.addSeries(self.zernike_series) self.zernike_chartview.setChart(self.zernike_plot_chart) self.zernike_layout.insertWidget(0,self.zernike_chartview)
This is working, but i think that is overkilling to reassign all this variables and rewrite all this code just to update the plot. Moreover to me this adds unnecessary load for the processor.
I will thank everyone that can help me ❤️
-
Hi there,
See the minimal example below (uses PyQt5 or PySide2) that hopefully addresses your issues/questions:
try: from PyQt5.QtCore import QTimerEvent from PyQt5.QtGui import QColor from PyQt5.QtWidgets import QMainWindow from PyQt5.QtChart import QChartView, QBarSet, QBarSeries, QChart except ImportError: from PySide2.QtCore import QTimerEvent from PySide2.QtGui import QColor from PySide2.QtWidgets import QMainWindow from PySide2.QtCharts import QtCharts QBarSet=QtCharts.QBarSet QBarSeries=QtCharts.QBarSeries QChart=QtCharts.QChart QChartView=QtCharts.QChartView class MainWindow(QMainWindow): def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self._data=[ [1,2,3,4,5,4,3,2,1], [5,4,3,2,1,2,3,4,5] ] self._currentDataIdx=0 self._barSet=QBarSet("Bar Set") self._barSet.setColor(QColor(0,230,0)) self._barSet.append(self._data[self._currentDataIdx]) self._barSeries=QBarSeries() self._barSeries.setBarWidth(1) self._barSeries.append(self._barSet) self._chart=QChart() self._chart.addSeries(self._barSeries) self._chart.createDefaultAxes() self._chart.legend().hide() self._chart.axisX(self._barSeries).setVisible(False) self._chart.axisY(self._barSeries).setVisible(False) # Set the Y-axis range/limits 0 to 6 self._chart.axisY(self._barSeries).setRange(0,6) self._chartView=QChartView(self._chart) self.setCentralWidget(self._chartView) self._timerId=self.startTimer(1000) def timerEvent(self, event:QTimerEvent): if self._timerId!=event.timerId(): return # Replace the data in the existing series self._currentDataIdx=1 if not self._currentDataIdx else 0 for i,n in enumerate(self._data[self._currentDataIdx]): self._barSet.replace(i,n) if __name__=="__main__": from sys import arg, exit try: from PyQt5.QtWidgets import QApplication except ImportError: from PySide2.QtWidgets import QApplication a=QApplication(argv) m=MainWindow() m.show() m.resize(640,480) exit(a.exec_())
Hope this helps :o)
-
This works! THANKS!
But, i don't know why, if i call the function
set_zernike_plot
inside the__init__
of the classMainWindow
, it doen't work, while if i put the same code of the function in the__init__
, than it works 🤔 -
Glad it works.
I'm not sure about your other issue, I would need a more complete minimal example to try and trace the issue.
-
+1 for stepping away from designer, causes way more problems than it solves!
I used
startTimer
out of habit more than anything and because it makes for (IMHO) cleaner code, but I take your point and here, for completeness, is my example usingQTimer
instead:try: from PyQt5.QtCore import QTimer, pyqtSlot from PyQt5.QtGui import QColor from PyQt5.QtWidgets import QMainWindow from PyQt5.QtChart import QChartView, QBarSet, QBarSeries, QChart except ImportError: from PySide2.QtCore import QTimer, Slot as pyqtSlot from PySide2.QtGui import QColor from PySide2.QtWidgets import QMainWindow from PySide2.QtCharts import QtCharts QBarSet=QtCharts.QBarSet QBarSeries=QtCharts.QBarSeries QChart=QtCharts.QChart QChartView=QtCharts.QChartView class MainWindow(QMainWindow): def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self._data=[ [1,2,3,4,5,4,3,2,1], [5,4,3,2,1,2,3,4,5] ] self._currentDataIdx=0 self._barSet=QBarSet("Bar Set") self._barSet.setColor(QColor(0,230,0)) self._barSet.append(self._data[self._currentDataIdx]) self._barSeries=QBarSeries() self._barSeries.setBarWidth(1) self._barSeries.append(self._barSet) self._chart=QChart() self._chart.addSeries(self._barSeries) self._chart.createDefaultAxes() self._chart.legend().hide() self._chart.axisX(self._barSeries).setVisible(False) self._chart.axisY(self._barSeries).setVisible(False) self._chart.axisY(self._barSeries).setRange(0,6) self._chartView=QChartView(self._chart) self.setCentralWidget(self._chartView) self._timer=QTimer() self._timer.timeout.connect(self.onTimeout) self._timer.start(1000) @pyqtSlot() def onTimeout(self): self._currentDataIdx=1 if not self._currentDataIdx else 0 for i,n in enumerate(self._data[self._currentDataIdx]): self._barSet.replace(i,n) if __name__=="__main__": from sys import argv, exit try: from PyQt5.QtWidgets import QApplication except ImportError: from PySide2.QtWidgets import QApplication a=QApplication(argv) m=MainWindow() m.show() m.resize(640,480) exit(a.exec_())
-
@Denni-0
With all due respect, please don't be condescending.My code worked perfectly for both PyQt5 and PySide2, I tested it before posting as I always do.
I don't know why you feel the need to correct my example, most of what you said is either personal taste or in fact incorrect. I am a python dev with decades of experience, I know what I'm doing. Your post will not help people learn, it is simply insulting.
-
@Denni-0
In which case, I would be interested to see the traceback from the failure for my own learning, as I have it working right here now in front of me (both with PyQt5 and PySide2).I'm not sure why moderators are required; I have in no way insulted you or made any 'accusations' and I did my best to be respectful. However, if I have unintentionally caused you offence, then please accept my apologies. However, my comments regarding the opinions you inserted as comments in to my code stand; most of it is personal taste and some of it is incorrect.
For example, here are just some of the ways you have broken PEP-8 conventions in your modified version of my example.
- Imports should not be aligned (extraneous whitespace): https://www.python.org/dev/peps/pep-0008/#id26.
- Indentation should be 4 spaces not 2: https://www.python.org/dev/peps/pep-0008/#id17
- Instance variable names should start with lowercase: https://www.python.org/dev/peps/pep-0008/#id37
Further, I pass
argv
toQApplication
because there are somethings that can me modified from the command line in every (Py)Qt5 program (should the user wish to) such as the application style (-style=plastique
).The long and the short of it is, I really don't know why you felt it necessary to comment so heavily on someone elses post. It is condescending to assume you have the right to mark everyone elses work and, as you were quick to point out, we have moderators to moderate forums, we don't need you to police them too.
-
@Denni-0
Cool, I’m glad we’re on a square footing :o) and thanks for the clarity around the suspected error.PEP-8 is the gold standard for Python code, that’s why I referenced it and because you’d made comments regarding good practice etc., not because you had mentioned these things specifically, sorry if I wasn’t clear. I was trying to highlight how a lot of the things you had changed and made quite in-depth comments about were actually due to your taste/style and technically could be considered incorrect as they go against the generally accepted standard for our language of choice. And sorry about the indent one, your code looked like it was using 2 spaces on the forum, but I was obviously wrong. Also, as I don’t agree with all of your points regarding good practice or readability/ease of understanding (
idx
is no better thani
,abs
no clearer thanenumerate
to a newb IMO) I was trying to reference an independent 3rd party of standing, i.e. the Python Software Foundation. The bits aboutsuper()
, the reasons I passparent
and**kwargs
and where imports belong I will leave; we’re not going to agree, but my reasons are as informed, heartfelt and, IMO, valid as yours, I just don’t think there’s anything to be gained by going on about it here.I don’t think we are going to agree on all the points raised and the nice thing is we don’t have to. I guess my objection was based on my code being classed as bad or wrong based on what I saw as semantics and preference rather than technical errors.
Anyway, I’d rather extend an olive branch, agree to disagree and move on. If the original OP was helped by one of both of us then that is why we’re here.
See you around the forums dude, live long and prosper.