I am trying to develop a modified version of the example "qmlscatter", available in Qt examples. My intention is to plot a flight trajectory in the 3D environment, by modifying the data points of the "Data.qml" file. The actual coordinates of the flight path are stored in three different QVectors QVector<double> "cogPosX_", "cogPosY_" and "cogPosZ_", each index represents a latter time step.
As the forums mention, I need to use the ".setContextProperty" function to update the data values in the QML file. However, I can't find really find out how to get it working. I guess the main reason is that I am not referencing to the right "dataModel" id. Here you have my code:
datapointobject.h // This should define a generic Object that contains X Y and Z coordinates for each data point.
#ifndef DATAPOINTOBJECT_H
#define DATAPOINTOBJECT_H
#include <QWidget>
class DataObject : public QObject
{
Q_OBJECT
Q_PROPERTY(double x READ x WRITE setX)
Q_PROPERTY(double y READ y WRITE setY)
Q_PROPERTY(double z READ z WRITE setZ)
public:
DataObject(QObject* parent =0);//Constructor
DataObject(const double & x, const double & y, const double & z, QObject * parent=0);
~DataObject();//Destructor
double x() const;
double y() const;
double z() const;
void setX(const double &x);
void setY(const double &y);
void setZ(const double &z);
signals:
void xChanged();
void yChanged();
void zChanged();
private:
double m_x;
double m_y;
double m_z;
};
#endif // DATAPOINTOBJECT_H
datapointobject.cpp // This defines the constructors and functions.
#include "datapointobject.h"
#include<QDebug>
DataObject::DataObject(QObject *parent)//Constructor
: QObject(parent)
{
}
DataObject::DataObject(const double &x, const double &y, const double &z, QObject *parent)//Constructor
:QObject(parent), m_x(x), m_y(y), m_z(z)
{
}
DataObject::~DataObject(){//Destructor
}
double DataObject::x() const
{
return m_x;
}
double DataObject::y() const
{
return m_y;
}
double DataObject::z() const
{
return m_z;
}
void DataObject::setX(const double &x){
if (x != m_x) {
m_x = x;
emit xChanged();
}
}
void DataObject::setY(const double &y){
if (y != m_y) {
m_y = y;
emit yChanged();
}
}
void DataObject::setZ(const double &z){
if (z != m_z) {
m_z = z;
emit zChanged();
}
}
threeDviewer.h // this header defines the "Flightviewer" class, which creates a QQuickView instance, the responsible for plotting the 3D Scatter.
#ifndef FLIGHTVIEWER_H
#define FLIGHTVIEWER_H
#include <QtWidgets>
#include <QtGui/QGuiApplication>
#include <QtCore/QDir>
#include <QtQuick/QQuickView>
#include <QtQml/QQmlEngine>
#include "flight.h"
#include <QtQml>
class Flightviewer: public QWidget
{
Q_OBJECT
public:
Flightviewer(Flight displayedFlight, QString directory, QWidget *parent = 0);//Constructor
virtual ~Flightviewer();//Destructor
QQuickView viewer;
void showWindow();
void readFlightTrajectory(Flight flight);
};
#endif // FLIGHTVIEWER_H
threeDviewer.cpp //This file configures the QQuickView viewer instance. It is the responsible for importing the flight data to the QML data file.
#include <QtGui/QGuiApplication>
#include <QtCore/QDir>
#include <QtQuick/QQuickView>
#include <QtQml/QQmlEngine>
#include "window.h"
#include <QWidget>
#include "threeDviewer.h"
#include "datapointobject.h"
Flightviewer::Flightviewer(Flight displayedFlight, QString directory, QWidget*parent){
// 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
qmlRegisterType<DataObject>();
readFlightTrajectory(displayedFlight);
viewer.setVisible(false);//Open only after clicked.
viewer.engine()->addImportPath(extraImportPath.arg(QGuiApplication::applicationDirPath(),
QString::fromLatin1("qml")));
//! [4]
QObject::connect(viewer.engine(), &QQmlEngine::quit, &viewer, &QWindow::close);
//! [4]
viewer.setTitle(QStringLiteral("3D Flight Trajectory Visualization"));
//! [3]
viewer.setSource(QUrl("qrc:/qmlscatter/qml/qmlscatter/main.qml"));
//! [3]
viewer.setResizeMode(QQuickView::SizeRootObjectToView);
//! [2]
//! [2]
}
Flightviewer::~Flightviewer(){
}
void Flightviewer::showWindow(){
viewer.showMaximized();
}
void Flightviewer::readFlightTrajectory(Flight flight){
QList<QObject*> dataList;
for (int i=0; i<flight.cogPosX_.size();i++){
dataList.append(new DataObject(flight.cogPosX_.at(i),flight.cogPosY_.at(i),flight.cogPosZ_.at(i)));
}
QQmlContext * ctxt = viewer.rootContext();
ctxt->setContextProperty("dataModel", QVariant::fromValue(dataList));
viewer.update();
}
The 'QQuickViewer viewer' is initialized in an outer function, with the following command:
void Form::run3D(){
threeDviewer= new Flightviewer(displayedFlights_[flightIndex],directory_);//initiate viewer
threeDviewer->showWindow();//Show viewer
}
The data in the qml file is defined exactly as in the "qmlscatter" example:
Data.qml:
import QtQuick 2.1
Item {
property alias model: dataModel
ListModel {
id: dataModel
//ListElement{ xPos: -1000.0; yPos: 500.0; zPos: -5.0 }
}
}
The data is accessed by the main.qml file, which defines the Scatter3D Item:
main.qml
//! [0]
import QtQuick 2.1
import QtQuick.Layouts 1.0
import QtDataVisualization 1.0
import "."
//! [0]
//! [1]
Rectangle {
id: mainView
//! [1]
width: 500
height: 500
//! [4]
Data {
id: seriesData
}
//! [4]
//! [13]
Theme3D {
id: themeIsabelle
type: Theme3D.ThemeIsabelle
font.family: "Lucida Handwriting"
font.pointSize: 40
}
//! [13]
Theme3D {
id: themeArmyBlue
type: Theme3D.ThemeArmyBlue
}
//! [8]
//! [9]
Item {
id: dataView
anchors.bottom: parent.bottom
//! [9]
width: parent.width
height: parent.height - buttonLayout.height
//! [8]
//! [2]
Scatter3D {
id: scatterGraph
width: dataView.width
height: dataView.height
//! [2]
//! [3]
theme: themeIsabelle
shadowQuality: AbstractGraph3D.ShadowQualitySoftLow
//! [3]
//! [6]
axisX.segmentCount: 3
axisX.subSegmentCount: 2
axisX.labelFormat: "%.2f"
axisZ.segmentCount: 2
axisZ.subSegmentCount: 2
axisZ.labelFormat: "%.2f"
axisY.segmentCount: 2
axisY.subSegmentCount: 2
axisY.labelFormat: "%.2f"
//! [6]
//! [5]
Scatter3DSeries {
id: scatterSeries
//! [5]
//! [10]
itemLabelFormat: "Series 1: X:@xLabel Y:@yLabel Z:@zLabel"
//! [10]
//! [11]
ItemModelScatterDataProxy {
itemModel: seriesData.model
xPosRole: "xPos"
yPosRole: "yPos"
zPosRole: "zPos"
}
//! [11]
}
}
}
RowLayout {
id: buttonLayout
Layout.minimumHeight: cameraToggle.height
width: parent.width
anchors.left: parent.left
spacing: 0
//! [7]
NewButton {
id: shadowToggle
Layout.fillHeight: true
Layout.fillWidth: true
text: scatterGraph.shadowsSupported ? "Hide Shadows" : "Shadows not supported"
enabled: scatterGraph.shadowsSupported
onClicked: {
if (scatterGraph.shadowQuality === AbstractGraph3D.ShadowQualityNone) {
scatterGraph.shadowQuality = AbstractGraph3D.ShadowQualitySoftLow;
text = "Hide Shadows";
} else {
scatterGraph.shadowQuality = AbstractGraph3D.ShadowQualityNone;
text = "Show Shadows";
}
}
}
//! [7]
NewButton {
id: smoothToggle
Layout.fillHeight: true
Layout.fillWidth: true
text: "Use Smooth for Series One"
onClicked: {
if (scatterSeries.meshSmooth === false) {
text = "Use Flat for Series One";
scatterSeries.meshSmooth = true;
} else {
text = "Use Smooth for Series One"
scatterSeries.meshSmooth = false;
}
}
}
NewButton {
id: cameraToggle
Layout.fillHeight: true
Layout.fillWidth: true
text: "Change Camera Placement"
onClicked: {
if (scatterGraph.scene.activeCamera.cameraPreset === Camera3D.CameraPresetFront) {
scatterGraph.scene.activeCamera.cameraPreset =
Camera3D.CameraPresetIsometricRightHigh;
} else {
scatterGraph.scene.activeCamera.cameraPreset = Camera3D.CameraPresetFront;
}
}
}
NewButton {
id: themeToggle
Layout.fillHeight: true
Layout.fillWidth: true
text: "Change Theme"
onClicked: {
if (scatterGraph.theme.type === Theme3D.ThemeArmyBlue) {
scatterGraph.theme = themeIsabelle
} else {
scatterGraph.theme = themeArmyBlue
}
if (scatterGraph.theme.backgroundEnabled === true) {
backgroundToggle.text = "Hide Background";
} else {
backgroundToggle.text = "Show Background";
}
}
}
NewButton {
id: backgroundToggle
Layout.fillHeight: true
Layout.fillWidth: true
text: "Hide Background"
onClicked: {
if (scatterGraph.theme.backgroundEnabled === true) {
scatterGraph.theme.backgroundEnabled = false;
text = "Show Background";
} else {
scatterGraph.theme.backgroundEnabled = true;
text = "Hide Background";
}
}
}
NewButton {
id: exitButton
Layout.fillHeight: true
Layout.fillWidth: true
text: "Quit"
onClicked: Qt.quit(0);
}
}
}
I would be very grateful if you can give me some hint on how to program this properly. Thanks in beforehand!
QAbstractItemModelnot a list of your data objects.(see here) This will help you to interact with the model ie. data change notifiers etc. Also you have to register yourDataObjectusingqmlRegisterType. QML knows nothing about types you've defined in C++. - folibis