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.
  • J Offline
    J Offline
    jars121
    wrote on 3 Dec 2017, 22:48 last edited by jars121 12 Mar 2017, 23:41
    #2

    Just thinking out loud, could I perhaps use a Repeater in my QML document, to add XYPoints for each index of the c++ Vector? Pseudo-code below.

     LineSeries {
            name: "LineSeries"
            Repeater {
                model: MyCppVector.size
                XYPoint { x: MyCppVector.array[index]; y: index }
            }
        }
    
    1 Reply Last reply
    0
    • 6 Offline
      6 Offline
      6thC
      wrote on 4 Dec 2017, 01:32 last edited by 6thC 12 Apr 2017, 01:33
      #3

      I too use std containers and have my own QML / C++ solution. I may not have the answer but I have mine.
      I use a pointer to the QML LineSeries and do the data gathering, processing and LineSeries updates in C++

      In QML I set my axis:

      ChartView {
      DateTimeAxis {
                     id: valueAxisX
                     format: "m"
                     reverse: true;
                 }
      ValueAxis {
      id: valueAxisY
      min: 0
      max: 100.0
      tickCount: 5
      /reverse: true;
      }
      LineSeries { ....
      

      and if you want performance - you got it - just specify in your series (mine are LineSeries):
      useOpenGL: true (see https://doc.qt.io/qt-5/qabstractseries.html#useOpenGL-prop) - there are some functionality sacrifices using this - but I need speed.

      As far as the data goes, you just need to change that std vec into a QVector<QPointF> and call the QXYSeries replace() https://doc.qt.io/qt-5/qxyseries.html#replace-5 - building your data up first and using replace is fast. I'd recommend not using anything like add - they fire way too much off.

      I use a ptr to the QML object gathered in advance to get access to the series I want.

      J 1 Reply Last reply 4 Dec 2017, 03:17
      0
      • 6 6thC
        4 Dec 2017, 01:32

        I too use std containers and have my own QML / C++ solution. I may not have the answer but I have mine.
        I use a pointer to the QML LineSeries and do the data gathering, processing and LineSeries updates in C++

        In QML I set my axis:

        ChartView {
        DateTimeAxis {
                       id: valueAxisX
                       format: "m"
                       reverse: true;
                   }
        ValueAxis {
        id: valueAxisY
        min: 0
        max: 100.0
        tickCount: 5
        /reverse: true;
        }
        LineSeries { ....
        

        and if you want performance - you got it - just specify in your series (mine are LineSeries):
        useOpenGL: true (see https://doc.qt.io/qt-5/qabstractseries.html#useOpenGL-prop) - there are some functionality sacrifices using this - but I need speed.

        As far as the data goes, you just need to change that std vec into a QVector<QPointF> and call the QXYSeries replace() https://doc.qt.io/qt-5/qxyseries.html#replace-5 - building your data up first and using replace is fast. I'd recommend not using anything like add - they fire way too much off.

        I use a ptr to the QML object gathered in advance to get access to the series I want.

        J Offline
        J Offline
        jars121
        wrote on 4 Dec 2017, 03:17 last edited by
        #4

        @6thC Thank you very much, you've introduced a number of new (to me) elements in your approach. I don't suppose you have an example of how you would set up the LineSeries in c++ and then point to the LineSeries in QML? Similarly, how exactly is the OpenGL declaration made in c++? I understand the use of QXYSeries.replace().

        6 1 Reply Last reply 4 Dec 2017, 04:07
        0
        • J jars121
          4 Dec 2017, 03:17

          @6thC Thank you very much, you've introduced a number of new (to me) elements in your approach. I don't suppose you have an example of how you would set up the LineSeries in c++ and then point to the LineSeries in QML? Similarly, how exactly is the OpenGL declaration made in c++? I understand the use of QXYSeries.replace().

          6 Offline
          6 Offline
          6thC
          wrote on 4 Dec 2017, 04:07 last edited by 6thC 12 Apr 2017, 04:13
          #5

          @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 1 Reply Last reply 4 Dec 2017, 06:36
          0
          • 6 6thC
            4 Dec 2017, 04:07

            @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 4 Dec 2017, 06:36 last edited by jars121 12 Apr 2017, 09:20
            #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
            • 6 Offline
              6 Offline
              6thC
              wrote on 4 Dec 2017, 23:11 last edited by 6thC 12 Apr 2017, 23:14
              #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 6 Dec 2017, 07:23
              0
              • 6 6thC
                4 Dec 2017, 23:11

                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 6 Dec 2017, 07:23 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
                • 6 Offline
                  6 Offline
                  6thC
                  wrote on 6 Dec 2017, 10:55 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 6 Dec 2017, 11:03
                  0
                  • 6 6thC
                    6 Dec 2017, 10:55

                    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 6 Dec 2017, 11:03 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
                    • 6 Offline
                      6 Offline
                      6thC
                      wrote on 6 Dec 2017, 23:32 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
                      • 6 Offline
                        6 Offline
                        6thC
                        wrote on 6 Dec 2017, 23:43 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
                        • 6 Offline
                          6 Offline
                          6thC
                          wrote on 6 Dec 2017, 23:53 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 8 Dec 2017, 06:16 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
                            • 6 Offline
                              6 Offline
                              6thC
                              wrote on 15 Dec 2017, 07:27 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