QT charts extremely slow - QLineSeries
-
Hi,
I use QT charts (QML-Python) for ploting, unfortunatelly I found out it is very slow (500 values takes about 4 seconds to plot!).
I found similar problem on the forum - they solved it (Creating the series first, and adding it to the Chart after all of the appends solved the problem)
but they use C instead of python - Is it possible to bring the solution to python? I could not find a solution :-(
Link is here: https://www.qtcentre.org/threads/69429-SOLVED-QLineSeries-extremely-slow -
Your idea to serie1.append(points) had no improvement - it took also about 5 seconds, but your second idea with serie1.replace(points) has great result!
Solution in python:
start = time.time() points = [] for i in self.x: points.append(QPointF(i, self.y[i])) # filling points with my prepared data serie1.replace(points) # fill list of points in one call end = time.time() print(end - start)
for 50000 values it takes only 126 ms, for 5000 values it takes 13 ms
I think we can consider this thread as solved - or any other ideas?
Thank you all @JonB @Bob64 @fcarney for help! -
@Witc
Just do exactly the same principle as what that reference shows. Create the series and then add it to the chart rather than adding an empty series to the chart and then putting data into the series. You don't need someone else to write it in Python for you. -
@JonB
Thank you for reply. I tried again like that: but it does not work, any idea how to pass the data to chart?
Thank you for any hintmain.py
#Edit# This Python file uses the following encoding: utf-8 import os import sys from pathlib import Path from PySide6.QtCore import QObject, Slot, Signal from PySide6.QtQml import QQmlApplicationEngine from PySide6.QtWidgets import QApplication from PySide6.QtCharts import QLineSeries class AppWindow(QObject): # Signals from python to QML sigPlotData = Signal(QLineSeries) pokus = Signal(str) def __init__(self): super().__init__(None) def plotData(self): x = [0,1,2,3,4,5,6,7,8,9] y = [10,20,10,5,23,15,12,2,9,21] mySerie = QLineSeries() #mySerie.attachAxis(self.xSigVal) #mySerie.attachAxis(self.ySigVal) for i in x: mySerie.append(i, y[i]) # pass the data to the QML chart self.sigPlotData.emit(mySerie) QApplication.processEvents() if __name__ == "__main__": app = QApplication(sys.argv) engine = QQmlApplicationEngine() appWin = AppWindow() engine.rootContext().setContextProperty("backend", appWin) engine.load(os.fspath(Path(__file__).resolve().parent / "main.qml")) if not engine.rootObjects(): sys.exit(-1) appWin.plotData() sys.exit(app.exec())
main.qml
import QtQuick import QtQuick.Window import QtQuick.Controls 6.3 import QtCharts 2.3 import QtQuick.Controls.Material 2.12 ApplicationWindow { id: root width: 960 height: 640 visible: true Rectangle { id: appRectangle color: "#2a2a2a" anchors.fill: parent ChartView { id: myChart title: "data view" anchors.fill: parent axes: [ ValueAxis{ id: axisX min: 0 max: 20//maxX }, ValueAxis{ id: axisY min: 0 max: 50 } ] Connections { target: backend function onSigPlotData(serie){ myChart.removeAllSeries(); var serie1 = myChart.createSeries(ChartView.SeriesTypeLine, "signal", axisX, axisY); serie1 = serie serie1.useOpenGl = true } } } } }
-
@Witc said in QT charts extremely slow - QLineSeries:
but it does not work
I do not know what that means.
I know nothing about QML. Have you verified
function onSigDatasetChanged(serie)
gets called at all? Do you have to do something to make themySerie = QLineSeries()
passable as a parameter to the signal inself.sigDatasetChanged.emit(mySerie)
? -
@JonB I had mistake when calling the signal, Now my
self.sigDatasetChanged.emit(mySerie)
is emited (renamed to sigPlotData), but no line is plotted - have no idea how to pass the mySerie to Chart
main.py repaired for emitting signal with mySerie:# This Python file uses the following encoding: utf-8 import os import sys from pathlib import Path from PySide6.QtCore import QObject, Slot, Signal from PySide6.QtQml import QQmlApplicationEngine from PySide6.QtWidgets import QApplication from PySide6.QtCharts import QLineSeries class AppWindow(QObject): # Signals from python to QML sigPlotData = Signal(QLineSeries) def __init__(self): super().__init__(None) def plotData(self): x = [0,1,2,3,4,5,6,7,8,9] y = [10,20,10,5,23,15,12,2,9,21] mySerie = QLineSeries() #mySerie.attachAxis(self.xSigVal) #mySerie.attachAxis(self.ySigVal) for i in x: mySerie.append(i, y[i]) # pass the data to the QML chart self.sigPlotData.emit(mySerie) QApplication.processEvents() if __name__ == "__main__": app = QApplication(sys.argv) engine = QQmlApplicationEngine() appWin = AppWindow() engine.rootContext().setContextProperty("backend", appWin) engine.load(os.fspath(Path(__file__).resolve().parent / "main.qml")) if not engine.rootObjects(): sys.exit(-1) appWin.plotData() sys.exit(app.exec())
-
@JonB said in QT charts extremely slow - QLineSeries:
Have you verified function
onSigDatasetChanged(serie)
gets called at all?var serie1 = myChart.createSeries(ChartView.SeriesTypeLine, "signal", axisX, axisY); serie1 = serie
I don't know, but this does not look right. What is the point of creating a series and assigning it to a variable if you then overwrite that variable on the next line? Aren't you supposed to do something like
serie1.append(serie)
ormyChart.addSeries(serie)
to put data points or a series onto a chart? -
-
@Witc said in QT charts extremely slow - QLineSeries:
I also told that I want to SeriesTypeLine type
Isn't that irrelevant when the series you have created and wish to add is already a
QLineSeries
?Last time of asking:
Have you verified function
onSigDatasetChanged(serie)gets called at all?
When that is working, print out what is in the
serie
parameter passed into it. Does it indeed contain all the datapoints you added into it? In which case I would have thoughtmyChart.addSeries(serie)
is indeed the right thing to do. -
@JonB I know the
function onSigPlotData(serie)
is called because in that function I addedconsole.log("We are going to plot")
and I cann see this text in consoleYou said print out what is in the serie - I tried like that:
console.log(serie)
- it only printed out QLineSeries(0x265e5c1c270) to my console -
@Witc said in QT charts extremely slow - QLineSeries:
I added console.log("We are going to plot") and I cann see this text in console
That is good but you didn't show/say so previously, so now we know.
I tried like that: console.log(serie) - it only printed out QLineSeries(0x265e5c1c270) to my console
So what can you do about this? Don't you think e.g.
console.log(serie.count())
would give better information? -
@Witc said in QT charts extremely slow - QLineSeries:
When add console.log(serie.count()) it prints out again just some addres,
That does not sound good. I know your series is OK at the Python side, the question is how to get it at the QML side.
I said I know nothing about QML. At this point I think you should read e.g. https://stackoverflow.com/questions/54459713/pass-c-lineseries-to-qml-charts
You cannot pass QLineSeries to QML since ChartView has no possibility to add series. There is ChartView.createSeries() only. So the only way is to create a series in QML and pass array of points from C++ so you could add the points to the series using XYSeries.append(x,y).
-
@JonB OK, so they say I should pass array of points from python to QML (It looks like we are getting to my first post here). I can pass X and Y point by point, but have no idea how to pass all my array or list
x = [0,1,2,3,4,5,6,7,8,9] y = [10,20,10,5,23,15,12,2,9,21]
-
@Witc
My final word: I think QMLChartView
/XYSeries
etc. are not the same thing as their QtQChartView
/QXYSeries
etc. counterparts. So when you saidI found similar problem on the forum - they solved it (Creating the series first, and adding it to the Chart after all of the appends solved the problem)
but they use C instead of python - Is it possible to bring the solution to python? I could not find a solution :-(
I don't think it has anything to do with C++/Python, you picked a Qt widgets issue/solution which cannot be applied to QML....
Which means I only see QML XYSeries.append(real x, real y) method to add one point at a time (I do not see a way to create a series on its own and then add it to a QML
ChartView
). Which you can call in a loop given the two arrays of points.If that is "too slow", I don't know what you can do. Maybe you can hide the chart view and re-show it after all points have been added, maybe that would make it faster.
Otherwise have you read e.g. https://stackoverflow.com/questions/38467769/add-c-qabstractseries-to-qml-chartview ? Does that help for C++ -> QML?
-
I only have experience with QML charts and a C++ back end, but the approach I have taken is to create the series in QML and expose functions from C++ that accept a series so that it can be filled from the C++ side.
I don't know how this translates to Python. In particular I do not know what the mechanism is for exposing Python-side functions into QML. I presume there is some sort of equivalent way of doing this (in C++ it is necessary to declare a function as
Q_INVOKABLE
in aQObject
-derived class). Also I do not know how efficient this is going to be as there will be additional marshalling between Python and the underlying C++ layer.This might be one of those cases where it is necessary to bite the bullet and drop down to C/C++ to handle this from the Python.
-
I don't know how to do this in Python, but this is how I solve this issue with QML charts. I create a function that updates all the data in the series at once that is callable from QML:
class PointUpdater : public QObject { Q_OBJECT public: PointUpdater(QObject* parent=nullptr) : QObject(parent) { } public slots: void update(QAbstractSeries *series, QVariantList points){ if(!series){ return; } QVector<QPointF> newpoints; for(QVariant point: points){ auto list = point.toList(); newpoints.push_back(QPointF(list.at(0).toDouble(), list.at(1).toDouble())); } QXYSeries *xySeries = static_cast<QXYSeries *>(series); xySeries->replace(newpoints); } };
-
@JonB @fcarney @Bob64
Probably with my best solution below, it takes about 5 seconds to plot 5000 valuesPython file - the slot fillSigChart is called from QML
@Slot(QLineSeries) def fillSigChart(self,serie1): start = time.time() for i in self.x: serie1.append(i, self.y[i]) # filling serie with my prepared data end = time.time() print(end - start)
In QML file: I call the slot above
Connections{ target: backend function onSigPlotData(x,y){ myChart.removeAllSeries(); myChart.zoomReset() var serie1 = myChart.createSeries(ChartView.SeriesTypeLine, "Signal ", axisX, axisY); serie1.useOpenGl = true backend.fillSigChart(serie1) // pass serie1 to python and then fill it with data } }