1 /* This file is part of the KDE project
2
3 Copyright 2007-2008 Johannes Simon <johannes.simon@gmail.com>
4 Copyright 2009-2010 Inge Wallin <inge@lysator.liu.se>
5 Copyright 2018 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 "PlotArea.h"
25
26 // Qt
27 #include <QPointF>
28 #include <QSizeF>
29 #include <QList>
30 #include <QImage>
31 #include <QPainter>
32 #include <QPainterPath>
33
34 // Calligra
35 #include <KoXmlNS.h>
36 #include <KoXmlReader.h>
37 #include <KoXmlWriter.h>
38 #include <KoGenStyles.h>
39 #include <KoStyleStack.h>
40 #include <KoOdfLoadingContext.h>
41 #include <Ko3dScene.h>
42 #include <KoOdfGraphicStyles.h>
43 #include <KoShapeLoadingContext.h>
44 #include <KoShapeSavingContext.h>
45 #include <KoTextShapeData.h>
46 #include <KoViewConverter.h>
47 #include <KoShapeBackground.h>
48
49 // KChart
50 #include <KChartChart>
51 #include <KChartCartesianAxis>
52 #include <KChartAbstractDiagram>
53 #include <kchart_version.h>
54 #include <KChartAbstractCartesianDiagram>
55 #include <KChartBarAttributes>
56 #include <KChartCartesianCoordinatePlane>
57 #include <KChartPolarCoordinatePlane>
58 #include <KChartRadarCoordinatePlane>
59 // Attribute Classes
60 #include <KChartFrameAttributes>
61 #include <KChartDataValueAttributes>
62 #include <KChartGridAttributes>
63 #include <KChartTextAttributes>
64 #include <KChartMarkerAttributes>
65 // Diagram Classes
66 #include <KChartBarDiagram>
67 #include <KChartPieDiagram>
68 #include <KChartLineDiagram>
69 #include <KChartRingDiagram>
70 #include <KChartPolarDiagram>
71
72 // KoChart
73 #include "Legend.h"
74 #include "Surface.h"
75 #include "Axis.h"
76 #include "DataSet.h"
77 #include "ChartProxyModel.h"
78 #include "ScreenConversions.h"
79 #include "ChartLayout.h"
80 #include "ChartDebug.h"
81
82 using namespace KoChart;
83
84 const int MAX_PIXMAP_SIZE = 1000;
85
86 Q_DECLARE_METATYPE(QPointer<QAbstractItemModel>)
87 typedef QList<KChart::AbstractCoordinatePlane*> CoordinatePlaneList;
88
89 class PlotArea::Private
90 {
91 public:
92 Private(PlotArea *q, ChartShape *parent);
93 ~Private();
94
95 void initAxes();
96 void updateAxesPosition();
97 CoordinatePlaneList coordinatePlanesForChartType(ChartType type);
98 void autoHideAxisTitles();
99
100 PlotArea *q;
101 // The parent chart shape
102 ChartShape *shape;
103
104 // ----------------------------------------------------------------
105 // Parts and properties of the chart
106
107 ChartType chartType;
108 ChartSubtype chartSubtype;
109
110 Surface *wall;
111 Surface *floor; // Only used in 3D charts
112
113 // The axes
114 QList<Axis*> axes;
115 QList<KoShape*> automaticallyHiddenAxisTitles;
116
117 // 3D properties
118 bool threeD;
119 Ko3dScene *threeDScene;
120
121 // ----------------------------------------------------------------
122 // Data specific to each chart type
123
124 // 1. Bar charts
125 // FIXME: OpenOffice stores these attributes in the axes' elements.
126 // The specs don't say anything at all about what elements can have
127 // these style attributes.
128 // chart:vertical attribute: see ODF v1.2,19.63
129 bool vertical;
130
131 // 2. Polar charts (pie/ring)
132 qreal angleOffset; // in degrees
133 qreal holeSize;
134
135 // ----------------------------------------------------------------
136 // The embedded KD Chart
137
138 // The KD Chart parts
139 KChart::Chart *const kdChart;
140 KChart::CartesianCoordinatePlane *const kdCartesianPlanePrimary;
141 KChart::CartesianCoordinatePlane *const kdCartesianPlaneSecondary;
142 KChart::PolarCoordinatePlane *const kdPolarPlane;
143 KChart::RadarCoordinatePlane *const kdRadarPlane;
144 QList<KChart::AbstractDiagram*> kdDiagrams;
145
146 // Caching: We can rerender faster if we cache KChart's output
147 QImage image;
148 bool paintPixmap;
149 QPointF lastZoomLevel;
150 QSizeF lastSize;
151 mutable bool pixmapRepaintRequested;
152
153 QPen stockRangeLinePen;
154 QBrush stockGainBrush;
155 QBrush stockLossBrush;
156
157 QString symbolType;
158 QString symbolName;
159 DataSet::ValueLabelType valueLabelType;
160 };
161
Private(PlotArea * q,ChartShape * parent)162 PlotArea::Private::Private(PlotArea *q, ChartShape *parent)
163 : q(q)
164 , shape(parent)
165 // Default type: normal bar chart
166 , chartType(BarChartType)
167 , chartSubtype(NormalChartSubtype)
168 , wall(0)
169 , floor(0)
170 , threeD(false)
171 , threeDScene(0)
172 // By default, x and y axes are not swapped.
173 , vertical(false)
174 // OpenOffice.org's default. It means the first pie slice starts at the
175 // very top (and then going counter-clockwise).
176 , angleOffset(90.0)
177 , holeSize(50.0) // KCharts approx default
178 // KD Chart stuff
179 , kdChart(new KChart::Chart())
180 , kdCartesianPlanePrimary(new KChart::CartesianCoordinatePlane(kdChart))
181 , kdCartesianPlaneSecondary(new KChart::CartesianCoordinatePlane(kdChart))
182 , kdPolarPlane(new KChart::PolarCoordinatePlane(kdChart))
183 , kdRadarPlane(new KChart::RadarCoordinatePlane(kdChart))
184 // Cache
185 , paintPixmap(true)
186 , pixmapRepaintRequested(true)
187 , symbolType("automatic")
188 {
189 kdCartesianPlanePrimary->setObjectName("primary");
190 kdCartesianPlaneSecondary->setObjectName("secondary");
191 // --- Prepare Primary Cartesian Coordinate Plane ---
192 KChart::GridAttributes gridAttributes;
193 gridAttributes.setGridVisible(false);
194 gridAttributes.setGridGranularitySequence(KChartEnums::GranularitySequence_10_50);
195 kdCartesianPlanePrimary->setGlobalGridAttributes(gridAttributes);
196
197 // --- Prepare Secondary Cartesian Coordinate Plane ---
198 kdCartesianPlaneSecondary->setGlobalGridAttributes(gridAttributes);
199
200 // --- Prepare Polar Coordinate Plane ---
201 KChart::GridAttributes polarGridAttributes;
202 polarGridAttributes.setGridVisible(false);
203 kdPolarPlane->setGlobalGridAttributes(polarGridAttributes);
204
205 // --- Prepare Radar Coordinate Plane ---
206 KChart::GridAttributes radarGridAttributes;
207 polarGridAttributes.setGridVisible(true);
208 kdRadarPlane->setGlobalGridAttributes(radarGridAttributes);
209
210 // By default we use a cartesian chart (bar chart), so the polar planes
211 // are not needed yet. They will be added on demand in setChartType().
212 kdChart->takeCoordinatePlane(kdPolarPlane);
213 kdChart->takeCoordinatePlane(kdRadarPlane);
214
215 shape->proxyModel()->setDataDimensions(1);
216
217 stockRangeLinePen.setWidthF(2.0);
218 stockGainBrush = QBrush(QColor(Qt::white));
219 stockLossBrush = QBrush(QColor(Qt::black));
220 }
221
~Private()222 PlotArea::Private::~Private()
223 {
224 // remove first to avoid crash
225 while (!kdChart->coordinatePlanes().isEmpty()) {
226 kdChart->takeCoordinatePlane(kdChart->coordinatePlanes().last());
227 }
228
229 qDeleteAll(axes);
230 delete kdCartesianPlanePrimary;
231 delete kdCartesianPlaneSecondary;
232 delete kdPolarPlane;
233 delete kdRadarPlane;
234 delete kdChart;
235 delete wall;
236 delete floor;
237 delete threeDScene;
238 }
239
initAxes()240 void PlotArea::Private::initAxes()
241 {
242 // The category data region is anchored to an axis and will be set on addAxis if the
243 // axis defines the Axis::categoryDataRegion(). So, clear it now.
244 q->proxyModel()->setCategoryDataRegion(CellRegion());
245 // Remove all old axes
246 while(!axes.isEmpty()) {
247 Axis *axis = axes.takeLast();
248 Q_ASSERT(axis);
249 if (axis->title())
250 automaticallyHiddenAxisTitles.removeAll(axis->title());
251 delete axis;
252 }
253 // There need to be at least these two axes. Their constructor will
254 // automatically add them to the plot area as child shape.
255 new Axis(q, XAxisDimension);
256 Axis *yAxis = new Axis(q, YAxisDimension);
257 yAxis->setShowMajorGrid(true);
258
259 updateAxesPosition();
260 }
261
updateAxesPosition()262 void PlotArea::Private::updateAxesPosition()
263 {
264 debugChartAxis<<axes;
265 for (int i = 0; i < axes.count(); ++i) {
266 axes.at(i)->updateKChartAxisPosition();
267 }
268 }
269
PlotArea(ChartShape * parent)270 PlotArea::PlotArea(ChartShape *parent)
271 : QObject()
272 , KoShape()
273 , d(new Private(this, parent))
274 {
275 setShapeId("ChartShapePlotArea"); // NB! used by defaulttool/ChartResizeStrategy.cpp
276
277 Q_ASSERT(d->shape);
278 Q_ASSERT(d->shape->proxyModel());
279
280 setAdditionalStyleAttribute("chart:auto-position", "true");
281 setAdditionalStyleAttribute("chart:auto-size", "true");
282
283 connect(d->shape->proxyModel(), SIGNAL(modelReset()),
284 this, SLOT(proxyModelStructureChanged()));
285 connect(d->shape->proxyModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
286 this, SLOT(proxyModelStructureChanged()));
287 connect(d->shape->proxyModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
288 this, SLOT(proxyModelStructureChanged()));
289 connect(d->shape->proxyModel(), SIGNAL(columnsInserted(QModelIndex,int,int)),
290 this, SLOT(proxyModelStructureChanged()));
291 connect(d->shape->proxyModel(), SIGNAL(columnsRemoved(QModelIndex,int,int)),
292 this, SLOT(proxyModelStructureChanged()));
293 connect(d->shape->proxyModel(), SIGNAL(columnsInserted(QModelIndex,int,int)),
294 this, SLOT(plotAreaUpdate()));
295 connect(d->shape->proxyModel(), SIGNAL(columnsRemoved(QModelIndex,int,int)),
296 this, SLOT(plotAreaUpdate()));
297 connect(d->shape->proxyModel(), SIGNAL(dataChanged()),
298 this, SLOT(plotAreaUpdate()));
299 }
300
~PlotArea()301 PlotArea::~PlotArea()
302 {
303 delete d;
304 }
305
306
plotAreaInit()307 void PlotArea::plotAreaInit()
308 {
309 d->kdChart->resize(size().toSize());
310 d->kdChart->replaceCoordinatePlane(d->kdCartesianPlanePrimary);
311 d->kdCartesianPlaneSecondary->setReferenceCoordinatePlane(d->kdCartesianPlanePrimary);
312 d->kdChart->addCoordinatePlane(d->kdCartesianPlaneSecondary);
313
314 KChart::FrameAttributes attr = d->kdChart->frameAttributes();
315 attr.setVisible(false);
316 d->kdChart->setFrameAttributes(attr);
317
318 d->wall = new Surface(this);
319 //d->floor = new Surface(this);
320
321 d->initAxes();
322
323 addAxesTitlesToLayout();
324 }
325
proxyModelStructureChanged()326 void PlotArea::proxyModelStructureChanged()
327 {
328 if (proxyModel()->isLoading())
329 return;
330
331 Q_ASSERT(xAxis());
332 Q_ASSERT(yAxis());
333 QMap<DataSet*, Axis*> attachedAxes;
334 QList<DataSet*> dataSets = proxyModel()->dataSets();
335
336 // Remember to what y axis each data set belongs
337 foreach(DataSet *dataSet, dataSets)
338 attachedAxes.insert(dataSet, dataSet->attachedAxis());
339
340 // Proxy structure and thus data sets changed, drop old state and
341 // clear all axes of data sets
342 foreach(Axis *axis, axes())
343 axis->clearDataSets();
344
345 // Now add the new list of data sets to the axis they belong to
346 foreach(DataSet *dataSet, dataSets) {
347 xAxis()->attachDataSet(dataSet);
348 // If they weren't assigned to a y axis before, use default y axis
349 if (attachedAxes[dataSet])
350 attachedAxes[dataSet]->attachDataSet(dataSet);
351 else
352 yAxis()->attachDataSet(dataSet);
353 }
354 }
355
proxyModel() const356 ChartProxyModel *PlotArea::proxyModel() const
357 {
358 return d->shape->proxyModel();
359 }
360
361
axes() const362 QList<Axis*> PlotArea::axes() const
363 {
364 return d->axes;
365 }
366
dataSets() const367 QList<DataSet*> PlotArea::dataSets() const
368 {
369 return proxyModel()->dataSets();
370 }
371
xAxis() const372 Axis *PlotArea::xAxis() const
373 {
374 foreach(Axis *axis, d->axes) {
375 if (axis->dimension() == XAxisDimension)
376 return axis;
377 }
378
379 return 0;
380 }
381
yAxis() const382 Axis *PlotArea::yAxis() const
383 {
384 foreach(Axis *axis, d->axes) {
385 if (axis->dimension() == YAxisDimension)
386 return axis;
387 }
388
389 return 0;
390 }
391
secondaryXAxis() const392 Axis *PlotArea::secondaryXAxis() const
393 {
394 bool firstXAxisFound = false;
395
396 foreach(Axis *axis, d->axes) {
397 if (axis->dimension() == XAxisDimension) {
398 if (firstXAxisFound)
399 return axis;
400 else
401 firstXAxisFound = true;
402 }
403 }
404
405 return 0;
406 }
407
secondaryYAxis() const408 Axis *PlotArea::secondaryYAxis() const
409 {
410 bool firstYAxisFound = false;
411
412 foreach(Axis *axis, d->axes) {
413 if (axis->dimension() == YAxisDimension) {
414 if (firstYAxisFound)
415 return axis;
416 else
417 firstYAxisFound = true;
418 }
419 }
420
421 return 0;
422 }
423
chartType() const424 ChartType PlotArea::chartType() const
425 {
426 return d->chartType;
427 }
428
chartSubType() const429 ChartSubtype PlotArea::chartSubType() const
430 {
431 return d->chartSubtype;
432 }
433
isThreeD() const434 bool PlotArea::isThreeD() const
435 {
436 return d->threeD;
437 }
438
isVertical() const439 bool PlotArea::isVertical() const
440 {
441 return d->chartType == BarChartType && d->vertical;
442 }
443
threeDScene() const444 Ko3dScene *PlotArea::threeDScene() const
445 {
446 return d->threeDScene;
447 }
448
angleOffset() const449 qreal PlotArea::angleOffset() const
450 {
451 return d->angleOffset;
452 }
453
holeSize() const454 qreal PlotArea::holeSize() const
455 {
456 return d->holeSize;
457 }
458
setHoleSize(qreal value)459 void PlotArea::setHoleSize(qreal value)
460 {
461 d->holeSize = value;
462 }
463
464 // FIXME: this should add the axxis as a child (set axis->parent())
addAxis(Axis * axis)465 bool PlotArea::addAxis(Axis *axis)
466 {
467 if (d->axes.contains(axis)) {
468 warnChart << "PlotArea::addAxis(): Trying to add already added axis.";
469 return false;
470 }
471
472 if (!axis) {
473 warnChart << "PlotArea::addAxis(): Pointer to axis is NULL!";
474 return false;
475 }
476 d->axes.append(axis);
477
478 if (axis->dimension() == XAxisDimension) {
479 // let each axis know about the other axis
480 foreach (Axis *_axis, d->axes) {
481 if (_axis->isVisible())
482 _axis->registerAxis(axis);
483 }
484 }
485
486 requestRepaint();
487
488 return true;
489 }
490
removeAxis(Axis * axis)491 bool PlotArea::removeAxis(Axis *axis)
492 {
493 bool removed = takeAxis(axis);
494 if (removed) {
495 // This also removes the axis' title, which is a shape as well
496 delete axis;
497 }
498 return removed;
499 }
500
501 // FIXME: this should remove the axis as a child (set axis->parent())
takeAxis(Axis * axis)502 bool PlotArea::takeAxis(Axis *axis)
503 {
504 if (!d->axes.contains(axis)) {
505 warnChart << "PlotArea::takeAxis(): Trying to remove non-added axis.";
506 return false;
507 }
508 if (!axis) {
509 warnChart << "PlotArea::takeAxis(): Pointer to axis is NULL!";
510 return false;
511 }
512 if (axis->title()) {
513 d->automaticallyHiddenAxisTitles.removeAll(axis->title());
514 }
515 d->axes.removeAll(axis);
516 axis->removeAxisFromDiagrams(true);
517 requestRepaint();
518 return true;
519 }
520
coordinatePlanesForChartType(ChartType type)521 CoordinatePlaneList PlotArea::Private::coordinatePlanesForChartType(ChartType type)
522 {
523 CoordinatePlaneList result;
524 switch (type) {
525 case BarChartType:
526 case LineChartType:
527 case AreaChartType:
528 case ScatterChartType:
529 case GanttChartType:
530 case SurfaceChartType:
531 case StockChartType:
532 case BubbleChartType:
533 result.append(kdCartesianPlanePrimary);
534 result.append(kdCartesianPlaneSecondary);
535 break;
536 case CircleChartType:
537 case RingChartType:
538 result.append(kdPolarPlane);
539 break;
540 case RadarChartType:
541 case FilledRadarChartType:
542 result.append(kdRadarPlane);
543 break;
544 case LastChartType:
545 Q_ASSERT("There's no coordinate plane for LastChartType");
546 break;
547 }
548
549 Q_ASSERT(!result.isEmpty());
550 return result;
551 }
552
553
autoHideAxisTitles()554 void PlotArea::Private::autoHideAxisTitles()
555 {
556 automaticallyHiddenAxisTitles.clear();
557 foreach (Axis *axis, axes) {
558 if (axis->title()->isVisible()) {
559 axis->title()->setVisible(false);
560 automaticallyHiddenAxisTitles.append(axis->title());
561 }
562 }
563 }
564
setChartType(ChartType type)565 void PlotArea::setChartType(ChartType type)
566 {
567 if (d->chartType == type)
568 return;
569
570 // Lots of things to do if the old and new types of coordinate
571 // systems don't match.
572 if (!isPolar(d->chartType) && isPolar(type)) {
573 d->autoHideAxisTitles();
574 }
575 else if (isPolar(d->chartType) && !isPolar(type)) {
576 foreach (KoShape *title, d->automaticallyHiddenAxisTitles) {
577 title->setVisible(true);
578 }
579 d->automaticallyHiddenAxisTitles.clear();
580 }
581 CellRegion region = d->shape->proxyModel()->cellRangeAddress();
582 if (type == CircleChartType || type == RingChartType) {
583 d->shape->proxyModel()->setManualControl(false);
584 xAxis()->clearDataSets();
585 yAxis()->clearDataSets();
586 if (secondaryYAxis()) {
587 secondaryYAxis()->clearDataSets();
588 }
589 if (secondaryXAxis()) {
590 secondaryXAxis()->clearDataSets();
591 }
592 }
593 CoordinatePlaneList planesToRemove;
594 // First remove secondary cartesian plane as it references the primary
595 // plane, otherwise KChart will come down crashing on us. Note that
596 // removing a plane that's not in the chart is not a problem.
597 planesToRemove << d->kdCartesianPlaneSecondary << d->kdCartesianPlanePrimary
598 << d->kdPolarPlane << d->kdRadarPlane;
599 foreach(KChart::AbstractCoordinatePlane *plane, planesToRemove)
600 d->kdChart->takeCoordinatePlane(plane);
601 CoordinatePlaneList newPlanes = d->coordinatePlanesForChartType(type);
602 foreach(KChart::AbstractCoordinatePlane *plane, newPlanes)
603 d->kdChart->addCoordinatePlane(plane);
604 Q_ASSERT(d->kdChart->coordinatePlanes() == newPlanes);
605
606 d->chartType = type;
607
608 foreach (Axis *axis, d->axes) {
609 axis->plotAreaChartTypeChanged(type);
610 }
611 if (type == CircleChartType || type == RingChartType) {
612 d->shape->proxyModel()->reset(region);
613 }
614 if (type != BarChartType) {
615 setVertical(false); // Only supported by bar charts
616 }
617 requestRepaint();
618 }
619
setChartSubType(ChartSubtype subType)620 void PlotArea::setChartSubType(ChartSubtype subType)
621 {
622 d->chartSubtype = subType;
623
624 foreach (Axis *axis, d->axes) {
625 axis->plotAreaChartSubTypeChanged(subType);
626 }
627 }
628
setThreeD(bool threeD)629 void PlotArea::setThreeD(bool threeD)
630 {
631 d->threeD = threeD;
632
633 foreach(Axis *axis, d->axes)
634 axis->setThreeD(threeD);
635
636 requestRepaint();
637 }
638
setVertical(bool vertical)639 void PlotArea::setVertical(bool vertical)
640 {
641 d->vertical = vertical;
642 foreach(Axis *axis, d->axes)
643 axis->plotAreaIsVerticalChanged();
644 }
645
646 // ----------------------------------------------------------------
647 // loading and saving
648
649
loadOdf(const KoXmlElement & plotAreaElement,KoShapeLoadingContext & context)650 bool PlotArea::loadOdf(const KoXmlElement &plotAreaElement,
651 KoShapeLoadingContext &context)
652 {
653 KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
654 KoOdfStylesReader &stylesReader = context.odfLoadingContext().stylesReader();
655
656 // The exact position defined in ODF overwrites the default layout position
657 // NOTE: Do not do this as it means functionallity changes just because you save and load.
658 // I don't think odf has an element/attribute that can hold this type of info.
659 // Also afaics libreoffice do not do this.
660 // if (plotAreaElement.hasAttributeNS(KoXmlNS::svg, "x") ||
661 // plotAreaElement.hasAttributeNS(KoXmlNS::svg, "y") ||
662 // plotAreaElement.hasAttributeNS(KoXmlNS::svg, "width") ||
663 // plotAreaElement.hasAttributeNS(KoXmlNS::svg, "height"))
664 // {
665 // parent()->layout()->setPosition(this, FloatingPosition);
666 // }
667
668 bool autoPosition = !(plotAreaElement.hasAttributeNS(KoXmlNS::svg, "x") && plotAreaElement.hasAttributeNS(KoXmlNS::svg, "y"));
669 bool autoSize = !(plotAreaElement.hasAttributeNS(KoXmlNS::svg, "width") && plotAreaElement.hasAttributeNS(KoXmlNS::svg, "height"));
670
671 context.odfLoadingContext().fillStyleStack(plotAreaElement, KoXmlNS::chart, "style-name", "chart");
672 loadOdfAttributes(plotAreaElement, context, OdfAllAttributes);
673
674 // First step is to clear all old axis instances.
675 while (!d->axes.isEmpty()) {
676 Axis *axis = d->axes.takeLast();
677 Q_ASSERT(axis);
678 // Clear this axis of all data sets, deleting any diagram associated with it.
679 axis->clearDataSets();
680 if (axis->title())
681 d->automaticallyHiddenAxisTitles.removeAll(axis->title());
682 delete axis;
683 }
684
685 // Now find out about things that are in the plotarea style.
686 //
687 // These things include chart subtype, special things for some
688 // chart types like line charts, stock charts, etc.
689 //
690 // Note that this has to happen BEFORE we create a axis and call
691 // there loadOdf method cause the axis will evaluate settings
692 // like the PlotArea::isVertical boolean.
693 bool candleStick = false;
694 if (plotAreaElement.hasAttributeNS(KoXmlNS::chart, "style-name")) {
695 styleStack.clear();
696 context.odfLoadingContext().fillStyleStack(plotAreaElement, KoXmlNS::chart, "style-name", "chart");
697
698 styleStack.setTypeProperties("graphic");
699 styleStack.setTypeProperties("chart");
700
701 if (styleStack.hasProperty(KoXmlNS::chart, "auto-position")) {
702 autoPosition |= styleStack.property(KoXmlNS::chart, "auto-position") == "true";
703 } else {
704 // To be backwards compatible we set auto-position to true as this was the original behaviour
705 // and is the way LO works
706 autoPosition = true;
707 }
708 if (styleStack.hasProperty(KoXmlNS::chart, "auto-size")) {
709 autoSize |= styleStack.property(KoXmlNS::chart, "auto-size") == "true" ;
710 } else {
711 // To be backwards compatible we set auto-size to true as this was the original behaviour
712 // and is the way LO works
713 autoSize = true;
714 }
715
716 // ring and pie
717 if (styleStack.hasProperty(KoXmlNS::chart, "angle-offset")) {
718 bool ok;
719 const qreal angleOffset = styleStack.property(KoXmlNS::chart, "angle-offset").toDouble(&ok);
720 if (ok) {
721 setAngleOffset(angleOffset);
722 }
723 }
724 // ring
725 if (styleStack.hasProperty(KoXmlNS::chart, "hole-size")) {
726 bool ok;
727 const qreal value = styleStack.property(KoXmlNS::chart, "hole-size").toDouble(&ok);
728 if (ok) {
729 setHoleSize(value);
730 }
731 }
732
733 // Check for 3D.
734 if (styleStack.hasProperty(KoXmlNS::chart, "three-dimensional"))
735 setThreeD(styleStack.property(KoXmlNS::chart, "three-dimensional") == "true");
736 d->threeDScene = load3dScene(plotAreaElement);
737
738 // Set subtypes stacked or percent.
739 // These are valid for Bar, Line, Area and Radar types.
740 if (styleStack.hasProperty(KoXmlNS::chart, "percentage")
741 && styleStack.property(KoXmlNS::chart, "percentage") == "true")
742 {
743 setChartSubType(PercentChartSubtype);
744 }
745 else if (styleStack.hasProperty(KoXmlNS::chart, "stacked")
746 && styleStack.property(KoXmlNS::chart, "stacked") == "true")
747 {
748 setChartSubType(StackedChartSubtype);
749 }
750
751 // Data specific to bar charts
752 if (styleStack.hasProperty(KoXmlNS::chart, "vertical"))
753 setVertical(styleStack.property(KoXmlNS::chart, "vertical") == "true");
754
755 // Data specific to stock charts
756 if (styleStack.hasProperty(KoXmlNS::chart, "japanese-candle-stick")) {
757 candleStick = styleStack.property(KoXmlNS::chart, "japanese-candle-stick") == "true";
758 }
759
760 // Special properties for various chart types
761 #if 0
762 switch () {
763 case BarChartType:
764 if (styleStack)
765 ;
766 }
767 #endif
768 styleStack.clear();
769 context.odfLoadingContext().fillStyleStack(plotAreaElement, KoXmlNS::chart, "style-name", "chart");
770 }
771 setAdditionalStyleAttribute("chart:auto-position", autoPosition ? "true" : "false");
772 setAdditionalStyleAttribute("chart:auto-size", autoSize ? "true" : "false");
773
774 // Now create and load the axis from the ODF. This needs to happen
775 // AFTER we did set some of the basic settings above so the axis
776 // can use those basic settings to evaluate it's own settings
777 // depending on them. This is especially required for the
778 // PlotArea::isVertical() boolean flag else things will go wrong.
779 KoXmlElement n;
780 forEachElement (n, plotAreaElement) {
781 if (n.namespaceURI() != KoXmlNS::chart)
782 continue;
783
784 if (n.localName() == "axis") {
785 if (!n.hasAttributeNS(KoXmlNS::chart, "dimension")) {
786 // We have to know what dimension the axis is supposed to be..
787 qInfo()<<Q_FUNC_INFO<<"No axis dimension";
788 continue;
789 }
790 const QString dimension = n.attributeNS(KoXmlNS::chart, "dimension", QString());
791 AxisDimension dim;
792 if (dimension == "x") dim = XAxisDimension;
793 else if (dimension == "y") dim = YAxisDimension;
794 else if (dimension == "z") dim = ZAxisDimension;
795 else continue;
796 Axis *axis = new Axis(this, dim);
797 if (dim == YAxisDimension) {
798 if (axis == yAxis()) {
799 } else if (axis == secondaryYAxis()) {
800 }
801 }
802 debugChartOdf<<"axis dimension"<<dimension<<dim;
803 axis->loadOdf(n, context);
804 }
805 }
806
807 // Two axes are mandatory, check that we have them.
808 if (!xAxis()) {
809 Axis *xAxis = new Axis(this, XAxisDimension);
810 xAxis->setVisible(false);
811 }
812 if (!yAxis()) {
813 Axis *yAxis = new Axis(this, YAxisDimension);
814 yAxis->setVisible(false);
815 }
816
817 // Now, after the axes, load the datasets.
818 // Note that this only contains properties of the datasets, the
819 // actual data is not stored here.
820 //
821 // FIXME: Isn't the proxy model a strange place to store this data?
822 proxyModel()->loadOdf(plotAreaElement, context, d->chartType);
823
824 // Now load the surfaces (wall and possibly floor)
825 // FIXME: Use named tags instead of looping?
826 forEachElement (n, plotAreaElement) {
827 if (n.namespaceURI() != KoXmlNS::chart)
828 continue;
829
830 if (n.localName() == "wall") {
831 d->wall->loadOdf(n, context);
832 }
833 else if (n.localName() == "floor") {
834 // The floor is not always present, so allocate it if needed.
835 // FIXME: Load floor, even if we don't really support it yet
836 // and save it back to ODF.
837 //if (!d->floor)
838 // d->floor = new Surface(this);
839 //d->floor->loadOdf(n, context);
840 } else if (n.localName() == "stock-gain-marker") {
841 styleStack.clear();
842 context.odfLoadingContext().fillStyleStack(n, KoXmlNS::chart, "style-name", "chart");
843 styleStack.setTypeProperties("graphic");
844 if (styleStack.hasProperty(KoXmlNS::draw, "fill")) {
845 d->stockGainBrush = KoOdfGraphicStyles::loadOdfFillStyle(styleStack, styleStack.property(KoXmlNS::draw, "fill"), stylesReader);
846 debugChartOdf<<n.localName()<<d->stockGainBrush;
847 } else {
848 warnChartOdf<<n.localName()<<"Missing 'draw:fill' property in style"<<n.attributeNS(KoXmlNS::chart, "style-name");
849 }
850 } else if (n.localName() == "stock-loss-marker") {
851 styleStack.clear();
852 context.odfLoadingContext().fillStyleStack(n, KoXmlNS::chart, "style-name", "chart");
853 styleStack.setTypeProperties("graphic");
854 if (styleStack.hasProperty(KoXmlNS::draw, "fill")) {
855 d->stockLossBrush = KoOdfGraphicStyles::loadOdfFillStyle(styleStack, styleStack.property(KoXmlNS::draw, "fill"), stylesReader);
856 debugChartOdf<<n.localName()<<d->stockLossBrush;
857 } else {
858 warnChartOdf<<n.localName()<<"Missing 'draw:fill' property in style"<<n.attributeNS(KoXmlNS::chart, "style-name");
859 }
860 } else if (n.localName() == "stock-range-line") {
861 styleStack.clear();
862 context.odfLoadingContext().fillStyleStack(n, KoXmlNS::chart, "style-name", "chart");
863 styleStack.setTypeProperties("graphic");
864 if (styleStack.hasProperty(KoXmlNS::draw, "stroke")) {
865 d->stockRangeLinePen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, styleStack.property(KoXmlNS::draw, "stroke"), stylesReader);
866 debugChartOdf<<n.localName()<<d->stockRangeLinePen;
867 } else {
868 warnChartOdf<<n.localName()<<"Missing 'draw:stroke' property in style"<<n.attributeNS(KoXmlNS::chart, "style-name");
869 }
870 } else if (n.localName() != "axis" && n.localName() != "series") {
871 warnChart << "PlotArea::loadOdf(): Unknown tag name " << n.localName();
872 }
873 }
874 if (d->chartType == StockChartType) {
875 // The number of data sets determines stock chart subtype
876 if (proxyModel()->rowCount() > 3) {
877 if (candleStick) {
878 setChartSubType(CandlestickChartSubtype);
879 } else {
880 setChartSubType(OpenHighLowCloseChartSubtype);
881 }
882 }
883 }
884
885 // Connect axes to datasets and cleanup
886 foreach(DataSet *ds, d->shape->proxyModel()->dataSets()) {
887 foreach(Axis *axis, d->axes) {
888 if (axis->name() == ds->axisName()) {
889 axis->attachDataSet(ds);
890 }
891 }
892 }
893 debugChartOdf<<d->chartType<<d->chartSubtype<<d->axes;
894 if (isPolar(d->chartType)) {
895 d->autoHideAxisTitles();
896 }
897 foreach(Axis *axis, d->axes) {
898 axis->setName(QString());
899 }
900
901 // update kchart axis position for all axes
902 d->updateAxesPosition();
903 // add axes titles to layout
904 addAxesTitlesToLayout();
905
906 return true;
907 }
908
saveOdf(KoShapeSavingContext & context) const909 void PlotArea::saveOdf(KoShapeSavingContext &context) const
910 {
911 KoXmlWriter &bodyWriter = context.xmlWriter();
912 //KoGenStyles &mainStyles = context.mainStyles();
913 bodyWriter.startElement("chart:plot-area");
914
915 KoGenStyle plotAreaStyle(KoGenStyle::ChartAutoStyle, "chart");
916
917 // Data direction
918 const Qt::Orientation direction = proxyModel()->dataDirection();
919 plotAreaStyle.addProperty("chart:series-source",
920 (direction == Qt::Horizontal)
921 ? "rows" : "columns");
922
923 // Save chart subtype
924 saveOdfSubType(bodyWriter, plotAreaStyle);
925
926 // Save extra stuff (like auto-position)
927 QMap<QByteArray, QString>::const_iterator it(additionalStyleAttributes().constBegin());
928 for (; it != additionalStyleAttributes().constEnd(); ++it) {
929 plotAreaStyle.addProperty(it.key(), it.value(), KoGenStyle::ChartType);
930 }
931
932 // save graphic-properties and insert style
933 bodyWriter.addAttribute("chart:style-name",
934 saveStyle(plotAreaStyle, context));
935
936 const QSizeF s(size());
937 const QPointF p(position());
938 bodyWriter.addAttributePt("svg:width", s.width());
939 bodyWriter.addAttributePt("svg:height", s.height());
940 bodyWriter.addAttributePt("svg:x", p.x());
941 bodyWriter.addAttributePt("svg:y", p.y());
942
943 CellRegion cellRangeAddress = d->shape->proxyModel()->cellRangeAddress();
944 bodyWriter.addAttribute("table:cell-range-address", cellRangeAddress.toString());
945
946 // About the data:
947 // Save if the first row / column contain headers.
948 QString dataSourceHasLabels;
949 if (proxyModel()->firstRowIsLabel()) {
950 if (proxyModel()->firstColumnIsLabel())
951 dataSourceHasLabels = "both";
952 else
953 dataSourceHasLabels = "row";
954 } else {
955 if (proxyModel()->firstColumnIsLabel())
956 dataSourceHasLabels = "column";
957 else
958 dataSourceHasLabels = "none";
959 }
960 // Note: this is saved in the plotarea attributes and not the style.
961 bodyWriter.addAttribute("chart:data-source-has-labels", dataSourceHasLabels);
962
963 if (d->threeDScene) {
964 d->threeDScene->saveOdfAttributes(bodyWriter);
965 }
966 if (d->chartType == StockChartType) {
967 QString styleName;
968
969 bodyWriter.startElement("chart:stock-gain-marker");
970 KoGenStyle stockGainStyle(KoGenStyle::ChartAutoStyle, "chart");
971 KoOdfGraphicStyles::saveOdfFillStyle(stockGainStyle, context.mainStyles(), d->stockGainBrush);
972 styleName = context.mainStyles().insert(stockGainStyle, "ch");
973 bodyWriter.addAttribute("chart:style-name", styleName);
974 bodyWriter.endElement(); // chart:stock-gain-marker
975
976 bodyWriter.startElement("chart:stock-loss-marker");
977 KoGenStyle stockLossStyle(KoGenStyle::ChartAutoStyle, "chart");
978 KoOdfGraphicStyles::saveOdfFillStyle(stockLossStyle, context.mainStyles(), d->stockLossBrush);
979 styleName = context.mainStyles().insert(stockLossStyle, "ch");
980 bodyWriter.addAttribute("chart:style-name", styleName);
981 bodyWriter.endElement(); // chart:stock-loss-marker
982
983 bodyWriter.startElement("chart:stock-range-line");
984 KoGenStyle stockRangeStyle(KoGenStyle::ChartAutoStyle, "chart");
985 KoOdfGraphicStyles::saveOdfStrokeStyle(stockRangeStyle, context.mainStyles(), d->stockRangeLinePen);
986 styleName = context.mainStyles().insert(stockRangeStyle, "ch");
987 bodyWriter.addAttribute("chart:style-name", styleName);
988 bodyWriter.endElement(); // chart:stock-range-line
989 }
990
991 // Done with the attributes, start writing the children.
992
993 // Save the axes.
994 foreach(Axis *axis, d->axes) {
995 axis->saveOdf(context);
996 }
997
998 if (d->threeDScene) {
999 d->threeDScene->saveOdfChildren(bodyWriter);
1000 }
1001
1002 // Save data series
1003 d->shape->proxyModel()->saveOdf(context);
1004
1005 // Save the floor and wall of the plotarea.
1006 d->wall->saveOdf(context, "chart:wall");
1007 //if (d->floor)
1008 // d->floor->saveOdf(context, "chart:floor");
1009
1010 bodyWriter.endElement(); // chart:plot-area
1011 }
1012
saveOdfSubType(KoXmlWriter & xmlWriter,KoGenStyle & plotAreaStyle) const1013 void PlotArea::saveOdfSubType(KoXmlWriter& xmlWriter,
1014 KoGenStyle& plotAreaStyle) const
1015 {
1016 Q_UNUSED(xmlWriter);
1017
1018 switch (d->chartType) {
1019 case BarChartType:
1020 switch(d->chartSubtype) {
1021 case NoChartSubtype:
1022 case NormalChartSubtype:
1023 break;
1024 case StackedChartSubtype:
1025 plotAreaStyle.addProperty("chart:stacked", "true");
1026 break;
1027 case PercentChartSubtype:
1028 plotAreaStyle.addProperty("chart:percentage", "true");
1029 break;
1030 }
1031
1032 if (d->threeD) {
1033 plotAreaStyle.addProperty("chart:three-dimensional", "true");
1034 }
1035
1036 // Data specific to bar charts
1037 if (d->vertical)
1038 plotAreaStyle.addProperty("chart:vertical", "true");
1039 // Don't save this if zero, because that's the default.
1040 //plotAreaStyle.addProperty("chart:lines-used", 0); // FIXME: for now
1041 break;
1042
1043 case LineChartType:
1044 switch(d->chartSubtype) {
1045 case NoChartSubtype:
1046 case NormalChartSubtype:
1047 break;
1048 case StackedChartSubtype:
1049 plotAreaStyle.addProperty("chart:stacked", "true");
1050 break;
1051 case PercentChartSubtype:
1052 plotAreaStyle.addProperty("chart:percentage", "true");
1053 break;
1054 }
1055 if (d->threeD) {
1056 plotAreaStyle.addProperty("chart:three-dimensional", "true");
1057 // FIXME: Save all 3D attributes too.
1058 }
1059 // FIXME: What does this mean?
1060 plotAreaStyle.addProperty("chart:symbol-type", "automatic");
1061 break;
1062
1063 case AreaChartType:
1064 switch(d->chartSubtype) {
1065 case NoChartSubtype:
1066 case NormalChartSubtype:
1067 break;
1068 case StackedChartSubtype:
1069 plotAreaStyle.addProperty("chart:stacked", "true");
1070 break;
1071 case PercentChartSubtype:
1072 plotAreaStyle.addProperty("chart:percentage", "true");
1073 break;
1074 }
1075
1076 if (d->threeD) {
1077 plotAreaStyle.addProperty("chart:three-dimensional", "true");
1078 // FIXME: Save all 3D attributes too.
1079 }
1080 break;
1081
1082 case CircleChartType:
1083 plotAreaStyle.addProperty("chart:angle-offset", QString::number(d->angleOffset));
1084 break;
1085
1086 case RingChartType:
1087 plotAreaStyle.addProperty("chart:angle-offset", QString::number(d->angleOffset));
1088 plotAreaStyle.addProperty("chart:hole-size", QString::number(d->holeSize));
1089 break;
1090
1091 case ScatterChartType:
1092 // FIXME
1093 break;
1094 case RadarChartType:
1095 case FilledRadarChartType:
1096 // Save subtype of the Radar chart.
1097 switch(d->chartSubtype) {
1098 case NoChartSubtype:
1099 case NormalChartSubtype:
1100 break;
1101 case StackedChartSubtype:
1102 plotAreaStyle.addProperty("chart:stacked", "true");
1103 break;
1104 case PercentChartSubtype:
1105 plotAreaStyle.addProperty("chart:percentage", "true");
1106 break;
1107 }
1108 break;
1109
1110 case StockChartType: {
1111 switch(d->chartSubtype) {
1112 case NoChartSubtype:
1113 case HighLowCloseChartSubtype:
1114 case OpenHighLowCloseChartSubtype:
1115 plotAreaStyle.addProperty("chart:japanese-candle-stick", "false");
1116 break;
1117 case CandlestickChartSubtype:
1118 plotAreaStyle.addProperty("chart:japanese-candle-stick", "true");
1119 break;
1120 }
1121 }
1122 case BubbleChartType:
1123 case SurfaceChartType:
1124 case GanttChartType:
1125 // FIXME
1126 break;
1127
1128 // This is not a valid type, but needs to be handled to avoid
1129 // a warning from gcc.
1130 case LastChartType:
1131 default:
1132 // FIXME
1133 break;
1134 }
1135 }
1136
setAngleOffset(qreal angle)1137 void PlotArea::setAngleOffset(qreal angle)
1138 {
1139 d->angleOffset = angle;
1140
1141 emit angleOffsetChanged(angle);
1142 }
1143
parent() const1144 ChartShape *PlotArea::parent() const
1145 {
1146 // There has to be a valid parent
1147 Q_ASSERT(d->shape);
1148 return d->shape;
1149 }
1150
kdCartesianPlane(Axis * axis) const1151 KChart::CartesianCoordinatePlane *PlotArea::kdCartesianPlane(Axis *axis) const
1152 {
1153 if (axis) {
1154 Q_ASSERT(d->axes.contains(axis));
1155 // Only a secondary y axis gets the secondary plane
1156 if (axis->dimension() == YAxisDimension && axis != yAxis())
1157 return d->kdCartesianPlaneSecondary;
1158 }
1159
1160 return d->kdCartesianPlanePrimary;
1161 }
1162
kdPolarPlane() const1163 KChart::PolarCoordinatePlane *PlotArea::kdPolarPlane() const
1164 {
1165 return d->kdPolarPlane;
1166 }
1167
kdRadarPlane() const1168 KChart::RadarCoordinatePlane *PlotArea::kdRadarPlane() const
1169 {
1170 return d->kdRadarPlane;
1171 }
1172
kdChart() const1173 KChart::Chart *PlotArea::kdChart() const
1174 {
1175 return d->kdChart;
1176 }
1177
registerKdDiagram(KChart::AbstractDiagram * diagram)1178 bool PlotArea::registerKdDiagram(KChart::AbstractDiagram *diagram)
1179 {
1180 if (d->kdDiagrams.contains(diagram))
1181 return false;
1182
1183 d->kdDiagrams.append(diagram);
1184 return true;
1185 }
1186
deregisterKdDiagram(KChart::AbstractDiagram * diagram)1187 bool PlotArea::deregisterKdDiagram(KChart::AbstractDiagram *diagram)
1188 {
1189 if (!d->kdDiagrams.contains(diagram))
1190 return false;
1191
1192 d->kdDiagrams.removeAll(diagram);
1193 return true;
1194 }
1195
1196 // HACK to get kdChart to recognize secondary planes
registerKdPlane(KChart::AbstractCoordinatePlane * plane)1197 void PlotArea::registerKdPlane(KChart::AbstractCoordinatePlane *plane)
1198 {
1199 int pos = d->kdChart->coordinatePlanes().indexOf(plane);
1200 if (pos >= 1) {
1201 // secondary plane
1202 d->kdChart->takeCoordinatePlane(plane);
1203 d->kdChart->insertCoordinatePlane(pos, plane);
1204 } else if (pos < 0) {
1205 d->kdChart->addCoordinatePlane(plane);
1206 }
1207 }
1208
plotAreaUpdate()1209 void PlotArea::plotAreaUpdate()
1210 {
1211 parent()->legend()->update();
1212 if (d->chartType == StockChartType) {
1213 updateKChartStockAttributes();
1214 }
1215 requestRepaint();
1216 foreach(Axis* axis, d->axes)
1217 axis->update();
1218
1219 KoShape::update();
1220 }
1221
requestRepaint() const1222 void PlotArea::requestRepaint() const
1223 {
1224 d->pixmapRepaintRequested = true;
1225 }
1226
paintPixmap(QPainter & painter,const KoViewConverter & converter)1227 void PlotArea::paintPixmap(QPainter &painter, const KoViewConverter &converter)
1228 {
1229 // Adjust the size of the painting area to the current zoom level
1230 const QSize paintRectSize = converter.documentToView(size()).toSize();
1231 const QSize plotAreaSize = size().toSize();
1232 const int borderX = 4;
1233 const int borderY = 4;
1234
1235 // Only use a pixmap with sane sizes
1236 d->paintPixmap = false;//paintRectSize.width() < MAX_PIXMAP_SIZE || paintRectSize.height() < MAX_PIXMAP_SIZE;
1237
1238 if (d->paintPixmap) {
1239 d->image = QImage(paintRectSize, QImage::Format_RGB32);
1240
1241 // Copy the painter's render hints, such as antialiasing
1242 QPainter pixmapPainter(&d->image);
1243 pixmapPainter.setRenderHints(painter.renderHints());
1244 pixmapPainter.setRenderHint(QPainter::Antialiasing, false);
1245
1246 // scale the painter's coordinate system to fit the current zoom level
1247 applyConversion(pixmapPainter, converter);
1248
1249 d->kdChart->paint(&pixmapPainter, QRect(QPoint(borderX, borderY),
1250 QSize(plotAreaSize.width() - 2 * borderX,
1251 plotAreaSize.height() - 2 * borderY)));
1252 } else {
1253 d->kdChart->paint(&painter, QRect(QPoint(borderX, borderY),
1254 QSize(plotAreaSize.width() - 2 * borderX,
1255 plotAreaSize.height() - 2 * borderY)));
1256 }
1257 }
1258
paint(QPainter & painter,const KoViewConverter & converter,KoShapePaintingContext & paintContext)1259 void PlotArea::paint(QPainter& painter, const KoViewConverter& converter, KoShapePaintingContext &paintContext)
1260 {
1261 //painter.save();
1262
1263 // First of all, scale the painter's coordinate system to fit the current zoom level
1264 applyConversion(painter, converter);
1265
1266 // Calculate the clipping rect
1267 QRectF paintRect = QRectF(QPointF(0, 0), size());
1268 painter.setClipRect(paintRect, Qt::IntersectClip);
1269
1270 // Paint the background
1271 if (background()) {
1272 QPainterPath p;
1273 p.addRect(paintRect);
1274 background()->paint(painter, converter, paintContext, p);
1275 }
1276
1277 // Get the current zoom level
1278 QPointF zoomLevel;
1279 converter.zoom(&zoomLevel.rx(), &zoomLevel.ry());
1280
1281 // Only repaint the pixmap if it is scheduled, the zoom level
1282 // changed or the shape was resized.
1283 /*if ( d->pixmapRepaintRequested
1284 || d->lastZoomLevel != zoomLevel
1285 || d->lastSize != size()
1286 || !d->paintPixmap) {
1287 // TODO (js): What if two zoom levels are constantly being
1288 // requested? At the moment, this *is* the case,
1289 // due to the fact that the shape is also rendered
1290 // in the page overview in Stage. Every time
1291 // the window is hidden and shown again, a repaint
1292 // is requested --> laggy performance, especially
1293 // when quickly switching through windows.
1294 //
1295 // ANSWER (iw): what about having a small mapping between size
1296 // in pixels and pixmaps? The size could be 2 or
1297 // at most 3. We could manage the replacing
1298 // using LRU.
1299 paintPixmap(painter, converter);
1300 d->pixmapRepaintRequested = false;
1301 d->lastZoomLevel = zoomLevel;
1302 d->lastSize = size();
1303 }*/
1304 painter.setRenderHint(QPainter::Antialiasing, false);
1305
1306 // KChart thinks in pixels, Calligra in pt
1307 ScreenConversions::scaleFromPtToPx(painter);
1308
1309 // Only paint the actual chart if there is a certain minimal size,
1310 // because otherwise kdchart will crash.
1311 QRect kdchartRect = ScreenConversions::scaleFromPtToPx(paintRect, painter);
1312 // Turn off clipping so that border (or "frame") drawn by KChart::Chart
1313 // is not not cut off.
1314 painter.setClipping(false);
1315 if (kdchartRect.width() > 10 && kdchartRect.height() > 10) {
1316 d->kdChart->paint(&painter, kdchartRect);
1317 }
1318 //painter.restore();
1319
1320 // Paint the cached pixmap if we got a GO from paintPixmap()
1321 //if (d->paintPixmap)
1322 // painter.drawImage(0, 0, d->image);
1323 }
1324
relayout() const1325 void PlotArea::relayout() const
1326 {
1327 d->kdCartesianPlanePrimary->relayout();
1328 d->kdCartesianPlaneSecondary->relayout();
1329 d->kdPolarPlane->relayout();
1330 d->kdRadarPlane->relayout();
1331 update();
1332 }
1333
addTitleToLayout()1334 void PlotArea::addTitleToLayout()
1335 {
1336 addAxesTitlesToLayout(); // for now
1337 }
1338
addAxesTitlesToLayout()1339 void PlotArea::addAxesTitlesToLayout()
1340 {
1341 ChartLayout *layout = d->shape->layout();
1342 Axis *axis = xAxis();
1343 if (axis) {
1344 layout->remove(axis->title());
1345 layout->setItemType(axis->title(), XAxisTitleType);
1346 }
1347 axis = yAxis();
1348 if (axis) {
1349 layout->remove(axis->title());
1350 layout->setItemType(axis->title(), YAxisTitleType);
1351 }
1352 axis = secondaryXAxis();
1353 if (axis) {
1354 layout->remove(axis->title());
1355 layout->setItemType(axis->title(), SecondaryXAxisTitleType);
1356 }
1357 axis = secondaryYAxis();
1358 if (axis) {
1359 layout->remove(axis->title());
1360 layout->setItemType(axis->title(), SecondaryYAxisTitleType);
1361 }
1362 }
1363
setStockRangeLinePen(const QPen & pen)1364 void PlotArea::setStockRangeLinePen(const QPen &pen)
1365 {
1366 d->stockRangeLinePen = pen;
1367 }
1368
stockRangeLinePen() const1369 QPen PlotArea::stockRangeLinePen() const
1370 {
1371 return d->stockRangeLinePen;
1372 }
1373
setStockGainBrush(const QBrush & brush)1374 void PlotArea::setStockGainBrush(const QBrush &brush)
1375 {
1376 d->stockGainBrush = brush;
1377 }
1378
stockGainBrush() const1379 QBrush PlotArea::stockGainBrush() const
1380 {
1381 return d->stockGainBrush;
1382 }
1383
setStockLossBrush(const QBrush & brush)1384 void PlotArea::setStockLossBrush(const QBrush &brush)
1385 {
1386 d->stockLossBrush = brush;
1387 }
1388
stockLossBrush() const1389 QBrush PlotArea::stockLossBrush() const
1390 {
1391 return d->stockLossBrush;
1392 }
1393
updateKChartStockAttributes()1394 void PlotArea::updateKChartStockAttributes()
1395 {
1396 for (Axis *a : d->axes) {
1397 a->updateKChartStockAttributes();
1398 }
1399 }
1400
valueLabelType() const1401 DataSet::ValueLabelType PlotArea::valueLabelType() const
1402 {
1403 return d->valueLabelType;
1404 }
1405
symbolType() const1406 QString PlotArea::symbolType() const
1407 {
1408 return d->symbolType;
1409 }
1410
setSymbolType(const QString & type)1411 void PlotArea::setSymbolType(const QString &type)
1412 {
1413 d->symbolType = type;
1414 }
1415
symbolName() const1416 QString PlotArea::symbolName() const
1417 {
1418 return d->symbolName;
1419 }
1420
setSymbolName(const QString & name)1421 void PlotArea::setSymbolName(const QString &name)
1422 {
1423 d->symbolName = name;
1424 }
1425
setValueLabelType(const DataSet::ValueLabelType & type)1426 void PlotArea::setValueLabelType(const DataSet::ValueLabelType &type)
1427 {
1428 d->valueLabelType = type;
1429 }
1430