1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qscatter3dseries_p.h"
5#include "scatteritemmodelhandler_p.h"
6
7#include <QtGui/qquaternion.h>
8
9QT_BEGIN_NAMESPACE
10
11ScatterItemModelHandler::ScatterItemModelHandler(QItemModelScatterDataProxy *proxy, QObject *parent)
12 : AbstractItemModelHandler(parent)
13 , m_proxy(proxy)
14 , m_proxyArray(0)
15 , m_xPosRole(noRoleIndex)
16 , m_yPosRole(noRoleIndex)
17 , m_zPosRole(noRoleIndex)
18 , m_rotationRole(noRoleIndex)
19 , m_scaleRole(noRoleIndex)
20 , m_haveXPosPattern(false)
21 , m_haveYPosPattern(false)
22 , m_haveZPosPattern(false)
23 , m_haveRotationPattern(false)
24 , m_haveScalePattern(false)
25{}
26
27ScatterItemModelHandler::~ScatterItemModelHandler() {}
28
29void ScatterItemModelHandler::handleDataChanged(const QModelIndex &topLeft,
30 const QModelIndex &bottomRight,
31 const QList<int> &roles)
32{
33 // Do nothing if full reset already pending
34 if (!m_fullReset) {
35 if (m_itemModel->columnCount() > 1) {
36 // If the data model is multi-column, do full asynchronous reset to
37 // simplify things
38 AbstractItemModelHandler::handleDataChanged(topLeft, bottomRight, roles);
39 } else {
40 int start = qMin(a: topLeft.row(), b: bottomRight.row());
41 int end = qMax(a: topLeft.row(), b: bottomRight.row());
42
43 QScatterDataArray array(end - start + 1);
44 int count = 0;
45 for (int i = start; i <= end; i++)
46 modelPosToScatterItem(modelRow: i, modelColumn: 0, item&: array[count++]);
47
48 m_proxy->setItems(index: start, items: array);
49 }
50 }
51}
52
53void ScatterItemModelHandler::handleRowsInserted(const QModelIndex &parent, int start, int end)
54{
55 // Do nothing if full reset already pending
56 if (!m_fullReset) {
57 if (!m_proxy->itemCount() || m_itemModel->columnCount() > 1) {
58 // If inserting into an empty array, do full asynchronous reset to avoid
59 // multiple separate inserts when initializing the model. If the data
60 // model is multi-column, do full asynchronous reset to simplify things
61 AbstractItemModelHandler::handleRowsInserted(parent, start, end);
62 } else {
63 QScatterDataArray array(end - start + 1);
64 int count = 0;
65 for (int i = start; i <= end; i++)
66 modelPosToScatterItem(modelRow: i, modelColumn: 0, item&: array[count++]);
67
68 m_proxy->insertItems(index: start, items: array);
69 }
70 }
71}
72
73void ScatterItemModelHandler::handleRowsRemoved(const QModelIndex &parent, int start, int end)
74{
75 Q_UNUSED(parent);
76
77 // Do nothing if full reset already pending
78 if (!m_fullReset) {
79 if (m_itemModel->columnCount() > 1) {
80 // If the data model is multi-column, do full asynchronous reset to
81 // simplify things
82 AbstractItemModelHandler::handleRowsRemoved(parent, start, end);
83 } else {
84 m_proxy->removeItems(index: start, removeCount: end - start + 1);
85 }
86 }
87}
88
89static inline QQuaternion toQuaternion(const QVariant &variant)
90{
91 if (variant.canConvert<QQuaternion>()) {
92 return variant.value<QQuaternion>();
93 } else if (variant.canConvert<QString>()) {
94 QString s = variant.toString();
95 if (!s.isEmpty()) {
96 bool angleAndAxis = false;
97 if (s.startsWith(c: QLatin1Char('@'))) {
98 angleAndAxis = true;
99 s = s.mid(position: 1);
100 }
101 if (s.count(c: QLatin1Char(',')) == 3) {
102 qsizetype index = s.indexOf(ch: QLatin1Char(','));
103 qsizetype index2 = s.indexOf(ch: QLatin1Char(','), from: index + 1);
104 qsizetype index3 = s.indexOf(ch: QLatin1Char(','), from: index2 + 1);
105
106 bool sGood, xGood, yGood, zGood;
107 float sCoord = s.left(n: index).toFloat(ok: &sGood);
108 float xCoord = s.mid(position: index + 1, n: index2 - index - 1).toFloat(ok: &xGood);
109 float yCoord = s.mid(position: index2 + 1, n: index3 - index2 - 1).toFloat(ok: &yGood);
110 float zCoord = s.mid(position: index3 + 1).toFloat(ok: &zGood);
111
112 if (sGood && xGood && yGood && zGood) {
113 if (angleAndAxis)
114 return QQuaternion::fromAxisAndAngle(x: xCoord, y: yCoord, z: zCoord, angle: sCoord);
115 else
116 return QQuaternion(sCoord, xCoord, yCoord, zCoord);
117 }
118 }
119 }
120 }
121 return QQuaternion();
122}
123
124static inline QVector3D toVector3D(const QVariant &variant)
125{
126 if (variant.canConvert<QVector3D>()) {
127 return variant.value<QVector3D>();
128 } else if (variant.canConvert<QString>()) {
129 QString s = variant.toString();
130 if (!s.isEmpty()) {
131 if (s.count(c: QLatin1Char(',')) == 2) {
132 qsizetype index = s.indexOf(ch: QLatin1Char(','));
133 qsizetype index2 = s.indexOf(ch: QLatin1Char(','), from: index + 1);
134
135 bool xGood, yGood, zGood;
136 float xCoord = s.left(n: index).toFloat(ok: &xGood);
137 float yCoord = s.mid(position: index + 1, n: index2 - index - 1).toFloat(ok: &yGood);
138 float zCoord = s.mid(position: index2 + 1).toFloat(ok: &zGood);
139
140 if (xGood && yGood && zGood) {
141 return QVector3D(xCoord, yCoord, zCoord);
142 }
143 }
144 }
145 }
146 return QVector3D();
147
148}
149
150void ScatterItemModelHandler::modelPosToScatterItem(int modelRow,
151 int modelColumn,
152 QScatterDataItem &item)
153{
154 QModelIndex index = m_itemModel->index(row: modelRow, column: modelColumn);
155 float xPos;
156 float yPos;
157 float zPos;
158 if (m_xPosRole != noRoleIndex) {
159 QVariant xValueVar = index.data(arole: m_xPosRole);
160 if (m_haveXPosPattern)
161 xPos = xValueVar.toString().replace(re: m_xPosPattern, after: m_xPosReplace).toFloat();
162 else
163 xPos = xValueVar.toFloat();
164 } else {
165 xPos = 0.0f;
166 }
167 if (m_yPosRole != noRoleIndex) {
168 QVariant yValueVar = index.data(arole: m_yPosRole);
169 if (m_haveYPosPattern)
170 yPos = yValueVar.toString().replace(re: m_yPosPattern, after: m_yPosReplace).toFloat();
171 else
172 yPos = yValueVar.toFloat();
173 } else {
174 yPos = 0.0f;
175 }
176 if (m_zPosRole != noRoleIndex) {
177 QVariant zValueVar = index.data(arole: m_zPosRole);
178 if (m_haveZPosPattern)
179 zPos = zValueVar.toString().replace(re: m_zPosPattern, after: m_zPosReplace).toFloat();
180 else
181 zPos = zValueVar.toFloat();
182 } else {
183 zPos = 0.0f;
184 }
185 if (m_rotationRole != noRoleIndex) {
186 QVariant rotationVar = index.data(arole: m_rotationRole);
187 if (m_haveRotationPattern) {
188 item.setRotation(toQuaternion(
189 variant: QVariant(rotationVar.toString().replace(re: m_rotationPattern, after: m_rotationReplace))));
190 } else {
191 item.setRotation(toQuaternion(variant: rotationVar));
192 }
193 }
194
195 item.setPosition(QVector3D(xPos, yPos, zPos));
196}
197
198QVector3D ScatterItemModelHandler::modelDataToScale(int modelRow,
199 int modelColumn)
200{
201 QModelIndex index = m_itemModel->index(row: modelRow, column: modelColumn);
202
203 if (m_scaleRole != noRoleIndex) {
204 QVariant scaleVar = index.data(arole: m_scaleRole);
205 QVector3D scale = QVector3D(1.0, 1.0, 1.0);
206 if (m_haveScalePattern) {
207 scale = toVector3D(
208 variant: QVariant(scaleVar.toString().replace(re: m_scalePattern, after: m_scaleReplace)));
209 } else {
210 scale = toVector3D(variant: scaleVar);
211 }
212 return scale;
213 }
214
215 return QVector3D(1.0f, 1.0f, 1.0f);
216}
217
218// Resolve entire item model into QScatterDataArray.
219void ScatterItemModelHandler::resolveModel()
220{
221 if (m_itemModel.isNull()) {
222 QScatterDataArray empty;
223 m_proxy->resetArray(newArray: empty);
224 m_proxyArray.clear();
225 return;
226 }
227
228 m_xPosPattern = m_proxy->xPosRolePattern();
229 m_yPosPattern = m_proxy->yPosRolePattern();
230 m_zPosPattern = m_proxy->zPosRolePattern();
231 m_rotationPattern = m_proxy->rotationRolePattern();
232 m_scalePattern = m_proxy->scaleRolePattern();
233 m_xPosReplace = m_proxy->xPosRoleReplace();
234 m_yPosReplace = m_proxy->yPosRoleReplace();
235 m_zPosReplace = m_proxy->zPosRoleReplace();
236 m_rotationReplace = m_proxy->rotationRoleReplace();
237 m_scaleReplace = m_proxy->scaleRoleReplace();
238 m_haveXPosPattern = !m_xPosPattern.namedCaptureGroups().isEmpty() && m_xPosPattern.isValid();
239 m_haveYPosPattern = !m_yPosPattern.namedCaptureGroups().isEmpty() && m_yPosPattern.isValid();
240 m_haveZPosPattern = !m_zPosPattern.namedCaptureGroups().isEmpty() && m_zPosPattern.isValid();
241 m_haveRotationPattern = !m_rotationPattern.namedCaptureGroups().isEmpty()
242 && m_rotationPattern.isValid();
243 m_haveScalePattern = !m_scalePattern.namedCaptureGroups().isEmpty()
244 && m_scalePattern.isValid();
245
246 QHash<int, QByteArray> roleHash = m_itemModel->roleNames();
247 m_xPosRole = roleHash.key(value: m_proxy->xPosRole().toLatin1(), defaultKey: noRoleIndex);
248 m_yPosRole = roleHash.key(value: m_proxy->yPosRole().toLatin1(), defaultKey: noRoleIndex);
249 m_zPosRole = roleHash.key(value: m_proxy->zPosRole().toLatin1(), defaultKey: noRoleIndex);
250 m_rotationRole = roleHash.key(value: m_proxy->rotationRole().toLatin1(), defaultKey: noRoleIndex);
251 m_scaleRole = roleHash.key(value: m_proxy->scaleRole().toLatin1(), defaultKey: noRoleIndex);
252 const int columnCount = m_itemModel->columnCount();
253 const int rowCount = m_itemModel->rowCount();
254 const int totalCount = rowCount * columnCount;
255 int runningCount = 0;
256
257 // If dimensions have changed, recreate the array
258 if (m_proxyArray.data() != m_proxy->series()->dataArray().data()
259 || totalCount != m_proxyArray.size()) {
260 m_proxyArray.resize(size: totalCount);
261 m_scaleArray.resize(size: totalCount);
262 }
263
264 // Parse data into newProxyArray
265 for (int i = 0; i < rowCount; i++) {
266 for (int j = 0; j < columnCount; j++) {
267 modelPosToScatterItem(modelRow: i, modelColumn: j, item&: m_proxyArray[runningCount]);
268 m_scaleArray[runningCount] = modelDataToScale(modelRow: i, modelColumn: j);
269 runningCount++;
270 }
271 }
272
273 m_proxy->resetArray(newArray: m_proxyArray);
274 m_proxy->resetScaleArray(newArray: m_scaleArray);
275}
276
277QT_END_NAMESPACE
278

source code of qtgraphs/src/graphs3d/data/scatteritemmodelhandler.cpp