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:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With