1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qsurface3dseries_p.h"
5#include "surfaceitemmodelhandler_p.h"
6
7QT_BEGIN_NAMESPACE
8
9SurfaceItemModelHandler::SurfaceItemModelHandler(QItemModelSurfaceDataProxy *proxy, QObject *parent)
10 : AbstractItemModelHandler(parent)
11 , m_proxy(proxy)
12 , m_proxyArray(0)
13 , m_xPosRole(noRoleIndex)
14 , m_yPosRole(noRoleIndex)
15 , m_zPosRole(noRoleIndex)
16 , m_haveXPosPattern(false)
17 , m_haveYPosPattern(false)
18 , m_haveZPosPattern(false)
19{}
20
21SurfaceItemModelHandler::~SurfaceItemModelHandler() {}
22
23void SurfaceItemModelHandler::handleDataChanged(const QModelIndex &topLeft,
24 const QModelIndex &bottomRight,
25 const QList<int> &roles)
26{
27 // Do nothing if full reset already pending
28 if (!m_fullReset) {
29 if (!m_proxy->useModelCategories()) {
30 // If the data model doesn't directly map rows and columns, we cannot
31 // optimize
32 AbstractItemModelHandler::handleDataChanged(topLeft, bottomRight, roles);
33 } else {
34 int startRow = qMin(a: topLeft.row(), b: bottomRight.row());
35 int endRow = qMax(a: topLeft.row(), b: bottomRight.row());
36 int startCol = qMin(a: topLeft.column(), b: bottomRight.column());
37 int endCol = qMax(a: topLeft.column(), b: bottomRight.column());
38
39 for (int i = startRow; i <= endRow; i++) {
40 for (int j = startCol; j <= endCol; j++) {
41 QModelIndex index = m_itemModel->index(row: i, column: j);
42 QSurfaceDataItem item;
43 QVariant xValueVar = index.data(arole: m_xPosRole);
44 QVariant yValueVar = index.data(arole: m_yPosRole);
45 QVariant zValueVar = index.data(arole: m_zPosRole);
46 const QSurfaceDataItem &oldItem = m_proxy->itemAt(rowIndex: i, columnIndex: j);
47 float xPos;
48 float yPos;
49 float zPos;
50 if (m_xPosRole != noRoleIndex) {
51 if (m_haveXPosPattern) {
52 xPos = xValueVar.toString()
53 .replace(re: m_xPosPattern, after: m_xPosReplace)
54 .toFloat();
55 } else {
56 xPos = xValueVar.toFloat();
57 }
58 } else {
59 xPos = oldItem.x();
60 }
61
62 if (m_haveYPosPattern)
63 yPos = yValueVar.toString().replace(re: m_yPosPattern, after: m_yPosReplace).toFloat();
64 else
65 yPos = yValueVar.toFloat();
66
67 if (m_zPosRole != noRoleIndex) {
68 if (m_haveZPosPattern) {
69 zPos = zValueVar.toString()
70 .replace(re: m_zPosPattern, after: m_zPosReplace)
71 .toFloat();
72 } else {
73 zPos = zValueVar.toFloat();
74 }
75 } else {
76 zPos = oldItem.z();
77 }
78 item.setPosition(QVector3D(xPos, yPos, zPos));
79 m_proxy->setItem(rowIndex: i, columnIndex: j, item);
80 }
81 }
82 }
83 }
84}
85
86// Resolve entire item model into QSurfaceDataArray.
87void SurfaceItemModelHandler::resolveModel()
88{
89 if (m_itemModel.isNull()) {
90 m_proxy->resetArray();
91 m_proxyArray.clear();
92 return;
93 }
94
95 if (!m_proxy->useModelCategories()
96 && (m_proxy->rowRole().isEmpty() || m_proxy->columnRole().isEmpty())) {
97 m_proxy->resetArray();
98 m_proxyArray.clear();
99 return;
100 }
101
102 // Position patterns can be reused on single item changes, so store them to
103 // member variables.
104 QRegularExpression rowPattern(m_proxy->rowRolePattern());
105 QRegularExpression colPattern(m_proxy->columnRolePattern());
106 m_xPosPattern = m_proxy->xPosRolePattern();
107 m_yPosPattern = m_proxy->yPosRolePattern();
108 m_zPosPattern = m_proxy->zPosRolePattern();
109 QString rowReplace = m_proxy->rowRoleReplace();
110 QString colReplace = m_proxy->columnRoleReplace();
111 m_xPosReplace = m_proxy->xPosRoleReplace();
112 m_yPosReplace = m_proxy->yPosRoleReplace();
113 m_zPosReplace = m_proxy->zPosRoleReplace();
114 bool haveRowPattern = !rowPattern.namedCaptureGroups().isEmpty() && rowPattern.isValid();
115 bool haveColPattern = !colPattern.namedCaptureGroups().isEmpty() && colPattern.isValid();
116 m_haveXPosPattern = !m_xPosPattern.namedCaptureGroups().isEmpty() && m_xPosPattern.isValid();
117 m_haveYPosPattern = !m_yPosPattern.namedCaptureGroups().isEmpty() && m_yPosPattern.isValid();
118 m_haveZPosPattern = !m_zPosPattern.namedCaptureGroups().isEmpty() && m_zPosPattern.isValid();
119
120 QHash<int, QByteArray> roleHash = m_itemModel->roleNames();
121
122 // Default to display role if no mapping
123 m_xPosRole = roleHash.key(value: m_proxy->xPosRole().toLatin1(), defaultKey: noRoleIndex);
124 m_yPosRole = roleHash.key(value: m_proxy->yPosRole().toLatin1(), defaultKey: Qt::DisplayRole);
125 m_zPosRole = roleHash.key(value: m_proxy->zPosRole().toLatin1(), defaultKey: noRoleIndex);
126 int rowCount = m_itemModel->rowCount();
127 int columnCount = m_itemModel->columnCount();
128
129 if (m_proxy->useModelCategories()) {
130 // If dimensions have changed, recreate the array
131 if (m_proxyArray.data() != m_proxy->series()->dataArray().data()
132 || columnCount != m_proxy->columnCount() || rowCount != m_proxyArray.size()) {
133 m_proxyArray.clear();
134 m_proxyArray.reserve(asize: rowCount);
135 for (int i = 0; i < rowCount; i++)
136 m_proxyArray.append(t: QSurfaceDataRow(columnCount));
137 }
138 for (int i = 0; i < rowCount; i++) {
139 QSurfaceDataRow &newProxyRow = m_proxyArray[i];
140 for (int j = 0; j < columnCount; j++) {
141 QModelIndex index = m_itemModel->index(row: i, column: j);
142 QVariant xValueVar = index.data(arole: m_xPosRole);
143 QVariant yValueVar = index.data(arole: m_yPosRole);
144 QVariant zValueVar = index.data(arole: m_zPosRole);
145 float xPos;
146 float yPos;
147 float zPos;
148 if (m_xPosRole != noRoleIndex) {
149 if (m_haveXPosPattern)
150 xPos = xValueVar.toString().replace(re: m_xPosPattern, after: m_xPosReplace).toFloat();
151 else
152 xPos = xValueVar.toFloat();
153 } else {
154 QString header = m_itemModel->headerData(section: j, orientation: Qt::Horizontal).toString();
155 bool ok = false;
156 float headerValue = header.toFloat(ok: &ok);
157 if (ok)
158 xPos = headerValue;
159 else
160 xPos = float(j);
161 }
162
163 if (m_haveYPosPattern)
164 yPos = yValueVar.toString().replace(re: m_yPosPattern, after: m_yPosReplace).toFloat();
165 else
166 yPos = yValueVar.toFloat();
167
168 if (m_zPosRole != noRoleIndex) {
169 if (m_haveZPosPattern)
170 zPos = zValueVar.toString().replace(re: m_zPosPattern, after: m_zPosReplace).toFloat();
171 else
172 zPos = zValueVar.toFloat();
173 } else {
174 QString header = m_itemModel->headerData(section: i, orientation: Qt::Vertical).toString();
175 bool ok = false;
176 float headerValue = header.toFloat(ok: &ok);
177 if (ok)
178 zPos = headerValue;
179 else
180 zPos = float(i);
181 }
182
183 newProxyRow[j].setPosition(QVector3D(xPos, yPos, zPos));
184 }
185 }
186 } else {
187 int rowRole = roleHash.key(value: m_proxy->rowRole().toLatin1());
188 int columnRole = roleHash.key(value: m_proxy->columnRole().toLatin1());
189 if (m_xPosRole == noRoleIndex)
190 m_xPosRole = columnRole;
191 if (m_zPosRole == noRoleIndex)
192 m_zPosRole = rowRole;
193
194 bool generateRows = m_proxy->autoRowCategories();
195 bool generateColumns = m_proxy->autoColumnCategories();
196
197 QStringList rowList;
198 QStringList columnList;
199 // For detecting duplicates in categories generation, using QHashes should
200 // be faster than simple QStringList::contains() check.
201 QHash<QString, bool> rowListHash;
202 QHash<QString, bool> columnListHash;
203
204 bool cumulative = m_proxy->multiMatchBehavior()
205 == QItemModelSurfaceDataProxy::MultiMatchBehavior::Average
206 || m_proxy->multiMatchBehavior()
207 == QItemModelSurfaceDataProxy::MultiMatchBehavior::CumulativeY;
208 bool average = m_proxy->multiMatchBehavior()
209 == QItemModelSurfaceDataProxy::MultiMatchBehavior::Average;
210 bool takeFirst = m_proxy->multiMatchBehavior()
211 == QItemModelSurfaceDataProxy::MultiMatchBehavior::First;
212 QHash<QString, QHash<QString, int>> *matchCountMap = 0;
213 if (cumulative)
214 matchCountMap = new QHash<QString, QHash<QString, int>>;
215
216 // Sort values into rows and columns
217 typedef QHash<QString, QVector3D> ColumnValueMap;
218 QHash<QString, ColumnValueMap> itemValueMap;
219 for (int i = 0; i < rowCount; i++) {
220 for (int j = 0; j < columnCount; j++) {
221 QModelIndex index = m_itemModel->index(row: i, column: j);
222 QString rowRoleStr = index.data(arole: rowRole).toString();
223 if (haveRowPattern)
224 rowRoleStr.replace(re: rowPattern, after: rowReplace);
225 QString columnRoleStr = index.data(arole: columnRole).toString();
226 if (haveColPattern)
227 columnRoleStr.replace(re: colPattern, after: colReplace);
228 QVariant xValueVar = index.data(arole: m_xPosRole);
229 QVariant yValueVar = index.data(arole: m_yPosRole);
230 QVariant zValueVar = index.data(arole: m_zPosRole);
231 float xPos;
232 float yPos;
233 float zPos;
234 if (m_haveXPosPattern)
235 xPos = xValueVar.toString().replace(re: m_xPosPattern, after: m_xPosReplace).toFloat();
236 else
237 xPos = xValueVar.toFloat();
238 if (m_haveYPosPattern)
239 yPos = yValueVar.toString().replace(re: m_yPosPattern, after: m_yPosReplace).toFloat();
240 else
241 yPos = yValueVar.toFloat();
242 if (m_haveZPosPattern)
243 zPos = zValueVar.toString().replace(re: m_zPosPattern, after: m_zPosReplace).toFloat();
244 else
245 zPos = zValueVar.toFloat();
246
247 QVector3D itemPos(xPos, yPos, zPos);
248
249 if (cumulative)
250 (*matchCountMap)[rowRoleStr][columnRoleStr]++;
251
252 if (cumulative) {
253 itemValueMap[rowRoleStr][columnRoleStr] += itemPos;
254 } else {
255 if (takeFirst && itemValueMap.contains(key: rowRoleStr)) {
256 if (itemValueMap.value(key: rowRoleStr).contains(key: columnRoleStr))
257 continue; // We already have a value for this row/column combo
258 }
259 itemValueMap[rowRoleStr][columnRoleStr] = itemPos;
260 }
261
262 if (generateRows && !rowListHash.value(key: rowRoleStr, defaultValue: false)) {
263 rowListHash.insert(key: rowRoleStr, value: true);
264 rowList << rowRoleStr;
265 }
266 if (generateColumns && !columnListHash.value(key: columnRoleStr, defaultValue: false)) {
267 columnListHash.insert(key: columnRoleStr, value: true);
268 columnList << columnRoleStr;
269 }
270 }
271 }
272
273 if (generateRows)
274 m_proxy->d_func()->m_rowCategories = rowList;
275 else
276 rowList = m_proxy->rowCategories();
277
278 if (generateColumns)
279 m_proxy->d_func()->m_columnCategories = columnList;
280 else
281 columnList = m_proxy->columnCategories();
282
283 // If dimensions have changed, recreate the array
284 if (m_proxyArray.data() != m_proxy->series()->dataArray().data()
285 || columnList.size() != m_proxy->columnCount()
286 || rowList.size() != m_proxyArray.size()) {
287 m_proxyArray.clear();
288 m_proxyArray.reserve(asize: rowList.size());
289 for (int i = 0; i < rowList.size(); i++)
290 m_proxyArray.append(t: QSurfaceDataRow(columnList.size()));
291 }
292 // Create data array from itemValueMap
293 for (int i = 0; i < rowList.size(); i++) {
294 QString rowKey = rowList.at(i);
295 QSurfaceDataRow &newProxyRow = m_proxyArray[i];
296 for (int j = 0; j < columnList.size(); j++) {
297 QVector3D &itemPos = itemValueMap[rowKey][columnList.at(i: j)];
298 if (cumulative) {
299 float divisor = float((*matchCountMap)[rowKey][columnList.at(i: j)]);
300 if (divisor) {
301 if (average) {
302 itemPos /= divisor;
303 } else { // cumulativeY
304 itemPos.setX(itemPos.x() / divisor);
305 itemPos.setZ(itemPos.z() / divisor);
306 }
307 }
308 }
309 newProxyRow[j].setPosition(itemPos);
310 }
311 }
312
313 delete matchCountMap;
314 }
315
316 m_proxy->resetArray(newArray: m_proxyArray);
317}
318
319QT_END_NAMESPACE
320

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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