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

source code of qtcharts/src/charts/boxplotchart/qboxplotmodelmapper.cpp