File indexing completed on 2024-05-12 16:33:32
0001 /* This file is part of the KDE project 0002 0003 Copyright 2007-2008 Johannes Simon <johannes.simon@gmail.com> 0004 Copyright 2008-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 "DataSet.h" 0028 #include "ChartDebug.h" 0029 0030 // Qt 0031 #include <QAbstractItemModel> 0032 #include <QString> 0033 #include <QPen> 0034 #include <QColor> 0035 #include <QPainter> 0036 0037 // KF5 0038 #include <klocalizedstring.h> 0039 0040 // KChart 0041 #include <KChartDataValueAttributes> 0042 #include <KChartPieAttributes> 0043 #include <KChartTextAttributes> 0044 #include <KChartRelativePosition> 0045 #include <KChartPosition> 0046 #include <KChartAbstractDiagram> 0047 #include <KChartMeasure> 0048 #include "KChartModel.h" 0049 0050 // KoChart 0051 #include "Axis.h" 0052 #include "PlotArea.h" 0053 #include "Surface.h" 0054 #include "OdfLoadingHelper.h" 0055 0056 // Calligra 0057 #include <KoXmlNS.h> 0058 #include <KoOdfGraphicStyles.h> 0059 #include <KoStyleStack.h> 0060 #include <KoXmlReader.h> 0061 #include <KoShapeLoadingContext.h> 0062 #include <KoShapeSavingContext.h> 0063 #include <KoOdfLoadingContext.h> 0064 #include <KoOdfWorkaround.h> 0065 #include <KoGenStyle.h> 0066 #include <KoGenStyles.h> 0067 #include <KoXmlWriter.h> 0068 0069 using namespace KoChart; 0070 0071 const int numDefaultMarkerTypes = 15; 0072 0073 // These are the values that are defined for chart:symbol-name 0074 // Ref: ODF 1.2 20.54 chart:symbol-name, page 716 0075 const QByteArray symbolNames[] = { 0076 "square", 0077 "diamond", 0078 "arrow-down", 0079 "arrow-up", 0080 "arrow-right", 0081 "arrow-left", 0082 "bow-tie", 0083 "hourglass", 0084 "circle", 0085 "star", 0086 "x", 0087 "plus", 0088 "asterisk", 0089 "horizontal-bar", 0090 "vertical-bar" 0091 }; 0092 0093 const KChart::MarkerAttributes::MarkerStyle defaultMarkerTypes[]= { 0094 KChart::MarkerAttributes::MarkerSquare, // 0 0095 KChart::MarkerAttributes::MarkerDiamond, // 1 0096 KChart::MarkerAttributes::MarkerArrowDown, // 2 0097 KChart::MarkerAttributes::MarkerArrowUp, // 3 0098 KChart::MarkerAttributes::MarkerArrowRight, // 4 0099 KChart::MarkerAttributes::MarkerArrowLeft, // 5 0100 KChart::MarkerAttributes::MarkerBowTie, // 6 0101 KChart::MarkerAttributes::MarkerHourGlass, // 7 0102 KChart::MarkerAttributes::MarkerCircle, // 8 0103 KChart::MarkerAttributes::MarkerStar, // 9 0104 KChart::MarkerAttributes::MarkerX, // 10 0105 KChart::MarkerAttributes::MarkerCross, // 11 0106 KChart::MarkerAttributes::MarkerAsterisk, // 12 0107 KChart::MarkerAttributes::MarkerHorizontalBar,// 13 0108 KChart::MarkerAttributes::MarkerVerticalBar, // 14 0109 0110 // Not used: 0111 KChart::MarkerAttributes::MarkerRing, 0112 KChart::MarkerAttributes::MarkerFastCross, 0113 KChart::MarkerAttributes::Marker1Pixel, 0114 KChart::MarkerAttributes::Marker4Pixels, 0115 KChart::MarkerAttributes::NoMarker 0116 }; 0117 0118 static KChart::MarkerAttributes::MarkerStyle odf2kdMarker(OdfMarkerStyle style); 0119 0120 // just to get access to paintMarker method, until there is a proper way to 0121 // have markers painted by some util class 0122 class MarkerPainterDummyDiagram : public KChart::AbstractDiagram 0123 { 0124 public: 0125 MarkerPainterDummyDiagram() {} 0126 void doPaintMarker( QPainter* painter, 0127 const KChart::MarkerAttributes& markerAttributes, 0128 const QBrush& brush, const QPen& pen, 0129 const QPointF& point, const QSizeF& size ); 0130 public: // abstract KChart::AbstractDiagram API 0131 void paint ( KChart::PaintContext* /*paintContext*/ ) override {} 0132 void resize ( const QSizeF& /*area*/ ) override {} 0133 protected: // abstract KChart::AbstractDiagram API 0134 const QPair<QPointF, QPointF> calculateDataBoundaries() const override { return QPair<QPointF, QPointF>(); } 0135 }; 0136 0137 void MarkerPainterDummyDiagram::doPaintMarker(QPainter* painter, const KChart::MarkerAttributes& markerAttributes, const QBrush& brush, const QPen& pen, const QPointF& point, const QSizeF& size) 0138 { 0139 paintMarker(painter, markerAttributes, brush, pen, point, size); 0140 } 0141 0142 0143 class DataSet::Private 0144 { 0145 public: 0146 Private(DataSet *parent, int dataSetNr); 0147 ~Private(); 0148 0149 void updateSize(); 0150 bool hasOwnChartType() const; 0151 ChartType effectiveChartType() const; 0152 bool isValidDataPoint(const QPoint &point) const; 0153 QVariant data(const CellRegion ®ion, int index, int role) const; 0154 QString formatData(const CellRegion ®ion, int index, int role) const; 0155 0156 QBrush defaultBrush() const; 0157 QBrush defaultBrush(int section) const; 0158 0159 KChart::MarkerAttributes defaultMarkerAttributes() const; 0160 0161 // Returns an instance of DataValueAttributes with sane default values in 0162 // relation to KoChart 0163 KChart::DataValueAttributes defaultDataValueAttributes() const; 0164 /// Copies Private::dataValueAttributes to this section if it doesn't 0165 /// have its own DataValueAttributes copy yet. 0166 void insertDataValueAttributeSectionIfNecessary(int section); 0167 0168 /** 0169 * FIXME: Refactor (post-2.3) 0170 * 1) Maximum bubble width should be determined in ChartProxyModel 0171 * 2) Actual marker size and other KChart::MarkerAttributes should 0172 * be set by some kind of adapter for KD Chart, e.g. KChartModel. 0173 * 0174 * This determines the maximum bubble size of *all* data points in 0175 * the diagram this data set belongs to so that the actual value used to 0176 * draw the bubbles is relative to this value. 0177 * 0178 * For more info on how bubble sizes are calculated, see 0179 * http://qa.openoffice.org/issues/show_bug.cgi?id=64689 0180 */ 0181 qreal maxBubbleSize() const; 0182 0183 QPen defaultPen() const; 0184 0185 void dataChanged(KChartModel::DataRole role, const QRect &rect) const; 0186 void setAttributesAccordingToType(); 0187 0188 DataSet *parent; 0189 0190 ChartType chartType; 0191 ChartSubtype chartSubType; 0192 0193 Axis *attachedAxis; 0194 QString axisName; 0195 bool showMeanValue; 0196 QPen meanValuePen; 0197 bool showLowerErrorIndicator; 0198 bool showUpperErrorIndicator; 0199 QPen errorIndicatorPen; 0200 ErrorCategory errorCategory; 0201 qreal errorPercentage; 0202 qreal errorMargin; 0203 qreal lowerErrorLimit; 0204 qreal upperErrorLimit; 0205 // Determines whether pen has been set 0206 bool penIsSet; 0207 // Determines whether brush has been set 0208 bool brushIsSet; 0209 QPen pen; 0210 QBrush brush; 0211 QMap<int, DataSet::ValueLabelType> valueLabelType; 0212 0213 KChart::PieAttributes pieAttributes; 0214 KChart::DataValueAttributes dataValueAttributes; 0215 0216 void readValueLabelType(KoStyleStack &styleStack, int section = -1); 0217 0218 // Note: Set section-specific attributes only if really necessary. 0219 // They will override the respective global attributes. 0220 QMap<int, QPen> pens; 0221 QMap<int, QBrush> brushes; 0222 QMap<int, KChart::PieAttributes> sectionsPieAttributes; 0223 QMap<int, KChart::DataValueAttributes> sectionsDataValueAttributes; 0224 0225 /// The number of this series is passed in the constructor and after 0226 /// that never changes. 0227 const int num; 0228 0229 // The different CellRegions for a dataset 0230 // Note: These are all 1-dimensional, i.e. vectors. 0231 CellRegion labelDataRegion; // one cell that holds the label 0232 CellRegion yDataRegion; // normal y values 0233 CellRegion xDataRegion; // x values -- only for scatter & bubble charts 0234 CellRegion customDataRegion;// used for bubble width in bubble charts 0235 // FIXME: Remove category region from DataSet - this is not the place 0236 // it belongs to. 0237 CellRegion categoryDataRegion; // x labels -- same for all datasets 0238 0239 KChartModel *kdChartModel; 0240 0241 int size; 0242 0243 /// Used if no data region for the label is specified 0244 const QString defaultLabel; 0245 // TODO markers can also apply to chart:data-point>, <chart:series> or <chart:plot-area> 0246 int symbolID; 0247 OdfSymbolType odfSymbolType; // Used for markers 0248 bool markersUsed; 0249 0250 int loadedDimensions; 0251 0252 KoOdfNumberStyles::NumericStyleFormat *numericStyleFormat; 0253 }; 0254 0255 DataSet::Private::Private(DataSet *parent, int dataSetNr) : 0256 parent(parent), 0257 chartType(LastChartType), 0258 chartSubType(NoChartSubtype), 0259 attachedAxis(0), 0260 showMeanValue(false), 0261 showLowerErrorIndicator(false), 0262 showUpperErrorIndicator(false), 0263 errorPercentage(0.0), 0264 errorMargin(0.0), 0265 lowerErrorLimit(0.0), 0266 upperErrorLimit(0.0), 0267 penIsSet(false), 0268 brushIsSet(false), 0269 pen(QPen(Qt::black)), 0270 brush(QColor(Qt::white)), 0271 dataValueAttributes(defaultDataValueAttributes()), 0272 num(dataSetNr), 0273 kdChartModel(0), 0274 size(0), 0275 defaultLabel(i18n("Series %1", dataSetNr + 1)), 0276 symbolID(0), 0277 odfSymbolType(AutomaticSymbol), 0278 markersUsed(false), 0279 loadedDimensions(0), 0280 numericStyleFormat(0) 0281 { 0282 } 0283 0284 DataSet::Private::~Private() 0285 { 0286 delete numericStyleFormat; 0287 } 0288 0289 KChart::MarkerAttributes DataSet::Private::defaultMarkerAttributes() const 0290 { 0291 KChart::MarkerAttributes ma; 0292 // Don't show markers unless we turn them on 0293 ma.setVisible(false); 0294 // The marker size is specified in pixels, but scaled by the painter's zoom level 0295 ma.setMarkerSizeMode(KChart::MarkerAttributes::AbsoluteSizeScaled); 0296 return ma; 0297 } 0298 0299 KChart::DataValueAttributes DataSet::Private::defaultDataValueAttributes() const 0300 { 0301 KChart::DataValueAttributes attr; 0302 KChart::TextAttributes textAttr = attr.textAttributes(); 0303 // Don't show value labels by default 0304 textAttr.setVisible(false); 0305 KChart::Measure fontSize = textAttr.fontSize(); 0306 attr.setMarkerAttributes(defaultMarkerAttributes()); 0307 fontSize.setValue(10); 0308 // Don't change font size with chart size 0309 fontSize.setCalculationMode(KChartEnums::MeasureCalculationModeAbsolute); 0310 textAttr.setFontSize(fontSize); 0311 // Draw text horizontally 0312 textAttr.setRotation(0); 0313 attr.setTextAttributes(textAttr); 0314 // Set positive value position 0315 KChart::RelativePosition positivePosition = attr.positivePosition(); 0316 if (chartType == KoChart::BarChartType && chartSubType != KoChart::NormalChartSubtype) { 0317 positivePosition.setAlignment(Qt::AlignCenter); 0318 positivePosition.setReferencePosition(KChartEnums::PositionCenter); 0319 } 0320 else if (chartType == KoChart::BarChartType && chartSubType == KoChart::NormalChartSubtype) { 0321 positivePosition.setAlignment(Qt::AlignHCenter | Qt::AlignTop); 0322 positivePosition.setReferencePosition(KChartEnums::PositionNorth); 0323 } 0324 else { 0325 positivePosition.setAlignment(Qt::AlignHCenter | Qt::AlignTop); 0326 positivePosition.setReferencePosition(KChartEnums::PositionNorthWest); 0327 } 0328 positivePosition.setHorizontalPadding(0.0); 0329 positivePosition.setVerticalPadding(-100.0); 0330 attr.setPositivePosition(positivePosition); 0331 0332 // Set negative value position 0333 KChart::RelativePosition negativePosition = attr.negativePosition(); 0334 if (chartType == KoChart::BarChartType && chartSubType != KoChart::NormalChartSubtype) { 0335 negativePosition.setAlignment(Qt::AlignCenter); 0336 negativePosition.setReferencePosition(KChartEnums::PositionCenter); 0337 } 0338 else if (chartType == KoChart::BarChartType && chartSubType == KoChart::NormalChartSubtype) { 0339 negativePosition.setAlignment(Qt::AlignHCenter | Qt::AlignBottom); 0340 negativePosition.setReferencePosition(KChartEnums::PositionSouth); 0341 } 0342 else { 0343 negativePosition.setAlignment(Qt::AlignHCenter | Qt::AlignBottom); 0344 negativePosition.setReferencePosition(KChartEnums::PositionSouthWest); 0345 } 0346 negativePosition.setHorizontalPadding(0.0); 0347 negativePosition.setVerticalPadding(100.0); 0348 attr.setNegativePosition(negativePosition); 0349 0350 // No decimal digits by default 0351 attr.setDecimalDigits(0); 0352 // Show all values, even if they overlap 0353 attr.setShowOverlappingDataLabels(true); 0354 // Yes, data point labels can repeatedly have the same text. (e.g. the same value) 0355 attr.setShowRepetitiveDataLabels(true); 0356 0357 attr.setVisible(true); 0358 0359 return attr; 0360 } 0361 0362 void DataSet::Private::insertDataValueAttributeSectionIfNecessary(int section) 0363 { 0364 Q_ASSERT(section >= 0); 0365 if (!sectionsDataValueAttributes.contains(section)) 0366 sectionsDataValueAttributes[section] = dataValueAttributes; 0367 } 0368 0369 void DataSet::Private::updateSize() 0370 { 0371 int newSize = 0; 0372 newSize = qMax(newSize, xDataRegion.cellCount()); 0373 newSize = qMax(newSize, yDataRegion.cellCount()); 0374 newSize = qMax(newSize, customDataRegion.cellCount()); 0375 newSize = qMax(newSize, categoryDataRegion.cellCount()); 0376 0377 if (size != newSize) { 0378 size = newSize; 0379 if (kdChartModel) 0380 kdChartModel->dataSetSizeChanged(parent, size); 0381 } 0382 } 0383 0384 bool DataSet::Private::hasOwnChartType() const 0385 { 0386 return chartType != LastChartType; 0387 } 0388 0389 0390 /** 0391 * Returns the effective chart type of this data set, i.e. 0392 * returns the chart type of the diagram this data set is 0393 * attached to if no chart type is set, or otherwise this data 0394 * set's chart type. 0395 */ 0396 ChartType DataSet::Private::effectiveChartType() const 0397 { 0398 if (hasOwnChartType()) 0399 return chartType; 0400 0401 Q_ASSERT(attachedAxis); 0402 return attachedAxis->plotArea()->chartType(); 0403 } 0404 0405 bool DataSet::Private::isValidDataPoint(const QPoint &point) const 0406 { 0407 if (point.y() < 0 || point.x() < 0) 0408 return false; 0409 0410 // We can't point to horizontal and vertical header data at the same time 0411 if (point.x() == 0 && point.y() == 0) 0412 return false; 0413 0414 return true; 0415 } 0416 0417 QVariant DataSet::Private::data(const CellRegion ®ion, int index, int role) const 0418 { 0419 if (!region.isValid()) 0420 return QVariant(); 0421 if (!region.hasPointAtIndex(index)) 0422 return QVariant(); 0423 0424 // Convert the given index in this dataset to a data point in the 0425 // source model. 0426 QPoint dataPoint = region.pointAtIndex(index); 0427 Table *table = region.table(); 0428 Q_ASSERT(table); 0429 QAbstractItemModel *model = table->model(); 0430 // This means the table the region lies in has been removed, but nobody 0431 // has changed the region in the meantime. That is a perfectly valid 0432 // scenario, so just return invalid data. 0433 if (!model) 0434 return QVariant(); 0435 0436 // Check if the data point is valid 0437 const bool validDataPoint = isValidDataPoint(dataPoint); 0438 0439 // Remove, since it makes Calligra Sheets crash when inserting a chart for 0440 // a 1x1 cell region. 0441 //Q_ASSERT(validDataPoint); 0442 if (!validDataPoint) 0443 return QVariant(); 0444 0445 // The top-left point is (1,1). (0,y) or (x,0) refers to header data. 0446 const bool verticalHeaderData = dataPoint.x() == 0; 0447 const bool horizontalHeaderData = dataPoint.y() == 0; 0448 const int row = dataPoint.y() - 1; 0449 const int col = dataPoint.x() - 1; 0450 0451 QVariant data; 0452 if (verticalHeaderData) 0453 data = model->headerData(row, Qt::Vertical, role); 0454 else if (horizontalHeaderData) 0455 data = model->headerData(col, Qt::Horizontal, role); 0456 else { 0457 const QModelIndex &index = model->index(row, col); 0458 //Q_ASSERT(index.isValid()); 0459 if (index.isValid()) 0460 data = model->data(index, role); 0461 } 0462 return data; 0463 } 0464 0465 QString DataSet::Private::formatData(const CellRegion ®ion, int index, int role) const 0466 { 0467 QVariant v = data(region, index, role); 0468 QString s; 0469 if (v.type() == QVariant::Double) { 0470 // Don't use v.toString() else a double/float would lose precision 0471 // and something like "36.5207" would become "36.520660888888912". 0472 QTextStream ts(&s); 0473 //ts.setRealNumberNotation(QTextStream::FixedNotation); 0474 //ts.setRealNumberPrecision(); 0475 ts << v.toDouble(); 0476 } else { 0477 s = v.toString(); 0478 } 0479 return numericStyleFormat ? KoOdfNumberStyles::format(s, *numericStyleFormat) : s; 0480 } 0481 0482 QBrush DataSet::Private::defaultBrush() const 0483 { 0484 Qt::Orientation modelDataDirection = kdChartModel->dataDirection(); 0485 // A data set-wide default brush only makes sense if the legend shows 0486 // data set labels, not the category data. See notes on data directions 0487 // in KChartModel.h for details. 0488 if (modelDataDirection == Qt::Vertical) 0489 return defaultDataSetColor(num); 0490 // FIXME: What to return in the other case? 0491 return QBrush(); 0492 } 0493 0494 QBrush DataSet::Private::defaultBrush(int section) const 0495 { 0496 Qt::Orientation modelDataDirection = kdChartModel->dataDirection(); 0497 // Horizontally aligned diagrams have a specific color per category 0498 // See for example pie or ring charts. A pie chart contains a single 0499 // data set, but the slices default to different brushes. 0500 if (modelDataDirection == Qt::Horizontal) 0501 return defaultDataSetColor(section); 0502 // Vertically aligned diagrams default to one brush per data set 0503 return defaultBrush(); 0504 } 0505 0506 QPen DataSet::Private::defaultPen() const 0507 { 0508 QPen pen(Qt::black); 0509 ChartType chartType = effectiveChartType(); 0510 if (chartType == LineChartType || 0511 chartType == ScatterChartType) { 0512 if (penIsSet) { 0513 pen = pen; 0514 } else { 0515 pen = QPen(defaultDataSetColor(num)); 0516 } 0517 } 0518 return pen; 0519 } 0520 0521 0522 DataSet::DataSet(int dataSetNr) 0523 : d(new Private(this, dataSetNr)) 0524 { 0525 Q_ASSERT(dataSetNr >= 0); 0526 } 0527 0528 DataSet::~DataSet() 0529 { 0530 if (d->attachedAxis) 0531 d->attachedAxis->detachDataSet(this, true); 0532 0533 delete d; 0534 } 0535 0536 0537 ChartType DataSet::chartType() const 0538 { 0539 return d->chartType; 0540 } 0541 0542 ChartSubtype DataSet::chartSubType() const 0543 { 0544 return d->chartSubType; 0545 } 0546 0547 Axis *DataSet::attachedAxis() const 0548 { 0549 return d->attachedAxis; 0550 } 0551 0552 bool DataSet::showMeanValue() const 0553 { 0554 return d->showMeanValue; 0555 } 0556 0557 QPen DataSet::meanValuePen() const 0558 { 0559 return d->meanValuePen; 0560 } 0561 0562 bool DataSet::showLowerErrorIndicator() const 0563 { 0564 return d->showLowerErrorIndicator; 0565 } 0566 0567 bool DataSet::showUpperErrorIndicator() const 0568 { 0569 return d->showUpperErrorIndicator; 0570 } 0571 0572 QPen DataSet::errorIndicatorPen() const 0573 { 0574 return d->errorIndicatorPen; 0575 } 0576 0577 ErrorCategory DataSet::errorCategory() const 0578 { 0579 return d->errorCategory; 0580 } 0581 0582 qreal DataSet::errorPercentage() const 0583 { 0584 return d->errorPercentage; 0585 } 0586 0587 qreal DataSet::errorMargin() const 0588 { 0589 return d->errorMargin; 0590 } 0591 0592 qreal DataSet::lowerErrorLimit() const 0593 { 0594 return d->lowerErrorLimit; 0595 } 0596 0597 qreal DataSet::upperErrorLimit() const 0598 { 0599 return d->upperErrorLimit; 0600 } 0601 0602 void DataSet::Private::setAttributesAccordingToType() 0603 { 0604 KChart::DataValueAttributes attr = dataValueAttributes; 0605 KChart::RelativePosition positivePosition = attr.positivePosition(); 0606 if (chartType == KoChart::BarChartType && chartSubType != KoChart::NormalChartSubtype) { 0607 positivePosition.setAlignment(Qt::AlignCenter); 0608 positivePosition.setReferencePosition(KChartEnums::PositionCenter); 0609 } 0610 else if (chartType == KoChart::BarChartType && chartSubType == KoChart::NormalChartSubtype) { 0611 positivePosition.setAlignment(Qt::AlignHCenter | Qt::AlignTop); 0612 positivePosition.setReferencePosition(KChartEnums::PositionNorth); 0613 } 0614 else { 0615 positivePosition.setAlignment(Qt::AlignHCenter | Qt::AlignTop); 0616 positivePosition.setReferencePosition(KChartEnums::PositionNorthWest); 0617 } 0618 positivePosition.setHorizontalPadding(0.0); 0619 positivePosition.setVerticalPadding(-100.0); 0620 attr.setPositivePosition(positivePosition); 0621 0622 // Set negative value position 0623 KChart::RelativePosition negativePosition = attr.negativePosition(); 0624 if (chartType == KoChart::BarChartType && chartSubType != KoChart::NormalChartSubtype) { 0625 negativePosition.setAlignment(Qt::AlignCenter); 0626 negativePosition.setReferencePosition(KChartEnums::PositionCenter); 0627 } 0628 else if (chartType == KoChart::BarChartType && chartSubType == KoChart::NormalChartSubtype) { 0629 negativePosition.setAlignment(Qt::AlignHCenter | Qt::AlignBottom); 0630 negativePosition.setReferencePosition(KChartEnums::PositionSouth); 0631 } 0632 else { 0633 negativePosition.setAlignment(Qt::AlignHCenter | Qt::AlignBottom); 0634 negativePosition.setReferencePosition(KChartEnums::PositionSouthWest); 0635 } 0636 negativePosition.setHorizontalPadding(0.0); 0637 negativePosition.setVerticalPadding(100.0); 0638 attr.setNegativePosition(negativePosition); 0639 dataValueAttributes = attr; 0640 0641 for (int i = 0; i < sectionsDataValueAttributes.count(); ++i) { 0642 Q_ASSERT(sectionsDataValueAttributes.contains(i)); 0643 KChart::DataValueAttributes attr = sectionsDataValueAttributes[i]; 0644 KChart::RelativePosition positivePosition = attr.positivePosition(); 0645 if (chartType == KoChart::BarChartType && chartSubType != KoChart::NormalChartSubtype) { 0646 positivePosition.setAlignment(Qt::AlignCenter); 0647 positivePosition.setReferencePosition(KChartEnums::PositionCenter); 0648 } 0649 else if (chartType == KoChart::BarChartType && chartSubType == KoChart::NormalChartSubtype) { 0650 positivePosition.setAlignment(Qt::AlignHCenter | Qt::AlignTop); 0651 positivePosition.setReferencePosition(KChartEnums::PositionNorth); 0652 } 0653 else { 0654 positivePosition.setAlignment(Qt::AlignHCenter | Qt::AlignTop); 0655 positivePosition.setReferencePosition(KChartEnums::PositionNorthWest); 0656 } 0657 positivePosition.setHorizontalPadding(0.0); 0658 positivePosition.setVerticalPadding(-100.0); 0659 attr.setPositivePosition(positivePosition); 0660 0661 // Set negative value position 0662 KChart::RelativePosition negativePosition = attr.negativePosition(); 0663 if (chartType == KoChart::BarChartType && chartSubType != KoChart::NormalChartSubtype) { 0664 negativePosition.setAlignment(Qt::AlignCenter); 0665 negativePosition.setReferencePosition(KChartEnums::PositionCenter); 0666 } 0667 else if (chartType == KoChart::BarChartType && chartSubType == KoChart::NormalChartSubtype) { 0668 negativePosition.setAlignment(Qt::AlignHCenter | Qt::AlignBottom); 0669 negativePosition.setReferencePosition(KChartEnums::PositionSouth); 0670 } 0671 else { 0672 negativePosition.setAlignment(Qt::AlignHCenter | Qt::AlignBottom); 0673 negativePosition.setReferencePosition(KChartEnums::PositionSouthWest); 0674 } 0675 negativePosition.setHorizontalPadding(0.0); 0676 negativePosition.setVerticalPadding(100.0); 0677 attr.setNegativePosition(negativePosition); 0678 sectionsDataValueAttributes[i] = attr; 0679 } 0680 } 0681 0682 0683 void DataSet::setChartType(ChartType type) 0684 { 0685 if (type == d->chartType) 0686 return; 0687 0688 Axis *axis = d->attachedAxis; 0689 if (axis) 0690 axis->detachDataSet(this); 0691 0692 d->chartType = type; 0693 d->setAttributesAccordingToType(); 0694 0695 if (axis) 0696 axis->attachDataSet(this); 0697 0698 switch (type) { 0699 case LineChartType: 0700 case AreaChartType: 0701 case ScatterChartType: 0702 case RadarChartType: 0703 case FilledRadarChartType: 0704 d->markersUsed = true; 0705 break; 0706 default: 0707 d->markersUsed = false; 0708 break; 0709 } 0710 } 0711 0712 void DataSet::setChartSubType(ChartSubtype subType) 0713 { 0714 if (subType == d->chartSubType) 0715 return; 0716 0717 Axis *axis = d->attachedAxis; 0718 axis->detachDataSet(this); 0719 0720 d->chartSubType = subType; 0721 d->setAttributesAccordingToType(); 0722 0723 axis->attachDataSet(this); 0724 } 0725 0726 0727 void DataSet::setAttachedAxis(Axis *axis) 0728 { 0729 d->attachedAxis = axis; 0730 } 0731 0732 QPen DataSet::pen() const 0733 { 0734 return d->penIsSet ? d->pen : d->defaultPen(); 0735 } 0736 0737 QBrush DataSet::brush() const 0738 { 0739 return d->brushIsSet ? d->brush : d->defaultBrush(); 0740 } 0741 0742 OdfMarkerStyle DataSet::markerStyle() const 0743 { 0744 OdfMarkerStyle style = (OdfMarkerStyle)(d->symbolID); 0745 return style; 0746 } 0747 0748 QIcon DataSet::markerIcon(OdfMarkerStyle markerStyle) 0749 { 0750 QPixmap markerPixmap(16,16); 0751 markerPixmap.fill(QColor(255,255,255,0)); 0752 QPainter painter(&markerPixmap); 0753 KChart::MarkerAttributes matt; 0754 matt.setMarkerStyle(odf2kdMarker(markerStyle)); 0755 MarkerPainterDummyDiagram().doPaintMarker(&painter, matt, brush(), pen(), QPointF(7,7), QSizeF(12,12)); 0756 QIcon markerIcon = QIcon(markerPixmap); 0757 return markerIcon; 0758 } 0759 0760 QPen DataSet::pen(int section) const 0761 { 0762 if (d->pens.contains(section)) 0763 return d->pens[section]; 0764 return pen(); 0765 } 0766 0767 KChart::PieAttributes DataSet::pieAttributes() const 0768 { 0769 return d->pieAttributes; 0770 } 0771 0772 QBrush DataSet::brush(int section) const 0773 { 0774 Qt::Orientation modelDataDirection = d->kdChartModel->dataDirection(); 0775 // Horizontally aligned diagrams have a specific color per category 0776 // See for example pie or ring charts. A pie chart contains a single 0777 // data set, but the slices must have different brushes. 0778 if (modelDataDirection == Qt::Horizontal) { 0779 if (d->brushes.contains(section)) { 0780 return d->brushes[section]; 0781 } 0782 return d->defaultBrush(section); 0783 } 0784 // Vertically aligned diagrams only have one brush per data set 0785 return brush(); 0786 } 0787 0788 KChart::PieAttributes DataSet::pieAttributes(int section) const 0789 { 0790 if(d->sectionsPieAttributes.contains(section)) 0791 return d->sectionsPieAttributes[section]; 0792 return pieAttributes(); 0793 } 0794 0795 qreal DataSet::Private::maxBubbleSize() const 0796 { 0797 // TODO: Improve performance by caching. This is currently O(n^2). 0798 // this is not in O(n^2), its quite linear on the number of datapoints 0799 // however it could be constant for any then the first case by implementing 0800 // cashing 0801 qreal max = 0.0; 0802 Q_ASSERT(kdChartModel); 0803 QList<DataSet*> dataSets = kdChartModel->dataSets(); 0804 foreach(DataSet *dataSet, dataSets) 0805 for (int i = 0; i < dataSet->size(); i++) 0806 max = qMax(max, dataSet->customData(i).toReal()); 0807 return max; 0808 } 0809 0810 KChart::DataValueAttributes DataSet::dataValueAttributes(int section /* = -1 */) const 0811 { 0812 KChart::DataValueAttributes attr(d->dataValueAttributes); 0813 Q_ASSERT(attr.isVisible() == d->dataValueAttributes.isVisible()); 0814 if (d->sectionsDataValueAttributes.contains(section)) 0815 attr = d->sectionsDataValueAttributes[section]; 0816 0817 /* 0818 * Update attributes that are related to properties out of the data 0819 * sets's reach and thus might have changed in the meanwhile. 0820 */ 0821 KChart::MarkerAttributes ma(attr.markerAttributes()); 0822 0823 // The chart type is a property of the plot area, check that. 0824 switch (d->effectiveChartType()) { 0825 case BarChartType: 0826 case CircleChartType: 0827 case RingChartType: 0828 case StockChartType: 0829 { 0830 Q_ASSERT(attr.isVisible()); 0831 ma.setMarkerStyle(KChart::MarkerAttributes::MarkerSquare); 0832 ma.setMarkerSize(QSize(10, 10)); 0833 ma.setVisible(true); // For legend 0834 break; 0835 } 0836 case BubbleChartType: 0837 { 0838 Q_ASSERT(attachedAxis()); 0839 Q_ASSERT(attachedAxis()->plotArea()); 0840 ma.setMarkerStyle(KChart::MarkerAttributes::MarkerCircle); 0841 ma.setThreeD(attachedAxis()->plotArea()->isThreeD()); 0842 qreal maxSize = d->maxBubbleSize(); 0843 if (section >= 0) { 0844 qreal bubbleWidth = customData(section).toReal(); 0845 // All bubble sizes are relative to the maximum bubble size 0846 if (maxSize != 0.0) 0847 bubbleWidth /= maxSize; 0848 // Whereas the maximum size is relative to 1/4 * min(dw, dh), 0849 // with dw, dh being the width and height of the diagram 0850 bubbleWidth *= 0.25; 0851 ma.setMarkerSizeMode(KChart::MarkerAttributes::RelativeToDiagramWidthHeightMin); 0852 ma.setMarkerSize(QSizeF(bubbleWidth, bubbleWidth)); 0853 } 0854 ma.setVisible(true); 0855 break; 0856 } 0857 default: 0858 Q_ASSERT(attr.isVisible()); 0859 switch (d->odfSymbolType) { 0860 case NoSymbol: 0861 ma.setVisible(false); 0862 break; 0863 case ImageSymbol: // Not supported 0864 case AutomaticSymbol: 0865 ma.setMarkerStyle(odf2kdMarker((OdfMarkerStyle)(d->num % numDefaultMarkerTypes))); 0866 ma.setMarkerSize(QSize(10, 10)); 0867 ma.setVisible(true); 0868 case NamedSymbol: 0869 ma.setMarkerStyle(odf2kdMarker((OdfMarkerStyle)d->symbolID)); 0870 ma.setVisible(true); 0871 break; 0872 } 0873 break; 0874 } 0875 0876 ma.setMarkerColor(brush(section).color()); 0877 ma.setPen(pen(section)); 0878 0879 QLocale locale; 0880 QString dataLabel = ""; // must not be isNull() because then KChart uses a default text 0881 ValueLabelType type = valueLabelType(section); 0882 if (type.category) { 0883 QString s = categoryData(section, Qt::DisplayRole).toString().trimmed(); 0884 if (!s.isEmpty()) dataLabel += s + QLatin1Char(' '); 0885 } 0886 if (type.number) { 0887 QString s; 0888 if (d->effectiveChartType() == BubbleChartType) { 0889 s = d->formatData(d->customDataRegion, section, Qt::DisplayRole); 0890 } else { 0891 s = d->formatData(d->yDataRegion, section, Qt::DisplayRole); 0892 } 0893 if (!s.isEmpty()) dataLabel += s + QLatin1Char(' '); 0894 } 0895 if (type.percentage) { 0896 bool ok; 0897 qreal value; 0898 if (d->effectiveChartType() == BubbleChartType) { 0899 value = customData(section, Qt::EditRole).toDouble(&ok); 0900 if (ok) { 0901 qreal sum = 0.0; 0902 for(int i = 0; i < d->customDataRegion.cellCount(); ++i) { 0903 sum += customData(i, Qt::EditRole).toDouble(); 0904 } 0905 if (sum == 0.0) 0906 ok = false; 0907 else 0908 value = value / sum * 100.0; 0909 } 0910 } else { 0911 value = yData(section, Qt::EditRole).toDouble(&ok); 0912 if (ok) { 0913 qreal sum = 0.0; 0914 for(int i = 0; i < d->yDataRegion.cellCount(); ++i) { 0915 sum += yData(i, Qt::EditRole).toDouble(); 0916 } 0917 if (sum == 0.0) 0918 ok = false; 0919 else 0920 value = value / sum * 100.0; 0921 } 0922 } 0923 if (ok) 0924 dataLabel += locale.toString(value, 'f', 0) + locale.percent(); 0925 } 0926 attr.setDataLabel(dataLabel.trimmed()); 0927 0928 attr.setMarkerAttributes(ma); 0929 0930 return attr; 0931 } 0932 0933 KChart::MarkerAttributes DataSet::getMarkerAttributes(int section) const 0934 { 0935 KChart::DataValueAttributes attr(d->dataValueAttributes); 0936 Q_ASSERT(attr.isVisible() == d->dataValueAttributes.isVisible()); 0937 if (d->sectionsDataValueAttributes.contains(section)) 0938 attr = d->sectionsDataValueAttributes[section]; 0939 0940 KChart::MarkerAttributes ma(attr.markerAttributes()); 0941 ma.setMarkerStyle(odf2kdMarker((OdfMarkerStyle)d->symbolID)); 0942 ma.setMarkerSize(QSize(10, 10)); 0943 ma.setVisible(true); 0944 0945 return ma; 0946 } 0947 0948 void DataSet::setMarkerAttributes(const KChart::MarkerAttributes &attribs, int section) 0949 { 0950 KChart::DataValueAttributes attr(d->dataValueAttributes); 0951 Q_ASSERT(attr.isVisible() == d->dataValueAttributes.isVisible()); 0952 if (d->sectionsDataValueAttributes.contains(section)) 0953 attr = d->sectionsDataValueAttributes[section]; 0954 0955 attr.setMarkerAttributes(attribs); 0956 d->dataValueAttributes = attr; 0957 } 0958 0959 OdfSymbolType DataSet::odfSymbolType() const 0960 { 0961 return d->odfSymbolType; 0962 } 0963 0964 void DataSet::setOdfSymbolType(OdfSymbolType type) 0965 { 0966 d->odfSymbolType = type; 0967 } 0968 0969 void DataSet::setPen(const QPen &pen) 0970 { 0971 d->pen = pen; 0972 d->penIsSet = true; 0973 if (d->kdChartModel) 0974 d->kdChartModel->dataSetChanged(this); 0975 // KChart::MarkerAttributes ma(d->dataValueAttributes.markerAttributes()); 0976 // ma.setPen(pen); 0977 // d->dataValueAttributes.setMarkerAttributes(ma); 0978 // for (QMap< int, KChart::DataValueAttributes >::iterator it = d->sectionsDataValueAttributes.begin(); 0979 // it != d->sectionsDataValueAttributes.end(); ++it){ 0980 // KChart::MarkerAttributes mattr(it->markerAttributes()); 0981 // mattr.setMarkerColor(pen.color()); 0982 // it->setMarkerAttributes(mattr); 0983 // } 0984 0985 } 0986 0987 void DataSet::setBrush(const QBrush &brush) 0988 { 0989 d->brush = brush; 0990 d->brushIsSet = true; 0991 if (d->kdChartModel) 0992 d->kdChartModel->dataSetChanged(this); 0993 // KChart::MarkerAttributes ma(d->dataValueAttributes.markerAttributes()); 0994 // ma.setMarkerColor(brush.color()); 0995 // d->dataValueAttributes.setMarkerAttributes(ma); 0996 // for (QMap< int, KChart::DataValueAttributes >::iterator it = d->sectionsDataValueAttributes.begin(); 0997 // it != d->sectionsDataValueAttributes.end(); ++it){ 0998 // KChart::MarkerAttributes mattr(it->markerAttributes()); 0999 // mattr.setMarkerColor(brush.color()); 1000 // it->setMarkerAttributes(mattr); 1001 // } 1002 } 1003 1004 void DataSet::setPieExplodeFactor(int factor) 1005 { 1006 d->pieAttributes.setExplodeFactor((qreal)factor / (qreal)100); 1007 if(d->kdChartModel) 1008 d->kdChartModel->dataSetChanged(this); 1009 } 1010 1011 void DataSet::setPen(int section, const QPen &pen) 1012 { 1013 if (section < 0) { 1014 setPen(pen); 1015 return; 1016 } 1017 d->pens[section] = pen; 1018 if (d->kdChartModel) 1019 d->kdChartModel->dataSetChanged(this, KChartModel::PenDataRole, section); 1020 d->insertDataValueAttributeSectionIfNecessary(section); 1021 // KChart::MarkerAttributes mas(d->sectionsDataValueAttributes[section].markerAttributes()); 1022 // mas.setPen(pen); 1023 // d->sectionsDataValueAttributes[section].setMarkerAttributes(mas); 1024 } 1025 1026 void DataSet::setBrush(int section, const QBrush &brush) 1027 { 1028 if (section < 0) { 1029 setBrush(brush); 1030 return; 1031 } 1032 d->brushes[section] = brush; 1033 if (d->kdChartModel) 1034 d->kdChartModel->dataSetChanged(this, KChartModel::BrushDataRole, section); 1035 d->insertDataValueAttributeSectionIfNecessary(section); 1036 // KChart::MarkerAttributes mas(d->sectionsDataValueAttributes[section].markerAttributes()); 1037 // mas.setMarkerColor(brush.color()); 1038 // d->sectionsDataValueAttributes[section].setMarkerAttributes(mas); 1039 } 1040 1041 void DataSet::setMarkerStyle(OdfMarkerStyle style) 1042 { 1043 KChart::MarkerAttributes matt = getMarkerAttributes(); 1044 matt.setMarkerStyle(odf2kdMarker(style)); 1045 setMarkerAttributes(matt); 1046 1047 d->symbolID = style; 1048 } 1049 1050 void DataSet::setPieExplodeFactor(int section, int factor) 1051 { 1052 if (section < 0) { 1053 setPieExplodeFactor(factor); 1054 return; 1055 } 1056 KChart::PieAttributes &pieAttributes = d->sectionsPieAttributes[section]; 1057 pieAttributes.setExplodeFactor((qreal)factor / (qreal)100); 1058 if (d->kdChartModel) 1059 d->kdChartModel->dataSetChanged(this, KChartModel::PieAttributesRole, section); 1060 } 1061 1062 int DataSet::number() const 1063 { 1064 return d->num; 1065 } 1066 1067 void DataSet::setShowMeanValue(bool show) 1068 { 1069 d->showMeanValue = show; 1070 } 1071 1072 void DataSet::setMeanValuePen(const QPen &pen) 1073 { 1074 d->meanValuePen = pen; 1075 } 1076 1077 void DataSet::setShowLowerErrorIndicator(bool show) 1078 { 1079 d->showLowerErrorIndicator = show; 1080 } 1081 1082 void DataSet::setShowUpperErrorIndicator(bool show) 1083 { 1084 d->showUpperErrorIndicator = show; 1085 } 1086 1087 void DataSet::setShowErrorIndicators(bool lower, bool upper) 1088 { 1089 setShowLowerErrorIndicator(lower); 1090 setShowUpperErrorIndicator(upper); 1091 } 1092 1093 void DataSet::setErrorIndicatorPen(const QPen &pen) 1094 { 1095 d->errorIndicatorPen = pen; 1096 } 1097 1098 void DataSet::setErrorCategory(ErrorCategory category) 1099 { 1100 d->errorCategory = category; 1101 } 1102 1103 void DataSet::setErrorPercentage(qreal percentage) 1104 { 1105 d->errorPercentage = percentage; 1106 } 1107 1108 void DataSet::setErrorMargin(qreal margin) 1109 { 1110 d->errorMargin = margin; 1111 } 1112 1113 void DataSet::setLowerErrorLimit(qreal limit) 1114 { 1115 d->lowerErrorLimit = limit; 1116 } 1117 1118 void DataSet::setUpperErrorLimit(qreal limit) 1119 { 1120 d->upperErrorLimit = limit; 1121 } 1122 1123 QVariant DataSet::xData(int index, int role) const 1124 { 1125 // Sometimes a bubble chart is created with a table with 4 columns. 1126 // What we do here is assign the 2 columns per data set, so we have 1127 // 2 data sets in total afterwards. The first column is y data, the second 1128 // bubble width. Same for the second data set. So there is nothing left 1129 // for x data. Instead use a fall-back to the data points index. 1130 // Note by danders: 1131 // ODF spec says bubble charts *must* have xdata, ydata and bubble width. 1132 // However LO allows for no xdata in which case it used data index (as we do here). 1133 QVariant data = d->data(d->xDataRegion, index, role); 1134 if (data.isValid() && data.canConvert< double >() && data.convert(QVariant::Double) ) 1135 return data; 1136 return QVariant(index + 1); 1137 } 1138 1139 QVariant DataSet::yData(int index, int role) const 1140 { 1141 // No fall-back necessary. y data region must be specified if needed. 1142 // (may also be part of 'domain' in ODF terms, but only in case of 1143 // scatter and bubble charts) 1144 return d->data(d->yDataRegion, index, role); 1145 } 1146 1147 QVariant DataSet::customData(int index, int role) const 1148 { 1149 // No fall-back necessary. ('custom' [1]) data region (part of 'domain' in 1150 // ODF terms) must be specified if needed. See ODF v1.1 §10.9.1 1151 return d->data(d->customDataRegion, index, role); 1152 // [1] In fact, 'custom' data only refers to the bubble width of bubble 1153 // charts at the moment. 1154 } 1155 1156 QVariant DataSet::categoryData(int index, int role) const 1157 { 1158 // There's no cell that holds this category's data 1159 // (i.e., the region is either too short or simply empty) 1160 // if (!d->categoryDataRegion.hasPointAtIndex(index)) 1161 // return QString::number(index + 1); 1162 1163 if (d->categoryDataRegion.rects().isEmpty()) { 1164 // There's no cell that holds this category's data 1165 // (i.e., the region is either too short or simply empty) 1166 return QString::number(index + 1); 1167 } 1168 1169 foreach (const QRect &rect, d->categoryDataRegion.rects()) { 1170 if (rect.width() == 1 || rect.height() == 1) { 1171 // Handle the clear case of either horizontal or vertical 1172 // ranges with only one row/column. 1173 const QVariant data = d->data(d->categoryDataRegion, index, role); 1174 if (data.isValid()) 1175 return data; 1176 } else { 1177 // Operate on the last row in the defined in the categoryDataRegion. 1178 // If multiple rows are given then we would need to build up multiple 1179 // lines of category labels. Each line of labels would represent 1180 // one row. If the category labels are displayed at the x-axis below 1181 // the chart then the last row will be displayed as first label line, 1182 // the row before the last row would be displayed as second label 1183 // line below the first line and so on. Since we don't support 1184 // multiple label lines for categories yet we only display the last 1185 // row aka the very first label line. 1186 CellRegion c(d->categoryDataRegion.table(), QRect(rect.x(), rect.bottom(), rect.width(), 1)); 1187 const QVariant data = d->data(c, index, role); 1188 if (data.isValid() /* && !data.toString().isEmpty() */) 1189 return data; 1190 } 1191 } 1192 1193 // The cell is empty 1194 return QString(""); 1195 } 1196 1197 QVariant DataSet::labelData() const 1198 { 1199 QString label; 1200 if (d->labelDataRegion.isValid()) { 1201 const int cellCount = d->labelDataRegion.cellCount(); 1202 for (int i = 0; i < cellCount; i++) { 1203 QString s = d->data(d->labelDataRegion, i, Qt::EditRole).toString(); 1204 if (!s.isEmpty()) { 1205 if (!label.isEmpty()) 1206 label += QLatin1Char(' '); 1207 label += s; 1208 } 1209 } 1210 } 1211 if (label.isEmpty()) { 1212 label = d->defaultLabel; 1213 } 1214 return QVariant(label); 1215 } 1216 1217 QString DataSet::defaultLabelData() const 1218 { 1219 return d->defaultLabel; 1220 } 1221 1222 CellRegion DataSet::xDataRegion() const 1223 { 1224 return d->xDataRegion; 1225 } 1226 1227 CellRegion DataSet::yDataRegion() const 1228 { 1229 return d->yDataRegion; 1230 } 1231 1232 CellRegion DataSet::customDataRegion() const 1233 { 1234 return d->customDataRegion; 1235 } 1236 1237 CellRegion DataSet::categoryDataRegion() const 1238 { 1239 return d->categoryDataRegion; 1240 } 1241 1242 CellRegion DataSet::labelDataRegion() const 1243 { 1244 return d->labelDataRegion; 1245 } 1246 1247 1248 void DataSet::setXDataRegion(const CellRegion ®ion) 1249 { 1250 d->xDataRegion = region; 1251 d->updateSize(); 1252 1253 if (d->kdChartModel) 1254 d->kdChartModel->dataSetChanged(this, KChartModel::XDataRole); 1255 } 1256 1257 void DataSet::setYDataRegion(const CellRegion ®ion) 1258 { 1259 d->yDataRegion = region; 1260 d->updateSize(); 1261 1262 if (d->kdChartModel) 1263 d->kdChartModel->dataSetChanged(this, KChartModel::YDataRole); 1264 } 1265 1266 void DataSet::setCustomDataRegion(const CellRegion ®ion) 1267 { 1268 d->customDataRegion = region; 1269 d->updateSize(); 1270 1271 if (d->kdChartModel) 1272 d->kdChartModel->dataSetChanged(this, KChartModel::CustomDataRole); 1273 } 1274 1275 void DataSet::setCategoryDataRegion(const CellRegion ®ion) 1276 { 1277 d->categoryDataRegion = region; 1278 d->updateSize(); 1279 1280 if (d->kdChartModel) 1281 d->kdChartModel->dataSetChanged(this, KChartModel::CategoryDataRole); 1282 } 1283 1284 void DataSet::setLabelDataRegion(const CellRegion ®ion) 1285 { 1286 d->labelDataRegion = region; 1287 d->updateSize(); 1288 1289 if (d->kdChartModel) 1290 d->kdChartModel->dataSetChanged(this); 1291 } 1292 1293 1294 int DataSet::size() const 1295 { 1296 return qMax(1, d->size); 1297 } 1298 1299 void DataSet::Private::dataChanged(KChartModel::DataRole role, const QRect &rect) const 1300 { 1301 if (!kdChartModel) 1302 return; 1303 Q_UNUSED(rect); 1304 1305 // Stubbornly pretend like everything changed. This as well should be 1306 // refactored to be done in ChartProxyModel, then we can also fine-tune 1307 // it for performance. 1308 kdChartModel->dataSetChanged(parent, role, 0, size - 1); 1309 } 1310 1311 void DataSet::yDataChanged(const QRect ®ion) const 1312 { 1313 d->dataChanged(KChartModel::YDataRole, region); 1314 } 1315 1316 void DataSet::xDataChanged(const QRect ®ion) const 1317 { 1318 d->dataChanged(KChartModel::XDataRole, region); 1319 } 1320 1321 void DataSet::customDataChanged(const QRect ®ion) const 1322 { 1323 d->dataChanged(KChartModel::CustomDataRole, region); 1324 } 1325 1326 void DataSet::labelDataChanged(const QRect ®ion) const 1327 { 1328 d->dataChanged(KChartModel::LabelDataRole, region); 1329 } 1330 1331 void DataSet::categoryDataChanged(const QRect ®ion) const 1332 { 1333 d->dataChanged(KChartModel::CategoryDataRole, region); 1334 } 1335 1336 int DataSet::dimension() const 1337 { 1338 return numDimensions(d->effectiveChartType()); 1339 } 1340 1341 void DataSet::setKdChartModel(KChartModel *model) 1342 { 1343 d->kdChartModel = model; 1344 } 1345 1346 KChartModel *DataSet::kdChartModel() const 1347 { 1348 return d->kdChartModel; 1349 } 1350 1351 void DataSet::setValueLabelType(const ValueLabelType &type, int section /* = -1 */) 1352 { 1353 if (section >= 0) 1354 d->insertDataValueAttributeSectionIfNecessary(section); 1355 1356 d->valueLabelType[section] = type; 1357 1358 // This is a reference, not a copy! 1359 KChart::DataValueAttributes &attr = section >= 0 ? 1360 d->sectionsDataValueAttributes[section] : 1361 d->dataValueAttributes; 1362 1363 KChart::TextAttributes ta (attr.textAttributes()); 1364 1365 ta.setVisible(!type.noLabel()); 1366 1367 KChart::Measure m = ta.fontSize(); 1368 m.setValue(8); // same small font the legend is using 1369 ta.setFontSize(m); 1370 1371 attr.setTextAttributes(ta); 1372 1373 if (d->kdChartModel) { 1374 if (section >= 0) 1375 d->kdChartModel->dataSetChanged(this, KChartModel::DataValueAttributesRole, section); 1376 else 1377 d->kdChartModel->dataSetChanged(this); 1378 } 1379 } 1380 1381 DataSet::ValueLabelType DataSet::valueLabelType(int section /* = -1 */) const 1382 { 1383 if (d->valueLabelType.contains(section)) 1384 return d->valueLabelType[section]; 1385 if (d->valueLabelType.contains(-1)) 1386 return d->valueLabelType[-1]; 1387 return ValueLabelType(); 1388 } 1389 1390 bool loadBrushAndPen(KoStyleStack &styleStack, KoShapeLoadingContext &context, 1391 const KoXmlElement &n, QBrush& brush, bool& brushLoaded, QPen& pen, bool& penLoaded) 1392 { 1393 if (n.hasAttributeNS(KoXmlNS::chart, "style-name")) { 1394 KoOdfLoadingContext &odfLoadingContext = context.odfLoadingContext(); 1395 brushLoaded = false; 1396 penLoaded = false; 1397 1398 styleStack.setTypeProperties("graphic"); 1399 1400 if (styleStack.hasProperty(KoXmlNS::draw, "stroke")) { 1401 QString stroke = styleStack.property(KoXmlNS::draw, "stroke"); 1402 pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, stroke, odfLoadingContext.stylesReader()); 1403 penLoaded = true; 1404 } 1405 1406 if (styleStack.hasProperty(KoXmlNS::draw, "fill")) { 1407 QString fill = styleStack.property(KoXmlNS::draw, "fill"); 1408 if (fill == "solid" || fill == "hatch") { 1409 brush = KoOdfGraphicStyles::loadOdfFillStyle(styleStack, fill, odfLoadingContext.stylesReader()); 1410 brushLoaded = true; 1411 } else if (fill == "gradient") { 1412 brush = KoOdfGraphicStyles::loadOdfGradientStyle(styleStack, odfLoadingContext.stylesReader(), QSizeF(5.0, 60.0)); 1413 brushLoaded = true; 1414 } else if (fill == "bitmap") { 1415 brush = Surface::loadOdfPatternStyle(styleStack, odfLoadingContext, QSizeF(5.0, 60.0)); 1416 brushLoaded = true; 1417 } 1418 } 1419 } 1420 1421 #ifndef NWORKAROUND_ODF_BUGS 1422 if(! penLoaded) { 1423 penLoaded = KoOdfWorkaround::fixMissingStroke(pen, n, context); 1424 } 1425 if(! brushLoaded) { 1426 QColor fixedColor = KoOdfWorkaround::fixMissingFillColor(n, context); 1427 if (fixedColor.isValid()) { 1428 brush = fixedColor; 1429 brushLoaded = true; 1430 } 1431 } 1432 #endif 1433 return true; 1434 } 1435 1436 /** 1437 * The valueLabelType can be read from a few different places; 1438 * - chart:data-label 1439 * - chart:data-point 1440 * - chart:series 1441 * - chart:plot-area 1442 * 1443 * Since we somehow need to merge e.g. a global one defined at the plot-area 1444 * together with a local one defined in a series we need to make sure to 1445 * fetch + change only what is redefined + reapply. 1446 * 1447 * The question is if this is 100% the correct thing to do or if we 1448 * need more logic that e.g. differs between where the data-labels got 1449 * defined? It would make sense but there is no information about that 1450 * available and it seems OO.org/LO just save redundant information 1451 * here at least with pie-charts... 1452 */ 1453 void DataSet::Private::readValueLabelType(KoStyleStack &styleStack, int section /* = -1 */) 1454 { 1455 DataSet::ValueLabelType type = parent->valueLabelType(section); 1456 1457 const QString number = styleStack.property(KoXmlNS::chart, "data-label-number"); 1458 if (!number.isNull()) { 1459 type.numberIsLoaded = true; 1460 type.number = (number == "value" || number == "value-and-percentage"); 1461 type.percentage = (number == "percentage" || number == "value-and-percentage"); 1462 } 1463 1464 const QString text = styleStack.property(KoXmlNS::chart, "data-label-text"); 1465 if (!text.isNull()) { 1466 type.categoryIsLoaded = true; 1467 type.category = (text == "true"); 1468 } 1469 1470 const QString symbol = styleStack.property(KoXmlNS::chart, "data-label-symbol"); 1471 if (!symbol.isNull()) { 1472 warnChartOdf<<"data-label-symbol not supported"; 1473 type.symbolIsLoaded = true; 1474 type.symbol = (symbol == "true"); 1475 } 1476 1477 parent->setValueLabelType(type, section); 1478 } 1479 1480 bool DataSet::loadOdf(const KoXmlElement &n, 1481 KoShapeLoadingContext &context) 1482 { 1483 KoOdfLoadingContext &odfLoadingContext = context.odfLoadingContext(); 1484 KoOdfStylesReader &stylesReader = odfLoadingContext.stylesReader(); 1485 KoStyleStack &styleStack = odfLoadingContext.styleStack(); 1486 styleStack.clear(); 1487 odfLoadingContext.fillStyleStack(n, KoXmlNS::chart, "style-name", "chart"); 1488 1489 QString styleName = n.attributeNS(KoXmlNS::chart, "style-name", QString()); 1490 const KoXmlElement *stylElement = stylesReader.findStyle(styleName, "chart"); 1491 if (stylElement) { 1492 const QString dataStyleName = stylElement->attributeNS(KoXmlNS::style, "data-style-name", QString()); 1493 if (!dataStyleName.isEmpty()) { 1494 if (stylesReader.dataFormats().contains(dataStyleName)) { 1495 QPair<KoOdfNumberStyles::NumericStyleFormat, KoXmlElement*> dataStylePair = stylesReader.dataFormats()[dataStyleName]; 1496 delete d->numericStyleFormat; 1497 d->numericStyleFormat = new KoOdfNumberStyles::NumericStyleFormat(dataStylePair.first); 1498 } 1499 } 1500 } 1501 1502 OdfLoadingHelper *helper = (OdfLoadingHelper*)context.sharedData(OdfLoadingHelperId); 1503 // OOo assumes that if we use an internal model only, the columns are 1504 // interpreted as consecutive data series. Thus we can (and must) ignore 1505 // any chart:cell-range-address attribute associated with a series or 1506 // data point. Instead the regions are used that are automatically 1507 // assigned by SingleModelHelper whenever the structure of the internal 1508 // model changes. 1509 bool ignoreCellRanges = false; 1510 // Some OOo documents save incorrect cell ranges. For those this fix was intended. 1511 // Find out which documents exactly and only use fix for as few cases as possible. 1512 #if 0 1513 #ifndef NWORKAROUND_ODF_BUGS 1514 if (context.odfLoadingContext().generatorType() == KoOdfLoadingContext::OpenOffice) 1515 ignoreCellRanges = helper->chartUsesInternalModelOnly; 1516 #endif 1517 #endif 1518 1519 { 1520 QBrush brush(Qt::NoBrush); 1521 QPen pen(Qt::NoPen); 1522 bool brushLoaded = false; 1523 bool penLoaded = false; 1524 loadBrushAndPen(styleStack, context, n, brush, brushLoaded, pen, penLoaded); 1525 if (penLoaded) 1526 setPen(pen); 1527 if (brushLoaded) 1528 setBrush(brush); 1529 styleStack.setTypeProperties("chart"); 1530 if (styleStack.hasProperty(KoXmlNS::chart, "pie-offset")) 1531 setPieExplodeFactor(styleStack.property(KoXmlNS::chart, "pie-offset").toInt()); 1532 } 1533 1534 bool bubbleChart = false; 1535 bool scatterChart = false; 1536 if (n.hasAttributeNS(KoXmlNS::chart, "class")) { 1537 QString charttype = n.attributeNS(KoXmlNS::chart, "class", QString()); 1538 bubbleChart = charttype == "chart:bubble"; 1539 scatterChart = charttype == "chart:scatter"; 1540 } 1541 1542 // The <chart:domain> element specifies coordinate values required by particular chart types. 1543 // For scatter charts, one <chart:domain> element shall exist. Its table:cell-range-address 1544 // attribute references the x coordinate values for the scatter chart. 1545 // For bubble charts, two <chart:domain> elements shall exist. The values for the y-coordinates are 1546 // given by the first <chart:domain> element. The values for the x-coordinates are given by the 1547 // second <chart:domain> element. 1548 // At least one <chart:series> element of a given chart:class shall have the necessary 1549 // number of <chart:domain> sub-elements. All other <chart:series> elements with the same 1550 // chart:class may omit the <chart:domain> sub-elements and use the previously defined. 1551 if ((scatterChart || bubbleChart) && n.hasChildNodes()) { 1552 int domainCount = 0; 1553 KoXmlNode cn = n.firstChild(); 1554 while (!cn.isNull()){ 1555 KoXmlElement elem = cn.toElement(); 1556 const QString name = elem.tagName(); 1557 if (name == "domain" && elem.hasAttributeNS(KoXmlNS::table, "cell-range-address") && !ignoreCellRanges) { 1558 if ((domainCount == 0 && scatterChart) || (domainCount == 1 && bubbleChart)) { 1559 const QString region = elem.attributeNS(KoXmlNS::table, "cell-range-address", QString()); 1560 setXDataRegion(CellRegion(helper->tableSource, region)); 1561 } 1562 else { 1563 const QString region = elem.attributeNS(KoXmlNS::table, "cell-range-address", QString()); 1564 setYDataRegion(CellRegion(helper->tableSource, region)); 1565 } 1566 ++domainCount; 1567 if ((bubbleChart && domainCount == 2) || scatterChart) 1568 break; // We are finished and don't expect more domain's. 1569 } 1570 cn = cn.nextSibling(); 1571 } 1572 } 1573 if (n.hasAttributeNS(KoXmlNS::chart, "attached-axis")) { 1574 d->axisName = n.attributeNS(KoXmlNS::chart, "attached-axis"); 1575 } 1576 if (n.hasAttributeNS(KoXmlNS::chart, "values-cell-range-address") && !ignoreCellRanges) { 1577 const QString regionString = n.attributeNS(KoXmlNS::chart, "values-cell-range-address", QString()); 1578 const CellRegion region(helper->tableSource, regionString); 1579 if (bubbleChart) { 1580 setCustomDataRegion(region); 1581 } 1582 else { 1583 setYDataRegion(region); 1584 } 1585 1586 if (!bubbleChart && d->loadedDimensions == 0) { 1587 setYDataRegion(region); 1588 ++d->loadedDimensions; 1589 } 1590 } 1591 if (n.hasAttributeNS(KoXmlNS::chart, "label-cell-address") && !ignoreCellRanges) { 1592 const QString region = n.attributeNS(KoXmlNS::chart, "label-cell-address", QString()); 1593 setLabelDataRegion(CellRegion(helper->tableSource, region)); 1594 } 1595 1596 if (n.hasAttributeNS(KoXmlNS::chart, "class") && !ignoreCellRanges) { 1597 const QString chartClass = n.attributeNS(KoXmlNS::chart, "class", QString()); 1598 if (d->chartType == RingChartType && chartClass == "chart:circle") { 1599 // LO marks all datasets in a ring chart as circle 1600 // We keep it as RingChartType 1601 } else { 1602 KoChart::ChartType chartType = KoChart::BarChartType; 1603 for (int type = 0; type < (int)LastChartType; ++type) { 1604 if (chartClass == odfCharttype(type)) { 1605 chartType = (ChartType)type; 1606 setChartType(chartType); 1607 break; 1608 } 1609 } 1610 } 1611 } 1612 1613 d->readValueLabelType(styleStack); 1614 1615 if (styleStack.hasProperty(KoXmlNS::chart, "symbol-type")) { 1616 const QString name = styleStack.property(KoXmlNS::chart, "symbol-type"); 1617 if (name == "automatic") { 1618 d->odfSymbolType = AutomaticSymbol; 1619 d->symbolID = d->num % numDefaultMarkerTypes; 1620 } 1621 else if (name == "named-symbol") { 1622 d->odfSymbolType = NamedSymbol; 1623 if (styleStack.hasProperty(KoXmlNS::chart, "symbol-name")) { 1624 1625 const QString type = styleStack.property(KoXmlNS::chart, "symbol-name"); 1626 if (type == "square") 1627 d->symbolID = 0; 1628 else if (type == "diamond") 1629 d->symbolID = 1; 1630 else if (type == "arrow-down") 1631 d->symbolID = 2; 1632 else if (type == "arrow-up") 1633 d->symbolID = 3; 1634 else if (type == "arrow-right") 1635 d->symbolID = 4; 1636 else if (type == "arrow-left") 1637 d->symbolID = 5; 1638 else if (type == "bow-tie") 1639 d->symbolID = 6; 1640 else if (type == "hourglass") 1641 d->symbolID = 7; 1642 else if (type == "circle") 1643 d->symbolID = 8; 1644 else if (type == "star") 1645 d->symbolID = 9; 1646 else if (type == "x") 1647 d->symbolID = 10; 1648 else if (type == "plus") 1649 d->symbolID = 11; 1650 else if (type == "asterisk") 1651 d->symbolID = 12; 1652 else if (type == "horizontal-bar") 1653 d->symbolID = 13; 1654 else if (type == "vertical-bar") 1655 d->symbolID = 14; 1656 else 1657 d->symbolID = 0; 1658 } 1659 } else if (name == "none") { 1660 d->odfSymbolType = NoSymbol; 1661 } else if (name == "image") { 1662 // TODO: Not supported by KChart 1663 warnChartOdf<<"symbol-type = image not supported"; 1664 } else { 1665 errorChartOdf<<"Unknown symbol-type"<<name; 1666 } 1667 } 1668 1669 // load data points 1670 KoXmlElement m; 1671 int loadedDataPointCount = 0; 1672 forEachElement (m, n) { 1673 if (m.namespaceURI() != KoXmlNS::chart) 1674 continue; 1675 if (m.localName() != "data-point") 1676 continue; 1677 1678 styleStack.clear(); 1679 odfLoadingContext.fillStyleStack(m, KoXmlNS::chart, "style-name", "chart"); 1680 1681 QBrush brush(Qt::NoBrush); 1682 QPen pen(Qt::NoPen); 1683 bool brushLoaded = false; 1684 bool penLoaded = false; 1685 loadBrushAndPen(styleStack, context, m, brush, brushLoaded, pen, penLoaded); 1686 if(penLoaded) 1687 setPen(loadedDataPointCount, pen); 1688 if(brushLoaded) 1689 setBrush(loadedDataPointCount, brush); 1690 1691 //load pie explode factor 1692 styleStack.setTypeProperties("chart"); 1693 if(styleStack.hasProperty(KoXmlNS::chart, "pie-offset")) 1694 setPieExplodeFactor(loadedDataPointCount, styleStack.property(KoXmlNS::chart, "pie-offset").toInt()); 1695 1696 d->readValueLabelType(styleStack, loadedDataPointCount); 1697 1698 ++loadedDataPointCount; 1699 } 1700 return true; 1701 } 1702 1703 bool DataSet::loadSeriesIntoDataset(const KoXmlElement &n, KoShapeLoadingContext &context) 1704 { 1705 KoOdfLoadingContext &odfLoadingContext = context.odfLoadingContext(); 1706 KoStyleStack &styleStack = odfLoadingContext.styleStack(); 1707 styleStack.clear(); 1708 odfLoadingContext.fillStyleStack(n, KoXmlNS::chart, "style-name", "chart"); 1709 1710 OdfLoadingHelper *helper = (OdfLoadingHelper*)context.sharedData(OdfLoadingHelperId); 1711 // OOo assumes that if we use an internal model only, the columns are 1712 // interpreted as consecutive data series. Thus we can (and must) ignore 1713 // any chart:cell-range-address attribute associated with a series or 1714 // data point. Instead the regions are used that are automatically 1715 // assigned by SingleModelHelper whenever the structure of the internal 1716 // model changes. 1717 bool ignoreCellRanges = false; 1718 styleStack.setTypeProperties("chart"); 1719 1720 if (n.hasChildNodes()){ 1721 KoXmlNode cn = n.firstChild(); 1722 while (!cn.isNull()){ 1723 KoXmlElement elem = cn.toElement(); 1724 const QString name = elem.tagName(); 1725 if (name == "domain" && elem.hasAttributeNS(KoXmlNS::table, "cell-range-address") && !ignoreCellRanges) { 1726 Q_ASSERT(false); 1727 if (d->loadedDimensions == 0) { 1728 const QString region = elem.attributeNS(KoXmlNS::table, "cell-range-address", QString()); 1729 setXDataRegion(CellRegion(helper->tableSource, region)); 1730 ++d->loadedDimensions; 1731 } 1732 else if (d->loadedDimensions == 1) { 1733 const QString region = elem.attributeNS(KoXmlNS::table, "cell-range-address", QString()); 1734 // as long as there is not default table for missing data series the same region is used twice 1735 // to ensure the diagram is displayed, even if not as expected from o office or ms office 1736 setYDataRegion(CellRegion(helper->tableSource, region)); 1737 ++d->loadedDimensions; 1738 } 1739 else if (d->loadedDimensions == 2) { 1740 const QString region = elem.attributeNS(KoXmlNS::table, "cell-range-address", QString()); 1741 // as long as there is not default table for missing data series the same region is used twice 1742 // to ensure the diagram is displayed, even if not as expected from o office or ms office 1743 setCustomDataRegion(CellRegion(helper->tableSource, region)); 1744 ++d->loadedDimensions; 1745 } 1746 1747 } 1748 cn = cn.nextSibling(); 1749 } 1750 } 1751 1752 if (n.hasAttributeNS(KoXmlNS::chart, "values-cell-range-address") && !ignoreCellRanges) { 1753 const QString regionString = n.attributeNS(KoXmlNS::chart, "values-cell-range-address", QString()); 1754 const CellRegion region(helper->tableSource, regionString); 1755 if (d->loadedDimensions == 0) { 1756 setYDataRegion(CellRegion(region)); 1757 ++d->loadedDimensions; 1758 } 1759 else if (d->loadedDimensions == 1) { 1760 // as long as there is not default table for missing data series the same region is used twice 1761 // to ensure the diagram is displayed, even if not as expected from o office or ms office 1762 setYDataRegion(CellRegion(region)); 1763 ++d->loadedDimensions; 1764 } 1765 else if (d->loadedDimensions == 2) { 1766 // As long as there is no default table for missing data 1767 // series the same region is used twice to ensure the 1768 // diagram is displayed, even if not as expected from open 1769 // office or ms office. 1770 setCustomDataRegion(CellRegion(region)); 1771 ++d->loadedDimensions; 1772 } 1773 } 1774 //store the cell address corresponding to the label of the correct data series 1775 if (d->loadedDimensions == 2 && n.hasAttributeNS(KoXmlNS::chart, "label-cell-address") && !ignoreCellRanges) { 1776 const QString region = n.attributeNS(KoXmlNS::chart, "label-cell-address", QString()); 1777 setLabelDataRegion(CellRegion(helper->tableSource, region)); 1778 } 1779 1780 d->readValueLabelType(styleStack); 1781 1782 return true; 1783 } 1784 1785 void DataSet::saveOdf(KoShapeSavingContext &context) const 1786 { 1787 KoXmlWriter &bodyWriter = context.xmlWriter(); 1788 KoGenStyles &mainStyles = context.mainStyles(); 1789 1790 bodyWriter.startElement("chart:series"); 1791 1792 KoGenStyle style(KoGenStyle::ChartAutoStyle, "chart"); 1793 1794 if (pieAttributes().explode()) { 1795 const int pieExplode = (int)(pieAttributes().explodeFactor()*100); 1796 style.addProperty("chart:pie-offset", pieExplode, KoGenStyle::ChartType); 1797 } 1798 1799 DataSet::ValueLabelType type = valueLabelType(); 1800 if (!type.noLabel()) { 1801 if (type.number && type.percentage) { 1802 style.addProperty("chart:data-label-number", "value-and-percentage"); 1803 } else if (type.number) { 1804 style.addProperty("chart:data-label-number", "value"); 1805 } else if (type.percentage) { 1806 style.addProperty("chart:data-label-number", "percentage"); 1807 } else { 1808 style.addProperty("chart:data-label-number", "none"); 1809 } 1810 style.addProperty("chart:data-label-text", type.category ? "true" : "false"); 1811 // TODO Not supported, should we write back if loaded? 1812 // if (type.symbolIsLoaded) { 1813 // style.addProperty("chart:data-label-symbol", type.symbol ? "true" : "false"); 1814 // } 1815 } 1816 if (d->markersUsed) { 1817 switch (d->odfSymbolType) { 1818 case NoSymbol: 1819 break; 1820 case NamedSymbol: { 1821 QString symbolType = "named-symbol"; 1822 QString symbolName; 1823 switch (d->symbolID) { 1824 case MarkerSquare: symbolName = "square"; break; 1825 case MarkerDiamond: symbolName = "diamond"; break; 1826 case MarkerArrowDown: symbolName = "arrow-down"; break; 1827 case MarkerArrowUp: symbolName = "arrow-up"; break; 1828 case MarkerArrowRight: symbolName = "arrow-right"; break; 1829 case MarkerArrowLeft: symbolName = "arrow-left"; break; 1830 case MarkerBowTie: symbolName = "bow-tie"; break; 1831 case MarkerHourGlass: symbolName = "hourglass"; break; 1832 case MarkerCircle: symbolName = "circle"; break; 1833 case MarkerStar: symbolName = "star"; break; 1834 case MarkerX: symbolName = 'x'; break; 1835 case MarkerCross: symbolName = "plus"; break; 1836 case MarkerAsterisk: symbolName = "asterisk"; break; 1837 case MarkerHorizontalBar: symbolName = "horizontal-bar"; break; 1838 case MarkerVerticalBar: symbolName = "vertical-bar"; break; 1839 default: 1840 break; 1841 } 1842 if (!symbolName.isEmpty()) { 1843 style.addProperty("chart:symbol-type", symbolType, KoGenStyle::ChartType); 1844 style.addProperty("chart:symbol-name", symbolName, KoGenStyle::ChartType); 1845 } 1846 break; 1847 } 1848 case ImageSymbol: // TODO: Not supported 1849 case AutomaticSymbol: 1850 style.addProperty("chart:symbol-type", "automatic", KoGenStyle::ChartType); 1851 break; 1852 default: 1853 Q_ASSERT(false); 1854 break; 1855 } 1856 } 1857 1858 KoOdfGraphicStyles::saveOdfFillStyle(style, mainStyles, brush()); 1859 KoOdfGraphicStyles::saveOdfStrokeStyle(style, mainStyles, pen()); 1860 1861 const QString styleName = mainStyles.insert(style, "ch"); 1862 bodyWriter.addAttribute("chart:style-name", styleName); 1863 1864 // Save cell regions for values if defined. 1865 if (chartType() != KoChart::BubbleChartType) { 1866 QString values = yDataRegion().toString(); 1867 if (!values.isEmpty()) 1868 bodyWriter.addAttribute("chart:values-cell-range-address", values); 1869 } 1870 // Save cell regions for labels if defined. If not defined then the internal 1871 // table:table "local-table" (the data is stored in the ChartTableModel) is used. 1872 QString label = labelDataRegion().toString(); 1873 if (!label.isEmpty()) 1874 bodyWriter.addAttribute("chart:label-cell-address", label); 1875 1876 int charttype = LastChartType; 1877 if (d->chartType == RingChartType || d->chartType == LastChartType) { 1878 if (d->attachedAxis->plotArea()->chartType() == RingChartType) { 1879 charttype = CircleChartType; // LO needs this 1880 } 1881 } 1882 if (charttype == LastChartType) { 1883 charttype = d->effectiveChartType(); 1884 } 1885 QString chartClass = odfCharttype(charttype); 1886 if (!chartClass.isEmpty()) { 1887 bodyWriter.addAttribute("chart:class", chartClass); 1888 } 1889 if (d->attachedAxis) { 1890 bodyWriter.addAttribute("chart:attached-axis", d->attachedAxis->name()); 1891 } 1892 if (chartType() == KoChart::CircleChartType || chartType() == KoChart::RingChartType) { 1893 for (int j=0; j<yDataRegion().cellCount(); ++j) { 1894 bodyWriter.startElement("chart:data-point"); 1895 1896 KoGenStyle dps(KoGenStyle::GraphicAutoStyle, "chart"); 1897 dps.addProperty("draw:fill", "solid", KoGenStyle::GraphicType); 1898 dps.addProperty("draw:fill-color", brush(j).color().name(), KoGenStyle::GraphicType); 1899 1900 const QString styleName = mainStyles.insert(dps, "ch"); 1901 bodyWriter.addAttribute("chart:style-name", styleName ); 1902 1903 bodyWriter.endElement(); 1904 } 1905 } 1906 if (chartType() == KoChart::BubbleChartType) { 1907 // Custom data shall contain bubble size 1908 QString values = customDataRegion().toString(); 1909 bodyWriter.addAttribute("chart:values-cell-range-address", values); 1910 bodyWriter.startElement("chart:domain"); 1911 // Y-data shall be the first domain 1912 values = yDataRegion().toString(); 1913 bodyWriter.addAttribute("table:cell-range-address", values); 1914 bodyWriter.endElement(); 1915 // X-data shall be the second domain 1916 values = xDataRegion().toString(); 1917 if (!values.isEmpty()) { 1918 // Note, this is contrary to ODF but in line with LO 1919 bodyWriter.startElement("chart:domain"); 1920 bodyWriter.addAttribute("table:cell-range-address", values); 1921 bodyWriter.endElement(); 1922 } 1923 } 1924 bodyWriter.endElement(); // chart:series 1925 } 1926 1927 static KChart::MarkerAttributes::MarkerStyle odf2kdMarker(OdfMarkerStyle style) { 1928 switch (style) { 1929 case MarkerSquare: 1930 return KChart::MarkerAttributes::MarkerSquare; 1931 case MarkerDiamond: 1932 return KChart::MarkerAttributes::MarkerDiamond; 1933 case MarkerArrowDown: 1934 return KChart::MarkerAttributes::MarkerArrowDown; 1935 case MarkerArrowUp: 1936 return KChart::MarkerAttributes::MarkerArrowUp; 1937 case MarkerArrowRight: 1938 return KChart::MarkerAttributes::MarkerArrowRight; 1939 case MarkerArrowLeft: 1940 return KChart::MarkerAttributes::MarkerArrowLeft; 1941 case MarkerBowTie: 1942 return KChart::MarkerAttributes::MarkerBowTie; 1943 case MarkerHourGlass: 1944 return KChart::MarkerAttributes::MarkerHourGlass; 1945 case MarkerCircle: 1946 return KChart::MarkerAttributes::MarkerCircle; 1947 case MarkerStar: 1948 return KChart::MarkerAttributes::MarkerStar; 1949 case MarkerX: 1950 return KChart::MarkerAttributes::MarkerX; 1951 case MarkerCross: 1952 return KChart::MarkerAttributes::MarkerCross; 1953 case MarkerAsterisk: 1954 return KChart::MarkerAttributes::MarkerAsterisk; 1955 case MarkerHorizontalBar: 1956 return KChart::MarkerAttributes::MarkerHorizontalBar; 1957 case MarkerVerticalBar: 1958 return KChart::MarkerAttributes::MarkerVerticalBar; 1959 case MarkerRing: 1960 return KChart::MarkerAttributes::MarkerRing; 1961 case MarkerFastCross: 1962 return KChart::MarkerAttributes::MarkerFastCross; 1963 case Marker1Pixel: 1964 return KChart::MarkerAttributes::Marker1Pixel; 1965 case Marker4Pixels: 1966 return KChart::MarkerAttributes::Marker4Pixels; 1967 } 1968 1969 return KChart::MarkerAttributes::MarkerSquare; 1970 } 1971 1972 QString DataSet::axisName() const 1973 { 1974 return d->axisName; 1975 } 1976 1977 QDebug operator<<(QDebug dbg, const KoChart::DataSet *ds) 1978 { 1979 if (ds) { 1980 QVariantList x; 1981 for (int i = 0; i < ds->size(); ++i) { 1982 x << ds->xData(i); 1983 } 1984 QVariantList y; 1985 for (int i = 0; i < ds->size(); ++i) { 1986 y << ds->yData(i); 1987 } 1988 QVariantList cust; 1989 for (int i = 0; i < ds->size(); ++i) { 1990 cust << ds->customData(i); 1991 } 1992 QVariantList cat; 1993 for (int i = 0; i < ds->size(); ++i) { 1994 cat << ds->categoryData(i); 1995 } 1996 QString axis = ds->attachedAxis() ? ds->attachedAxis()->name() : "0x0"; 1997 return dbg.nospace()<<endl 1998 <<"\tDataSet[chart:"<<ds->chartType()<<" axis:"<<axis<<" size:"<<ds->size()<<" label:"<<ds->labelData()<<endl 1999 <<"\t X:"<<ds->xDataRegion().toString()<<':'<<x<<endl 2000 <<"\t Y:"<<ds->yDataRegion().toString()<<':'<<y<<endl 2001 <<"\t Cust:"<<ds->customDataRegion().toString()<<':'<<cust<<endl 2002 <<"\t Cat:"<<ds->categoryDataRegion().toString()<<':'<<cat<<endl 2003 <<"\t]"; 2004 } 2005 return dbg.noquote()<<"DataSet(0x0)"; 2006 } 2007 2008 QDebug operator<<(QDebug dbg, const KoChart::DataSet::ValueLabelType &v) 2009 { 2010 QStringList lst; 2011 if (v.number) lst << "N"; 2012 if (v.percentage) lst << "%"; 2013 if (v.category) lst << "C"; 2014 if (v.symbol) lst << "S"; 2015 QString s = lst.isEmpty() ? QString("None") : lst.join(','); 2016 dbg.nospace() << "ValueLabelType[" << s << ']'; 2017 return dbg.space(); 2018 }