summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSami Varanka <sami.varanka@qt.io>2024-05-02 17:18:30 +0300
committerSami Varanka <sami.varanka@qt.io>2024-05-10 10:57:39 +0300
commit09f0342e64e29244c003a06467f0d1f2ee54567b (patch)
tree3864765f1cdc11f86d9fa75f0a3e1e8d9584574d
parent34734ae3f8c8b2bad9b9ec98a810de869fedcc57 (diff)
Add XYModelMapper
Task-number: QTBUG-125027 Change-Id: Ic0ea8bb216abe3dd5a5732958ab5dfe2fdc7e610 Reviewed-by: Kaj Grönholm <kaj.gronholm@qt.io>
-rw-r--r--src/graphs2d/CMakeLists.txt1
-rw-r--r--src/graphs2d/xychart/qxymodelmapper.cpp650
-rw-r--r--src/graphs2d/xychart/qxymodelmapper.h67
-rw-r--r--src/graphs2d/xychart/qxymodelmapper_p.h68
-rw-r--r--tests/auto/cpp2dtest/CMakeLists.txt1
-rw-r--r--tests/auto/cpp2dtest/qgxymodelmapper/CMakeLists.txt10
-rw-r--r--tests/auto/cpp2dtest/qgxymodelmapper/tst_xymodelmapper.cpp584
7 files changed, 1381 insertions, 0 deletions
diff --git a/src/graphs2d/CMakeLists.txt b/src/graphs2d/CMakeLists.txt
index 318cd74..4e5759a 100644
--- a/src/graphs2d/CMakeLists.txt
+++ b/src/graphs2d/CMakeLists.txt
@@ -16,6 +16,7 @@ qt_internal_extend_target(Graphs
scatterchart/qscatterseries.cpp scatterchart/qscatterseries.h scatterchart/qscatterseries_p.h
xychart/qxyseries.cpp xychart/qxyseries.h xychart/qxyseries_p.h
xychart/qxypoint.cpp xychart/qxypoint_p.h
+ xychart/qxymodelmapper.cpp xychart/qxymodelmapper.h xychart/qxymodelmapper_p.h
axis/qabstractaxis.cpp axis/qabstractaxis.h axis/qabstractaxis_p.h
axis/barcategoryaxis/qbarcategoryaxis.cpp axis/barcategoryaxis/qbarcategoryaxis.h axis/barcategoryaxis/qbarcategoryaxis_p.h
axis/axisgrid.cpp axis/axisgrid_p.h
diff --git a/src/graphs2d/xychart/qxymodelmapper.cpp b/src/graphs2d/xychart/qxymodelmapper.cpp
new file mode 100644
index 0000000..22ec057
--- /dev/null
+++ b/src/graphs2d/xychart/qxymodelmapper.cpp
@@ -0,0 +1,650 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+#include <QtCore/QAbstractItemModel>
+#include <QtGraphs/QXYModelMapper>
+#include <QtGraphs/QXYSeries>
+#include "qxymodelmapper_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QXYModelMapper
+ \inmodule QtGraphs
+ \ingroup graphs_2D
+
+ \brief The QXYModelMapper class is a model mapper for line,
+ spline, and scatter series.
+
+ Model mappers enable using a data model derived from the QAbstractItemModel
+ class as a data source for a chart. A vertical model mapper is used to
+ create a connection between a line, spline, or scatter series and the data
+ model that has \e X and \e Y columns for the coordinates and holds the data
+ points for the XYSeries as rows. A \e TableModel is a natural choice
+ for the model.
+
+ Both model and series properties can be used to manipulate the data. The
+ model mapper keeps the series and the data model in sync.
+
+ \sa QXYSeries
+*/
+/*!
+ \qmltype XYModelMapper
+ \instantiates QXYModelMapper
+ \inqmlmodule QtGraphs
+ \ingroup graphs_qml_2D
+
+ \brief A model mapper for XYSeries.
+
+ Model mappers enable using a data model derived from the QAbstractItemModel
+ class as a data source for a chart. A vertical model mapper is used to
+ create a connection between a line, spline, or scatter series and the data
+ model that has \e X and \e Y columns for the coordinates and holds the data
+ points for the XYSeries as rows. A \e TableModel is a natural choice
+ for the model.
+
+ Both model and series properties can be used to manipulate the data. The
+ model mapper keeps the series and the data model in sync.
+
+ \sa XYSeries
+*/
+
+QXYModelMapper::~QXYModelMapper() {}
+
+QXYModelMapper::QXYModelMapper(QObject *parent)
+ : QObject{*(new QXYModelMapperPrivate), parent}
+{}
+
+QXYModelMapper::QXYModelMapper(QXYModelMapperPrivate &dd, QObject *parent)
+ : QObject(dd, parent)
+{}
+
+QAbstractItemModel *QXYModelMapper::model() const
+{
+ Q_D(const QXYModelMapper);
+ return d->m_model;
+}
+
+void QXYModelMapper::setModel(QAbstractItemModel *model)
+{
+ if (model == 0)
+ return;
+
+ Q_D(QXYModelMapper);
+ if (d->m_model) {
+ QObjectPrivate::disconnect(d->m_model,
+ &QAbstractItemModel::dataChanged,
+ d,
+ &QXYModelMapperPrivate::onModelUpdated);
+ QObjectPrivate::disconnect(d->m_model,
+ &QAbstractItemModel::rowsInserted,
+ d,
+ &QXYModelMapperPrivate::onModelRowsAdded);
+ QObjectPrivate::disconnect(d->m_model,
+ &QAbstractItemModel::rowsRemoved,
+ d,
+ &QXYModelMapperPrivate::onModelRowsRemoved);
+ QObjectPrivate::disconnect(d->m_model,
+ &QAbstractItemModel::columnsInserted,
+ d,
+ &QXYModelMapperPrivate::onModelColumnsAdded);
+ QObjectPrivate::disconnect(d->m_model,
+ &QAbstractItemModel::columnsRemoved,
+ d,
+ &QXYModelMapperPrivate::onModelColumnsRemoved);
+ QObjectPrivate::disconnect(d->m_model,
+ &QAbstractItemModel::modelReset,
+ d,
+ &QXYModelMapperPrivate::initializeXYFromModel);
+ QObjectPrivate::disconnect(d->m_model,
+ &QAbstractItemModel::layoutChanged,
+ d,
+ &QXYModelMapperPrivate::initializeXYFromModel);
+ QObjectPrivate::disconnect(d->m_model,
+ &QAbstractItemModel::destroyed,
+ d,
+ &QXYModelMapperPrivate::handleModelDestroyed);
+ }
+
+ d->m_model = model;
+ d->initializeXYFromModel();
+ // connect signals from the model
+ QObjectPrivate::connect(d->m_model,
+ &QAbstractItemModel::dataChanged,
+ d,
+ &QXYModelMapperPrivate::onModelUpdated);
+ QObjectPrivate::connect(d->m_model,
+ &QAbstractItemModel::rowsInserted,
+ d,
+ &QXYModelMapperPrivate::onModelRowsAdded);
+ QObjectPrivate::connect(d->m_model,
+ &QAbstractItemModel::rowsRemoved,
+ d,
+ &QXYModelMapperPrivate::onModelRowsRemoved);
+ QObjectPrivate::connect(d->m_model,
+ &QAbstractItemModel::columnsInserted,
+ d,
+ &QXYModelMapperPrivate::onModelColumnsAdded);
+ QObjectPrivate::connect(d->m_model,
+ &QAbstractItemModel::columnsRemoved,
+ d,
+ &QXYModelMapperPrivate::onModelColumnsRemoved);
+
+ QObjectPrivate::connect(d->m_model,
+ &QAbstractItemModel::modelReset,
+ d,
+ &QXYModelMapperPrivate::initializeXYFromModel);
+ QObjectPrivate::connect(d->m_model,
+ &QAbstractItemModel::layoutChanged,
+ d,
+ &QXYModelMapperPrivate::initializeXYFromModel);
+ QObjectPrivate::connect(d->m_model,
+ &QAbstractItemModel::destroyed,
+ d,
+ &QXYModelMapperPrivate::handleModelDestroyed);
+ Q_EMIT modelChanged();
+}
+
+QXYSeries *QXYModelMapper::series() const
+{
+ Q_D(const QXYModelMapper);
+ return d->m_series;
+}
+
+void QXYModelMapper::setSeries(QXYSeries *series)
+{
+ Q_D(QXYModelMapper);
+ if (d->m_series) {
+ QObjectPrivate::disconnect(d->m_series,
+ &QXYSeries::pointAdded,
+ d,
+ &QXYModelMapperPrivate::onPointAdded);
+ QObjectPrivate::disconnect(d->m_series,
+ &QXYSeries::pointRemoved,
+ d,
+ &QXYModelMapperPrivate::onPointRemoved);
+ QObjectPrivate::disconnect(d->m_series,
+ &QXYSeries::pointReplaced,
+ d,
+ &QXYModelMapperPrivate::onPointReplaced);
+ QObjectPrivate::disconnect(d->m_series,
+ &QXYSeries::destroyed,
+ d,
+ &QXYModelMapperPrivate::handleSeriesDestroyed);
+ QObjectPrivate::disconnect(d->m_series,
+ &QXYSeries::pointsRemoved,
+ d,
+ &QXYModelMapperPrivate::onPointsRemoved);
+ }
+
+ if (series == 0)
+ return;
+
+ d->m_series = series;
+ d->initializeXYFromModel();
+ // connect the signals from the series
+ QObjectPrivate::connect(d->m_series,
+ &QXYSeries::pointAdded,
+ d,
+ &QXYModelMapperPrivate::onPointAdded);
+ QObjectPrivate::connect(d->m_series,
+ &QXYSeries::pointRemoved,
+ d,
+ &QXYModelMapperPrivate::onPointRemoved);
+ QObjectPrivate::connect(d->m_series,
+ &QXYSeries::pointReplaced,
+ d,
+ &QXYModelMapperPrivate::onPointReplaced);
+ QObjectPrivate::connect(d->m_series,
+ &QXYSeries::destroyed,
+ d,
+ &QXYModelMapperPrivate::handleSeriesDestroyed);
+ QObjectPrivate::connect(d->m_series,
+ &QXYSeries::pointsRemoved,
+ d,
+ &QXYModelMapperPrivate::onPointsRemoved);
+ Q_EMIT seriesChanged();
+}
+
+int QXYModelMapper::first() const
+{
+ Q_D(const QXYModelMapper);
+ return d->m_first;
+}
+
+void QXYModelMapper::setFirst(int first)
+{
+ Q_D(QXYModelMapper);
+ d->m_first = qMax(first, 0);
+ d->initializeXYFromModel();
+ Q_EMIT firstChanged();
+}
+
+int QXYModelMapper::count() const
+{
+ Q_D(const QXYModelMapper);
+ return d->m_count;
+}
+
+void QXYModelMapper::setCount(int count)
+{
+ Q_D(QXYModelMapper);
+ d->m_count = qMax(count, -1);
+ d->initializeXYFromModel();
+ Q_EMIT countChanged();
+}
+
+Qt::Orientation QXYModelMapper::orientation() const
+{
+ Q_D(const QXYModelMapper);
+ return d->m_orientation;
+}
+
+void QXYModelMapper::setOrientation(Qt::Orientation orientation)
+{
+ Q_D(QXYModelMapper);
+ d->m_orientation = orientation;
+ d->initializeXYFromModel();
+ Q_EMIT orientationChanged();
+}
+
+int QXYModelMapper::xSection() const
+{
+ Q_D(const QXYModelMapper);
+ return d->m_xSection;
+}
+
+void QXYModelMapper::setXSection(int xSection)
+{
+ Q_D(QXYModelMapper);
+ d->m_xSection = qMax(-1, xSection);
+ d->initializeXYFromModel();
+ Q_EMIT xSectionChanged();
+}
+
+int QXYModelMapper::ySection() const
+{
+ Q_D(const QXYModelMapper);
+ return d->m_ySection;
+}
+
+void QXYModelMapper::setYSection(int ySection)
+{
+ Q_D(QXYModelMapper);
+ d->m_ySection = qMax(-1, ySection);
+ d->initializeXYFromModel();
+ Q_EMIT ySectionChanged();
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+QXYModelMapperPrivate::QXYModelMapperPrivate() {}
+
+QXYModelMapperPrivate::~QXYModelMapperPrivate() {}
+
+void QXYModelMapperPrivate::blockModelSignals(bool block)
+{
+ m_modelSignalsBlock = block;
+}
+
+void QXYModelMapperPrivate::blockSeriesSignals(bool block)
+{
+ m_seriesSignalsBlock = block;
+}
+
+QModelIndex QXYModelMapperPrivate::xModelIndex(int xPos)
+{
+ if (m_count != -1 && xPos >= m_count)
+ return QModelIndex(); // invalid
+
+ if (m_orientation == Qt::Vertical)
+ return m_model->index(xPos + m_first, m_xSection);
+ else
+ return m_model->index(m_xSection, xPos + m_first);
+}
+
+QModelIndex QXYModelMapperPrivate::yModelIndex(int yPos)
+{
+ if (m_count != -1 && yPos >= m_count)
+ return QModelIndex(); // invalid
+
+ if (m_orientation == Qt::Vertical)
+ return m_model->index(yPos + m_first, m_ySection);
+ else
+ return m_model->index(m_ySection, yPos + m_first);
+}
+
+qreal QXYModelMapperPrivate::valueFromModel(QModelIndex index)
+{
+ QVariant value = m_model->data(index, Qt::DisplayRole);
+ switch (value.metaType().id()) {
+ case QMetaType::QDateTime:
+ return value.toDateTime().toMSecsSinceEpoch();
+ case QMetaType::QDate:
+ return value.toDate().startOfDay().toMSecsSinceEpoch();
+ default:
+ return value.toReal();
+ }
+}
+
+void QXYModelMapperPrivate::setValueToModel(QModelIndex index, qreal value)
+{
+ QVariant oldValue = m_model->data(index, Qt::DisplayRole);
+ switch (oldValue.metaType().id()) {
+ case QMetaType::QDateTime:
+ m_model->setData(index, QDateTime::fromMSecsSinceEpoch(value));
+ break;
+ case QMetaType::QDate:
+ m_model->setData(index, QDateTime::fromMSecsSinceEpoch(value).date());
+ break;
+ default:
+ m_model->setData(index, value);
+ }
+}
+
+void QXYModelMapperPrivate::onPointAdded(int pointPos)
+{
+ if (m_seriesSignalsBlock)
+ return;
+
+ if (m_count != -1)
+ m_count += 1;
+
+ blockModelSignals();
+ if (m_orientation == Qt::Vertical)
+ m_model->insertRows(pointPos + m_first, 1);
+ else
+ m_model->insertColumns(pointPos + m_first, 1);
+
+ setValueToModel(xModelIndex(pointPos), m_series->points().at(pointPos).x());
+ setValueToModel(yModelIndex(pointPos), m_series->points().at(pointPos).y());
+ blockModelSignals(false);
+}
+
+void QXYModelMapperPrivate::onPointRemoved(int pointPos)
+{
+ if (m_seriesSignalsBlock)
+ return;
+
+ if (m_count != -1)
+ m_count -= 1;
+
+ blockModelSignals();
+ if (m_orientation == Qt::Vertical)
+ m_model->removeRow(pointPos + m_first);
+ else
+ m_model->removeColumn(pointPos + m_first);
+ blockModelSignals(false);
+}
+
+void QXYModelMapperPrivate::onPointsRemoved(int pointPos, int count)
+{
+ if (m_seriesSignalsBlock)
+ return;
+
+ m_count -= count;
+
+ if (m_count < -1)
+ m_count = -1;
+
+ blockModelSignals();
+ if (m_orientation == Qt::Vertical)
+ m_model->removeRows(pointPos + m_first, count);
+ else
+ m_model->removeColumns(pointPos + m_first, count);
+ blockModelSignals(false);
+}
+
+void QXYModelMapperPrivate::onPointReplaced(int pointPos)
+{
+ if (m_seriesSignalsBlock)
+ return;
+
+ blockModelSignals();
+ setValueToModel(xModelIndex(pointPos), m_series->points().at(pointPos).x());
+ setValueToModel(yModelIndex(pointPos), m_series->points().at(pointPos).y());
+ blockModelSignals(false);
+}
+
+void QXYModelMapperPrivate::handleSeriesDestroyed()
+{
+ m_series = 0;
+}
+
+void QXYModelMapperPrivate::onModelUpdated(QModelIndex topLeft, QModelIndex bottomRight)
+{
+ if (m_model == 0 || m_series == 0)
+ return;
+
+ if (m_modelSignalsBlock)
+ return;
+
+ blockSeriesSignals();
+ QModelIndex index;
+ QPointF newPoint;
+ int indexColumn = 0;
+ int indexRow = 0;
+ for (int row = topLeft.row(); row <= bottomRight.row(); row++) {
+ for (int column = topLeft.column(); column <= bottomRight.column(); column++) {
+ index = topLeft.sibling(row, column);
+ indexColumn = index.column();
+ indexRow = index.row();
+ if (m_orientation == Qt::Vertical
+ && (indexColumn == m_xSection || indexColumn == m_ySection)) {
+ if (indexRow >= m_first && (m_count == -1 || indexRow < m_first + m_count)) {
+ QModelIndex xIndex = xModelIndex(indexRow - m_first);
+ QModelIndex yIndex = yModelIndex(indexRow - m_first);
+ if (xIndex.isValid() && yIndex.isValid()) {
+ newPoint.setX(valueFromModel(xIndex));
+ newPoint.setY(valueFromModel(yIndex));
+ m_series->replace(indexRow - m_first, newPoint);
+ }
+ }
+ } else if (m_orientation == Qt::Horizontal
+ && (indexRow == m_xSection || indexRow == m_ySection)) {
+ if (indexColumn >= m_first && (m_count == -1 || indexColumn < m_first + m_count)) {
+ QModelIndex xIndex = xModelIndex(indexColumn - m_first);
+ QModelIndex yIndex = yModelIndex(indexColumn - m_first);
+ if (xIndex.isValid() && yIndex.isValid()) {
+ newPoint.setX(valueFromModel(xIndex));
+ newPoint.setY(valueFromModel(yIndex));
+ m_series->replace(indexColumn - m_first, newPoint);
+ }
+ }
+ }
+ }
+ }
+ blockSeriesSignals(false);
+}
+
+void QXYModelMapperPrivate::onModelRowsAdded(QModelIndex parent, int start, int end)
+{
+ Q_UNUSED(parent);
+ if (m_modelSignalsBlock)
+ return;
+
+ blockSeriesSignals();
+ if (m_orientation == Qt::Vertical) {
+ insertData(start, end);
+ } else if (start <= m_xSection || start <= m_ySection) {
+ // if the changes affect the map - reinitialize the xy
+ initializeXYFromModel();
+ }
+ blockSeriesSignals(false);
+}
+
+void QXYModelMapperPrivate::onModelRowsRemoved(QModelIndex parent, int start, int end)
+{
+ Q_UNUSED(parent);
+ if (m_modelSignalsBlock)
+ return;
+
+ blockSeriesSignals();
+ if (m_orientation == Qt::Vertical) {
+ removeData(start, end);
+ } else if (start <= m_xSection || start <= m_ySection) {
+ // if the changes affect the map - reinitialize the xy
+ initializeXYFromModel();
+ }
+ blockSeriesSignals(false);
+}
+
+void QXYModelMapperPrivate::onModelColumnsAdded(QModelIndex parent, int start, int end)
+{
+ Q_UNUSED(parent);
+ if (m_modelSignalsBlock)
+ return;
+
+ blockSeriesSignals();
+ if (m_orientation == Qt::Horizontal) {
+ insertData(start, end);
+ } else if (start <= m_xSection || start <= m_ySection) {
+ // if the changes affect the map - reinitialize the xy
+ initializeXYFromModel();
+ }
+ blockSeriesSignals(false);
+}
+
+void QXYModelMapperPrivate::onModelColumnsRemoved(QModelIndex parent, int start, int end)
+{
+ Q_UNUSED(parent);
+ if (m_modelSignalsBlock)
+ return;
+
+ blockSeriesSignals();
+ if (m_orientation == Qt::Horizontal) {
+ removeData(start, end);
+ } else if (start <= m_xSection || start <= m_ySection) {
+ // if the changes affect the map - reinitialize the xy
+ initializeXYFromModel();
+ }
+ blockSeriesSignals(false);
+}
+
+void QXYModelMapperPrivate::handleModelDestroyed()
+{
+ m_model = 0;
+}
+
+void QXYModelMapperPrivate::insertData(int start, int end)
+{
+ if (m_model == 0 || m_series == 0)
+ return;
+
+ if (m_count != -1 && start >= m_first + m_count) {
+ return;
+ } else {
+ int addedCount = end - start + 1;
+ if (m_count != -1 && addedCount > m_count)
+ addedCount = m_count;
+ int first = qMax(start, m_first);
+ int last = qMin(first + addedCount - 1,
+ m_orientation == Qt::Vertical ? m_model->rowCount() - 1
+ : m_model->columnCount() - 1);
+ for (int i = first; i <= last; i++) {
+ QPointF point;
+ QModelIndex xIndex = xModelIndex(i - m_first);
+ QModelIndex yIndex = yModelIndex(i - m_first);
+ if (xIndex.isValid() && yIndex.isValid()) {
+ point.setX(valueFromModel(xIndex));
+ point.setY(valueFromModel(yIndex));
+ m_series->insert(i - m_first, point);
+ }
+ }
+
+ // remove excess of points (above m_count)
+ if (m_count != -1 && m_series->points().size() > m_count) {
+ for (int i = m_series->points().size() - 1; i >= m_count; i--)
+ m_series->remove(m_series->points().at(i));
+ }
+ }
+}
+
+void QXYModelMapperPrivate::removeData(int start, int end)
+{
+ if (m_model == 0 || m_series == 0)
+ return;
+
+ int removedCount = end - start + 1;
+ if (m_count != -1 && start >= m_first + m_count) {
+ return;
+ } else {
+ int toRemove = qMin(m_series->count(),
+ removedCount); // first find how many items can actually be removed
+ int first = qMax(start, m_first); // get the index of the first item that will be removed.
+ int last = qMin(first + toRemove - 1,
+ m_series->count() + m_first
+ - 1); // get the index of the last item that will be removed.
+ for (int i = last; i >= first; i--)
+ m_series->remove(m_series->points().at(i - m_first));
+
+ if (m_count != -1) {
+ int itemsAvailable; // check how many are available to be added
+ if (m_orientation == Qt::Vertical)
+ itemsAvailable = m_model->rowCount() - m_first - m_series->count();
+ else
+ itemsAvailable = m_model->columnCount() - m_first - m_series->count();
+ int toBeAdded = qMin(
+ itemsAvailable,
+ m_count
+ - m_series->count()); // add not more items than there is space left to be filled.
+ int currentSize = m_series->count();
+ if (toBeAdded > 0) {
+ for (int i = m_series->count(); i < currentSize + toBeAdded; i++) {
+ QPointF point;
+ QModelIndex xIndex = xModelIndex(i);
+ QModelIndex yIndex = yModelIndex(i);
+ if (xIndex.isValid() && yIndex.isValid()) {
+ point.setX(valueFromModel(xIndex));
+ point.setY(valueFromModel(yIndex));
+ m_series->insert(i, point);
+ }
+ }
+ }
+ }
+ }
+}
+
+void QXYModelMapperPrivate::initializeXYFromModel()
+{
+ if (m_model == 0 || m_series == 0)
+ return;
+
+ blockSeriesSignals();
+ // clear current content
+ m_series->clear();
+
+ // create the initial points set
+ int pointPos = 0;
+ QModelIndex xIndex = xModelIndex(pointPos);
+ QModelIndex yIndex = yModelIndex(pointPos);
+
+ if (xIndex.isValid() && yIndex.isValid()) {
+ while (xIndex.isValid() && yIndex.isValid()) {
+ QPointF point;
+ point.setX(valueFromModel(xIndex));
+ point.setY(valueFromModel(yIndex));
+ m_series->append(point);
+ pointPos++;
+ xIndex = xModelIndex(pointPos);
+ yIndex = yModelIndex(pointPos);
+ // Don't warn about invalid index after the first, those are valid and used to
+ // determine when we should end looping.
+ }
+ } else {
+ // Invalid index right off the bat means series will be left empty, so output a warning,
+ // unless model is also empty
+ int count = m_orientation == Qt::Vertical ? m_model->rowCount() : m_model->columnCount();
+ if (count > 0) {
+ if (!xIndex.isValid()) {
+ qWarning() << __FUNCTION__
+ << QStringLiteral("Invalid X coordinate index in model mapper.");
+ } else if (!yIndex.isValid()) {
+ qWarning() << __FUNCTION__
+ << QStringLiteral("Invalid Y coordinate index in model mapper.");
+ }
+ }
+ }
+
+ blockSeriesSignals(false);
+}
+QT_END_NAMESPACE
diff --git a/src/graphs2d/xychart/qxymodelmapper.h b/src/graphs2d/xychart/qxymodelmapper.h
new file mode 100644
index 0000000..1aaeba4
--- /dev/null
+++ b/src/graphs2d/xychart/qxymodelmapper.h
@@ -0,0 +1,67 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+#ifndef QXYMODELMAPPER_H
+#define QXYMODELMAPPER_H
+#include <QtCore/qobject.h>
+#include <QtGraphs/qgraphsglobal.h>
+#include <QtQmlIntegration/qqmlintegration.h>
+
+Q_MOC_INCLUDE(<QtGraphs / qxyseries.h>)
+Q_MOC_INCLUDE(<QtCore / qabstractitemmodel.h>)
+QT_BEGIN_NAMESPACE
+
+class QXYModelMapperPrivate;
+class QXYSeries;
+class QAbstractItemModel;
+class Q_GRAPHS_EXPORT QXYModelMapper : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QXYSeries *series READ series WRITE setSeries NOTIFY seriesChanged)
+ Q_PROPERTY(QAbstractItemModel *model READ model WRITE setModel NOTIFY modelChanged)
+ Q_PROPERTY(int xSection READ xSection WRITE setXSection NOTIFY xSectionChanged)
+ Q_PROPERTY(int ySection READ ySection WRITE setYSection NOTIFY ySectionChanged)
+ Q_PROPERTY(int first READ first WRITE setFirst NOTIFY firstChanged)
+ Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged)
+ Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation NOTIFY
+ orientationChanged FINAL)
+ QML_NAMED_ELEMENT(XYModelMapper)
+ Q_DECLARE_PRIVATE(QXYModelMapper)
+public:
+ explicit QXYModelMapper(QObject *parent = nullptr);
+ ~QXYModelMapper() override;
+
+ QAbstractItemModel *model() const;
+ void setModel(QAbstractItemModel *model);
+
+ QXYSeries *series() const;
+ void setSeries(QXYSeries *series);
+
+ int first() const;
+ void setFirst(int first);
+
+ int count() const;
+ void setCount(int count);
+
+ Qt::Orientation orientation() const;
+ void setOrientation(Qt::Orientation orientation);
+
+ int xSection() const;
+ void setXSection(int xSection);
+
+ int ySection() const;
+ void setYSection(int ySection);
+Q_SIGNALS:
+ void seriesChanged();
+ void modelChanged();
+ void xSectionChanged();
+ void ySectionChanged();
+ void firstChanged();
+ void countChanged();
+ void orientationChanged();
+
+protected:
+ QXYModelMapper(QXYModelMapperPrivate &dd, QObject *parent = nullptr);
+};
+QT_END_NAMESPACE
+#endif // QXYMODELMAPPER_H
diff --git a/src/graphs2d/xychart/qxymodelmapper_p.h b/src/graphs2d/xychart/qxymodelmapper_p.h
new file mode 100644
index 0000000..3b035b7
--- /dev/null
+++ b/src/graphs2d/xychart/qxymodelmapper_p.h
@@ -0,0 +1,68 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+#ifndef QXYMODELMAPPER_P_H
+#define QXYMODELMAPPER_P_H
+
+#include <QtGraphs/QXYModelMapper>
+#include <private/qobject_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QXYModelMapperPrivate : public QObjectPrivate
+{
+ Q_DECLARE_PUBLIC(QXYModelMapper)
+public:
+ QXYModelMapperPrivate();
+ ~QXYModelMapperPrivate() override;
+
+public Q_SLOTS:
+ // for the model
+ void onModelUpdated(QModelIndex topLeft, QModelIndex bottomRight);
+ void onModelRowsAdded(QModelIndex parent, int start, int end);
+ void onModelRowsRemoved(QModelIndex parent, int start, int end);
+ void onModelColumnsAdded(QModelIndex parent, int start, int end);
+ void onModelColumnsRemoved(QModelIndex parent, int start, int end);
+ void handleModelDestroyed();
+
+ // for the series
+ void onPointAdded(int pointPos);
+ void onPointRemoved(int pointPos);
+ void onPointsRemoved(int pointPos, int count);
+ void onPointReplaced(int pointPos);
+ void handleSeriesDestroyed();
+
+ void initializeXYFromModel();
+
+private:
+ QModelIndex xModelIndex(int xPos);
+ QModelIndex yModelIndex(int yPos);
+ void insertData(int start, int end);
+ void removeData(int start, int end);
+ void blockModelSignals(bool block = true);
+ void blockSeriesSignals(bool block = true);
+ qreal valueFromModel(QModelIndex index);
+ void setValueToModel(QModelIndex index, qreal value);
+
+private:
+ QXYSeries *m_series = nullptr;
+ QAbstractItemModel *m_model = nullptr;
+ int m_first = 0;
+ int m_count = -1;
+ Qt::Orientation m_orientation = Qt::Vertical;
+ int m_xSection = -1;
+ int m_ySection = -1;
+ bool m_seriesSignalsBlock = false;
+ bool m_modelSignalsBlock = false;
+};
+
+QT_END_NAMESPACE
+#endif // QXYMODELMAPPER_P_H
diff --git a/tests/auto/cpp2dtest/CMakeLists.txt b/tests/auto/cpp2dtest/CMakeLists.txt
index 7b74dd1..bbee3a8 100644
--- a/tests/auto/cpp2dtest/CMakeLists.txt
+++ b/tests/auto/cpp2dtest/CMakeLists.txt
@@ -16,3 +16,4 @@ add_subdirectory(qgsplines)
add_subdirectory(qgarea)
add_subdirectory(qgbarmodelmapper)
add_subdirectory(qgpiemodelmapper)
+add_subdirectory(qgxymodelmapper)
diff --git a/tests/auto/cpp2dtest/qgxymodelmapper/CMakeLists.txt b/tests/auto/cpp2dtest/qgxymodelmapper/CMakeLists.txt
new file mode 100644
index 0000000..43e2970
--- /dev/null
+++ b/tests/auto/cpp2dtest/qgxymodelmapper/CMakeLists.txt
@@ -0,0 +1,10 @@
+qt_internal_add_test(tst_qgxymodelmapper
+ SOURCES
+ tst_xymodelmapper.cpp
+ INCLUDE_DIRECTORIES
+ ../common
+ LIBRARIES
+ Qt::Gui
+ Qt::GuiPrivate
+ Qt::Graphs
+)
diff --git a/tests/auto/cpp2dtest/qgxymodelmapper/tst_xymodelmapper.cpp b/tests/auto/cpp2dtest/qgxymodelmapper/tst_xymodelmapper.cpp
new file mode 100644
index 0000000..bc2830c
--- /dev/null
+++ b/tests/auto/cpp2dtest/qgxymodelmapper/tst_xymodelmapper.cpp
@@ -0,0 +1,584 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtCore/QString>
+#include <QtTest/QtTest>
+
+#include <QtGraphs/QLineSeries>
+#include <QtGraphs/QXYModelMapper>
+#include <QtGraphs/QXYSeries>
+#include <QtGui/QStandardItemModel>
+
+QT_USE_NAMESPACE
+
+class tst_qgxymodelmapper : public QObject
+{
+ Q_OBJECT
+
+public:
+ tst_qgxymodelmapper();
+ void createVerticalMapper();
+ void createHorizontalMapper();
+
+private Q_SLOTS:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+ void verticalMapper_data();
+ void verticalMapper();
+ void verticalMapperCustomMapping_data();
+ void verticalMapperCustomMapping();
+ void horizontalMapper_data();
+ void horizontalMapper();
+ void horizontalMapperCustomMapping_data();
+ void horizontalMapperCustomMapping();
+ void seriesUpdated();
+ void verticalModelInsertRows();
+ void verticalModelRemoveRows();
+ void verticalModelInsertColumns();
+ void verticalModelRemoveColumns();
+ void horizontalModelInsertRows();
+ void horizontalModelRemoveRows();
+ void horizontalModelInsertColumns();
+ void horizontalModelRemoveColumns();
+ void modelUpdateCell();
+ void verticalMapperSignals();
+ void horizontalMapperSignals();
+
+private:
+ QStandardItemModel *m_model;
+ int m_modelRowCount;
+ int m_modelColumnCount;
+
+ QXYModelMapper *m_hMapper;
+ QXYModelMapper *m_vMapper;
+
+ QXYSeries *m_series;
+};
+
+tst_qgxymodelmapper::tst_qgxymodelmapper()
+ : m_model(0)
+ , m_modelRowCount(10)
+ , m_modelColumnCount(8)
+ , m_hMapper(0)
+ , m_vMapper(0)
+ , m_series(0)
+{}
+
+void tst_qgxymodelmapper::createVerticalMapper()
+{
+ m_vMapper = new QXYModelMapper;
+ QVERIFY(m_vMapper->model() == 0);
+ m_vMapper->setXSection(0);
+ m_vMapper->setYSection(1);
+ m_vMapper->setModel(m_model);
+ m_vMapper->setSeries(m_series);
+}
+
+void tst_qgxymodelmapper::createHorizontalMapper()
+{
+ m_hMapper = new QXYModelMapper;
+ QVERIFY(m_hMapper->model() == 0);
+ m_hMapper->setOrientation(Qt::Horizontal);
+ m_hMapper->setXSection(0);
+ m_hMapper->setYSection(1);
+ m_hMapper->setModel(m_model);
+ m_hMapper->setSeries(m_series);
+}
+
+void tst_qgxymodelmapper::init()
+{
+ m_series = new QLineSeries;
+
+ m_model = new QStandardItemModel(m_modelRowCount, m_modelColumnCount, this);
+ for (int row = 0; row < m_modelRowCount; ++row) {
+ for (int column = 0; column < m_modelColumnCount; column++) {
+ m_model->setData(m_model->index(row, column), row * column);
+ }
+ }
+}
+
+void tst_qgxymodelmapper::cleanup()
+{
+ m_series->deleteLater();
+ m_series = 0;
+
+ m_model->clear();
+ m_model->deleteLater();
+ m_model = 0;
+
+ if (m_vMapper) {
+ m_vMapper->deleteLater();
+ m_vMapper = 0;
+ }
+
+ if (m_hMapper) {
+ m_hMapper->deleteLater();
+ m_hMapper = 0;
+ }
+}
+
+void tst_qgxymodelmapper::initTestCase() {}
+
+void tst_qgxymodelmapper::cleanupTestCase()
+{
+ QTest::qWait(1); // Allow final deleteLaters to run
+}
+
+void tst_qgxymodelmapper::verticalMapper_data()
+{
+ QTest::addColumn<int>("xColumn");
+ QTest::addColumn<int>("yColumn");
+ QTest::addColumn<int>("expectedCount");
+ QTest::newRow("different x and y columns") << 0 << 1 << m_modelRowCount;
+ QTest::newRow("same x and y columns") << 1 << 1 << m_modelRowCount;
+ QTest::newRow("invalid x column and correct y column") << -3 << 1 << 0;
+ QTest::newRow("x column beyond the size of model and correct y column")
+ << m_modelColumnCount << 1 << 0;
+ QTest::newRow("x column beyond the size of model and invalid y column")
+ << m_modelColumnCount << -1 << 0;
+}
+
+void tst_qgxymodelmapper::verticalMapper()
+{
+ QFETCH(int, xColumn);
+ QFETCH(int, yColumn);
+ QFETCH(int, expectedCount);
+
+ QXYModelMapper *mapper = new QXYModelMapper;
+ QVERIFY(mapper->model() == 0);
+
+ mapper->setXSection(xColumn);
+ mapper->setYSection(yColumn);
+ mapper->setModel(m_model);
+ mapper->setSeries(m_series);
+
+ QCOMPARE(m_series->count(), expectedCount);
+ QCOMPARE(mapper->xSection(), qMax(-1, xColumn));
+ QCOMPARE(mapper->ySection(), qMax(-1, yColumn));
+
+ delete mapper;
+ mapper = 0;
+}
+
+void tst_qgxymodelmapper::verticalMapperCustomMapping_data()
+{
+ QTest::addColumn<int>("first");
+ QTest::addColumn<int>("countLimit");
+ QTest::addColumn<int>("expectedCount");
+ QTest::newRow("first: 0, unlimited count") << 0 << -1 << m_modelRowCount;
+ QTest::newRow("first: 3, unlimited count") << 3 << -1 << m_modelRowCount - 3;
+ QTest::newRow("first: 0, count: 5") << 0 << 5 << qMin(5, m_modelRowCount);
+ QTest::newRow("first: 3, count: 5") << 3 << 5 << qMin(5, m_modelRowCount - 3);
+ QTest::newRow("first: +1 greater then the number of rows in the model, unlimited count")
+ << m_modelRowCount + 1 << -1 << 0;
+ QTest::newRow("first: +1 greater then the number of rows in the model, count: 5")
+ << m_modelRowCount + 1 << 5 << 0;
+ QTest::newRow("first: 0, count: +3 greater than the number of rows in the model (should limit "
+ "to the size of model)")
+ << 0 << m_modelRowCount + 3 << m_modelRowCount;
+ QTest::newRow("first: -3(invalid - should default to 0), unlimited count")
+ << -3 << -1 << m_modelRowCount;
+ QTest::newRow("first: 0, count: -3 (invalid - shlould default to -1)")
+ << 0 << -3 << m_modelRowCount;
+ QTest::newRow(
+ "first: -3(invalid - should default to 0), count: -3 (invalid - shlould default to -1)")
+ << -3 << -3 << m_modelRowCount;
+}
+
+void tst_qgxymodelmapper::verticalMapperCustomMapping()
+{
+ QFETCH(int, first);
+ QFETCH(int, countLimit);
+ QFETCH(int, expectedCount);
+
+ QCOMPARE(m_series->count(), 0);
+
+ QXYModelMapper *mapper = new QXYModelMapper;
+ mapper->setXSection(0);
+ mapper->setYSection(1);
+ mapper->setModel(m_model);
+ mapper->setSeries(m_series);
+ mapper->setFirst(first);
+ mapper->setCount(countLimit);
+
+ QCOMPARE(m_series->count(), expectedCount);
+
+ // change values column mapping to invalid
+ mapper->setXSection(-1);
+ mapper->setYSection(1);
+
+ QCOMPARE(m_series->count(), 0);
+
+ delete mapper;
+ mapper = 0;
+}
+
+void tst_qgxymodelmapper::horizontalMapper_data()
+{
+ QTest::addColumn<int>("xRow");
+ QTest::addColumn<int>("yRow");
+ QTest::addColumn<int>("expectedCount");
+ QTest::newRow("different x and y rows") << 0 << 1 << m_modelColumnCount;
+ QTest::newRow("same x and y rows") << 1 << 1 << m_modelColumnCount;
+ QTest::newRow("invalid x row and correct y row") << -3 << 1 << 0;
+ QTest::newRow("x row beyond the size of model and correct y row") << m_modelRowCount << 1 << 0;
+ QTest::newRow("x row beyond the size of model and invalid y row") << m_modelRowCount << -1 << 0;
+}
+
+void tst_qgxymodelmapper::horizontalMapper()
+{
+ QFETCH(int, xRow);
+ QFETCH(int, yRow);
+ QFETCH(int, expectedCount);
+
+ QXYModelMapper *mapper = new QXYModelMapper;
+ mapper->setOrientation(Qt::Horizontal);
+ mapper->setXSection(xRow);
+ mapper->setYSection(yRow);
+ mapper->setModel(m_model);
+ mapper->setSeries(m_series);
+
+ QCOMPARE(m_series->count(), expectedCount);
+ QCOMPARE(mapper->xSection(), qMax(-1, xRow));
+ QCOMPARE(mapper->ySection(), qMax(-1, yRow));
+
+ delete mapper;
+ mapper = 0;
+}
+
+void tst_qgxymodelmapper::horizontalMapperCustomMapping_data()
+{
+ QTest::addColumn<int>("first");
+ QTest::addColumn<int>("countLimit");
+ QTest::addColumn<int>("expectedCount");
+ QTest::newRow("first: 0, unlimited count") << 0 << -1 << m_modelColumnCount;
+ QTest::newRow("first: 3, unlimited count") << 3 << -1 << m_modelColumnCount - 3;
+ QTest::newRow("first: 0, count: 5") << 0 << 5 << qMin(5, m_modelColumnCount);
+ QTest::newRow("first: 3, count: 5") << 3 << 5 << qMin(5, m_modelColumnCount - 3);
+ QTest::newRow("first: +1 greater then the number of columns in the model, unlimited count")
+ << m_modelColumnCount + 1 << -1 << 0;
+ QTest::newRow("first: +1 greater then the number of columns in the model, count: 5")
+ << m_modelColumnCount + 1 << 5 << 0;
+ QTest::newRow("first: 0, count: +3 greater than the number of columns in the model (should "
+ "limit to the size of model)")
+ << 0 << m_modelColumnCount + 3 << m_modelColumnCount;
+ QTest::newRow("first: -3(invalid - should default to 0), unlimited count")
+ << -3 << -1 << m_modelColumnCount;
+ QTest::newRow("first: 0, count: -3 (invalid - shlould default to -1)")
+ << 0 << -3 << m_modelColumnCount;
+ QTest::newRow(
+ "first: -3(invalid - should default to 0), count: -3 (invalid - shlould default to -1)")
+ << -3 << -3 << m_modelColumnCount;
+}
+
+void tst_qgxymodelmapper::horizontalMapperCustomMapping()
+{
+ QFETCH(int, first);
+ QFETCH(int, countLimit);
+ QFETCH(int, expectedCount);
+
+ QCOMPARE(m_series->count(), 0);
+
+ QXYModelMapper *mapper = new QXYModelMapper;
+ mapper->setOrientation(Qt::Horizontal);
+ mapper->setXSection(0);
+ mapper->setYSection(1);
+ mapper->setModel(m_model);
+ mapper->setSeries(m_series);
+ mapper->setFirst(first);
+ mapper->setCount(countLimit);
+
+ QCOMPARE(m_series->count(), expectedCount);
+
+ // change values row mapping to invalid
+ mapper->setXSection(-1);
+ mapper->setYSection(1);
+
+ QCOMPARE(m_series->count(), 0);
+
+ delete mapper;
+ mapper = 0;
+}
+
+void tst_qgxymodelmapper::seriesUpdated()
+{
+ // setup the mapper
+ createVerticalMapper();
+ QCOMPARE(m_series->count(), m_modelRowCount);
+ QCOMPARE(m_vMapper->count(), -1);
+
+ m_series->append(QPointF(100, 100));
+ QCOMPARE(m_series->count(), m_modelRowCount + 1);
+ QCOMPARE(m_vMapper->count(),
+ -1); // the value should not change as it indicates 'all' items there are in the model
+
+ m_series->remove(m_series->points().last());
+ QCOMPARE(m_series->count(), m_modelRowCount);
+ QCOMPARE(m_vMapper->count(),
+ -1); // the value should not change as it indicates 'all' items there are in the model
+
+ m_series->removePoints(1, m_modelRowCount - 4);
+ QCOMPARE(m_series->count(), 4);
+ QCOMPARE(m_vMapper->count(),
+ -1); // the value should not change as it indicates 'all' items there are in the model
+
+ m_series->replace(m_series->points().first(), QPointF(25.0, 75.0));
+ QCOMPARE(m_model->data(m_model->index(0, 0)).toReal(), 25.0);
+ QCOMPARE(m_model->data(m_model->index(0, 1)).toReal(), 75.0);
+}
+
+void tst_qgxymodelmapper::verticalModelInsertRows()
+{
+ // setup the mapper
+ createVerticalMapper();
+ QCOMPARE(m_series->count(), m_modelRowCount);
+ QVERIFY(m_vMapper->model() != 0);
+
+ int insertCount = 4;
+ m_model->insertRows(3, insertCount);
+ QCOMPARE(m_series->count(), m_modelRowCount + insertCount);
+
+ int first = 3;
+ m_vMapper->setFirst(3);
+ QCOMPARE(m_series->count(), m_modelRowCount + insertCount - first);
+
+ m_model->insertRows(3, insertCount);
+ QCOMPARE(m_series->count(), m_modelRowCount + 2 * insertCount - first);
+
+ int countLimit = 6;
+ m_vMapper->setCount(countLimit);
+ QCOMPARE(m_series->count(), qMin(countLimit, m_modelRowCount + 2 * insertCount - first));
+
+ m_model->insertRows(3, insertCount);
+ QCOMPARE(m_series->count(), qMin(countLimit, m_modelRowCount + 3 * insertCount - first));
+
+ m_vMapper->setFirst(0);
+ QCOMPARE(m_series->count(), qMin(countLimit, m_modelRowCount + 3 * insertCount));
+
+ m_vMapper->setCount(-1);
+ QCOMPARE(m_series->count(), m_modelRowCount + 3 * insertCount);
+}
+
+void tst_qgxymodelmapper::verticalModelRemoveRows()
+{
+ // setup the mapper
+ createVerticalMapper();
+ QCOMPARE(m_series->count(), m_modelRowCount);
+ QVERIFY(m_vMapper->model() != 0);
+
+ int removeCount = 2;
+ m_model->removeRows(1, removeCount);
+ QCOMPARE(m_series->count(), m_modelRowCount - removeCount);
+
+ int first = 1;
+ m_vMapper->setFirst(first);
+ QCOMPARE(m_series->count(), m_modelRowCount - removeCount - first);
+
+ m_model->removeRows(1, removeCount);
+ QCOMPARE(m_series->count(), m_modelRowCount - 2 * removeCount - first);
+
+ int countLimit = 3;
+ m_vMapper->setCount(countLimit);
+ QCOMPARE(m_series->count(), qMin(countLimit, m_modelRowCount - 2 * removeCount - first));
+
+ m_model->removeRows(1, removeCount);
+ QCOMPARE(m_series->count(), qMin(countLimit, m_modelRowCount - 3 * removeCount - first));
+
+ m_vMapper->setFirst(0);
+ QCOMPARE(m_series->count(), qMin(countLimit, m_modelRowCount - 3 * removeCount));
+
+ m_vMapper->setCount(-1);
+ QCOMPARE(m_series->count(), m_modelRowCount - 3 * removeCount);
+}
+
+void tst_qgxymodelmapper::verticalModelInsertColumns()
+{
+ // setup the mapper
+ createVerticalMapper();
+ QCOMPARE(m_series->count(), m_modelRowCount);
+ QVERIFY(m_vMapper->model() != 0);
+
+ int insertCount = 4;
+ m_model->insertColumns(3, insertCount);
+ QCOMPARE(m_series->count(), m_modelRowCount);
+}
+
+void tst_qgxymodelmapper::verticalModelRemoveColumns()
+{
+ // setup the mapper
+ createVerticalMapper();
+ QCOMPARE(m_series->count(), m_modelRowCount);
+ QVERIFY(m_vMapper->model() != 0);
+
+ int removeCount = m_modelColumnCount - 2;
+ m_model->removeColumns(0, removeCount);
+ QCOMPARE(m_series->count(), m_modelRowCount);
+
+ // leave only one column
+ m_model->removeColumns(0, m_modelColumnCount - removeCount - 1);
+ QCOMPARE(m_series->count(), 0);
+}
+
+void tst_qgxymodelmapper::horizontalModelInsertRows()
+{
+ // setup the mapper
+ createHorizontalMapper();
+ QCOMPARE(m_series->count(), m_modelColumnCount);
+ QVERIFY(m_hMapper->model() != 0);
+
+ int insertCount = 4;
+ m_model->insertRows(3, insertCount);
+ QCOMPARE(m_series->count(), m_modelColumnCount);
+}
+
+void tst_qgxymodelmapper::horizontalModelRemoveRows()
+{
+ // setup the mapper
+ createHorizontalMapper();
+ QCOMPARE(m_series->count(), m_modelColumnCount);
+ QVERIFY(m_hMapper->model() != 0);
+
+ int removeCount = m_modelRowCount - 2;
+ m_model->removeRows(0, removeCount);
+ QCOMPARE(m_series->count(), m_modelColumnCount);
+
+ // leave only one column
+ m_model->removeRows(0, m_modelRowCount - removeCount - 1);
+ QCOMPARE(m_series->count(), 0);
+}
+
+void tst_qgxymodelmapper::horizontalModelInsertColumns()
+{
+ // setup the mapper
+ createHorizontalMapper();
+ QCOMPARE(m_series->count(), m_modelColumnCount);
+ QVERIFY(m_hMapper->model() != 0);
+
+ int insertCount = 4;
+ m_model->insertColumns(3, insertCount);
+ QCOMPARE(m_series->count(), m_modelColumnCount + insertCount);
+
+ int first = 3;
+ m_hMapper->setFirst(3);
+ QCOMPARE(m_series->count(), m_modelColumnCount + insertCount - first);
+
+ m_model->insertColumns(3, insertCount);
+ QCOMPARE(m_series->count(), m_modelColumnCount + 2 * insertCount - first);
+
+ int countLimit = 6;
+ m_hMapper->setCount(countLimit);
+ QCOMPARE(m_series->count(), qMin(countLimit, m_modelColumnCount + 2 * insertCount - first));
+
+ m_model->insertColumns(3, insertCount);
+ QCOMPARE(m_series->count(), qMin(countLimit, m_modelColumnCount + 3 * insertCount - first));
+
+ m_hMapper->setFirst(0);
+ QCOMPARE(m_series->count(), qMin(countLimit, m_modelColumnCount + 3 * insertCount));
+
+ m_hMapper->setCount(-1);
+ QCOMPARE(m_series->count(), m_modelColumnCount + 3 * insertCount);
+}
+
+void tst_qgxymodelmapper::horizontalModelRemoveColumns()
+{
+ // setup the mapper
+ createHorizontalMapper();
+ QCOMPARE(m_series->count(), m_modelColumnCount);
+ QVERIFY(m_hMapper->model() != 0);
+
+ int removeCount = 2;
+ m_model->removeColumns(1, removeCount);
+ QCOMPARE(m_series->count(), m_modelColumnCount - removeCount);
+
+ int first = 1;
+ m_hMapper->setFirst(first);
+ QCOMPARE(m_series->count(), m_modelColumnCount - removeCount - first);
+
+ m_model->removeColumns(1, removeCount);
+ QCOMPARE(m_series->count(), m_modelColumnCount - 2 * removeCount - first);
+
+ int countLimit = 3;
+ m_hMapper->setCount(countLimit);
+ QCOMPARE(m_series->count(), qMin(countLimit, m_modelColumnCount - 2 * removeCount - first));
+
+ m_model->removeColumns(1, removeCount);
+ QCOMPARE(m_series->count(), qMin(countLimit, m_modelColumnCount - 3 * removeCount - first));
+
+ m_hMapper->setFirst(0);
+ QCOMPARE(m_series->count(), qMin(countLimit, m_modelColumnCount - 3 * removeCount));
+
+ m_hMapper->setCount(-1);
+ QCOMPARE(m_series->count(), m_modelColumnCount - 3 * removeCount);
+}
+
+void tst_qgxymodelmapper::modelUpdateCell()
+{
+ // setup the mapper
+ createVerticalMapper();
+
+ QVERIFY(m_model->setData(m_model->index(1, 0), 44));
+ QCOMPARE(m_series->points().at(1).x(), 44.0);
+ QCOMPARE(m_model->data(m_model->index(1, 0)).toReal(), 44.0);
+}
+
+void tst_qgxymodelmapper::verticalMapperSignals()
+{
+ QXYModelMapper *mapper = new QXYModelMapper;
+
+ QSignalSpy spy0(mapper, SIGNAL(firstChanged()));
+ QSignalSpy spy1(mapper, SIGNAL(countChanged()));
+ QSignalSpy spy2(mapper, SIGNAL(xSectionChanged()));
+ QSignalSpy spy3(mapper, SIGNAL(ySectionChanged()));
+ QSignalSpy spy4(mapper, SIGNAL(modelChanged()));
+ QSignalSpy spy5(mapper, SIGNAL(seriesChanged()));
+
+ mapper->setXSection(0);
+ mapper->setYSection(1);
+ mapper->setModel(m_model);
+ mapper->setSeries(m_series);
+ mapper->setFirst(1);
+ mapper->setCount(5);
+
+ QCOMPARE(spy0.size(), 1);
+ QCOMPARE(spy1.size(), 1);
+ QCOMPARE(spy2.size(), 1);
+ QCOMPARE(spy3.size(), 1);
+ QCOMPARE(spy4.size(), 1);
+ QCOMPARE(spy5.size(), 1);
+
+ delete mapper;
+}
+
+void tst_qgxymodelmapper::horizontalMapperSignals()
+{
+ QXYModelMapper *mapper = new QXYModelMapper;
+
+ QSignalSpy spy0(mapper, SIGNAL(firstChanged()));
+ QSignalSpy spy1(mapper, SIGNAL(countChanged()));
+ QSignalSpy spy2(mapper, SIGNAL(xSectionChanged()));
+ QSignalSpy spy3(mapper, SIGNAL(ySectionChanged()));
+ QSignalSpy spy4(mapper, SIGNAL(modelChanged()));
+ QSignalSpy spy5(mapper, SIGNAL(seriesChanged()));
+
+ mapper->setXSection(0);
+ mapper->setYSection(1);
+ mapper->setModel(m_model);
+ mapper->setSeries(m_series);
+ mapper->setFirst(1);
+ mapper->setCount(5);
+
+ QCOMPARE(spy0.size(), 1);
+ QCOMPARE(spy1.size(), 1);
+ QCOMPARE(spy2.size(), 1);
+ QCOMPARE(spy3.size(), 1);
+ QCOMPARE(spy4.size(), 1);
+ QCOMPARE(spy5.size(), 1);
+
+ delete mapper;
+}
+
+QTEST_MAIN(tst_qgxymodelmapper)
+
+#include "tst_xymodelmapper.moc"