Search code examples
c++qtqmlqabstractitemmodel

Continuously update a QML LineSeries ChartView with a data model from C++


I have a ChartView in QML I'm trying to update from a data model in C++, however, I'm running into certain issues where I can't seem to figure out a way to update the LineSeries properly.

My Sample QML Code looks like:

ChartView {
    id: dataChartView
    animationOptions: ChartView.NoAnimation
    theme: ChartView.ChartThemeDark
    antialiasing: true
    Layout.fillHeight: true
    Layout.fillWidth: true

    ValueAxis {
        id: axisXdata
        min: dataManager.xMin
        max: dataManager.xMax
    }

    ValueAxis {
        id: axisYdata
        min: 0
        max: 1
    }

    LineSeries {
        id:  dataLineSeries
        name: "Angle"
        axisX: axisXdata
        axisY: axisYdata
    }

    VXYModelMapper {
        id:  dataModelMapper
        model: dataManager.dataModel[data]
        series: dataLineSeries
        firstRow: 1
        xColumn: 0
        yColumn: 1
    }
}

The underlying model is a QAbstractTableModel that looks like this:

class DataModel : public QAbstractTableModel
{
    Q_OBJECT
public:
    enum{
        NameRole,
        ValueRole
    };

    explicit DataModel(QObject *parent = nullptr);
    QHash<int, QByteArray> roleNames() const override;
    QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
    int rowCount(const QModelIndex& parent = QModelIndex()) const override;
    int columnCount(const QModelIndex &parent = QModelIndex()) const override;
    void setMaxSize(int maxSize);
    int maxSize() const;
    Q_INVOKABLE void addData(const QPointF& point);

     public:
         void handleNewData(const QPointF& point);
    
    private:
        QVector<QPointF> m_data;
        int m_ModelMaxSize;
    
    signals:
         void newDataAdded(const QPointF& point);
    
    
    };

and the relevant addData() function simply just pushes the Point into the m_data vector

void MsclDataModel::addData(const QPointF &point)
{
    beginInsertRows(QModelIndex(), rowCount(), rowCount());
    m_data.push_back(point);
    endInsertRows();
}

I then have another class called DataManager which runs a function in another thread and just adds points to the data model

void DataManager::GeneratePoints()
{
    setXMin(QDateTime::currentDateTime().toMSecsSinceEpoch()); // for the min and max on the horizontal axis
    for(double t=0 ; ; t+=1)
       {
           double y = (1 + sin(t/10.0)) / 2.0;

        // many data models are required
           for(const auto& model : m_Models)
           {
               auto time = QDateTime::currentDateTime().toMSecsSinceEpoch();
               setXMax(qMax(m_xMax, time));
               model->handleNewData(QPointF(time, y));
           }
           std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}

My issue is with this method and with the number of models I have, the application slows to a crawl and crashes eventually after about a minute or so. I've tried to constrain the addData() function to only show the latest 500 points by using something like this:

void DataModel::addData(const QPointF &point)
{
    beginInsertRows(QModelIndex(), rowCount(), rowCount());
    if(m_data.size() == 500)
    {
        m_data.erase(m_data.begin());
    }
    m_data.push_back(point);
    endInsertRows();
}

However, my output looks something like: DataModel stops updating where the data model just stops updating after the 500th point.

I've also tried using a List to pop after max size of the list has been reached but the output is still the same. What am I doing wrong here?


Solution

  • Instead of beginInsertRows() and endInsertRows() I've used beginResetModel() and endResetModel() which works. I assume the range for the beginInsertRows() is wrong and it doesn't send an update anymore, so it stops drawing. You would probably also need to call beginRemoveRows() I would stick with a full reset.

    void DataModel::addData(const QPointF &point)
    {
        beginResetModel();
    
        if (m_data.size() == 500) {
            m_data.pop_front();
            emit xMinChanged();
        }
    
        m_data.push_back(point);
        emit xMaxChanged();
    
        endResetModel();
    }
    

    enter image description here


    Edit: This will also work.

    void DataModel::addData(const QPointF &point)
    {
        if (m_data.size() == 500) {
            beginRemoveRows(QModelIndex(), 0, 0);
            m_data.pop_front();
            endRemoveRows();
            emit xMinChanged();
        }
    
        beginInsertRows(QModelIndex(), rowCount(), rowCount());
        m_data.push_back(point);
        endInsertRows();
        emit xMaxChanged();
    }