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

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