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

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