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

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