1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include <QtCharts/QBoxPlotModelMapper> |
5 | #include <private/qboxplotmodelmapper_p.h> |
6 | #include <QtCharts/QBoxPlotSeries> |
7 | #include <QtCharts/QBoxSet> |
8 | #include <QtCharts/QChart> |
9 | #include <QtCore/QAbstractItemModel> |
10 | |
11 | QT_BEGIN_NAMESPACE |
12 | |
13 | /*! |
14 | \class QBoxPlotModelMapper |
15 | \inmodule QtCharts |
16 | \brief The QBoxPlotModelMapper class is the base class for box plot model |
17 | mapper classes. |
18 | \internal |
19 | |
20 | Model mappers enable using a data model derived from the QAbstractItemModel |
21 | class as a data source for a chart. |
22 | */ |
23 | |
24 | QBoxPlotModelMapper::QBoxPlotModelMapper(QObject *parent) : |
25 | QObject(parent), |
26 | d_ptr(new QBoxPlotModelMapperPrivate(this)) |
27 | { |
28 | } |
29 | |
30 | QAbstractItemModel *QBoxPlotModelMapper::model() const |
31 | { |
32 | Q_D(const QBoxPlotModelMapper); |
33 | return d->m_model; |
34 | } |
35 | |
36 | void QBoxPlotModelMapper::setModel(QAbstractItemModel *model) |
37 | { |
38 | if (model == 0) |
39 | return; |
40 | |
41 | Q_D(QBoxPlotModelMapper); |
42 | if (d->m_model) |
43 | disconnect(sender: d->m_model, signal: 0, receiver: d, member: 0); |
44 | |
45 | d->m_model = model; |
46 | d->initializeBoxFromModel(); |
47 | // connect signals from the model |
48 | connect(sender: d->m_model, SIGNAL(modelReset()), receiver: d, SLOT(initializeBoxFromModel())); |
49 | connect(sender: d->m_model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), receiver: d, SLOT(modelUpdated(QModelIndex,QModelIndex))); |
50 | connect(sender: d->m_model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), receiver: d, SLOT(modelHeaderDataUpdated(Qt::Orientation,int,int))); |
51 | connect(sender: d->m_model, SIGNAL(rowsInserted(QModelIndex,int,int)), receiver: d, SLOT(modelRowsAdded(QModelIndex,int,int))); |
52 | connect(sender: d->m_model, SIGNAL(rowsRemoved(QModelIndex,int,int)), receiver: d, SLOT(modelRowsRemoved(QModelIndex,int,int))); |
53 | connect(sender: d->m_model, SIGNAL(columnsInserted(QModelIndex,int,int)), receiver: d, SLOT(modelColumnsAdded(QModelIndex,int,int))); |
54 | connect(sender: d->m_model, SIGNAL(columnsRemoved(QModelIndex,int,int)), receiver: d, SLOT(modelColumnsRemoved(QModelIndex,int,int))); |
55 | connect(sender: d->m_model, SIGNAL(destroyed()), receiver: d, SLOT(handleModelDestroyed())); |
56 | } |
57 | |
58 | QBoxPlotSeries *QBoxPlotModelMapper::series() const |
59 | { |
60 | Q_D(const QBoxPlotModelMapper); |
61 | return d->m_series; |
62 | } |
63 | |
64 | void QBoxPlotModelMapper::setSeries(QBoxPlotSeries *series) |
65 | { |
66 | Q_D(QBoxPlotModelMapper); |
67 | if (d->m_series) |
68 | disconnect(sender: d->m_series, signal: 0, receiver: d, member: 0); |
69 | |
70 | if (series == 0) |
71 | return; |
72 | |
73 | d->m_series = series; |
74 | d->initializeBoxFromModel(); |
75 | // connect the signals from the series |
76 | connect(sender: d->m_series, SIGNAL(boxsetsAdded(QList<QBoxSet *>)), receiver: d, SLOT(boxSetsAdded(QList<QBoxSet *>))); |
77 | connect(sender: d->m_series, SIGNAL(boxsetsRemoved(QList<QBoxSet *>)), receiver: d, SLOT(boxSetsRemoved(QList<QBoxSet *>))); |
78 | connect(sender: d->m_series, SIGNAL(destroyed()), receiver: d, SLOT(handleSeriesDestroyed())); |
79 | } |
80 | |
81 | /*! |
82 | Returns which row/column of the model contains the first values of the QBoxSets in the series. |
83 | The default value is 0. |
84 | */ |
85 | int QBoxPlotModelMapper::first() const |
86 | { |
87 | Q_D(const QBoxPlotModelMapper); |
88 | return d->m_first; |
89 | } |
90 | |
91 | /*! |
92 | Sets which row/column of the model contains the \a first values of the QBoxSets in the series. |
93 | The default value is 0. |
94 | */ |
95 | void QBoxPlotModelMapper::setFirst(int first) |
96 | { |
97 | Q_D(QBoxPlotModelMapper); |
98 | d->m_first = qMax(a: first, b: 0); |
99 | d->initializeBoxFromModel(); |
100 | } |
101 | |
102 | /*! |
103 | Returns the number of rows/columns of the model that are mapped as the data for QBoxPlotSeries |
104 | Minimal and default value is: -1 (count limited by the number of rows/columns in the model) |
105 | */ |
106 | int QBoxPlotModelMapper::count() const |
107 | { |
108 | Q_D(const QBoxPlotModelMapper); |
109 | return d->m_count; |
110 | } |
111 | |
112 | /*! |
113 | Sets the \a count of rows/columns of the model that are mapped as the data for QBoxPlotSeries |
114 | Minimal and default value is: -1 (count limited by the number of rows/columns in the model) |
115 | */ |
116 | void QBoxPlotModelMapper::setCount(int count) |
117 | { |
118 | Q_D(QBoxPlotModelMapper); |
119 | d->m_count = qMax(a: count, b: -1); |
120 | d->initializeBoxFromModel(); |
121 | } |
122 | |
123 | /*! |
124 | Returns the orientation that is used when QBoxPlotModelMapper accesses the model. |
125 | This means whether the consecutive values of the box-and-whiskers set are read from row (Qt::Horizontal) |
126 | or from columns (Qt::Vertical) |
127 | */ |
128 | Qt::Orientation QBoxPlotModelMapper::orientation() const |
129 | { |
130 | Q_D(const QBoxPlotModelMapper); |
131 | return d->m_orientation; |
132 | } |
133 | |
134 | /*! |
135 | Returns the \a orientation that is used when QBoxPlotModelMapper accesses the model. |
136 | This mean whether the consecutive values of the box-and-whiskers set are read from row (Qt::Horizontal) |
137 | or from columns (Qt::Vertical) |
138 | */ |
139 | void QBoxPlotModelMapper::setOrientation(Qt::Orientation orientation) |
140 | { |
141 | Q_D(QBoxPlotModelMapper); |
142 | d->m_orientation = orientation; |
143 | d->initializeBoxFromModel(); |
144 | } |
145 | |
146 | /*! |
147 | Returns which section of the model is used as the data source for the first box set |
148 | */ |
149 | int QBoxPlotModelMapper::firstBoxSetSection() const |
150 | { |
151 | Q_D(const QBoxPlotModelMapper); |
152 | return d->m_firstBoxSetSection; |
153 | } |
154 | |
155 | /*! |
156 | Sets the model section that is used as the data source for the first box set |
157 | Parameter \a firstBoxSetSection specifies the section of the model. |
158 | */ |
159 | void QBoxPlotModelMapper::setFirstBoxSetSection(int firstBoxSetSection) |
160 | { |
161 | Q_D(QBoxPlotModelMapper); |
162 | d->m_firstBoxSetSection = qMax(a: -1, b: firstBoxSetSection); |
163 | d->initializeBoxFromModel(); |
164 | } |
165 | |
166 | /*! |
167 | Returns which section of the model is used as the data source for the last box set |
168 | */ |
169 | int QBoxPlotModelMapper::lastBoxSetSection() const |
170 | { |
171 | Q_D(const QBoxPlotModelMapper); |
172 | return d->m_lastBoxSetSection; |
173 | } |
174 | |
175 | /*! |
176 | Sets the model section that is used as the data source for the last box set |
177 | Parameter \a lastBoxSetSection specifies the section of the model. |
178 | */ |
179 | void QBoxPlotModelMapper::setLastBoxSetSection(int lastBoxSetSection) |
180 | { |
181 | Q_D(QBoxPlotModelMapper); |
182 | d->m_lastBoxSetSection = qMax(a: -1, b: lastBoxSetSection); |
183 | d->initializeBoxFromModel(); |
184 | } |
185 | |
186 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
187 | |
188 | QBoxPlotModelMapperPrivate::QBoxPlotModelMapperPrivate(QBoxPlotModelMapper *q) : |
189 | QObject(q), |
190 | m_series(0), |
191 | m_model(0), |
192 | m_first(0), |
193 | m_count(-1), |
194 | m_orientation(Qt::Vertical), |
195 | m_firstBoxSetSection(-1), |
196 | m_lastBoxSetSection(-1), |
197 | m_seriesSignalsBlock(false), |
198 | m_modelSignalsBlock(false), |
199 | q_ptr(q) |
200 | { |
201 | } |
202 | |
203 | void QBoxPlotModelMapperPrivate::blockModelSignals(bool block) |
204 | { |
205 | m_modelSignalsBlock = block; |
206 | } |
207 | |
208 | void QBoxPlotModelMapperPrivate::blockSeriesSignals(bool block) |
209 | { |
210 | m_seriesSignalsBlock = block; |
211 | } |
212 | |
213 | QBoxSet *QBoxPlotModelMapperPrivate::boxSet(QModelIndex index) |
214 | { |
215 | if (!index.isValid()) |
216 | return 0; |
217 | |
218 | if (m_orientation == Qt::Vertical && index.column() >= m_firstBoxSetSection && index.column() <= m_lastBoxSetSection) { |
219 | if (index.row() >= m_first && (m_count == - 1 || index.row() < m_first + m_count)) |
220 | return m_series->boxSets().at(i: index.column() - m_firstBoxSetSection); |
221 | } else if (m_orientation == Qt::Horizontal && index.row() >= m_firstBoxSetSection && index.row() <= m_lastBoxSetSection) { |
222 | if (index.column() >= m_first && (m_count == - 1 || index.column() < m_first + m_count)) |
223 | return m_series->boxSets().at(i: index.row() - m_firstBoxSetSection); |
224 | } |
225 | return 0; // This part of model has not been mapped to any boxset |
226 | } |
227 | |
228 | QModelIndex QBoxPlotModelMapperPrivate::boxModelIndex(int boxSection, int posInBar) |
229 | { |
230 | if (m_count != -1 && posInBar >= m_count) |
231 | return QModelIndex(); // invalid |
232 | |
233 | if (boxSection < m_firstBoxSetSection || boxSection > m_lastBoxSetSection) |
234 | return QModelIndex(); // invalid |
235 | |
236 | if (m_orientation == Qt::Vertical) |
237 | return m_model->index(row: posInBar + m_first, column: boxSection); |
238 | else |
239 | return m_model->index(row: boxSection, column: posInBar + m_first); |
240 | } |
241 | |
242 | void QBoxPlotModelMapperPrivate::handleSeriesDestroyed() |
243 | { |
244 | m_series = 0; |
245 | } |
246 | |
247 | void QBoxPlotModelMapperPrivate::modelUpdated(QModelIndex topLeft, QModelIndex bottomRight) |
248 | { |
249 | Q_UNUSED(topLeft); |
250 | Q_UNUSED(bottomRight); |
251 | |
252 | if (m_model == 0 || m_series == 0) |
253 | return; |
254 | |
255 | if (m_modelSignalsBlock) |
256 | return; |
257 | |
258 | blockSeriesSignals(); |
259 | QModelIndex index; |
260 | for (int row = topLeft.row(); row <= bottomRight.row(); row++) { |
261 | for (int column = topLeft.column(); column <= bottomRight.column(); column++) { |
262 | index = topLeft.sibling(arow: row, acolumn: column); |
263 | QBoxSet *box = boxSet(index); |
264 | if (box) { |
265 | if (m_orientation == Qt::Vertical) |
266 | box->setValue(index: row - m_first, value: m_model->data(index).toReal()); |
267 | else |
268 | box->setValue(index: column - m_first, value: m_model->data(index).toReal()); |
269 | } |
270 | } |
271 | } |
272 | blockSeriesSignals(block: false); |
273 | } |
274 | |
275 | void QBoxPlotModelMapperPrivate::(Qt::Orientation orientation, int first, int last) |
276 | { |
277 | Q_UNUSED(orientation); |
278 | Q_UNUSED(first); |
279 | Q_UNUSED(last); |
280 | } |
281 | |
282 | void QBoxPlotModelMapperPrivate::modelRowsAdded(QModelIndex parent, int start, int end) |
283 | { |
284 | Q_UNUSED(parent); |
285 | if (m_modelSignalsBlock) |
286 | return; |
287 | |
288 | blockSeriesSignals(); |
289 | if (m_orientation == Qt::Vertical) |
290 | insertData(start, end); |
291 | else if (start <= m_firstBoxSetSection || start <= m_lastBoxSetSection) // if the changes affect the map - reinitialize |
292 | initializeBoxFromModel(); |
293 | blockSeriesSignals(block: false); |
294 | } |
295 | |
296 | void QBoxPlotModelMapperPrivate::modelRowsRemoved(QModelIndex parent, int start, int end) |
297 | { |
298 | Q_UNUSED(parent); |
299 | if (m_modelSignalsBlock) |
300 | return; |
301 | |
302 | blockSeriesSignals(); |
303 | if (m_orientation == Qt::Vertical) |
304 | removeData(start, end); |
305 | else if (start <= m_firstBoxSetSection || start <= m_lastBoxSetSection) // if the changes affect the map - reinitialize |
306 | initializeBoxFromModel(); |
307 | blockSeriesSignals(block: false); |
308 | } |
309 | |
310 | void QBoxPlotModelMapperPrivate::modelColumnsAdded(QModelIndex parent, int start, int end) |
311 | { |
312 | Q_UNUSED(parent); |
313 | if (m_modelSignalsBlock) |
314 | return; |
315 | |
316 | blockSeriesSignals(); |
317 | if (m_orientation == Qt::Horizontal) |
318 | insertData(start, end); |
319 | else if (start <= m_firstBoxSetSection || start <= m_lastBoxSetSection) // if the changes affect the map - reinitialize |
320 | initializeBoxFromModel(); |
321 | blockSeriesSignals(block: false); |
322 | } |
323 | |
324 | void QBoxPlotModelMapperPrivate::modelColumnsRemoved(QModelIndex parent, int start, int end) |
325 | { |
326 | Q_UNUSED(parent); |
327 | if (m_modelSignalsBlock) |
328 | return; |
329 | |
330 | blockSeriesSignals(); |
331 | if (m_orientation == Qt::Horizontal) |
332 | removeData(start, end); |
333 | else if (start <= m_firstBoxSetSection || start <= m_lastBoxSetSection) // if the changes affect the map - reinitialize |
334 | initializeBoxFromModel(); |
335 | blockSeriesSignals(block: false); |
336 | } |
337 | |
338 | void QBoxPlotModelMapperPrivate::handleModelDestroyed() |
339 | { |
340 | m_model = 0; |
341 | } |
342 | |
343 | void QBoxPlotModelMapperPrivate::insertData(int start, int end) |
344 | { |
345 | Q_UNUSED(end); |
346 | Q_UNUSED(start); |
347 | Q_UNUSED(end); |
348 | // Currently boxplotchart needs to be fully recalculated when change is made. |
349 | // Re-initialize |
350 | initializeBoxFromModel(); |
351 | } |
352 | |
353 | void QBoxPlotModelMapperPrivate::removeData(int start, int end) |
354 | { |
355 | Q_UNUSED(end); |
356 | Q_UNUSED(start); |
357 | Q_UNUSED(end); |
358 | // Currently boxplotchart needs to be fully recalculated when change is made. |
359 | // Re-initialize |
360 | initializeBoxFromModel(); |
361 | } |
362 | |
363 | void QBoxPlotModelMapperPrivate::boxSetsAdded(const QList<QBoxSet *> &sets) |
364 | { |
365 | if (m_seriesSignalsBlock) |
366 | return; |
367 | |
368 | if (sets.size() == 0) |
369 | return; |
370 | |
371 | int firstIndex = m_series->boxSets().indexOf(t: sets.at(i: 0)); |
372 | if (firstIndex == -1) |
373 | return; |
374 | |
375 | int maxCount = 0; |
376 | for (int i = 0; i < sets.size(); i++) { |
377 | if (sets.at(i)->count() > m_count) |
378 | maxCount = sets.at(i)->count(); |
379 | } |
380 | |
381 | if (m_count != -1 && m_count < maxCount) |
382 | m_count = maxCount; |
383 | |
384 | m_lastBoxSetSection += sets.size(); |
385 | |
386 | blockModelSignals(); |
387 | int modelCapacity = m_orientation == Qt::Vertical ? m_model->rowCount() - m_first : m_model->columnCount() - m_first; |
388 | if (maxCount > modelCapacity) { |
389 | if (m_orientation == Qt::Vertical) |
390 | m_model->insertRows(row: m_model->rowCount(), count: maxCount - modelCapacity); |
391 | else |
392 | m_model->insertColumns(column: m_model->columnCount(), count: maxCount - modelCapacity); |
393 | } |
394 | |
395 | if (m_orientation == Qt::Vertical) |
396 | m_model->insertColumns(column: firstIndex + m_firstBoxSetSection, count: sets.size()); |
397 | else |
398 | m_model->insertRows(row: firstIndex + m_firstBoxSetSection, count: sets.size()); |
399 | |
400 | |
401 | for (int i = firstIndex + m_firstBoxSetSection; i < firstIndex + m_firstBoxSetSection + sets.size(); i++) { |
402 | for (int j = 0; j < sets.at(i: i - firstIndex - m_firstBoxSetSection)->count(); j++) |
403 | m_model->setData(index: boxModelIndex(boxSection: i, posInBar: j), value: sets.at(i: i - firstIndex - m_firstBoxSetSection)->at(index: j)); |
404 | } |
405 | blockModelSignals(block: false); |
406 | initializeBoxFromModel(); |
407 | } |
408 | |
409 | void QBoxPlotModelMapperPrivate::boxSetsRemoved(const QList<QBoxSet *> &sets) |
410 | { |
411 | if (m_seriesSignalsBlock) |
412 | return; |
413 | |
414 | if (sets.size() == 0) |
415 | return; |
416 | |
417 | int firstIndex = m_boxSets.indexOf(t: sets.at(i: 0)); |
418 | if (firstIndex == -1) |
419 | return; |
420 | |
421 | m_lastBoxSetSection -= sets.size(); |
422 | |
423 | for (int i = firstIndex + sets.size() - 1; i >= firstIndex; i--) |
424 | m_boxSets.removeAt(i); |
425 | |
426 | blockModelSignals(); |
427 | if (m_orientation == Qt::Vertical) |
428 | m_model->removeColumns(column: firstIndex + m_firstBoxSetSection, count: sets.size()); |
429 | else |
430 | m_model->removeRows(row: firstIndex + m_firstBoxSetSection, count: sets.size()); |
431 | blockModelSignals(block: false); |
432 | initializeBoxFromModel(); |
433 | } |
434 | |
435 | void QBoxPlotModelMapperPrivate::boxValueChanged(int index) |
436 | { |
437 | if (m_seriesSignalsBlock) |
438 | return; |
439 | |
440 | int boxSetIndex = m_boxSets.indexOf(t: qobject_cast<QBoxSet *>(object: QObject::sender())); |
441 | |
442 | blockModelSignals(); |
443 | m_model->setData(index: boxModelIndex(boxSection: boxSetIndex + m_firstBoxSetSection, posInBar: index), value: m_boxSets.at(i: boxSetIndex)->at(index)); |
444 | blockModelSignals(block: false); |
445 | initializeBoxFromModel(); |
446 | } |
447 | |
448 | void QBoxPlotModelMapperPrivate::initializeBoxFromModel() |
449 | { |
450 | if (m_model == 0 || m_series == 0) |
451 | return; |
452 | |
453 | blockSeriesSignals(); |
454 | // clear current content |
455 | m_series->clear(); |
456 | m_boxSets.clear(); |
457 | |
458 | // create the initial box-and-whiskers sets |
459 | for (int i = m_firstBoxSetSection; i <= m_lastBoxSetSection; i++) { |
460 | int posInBar = 0; |
461 | QModelIndex boxIndex = boxModelIndex(boxSection: i, posInBar); |
462 | // check if there is such model index |
463 | if (boxIndex.isValid()) { |
464 | QBoxSet *boxSet = new QBoxSet(); |
465 | while (boxIndex.isValid()) { |
466 | boxSet->append(value: m_model->data(index: boxIndex, role: Qt::DisplayRole).toDouble()); |
467 | posInBar++; |
468 | boxIndex = boxModelIndex(boxSection: i, posInBar); |
469 | } |
470 | connect(sender: boxSet, SIGNAL(valueChanged(int)), receiver: this, SLOT(boxValueChanged(int))); |
471 | m_series->append(box: boxSet); |
472 | m_boxSets.append(t: boxSet); |
473 | } else { |
474 | break; |
475 | } |
476 | } |
477 | blockSeriesSignals(block: false); |
478 | } |
479 | |
480 | QT_END_NAMESPACE |
481 | |
482 | #include "moc_qboxplotmodelmapper.cpp" |
483 | #include "moc_qboxplotmodelmapper_p.cpp" |
484 | |