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

source code of qtdatavis3d/src/datavisualization/data/surfaceitemmodelhandler.cpp