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