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