Search code examples
c++qmlqtcharts

How to create chart in QML with series data exposed from c++


I am filling QLineSeries data on c++ side and QML chart is supposed to consume them (and update as they change). Data producer is connected to newData which are added to the series data, and that should trigger repaint of the chart.

Previously, the LineSeries were manipulated in QML but now I don't know how to make the c++ QLineSeries instances accessible to QML.

// ...
#include<QtCharts/QLineSeries>
using namespace QtCharts;

/* class holding all data to be displayed as properties */
class UiData: public QObject{
   Q_OBJECT
   Q_PROPERTY(QLineSeries *xy READ getXy NOTIFY xyChanged);
   QLineSeries* getXy(){return &xy; }
   signals:
      void xyChanged();
   public slots:
      void newData(float x, float y){
         xy.append(x,y);
         emit xyChanged();
      }
   private:
      QLineSeries xy;
}

int main(int argc, char* argv[]){
   QApplication app(argc,argv);
   QQmlApplicationEngine engine;
   UiData uiData;
   /* make the instance accessiblt from QML */
   engine.rootContext()->setContextProperty("uiData",&uiData);
   engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
   // ...
   return qApp->exec();
}

main.qml:

ChartView{
   ValueAxis{
      id: _axisX;
   }
   ValueAxis{
      id: _axisY;
   }
   /*
   BEFORE:
   data in QML: easy, plus had JS slot calling _xySeries.append(...)
   */
   LineSeries{
      id: _xySeries;
      axisX: _axisX;
      axisY: _axisY;
   }

   /*
   NOW:
   how to use an already existing LineSeries instance (uiData.xy),
   and only change a few attributes, like axisX and axisY?

   Something like this:

   LineSeries(uiData.xy) {
      axisX: _axisX;
      axisY: _axisY;
   }

   or like this:

   component.onCompleted:{
      // where is something like ChartView.addSeries?
      // (there is ChartView.removeSeries already)
      uiData.xy.axisX=_axisX;                    
      uiData.xy.axisY=_axisY;
      addSeries(uiData.xy);
   }
   */
};

Is there a good solution to this?

In an extreme case, I can also create the whole ChartView in c++ (but then again, how to insert it into the QML?).


Solution

  • It can be said that LineSeries is a QLineSeries + other functionalities, so ChartView uses those other functionalities.

    What you must do is access the LineSeries in UiData, for this you must use the memory position.

    class UiData: public QObject{
        Q_OBJECT
        Q_PROPERTY(QLineSeries* xy READ xy WRITE setXy NOTIFY xyChanged)
    public:
        ...
        QLineSeries *xy() const
        {
            return mXy;
        }
        void setXy(QLineSeries *xy)
        {
            if(mXy == xy) return;
            if(mXy)
                if(mXy->parent() == this) // is owner
                    delete mXy;
            mXy = xy;
            emit xyChanged();
        }
    public slots:
        void newData(qreal x, qreal y){
            if(mXy){
                mXy->append(x, y);
            }
        }
        ...
    signals:
        void xyChanged();
        ...
    private:
        QLineSeries *mXy;
    };
    

    *.qml

    ChartView{
        ValueAxis{
            id: _axisX;
        }
        ValueAxis{
            id: _axisY;
        }
        LineSeries{
            id: _xySeries;
            axisX: _axisX;
            axisY: _axisY;
        }
    }
    Component.onCompleted: uiData.xy = _xySeries // <----
    

    As you can see, a QLineSeries is not being created in C ++, but the pointer serves only to reference LineSeries.

    The complete example can be found in the following link.