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