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

Provided by KDAB

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

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