File indexing completed on 2024-05-12 16:33:33

0001 /* This file is part of the KDE project
0002 
0003    Copyright 2008 Johannes Simon <johannes.simon@gmail.com>
0004    Copyright 2009 Inge Wallin    <inge@lysator.liu.se>
0005    Copyright (C) 2010 Carlos Licea <carlos@kdab.com>
0006    Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
0007      Contact: Suresh Chande suresh.chande@nokia.com
0008 
0009    This library is free software; you can redistribute it and/or
0010    modify it under the terms of the GNU Library General Public
0011    License as published by the Free Software Foundation; either
0012    version 2 of the License, or (at your option) any later version.
0013 
0014    This library is distributed in the hope that it will be useful,
0015    but WITHOUT ANY WARRANTY; without even the implied warranty of
0016    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0017    Library General Public License for more details.
0018 
0019    You should have received a copy of the GNU Library General Public License
0020    along with this library; see the file COPYING.LIB.  If not, write to
0021    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0022    Boston, MA 02110-1301, USA.
0023 */
0024 
0025 
0026 // Own
0027 #include "KChartModel.h"
0028 
0029 // KoChart
0030 #include "DataSet.h"
0031 #include "PlotArea.h"
0032 #include "ChartDebug.h"
0033 
0034 // KChart
0035 #include <KChartGlobal>
0036 #include <KChartPieAttributes>
0037 #include <KChartDataValueAttributes>
0038 
0039 using namespace KoChart;
0040 
0041 /**
0042  * Method for debugging purposes.
0043  */
0044 QString roleToString(int role)
0045 {
0046     switch (role) {
0047         case Qt::DisplayRole:
0048             return "Qt::DisplayRole";
0049         case KChart::DatasetBrushRole:
0050             return "KChart::DatasetBrushRole";
0051         case KChart::DatasetPenRole:
0052             return "KChart::DatasetPenRole";
0053         case KChart::PieAttributesRole:
0054             return "KChart::PieAttributesRole";
0055         case KChart::DataValueLabelAttributesRole:
0056             return "KChart::DataValueLabelAttributesRole";
0057         case KChart::ThreeDAttributesRole:
0058             return "KChart::ThreeDAttributesRole";
0059         case KChart::LineAttributesRole:
0060             return "KChart::LineAttributesRole";
0061         case KChart::ThreeDLineAttributesRole:
0062             return "KChart::ThreeDLineAttributesRole";
0063         case KChart::BarAttributesRole:
0064             return "KChart::BarAttributesRole";
0065         case KChart::StockBarAttributesRole:
0066             return "KChart::StockBarAttributesRole";
0067         case KChart::ThreeDBarAttributesRole:
0068             return "KChart::ThreeDBarAttributesRole";
0069         case KChart::ThreeDPieAttributesRole:
0070             return "KChart::ThreeDPieAttributesRole";
0071         case KChart::DataHiddenRole:
0072             return "KChart::DataHiddenRole";
0073         case KChart::ValueTrackerAttributesRole:
0074             return "KChart::ValueTrackerAttributesRole";
0075         case KChart::CommentRole:
0076             return "KChart::CommentRole";
0077     }
0078     return "Unknown DataRole";
0079 }
0080 
0081 class KChartModel::Private {
0082 public:
0083     Private(KChartModel *parent, PlotArea *plotArea);
0084     ~Private();
0085 
0086     KChartModel *const q;
0087     PlotArea *const plotArea;
0088 
0089     /**
0090      * Returns the index of a given dataSet. If it is not present in
0091      * this model (i.e. not attached, thus not in dataSets), an index
0092      * is returned before which this dataSet should be inserted.
0093      */
0094     int  dataSetIndex(DataSet *dataSet) const;
0095 
0096     /**
0097      * Returns the cached (!) max size of all data sets in this model.
0098      */
0099     int  maxDataSetSize() const;
0100 
0101     /**
0102      * Calculates the maximum size of all data sets in the passed list.
0103      */
0104     int  calcMaxDataSetSize(QList<DataSet*> list) const;
0105 
0106     /**
0107      * Only calculates the new size for the current data set list,
0108      * but does not update it.
0109      */
0110     int  calcMaxDataSetSize() const;
0111 
0112     /**
0113      * Returns the first model index a certain data point of a data occupies
0114      * in this model.
0115      *
0116      * Note that dataPointFirstModelIndex(..) == dataPointLastModelIndex(..)
0117      * in case we have only one data dimension.
0118      */
0119     QModelIndex dataPointFirstModelIndex(int dataSetNumber, int index);
0120 
0121     /**
0122      * Returns the last model index a certain data point of a data occupies
0123      * in this model.
0124      */
0125     QModelIndex dataPointLastModelIndex(int dataSetNumber, int index);
0126 
0127     bool isKnownDataRole(int role) const;
0128 
0129     int             dataDimensions;
0130     int             biggestDataSetSize;
0131     QList<DataSet*> dataSets;
0132 
0133     Qt::Orientation dataDirection;
0134 };
0135 
0136 
0137 KChartModel::Private::Private(KChartModel *parent, PlotArea *_plotArea)
0138     : q(parent)
0139     , plotArea(_plotArea)
0140 {
0141     dataDimensions      = 1;
0142     dataDirection       = Qt::Vertical;
0143     biggestDataSetSize  = 0;
0144 }
0145 
0146 KChartModel::Private::~Private()
0147 {
0148 }
0149 
0150 int KChartModel::Private::maxDataSetSize() const
0151 {
0152     return biggestDataSetSize;
0153 }
0154 
0155 int KChartModel::Private::calcMaxDataSetSize(QList<DataSet*> list) const
0156 {
0157     int maxSize = 0;
0158     foreach (DataSet *dataSet, list)
0159         maxSize = qMax(maxSize, dataSet->size());
0160     return maxSize;
0161 }
0162 
0163 int KChartModel::Private::calcMaxDataSetSize() const
0164 {
0165     return calcMaxDataSetSize(dataSets);
0166 }
0167 
0168 int KChartModel::Private::dataSetIndex(DataSet *dataSet) const
0169 {
0170     // If data set is not in our list yet, find out where to insert it
0171     if (!dataSets.contains(dataSet)) {
0172         for (int i = 0; i < dataSets.size(); i++) {
0173             if (dataSet->number() < dataSets[i]->number())
0174                 return i;
0175         }
0176 
0177         return dataSets.size();
0178     }
0179 
0180     // Otherwise simply return its index
0181     return dataSets.indexOf(dataSet);
0182 }
0183 
0184 QModelIndex KChartModel::Private::dataPointFirstModelIndex(int dataSetNumber, int index)
0185 {
0186     // Use the first row or column this data set occupies, assuming the data
0187     // direction is horizontal or vertical, respectively.
0188     int dataSetRowOrCol = dataSetNumber * dataDimensions;
0189     if (dataDirection == Qt::Vertical)
0190         return q->index(index, dataSetRowOrCol);
0191     return q->index(dataSetRowOrCol, index);
0192 }
0193 
0194 QModelIndex KChartModel::Private::dataPointLastModelIndex(int dataSetNumber, int index)
0195 {
0196     // Use the last row or column this data set occupies, assuming the data
0197     // direction is horizontal or vertical, respectively.
0198     int dataSetRowOrCol = (dataSetNumber + 1) * dataDimensions - 1;
0199     if (dataDirection == Qt::Vertical)
0200         return q->index(index, dataSetRowOrCol);
0201     return q->index(dataSetRowOrCol, index);
0202 }
0203 
0204 
0205 // ================================================================
0206 //                     class KChartModel
0207 
0208 
0209 KChartModel::KChartModel(PlotArea *plotArea, QObject *parent)
0210     : QAbstractItemModel(parent),
0211       d(new Private(this, plotArea))
0212 {
0213 }
0214 
0215 KChartModel::~KChartModel()
0216 {
0217     delete d;
0218 }
0219 
0220 
0221 void KChartModel::setDataDirection(Qt::Orientation direction)
0222 {
0223     d->dataDirection = direction;
0224 }
0225 
0226 Qt::Orientation KChartModel::dataDirection() const
0227 {
0228     return d->dataDirection;
0229 }
0230 
0231 Qt::Orientation KChartModel::categoryDirection() const
0232 {
0233     return d->dataDirection == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal;
0234 }
0235 
0236 QVariant KChartModel::data(const QModelIndex &index,
0237                             int role /* = Qt::DisplayRole */) const
0238 {
0239     if (!index.isValid() ||
0240          !d->isKnownDataRole(role)) {
0241         return QVariant();
0242     }
0243 //     if (!role == Qt::DisplayRole) {qInfo()<<Q_FUNC_INFO<<index.row()<<roleToString(role);}
0244     int dataSetNumber, section;
0245     // Offset from the data set's row or column (depending on the data direction).
0246     // With one data dimension, it's always 0. Otherwise it's 1 for y data, and
0247     // in all other cases 0 as well.
0248     // In other words, with one data dimension, there's only y data. With two data
0249     // dimensions, there's x and y data, and the x data comes before the y data,
0250     // each data set thus occupying two rows/columns.
0251     // With three data dimensions there will be
0252     // x, y and some kind of custom data. Like the size of a bubble in a bubble chart.
0253     int dataSection;
0254 
0255     if (dataDirection() == Qt::Horizontal) {
0256         dataSetNumber = index.row() / d->dataDimensions;
0257         dataSection = index.row() % d->dataDimensions;
0258         section = index.column();
0259     } else {
0260         dataSetNumber = index.column() / d->dataDimensions;
0261         dataSection = index.column() % d->dataDimensions;
0262         section = index.row();
0263     }
0264 
0265     if (dataSetNumber >= d->dataSets.size())
0266         return QVariant();
0267 
0268     DataSet *dataSet = d->dataSets[dataSetNumber];
0269     switch (role) {
0270     case Qt::DisplayRole:
0271         if (d->dataDimensions > 1 && dataSection == 0)
0272             return dataSet->xData(section);
0273         else if (d->dataDimensions > 2 && dataSection == 2)
0274             return dataSet->customData(section);
0275         else
0276             return dataSet->yData(section);
0277     case KChart::DatasetBrushRole:
0278         return dataSet->brush(section);
0279     case KChart::DatasetPenRole:
0280         return dataSet->pen(section);
0281     case KChart::PieAttributesRole:
0282         return QVariant::fromValue(dataSet->pieAttributes(section));
0283     case KChart::DataValueLabelAttributesRole:
0284         return QVariant::fromValue(dataSet->dataValueAttributes(section));
0285     }
0286 
0287     // TODO (Johannes): Support for third data dimension
0288     // We need to implement zData in Dataset first.
0289 
0290     Q_ASSERT("Error: Known data role wasn't handled.");
0291     return QVariant();
0292 }
0293 
0294 
0295 void KChartModel::dataSetChanged(DataSet *dataSet)
0296 {
0297     Q_ASSERT(d->dataSets.contains(dataSet));
0298     if (!d->dataSets.contains(dataSet))
0299         return;
0300 
0301     int dataSetNumber = d->dataSetIndex(dataSet);
0302 
0303     // Header data that belongs to this data set (e.g. label)
0304     int first = dataSetNumber * dataDimensions();
0305     int last = first + dataDimensions() - 1;
0306     emit headerDataChanged(dataDirection(), first, last);
0307 }
0308 
0309 void KChartModel::dataSetChanged(DataSet *dataSet, DataRole /*role*/,
0310                                   int first /* = -1 */, int last /* = -1 */)
0311 {
0312     Q_ASSERT(d->dataSets.contains(dataSet));
0313     if (!d->dataSets.contains(dataSet))
0314         return;
0315 
0316     const int lastIndex = d->biggestDataSetSize - 1;
0317     // be sure the 'first' and 'last' referenced rows are within our boundaries
0318     first = qMin(first, lastIndex);
0319     last = qMin(last, lastIndex);
0320     // 'first' defaults to -1, which means that all data points changed.
0321     if (first == -1) {
0322         first = 0;
0323         last = lastIndex;
0324     }
0325     // 'last' defaults to -1, which means only one column was changed.
0326     else if (last == -1)
0327         last = first;
0328     // 'first' can be negative either cause rowCount()==0 or cause it
0329     // still contains the default value of -1. In both cases we abort
0330     // and don't progress the update-request future. Same is true for
0331     // last which should at this point contain a valid row index too.
0332     if (first < 0 || last < 0)
0333         return;
0334     // be sure we are not dealing with inverse order
0335     if (last < first)
0336         qSwap(first, last);
0337 
0338     int dataSetNumber = d->dataSetIndex(dataSet);
0339     emit dataChanged(d->dataPointFirstModelIndex(dataSetNumber, first),
0340                      d->dataPointLastModelIndex(dataSetNumber, last));
0341 }
0342 
0343 void KChartModel::dataSetSizeChanged(DataSet *dataSet, int newSize)
0344 {
0345     Q_UNUSED(newSize);
0346 
0347     int dataSetIndex = d->dataSets.indexOf(dataSet);
0348     if (dataSetIndex < 0) {
0349         warnChart << "KChartModel::dataSetSizeChanged(): The data set is not assigned to this model.";
0350         return;
0351     }
0352 
0353     // Old max data set size is cached.
0354     const int oldMaxSize = d->maxDataSetSize();
0355     // Determine new max data set size (the size of dataSet has been changed already)
0356     const int newMaxSize = d->calcMaxDataSetSize();
0357 
0358     // Columns/rows have been added
0359     if (newMaxSize > oldMaxSize) {
0360         if (d->dataDirection == Qt::Horizontal)
0361             beginInsertColumns(QModelIndex(), oldMaxSize, newMaxSize - 1);
0362         else
0363             beginInsertRows(QModelIndex(), oldMaxSize, newMaxSize - 1);
0364 
0365         d->biggestDataSetSize = d->calcMaxDataSetSize();
0366 
0367         if (d->dataDirection == Qt::Horizontal)
0368             endInsertColumns();
0369         else
0370             endInsertRows();
0371         // Columns/rows have been removed
0372     } else if (newMaxSize < oldMaxSize) {
0373         if (d->dataDirection == Qt::Horizontal)
0374             beginRemoveColumns(QModelIndex(), newMaxSize, oldMaxSize - 1);
0375         else
0376             beginRemoveRows(QModelIndex(), newMaxSize, oldMaxSize - 1);
0377 
0378         d->biggestDataSetSize = d->calcMaxDataSetSize();
0379 
0380         if (d->dataDirection == Qt::Horizontal)
0381             endRemoveColumns();
0382         else
0383             endRemoveRows();
0384     }
0385 }
0386 
0387 void KChartModel::slotColumnsInserted(const QModelIndex& parent, int start, int end)
0388 {
0389     if (d->dataDirection == Qt::Horizontal) {
0390         beginInsertColumns(parent, start, end);
0391         endInsertColumns();
0392     } else {
0393         beginInsertRows(parent, start, end);
0394         endInsertRows();
0395     }
0396 }
0397 
0398 bool KChartModel::Private::isKnownDataRole(int role) const
0399 {
0400     switch (role) {
0401     case Qt::DisplayRole:
0402     case KChart::DatasetPenRole:
0403     case KChart::DatasetBrushRole:
0404     case KChart::PieAttributesRole:
0405     case KChart::DataValueLabelAttributesRole:
0406         return true;
0407     }
0408 
0409     return false;
0410 }
0411 
0412 QVariant KChartModel::headerData(int section,
0413                                   Qt::Orientation orientation,
0414                                   int role /* = Qt::DisplayRole */) const
0415 {
0416     if (!d->isKnownDataRole(role)) {
0417         return QVariant();
0418     }
0419 
0420     if (d->dataSets.isEmpty()) {
0421         warnChart << "KChartModel::headerData(): Attempting to request header, but model has no datasets assigned to it.";
0422         return QVariant();
0423     }
0424 
0425     if (orientation != d->dataDirection) {
0426         int dataSetNumber = section / d->dataDimensions;
0427         if (d->dataSets.count() <= dataSetNumber || dataSetNumber < 0) {
0428             warnChart << "KChartModel::headerData(): trying to get more datasets than we have.";
0429             return QVariant();
0430         }
0431 
0432         DataSet *dataSet  = d->dataSets[dataSetNumber];
0433 
0434         switch (role) {
0435         case Qt::DisplayRole:
0436             return dataSet->labelData();
0437         case KChart::DatasetBrushRole:
0438             return dataSet->brush();
0439         case KChart::DatasetPenRole:
0440             return dataSet->pen();
0441         case KChart::PieAttributesRole:
0442             return QVariant::fromValue(dataSet->pieAttributes());
0443         case KChart::DataValueLabelAttributesRole:
0444             return QVariant::fromValue(dataSet->dataValueAttributes());
0445         }
0446     }
0447     /* else if (orientation == d->dataDirection) { */
0448     // FIXME: Find a way to *not* use the first data set, but some method
0449     // to set category data (including category pen and brush) properly
0450     // (Setter methods in this class?)
0451     DataSet *dataSet = d->dataSets[0];
0452     switch (role) {
0453     case Qt::DisplayRole:
0454         return dataSet->categoryData(section);
0455     case KChart::DatasetBrushRole:
0456         return dataSet->brush(section);
0457     case KChart::DatasetPenRole:
0458         return dataSet->pen(section);
0459     case KChart::PieAttributesRole:
0460         return QVariant::fromValue(dataSet->pieAttributes(section));
0461     }
0462     /* } */
0463 
0464     // Do return something even though we should never get to this point.
0465     return QVariant();
0466 }
0467 
0468 QModelIndex KChartModel::index(int row, int column,
0469                                  const QModelIndex &parent) const
0470 {
0471     // Seems following can happen in which case we shouldn't return a
0472     // QModelIndex with an invalid position cause else other things
0473     // may go wrong.
0474     if(row >= rowCount(parent) || column >= columnCount(parent)) {
0475         return QModelIndex();
0476     }
0477 
0478     return createIndex(row, column, static_cast<quintptr>(0));
0479 }
0480 
0481 QModelIndex KChartModel::parent(const QModelIndex &index) const
0482 {
0483     Q_UNUSED(index);
0484 
0485     return QModelIndex();
0486 }
0487 
0488 int KChartModel::rowCount(const QModelIndex &parent /* = QModelIndex() */) const
0489 {
0490     Q_UNUSED(parent);
0491 
0492     int rows;
0493     if (d->dataDirection == Qt::Vertical)
0494         rows = d->maxDataSetSize();
0495     else
0496         rows = d->dataSets.size() * d->dataDimensions;
0497     return rows;
0498 }
0499 
0500 int KChartModel::columnCount(const QModelIndex &parent /* = QModelIndex() */) const
0501 {
0502     Q_UNUSED(parent);
0503 
0504     int columns;
0505     if (d->dataDirection == Qt::Vertical) {
0506         columns = d->dataSets.size() * d->dataDimensions;
0507     }
0508     else
0509         columns = d->maxDataSetSize();
0510 
0511     return columns;
0512 }
0513 
0514 void KChartModel::setDataDimensions(int dataDimensions)
0515 {
0516     d->dataDimensions = dataDimensions;
0517 }
0518 
0519 int KChartModel::dataDimensions() const
0520 {
0521     return d->dataDimensions;
0522 }
0523 
0524 void KChartModel::addDataSet(DataSet *dataSet)
0525 {
0526     if (d->dataSets.contains(dataSet)) {
0527         warnChart << "KChartModel::addDataSet(): Attempting to insert already-contained data set";
0528         return;
0529     }
0530     dataSet->setKdChartModel(this);
0531 
0532     int dataSetIndex = d->dataSetIndex(dataSet);
0533 
0534     if (!d->dataSets.isEmpty()) {
0535         const int columnAboutToBeInserted = dataSetIndex * d->dataDimensions;
0536         if (d->dataDirection == Qt::Vertical) {
0537             beginInsertColumns(QModelIndex(), columnAboutToBeInserted,
0538                                 columnAboutToBeInserted + d->dataDimensions - 1);
0539         }
0540         else
0541             beginInsertRows(QModelIndex(), columnAboutToBeInserted,
0542                              columnAboutToBeInserted + d->dataDimensions - 1);
0543         d->dataSets.insert(dataSetIndex, dataSet);
0544 
0545         if (d->dataDirection == Qt::Vertical)
0546             endInsertColumns();
0547         else
0548             endInsertRows();
0549 
0550         const int dataSetSize = dataSet->size();
0551         if (dataSetSize > d->maxDataSetSize()) {
0552             if (d->dataDirection == Qt::Vertical)
0553                 beginInsertRows(QModelIndex(),
0554                                  d->maxDataSetSize(), dataSetSize - 1);
0555             else
0556                 beginInsertColumns(QModelIndex(),
0557                                     d->maxDataSetSize(), dataSetSize - 1);
0558             d->biggestDataSetSize = d->calcMaxDataSetSize();
0559             if (d->dataDirection == Qt::Vertical)
0560                 endInsertRows();
0561             else
0562                 endInsertColumns();
0563         }
0564     }
0565     else {
0566         // If we had no datasets before, we haven't had a valid
0567         // structure yet.  Thus, emit the modelReset() signal.
0568         beginResetModel();
0569         d->dataSets.append(dataSet);
0570         d->biggestDataSetSize = d->calcMaxDataSetSize();
0571         endResetModel();
0572     }
0573 }
0574 
0575 void KChartModel::removeDataSet(DataSet *dataSet, bool silent)
0576 {
0577     const int dataSetIndex = d->dataSets.indexOf(dataSet);
0578     if (dataSetIndex < 0)
0579         return;
0580 
0581     if (silent) {
0582         d->dataSets.removeAt(dataSetIndex);
0583         d->biggestDataSetSize = d->calcMaxDataSetSize();
0584     }
0585     else {
0586         // Simulate removing this dataSet without actually doing so
0587         // in order to calculate new max data set size
0588         QList<DataSet*> _dataSets(d->dataSets);
0589         _dataSets.removeAll(dataSet);
0590         // Cached size
0591         int oldMaxDataSetSize = d->maxDataSetSize();
0592         // Max size for new list
0593         int newMaxDataSetSize = d->calcMaxDataSetSize(_dataSets);
0594 
0595         if (newMaxDataSetSize < oldMaxDataSetSize) {
0596             if (d->dataDirection == Qt::Horizontal) {
0597                 beginRemoveColumns(QModelIndex(), newMaxDataSetSize, oldMaxDataSetSize - 1);
0598             } else {
0599                 beginRemoveRows(QModelIndex(), newMaxDataSetSize, oldMaxDataSetSize - 1);
0600             }
0601             d->dataSets = _dataSets;
0602             d->biggestDataSetSize = newMaxDataSetSize;
0603             if (d->dataDirection == Qt::Horizontal) {
0604                 endRemoveColumns();
0605             } else {
0606                 endRemoveRows();
0607             }
0608         }
0609         // If the dataSet has been removed above we cannot remove it again.
0610         // This should only happen when the last dataSet is removed
0611         if (d->dataSets.contains(dataSet)) {
0612             int first = dataSetIndex * d->dataDimensions;
0613             int last = first + d->dataDimensions - 1;
0614             if (d->dataDirection == Qt::Horizontal) {
0615                 beginRemoveRows(QModelIndex(), first, last);
0616             } else {
0617                 beginRemoveColumns(QModelIndex(), first, last);
0618             }
0619             d->dataSets.removeAt(dataSetIndex);
0620             if (d->dataDirection == Qt::Horizontal) {
0621                 endRemoveRows();
0622             } else {
0623                 endRemoveColumns();
0624             }
0625         } else {
0626             Q_ASSERT(d->dataSets.count() == 0);
0627             // tell consumers that there is nothing left
0628             beginResetModel();
0629             endResetModel();
0630         }
0631     }
0632 }
0633 
0634 QList<DataSet*> KChartModel::dataSets() const
0635 {
0636     return d->dataSets;
0637 }