QML LineSeries with c++ Vector Data
-
@6thC Thanks again, that's given me plenty to work with.
I'm doing some reading at the moment, and have found that I can't use QtCharts with QGuiApplication, and have to instead use QApplication? I've tried swapping QGuiApplication in my .pro and .cpp files (QT += widgets and #include <QApplication> respectively), which prevents me from using the various QQml classes I'm already using (QQmlApplicationEngine, QQmlEngine, etc.)?
I must be missing some quite elementary; how can I use both QApplication for QtCharts and QGuiApplication for all my QML-based classes at the same time?
EDIT: It looks like I wasn't declaring QT += widgets early enough; I've now got a static LineSeries populated within my QML view. Now I'll start working on having c++ build the LineSeries and update/replace the plot in real-time.
-
Sweet, yes, with Charts it's Widget based. My project file has this entry:
QT += qml quick widgets quickcontrols2 charts
You should be good to go already on the charts end then! Just update the data. When the points have been replaced using:
void QXYSeries::replace(QVector<QPointF> points)
it Emits
QXYSeries::pointsReplaced()
You may need this somewhere - if it's working already I'd call it a win!:
QT_CHARTS_USE_NAMESPACE
I found most of this from pulling apart and playing with:
Qml OscilloscopeTake a look inside if you are curious. I modified this in a copy to use many more points.
It was 10 * series, each with 100, 000 points updating with the same random data found in the Oscilloscope example app - all at rates every 16ms!And I just have a crappy Intel HD Graphics 530! It pulls ~45 to 60 fps with this going on. Anyhow, good luck, let me know.
-
@6thC Thanks again for your help!
I've poured over the linked QML Oscilloscope example, and am afraid I've confused myself even further. I've just come across to c++ from Python (PyQt and PySide), so the declarations, use of header files, contextProperties, etc. are thoroughly confusing me at this point.
Just so I'm clear, do you update the QPointF vector/array from c++? The QML Oscilloscope example has a Timer function in the .qml ChartView file which polls c++ for data, which isn't the approach I'd like to use. As (I believe you've described), I'd like for c++ to collect the data and push it to QML (using the replace() function as you've described).
I've included a detailed breakdown of where I'm up to below (irrelevant sections omitted for simplicity).
Data.cpp:
#include "data.h" #include <QtCharts/QXYSeries" QT_CHARTS_USE_NAMESPACE Q_DECLARE_METATYPE(QAbstractSeries *) Q_DECLARE_METATYPE(QAbstractAxis *) Data::Data() : m_Value(0), m_Index(-1) { qRegisterMetaType<QAbstractSeries*>(); qRegisterMetaType<QAbstractAxis*>(); this->m_Timer = new QTimer(this); this->m_Timer->setInterval(200); connect(this->m_Timer, &QTimer::timeout, this, &Data::Timeout); this->m_Timer->start(); } void Data::dataUpdate(QAbstractSeries *series) { if (series) { QXYSeries *xySeries = static_cast<QXYSeries *>(series); m_Index++; if (m_Index > m_Data.count() - 1) m_Index = 0; QVector<QPointF> points = m_Data.at(m_Index); xySeries->replace(points); } } void Data::generateData(int value1, int value2) { //Receive the latest data values and append them to m_Data? //Once the vector/array has reached a certain size (e.g. 10), //Append new values to the end and remove initial values so the //Data set 'scrolls' with a fixed size } void DataDemo::Timeout() { int HIGH = 10; int LOW = 0; this->m_Value = rand() % (HIGH - LOW + 1) + LOW; emit ValueChanged(); generateData(m_Value); }
DataDemo.h:
#ifndef DATADEMO_H #define DATADEMO_H #include <QObject> #include <QTimer> #include <QtCore/QObject> #include <QtCharts/QAbstractSeries> QT_BEGIN_NAMESPACE class QQuickView; QT_END_NAMESPACE QT_CHARTS_USE_NAMESPACE class Data : public QObjects { Q_OBJECT public: Data(); Q_PROPERTY(int value READ value NOTIFY valueChanged) int value(){return this->m_Value;} //The below was taken from the QML Oscilloscope example //I don't know if it's needed in my case or what it's for explicit Data(QQuickView *appViewer, QObject *parent = 0); signals: void valueChanged(); private slots: void Timeout(); public slots: void dataUpdate(QAbstractSeries *series); void generateData(int value1, int value2); private: int m_Value; QTimer * m_Timer; QQuickView *m_appViewer; QList<QVector<QPointF> > m_Data; int m_index; }; #endif // DATADEMO_H
main.cpp:
//#include a whole host of items here using namespace std; int main(int argc, char *argv[]) { QApplication app(argc, argv); QQmlApplicationEngine engine; engine.rootContext()->setContextProperty(QStringLiteral("Data"), new Data()); engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); if (engine.rootObjects().isEmpty()) return -1; return app.exec(); }
main.qml:
ChartView { id: chartView property bool openGL: true property bool openGLSupported: true antialiasing: true onOpenGLChanged: { if (openGLSupported) { series("signal 1").useOpenGL = openGL; } } Component.onCompleted: { if (!series("signal 1").useOpenGL) { openGLSupported = false openGL = false } } //A couple of ValueAxis elements here LineSeries { id: lineSeries1 name: "signal 1" //If I use the below line I get OpenGLFrameBuffer issues for some reason useOpenGL: chartView.openGL } //I don't know if I want to use this Timer function (straight from the Oscilloscope demo) //I would rather the data update be pushed from c++ than requested from QML Timer { id: refreshTimer interval: 200 running: true repeat: true onTriggered: { Data.dataUpdate(chartView.series(0)); } } }
I've cut a considerable amount out of my actual code, and have renamed almost everything to make it easier to follow, so if something doesn't make sense it's because I've done so incorrectly for this post. The application compiles and runs without error (provide the openGL line mentioned above is commented out), but the plot remains empty (the axes, grid, etc. are shown).
Where am I going wrong?
-
Sorry to have confused you - they've only made that data class and timer so there's a constant supply of new data to show charts off - yes, you do use your data and with signals and slots - only replace the series points when you wish/require.
I won't be near a machine for another 12 hours or so but I'll take a look at what you have and see if I can help you out then.
Oh and sorry, I must have sent you the wrong example app, the one I had was a pure widget app I abused to make myself, it had a timer and everything but in c++ side.
-
@6thC That's quite alright! I'm thoroughly confused almost all the time at the moment as I embark on this c++ adventure; your input has been one of the few things I've managed to delve into and understand at least at a high level.
If you don't mind have a flick through whenever you get a chance I'd really appreciate it! I'll continue reading for the time being, and will made another attempt tomorrow. Thanks again!
-
Bit of a sidetrack, but maybe useful if you follow me this way later on... I've just been testing closing and destroy()ing charts from the QML side. I've come across a nice memory access violation with my design. I should have seen it coming - using pointers to objects that weren't mine.
I think my design has it's risks, I have mitigated it by calling my remove method on close just before I call destroy.
Realizing this and solving that also exposed a risk in my own c++ design where I would deadlock (certainly) when I go multi-thread - and could hit currently via using a QML created signal.
I was emit'ing inside c++ but that emit was inside iterations of a container member protected by a mutex / std::lock_guard. The slot connected to that emit'd signal sent it's own signal which a different slot but in the original class used a lock over that same member container.Anyhow. I'm learning as I go - I'm kind of apologizing if my way isn't the ideal way - it is my way and my way is working for me currently. Whether it's the most efficient way... I always like to listen to others with experience.
-
To fix what you have - as long as in c++ somewhere you use charts you have:
#include <QtCharts> // I think that macro earlier just does the namespacing, I just removed the macro in my own application for this myself... using namespace QtCharts;
Really, the only changes you need to make is in your ChartView (QML).
- give it dimensions somehow
height & width / anchors.fill: parent; / etc - give it Axes ( axis(s) )
DateTimeAxis { id: valueAxisX format: "s" reverse: true; } ValueAxis { id: valueAxisY // vertical axis min: 0 max: 100.0 tickCount: 5 //reverse: true; }
- in your LineSeries - assign it those axes:
axisX: valueAxisX; axisY : valueAxisY ;
- give it dimensions somehow
-
As far as OpenGL issues go... are you setting anything OpenGL manually? Setting the QSurfaceFormat yourself?
Cause I had support raise this: https://bugreports.qt.io/browse/QTBUG-63807 - should be fixed now in 5.9.3
Let me know - hopefully now you can ditch all timer references and get to pumping that chart series with data.
-
I'm afraid I'm getting progressively more lost the longer I attempt this. I'm reading through the QML Oscilloscope example for the 10th time, and I just can't understand how to apply that to my function. I.e. pushing data to the chart rather than QML requesting data.
I'll continue reading and attempting, and will hopefully post back here with some updates/revised code of the coming couple of days.
-
Oh hey. Didn't see you were still stuck.
So if you've QML declared your (LineSeries, ScatterSeries, and SplineSeries) - in C++ you'd have a slot (or Q_INVOKABLE) receive the object as a QXYSeries* ptr.
Once you have access to a QXYSeries object: (you have a pointer to your series and you know which c++ std::vector matches what series etc) the only work you have to do is reformat your std::vector into a QVector<QPointF> before the replace() call of the QLineSeries*
The LineSeries will do the work to repaint.
I guess it signals: void QXYSeries::pointsReplaced() which kicks of some ChartView dirty or repaint request. Anyhow, it's free to us.