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