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 }