Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. QML LineSeries with c++ Vector Data

QML LineSeries with c++ Vector Data

Scheduled Pinned Locked Moved Unsolved QML and Qt Quick
lineseriesqmlvector
15 Posts 2 Posters 14.6k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • 6thC6 6thC

    @jars121
    QML XYSeries gives you
    QML AbstractSeries
    which exposes: https://doc.qt.io/qt-5/qml-qtcharts-abstractseries.html#useOpenGL-prop

    useOpenGL : bool
    

    But you can also in C++ use the QAbstractSeries : useOpenGL https://doc.qt.io/qt-5/qabstractseries.html#useOpenGL-prop with the pointer of your series as such:

    pSeries->setUseOpenGL(true );
    // or disable
    pSeries->setUseOpenGL(false);
    

    I group up my series into charts both the LineSeries and the ChartView through a slot. I use the ChartView to set value dynamic ranges series data.

    I've exposed this ChartPointers instance to the QML Engine as: ChartPointersCpp

    context->setContextProperty(QLatin1Literal("ChartPointersCpp"), &coreEngineInstance->Charts );
    

    Called this instance's addSeries with the chart and series QML instances as such:

    // QML
    ChartView {
                id: chart;
    ...
    // I've just lazily hardcoded the series instance - I'm sure dynamic is fine too
    LineSeries {id: series1; ...
    LineSeries {id: series2; ...
    LineSeries {id: series3; ...
    ...
    

    I have an event trigger (onMouseclick: on a (custom legend) item to set the series visible: true/false

    function processSeries(series){
    visible = !visible; // toggle
    ... if visible 
    ChartPointersCpp.addSeries(chart, series);
    ... else not visible
    ChartPointersCpp.removeSeries(series); // we already know the chartview ptr from c++ now
    

    I receives this call as:

    ChartPointers::addSeries(QObject* chartView, QLineSeries *pSeries);
    

    Once you have pSeries ... QAbstractSeries (I have a QLineSeries hardcoded) is all yours. Just be careful for nullptrs and the rest as I still let QML be it's parent etc, my c++ doesn't assume there's a valid ptr, if it's invalid it will remove it from the memory container as it was nullptr

    I use the chartView to access chart properties declared by me around dynamic ranges:

    qint64 beginMs = pChartView->property("beginMs").toLongLong();
    

    Think that should be enough to have you having some fun...

    J Offline
    J Offline
    jars121
    wrote on last edited by jars121
    #6

    @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.

    1 Reply Last reply
    0
    • 6thC6 Offline
      6thC6 Offline
      6thC
      wrote on last edited by 6thC
      #7

      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 Oscilloscope

      Take 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.

      J 1 Reply Last reply
      0
      • 6thC6 6thC

        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 Oscilloscope

        Take 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.

        J Offline
        J Offline
        jars121
        wrote on last edited by
        #8

        @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?

        1 Reply Last reply
        0
        • 6thC6 Offline
          6thC6 Offline
          6thC
          wrote on last edited by
          #9

          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.

          J 1 Reply Last reply
          0
          • 6thC6 6thC

            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.

            J Offline
            J Offline
            jars121
            wrote on last edited by
            #10

            @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!

            1 Reply Last reply
            0
            • 6thC6 Offline
              6thC6 Offline
              6thC
              wrote on last edited by
              #11

              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.

              1 Reply Last reply
              0
              • 6thC6 Offline
                6thC6 Offline
                6thC
                wrote on last edited by
                #12

                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 ;
                
                1 Reply Last reply
                0
                • 6thC6 Offline
                  6thC6 Offline
                  6thC
                  wrote on last edited by
                  #13

                  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.

                  1 Reply Last reply
                  0
                  • J Offline
                    J Offline
                    jars121
                    wrote on last edited by
                    #14

                    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.

                    1 Reply Last reply
                    0
                    • 6thC6 Offline
                      6thC6 Offline
                      6thC
                      wrote on last edited by 6thC
                      #15

                      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.

                      1 Reply Last reply
                      1

                      • Login

                      • Login or register to search.
                      • First post
                        Last post
                      0
                      • Categories
                      • Recent
                      • Tags
                      • Popular
                      • Users
                      • Groups
                      • Search
                      • Get Qt Extensions
                      • Unsolved