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 }