Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. LineSeries with QAbstractListModel's roles
QtWS25 Last Chance

LineSeries with QAbstractListModel's roles

Scheduled Pinned Locked Moved Unsolved QML and Qt Quick
qabstractlistmoqmlchartssplineline
12 Posts 3 Posters 2.1k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • T Offline
    T Offline
    Tyrannic
    wrote on 15 Oct 2020, 15:53 last edited by
    #1

    Hello, I've been searching for a way to use the QML Charts with a class derived from QAbstractListModel. It's a fairly trivial implementation of QAbstractListModel with user-defined roles. I've successfully been able to use QML's ListView by specifying custom delegates that use the named roles, but I would like a similar feature within QML Charts. The closest I've seen is the use of VXYModelMapper. However, that deals directly with columns of data, and not roles. I would prefer to not have to change my model from a QAbstractListModel to a QAbstractTableModel.

    Would I need to reimplement my own model mapper? Is there an existing one? Is there a better way to do all of this?

    Here's my trival implementation of QAbstractListModel:
    TrivialListModel.h:

    struct ModelElement {
        QDateTime receivedTime;
        int var1;
        double var2;
    };
    
    class TrivialListModel : public QAbstractListModel
    {
        Q_OBJECT
    public:
        enum Roles {
            ReceivedTime = Qt::UserRole + 1,
            Var1,
            Var2
        };
        Q_ENUM(Roles)
    
        explicit TrivialListModel (QObject *parent = nullptr);
    
        // Basic functionality:
        QModelIndex index(int row, int column, const QModelIndex& parent) const override;
        int rowCount(const QModelIndex &parent = QModelIndex()) const override;
        QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
        virtual QHash<int, QByteArray> roleNames() const override;
    
        // Custom functionality
        void addData(const ModelElement& element);
    
    private:
        QList<ModelElement> m_data;
    };
    

    TrivialListModel.cpp:

    TrivialListModel::TrivialListModel(QObject *parent)
        : QAbstractListModel(parent)
    {
    }
    
    QModelIndex TrivialListModel::index(int row, int column, const QModelIndex& parent) const
    {
        if(parent.isValid()) {
            return {};
        }
    
        return createIndex(row, column);
    }
    
    int TrivialListModel::rowCount(const QModelIndex &parent) const
    {
        if (parent.isValid())
            return 0;
    
        return m_data.size();
    }
    
    QVariant TrivialListModel::data(const QModelIndex &index, int role) const
    {
        if (!index.isValid())
            return QVariant();
    
        if (index.row() < 0 || index.row() >= m_data.size())
            return QVariant();
    
        const auto modelElement = m_data.at(index.row());
    
        switch(role) {
            case ReceivedTime: {
                return modelElement.receivedTime;
            }
            case Var1: {
                return modelElement.var1;
            }
            case Var2: {
                return modelElement.var2;
            }
        }
    
        return {};
    }
    
    QHash<int, QByteArray> TrivialListModel::roleNames() const
    {
        return {
            {ReceivedTime, "receivedTime"},
            {Var1, "var1"},
            {Var2, "var2"}
        }
    }
    
    void TrivialListModel::addData(const ModelElement& element)
    {
        beginInsertRows(QModelIndex(), m_data.size(), m_data.size());
    
        m_data.append(element);
    
        endInsertRows();
    }
    

    Desired QML (or something similar):

    ChartView {
        anchors.fill: parent
        antialiasing: true
        legend.visible: true
    
        SplineSeries {
            VXYModelMapper { // Custom ModelWrapper?
                model: listModel // Assume this was already put into the root context
                xColumn: "receivedTime"
                yColumn: "var1"
            }
        }
    }
    

    Any thoughts on this would be greatly appreciated. Thanks!

    1 Reply Last reply
    0
    • F Offline
      F Offline
      fcarney
      wrote on 15 Oct 2020, 16:43 last edited by
      #2

      I am not sure of exactly what you need, but would a DelegateModel help with this? I am not sure if the DelegateModel can create the objects SplineSeries requires though.

      C++ is a perfectly valid school of magic.

      T 1 Reply Last reply 15 Oct 2020, 18:07
      0
      • F fcarney
        15 Oct 2020, 16:43

        I am not sure of exactly what you need, but would a DelegateModel help with this? I am not sure if the DelegateModel can create the objects SplineSeries requires though.

        T Offline
        T Offline
        Tyrannic
        wrote on 15 Oct 2020, 18:07 last edited by
        #3

        @fcarney I don't think a DelegateModel would be appropriate for what I'm looking for. Essentially I'm just looking for a way to map chart series to a model's roles and not columns.

        1 Reply Last reply
        0
        • F Offline
          F Offline
          fcarney
          wrote on 15 Oct 2020, 19:12 last edited by fcarney
          #4

          I don't think SplineSeries takes a model. You are right DelegateModel maps a model to a model. You want something that can read a model and instance objects in the SplineSeries:

          import QtQuick 2.12
          import QtQuick.Controls 2.12
          import QtQuick.Window 2.12
          import QtCharts 2.15
          import QtQml.Models 2.12
          
          ApplicationWindow {
              visible: true
              width: 400
              height: 900
              title: qsTr("Dynamic Charts")
          
              ListModel {
                  id: listModel
          
                  ListElement {xv:0; yv:0}
                  ListElement {xv:0.25; yv:0.10}
                  ListElement {xv:0.25; yv:0.25}
                  ListElement {xv:0.50; yv:0.50}
              }
          
              Column {
                  width: parent.width
                  ChartView {
                      width: parent.width
                      height: 300
                      antialiasing: true
                      legend.visible: false
          
                      Instantiator {
                          model: listModel
                          onObjectAdded: splineseries.insert(index, object.x, object.y)
                          onObjectRemoved: splineseries.remove(index)
                          QtObject{
                              property real x: xv
                              property real y: yv
                          }
                      }
          
                      SplineSeries {
                          id: splineseries
                      }
                  }
              }
          
              //Component.onCompleted: console.log(listModel.count)
          }
          

          I tried a Repeater inside the SplineSeries, but it would not create items for whatever reason. Unsure why. I don't think the SplineSeries liked the Repeater.

          C++ is a perfectly valid school of magic.

          T 1 Reply Last reply 15 Oct 2020, 21:30
          0
          • F fcarney
            15 Oct 2020, 19:12

            I don't think SplineSeries takes a model. You are right DelegateModel maps a model to a model. You want something that can read a model and instance objects in the SplineSeries:

            import QtQuick 2.12
            import QtQuick.Controls 2.12
            import QtQuick.Window 2.12
            import QtCharts 2.15
            import QtQml.Models 2.12
            
            ApplicationWindow {
                visible: true
                width: 400
                height: 900
                title: qsTr("Dynamic Charts")
            
                ListModel {
                    id: listModel
            
                    ListElement {xv:0; yv:0}
                    ListElement {xv:0.25; yv:0.10}
                    ListElement {xv:0.25; yv:0.25}
                    ListElement {xv:0.50; yv:0.50}
                }
            
                Column {
                    width: parent.width
                    ChartView {
                        width: parent.width
                        height: 300
                        antialiasing: true
                        legend.visible: false
            
                        Instantiator {
                            model: listModel
                            onObjectAdded: splineseries.insert(index, object.x, object.y)
                            onObjectRemoved: splineseries.remove(index)
                            QtObject{
                                property real x: xv
                                property real y: yv
                            }
                        }
            
                        SplineSeries {
                            id: splineseries
                        }
                    }
                }
            
                //Component.onCompleted: console.log(listModel.count)
            }
            

            I tried a Repeater inside the SplineSeries, but it would not create items for whatever reason. Unsure why. I don't think the SplineSeries liked the Repeater.

            T Offline
            T Offline
            Tyrannic
            wrote on 15 Oct 2020, 21:30 last edited by Tyrannic
            #5

            @fcarney I did get your example code to work, but expanding that to my problem seems a bit unfeasible. I provided my "trivial" example, but in the real world I have almost 100 roles. It seems that I'd have to create a property in the QtObject that was in your Instantiator for each one of my roles.

            I'm really just looking for something that works somewhat like the example I provided originally, because there's actually a 3d charting QML object that does do it via roles, exactly the way I want, except in 3d and not 2d:

            Scatter3D {
                anchors.fill: parent
                        
                Scatter3DSeries {
                    ItemModelScatterDataProxy {
                        itemModel: itemModel
                                
                        xPosRole: "receivedTime" // Item roles from the original code I provided
                        yPosRole: "val1"
                        zPosRole: "val2"
                    }
                }
            }
            

            I'd really like essentially what is above, but in 2d chart form. I'm not sure why QML provides these objects for their 3d charting library, but not the 2d one.

            1 Reply Last reply
            0
            • GrecKoG Online
              GrecKoG Online
              GrecKo
              Qt Champions 2018
              wrote on 16 Oct 2020, 07:40 last edited by
              #6

              You correctly identified the problem that VXYModelMapper works with model with columns and your model has multiple roles instead of columns.

              If you don't want to modify your model to also add columns, you could write a proxy model transposing roles to columns as a middle man. For inspiration, the reverse has been done there : https://github.com/KDAB/KDToolBox/tree/master/qt/model_view/sortProxyModel

              T 1 Reply Last reply 16 Oct 2020, 20:42
              0
              • GrecKoG GrecKo
                16 Oct 2020, 07:40

                You correctly identified the problem that VXYModelMapper works with model with columns and your model has multiple roles instead of columns.

                If you don't want to modify your model to also add columns, you could write a proxy model transposing roles to columns as a middle man. For inspiration, the reverse has been done there : https://github.com/KDAB/KDToolBox/tree/master/qt/model_view/sortProxyModel

                T Offline
                T Offline
                Tyrannic
                wrote on 16 Oct 2020, 20:42 last edited by Tyrannic
                #7

                @GrecKo Thanks GrecKo for the inspiration for that. I created my own QIdentityProxyModel (wasn't sure if I should use that one, or QAbstractProxyModel) as follows:

                class TrivialTableProxyModel: public QIdentityProxyModel
                {
                    Q_OBJECT
                    QML_ELEMENT
                public:
                    TrivialTableProxyModel(QObject* parent = nullptr) : QIdentityProxyModel(parent) {};
                
                    virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override {
                        if(!index.isValid()) {
                            return {};
                        }
                        
                        if(role != Qt::DisplayRole) {
                            return {};
                        }
                        qDebug() << "Proxy:" << index << ", Role:" << role;
                        
                        return sourceModel()->data(createIndex(index.row(), 0), index.column() + Qt::UserRole + 1); // Adding Qt::UserRole+1 since that's where my roles start
                    }
                

                And in QML I implemented it as follows:

                ChartView {
                    anchors.fill: parent
                    antialiasing: true
                    legend.visible: true
                    
                    ScatterSeries {
                        name: "Data"
                        axisX: DateTimeAxis { }
                        axisY: ValueAxis { }
                
                        VXYModelMapper {
                            xColumn: 0 // Received time role/column
                            yColumn: 1 // Var1 role/column
                            model: trivialTableProxyModel // Assume this was inserted into the root context
                        }
                    }
                }
                

                It seems like it's much closer (I even got it working with manually setting the VXYModelMapper's rowCount), but when I leave it at the default -1, it seems to endlessly call the data function. I even went through the effort of turning my original model into a QAbstractTableModel, but with the same result. Here's an output of the print statements from the proxy's data function and the model's data function:

                Proxy: QModelIndex(1,0,0x0,HousekeepingTableProxyModel(0x399856f580)), Role: 0
                Model: QModelIndex(1,0,0x0,HousekeepingTableProxyModel(0x399856f580)), Role: 1
                Proxy: QModelIndex(1,1,0x0,HousekeepingTableProxyModel(0x399856f580)), Role: 0
                Model: QModelIndex(1,0,0x0,HousekeepingTableProxyModel(0x399856f580)), Role: 1
                Proxy: QModelIndex(2,0,0x0,HousekeepingTableProxyModel(0x399856f580)), Role: 0
                Model: QModelIndex(2,0,0x0,HousekeepingTableProxyModel(0x399856f580)), Role: 1
                Proxy: QModelIndex(2,1,0x0,HousekeepingTableProxyModel(0x399856f580)), Role: 0
                Model: QModelIndex(2,0,0x0,HousekeepingTableProxyModel(0x399856f580)), Role: 1
                ...
                Proxy: QModelIndex(12065,0,0x0,HousekeepingTableProxyModel(0x399856f580)), Role: 0
                Model: QModelIndex(12065,0,0x0,HousekeepingTableProxyModel(0x399856f580)), Role: 1
                Proxy: QModelIndex(12065,1,0x0,HousekeepingTableProxyModel(0x399856f580)), Role: 0
                Model: QModelIndex(12065,0,0x0,HousekeepingTableProxyModel(0x399856f580)), Role: 1
                Proxy: QModelIndex(12066,0,0x0,HousekeepingTableProxyModel(0x399856f580)), Role: 0
                Model: QModelIndex(12066,0,0x0,HousekeepingTableProxyModel(0x399856f580)), Role: 1
                Proxy: QModelIndex(12066,1,0x0,HousekeepingTableProxyModel(0x399856f580)), Role: 0
                Model: QModelIndex(12066,0,0x0,HousekeepingTableProxyModel(0x399856f580)), Role: 1
                

                It may be hard to decipher, but the translation is working from roles/columns. I'm just now not sure why VXYModelMapper endlessly calls the data functions with ever increasing row counts. Is this a bug, or did I implement my model incorrectly?

                1 Reply Last reply
                0
                • F Offline
                  F Offline
                  fcarney
                  wrote on 16 Oct 2020, 20:47 last edited by
                  #8

                  @Tyrannic said in LineSeries with QAbstractListModel's roles:

                  ScatterSeries

                  I don't see a dataProxy in the ScatterSeries docs like I see in the Scatter3DSeries. I don't see any docs that a ScatterSeries takes a model. How is this supposed to work?

                  C++ is a perfectly valid school of magic.

                  T 1 Reply Last reply 16 Oct 2020, 20:56
                  0
                  • F Offline
                    F Offline
                    fcarney
                    wrote on 16 Oct 2020, 20:56 last edited by
                    #9

                    Anyway, I may have a way to do go wide on your data. But I am unsure if this fits your data. I don't have a good feel for what is in your model:

                    import QtQuick 2.12
                    import QtQuick.Controls 2.12
                    import QtQuick.Window 2.12
                    import QtCharts 2.15
                    import QtQml.Models 2.12
                    
                    ApplicationWindow {
                        visible: true
                        width: 400
                        height: 900
                        title: qsTr("Dynamic Charts")
                    
                        ListModel {
                            id: listModel
                    
                            ListElement {xv1:0.00; yv1:0.00; xv2:0.10; yv2:0.10; xv3:0.25; yv3:0.10; xv4:0.10; yv4:0.25}
                            ListElement {xv1:0.50; yv1:0.50; xv2:0.75; yv2:0.50; xv3:0.50; yv3:0.75; xv4:1.00; yv4:1.00}
                        }
                    
                        Column {
                            width: parent.width
                            ChartView {
                                width: parent.width
                                height: 300
                                antialiasing: true
                                legend.visible: false
                    
                                Instantiator {
                                    model: listModel
                                    onObjectAdded: {
                                        for(var subindex=0; subindex<8; ++subindex){
                                            console.log(index*8+subindex)
                                            splineseries.insert(index*8+subindex, object.points[subindex].x, object.points[subindex].y)
                                        }
                                    }
                                    onObjectRemoved: {
                                        for(var subindex=0; subindex<8; ++subindex){
                                            splineseries.remove(index*8+subindex)
                                        }
                                    }
                                    delegate: QtObject{
                                        property var points: {
                                            var arr = []
                    
                                            arr.push(Qt.point(xv1, yv1))
                                            arr.push(Qt.point(xv2, yv2))
                                            arr.push(Qt.point(xv3, yv3))
                                            arr.push(Qt.point(xv4, yv4))
                    
                                            return arr
                                        }
                                    }
                                }
                    
                                SplineSeries {
                                    id: splineseries
                                }
                            }
                        }
                    }
                    

                    C++ is a perfectly valid school of magic.

                    1 Reply Last reply
                    0
                    • F fcarney
                      16 Oct 2020, 20:47

                      @Tyrannic said in LineSeries with QAbstractListModel's roles:

                      ScatterSeries

                      I don't see a dataProxy in the ScatterSeries docs like I see in the Scatter3DSeries. I don't see any docs that a ScatterSeries takes a model. How is this supposed to work?

                      T Offline
                      T Offline
                      Tyrannic
                      wrote on 16 Oct 2020, 20:56 last edited by
                      #10

                      @fcarney Well, that was somewhat the point, was that it was frustrating that the Scatter3dSeries could take a roles as demonstrated by my example code, but not the ScatterSeries. Either way, I'm one step closer to having a solution thanks to @GrecKo, but as illustrated by my last post, VXYModelMapper seems to be misbehaving when rowCount is -1. That or I messed up the Proxy and/or model.

                      1 Reply Last reply
                      0
                      • F Offline
                        F Offline
                        fcarney
                        wrote on 16 Oct 2020, 20:59 last edited by fcarney
                        #11

                        For checking your model put this in your models constructor:

                        new QAbstractItemModelTester(this, QAbstractItemModelTester::FailureReportingMode::Warning, this);
                        

                        This will print out issues it finds. You can look at the source code to that class to see exactly what it is testing in the Qt source.

                        C++ is a perfectly valid school of magic.

                        T 1 Reply Last reply 16 Oct 2020, 21:08
                        0
                        • F fcarney
                          16 Oct 2020, 20:59

                          For checking your model put this in your models constructor:

                          new QAbstractItemModelTester(this, QAbstractItemModelTester::FailureReportingMode::Warning, this);
                          

                          This will print out issues it finds. You can look at the source code to that class to see exactly what it is testing in the Qt source.

                          T Offline
                          T Offline
                          Tyrannic
                          wrote on 16 Oct 2020, 21:08 last edited by
                          #12

                          @fcarney I put the QAbstractItemModelTester in both my model, and my proxy model, and... no errors. I also swapped it to FailureReportingMode::Fatal to double check, and nothing. The endless calls to data from VXYModelMapper continue.

                          1 Reply Last reply
                          0

                          1/12

                          15 Oct 2020, 15:53

                          • Login

                          • Login or register to search.
                          1 out of 12
                          • First post
                            1/12
                            Last post
                          0
                          • Categories
                          • Recent
                          • Tags
                          • Popular
                          • Users
                          • Groups
                          • Search
                          • Get Qt Extensions
                          • Unsolved