1 /* This file is part of the KDE project
2 
3    Copyright 2007-2008 Johannes Simon <johannes.simon@gmail.com>
4    Copyright 2008-2009 Inge Wallin    <inge@lysator.liu.se>
5    Copyright (C) 2010 Carlos Licea    <carlos@kdab.com>
6    Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
7      Contact: Suresh Chande suresh.chande@nokia.com
8 
9    This library is free software; you can redistribute it and/or
10    modify it under the terms of the GNU Library General Public
11    License as published by the Free Software Foundation; either
12    version 2 of the License, or (at your option) any later version.
13 
14    This library is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17    Library General Public License for more details.
18 
19    You should have received a copy of the GNU Library General Public License
20    along with this library; see the file COPYING.LIB.  If not, write to
21    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22  * Boston, MA 02110-1301, USA.
23 */
24 
25 
26 // Own
27 #include "DataSet.h"
28 #include "ChartDebug.h"
29 
30 // Qt
31 #include <QAbstractItemModel>
32 #include <QString>
33 #include <QPen>
34 #include <QColor>
35 #include <QPainter>
36 
37 // KF5
38 #include <klocalizedstring.h>
39 
40 // KChart
41 #include <KChartDataValueAttributes>
42 #include <KChartPieAttributes>
43 #include <KChartTextAttributes>
44 #include <KChartRelativePosition>
45 #include <KChartPosition>
46 #include <KChartAbstractDiagram>
47 #include <KChartMeasure>
48 #include "KChartModel.h"
49 
50 // KoChart
51 #include "Axis.h"
52 #include "PlotArea.h"
53 #include "Surface.h"
54 #include "OdfLoadingHelper.h"
55 
56 // Calligra
57 #include <KoXmlNS.h>
58 #include <KoOdfGraphicStyles.h>
59 #include <KoStyleStack.h>
60 #include <KoXmlReader.h>
61 #include <KoShapeLoadingContext.h>
62 #include <KoShapeSavingContext.h>
63 #include <KoOdfLoadingContext.h>
64 #include <KoOdfWorkaround.h>
65 #include <KoGenStyle.h>
66 #include <KoGenStyles.h>
67 #include <KoXmlWriter.h>
68 
69 using namespace KoChart;
70 
71 const int numDefaultMarkerTypes = 15;
72 
73 // These are the values that are defined for chart:symbol-name
74 // Ref: ODF 1.2 20.54 chart:symbol-name, page 716
75 const QByteArray symbolNames[] = {
76     "square",
77     "diamond",
78     "arrow-down",
79     "arrow-up",
80     "arrow-right",
81     "arrow-left",
82     "bow-tie",
83     "hourglass",
84     "circle",
85     "star",
86     "x",
87     "plus",
88     "asterisk",
89     "horizontal-bar",
90     "vertical-bar"
91 };
92 
93 const KChart::MarkerAttributes::MarkerStyle defaultMarkerTypes[]= {
94     KChart::MarkerAttributes::MarkerSquare,     // 0
95     KChart::MarkerAttributes::MarkerDiamond,    // 1
96     KChart::MarkerAttributes::MarkerArrowDown,  // 2
97     KChart::MarkerAttributes::MarkerArrowUp,    // 3
98     KChart::MarkerAttributes::MarkerArrowRight, // 4
99     KChart::MarkerAttributes::MarkerArrowLeft,  // 5
100     KChart::MarkerAttributes::MarkerBowTie,     // 6
101     KChart::MarkerAttributes::MarkerHourGlass,  // 7
102     KChart::MarkerAttributes::MarkerCircle,     // 8
103     KChart::MarkerAttributes::MarkerStar,       // 9
104     KChart::MarkerAttributes::MarkerX,          // 10
105     KChart::MarkerAttributes::MarkerCross,      // 11
106     KChart::MarkerAttributes::MarkerAsterisk,   // 12
107     KChart::MarkerAttributes::MarkerHorizontalBar,// 13
108     KChart::MarkerAttributes::MarkerVerticalBar, // 14
109 
110     // Not used:
111     KChart::MarkerAttributes::MarkerRing,
112     KChart::MarkerAttributes::MarkerFastCross,
113     KChart::MarkerAttributes::Marker1Pixel,
114     KChart::MarkerAttributes::Marker4Pixels,
115     KChart::MarkerAttributes::NoMarker
116 };
117 
118 static KChart::MarkerAttributes::MarkerStyle odf2kdMarker(OdfMarkerStyle style);
119 
120 // just to get access to paintMarker method, until there is a proper way to
121 // have markers painted by some util class
122 class MarkerPainterDummyDiagram : public KChart::AbstractDiagram
123 {
124 public:
MarkerPainterDummyDiagram()125     MarkerPainterDummyDiagram() {}
126     void doPaintMarker( QPainter* painter,
127                         const KChart::MarkerAttributes& markerAttributes,
128                         const QBrush& brush, const QPen& pen,
129                         const QPointF& point, const QSizeF& size );
130 public: // abstract KChart::AbstractDiagram API
paint(KChart::PaintContext *)131     void paint ( KChart::PaintContext* /*paintContext*/ ) override {}
resize(const QSizeF &)132     void resize ( const QSizeF& /*area*/ ) override {}
133 protected: // abstract KChart::AbstractDiagram API
calculateDataBoundaries() const134     const QPair<QPointF, QPointF> calculateDataBoundaries() const override { return QPair<QPointF, QPointF>(); }
135 };
136 
doPaintMarker(QPainter * painter,const KChart::MarkerAttributes & markerAttributes,const QBrush & brush,const QPen & pen,const QPointF & point,const QSizeF & size)137 void MarkerPainterDummyDiagram::doPaintMarker(QPainter* painter, const KChart::MarkerAttributes& markerAttributes, const QBrush& brush, const QPen& pen, const QPointF& point, const QSizeF& size)
138 {
139     paintMarker(painter, markerAttributes, brush, pen, point, size);
140 }
141 
142 
143 class DataSet::Private
144 {
145 public:
146     Private(DataSet *parent, int dataSetNr);
147     ~Private();
148 
149     void         updateSize();
150     bool         hasOwnChartType() const;
151     ChartType    effectiveChartType() const;
152     bool         isValidDataPoint(const QPoint &point) const;
153     QVariant     data(const CellRegion &region, int index, int role) const;
154     QString      formatData(const CellRegion &region, int index, int role) const;
155 
156     QBrush defaultBrush() const;
157     QBrush defaultBrush(int section) const;
158 
159     KChart::MarkerAttributes defaultMarkerAttributes() const;
160 
161     // Returns an instance of DataValueAttributes with sane default values in
162     // relation to KoChart
163     KChart::DataValueAttributes defaultDataValueAttributes() const;
164     /// Copies Private::dataValueAttributes to this section if it doesn't
165     /// have its own DataValueAttributes copy yet.
166     void insertDataValueAttributeSectionIfNecessary(int section);
167 
168     /**
169      * FIXME: Refactor (post-2.3)
170      *        1) Maximum bubble width should be determined in ChartProxyModel
171      *        2) Actual marker size and other KChart::MarkerAttributes should
172      *           be set by some kind of adapter for KD Chart, e.g. KChartModel.
173      *
174      * This determines the maximum bubble size of *all* data points in
175      * the diagram this data set belongs to so that the actual value used to
176      * draw the bubbles is relative to this value.
177      *
178      * For more info on how bubble sizes are calculated, see
179      * http://qa.openoffice.org/issues/show_bug.cgi?id=64689
180      */
181     qreal maxBubbleSize() const;
182 
183     QPen defaultPen() const;
184 
185     void dataChanged(KChartModel::DataRole role, const QRect &rect) const;
186     void setAttributesAccordingToType();
187 
188     DataSet      *parent;
189 
190     ChartType     chartType;
191     ChartSubtype  chartSubType;
192 
193     Axis *attachedAxis;
194     QString axisName;
195     bool showMeanValue;
196     QPen meanValuePen;
197     bool showLowerErrorIndicator;
198     bool showUpperErrorIndicator;
199     QPen errorIndicatorPen;
200     ErrorCategory errorCategory;
201     qreal errorPercentage;
202     qreal errorMargin;
203     qreal lowerErrorLimit;
204     qreal upperErrorLimit;
205     // Determines whether pen has been set
206     bool penIsSet;
207     // Determines whether brush has been set
208     bool brushIsSet;
209     QPen pen;
210     QBrush brush;
211     QMap<int, DataSet::ValueLabelType> valueLabelType;
212 
213     KChart::PieAttributes pieAttributes;
214     KChart::DataValueAttributes dataValueAttributes;
215 
216     void readValueLabelType(KoStyleStack &styleStack, int section = -1);
217 
218     // Note: Set section-specific attributes only if really necessary.
219     //       They will override the respective global attributes.
220     QMap<int, QPen> pens;
221     QMap<int, QBrush> brushes;
222     QMap<int, KChart::PieAttributes> sectionsPieAttributes;
223     QMap<int, KChart::DataValueAttributes> sectionsDataValueAttributes;
224 
225     /// The number of this series is passed in the constructor and after
226     /// that never changes.
227     const int num;
228 
229     // The different CellRegions for a dataset
230     // Note: These are all 1-dimensional, i.e. vectors.
231     CellRegion labelDataRegion; // one cell that holds the label
232     CellRegion yDataRegion;     // normal y values
233     CellRegion xDataRegion;     // x values -- only for scatter & bubble charts
234     CellRegion customDataRegion;// used for bubble width in bubble charts
235     // FIXME: Remove category region from DataSet - this is not the place
236     // it belongs to.
237     CellRegion categoryDataRegion; // x labels -- same for all datasets
238 
239     KChartModel *kdChartModel;
240 
241     int size;
242 
243     /// Used if no data region for the label is specified
244     const QString defaultLabel;
245     // TODO markers can also apply to chart:data-point>, <chart:series> or <chart:plot-area>
246     int symbolID;
247     OdfSymbolType odfSymbolType; // Used for markers
248     bool markersUsed;
249 
250     int loadedDimensions;
251 
252     KoOdfNumberStyles::NumericStyleFormat *numericStyleFormat;
253 };
254 
Private(DataSet * parent,int dataSetNr)255 DataSet::Private::Private(DataSet *parent, int dataSetNr) :
256     parent(parent),
257     chartType(LastChartType),
258     chartSubType(NoChartSubtype),
259     attachedAxis(0),
260     showMeanValue(false),
261     showLowerErrorIndicator(false),
262     showUpperErrorIndicator(false),
263     errorPercentage(0.0),
264     errorMargin(0.0),
265     lowerErrorLimit(0.0),
266     upperErrorLimit(0.0),
267     penIsSet(false),
268     brushIsSet(false),
269     pen(QPen(Qt::black)),
270     brush(QColor(Qt::white)),
271     dataValueAttributes(defaultDataValueAttributes()),
272     num(dataSetNr),
273     kdChartModel(0),
274     size(0),
275     defaultLabel(i18n("Series %1", dataSetNr + 1)),
276     symbolID(0),
277     odfSymbolType(AutomaticSymbol),
278     markersUsed(false),
279     loadedDimensions(0),
280     numericStyleFormat(0)
281 {
282 }
283 
~Private()284 DataSet::Private::~Private()
285 {
286     delete numericStyleFormat;
287 }
288 
defaultMarkerAttributes() const289 KChart::MarkerAttributes DataSet::Private::defaultMarkerAttributes() const
290 {
291     KChart::MarkerAttributes ma;
292     // Don't show markers unless we turn them on
293     ma.setVisible(false);
294     // The marker size is specified in pixels, but scaled by the painter's zoom level
295     ma.setMarkerSizeMode(KChart::MarkerAttributes::AbsoluteSizeScaled);
296     return ma;
297 }
298 
defaultDataValueAttributes() const299 KChart::DataValueAttributes DataSet::Private::defaultDataValueAttributes() const
300 {
301     KChart::DataValueAttributes attr;
302     KChart::TextAttributes textAttr = attr.textAttributes();
303     // Don't show value labels by default
304     textAttr.setVisible(false);
305     KChart::Measure fontSize = textAttr.fontSize();
306     attr.setMarkerAttributes(defaultMarkerAttributes());
307     fontSize.setValue(10);
308     // Don't change font size with chart size
309     fontSize.setCalculationMode(KChartEnums::MeasureCalculationModeAbsolute);
310     textAttr.setFontSize(fontSize);
311     // Draw text horizontally
312     textAttr.setRotation(0);
313     attr.setTextAttributes(textAttr);
314     // Set positive value position
315     KChart::RelativePosition positivePosition = attr.positivePosition();
316     if (chartType ==  KoChart::BarChartType && chartSubType != KoChart::NormalChartSubtype) {
317         positivePosition.setAlignment(Qt::AlignCenter);
318         positivePosition.setReferencePosition(KChartEnums::PositionCenter);
319     }
320     else if (chartType ==  KoChart::BarChartType && chartSubType == KoChart::NormalChartSubtype) {
321         positivePosition.setAlignment(Qt::AlignHCenter | Qt::AlignTop);
322         positivePosition.setReferencePosition(KChartEnums::PositionNorth);
323     }
324     else {
325         positivePosition.setAlignment(Qt::AlignHCenter | Qt::AlignTop);
326         positivePosition.setReferencePosition(KChartEnums::PositionNorthWest);
327     }
328     positivePosition.setHorizontalPadding(0.0);
329     positivePosition.setVerticalPadding(-100.0);
330     attr.setPositivePosition(positivePosition);
331 
332     // Set negative value position
333     KChart::RelativePosition negativePosition = attr.negativePosition();
334     if (chartType ==  KoChart::BarChartType && chartSubType != KoChart::NormalChartSubtype) {
335         negativePosition.setAlignment(Qt::AlignCenter);
336         negativePosition.setReferencePosition(KChartEnums::PositionCenter);
337     }
338     else if (chartType ==  KoChart::BarChartType && chartSubType == KoChart::NormalChartSubtype) {
339         negativePosition.setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
340         negativePosition.setReferencePosition(KChartEnums::PositionSouth);
341     }
342     else {
343         negativePosition.setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
344         negativePosition.setReferencePosition(KChartEnums::PositionSouthWest);
345     }
346     negativePosition.setHorizontalPadding(0.0);
347     negativePosition.setVerticalPadding(100.0);
348     attr.setNegativePosition(negativePosition);
349 
350     // No decimal digits by default
351     attr.setDecimalDigits(0);
352     // Show all values, even if they overlap
353     attr.setShowOverlappingDataLabels(true);
354     // Yes, data point labels can repeatedly have the same text. (e.g. the same value)
355     attr.setShowRepetitiveDataLabels(true);
356 
357     attr.setVisible(true);
358 
359     return attr;
360 }
361 
insertDataValueAttributeSectionIfNecessary(int section)362 void DataSet::Private::insertDataValueAttributeSectionIfNecessary(int section)
363 {
364     Q_ASSERT(section >= 0);
365     if (!sectionsDataValueAttributes.contains(section))
366         sectionsDataValueAttributes[section] = dataValueAttributes;
367 }
368 
updateSize()369 void DataSet::Private::updateSize()
370 {
371     int newSize = 0;
372     newSize = qMax(newSize, xDataRegion.cellCount());
373     newSize = qMax(newSize, yDataRegion.cellCount());
374     newSize = qMax(newSize, customDataRegion.cellCount());
375     newSize = qMax(newSize, categoryDataRegion.cellCount());
376 
377     if (size != newSize) {
378         size = newSize;
379         if (kdChartModel)
380             kdChartModel->dataSetSizeChanged(parent, size);
381     }
382 }
383 
hasOwnChartType() const384 bool DataSet::Private::hasOwnChartType() const
385 {
386     return chartType != LastChartType;
387 }
388 
389 
390 /**
391  * Returns the effective chart type of this data set, i.e.
392  * returns the chart type of the diagram this data set is
393  * attached to if no chart type is set, or otherwise this data
394  * set's chart type.
395  */
effectiveChartType() const396 ChartType DataSet::Private::effectiveChartType() const
397 {
398     if (hasOwnChartType())
399         return chartType;
400 
401     Q_ASSERT(attachedAxis);
402     return attachedAxis->plotArea()->chartType();
403 }
404 
isValidDataPoint(const QPoint & point) const405 bool DataSet::Private::isValidDataPoint(const QPoint &point) const
406 {
407     if (point.y() < 0 || point.x() < 0)
408         return false;
409 
410     // We can't point to horizontal and vertical header data at the same time
411     if (point.x() == 0 && point.y() == 0)
412         return false;
413 
414     return true;
415 }
416 
data(const CellRegion & region,int index,int role) const417 QVariant DataSet::Private::data(const CellRegion &region, int index, int role) const
418 {
419     if (!region.isValid())
420         return QVariant();
421     if (!region.hasPointAtIndex(index))
422         return QVariant();
423 
424     // Convert the given index in this dataset to a data point in the
425     // source model.
426     QPoint dataPoint = region.pointAtIndex(index);
427     Table *table = region.table();
428     Q_ASSERT(table);
429     QAbstractItemModel *model = table->model();
430     // This means the table the region lies in has been removed, but nobody
431     // has changed the region in the meantime. That is a perfectly valid
432     // scenario, so just return invalid data.
433     if (!model)
434         return QVariant();
435 
436     // Check if the data point is valid
437     const bool validDataPoint = isValidDataPoint(dataPoint);
438 
439     // Remove, since it makes Calligra Sheets crash when inserting a chart for
440     // a 1x1 cell region.
441     //Q_ASSERT(validDataPoint);
442     if (!validDataPoint)
443         return QVariant();
444 
445     // The top-left point is (1,1). (0,y) or (x,0) refers to header data.
446     const bool verticalHeaderData   = dataPoint.x() == 0;
447     const bool horizontalHeaderData = dataPoint.y() == 0;
448     const int row = dataPoint.y() - 1;
449     const int col = dataPoint.x() - 1;
450 
451     QVariant data;
452     if (verticalHeaderData)
453         data = model->headerData(row, Qt::Vertical, role);
454     else if (horizontalHeaderData)
455         data = model->headerData(col, Qt::Horizontal, role);
456     else {
457         const QModelIndex &index = model->index(row, col);
458         //Q_ASSERT(index.isValid());
459         if (index.isValid())
460             data = model->data(index, role);
461     }
462     return data;
463 }
464 
formatData(const CellRegion & region,int index,int role) const465 QString DataSet::Private::formatData(const CellRegion &region, int index, int role) const
466 {
467     QVariant v = data(region, index, role);
468     QString s;
469     if (v.type() == QVariant::Double) {
470         // Don't use v.toString() else a double/float would lose precision
471         // and something like "36.5207" would become "36.520660888888912".
472         QTextStream ts(&s);
473         //ts.setRealNumberNotation(QTextStream::FixedNotation);
474         //ts.setRealNumberPrecision();
475         ts << v.toDouble();
476     } else {
477         s = v.toString();
478     }
479     return numericStyleFormat ? KoOdfNumberStyles::format(s, *numericStyleFormat) : s;
480 }
481 
defaultBrush() const482 QBrush DataSet::Private::defaultBrush() const
483 {
484     Qt::Orientation modelDataDirection = kdChartModel->dataDirection();
485     // A data set-wide default brush only makes sense if the legend shows
486     // data set labels, not the category data. See notes on data directions
487     // in KChartModel.h for details.
488     if (modelDataDirection == Qt::Vertical)
489         return defaultDataSetColor(num);
490     // FIXME: What to return in the other case?
491     return QBrush();
492 }
493 
defaultBrush(int section) const494 QBrush DataSet::Private::defaultBrush(int section) const
495 {
496     Qt::Orientation modelDataDirection = kdChartModel->dataDirection();
497     // Horizontally aligned diagrams have a specific color per category
498     // See for example pie or ring charts. A pie chart contains a single
499     // data set, but the slices default to different brushes.
500     if (modelDataDirection == Qt::Horizontal)
501         return defaultDataSetColor(section);
502     // Vertically aligned diagrams default to one brush per data set
503     return defaultBrush();
504 }
505 
defaultPen() const506 QPen DataSet::Private::defaultPen() const
507 {
508     QPen pen(Qt::black);
509     ChartType chartType = effectiveChartType();
510     if (chartType == LineChartType ||
511          chartType == ScatterChartType) {
512         if (penIsSet) {
513             pen = pen;
514         } else {
515             pen = QPen(defaultDataSetColor(num));
516         }
517     }
518     return pen;
519 }
520 
521 
DataSet(int dataSetNr)522 DataSet::DataSet(int dataSetNr)
523     : d(new Private(this, dataSetNr))
524 {
525     Q_ASSERT(dataSetNr >= 0);
526 }
527 
~DataSet()528 DataSet::~DataSet()
529 {
530     if (d->attachedAxis)
531         d->attachedAxis->detachDataSet(this, true);
532 
533     delete d;
534 }
535 
536 
chartType() const537 ChartType DataSet::chartType() const
538 {
539     return d->chartType;
540 }
541 
chartSubType() const542 ChartSubtype DataSet::chartSubType() const
543 {
544     return d->chartSubType;
545 }
546 
attachedAxis() const547 Axis *DataSet::attachedAxis() const
548 {
549     return d->attachedAxis;
550 }
551 
showMeanValue() const552 bool DataSet::showMeanValue() const
553 {
554     return d->showMeanValue;
555 }
556 
meanValuePen() const557 QPen DataSet::meanValuePen() const
558 {
559     return d->meanValuePen;
560 }
561 
showLowerErrorIndicator() const562 bool DataSet::showLowerErrorIndicator() const
563 {
564     return d->showLowerErrorIndicator;
565 }
566 
showUpperErrorIndicator() const567 bool DataSet::showUpperErrorIndicator() const
568 {
569     return d->showUpperErrorIndicator;
570 }
571 
errorIndicatorPen() const572 QPen DataSet::errorIndicatorPen() const
573 {
574     return d->errorIndicatorPen;
575 }
576 
errorCategory() const577 ErrorCategory DataSet::errorCategory() const
578 {
579     return d->errorCategory;
580 }
581 
errorPercentage() const582 qreal DataSet::errorPercentage() const
583 {
584     return d->errorPercentage;
585 }
586 
errorMargin() const587 qreal DataSet::errorMargin() const
588 {
589     return d->errorMargin;
590 }
591 
lowerErrorLimit() const592 qreal DataSet::lowerErrorLimit() const
593 {
594     return d->lowerErrorLimit;
595 }
596 
upperErrorLimit() const597 qreal DataSet::upperErrorLimit() const
598 {
599     return d->upperErrorLimit;
600 }
601 
setAttributesAccordingToType()602 void DataSet::Private::setAttributesAccordingToType()
603 {
604     KChart::DataValueAttributes attr = dataValueAttributes;
605     KChart::RelativePosition positivePosition = attr.positivePosition();
606     if (chartType ==  KoChart::BarChartType && chartSubType != KoChart::NormalChartSubtype) {
607         positivePosition.setAlignment(Qt::AlignCenter);
608         positivePosition.setReferencePosition(KChartEnums::PositionCenter);
609     }
610     else if (chartType ==  KoChart::BarChartType && chartSubType == KoChart::NormalChartSubtype) {
611         positivePosition.setAlignment(Qt::AlignHCenter | Qt::AlignTop);
612         positivePosition.setReferencePosition(KChartEnums::PositionNorth);
613     }
614     else {
615         positivePosition.setAlignment(Qt::AlignHCenter | Qt::AlignTop);
616         positivePosition.setReferencePosition(KChartEnums::PositionNorthWest);
617     }
618     positivePosition.setHorizontalPadding(0.0);
619     positivePosition.setVerticalPadding(-100.0);
620     attr.setPositivePosition(positivePosition);
621 
622     // Set negative value position
623     KChart::RelativePosition negativePosition = attr.negativePosition();
624     if (chartType ==  KoChart::BarChartType && chartSubType != KoChart::NormalChartSubtype) {
625         negativePosition.setAlignment(Qt::AlignCenter);
626         negativePosition.setReferencePosition(KChartEnums::PositionCenter);
627     }
628     else if (chartType ==  KoChart::BarChartType && chartSubType == KoChart::NormalChartSubtype) {
629         negativePosition.setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
630         negativePosition.setReferencePosition(KChartEnums::PositionSouth);
631     }
632     else {
633         negativePosition.setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
634         negativePosition.setReferencePosition(KChartEnums::PositionSouthWest);
635     }
636     negativePosition.setHorizontalPadding(0.0);
637     negativePosition.setVerticalPadding(100.0);
638     attr.setNegativePosition(negativePosition);
639     dataValueAttributes = attr;
640 
641     for (int i = 0; i < sectionsDataValueAttributes.count(); ++i) {
642         Q_ASSERT(sectionsDataValueAttributes.contains(i));
643         KChart::DataValueAttributes attr = sectionsDataValueAttributes[i];
644         KChart::RelativePosition positivePosition = attr.positivePosition();
645         if (chartType ==  KoChart::BarChartType && chartSubType != KoChart::NormalChartSubtype) {
646             positivePosition.setAlignment(Qt::AlignCenter);
647             positivePosition.setReferencePosition(KChartEnums::PositionCenter);
648         }
649         else if (chartType ==  KoChart::BarChartType && chartSubType == KoChart::NormalChartSubtype) {
650             positivePosition.setAlignment(Qt::AlignHCenter | Qt::AlignTop);
651             positivePosition.setReferencePosition(KChartEnums::PositionNorth);
652         }
653         else {
654             positivePosition.setAlignment(Qt::AlignHCenter | Qt::AlignTop);
655             positivePosition.setReferencePosition(KChartEnums::PositionNorthWest);
656         }
657         positivePosition.setHorizontalPadding(0.0);
658         positivePosition.setVerticalPadding(-100.0);
659         attr.setPositivePosition(positivePosition);
660 
661         // Set negative value position
662         KChart::RelativePosition negativePosition = attr.negativePosition();
663         if (chartType ==  KoChart::BarChartType && chartSubType != KoChart::NormalChartSubtype) {
664             negativePosition.setAlignment(Qt::AlignCenter);
665             negativePosition.setReferencePosition(KChartEnums::PositionCenter);
666         }
667         else if (chartType == KoChart::BarChartType && chartSubType == KoChart::NormalChartSubtype) {
668             negativePosition.setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
669             negativePosition.setReferencePosition(KChartEnums::PositionSouth);
670         }
671         else {
672             negativePosition.setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
673             negativePosition.setReferencePosition(KChartEnums::PositionSouthWest);
674         }
675         negativePosition.setHorizontalPadding(0.0);
676         negativePosition.setVerticalPadding(100.0);
677         attr.setNegativePosition(negativePosition);
678         sectionsDataValueAttributes[i] = attr;
679     }
680 }
681 
682 
setChartType(ChartType type)683 void DataSet::setChartType(ChartType type)
684 {
685     if (type == d->chartType)
686         return;
687 
688     Axis  *axis = d->attachedAxis;
689     if (axis)
690         axis->detachDataSet(this);
691 
692     d->chartType = type;
693     d->setAttributesAccordingToType();
694 
695     if (axis)
696         axis->attachDataSet(this);
697 
698     switch (type) {
699         case LineChartType:
700         case AreaChartType:
701         case ScatterChartType:
702         case RadarChartType:
703         case FilledRadarChartType:
704             d->markersUsed = true;
705             break;
706         default:
707             d->markersUsed = false;
708             break;
709     }
710 }
711 
setChartSubType(ChartSubtype subType)712 void DataSet::setChartSubType(ChartSubtype subType)
713 {
714     if (subType == d->chartSubType)
715         return;
716 
717     Axis *axis = d->attachedAxis;
718     axis->detachDataSet(this);
719 
720     d->chartSubType = subType;
721     d->setAttributesAccordingToType();
722 
723     axis->attachDataSet(this);
724 }
725 
726 
setAttachedAxis(Axis * axis)727 void DataSet::setAttachedAxis(Axis *axis)
728 {
729     d->attachedAxis = axis;
730 }
731 
pen() const732 QPen DataSet::pen() const
733 {
734     return d->penIsSet ? d->pen : d->defaultPen();
735 }
736 
brush() const737 QBrush DataSet::brush() const
738 {
739     return d->brushIsSet ? d->brush : d->defaultBrush();
740 }
741 
markerStyle() const742 OdfMarkerStyle DataSet::markerStyle() const
743 {
744     OdfMarkerStyle style = (OdfMarkerStyle)(d->symbolID);
745     return style;
746 }
747 
markerIcon(OdfMarkerStyle markerStyle)748 QIcon DataSet::markerIcon(OdfMarkerStyle markerStyle)
749 {
750     QPixmap markerPixmap(16,16);
751     markerPixmap.fill(QColor(255,255,255,0));
752     QPainter painter(&markerPixmap);
753     KChart::MarkerAttributes matt;
754     matt.setMarkerStyle(odf2kdMarker(markerStyle));
755     MarkerPainterDummyDiagram().doPaintMarker(&painter, matt, brush(), pen(), QPointF(7,7), QSizeF(12,12));
756     QIcon markerIcon = QIcon(markerPixmap);
757     return markerIcon;
758 }
759 
pen(int section) const760 QPen DataSet::pen(int section) const
761 {
762     if (d->pens.contains(section))
763         return d->pens[section];
764     return pen();
765 }
766 
pieAttributes() const767 KChart::PieAttributes DataSet::pieAttributes() const
768 {
769     return d->pieAttributes;
770 }
771 
brush(int section) const772 QBrush DataSet::brush(int section) const
773 {
774     Qt::Orientation modelDataDirection = d->kdChartModel->dataDirection();
775     // Horizontally aligned diagrams have a specific color per category
776     // See for example pie or ring charts. A pie chart contains a single
777     // data set, but the slices must have different brushes.
778     if (modelDataDirection == Qt::Horizontal) {
779         if (d->brushes.contains(section)) {
780             return d->brushes[section];
781         }
782         return d->defaultBrush(section);
783     }
784     // Vertically aligned diagrams only have one brush per data set
785     return brush();
786 }
787 
pieAttributes(int section) const788 KChart::PieAttributes DataSet::pieAttributes(int section) const
789 {
790     if(d->sectionsPieAttributes.contains(section))
791         return d->sectionsPieAttributes[section];
792     return pieAttributes();
793 }
794 
maxBubbleSize() const795 qreal DataSet::Private::maxBubbleSize() const
796 {
797     // TODO: Improve performance by caching. This is currently O(n^2).
798     // this is not in O(n^2), its quite linear on the number of datapoints
799     // however it could be constant for any then the first case by implementing
800     // cashing
801     qreal max = 0.0;
802     Q_ASSERT(kdChartModel);
803     QList<DataSet*> dataSets = kdChartModel->dataSets();
804     foreach(DataSet *dataSet, dataSets)
805         for (int i = 0; i < dataSet->size(); i++)
806             max = qMax(max, dataSet->customData(i).toReal());
807     return max;
808 }
809 
dataValueAttributes(int section) const810 KChart::DataValueAttributes DataSet::dataValueAttributes(int section /* = -1 */) const
811 {
812     KChart::DataValueAttributes attr(d->dataValueAttributes);
813     Q_ASSERT(attr.isVisible() == d->dataValueAttributes.isVisible());
814     if (d->sectionsDataValueAttributes.contains(section))
815         attr = d->sectionsDataValueAttributes[section];
816 
817     /*
818      * Update attributes that are related to properties out of the data
819      * sets's reach and thus might have changed in the meanwhile.
820      */
821     KChart::MarkerAttributes ma(attr.markerAttributes());
822 
823     // The chart type is a property of the plot area, check that.
824     switch (d->effectiveChartType()) {
825     case BarChartType:
826     case CircleChartType:
827     case RingChartType:
828     case StockChartType:
829     {
830         Q_ASSERT(attr.isVisible());
831         ma.setMarkerStyle(KChart::MarkerAttributes::MarkerSquare);
832         ma.setMarkerSize(QSize(10, 10));
833         ma.setVisible(true); // For legend
834         break;
835     }
836     case BubbleChartType:
837     {
838         Q_ASSERT(attachedAxis());
839         Q_ASSERT(attachedAxis()->plotArea());
840         ma.setMarkerStyle(KChart::MarkerAttributes::MarkerCircle);
841         ma.setThreeD(attachedAxis()->plotArea()->isThreeD());
842         qreal maxSize = d->maxBubbleSize();
843         if (section >= 0) {
844             qreal bubbleWidth = customData(section).toReal();
845             // All bubble sizes are relative to the maximum bubble size
846             if (maxSize != 0.0)
847                 bubbleWidth /= maxSize;
848             // Whereas the maximum size is relative to 1/4 * min(dw, dh),
849             // with dw, dh being the width and height of the diagram
850             bubbleWidth *= 0.25;
851             ma.setMarkerSizeMode(KChart::MarkerAttributes::RelativeToDiagramWidthHeightMin);
852             ma.setMarkerSize(QSizeF(bubbleWidth, bubbleWidth));
853         }
854         ma.setVisible(true);
855         break;
856     }
857     default:
858         Q_ASSERT(attr.isVisible());
859         switch (d->odfSymbolType) {
860             case NoSymbol:
861                 ma.setVisible(false);
862                 break;
863             case ImageSymbol: // Not supported
864             case AutomaticSymbol:
865                 ma.setMarkerStyle(odf2kdMarker((OdfMarkerStyle)(d->num % numDefaultMarkerTypes)));
866                 ma.setMarkerSize(QSize(10, 10));
867                 ma.setVisible(true);
868             case NamedSymbol:
869                 ma.setMarkerStyle(odf2kdMarker((OdfMarkerStyle)d->symbolID));
870                 ma.setVisible(true);
871                 break;
872         }
873         break;
874     }
875 
876     ma.setMarkerColor(brush(section).color());
877     ma.setPen(pen(section));
878 
879     QLocale locale;
880     QString dataLabel = ""; // must not be isNull() because then KChart uses a default text
881     ValueLabelType type = valueLabelType(section);
882     if (type.category) {
883         QString s = categoryData(section, Qt::DisplayRole).toString().trimmed();
884         if (!s.isEmpty()) dataLabel += s + QLatin1Char(' ');
885     }
886     if (type.number) {
887         QString s;
888         if (d->effectiveChartType() == BubbleChartType) {
889             s = d->formatData(d->customDataRegion, section, Qt::DisplayRole);
890         } else {
891             s = d->formatData(d->yDataRegion, section, Qt::DisplayRole);
892         }
893         if (!s.isEmpty()) dataLabel += s + QLatin1Char(' ');
894     }
895     if (type.percentage) {
896         bool ok;
897         qreal value;
898         if (d->effectiveChartType() == BubbleChartType) {
899             value = customData(section, Qt::EditRole).toDouble(&ok);
900             if (ok) {
901                 qreal sum = 0.0;
902                 for(int i = 0; i < d->customDataRegion.cellCount(); ++i) {
903                     sum += customData(i, Qt::EditRole).toDouble();
904                 }
905                 if (sum == 0.0)
906                     ok = false;
907                 else
908                     value = value / sum * 100.0;
909             }
910         } else {
911             value = yData(section, Qt::EditRole).toDouble(&ok);
912             if (ok) {
913                 qreal sum = 0.0;
914                 for(int i = 0; i < d->yDataRegion.cellCount(); ++i) {
915                     sum += yData(i, Qt::EditRole).toDouble();
916                 }
917                 if (sum == 0.0)
918                     ok = false;
919                 else
920                     value = value / sum * 100.0;
921             }
922         }
923         if (ok)
924             dataLabel += locale.toString(value, 'f', 0) + locale.percent();
925     }
926     attr.setDataLabel(dataLabel.trimmed());
927 
928     attr.setMarkerAttributes(ma);
929 
930     return attr;
931 }
932 
getMarkerAttributes(int section) const933 KChart::MarkerAttributes DataSet::getMarkerAttributes(int section) const
934 {
935     KChart::DataValueAttributes attr(d->dataValueAttributes);
936     Q_ASSERT(attr.isVisible() == d->dataValueAttributes.isVisible());
937     if (d->sectionsDataValueAttributes.contains(section))
938         attr = d->sectionsDataValueAttributes[section];
939 
940     KChart::MarkerAttributes ma(attr.markerAttributes());
941     ma.setMarkerStyle(odf2kdMarker((OdfMarkerStyle)d->symbolID));
942     ma.setMarkerSize(QSize(10, 10));
943     ma.setVisible(true);
944 
945     return ma;
946 }
947 
setMarkerAttributes(const KChart::MarkerAttributes & attribs,int section)948 void DataSet::setMarkerAttributes(const KChart::MarkerAttributes &attribs, int section)
949 {
950     KChart::DataValueAttributes attr(d->dataValueAttributes);
951     Q_ASSERT(attr.isVisible() == d->dataValueAttributes.isVisible());
952     if (d->sectionsDataValueAttributes.contains(section))
953         attr = d->sectionsDataValueAttributes[section];
954 
955     attr.setMarkerAttributes(attribs);
956     d->dataValueAttributes = attr;
957 }
958 
odfSymbolType() const959 OdfSymbolType DataSet::odfSymbolType() const
960 {
961     return d->odfSymbolType;
962 }
963 
setOdfSymbolType(OdfSymbolType type)964 void DataSet::setOdfSymbolType(OdfSymbolType type)
965 {
966     d->odfSymbolType = type;
967 }
968 
setPen(const QPen & pen)969 void DataSet::setPen(const QPen &pen)
970 {
971     d->pen = pen;
972     d->penIsSet = true;
973     if (d->kdChartModel)
974         d->kdChartModel->dataSetChanged(this);
975 //     KChart::MarkerAttributes ma(d->dataValueAttributes.markerAttributes());
976 //     ma.setPen(pen);
977 //     d->dataValueAttributes.setMarkerAttributes(ma);
978 //     for (QMap< int, KChart::DataValueAttributes >::iterator it = d->sectionsDataValueAttributes.begin();
979 //           it != d->sectionsDataValueAttributes.end(); ++it){
980 //         KChart::MarkerAttributes mattr(it->markerAttributes());
981 //         mattr.setMarkerColor(pen.color());
982 //         it->setMarkerAttributes(mattr);
983 //     }
984 
985 }
986 
setBrush(const QBrush & brush)987 void DataSet::setBrush(const QBrush &brush)
988 {
989     d->brush = brush;
990     d->brushIsSet = true;
991     if (d->kdChartModel)
992         d->kdChartModel->dataSetChanged(this);
993 //     KChart::MarkerAttributes ma(d->dataValueAttributes.markerAttributes());
994 //     ma.setMarkerColor(brush.color());
995 //     d->dataValueAttributes.setMarkerAttributes(ma);
996 //     for (QMap< int, KChart::DataValueAttributes >::iterator it = d->sectionsDataValueAttributes.begin();
997 //           it != d->sectionsDataValueAttributes.end(); ++it){
998 //         KChart::MarkerAttributes mattr(it->markerAttributes());
999 //         mattr.setMarkerColor(brush.color());
1000 //         it->setMarkerAttributes(mattr);
1001 //     }
1002 }
1003 
setPieExplodeFactor(int factor)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 
setPen(int section,const QPen & pen)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 
setBrush(int section,const QBrush & brush)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 
setMarkerStyle(OdfMarkerStyle style)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 
setPieExplodeFactor(int section,int factor)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 
number() const1062 int DataSet::number() const
1063 {
1064     return d->num;
1065 }
1066 
setShowMeanValue(bool show)1067 void DataSet::setShowMeanValue(bool show)
1068 {
1069     d->showMeanValue = show;
1070 }
1071 
setMeanValuePen(const QPen & pen)1072 void DataSet::setMeanValuePen(const QPen &pen)
1073 {
1074     d->meanValuePen = pen;
1075 }
1076 
setShowLowerErrorIndicator(bool show)1077 void DataSet::setShowLowerErrorIndicator(bool show)
1078 {
1079     d->showLowerErrorIndicator = show;
1080 }
1081 
setShowUpperErrorIndicator(bool show)1082 void DataSet::setShowUpperErrorIndicator(bool show)
1083 {
1084     d->showUpperErrorIndicator = show;
1085 }
1086 
setShowErrorIndicators(bool lower,bool upper)1087 void DataSet::setShowErrorIndicators(bool lower, bool upper)
1088 {
1089     setShowLowerErrorIndicator(lower);
1090     setShowUpperErrorIndicator(upper);
1091 }
1092 
setErrorIndicatorPen(const QPen & pen)1093 void DataSet::setErrorIndicatorPen(const QPen &pen)
1094 {
1095     d->errorIndicatorPen = pen;
1096 }
1097 
setErrorCategory(ErrorCategory category)1098 void DataSet::setErrorCategory(ErrorCategory category)
1099 {
1100     d->errorCategory = category;
1101 }
1102 
setErrorPercentage(qreal percentage)1103 void DataSet::setErrorPercentage(qreal percentage)
1104 {
1105     d->errorPercentage = percentage;
1106 }
1107 
setErrorMargin(qreal margin)1108 void DataSet::setErrorMargin(qreal margin)
1109 {
1110     d->errorMargin = margin;
1111 }
1112 
setLowerErrorLimit(qreal limit)1113 void DataSet::setLowerErrorLimit(qreal limit)
1114 {
1115     d->lowerErrorLimit = limit;
1116 }
1117 
setUpperErrorLimit(qreal limit)1118 void DataSet::setUpperErrorLimit(qreal limit)
1119 {
1120     d->upperErrorLimit = limit;
1121 }
1122 
xData(int index,int role) const1123 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 
yData(int index,int role) const1139 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 
customData(int index,int role) const1147 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 
categoryData(int index,int role) const1156 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 
labelData() const1197 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 
defaultLabelData() const1217 QString DataSet::defaultLabelData() const
1218 {
1219     return d->defaultLabel;
1220 }
1221 
xDataRegion() const1222 CellRegion DataSet::xDataRegion() const
1223 {
1224     return d->xDataRegion;
1225 }
1226 
yDataRegion() const1227 CellRegion DataSet::yDataRegion() const
1228 {
1229     return d->yDataRegion;
1230 }
1231 
customDataRegion() const1232 CellRegion DataSet::customDataRegion() const
1233 {
1234     return d->customDataRegion;
1235 }
1236 
categoryDataRegion() const1237 CellRegion DataSet::categoryDataRegion() const
1238 {
1239     return d->categoryDataRegion;
1240 }
1241 
labelDataRegion() const1242 CellRegion DataSet::labelDataRegion() const
1243 {
1244     return d->labelDataRegion;
1245 }
1246 
1247 
setXDataRegion(const CellRegion & region)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 
setYDataRegion(const CellRegion & region)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 
setCustomDataRegion(const CellRegion & region)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 
setCategoryDataRegion(const CellRegion & region)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 
setLabelDataRegion(const CellRegion & region)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 
size() const1294 int DataSet::size() const
1295 {
1296     return qMax(1, d->size);
1297 }
1298 
dataChanged(KChartModel::DataRole role,const QRect & rect) const1299 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 
yDataChanged(const QRect & region) const1311 void DataSet::yDataChanged(const QRect &region) const
1312 {
1313     d->dataChanged(KChartModel::YDataRole, region);
1314 }
1315 
xDataChanged(const QRect & region) const1316 void DataSet::xDataChanged(const QRect &region) const
1317 {
1318     d->dataChanged(KChartModel::XDataRole, region);
1319 }
1320 
customDataChanged(const QRect & region) const1321 void DataSet::customDataChanged(const QRect &region) const
1322 {
1323     d->dataChanged(KChartModel::CustomDataRole, region);
1324 }
1325 
labelDataChanged(const QRect & region) const1326 void DataSet::labelDataChanged(const QRect &region) const
1327 {
1328     d->dataChanged(KChartModel::LabelDataRole, region);
1329 }
1330 
categoryDataChanged(const QRect & region) const1331 void DataSet::categoryDataChanged(const QRect &region) const
1332 {
1333     d->dataChanged(KChartModel::CategoryDataRole, region);
1334 }
1335 
dimension() const1336 int DataSet::dimension() const
1337 {
1338     return numDimensions(d->effectiveChartType());
1339 }
1340 
setKdChartModel(KChartModel * model)1341 void DataSet::setKdChartModel(KChartModel *model)
1342 {
1343     d->kdChartModel = model;
1344 }
1345 
kdChartModel() const1346 KChartModel *DataSet::kdChartModel() const
1347 {
1348     return d->kdChartModel;
1349 }
1350 
setValueLabelType(const ValueLabelType & type,int section)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 
valueLabelType(int section) const1381 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 
loadBrushAndPen(KoStyleStack & styleStack,KoShapeLoadingContext & context,const KoXmlElement & n,QBrush & brush,bool & brushLoaded,QPen & pen,bool & penLoaded)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 */
readValueLabelType(KoStyleStack & styleStack,int section)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 
loadOdf(const KoXmlElement & n,KoShapeLoadingContext & context)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 
loadSeriesIntoDataset(const KoXmlElement & n,KoShapeLoadingContext & context)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 
saveOdf(KoShapeSavingContext & context) const1785 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 
odf2kdMarker(OdfMarkerStyle style)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 
axisName() const1972 QString DataSet::axisName() const
1973 {
1974     return d->axisName;
1975 }
1976 
operator <<(QDebug dbg,const KoChart::DataSet * ds)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 
operator <<(QDebug dbg,const KoChart::DataSet::ValueLabelType & v)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 }
2019