Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

displaying a C++ QChart in a QML ChartView delegate



  • Hello all,

    I am looking to visually display several real-time QCharts that are updated with simulation data. I should have no problem with this portion; what I am confused about is the C++/QML interface.

    Disclaimer: I am using Qt 5.9.3 for a specific device.

    I have read through this LineChart example: https://doc.qt.io/qt-5.11/qtcharts-linechart-example.html

    And also the ChartView documentation: https://doc.qt.io/qt-5.11/qml-qtcharts-chartview.html

    My current approach is to use the following class as a context property:

    #include <QtCharts>
    #include <QLineSeries>
    #include <QProcess>
    #include <QQmlComponent>
    #include <QList>
    #include <QStringList>
    #include <QString>
    
    /*This class will be responsible for one specific module. Depending on the page, this model will contain a select number of params.
     * For example, on the summary page, we only want a brief overview of Priority 1 measurements, so the data models will be less compact.
     * Whereas on the following pages that detail full modules, the models will be larger.
     */
    
    
    class DataModel: public QObject
    {
    
        friend class QAbstractListModel;
    
        Q_OBJECT
    
    
    public:
        explicit DataModel(QObject * parent = nullptr);
        explicit DataModel(const QString file); //this is our main constructor here.
        explicit DataModel(DataModel&&); //move constructor
        explicit DataModel(const DataModel&); //copy constructor
    
        DataModel& operator=(const DataModel&);
        DataModel& operator=(DataModel&&);
    
        Q_INVOKABLE QString getTextBodyAt(int i);
        Q_INVOKABLE DataModule *getModuleAt(int i);
        Q_INVOKABLE int getModulesLength() { return modules.length(); }
        Q_INVOKABLE int getGraphsLength() { return graphs.length(); }
        Q_INVOKABLE DataPiece *getDataAt(int index) const;
        Q_INVOKABLE QLineSeries* getSeriesAt(int index) const {return series.at(index);}
        Q_INVOKABLE int getDataCount();
    
        int timeToSeconds(QTime* time) const;
        void addModule(DataModule* mod_);
        void addDataGraph(DataPiece* piece);
        void replaceDataGraph(DataPiece* piece, int index); //replaces the graph at the given index with a new graph for the supplied DataPiece.
        void removeDataGraphAt(int i); //removes the graph at the given index.
        const QVector<DataModule*> getModel() const;  
        const QVector<DataPiece*> getGraphs() {return graphs;}
        const QVector<QLineSeries*> getSeries() {return series;}
        const QVector<QTime*> getTimes() {return timers;}
    
        ~DataModel();
    signals:
        void pieceChanged(DataPiece*); //this signal might not be used anywhere.
        void pieceAdded(DataPiece*);
        void pieceReplaced(DataPiece*);
    
    public slots:
        void onClose();
        void on_pieceAdded(DataPiece *piece);
        void on_pieceReplaced(DataPiece *piece);
    
    private:
        QVector<DataModule*> modules; //separates Modules into Rectangles. Spacing modifiable.
        QVector<DataPiece*> graphs; //for every DataPiece, we will also have a QLineSeries and a QTimer.
        QVector<QLineSeries*> series;
        QVector<QTime*> timers;
    
    
    };
    

    Allowing me to import QLineSeries into my ListView of ChartViews here:

    //this will display our charts.
                        ListView {
                            objectName: "chartListView"
                            id: chartListView
                            x: 0
                            y: 130
                            width: 350
                            height: 350
    
                            anchors.top: controlRect.bottom
                            orientation: Qt.Vertical
    
                            /*
                                Procedure:
                                -load a DataPiece in to GraphModel. (name, units, value, maximum, minimum)
                                -Set up ChartView & LineSeries according to that DataPiece.
                             */
    
                            model: dataModel.getGraphsLength()
    
                            delegate: ChartView {
    
                                objectName: "chartView"
                                width: 350
                                height: 350
                                title: graphModel.getDataAt(index).getModuleName() + " " + graphModel.getDataAt(index).getName() + " vs. time"
                                property LineSeries imported_data
                                //data:
    
                                //need to import our data from dataModel. This is on the to-do list.
                            }
                        }
    

    -Is this a good approach? I am unsure if, in my context property class, if I should have a QVector of QCharts, or of QLineSeries.

    -In addition, I noticed in QML that ChartView has a property called "data", to which the ChartView documentation makes zero mention. Is this something I could use to import data to my delegate? If so, what type does "data" want? (LineSeries?)

    -Given that my axes will remain static for a given ChartView, I am thinking that these should be set up in my QML delegate. Is my thinking correct on this?

    Note: I should add that it is intended for the user to be able to add or remove graphs from the ListView of ChartViews.

    I apologize for the load of questions, I am just sort of in the dark as to how I should do this the proper way. The Qt documentation is lacking on this topic.

    Thanks in advance.



  • Still actively working to make this thing work. I would appreciate any comments on what I am trying to do, and if there is an easier or better way to do it.

    Thanks



  • Okay, so I have completely set up my data + signal handlers for new data in C++.

    My current problem is providing the correct format to the delegate of my ListView that will hold my dynamic set of ChartViews.

    Here is the .qml code:

    ListView {
                            objectName: "chartListView"
                            id: chartListView
                            x: 0
                            y: 130
                            width: 350
                            height: 350
    
                            anchors.top: controlRect.bottom
                            orientation: Qt.Vertical
    
                            /*
                                Procedure:
                                -load a DataPiece in to GraphModel. (name, units, value, maximum, minimum)
                                -Set up ChartView & LineSeries according to that DataPiece.
                             */
    
                            model: dataModel.getGraphsLength()
                            delegate: Component {
                                    ChartView { data: dataModel.getChartAt(index) }
    
                            } //data handling is done in C++.
    
                        }
    

    dataModel is a C++ class that I have registered as a context property.
    The error message I am getting from QML is : Error: Unknown method return type: QChart*. It points to where I am trying to set my ListView delegate.

    datamodel.h:

    class DataModel: public QObject
    {
    
        friend class QAbstractListModel;
    
        Q_OBJECT
    
    
    public:
        explicit DataModel(QObject * parent = nullptr);
        explicit DataModel(const QString file); //this is our main constructor here.
        explicit DataModel(DataModel&&); //move constructor
        explicit DataModel(const DataModel&); //copy constructor
    
        DataModel& operator=(const DataModel&);
        DataModel& operator=(DataModel&&);
    
        Q_INVOKABLE QString getTextBodyAt(int i);
        Q_INVOKABLE DataModule *getModuleAt(int i);
        Q_INVOKABLE int getModulesLength() { return modules.length(); }
        Q_INVOKABLE int getGraphsLength() { return graphs.length(); }
        Q_INVOKABLE DataPiece *getDataAt(int index) const;
        Q_INVOKABLE QChart* getChartAt(int index) const {return charts.at(index)->chart();}
        Q_INVOKABLE int getDataCount();
    
        //depending on the needed accuracy, we will have to look at which data type is needed. Probably won't end up being an int in all likelihood.
        Q_INVOKABLE int getCurrentTimeAt(int index);
    
        long timeToSeconds(QTime* time) const;
        void addModule(DataModule* mod_);
        void addDataGraph(DataPiece* piece);
        void replaceDataGraph(DataPiece* piece, int index); //replaces the graph at the given index with a new graph for the supplied DataPiece.
        void removeDataGraphAt(int i); //removes the graph at the given index.
        const QVector<DataModule*> getModel() const;  
        const QVector<DataPiece*> getGraphs() {return graphs;}
        const QVector<QTime*> getTimes() {return timers;}
    
        ~DataModel();
    signals:
        void pieceChanged(DataPiece*); //this signal might not be used anywhere.
        void pieceAdded(DataPiece*);
        void pieceReplaced(DataPiece*);
    
    public slots:
        void onClose();
        void on_pieceAdded(DataPiece *piece);
        void on_pieceReplaced(DataPiece *piece);
        void on_processError(QProcess::ProcessError error);
        void on_newValue(QString val);
    
    private:
        QVector<DataModule*> modules; //separates Modules into Rectangles. Spacing modifiable.
        QVector<DataPiece*> graphs; //for every DataPiece, we will also have a QLineSeries and a QTimer.
        QVector<QChartView*> charts;
        QVector<QTime*> timers;
    
    
    };
    

    So, should I be setting the delegate to be of type ChartView or Component? If I were to set it as a Component, how would I go about assigning its visual content to be my C++ ChartView at the index?


  • Lifetime Qt Champion

    Hi,

    First thing: QObject based class are not copiable.

    QChart is a widget, so if you want to use it from QtQuick, you should take a look at KDAB's Declarative Widgets module



  • @SGaist Sounds good. After wrestling with the QtCharts API since early this morning, I am kicking it to the curb and going with QCustomPlot. Seems to be a much cleaner, more efficient, and easier to use. I can't imagine I will have the same problems with QCustomPlot as I was with QtCharts.

    Nonetheless, I appreciate the response!


  • Lifetime Qt Champion

    AFAIK, lots of people are satisfied with QCustomPlot.

    Depending on what you want/need, Qwt might also be of interest.


Log in to reply