1 | // Copyright (C) 2024 The Qt Company Ltd. |
---|---|
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | #include <QtCore/QAbstractItemModel> |
4 | #include <QtGraphs/QXYModelMapper> |
5 | #include <QtGraphs/QXYSeries> |
6 | #include "qxymodelmapper_p.h" |
7 | |
8 | QT_BEGIN_NAMESPACE |
9 | |
10 | /*! |
11 | \class QXYModelMapper |
12 | \inmodule QtGraphs |
13 | \ingroup graphs_2D |
14 | |
15 | \brief The QXYModelMapper class is a model mapper for line, |
16 | spline, and scatter series. |
17 | |
18 | Model mappers enable using a data model derived from the QAbstractItemModel |
19 | class as a data source for a graph. A model mapper is used to |
20 | create a connection between a line, spline, or scatter series. |
21 | A \e TableModel is a natural choice |
22 | for the model. |
23 | |
24 | Both model and series properties can be used to manipulate the data. The |
25 | model mapper keeps the series and the data model in sync. |
26 | |
27 | \sa QXYSeries |
28 | */ |
29 | /*! |
30 | \qmltype XYModelMapper |
31 | \nativetype QXYModelMapper |
32 | \inqmlmodule QtGraphs |
33 | \ingroup graphs_qml_2D |
34 | |
35 | \brief A model mapper for XYSeries. |
36 | |
37 | Model mappers enable using a data model derived from the QAbstractItemModel |
38 | class as a data source for a graph. A model mapper is used to |
39 | create a connection between a line, spline, or scatter series. |
40 | A \e TableModel is a natural choice |
41 | for the model. |
42 | |
43 | Both model and series properties can be used to manipulate the data. The |
44 | model mapper keeps the series and the data model in sync. |
45 | |
46 | \sa XYSeries |
47 | */ |
48 | |
49 | /*! |
50 | \property QXYModelMapper::series |
51 | \brief The series that is used by the mapper. |
52 | |
53 | All the data in the series is discarded when it is set to the mapper. |
54 | When a new series is specified, the old series is disconnected (but it |
55 | preserves its data). |
56 | */ |
57 | /*! |
58 | \qmlproperty XYSeries XYModelMapper::series |
59 | The series that is used by the mapper. All the data in the series is |
60 | discarded when it is set to the mapper. When a new series is specified, the |
61 | old series is disconnected (but it preserves its data). |
62 | */ |
63 | |
64 | /*! |
65 | \property QXYModelMapper::model |
66 | \brief The model that is used by the mapper. |
67 | */ |
68 | /*! |
69 | \qmlproperty SomeModel XYModelMapper::model |
70 | The data model that is used by the mapper. You need to implement the model |
71 | and expose it to QML. |
72 | |
73 | \note The model has to support adding and removing rows or columns and |
74 | modifying the data in the cells. |
75 | */ |
76 | |
77 | /*! |
78 | \property QXYModelMapper::xSection |
79 | \brief The section of the model that contains the x-coordinates of data |
80 | points. |
81 | |
82 | The default value is -1 (invalid mapping). |
83 | |
84 | \sa QXYModelMapper::orientation |
85 | */ |
86 | /*! |
87 | \qmlproperty qsizetype XYModelMapper::xSection |
88 | the section of the model that contains the x-coordinates of data points. |
89 | The default value is -1 (invalid mapping). |
90 | |
91 | \sa orientation |
92 | */ |
93 | |
94 | /*! |
95 | \property QXYModelMapper::ySection |
96 | \brief the section of the model that contains the y-coordinates of data |
97 | points. |
98 | |
99 | The default value is -1 (invalid mapping). |
100 | |
101 | \sa QXYModelMapper::orientation |
102 | */ |
103 | /*! |
104 | \qmlproperty qsizetype XYModelMapper::ySection |
105 | the section of the model that contains the y-coordinates of data points. |
106 | The default value is -1 (invalid mapping). |
107 | |
108 | \sa orientation |
109 | */ |
110 | |
111 | /*! |
112 | \property QXYModelMapper::first |
113 | \brief The row of the model that contains the data for the first point |
114 | of the series. |
115 | |
116 | The minimum and default value is 0. |
117 | |
118 | \sa QXYModelMapper::orientation |
119 | */ |
120 | /*! |
121 | \qmlproperty qsizetype XYModelMapper::first |
122 | The row of the model that contains the data for the first point of the series. |
123 | The default value is 0. |
124 | |
125 | \sa orientation |
126 | */ |
127 | |
128 | /*! |
129 | \property QXYModelMapper::count |
130 | \brief The number of rows of the model that are mapped as the data for series. |
131 | |
132 | The minimum and default value is -1 (the number is limited by the number of |
133 | rows in the model). |
134 | |
135 | \sa QXYModelMapper::orientation |
136 | */ |
137 | /*! |
138 | \qmlproperty qsizetype XYModelMapper::count |
139 | The number of rows of the model that are mapped as the data for series. The default value is |
140 | -1 (the number is limited by the number of rows in the model). |
141 | |
142 | \sa orientation |
143 | */ |
144 | |
145 | /*! |
146 | \property QXYModelMapper::orientation |
147 | \brief Tells the modelmapper how to map data from a model. If |
148 | \c{Qt::Vertical} is used, each of the model's columns defines a bar set, and the |
149 | model's rows define the categories. When the orientation is set to |
150 | \c{Qt::Horizontal}, each of the model's rows defines a bar set, and the model's |
151 | columns define categories. |
152 | |
153 | The default value is \c{Qt::Vertical} |
154 | */ |
155 | /*! |
156 | \qmlproperty orientation XYModelMapper::orientation |
157 | Tells the modelmapper how to map data from a model. If |
158 | \c{Qt.Vertical} is used, the model has \e X and \e Y columns, and the |
159 | model's rows define the data points. When the orientation is set to |
160 | \c{Qt.Horizontal}, the model has \e X and \e Y rows, and the model's |
161 | columns define the data points. |
162 | */ |
163 | |
164 | /*! |
165 | \qmlsignal QXYModelMapper::seriesChanged() |
166 | |
167 | This signal is emitted when the series that the mapper is connected to changes. |
168 | */ |
169 | |
170 | /*! |
171 | \qmlsignal XYModelMapper::modelChanged() |
172 | |
173 | This signal is emitted when the model that the mapper is connected to changes. |
174 | */ |
175 | |
176 | /*! |
177 | \qmlsignal XYModelMapper::xSectionChanged() |
178 | |
179 | This signal is emitted when the section that contains the x-coordinates of |
180 | data points changes. |
181 | */ |
182 | |
183 | /*! |
184 | \qmlsignal XYModelMapper::ySectionChanged() |
185 | |
186 | This signal is emitted when the section that contains the y-coordinates of |
187 | data points changes. |
188 | */ |
189 | |
190 | /*! |
191 | \qmlsignal XYModelMapper::firstChanged() |
192 | This signal is emitted when the first row changes. |
193 | */ |
194 | |
195 | /*! |
196 | \qmlsignal XYModelMapper::countChanged() |
197 | This signal is emitted when the number of rows changes. |
198 | */ |
199 | |
200 | /*! |
201 | \qmlsignal BarModelMapper::orientationChanged() |
202 | This signal is emitted when the orientation changes. |
203 | */ |
204 | |
205 | QXYModelMapper::~QXYModelMapper() {} |
206 | |
207 | QXYModelMapper::QXYModelMapper(QObject *parent) |
208 | : QObject{*(new QXYModelMapperPrivate), parent} |
209 | {} |
210 | |
211 | QXYModelMapper::QXYModelMapper(QXYModelMapperPrivate &dd, QObject *parent) |
212 | : QObject(dd, parent) |
213 | {} |
214 | |
215 | QAbstractItemModel *QXYModelMapper::model() const |
216 | { |
217 | Q_D(const QXYModelMapper); |
218 | return d->m_model; |
219 | } |
220 | |
221 | void QXYModelMapper::setModel(QAbstractItemModel *model) |
222 | { |
223 | if (model == 0) |
224 | return; |
225 | |
226 | Q_D(QXYModelMapper); |
227 | if (d->m_model) { |
228 | QObjectPrivate::disconnect(sender: d->m_model, |
229 | signal: &QAbstractItemModel::dataChanged, |
230 | receiverPrivate: d, |
231 | slot: &QXYModelMapperPrivate::onModelUpdated); |
232 | QObjectPrivate::disconnect(sender: d->m_model, |
233 | signal: &QAbstractItemModel::rowsInserted, |
234 | receiverPrivate: d, |
235 | slot: &QXYModelMapperPrivate::onModelRowsAdded); |
236 | QObjectPrivate::disconnect(sender: d->m_model, |
237 | signal: &QAbstractItemModel::rowsRemoved, |
238 | receiverPrivate: d, |
239 | slot: &QXYModelMapperPrivate::onModelRowsRemoved); |
240 | QObjectPrivate::disconnect(sender: d->m_model, |
241 | signal: &QAbstractItemModel::columnsInserted, |
242 | receiverPrivate: d, |
243 | slot: &QXYModelMapperPrivate::onModelColumnsAdded); |
244 | QObjectPrivate::disconnect(sender: d->m_model, |
245 | signal: &QAbstractItemModel::columnsRemoved, |
246 | receiverPrivate: d, |
247 | slot: &QXYModelMapperPrivate::onModelColumnsRemoved); |
248 | QObjectPrivate::disconnect(sender: d->m_model, |
249 | signal: &QAbstractItemModel::modelReset, |
250 | receiverPrivate: d, |
251 | slot: &QXYModelMapperPrivate::initializeXYFromModel); |
252 | QObjectPrivate::disconnect(sender: d->m_model, |
253 | signal: &QAbstractItemModel::layoutChanged, |
254 | receiverPrivate: d, |
255 | slot: &QXYModelMapperPrivate::initializeXYFromModel); |
256 | QObjectPrivate::disconnect(sender: d->m_model, |
257 | signal: &QAbstractItemModel::destroyed, |
258 | receiverPrivate: d, |
259 | slot: &QXYModelMapperPrivate::handleModelDestroyed); |
260 | } |
261 | |
262 | d->m_model = model; |
263 | d->initializeXYFromModel(); |
264 | // connect signals from the model |
265 | QObjectPrivate::connect(sender: d->m_model, |
266 | signal: &QAbstractItemModel::dataChanged, |
267 | receiverPrivate: d, |
268 | slot: &QXYModelMapperPrivate::onModelUpdated); |
269 | QObjectPrivate::connect(sender: d->m_model, |
270 | signal: &QAbstractItemModel::rowsInserted, |
271 | receiverPrivate: d, |
272 | slot: &QXYModelMapperPrivate::onModelRowsAdded); |
273 | QObjectPrivate::connect(sender: d->m_model, |
274 | signal: &QAbstractItemModel::rowsRemoved, |
275 | receiverPrivate: d, |
276 | slot: &QXYModelMapperPrivate::onModelRowsRemoved); |
277 | QObjectPrivate::connect(sender: d->m_model, |
278 | signal: &QAbstractItemModel::columnsInserted, |
279 | receiverPrivate: d, |
280 | slot: &QXYModelMapperPrivate::onModelColumnsAdded); |
281 | QObjectPrivate::connect(sender: d->m_model, |
282 | signal: &QAbstractItemModel::columnsRemoved, |
283 | receiverPrivate: d, |
284 | slot: &QXYModelMapperPrivate::onModelColumnsRemoved); |
285 | |
286 | QObjectPrivate::connect(sender: d->m_model, |
287 | signal: &QAbstractItemModel::modelReset, |
288 | receiverPrivate: d, |
289 | slot: &QXYModelMapperPrivate::initializeXYFromModel); |
290 | QObjectPrivate::connect(sender: d->m_model, |
291 | signal: &QAbstractItemModel::layoutChanged, |
292 | receiverPrivate: d, |
293 | slot: &QXYModelMapperPrivate::initializeXYFromModel); |
294 | QObjectPrivate::connect(sender: d->m_model, |
295 | signal: &QAbstractItemModel::destroyed, |
296 | receiverPrivate: d, |
297 | slot: &QXYModelMapperPrivate::handleModelDestroyed); |
298 | Q_EMIT modelChanged(); |
299 | } |
300 | |
301 | QXYSeries *QXYModelMapper::series() const |
302 | { |
303 | Q_D(const QXYModelMapper); |
304 | return d->m_series; |
305 | } |
306 | |
307 | void QXYModelMapper::setSeries(QXYSeries *series) |
308 | { |
309 | Q_D(QXYModelMapper); |
310 | if (d->m_series) { |
311 | QObjectPrivate::disconnect(sender: d->m_series, |
312 | signal: &QXYSeries::pointAdded, |
313 | receiverPrivate: d, |
314 | slot: &QXYModelMapperPrivate::onPointAdded); |
315 | QObjectPrivate::disconnect(sender: d->m_series, |
316 | signal: &QXYSeries::pointRemoved, |
317 | receiverPrivate: d, |
318 | slot: &QXYModelMapperPrivate::onPointRemoved); |
319 | QObjectPrivate::disconnect(sender: d->m_series, |
320 | signal: &QXYSeries::pointReplaced, |
321 | receiverPrivate: d, |
322 | slot: &QXYModelMapperPrivate::onPointReplaced); |
323 | QObjectPrivate::disconnect(sender: d->m_series, |
324 | signal: &QXYSeries::destroyed, |
325 | receiverPrivate: d, |
326 | slot: &QXYModelMapperPrivate::handleSeriesDestroyed); |
327 | QObjectPrivate::disconnect(sender: d->m_series, |
328 | signal: &QXYSeries::pointsRemoved, |
329 | receiverPrivate: d, |
330 | slot: &QXYModelMapperPrivate::onPointsRemoved); |
331 | } |
332 | |
333 | if (series == 0) |
334 | return; |
335 | |
336 | d->m_series = series; |
337 | d->initializeXYFromModel(); |
338 | // connect the signals from the series |
339 | QObjectPrivate::connect(sender: d->m_series, |
340 | signal: &QXYSeries::pointAdded, |
341 | receiverPrivate: d, |
342 | slot: &QXYModelMapperPrivate::onPointAdded); |
343 | QObjectPrivate::connect(sender: d->m_series, |
344 | signal: &QXYSeries::pointRemoved, |
345 | receiverPrivate: d, |
346 | slot: &QXYModelMapperPrivate::onPointRemoved); |
347 | QObjectPrivate::connect(sender: d->m_series, |
348 | signal: &QXYSeries::pointReplaced, |
349 | receiverPrivate: d, |
350 | slot: &QXYModelMapperPrivate::onPointReplaced); |
351 | QObjectPrivate::connect(sender: d->m_series, |
352 | signal: &QXYSeries::destroyed, |
353 | receiverPrivate: d, |
354 | slot: &QXYModelMapperPrivate::handleSeriesDestroyed); |
355 | QObjectPrivate::connect(sender: d->m_series, |
356 | signal: &QXYSeries::pointsRemoved, |
357 | receiverPrivate: d, |
358 | slot: &QXYModelMapperPrivate::onPointsRemoved); |
359 | Q_EMIT seriesChanged(); |
360 | } |
361 | |
362 | qsizetype QXYModelMapper::first() const |
363 | { |
364 | Q_D(const QXYModelMapper); |
365 | return d->m_first; |
366 | } |
367 | |
368 | void QXYModelMapper::setFirst(qsizetype first) |
369 | { |
370 | Q_D(QXYModelMapper); |
371 | d->m_first = qMax(a: first, b: 0); |
372 | d->initializeXYFromModel(); |
373 | Q_EMIT firstChanged(); |
374 | } |
375 | |
376 | qsizetype QXYModelMapper::count() const |
377 | { |
378 | Q_D(const QXYModelMapper); |
379 | return d->m_count; |
380 | } |
381 | |
382 | void QXYModelMapper::setCount(qsizetype count) |
383 | { |
384 | Q_D(QXYModelMapper); |
385 | d->m_count = qMax(a: count, b: -1); |
386 | d->initializeXYFromModel(); |
387 | Q_EMIT countChanged(); |
388 | } |
389 | |
390 | Qt::Orientation QXYModelMapper::orientation() const |
391 | { |
392 | Q_D(const QXYModelMapper); |
393 | return d->m_orientation; |
394 | } |
395 | |
396 | void QXYModelMapper::setOrientation(Qt::Orientation orientation) |
397 | { |
398 | Q_D(QXYModelMapper); |
399 | d->m_orientation = orientation; |
400 | d->initializeXYFromModel(); |
401 | Q_EMIT orientationChanged(); |
402 | } |
403 | |
404 | qsizetype QXYModelMapper::xSection() const |
405 | { |
406 | Q_D(const QXYModelMapper); |
407 | return d->m_xSection; |
408 | } |
409 | |
410 | void QXYModelMapper::setXSection(qsizetype xSection) |
411 | { |
412 | Q_D(QXYModelMapper); |
413 | d->m_xSection = qMax(a: -1, b: xSection); |
414 | d->initializeXYFromModel(); |
415 | Q_EMIT xSectionChanged(); |
416 | } |
417 | |
418 | qsizetype QXYModelMapper::ySection() const |
419 | { |
420 | Q_D(const QXYModelMapper); |
421 | return d->m_ySection; |
422 | } |
423 | |
424 | void QXYModelMapper::setYSection(qsizetype ySection) |
425 | { |
426 | Q_D(QXYModelMapper); |
427 | d->m_ySection = qMax(a: -1, b: ySection); |
428 | d->initializeXYFromModel(); |
429 | Q_EMIT ySectionChanged(); |
430 | } |
431 | |
432 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
433 | |
434 | QXYModelMapperPrivate::QXYModelMapperPrivate() {} |
435 | |
436 | QXYModelMapperPrivate::~QXYModelMapperPrivate() {} |
437 | |
438 | void QXYModelMapperPrivate::blockModelSignals(bool block) |
439 | { |
440 | m_modelSignalsBlock = block; |
441 | } |
442 | |
443 | void QXYModelMapperPrivate::blockSeriesSignals(bool block) |
444 | { |
445 | m_seriesSignalsBlock = block; |
446 | } |
447 | |
448 | QModelIndex QXYModelMapperPrivate::xModelIndex(qsizetype xIndex) |
449 | { |
450 | if (m_count != -1 && xIndex >= m_count) |
451 | return QModelIndex(); // invalid |
452 | |
453 | if (m_orientation == Qt::Vertical) |
454 | return m_model->index(row: int(xIndex) + m_first, column: m_xSection); |
455 | else |
456 | return m_model->index(row: m_xSection, column: int(xIndex) + m_first); |
457 | } |
458 | |
459 | QModelIndex QXYModelMapperPrivate::yModelIndex(qsizetype yIndex) |
460 | { |
461 | if (m_count != -1 && yIndex >= m_count) |
462 | return QModelIndex(); // invalid |
463 | |
464 | if (m_orientation == Qt::Vertical) |
465 | return m_model->index(row: int(yIndex) + m_first, column: m_ySection); |
466 | else |
467 | return m_model->index(row: m_ySection, column: int(yIndex) + m_first); |
468 | } |
469 | |
470 | qreal QXYModelMapperPrivate::valueFromModel(QModelIndex index) |
471 | { |
472 | QVariant value = m_model->data(index, role: Qt::DisplayRole); |
473 | switch (value.metaType().id()) { |
474 | case QMetaType::QDateTime: |
475 | return value.toDateTime().toMSecsSinceEpoch(); |
476 | case QMetaType::QDate: |
477 | return value.toDate().startOfDay().toMSecsSinceEpoch(); |
478 | default: |
479 | return value.toReal(); |
480 | } |
481 | } |
482 | |
483 | void QXYModelMapperPrivate::setValueToModel(QModelIndex index, qreal value) |
484 | { |
485 | QVariant oldValue = m_model->data(index, role: Qt::DisplayRole); |
486 | switch (oldValue.metaType().id()) { |
487 | case QMetaType::QDateTime: |
488 | m_model->setData(index, value: QDateTime::fromMSecsSinceEpoch(msecs: value)); |
489 | break; |
490 | case QMetaType::QDate: |
491 | m_model->setData(index, value: QDateTime::fromMSecsSinceEpoch(msecs: value).date()); |
492 | break; |
493 | default: |
494 | m_model->setData(index, value); |
495 | } |
496 | } |
497 | |
498 | void QXYModelMapperPrivate::onPointAdded(qsizetype pointIndex) |
499 | { |
500 | if (m_seriesSignalsBlock) |
501 | return; |
502 | |
503 | if (m_count != -1) |
504 | m_count += 1; |
505 | |
506 | blockModelSignals(); |
507 | if (m_orientation == Qt::Vertical) |
508 | m_model->insertRows(row: int(pointIndex) + m_first, count: 1); |
509 | else |
510 | m_model->insertColumns(column: int(pointIndex) + m_first, count: 1); |
511 | |
512 | setValueToModel(index: xModelIndex(xIndex: pointIndex), value: m_series->points().at(i: pointIndex).x()); |
513 | setValueToModel(index: yModelIndex(yIndex: pointIndex), value: m_series->points().at(i: pointIndex).y()); |
514 | blockModelSignals(block: false); |
515 | } |
516 | |
517 | void QXYModelMapperPrivate::onPointRemoved(qsizetype pointIndex) |
518 | { |
519 | if (m_seriesSignalsBlock) |
520 | return; |
521 | |
522 | if (m_count != -1) |
523 | m_count -= 1; |
524 | |
525 | blockModelSignals(); |
526 | if (m_orientation == Qt::Vertical) |
527 | m_model->removeRow(arow: int(pointIndex) + m_first); |
528 | else |
529 | m_model->removeColumn(acolumn: int(pointIndex) + m_first); |
530 | blockModelSignals(block: false); |
531 | } |
532 | |
533 | void QXYModelMapperPrivate::onPointsRemoved(qsizetype pointIndex, qsizetype count) |
534 | { |
535 | if (m_seriesSignalsBlock) |
536 | return; |
537 | |
538 | m_count -= count; |
539 | |
540 | if (m_count < -1) |
541 | m_count = -1; |
542 | |
543 | blockModelSignals(); |
544 | if (m_orientation == Qt::Vertical) |
545 | m_model->removeRows(row: int(pointIndex) + m_first, count: int(count)); |
546 | else |
547 | m_model->removeColumns(column: int(pointIndex) + m_first, count: int(count)); |
548 | blockModelSignals(block: false); |
549 | } |
550 | |
551 | void QXYModelMapperPrivate::onPointReplaced(qsizetype pointIndex) |
552 | { |
553 | if (m_seriesSignalsBlock) |
554 | return; |
555 | |
556 | blockModelSignals(); |
557 | setValueToModel(index: xModelIndex(xIndex: pointIndex), value: m_series->points().at(i: pointIndex).x()); |
558 | setValueToModel(index: yModelIndex(yIndex: pointIndex), value: m_series->points().at(i: pointIndex).y()); |
559 | blockModelSignals(block: false); |
560 | } |
561 | |
562 | void QXYModelMapperPrivate::handleSeriesDestroyed() |
563 | { |
564 | m_series = 0; |
565 | } |
566 | |
567 | void QXYModelMapperPrivate::onModelUpdated(QModelIndex topLeft, QModelIndex bottomRight) |
568 | { |
569 | if (m_model == 0 || m_series == 0) |
570 | return; |
571 | |
572 | if (m_modelSignalsBlock) |
573 | return; |
574 | |
575 | blockSeriesSignals(); |
576 | QModelIndex index; |
577 | QPointF newPoint; |
578 | int indexColumn = 0; |
579 | int indexRow = 0; |
580 | for (int row = topLeft.row(); row <= bottomRight.row(); row++) { |
581 | for (int column = topLeft.column(); column <= bottomRight.column(); column++) { |
582 | index = topLeft.sibling(arow: row, acolumn: column); |
583 | indexColumn = index.column(); |
584 | indexRow = index.row(); |
585 | if (m_orientation == Qt::Vertical |
586 | && (indexColumn == m_xSection || indexColumn == m_ySection)) { |
587 | if (indexRow >= m_first && (m_count == -1 || indexRow < m_first + m_count)) { |
588 | QModelIndex xIndex = xModelIndex(xIndex: indexRow - m_first); |
589 | QModelIndex yIndex = yModelIndex(yIndex: indexRow - m_first); |
590 | if (xIndex.isValid() && yIndex.isValid()) { |
591 | newPoint.setX(valueFromModel(index: xIndex)); |
592 | newPoint.setY(valueFromModel(index: yIndex)); |
593 | m_series->replace(index: indexRow - m_first, newPoint); |
594 | } |
595 | } |
596 | } else if (m_orientation == Qt::Horizontal |
597 | && (indexRow == m_xSection || indexRow == m_ySection)) { |
598 | if (indexColumn >= m_first && (m_count == -1 || indexColumn < m_first + m_count)) { |
599 | QModelIndex xIndex = xModelIndex(xIndex: indexColumn - m_first); |
600 | QModelIndex yIndex = yModelIndex(yIndex: indexColumn - m_first); |
601 | if (xIndex.isValid() && yIndex.isValid()) { |
602 | newPoint.setX(valueFromModel(index: xIndex)); |
603 | newPoint.setY(valueFromModel(index: yIndex)); |
604 | m_series->replace(index: indexColumn - m_first, newPoint); |
605 | } |
606 | } |
607 | } |
608 | } |
609 | } |
610 | blockSeriesSignals(block: false); |
611 | } |
612 | |
613 | void QXYModelMapperPrivate::onModelRowsAdded(QModelIndex parent, qsizetype start, qsizetype end) |
614 | { |
615 | Q_UNUSED(parent); |
616 | if (m_modelSignalsBlock) |
617 | return; |
618 | |
619 | blockSeriesSignals(); |
620 | if (m_orientation == Qt::Vertical) { |
621 | insertData(start, end); |
622 | } else if (start <= m_xSection || start <= m_ySection) { |
623 | // if the changes affect the map - reinitialize the xy |
624 | initializeXYFromModel(); |
625 | } |
626 | blockSeriesSignals(block: false); |
627 | } |
628 | |
629 | void QXYModelMapperPrivate::onModelRowsRemoved(QModelIndex parent, qsizetype start, qsizetype end) |
630 | { |
631 | Q_UNUSED(parent); |
632 | if (m_modelSignalsBlock) |
633 | return; |
634 | |
635 | blockSeriesSignals(); |
636 | if (m_orientation == Qt::Vertical) { |
637 | removeData(start, end); |
638 | } else if (start <= m_xSection || start <= m_ySection) { |
639 | // if the changes affect the map - reinitialize the xy |
640 | initializeXYFromModel(); |
641 | } |
642 | blockSeriesSignals(block: false); |
643 | } |
644 | |
645 | void QXYModelMapperPrivate::onModelColumnsAdded(QModelIndex parent, qsizetype start, qsizetype end) |
646 | { |
647 | Q_UNUSED(parent); |
648 | if (m_modelSignalsBlock) |
649 | return; |
650 | |
651 | blockSeriesSignals(); |
652 | if (m_orientation == Qt::Horizontal) { |
653 | insertData(start, end); |
654 | } else if (start <= m_xSection || start <= m_ySection) { |
655 | // if the changes affect the map - reinitialize the xy |
656 | initializeXYFromModel(); |
657 | } |
658 | blockSeriesSignals(block: false); |
659 | } |
660 | |
661 | void QXYModelMapperPrivate::onModelColumnsRemoved(QModelIndex parent, qsizetype start, qsizetype end) |
662 | { |
663 | Q_UNUSED(parent); |
664 | if (m_modelSignalsBlock) |
665 | return; |
666 | |
667 | blockSeriesSignals(); |
668 | if (m_orientation == Qt::Horizontal) { |
669 | removeData(start, end); |
670 | } else if (start <= m_xSection || start <= m_ySection) { |
671 | // if the changes affect the map - reinitialize the xy |
672 | initializeXYFromModel(); |
673 | } |
674 | blockSeriesSignals(block: false); |
675 | } |
676 | |
677 | void QXYModelMapperPrivate::handleModelDestroyed() |
678 | { |
679 | m_model = 0; |
680 | } |
681 | |
682 | void QXYModelMapperPrivate::insertData(int start, int end) |
683 | { |
684 | if (m_model == 0 || m_series == 0) |
685 | return; |
686 | |
687 | if (m_count != -1 && start >= m_first + m_count) { |
688 | return; |
689 | } else { |
690 | int addedCount = end - start + 1; |
691 | if (m_count != -1 && addedCount > m_count) |
692 | addedCount = m_count; |
693 | int first = qMax(a: start, b: m_first); |
694 | int last = qMin(a: first + addedCount - 1, |
695 | b: m_orientation == Qt::Vertical ? m_model->rowCount() - 1 |
696 | : m_model->columnCount() - 1); |
697 | for (int i = first; i <= last; i++) { |
698 | QPointF point; |
699 | QModelIndex xIndex = xModelIndex(xIndex: i - m_first); |
700 | QModelIndex yIndex = yModelIndex(yIndex: i - m_first); |
701 | if (xIndex.isValid() && yIndex.isValid()) { |
702 | point.setX(valueFromModel(index: xIndex)); |
703 | point.setY(valueFromModel(index: yIndex)); |
704 | m_series->insert(index: i - m_first, point); |
705 | } |
706 | } |
707 | |
708 | // remove excess of points (above m_count) |
709 | if (m_count != -1 && m_series->points().size() > m_count) { |
710 | for (qsizetype i = m_series->points().size() - 1; i >= m_count; i--) |
711 | m_series->remove(point: m_series->points().at(i)); |
712 | } |
713 | } |
714 | } |
715 | |
716 | void QXYModelMapperPrivate::removeData(int start, int end) |
717 | { |
718 | if (m_model == 0 || m_series == 0) |
719 | return; |
720 | |
721 | int removedCount = end - start + 1; |
722 | if (m_count != -1 && start >= m_first + m_count) { |
723 | return; |
724 | } else { |
725 | int toRemove = qMin(a: int(m_series->count()), |
726 | b: removedCount); // first find how many items can actually be removed |
727 | int first = qMax(a: start, b: m_first); // get the index of the first item that will be removed. |
728 | int last = qMin(a: first + toRemove - 1, |
729 | b: int(m_series->count()) + m_first |
730 | - 1); // get the index of the last item that will be removed. |
731 | for (int i = last; i >= first; i--) |
732 | m_series->remove(point: m_series->points().at(i: i - m_first)); |
733 | |
734 | if (m_count != -1) { |
735 | qsizetype itemsAvailable; // check how many are available to be added |
736 | if (m_orientation == Qt::Vertical) |
737 | itemsAvailable = m_model->rowCount() - m_first - m_series->count(); |
738 | else |
739 | itemsAvailable = m_model->columnCount() - m_first - m_series->count(); |
740 | int toBeAdded = qMin( |
741 | a: int(itemsAvailable), |
742 | b: m_count |
743 | - int(m_series->count())); // add not more items than there is space left to be filled. |
744 | qsizetype currentSize = m_series->count(); |
745 | if (toBeAdded > 0) { |
746 | for (qsizetype i = m_series->count(); i < currentSize + toBeAdded; i++) { |
747 | QPointF point; |
748 | QModelIndex xIndex = xModelIndex(xIndex: i); |
749 | QModelIndex yIndex = yModelIndex(yIndex: i); |
750 | if (xIndex.isValid() && yIndex.isValid()) { |
751 | point.setX(valueFromModel(index: xIndex)); |
752 | point.setY(valueFromModel(index: yIndex)); |
753 | m_series->insert(index: i, point); |
754 | } |
755 | } |
756 | } |
757 | } |
758 | } |
759 | } |
760 | |
761 | void QXYModelMapperPrivate::initializeXYFromModel() |
762 | { |
763 | if (m_model == 0 || m_series == 0) |
764 | return; |
765 | |
766 | blockSeriesSignals(); |
767 | // clear current content |
768 | m_series->clear(); |
769 | |
770 | // create the initial points set |
771 | int pointPos = 0; |
772 | QModelIndex xIndex = xModelIndex(xIndex: pointPos); |
773 | QModelIndex yIndex = yModelIndex(yIndex: pointPos); |
774 | |
775 | if (xIndex.isValid() && yIndex.isValid()) { |
776 | while (xIndex.isValid() && yIndex.isValid()) { |
777 | QPointF point; |
778 | point.setX(valueFromModel(index: xIndex)); |
779 | point.setY(valueFromModel(index: yIndex)); |
780 | m_series->append(point); |
781 | pointPos++; |
782 | xIndex = xModelIndex(xIndex: pointPos); |
783 | yIndex = yModelIndex(yIndex: pointPos); |
784 | // Don't warn about invalid index after the first, those are valid and used to |
785 | // determine when we should end looping. |
786 | } |
787 | } else { |
788 | // Invalid index right off the bat means series will be left empty, so output a warning, |
789 | // unless model is also empty |
790 | int count = m_orientation == Qt::Vertical ? m_model->rowCount() : m_model->columnCount(); |
791 | if (count > 0) { |
792 | if (!xIndex.isValid()) { |
793 | qWarning(msg: "%ls Invalid X coordinate index in model mapper.", |
794 | qUtf16Printable(QString::fromUtf8(__func__))); |
795 | } else if (!yIndex.isValid()) { |
796 | qWarning(msg: "%ls Invalid Y coordinate index in model mapper.", |
797 | qUtf16Printable(QString::fromUtf8(__func__))); |
798 | } |
799 | } |
800 | } |
801 | |
802 | blockSeriesSignals(block: false); |
803 | } |
804 | QT_END_NAMESPACE |
805 |
Definitions
- ~QXYModelMapper
- QXYModelMapper
- QXYModelMapper
- model
- setModel
- series
- setSeries
- first
- setFirst
- count
- setCount
- orientation
- setOrientation
- xSection
- setXSection
- ySection
- setYSection
- QXYModelMapperPrivate
- ~QXYModelMapperPrivate
- blockModelSignals
- blockSeriesSignals
- xModelIndex
- yModelIndex
- valueFromModel
- setValueToModel
- onPointAdded
- onPointRemoved
- onPointsRemoved
- onPointReplaced
- handleSeriesDestroyed
- onModelUpdated
- onModelRowsAdded
- onModelRowsRemoved
- onModelColumnsAdded
- onModelColumnsRemoved
- handleModelDestroyed
- insertData
- removeData
Learn to use CMake with our Intro Training
Find out more