1 /* This file is part of the KDE project
2
3 Copyright 2007 Johannes Simon <johannes.simon@gmail.com>
4 Copyright 2009 Inge Wallin <inge@lysator.liu.se>
5 Copyright 2017 Dag Andersen <danders@get2net.dk>
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public
9 License as published by the Free Software Foundation; either
10 version 2 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Library General Public License for more details.
16
17 You should have received a copy of the GNU Library General Public License
18 along with this library; see the file COPYING.LIB. If not, write to
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA.
21 */
22
23 // Own
24 #include "Axis.h"
25
26 // Qt
27 #include <QList>
28 #include <QString>
29 #include <QTextDocument>
30 #include <QPointer>
31
32 // Calligra
33 #include <KoShapeLoadingContext.h>
34 #include <KoShapeSavingContext.h>
35 #include <KoShapeRegistry.h>
36 #include <KoXmlReader.h>
37 #include <KoXmlWriter.h>
38 #include <KoGenStyles.h>
39 #include <KoXmlNS.h>
40 #include <KoTextShapeData.h>
41 #include <KoOdfStylesReader.h>
42 #include <KoUnit.h>
43 #include <KoStyleStack.h>
44 #include <KoOdfLoadingContext.h>
45 #include <KoCharacterStyle.h>
46 #include <KoOdfGraphicStyles.h>
47 #include <KoOdfWorkaround.h>
48 #include <KoTextDocumentLayout.h>
49 #include <KoOdfNumberStyles.h>
50
51 // KChart
52 #include <KChartChart>
53 #include <KChartLegend>
54 #include <KChartCartesianAxis>
55 #include <KChartCartesianCoordinatePlane>
56 #include <KChartRadarCoordinatePlane>
57 #include <KChartGridAttributes>
58 #include <KChartBarDiagram>
59 #include <KChartLineDiagram>
60 #include <KChartPieDiagram>
61 #include <KChartPlotter>
62 #include <KChartStockDiagram>
63 #include <KChartRingDiagram>
64 #include <KChartRadarDiagram>
65 #include <KChartBarAttributes>
66 #include <KChartPieAttributes>
67 #include <KChartThreeDBarAttributes>
68 #include <KChartThreeDPieAttributes>
69 #include <KChartThreeDLineAttributes>
70 #include <KChartBackgroundAttributes>
71 #include <KChartRulerAttributes>
72 #include <kchart_version.h>
73
74 // KoChart
75 #include "PlotArea.h"
76 #include "KChartModel.h"
77 #include "DataSet.h"
78 #include "Legend.h"
79 #include "KChartConvertions.h"
80 #include "ChartProxyModel.h"
81 #include "TextLabelDummy.h"
82 #include "ChartLayout.h"
83 #include "OdfLoadingHelper.h"
84 #include "OdfHelper.h"
85 #include "ChartDebug.h"
86
87
88 using namespace KoChart;
89
90 class Axis::Private
91 {
92 public:
93 Private(Axis *axis, AxisDimension dim);
94 ~Private();
95
96 void adjustAllDiagrams();
97 /// Updates the axis position in the chart's layout
98 /// FIXME: We should instead implement a generic layout position method
99 /// and have the layout find out about our position when it changes.
100 void updatePosition();
101
102 void registerDiagram(KChart::AbstractDiagram *diagram);
103
104 KChart::AbstractDiagram *getDiagramAndCreateIfNeeded(ChartType chartType);
105 KChart::AbstractDiagram *getDiagram(ChartType chartType);
106 void deleteDiagram(ChartType chartType);
107 void deleteDiagram(KChart::AbstractDiagram *diagram);
108
109 void restoreDiagrams();
110
111 void createBarDiagram();
112 void createLineDiagram();
113 void createAreaDiagram();
114 void createCircleDiagram();
115 void createRingDiagram();
116 void createRadarDiagram(bool filled);
117 void createScatterDiagram();
118 void createStockDiagram();
119 void createBubbleDiagram();
120 void createSurfaceDiagram();
121 void createGanttDiagram();
122 void applyAttributesToDataSet(DataSet* set, ChartType newCharttype);
123
124 // Pointer to Axis that owns this Private instance
125 Axis * const q;
126
127 PlotArea *plotArea;
128
129 const AxisDimension dimension;
130
131 KoShape *title;
132 TextLabelData *titleData;
133
134 /// FIXME: Unused variable 'id', including id() getter
135 QString id;
136 QList<DataSet*> dataSets;
137 qreal majorInterval;
138 int minorIntervalDivisor;
139 bool showInnerMinorTicks;
140 bool showOuterMinorTicks;
141 bool showInnerMajorTicks;
142 bool showOuterMajorTicks;
143 bool logarithmicScaling;
144 bool showMajorGrid;
145 bool showMinorGrid;
146 bool useAutomaticMajorInterval;
147 bool useAutomaticMinorInterval;
148 bool useAutomaticMinimumRange;
149 bool useAutomaticMaximumRange;
150
151 KChart::CartesianAxis *const kdAxis;
152 KChart::CartesianCoordinatePlane *kdPlane;
153 KChart::PolarCoordinatePlane *kdPolarPlane;
154 KChart::RadarCoordinatePlane *kdRadarPlane;
155 KoOdfNumberStyles::NumericStyleFormat *numericStyleFormat;
156
157 QList<QPointer<KChart::AbstractCartesianDiagram> > diagrams;
158
159 QPointer<KChart::BarDiagram> kdBarDiagram;
160 QPointer<KChart::LineDiagram> kdLineDiagram;
161 QPointer<KChart::LineDiagram> kdAreaDiagram;
162 QPointer<KChart::PieDiagram> kdCircleDiagram;
163 QPointer<KChart::RingDiagram> kdRingDiagram;
164 QPointer<KChart::RadarDiagram> kdRadarDiagram;
165 QPointer<KChart::Plotter> kdScatterDiagram;
166 QPointer<KChart::StockDiagram> kdStockDiagram;
167 QPointer<KChart::Plotter> kdBubbleDiagram;
168 // FIXME BUG: Somehow we need to visualize something for these
169 // missing chart types. We have some alternatives:
170 // 1. Show an empty area
171 // 2. Show a text "unsupported chart type"
172 // 3. Exchange for something else, e.g. a bar chart.
173 // ... More?
174 //
175 // NOTE: Whatever we do, we should always store the data so that
176 // it can be saved back into the file for a perfect
177 // roundtrip.
178 QPointer<KChart::BarDiagram> kdSurfaceDiagram;
179 QPointer<KChart::BarDiagram> kdGanttDiagram;
180
181 ChartType plotAreaChartType;
182 ChartSubtype plotAreaChartSubType;
183
184 // If KChart::LineDiagram::centerDataPoints() property is set to true,
185 // the data points drawn in a line (i.e., also an area) diagram start at
186 // an offset of 0.5, that is, in the middle of a column in the diagram.
187 // Set flag to true if at least one dataset is attached to this axis
188 // that belongs to a horizontal bar chart
189 bool centerDataPoints;
190
191 int gapBetweenBars;
192 int gapBetweenSets;
193
194 // TODO: Save
195 // See ODF v1.2 $19.12 (chart:display-label)
196 bool showLabels;
197 bool showOverlappingDataLabels;
198
199 bool isVisible;
200 QString name;
201
202 QString axisPosition;
203 QString axisLabelsPosition;
204
205 };
206
207 class CartesianAxis : public KChart::CartesianAxis
208 {
209 public:
CartesianAxis(KoChart::Axis * _axis)210 CartesianAxis(KoChart::Axis *_axis) : KChart::CartesianAxis(), axis(_axis) {}
~CartesianAxis()211 virtual ~CartesianAxis() {}
customizedLabel(const QString & label) const212 const QString customizedLabel(const QString& label) const override {
213 if (KoOdfNumberStyles::NumericStyleFormat *n = axis->numericStyleFormat())
214 return KoOdfNumberStyles::format(label, *n);
215 return label;
216 }
217 private:
218 KoChart::Axis *axis;
219 };
220
Private(Axis * axis,AxisDimension dim)221 Axis::Private::Private(Axis *axis, AxisDimension dim)
222 : q(axis)
223 , dimension(dim)
224 , kdAxis(new CartesianAxis(axis))
225 , kdPlane(0)
226 , kdPolarPlane(0)
227 , kdRadarPlane(0)
228 , numericStyleFormat(0)
229 {
230 centerDataPoints = false;
231
232 gapBetweenBars = 0;
233 gapBetweenSets = 100;
234
235 isVisible = true;
236
237 useAutomaticMajorInterval = true;
238 useAutomaticMinorInterval = true;
239 useAutomaticMinimumRange = true;
240 useAutomaticMaximumRange = true;
241
242 majorInterval = 2;
243 minorIntervalDivisor = 1;
244
245 showMajorGrid = false;
246 showMinorGrid = false;
247
248 logarithmicScaling = false;
249
250 showInnerMinorTicks = false;
251 showOuterMinorTicks = false;
252 showInnerMajorTicks = false;
253 showOuterMajorTicks = true;
254
255 showOverlappingDataLabels = false;
256 showLabels = true;
257
258 title = 0;
259 titleData = 0;
260
261 KChart::RulerAttributes attr = kdAxis->rulerAttributes();
262 attr.setShowRulerLine(true);
263 attr.setRulerLinePen(QPen());
264 kdAxis->setRulerAttributes(attr);
265 }
266
~Private()267 Axis::Private::~Private()
268 {
269 Q_ASSERT(plotArea);
270
271 q->removeAxisFromDiagrams();
272
273 delete kdBarDiagram;
274 delete kdLineDiagram;
275 delete kdAreaDiagram;
276 delete kdCircleDiagram;
277 delete kdRingDiagram;
278 delete kdRadarDiagram;
279 delete kdScatterDiagram;
280 delete kdStockDiagram;
281 delete kdBubbleDiagram;
282 delete kdSurfaceDiagram;
283 delete kdGanttDiagram;
284
285 delete numericStyleFormat;
286
287 delete kdAxis;
288
289 foreach(DataSet *dataSet, dataSets)
290 dataSet->setAttachedAxis(0);
291 }
292
registerDiagram(KChart::AbstractDiagram * diagram)293 void Axis::Private::registerDiagram(KChart::AbstractDiagram *diagram)
294 {
295 QObject::connect(plotArea->proxyModel(), SIGNAL(columnsInserted(QModelIndex,int,int)),
296 diagram->model(), SLOT(slotColumnsInserted(QModelIndex,int,int)));
297
298 QObject::connect(diagram, SIGNAL(propertiesChanged()),
299 plotArea, SLOT(plotAreaUpdate()));
300 QObject::connect(diagram, SIGNAL(layoutChanged(AbstractDiagram*)),
301 plotArea, SLOT(plotAreaUpdate()));
302 QObject::connect(diagram, SIGNAL(modelsChanged()),
303 plotArea, SLOT(plotAreaUpdate()));
304 QObject::connect(diagram, SIGNAL(dataHidden()),
305 plotArea, SLOT(plotAreaUpdate()));
306 }
307
getDiagramAndCreateIfNeeded(ChartType chartType)308 KChart::AbstractDiagram *Axis::Private::getDiagramAndCreateIfNeeded(ChartType chartType)
309 {
310 KChart::AbstractDiagram *diagram = 0;
311
312 switch (chartType) {
313 case BarChartType:
314 if (!kdBarDiagram)
315 createBarDiagram();
316 diagram = kdBarDiagram;
317 break;
318 case LineChartType:
319 if (!kdLineDiagram)
320 createLineDiagram();
321 diagram = kdLineDiagram;
322 break;
323 case AreaChartType:
324 if (!kdAreaDiagram)
325 createAreaDiagram();
326 diagram = kdAreaDiagram;
327 break;
328 case CircleChartType:
329 if (!kdCircleDiagram)
330 createCircleDiagram();
331 diagram = kdCircleDiagram;
332 break;
333 case RingChartType:
334 if (!kdRingDiagram)
335 createRingDiagram();
336 diagram = kdRingDiagram;
337 break;
338 case RadarChartType:
339 case FilledRadarChartType:
340 if (!kdRadarDiagram)
341 createRadarDiagram(chartType == FilledRadarChartType);
342 diagram = kdRadarDiagram;
343 break;
344 case ScatterChartType:
345 if (!kdScatterDiagram)
346 createScatterDiagram();
347 diagram = kdScatterDiagram;
348 break;
349 case StockChartType:
350 if (!kdStockDiagram)
351 createStockDiagram();
352 diagram = kdStockDiagram;
353 break;
354 case BubbleChartType:
355 if (!kdBubbleDiagram)
356 createBubbleDiagram();
357 diagram = kdBubbleDiagram;
358 break;
359 case SurfaceChartType:
360 if (!kdSurfaceDiagram)
361 createSurfaceDiagram();
362 diagram = kdSurfaceDiagram;
363 break;
364 case GanttChartType:
365 if (!kdGanttDiagram)
366 createGanttDiagram();
367 diagram = kdGanttDiagram;
368 break;
369 default:
370 ;
371 }
372 diagram->setObjectName(q->name()); // for debug
373 adjustAllDiagrams();
374 debugChartAxis<<q->name()<<"diagram"<<diagram<<"for"<<chartType;
375 return diagram;
376 }
377
378 /**
379 * Returns currently used internal KChart diagram for the specified chart type
380 */
getDiagram(ChartType chartType)381 KChart::AbstractDiagram *Axis::Private::getDiagram(ChartType chartType)
382 {
383 switch (chartType) {
384 case BarChartType:
385 return kdBarDiagram;
386 case LineChartType:
387 return kdLineDiagram;
388 case AreaChartType:
389 return kdAreaDiagram;
390 case CircleChartType:
391 return kdCircleDiagram;
392 case RingChartType:
393 return kdRingDiagram;
394 case RadarChartType:
395 case FilledRadarChartType:
396 return kdRadarDiagram;
397 case ScatterChartType:
398 return kdScatterDiagram;
399 case StockChartType:
400 return kdStockDiagram;
401 case BubbleChartType:
402 return kdBubbleDiagram;
403 case SurfaceChartType:
404 return kdSurfaceDiagram;
405 case GanttChartType:
406 return kdGanttDiagram;
407 case LastChartType:
408 return 0;
409 // Compiler warning for unhandled chart type is intentional.
410 }
411 Q_ASSERT(!"Unhandled chart type");
412 return 0;
413 }
414
deleteDiagram(KChart::AbstractDiagram * diagram)415 void Axis::Private::deleteDiagram(KChart::AbstractDiagram *diagram)
416 {
417 Q_ASSERT(diagram);
418 if (diagram->coordinatePlane()) {
419 diagram->coordinatePlane()->takeDiagram(diagram);
420 }
421 delete diagram;
422 adjustAllDiagrams();
423 }
424
deleteDiagram(ChartType chartType)425 void Axis::Private::deleteDiagram(ChartType chartType)
426 {
427 KChart::AbstractDiagram *diagram = getDiagram(chartType);
428 if (diagram) {
429 deleteDiagram(diagram);
430 }
431 }
432
433
createBarDiagram()434 void Axis::Private::createBarDiagram()
435 {
436 Q_ASSERT(kdBarDiagram == 0);
437
438 kdBarDiagram = new KChart::BarDiagram(plotArea->kdChart(), kdPlane);
439 KChartModel *model = new KChartModel(plotArea, kdBarDiagram);
440 kdBarDiagram->setModel(model);
441 registerDiagram(kdBarDiagram);
442
443 // By 'vertical', KChart means the orientation of a chart's bars,
444 // not the orientation of the x axis.
445 kdBarDiagram->setOrientation(plotArea->isVertical() ? Qt::Horizontal : Qt::Vertical);
446 kdBarDiagram->setPen(QPen(Qt::black, 0.0));
447
448 kdBarDiagram->setAllowOverlappingDataValueTexts(showOverlappingDataLabels);
449
450 if (plotAreaChartSubType == StackedChartSubtype)
451 kdBarDiagram->setType(KChart::BarDiagram::Stacked);
452 else if (plotAreaChartSubType == PercentChartSubtype) {
453 kdBarDiagram->setType(KChart::BarDiagram::Percent);
454 kdBarDiagram->setUnitSuffix("%", kdBarDiagram->orientation());
455 }
456
457 if (isVisible) {
458 kdBarDiagram->addAxis(kdAxis);
459 q->registerDiagram(kdBarDiagram);
460 }
461 kdPlane->addDiagram(kdBarDiagram);
462
463 Q_ASSERT(plotArea);
464 foreach (Axis *axis, plotArea->axes()) {
465 if (axis->isVisible() && axis->dimension() == XAxisDimension) {
466 kdBarDiagram->addAxis(axis->kdAxis());
467 axis->registerDiagram(kdBarDiagram);
468 }
469 }
470
471 // Set default bar diagram attributes
472 q->setGapBetweenBars(gapBetweenBars);
473 q->setGapBetweenSets(gapBetweenSets);
474
475 // Propagate existing settings
476 KChart::ThreeDBarAttributes attributes(kdBarDiagram->threeDBarAttributes());
477 attributes.setEnabled(plotArea->isThreeD());
478 attributes.setThreeDBrushEnabled(plotArea->isThreeD());
479 kdBarDiagram->setThreeDBarAttributes(attributes);
480
481 q->plotAreaIsVerticalChanged();
482
483 plotArea->parent()->legend()->kdLegend()->addDiagram(kdBarDiagram);
484 }
485
createLineDiagram()486 void Axis::Private::createLineDiagram()
487 {
488 Q_ASSERT(kdLineDiagram == 0);
489
490 kdLineDiagram = new KChart::LineDiagram(plotArea->kdChart(), kdPlane);
491 KChartModel *model = new KChartModel(plotArea, kdLineDiagram);
492 kdLineDiagram->setModel(model);
493 registerDiagram(kdLineDiagram);
494
495 kdLineDiagram->setAllowOverlappingDataValueTexts(showOverlappingDataLabels);
496
497 if (plotAreaChartSubType == StackedChartSubtype)
498 kdLineDiagram->setType(KChart::LineDiagram::Stacked);
499 else if (plotAreaChartSubType == PercentChartSubtype)
500 kdLineDiagram->setType(KChart::LineDiagram::Percent);
501
502 if (isVisible) {
503 kdLineDiagram->addAxis(kdAxis);
504 q->registerDiagram(kdLineDiagram);
505 }
506 kdPlane->addDiagram(kdLineDiagram);
507
508 Q_ASSERT(plotArea);
509 foreach (Axis *axis, plotArea->axes()) {
510 if (axis->isVisible() && axis->dimension() == XAxisDimension) {
511 kdLineDiagram->addAxis(axis->kdAxis());
512 axis->registerDiagram(kdLineDiagram);
513 }
514 }
515
516 // Propagate existing settings
517 KChart::ThreeDLineAttributes attributes(kdLineDiagram->threeDLineAttributes());
518 attributes.setEnabled(plotArea->isThreeD());
519 attributes.setThreeDBrushEnabled(plotArea->isThreeD());
520 kdLineDiagram->setThreeDLineAttributes(attributes);
521
522 KChart::LineAttributes lineAttr = kdLineDiagram->lineAttributes();
523 lineAttr.setMissingValuesPolicy(KChart::LineAttributes::MissingValuesHideSegments);
524 kdLineDiagram->setLineAttributes(lineAttr);
525
526 plotArea->parent()->legend()->kdLegend()->addDiagram(kdLineDiagram);
527 }
528
createAreaDiagram()529 void Axis::Private::createAreaDiagram()
530 {
531 Q_ASSERT(kdAreaDiagram == 0);
532
533 kdAreaDiagram = new KChart::LineDiagram(plotArea->kdChart(), kdPlane);
534 KChartModel *model = new KChartModel(plotArea, kdAreaDiagram);
535 kdAreaDiagram->setModel(model);
536 registerDiagram(kdAreaDiagram);
537 KChart::LineAttributes attr = kdAreaDiagram->lineAttributes();
538 // Draw the area under the lines. This makes this diagram an area chart.
539 attr.setDisplayArea(true);
540 kdAreaDiagram->setLineAttributes(attr);
541 kdAreaDiagram->setPen(QPen(Qt::black, 0.0));
542 // KD Chart by default draws the first data set as last line in a normal
543 // line diagram, we however want the first series to appear in front.
544 kdAreaDiagram->setReverseDatasetOrder(true);
545
546 kdAreaDiagram->setAllowOverlappingDataValueTexts(showOverlappingDataLabels);
547
548 if (plotAreaChartSubType == StackedChartSubtype)
549 kdAreaDiagram->setType(KChart::LineDiagram::Stacked);
550 else if (plotAreaChartSubType == PercentChartSubtype)
551 {
552 kdAreaDiagram->setType(KChart::LineDiagram::Percent);
553 kdAreaDiagram->setUnitSuffix("%", Qt::Vertical);
554 }
555
556 if (isVisible) {
557 kdAreaDiagram->addAxis(kdAxis);
558 q->registerDiagram(kdAreaDiagram);
559 }
560 kdPlane->addDiagram(kdAreaDiagram);
561
562 Q_ASSERT(plotArea);
563 foreach (Axis *axis, plotArea->axes()) {
564 if (axis->isVisible() && axis->dimension() == XAxisDimension) {
565 kdAreaDiagram->addAxis(axis->kdAxis());
566 axis->registerDiagram(kdAreaDiagram);
567 }
568 }
569
570 // Propagate existing settings
571 KChart::ThreeDLineAttributes attributes(kdAreaDiagram->threeDLineAttributes());
572 attributes.setEnabled(plotArea->isThreeD());
573 attributes.setThreeDBrushEnabled(plotArea->isThreeD());
574 kdAreaDiagram->setThreeDLineAttributes(attributes);
575
576 plotArea->parent()->legend()->kdLegend()->addDiagram(kdAreaDiagram);
577 }
578
createCircleDiagram()579 void Axis::Private::createCircleDiagram()
580 {
581 Q_ASSERT(kdCircleDiagram == 0);
582
583 kdCircleDiagram = new KChart::PieDiagram(plotArea->kdChart(), kdPolarPlane);
584 KChartModel *model = new KChartModel(plotArea, kdCircleDiagram);
585 kdCircleDiagram->setModel(model);
586 registerDiagram(kdCircleDiagram);
587
588 model->setDataDirection(Qt::Horizontal);
589
590 plotArea->parent()->legend()->kdLegend()->addDiagram(kdCircleDiagram);
591 kdPolarPlane->addDiagram(kdCircleDiagram);
592
593 // Propagate existing settings
594 KChart::ThreeDPieAttributes attributes(kdCircleDiagram->threeDPieAttributes());
595 attributes.setEnabled(plotArea->isThreeD());
596 attributes.setThreeDBrushEnabled(plotArea->isThreeD());
597 kdCircleDiagram->setThreeDPieAttributes(attributes);
598
599 // Initialize with default values that are specified in PlotArea
600 // Note: KChart takes an int here, though ODF defines the offset to be a double.
601 kdPolarPlane->setStartPosition((int)plotArea->angleOffset());
602 }
603
createRingDiagram()604 void Axis::Private::createRingDiagram()
605 {
606 Q_ASSERT(kdRingDiagram == 0);
607
608 kdRingDiagram = new KChart::RingDiagram(plotArea->kdChart(), kdPolarPlane);
609 KChartModel *model = new KChartModel(plotArea, kdRingDiagram);
610 kdRingDiagram->setModel(model);
611 registerDiagram(kdRingDiagram);
612
613 model->setDataDirection(Qt::Horizontal);
614
615 plotArea->parent()->legend()->kdLegend()->addDiagram(kdRingDiagram);
616 kdPolarPlane->addDiagram(kdRingDiagram);
617
618 // Propagate existing settings
619 KChart::ThreeDPieAttributes attributes(kdRingDiagram->threeDPieAttributes());
620 attributes.setEnabled(plotArea->isThreeD());
621 attributes.setThreeDBrushEnabled(plotArea->isThreeD());
622 kdRingDiagram->setThreeDPieAttributes(attributes);
623
624 // Initialize with default values that are specified in PlotArea
625 // Note: KChart takes an int here, though ODF defines the offset to be a double.
626 kdPolarPlane->setStartPosition((int)plotArea->angleOffset());
627 }
628
createRadarDiagram(bool filled)629 void Axis::Private::createRadarDiagram(bool filled)
630 {
631 Q_ASSERT(kdRadarDiagram == 0);
632
633 //kdRadarDiagramModel->setDataDimensions(2);
634 //kdRadarDiagramModel->setDataDirection(Qt::Horizontal);
635
636 kdRadarDiagram = new KChart::RadarDiagram(plotArea->kdChart(), kdRadarPlane);
637 KChartModel *model = new KChartModel(plotArea, kdRadarDiagram);
638 kdRadarDiagram->setModel(model);
639 registerDiagram(kdRadarDiagram);
640
641 kdRadarDiagram->setCloseDatasets(true);
642
643 if (filled) {
644 // Don't use a solid fill of 1.0 but a more transparent one so the
645 // grid and the data-value-labels are still visible plus it provides
646 // a better look (other areas can still be seen) even if it's slightly
647 // different from what OO.org does.
648 kdRadarDiagram->setFillAlpha(0.4);
649 }
650
651 #if 0 // Stacked and Percent not supported by KChart.
652 if (plotAreaChartSubType == StackedChartSubtype)
653 kdRadarDiagram->setType(KChart::PolarDiagram::Stacked);
654 else if (plotAreaChartSubType == PercentChartSubtype)
655 kdRadarDiagram->setType(KChart::PolarDiagram::Percent);
656 #endif
657 plotArea->parent()->legend()->kdLegend()->addDiagram(kdRadarDiagram);
658 kdRadarPlane->addDiagram(kdRadarDiagram);
659 }
660
createScatterDiagram()661 void Axis::Private::createScatterDiagram()
662 {
663 Q_ASSERT(kdScatterDiagram == 0);
664 Q_ASSERT(plotArea);
665
666 kdScatterDiagram = new KChart::Plotter(plotArea->kdChart(), kdPlane);
667 KChartModel *model = new KChartModel(plotArea, kdScatterDiagram);
668 kdScatterDiagram->setModel(model);
669 registerDiagram(kdScatterDiagram);
670
671 model->setDataDimensions(2);
672
673 kdScatterDiagram->setPen(Qt::NoPen);
674
675 if (isVisible) {
676 kdScatterDiagram->addAxis(kdAxis);
677 q->registerDiagram(kdScatterDiagram);
678 }
679 kdPlane->addDiagram(kdScatterDiagram);
680
681
682 foreach (Axis *axis, plotArea->axes()) {
683 if (axis->isVisible() && axis->dimension() == XAxisDimension) {
684 kdScatterDiagram->addAxis(axis->kdAxis());
685 axis->registerDiagram(kdScatterDiagram);
686 }
687 }
688
689 // Propagate existing settings
690 KChart::ThreeDLineAttributes attributes(kdScatterDiagram->threeDLineAttributes());
691 attributes.setEnabled(plotArea->isThreeD());
692 attributes.setThreeDBrushEnabled(plotArea->isThreeD());
693 kdScatterDiagram->setThreeDLineAttributes(attributes);
694
695 plotArea->parent()->legend()->kdLegend()->addDiagram(kdScatterDiagram);
696 }
697
createStockDiagram()698 void Axis::Private::createStockDiagram()
699 {
700 Q_ASSERT(kdStockDiagram == 0);
701
702 kdStockDiagram = new KChart::StockDiagram(plotArea->kdChart(), kdPlane);
703 KChartModel *model = new KChartModel(plotArea, kdStockDiagram);
704 kdStockDiagram->setModel(model);
705 switch (plotAreaChartSubType) {
706 case HighLowCloseChartSubtype:
707 kdStockDiagram->setType(KChart::StockDiagram::HighLowClose);
708 break;
709 case OpenHighLowCloseChartSubtype:
710 kdStockDiagram->setType(KChart::StockDiagram::OpenHighLowClose);
711 break;
712 case CandlestickChartSubtype:
713 kdStockDiagram->setType(KChart::StockDiagram::Candlestick);
714 break;
715 }
716 registerDiagram(kdStockDiagram);
717
718 model->setDataDimensions(numDimensions(StockChartType));
719
720 #if 0 // Stacked and Percent not supported by KChart.
721 if (plotAreaChartSubType == StackedChartSubtype)
722 kdStockDiagram->setType(KChart::StockDiagram::Stacked);
723 else if (plotAreaChartSubType == PercentChartSubtype)
724 kdStockDiagram->setType(KChart::StockDiagram::Percent);
725 #endif
726
727 if (isVisible) {
728 kdStockDiagram->addAxis(kdAxis);
729 q->registerDiagram(kdStockDiagram);
730 }
731 kdPlane->addDiagram(kdStockDiagram);
732
733 Q_ASSERT(plotArea);
734 foreach (Axis *axis, plotArea->axes()) {
735 if (axis->isVisible() && axis->dimension() == XAxisDimension) {
736 kdStockDiagram->addAxis(axis->kdAxis());
737 axis->registerDiagram(kdStockDiagram);
738 }
739 }
740
741 plotArea->parent()->legend()->kdLegend()->addDiagram(kdStockDiagram);
742 q->updateKChartStockAttributes();
743 }
744
createBubbleDiagram()745 void Axis::Private::createBubbleDiagram()
746 {
747 Q_ASSERT(kdBubbleDiagram == 0);
748 Q_ASSERT(plotArea);
749
750 kdBubbleDiagram = new KChart::Plotter(plotArea->kdChart(), kdPlane);
751 KChartModel *model = new KChartModel(plotArea, kdBubbleDiagram);
752 kdBubbleDiagram->setModel(model);
753 registerDiagram(kdBubbleDiagram);
754
755 model->setDataDimensions(2);
756
757 kdPlane->addDiagram(kdBubbleDiagram);
758
759 foreach (Axis *axis, plotArea->axes()) {
760 //if (axis->dimension() == XAxisDimension)
761 if (axis->isVisible() && axis->dimension() == XAxisDimension) {
762 kdBubbleDiagram->addAxis(axis->kdAxis());
763 q->registerDiagram(kdBubbleDiagram);
764 }
765 }
766
767 // disable the connecting line
768 KChart::LineAttributes la = kdBubbleDiagram->lineAttributes();
769 la.setVisible(false);
770 kdBubbleDiagram->setLineAttributes(la);
771
772 plotArea->parent()->legend()->kdLegend()->addDiagram(kdBubbleDiagram);
773 }
774
createSurfaceDiagram()775 void Axis::Private::createSurfaceDiagram()
776 {
777 Q_ASSERT(!kdSurfaceDiagram);
778
779 // This is a so far a by KChart unsupported chart type.
780 // Fall back to bar diagram for now.
781 kdSurfaceDiagram = new KChart::BarDiagram(plotArea->kdChart(), kdPlane);
782 KChartModel *model = new KChartModel(plotArea, kdSurfaceDiagram);
783 kdSurfaceDiagram->setModel(model);
784 registerDiagram(kdSurfaceDiagram);
785 plotArea->parent()->legend()->kdLegend()->addDiagram(kdSurfaceDiagram);
786 kdPlane->addDiagram(kdSurfaceDiagram);
787 }
788
createGanttDiagram()789 void Axis::Private::createGanttDiagram()
790 {
791 // This is a so far a by KChart unsupported chart type (through KDGantt got merged into KChart with 2.3)
792 Q_ASSERT(!kdGanttDiagram);
793
794 // This is a so far a by KChart unsupported chart type.
795 // Fall back to bar diagram for now.
796 kdGanttDiagram = new KChart::BarDiagram(plotArea->kdChart(), kdPlane);
797 KChartModel *model = new KChartModel(plotArea, kdGanttDiagram);
798 kdGanttDiagram->setModel(model);
799 registerDiagram(kdGanttDiagram);
800 plotArea->parent()->legend()->kdLegend()->addDiagram(kdGanttDiagram);
801 kdPlane->addDiagram(kdGanttDiagram);
802 }
803
804 /**
805 * Automatically adjusts the diagram so that all currently displayed
806 * diagram types fit together.
807 */
adjustAllDiagrams()808 void Axis::Private::adjustAllDiagrams()
809 {
810 // If at least one dataset is attached that belongs to a
811 // horizontal bar chart, set centerDataPoints to true.
812 centerDataPoints = kdBarDiagram != 0;
813 if (kdLineDiagram)
814 kdLineDiagram->setCenterDataPoints(centerDataPoints);
815 if (kdAreaDiagram)
816 kdAreaDiagram->setCenterDataPoints(centerDataPoints);
817 }
818
819
820 // ================================================================
821 // class Axis
822
823 // FIXME: make it possible to create an axis without parent, and
824 // when it is removed, actually remove it from parent (signals and all)
825
Axis(PlotArea * parent,AxisDimension dimension)826 Axis::Axis(PlotArea *parent, AxisDimension dimension)
827 : d(new Private(this, dimension))
828 {
829 Q_ASSERT(parent);
830
831 parent->addAxis(this);
832
833 d->plotArea = parent;
834 d->kdAxis->setObjectName(name());
835 KChart::BackgroundAttributes batt(d->kdAxis->backgroundAttributes());
836 batt.setBrush(QBrush(Qt::white));
837 d->kdAxis->setBackgroundAttributes(batt);
838 setFontSize(8.0); // also sets MeasureCalculationModeAbsolute
839 d->kdPlane = parent->kdCartesianPlane(this);
840 d->kdPolarPlane = parent->kdPolarPlane();
841 d->kdRadarPlane = parent->kdRadarPlane();
842
843 d->plotAreaChartType = d->plotArea->chartType();
844 d->plotAreaChartSubType = d->plotArea->chartSubType();
845
846 setOdfAxisPosition("start");
847 setOdfAxisLabelsPosition("near-axis");
848
849 KoShapeFactoryBase *textShapeFactory = KoShapeRegistry::instance()->value(TextShapeId);
850 if (textShapeFactory)
851 d->title = textShapeFactory->createDefaultShape(parent->parent()->resourceManager());
852 if (d->title) {
853 d->titleData = qobject_cast<TextLabelData*>(d->title->userData());
854 if (d->titleData == 0) {
855 d->titleData = new TextLabelData;
856 d->title->setUserData(d->titleData);
857 }
858
859 QFont font = d->titleData->document()->defaultFont();
860 font.setPointSizeF(9);
861 d->titleData->document()->setDefaultFont(font);
862 }
863 else {
864 d->title = new TextLabelDummy;
865 d->titleData = new TextLabelData;
866 KoTextDocumentLayout *documentLayout = new KoTextDocumentLayout(d->titleData->document());
867 d->titleData->document()->setDocumentLayout(documentLayout);
868 d->title->setUserData(d->titleData);
869 }
870 d->title->setSize(QSizeF(CM_TO_POINT(3), CM_TO_POINT(0.75)));
871
872 d->plotArea->parent()->addShape(d->title);
873 d->plotArea->parent()->setClipped(d->title, true);
874 d->plotArea->parent()->setInheritsTransform(d->title, true);
875 d->title->setDeletable(false);
876 d->title->setZIndex(5);
877 d->title->setToolDelegates(QSet<KoShape*>()<<parent->parent()<<d->title); // Enable chart tool
878 d->titleData->setResizeMethod(KoTextShapeDataBase::AutoResize);
879 d->title->setAdditionalStyleAttribute("chart:auto-position", "true");
880 d->title->setAllowedInteraction(KoShape::ShearingAllowed, false);
881 d->title->setAllowedInteraction(KoShape::RotationAllowed, false);
882 d->title->setAllowedInteraction(KoShape::ResizeAllowed, false);
883 d->title->setVisible(false); // Needed to avoid problems when creating secondary axes (Axis creation needs review/refactoring)
884
885 connect(d->plotArea, SIGNAL(angleOffsetChanged(qreal)), this, SLOT(setAngleOffset(qreal)));
886 connect(d->plotArea, SIGNAL(holeSizeChanged(qreal)), this, SLOT(setHoleSize(qreal)));
887
888 d->updatePosition();
889 }
890
~Axis()891 Axis::~Axis()
892 {
893 Q_ASSERT(d->plotArea);
894 d->plotArea->parent()->KoShapeContainer::removeShape(d->title);
895
896 Q_ASSERT(d->title);
897 delete d->title;
898
899 delete d;
900 }
901
plotArea() const902 PlotArea* Axis::plotArea() const
903 {
904 return d->plotArea;
905 }
906
title() const907 KoShape *Axis::title() const
908 {
909 return d->title;
910 }
911
titleText() const912 QString Axis::titleText() const
913 {
914 return d->titleData->document()->toPlainText();
915 }
916
showLabels() const917 bool Axis::showLabels() const
918 {
919 return d->showLabels;
920 }
921
showOverlappingDataLabels() const922 bool Axis::showOverlappingDataLabels() const
923 {
924 return d->showOverlappingDataLabels;
925 }
926
id() const927 QString Axis::id() const
928 {
929 return d->id;
930 }
931
dimension() const932 AxisDimension Axis::dimension() const
933 {
934 return d->dimension;
935 }
936
dataSets() const937 QList<DataSet*> Axis::dataSets() const
938 {
939 return d->dataSets;
940 }
941
attachDataSet(DataSet * dataSet)942 bool Axis::attachDataSet(DataSet *dataSet)
943 {
944 Q_ASSERT(!d->dataSets.contains(dataSet));
945 if (d->dataSets.contains(dataSet))
946 return false;
947
948 d->dataSets.append(dataSet);
949
950 if (dimension() == YAxisDimension) {
951 dataSet->setAttachedAxis(this);
952
953 ChartType chartType = dataSet->chartType();
954 if (chartType == LastChartType)
955 chartType = d->plotAreaChartType;
956
957 KChart::AbstractDiagram *diagram = d->getDiagramAndCreateIfNeeded(chartType);
958 Q_ASSERT(diagram);
959 KChartModel *model = dynamic_cast<KChartModel*>(diagram->model());
960 Q_ASSERT(model);
961
962 model->addDataSet(dataSet);
963
964 layoutPlanes();
965 requestRepaint();
966 }
967
968 return true;
969 }
970
detachDataSet(DataSet * dataSet,bool silent)971 bool Axis::detachDataSet(DataSet *dataSet, bool silent)
972 {
973 Q_ASSERT(d->dataSets.contains(dataSet));
974 if (!d->dataSets.contains(dataSet))
975 return false;
976 d->dataSets.removeAll(dataSet);
977
978 if (dimension() == YAxisDimension) {
979 ChartType chartType = dataSet->chartType();
980 if (chartType == LastChartType)
981 chartType = d->plotAreaChartType;
982
983 KChart::AbstractDiagram *oldDiagram = d->getDiagram(chartType);
984 Q_ASSERT(oldDiagram);
985 KChartModel *oldModel = dynamic_cast<KChartModel*>(oldDiagram->model());
986 Q_ASSERT(oldModel);
987
988 const int rowCount = oldModel->dataDirection() == Qt::Vertical
989 ? oldModel->columnCount() : oldModel->rowCount();
990 // If there's only as many rows as needed for *one*
991 // dataset, that means that the dataset we're removing is
992 // the last one in the model --> delete model
993 if (rowCount == oldModel->dataDimensions())
994 d->deleteDiagram(chartType);
995 else
996 oldModel->removeDataSet(dataSet, silent);
997
998 dataSet->setKdChartModel(0);
999 dataSet->setAttachedAxis(0);
1000
1001 if (!silent) {
1002 layoutPlanes();
1003 requestRepaint();
1004 }
1005 }
1006
1007 return true;
1008 }
1009
clearDataSets()1010 void Axis::clearDataSets()
1011 {
1012 QList<DataSet*> list = d->dataSets;
1013 foreach(DataSet *dataSet, list)
1014 detachDataSet(dataSet, true);
1015 }
1016
showRuler() const1017 bool Axis::showRuler() const
1018 {
1019 return d->kdAxis->rulerAttributes().showRulerLine();
1020 }
1021
setShowRuler(bool show)1022 void Axis::setShowRuler(bool show)
1023 {
1024 KChart::RulerAttributes attr = d->kdAxis->rulerAttributes();
1025 attr.setShowRulerLine(!attr.showRulerLine());
1026 d->kdAxis->setRulerAttributes(attr);
1027 }
1028
majorInterval() const1029 qreal Axis::majorInterval() const
1030 {
1031 return d->majorInterval;
1032 }
1033
setMajorInterval(qreal interval)1034 void Axis::setMajorInterval(qreal interval)
1035 {
1036 // Don't overwrite if automatic interval is being requested (for
1037 // interval = 0)
1038 if (interval != 0.0) {
1039 d->majorInterval = interval;
1040 d->useAutomaticMajorInterval = false;
1041 } else
1042 d->useAutomaticMajorInterval = true;
1043
1044 // KChart
1045 KChart::GridAttributes attributes = d->kdPlane->gridAttributes(orientation());
1046 attributes.setGridStepWidth(interval);
1047 d->kdPlane->setGridAttributes(orientation(), attributes);
1048
1049 attributes = d->kdPolarPlane->gridAttributes(true);
1050 attributes.setGridStepWidth(interval);
1051 d->kdPolarPlane->setGridAttributes(true, attributes);
1052
1053 // FIXME: Hide minor tick marks more appropriately
1054 if (!d->showMinorGrid && interval != 0.0)
1055 setMinorInterval(interval);
1056
1057 requestRepaint();
1058 }
1059
minorInterval() const1060 qreal Axis::minorInterval() const
1061 {
1062 return (d->majorInterval / (qreal)d->minorIntervalDivisor);
1063 }
1064
setMinorInterval(qreal interval)1065 void Axis::setMinorInterval(qreal interval)
1066 {
1067 if (interval == 0.0)
1068 setMinorIntervalDivisor(0);
1069 else
1070 setMinorIntervalDivisor(int(qRound(d->majorInterval / interval)));
1071 }
1072
minorIntervalDivisor() const1073 int Axis::minorIntervalDivisor() const
1074 {
1075 return d->minorIntervalDivisor;
1076 }
1077
setMinorIntervalDivisor(int divisor)1078 void Axis::setMinorIntervalDivisor(int divisor)
1079 {
1080 // A divisor of 0.0 means automatic minor interval calculation
1081 if (divisor != 0) {
1082 d->minorIntervalDivisor = divisor;
1083 d->useAutomaticMinorInterval = false;
1084 } else
1085 d->useAutomaticMinorInterval = true;
1086
1087 // KChart
1088 KChart::GridAttributes attributes = d->kdPlane->gridAttributes(orientation());
1089 attributes.setGridSubStepWidth((divisor != 0) ? (d->majorInterval / divisor) : 0.0);
1090 d->kdPlane->setGridAttributes(orientation(), attributes);
1091
1092 attributes = d->kdPolarPlane->gridAttributes(true);
1093 attributes.setGridSubStepWidth((divisor != 0) ? (d->majorInterval / divisor) : 0.0);
1094 d->kdPolarPlane->setGridAttributes(true, attributes);
1095
1096 requestRepaint();
1097 }
1098
useAutomaticMajorInterval() const1099 bool Axis::useAutomaticMajorInterval() const
1100 {
1101 return d->useAutomaticMajorInterval;
1102 }
1103
useAutomaticMinorInterval() const1104 bool Axis::useAutomaticMinorInterval() const
1105 {
1106 return d->useAutomaticMinorInterval;
1107 }
1108
setUseAutomaticMajorInterval(bool automatic)1109 void Axis::setUseAutomaticMajorInterval(bool automatic)
1110 {
1111 d->useAutomaticMajorInterval = automatic;
1112 // A value of 0.0 will activate automatic intervals,
1113 // but not change d->majorInterval
1114 setMajorInterval(automatic ? 0.0 : majorInterval());
1115 }
1116
setUseAutomaticMinorInterval(bool automatic)1117 void Axis::setUseAutomaticMinorInterval(bool automatic)
1118 {
1119 d->useAutomaticMinorInterval = automatic;
1120 // A value of 0.0 will activate automatic intervals,
1121 // but not change d->minorIntervalDivisor
1122 setMinorInterval(automatic ? 0.0 : minorInterval());
1123 }
1124
showInnerMinorTicks() const1125 bool Axis::showInnerMinorTicks() const
1126 {
1127 return d->showInnerMinorTicks;
1128 }
1129
showOuterMinorTicks() const1130 bool Axis::showOuterMinorTicks() const
1131 {
1132 return d->showOuterMinorTicks;
1133 }
1134
showInnerMajorTicks() const1135 bool Axis::showInnerMajorTicks() const
1136 {
1137 return d->showInnerMinorTicks;
1138 }
1139
showOuterMajorTicks() const1140 bool Axis::showOuterMajorTicks() const
1141 {
1142 return d->showOuterMajorTicks;
1143 }
1144
setShowInnerMinorTicks(bool showTicks)1145 void Axis::setShowInnerMinorTicks(bool showTicks)
1146 {
1147 d->showInnerMinorTicks = showTicks;
1148 KChart::RulerAttributes attr = kdAxis()->rulerAttributes();
1149 attr.setShowMinorTickMarks(d->showInnerMinorTicks || d->showOuterMinorTicks);
1150 kdAxis()->setRulerAttributes(attr);
1151 }
1152
setShowOuterMinorTicks(bool showTicks)1153 void Axis::setShowOuterMinorTicks(bool showTicks)
1154 {
1155 d->showOuterMinorTicks = showTicks;
1156 KChart::RulerAttributes attr = kdAxis()->rulerAttributes();
1157 attr.setShowMinorTickMarks(d->showInnerMinorTicks || d->showOuterMinorTicks);
1158 kdAxis()->setRulerAttributes(attr);
1159 }
1160
setShowInnerMajorTicks(bool showTicks)1161 void Axis::setShowInnerMajorTicks(bool showTicks)
1162 {
1163 d->showInnerMajorTicks = showTicks;
1164 KChart::RulerAttributes attr = kdAxis()->rulerAttributes();
1165 attr.setShowMajorTickMarks(d->showInnerMajorTicks || d->showOuterMajorTicks);
1166 kdAxis()->setRulerAttributes(attr);
1167 }
1168
setShowOuterMajorTicks(bool showTicks)1169 void Axis::setShowOuterMajorTicks(bool showTicks)
1170 {
1171 d->showOuterMajorTicks = showTicks;
1172 KChart::RulerAttributes attr = kdAxis()->rulerAttributes();
1173 attr.setShowMajorTickMarks(d->showInnerMajorTicks || d->showOuterMajorTicks);
1174 kdAxis()->setRulerAttributes(attr);
1175 }
1176
setScalingLogarithmic(bool logarithmicScaling)1177 void Axis::setScalingLogarithmic(bool logarithmicScaling)
1178 {
1179 d->logarithmicScaling = logarithmicScaling;
1180
1181 if (dimension() != YAxisDimension)
1182 return;
1183
1184 d->kdPlane->setAxesCalcModeY(d->logarithmicScaling
1185 ? KChart::AbstractCoordinatePlane::Logarithmic
1186 : KChart::AbstractCoordinatePlane::Linear);
1187 d->kdPlane->layoutPlanes();
1188
1189 requestRepaint();
1190 }
1191
scalingIsLogarithmic() const1192 bool Axis::scalingIsLogarithmic() const
1193 {
1194 return d->logarithmicScaling;
1195 }
1196
showMajorGrid() const1197 bool Axis::showMajorGrid() const
1198 {
1199 return d->showMajorGrid;
1200 }
1201
setShowMajorGrid(bool showGrid)1202 void Axis::setShowMajorGrid(bool showGrid)
1203 {
1204 d->showMajorGrid = showGrid;
1205
1206 // KChart
1207 KChart::GridAttributes attributes = d->kdPlane->gridAttributes(orientation());
1208 attributes.setGridVisible(d->showMajorGrid);
1209 d->kdPlane->setGridAttributes(orientation(), attributes);
1210
1211 attributes = d->kdPolarPlane->gridAttributes(true);
1212 attributes.setGridVisible(d->showMajorGrid);
1213 d->kdPolarPlane->setGridAttributes(true, attributes);
1214
1215 requestRepaint();
1216 }
1217
showMinorGrid() const1218 bool Axis::showMinorGrid() const
1219 {
1220 return d->showMinorGrid;
1221 }
1222
setShowMinorGrid(bool showGrid)1223 void Axis::setShowMinorGrid(bool showGrid)
1224 {
1225 d->showMinorGrid = showGrid;
1226
1227 // KChart
1228 KChart::GridAttributes attributes = d->kdPlane->gridAttributes(orientation());
1229 attributes.setSubGridVisible(d->showMinorGrid);
1230 d->kdPlane->setGridAttributes(orientation(), attributes);
1231
1232 attributes = d->kdPolarPlane->gridAttributes(true);
1233 attributes.setSubGridVisible(d->showMinorGrid);
1234 d->kdPolarPlane->setGridAttributes(true, attributes);
1235
1236 requestRepaint();
1237 }
1238
setTitleText(const QString & text)1239 void Axis::setTitleText(const QString &text)
1240 {
1241 d->titleData->document()->setPlainText(text);
1242 }
1243
setShowLabels(bool show)1244 void Axis::setShowLabels(bool show)
1245 {
1246 d->showLabels = show;
1247
1248 KChart::TextAttributes textAttr = d->kdAxis->textAttributes();
1249 textAttr.setVisible(show);
1250 d->kdAxis->setTextAttributes(textAttr);
1251 }
1252
setShowOverlappingDataLabels(bool show)1253 void Axis::setShowOverlappingDataLabels(bool show)
1254 {
1255 d->showOverlappingDataLabels = show;
1256 }
1257
orientation() const1258 Qt::Orientation Axis::orientation() const
1259 {
1260 bool chartIsVertical = d->plotArea->isVertical();
1261 bool horizontal = d->dimension == (chartIsVertical ? YAxisDimension
1262 : XAxisDimension);
1263 return horizontal ? Qt::Horizontal : Qt::Vertical;
1264 }
1265
loadOdf(const KoXmlElement & axisElement,KoShapeLoadingContext & context)1266 bool Axis::loadOdf(const KoXmlElement &axisElement, KoShapeLoadingContext &context)
1267 {
1268 KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
1269 KoOdfStylesReader &stylesReader = context.odfLoadingContext().stylesReader();
1270 OdfLoadingHelper *helper = (OdfLoadingHelper*)context.sharedData(OdfLoadingHelperId);
1271 bool reverseAxis = false;
1272
1273 d->title->setVisible(false);
1274
1275 QPen gridPen(Qt::NoPen);
1276 QPen subGridPen(Qt::NoPen);
1277
1278 d->showMajorGrid = false;
1279 d->showMinorGrid = false;
1280
1281 d->showInnerMinorTicks = false;
1282 d->showOuterMinorTicks = false;
1283 d->showInnerMajorTicks = false;
1284 d->showOuterMajorTicks = true;
1285
1286 // Use automatic interval calculation by default
1287 setMajorInterval(0.0);
1288 setMinorInterval(0.0);
1289
1290 if (!axisElement.isNull()) {
1291
1292 QString styleName = axisElement.attributeNS(KoXmlNS::chart, "style-name", QString());
1293 const KoXmlElement *stylElement = stylesReader.findStyle(styleName, "chart");
1294 if (stylElement) {
1295 const QString dataStyleName = stylElement->attributeNS(KoXmlNS::style, "data-style-name", QString());
1296 if (!dataStyleName.isEmpty() && stylesReader.dataFormats().contains(dataStyleName)) {
1297 delete d->numericStyleFormat;
1298 d->numericStyleFormat = new KoOdfNumberStyles::NumericStyleFormat(stylesReader.dataFormats()[dataStyleName].first);
1299 }
1300 }
1301
1302 KoXmlElement n;
1303 forEachElement (n, axisElement) {
1304 if (n.namespaceURI() != KoXmlNS::chart)
1305 continue;
1306 if (n.localName() == "title") {
1307 OdfHelper::loadOdfTitle(d->title, n, context);
1308 // title shall *always* have AutoResize
1309 d->titleData->setResizeMethod(KoTextShapeDataBase::AutoResize);
1310 }
1311 else if (n.localName() == "grid") {
1312 bool major = false;
1313 if (n.hasAttributeNS(KoXmlNS::chart, "class")) {
1314 const QString className = n.attributeNS(KoXmlNS::chart, "class");
1315 if (className == "major")
1316 major = true;
1317 } else {
1318 warnChart << "Error: Axis' <chart:grid> element contains no valid class. It must be either \"major\" or \"minor\".";
1319 continue;
1320 }
1321
1322 if (major) {
1323 d->showMajorGrid = true;
1324 } else {
1325 d->showMinorGrid = true;
1326 }
1327
1328 if (n.hasAttributeNS(KoXmlNS::chart, "style-name")) {
1329 styleStack.clear();
1330 context.odfLoadingContext().fillStyleStack(n, KoXmlNS::style, "style-name", "chart");
1331 styleStack.setTypeProperties("graphic");
1332 if (styleStack.hasProperty(KoXmlNS::svg, "stroke-color")) {
1333 const QString strokeColor = styleStack.property(KoXmlNS::svg, "stroke-color");
1334 //d->showMajorGrid = true;
1335 if (major)
1336 gridPen = QPen(QColor(strokeColor));
1337 else
1338 subGridPen = QPen(QColor(strokeColor));
1339 }
1340 }
1341 }
1342 else if (n.localName() == "categories") {
1343 if (n.hasAttributeNS(KoXmlNS::table, "cell-range-address")) {
1344 const CellRegion region = CellRegion(helper->tableSource, n.attributeNS(KoXmlNS::table, "cell-range-address"));
1345 helper->categoryRegionSpecifiedInXAxis = true;
1346 plotArea()->proxyModel()->setCategoryDataRegion(region);
1347 }
1348 }
1349 }
1350
1351 if (axisElement.hasAttributeNS(KoXmlNS::chart, "axis-name")) {
1352 const QString name = axisElement.attributeNS(KoXmlNS::chart, "name", QString());
1353 setName(name);
1354 }
1355
1356 // NOTE: chart:dimension already handled by PlotArea before and passed
1357 // explicitly in the constructor.
1358 } else {
1359 warnChartOdf<<"No axis element";
1360 }
1361
1362 if (axisElement.hasAttributeNS(KoXmlNS::chart, "style-name")) {
1363 styleStack.clear();
1364 context.odfLoadingContext().fillStyleStack(axisElement, KoXmlNS::chart, "style-name", "chart");
1365
1366 KoCharacterStyle charStyle;
1367 charStyle.loadOdf(&axisElement, context);
1368 setFont(charStyle.font());
1369
1370 styleStack.setTypeProperties("chart");
1371
1372 if (styleStack.hasProperty(KoXmlNS::chart, "logarithmic")
1373 && styleStack.property(KoXmlNS::chart, "logarithmic") == "true")
1374 {
1375 setScalingLogarithmic(true);
1376 }
1377
1378 if (styleStack.hasProperty(KoXmlNS::chart, "reverse-direction")
1379 && styleStack.property(KoXmlNS::chart, "reverse-direction") == "true")
1380 {
1381 reverseAxis = true;
1382 }
1383
1384 if (styleStack.hasProperty(KoXmlNS::chart, "interval-major"))
1385 setMajorInterval(KoUnit::parseValue(styleStack.property(KoXmlNS::chart, "interval-major")));
1386 if (styleStack.hasProperty(KoXmlNS::chart, "interval-minor-divisor"))
1387 setMinorIntervalDivisor(KoUnit::parseValue(styleStack.property(KoXmlNS::chart, "interval-minor-divisor")));
1388 else if (styleStack.hasProperty(KoXmlNS::chart, "interval-minor"))
1389 setMinorInterval(KoUnit::parseValue(styleStack.property(KoXmlNS::chart, "interval-minor")));
1390
1391 if (styleStack.hasProperty(KoXmlNS::chart, "tick-marks-minor-inner"))
1392 setShowInnerMinorTicks(styleStack.property(KoXmlNS::chart, "tick-marks-minor-inner") == "true");
1393 if (styleStack.hasProperty(KoXmlNS::chart, "tick-marks-minor-outer"))
1394 setShowOuterMinorTicks(styleStack.property(KoXmlNS::chart, "tick-marks-minor-outer") == "true");
1395 if (styleStack.hasProperty(KoXmlNS::chart, "tick-marks-major-inner"))
1396 setShowInnerMajorTicks(styleStack.property(KoXmlNS::chart, "tick-marks-major-inner") == "true");
1397 if (styleStack.hasProperty(KoXmlNS::chart, "tick-marks-major-outer"))
1398 setShowOuterMajorTicks(styleStack.property(KoXmlNS::chart, "tick-marks-major-outer") == "true");
1399
1400 if (styleStack.hasProperty(KoXmlNS::chart, "display-label"))
1401 setShowLabels(styleStack.property(KoXmlNS::chart, "display-label") != "false");
1402 if (styleStack.hasProperty(KoXmlNS::chart, "text-overlap"))
1403 setShowOverlappingDataLabels(styleStack.property(KoXmlNS::chart, "text-overlap") != "false");
1404 if (styleStack.hasProperty(KoXmlNS::chart, "visible"))
1405 setVisible(styleStack.property(KoXmlNS::chart, "visible") != "false");
1406 if (styleStack.hasProperty(KoXmlNS::chart, "minimum")) {
1407 const qreal minimum = styleStack.property(KoXmlNS::chart, "minimum").toDouble();
1408 const qreal maximum = orientation() == Qt::Vertical
1409 ? d->kdPlane->verticalRange().second
1410 : d->kdPlane->horizontalRange().second;
1411 if (orientation() == Qt::Vertical)
1412 d->kdPlane->setVerticalRange(qMakePair(minimum, maximum));
1413 else
1414 d->kdPlane->setHorizontalRange(qMakePair(minimum, maximum));
1415 d->useAutomaticMinimumRange = false;
1416 }
1417 if (styleStack.hasProperty(KoXmlNS::chart, "maximum")) {
1418 const qreal minimum = orientation() == Qt::Vertical
1419 ? d->kdPlane->verticalRange().first
1420 : d->kdPlane->horizontalRange().first;
1421 const qreal maximum = styleStack.property(KoXmlNS::chart, "maximum").toDouble();
1422 if (orientation() == Qt::Vertical)
1423 d->kdPlane->setVerticalRange(qMakePair(minimum, maximum));
1424 else
1425 d->kdPlane->setHorizontalRange(qMakePair(minimum, maximum));
1426 d->useAutomaticMaximumRange = false;
1427 }
1428 /*if (styleStack.hasProperty(KoXmlNS::chart, "origin")) {
1429 const qreal origin = KoUnit::parseValue(styleStack.property(KoXmlNS::chart, "origin"));
1430 }*/
1431 styleStack.setTypeProperties("chart");
1432 if (styleStack.hasProperty(KoXmlNS::chart, "axis-position")) {
1433 d->axisPosition = styleStack.property(KoXmlNS::chart, "axis-position");
1434 }
1435 if (styleStack.hasProperty(KoXmlNS::chart, "axis-label-position")) {
1436 d->axisLabelsPosition = styleStack.property(KoXmlNS::chart, "axis-label-position");
1437 }
1438
1439 styleStack.setTypeProperties("text");
1440 if (styleStack.hasProperty(KoXmlNS::fo, "font-size")) {
1441 const qreal fontSize = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "font-size"));
1442 setFontSize(fontSize);
1443 }
1444 if (styleStack.hasProperty(KoXmlNS::fo, "font-color")) {
1445 QString fontColorString = styleStack.property(KoXmlNS::fo, "font-color");
1446 QColor color(fontColorString);
1447 if (color.isValid()) {
1448 KChart::TextAttributes tatt = kdAxis()->textAttributes();
1449 QPen pen = tatt.pen();
1450 pen.setColor(color);
1451 tatt.setPen(pen);
1452 kdAxis()->setTextAttributes(tatt);
1453 }
1454 }
1455 if (styleStack.hasProperty(KoXmlNS::fo, "font-family")) {
1456 QString fontFamilyString = styleStack.property(KoXmlNS::fo, "font-family");
1457 if (!fontFamilyString.isEmpty()) {
1458 QFont f = this->font();
1459 f.setFamily(fontFamilyString);
1460 setFont(f);
1461 }
1462 }
1463 if (styleStack.hasProperty(KoXmlNS::fo, "font-style")) {
1464 QString fontStyle = styleStack.property(KoXmlNS::fo, "font-style");
1465 if (fontStyle == "italic") {
1466 QFont f = this->font();
1467 f.setItalic(true);
1468 setFont(f);
1469 } else if (fontStyle == "oblique") {
1470 // TODO
1471 }
1472 }
1473 if (styleStack.hasProperty(KoXmlNS::fo, "font-weight")) {
1474 QString fontWeight = styleStack.property(KoXmlNS::fo, "font-weight");
1475 //fo:font-weight attribute are normal, bold, 100, 200, 300, 400, 500, 600, 700, 800 or 900.
1476 if (fontWeight == "bold") {
1477 QFont f = this->font();
1478 f.setBold(true);
1479 setFont(f);
1480 } else {
1481 // TODO
1482 }
1483 }
1484 } else {
1485 warnChartOdf<<"Axis element has no style information";
1486 setShowLabels(KoOdfWorkaround::fixMissingStyle_DisplayLabel(axisElement, context));
1487 }
1488
1489 KChart::GridAttributes gridAttr = d->kdPlane->gridAttributes(orientation());
1490 gridAttr.setGridVisible(d->showMajorGrid);
1491 gridAttr.setSubGridVisible(d->showMinorGrid);
1492 if (gridPen.style() != Qt::NoPen)
1493 gridAttr.setGridPen(gridPen);
1494 if (subGridPen.style() != Qt::NoPen)
1495 gridAttr.setSubGridPen(subGridPen);
1496 d->kdPlane->setGridAttributes(orientation(), gridAttr);
1497
1498 gridAttr = d->kdPolarPlane->gridAttributes(orientation());
1499 gridAttr.setGridVisible(d->showMajorGrid);
1500 gridAttr.setSubGridVisible(d->showMinorGrid);
1501 if (gridPen.style() != Qt::NoPen)
1502 gridAttr.setGridPen(gridPen);
1503 if (subGridPen.style() != Qt::NoPen)
1504 gridAttr.setSubGridPen(subGridPen);
1505 // if (plotArea()->chartType() == RadarChartType || plotArea()->chartType() == FilledRadarChartType)
1506 // d->kdPolarPlane->setGridAttributes(false, gridAttr);
1507 // else
1508 d->kdPolarPlane->setGridAttributes(true, gridAttr);
1509
1510 gridAttr = d->kdRadarPlane->globalGridAttributes();
1511 gridAttr.setGridVisible(d->showMajorGrid);
1512 gridAttr.setSubGridVisible(d->showMinorGrid);
1513 if (gridPen.style() != Qt::NoPen)
1514 gridAttr.setGridPen(gridPen);
1515 if (subGridPen.style() != Qt::NoPen)
1516 gridAttr.setSubGridPen(subGridPen);
1517 d->kdRadarPlane->setGlobalGridAttributes(gridAttr);
1518 KChart::TextAttributes ta(d->kdRadarPlane->textAttributes());
1519 ta.setVisible(helper->categoryRegionSpecifiedInXAxis);
1520 ta.setFont(font());
1521 ta.setFontSize(50);
1522 d->kdRadarPlane->setTextAttributes(ta);
1523
1524 if (reverseAxis) {
1525 KChart::CartesianCoordinatePlane *plane = dynamic_cast<KChart::CartesianCoordinatePlane*>(kdPlane());
1526 if (plane) {
1527 if (orientation() == Qt::Horizontal)
1528 plane->setHorizontalRangeReversed(reverseAxis);
1529 else // Qt::Vertical
1530 plane->setVerticalRangeReversed(reverseAxis);
1531 }
1532 }
1533
1534 // Style of axis is still in styleStack
1535 if (!loadOdfChartSubtypeProperties(axisElement, context)) {
1536 return false;
1537 }
1538 if (titleText().isEmpty()) {
1539 // do not allow visible empty text
1540 d->title->setVisible(false);
1541 }
1542 requestRepaint();
1543
1544 debugChartOdf<<"Loaded axis:"<<name()<<"dimension:"<<dimension();
1545 return true;
1546 }
1547
loadOdfChartSubtypeProperties(const KoXmlElement & axisElement,KoShapeLoadingContext & context)1548 bool Axis::loadOdfChartSubtypeProperties(const KoXmlElement &axisElement,
1549 KoShapeLoadingContext &context)
1550 {
1551 Q_UNUSED(axisElement);
1552 KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
1553 styleStack.setTypeProperties("chart");
1554
1555 // Load these attributes regardless of the actual chart type. They'll have
1556 // no effect if their respective chart type is not in use.
1557 // However, they'll be saved back to ODF that way.
1558 if (styleStack.hasProperty(KoXmlNS::chart, "gap-width"))
1559 setGapBetweenSets(KoUnit::parseValue(styleStack.property(KoXmlNS::chart, "gap-width")));
1560 if (styleStack.hasProperty(KoXmlNS::chart, "overlap"))
1561 // The minus is intended!
1562 setGapBetweenBars(-KoUnit::parseValue(styleStack.property(KoXmlNS::chart, "overlap")));
1563
1564 return true;
1565 }
1566
setName(const QString & name)1567 void Axis::setName(const QString &name)
1568 {
1569 d->name = name;
1570 }
1571
1572 // NOTE: only used during save/load to enable attaching axis to datasets
name() const1573 QString Axis::name() const
1574 {
1575 if (!d->name.isEmpty()) {
1576 return d->name;
1577 }
1578 QString name;
1579 switch(dimension()) {
1580 case XAxisDimension:
1581 name = QLatin1Char('x');
1582 break;
1583 case YAxisDimension:
1584 name = QLatin1Char('y');
1585 break;
1586 case ZAxisDimension:
1587 name = QLatin1Char('z');
1588 break;
1589 }
1590 int i = 1;
1591 foreach (Axis *axis, d->plotArea->axes()) {
1592 if (axis == this)
1593 break;
1594 if (axis->dimension() == dimension())
1595 i++;
1596 }
1597 if (i == 1)
1598 name = "primary-" + name;
1599 else if (i == 2)
1600 name = "secondary-" + name;
1601 // Usually, there's not more than two axes of the same dimension.
1602 // But use a fallback name here nevertheless.
1603 else
1604 name = QString::number(i) + '-' + name;
1605
1606 return name;
1607 }
1608
saveOdf(KoShapeSavingContext & context)1609 void Axis::saveOdf(KoShapeSavingContext &context)
1610 {
1611 KoXmlWriter &bodyWriter = context.xmlWriter();
1612 KoGenStyles &mainStyles = context.mainStyles();
1613 bodyWriter.startElement("chart:axis");
1614
1615 KoGenStyle axisStyle(KoGenStyle::ChartAutoStyle, "chart");
1616 axisStyle.addProperty("chart:logarithmic", scalingIsLogarithmic());
1617
1618
1619 axisStyle.addProperty("chart:reverse-direction", axisDirectionReversed());
1620 if (!d->axisPosition.isEmpty()) {
1621 axisStyle.addProperty("chart:axis-position", d->axisPosition);
1622 }
1623 if (!d->axisLabelsPosition.isEmpty()) {
1624 axisStyle.addProperty("chart:axis-label-position", d->axisLabelsPosition);
1625 }
1626
1627 axisStyle.addProperty("chart:tick-marks-minor-inner", showInnerMinorTicks());
1628 axisStyle.addProperty("chart:tick-marks-minor-outer", showOuterMinorTicks());
1629 axisStyle.addProperty("chart:tick-marks-major-inner", showInnerMajorTicks());
1630 axisStyle.addProperty("chart:tick-marks-major-outer", showOuterMajorTicks());
1631
1632 axisStyle.addProperty("chart:display-label", showLabels());
1633 axisStyle.addProperty("chart:text-overlap", showOverlappingDataLabels());
1634 axisStyle.addProperty("chart:visible", isVisible());
1635 if (dimension() == YAxisDimension) {
1636 axisStyle.addProperty("chart:gap-width", d->gapBetweenSets);
1637 axisStyle.addProperty("chart:overlap", -d->gapBetweenBars);
1638 }
1639
1640 if (!d->useAutomaticMinimumRange) {
1641 const qreal minimum = orientation() == Qt::Vertical
1642 ? d->kdPlane->verticalRange().first
1643 : d->kdPlane->horizontalRange().first;
1644 axisStyle.addProperty("chart:minimum", (int)minimum);
1645 }
1646 if (!d->useAutomaticMaximumRange) {
1647 const qreal maximum = orientation() == Qt::Vertical
1648 ? d->kdPlane->verticalRange().second
1649 : d->kdPlane->horizontalRange().second;
1650 axisStyle.addProperty("chart:maximum", (int)maximum);
1651 }
1652
1653 //axisStyle.addPropertyPt("chart:origin", origin);
1654
1655 KChart::TextAttributes tatt = kdAxis()->textAttributes();
1656 QPen pen = tatt.pen();
1657 axisStyle.addProperty("fo:font-color", pen.color().name(), KoGenStyle::TextType);
1658 axisStyle.addProperty("fo:font-family", tatt.font().family(), KoGenStyle::TextType);
1659 axisStyle.addPropertyPt("fo:font-size", fontSize(), KoGenStyle::TextType);
1660 if (font().bold()) {
1661 axisStyle.addProperty("fo:font-weight", "bold" , KoGenStyle::TextType);
1662 // TODO support other weights
1663 }
1664 if (font().italic()) {
1665 axisStyle.addProperty("fo:font-style", "italic" , KoGenStyle::TextType);
1666 // TODO oblique
1667 }
1668
1669 const QString styleName = mainStyles.insert(axisStyle, "ch");
1670 bodyWriter.addAttribute("chart:style-name", styleName);
1671
1672 // TODO scale: logarithmic/linear
1673 // TODO visibility
1674
1675 if (dimension() == XAxisDimension)
1676 bodyWriter.addAttribute("chart:dimension", "x");
1677 else if (dimension() == YAxisDimension)
1678 bodyWriter.addAttribute("chart:dimension", "y");
1679
1680 bodyWriter.addAttribute("chart:name", name());
1681
1682 OdfHelper::saveOdfTitle(d->title, bodyWriter, "chart:title", context);
1683
1684 if (plotArea()->proxyModel()->categoryDataRegion().isValid()) {
1685 bodyWriter.startElement("chart:categories");
1686 bodyWriter.addAttribute("table:cell-range-address", plotArea()->proxyModel()->categoryDataRegion().toString());
1687 bodyWriter.endElement();
1688 }
1689
1690 if (showMajorGrid())
1691 saveOdfGrid(context, OdfMajorGrid);
1692 if (showMinorGrid())
1693 saveOdfGrid(context, OdfMinorGrid);
1694
1695 bodyWriter.endElement(); // chart:axis
1696 }
1697
saveOdfGrid(KoShapeSavingContext & context,OdfGridClass gridClass)1698 void Axis::saveOdfGrid(KoShapeSavingContext &context, OdfGridClass gridClass)
1699 {
1700 KoXmlWriter &bodyWriter = context.xmlWriter();
1701 KoGenStyles &mainStyles = context.mainStyles();
1702
1703 KoGenStyle gridStyle(KoGenStyle::GraphicAutoStyle, "chart");
1704
1705 KChart::GridAttributes attributes = d->kdPlane->gridAttributes(orientation());
1706 QPen gridPen = (gridClass == OdfMinorGrid ? attributes.subGridPen() : attributes.gridPen());
1707 KoOdfGraphicStyles::saveOdfStrokeStyle(gridStyle, mainStyles, gridPen);
1708
1709 bodyWriter.startElement("chart:grid");
1710 bodyWriter.addAttribute("chart:class", gridClass == OdfMinorGrid ? "minor" : "major");
1711
1712 bodyWriter.addAttribute("chart:style-name", mainStyles.insert(gridStyle, "ch"));
1713 bodyWriter.endElement(); // chart:grid
1714 }
1715
update() const1716 void Axis::update() const
1717 {
1718 if (d->kdBarDiagram) {
1719 d->kdBarDiagram->doItemsLayout();
1720 d->kdBarDiagram->update();
1721 }
1722
1723 if (d->kdLineDiagram) {
1724 d->kdLineDiagram->doItemsLayout();
1725 d->kdLineDiagram->update();
1726 }
1727
1728 if (d->kdStockDiagram) {
1729 d->kdStockDiagram->doItemsLayout();
1730 d->kdStockDiagram->update();
1731 }
1732
1733 d->plotArea->parent()->requestRepaint();
1734 }
1735
kdAxis() const1736 KChart::CartesianAxis *Axis::kdAxis() const
1737 {
1738 return d->kdAxis;
1739 }
1740
kdPlane() const1741 KChart::AbstractCoordinatePlane *Axis::kdPlane() const
1742 {
1743 return d->kdPlane;
1744 }
1745
plotAreaChartTypeChanged(ChartType newChartType)1746 void Axis::plotAreaChartTypeChanged(ChartType newChartType)
1747 {
1748 if (dimension() != YAxisDimension)
1749 return;
1750
1751 // Return if there's nothing to do
1752 if (newChartType == d->plotAreaChartType)
1753 return;
1754
1755 if (d->dataSets.isEmpty()) {
1756 d->plotAreaChartType = newChartType;
1757 return;
1758 }
1759
1760 //qDebug() << "changed ChartType";
1761
1762 ChartType oldChartType = d->plotAreaChartType;
1763
1764 debugChartAxis<<oldChartType<<"->"<<newChartType;
1765 // Change only the fill in case of type change from RadarChartType to FilledRadarChartType
1766 // or viceversa as rest of the properties remain same
1767 if (newChartType == RadarChartType && oldChartType == FilledRadarChartType) {
1768 d->kdRadarDiagram->setFillAlpha(0);
1769 } else if (newChartType == FilledRadarChartType && oldChartType == RadarChartType) {
1770 d->kdRadarDiagram->setFillAlpha(0.4);
1771 } else {
1772 KChart::AbstractDiagram *newDiagram = d->getDiagram(newChartType);
1773 if (newDiagram) {
1774 debugChartAxis<<"already exists:"<<newDiagram;
1775 // Some dataset(s) have been attached to this diagram,
1776 // we delete it to get a fresh start
1777 d->deleteDiagram(newDiagram);
1778 }
1779 newDiagram = d->getDiagramAndCreateIfNeeded(newChartType);
1780
1781 KChartModel *newModel = dynamic_cast<KChartModel*>(newDiagram->model());
1782 // FIXME: This causes a crash on unimplemented types. We should
1783 // handle that in some other way.
1784 Q_ASSERT(newModel);
1785
1786 foreach (DataSet *dataSet, d->dataSets) {
1787 //if (dataSet->chartType() != LastChartType) {
1788 dataSet->setChartType(LastChartType);
1789 dataSet->setChartSubType(NoChartSubtype);
1790 //}
1791 }
1792
1793 KChart::AbstractDiagram *oldDiagram = d->getDiagram(oldChartType);
1794 Q_ASSERT(oldDiagram);
1795 // We need to know the old model so that we can remove the data sets
1796 // from the old model that we added to the new model.
1797 KChartModel *oldModel = dynamic_cast<KChartModel*>(oldDiagram->model());
1798 Q_ASSERT(oldModel);
1799
1800 foreach (DataSet *dataSet, d->dataSets) {
1801 if (dataSet->chartType() != LastChartType) {
1802 continue;
1803 }
1804 newModel->addDataSet(dataSet);
1805 const int dataSetCount = oldModel->dataDirection() == Qt::Vertical
1806 ? oldModel->columnCount() : oldModel->rowCount();
1807 if (dataSetCount == oldModel->dataDimensions()) {
1808 d->deleteDiagram(oldChartType);
1809 } else {
1810 oldModel->removeDataSet(dataSet);
1811 }
1812 }
1813 }
1814
1815 d->plotAreaChartType = newChartType;
1816
1817 layoutPlanes();
1818
1819 requestRepaint();
1820 }
1821
plotAreaChartSubTypeChanged(ChartSubtype subType)1822 void Axis::plotAreaChartSubTypeChanged(ChartSubtype subType)
1823 {
1824 d->plotAreaChartSubType = subType;
1825 if (d->kdBarDiagram) {
1826 d->kdBarDiagram->setUnitSuffix("", d->kdBarDiagram->orientation());
1827 }
1828 switch (d->plotAreaChartType) {
1829 case BarChartType:
1830 if (d->kdBarDiagram) {
1831 KChart::BarDiagram::BarType type;
1832 switch (subType) {
1833 case StackedChartSubtype:
1834 type = KChart::BarDiagram::Stacked; break;
1835 case PercentChartSubtype:
1836 type = KChart::BarDiagram::Percent;
1837 d->kdBarDiagram->setUnitSuffix("%", d->kdBarDiagram->orientation());
1838 break;
1839 default:
1840 type = KChart::BarDiagram::Normal;
1841 }
1842 d->kdBarDiagram->setType(type);
1843 }
1844 break;
1845 case LineChartType:
1846 if (d->kdLineDiagram) {
1847 KChart::LineDiagram::LineType type;
1848 switch (subType) {
1849 case StackedChartSubtype:
1850 type = KChart::LineDiagram::Stacked; break;
1851 case PercentChartSubtype:
1852 type = KChart::LineDiagram::Percent;
1853 d->kdLineDiagram->setUnitSuffix("%", Qt::Vertical);
1854 break;
1855 default:
1856 type = KChart::LineDiagram::Normal;
1857 }
1858 d->kdLineDiagram->setType(type);
1859 }
1860 break;
1861 case AreaChartType:
1862 if (d->kdAreaDiagram) {
1863 KChart::LineDiagram::LineType type;
1864 switch (subType) {
1865 case StackedChartSubtype:
1866 type = KChart::LineDiagram::Stacked; break;
1867 case PercentChartSubtype:
1868 type = KChart::LineDiagram::Percent;
1869 d->kdAreaDiagram->setUnitSuffix("%", Qt::Vertical);
1870 break;
1871 default:
1872 type = KChart::LineDiagram::Normal;
1873 }
1874 d->kdAreaDiagram->setType(type);
1875 }
1876 break;
1877 case RadarChartType:
1878 case FilledRadarChartType:
1879 #if 0 // FIXME: Stacked and Percent not supported by KChart
1880 if (d->kdRadarDiagram) {
1881 KChart::PolarDiagram::PolarType type;
1882 switch (subType) {
1883 case StackedChartSubtype:
1884 type = KChart::PolarDiagram::Stacked; break;
1885 case PercentChartSubtype:
1886 type = KChart::PolarDiagram::Percent; break;
1887 default:
1888 type = KChart::PolarDiagram::Normal;
1889 }
1890 d->kdRadarDiagram->setType(type);
1891 }
1892 #endif
1893 break;
1894 case StockChartType:
1895 if (d->kdStockDiagram) {
1896 KChart::StockDiagram::Type type;
1897 switch (subType) {
1898 case CandlestickChartSubtype:
1899 type = KChart::StockDiagram::Candlestick;
1900 break;
1901 case OpenHighLowCloseChartSubtype:
1902 type = KChart::StockDiagram::OpenHighLowClose;
1903 break;
1904 default:
1905 type = KChart::StockDiagram::HighLowClose;
1906 break;
1907 }
1908 d->kdStockDiagram->setType(type);
1909 }
1910 break;
1911 default:;
1912 // FIXME: Implement more chart types
1913 }
1914 Q_FOREACH(DataSet* set, d->dataSets) {
1915 set->setChartType(d->plotAreaChartType);
1916 set->setChartSubType(subType);
1917 }
1918 }
1919
plotAreaIsVerticalChanged()1920 void Axis::plotAreaIsVerticalChanged()
1921 {
1922 if (d->kdBarDiagram) {
1923 d->kdBarDiagram->setOrientation(d->plotArea->isVertical() ? Qt::Horizontal : Qt::Vertical);
1924 }
1925 updateKChartAxisPosition();
1926 }
1927
updatePosition()1928 void Axis::Private::updatePosition()
1929 {
1930 // // Is the first x or y axis?
1931 // bool first = (dimension == XAxisDimension) ? plotArea->xAxis() == q
1932 // : plotArea->yAxis() == q;
1933 //
1934 // Position position;
1935 // ItemType type = GenericItemType;
1936 // if (q->orientation() == Qt::Horizontal) {
1937 // position = first ? BottomPosition : TopPosition;
1938 // type = first ? XAxisTitleType : SecondaryXAxisTitleType;
1939 // } else {
1940 // position = first ? StartPosition : EndPosition;
1941 // type = first ? YAxisTitleType : SecondaryYAxisTitleType;
1942 // }
1943 // // KChart
1944 // kdAxis->setPosition(PositionToKChartAxisPosition(position));
1945 // ChartLayout *layout = plotArea->parent()->layout();
1946 // layout->setPosition(title, position, type);
1947 // layout->layout();
1948 //
1949 // q->requestRepaint();
1950 }
1951
registerAxis(Axis * axis)1952 void Axis::registerAxis(Axis *axis)
1953 {
1954 if (d->kdBarDiagram) {
1955 d->kdBarDiagram->addAxis(axis->kdAxis());
1956 axis->registerDiagram(d->kdBarDiagram);
1957 }
1958 if (d->kdLineDiagram) {
1959 d->kdLineDiagram->addAxis(axis->kdAxis());
1960 axis->registerDiagram(d->kdLineDiagram);
1961 }
1962 if (d->kdAreaDiagram) {
1963 d->kdAreaDiagram->addAxis(axis->kdAxis());
1964 axis->registerDiagram(d->kdAreaDiagram);
1965 }
1966 if (d->kdScatterDiagram) {
1967 d->kdScatterDiagram->addAxis(axis->kdAxis());
1968 axis->registerDiagram(d->kdScatterDiagram);
1969 }
1970 if (d->kdStockDiagram) {
1971 d->kdStockDiagram->addAxis(axis->kdAxis());
1972 axis->registerDiagram(d->kdStockDiagram);
1973 }
1974 if (d->kdBubbleDiagram) {
1975 d->kdBubbleDiagram->addAxis(axis->kdAxis());
1976 axis->registerDiagram(d->kdBubbleDiagram);
1977 }
1978 // FIXME: Add all diagrams here
1979
1980 }
1981
registerDiagram(KChart::AbstractCartesianDiagram * diagram)1982 void Axis::registerDiagram(KChart::AbstractCartesianDiagram *diagram)
1983 {
1984 if (!d->diagrams.contains(diagram)) {
1985 d->diagrams << diagram;
1986 }
1987 }
1988
restoreDiagrams()1989 void Axis::Private::restoreDiagrams()
1990 {
1991 diagrams.removeAll(nullptr);
1992 for (KChart::AbstractCartesianDiagram *diag : diagrams) {
1993 diag->addAxis(kdAxis);
1994 }
1995 }
1996
removeAxisFromDiagrams(bool clear)1997 void Axis::removeAxisFromDiagrams(bool clear)
1998 {
1999 // HACK to remove an x-axis from a y-axis diagram
2000 d->diagrams.removeAll(nullptr);
2001 for (KChart::AbstractCartesianDiagram *diag : d->diagrams) {
2002 diag->takeAxis(d->kdAxis);
2003 }
2004 if (clear) {
2005 d->diagrams.clear();
2006 }
2007 }
2008
setThreeD(bool threeD)2009 void Axis::setThreeD(bool threeD)
2010 {
2011 // FIXME: Setting KD Chart attributes does not belong here. They should be
2012 // determined dynamically somehow.
2013 // KChart
2014 if (d->kdBarDiagram) {
2015 KChart::ThreeDBarAttributes attributes(d->kdBarDiagram->threeDBarAttributes());
2016 attributes.setEnabled(threeD);
2017 attributes.setDepth(15.0);
2018 attributes.setThreeDBrushEnabled(threeD);
2019 d->kdBarDiagram->setThreeDBarAttributes(attributes);
2020 }
2021
2022 if (d->kdLineDiagram) {
2023 KChart::ThreeDLineAttributes attributes(d->kdLineDiagram->threeDLineAttributes());
2024 attributes.setEnabled(threeD);
2025 attributes.setDepth(15.0);
2026 attributes.setThreeDBrushEnabled(threeD);
2027 d->kdLineDiagram->setThreeDLineAttributes(attributes);
2028 }
2029
2030 if (d->kdAreaDiagram) {
2031 KChart::ThreeDLineAttributes attributes(d->kdAreaDiagram->threeDLineAttributes());
2032 attributes.setEnabled(threeD);
2033 attributes.setDepth(15.0);
2034 attributes.setThreeDBrushEnabled(threeD);
2035 d->kdAreaDiagram->setThreeDLineAttributes(attributes);
2036 }
2037
2038 if (d->kdCircleDiagram) {
2039 KChart::ThreeDPieAttributes attributes(d->kdCircleDiagram->threeDPieAttributes());
2040 attributes.setEnabled(threeD);
2041 attributes.setDepth(15.0);
2042 attributes.setThreeDBrushEnabled(threeD);
2043 d->kdCircleDiagram->setThreeDPieAttributes(attributes);
2044 }
2045
2046 if (d->kdRingDiagram) {
2047 KChart::ThreeDPieAttributes attributes(d->kdRingDiagram->threeDPieAttributes());
2048 attributes.setEnabled(threeD);
2049 attributes.setDepth(15.0);
2050 attributes.setThreeDBrushEnabled(threeD);
2051 d->kdRingDiagram->setThreeDPieAttributes(attributes);
2052 }
2053
2054 // The following types don't support 3D, at least not in KChart:
2055 // scatter, radar, stock, bubble, surface, gantt
2056
2057 requestRepaint();
2058 }
2059
requestRepaint() const2060 void Axis::requestRepaint() const
2061 {
2062 d->plotArea->requestRepaint();
2063 }
2064
layoutPlanes()2065 void Axis::layoutPlanes()
2066 {
2067 d->kdPlane->layoutPlanes();
2068 d->kdPolarPlane->layoutPlanes();
2069 d->kdRadarPlane->layoutPlanes();
2070 }
2071
gapBetweenBars() const2072 int Axis::gapBetweenBars() const
2073 {
2074 return d->gapBetweenBars;
2075 }
2076
setGapBetweenBars(int percent)2077 void Axis::setGapBetweenBars(int percent)
2078 {
2079 // This method is also used to override KChart's default attributes.
2080 // Do not just return and do nothing if value doesn't differ from stored one.
2081 d->gapBetweenBars = percent;
2082
2083 if (d->kdBarDiagram) {
2084 KChart::BarAttributes attributes = d->kdBarDiagram->barAttributes();
2085 attributes.setBarGapFactor((float)percent / 100.0);
2086 d->kdBarDiagram->setBarAttributes(attributes);
2087 }
2088
2089 requestRepaint();
2090 }
2091
gapBetweenSets() const2092 int Axis::gapBetweenSets() const
2093 {
2094 return d->gapBetweenSets;
2095 }
2096
setGapBetweenSets(int percent)2097 void Axis::setGapBetweenSets(int percent)
2098 {
2099 // This method is also used to override KChart's default attributes.
2100 // Do not just return and do nothing if value doesn't differ from stored one.
2101 d->gapBetweenSets = percent;
2102
2103 if (d->kdBarDiagram) {
2104 KChart::BarAttributes attributes = d->kdBarDiagram->barAttributes();
2105 attributes.setGroupGapFactor((float)percent / 100.0);
2106 d->kdBarDiagram->setBarAttributes(attributes);
2107 }
2108
2109 requestRepaint();
2110 }
2111
setAngleOffset(qreal angle)2112 void Axis::setAngleOffset(qreal angle)
2113 {
2114 // only set if we already have a diagram else the value will be picked up on creating the diagram
2115 if (d->kdPolarPlane->diagram()) {
2116 d->kdPolarPlane->setStartPosition(angle);
2117
2118 requestRepaint();
2119 }
2120 }
2121
setHoleSize(qreal value)2122 void Axis::setHoleSize(qreal value)
2123 {
2124 //TODO KChart does not support
2125 }
2126
font() const2127 QFont Axis::font() const
2128 {
2129 return d->kdAxis->textAttributes().font();
2130 }
2131
setFont(const QFont & font)2132 void Axis::setFont(const QFont &font)
2133 {
2134 // Set the KChart axis to use this font
2135 KChart::TextAttributes attr = d->kdAxis->textAttributes();
2136 attr.setFont(font);
2137 d->kdAxis->setTextAttributes(attr);
2138 }
2139
fontSize() const2140 qreal Axis::fontSize() const
2141 {
2142 return d->kdAxis->textAttributes().fontSize().value();
2143 }
2144
setFontSize(qreal size)2145 void Axis::setFontSize(qreal size)
2146 {
2147 // KChart has its own fontsize storage, it does not use QFont
2148 KChart::TextAttributes attributes = d->kdAxis->textAttributes();
2149 attributes.setFontSize(KChart::Measure(size, KChartEnums::MeasureCalculationModeAbsolute));
2150 d->kdAxis->setTextAttributes(attributes);
2151
2152 // Keep font in sync
2153 QFont f = font();
2154 f.setPointSizeF(size);
2155 setFont(f);
2156 }
2157
isVisible() const2158 bool Axis::isVisible() const
2159 {
2160 return d->isVisible;
2161 }
2162
setVisible(bool visible)2163 void Axis::setVisible(bool visible)
2164 {
2165 debugChartAxis<<d->isVisible<<"->"<<visible<<d->kdBarDiagram;
2166 d->isVisible = visible;
2167 if (visible) {
2168 d->restoreDiagrams();
2169 } else {
2170 removeAxisFromDiagrams();
2171 }
2172 }
2173
numericStyleFormat() const2174 KoOdfNumberStyles::NumericStyleFormat *Axis::numericStyleFormat() const
2175 {
2176 return d->numericStyleFormat;
2177 }
2178
SetNumericStyleFormat(KoOdfNumberStyles::NumericStyleFormat * numericStyleFormat) const2179 void Axis::SetNumericStyleFormat(KoOdfNumberStyles::NumericStyleFormat *numericStyleFormat) const
2180 {
2181 delete d->numericStyleFormat;
2182 d->numericStyleFormat = numericStyleFormat;
2183 }
2184
setOdfAxisPosition(const QString & odfpos)2185 void Axis::setOdfAxisPosition(const QString &odfpos)
2186 {
2187 d->axisPosition = odfpos;
2188 }
2189
odfAxisPosition() const2190 QString Axis::odfAxisPosition() const
2191 {
2192 return d->axisPosition;
2193 }
2194
updateKChartAxisPosition()2195 void Axis::updateKChartAxisPosition()
2196 {
2197 if (!isCartesian(d->plotArea->chartType())) {
2198 debugChartAxis<<name()<<"Not a cartesian chart"<<d->plotArea->chartType();
2199 return;
2200 }
2201 KChart::CartesianAxis::Position pos;
2202 if (d->plotArea->xAxis() == this) {
2203 if (d->plotArea->isVertical()) {
2204 pos = KChart::CartesianAxis::Left;
2205 if (d->axisPosition == "end") {
2206 pos = KChart::CartesianAxis::Right;
2207 }
2208 Axis *yAxis = d->plotArea->yAxis();
2209 if (yAxis && yAxis->axisDirectionReversed()) {
2210 pos = pos == KChart::CartesianAxis::Left ? KChart::CartesianAxis::Right : KChart::CartesianAxis::Left;
2211 }
2212 } else {
2213 pos = KChart::CartesianAxis::Bottom;
2214 if (d->axisPosition == "end") {
2215 pos = KChart::CartesianAxis::Top;
2216 }
2217 Axis *yAxis = d->plotArea->yAxis();
2218 if (yAxis && yAxis->axisDirectionReversed()) {
2219 pos = pos == KChart::CartesianAxis::Bottom ? KChart::CartesianAxis::Top : KChart::CartesianAxis::Bottom;
2220 }
2221 }
2222 d->kdAxis->setPosition(pos);
2223 } else if (d->plotArea->yAxis() == this) {
2224 if (d->plotArea->isVertical()) {
2225 pos = KChart::CartesianAxis::Bottom;
2226 if (d->axisPosition == "end") {
2227 pos = KChart::CartesianAxis::Top;
2228 }
2229 Axis *xAxis = d->plotArea->xAxis();
2230 if (xAxis && xAxis->axisDirectionReversed()) {
2231 pos = pos == KChart::CartesianAxis::Bottom ? KChart::CartesianAxis::Top : KChart::CartesianAxis::Bottom;
2232 }
2233 } else {
2234 pos = KChart::CartesianAxis::Left;
2235 if (d->axisPosition == "end") {
2236 pos = KChart::CartesianAxis::Right;
2237 }
2238 Axis *xAxis = d->plotArea->xAxis();
2239 if (xAxis && xAxis->axisDirectionReversed()) {
2240 pos = pos == KChart::CartesianAxis::Left ? KChart::CartesianAxis::Right : KChart::CartesianAxis::Left;
2241 }
2242 }
2243 d->kdAxis->setPosition(pos);
2244 } else if (d->plotArea->secondaryXAxis() == this) {
2245 if (d->plotArea->isVertical()) {
2246 pos = KChart::CartesianAxis::Right;
2247 if (d->axisPosition == "start") {
2248 pos = KChart::CartesianAxis::Left;
2249 }
2250 Axis *yAxis = d->plotArea->yAxis();
2251 if (yAxis && yAxis->axisDirectionReversed()) {
2252 pos = pos == KChart::CartesianAxis::Left ? KChart::CartesianAxis::Right : KChart::CartesianAxis::Left;
2253 }
2254 } else {
2255 pos = KChart::CartesianAxis::Top;
2256 if (d->axisPosition == "start") {
2257 pos = KChart::CartesianAxis::Bottom;
2258 }
2259 Axis *yAxis = d->plotArea->yAxis();
2260 if (yAxis && yAxis->axisDirectionReversed()) {
2261 pos = pos == KChart::CartesianAxis::Top ? KChart::CartesianAxis::Bottom : KChart::CartesianAxis::Top;
2262 }
2263 }
2264 d->kdAxis->setPosition(pos);
2265 } else if (d->plotArea->secondaryYAxis() == this) {
2266 if (d->plotArea->isVertical()) {
2267 pos = KChart::CartesianAxis::Top;
2268 if (d->axisPosition == "start") {
2269 pos = KChart::CartesianAxis::Bottom;
2270 }
2271 Axis *xAxis = d->plotArea->xAxis();
2272 if (xAxis && xAxis->axisDirectionReversed()) {
2273 pos = pos == KChart::CartesianAxis::Bottom ? KChart::CartesianAxis::Top : KChart::CartesianAxis::Bottom;
2274 }
2275 } else {
2276 pos = KChart::CartesianAxis::Right;
2277 if (d->axisPosition == "start") {
2278 pos = KChart::CartesianAxis::Left;
2279 }
2280 Axis *xAxis = d->plotArea->xAxis();
2281 if (xAxis && xAxis->axisDirectionReversed()) {
2282 pos = pos == KChart::CartesianAxis::Right ? KChart::CartesianAxis::Left : KChart::CartesianAxis::Right;
2283 }
2284 }
2285 d->kdAxis->setPosition(pos);
2286 }
2287 debugChartAxis<<name()<<d->kdAxis<<pos<<d->kdAxis->isAbscissa();
2288 d->plotArea->plotAreaUpdate();
2289 }
2290
kchartAxisPosition() const2291 CartesianAxis::Position Axis::kchartAxisPosition() const
2292 {
2293 return d->kdAxis->position();
2294 }
2295
actualAxisPosition() const2296 CartesianAxis::Position Axis::actualAxisPosition() const
2297 {
2298 CartesianAxis::Position pos = d->kdAxis->position();
2299 if (d->plotArea->isVertical()) {
2300 switch (pos) {
2301 case KChart::CartesianAxis::Bottom:
2302 pos = KChart::CartesianAxis::Left;
2303 break;
2304 case KChart::CartesianAxis::Top:
2305 pos = KChart::CartesianAxis::Right;
2306 break;
2307 case KChart::CartesianAxis::Left:
2308 pos = KChart::CartesianAxis::Bottom;
2309 break;
2310 case KChart::CartesianAxis::Right:
2311 pos = KChart::CartesianAxis::Top;
2312 break;
2313 }
2314 }
2315 return pos;
2316 }
2317
axisDirectionReversed() const2318 bool Axis::axisDirectionReversed() const
2319 {
2320 bool reversed = false;
2321 KChart::CartesianCoordinatePlane *plane = dynamic_cast<KChart::CartesianCoordinatePlane*>(kdPlane());
2322 if (plane) {
2323 if (orientation() == Qt::Horizontal)
2324 reversed = plane->isHorizontalRangeReversed();
2325 else // Qt::Vertical
2326 reversed = plane->isVerticalRangeReversed();
2327 }
2328 return reversed;
2329 }
2330
setOdfAxisLabelsPosition(const QString & odfpos)2331 void Axis::setOdfAxisLabelsPosition(const QString &odfpos)
2332 {
2333 d->axisLabelsPosition = odfpos;
2334 }
2335
odfAxisLabelsPosition() const2336 QString Axis::odfAxisLabelsPosition() const
2337 {
2338 return d->axisLabelsPosition;
2339 }
2340
updateKChartStockAttributes()2341 void Axis::updateKChartStockAttributes()
2342 {
2343 if (d->kdStockDiagram) {
2344 d->kdStockDiagram->setLowHighLinePen(d->plotArea->stockRangeLinePen());
2345 d->kdStockDiagram->setUpTrendCandlestickBrush(d->plotArea->stockGainBrush());
2346 d->kdStockDiagram->setDownTrendCandlestickBrush(d->plotArea->stockLossBrush());
2347 d->kdStockDiagram->setUpTrendCandlestickPen(d->plotArea->stockRangeLinePen());
2348 d->kdStockDiagram->setDownTrendCandlestickPen(d->plotArea->stockRangeLinePen());
2349 }
2350 }
2351
operator <<(QDebug dbg,KoChart::Axis * a)2352 QDebug operator<<(QDebug dbg, KoChart::Axis *a)
2353 {
2354 dbg.nospace().noquote() <<"Axis["<<a->name()<<']';
2355 return dbg.space().quote();
2356 }
2357