File indexing completed on 2024-05-12 16:33:35

0001 /* This file is part of the KDE project
0002 
0003    Copyright 2007-2008 Johannes Simon <johannes.simon@gmail.com>
0004    Copyright 2009-2010 Inge Wallin <inge@lysator.liu.se>
0005    Copyright 2018 Dag Andersen <danders@get2net.dk>
0006 
0007    This library is free software; you can redistribute it and/or
0008    modify it under the terms of the GNU Library General Public
0009    License as published by the Free Software Foundation; either
0010    version 2 of the License, or (at your option) any later version.
0011 
0012    This library is distributed in the hope that it will be useful,
0013    but WITHOUT ANY WARRANTY; without even the implied warranty of
0014    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015    Library General Public License for more details.
0016 
0017    You should have received a copy of the GNU Library General Public License
0018    along with this library; see the file COPYING.LIB.  If not, write to
0019    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0020    Boston, MA 02110-1301, USA.
0021 */
0022 
0023 // Own
0024 #include "PlotArea.h"
0025 
0026 // Qt
0027 #include <QPointF>
0028 #include <QSizeF>
0029 #include <QList>
0030 #include <QImage>
0031 #include <QPainter>
0032 #include <QPainterPath>
0033 
0034 // Calligra
0035 #include <KoXmlNS.h>
0036 #include <KoXmlReader.h>
0037 #include <KoXmlWriter.h>
0038 #include <KoGenStyles.h>
0039 #include <KoStyleStack.h>
0040 #include <KoOdfLoadingContext.h>
0041 #include <Ko3dScene.h>
0042 #include <KoOdfGraphicStyles.h>
0043 #include <KoShapeLoadingContext.h>
0044 #include <KoShapeSavingContext.h>
0045 #include <KoTextShapeData.h>
0046 #include <KoViewConverter.h>
0047 #include <KoShapeBackground.h>
0048 
0049 // KChart
0050 #include <KChartChart>
0051 #include <KChartCartesianAxis>
0052 #include <KChartAbstractDiagram>
0053 #include <kchart_version.h>
0054 #include <KChartAbstractCartesianDiagram>
0055 #include <KChartBarAttributes>
0056 #include <KChartCartesianCoordinatePlane>
0057 #include <KChartPolarCoordinatePlane>
0058 #include <KChartRadarCoordinatePlane>
0059 // Attribute Classes
0060 #include <KChartFrameAttributes>
0061 #include <KChartDataValueAttributes>
0062 #include <KChartGridAttributes>
0063 #include <KChartTextAttributes>
0064 #include <KChartMarkerAttributes>
0065 // Diagram Classes
0066 #include <KChartBarDiagram>
0067 #include <KChartPieDiagram>
0068 #include <KChartLineDiagram>
0069 #include <KChartRingDiagram>
0070 #include <KChartPolarDiagram>
0071 
0072 // KoChart
0073 #include "Legend.h"
0074 #include "Surface.h"
0075 #include "Axis.h"
0076 #include "DataSet.h"
0077 #include "ChartProxyModel.h"
0078 #include "ScreenConversions.h"
0079 #include "ChartLayout.h"
0080 #include "ChartDebug.h"
0081 
0082 using namespace KoChart;
0083 
0084 const int MAX_PIXMAP_SIZE = 1000;
0085 
0086 Q_DECLARE_METATYPE(QPointer<QAbstractItemModel>)
0087 typedef QList<KChart::AbstractCoordinatePlane*> CoordinatePlaneList;
0088 
0089 class PlotArea::Private
0090 {
0091 public:
0092     Private(PlotArea *q, ChartShape *parent);
0093     ~Private();
0094 
0095     void initAxes();
0096     void updateAxesPosition();
0097     CoordinatePlaneList coordinatePlanesForChartType(ChartType type);
0098     void autoHideAxisTitles();
0099 
0100     PlotArea *q;
0101     // The parent chart shape
0102     ChartShape *shape;
0103 
0104     // ----------------------------------------------------------------
0105     // Parts and properties of the chart
0106 
0107     ChartType     chartType;
0108     ChartSubtype  chartSubtype;
0109 
0110     Surface       *wall;
0111     Surface       *floor;       // Only used in 3D charts
0112 
0113     // The axes
0114     QList<Axis*>     axes;
0115     QList<KoShape*>  automaticallyHiddenAxisTitles;
0116 
0117     // 3D properties
0118     bool       threeD;
0119     Ko3dScene *threeDScene;
0120 
0121     // ----------------------------------------------------------------
0122     // Data specific to each chart type
0123 
0124     // 1. Bar charts
0125     // FIXME: OpenOffice stores these attributes in the axes' elements.
0126     // The specs don't say anything at all about what elements can have
0127     // these style attributes.
0128     // chart:vertical attribute: see ODF v1.2,19.63
0129     bool  vertical;
0130 
0131     // 2. Polar charts (pie/ring)
0132     qreal angleOffset;       // in degrees
0133     qreal holeSize;
0134 
0135     // ----------------------------------------------------------------
0136     // The embedded KD Chart
0137 
0138     // The KD Chart parts
0139     KChart::Chart                    *const kdChart;
0140     KChart::CartesianCoordinatePlane *const kdCartesianPlanePrimary;
0141     KChart::CartesianCoordinatePlane *const kdCartesianPlaneSecondary;
0142     KChart::PolarCoordinatePlane     *const kdPolarPlane;
0143     KChart::RadarCoordinatePlane     *const kdRadarPlane;
0144     QList<KChart::AbstractDiagram*>   kdDiagrams;
0145 
0146     // Caching: We can rerender faster if we cache KChart's output
0147     QImage   image;
0148     bool     paintPixmap;
0149     QPointF  lastZoomLevel;
0150     QSizeF   lastSize;
0151     mutable bool pixmapRepaintRequested;
0152 
0153     QPen stockRangeLinePen;
0154     QBrush stockGainBrush;
0155     QBrush stockLossBrush;
0156 
0157     QString symbolType;
0158     QString symbolName;
0159     DataSet::ValueLabelType valueLabelType;
0160 };
0161 
0162 PlotArea::Private::Private(PlotArea *q, ChartShape *parent)
0163     : q(q)
0164     , shape(parent)
0165     // Default type: normal bar chart
0166     , chartType(BarChartType)
0167     , chartSubtype(NormalChartSubtype)
0168     , wall(0)
0169     , floor(0)
0170     , threeD(false)
0171     , threeDScene(0)
0172     // By default, x and y axes are not swapped.
0173     , vertical(false)
0174     // OpenOffice.org's default. It means the first pie slice starts at the
0175     // very top (and then going counter-clockwise).
0176     , angleOffset(90.0)
0177     , holeSize(50.0) // KCharts approx default
0178     // KD Chart stuff
0179     , kdChart(new KChart::Chart())
0180     , kdCartesianPlanePrimary(new KChart::CartesianCoordinatePlane(kdChart))
0181     , kdCartesianPlaneSecondary(new KChart::CartesianCoordinatePlane(kdChart))
0182     , kdPolarPlane(new KChart::PolarCoordinatePlane(kdChart))
0183     , kdRadarPlane(new KChart::RadarCoordinatePlane(kdChart))
0184     // Cache
0185     , paintPixmap(true)
0186     , pixmapRepaintRequested(true)
0187     , symbolType("automatic")
0188 {
0189     kdCartesianPlanePrimary->setObjectName("primary");
0190     kdCartesianPlaneSecondary->setObjectName("secondary");
0191     // --- Prepare Primary Cartesian Coordinate Plane ---
0192     KChart::GridAttributes gridAttributes;
0193     gridAttributes.setGridVisible(false);
0194     gridAttributes.setGridGranularitySequence(KChartEnums::GranularitySequence_10_50);
0195     kdCartesianPlanePrimary->setGlobalGridAttributes(gridAttributes);
0196 
0197     // --- Prepare Secondary Cartesian Coordinate Plane ---
0198     kdCartesianPlaneSecondary->setGlobalGridAttributes(gridAttributes);
0199 
0200     // --- Prepare Polar Coordinate Plane ---
0201     KChart::GridAttributes polarGridAttributes;
0202     polarGridAttributes.setGridVisible(false);
0203     kdPolarPlane->setGlobalGridAttributes(polarGridAttributes);
0204 
0205     // --- Prepare Radar Coordinate Plane ---
0206     KChart::GridAttributes radarGridAttributes;
0207     polarGridAttributes.setGridVisible(true);
0208     kdRadarPlane->setGlobalGridAttributes(radarGridAttributes);
0209 
0210     // By default we use a cartesian chart (bar chart), so the polar planes
0211     // are not needed yet. They will be added on demand in setChartType().
0212     kdChart->takeCoordinatePlane(kdPolarPlane);
0213     kdChart->takeCoordinatePlane(kdRadarPlane);
0214 
0215     shape->proxyModel()->setDataDimensions(1);
0216 
0217     stockRangeLinePen.setWidthF(2.0);
0218     stockGainBrush = QBrush(QColor(Qt::white));
0219     stockLossBrush = QBrush(QColor(Qt::black));
0220 }
0221 
0222 PlotArea::Private::~Private()
0223 {
0224     // remove first to avoid crash
0225     while (!kdChart->coordinatePlanes().isEmpty()) {
0226         kdChart->takeCoordinatePlane(kdChart->coordinatePlanes().last());
0227     }
0228 
0229     qDeleteAll(axes);
0230     delete kdCartesianPlanePrimary;
0231     delete kdCartesianPlaneSecondary;
0232     delete kdPolarPlane;
0233     delete kdRadarPlane;
0234     delete kdChart;
0235     delete wall;
0236     delete floor;
0237     delete threeDScene;
0238 }
0239 
0240 void PlotArea::Private::initAxes()
0241 {
0242     // The category data region is anchored to an axis and will be set on addAxis if the
0243     // axis defines the Axis::categoryDataRegion(). So, clear it now.
0244     q->proxyModel()->setCategoryDataRegion(CellRegion());
0245     // Remove all old axes
0246     while(!axes.isEmpty()) {
0247         Axis *axis = axes.takeLast();
0248         Q_ASSERT(axis);
0249         if (axis->title())
0250             automaticallyHiddenAxisTitles.removeAll(axis->title());
0251         delete axis;
0252     }
0253     // There need to be at least these two axes. Their constructor will
0254     // automatically add them to the plot area as child shape.
0255     new Axis(q, XAxisDimension);
0256     Axis *yAxis = new Axis(q, YAxisDimension);
0257     yAxis->setShowMajorGrid(true);
0258 
0259     updateAxesPosition();
0260 }
0261 
0262 void PlotArea::Private::updateAxesPosition()
0263 {
0264     debugChartAxis<<axes;
0265     for (int i = 0; i < axes.count(); ++i) {
0266         axes.at(i)->updateKChartAxisPosition();
0267     }
0268 }
0269 
0270 PlotArea::PlotArea(ChartShape *parent)
0271     : QObject()
0272     , KoShape()
0273     , d(new Private(this, parent))
0274 {
0275     setShapeId("ChartShapePlotArea"); // NB! used by defaulttool/ChartResizeStrategy.cpp
0276 
0277     Q_ASSERT(d->shape);
0278     Q_ASSERT(d->shape->proxyModel());
0279 
0280     setAdditionalStyleAttribute("chart:auto-position", "true");
0281     setAdditionalStyleAttribute("chart:auto-size", "true");
0282 
0283     connect(d->shape->proxyModel(), SIGNAL(modelReset()),
0284             this,                   SLOT(proxyModelStructureChanged()));
0285     connect(d->shape->proxyModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
0286             this,                   SLOT(proxyModelStructureChanged()));
0287     connect(d->shape->proxyModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
0288             this,                   SLOT(proxyModelStructureChanged()));
0289     connect(d->shape->proxyModel(), SIGNAL(columnsInserted(QModelIndex,int,int)),
0290             this,                   SLOT(proxyModelStructureChanged()));
0291     connect(d->shape->proxyModel(), SIGNAL(columnsRemoved(QModelIndex,int,int)),
0292             this,                   SLOT(proxyModelStructureChanged()));
0293     connect(d->shape->proxyModel(), SIGNAL(columnsInserted(QModelIndex,int,int)),
0294             this,                   SLOT(plotAreaUpdate()));
0295     connect(d->shape->proxyModel(), SIGNAL(columnsRemoved(QModelIndex,int,int)),
0296             this,                   SLOT(plotAreaUpdate()));
0297     connect(d->shape->proxyModel(), SIGNAL(dataChanged()),
0298             this,                   SLOT(plotAreaUpdate()));
0299 }
0300 
0301 PlotArea::~PlotArea()
0302 {
0303     delete d;
0304 }
0305 
0306 
0307 void PlotArea::plotAreaInit()
0308 {
0309     d->kdChart->resize(size().toSize());
0310     d->kdChart->replaceCoordinatePlane(d->kdCartesianPlanePrimary);
0311     d->kdCartesianPlaneSecondary->setReferenceCoordinatePlane(d->kdCartesianPlanePrimary);
0312     d->kdChart->addCoordinatePlane(d->kdCartesianPlaneSecondary);
0313 
0314     KChart::FrameAttributes attr = d->kdChart->frameAttributes();
0315     attr.setVisible(false);
0316     d->kdChart->setFrameAttributes(attr);
0317 
0318     d->wall = new Surface(this);
0319     //d->floor = new Surface(this);
0320 
0321     d->initAxes();
0322 
0323     addAxesTitlesToLayout();
0324 }
0325 
0326 void PlotArea::proxyModelStructureChanged()
0327 {
0328     if (proxyModel()->isLoading())
0329         return;
0330 
0331     Q_ASSERT(xAxis());
0332     Q_ASSERT(yAxis());
0333     QMap<DataSet*, Axis*> attachedAxes;
0334     QList<DataSet*> dataSets = proxyModel()->dataSets();
0335 
0336     // Remember to what y axis each data set belongs
0337     foreach(DataSet *dataSet, dataSets)
0338         attachedAxes.insert(dataSet, dataSet->attachedAxis());
0339 
0340     // Proxy structure and thus data sets changed, drop old state and
0341     // clear all axes of data sets
0342     foreach(Axis *axis, axes())
0343         axis->clearDataSets();
0344 
0345     // Now add the new list of data sets to the axis they belong to
0346     foreach(DataSet *dataSet, dataSets) {
0347         xAxis()->attachDataSet(dataSet);
0348         // If they weren't assigned to a y axis before, use default y axis
0349         if (attachedAxes[dataSet])
0350             attachedAxes[dataSet]->attachDataSet(dataSet);
0351         else
0352             yAxis()->attachDataSet(dataSet);
0353     }
0354 }
0355 
0356 ChartProxyModel *PlotArea::proxyModel() const
0357 {
0358     return d->shape->proxyModel();
0359 }
0360 
0361 
0362 QList<Axis*> PlotArea::axes() const
0363 {
0364     return d->axes;
0365 }
0366 
0367 QList<DataSet*> PlotArea::dataSets() const
0368 {
0369     return proxyModel()->dataSets();
0370 }
0371 
0372 Axis *PlotArea::xAxis() const
0373 {
0374     foreach(Axis *axis, d->axes) {
0375         if (axis->dimension() == XAxisDimension)
0376             return axis;
0377     }
0378 
0379     return 0;
0380 }
0381 
0382 Axis *PlotArea::yAxis() const
0383 {
0384     foreach(Axis *axis, d->axes) {
0385         if (axis->dimension() == YAxisDimension)
0386             return axis;
0387     }
0388 
0389     return 0;
0390 }
0391 
0392 Axis *PlotArea::secondaryXAxis() const
0393 {
0394     bool firstXAxisFound = false;
0395 
0396     foreach(Axis *axis, d->axes) {
0397         if (axis->dimension() == XAxisDimension) {
0398             if (firstXAxisFound)
0399                 return axis;
0400             else
0401                 firstXAxisFound = true;
0402         }
0403     }
0404 
0405     return 0;
0406 }
0407 
0408 Axis *PlotArea::secondaryYAxis() const
0409 {
0410     bool firstYAxisFound = false;
0411 
0412     foreach(Axis *axis, d->axes) {
0413         if (axis->dimension() == YAxisDimension) {
0414             if (firstYAxisFound)
0415                 return axis;
0416             else
0417                 firstYAxisFound = true;
0418         }
0419     }
0420 
0421     return 0;
0422 }
0423 
0424 ChartType PlotArea::chartType() const
0425 {
0426     return d->chartType;
0427 }
0428 
0429 ChartSubtype PlotArea::chartSubType() const
0430 {
0431     return d->chartSubtype;
0432 }
0433 
0434 bool PlotArea::isThreeD() const
0435 {
0436     return d->threeD;
0437 }
0438 
0439 bool PlotArea::isVertical() const
0440 {
0441     return d->chartType == BarChartType && d->vertical;
0442 }
0443 
0444 Ko3dScene *PlotArea::threeDScene() const
0445 {
0446     return d->threeDScene;
0447 }
0448 
0449 qreal PlotArea::angleOffset() const
0450 {
0451     return d->angleOffset;
0452 }
0453 
0454 qreal PlotArea::holeSize() const
0455 {
0456     return d->holeSize;
0457 }
0458 
0459 void PlotArea::setHoleSize(qreal value)
0460 {
0461     d->holeSize = value;
0462 }
0463 
0464 // FIXME: this should add the axxis as a child (set axis->parent())
0465 bool PlotArea::addAxis(Axis *axis)
0466 {
0467     if (d->axes.contains(axis)) {
0468         warnChart << "PlotArea::addAxis(): Trying to add already added axis.";
0469         return false;
0470     }
0471 
0472     if (!axis) {
0473         warnChart << "PlotArea::addAxis(): Pointer to axis is NULL!";
0474         return false;
0475     }
0476     d->axes.append(axis);
0477 
0478     if (axis->dimension() == XAxisDimension) {
0479         // let each axis know about the other axis
0480         foreach (Axis *_axis, d->axes) {
0481             if (_axis->isVisible())
0482                 _axis->registerAxis(axis);
0483         }
0484     }
0485 
0486     requestRepaint();
0487 
0488     return true;
0489 }
0490 
0491 bool PlotArea::removeAxis(Axis *axis)
0492 {
0493     bool removed = takeAxis(axis);
0494     if (removed) {
0495         // This also removes the axis' title, which is a shape as well
0496         delete axis;
0497     }
0498     return removed;
0499 }
0500 
0501 // FIXME: this should remove the axis as a child (set axis->parent())
0502 bool PlotArea::takeAxis(Axis *axis)
0503 {
0504     if (!d->axes.contains(axis)) {
0505         warnChart << "PlotArea::takeAxis(): Trying to remove non-added axis.";
0506         return false;
0507     }
0508     if (!axis) {
0509         warnChart << "PlotArea::takeAxis(): Pointer to axis is NULL!";
0510         return false;
0511     }
0512     if (axis->title()) {
0513         d->automaticallyHiddenAxisTitles.removeAll(axis->title());
0514     }
0515     d->axes.removeAll(axis);
0516     axis->removeAxisFromDiagrams(true);
0517     requestRepaint();
0518     return true;
0519 }
0520 
0521 CoordinatePlaneList PlotArea::Private::coordinatePlanesForChartType(ChartType type)
0522 {
0523     CoordinatePlaneList result;
0524     switch (type) {
0525     case BarChartType:
0526     case LineChartType:
0527     case AreaChartType:
0528     case ScatterChartType:
0529     case GanttChartType:
0530     case SurfaceChartType:
0531     case StockChartType:
0532     case BubbleChartType:
0533         result.append(kdCartesianPlanePrimary);
0534         result.append(kdCartesianPlaneSecondary);
0535         break;
0536     case CircleChartType:
0537     case RingChartType:
0538         result.append(kdPolarPlane);
0539         break;
0540     case RadarChartType:
0541     case FilledRadarChartType:
0542         result.append(kdRadarPlane);
0543         break;
0544     case LastChartType:
0545         Q_ASSERT("There's no coordinate plane for LastChartType");
0546         break;
0547     }
0548 
0549     Q_ASSERT(!result.isEmpty());
0550     return result;
0551 }
0552 
0553 
0554 void PlotArea::Private::autoHideAxisTitles()
0555 {
0556     automaticallyHiddenAxisTitles.clear();
0557     foreach (Axis *axis, axes) {
0558         if (axis->title()->isVisible()) {
0559             axis->title()->setVisible(false);
0560             automaticallyHiddenAxisTitles.append(axis->title());
0561         }
0562     }
0563 }
0564 
0565 void PlotArea::setChartType(ChartType type)
0566 {
0567     if (d->chartType == type)
0568         return;
0569 
0570     // Lots of things to do if the old and new types of coordinate
0571     // systems don't match.
0572     if (!isPolar(d->chartType) && isPolar(type)) {
0573         d->autoHideAxisTitles();
0574     }
0575     else if (isPolar(d->chartType) && !isPolar(type)) {
0576         foreach (KoShape *title, d->automaticallyHiddenAxisTitles) {
0577             title->setVisible(true);
0578         }
0579         d->automaticallyHiddenAxisTitles.clear();
0580     }
0581     CellRegion region = d->shape->proxyModel()->cellRangeAddress();
0582     if (type == CircleChartType || type == RingChartType) {
0583         d->shape->proxyModel()->setManualControl(false);
0584         xAxis()->clearDataSets();
0585         yAxis()->clearDataSets();
0586         if (secondaryYAxis()) {
0587             secondaryYAxis()->clearDataSets();
0588         }
0589         if (secondaryXAxis()) {
0590             secondaryXAxis()->clearDataSets();
0591         }
0592     }
0593     CoordinatePlaneList planesToRemove;
0594     // First remove secondary cartesian plane as it references the primary
0595     // plane, otherwise KChart will come down crashing on us. Note that
0596     // removing a plane that's not in the chart is not a problem.
0597     planesToRemove << d->kdCartesianPlaneSecondary << d->kdCartesianPlanePrimary
0598                    << d->kdPolarPlane << d->kdRadarPlane;
0599     foreach(KChart::AbstractCoordinatePlane *plane, planesToRemove)
0600         d->kdChart->takeCoordinatePlane(plane);
0601     CoordinatePlaneList newPlanes = d->coordinatePlanesForChartType(type);
0602     foreach(KChart::AbstractCoordinatePlane *plane, newPlanes)
0603         d->kdChart->addCoordinatePlane(plane);
0604     Q_ASSERT(d->kdChart->coordinatePlanes() == newPlanes);
0605 
0606     d->chartType = type;
0607 
0608     foreach (Axis *axis, d->axes) {
0609         axis->plotAreaChartTypeChanged(type);
0610     }
0611     if (type == CircleChartType || type == RingChartType) {
0612         d->shape->proxyModel()->reset(region);
0613     }
0614     if (type != BarChartType) {
0615         setVertical(false); // Only supported by bar charts
0616     }
0617     requestRepaint();
0618 }
0619 
0620 void PlotArea::setChartSubType(ChartSubtype subType)
0621 {
0622     d->chartSubtype = subType;
0623 
0624     foreach (Axis *axis, d->axes) {
0625         axis->plotAreaChartSubTypeChanged(subType);
0626     }
0627 }
0628 
0629 void PlotArea::setThreeD(bool threeD)
0630 {
0631     d->threeD = threeD;
0632 
0633     foreach(Axis *axis, d->axes)
0634         axis->setThreeD(threeD);
0635 
0636     requestRepaint();
0637 }
0638 
0639 void PlotArea::setVertical(bool vertical)
0640 {
0641     d->vertical = vertical;
0642     foreach(Axis *axis, d->axes)
0643         axis->plotAreaIsVerticalChanged();
0644 }
0645 
0646 // ----------------------------------------------------------------
0647 //                         loading and saving
0648 
0649 
0650 bool PlotArea::loadOdf(const KoXmlElement &plotAreaElement,
0651                        KoShapeLoadingContext &context)
0652 {
0653     KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
0654     KoOdfStylesReader &stylesReader = context.odfLoadingContext().stylesReader();
0655 
0656     // The exact position defined in ODF overwrites the default layout position
0657     // NOTE: Do not do this as it means functionallity changes just because you save and load.
0658     // I don't think odf has an element/attribute that can hold this type of info.
0659     // Also afaics libreoffice do not do this.
0660 //     if (plotAreaElement.hasAttributeNS(KoXmlNS::svg, "x") ||
0661 //         plotAreaElement.hasAttributeNS(KoXmlNS::svg, "y") ||
0662 //         plotAreaElement.hasAttributeNS(KoXmlNS::svg, "width") ||
0663 //         plotAreaElement.hasAttributeNS(KoXmlNS::svg, "height"))
0664 //     {
0665 //         parent()->layout()->setPosition(this, FloatingPosition);
0666 //     }
0667 
0668     bool autoPosition = !(plotAreaElement.hasAttributeNS(KoXmlNS::svg, "x") && plotAreaElement.hasAttributeNS(KoXmlNS::svg, "y"));
0669     bool autoSize = !(plotAreaElement.hasAttributeNS(KoXmlNS::svg, "width") && plotAreaElement.hasAttributeNS(KoXmlNS::svg, "height"));
0670 
0671     context.odfLoadingContext().fillStyleStack(plotAreaElement, KoXmlNS::chart, "style-name", "chart");
0672     loadOdfAttributes(plotAreaElement, context, OdfAllAttributes);
0673 
0674     // First step is to clear all old axis instances.
0675     while (!d->axes.isEmpty()) {
0676         Axis *axis = d->axes.takeLast();
0677         Q_ASSERT(axis);
0678         // Clear this axis of all data sets, deleting any diagram associated with it.
0679         axis->clearDataSets();
0680         if (axis->title())
0681             d->automaticallyHiddenAxisTitles.removeAll(axis->title());
0682         delete axis;
0683     }
0684 
0685     // Now find out about things that are in the plotarea style.
0686     //
0687     // These things include chart subtype, special things for some
0688     // chart types like line charts, stock charts, etc.
0689     //
0690     // Note that this has to happen BEFORE we create a axis and call
0691     // there loadOdf method cause the axis will evaluate settings
0692     // like the PlotArea::isVertical boolean.
0693     bool candleStick = false;
0694     if (plotAreaElement.hasAttributeNS(KoXmlNS::chart, "style-name")) {
0695         styleStack.clear();
0696         context.odfLoadingContext().fillStyleStack(plotAreaElement, KoXmlNS::chart, "style-name", "chart");
0697 
0698         styleStack.setTypeProperties("graphic");
0699         styleStack.setTypeProperties("chart");
0700 
0701         if (styleStack.hasProperty(KoXmlNS::chart, "auto-position")) {
0702             autoPosition |= styleStack.property(KoXmlNS::chart, "auto-position") == "true";
0703         } else {
0704             // To be backwards compatible we set auto-position to true as this was the original behaviour
0705             // and is the way LO works
0706             autoPosition = true;
0707         }
0708         if (styleStack.hasProperty(KoXmlNS::chart, "auto-size")) {
0709             autoSize |= styleStack.property(KoXmlNS::chart, "auto-size") == "true" ;
0710         } else {
0711             // To be backwards compatible we set auto-size to true as this was the original behaviour
0712             // and is the way LO works
0713             autoSize = true;
0714         }
0715 
0716         // ring and pie
0717         if (styleStack.hasProperty(KoXmlNS::chart, "angle-offset")) {
0718             bool ok;
0719             const qreal angleOffset = styleStack.property(KoXmlNS::chart, "angle-offset").toDouble(&ok);
0720             if (ok) {
0721                 setAngleOffset(angleOffset);
0722             }
0723         }
0724         // ring
0725         if (styleStack.hasProperty(KoXmlNS::chart, "hole-size")) {
0726             bool ok;
0727             const qreal value = styleStack.property(KoXmlNS::chart, "hole-size").toDouble(&ok);
0728             if (ok) {
0729                 setHoleSize(value);
0730             }
0731         }
0732 
0733         // Check for 3D.
0734         if (styleStack.hasProperty(KoXmlNS::chart, "three-dimensional"))
0735             setThreeD(styleStack.property(KoXmlNS::chart, "three-dimensional") == "true");
0736         d->threeDScene = load3dScene(plotAreaElement);
0737 
0738         // Set subtypes stacked or percent.
0739         // These are valid for Bar, Line, Area and Radar types.
0740         if (styleStack.hasProperty(KoXmlNS::chart, "percentage")
0741              && styleStack.property(KoXmlNS::chart, "percentage") == "true")
0742         {
0743             setChartSubType(PercentChartSubtype);
0744         }
0745         else if (styleStack.hasProperty(KoXmlNS::chart, "stacked")
0746                   && styleStack.property(KoXmlNS::chart, "stacked") == "true")
0747         {
0748             setChartSubType(StackedChartSubtype);
0749         }
0750 
0751         // Data specific to bar charts
0752         if (styleStack.hasProperty(KoXmlNS::chart, "vertical"))
0753             setVertical(styleStack.property(KoXmlNS::chart, "vertical") == "true");
0754 
0755         // Data specific to stock charts
0756         if (styleStack.hasProperty(KoXmlNS::chart, "japanese-candle-stick")) {
0757             candleStick = styleStack.property(KoXmlNS::chart, "japanese-candle-stick") == "true";
0758         }
0759 
0760         // Special properties for various chart types
0761 #if 0
0762         switch () {
0763         case BarChartType:
0764             if (styleStack)
0765                 ;
0766         }
0767 #endif
0768         styleStack.clear();
0769         context.odfLoadingContext().fillStyleStack(plotAreaElement, KoXmlNS::chart, "style-name", "chart");
0770     }
0771     setAdditionalStyleAttribute("chart:auto-position", autoPosition ? "true" : "false");
0772     setAdditionalStyleAttribute("chart:auto-size", autoSize ? "true" : "false");
0773 
0774     // Now create and load the axis from the ODF. This needs to happen
0775     // AFTER we did set some of the basic settings above so the axis
0776     // can use those basic settings to evaluate it's own settings
0777     // depending on them. This is especially required for the
0778     // PlotArea::isVertical() boolean flag else things will go wrong.
0779     KoXmlElement n;
0780     forEachElement (n, plotAreaElement) {
0781         if (n.namespaceURI() != KoXmlNS::chart)
0782             continue;
0783 
0784         if (n.localName() == "axis") {
0785             if (!n.hasAttributeNS(KoXmlNS::chart, "dimension")) {
0786                 // We have to know what dimension the axis is supposed to be..
0787                 qInfo()<<Q_FUNC_INFO<<"No axis dimension";
0788                 continue;
0789             }
0790             const QString dimension = n.attributeNS(KoXmlNS::chart, "dimension", QString());
0791             AxisDimension dim;
0792             if      (dimension == "x") dim = XAxisDimension;
0793             else if (dimension == "y") dim = YAxisDimension;
0794             else if (dimension == "z") dim = ZAxisDimension;
0795             else continue;
0796             Axis *axis = new Axis(this, dim);
0797             if (dim == YAxisDimension) {
0798                 if (axis == yAxis()) {
0799                 } else if (axis == secondaryYAxis()) {
0800                 }
0801             }
0802             debugChartOdf<<"axis dimension"<<dimension<<dim;
0803             axis->loadOdf(n, context);
0804         }
0805     }
0806 
0807     // Two axes are mandatory, check that we have them.
0808     if (!xAxis()) {
0809         Axis *xAxis = new Axis(this, XAxisDimension);
0810         xAxis->setVisible(false);
0811     }
0812     if (!yAxis()) {
0813         Axis *yAxis = new Axis(this, YAxisDimension);
0814         yAxis->setVisible(false);
0815     }
0816 
0817     // Now, after the axes, load the datasets.
0818     // Note that this only contains properties of the datasets, the
0819     // actual data is not stored here.
0820     //
0821     // FIXME: Isn't the proxy model a strange place to store this data?
0822     proxyModel()->loadOdf(plotAreaElement, context, d->chartType);
0823 
0824     // Now load the surfaces (wall and possibly floor)
0825     // FIXME: Use named tags instead of looping?
0826     forEachElement (n, plotAreaElement) {
0827         if (n.namespaceURI() != KoXmlNS::chart)
0828             continue;
0829 
0830         if (n.localName() == "wall") {
0831             d->wall->loadOdf(n, context);
0832         }
0833         else if (n.localName() == "floor") {
0834             // The floor is not always present, so allocate it if needed.
0835             // FIXME: Load floor, even if we don't really support it yet
0836             // and save it back to ODF.
0837             //if (!d->floor)
0838             //    d->floor = new Surface(this);
0839             //d->floor->loadOdf(n, context);
0840         } else if (n.localName() == "stock-gain-marker") {
0841             styleStack.clear();
0842             context.odfLoadingContext().fillStyleStack(n, KoXmlNS::chart, "style-name", "chart");
0843             styleStack.setTypeProperties("graphic");
0844             if (styleStack.hasProperty(KoXmlNS::draw, "fill")) {
0845                 d->stockGainBrush = KoOdfGraphicStyles::loadOdfFillStyle(styleStack, styleStack.property(KoXmlNS::draw, "fill"), stylesReader);
0846                 debugChartOdf<<n.localName()<<d->stockGainBrush;
0847             } else {
0848                 warnChartOdf<<n.localName()<<"Missing 'draw:fill' property in style"<<n.attributeNS(KoXmlNS::chart, "style-name");
0849             }
0850         } else if (n.localName() == "stock-loss-marker") {
0851             styleStack.clear();
0852             context.odfLoadingContext().fillStyleStack(n, KoXmlNS::chart, "style-name", "chart");
0853             styleStack.setTypeProperties("graphic");
0854             if (styleStack.hasProperty(KoXmlNS::draw, "fill")) {
0855                 d->stockLossBrush = KoOdfGraphicStyles::loadOdfFillStyle(styleStack, styleStack.property(KoXmlNS::draw, "fill"), stylesReader);
0856                 debugChartOdf<<n.localName()<<d->stockLossBrush;
0857             } else {
0858                 warnChartOdf<<n.localName()<<"Missing 'draw:fill' property in style"<<n.attributeNS(KoXmlNS::chart, "style-name");
0859             }
0860         } else if (n.localName() == "stock-range-line") {
0861             styleStack.clear();
0862             context.odfLoadingContext().fillStyleStack(n, KoXmlNS::chart, "style-name", "chart");
0863             styleStack.setTypeProperties("graphic");
0864             if (styleStack.hasProperty(KoXmlNS::draw, "stroke")) {
0865                 d->stockRangeLinePen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, styleStack.property(KoXmlNS::draw, "stroke"), stylesReader);
0866                 debugChartOdf<<n.localName()<<d->stockRangeLinePen;
0867             } else {
0868                 warnChartOdf<<n.localName()<<"Missing 'draw:stroke' property in style"<<n.attributeNS(KoXmlNS::chart, "style-name");
0869             }
0870         } else if (n.localName() != "axis" && n.localName() != "series") {
0871             warnChart << "PlotArea::loadOdf(): Unknown tag name " << n.localName();
0872         }
0873     }
0874     if (d->chartType == StockChartType) {
0875         // The number of data sets determines stock chart subtype
0876         if (proxyModel()->rowCount() > 3) {
0877             if (candleStick) {
0878                 setChartSubType(CandlestickChartSubtype);
0879             } else {
0880                 setChartSubType(OpenHighLowCloseChartSubtype);
0881             }
0882         }
0883     }
0884 
0885     // Connect axes to datasets and cleanup
0886     foreach(DataSet *ds, d->shape->proxyModel()->dataSets()) {
0887         foreach(Axis *axis, d->axes) {
0888             if (axis->name() == ds->axisName()) {
0889                 axis->attachDataSet(ds);
0890             }
0891         }
0892     }
0893     debugChartOdf<<d->chartType<<d->chartSubtype<<d->axes;
0894     if (isPolar(d->chartType)) {
0895         d->autoHideAxisTitles();
0896     }
0897     foreach(Axis *axis, d->axes) {
0898         axis->setName(QString());
0899     }
0900 
0901     // update kchart axis position for all axes
0902     d->updateAxesPosition();
0903     // add axes titles to layout
0904     addAxesTitlesToLayout();
0905 
0906     return true;
0907 }
0908 
0909 void PlotArea::saveOdf(KoShapeSavingContext &context) const
0910 {
0911     KoXmlWriter &bodyWriter = context.xmlWriter();
0912     //KoGenStyles &mainStyles = context.mainStyles();
0913     bodyWriter.startElement("chart:plot-area");
0914 
0915     KoGenStyle plotAreaStyle(KoGenStyle::ChartAutoStyle, "chart");
0916 
0917     // Data direction
0918     const Qt::Orientation direction = proxyModel()->dataDirection();
0919     plotAreaStyle.addProperty("chart:series-source",
0920                                (direction == Qt::Horizontal)
0921                                ? "rows" : "columns");
0922 
0923     // Save chart subtype
0924     saveOdfSubType(bodyWriter, plotAreaStyle);
0925 
0926     // Save extra stuff (like auto-position)
0927     QMap<QByteArray, QString>::const_iterator it(additionalStyleAttributes().constBegin());
0928     for (; it != additionalStyleAttributes().constEnd(); ++it) {
0929         plotAreaStyle.addProperty(it.key(), it.value(), KoGenStyle::ChartType);
0930     }
0931 
0932     // save graphic-properties and insert style
0933     bodyWriter.addAttribute("chart:style-name",
0934                              saveStyle(plotAreaStyle, context));
0935 
0936     const QSizeF s(size());
0937     const QPointF p(position());
0938     bodyWriter.addAttributePt("svg:width",  s.width());
0939     bodyWriter.addAttributePt("svg:height", s.height());
0940     bodyWriter.addAttributePt("svg:x", p.x());
0941     bodyWriter.addAttributePt("svg:y", p.y());
0942 
0943     CellRegion cellRangeAddress = d->shape->proxyModel()->cellRangeAddress();
0944     bodyWriter.addAttribute("table:cell-range-address", cellRangeAddress.toString());
0945 
0946     // About the data:
0947     //   Save if the first row / column contain headers.
0948     QString  dataSourceHasLabels;
0949     if (proxyModel()->firstRowIsLabel()) {
0950         if (proxyModel()->firstColumnIsLabel())
0951             dataSourceHasLabels = "both";
0952         else
0953             dataSourceHasLabels = "row";
0954     } else {
0955         if (proxyModel()->firstColumnIsLabel())
0956             dataSourceHasLabels = "column";
0957         else
0958             dataSourceHasLabels = "none";
0959     }
0960     // Note: this is saved in the plotarea attributes and not the style.
0961     bodyWriter.addAttribute("chart:data-source-has-labels", dataSourceHasLabels);
0962 
0963     if (d->threeDScene) {
0964         d->threeDScene->saveOdfAttributes(bodyWriter);
0965     }
0966     if (d->chartType == StockChartType) {
0967         QString styleName;
0968 
0969         bodyWriter.startElement("chart:stock-gain-marker");
0970         KoGenStyle stockGainStyle(KoGenStyle::ChartAutoStyle, "chart");
0971         KoOdfGraphicStyles::saveOdfFillStyle(stockGainStyle, context.mainStyles(), d->stockGainBrush);
0972         styleName = context.mainStyles().insert(stockGainStyle, "ch");
0973         bodyWriter.addAttribute("chart:style-name", styleName);
0974         bodyWriter.endElement(); // chart:stock-gain-marker
0975 
0976         bodyWriter.startElement("chart:stock-loss-marker");
0977         KoGenStyle stockLossStyle(KoGenStyle::ChartAutoStyle, "chart");
0978         KoOdfGraphicStyles::saveOdfFillStyle(stockLossStyle, context.mainStyles(), d->stockLossBrush);
0979         styleName = context.mainStyles().insert(stockLossStyle, "ch");
0980         bodyWriter.addAttribute("chart:style-name", styleName);
0981         bodyWriter.endElement(); // chart:stock-loss-marker
0982 
0983         bodyWriter.startElement("chart:stock-range-line");
0984         KoGenStyle stockRangeStyle(KoGenStyle::ChartAutoStyle, "chart");
0985         KoOdfGraphicStyles::saveOdfStrokeStyle(stockRangeStyle, context.mainStyles(), d->stockRangeLinePen);
0986         styleName = context.mainStyles().insert(stockRangeStyle, "ch");
0987         bodyWriter.addAttribute("chart:style-name", styleName);
0988         bodyWriter.endElement(); // chart:stock-range-line
0989     }
0990 
0991     // Done with the attributes, start writing the children.
0992 
0993     // Save the axes.
0994     foreach(Axis *axis, d->axes) {
0995         axis->saveOdf(context);
0996     }
0997 
0998     if (d->threeDScene) {
0999         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 
1013 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 
1137 void PlotArea::setAngleOffset(qreal angle)
1138 {
1139     d->angleOffset = angle;
1140 
1141     emit angleOffsetChanged(angle);
1142 }
1143 
1144 ChartShape *PlotArea::parent() const
1145 {
1146     // There has to be a valid parent
1147     Q_ASSERT(d->shape);
1148     return d->shape;
1149 }
1150 
1151 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 
1163 KChart::PolarCoordinatePlane *PlotArea::kdPolarPlane() const
1164 {
1165     return d->kdPolarPlane;
1166 }
1167 
1168 KChart::RadarCoordinatePlane *PlotArea::kdRadarPlane() const
1169 {
1170     return d->kdRadarPlane;
1171 }
1172 
1173 KChart::Chart *PlotArea::kdChart() const
1174 {
1175     return d->kdChart;
1176 }
1177 
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 
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
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 
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 
1222 void PlotArea::requestRepaint() const
1223 {
1224     d->pixmapRepaintRequested = true;
1225 }
1226 
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 
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 
1325 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 
1334 void PlotArea::addTitleToLayout()
1335 {
1336     addAxesTitlesToLayout(); // for now
1337 }
1338 
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 
1364 void PlotArea::setStockRangeLinePen(const QPen &pen)
1365 {
1366     d->stockRangeLinePen = pen;
1367 }
1368 
1369 QPen PlotArea::stockRangeLinePen() const
1370 {
1371     return d->stockRangeLinePen;
1372 }
1373 
1374 void PlotArea::setStockGainBrush(const QBrush &brush)
1375 {
1376     d->stockGainBrush = brush;
1377 }
1378 
1379 QBrush PlotArea::stockGainBrush() const
1380 {
1381     return d->stockGainBrush;
1382 }
1383 
1384 void PlotArea::setStockLossBrush(const QBrush &brush)
1385 {
1386     d->stockLossBrush = brush;
1387 }
1388 
1389 QBrush PlotArea::stockLossBrush() const
1390 {
1391     return d->stockLossBrush;
1392 }
1393 
1394 void PlotArea::updateKChartStockAttributes()
1395 {
1396     for (Axis *a : d->axes) {
1397         a->updateKChartStockAttributes();
1398     }
1399 }
1400 
1401 DataSet::ValueLabelType PlotArea::valueLabelType() const
1402 {
1403     return d->valueLabelType;
1404 }
1405 
1406 QString PlotArea::symbolType() const
1407 {
1408     return d->symbolType;
1409 }
1410 
1411 void PlotArea::setSymbolType(const QString &type)
1412 {
1413     d->symbolType = type;
1414 }
1415 
1416 QString PlotArea::symbolName() const
1417 {
1418     return d->symbolName;
1419 }
1420 
1421 void PlotArea::setSymbolName(const QString &name)
1422 {
1423     d->symbolName = name;
1424 }
1425 
1426 void PlotArea::setValueLabelType(const DataSet::ValueLabelType &type)
1427 {
1428     d->valueLabelType = type;
1429 }