QML Repeater with Qt Graphs Invalid Y Values
-
I'm trying to have a TableModel work in Qt Graphs work with a QML Repeater. The graph works correctly when I comment out the Repeater and uncomment the included non-repeater version in QML. However when the Repeater is present, the model throws the error
initializeXYFromModel Invalid Y coordinate index in model mapper. file:<filepath-to-qml/RepeaterPlot.qml:49:17: Unable to assign QString to qlonglongan equal amount of times to the number specified in the model property.
All of this makes me think this is a QML problem rather than a model/C++ problem. I have hard coded all of the source file values for forum purposes. I'm using Qt 6.9.3 on windows 10.
Would anyone have a hunch why the Y value column is not working no matter what I do and/or have suggestions for fixing it?
//QML import QtQuick import QtGraphs import MyImport GraphsView { id: graphsViewRoot anchors.fill: parent axisX: ValueAxis { id: xAxis min: -10 max: 260 } axisY: ValueAxis { id: yAxis min: -60 max: 60 } Repeater { model: 2 parent: graphsViewRoot delegate: Item { // Define the LineSeries instance LineSeries { id: lineSeries1 name: "Data Series " //+ (model.index + 1) } // Define the XYModelMapper instance XYModelMapper { model: MyRepeaterModel series: lineSeries1 xSection: 0 ySection: model.index + 1 } } } /* /////////////////////////////////////////////////////////////////// ////////////////THIS WORKS BY ITSELF///////////////// //////////////////////////////////////////////////////////////////////////////////////// XYModelMapper { model: MyRepeaterModel series: lineSeries1 xSection: 0 ySection: 1 } LineSeries { id: lineSeries1 name: "Data Series 1" } */ }////mainwindow and QML exposure myRepeaterModel = new RepeaterModel(); qmlRegisterSingletonInstance("MyImport", 1, 0, "MyRepeaterModel",myRepeaterModel); //Source #include "RepeaterModel.hpp" #include <iostream> RepeaterModel::RepeaterModel(QObject *parent) : QAbstractTableModel(parent){ mySpecStruct = {}; } int RepeaterModel::rowCount(const QModelIndex &parent) const{ if (parent.isValid()) return 0; return 64; } int RepeaterModel::columnCount(const QModelIndex &parent) const{ if (parent.isValid()) return 0; return 3; } QHash<int, QByteArray> RepeaterModel::roleNames() const{ QHash<int, QByteArray> roles; roles[XRole] = "xPos"; roles[YRole] = "yPos"; return roles; } QVariant RepeaterModel::data(const QModelIndex &index, int role) const{ if (!index.isValid()) return QVariant(); int row = index.row(); int col = index.column(); if (role == Qt::DisplayRole) { if (col == 0) { return row; } else if (col == 1) { return 2; } else if (col == 2) { return 3; } } return QVariant(); } ////header #ifndef RepeaterModel_HPP #define RepeaterModel_HPP #include <QAbstractTableModel> class RepeaterModel : public QAbstractTableModel { Q_OBJECT enum MyRoles { XRole = Qt::UserRole + 1, YRole }; public: explicit RepeaterModel(QObject *parent = nullptr); // Basic functionality: int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QHash<int, QByteArray> roleNames() const override; void SetSpectrumData(ftStruct& specStruct); ftStruct getSpectrumData() const; signals: void spectrumChanged(); private: ftStruct mySpecStruct; }; #endif // RepeaterModel_HPP -
Repeater cannot be used to add LineSeries to a GraphsView. LineSeries is not a visual Item (not a QML Item), and GraphsView does not process its QML children. As a result, series created by Repeater are either never instantiated correctly or never registered with the graph.
The correct solution is to use Instantiator and explicitly call GraphsView.addSeries().
Instantiator is intended for exactly this case: creating non-visual objects and manually attaching them.
Your code below is the correct and supported approach:
import QtQuick import QtQuick.Controls.Basic import QtQuick.Controls 6.5 import QtGraphs import Qt.labs.qmlmodels Item { id: processPlot property var model: TableModel { id: seriesModel TableModelColumn { display: "time" } TableModelColumn { display: "raw_pressure" } TableModelColumn { display: "filtered_pressure" } TableModelColumn { display: "raw_position" } TableModelColumn { display: "filtered_position" } TableModelColumn { display: "raw_speed" } TableModelColumn { display: "filtered_speed" } rows: [ { time: 0, raw_pressure: 0, filtered_pressure: 0, raw_position: 240000, filtered_position: 240000, raw_speed: 0, filtered_speed: 0, }, { time: 10, raw_pressure: 5, filtered_pressure: 4, raw_position: 240400, filtered_position: 240400, raw_speed: 20, filtered_speed: 18, }, { time: 20, raw_pressure: 10, filtered_pressure: 9, raw_position: 241000, filtered_position: 241000, raw_speed: 20, filtered_speed: 19, } ] } GraphsView { id: graphsView anchors.fill: parent axisX: ValueAxis { max: 100 } axisY: ValueAxis {} } Instantiator { model: 6 Component.onCompleted: { console.log("Repeater completed"); } delegate: LineSeries { id: series Component.onCompleted: { console.log("Repeated item completed"); graphsView.addSeries(series); } XYModelMapper { model: processPlot.model series: series xSection: 0 ySection: index + 1 } Component.onDestruction: graphsView.removeSeries(series) } } } -
Thank you very much for looking at this in the first place. The Initializer is an unfamiliar Component I'll be using better. The TableModel however, is something I cannot use.
When your provide code works runs by itself, it doesn't produce the error. However, the reason for the initializeXYModel is tied to QAbstractTableModel, not the Repeater or the Instantiator . When I run the XYModelMapper with the TableModel from the Qt.labs.qmlmodels as the model, the error disappears. However, when I use the QAbstractTableModel, the error is thrown with either the Repeater or the Instantiator .
Is there a way to have the Instantiator or Repeater work with the QAbstractTableModel as the model property to your knowledge?