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