File indexing completed on 2024-05-12 16:33:28
0001 /* This file is part of the KDE project 0002 0003 Copyright 2007 Johannes Simon <johannes.simon@gmail.com> 0004 Copyright 2009 Inge Wallin <inge@lysator.liu.se> 0005 0006 This library is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU Library General Public 0008 License as published by the Free Software Foundation; either 0009 version 2 of the License, or (at your option) any later version. 0010 0011 This library is distributed in the hope that it will be useful, 0012 but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0014 Library General Public License for more details. 0015 0016 You should have received a copy of the GNU Library General Public License 0017 along with this library; see the file COPYING.LIB. If not, write to 0018 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0019 Boston, MA 02110-1301, USA. 0020 */ 0021 0022 0023 // Own 0024 #include "ChartProxyModel.h" 0025 #include "PlotArea.h" 0026 0027 // Qt 0028 #include <QRegion> 0029 #include <QPoint> 0030 0031 // Calligra 0032 #include <KoXmlReader.h> 0033 #include <KoShapeLoadingContext.h> 0034 #include <KoShapeSavingContext.h> 0035 #include <KoStyleStack.h> 0036 #include <KoXmlNS.h> 0037 #include <KoOdfLoadingContext.h> 0038 #include <KoOdfStylesReader.h> 0039 #include <KoOdfGraphicStyles.h> 0040 #include <interfaces/KoChartModel.h> 0041 0042 // KoChart 0043 #include "Axis.h" 0044 #include "DataSet.h" 0045 #include "TableSource.h" 0046 #include "OdfLoadingHelper.h" 0047 #include "ChartTableModel.h" 0048 #include "ChartDebug.h" 0049 0050 // Other 0051 #include <algorithm> 0052 0053 using namespace KoChart; 0054 0055 0056 // ================================================================ 0057 // Class ChartProxyModel::Private 0058 0059 0060 class ChartProxyModel::Private { 0061 public: 0062 Private(ChartProxyModel *parent, ChartShape *shape, TableSource *source); 0063 ~Private(); 0064 0065 ChartProxyModel *const q; 0066 ChartShape *const shape; 0067 TableSource *const tableSource; 0068 0069 /// Set to true if we're in the process of loading data from ODF. 0070 /// Used to avoid repeatedly updating data. 0071 bool isLoading; 0072 0073 bool manualControl; 0074 0075 bool firstRowIsLabel; 0076 bool firstColumnIsLabel; 0077 Qt::Orientation dataDirection; 0078 int dataDimensions; 0079 0080 CellRegion categoryDataRegion; 0081 0082 QVector< CellRegion > dataSetRegions; 0083 0084 QList<DataSet*> dataSets; 0085 QList<DataSet*> removedDataSets; 0086 0087 CellRegion selection; 0088 0089 /** 0090 * Discards old and creates new data sets from the current region selection. 0091 */ 0092 void rebuildDataMap(); 0093 0094 /** 0095 * Extracts a list of data sets (with x data region, y data region, etc. 0096 * assigned) from the current d->selection. 0097 * 0098 * Unless the list *dataSetsToRecycle is empty, it will reuse as many 0099 * DataSet instances from there as possible and remove them from the list. 0100 * 0101 * As a side effect, this method sets d->categoryDataRegion if 0102 * overrideCategories is true. 0103 */ 0104 QList<DataSet*> createDataSetsFromRegion(QList<DataSet*> *dataSetsToRecycle, 0105 bool overrideCategories = true); 0106 }; 0107 0108 ChartProxyModel::Private::Private(ChartProxyModel *parent, ChartShape *shape, TableSource *source) 0109 : q(parent) 0110 , shape(shape) 0111 , tableSource(source) 0112 , isLoading(false) 0113 , manualControl(false) 0114 { 0115 firstRowIsLabel = false; 0116 firstColumnIsLabel = false; 0117 dataDimensions = 1; 0118 0119 // Determines what orientation the data points in a data series 0120 // have when multiple data sets are created from one source 0121 // region. For example, vertical means that each column in the source 0122 // region is assigned to one data series. 0123 // Default to Qt::Vertical, as that's what OOo does also. 0124 dataDirection = Qt::Vertical; 0125 } 0126 0127 ChartProxyModel::Private::~Private() 0128 { 0129 qDeleteAll(dataSets); 0130 qDeleteAll(removedDataSets); 0131 } 0132 0133 0134 // ================================================================ 0135 // Class ChartProxyModel 0136 0137 0138 ChartProxyModel::ChartProxyModel(ChartShape *shape, TableSource *source) 0139 : QAbstractTableModel(), 0140 d(new Private(this, shape, source)) 0141 { 0142 connect(source, SIGNAL(tableAdded(Table*)), 0143 this, SLOT(addTable(Table*))); 0144 connect(source, SIGNAL(tableRemoved(Table*)), 0145 this, SLOT(removeTable(Table*))); 0146 } 0147 0148 ChartProxyModel::~ChartProxyModel() 0149 { 0150 delete d; 0151 } 0152 0153 void ChartProxyModel::reset(const CellRegion& region) 0154 { 0155 d->selection = region; 0156 d->rebuildDataMap(); 0157 } 0158 0159 CellRegion ChartProxyModel::cellRangeAddress() const 0160 { 0161 return d->selection; 0162 } 0163 0164 void ChartProxyModel::Private::rebuildDataMap() 0165 { 0166 if (manualControl) { 0167 return; 0168 } 0169 // This was intended to speed up the loading process, by executing this 0170 // method only once in endLoading(), however the approach is actually 0171 // incorrect as it would potentially override a data set's regions 0172 // set by "somebody" else in the meantime. 0173 // if (isLoading) 0174 // return; 0175 q->beginResetModel(); 0176 q->invalidateDataSets(); 0177 dataSets = createDataSetsFromRegion(&removedDataSets); 0178 q->endResetModel(); 0179 } 0180 0181 void ChartProxyModel::addTable(Table *table) 0182 { 0183 QAbstractItemModel *model = table->model(); 0184 connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), 0185 this, SLOT(dataChanged(QModelIndex,QModelIndex))); 0186 } 0187 0188 void ChartProxyModel::removeTable(Table *table) 0189 { 0190 QAbstractItemModel *model = table->model(); 0191 model->disconnect(this); 0192 } 0193 0194 /** 0195 * Returns a row of a given region (i.e. a part of it with height 1), cutting 0196 * off the first @a colOffset cells in that row. 0197 * 0198 * Examples: extractRow(A1:C2, 0, 0) --> A1:C1 0199 * extractRow(A1:C2, 1, 0) --> A2:C2 0200 * extractRow(A1:C2, 0, 1) --> B1:C1 0201 * 0202 * See notes in createDataSetsFromRegion() for further details. 0203 * 0204 * @param region The region to extract the row from 0205 * @param row The number of the row, starting with 0 0206 * @param colOffset How many of the first columns to cut from the resulting row 0207 */ 0208 static QVector<QRect> extractRow(const QVector<QRect> &rects, int colOffset, bool extractLabel) 0209 { 0210 if (colOffset == 0) 0211 return extractLabel ? QVector<QRect>() : rects; 0212 QVector<QRect> result; 0213 foreach(const QRect &rect, rects) { 0214 if (extractLabel) { 0215 QRect r(rect.topLeft(), QSize(colOffset, rect.height())); 0216 result.append(r); 0217 } 0218 else { 0219 //Q_ASSERT(rect.width() > colOffset); 0220 if (rect.width() > colOffset) { 0221 QPoint topLeft = rect.topLeft() + QPoint(colOffset, 0); 0222 QRect r(topLeft, QSize(rect.width() - colOffset, rect.height())); 0223 result.append(r); 0224 } 0225 } 0226 } 0227 return result; 0228 } 0229 0230 /** 0231 * Returns a column of a given region, cutting off the first @a rowOffset 0232 * rows in that column. 0233 * 0234 * Examples: extractColumn(A1:C2, 0, 0) --> A1:A2 0235 * extractColumn(A1:C2;D1;F2, 0, 0) --> A1:A2;D1:D2 0236 * 0237 * See notes in createDataSetsFromRegion() for further details. 0238 * 0239 * @param region The region to extract the row from 0240 * @param col The number of the column, starting with 0 0241 * @param rowOffset How many of the first rows to cut from the resulting column 0242 */ 0243 static QVector<QRect> extractColumn(const QVector<QRect> &rects, int rowOffset, bool extractLabel) 0244 { 0245 if (rowOffset == 0) 0246 return extractLabel ? QVector<QRect>() : rects; 0247 QVector<QRect> result; 0248 foreach(const QRect &rect, rects) { 0249 if (extractLabel) { 0250 QRect r(rect.topLeft(), QSize(rect.width(), rowOffset)); 0251 result.append(r); 0252 } 0253 else { 0254 //Q_ASSERT(rect.height() > rowOffset); 0255 if (rect.height() > rowOffset) { 0256 QPoint topLeft = rect.topLeft() + QPoint(0, rowOffset); 0257 QRect r(topLeft, QSize(rect.width(), rect.height() - rowOffset)); 0258 result.append(r); 0259 } 0260 } 0261 } 0262 return result; 0263 } 0264 0265 /** 0266 * Returns a according to the dataDirection sorted data-region. 0267 * 0268 * First prepare the dataRegions to have them 0269 * - proper sorted either from left-to-right if the dataDirection is 0270 * Qt::Horizontal or from top-to-bottom when Qt::Vertical. 0271 * - have multiple dimensions proper split-up into single rows/columns 0272 * to produce one DataSet per row/column. 0273 * 0274 * The resulting map is build up depending on the dataDirection; 0275 * - Qt::Horizontal: key is the row and values are the columns in the row. 0276 * - Qt::Vertical: key is the column and values are the rows in the column. 0277 * 0278 * @param dataDirection The direction of the data. This could be either 0279 * dataDirection == Qt::Horizontal or dataDirection == Qt::Vertical. 0280 * @param dataRegions The unsorted list of data-regions. 0281 * @return Compared to the QVector that goes in the resulting map makes 0282 * following sure; 0283 * - sorted according to the dataDirection either by row or column 0284 * - duplicate definitions are removed/compressed. 0285 * - definitions or multiple rows and columns in one QRect are split 0286 * in rows/columns what makes sure we earn one dataRegion per 0287 * row/column. This is needed so for example the labelRegion and 0288 * all other things operate on exactly one row/column. 0289 */ 0290 QMap<int, QVector<QRect> > sortDataRegions(Qt::Orientation dataDirection, QVector<QRect> dataRegions) 0291 { 0292 QMap<int, QVector<QRect> > sortedDataRegions; 0293 if (dataDirection == Qt::Horizontal) { 0294 // Split up region in horizontal rectangles that are sorted from top to bottom 0295 QMap<int, QVector<QRect> > rows; 0296 foreach (const QRect &rect, dataRegions) { 0297 int x = rect.topLeft().x(); 0298 for (int y = rect.topLeft().y(); y <= rect.bottomLeft().y(); y++) { 0299 QRect dataRect = QRect(QPoint(x, y), QSize(rect.width(), 1)); 0300 if (!rows.contains(y)) 0301 rows.insert(y, QVector<QRect>()); 0302 rows[y].append(dataRect); 0303 } 0304 } 0305 0306 // Sort rectangles in each row from left to right. 0307 QMapIterator<int, QVector<QRect> > i(rows); 0308 while (i.hasNext()) { 0309 i.next(); 0310 int row = i.key(); 0311 QVector<QRect> unsortedRects = i.value(); 0312 QVector<QRect> sortedRects; 0313 foreach (const QRect &rect, unsortedRects) { 0314 int index; 0315 for (index = 0; index < sortedRects.size(); index++) 0316 if (rect.topLeft().x() <= sortedRects[index].topLeft().x()) 0317 break; 0318 sortedRects.insert(index, rect); 0319 } 0320 sortedDataRegions.insert(row, sortedRects); 0321 } 0322 } else { 0323 // Split up region in horizontal rectangles that are sorted from top to bottom 0324 QMap<int, QVector<QRect> > columns; 0325 foreach (const QRect &rect, dataRegions) { 0326 int y = rect.topLeft().y(); 0327 for (int x = rect.topLeft().x(); x <= rect.topRight().x(); ++x) { 0328 QRect dataRect = QRect(QPoint(x, y), QSize(1, rect.height())); 0329 if (!columns.contains(x)) 0330 columns.insert(x, QVector<QRect>()); 0331 columns[x].append(dataRect); 0332 } 0333 } 0334 0335 // Sort rectangles in each column from top to bottom 0336 QMapIterator<int, QVector<QRect> > i(columns); 0337 while (i.hasNext()) { 0338 i.next(); 0339 int col = i.key(); 0340 QVector<QRect> unsortedRects = i.value(); 0341 QVector<QRect> sortedRects; 0342 foreach (const QRect &rect, unsortedRects) { 0343 int index; 0344 for (index = 0; index < sortedRects.size(); ++index) 0345 if (rect.topLeft().y() <= sortedRects[index].topLeft().y()) 0346 break; 0347 sortedRects.insert(index, rect); 0348 } 0349 sortedDataRegions.insert(col, sortedRects); 0350 } 0351 } 0352 return sortedDataRegions; 0353 } 0354 0355 QList<DataSet*> ChartProxyModel::Private::createDataSetsFromRegion(QList<DataSet*> *dataSetsToRecycle, 0356 bool overrideCategories) 0357 { 0358 if (manualControl) { 0359 return dataSets; 0360 } 0361 if (!selection.isValid()) 0362 return QList<DataSet*>(); 0363 0364 // First prepare the dataRegions to have them 0365 // 1) proper sorted either from left-to-right if the dataDirection is 0366 // Qt::Horizontal or from top-to-bottom when Qt::Vertical. 0367 // 2) have multiple dimensions proper split-up into single rows/columns 0368 // to produce one DataSet per row/column. 0369 // 0370 // The resulting map is build up depending on the dataDirection; 0371 // - Qt::Horizontal: key is the row and values are the columns in the row. 0372 // - Qt::Vertical: key is the column and values are the rows in the column. 0373 QMap<int, QVector<QRect> > sortedDataRegions = sortDataRegions(dataDirection, selection.rects()); 0374 0375 // In the end, the contents of this list will look something like this: 0376 // (Category-Data, X-Data, Y-Data, Y-Data, Y-Data) 0377 // Semantic separation of the regions will follow later. 0378 QList<CellRegion> dataRegions; 0379 // This region exclusively contains (global) data set labels, i.e. 0380 // one label per data set (thus in opposite data direction) 0381 QList<CellRegion> labelRegions; 0382 0383 // Fill dataRegions and set categoryRegion. 0384 // Note that here, we don't exactly know yet what region will be used for 0385 // what data set, we also don't know yet what data these regions contain. 0386 int rowOffset = firstRowIsLabel ? 1 : 0; 0387 int colOffset = firstColumnIsLabel ? 1 : 0; 0388 0389 // Determines how many individual rows/columns will be assigned per data set. 0390 // It is at least one, but if there's more than one data dimension, the x 0391 // data is shared among all data sets, thus - 1. 0392 int regionsPerDataSet = qMax(1, dataDimensions - 1); 0393 0394 // Determinate the number of rows and maximum number of columns used. 0395 // FIXME this logic is rather buggy. We better should use our sortedDataRegions 0396 // here but that would need some testing for regression in e.g. 0397 // bubble-charts which I don't had the time to do. So, leaving the logic as 0398 // it was before for now. If you run into problems with bubble charts and 0399 // there x-values then this foreach-logic here is very likely the reason. 0400 int rows = 0; 0401 int cols = 0; 0402 foreach(const QRect &rect, selection.rects()) { 0403 rows += rect.height(); 0404 cols = qMax(cols, rect.width()); 0405 } 0406 0407 bool extractXData = dataDimensions > 1 && 0408 // Checks if the remaining data regions would fit exactly to the 0409 // remaining data sets. If not, skip x data. This is only the case 0410 // for bubble charts, (the only case of regionsPerDataSet == 2), so 0411 // skipping x data will allow the last data set to also be assigned 0412 // a bubble width region. This is exactly what OOo does. 0413 (dataDirection == Qt::Horizontal ? rows - 1 - rowOffset 0414 : cols - 1 -colOffset) % regionsPerDataSet == 0; 0415 0416 // When x data is present, it occupies the first non-header row/column 0417 if (extractXData && dataDirection == Qt::Horizontal) 0418 ++rowOffset; 0419 if (extractXData && dataDirection == Qt::Vertical) 0420 ++colOffset; 0421 0422 // This is the logic that extracts all the subregions from selection 0423 // that are later used for the data sets 0424 Table *internalTable = shape ? shape->tableSource()->get(shape->internalModel()) : 0; 0425 QList<int> sortedDataKeys = sortedDataRegions.keys(); 0426 std::sort(sortedDataKeys.begin(), sortedDataKeys.end()); 0427 foreach(int key, sortedDataKeys) { 0428 QVector<QRect> rects = sortedDataRegions[key]; 0429 QVector<QRect> dataRects; 0430 CellRegion labelRegion; 0431 if (dataDirection == Qt::Horizontal) { 0432 if (firstColumnIsLabel) { 0433 QVector<QRect> labelRects = extractRow(rects, colOffset, true); 0434 labelRegion = labelRects.isEmpty() ? CellRegion() : CellRegion(selection.table(), labelRects); 0435 } 0436 else { 0437 labelRegion = internalTable ? CellRegion(internalTable, QPoint(1, key)) : CellRegion(); 0438 } 0439 dataRects = extractRow(rects, colOffset, false); 0440 } 0441 else { 0442 if (firstRowIsLabel) { 0443 QVector<QRect> labelRects = extractColumn(rects, rowOffset, true); 0444 labelRegion = labelRects.isEmpty() ? CellRegion() : CellRegion(selection.table(), labelRects); 0445 } 0446 else { 0447 labelRegion = internalTable ? CellRegion(internalTable, QPoint(key, 1)) : CellRegion(); 0448 } 0449 dataRects = extractColumn(rects, rowOffset, false); 0450 } 0451 0452 labelRegions.append(labelRegion); 0453 dataRegions.append(dataRects.isEmpty() ? CellRegion() : CellRegion(selection.table(), dataRects)); 0454 } 0455 0456 bool useCategories = 0457 (dataDirection == Qt::Horizontal && firstRowIsLabel) || 0458 (dataDirection == Qt::Vertical && firstColumnIsLabel); 0459 0460 /* 0461 debugChart << "selection=" << selection.toString(); 0462 debugChart << "dataDirection=" << (dataDirection == Qt::Horizontal ? "Horizontal" : "Vertical"); 0463 debugChart << "firstRowIsLabel=" << firstRowIsLabel; 0464 debugChart << "firstColumnIsLabel=" << firstColumnIsLabel; 0465 debugChart << "overrideCategories=" << overrideCategories; 0466 debugChart << "useCategories=" << useCategories; 0467 debugChart << "dataRegions.count()="<<dataRegions.count(); 0468 */ 0469 0470 // Regions shared by all data sets: categories and x-data 0471 0472 //FIXME don't use overrideCategories for the categoryDataRegion's but for 0473 //the categoryLabelRegion (which we need to introduce and use). 0474 if(overrideCategories) categoryDataRegion = CellRegion(); 0475 0476 if (!dataRegions.isEmpty()) { 0477 // Q_ASSERT(label.isValid()); 0478 // Q_ASSERT(data.isValid()); 0479 if (useCategories) { 0480 labelRegions.takeFirst(); 0481 categoryDataRegion = dataRegions.takeFirst(); 0482 } 0483 } 0484 0485 CellRegion xData; 0486 if (!dataRegions.isEmpty() && extractXData) { 0487 labelRegions.removeFirst(); 0488 xData = dataRegions.takeFirst(); 0489 } 0490 0491 QList<DataSet*> createdDataSets; 0492 int dataSetNumber = 0; 0493 // Now assign all dataRegions to a number of data sets. 0494 // Here they're semantically separated into x data, y data, etc. 0495 Q_ASSERT(dataRegions.count() == labelRegions.count()); 0496 while (!dataRegions.isEmpty()) { 0497 // Get a data set instance we can use 0498 DataSet *dataSet; 0499 if (!dataSetsToRecycle->isEmpty()) 0500 dataSet = dataSetsToRecycle->takeFirst(); 0501 else 0502 dataSet = new DataSet(dataSetNumber); 0503 0504 // category and x data are "global" regions shared among all data sets 0505 dataSet->setCategoryDataRegion(categoryDataRegion); 0506 dataSet->setXDataRegion(xData); 0507 0508 // the name-value used for e.g. the legend label 0509 dataSet->setLabelDataRegion(labelRegions.takeFirst()); 0510 0511 // the range for y-values 0512 dataSet->setYDataRegion(dataRegions.takeFirst()); 0513 0514 // the custom data (e.g. bubble width) 0515 if (!dataRegions.isEmpty() && dataDimensions > 2) 0516 dataSet->setCustomDataRegion(dataRegions.takeFirst()); 0517 else 0518 dataSet->setCustomDataRegion(CellRegion()); 0519 0520 /* 0521 debugChart << "xDataRegion=" << dataSet->xDataRegion().toString(); 0522 debugChart << "yDataRegion=" << dataSet->yDataRegion().toString(); 0523 debugChart << "categoryDataRegion=" << dataSet->categoryDataRegion().toString(); 0524 debugChart << "labelDataRegion=" << dataSet->labelDataRegion().toString(); 0525 debugChart << "customDataRegion=" << dataSet->customDataRegion().toString(); 0526 */ 0527 0528 createdDataSets.append(dataSet); 0529 0530 // Increment number at the very end! 0531 dataSetNumber++; 0532 } 0533 return createdDataSets; 0534 } 0535 0536 void ChartProxyModel::saveOdf(KoShapeSavingContext &context) const 0537 { 0538 int dataSetCount = rowCount(); 0539 if (d->shape->chartType() == StockChartType && d->shape->chartSubType() == HighLowCloseChartSubtype && dataSetCount > 3) { 0540 // When loading, stock chart subtype is determined by number of datasets (sigh) 0541 dataSetCount = 3; 0542 } 0543 for (int i = 0; i < dataSetCount; ++i) { 0544 d->dataSets.at(i)->saveOdf(context); 0545 } 0546 } 0547 0548 // This loads the properties of the datasets (chart:series). 0549 // FIXME: This is a strange place to load them (the proxy model) 0550 bool ChartProxyModel::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context, ChartType type) 0551 { 0552 Q_ASSERT(d->isLoading); 0553 0554 // PlotArea* plotArea = dynamic_cast< PlotArea* >(QObject::parent()); 0555 0556 // const bool stockChart = element.attributeNS(KoXmlNS::chart, "class", QString()) == "chart:stock"; 0557 0558 OdfLoadingHelper *helper = (OdfLoadingHelper*)context.sharedData(OdfLoadingHelperId); 0559 bool ignoreCellRanges = helper->chartUsesInternalModelOnly; 0560 // Some OOo documents save incorrect cell ranges. For those this fix was intended. 0561 // Find out which documents exactly and only use fix for as few cases as possible. 0562 #if 0 0563 // If we exclusively use the chart's internal model then all data 0564 // is taken from there and each data set is automatically assigned 0565 // the rows it belongs to. 0566 bool ignoreCellRanges = helper->chartUsesInternalModelOnly; 0567 #endif 0568 0569 beginResetModel(); 0570 0571 if (element.hasAttributeNS(KoXmlNS::chart, "style-name")) { 0572 KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); 0573 styleStack.clear(); 0574 context.odfLoadingContext().fillStyleStack(element, KoXmlNS::chart, "style-name", "chart"); 0575 0576 // Data direction: It's in the plotarea style. 0577 if (styleStack.hasProperty(KoXmlNS::chart, "series-source")) { 0578 QString seriesSource = styleStack.property(KoXmlNS::chart, "series-source"); 0579 // Check if the direction for data series is vertical or horizontal. 0580 if (seriesSource == "rows") 0581 d->dataDirection = Qt::Horizontal; 0582 else if (seriesSource == "columns") 0583 d->dataDirection = Qt::Vertical; 0584 // Otherwise leave our default value 0585 } 0586 } 0587 0588 // Find out if the data table contains labels as first row and/or column. 0589 // This is in the plot-area element itself. 0590 0591 // Do not ignore the data-source-has-labels in any case, even if a 0592 // category data region is specified for an axis, as the first 0593 // column still has to be excluded from the actual data region if 0594 // e.g. data-source-has-labels is set to "column" If an axis 0595 // contains the chart:categories element, the category data region 0596 // will automatically be set on every data set attached to that 0597 // axis. See Axis::attachDataSet(). 0598 const QString dataSourceHasLabels 0599 = element.attributeNS(KoXmlNS::chart, "data-source-has-labels"); 0600 if (dataSourceHasLabels == "both") { 0601 d->firstRowIsLabel = true; 0602 d->firstColumnIsLabel = true; 0603 } else if (dataSourceHasLabels == "row") { 0604 d->firstRowIsLabel = true; 0605 d->firstColumnIsLabel = false; 0606 } else if (dataSourceHasLabels == "column") { 0607 d->firstRowIsLabel = false; 0608 d->firstColumnIsLabel = true; 0609 } else { 0610 // dataSourceHasLabels == "none" or wrong value 0611 d->firstRowIsLabel = false; 0612 d->firstColumnIsLabel = false; 0613 } 0614 0615 // For every dataset, there must be an explicit <chart:series> element, 0616 // which we will load later. 0617 d->dataSets.clear(); 0618 d->removedDataSets.clear(); 0619 0620 // A cell range for all data is optional. 0621 // If cell ranges are in addition specified for one or more of these 0622 // data series, they'll be overwritten by the data loaded from the series 0623 if (!ignoreCellRanges 0624 && element.hasAttributeNS(KoXmlNS::table, "cell-range-address")) 0625 { 0626 QString cellRangeAddress = element.attributeNS(KoXmlNS::table, "cell-range-address"); 0627 d->selection = CellRegion(d->tableSource, cellRangeAddress); 0628 // Otherwise use all data from internal table 0629 } else if (helper->chartUsesInternalModelOnly) { 0630 QList<Table*> tables = helper->tableSource->tableMap().values(); 0631 Q_ASSERT(tables.count() == 1); 0632 Table *internalTable = tables.first(); 0633 Q_ASSERT(internalTable->model()); 0634 int rowCount = internalTable->model()->rowCount(); 0635 int colCount = internalTable->model()->columnCount(); 0636 d->selection = CellRegion(internalTable, QRect(1, 1, colCount, rowCount)); 0637 } 0638 0639 // This is what we'll use as basis for the data sets we "produce" from ODF. 0640 // This might be data sets that were "instantiated" from the internal 0641 // table or from an arbitrary selection of other tables as specified 0642 // in the PlotArea's table:cell-range-address attribute (parsed above). 0643 QList<DataSet*> createdDataSets = d->createDataSetsFromRegion(&d->removedDataSets, 0644 !helper->categoryRegionSpecifiedInXAxis); 0645 0646 bool isBubble = d->shape->plotArea()->chartType() == BubbleChartType; 0647 bool isScatter = d->shape->plotArea()->chartType() == ScatterChartType; 0648 CellRegion prevXData, prevYData; 0649 0650 int loadedDataSetCount = 0; 0651 KoXmlElement n; 0652 forEachElement (n, element) { 0653 if (n.namespaceURI() != KoXmlNS::chart) { 0654 continue; 0655 } 0656 if (n.localName() == "series") { 0657 DataSet *dataSet; 0658 if (loadedDataSetCount < createdDataSets.size()) { 0659 dataSet = createdDataSets[loadedDataSetCount]; 0660 } else { 0661 // the datasetnumber needs to be known at construction time, to ensure 0662 // default colors are set correctly 0663 dataSet = new DataSet(d->dataSets.size()); 0664 } 0665 dataSet->setChartType(type); 0666 d->dataSets.append(dataSet); 0667 if (d->categoryDataRegion.isValid()) { 0668 dataSet->setCategoryDataRegion(d->categoryDataRegion); 0669 } 0670 dataSet->loadOdf(n, context); 0671 0672 if (isBubble || isScatter) { 0673 // bubble- and scatter-charts have chart:domain's that define the 0674 // x- and y-data. But if they are not defined in the series then 0675 // a previous defined one needs to be used. 0676 // NOTE: This is not quite what odf specifies, but imho better (danders) 0677 if (dataSet->xDataRegion().isValid()) { 0678 prevXData = dataSet->xDataRegion(); 0679 } 0680 else { 0681 dataSet->setXDataRegion(prevXData); 0682 } 0683 if (isBubble) { 0684 if (dataSet->yDataRegion().isValid()) { 0685 prevYData = dataSet->yDataRegion(); 0686 } 0687 else { 0688 dataSet->setYDataRegion(prevYData); 0689 } 0690 } 0691 } 0692 ++loadedDataSetCount; 0693 } 0694 } 0695 0696 //rebuildDataMap(); 0697 endResetModel(); 0698 0699 return true; 0700 } 0701 0702 0703 QVariant ChartProxyModel::data(const QModelIndex &index, 0704 int role) const 0705 { 0706 Q_UNUSED(index); 0707 Q_UNUSED(role); 0708 Q_ASSERT("To be implemented"); 0709 return QVariant(); 0710 } 0711 0712 void ChartProxyModel::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) 0713 { 0714 QPoint topLeftPoint(topLeft.column() + 1, topLeft.row() + 1); 0715 0716 // Excerpt from the Qt reference for QRect::bottomRight() which is 0717 // used for calculating bottomRight. Note that for historical 0718 // reasons this function returns 0719 // QPoint(left() + width() -1, top() + height() - 1). 0720 QPoint bottomRightPoint(bottomRight.column() + 1, bottomRight.row() + 1); 0721 QRect dataChangedRect = QRect(topLeftPoint, 0722 QSize(bottomRightPoint.x() - topLeftPoint.x() + 1, 0723 bottomRightPoint.y() - topLeftPoint.y() + 1)); 0724 // Precisely determine what data in what table changed so that we don't 0725 // do unnecessary, expensive updates. 0726 Table *table = d->tableSource->get(topLeft.model()); 0727 CellRegion dataChangedRegion(table, dataChangedRect); 0728 0729 foreach (DataSet *dataSet, d->dataSets) { 0730 if (dataSet->xDataRegion().intersects(dataChangedRegion)) 0731 dataSet->xDataChanged(QRect()); 0732 0733 if (dataSet->yDataRegion().intersects(dataChangedRegion)) 0734 dataSet->yDataChanged(QRect()); 0735 0736 if (dataSet->categoryDataRegion().intersects(dataChangedRegion)) 0737 dataSet->categoryDataChanged(QRect()); 0738 0739 if (dataSet->labelDataRegion().intersects(dataChangedRegion)) 0740 dataSet->labelDataChanged(QRect()); 0741 0742 if (dataSet->customDataRegion().intersects(dataChangedRegion)) 0743 dataSet->customDataChanged(QRect()); 0744 } 0745 0746 emit dataChanged(); 0747 } 0748 0749 0750 QVariant ChartProxyModel::headerData(int section, 0751 Qt::Orientation orientation, 0752 int role /* = Qt::DisplayRole */) const 0753 { 0754 Q_UNUSED(section); 0755 Q_UNUSED(orientation); 0756 Q_UNUSED(role); 0757 Q_ASSERT("To be implemented"); 0758 return QVariant(); 0759 } 0760 0761 0762 QModelIndex ChartProxyModel::parent(const QModelIndex &index) const 0763 { 0764 Q_UNUSED(index); 0765 0766 return QModelIndex(); 0767 } 0768 0769 int ChartProxyModel::rowCount(const QModelIndex &/*parent = QModelIndex() */) const 0770 { 0771 return d->dataSets.count(); 0772 } 0773 0774 0775 int ChartProxyModel::columnCount(const QModelIndex &/*parent = QModelIndex() */) const 0776 { 0777 // FIXME: Replace this by the actual column count once the proxy is properly being used. 0778 int cols = 0; 0779 for (int i = 0; i < d->dataSets.count(); ++i) { 0780 cols = qMax(cols, d->dataSets.at(i)->size()); 0781 } 0782 return cols; 0783 } 0784 0785 void ChartProxyModel::setFirstRowIsLabel(bool b) 0786 { 0787 if (b == d->firstRowIsLabel) 0788 return; 0789 0790 d->firstRowIsLabel = b; 0791 d->rebuildDataMap(); 0792 } 0793 0794 0795 void ChartProxyModel::setFirstColumnIsLabel(bool b) 0796 { 0797 if (b == d->firstColumnIsLabel) 0798 return; 0799 0800 d->firstColumnIsLabel = b; 0801 d->rebuildDataMap(); 0802 } 0803 0804 Qt::Orientation ChartProxyModel::dataDirection() 0805 { 0806 return d->dataDirection; 0807 } 0808 0809 void ChartProxyModel::invalidateDataSets() 0810 { 0811 d->removedDataSets = d->dataSets; 0812 d->dataSets.clear(); 0813 } 0814 0815 void ChartProxyModel::beginLoading() 0816 { 0817 Q_ASSERT(!d->isLoading); 0818 0819 beginResetModel(); 0820 0821 // FIXME: invalidateDataSets() used to be called explicitly at the beginning 0822 // of ChartShape::loadOdf(). Now beginLoading() is called instead. 0823 // So, is invalidateDataSets() still necessary here? 0824 invalidateDataSets(); 0825 d->isLoading = true; 0826 } 0827 0828 void ChartProxyModel::endLoading() 0829 { 0830 Q_ASSERT(d->isLoading); 0831 d->isLoading = false; 0832 0833 // Doing this here is wrong, the data set's cell regions set in 0834 // DataSet::loadOdf() would get overridden. 0835 // d->rebuildDataMap(); 0836 0837 endResetModel(); 0838 } 0839 0840 bool ChartProxyModel::isLoading() const 0841 { 0842 return d->isLoading; 0843 } 0844 0845 void ChartProxyModel::setDataDirection(Qt::Orientation orientation) 0846 { 0847 if (d->dataDirection == orientation) 0848 return; 0849 0850 d->dataDirection = orientation; 0851 d->rebuildDataMap(); 0852 } 0853 0854 void ChartProxyModel::setDataDimensions(int dimensions) 0855 { 0856 if (d->dataDimensions == dimensions) 0857 return; 0858 0859 d->dataDimensions = dimensions; 0860 d->rebuildDataMap(); 0861 } 0862 0863 bool ChartProxyModel::firstRowIsLabel() const 0864 { 0865 return d->firstRowIsLabel; 0866 } 0867 0868 bool ChartProxyModel::firstColumnIsLabel() const 0869 { 0870 return d->firstColumnIsLabel; 0871 } 0872 0873 CellRegion ChartProxyModel::categoryDataRegion() const 0874 { 0875 return d->categoryDataRegion; 0876 } 0877 0878 void ChartProxyModel::setCategoryDataRegion(const CellRegion ®ion) 0879 { 0880 d->categoryDataRegion = region; 0881 } 0882 0883 QList<DataSet*> ChartProxyModel::dataSets() const 0884 { 0885 return d->dataSets; 0886 } 0887 0888 void ChartProxyModel::addDataSet(int pos) 0889 { 0890 int dataSetNr = pos; 0891 QMap<int, int> numbers; 0892 for (int i = 0; i < d->dataSets.count(); ++i) { 0893 numbers[d->dataSets.at(i)->number()] = i; 0894 } 0895 if (numbers.contains(pos)) { 0896 // find first free one 0897 for (int i = 1; i < numbers.count(); ++ i) { 0898 if (!numbers.contains(i)) { 0899 dataSetNr = i; 0900 break; 0901 } 0902 } 0903 } 0904 DataSet *ds = new DataSet(dataSetNr); 0905 if (!d->dataSets.isEmpty()) { 0906 DataSet *copy = d->dataSets.first(); 0907 ds->setXDataRegion(copy->xDataRegion()); 0908 ds->setYDataRegion(copy->yDataRegion()); 0909 ds->setCustomDataRegion(copy->customDataRegion()); 0910 ds->setCategoryDataRegion(copy->categoryDataRegion()); 0911 } 0912 d->dataSets.insert(pos, ds); 0913 } 0914 0915 bool ChartProxyModel::insertRows(int row, int count, const QModelIndex &/*parent*/) 0916 { 0917 if (count < 1 || row < 0 || row > d->dataSets.count()) { 0918 return false; 0919 } 0920 beginResetModel(); 0921 for (int i = 0; i < count; ++i) { 0922 addDataSet(row + i); 0923 } 0924 endResetModel(); 0925 dataChanged(QModelIndex(), QModelIndex()); 0926 return true; 0927 } 0928 0929 bool ChartProxyModel::removeRows(int row, int count, const QModelIndex &/*parent*/) 0930 { 0931 if (count < 1 || row < 0 || row >= d->dataSets.count()) { 0932 return false; 0933 } 0934 beginResetModel(); 0935 QList<DataSet*> remove; 0936 for (int i = 0; i < count; ++i) { 0937 remove << d->dataSets.at(row + i); 0938 } 0939 for (DataSet *ds : remove) { 0940 d->dataSets.removeAll(ds); 0941 delete ds; 0942 } 0943 endResetModel(); 0944 dataChanged(QModelIndex(), QModelIndex()); 0945 return true; 0946 } 0947 0948 void ChartProxyModel::setManualControl(bool value) 0949 { 0950 d->manualControl = value; 0951 } 0952 0953 bool ChartProxyModel::manualControl() 0954 { 0955 return d->manualControl; 0956 }