Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Push QML ChartView updates from c++

Tags:

I'm trying to adapt the Qt5.9 QML Oscilloscope example to have the graph data pushed from c++ rather than requested from QML. Below are the pertinent sections from the QML Oscilloscope example.

datasource.h:

#ifndef DATASOURCE_H
#define DATASOURCE_H

#include <QtCore/QObject>
#include <QtCharts/QAbstractSeries>

QT_BEGIN_NAMESPACE
class QQuickView;
QT_END_NAMESPACE

QT_CHARTS_USE_NAMESPACE

class DataSource : public QObject
{
    Q_OBJECT
public:
    explicit DataSource(QQuickView *appViewer, QObject *parent = 0);

Q_SIGNALS:

public slots:
    void generateData(int type, int rowCount, int colCount);
    void update(QAbstractSeries *series);

private:
    QQuickView *m_appViewer;
    QList<QVector<QPointF> > m_data;
    int m_index;
};

#endif // DATASOURCE_H

datasource.cpp:

#include "datasource.h"
#include <QtCharts/QXYSeries>
#include <QtCharts/QAreaSeries>
#include <QtQuick/QQuickView>
#include <QtQuick/QQuickItem>
#include <QtCore/QDebug>
#include <QtCore/QtMath>

QT_CHARTS_USE_NAMESPACE

Q_DECLARE_METATYPE(QAbstractSeries *)
Q_DECLARE_METATYPE(QAbstractAxis *)

DataSource::DataSource(QQuickView *appViewer, QObject *parent) :
    QObject(parent),
    m_appViewer(appViewer),
    m_index(-1)
{
    qRegisterMetaType<QAbstractSeries*>();
    qRegisterMetaType<QAbstractAxis*>();

    generateData(0, 5, 1024);
}

void DataSource::update(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);
        // Use replace instead of clear + append, it's optimized for performance
        xySeries->replace(points);
    }
}

void DataSource::generateData(int type, int rowCount, int colCount)
{
    // Remove previous data
    m_data.clear();

    // Append the new data depending on the type
    for (int i(0); i < rowCount; i++) {
        QVector<QPointF> points;
        points.reserve(colCount);
        for (int j(0); j < colCount; j++) {
            qreal x(0);
            qreal y(0);
            switch (type) {
            case 0:
                // data with sin + random component
                y = qSin(3.14159265358979 / 50 * j) + 0.5 + (qreal) rand() / (qreal) RAND_MAX;
                x = j;
                break;
            case 1:
                // linear data
                x = j;
                y = (qreal) i / 10;
                break;
            default:
                // unknown, do nothing
                break;
            }
            points.append(QPointF(x, y));
        }
        m_data.append(points);
    }
}

main.cpp:

#include <QtWidgets/QApplication>
#include <QtQml/QQmlContext>
#include <QtQuick/QQuickView>
#include <QtQml/QQmlEngine>
#include <QtCore/QDir>
#include "datasource.h"

int main(int argc, char *argv[])
{
    // Qt Charts uses Qt Graphics View Framework for drawing, therefore 
QApplication must be used.
    QApplication app(argc, argv);

    QQuickView viewer;

    // The following are needed to make examples run without having to install the module
    // in desktop environments.
#ifdef Q_OS_WIN
    QString extraImportPath(QStringLiteral("%1/../../../../%2"));
#else
    QString extraImportPath(QStringLiteral("%1/../../../%2"));
#endif
    viewer.engine()->addImportPath(extraImportPath.arg(QGuiApplication::applicationDirPath(),
                                  QString::fromLatin1("qml")));
    //QObject::connect(viewer.engine(), &QQmlEngine::quit, &viewer, &QWindow::close);

    viewer.setTitle(QStringLiteral("QML Oscilloscope"));

    DataSource dataSource(&viewer);
    viewer.rootContext()->setContextProperty("dataSource", &dataSource);

    viewer.setSource(QUrl("qrc:/qml/qmloscilloscope/main.qml"));
    viewer.setResizeMode(QQuickView::SizeRootObjectToView);
    viewer.setColor(QColor("#404040"));
    viewer.show();

    return app.exec();
}

ScopeView.qml:

import QtQuick 2.0
import QtCharts 2.1

ChartView {
    id: chartView
    animationOptions: ChartView.NoAnimation
    theme: ChartView.ChartThemeDark
    property bool openGL: true
    property bool openGLSupported: true
    onOpenGLChanged: {
        if (openGLSupported) {
            series("signal 1").useOpenGL = openGL;
        }
    }
    Component.onCompleted: {
        if (!series("signal 1").useOpenGL) {
            openGLSupported = false
            openGL = false
        }
    }

    ValueAxis {
        id: axisY1
        min: -1
        max: 4
    }

    ValueAxis {
        id: axisX
        min: 0
        max: 1024
    }

    LineSeries {
        id: lineSeries1
        name: "signal 1"
        axisX: axisX
        axisY: axisY1
        useOpenGL: chartView.openGL
    }

    Timer {
        id: refreshTimer
        interval: 1 / 60 * 1000 // 60 Hz
        running: true
       repeat: true
        onTriggered: {
            dataSource.update(chartView.series(0));
        }
    }
}

Rather than using the Timer in QML, I'd like to use an existing Timeout in a c++ class to push new data to the QML ChartView. I have two questions:

  1. How would I achieve this for the QML Oscilloscope example posted above?
  2. What format would be most suitable for the c++ data to facilitate this? I'm thinking a QVector of some sort; the data will be an integer or float with a vector index.
like image 720
jars121 Avatar asked Dec 09 '17 00:12

jars121


1 Answers

As you say in a comment you need to pass a series, then we create a method that receives the series and saves it in a member of the C ++ class, We also create a QTimer, and we do the same to update the interval:

*.h

public:
    Q_INVOKABLE void setSeries(QAbstractSeries *series);
    Q_INVOKABLE void setInterval(int interval);
    [...]
private:
    QXYSeries *mSeries;
    QTimer *timer;
    [...]

*.cpp

void DataSource::setSeries(QAbstractSeries *series)
{
    if (series) {
        mSeries = static_cast<QXYSeries *>(series);
    }
}

Then we remove the update argument and use mSeries:

DataSource::DataSource(QQuickView *appViewer, QObject *parent) :
    QObject(parent),
    m_appViewer(appViewer),
    m_index(-1)
{
    [...]
    timer = new QTimer(this);
    connect(timer, &QTimer::timeout, this, &DataSource::update);
    timer->start(1 / 60 * 1000 );
}

void DataSource::update()
{
    if (mSeries) {
        m_index++;
        if (m_index > m_data.count() - 1)
            m_index = 0;

        QVector<QPointF> points = m_data.at(m_index);
        // Use replace instead of clear + append, it's optimized for performance
        mSeries->replace(points);
    }
}

void DataSource::setInterval(int interval)
{
    if(timer){
        if(timer->isActive())
            timer->stop();
        timer->start(interval);
    }
}

*.qml

Component.onCompleted: {
    dataSource.setSeries(chartView.series(0));
    if (!series("signal 1").useOpenGL) {
        openGLSupported = false
        openGL = false
    }
}
[...]
function changeRefreshRate(rate) {
    dataSource.setInterval(1 / Number(rate) * 1000);
    //refreshTimer.interval = 1 / Number(rate) * 1000;
}

You can find the complete example in the following link.

like image 84
eyllanesc Avatar answered Oct 10 '22 21:10

eyllanesc