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 &region, int index, int role) const;
0154     QString      formatData(const CellRegion &region, 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 &region, 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 &region, 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 &region)
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 &region)
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 &region)
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 &region)
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 &region)
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 &region) const
1312 {
1313     d->dataChanged(KChartModel::YDataRole, region);
1314 }
1315 
1316 void DataSet::xDataChanged(const QRect &region) const
1317 {
1318     d->dataChanged(KChartModel::XDataRole, region);
1319 }
1320 
1321 void DataSet::customDataChanged(const QRect &region) const
1322 {
1323     d->dataChanged(KChartModel::CustomDataRole, region);
1324 }
1325 
1326 void DataSet::labelDataChanged(const QRect &region) const
1327 {
1328     d->dataChanged(KChartModel::LabelDataRole, region);
1329 }
1330 
1331 void DataSet::categoryDataChanged(const QRect &region) 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 }