1 /* This file is part of the KDE project
2
3 Copyright 2008 Johannes Simon <johannes.simon@gmail.com>
4 Copyright 2009 Inge Wallin <inge@lysator.liu.se>
5 Copyright (C) 2010 Carlos Licea <carlos@kdab.com>
6 Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
7 Contact: Suresh Chande suresh.chande@nokia.com
8
9 This library is free software; you can redistribute it and/or
10 modify it under the terms of the GNU Library General Public
11 License as published by the Free Software Foundation; either
12 version 2 of the License, or (at your option) any later version.
13
14 This library is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Library General Public License for more details.
18
19 You should have received a copy of the GNU Library General Public License
20 along with this library; see the file COPYING.LIB. If not, write to
21 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 Boston, MA 02110-1301, USA.
23 */
24
25
26 // Own
27 #include "KChartModel.h"
28
29 // KoChart
30 #include "DataSet.h"
31 #include "PlotArea.h"
32 #include "ChartDebug.h"
33
34 // KChart
35 #include <KChartGlobal>
36 #include <KChartPieAttributes>
37 #include <KChartDataValueAttributes>
38
39 using namespace KoChart;
40
41 /**
42 * Method for debugging purposes.
43 */
roleToString(int role)44 QString roleToString(int role)
45 {
46 switch (role) {
47 case Qt::DisplayRole:
48 return "Qt::DisplayRole";
49 case KChart::DatasetBrushRole:
50 return "KChart::DatasetBrushRole";
51 case KChart::DatasetPenRole:
52 return "KChart::DatasetPenRole";
53 case KChart::PieAttributesRole:
54 return "KChart::PieAttributesRole";
55 case KChart::DataValueLabelAttributesRole:
56 return "KChart::DataValueLabelAttributesRole";
57 case KChart::ThreeDAttributesRole:
58 return "KChart::ThreeDAttributesRole";
59 case KChart::LineAttributesRole:
60 return "KChart::LineAttributesRole";
61 case KChart::ThreeDLineAttributesRole:
62 return "KChart::ThreeDLineAttributesRole";
63 case KChart::BarAttributesRole:
64 return "KChart::BarAttributesRole";
65 case KChart::StockBarAttributesRole:
66 return "KChart::StockBarAttributesRole";
67 case KChart::ThreeDBarAttributesRole:
68 return "KChart::ThreeDBarAttributesRole";
69 case KChart::ThreeDPieAttributesRole:
70 return "KChart::ThreeDPieAttributesRole";
71 case KChart::DataHiddenRole:
72 return "KChart::DataHiddenRole";
73 case KChart::ValueTrackerAttributesRole:
74 return "KChart::ValueTrackerAttributesRole";
75 case KChart::CommentRole:
76 return "KChart::CommentRole";
77 }
78 return "Unknown DataRole";
79 }
80
81 class KChartModel::Private {
82 public:
83 Private(KChartModel *parent, PlotArea *plotArea);
84 ~Private();
85
86 KChartModel *const q;
87 PlotArea *const plotArea;
88
89 /**
90 * Returns the index of a given dataSet. If it is not present in
91 * this model (i.e. not attached, thus not in dataSets), an index
92 * is returned before which this dataSet should be inserted.
93 */
94 int dataSetIndex(DataSet *dataSet) const;
95
96 /**
97 * Returns the cached (!) max size of all data sets in this model.
98 */
99 int maxDataSetSize() const;
100
101 /**
102 * Calculates the maximum size of all data sets in the passed list.
103 */
104 int calcMaxDataSetSize(QList<DataSet*> list) const;
105
106 /**
107 * Only calculates the new size for the current data set list,
108 * but does not update it.
109 */
110 int calcMaxDataSetSize() const;
111
112 /**
113 * Returns the first model index a certain data point of a data occupies
114 * in this model.
115 *
116 * Note that dataPointFirstModelIndex(..) == dataPointLastModelIndex(..)
117 * in case we have only one data dimension.
118 */
119 QModelIndex dataPointFirstModelIndex(int dataSetNumber, int index);
120
121 /**
122 * Returns the last model index a certain data point of a data occupies
123 * in this model.
124 */
125 QModelIndex dataPointLastModelIndex(int dataSetNumber, int index);
126
127 bool isKnownDataRole(int role) const;
128
129 int dataDimensions;
130 int biggestDataSetSize;
131 QList<DataSet*> dataSets;
132
133 Qt::Orientation dataDirection;
134 };
135
136
Private(KChartModel * parent,PlotArea * _plotArea)137 KChartModel::Private::Private(KChartModel *parent, PlotArea *_plotArea)
138 : q(parent)
139 , plotArea(_plotArea)
140 {
141 dataDimensions = 1;
142 dataDirection = Qt::Vertical;
143 biggestDataSetSize = 0;
144 }
145
~Private()146 KChartModel::Private::~Private()
147 {
148 }
149
maxDataSetSize() const150 int KChartModel::Private::maxDataSetSize() const
151 {
152 return biggestDataSetSize;
153 }
154
calcMaxDataSetSize(QList<DataSet * > list) const155 int KChartModel::Private::calcMaxDataSetSize(QList<DataSet*> list) const
156 {
157 int maxSize = 0;
158 foreach (DataSet *dataSet, list)
159 maxSize = qMax(maxSize, dataSet->size());
160 return maxSize;
161 }
162
calcMaxDataSetSize() const163 int KChartModel::Private::calcMaxDataSetSize() const
164 {
165 return calcMaxDataSetSize(dataSets);
166 }
167
dataSetIndex(DataSet * dataSet) const168 int KChartModel::Private::dataSetIndex(DataSet *dataSet) const
169 {
170 // If data set is not in our list yet, find out where to insert it
171 if (!dataSets.contains(dataSet)) {
172 for (int i = 0; i < dataSets.size(); i++) {
173 if (dataSet->number() < dataSets[i]->number())
174 return i;
175 }
176
177 return dataSets.size();
178 }
179
180 // Otherwise simply return its index
181 return dataSets.indexOf(dataSet);
182 }
183
dataPointFirstModelIndex(int dataSetNumber,int index)184 QModelIndex KChartModel::Private::dataPointFirstModelIndex(int dataSetNumber, int index)
185 {
186 // Use the first row or column this data set occupies, assuming the data
187 // direction is horizontal or vertical, respectively.
188 int dataSetRowOrCol = dataSetNumber * dataDimensions;
189 if (dataDirection == Qt::Vertical)
190 return q->index(index, dataSetRowOrCol);
191 return q->index(dataSetRowOrCol, index);
192 }
193
dataPointLastModelIndex(int dataSetNumber,int index)194 QModelIndex KChartModel::Private::dataPointLastModelIndex(int dataSetNumber, int index)
195 {
196 // Use the last row or column this data set occupies, assuming the data
197 // direction is horizontal or vertical, respectively.
198 int dataSetRowOrCol = (dataSetNumber + 1) * dataDimensions - 1;
199 if (dataDirection == Qt::Vertical)
200 return q->index(index, dataSetRowOrCol);
201 return q->index(dataSetRowOrCol, index);
202 }
203
204
205 // ================================================================
206 // class KChartModel
207
208
KChartModel(PlotArea * plotArea,QObject * parent)209 KChartModel::KChartModel(PlotArea *plotArea, QObject *parent)
210 : QAbstractItemModel(parent),
211 d(new Private(this, plotArea))
212 {
213 }
214
~KChartModel()215 KChartModel::~KChartModel()
216 {
217 delete d;
218 }
219
220
setDataDirection(Qt::Orientation direction)221 void KChartModel::setDataDirection(Qt::Orientation direction)
222 {
223 d->dataDirection = direction;
224 }
225
dataDirection() const226 Qt::Orientation KChartModel::dataDirection() const
227 {
228 return d->dataDirection;
229 }
230
categoryDirection() const231 Qt::Orientation KChartModel::categoryDirection() const
232 {
233 return d->dataDirection == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal;
234 }
235
data(const QModelIndex & index,int role) const236 QVariant KChartModel::data(const QModelIndex &index,
237 int role /* = Qt::DisplayRole */) const
238 {
239 if (!index.isValid() ||
240 !d->isKnownDataRole(role)) {
241 return QVariant();
242 }
243 // if (!role == Qt::DisplayRole) {qInfo()<<Q_FUNC_INFO<<index.row()<<roleToString(role);}
244 int dataSetNumber, section;
245 // Offset from the data set's row or column (depending on the data direction).
246 // With one data dimension, it's always 0. Otherwise it's 1 for y data, and
247 // in all other cases 0 as well.
248 // In other words, with one data dimension, there's only y data. With two data
249 // dimensions, there's x and y data, and the x data comes before the y data,
250 // each data set thus occupying two rows/columns.
251 // With three data dimensions there will be
252 // x, y and some kind of custom data. Like the size of a bubble in a bubble chart.
253 int dataSection;
254
255 if (dataDirection() == Qt::Horizontal) {
256 dataSetNumber = index.row() / d->dataDimensions;
257 dataSection = index.row() % d->dataDimensions;
258 section = index.column();
259 } else {
260 dataSetNumber = index.column() / d->dataDimensions;
261 dataSection = index.column() % d->dataDimensions;
262 section = index.row();
263 }
264
265 if (dataSetNumber >= d->dataSets.size())
266 return QVariant();
267
268 DataSet *dataSet = d->dataSets[dataSetNumber];
269 switch (role) {
270 case Qt::DisplayRole:
271 if (d->dataDimensions > 1 && dataSection == 0)
272 return dataSet->xData(section);
273 else if (d->dataDimensions > 2 && dataSection == 2)
274 return dataSet->customData(section);
275 else
276 return dataSet->yData(section);
277 case KChart::DatasetBrushRole:
278 return dataSet->brush(section);
279 case KChart::DatasetPenRole:
280 return dataSet->pen(section);
281 case KChart::PieAttributesRole:
282 return QVariant::fromValue(dataSet->pieAttributes(section));
283 case KChart::DataValueLabelAttributesRole:
284 return QVariant::fromValue(dataSet->dataValueAttributes(section));
285 }
286
287 // TODO (Johannes): Support for third data dimension
288 // We need to implement zData in Dataset first.
289
290 Q_ASSERT("Error: Known data role wasn't handled.");
291 return QVariant();
292 }
293
294
dataSetChanged(DataSet * dataSet)295 void KChartModel::dataSetChanged(DataSet *dataSet)
296 {
297 Q_ASSERT(d->dataSets.contains(dataSet));
298 if (!d->dataSets.contains(dataSet))
299 return;
300
301 int dataSetNumber = d->dataSetIndex(dataSet);
302
303 // Header data that belongs to this data set (e.g. label)
304 int first = dataSetNumber * dataDimensions();
305 int last = first + dataDimensions() - 1;
306 emit headerDataChanged(dataDirection(), first, last);
307 }
308
dataSetChanged(DataSet * dataSet,DataRole,int first,int last)309 void KChartModel::dataSetChanged(DataSet *dataSet, DataRole /*role*/,
310 int first /* = -1 */, int last /* = -1 */)
311 {
312 Q_ASSERT(d->dataSets.contains(dataSet));
313 if (!d->dataSets.contains(dataSet))
314 return;
315
316 const int lastIndex = d->biggestDataSetSize - 1;
317 // be sure the 'first' and 'last' referenced rows are within our boundaries
318 first = qMin(first, lastIndex);
319 last = qMin(last, lastIndex);
320 // 'first' defaults to -1, which means that all data points changed.
321 if (first == -1) {
322 first = 0;
323 last = lastIndex;
324 }
325 // 'last' defaults to -1, which means only one column was changed.
326 else if (last == -1)
327 last = first;
328 // 'first' can be negative either cause rowCount()==0 or cause it
329 // still contains the default value of -1. In both cases we abort
330 // and don't progress the update-request future. Same is true for
331 // last which should at this point contain a valid row index too.
332 if (first < 0 || last < 0)
333 return;
334 // be sure we are not dealing with inverse order
335 if (last < first)
336 qSwap(first, last);
337
338 int dataSetNumber = d->dataSetIndex(dataSet);
339 emit dataChanged(d->dataPointFirstModelIndex(dataSetNumber, first),
340 d->dataPointLastModelIndex(dataSetNumber, last));
341 }
342
dataSetSizeChanged(DataSet * dataSet,int newSize)343 void KChartModel::dataSetSizeChanged(DataSet *dataSet, int newSize)
344 {
345 Q_UNUSED(newSize);
346
347 int dataSetIndex = d->dataSets.indexOf(dataSet);
348 if (dataSetIndex < 0) {
349 warnChart << "KChartModel::dataSetSizeChanged(): The data set is not assigned to this model.";
350 return;
351 }
352
353 // Old max data set size is cached.
354 const int oldMaxSize = d->maxDataSetSize();
355 // Determine new max data set size (the size of dataSet has been changed already)
356 const int newMaxSize = d->calcMaxDataSetSize();
357
358 // Columns/rows have been added
359 if (newMaxSize > oldMaxSize) {
360 if (d->dataDirection == Qt::Horizontal)
361 beginInsertColumns(QModelIndex(), oldMaxSize, newMaxSize - 1);
362 else
363 beginInsertRows(QModelIndex(), oldMaxSize, newMaxSize - 1);
364
365 d->biggestDataSetSize = d->calcMaxDataSetSize();
366
367 if (d->dataDirection == Qt::Horizontal)
368 endInsertColumns();
369 else
370 endInsertRows();
371 // Columns/rows have been removed
372 } else if (newMaxSize < oldMaxSize) {
373 if (d->dataDirection == Qt::Horizontal)
374 beginRemoveColumns(QModelIndex(), newMaxSize, oldMaxSize - 1);
375 else
376 beginRemoveRows(QModelIndex(), newMaxSize, oldMaxSize - 1);
377
378 d->biggestDataSetSize = d->calcMaxDataSetSize();
379
380 if (d->dataDirection == Qt::Horizontal)
381 endRemoveColumns();
382 else
383 endRemoveRows();
384 }
385 }
386
slotColumnsInserted(const QModelIndex & parent,int start,int end)387 void KChartModel::slotColumnsInserted(const QModelIndex& parent, int start, int end)
388 {
389 if (d->dataDirection == Qt::Horizontal) {
390 beginInsertColumns(parent, start, end);
391 endInsertColumns();
392 } else {
393 beginInsertRows(parent, start, end);
394 endInsertRows();
395 }
396 }
397
isKnownDataRole(int role) const398 bool KChartModel::Private::isKnownDataRole(int role) const
399 {
400 switch (role) {
401 case Qt::DisplayRole:
402 case KChart::DatasetPenRole:
403 case KChart::DatasetBrushRole:
404 case KChart::PieAttributesRole:
405 case KChart::DataValueLabelAttributesRole:
406 return true;
407 }
408
409 return false;
410 }
411
headerData(int section,Qt::Orientation orientation,int role) const412 QVariant KChartModel::headerData(int section,
413 Qt::Orientation orientation,
414 int role /* = Qt::DisplayRole */) const
415 {
416 if (!d->isKnownDataRole(role)) {
417 return QVariant();
418 }
419
420 if (d->dataSets.isEmpty()) {
421 warnChart << "KChartModel::headerData(): Attempting to request header, but model has no datasets assigned to it.";
422 return QVariant();
423 }
424
425 if (orientation != d->dataDirection) {
426 int dataSetNumber = section / d->dataDimensions;
427 if (d->dataSets.count() <= dataSetNumber || dataSetNumber < 0) {
428 warnChart << "KChartModel::headerData(): trying to get more datasets than we have.";
429 return QVariant();
430 }
431
432 DataSet *dataSet = d->dataSets[dataSetNumber];
433
434 switch (role) {
435 case Qt::DisplayRole:
436 return dataSet->labelData();
437 case KChart::DatasetBrushRole:
438 return dataSet->brush();
439 case KChart::DatasetPenRole:
440 return dataSet->pen();
441 case KChart::PieAttributesRole:
442 return QVariant::fromValue(dataSet->pieAttributes());
443 case KChart::DataValueLabelAttributesRole:
444 return QVariant::fromValue(dataSet->dataValueAttributes());
445 }
446 }
447 /* else if (orientation == d->dataDirection) { */
448 // FIXME: Find a way to *not* use the first data set, but some method
449 // to set category data (including category pen and brush) properly
450 // (Setter methods in this class?)
451 DataSet *dataSet = d->dataSets[0];
452 switch (role) {
453 case Qt::DisplayRole:
454 return dataSet->categoryData(section);
455 case KChart::DatasetBrushRole:
456 return dataSet->brush(section);
457 case KChart::DatasetPenRole:
458 return dataSet->pen(section);
459 case KChart::PieAttributesRole:
460 return QVariant::fromValue(dataSet->pieAttributes(section));
461 }
462 /* } */
463
464 // Do return something even though we should never get to this point.
465 return QVariant();
466 }
467
index(int row,int column,const QModelIndex & parent) const468 QModelIndex KChartModel::index(int row, int column,
469 const QModelIndex &parent) const
470 {
471 // Seems following can happen in which case we shouldn't return a
472 // QModelIndex with an invalid position cause else other things
473 // may go wrong.
474 if(row >= rowCount(parent) || column >= columnCount(parent)) {
475 return QModelIndex();
476 }
477
478 return createIndex(row, column, static_cast<quintptr>(0));
479 }
480
parent(const QModelIndex & index) const481 QModelIndex KChartModel::parent(const QModelIndex &index) const
482 {
483 Q_UNUSED(index);
484
485 return QModelIndex();
486 }
487
rowCount(const QModelIndex & parent) const488 int KChartModel::rowCount(const QModelIndex &parent /* = QModelIndex() */) const
489 {
490 Q_UNUSED(parent);
491
492 int rows;
493 if (d->dataDirection == Qt::Vertical)
494 rows = d->maxDataSetSize();
495 else
496 rows = d->dataSets.size() * d->dataDimensions;
497 return rows;
498 }
499
columnCount(const QModelIndex & parent) const500 int KChartModel::columnCount(const QModelIndex &parent /* = QModelIndex() */) const
501 {
502 Q_UNUSED(parent);
503
504 int columns;
505 if (d->dataDirection == Qt::Vertical) {
506 columns = d->dataSets.size() * d->dataDimensions;
507 }
508 else
509 columns = d->maxDataSetSize();
510
511 return columns;
512 }
513
setDataDimensions(int dataDimensions)514 void KChartModel::setDataDimensions(int dataDimensions)
515 {
516 d->dataDimensions = dataDimensions;
517 }
518
dataDimensions() const519 int KChartModel::dataDimensions() const
520 {
521 return d->dataDimensions;
522 }
523
addDataSet(DataSet * dataSet)524 void KChartModel::addDataSet(DataSet *dataSet)
525 {
526 if (d->dataSets.contains(dataSet)) {
527 warnChart << "KChartModel::addDataSet(): Attempting to insert already-contained data set";
528 return;
529 }
530 dataSet->setKdChartModel(this);
531
532 int dataSetIndex = d->dataSetIndex(dataSet);
533
534 if (!d->dataSets.isEmpty()) {
535 const int columnAboutToBeInserted = dataSetIndex * d->dataDimensions;
536 if (d->dataDirection == Qt::Vertical) {
537 beginInsertColumns(QModelIndex(), columnAboutToBeInserted,
538 columnAboutToBeInserted + d->dataDimensions - 1);
539 }
540 else
541 beginInsertRows(QModelIndex(), columnAboutToBeInserted,
542 columnAboutToBeInserted + d->dataDimensions - 1);
543 d->dataSets.insert(dataSetIndex, dataSet);
544
545 if (d->dataDirection == Qt::Vertical)
546 endInsertColumns();
547 else
548 endInsertRows();
549
550 const int dataSetSize = dataSet->size();
551 if (dataSetSize > d->maxDataSetSize()) {
552 if (d->dataDirection == Qt::Vertical)
553 beginInsertRows(QModelIndex(),
554 d->maxDataSetSize(), dataSetSize - 1);
555 else
556 beginInsertColumns(QModelIndex(),
557 d->maxDataSetSize(), dataSetSize - 1);
558 d->biggestDataSetSize = d->calcMaxDataSetSize();
559 if (d->dataDirection == Qt::Vertical)
560 endInsertRows();
561 else
562 endInsertColumns();
563 }
564 }
565 else {
566 // If we had no datasets before, we haven't had a valid
567 // structure yet. Thus, emit the modelReset() signal.
568 beginResetModel();
569 d->dataSets.append(dataSet);
570 d->biggestDataSetSize = d->calcMaxDataSetSize();
571 endResetModel();
572 }
573 }
574
removeDataSet(DataSet * dataSet,bool silent)575 void KChartModel::removeDataSet(DataSet *dataSet, bool silent)
576 {
577 const int dataSetIndex = d->dataSets.indexOf(dataSet);
578 if (dataSetIndex < 0)
579 return;
580
581 if (silent) {
582 d->dataSets.removeAt(dataSetIndex);
583 d->biggestDataSetSize = d->calcMaxDataSetSize();
584 }
585 else {
586 // Simulate removing this dataSet without actually doing so
587 // in order to calculate new max data set size
588 QList<DataSet*> _dataSets(d->dataSets);
589 _dataSets.removeAll(dataSet);
590 // Cached size
591 int oldMaxDataSetSize = d->maxDataSetSize();
592 // Max size for new list
593 int newMaxDataSetSize = d->calcMaxDataSetSize(_dataSets);
594
595 if (newMaxDataSetSize < oldMaxDataSetSize) {
596 if (d->dataDirection == Qt::Horizontal) {
597 beginRemoveColumns(QModelIndex(), newMaxDataSetSize, oldMaxDataSetSize - 1);
598 } else {
599 beginRemoveRows(QModelIndex(), newMaxDataSetSize, oldMaxDataSetSize - 1);
600 }
601 d->dataSets = _dataSets;
602 d->biggestDataSetSize = newMaxDataSetSize;
603 if (d->dataDirection == Qt::Horizontal) {
604 endRemoveColumns();
605 } else {
606 endRemoveRows();
607 }
608 }
609 // If the dataSet has been removed above we cannot remove it again.
610 // This should only happen when the last dataSet is removed
611 if (d->dataSets.contains(dataSet)) {
612 int first = dataSetIndex * d->dataDimensions;
613 int last = first + d->dataDimensions - 1;
614 if (d->dataDirection == Qt::Horizontal) {
615 beginRemoveRows(QModelIndex(), first, last);
616 } else {
617 beginRemoveColumns(QModelIndex(), first, last);
618 }
619 d->dataSets.removeAt(dataSetIndex);
620 if (d->dataDirection == Qt::Horizontal) {
621 endRemoveRows();
622 } else {
623 endRemoveColumns();
624 }
625 } else {
626 Q_ASSERT(d->dataSets.count() == 0);
627 // tell consumers that there is nothing left
628 beginResetModel();
629 endResetModel();
630 }
631 }
632 }
633
dataSets() const634 QList<DataSet*> KChartModel::dataSets() const
635 {
636 return d->dataSets;
637 }
638