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.
-
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
orComponent
? 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? -
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
-
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!
-
AFAIK, lots of people are satisfied with QCustomPlot.
Depending on what you want/need, Qwt might also be of interest.