1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include <QtCharts/QXYModelMapper>
5#include <private/qxymodelmapper_p.h>
6#include <QtCharts/QXYSeries>
7#include <QtCore/QAbstractItemModel>
8#include <QtCore/QDateTime>
9#include <QtCore/QDebug>
10
11QT_BEGIN_NAMESPACE
12
13/*!
14 Constructs a mapper object which is a child of \a parent.
15*/
16QXYModelMapper::QXYModelMapper(QObject *parent)
17 : QObject(parent),
18 d_ptr(new QXYModelMapperPrivate(this))
19{
20}
21
22/*!
23 \internal
24*/
25QAbstractItemModel *QXYModelMapper::model() const
26{
27 Q_D(const QXYModelMapper);
28 return d->m_model;
29}
30
31/*!
32 \internal
33*/
34void QXYModelMapper::setModel(QAbstractItemModel *model)
35{
36 if (model == 0)
37 return;
38
39 Q_D(QXYModelMapper);
40 if (d->m_model)
41 disconnect(sender: d->m_model, signal: 0, receiver: d, member: 0);
42
43 d->m_model = model;
44 d->initializeXYFromModel();
45 // connect signals from the model
46 connect(sender: d->m_model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), receiver: d, SLOT(modelUpdated(QModelIndex,QModelIndex)));
47 connect(sender: d->m_model, SIGNAL(rowsInserted(QModelIndex,int,int)), receiver: d, SLOT(modelRowsAdded(QModelIndex,int,int)));
48 connect(sender: d->m_model, SIGNAL(rowsRemoved(QModelIndex,int,int)), receiver: d, SLOT(modelRowsRemoved(QModelIndex,int,int)));
49 connect(sender: d->m_model, SIGNAL(columnsInserted(QModelIndex,int,int)), receiver: d, SLOT(modelColumnsAdded(QModelIndex,int,int)));
50 connect(sender: d->m_model, SIGNAL(columnsRemoved(QModelIndex,int,int)), receiver: d, SLOT(modelColumnsRemoved(QModelIndex,int,int)));
51 connect(sender: d->m_model, SIGNAL(modelReset()), receiver: d, SLOT(initializeXYFromModel()));
52 connect(sender: d->m_model, SIGNAL(layoutChanged()), receiver: d, SLOT(initializeXYFromModel()));
53 connect(sender: d->m_model, SIGNAL(destroyed()), receiver: d, SLOT(handleModelDestroyed()));
54}
55
56/*!
57 \internal
58*/
59QXYSeries *QXYModelMapper::series() const
60{
61 Q_D(const QXYModelMapper);
62 return d->m_series;
63}
64
65/*!
66 \internal
67*/
68void QXYModelMapper::setSeries(QXYSeries *series)
69{
70 Q_D(QXYModelMapper);
71 if (d->m_series)
72 disconnect(sender: d->m_series, signal: 0, receiver: d, member: 0);
73
74 if (series == 0)
75 return;
76
77 d->m_series = series;
78 d->initializeXYFromModel();
79 // connect the signals from the series
80 connect(sender: d->m_series, SIGNAL(pointAdded(int)), receiver: d, SLOT(handlePointAdded(int)));
81 connect(sender: d->m_series, SIGNAL(pointRemoved(int)), receiver: d, SLOT(handlePointRemoved(int)));
82 connect(sender: d->m_series, SIGNAL(pointReplaced(int)), receiver: d, SLOT(handlePointReplaced(int)));
83 connect(sender: d->m_series, SIGNAL(destroyed()), receiver: d, SLOT(handleSeriesDestroyed()));
84 connect(sender: d->m_series, SIGNAL(pointsRemoved(int,int)), receiver: d, SLOT(handlePointsRemoved(int,int)));
85}
86
87/*!
88 \internal
89*/
90int QXYModelMapper::first() const
91{
92 Q_D(const QXYModelMapper);
93 return d->m_first;
94}
95
96/*!
97 \internal
98*/
99void QXYModelMapper::setFirst(int first)
100{
101 Q_D(QXYModelMapper);
102 d->m_first = qMax(a: first, b: 0);
103 d->initializeXYFromModel();
104}
105
106/*!
107 \internal
108*/
109int QXYModelMapper::count() const
110{
111 Q_D(const QXYModelMapper);
112 return d->m_count;
113}
114
115/*!
116 \internal
117*/
118void QXYModelMapper::setCount(int count)
119{
120 Q_D(QXYModelMapper);
121 d->m_count = qMax(a: count, b: -1);
122 d->initializeXYFromModel();
123}
124
125/*!
126 Returns the orientation that is used when QXYModelMapper accesses the model.
127 This mean whether the consecutive x/y values of the QXYSeries are read from rows (Qt::Horizontal)
128 or from columns (Qt::Vertical)
129*/
130Qt::Orientation QXYModelMapper::orientation() const
131{
132 Q_D(const QXYModelMapper);
133 return d->m_orientation;
134}
135
136/*!
137 Returns the \a orientation that is used when QXYModelMapper accesses the model.
138 This mean whether the consecutive x/y values of the QXYSeries are read from rows (Qt::Horizontal)
139 or from columns (Qt::Vertical)
140*/
141void QXYModelMapper::setOrientation(Qt::Orientation orientation)
142{
143 Q_D(QXYModelMapper);
144 d->m_orientation = orientation;
145 d->initializeXYFromModel();
146}
147
148/*!
149 Returns which section of the model is kept in sync with the x values of the QXYSeries
150*/
151int QXYModelMapper::xSection() const
152{
153 Q_D(const QXYModelMapper);
154 return d->m_xSection;
155}
156
157/*!
158 Sets the model section that is kept in sync with the x values of the QXYSeries.
159 Parameter \a xSection specifies the section of the model.
160*/
161void QXYModelMapper::setXSection(int xSection)
162{
163 Q_D(QXYModelMapper);
164 d->m_xSection = qMax(a: -1, b: xSection);
165 d->initializeXYFromModel();
166}
167
168/*!
169 Returns which section of the model is kept in sync with the y values of the QXYSeries
170*/
171int QXYModelMapper::ySection() const
172{
173 Q_D(const QXYModelMapper);
174 return d->m_ySection;
175}
176
177/*!
178 Sets the model section that is kept in sync with the y values of the QXYSeries.
179 Parameter \a ySection specifies the section of the model.
180*/
181void QXYModelMapper::setYSection(int ySection)
182{
183 Q_D(QXYModelMapper);
184 d->m_ySection = qMax(a: -1, b: ySection);
185 d->initializeXYFromModel();
186}
187
188///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
189
190QXYModelMapperPrivate::QXYModelMapperPrivate(QXYModelMapper *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_xSection(-1),
198 m_ySection(-1),
199 m_seriesSignalsBlock(false),
200 m_modelSignalsBlock(false),
201 q_ptr(q)
202{
203}
204
205void QXYModelMapperPrivate::blockModelSignals(bool block)
206{
207 m_modelSignalsBlock = block;
208}
209
210void QXYModelMapperPrivate::blockSeriesSignals(bool block)
211{
212 m_seriesSignalsBlock = block;
213}
214
215QModelIndex QXYModelMapperPrivate::xModelIndex(int xPos)
216{
217 if (m_count != -1 && xPos >= m_count)
218 return QModelIndex(); // invalid
219
220 if (m_orientation == Qt::Vertical)
221 return m_model->index(row: xPos + m_first, column: m_xSection);
222 else
223 return m_model->index(row: m_xSection, column: xPos + m_first);
224}
225
226QModelIndex QXYModelMapperPrivate::yModelIndex(int yPos)
227{
228 if (m_count != -1 && yPos >= m_count)
229 return QModelIndex(); // invalid
230
231 if (m_orientation == Qt::Vertical)
232 return m_model->index(row: yPos + m_first, column: m_ySection);
233 else
234 return m_model->index(row: m_ySection, column: yPos + m_first);
235}
236
237qreal QXYModelMapperPrivate::valueFromModel(QModelIndex index)
238{
239 QVariant value = m_model->data(index, role: Qt::DisplayRole);
240 switch (value.metaType().id()) {
241 case QMetaType::QDateTime:
242 return value.toDateTime().toMSecsSinceEpoch();
243 case QMetaType::QDate:
244 return value.toDate().startOfDay().toMSecsSinceEpoch();
245 default:
246 return value.toReal();
247 }
248}
249
250void QXYModelMapperPrivate::setValueToModel(QModelIndex index, qreal value)
251{
252 QVariant oldValue = m_model->data(index, role: Qt::DisplayRole);
253 switch (oldValue.metaType().id()) {
254 case QMetaType::QDateTime:
255 m_model->setData(index, value: QDateTime::fromMSecsSinceEpoch(msecs: value));
256 break;
257 case QMetaType::QDate:
258 m_model->setData(index, value: QDateTime::fromMSecsSinceEpoch(msecs: value).date());
259 break;
260 default:
261 m_model->setData(index, value);
262 }
263}
264
265void QXYModelMapperPrivate::handlePointAdded(int pointPos)
266{
267 if (m_seriesSignalsBlock)
268 return;
269
270 if (m_count != -1)
271 m_count += 1;
272
273 blockModelSignals();
274 if (m_orientation == Qt::Vertical)
275 m_model->insertRows(row: pointPos + m_first, count: 1);
276 else
277 m_model->insertColumns(column: pointPos + m_first, count: 1);
278
279 setValueToModel(index: xModelIndex(xPos: pointPos), value: m_series->points().at(i: pointPos).x());
280 setValueToModel(index: yModelIndex(yPos: pointPos), value: m_series->points().at(i: pointPos).y());
281 blockModelSignals(block: false);
282}
283
284void QXYModelMapperPrivate::handlePointRemoved(int pointPos)
285{
286 if (m_seriesSignalsBlock)
287 return;
288
289 if (m_count != -1)
290 m_count -= 1;
291
292 blockModelSignals();
293 if (m_orientation == Qt::Vertical)
294 m_model->removeRow(arow: pointPos + m_first);
295 else
296 m_model->removeColumn(acolumn: pointPos + m_first);
297 blockModelSignals(block: false);
298}
299
300void QXYModelMapperPrivate::handlePointsRemoved(int pointPos, int count)
301{
302 if (m_seriesSignalsBlock)
303 return;
304
305 m_count -= count;
306
307 if (m_count < -1)
308 m_count = -1;
309
310 blockModelSignals();
311 if (m_orientation == Qt::Vertical)
312 m_model->removeRows(row: pointPos + m_first, count);
313 else
314 m_model->removeColumns(column: pointPos + m_first, count);
315 blockModelSignals(block: false);
316}
317
318void QXYModelMapperPrivate::handlePointReplaced(int pointPos)
319{
320 if (m_seriesSignalsBlock)
321 return;
322
323 blockModelSignals();
324 setValueToModel(index: xModelIndex(xPos: pointPos), value: m_series->points().at(i: pointPos).x());
325 setValueToModel(index: yModelIndex(yPos: pointPos), value: m_series->points().at(i: pointPos).y());
326 blockModelSignals(block: false);
327}
328
329void QXYModelMapperPrivate::handleSeriesDestroyed()
330{
331 m_series = 0;
332}
333
334void QXYModelMapperPrivate::modelUpdated(QModelIndex topLeft, QModelIndex bottomRight)
335{
336 if (m_model == 0 || m_series == 0)
337 return;
338
339 if (m_modelSignalsBlock)
340 return;
341
342 blockSeriesSignals();
343 QModelIndex index;
344 QPointF newPoint;
345 int indexColumn = 0;
346 int indexRow = 0;
347 for (int row = topLeft.row(); row <= bottomRight.row(); row++) {
348 for (int column = topLeft.column(); column <= bottomRight.column(); column++) {
349 index = topLeft.sibling(arow: row, acolumn: column);
350 indexColumn = index.column();
351 indexRow = index.row();
352 if (m_orientation == Qt::Vertical && (indexColumn == m_xSection || indexColumn == m_ySection)) {
353 if (indexRow >= m_first && (m_count == - 1 || indexRow < m_first + m_count)) {
354 QModelIndex xIndex = xModelIndex(xPos: indexRow - m_first);
355 QModelIndex yIndex = yModelIndex(yPos: indexRow - m_first);
356 if (xIndex.isValid() && yIndex.isValid()) {
357 newPoint.setX(valueFromModel(index: xIndex));
358 newPoint.setY(valueFromModel(index: yIndex));
359 m_series->replace(index: indexRow - m_first, newPoint);
360 }
361 }
362 } else if (m_orientation == Qt::Horizontal && (indexRow == m_xSection || indexRow == m_ySection)) {
363 if (indexColumn >= m_first && (m_count == - 1 || indexColumn < m_first + m_count)) {
364 QModelIndex xIndex = xModelIndex(xPos: indexColumn - m_first);
365 QModelIndex yIndex = yModelIndex(yPos: indexColumn - m_first);
366 if (xIndex.isValid() && yIndex.isValid()) {
367 newPoint.setX(valueFromModel(index: xIndex));
368 newPoint.setY(valueFromModel(index: yIndex));
369 m_series->replace(index: indexColumn - m_first, newPoint);
370 }
371 }
372 }
373 }
374 }
375 blockSeriesSignals(block: false);
376}
377
378void QXYModelMapperPrivate::modelRowsAdded(QModelIndex parent, int start, int end)
379{
380 Q_UNUSED(parent);
381 if (m_modelSignalsBlock)
382 return;
383
384 blockSeriesSignals();
385 if (m_orientation == Qt::Vertical)
386 insertData(start, end);
387 else if (start <= m_xSection || start <= m_ySection) // if the changes affect the map - reinitialize the xy
388 initializeXYFromModel();
389 blockSeriesSignals(block: false);
390}
391
392void QXYModelMapperPrivate::modelRowsRemoved(QModelIndex parent, int start, int end)
393{
394 Q_UNUSED(parent);
395 if (m_modelSignalsBlock)
396 return;
397
398 blockSeriesSignals();
399 if (m_orientation == Qt::Vertical)
400 removeData(start, end);
401 else if (start <= m_xSection || start <= m_ySection) // if the changes affect the map - reinitialize the xy
402 initializeXYFromModel();
403 blockSeriesSignals(block: false);
404}
405
406void QXYModelMapperPrivate::modelColumnsAdded(QModelIndex parent, int start, int end)
407{
408 Q_UNUSED(parent);
409 if (m_modelSignalsBlock)
410 return;
411
412 blockSeriesSignals();
413 if (m_orientation == Qt::Horizontal)
414 insertData(start, end);
415 else if (start <= m_xSection || start <= m_ySection) // if the changes affect the map - reinitialize the xy
416 initializeXYFromModel();
417 blockSeriesSignals(block: false);
418}
419
420void QXYModelMapperPrivate::modelColumnsRemoved(QModelIndex parent, int start, int end)
421{
422 Q_UNUSED(parent);
423 if (m_modelSignalsBlock)
424 return;
425
426 blockSeriesSignals();
427 if (m_orientation == Qt::Horizontal)
428 removeData(start, end);
429 else if (start <= m_xSection || start <= m_ySection) // if the changes affect the map - reinitialize the xy
430 initializeXYFromModel();
431 blockSeriesSignals(block: false);
432}
433
434void QXYModelMapperPrivate::handleModelDestroyed()
435{
436 m_model = 0;
437}
438
439void QXYModelMapperPrivate::insertData(int start, int end)
440{
441 if (m_model == 0 || m_series == 0)
442 return;
443
444 if (m_count != -1 && start >= m_first + m_count) {
445 return;
446 } else {
447 int addedCount = end - start + 1;
448 if (m_count != -1 && addedCount > m_count)
449 addedCount = m_count;
450 int first = qMax(a: start, b: m_first);
451 int last = qMin(a: first + addedCount - 1, b: m_orientation == Qt::Vertical ? m_model->rowCount() - 1 : m_model->columnCount() - 1);
452 for (int i = first; i <= last; i++) {
453 QPointF point;
454 QModelIndex xIndex = xModelIndex(xPos: i - m_first);
455 QModelIndex yIndex = yModelIndex(yPos: i - m_first);
456 if (xIndex.isValid() && yIndex.isValid()) {
457 point.setX(valueFromModel(index: xIndex));
458 point.setY(valueFromModel(index: yIndex));
459 m_series->insert(index: i - m_first, point);
460 }
461 }
462
463 // remove excess of points (above m_count)
464 if (m_count != -1 && m_series->points().size() > m_count)
465 for (int i = m_series->points().size() - 1; i >= m_count; i--) {
466 m_series->remove(point: m_series->points().at(i));
467 }
468 }
469}
470
471void QXYModelMapperPrivate::removeData(int start, int end)
472{
473 if (m_model == 0 || m_series == 0)
474 return;
475
476 int removedCount = end - start + 1;
477 if (m_count != -1 && start >= m_first + m_count) {
478 return;
479 } else {
480 int toRemove = qMin(a: m_series->count(), b: removedCount); // first find how many items can actually be removed
481 int first = qMax(a: start, b: m_first); // get the index of the first item that will be removed.
482 int last = qMin(a: first + toRemove - 1, b: m_series->count() + m_first - 1); // get the index of the last item that will be removed.
483 for (int i = last; i >= first; i--) {
484 m_series->remove(point: m_series->points().at(i: i - m_first));
485 }
486
487 if (m_count != -1) {
488 int itemsAvailable; // check how many are available to be added
489 if (m_orientation == Qt::Vertical)
490 itemsAvailable = m_model->rowCount() - m_first - m_series->count();
491 else
492 itemsAvailable = m_model->columnCount() - m_first - m_series->count();
493 int toBeAdded = qMin(a: itemsAvailable, b: m_count - m_series->count()); // add not more items than there is space left to be filled.
494 int currentSize = m_series->count();
495 if (toBeAdded > 0)
496 for (int i = m_series->count(); i < currentSize + toBeAdded; i++) {
497 QPointF point;
498 QModelIndex xIndex = xModelIndex(xPos: i);
499 QModelIndex yIndex = yModelIndex(yPos: i);
500 if (xIndex.isValid() && yIndex.isValid()) {
501 point.setX(valueFromModel(index: xIndex));
502 point.setY(valueFromModel(index: yIndex));
503 m_series->insert(index: i, point);
504 }
505 }
506 }
507 }
508}
509
510void QXYModelMapperPrivate::initializeXYFromModel()
511{
512 if (m_model == 0 || m_series == 0)
513 return;
514
515 blockSeriesSignals();
516 // clear current content
517 m_series->clear();
518
519 // create the initial points set
520 int pointPos = 0;
521 QModelIndex xIndex = xModelIndex(xPos: pointPos);
522 QModelIndex yIndex = yModelIndex(yPos: pointPos);
523
524 if (xIndex.isValid() && yIndex.isValid()) {
525 while (xIndex.isValid() && yIndex.isValid()) {
526 QPointF point;
527 point.setX(valueFromModel(index: xIndex));
528 point.setY(valueFromModel(index: yIndex));
529 m_series->append(point);
530 pointPos++;
531 xIndex = xModelIndex(xPos: pointPos);
532 yIndex = yModelIndex(yPos: pointPos);
533 // Don't warn about invalid index after the first, those are valid and used to
534 // determine when we should end looping.
535 }
536 } else {
537 // Invalid index right off the bat means series will be left empty, so output a warning,
538 // unless model is also empty
539 int count = m_orientation == Qt::Vertical ? m_model->rowCount() : m_model->columnCount();
540 if (count > 0) {
541 if (!xIndex.isValid())
542 qWarning() << __FUNCTION__ << QStringLiteral("Invalid X coordinate index in model mapper.");
543 else if (!yIndex.isValid())
544 qWarning() << __FUNCTION__ << QStringLiteral("Invalid Y coordinate index in model mapper.");
545 }
546 }
547
548 blockSeriesSignals(block: false);
549}
550
551QT_END_NAMESPACE
552
553#include "moc_qxymodelmapper.cpp"
554#include "moc_qxymodelmapper_p.cpp"
555

source code of qtcharts/src/charts/xychart/qxymodelmapper.cpp