summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSakaria Pouke <[email protected]>2025-05-27 16:30:25 +0300
committerSami Varanka <[email protected]>2025-05-30 16:50:09 +0000
commit65f74386ef747a1ac6c0d223191cc20aa2feb718 (patch)
treead8cfcea0ba39b74aea0126d3f831ca134fe37f4
parentc1ccb5b83439203dcebe093ebb475d642d5b2973 (diff)
Add scale to scatter itemHEADdev
Also update ItemModelScatterDataProxy and ScatterItemModelHandler. Added manual test for vector fields. Fixes: QTBUG-134242 Task-number: QTBUG-134558 Change-Id: Ie4f128419ea176d1b8e0f8e874f447ce7fb35b39 Reviewed-by: Sami Varanka <[email protected]>
-rw-r--r--src/graphs3d/data/qitemmodelscatterdataproxy.cpp171
-rw-r--r--src/graphs3d/data/qitemmodelscatterdataproxy.h25
-rw-r--r--src/graphs3d/data/qitemmodelscatterdataproxy_p.h3
-rw-r--r--src/graphs3d/data/qscatterdataitem.cpp25
-rw-r--r--src/graphs3d/data/qscatterdataitem.h13
-rw-r--r--src/graphs3d/data/scatteritemmodelhandler.cpp42
-rw-r--r--src/graphs3d/data/scatteritemmodelhandler_p.h4
-rw-r--r--src/graphs3d/qml/qquickgraphsscatter.cpp4
-rw-r--r--tests/auto/cpptest/qgscatter-modelproxy/tst_proxy.cpp19
-rw-r--r--tests/auto/qmltest/scatter3d/tst_proxy.qml36
-rw-r--r--tests/manual/CMakeLists.txt1
-rw-r--r--tests/manual/qmlvectorfield/CMakeLists.txt30
-rw-r--r--tests/manual/qmlvectorfield/main.cpp34
-rw-r--r--tests/manual/qmlvectorfield/qml/qmlvectorfield/main.qml113
14 files changed, 514 insertions, 6 deletions
diff --git a/src/graphs3d/data/qitemmodelscatterdataproxy.cpp b/src/graphs3d/data/qitemmodelscatterdataproxy.cpp
index 064a6c37..1e19b692 100644
--- a/src/graphs3d/data/qitemmodelscatterdataproxy.cpp
+++ b/src/graphs3d/data/qitemmodelscatterdataproxy.cpp
@@ -106,6 +106,14 @@ QT_BEGIN_NAMESPACE
*/
/*!
+ * \qmlproperty string ItemModelScatterDataProxy::scaleRole
+ *
+ * The model may supply the value for scale as either a variant that is
+ * \c{"x,y,z"}. The first will construct the
+ * vector3d directly with the given values.
+ */
+
+/*!
* \qmlproperty regExp ItemModelScatterDataProxy::xPosRolePattern
*
* When set, a search and replace is done on the value mapped by the x-position
@@ -153,6 +161,17 @@ QT_BEGIN_NAMESPACE
*/
/*!
+ * \qmlproperty regExp ItemModelScatterDataProxy::scaleRolePattern
+ * When set, a search and replace is done on the value mapped by the scale
+ * role before it is used
+ * as item scale. This property specifies the regular expression to find the
+ * portion of the mapped value to replace, and scaleRoleReplace property
+ * contains the replacement string.
+ *
+ * \sa scaleRole, scaleRoleReplace
+ */
+
+/*!
* \qmlproperty string ItemModelScatterDataProxy::xPosRoleReplace
*
* This property defines the replacement content to be used in conjunction with
@@ -200,6 +219,17 @@ QT_BEGIN_NAMESPACE
*/
/*!
+ * \qmlproperty string ItemModelScatterDataProxy::scaleRoleReplace
+ * This property defines the replacement content to be used in conjunction with
+ * scaleRolePattern. Defaults to an empty string. For more information on how
+ * the search and replace using regular expressions works, see
+ * the QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa scaleRole, scaleRolePattern
+ */
+
+/*!
\qmlsignal ItemModelScatterDataProxy::itemModelChanged(model itemModel)
This signal is emitted when itemModel changes to \a itemModel.
@@ -230,6 +260,12 @@ QT_BEGIN_NAMESPACE
*/
/*!
+ \qmlsignal ItemModelScatterDataProxy::scaleRoleChanged(string role)
+
+ This signal is emitted when scaleRole changes to \a role.
+*/
+
+/*!
\qmlsignal ItemModelScatterDataProxy::xPosRolePatternChanged(regExp pattern)
This signal is emitted when xPosRolePattern changes to \a pattern.
@@ -254,12 +290,24 @@ QT_BEGIN_NAMESPACE
*/
/*!
+ \qmlsignal ItemModelScatterDataProxy::scaleRolePatternChanged(regExp pattern)
+
+ This signal is emitted when scaleRolePattern changes to \a pattern.
+*/
+
+/*!
\qmlsignal ItemModelScatterDataProxy::rotationRoleReplaceChanged(string replace)
This signal is emitted when rotationRoleReplace changes to \a replace.
*/
/*!
+ \qmlsignal ItemModelScatterDataProxy::scaleRoleReplaceChanged(string replace)
+
+ This signal is emitted when scaleRoleReplace changes to \a replace.
+*/
+
+/*!
\qmlsignal ItemModelScatterDataProxy::xPosRoleReplaceChanged(string replace)
This signal is emitted when xPosRoleReplace changes to \a replace.
@@ -348,6 +396,33 @@ QItemModelScatterDataProxy::QItemModelScatterDataProxy(QAbstractItemModel *itemM
}
/*!
+ * Constructs QItemModelScatterDataProxy with \a itemModel and an optional \a
+ * parent. The proxy doesn't take ownership of the \a itemModel, as item models
+ * are typically owned by other controls. The xPosRole property is set to \a
+ * xPosRole, the yPosRole property to \a yPosRole, the zPosRole property to \a
+ * zPosRole, the rotation property to \a rotationRole, and the scale role
+ * property to \a scaleRole.
+ */
+QItemModelScatterDataProxy::QItemModelScatterDataProxy(QAbstractItemModel *itemModel,
+ const QString &xPosRole,
+ const QString &yPosRole,
+ const QString &zPosRole,
+ const QString &rotationRole,
+ const QString &scaleRole,
+ QObject *parent)
+ : QScatterDataProxy(*(new QItemModelScatterDataProxyPrivate(this)), parent)
+{
+ Q_D(QItemModelScatterDataProxy);
+ d->m_itemModelHandler->setItemModel(itemModel);
+ d->m_xPosRole = xPosRole;
+ d->m_yPosRole = yPosRole;
+ d->m_zPosRole = zPosRole;
+ d->m_rotationRole = rotationRole;
+ d->m_scaleRole = scaleRole;
+ d->connectItemModelHandler();
+}
+
+/*!
* Destroys QItemModelScatterDataProxy.
*/
QItemModelScatterDataProxy::~QItemModelScatterDataProxy() {}
@@ -473,6 +548,30 @@ QString QItemModelScatterDataProxy::rotationRole() const
}
/*!
+ * \property QItemModelScatterDataProxy::scaleRole
+ *
+ * \brief The item model role to map into item scale.
+ *
+ * The model may supply the value for scale as either a variant that is
+ * \c{"x,y,z"}. The first will construct the
+ * vector3d directly with the given values.
+ */
+void QItemModelScatterDataProxy::setScaleRole(const QString &role)
+{
+ Q_D(QItemModelScatterDataProxy);
+ if (d->m_scaleRole != role) {
+ d->m_scaleRole = role;
+ emit scaleRoleChanged(role);
+ }
+}
+
+QString QItemModelScatterDataProxy::scaleRole() const
+{
+ Q_D(const QItemModelScatterDataProxy);
+ return d->m_scaleRole;
+}
+
+/*!
* \property QItemModelScatterDataProxy::xPosRolePattern
*
* \brief Whether search and replace is done on the value mapped by the x
@@ -593,6 +692,33 @@ QRegularExpression QItemModelScatterDataProxy::rotationRolePattern() const
}
/*!
+ * \property QItemModelScatterDataProxy::scaleRolePattern
+ *
+ * \brief Whether a search and replace is done on the value mapped by the
+ * scale role before it is used as item scale.
+ *
+ * This property specifies the regular expression to find the portion
+ * of the mapped value to replace and scaleRoleReplace property contains the
+ * replacement string.
+ *
+ * \sa scaleRole, scaleRoleReplace
+ */
+void QItemModelScatterDataProxy::setScaleRolePattern(const QRegularExpression &pattern)
+{
+ Q_D(QItemModelScatterDataProxy);
+ if (d->m_scaleRolePattern != pattern) {
+ d->m_scaleRolePattern = pattern;
+ emit scaleRolePatternChanged(pattern);
+ }
+}
+
+QRegularExpression QItemModelScatterDataProxy::scaleRolePattern() const
+{
+ Q_D(const QItemModelScatterDataProxy);
+ return d->m_scaleRolePattern;
+}
+
+/*!
* \property QItemModelScatterDataProxy::xPosRoleReplace
*
* \brief The replace content to be used in conjunction with the x position role
@@ -713,17 +839,46 @@ QString QItemModelScatterDataProxy::rotationRoleReplace() const
}
/*!
- * Changes \a xPosRole, \a yPosRole, \a zPosRole, and \a rotationRole mapping.
+ * \property QItemModelScatterDataProxy::scaleRoleReplace
+ *
+ * \brief The replace content to be used in conjunction with the scale role
+ * pattern.
+ *
+ * Defaults to an empty string. For more information on how the search and
+ * replace using regular expressions works, see QString::replace(const
+ * QRegularExpression &rx, const QString &after) function documentation.
+ *
+ * \sa scaleRole, scaleRolePattern
+ */
+void QItemModelScatterDataProxy::setScaleRoleReplace(const QString &replace)
+{
+ Q_D(QItemModelScatterDataProxy);
+ if (d->m_scaleRoleReplace != replace) {
+ d->m_scaleRoleReplace = replace;
+ emit scaleRoleReplaceChanged(replace);
+ }
+}
+
+QString QItemModelScatterDataProxy::scaleRoleReplace() const
+{
+ Q_D(const QItemModelScatterDataProxy);
+ return d->m_scaleRoleReplace;
+}
+
+/*!
+ * Changes \a xPosRole, \a yPosRole, \a zPosRole, \a rotationRole, and \a scaleRole mapping.
*/
void QItemModelScatterDataProxy::remap(const QString &xPosRole,
const QString &yPosRole,
const QString &zPosRole,
- const QString &rotationRole)
+ const QString &rotationRole,
+ const QString &scaleRole)
{
setXPosRole(xPosRole);
setYPosRole(yPosRole);
setZPosRole(zPosRole);
setRotationRole(rotationRole);
+ setScaleRole(scaleRole);
}
// QItemModelScatterDataProxyPrivate
@@ -761,6 +916,10 @@ void QItemModelScatterDataProxyPrivate::connectItemModelHandler()
m_itemModelHandler,
&AbstractItemModelHandler::handleMappingChanged);
QObject::connect(q,
+ &QItemModelScatterDataProxy::scaleRoleChanged,
+ m_itemModelHandler,
+ &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(q,
&QItemModelScatterDataProxy::xPosRolePatternChanged,
m_itemModelHandler,
&AbstractItemModelHandler::handleMappingChanged);
@@ -777,6 +936,10 @@ void QItemModelScatterDataProxyPrivate::connectItemModelHandler()
m_itemModelHandler,
&AbstractItemModelHandler::handleMappingChanged);
QObject::connect(q,
+ &QItemModelScatterDataProxy::scaleRolePatternChanged,
+ m_itemModelHandler,
+ &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(q,
&QItemModelScatterDataProxy::xPosRoleReplaceChanged,
m_itemModelHandler,
&AbstractItemModelHandler::handleMappingChanged);
@@ -792,6 +955,10 @@ void QItemModelScatterDataProxyPrivate::connectItemModelHandler()
&QItemModelScatterDataProxy::rotationRoleReplaceChanged,
m_itemModelHandler,
&AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(q,
+ &QItemModelScatterDataProxy::scaleRoleReplaceChanged,
+ m_itemModelHandler,
+ &AbstractItemModelHandler::handleMappingChanged);
}
QT_END_NAMESPACE
diff --git a/src/graphs3d/data/qitemmodelscatterdataproxy.h b/src/graphs3d/data/qitemmodelscatterdataproxy.h
index b600d790..fb76cc2f 100644
--- a/src/graphs3d/data/qitemmodelscatterdataproxy.h
+++ b/src/graphs3d/data/qitemmodelscatterdataproxy.h
@@ -24,6 +24,8 @@ class Q_GRAPHS_EXPORT QItemModelScatterDataProxy : public QScatterDataProxy
Q_PROPERTY(QString zPosRole READ zPosRole WRITE setZPosRole NOTIFY zPosRoleChanged FINAL)
Q_PROPERTY(QString rotationRole READ rotationRole WRITE setRotationRole NOTIFY
rotationRoleChanged FINAL)
+ Q_PROPERTY(QString scaleRole READ scaleRole WRITE setScaleRole NOTIFY
+ scaleRoleChanged FINAL REVISION(6, 10))
Q_PROPERTY(QRegularExpression xPosRolePattern READ xPosRolePattern WRITE setXPosRolePattern
NOTIFY xPosRolePatternChanged FINAL)
Q_PROPERTY(QRegularExpression yPosRolePattern READ yPosRolePattern WRITE setYPosRolePattern
@@ -32,6 +34,8 @@ class Q_GRAPHS_EXPORT QItemModelScatterDataProxy : public QScatterDataProxy
NOTIFY zPosRolePatternChanged FINAL)
Q_PROPERTY(QRegularExpression rotationRolePattern READ rotationRolePattern WRITE
setRotationRolePattern NOTIFY rotationRolePatternChanged FINAL)
+ Q_PROPERTY(QRegularExpression scaleRolePattern READ scaleRolePattern WRITE
+ setScaleRolePattern NOTIFY scaleRolePatternChanged FINAL REVISION (6, 10))
Q_PROPERTY(QString xPosRoleReplace READ xPosRoleReplace WRITE setXPosRoleReplace NOTIFY
xPosRoleReplaceChanged FINAL)
Q_PROPERTY(QString yPosRoleReplace READ yPosRoleReplace WRITE setYPosRoleReplace NOTIFY
@@ -40,6 +44,8 @@ class Q_GRAPHS_EXPORT QItemModelScatterDataProxy : public QScatterDataProxy
zPosRoleReplaceChanged FINAL)
Q_PROPERTY(QString rotationRoleReplace READ rotationRoleReplace WRITE setRotationRoleReplace
NOTIFY rotationRoleReplaceChanged FINAL)
+ Q_PROPERTY(QString scaleRoleReplace READ scaleRoleReplace WRITE setScaleRoleReplace
+ NOTIFY scaleRoleReplaceChanged FINAL REVISION (6, 10))
QML_NAMED_ELEMENT(ItemModelScatterDataProxy)
public:
@@ -56,6 +62,13 @@ public:
const QString &zPosRole,
const QString &rotationRole,
QObject *parent = nullptr);
+ explicit QItemModelScatterDataProxy(QAbstractItemModel *itemModel,
+ const QString &xPosRole,
+ const QString &yPosRole,
+ const QString &zPosRole,
+ const QString &rotationRole,
+ const QString &scaleRole,
+ QObject *parent = nullptr);
~QItemModelScatterDataProxy() override;
void setItemModel(QAbstractItemModel *itemModel);
@@ -69,11 +82,14 @@ public:
QString zPosRole() const;
void setRotationRole(const QString &role);
QString rotationRole() const;
+ void setScaleRole(const QString &role);
+ QString scaleRole() const;
void remap(const QString &xPosRole,
const QString &yPosRole,
const QString &zPosRole,
- const QString &rotationRole);
+ const QString &rotationRole,
+ const QString &scaleRole);
void setXPosRolePattern(const QRegularExpression &pattern);
QRegularExpression xPosRolePattern() const;
@@ -83,6 +99,8 @@ public:
QRegularExpression zPosRolePattern() const;
void setRotationRolePattern(const QRegularExpression &pattern);
QRegularExpression rotationRolePattern() const;
+ void setScaleRolePattern(const QRegularExpression &pattern);
+ QRegularExpression scaleRolePattern() const;
void setXPosRoleReplace(const QString &replace);
QString xPosRoleReplace() const;
@@ -92,6 +110,8 @@ public:
QString zPosRoleReplace() const;
void setRotationRoleReplace(const QString &replace);
QString rotationRoleReplace() const;
+ void setScaleRoleReplace(const QString &replace);
+ QString scaleRoleReplace() const;
Q_SIGNALS:
void itemModelChanged(const QAbstractItemModel *itemModel);
@@ -99,14 +119,17 @@ Q_SIGNALS:
void yPosRoleChanged(const QString &role);
void zPosRoleChanged(const QString &role);
void rotationRoleChanged(const QString &role);
+ void scaleRoleChanged(const QString &role);
void xPosRolePatternChanged(const QRegularExpression &pattern);
void yPosRolePatternChanged(const QRegularExpression &pattern);
void zPosRolePatternChanged(const QRegularExpression &pattern);
void rotationRolePatternChanged(const QRegularExpression &pattern);
+ void scaleRolePatternChanged(const QRegularExpression &pattern);
void rotationRoleReplaceChanged(const QString &replace);
void xPosRoleReplaceChanged(const QString &replace);
void yPosRoleReplaceChanged(const QString &replace);
void zPosRoleReplaceChanged(const QString &replace);
+ void scaleRoleReplaceChanged(const QString &replace);
private:
Q_DISABLE_COPY(QItemModelScatterDataProxy)
diff --git a/src/graphs3d/data/qitemmodelscatterdataproxy_p.h b/src/graphs3d/data/qitemmodelscatterdataproxy_p.h
index 9bd63677..f4fb09ec 100644
--- a/src/graphs3d/data/qitemmodelscatterdataproxy_p.h
+++ b/src/graphs3d/data/qitemmodelscatterdataproxy_p.h
@@ -37,16 +37,19 @@ private:
QString m_yPosRole;
QString m_zPosRole;
QString m_rotationRole;
+ QString m_scaleRole;
QRegularExpression m_xPosRolePattern;
QRegularExpression m_yPosRolePattern;
QRegularExpression m_zPosRolePattern;
QRegularExpression m_rotationRolePattern;
+ QRegularExpression m_scaleRolePattern;
QString m_xPosRoleReplace;
QString m_yPosRoleReplace;
QString m_zPosRoleReplace;
QString m_rotationRoleReplace;
+ QString m_scaleRoleReplace;
};
QT_END_NAMESPACE
diff --git a/src/graphs3d/data/qscatterdataitem.cpp b/src/graphs3d/data/qscatterdataitem.cpp
index 8d6b327d..f0c0d069 100644
--- a/src/graphs3d/data/qscatterdataitem.cpp
+++ b/src/graphs3d/data/qscatterdataitem.cpp
@@ -37,6 +37,18 @@
*/
/*!
+ * \fn QScatterDataItem::QScatterDataItem(QVector3D position, const QVector3D &scale)
+ * Constructs scatter data item with position \a position
+ * and scale \a scale.
+ */
+
+/*!
+ * \fn QScatterDataItem::QScatterDataItem(QVector3D position, const QQuaternion &rotation, const QVector3D &scale)
+ * Constructs scatter data item with position \a position,
+ * rotation \a rotation, and scale \a scale.
+ */
+
+/*!
* \fn void QScatterDataItem::setPosition(QVector3D pos)
* Sets the position \a pos for this data item.
*/
@@ -61,6 +73,19 @@
*/
/*!
+ * \fn void QScatterDataItem::setScale(const QVector3D &scale)
+ * Sets the scale \a scale for this data item.
+ * If the series also has itemSize, scale is multiplied by it.
+ * Defaults to QVector3D(1,1,1).
+ */
+
+/*!
+ * \fn QQuaternion QScatterDataItem::scale() const
+ * Returns the scale of this data item.
+ * \sa setScale()
+ */
+
+/*!
* \fn void QScatterDataItem::setX(float value)
* Sets the x-coordinate of the item position to the value \a value.
*/
diff --git a/src/graphs3d/data/qscatterdataitem.h b/src/graphs3d/data/qscatterdataitem.h
index ac685aa8..c34672ee 100644
--- a/src/graphs3d/data/qscatterdataitem.h
+++ b/src/graphs3d/data/qscatterdataitem.h
@@ -24,10 +24,22 @@ public:
, m_rotation(rotation)
{}
+ explicit QScatterDataItem(QVector3D position, const QVector3D &scale) noexcept
+ : m_position(position)
+ , m_scale(scale)
+ {}
+ explicit QScatterDataItem(QVector3D position, const QQuaternion &rotation, const QVector3D &scale) noexcept
+ : m_position(position)
+ , m_rotation(rotation)
+ , m_scale(scale)
+ {}
+
void setPosition(QVector3D pos) noexcept { m_position = pos; }
QVector3D position() const noexcept { return m_position; }
void setRotation(const QQuaternion &rot) noexcept { m_rotation = rot; }
QQuaternion rotation() const { return m_rotation; }
+ void setScale(const QVector3D &scale) noexcept { m_scale = scale; }
+ QVector3D scale() const noexcept { return m_scale; }
void setX(float value) noexcept { m_position.setX(value); }
void setY(float value) noexcept { m_position.setY(value); }
void setZ(float value) noexcept { m_position.setZ(value); }
@@ -38,6 +50,7 @@ public:
private:
QVector3D m_position = {};
QQuaternion m_rotation = {};
+ QVector3D m_scale = QVector3D(1,1,1);
Q_DECL_UNUSED_MEMBER quintptr reserved = 0;
};
diff --git a/src/graphs3d/data/scatteritemmodelhandler.cpp b/src/graphs3d/data/scatteritemmodelhandler.cpp
index cb9dfc8a..14239ffb 100644
--- a/src/graphs3d/data/scatteritemmodelhandler.cpp
+++ b/src/graphs3d/data/scatteritemmodelhandler.cpp
@@ -14,10 +14,12 @@ ScatterItemModelHandler::ScatterItemModelHandler(QItemModelScatterDataProxy *pro
, m_yPosRole(noRoleIndex)
, m_zPosRole(noRoleIndex)
, m_rotationRole(noRoleIndex)
+ , m_scaleRole(noRoleIndex)
, m_haveXPosPattern(false)
, m_haveYPosPattern(false)
, m_haveZPosPattern(false)
, m_haveRotationPattern(false)
+ , m_haveScalePattern(false)
{}
ScatterItemModelHandler::~ScatterItemModelHandler() {}
@@ -117,6 +119,32 @@ static inline QQuaternion toQuaternion(const QVariant &variant)
return QQuaternion();
}
+static inline QVector3D toVector3D(const QVariant &variant)
+{
+ if (variant.canConvert<QVector3D>()) {
+ return variant.value<QVector3D>();
+ } else if (variant.canConvert<QString>()) {
+ QString s = variant.toString();
+ if (!s.isEmpty()) {
+ if (s.count(QLatin1Char(',')) == 2) {
+ qsizetype index = s.indexOf(QLatin1Char(','));
+ qsizetype index2 = s.indexOf(QLatin1Char(','), index + 1);
+
+ bool xGood, yGood, zGood;
+ float xCoord = s.left(index).toFloat(&xGood);
+ float yCoord = s.mid(index + 1, index2 - index - 1).toFloat(&yGood);
+ float zCoord = s.mid(index2 + 1).toFloat(&zGood);
+
+ if (xGood && yGood && zGood) {
+ return QVector3D(xCoord, yCoord, zCoord);
+ }
+ }
+ }
+ }
+ return QVector3D();
+
+}
+
void ScatterItemModelHandler::modelPosToScatterItem(int modelRow,
int modelColumn,
QScatterDataItem &item)
@@ -161,6 +189,15 @@ void ScatterItemModelHandler::modelPosToScatterItem(int modelRow,
item.setRotation(toQuaternion(rotationVar));
}
}
+ if (m_scaleRole != noRoleIndex) {
+ QVariant scaleVar = index.data(m_scaleRole);
+ if (m_haveScalePattern) {
+ item.setScale(toVector3D(
+ QVariant(scaleVar.toString().replace(m_scalePattern, m_scaleReplace))));
+ } else {
+ item.setScale(toVector3D(scaleVar));
+ }
+ }
item.setPosition(QVector3D(xPos, yPos, zPos));
}
@@ -179,21 +216,26 @@ void ScatterItemModelHandler::resolveModel()
m_yPosPattern = m_proxy->yPosRolePattern();
m_zPosPattern = m_proxy->zPosRolePattern();
m_rotationPattern = m_proxy->rotationRolePattern();
+ m_scalePattern = m_proxy->scaleRolePattern();
m_xPosReplace = m_proxy->xPosRoleReplace();
m_yPosReplace = m_proxy->yPosRoleReplace();
m_zPosReplace = m_proxy->zPosRoleReplace();
m_rotationReplace = m_proxy->rotationRoleReplace();
+ m_scaleReplace = m_proxy->scaleRoleReplace();
m_haveXPosPattern = !m_xPosPattern.namedCaptureGroups().isEmpty() && m_xPosPattern.isValid();
m_haveYPosPattern = !m_yPosPattern.namedCaptureGroups().isEmpty() && m_yPosPattern.isValid();
m_haveZPosPattern = !m_zPosPattern.namedCaptureGroups().isEmpty() && m_zPosPattern.isValid();
m_haveRotationPattern = !m_rotationPattern.namedCaptureGroups().isEmpty()
&& m_rotationPattern.isValid();
+ m_haveScalePattern = !m_scalePattern.namedCaptureGroups().isEmpty()
+ && m_scalePattern.isValid();
QHash<int, QByteArray> roleHash = m_itemModel->roleNames();
m_xPosRole = roleHash.key(m_proxy->xPosRole().toLatin1(), noRoleIndex);
m_yPosRole = roleHash.key(m_proxy->yPosRole().toLatin1(), noRoleIndex);
m_zPosRole = roleHash.key(m_proxy->zPosRole().toLatin1(), noRoleIndex);
m_rotationRole = roleHash.key(m_proxy->rotationRole().toLatin1(), noRoleIndex);
+ m_scaleRole = roleHash.key(m_proxy->scaleRole().toLatin1(), noRoleIndex);
const int columnCount = m_itemModel->columnCount();
const int rowCount = m_itemModel->rowCount();
const int totalCount = rowCount * columnCount;
diff --git a/src/graphs3d/data/scatteritemmodelhandler_p.h b/src/graphs3d/data/scatteritemmodelhandler_p.h
index 26c46862..b64c1914 100644
--- a/src/graphs3d/data/scatteritemmodelhandler_p.h
+++ b/src/graphs3d/data/scatteritemmodelhandler_p.h
@@ -45,18 +45,22 @@ private:
int m_yPosRole;
int m_zPosRole;
int m_rotationRole;
+ int m_scaleRole;
QRegularExpression m_xPosPattern;
QRegularExpression m_yPosPattern;
QRegularExpression m_zPosPattern;
QRegularExpression m_rotationPattern;
+ QRegularExpression m_scalePattern;
QString m_xPosReplace;
QString m_yPosReplace;
QString m_zPosReplace;
QString m_rotationReplace;
+ QString m_scaleReplace;
bool m_haveXPosPattern;
bool m_haveYPosPattern;
bool m_haveZPosPattern;
bool m_haveRotationPattern;
+ bool m_haveScalePattern;
};
QT_END_NAMESPACE
diff --git a/src/graphs3d/qml/qquickgraphsscatter.cpp b/src/graphs3d/qml/qquickgraphsscatter.cpp
index e0283173..5aa007bc 100644
--- a/src/graphs3d/qml/qquickgraphsscatter.cpp
+++ b/src/graphs3d/qml/qquickgraphsscatter.cpp
@@ -281,7 +281,7 @@ void QQuickGraphsScatter::updateScatterGraphItemPositions(ScatterModel *graphMod
totalRotation = cameraTarget()->rotation();
dataPoint->setRotation(totalRotation);
- dataPoint->setScale(QVector3D(itemSize, itemSize, itemSize));
+ dataPoint->setScale(QVector3D(itemSize, itemSize, itemSize) * item.scale());
} else {
dataPoint->setVisible(false);
}
@@ -326,7 +326,7 @@ void QQuickGraphsScatter::updateScatterGraphItemPositions(ScatterModel *graphMod
dih.position = {posX, posY, posZ};
}
dih.rotation = totalRotation;
- dih.scale = {itemSize, itemSize, itemSize};
+ dih.scale = QVector3D(itemSize, itemSize, itemSize) * item.scale();
positions.push_back(dih);
} else {
diff --git a/tests/auto/cpptest/qgscatter-modelproxy/tst_proxy.cpp b/tests/auto/cpptest/qgscatter-modelproxy/tst_proxy.cpp
index 811aef55..b9aee104 100644
--- a/tests/auto/cpptest/qgscatter-modelproxy/tst_proxy.cpp
+++ b/tests/auto/cpptest/qgscatter-modelproxy/tst_proxy.cpp
@@ -75,10 +75,11 @@ void tst_proxy::construct()
QCOMPARE(proxy->yPosRole(), QString("y"));
QCOMPARE(proxy->zPosRole(), QString("z"));
QCOMPARE(proxy->rotationRole(), QString(""));
+ QCOMPARE(proxy->scaleRole(), QString(""));
delete proxy;
delete series;
- proxy = new QItemModelScatterDataProxy(table->model(), "x", "y", "z", "rot");
+ proxy = new QItemModelScatterDataProxy(table->model(), "x", "y", "z", "rot", "scale");
series = new QScatter3DSeries(proxy);
QVERIFY(proxy);
QVERIFY(series);
@@ -86,6 +87,7 @@ void tst_proxy::construct()
QCOMPARE(proxy->yPosRole(), QString("y"));
QCOMPARE(proxy->zPosRole(), QString("z"));
QCOMPARE(proxy->rotationRole(), QString("rot"));
+ QCOMPARE(proxy->scaleRole(), QString("scale"));
delete proxy;
delete series;
}
@@ -99,6 +101,9 @@ void tst_proxy::initialProperties()
QCOMPARE(m_proxy->rotationRole(), QString());
QCOMPARE(m_proxy->rotationRolePattern(), QRegularExpression());
QCOMPARE(m_proxy->rotationRoleReplace(), QString());
+ QCOMPARE(m_proxy->scaleRole(), QString());
+ QCOMPARE(m_proxy->scaleRolePattern(), QRegularExpression());
+ QCOMPARE(m_proxy->scaleRoleReplace(), QString());
QCOMPARE(m_proxy->xPosRole(), QString());
QCOMPARE(m_proxy->xPosRolePattern(), QRegularExpression());
QCOMPARE(m_proxy->xPosRoleReplace(), QString());
@@ -124,14 +129,17 @@ void tst_proxy::initializeProperties()
QSignalSpy yPosRoleSpy(m_proxy, &QItemModelScatterDataProxy::yPosRoleChanged);
QSignalSpy zPosRoleSpy(m_proxy, &QItemModelScatterDataProxy::zPosRoleChanged);
QSignalSpy rotationRoleSpy(m_proxy, &QItemModelScatterDataProxy::rotationRoleChanged);
+ QSignalSpy scaleRoleSpy(m_proxy, &QItemModelScatterDataProxy::scaleRoleChanged);
QSignalSpy xPosRolePatternSpy(m_proxy, &QItemModelScatterDataProxy::xPosRolePatternChanged);
QSignalSpy yPosRolePatternSpy(m_proxy, &QItemModelScatterDataProxy::yPosRolePatternChanged);
QSignalSpy zPosRolePatternSpy(m_proxy, &QItemModelScatterDataProxy::zPosRolePatternChanged);
QSignalSpy rotationRolePatternSpy(m_proxy, &QItemModelScatterDataProxy::rotationRolePatternChanged);
+ QSignalSpy scaleRolePatternSpy(m_proxy, &QItemModelScatterDataProxy::scaleRolePatternChanged);
QSignalSpy xPosRoleReplaceSpy(m_proxy, &QItemModelScatterDataProxy::xPosRoleReplaceChanged);
QSignalSpy yPosRoleReplaceSpy(m_proxy, &QItemModelScatterDataProxy::yPosRoleReplaceChanged);
QSignalSpy zPosRoleReplaceSpy(m_proxy, &QItemModelScatterDataProxy::zPosRoleReplaceChanged);
QSignalSpy rotationRoleReplaceSpy(m_proxy, &QItemModelScatterDataProxy::rotationRoleReplaceChanged);
+ QSignalSpy scaleRoleReplaceSpy(m_proxy, &QItemModelScatterDataProxy::scaleRoleReplaceChanged);
QTableWidget table;
@@ -139,6 +147,9 @@ void tst_proxy::initializeProperties()
m_proxy->setRotationRole("rotation");
m_proxy->setRotationRolePattern(QRegularExpression("/-/"));
m_proxy->setRotationRoleReplace("\\\\1");
+ m_proxy->setScaleRole("scale");
+ m_proxy->setScaleRolePattern(QRegularExpression("/-/"));
+ m_proxy->setScaleRoleReplace("\\\\1");
m_proxy->setXPosRole("X");
m_proxy->setXPosRolePattern(QRegularExpression("/-/"));
m_proxy->setXPosRoleReplace("\\\\1");
@@ -153,6 +164,9 @@ void tst_proxy::initializeProperties()
QCOMPARE(m_proxy->rotationRole(), QString("rotation"));
QCOMPARE(m_proxy->rotationRolePattern(), QRegularExpression("/-/"));
QCOMPARE(m_proxy->rotationRoleReplace(), QString("\\\\1"));
+ QCOMPARE(m_proxy->scaleRole(), QString("scale"));
+ QCOMPARE(m_proxy->scaleRolePattern(), QRegularExpression("/-/"));
+ QCOMPARE(m_proxy->scaleRoleReplace(), QString("\\\\1"));
QCOMPARE(m_proxy->xPosRole(), QString("X"));
QCOMPARE(m_proxy->xPosRolePattern(), QRegularExpression("/-/"));
QCOMPARE(m_proxy->xPosRoleReplace(), QString("\\\\1"));
@@ -168,14 +182,17 @@ void tst_proxy::initializeProperties()
QCOMPARE(yPosRoleSpy.size(), 1);
QCOMPARE(zPosRoleSpy.size(), 1);
QCOMPARE(rotationRoleSpy.size(), 1);
+ QCOMPARE(scaleRoleSpy.size(), 1);
QCOMPARE(xPosRolePatternSpy.size(), 1);
QCOMPARE(yPosRolePatternSpy.size(), 1);
QCOMPARE(zPosRolePatternSpy.size(), 1);
QCOMPARE(rotationRolePatternSpy.size(), 1);
+ QCOMPARE(scaleRolePatternSpy.size(), 1);
QCOMPARE(xPosRoleReplaceSpy.size(), 1);
QCOMPARE(yPosRoleReplaceSpy.size(), 1);
QCOMPARE(zPosRoleReplaceSpy.size(), 1);
QCOMPARE(rotationRoleReplaceSpy.size(), 1);
+ QCOMPARE(scaleRoleReplaceSpy.size(), 1);
}
void tst_proxy::addModel()
diff --git a/tests/auto/qmltest/scatter3d/tst_proxy.qml b/tests/auto/qmltest/scatter3d/tst_proxy.qml
index abea0404..9e861187 100644
--- a/tests/auto/qmltest/scatter3d/tst_proxy.qml
+++ b/tests/auto/qmltest/scatter3d/tst_proxy.qml
@@ -24,6 +24,9 @@ Item {
rotationRole: "rot"
rotationRolePattern: /-/
rotationRoleReplace: "\\1"
+ scaleRole: "scale"
+ scaleRolePattern: /-/
+ scaleRoleReplace: "\\1"
xPosRole: "x"
xPosRolePattern: /^.*-(\d\d)$/
xPosRoleReplace: "\\1"
@@ -50,6 +53,9 @@ Item {
compare(initial.rotationRole, "")
verify(initial.rotationRolePattern)
compare(initial.rotationRoleReplace, "")
+ compare(initial.scaleRole, "")
+ verify(initial.scaleRolePattern)
+ compare(initial.scaleRoleReplace, "")
compare(initial.xPosRole, "")
verify(initial.xPosRolePattern)
compare(initial.xPosRoleReplace, "")
@@ -75,6 +81,9 @@ Item {
compare(initialized.rotationRole, "rot")
compare(initialized.rotationRolePattern, /-/)
compare(initialized.rotationRoleReplace, "\\1")
+ compare(initialized.scaleRole, "scale")
+ compare(initialized.scaleRolePattern, /-/)
+ compare(initialized.scaleRoleReplace, "\\1")
compare(initialized.xPosRole, "x")
compare(initialized.xPosRolePattern, /^.*-(\d\d)$/)
compare(initialized.xPosRoleReplace, "\\1")
@@ -97,6 +106,9 @@ Item {
change.rotationRole = "rot"
change.rotationRolePattern = /-/
change.rotationRoleReplace = "\\1"
+ change.scaleRole = "scale"
+ change.scaleRolePattern = /-/
+ change.scaleRoleReplace = "\\1"
change.xPosRole = "x"
change.xPosRolePattern = /^.*-(\d\d)$/
change.xPosRoleReplace = "\\1"
@@ -111,6 +123,9 @@ Item {
compare(change.rotationRole, "rot")
compare(change.rotationRolePattern, /-/)
compare(change.rotationRoleReplace, "\\1")
+ compare(change.scaleRole, "scale")
+ compare(change.scaleRolePattern, /-/)
+ compare(change.scaleRoleReplace, "\\1")
compare(change.xPosRole, "x")
compare(change.xPosRolePattern, /^.*-(\d\d)$/)
compare(change.xPosRoleReplace, "\\1")
@@ -126,6 +141,9 @@ Item {
compare(rotationRoleSpy.count, 1)
compare(rotationPatternSpy.count, 1)
compare(rotationReplaceSpy.count, 1)
+ compare(scaleRoleSpy.count, 1)
+ compare(scalePatternSpy.count, 1)
+ compare(scaleReplaceSpy.count, 1)
compare(xPosRoleSpy.count, 1)
compare(xPosPatternSpy.count, 1)
compare(xPosReplaceSpy.count, 1)
@@ -163,6 +181,24 @@ Item {
}
SignalSpy {
+ id: scaleRoleSpy
+ target: change
+ signalName: "scaleRoleChanged"
+ }
+
+ SignalSpy {
+ id: scalePatternSpy
+ target: change
+ signalName: "scaleRolePatternChanged"
+ }
+
+ SignalSpy {
+ id: scaleReplaceSpy
+ target: change
+ signalName: "scaleRoleReplaceChanged"
+ }
+
+ SignalSpy {
id: xPosRoleSpy
target: change
signalName: "xPosRoleChanged"
diff --git a/tests/manual/CMakeLists.txt b/tests/manual/CMakeLists.txt
index ade4c5ee..d6a0684d 100644
--- a/tests/manual/CMakeLists.txt
+++ b/tests/manual/CMakeLists.txt
@@ -30,6 +30,7 @@ if(QT_FEATURE_graphs_3d)
add_subdirectory(qmlcustominput)
add_subdirectory(qmldynamicdata)
add_subdirectory(qmlspline)
+ add_subdirectory(qmlvectorfield)
endif()
if(QT_FEATURE_graphs_3d_surface3d)
add_subdirectory(qmlgradient)
diff --git a/tests/manual/qmlvectorfield/CMakeLists.txt b/tests/manual/qmlvectorfield/CMakeLists.txt
new file mode 100644
index 00000000..8fa3ec9f
--- /dev/null
+++ b/tests/manual/qmlvectorfield/CMakeLists.txt
@@ -0,0 +1,30 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+
+if (NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ project(tst_qmlvectorfield LANGUAGES C CXX)
+ find_package(Qt6BuildInternals COMPONENTS STANDALONE_TEST)
+endif()
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+qt_internal_add_manual_test(tst_qmlvectorfield
+ GUI
+ SOURCES
+ main.cpp
+ )
+
+target_link_libraries(tst_qmlvectorfield PUBLIC
+ Qt::Gui
+ Qt::Graphs
+ Qt::GraphsWidgets
+ )
+
+qt_internal_add_resource(tst_qmlvectorfield "qmlvectorfield"
+ PREFIX
+ "/"
+ FILES
+ "qml/qmlvectorfield/main.qml"
+ )
diff --git a/tests/manual/qmlvectorfield/main.cpp b/tests/manual/qmlvectorfield/main.cpp
new file mode 100644
index 00000000..20159ca6
--- /dev/null
+++ b/tests/manual/qmlvectorfield/main.cpp
@@ -0,0 +1,34 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+
+#include <QtGui/QGuiApplication>
+#include <QtCore/QDir>
+#include <QtQml/QQmlContext>
+#include <QtQuick/QQuickView>
+#include <QtQml/QQmlEngine>
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+
+ QQuickView viewer;
+
+ // 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
+ viewer.engine()->addImportPath(extraImportPath.arg(QGuiApplication::applicationDirPath(),
+ QString::fromLatin1("qml")));
+ QObject::connect(viewer.engine(), &QQmlEngine::quit, &viewer, &QWindow::close);
+
+ viewer.setTitle(QStringLiteral("QML Vector Field"));
+ viewer.setSource(QUrl("qrc:/qml/qmlvectorfield/main.qml"));
+ viewer.setResizeMode(QQuickView::SizeRootObjectToView);
+ viewer.show();
+
+ return app.exec();
+}
diff --git a/tests/manual/qmlvectorfield/qml/qmlvectorfield/main.qml b/tests/manual/qmlvectorfield/qml/qmlvectorfield/main.qml
new file mode 100644
index 00000000..598c6e67
--- /dev/null
+++ b/tests/manual/qmlvectorfield/qml/qmlvectorfield/main.qml
@@ -0,0 +1,113 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtCore
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+import QtQuick.Dialogs
+import QtGraphs
+
+Item {
+ id: mainView
+ width: 1280
+ height: 1024
+
+ ListModel {
+ id: data
+
+ property int pointsPerSide: 15
+ onPointsPerSideChanged: initalizeData()
+
+ function initalizeData() {
+ console.log("initalizing data")
+ data.clear()
+ for (let i = 0; i < pointsPerSide; i++) {
+ for (let j = 0; j < pointsPerSide; j++) {
+ data.append({"xPos": i, "yPos":0, "zPos":j, "rotation":"", "scale":""})
+ }
+ }
+ }
+ function updateQuaternions(pos) {
+ for (let i = 0; i < data.count; i++) {
+ let pointPos = Qt.vector3d(data.get(i).xPos,
+ 0,
+ data.get(i).zPos)
+
+
+ var forward = pos.minus(pointPos).normalized()
+ var forwardVector = Qt.vector3d(0,1,0)
+ var dot = forwardVector.dotProduct(forward)
+ var rotAngle = Math.acos(dot) / (2 * Math.PI);
+ var rotAxis = forwardVector.crossProduct(forward).normalized()
+
+ data.setProperty(i, "rotation", generateQuaternion(rotAxis, rotAngle))
+
+ var dist = pos.minus(pointPos).length()
+ data.setProperty(i, "yPos", (Math.sqrt(data.count) -dist))
+ data.setProperty(i, "scale", generateScale(1, dist + 0.1, 1))
+ }
+ }
+
+ function generateQuaternion(axis, angle) {
+ return "@" + angle * 360 + "," + axis.x + ","
+ + axis.y + "," + axis.z;
+ }
+
+ function generateScale(x, y, z) {
+ return x + "," + y + "," + z
+ }
+
+ Component.onCompleted: {
+ initalizeData()
+ updateQuaternions(Qt.vector3d(5,0,5))
+ }
+ }
+
+ Gradient {
+ id: scatterGradient
+ GradientStop { position: 0.0; color: "blue" }
+ GradientStop { id: middleGradient; position: 0.50; color: "green" }
+ GradientStop { position: 1.0; color: "red" }
+ }
+
+ GraphsTheme {
+ id: theme
+ theme: GraphsTheme.Theme.OrangeSeries
+ colorStyle: GraphsTheme.ColorStyle.RangeGradient
+ baseGradients: [scatterGradient]
+ }
+
+ Scatter3D {
+ id: scatterGraph
+ anchors.fill:parent
+ theme: theme
+
+
+ Scatter3DSeries {
+ id: scatterSeries
+
+ itemSize: 0.1
+ mesh: Abstract3DSeries.Mesh.Arrow
+
+ onSelectedItemChanged: {
+ if (selectedItem == -1)
+ return
+ var i = selectedItem
+ let pointPos = Qt.vector3d(data.get(i).xPos,
+ 0,
+ data.get(i).zPos)
+ data.updateQuaternions(pointPos)
+ }
+ ItemModelScatterDataProxy {
+ id: scatterProxy
+ itemModel: data
+ xPosRole: "xPos"
+ yPosRole: "yPos"
+ zPosRole: "zPos"
+ rotationRole: "rotation"
+ scaleRole: "scale"
+ }
+ }
+ }
+}