diff --git a/plugins/chartshape/Axis.cpp b/plugins/chartshape/Axis.cpp index 49f2f60d7bf..944ccb0a0b0 100644 --- a/plugins/chartshape/Axis.cpp +++ b/plugins/chartshape/Axis.cpp @@ -1,2351 +1,2352 @@ /* This file is part of the KDE project Copyright 2007 Johannes Simon Copyright 2009 Inge Wallin Copyright 2017 Dag Andersen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Own #include "Axis.h" // Qt #include #include #include #include // Calligra #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KChart #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KoChart #include "PlotArea.h" #include "KChartModel.h" #include "DataSet.h" #include "Legend.h" #include "KChartConvertions.h" #include "ChartProxyModel.h" #include "TextLabelDummy.h" #include "ChartLayout.h" #include "OdfLoadingHelper.h" #include "OdfHelper.h" #include "ChartDebug.h" using namespace KoChart; class Axis::Private { public: Private(Axis *axis, AxisDimension dim); ~Private(); void adjustAllDiagrams(); /// Updates the axis position in the chart's layout /// FIXME: We should instead implement a generic layout position method /// and have the layout find out about our position when it changes. void updatePosition(); void registerDiagram(KChart::AbstractDiagram *diagram); KChart::AbstractDiagram *getDiagramAndCreateIfNeeded(ChartType chartType); KChart::AbstractDiagram *getDiagram(ChartType chartType); void deleteDiagram(ChartType chartType); void deleteDiagram(KChart::AbstractDiagram *diagram); void restoreDiagrams(); void createBarDiagram(); void createLineDiagram(); void createAreaDiagram(); void createCircleDiagram(); void createRingDiagram(); void createRadarDiagram(bool filled); void createScatterDiagram(); void createStockDiagram(); void createBubbleDiagram(); void createSurfaceDiagram(); void createGanttDiagram(); void applyAttributesToDataSet(DataSet* set, ChartType newCharttype); // Pointer to Axis that owns this Private instance Axis * const q; PlotArea *plotArea; const AxisDimension dimension; KoShape *title; TextLabelData *titleData; /// FIXME: Unused variable 'id', including id() getter QString id; QList dataSets; qreal majorInterval; int minorIntervalDivisor; bool showInnerMinorTicks; bool showOuterMinorTicks; bool showInnerMajorTicks; bool showOuterMajorTicks; bool logarithmicScaling; bool showMajorGrid; bool showMinorGrid; bool useAutomaticMajorInterval; bool useAutomaticMinorInterval; bool useAutomaticMinimumRange; bool useAutomaticMaximumRange; KChart::CartesianAxis *const kdAxis; KChart::CartesianCoordinatePlane *kdPlane; KChart::PolarCoordinatePlane *kdPolarPlane; KChart::RadarCoordinatePlane *kdRadarPlane; KoOdfNumberStyles::NumericStyleFormat *numericStyleFormat; QList > diagrams; QPointer kdBarDiagram; QPointer kdLineDiagram; QPointer kdAreaDiagram; QPointer kdCircleDiagram; QPointer kdRingDiagram; QPointer kdRadarDiagram; QPointer kdScatterDiagram; QPointer kdStockDiagram; QPointer kdBubbleDiagram; // FIXME BUG: Somehow we need to visualize something for these // missing chart types. We have some alternatives: // 1. Show an empty area // 2. Show a text "unsupported chart type" // 3. Exchange for something else, e.g. a bar chart. // ... More? // // NOTE: Whatever we do, we should always store the data so that // it can be saved back into the file for a perfect // roundtrip. QPointer kdSurfaceDiagram; QPointer kdGanttDiagram; ChartType plotAreaChartType; ChartSubtype plotAreaChartSubType; // If KChart::LineDiagram::centerDataPoints() property is set to true, // the data points drawn in a line (i.e., also an area) diagram start at // an offset of 0.5, that is, in the middle of a column in the diagram. // Set flag to true if at least one dataset is attached to this axis // that belongs to a horizontal bar chart bool centerDataPoints; int gapBetweenBars; int gapBetweenSets; // TODO: Save // See ODF v1.2 $19.12 (chart:display-label) bool showLabels; bool showOverlappingDataLabels; bool isVisible; QString name; QString axisPosition; QString axisLabelsPosition; }; class CartesianAxis : public KChart::CartesianAxis { public: CartesianAxis(KoChart::Axis *_axis) : KChart::CartesianAxis(), axis(_axis) {} virtual ~CartesianAxis() {} const QString customizedLabel(const QString& label) const override { if (KoOdfNumberStyles::NumericStyleFormat *n = axis->numericStyleFormat()) return KoOdfNumberStyles::format(label, *n); return label; } private: KoChart::Axis *axis; }; Axis::Private::Private(Axis *axis, AxisDimension dim) : q(axis) , dimension(dim) , kdAxis(new CartesianAxis(axis)) , kdPlane(0) , kdPolarPlane(0) , kdRadarPlane(0) , numericStyleFormat(0) { centerDataPoints = false; gapBetweenBars = 0; gapBetweenSets = 100; isVisible = true; useAutomaticMajorInterval = true; useAutomaticMinorInterval = true; useAutomaticMinimumRange = true; useAutomaticMaximumRange = true; majorInterval = 2; minorIntervalDivisor = 1; showMajorGrid = false; showMinorGrid = false; logarithmicScaling = false; showInnerMinorTicks = false; showOuterMinorTicks = false; showInnerMajorTicks = false; showOuterMajorTicks = true; showOverlappingDataLabels = false; showLabels = true; title = 0; titleData = 0; KChart::RulerAttributes attr = kdAxis->rulerAttributes(); attr.setShowRulerLine(true); #if KCHART_VERSION >= ((2<<16)|(6<<8)|(89)) attr.setRulerLinePen(QPen()); #endif kdAxis->setRulerAttributes(attr); } Axis::Private::~Private() { Q_ASSERT(plotArea); q->removeAxisFromDiagrams(); delete kdBarDiagram; delete kdLineDiagram; delete kdAreaDiagram; delete kdCircleDiagram; delete kdRingDiagram; delete kdRadarDiagram; delete kdScatterDiagram; delete kdStockDiagram; delete kdBubbleDiagram; delete kdSurfaceDiagram; delete kdGanttDiagram; delete numericStyleFormat; delete kdAxis; foreach(DataSet *dataSet, dataSets) dataSet->setAttachedAxis(0); } void Axis::Private::registerDiagram(KChart::AbstractDiagram *diagram) { QObject::connect(plotArea->proxyModel(), SIGNAL(columnsInserted(QModelIndex,int,int)), diagram->model(), SLOT(slotColumnsInserted(QModelIndex,int,int))); QObject::connect(diagram, SIGNAL(propertiesChanged()), plotArea, SLOT(plotAreaUpdate())); QObject::connect(diagram, SIGNAL(layoutChanged(AbstractDiagram*)), plotArea, SLOT(plotAreaUpdate())); QObject::connect(diagram, SIGNAL(modelsChanged()), plotArea, SLOT(plotAreaUpdate())); QObject::connect(diagram, SIGNAL(dataHidden()), plotArea, SLOT(plotAreaUpdate())); } KChart::AbstractDiagram *Axis::Private::getDiagramAndCreateIfNeeded(ChartType chartType) { KChart::AbstractDiagram *diagram = 0; switch (chartType) { case BarChartType: if (!kdBarDiagram) createBarDiagram(); diagram = kdBarDiagram; break; case LineChartType: if (!kdLineDiagram) createLineDiagram(); diagram = kdLineDiagram; break; case AreaChartType: if (!kdAreaDiagram) createAreaDiagram(); diagram = kdAreaDiagram; break; case CircleChartType: if (!kdCircleDiagram) createCircleDiagram(); diagram = kdCircleDiagram; break; case RingChartType: if (!kdRingDiagram) createRingDiagram(); diagram = kdRingDiagram; break; case RadarChartType: case FilledRadarChartType: if (!kdRadarDiagram) createRadarDiagram(chartType == FilledRadarChartType); diagram = kdRadarDiagram; break; case ScatterChartType: if (!kdScatterDiagram) createScatterDiagram(); diagram = kdScatterDiagram; break; case StockChartType: if (!kdStockDiagram) createStockDiagram(); diagram = kdStockDiagram; break; case BubbleChartType: if (!kdBubbleDiagram) createBubbleDiagram(); diagram = kdBubbleDiagram; break; case SurfaceChartType: if (!kdSurfaceDiagram) createSurfaceDiagram(); diagram = kdSurfaceDiagram; break; case GanttChartType: if (!kdGanttDiagram) createGanttDiagram(); diagram = kdGanttDiagram; break; default: ; } diagram->setObjectName(q->name()); // for debug adjustAllDiagrams(); debugChartAxis<name()<<"diagram"<coordinatePlane()) { diagram->coordinatePlane()->takeDiagram(diagram); } delete diagram; adjustAllDiagrams(); } void Axis::Private::deleteDiagram(ChartType chartType) { KChart::AbstractDiagram *diagram = getDiagram(chartType); if (diagram) { deleteDiagram(diagram); } } void Axis::Private::createBarDiagram() { Q_ASSERT(kdBarDiagram == 0); kdBarDiagram = new KChart::BarDiagram(plotArea->kdChart(), kdPlane); KChartModel *model = new KChartModel(plotArea, kdBarDiagram); kdBarDiagram->setModel(model); registerDiagram(kdBarDiagram); // By 'vertical', KChart means the orientation of a chart's bars, // not the orientation of the x axis. kdBarDiagram->setOrientation(plotArea->isVertical() ? Qt::Horizontal : Qt::Vertical); kdBarDiagram->setPen(QPen(Qt::black, 0.0)); kdBarDiagram->setAllowOverlappingDataValueTexts(showOverlappingDataLabels); if (plotAreaChartSubType == StackedChartSubtype) kdBarDiagram->setType(KChart::BarDiagram::Stacked); else if (plotAreaChartSubType == PercentChartSubtype) { kdBarDiagram->setType(KChart::BarDiagram::Percent); kdBarDiagram->setUnitSuffix("%", kdBarDiagram->orientation()); } if (isVisible) { kdBarDiagram->addAxis(kdAxis); q->registerDiagram(kdBarDiagram); } kdPlane->addDiagram(kdBarDiagram); Q_ASSERT(plotArea); foreach (Axis *axis, plotArea->axes()) { if (axis->isVisible() && axis->dimension() == XAxisDimension) { kdBarDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(kdBarDiagram); } } // Set default bar diagram attributes q->setGapBetweenBars(gapBetweenBars); q->setGapBetweenSets(gapBetweenSets); // Propagate existing settings KChart::ThreeDBarAttributes attributes(kdBarDiagram->threeDBarAttributes()); attributes.setEnabled(plotArea->isThreeD()); attributes.setThreeDBrushEnabled(plotArea->isThreeD()); kdBarDiagram->setThreeDBarAttributes(attributes); q->plotAreaIsVerticalChanged(); plotArea->parent()->legend()->kdLegend()->addDiagram(kdBarDiagram); } void Axis::Private::createLineDiagram() { Q_ASSERT(kdLineDiagram == 0); kdLineDiagram = new KChart::LineDiagram(plotArea->kdChart(), kdPlane); KChartModel *model = new KChartModel(plotArea, kdLineDiagram); kdLineDiagram->setModel(model); registerDiagram(kdLineDiagram); kdLineDiagram->setAllowOverlappingDataValueTexts(showOverlappingDataLabels); if (plotAreaChartSubType == StackedChartSubtype) kdLineDiagram->setType(KChart::LineDiagram::Stacked); else if (plotAreaChartSubType == PercentChartSubtype) kdLineDiagram->setType(KChart::LineDiagram::Percent); if (isVisible) { kdLineDiagram->addAxis(kdAxis); q->registerDiagram(kdLineDiagram); } kdPlane->addDiagram(kdLineDiagram); Q_ASSERT(plotArea); foreach (Axis *axis, plotArea->axes()) { if (axis->isVisible() && axis->dimension() == XAxisDimension) { kdLineDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(kdLineDiagram); } } // Propagate existing settings KChart::ThreeDLineAttributes attributes(kdLineDiagram->threeDLineAttributes()); attributes.setEnabled(plotArea->isThreeD()); attributes.setThreeDBrushEnabled(plotArea->isThreeD()); kdLineDiagram->setThreeDLineAttributes(attributes); KChart::LineAttributes lineAttr = kdLineDiagram->lineAttributes(); lineAttr.setMissingValuesPolicy(KChart::LineAttributes::MissingValuesHideSegments); kdLineDiagram->setLineAttributes(lineAttr); plotArea->parent()->legend()->kdLegend()->addDiagram(kdLineDiagram); } void Axis::Private::createAreaDiagram() { Q_ASSERT(kdAreaDiagram == 0); kdAreaDiagram = new KChart::LineDiagram(plotArea->kdChart(), kdPlane); KChartModel *model = new KChartModel(plotArea, kdAreaDiagram); kdAreaDiagram->setModel(model); registerDiagram(kdAreaDiagram); KChart::LineAttributes attr = kdAreaDiagram->lineAttributes(); // Draw the area under the lines. This makes this diagram an area chart. attr.setDisplayArea(true); kdAreaDiagram->setLineAttributes(attr); kdAreaDiagram->setPen(QPen(Qt::black, 0.0)); // KD Chart by default draws the first data set as last line in a normal // line diagram, we however want the first series to appear in front. kdAreaDiagram->setReverseDatasetOrder(true); kdAreaDiagram->setAllowOverlappingDataValueTexts(showOverlappingDataLabels); if (plotAreaChartSubType == StackedChartSubtype) kdAreaDiagram->setType(KChart::LineDiagram::Stacked); else if (plotAreaChartSubType == PercentChartSubtype) { kdAreaDiagram->setType(KChart::LineDiagram::Percent); kdAreaDiagram->setUnitSuffix("%", Qt::Vertical); } if (isVisible) { kdAreaDiagram->addAxis(kdAxis); q->registerDiagram(kdAreaDiagram); } kdPlane->addDiagram(kdAreaDiagram); Q_ASSERT(plotArea); foreach (Axis *axis, plotArea->axes()) { if (axis->isVisible() && axis->dimension() == XAxisDimension) { kdAreaDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(kdAreaDiagram); } } // Propagate existing settings KChart::ThreeDLineAttributes attributes(kdAreaDiagram->threeDLineAttributes()); attributes.setEnabled(plotArea->isThreeD()); attributes.setThreeDBrushEnabled(plotArea->isThreeD()); kdAreaDiagram->setThreeDLineAttributes(attributes); plotArea->parent()->legend()->kdLegend()->addDiagram(kdAreaDiagram); } void Axis::Private::createCircleDiagram() { Q_ASSERT(kdCircleDiagram == 0); kdCircleDiagram = new KChart::PieDiagram(plotArea->kdChart(), kdPolarPlane); KChartModel *model = new KChartModel(plotArea, kdCircleDiagram); kdCircleDiagram->setModel(model); registerDiagram(kdCircleDiagram); model->setDataDirection(Qt::Horizontal); plotArea->parent()->legend()->kdLegend()->addDiagram(kdCircleDiagram); kdPolarPlane->addDiagram(kdCircleDiagram); // Propagate existing settings KChart::ThreeDPieAttributes attributes(kdCircleDiagram->threeDPieAttributes()); attributes.setEnabled(plotArea->isThreeD()); attributes.setThreeDBrushEnabled(plotArea->isThreeD()); kdCircleDiagram->setThreeDPieAttributes(attributes); // Initialize with default values that are specified in PlotArea // Note: KChart takes an int here, though ODF defines the offset to be a double. kdPolarPlane->setStartPosition((int)plotArea->angleOffset()); } void Axis::Private::createRingDiagram() { Q_ASSERT(kdRingDiagram == 0); kdRingDiagram = new KChart::RingDiagram(plotArea->kdChart(), kdPolarPlane); KChartModel *model = new KChartModel(plotArea, kdRingDiagram); kdRingDiagram->setModel(model); registerDiagram(kdRingDiagram); model->setDataDirection(Qt::Horizontal); plotArea->parent()->legend()->kdLegend()->addDiagram(kdRingDiagram); kdPolarPlane->addDiagram(kdRingDiagram); // Propagate existing settings KChart::ThreeDPieAttributes attributes(kdRingDiagram->threeDPieAttributes()); attributes.setEnabled(plotArea->isThreeD()); attributes.setThreeDBrushEnabled(plotArea->isThreeD()); kdRingDiagram->setThreeDPieAttributes(attributes); // Initialize with default values that are specified in PlotArea // Note: KChart takes an int here, though ODF defines the offset to be a double. kdPolarPlane->setStartPosition((int)plotArea->angleOffset()); } void Axis::Private::createRadarDiagram(bool filled) { Q_ASSERT(kdRadarDiagram == 0); //kdRadarDiagramModel->setDataDimensions(2); //kdRadarDiagramModel->setDataDirection(Qt::Horizontal); kdRadarDiagram = new KChart::RadarDiagram(plotArea->kdChart(), kdRadarPlane); KChartModel *model = new KChartModel(plotArea, kdRadarDiagram); kdRadarDiagram->setModel(model); registerDiagram(kdRadarDiagram); kdRadarDiagram->setCloseDatasets(true); if (filled) { // Don't use a solid fill of 1.0 but a more transparent one so the // grid and the data-value-labels are still visible plus it provides // a better look (other areas can still be seen) even if it's slightly // different from what OO.org does. kdRadarDiagram->setFillAlpha(0.4); } #if 0 // Stacked and Percent not supported by KChart. if (plotAreaChartSubType == StackedChartSubtype) kdRadarDiagram->setType(KChart::PolarDiagram::Stacked); else if (plotAreaChartSubType == PercentChartSubtype) kdRadarDiagram->setType(KChart::PolarDiagram::Percent); #endif plotArea->parent()->legend()->kdLegend()->addDiagram(kdRadarDiagram); kdRadarPlane->addDiagram(kdRadarDiagram); } void Axis::Private::createScatterDiagram() { Q_ASSERT(kdScatterDiagram == 0); Q_ASSERT(plotArea); kdScatterDiagram = new KChart::Plotter(plotArea->kdChart(), kdPlane); KChartModel *model = new KChartModel(plotArea, kdScatterDiagram); kdScatterDiagram->setModel(model); registerDiagram(kdScatterDiagram); model->setDataDimensions(2); kdScatterDiagram->setPen(Qt::NoPen); if (isVisible) { kdScatterDiagram->addAxis(kdAxis); q->registerDiagram(kdScatterDiagram); } kdPlane->addDiagram(kdScatterDiagram); foreach (Axis *axis, plotArea->axes()) { if (axis->isVisible() && axis->dimension() == XAxisDimension) { kdScatterDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(kdScatterDiagram); } } // Propagate existing settings KChart::ThreeDLineAttributes attributes(kdScatterDiagram->threeDLineAttributes()); attributes.setEnabled(plotArea->isThreeD()); attributes.setThreeDBrushEnabled(plotArea->isThreeD()); kdScatterDiagram->setThreeDLineAttributes(attributes); plotArea->parent()->legend()->kdLegend()->addDiagram(kdScatterDiagram); } void Axis::Private::createStockDiagram() { Q_ASSERT(kdStockDiagram == 0); kdStockDiagram = new KChart::StockDiagram(plotArea->kdChart(), kdPlane); KChartModel *model = new KChartModel(plotArea, kdStockDiagram); kdStockDiagram->setModel(model); switch (plotAreaChartSubType) { case HighLowCloseChartSubtype: kdStockDiagram->setType(KChart::StockDiagram::HighLowClose); break; case OpenHighLowCloseChartSubtype: kdStockDiagram->setType(KChart::StockDiagram::OpenHighLowClose); break; case CandlestickChartSubtype: kdStockDiagram->setType(KChart::StockDiagram::Candlestick); break; } registerDiagram(kdStockDiagram); model->setDataDimensions(numDimensions(StockChartType)); #if 0 // Stacked and Percent not supported by KChart. if (plotAreaChartSubType == StackedChartSubtype) kdStockDiagram->setType(KChart::StockDiagram::Stacked); else if (plotAreaChartSubType == PercentChartSubtype) kdStockDiagram->setType(KChart::StockDiagram::Percent); #endif if (isVisible) { kdStockDiagram->addAxis(kdAxis); q->registerDiagram(kdStockDiagram); } kdPlane->addDiagram(kdStockDiagram); Q_ASSERT(plotArea); foreach (Axis *axis, plotArea->axes()) { if (axis->isVisible() && axis->dimension() == XAxisDimension) { kdStockDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(kdStockDiagram); } } plotArea->parent()->legend()->kdLegend()->addDiagram(kdStockDiagram); q->updateKChartStockAttributes(); } void Axis::Private::createBubbleDiagram() { Q_ASSERT(kdBubbleDiagram == 0); Q_ASSERT(plotArea); kdBubbleDiagram = new KChart::Plotter(plotArea->kdChart(), kdPlane); KChartModel *model = new KChartModel(plotArea, kdBubbleDiagram); kdBubbleDiagram->setModel(model); registerDiagram(kdBubbleDiagram); model->setDataDimensions(2); kdPlane->addDiagram(kdBubbleDiagram); foreach (Axis *axis, plotArea->axes()) { //if (axis->dimension() == XAxisDimension) if (axis->isVisible() && axis->dimension() == XAxisDimension) { kdBubbleDiagram->addAxis(axis->kdAxis()); q->registerDiagram(kdBubbleDiagram); } } // disable the connecting line KChart::LineAttributes la = kdBubbleDiagram->lineAttributes(); la.setVisible(false); kdBubbleDiagram->setLineAttributes(la); plotArea->parent()->legend()->kdLegend()->addDiagram(kdBubbleDiagram); } void Axis::Private::createSurfaceDiagram() { Q_ASSERT(!kdSurfaceDiagram); // This is a so far a by KChart unsupported chart type. // Fall back to bar diagram for now. kdSurfaceDiagram = new KChart::BarDiagram(plotArea->kdChart(), kdPlane); KChartModel *model = new KChartModel(plotArea, kdSurfaceDiagram); kdSurfaceDiagram->setModel(model); registerDiagram(kdSurfaceDiagram); plotArea->parent()->legend()->kdLegend()->addDiagram(kdSurfaceDiagram); kdPlane->addDiagram(kdSurfaceDiagram); } void Axis::Private::createGanttDiagram() { // This is a so far a by KChart unsupported chart type (through KDGantt got merged into KChart with 2.3) Q_ASSERT(!kdGanttDiagram); // This is a so far a by KChart unsupported chart type. // Fall back to bar diagram for now. kdGanttDiagram = new KChart::BarDiagram(plotArea->kdChart(), kdPlane); KChartModel *model = new KChartModel(plotArea, kdGanttDiagram); kdGanttDiagram->setModel(model); registerDiagram(kdGanttDiagram); plotArea->parent()->legend()->kdLegend()->addDiagram(kdGanttDiagram); kdPlane->addDiagram(kdGanttDiagram); } /** * Automatically adjusts the diagram so that all currently displayed * diagram types fit together. */ void Axis::Private::adjustAllDiagrams() { // If at least one dataset is attached that belongs to a // horizontal bar chart, set centerDataPoints to true. centerDataPoints = kdBarDiagram != 0; if (kdLineDiagram) kdLineDiagram->setCenterDataPoints(centerDataPoints); if (kdAreaDiagram) kdAreaDiagram->setCenterDataPoints(centerDataPoints); } // ================================================================ // class Axis // FIXME: make it possible to create an axis without parent, and // when it is removed, actually remove it from parent (signals and all) Axis::Axis(PlotArea *parent, AxisDimension dimension) : d(new Private(this, dimension)) { Q_ASSERT(parent); parent->addAxis(this); d->plotArea = parent; d->kdAxis->setObjectName(name()); KChart::BackgroundAttributes batt(d->kdAxis->backgroundAttributes()); batt.setBrush(QBrush(Qt::white)); d->kdAxis->setBackgroundAttributes(batt); setFontSize(8.0); // also sets MeasureCalculationModeAbsolute d->kdPlane = parent->kdCartesianPlane(this); d->kdPolarPlane = parent->kdPolarPlane(); d->kdRadarPlane = parent->kdRadarPlane(); d->plotAreaChartType = d->plotArea->chartType(); d->plotAreaChartSubType = d->plotArea->chartSubType(); setOdfAxisPosition("start"); setOdfAxisLabelsPosition("near-axis"); KoShapeFactoryBase *textShapeFactory = KoShapeRegistry::instance()->value(TextShapeId); if (textShapeFactory) d->title = textShapeFactory->createDefaultShape(parent->parent()->resourceManager()); if (d->title) { d->titleData = qobject_cast(d->title->userData()); if (d->titleData == 0) { d->titleData = new TextLabelData; d->title->setUserData(d->titleData); } QFont font = d->titleData->document()->defaultFont(); font.setPointSizeF(9); d->titleData->document()->setDefaultFont(font); } else { d->title = new TextLabelDummy; d->titleData = new TextLabelData; KoTextDocumentLayout *documentLayout = new KoTextDocumentLayout(d->titleData->document()); d->titleData->document()->setDocumentLayout(documentLayout); d->title->setUserData(d->titleData); } d->title->setSize(QSizeF(CM_TO_POINT(3), CM_TO_POINT(0.75))); d->plotArea->parent()->addShape(d->title); d->plotArea->parent()->setClipped(d->title, true); d->plotArea->parent()->setInheritsTransform(d->title, true); d->title->setDeletable(false); d->title->setZIndex(5); d->title->setToolDelegates(QSet()<parent()<title); // Enable chart tool d->titleData->setResizeMethod(KoTextShapeDataBase::AutoResize); d->title->setAdditionalStyleAttribute("chart:auto-position", "true"); d->title->setAllowedInteraction(KoShape::ShearingAllowed, false); d->title->setVisible(false); // Needed to avoid problems when creating secondary axes (Axis creation needs review/refactoring) connect(d->plotArea, SIGNAL(angleOffsetChanged(qreal)), this, SLOT(setAngleOffset(qreal))); connect(d->plotArea, SIGNAL(holeSizeChanged(qreal)), this, SLOT(setHoleSize(qreal))); d->updatePosition(); } Axis::~Axis() { Q_ASSERT(d->plotArea); d->plotArea->parent()->KoShapeContainer::removeShape(d->title); Q_ASSERT(d->title); delete d->title; delete d; } PlotArea* Axis::plotArea() const { return d->plotArea; } KoShape *Axis::title() const { return d->title; } QString Axis::titleText() const { return d->titleData->document()->toPlainText(); } bool Axis::showLabels() const { return d->showLabels; } bool Axis::showOverlappingDataLabels() const { return d->showOverlappingDataLabels; } QString Axis::id() const { return d->id; } AxisDimension Axis::dimension() const { return d->dimension; } QList Axis::dataSets() const { return d->dataSets; } bool Axis::attachDataSet(DataSet *dataSet) { Q_ASSERT(!d->dataSets.contains(dataSet)); if (d->dataSets.contains(dataSet)) return false; d->dataSets.append(dataSet); if (dimension() == YAxisDimension) { dataSet->setAttachedAxis(this); ChartType chartType = dataSet->chartType(); if (chartType == LastChartType) chartType = d->plotAreaChartType; KChart::AbstractDiagram *diagram = d->getDiagramAndCreateIfNeeded(chartType); Q_ASSERT(diagram); KChartModel *model = dynamic_cast(diagram->model()); Q_ASSERT(model); model->addDataSet(dataSet); layoutPlanes(); requestRepaint(); } return true; } bool Axis::detachDataSet(DataSet *dataSet, bool silent) { Q_ASSERT(d->dataSets.contains(dataSet)); if (!d->dataSets.contains(dataSet)) return false; d->dataSets.removeAll(dataSet); if (dimension() == YAxisDimension) { ChartType chartType = dataSet->chartType(); if (chartType == LastChartType) chartType = d->plotAreaChartType; KChart::AbstractDiagram *oldDiagram = d->getDiagram(chartType); Q_ASSERT(oldDiagram); KChartModel *oldModel = dynamic_cast(oldDiagram->model()); Q_ASSERT(oldModel); const int rowCount = oldModel->dataDirection() == Qt::Vertical ? oldModel->columnCount() : oldModel->rowCount(); // If there's only as many rows as needed for *one* // dataset, that means that the dataset we're removing is // the last one in the model --> delete model if (rowCount == oldModel->dataDimensions()) d->deleteDiagram(chartType); else oldModel->removeDataSet(dataSet, silent); dataSet->setKdChartModel(0); dataSet->setAttachedAxis(0); if (!silent) { layoutPlanes(); requestRepaint(); } } return true; } void Axis::clearDataSets() { QList list = d->dataSets; foreach(DataSet *dataSet, list) detachDataSet(dataSet, true); } bool Axis::showRuler() const { return d->kdAxis->rulerAttributes().showRulerLine(); } void Axis::setShowRuler(bool show) { KChart::RulerAttributes attr = d->kdAxis->rulerAttributes(); attr.setShowRulerLine(!attr.showRulerLine()); d->kdAxis->setRulerAttributes(attr); } qreal Axis::majorInterval() const { return d->majorInterval; } void Axis::setMajorInterval(qreal interval) { // Don't overwrite if automatic interval is being requested (for // interval = 0) if (interval != 0.0) { d->majorInterval = interval; d->useAutomaticMajorInterval = false; } else d->useAutomaticMajorInterval = true; // KChart KChart::GridAttributes attributes = d->kdPlane->gridAttributes(orientation()); attributes.setGridStepWidth(interval); d->kdPlane->setGridAttributes(orientation(), attributes); attributes = d->kdPolarPlane->gridAttributes(true); attributes.setGridStepWidth(interval); d->kdPolarPlane->setGridAttributes(true, attributes); // FIXME: Hide minor tick marks more appropriately if (!d->showMinorGrid && interval != 0.0) setMinorInterval(interval); requestRepaint(); } qreal Axis::minorInterval() const { return (d->majorInterval / (qreal)d->minorIntervalDivisor); } void Axis::setMinorInterval(qreal interval) { if (interval == 0.0) setMinorIntervalDivisor(0); else setMinorIntervalDivisor(int(qRound(d->majorInterval / interval))); } int Axis::minorIntervalDivisor() const { return d->minorIntervalDivisor; } void Axis::setMinorIntervalDivisor(int divisor) { // A divisor of 0.0 means automatic minor interval calculation if (divisor != 0) { d->minorIntervalDivisor = divisor; d->useAutomaticMinorInterval = false; } else d->useAutomaticMinorInterval = true; // KChart KChart::GridAttributes attributes = d->kdPlane->gridAttributes(orientation()); attributes.setGridSubStepWidth((divisor != 0) ? (d->majorInterval / divisor) : 0.0); d->kdPlane->setGridAttributes(orientation(), attributes); attributes = d->kdPolarPlane->gridAttributes(true); attributes.setGridSubStepWidth((divisor != 0) ? (d->majorInterval / divisor) : 0.0); d->kdPolarPlane->setGridAttributes(true, attributes); requestRepaint(); } bool Axis::useAutomaticMajorInterval() const { return d->useAutomaticMajorInterval; } bool Axis::useAutomaticMinorInterval() const { return d->useAutomaticMinorInterval; } void Axis::setUseAutomaticMajorInterval(bool automatic) { d->useAutomaticMajorInterval = automatic; // A value of 0.0 will activate automatic intervals, // but not change d->majorInterval setMajorInterval(automatic ? 0.0 : majorInterval()); } void Axis::setUseAutomaticMinorInterval(bool automatic) { d->useAutomaticMinorInterval = automatic; // A value of 0.0 will activate automatic intervals, // but not change d->minorIntervalDivisor setMinorInterval(automatic ? 0.0 : minorInterval()); } bool Axis::showInnerMinorTicks() const { return d->showInnerMinorTicks; } bool Axis::showOuterMinorTicks() const { return d->showOuterMinorTicks; } bool Axis::showInnerMajorTicks() const { return d->showInnerMinorTicks; } bool Axis::showOuterMajorTicks() const { return d->showOuterMajorTicks; } void Axis::setShowInnerMinorTicks(bool showTicks) { d->showInnerMinorTicks = showTicks; KChart::RulerAttributes attr = kdAxis()->rulerAttributes(); attr.setShowMinorTickMarks(d->showInnerMinorTicks || d->showOuterMinorTicks); kdAxis()->setRulerAttributes(attr); } void Axis::setShowOuterMinorTicks(bool showTicks) { d->showOuterMinorTicks = showTicks; KChart::RulerAttributes attr = kdAxis()->rulerAttributes(); attr.setShowMinorTickMarks(d->showInnerMinorTicks || d->showOuterMinorTicks); kdAxis()->setRulerAttributes(attr); } void Axis::setShowInnerMajorTicks(bool showTicks) { d->showInnerMajorTicks = showTicks; KChart::RulerAttributes attr = kdAxis()->rulerAttributes(); attr.setShowMajorTickMarks(d->showInnerMajorTicks || d->showOuterMajorTicks); kdAxis()->setRulerAttributes(attr); } void Axis::setShowOuterMajorTicks(bool showTicks) { d->showOuterMajorTicks = showTicks; KChart::RulerAttributes attr = kdAxis()->rulerAttributes(); attr.setShowMajorTickMarks(d->showInnerMajorTicks || d->showOuterMajorTicks); kdAxis()->setRulerAttributes(attr); } void Axis::setScalingLogarithmic(bool logarithmicScaling) { d->logarithmicScaling = logarithmicScaling; if (dimension() != YAxisDimension) return; d->kdPlane->setAxesCalcModeY(d->logarithmicScaling ? KChart::AbstractCoordinatePlane::Logarithmic : KChart::AbstractCoordinatePlane::Linear); d->kdPlane->layoutPlanes(); requestRepaint(); } bool Axis::scalingIsLogarithmic() const { return d->logarithmicScaling; } bool Axis::showMajorGrid() const { return d->showMajorGrid; } void Axis::setShowMajorGrid(bool showGrid) { d->showMajorGrid = showGrid; // KChart KChart::GridAttributes attributes = d->kdPlane->gridAttributes(orientation()); attributes.setGridVisible(d->showMajorGrid); d->kdPlane->setGridAttributes(orientation(), attributes); attributes = d->kdPolarPlane->gridAttributes(true); attributes.setGridVisible(d->showMajorGrid); d->kdPolarPlane->setGridAttributes(true, attributes); requestRepaint(); } bool Axis::showMinorGrid() const { return d->showMinorGrid; } void Axis::setShowMinorGrid(bool showGrid) { d->showMinorGrid = showGrid; // KChart KChart::GridAttributes attributes = d->kdPlane->gridAttributes(orientation()); attributes.setSubGridVisible(d->showMinorGrid); d->kdPlane->setGridAttributes(orientation(), attributes); attributes = d->kdPolarPlane->gridAttributes(true); attributes.setSubGridVisible(d->showMinorGrid); d->kdPolarPlane->setGridAttributes(true, attributes); requestRepaint(); } void Axis::setTitleText(const QString &text) { d->titleData->document()->setPlainText(text); } void Axis::setShowLabels(bool show) { d->showLabels = show; KChart::TextAttributes textAttr = d->kdAxis->textAttributes(); textAttr.setVisible(show); d->kdAxis->setTextAttributes(textAttr); } void Axis::setShowOverlappingDataLabels(bool show) { d->showOverlappingDataLabels = show; } Qt::Orientation Axis::orientation() const { bool chartIsVertical = d->plotArea->isVertical(); bool horizontal = d->dimension == (chartIsVertical ? YAxisDimension : XAxisDimension); return horizontal ? Qt::Horizontal : Qt::Vertical; } bool Axis::loadOdf(const KoXmlElement &axisElement, KoShapeLoadingContext &context) { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); KoOdfStylesReader &stylesReader = context.odfLoadingContext().stylesReader(); OdfLoadingHelper *helper = (OdfLoadingHelper*)context.sharedData(OdfLoadingHelperId); bool reverseAxis = false; d->title->setVisible(false); QPen gridPen(Qt::NoPen); QPen subGridPen(Qt::NoPen); d->showMajorGrid = false; d->showMinorGrid = false; d->showInnerMinorTicks = false; d->showOuterMinorTicks = false; d->showInnerMajorTicks = false; d->showOuterMajorTicks = true; // Use automatic interval calculation by default setMajorInterval(0.0); setMinorInterval(0.0); if (!axisElement.isNull()) { QString styleName = axisElement.attributeNS(KoXmlNS::chart, "style-name", QString()); const KoXmlElement *stylElement = stylesReader.findStyle(styleName, "chart"); if (stylElement) { const QString dataStyleName = stylElement->attributeNS(KoXmlNS::style, "data-style-name", QString()); if (!dataStyleName.isEmpty() && stylesReader.dataFormats().contains(dataStyleName)) { delete d->numericStyleFormat; d->numericStyleFormat = new KoOdfNumberStyles::NumericStyleFormat(stylesReader.dataFormats()[dataStyleName].first); } } KoXmlElement n; forEachElement (n, axisElement) { if (n.namespaceURI() != KoXmlNS::chart) continue; if (n.localName() == "title") { OdfHelper::loadOdfTitle(d->title, n, context); // title shall *always* have AutoResize d->titleData->setResizeMethod(KoTextShapeDataBase::AutoResize); } else if (n.localName() == "grid") { bool major = false; if (n.hasAttributeNS(KoXmlNS::chart, "class")) { const QString className = n.attributeNS(KoXmlNS::chart, "class"); if (className == "major") major = true; } else { warnChart << "Error: Axis' element contains no valid class. It must be either \"major\" or \"minor\"."; continue; } if (major) { d->showMajorGrid = true; } else { d->showMinorGrid = true; } if (n.hasAttributeNS(KoXmlNS::chart, "style-name")) { styleStack.clear(); context.odfLoadingContext().fillStyleStack(n, KoXmlNS::style, "style-name", "chart"); styleStack.setTypeProperties("graphic"); if (styleStack.hasProperty(KoXmlNS::svg, "stroke-color")) { const QString strokeColor = styleStack.property(KoXmlNS::svg, "stroke-color"); //d->showMajorGrid = true; if (major) gridPen = QPen(QColor(strokeColor)); else subGridPen = QPen(QColor(strokeColor)); } } } else if (n.localName() == "categories") { if (n.hasAttributeNS(KoXmlNS::table, "cell-range-address")) { const CellRegion region = CellRegion(helper->tableSource, n.attributeNS(KoXmlNS::table, "cell-range-address")); helper->categoryRegionSpecifiedInXAxis = true; plotArea()->proxyModel()->setCategoryDataRegion(region); } } } if (axisElement.hasAttributeNS(KoXmlNS::chart, "axis-name")) { const QString name = axisElement.attributeNS(KoXmlNS::chart, "name", QString()); setName(name); } // NOTE: chart:dimension already handled by PlotArea before and passed // explicitly in the constructor. } if (axisElement.hasAttributeNS(KoXmlNS::chart, "style-name")) { styleStack.clear(); context.odfLoadingContext().fillStyleStack(axisElement, KoXmlNS::chart, "style-name", "chart"); KoCharacterStyle charStyle; charStyle.loadOdf(&axisElement, context); setFont(charStyle.font()); styleStack.setTypeProperties("chart"); if (styleStack.hasProperty(KoXmlNS::chart, "logarithmic") && styleStack.property(KoXmlNS::chart, "logarithmic") == "true") { setScalingLogarithmic(true); } if (styleStack.hasProperty(KoXmlNS::chart, "reverse-direction") && styleStack.property(KoXmlNS::chart, "reverse-direction") == "true") { reverseAxis = true; } if (styleStack.hasProperty(KoXmlNS::chart, "interval-major")) setMajorInterval(KoUnit::parseValue(styleStack.property(KoXmlNS::chart, "interval-major"))); if (styleStack.hasProperty(KoXmlNS::chart, "interval-minor-divisor")) setMinorIntervalDivisor(KoUnit::parseValue(styleStack.property(KoXmlNS::chart, "interval-minor-divisor"))); else if (styleStack.hasProperty(KoXmlNS::chart, "interval-minor")) setMinorInterval(KoUnit::parseValue(styleStack.property(KoXmlNS::chart, "interval-minor"))); if (styleStack.hasProperty(KoXmlNS::chart, "tick-marks-minor-inner")) setShowInnerMinorTicks(styleStack.property(KoXmlNS::chart, "tick-marks-minor-inner") == "true"); if (styleStack.hasProperty(KoXmlNS::chart, "tick-marks-minor-outer")) setShowOuterMinorTicks(styleStack.property(KoXmlNS::chart, "tick-marks-minor-outer") == "true"); if (styleStack.hasProperty(KoXmlNS::chart, "tick-marks-major-inner")) setShowInnerMajorTicks(styleStack.property(KoXmlNS::chart, "tick-marks-major-inner") == "true"); if (styleStack.hasProperty(KoXmlNS::chart, "tick-marks-major-outer")) setShowOuterMajorTicks(styleStack.property(KoXmlNS::chart, "tick-marks-major-outer") == "true"); if (styleStack.hasProperty(KoXmlNS::chart, "display-label")) setShowLabels(styleStack.property(KoXmlNS::chart, "display-label") != "false"); if (styleStack.hasProperty(KoXmlNS::chart, "text-overlap")) setShowOverlappingDataLabels(styleStack.property(KoXmlNS::chart, "text-overlap") != "false"); if (styleStack.hasProperty(KoXmlNS::chart, "visible")) setVisible(styleStack.property(KoXmlNS::chart, "visible") != "false"); if (styleStack.hasProperty(KoXmlNS::chart, "minimum")) { const qreal minimum = styleStack.property(KoXmlNS::chart, "minimum").toDouble(); const qreal maximum = orientation() == Qt::Vertical ? d->kdPlane->verticalRange().second : d->kdPlane->horizontalRange().second; if (orientation() == Qt::Vertical) d->kdPlane->setVerticalRange(qMakePair(minimum, maximum)); else d->kdPlane->setHorizontalRange(qMakePair(minimum, maximum)); d->useAutomaticMinimumRange = false; } if (styleStack.hasProperty(KoXmlNS::chart, "maximum")) { const qreal minimum = orientation() == Qt::Vertical ? d->kdPlane->verticalRange().first : d->kdPlane->horizontalRange().first; const qreal maximum = styleStack.property(KoXmlNS::chart, "maximum").toDouble(); if (orientation() == Qt::Vertical) d->kdPlane->setVerticalRange(qMakePair(minimum, maximum)); else d->kdPlane->setHorizontalRange(qMakePair(minimum, maximum)); d->useAutomaticMaximumRange = false; } /*if (styleStack.hasProperty(KoXmlNS::chart, "origin")) { const qreal origin = KoUnit::parseValue(styleStack.property(KoXmlNS::chart, "origin")); }*/ + styleStack.setTypeProperties("chart"); + if (styleStack.hasProperty(KoXmlNS::chart, "axis-position")) { + d->axisPosition = styleStack.property(KoXmlNS::chart, "axis-position"); + } + if (styleStack.hasProperty(KoXmlNS::chart, "axis-label-position")) { + d->axisLabelsPosition = styleStack.property(KoXmlNS::chart, "axis-label-position"); + } styleStack.setTypeProperties("text"); if (styleStack.hasProperty(KoXmlNS::fo, "font-size")) { const qreal fontSize = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "font-size")); setFontSize(fontSize); } if (styleStack.hasProperty(KoXmlNS::fo, "font-color")) { QString fontColorString = styleStack.property(KoXmlNS::fo, "font-color"); QColor color(fontColorString); if (color.isValid()) { KChart::TextAttributes tatt = kdAxis()->textAttributes(); QPen pen = tatt.pen(); pen.setColor(color); tatt.setPen(pen); kdAxis()->setTextAttributes(tatt); } } if (styleStack.hasProperty(KoXmlNS::fo, "font-family")) { QString fontFamilyString = styleStack.property(KoXmlNS::fo, "font-family"); if (!fontFamilyString.isEmpty()) { QFont f = this->font(); f.setFamily(fontFamilyString); setFont(f); } } if (styleStack.hasProperty(KoXmlNS::fo, "font-style")) { QString fontStyle = styleStack.property(KoXmlNS::fo, "font-style"); if (fontStyle == "italic") { QFont f = this->font(); f.setItalic(true); setFont(f); } else if (fontStyle == "oblique") { // TODO } } if (styleStack.hasProperty(KoXmlNS::fo, "font-weight")) { QString fontWeight = styleStack.property(KoXmlNS::fo, "font-weight"); //fo:font-weight attribute are normal, bold, 100, 200, 300, 400, 500, 600, 700, 800 or 900. if (fontWeight == "bold") { QFont f = this->font(); f.setBold(true); setFont(f); } else { // TODO } } - if (styleStack.hasProperty(KoXmlNS::chart, "axis-position")) { - d->axisPosition = styleStack.property(KoXmlNS::chart, "axis-position"); - } - if (styleStack.hasProperty(KoXmlNS::chart, "axis-label-position")) { - d->axisLabelsPosition = styleStack.property(KoXmlNS::chart, "axis-label-position"); - } } else { setShowLabels(KoOdfWorkaround::fixMissingStyle_DisplayLabel(axisElement, context)); } KChart::GridAttributes gridAttr = d->kdPlane->gridAttributes(orientation()); gridAttr.setGridVisible(d->showMajorGrid); gridAttr.setSubGridVisible(d->showMinorGrid); if (gridPen.style() != Qt::NoPen) gridAttr.setGridPen(gridPen); if (subGridPen.style() != Qt::NoPen) gridAttr.setSubGridPen(subGridPen); d->kdPlane->setGridAttributes(orientation(), gridAttr); gridAttr = d->kdPolarPlane->gridAttributes(orientation()); gridAttr.setGridVisible(d->showMajorGrid); gridAttr.setSubGridVisible(d->showMinorGrid); if (gridPen.style() != Qt::NoPen) gridAttr.setGridPen(gridPen); if (subGridPen.style() != Qt::NoPen) gridAttr.setSubGridPen(subGridPen); // if (plotArea()->chartType() == RadarChartType || plotArea()->chartType() == FilledRadarChartType) // d->kdPolarPlane->setGridAttributes(false, gridAttr); // else d->kdPolarPlane->setGridAttributes(true, gridAttr); gridAttr = d->kdRadarPlane->globalGridAttributes(); gridAttr.setGridVisible(d->showMajorGrid); gridAttr.setSubGridVisible(d->showMinorGrid); if (gridPen.style() != Qt::NoPen) gridAttr.setGridPen(gridPen); if (subGridPen.style() != Qt::NoPen) gridAttr.setSubGridPen(subGridPen); d->kdRadarPlane->setGlobalGridAttributes(gridAttr); KChart::TextAttributes ta(d->kdRadarPlane->textAttributes()); ta.setVisible(helper->categoryRegionSpecifiedInXAxis); ta.setFont(font()); ta.setFontSize(50); d->kdRadarPlane->setTextAttributes(ta); if (reverseAxis) { KChart::CartesianCoordinatePlane *plane = dynamic_cast(kdPlane()); if (plane) { if (orientation() == Qt::Horizontal) plane->setHorizontalRangeReversed(reverseAxis); else // Qt::Vertical plane->setVerticalRangeReversed(reverseAxis); } } // Style of axis is still in styleStack if (!loadOdfChartSubtypeProperties(axisElement, context)) { return false; } if (titleText().isEmpty()) { // do not allow visible empty text d->title->setVisible(false); } requestRepaint(); return true; } bool Axis::loadOdfChartSubtypeProperties(const KoXmlElement &axisElement, KoShapeLoadingContext &context) { Q_UNUSED(axisElement); KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.setTypeProperties("chart"); // Load these attributes regardless of the actual chart type. They'll have // no effect if their respective chart type is not in use. // However, they'll be saved back to ODF that way. if (styleStack.hasProperty(KoXmlNS::chart, "gap-width")) setGapBetweenSets(KoUnit::parseValue(styleStack.property(KoXmlNS::chart, "gap-width"))); if (styleStack.hasProperty(KoXmlNS::chart, "overlap")) // The minus is intended! setGapBetweenBars(-KoUnit::parseValue(styleStack.property(KoXmlNS::chart, "overlap"))); return true; } void Axis::setName(const QString &name) { d->name = name; } // NOTE: only used during save/load to enable attaching axis to datasets QString Axis::name() const { if (!d->name.isEmpty()) { return d->name; } QString name; switch(dimension()) { case XAxisDimension: name = QLatin1Char('x'); break; case YAxisDimension: name = QLatin1Char('y'); break; case ZAxisDimension: name = QLatin1Char('z'); break; } int i = 1; foreach (Axis *axis, d->plotArea->axes()) { if (axis == this) break; if (axis->dimension() == dimension()) i++; } if (i == 1) name = "primary-" + name; else if (i == 2) name = "secondary-" + name; // Usually, there's not more than two axes of the same dimension. // But use a fallback name here nevertheless. else name = QString::number(i) + '-' + name; return name; } void Axis::saveOdf(KoShapeSavingContext &context) { KoXmlWriter &bodyWriter = context.xmlWriter(); KoGenStyles &mainStyles = context.mainStyles(); bodyWriter.startElement("chart:axis"); KoGenStyle axisStyle(KoGenStyle::ChartAutoStyle, "chart"); axisStyle.addProperty("chart:logarithmic", scalingIsLogarithmic()); axisStyle.addProperty("chart:reverse-direction", axisDirectionReversed()); if (!d->axisPosition.isEmpty()) { axisStyle.addProperty("chart:axis-position", d->axisPosition); } if (!d->axisLabelsPosition.isEmpty()) { axisStyle.addProperty("chart:axis-label-position", d->axisLabelsPosition); } axisStyle.addProperty("chart:tick-marks-minor-inner", showInnerMinorTicks()); axisStyle.addProperty("chart:tick-marks-minor-outer", showOuterMinorTicks()); axisStyle.addProperty("chart:tick-marks-major-inner", showInnerMajorTicks()); axisStyle.addProperty("chart:tick-marks-major-outer", showOuterMajorTicks()); axisStyle.addProperty("chart:display-label", showLabels()); axisStyle.addProperty("chart:text-overlap", showOverlappingDataLabels()); axisStyle.addProperty("chart:visible", isVisible()); if (dimension() == YAxisDimension) { axisStyle.addProperty("chart:gap-width", d->gapBetweenSets); axisStyle.addProperty("chart:overlap", -d->gapBetweenBars); } if (!d->useAutomaticMinimumRange) { const qreal minimum = orientation() == Qt::Vertical ? d->kdPlane->verticalRange().first : d->kdPlane->horizontalRange().first; axisStyle.addProperty("chart:minimum", (int)minimum); } if (!d->useAutomaticMaximumRange) { const qreal maximum = orientation() == Qt::Vertical ? d->kdPlane->verticalRange().second : d->kdPlane->horizontalRange().second; axisStyle.addProperty("chart:maximum", (int)maximum); } //axisStyle.addPropertyPt("chart:origin", origin); KChart::TextAttributes tatt = kdAxis()->textAttributes(); QPen pen = tatt.pen(); axisStyle.addProperty("fo:font-color", pen.color().name(), KoGenStyle::TextType); axisStyle.addProperty("fo:font-family", tatt.font().family(), KoGenStyle::TextType); axisStyle.addPropertyPt("fo:font-size", fontSize(), KoGenStyle::TextType); if (font().bold()) { axisStyle.addProperty("fo:font-weight", "bold" , KoGenStyle::TextType); // TODO support other weights } if (font().italic()) { axisStyle.addProperty("fo:font-style", "italic" , KoGenStyle::TextType); // TODO oblique } const QString styleName = mainStyles.insert(axisStyle, "ch"); bodyWriter.addAttribute("chart:style-name", styleName); // TODO scale: logarithmic/linear // TODO visibility if (dimension() == XAxisDimension) bodyWriter.addAttribute("chart:dimension", "x"); else if (dimension() == YAxisDimension) bodyWriter.addAttribute("chart:dimension", "y"); bodyWriter.addAttribute("chart:name", name()); OdfHelper::saveOdfTitle(d->title, bodyWriter, "chart:title", context); if (plotArea()->proxyModel()->categoryDataRegion().isValid()) { bodyWriter.startElement("chart:categories"); bodyWriter.addAttribute("table:cell-range-address", plotArea()->proxyModel()->categoryDataRegion().toString()); bodyWriter.endElement(); } if (showMajorGrid()) saveOdfGrid(context, OdfMajorGrid); if (showMinorGrid()) saveOdfGrid(context, OdfMinorGrid); bodyWriter.endElement(); // chart:axis } void Axis::saveOdfGrid(KoShapeSavingContext &context, OdfGridClass gridClass) { KoXmlWriter &bodyWriter = context.xmlWriter(); KoGenStyles &mainStyles = context.mainStyles(); KoGenStyle gridStyle(KoGenStyle::GraphicAutoStyle, "chart"); KChart::GridAttributes attributes = d->kdPlane->gridAttributes(orientation()); QPen gridPen = (gridClass == OdfMinorGrid ? attributes.subGridPen() : attributes.gridPen()); KoOdfGraphicStyles::saveOdfStrokeStyle(gridStyle, mainStyles, gridPen); bodyWriter.startElement("chart:grid"); bodyWriter.addAttribute("chart:class", gridClass == OdfMinorGrid ? "minor" : "major"); bodyWriter.addAttribute("chart:style-name", mainStyles.insert(gridStyle, "ch")); bodyWriter.endElement(); // chart:grid } void Axis::update() const { if (d->kdBarDiagram) { d->kdBarDiagram->doItemsLayout(); d->kdBarDiagram->update(); } if (d->kdLineDiagram) { d->kdLineDiagram->doItemsLayout(); d->kdLineDiagram->update(); } if (d->kdStockDiagram) { d->kdStockDiagram->doItemsLayout(); d->kdStockDiagram->update(); } d->plotArea->parent()->requestRepaint(); } KChart::CartesianAxis *Axis::kdAxis() const { return d->kdAxis; } KChart::AbstractCoordinatePlane *Axis::kdPlane() const { return d->kdPlane; } void Axis::plotAreaChartTypeChanged(ChartType newChartType) { if (dimension() != YAxisDimension) return; // Return if there's nothing to do if (newChartType == d->plotAreaChartType) return; if (d->dataSets.isEmpty()) { d->plotAreaChartType = newChartType; return; } //qDebug() << "changed ChartType"; ChartType oldChartType = d->plotAreaChartType; debugChartAxis<"<kdRadarDiagram->setFillAlpha(0); } else if (newChartType == FilledRadarChartType && oldChartType == RadarChartType) { d->kdRadarDiagram->setFillAlpha(0.4); } else { KChart::AbstractDiagram *newDiagram = d->getDiagram(newChartType); if (newDiagram) { debugChartAxis<<"already exists:"<deleteDiagram(newDiagram); } newDiagram = d->getDiagramAndCreateIfNeeded(newChartType); KChartModel *newModel = dynamic_cast(newDiagram->model()); // FIXME: This causes a crash on unimplemented types. We should // handle that in some other way. Q_ASSERT(newModel); foreach (DataSet *dataSet, d->dataSets) { //if (dataSet->chartType() != LastChartType) { dataSet->setChartType(LastChartType); dataSet->setChartSubType(NoChartSubtype); //} } KChart::AbstractDiagram *oldDiagram = d->getDiagram(oldChartType); Q_ASSERT(oldDiagram); // We need to know the old model so that we can remove the data sets // from the old model that we added to the new model. KChartModel *oldModel = dynamic_cast(oldDiagram->model()); Q_ASSERT(oldModel); foreach (DataSet *dataSet, d->dataSets) { if (dataSet->chartType() != LastChartType) { continue; } newModel->addDataSet(dataSet); const int dataSetCount = oldModel->dataDirection() == Qt::Vertical ? oldModel->columnCount() : oldModel->rowCount(); if (dataSetCount == oldModel->dataDimensions()) { d->deleteDiagram(oldChartType); } else { oldModel->removeDataSet(dataSet); } } } d->plotAreaChartType = newChartType; layoutPlanes(); requestRepaint(); } void Axis::plotAreaChartSubTypeChanged(ChartSubtype subType) { d->plotAreaChartSubType = subType; if (d->kdBarDiagram) { d->kdBarDiagram->setUnitSuffix("", d->kdBarDiagram->orientation()); } switch (d->plotAreaChartType) { case BarChartType: if (d->kdBarDiagram) { KChart::BarDiagram::BarType type; switch (subType) { case StackedChartSubtype: type = KChart::BarDiagram::Stacked; break; case PercentChartSubtype: type = KChart::BarDiagram::Percent; d->kdBarDiagram->setUnitSuffix("%", d->kdBarDiagram->orientation()); break; default: type = KChart::BarDiagram::Normal; } d->kdBarDiagram->setType(type); } break; case LineChartType: if (d->kdLineDiagram) { KChart::LineDiagram::LineType type; switch (subType) { case StackedChartSubtype: type = KChart::LineDiagram::Stacked; break; case PercentChartSubtype: type = KChart::LineDiagram::Percent; d->kdLineDiagram->setUnitSuffix("%", Qt::Vertical); break; default: type = KChart::LineDiagram::Normal; } d->kdLineDiagram->setType(type); } break; case AreaChartType: if (d->kdAreaDiagram) { KChart::LineDiagram::LineType type; switch (subType) { case StackedChartSubtype: type = KChart::LineDiagram::Stacked; break; case PercentChartSubtype: type = KChart::LineDiagram::Percent; d->kdAreaDiagram->setUnitSuffix("%", Qt::Vertical); break; default: type = KChart::LineDiagram::Normal; } d->kdAreaDiagram->setType(type); } break; case RadarChartType: case FilledRadarChartType: #if 0 // FIXME: Stacked and Percent not supported by KChart if (d->kdRadarDiagram) { KChart::PolarDiagram::PolarType type; switch (subType) { case StackedChartSubtype: type = KChart::PolarDiagram::Stacked; break; case PercentChartSubtype: type = KChart::PolarDiagram::Percent; break; default: type = KChart::PolarDiagram::Normal; } d->kdRadarDiagram->setType(type); } #endif break; case StockChartType: if (d->kdStockDiagram) { KChart::StockDiagram::Type type; switch (subType) { case CandlestickChartSubtype: type = KChart::StockDiagram::Candlestick; break; case OpenHighLowCloseChartSubtype: type = KChart::StockDiagram::OpenHighLowClose; break; default: type = KChart::StockDiagram::HighLowClose; break; } d->kdStockDiagram->setType(type); } break; default:; // FIXME: Implement more chart types } Q_FOREACH(DataSet* set, d->dataSets) { set->setChartType(d->plotAreaChartType); set->setChartSubType(subType); } } void Axis::plotAreaIsVerticalChanged() { if (d->kdBarDiagram) { d->kdBarDiagram->setOrientation(d->plotArea->isVertical() ? Qt::Horizontal : Qt::Vertical); } updateKChartAxisPosition(); } void Axis::Private::updatePosition() { // // Is the first x or y axis? // bool first = (dimension == XAxisDimension) ? plotArea->xAxis() == q // : plotArea->yAxis() == q; // // Position position; // ItemType type = GenericItemType; // if (q->orientation() == Qt::Horizontal) { // position = first ? BottomPosition : TopPosition; // type = first ? XAxisTitleType : SecondaryXAxisTitleType; // } else { // position = first ? StartPosition : EndPosition; // type = first ? YAxisTitleType : SecondaryYAxisTitleType; // } // // KChart // kdAxis->setPosition(PositionToKChartAxisPosition(position)); // ChartLayout *layout = plotArea->parent()->layout(); // layout->setPosition(title, position, type); // layout->layout(); // // q->requestRepaint(); } void Axis::registerAxis(Axis *axis) { if (d->kdBarDiagram) { d->kdBarDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(d->kdBarDiagram); } if (d->kdLineDiagram) { d->kdLineDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(d->kdLineDiagram); } if (d->kdAreaDiagram) { d->kdAreaDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(d->kdAreaDiagram); } if (d->kdScatterDiagram) { d->kdScatterDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(d->kdScatterDiagram); } if (d->kdStockDiagram) { d->kdStockDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(d->kdStockDiagram); } if (d->kdBubbleDiagram) { d->kdBubbleDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(d->kdBubbleDiagram); } // FIXME: Add all diagrams here } void Axis::registerDiagram(KChart::AbstractCartesianDiagram *diagram) { if (!d->diagrams.contains(diagram)) { d->diagrams << diagram; } } void Axis::Private::restoreDiagrams() { diagrams.removeAll(nullptr); for (KChart::AbstractCartesianDiagram *diag : diagrams) { diag->addAxis(kdAxis); } } void Axis::removeAxisFromDiagrams(bool clear) { // HACK to remove an x-axis from a y-axis diagram d->diagrams.removeAll(nullptr); for (KChart::AbstractCartesianDiagram *diag : d->diagrams) { diag->takeAxis(d->kdAxis); } if (clear) { d->diagrams.clear(); } } void Axis::setThreeD(bool threeD) { // FIXME: Setting KD Chart attributes does not belong here. They should be // determined dynamically somehow. // KChart if (d->kdBarDiagram) { KChart::ThreeDBarAttributes attributes(d->kdBarDiagram->threeDBarAttributes()); attributes.setEnabled(threeD); attributes.setDepth(15.0); attributes.setThreeDBrushEnabled(threeD); d->kdBarDiagram->setThreeDBarAttributes(attributes); } if (d->kdLineDiagram) { KChart::ThreeDLineAttributes attributes(d->kdLineDiagram->threeDLineAttributes()); attributes.setEnabled(threeD); attributes.setDepth(15.0); attributes.setThreeDBrushEnabled(threeD); d->kdLineDiagram->setThreeDLineAttributes(attributes); } if (d->kdAreaDiagram) { KChart::ThreeDLineAttributes attributes(d->kdAreaDiagram->threeDLineAttributes()); attributes.setEnabled(threeD); attributes.setDepth(15.0); attributes.setThreeDBrushEnabled(threeD); d->kdAreaDiagram->setThreeDLineAttributes(attributes); } if (d->kdCircleDiagram) { KChart::ThreeDPieAttributes attributes(d->kdCircleDiagram->threeDPieAttributes()); attributes.setEnabled(threeD); attributes.setDepth(15.0); attributes.setThreeDBrushEnabled(threeD); d->kdCircleDiagram->setThreeDPieAttributes(attributes); } if (d->kdRingDiagram) { KChart::ThreeDPieAttributes attributes(d->kdRingDiagram->threeDPieAttributes()); attributes.setEnabled(threeD); attributes.setDepth(15.0); attributes.setThreeDBrushEnabled(threeD); d->kdRingDiagram->setThreeDPieAttributes(attributes); } // The following types don't support 3D, at least not in KChart: // scatter, radar, stock, bubble, surface, gantt requestRepaint(); } void Axis::requestRepaint() const { d->plotArea->requestRepaint(); } void Axis::layoutPlanes() { d->kdPlane->layoutPlanes(); d->kdPolarPlane->layoutPlanes(); d->kdRadarPlane->layoutPlanes(); } int Axis::gapBetweenBars() const { return d->gapBetweenBars; } void Axis::setGapBetweenBars(int percent) { // This method is also used to override KChart's default attributes. // Do not just return and do nothing if value doesn't differ from stored one. d->gapBetweenBars = percent; if (d->kdBarDiagram) { KChart::BarAttributes attributes = d->kdBarDiagram->barAttributes(); attributes.setBarGapFactor((float)percent / 100.0); d->kdBarDiagram->setBarAttributes(attributes); } requestRepaint(); } int Axis::gapBetweenSets() const { return d->gapBetweenSets; } void Axis::setGapBetweenSets(int percent) { // This method is also used to override KChart's default attributes. // Do not just return and do nothing if value doesn't differ from stored one. d->gapBetweenSets = percent; if (d->kdBarDiagram) { KChart::BarAttributes attributes = d->kdBarDiagram->barAttributes(); attributes.setGroupGapFactor((float)percent / 100.0); d->kdBarDiagram->setBarAttributes(attributes); } requestRepaint(); } void Axis::setAngleOffset(qreal angle) { // only set if we already have a diagram else the value will be picked up on creating the diagram if (d->kdPolarPlane->diagram()) { d->kdPolarPlane->setStartPosition(angle); requestRepaint(); } } void Axis::setHoleSize(qreal value) { //TODO KChart does not support } QFont Axis::font() const { return d->kdAxis->textAttributes().font(); } void Axis::setFont(const QFont &font) { // Set the KChart axis to use this font KChart::TextAttributes attr = d->kdAxis->textAttributes(); attr.setFont(font); d->kdAxis->setTextAttributes(attr); } qreal Axis::fontSize() const { return d->kdAxis->textAttributes().fontSize().value(); } void Axis::setFontSize(qreal size) { // KChart has its own fontsize storage, it does not use QFont KChart::TextAttributes attributes = d->kdAxis->textAttributes(); attributes.setFontSize(KChart::Measure(size, KChartEnums::MeasureCalculationModeAbsolute)); d->kdAxis->setTextAttributes(attributes); // Keep font in sync QFont f = font(); f.setPointSizeF(size); setFont(f); } bool Axis::isVisible() const { return d->isVisible; } void Axis::setVisible(bool visible) { debugChartAxis<isVisible<<"->"<kdBarDiagram; d->isVisible = visible; if (visible) { d->restoreDiagrams(); } else { removeAxisFromDiagrams(); } } KoOdfNumberStyles::NumericStyleFormat *Axis::numericStyleFormat() const { return d->numericStyleFormat; } void Axis::SetNumericStyleFormat(KoOdfNumberStyles::NumericStyleFormat *numericStyleFormat) const { delete d->numericStyleFormat; d->numericStyleFormat = numericStyleFormat; } void Axis::setOdfAxisPosition(const QString &odfpos) { d->axisPosition = odfpos; } QString Axis::odfAxisPosition() const { return d->axisPosition; } void Axis::updateKChartAxisPosition() { if (!isCartesian(d->plotArea->chartType())) { debugChartAxis<plotArea->chartType(); return; } KChart::CartesianAxis::Position pos; if (d->plotArea->xAxis() == this) { if (d->plotArea->isVertical()) { pos = KChart::CartesianAxis::Left; if (d->axisPosition == "end") { pos = KChart::CartesianAxis::Right; } Axis *yAxis = d->plotArea->yAxis(); if (yAxis && yAxis->axisDirectionReversed()) { pos = pos == KChart::CartesianAxis::Left ? KChart::CartesianAxis::Right : KChart::CartesianAxis::Left; } } else { pos = KChart::CartesianAxis::Bottom; if (d->axisPosition == "end") { pos = KChart::CartesianAxis::Top; } Axis *yAxis = d->plotArea->yAxis(); if (yAxis && yAxis->axisDirectionReversed()) { pos = pos == KChart::CartesianAxis::Bottom ? KChart::CartesianAxis::Top : KChart::CartesianAxis::Bottom; } } d->kdAxis->setPosition(pos); } else if (d->plotArea->yAxis() == this) { if (d->plotArea->isVertical()) { pos = KChart::CartesianAxis::Bottom; if (d->axisPosition == "end") { pos = KChart::CartesianAxis::Top; } Axis *xAxis = d->plotArea->xAxis(); if (xAxis && xAxis->axisDirectionReversed()) { pos = pos == KChart::CartesianAxis::Bottom ? KChart::CartesianAxis::Top : KChart::CartesianAxis::Bottom; } } else { pos = KChart::CartesianAxis::Left; if (d->axisPosition == "end") { pos = KChart::CartesianAxis::Right; } Axis *xAxis = d->plotArea->xAxis(); if (xAxis && xAxis->axisDirectionReversed()) { pos = pos == KChart::CartesianAxis::Left ? KChart::CartesianAxis::Right : KChart::CartesianAxis::Left; } } d->kdAxis->setPosition(pos); } else if (d->plotArea->secondaryXAxis() == this) { if (d->plotArea->isVertical()) { pos = KChart::CartesianAxis::Right; if (d->axisPosition == "start") { pos = KChart::CartesianAxis::Left; } Axis *yAxis = d->plotArea->yAxis(); if (yAxis && yAxis->axisDirectionReversed()) { pos = pos == KChart::CartesianAxis::Left ? KChart::CartesianAxis::Right : KChart::CartesianAxis::Left; } } else { pos = KChart::CartesianAxis::Top; if (d->axisPosition == "start") { pos = KChart::CartesianAxis::Bottom; } Axis *yAxis = d->plotArea->yAxis(); if (yAxis && yAxis->axisDirectionReversed()) { pos = pos == KChart::CartesianAxis::Top ? KChart::CartesianAxis::Bottom : KChart::CartesianAxis::Top; } } d->kdAxis->setPosition(pos); } else if (d->plotArea->secondaryYAxis() == this) { if (d->plotArea->isVertical()) { pos = KChart::CartesianAxis::Top; if (d->axisPosition == "start") { pos = KChart::CartesianAxis::Bottom; } Axis *xAxis = d->plotArea->xAxis(); if (xAxis && xAxis->axisDirectionReversed()) { pos = pos == KChart::CartesianAxis::Bottom ? KChart::CartesianAxis::Top : KChart::CartesianAxis::Bottom; } } else { pos = KChart::CartesianAxis::Right; if (d->axisPosition == "start") { pos = KChart::CartesianAxis::Left; } Axis *xAxis = d->plotArea->xAxis(); if (xAxis && xAxis->axisDirectionReversed()) { pos = pos == KChart::CartesianAxis::Right ? KChart::CartesianAxis::Left : KChart::CartesianAxis::Right; } } d->kdAxis->setPosition(pos); } debugChartAxis<kdAxis<kdAxis->isAbscissa(); d->plotArea->plotAreaUpdate(); } CartesianAxis::Position Axis::kchartAxisPosition() const { return d->kdAxis->position(); } CartesianAxis::Position Axis::actualAxisPosition() const { CartesianAxis::Position pos = d->kdAxis->position(); if (d->plotArea->isVertical()) { switch (pos) { case KChart::CartesianAxis::Bottom: pos = KChart::CartesianAxis::Left; break; case KChart::CartesianAxis::Top: pos = KChart::CartesianAxis::Right; break; case KChart::CartesianAxis::Left: pos = KChart::CartesianAxis::Bottom; break; case KChart::CartesianAxis::Right: pos = KChart::CartesianAxis::Top; break; } } return pos; } bool Axis::axisDirectionReversed() const { bool reversed = false; KChart::CartesianCoordinatePlane *plane = dynamic_cast(kdPlane()); if (plane) { if (orientation() == Qt::Horizontal) reversed = plane->isHorizontalRangeReversed(); else // Qt::Vertical reversed = plane->isVerticalRangeReversed(); } return reversed; } void Axis::setOdfAxisLabelsPosition(const QString &odfpos) { d->axisLabelsPosition = odfpos; } QString Axis::odfAxisLabelsPosition() const { return d->axisLabelsPosition; } void Axis::updateKChartStockAttributes() { if (d->kdStockDiagram) { d->kdStockDiagram->setLowHighLinePen(d->plotArea->stockRangeLinePen()); d->kdStockDiagram->setUpTrendCandlestickBrush(d->plotArea->stockGainBrush()); d->kdStockDiagram->setDownTrendCandlestickBrush(d->plotArea->stockLossBrush()); d->kdStockDiagram->setUpTrendCandlestickPen(d->plotArea->stockRangeLinePen()); d->kdStockDiagram->setDownTrendCandlestickPen(d->plotArea->stockRangeLinePen()); } } QDebug operator<<(QDebug dbg, KoChart::Axis *a) { dbg.nospace().noquote() <<"Axis["<name()<<']'; return dbg.space().quote(); } diff --git a/plugins/chartshape/ChartLayout.cpp b/plugins/chartshape/ChartLayout.cpp index 8afceb93f0e..8943113e494 100644 --- a/plugins/chartshape/ChartLayout.cpp +++ b/plugins/chartshape/ChartLayout.cpp @@ -1,853 +1,904 @@ /* This file is part of the KDE project Copyright 2017 Dag Andersen Copyright 2010 Johannes Simon This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // Qt #include #include // KoChart #include "ChartLayout.h" #include "Legend.h" #include "ChartDebug.h" #include "PlotArea.h" #include "Axis.h" #include "ScreenConversions.h" // Calligra #include #include using namespace KoChart; class ChartLayout::LayoutData { public: int itemType; QRectF rect; bool inheritsTransform; bool clipped; LayoutData(int _itemType = GenericItemType) : itemType(_itemType) , inheritsTransform(true) , clipped(true) {} }; // static bool ChartLayout::autoPosition(const KoShape *shape) { return shape->additionalStyleAttribute("chart:auto-position") == "true"; } // static bool ChartLayout::autoSize(const KoShape *shape) { return shape->additionalStyleAttribute("chart:auto-size") == "true"; } ChartLayout::ChartLayout() : m_doingLayout(false) , m_relayoutScheduled(false) , m_padding(5., 5., 5., 5.) , m_spacing(5., 5.) , m_layoutingEnabled(true) { } ChartLayout::~ChartLayout() { foreach(LayoutData *data, m_layoutItems.values()) delete data; } void ChartLayout::add(KoShape *shape) { Q_ASSERT(!m_layoutItems.contains(shape)); setItemType(shape, GenericItemType); } void ChartLayout::remove(KoShape *shape) { m_shapes.remove(m_shapes.key(shape)); if (m_layoutItems.contains(shape)) { // delete LayoutData delete m_layoutItems.value(shape); m_layoutItems.remove(shape); scheduleRelayout(); } } void ChartLayout::setClipped(const KoShape *shape, bool clipping) { Q_ASSERT(m_layoutItems.contains(const_cast(shape))); m_layoutItems.value(const_cast(shape))->clipped = clipping; } bool ChartLayout::isClipped(const KoShape *shape) const { Q_ASSERT(m_layoutItems.contains(const_cast(shape))); return m_layoutItems.value(const_cast(shape))->clipped; } void ChartLayout::setInheritsTransform(const KoShape *shape, bool inherit) { m_layoutItems.value(const_cast(shape))->inheritsTransform = inherit; } bool ChartLayout::inheritsTransform(const KoShape *shape) const { return m_layoutItems.value(const_cast(shape))->inheritsTransform; } int ChartLayout::count() const { return m_layoutItems.size(); } QList ChartLayout::shapes() const { return m_layoutItems.keys(); } void ChartLayout::setContainerRect(const QRectF &rect) { if (rect != m_containerRect) { m_containerRect = rect; scheduleRelayout(); } } void ChartLayout::containerChanged(KoShapeContainer *container, KoShape::ChangeType type) { switch(type) { case KoShape::StrokeChanged: case KoShape::SizeChanged: { QRectF rect(QPointF(0,0), container->size()); KoInsets insets = container->strokeInsets(); rect.adjust(insets.left / 2., insets.top / 2., -insets.right / 2., -insets.bottom / 2.); setContainerRect(rect); break; } case KoShape::BorderChanged: warnChartLayout<<"Border not handled"; break; default: break; } } bool ChartLayout::isChildLocked(const KoShape *shape) const { return shape->isGeometryProtected(); } void ChartLayout::setItemType(const KoShape *shape, ItemType itemType) { LayoutData *data = m_layoutItems.value(const_cast(shape)); if (!data) { data = new LayoutData(); m_layoutItems.insert(const_cast(shape), data); } data->itemType = itemType; m_shapes.remove(m_shapes.key(const_cast(shape))); // in case m_shapes.insert(itemType, const_cast(shape)); debugChartLayout< m_containerRect.right()) { move.setX(m_containerRect.right() - current.right()); } if (newRect.top() < m_containerRect.top()) { move.setY(m_containerRect.top() - current.top()); } else if (newRect.bottom() > m_containerRect.bottom()) { move.setY(m_containerRect.bottom() - current.bottom()); } } void ChartLayout::childChanged(KoShape *shape, KoShape::ChangeType type) { Q_UNUSED(shape); // Do not relayout again if we're currently in the process of a relayout. // Repositioning a layout item or resizing it will result in a cull of this method. if (m_doingLayout) return; // This can be fine-tuned, but right now, simply everything will be re-layouted. switch (type) { case KoShape::PositionChanged: case KoShape::SizeChanged: scheduleRelayout(); // FIXME: There's some cases that would require relayouting but that don't make sense // for chart items, e.g. ShearChanged. How should these changes be avoided or handled? default: break; } } void ChartLayout::scheduleRelayout() { m_relayoutScheduled = true; } KChart::CartesianAxis::Position axisPosition(PlotArea *plotarea, ItemType type) { KChart::CartesianAxis::Position apos = KChart::CartesianAxis::Bottom; switch (type) { case XAxisTitleType: { if (plotarea && plotarea->xAxis()) { apos = plotarea->xAxis()->kchartAxisPosition(); } break; } case YAxisTitleType: { if (plotarea && plotarea->yAxis()) { apos = plotarea->yAxis()->kchartAxisPosition(); } break; } case SecondaryXAxisTitleType: { if (plotarea && plotarea->secondaryXAxis()) { apos = plotarea->secondaryXAxis()->kchartAxisPosition(); } break; } case SecondaryYAxisTitleType: { if (plotarea && plotarea->secondaryYAxis()) { apos = plotarea->secondaryYAxis()->kchartAxisPosition(); } break; } } return apos; } qreal ChartLayout::xOffset(const QRectF &left, const QRectF &right, bool center) const { qreal x = (left.width() + (left.width() > 0.0 ? m_spacing.x() : 0.0) - right.width() - (right.width() > 0.0 ? m_spacing.x() : 0.0)); return center ? x / 2.0 : x; } qreal ChartLayout::yOffset(const QRectF &top, const QRectF &bottom, bool center) const { qreal y = (top.height() + (top.height() > 0.0 ? m_spacing.y() : 0.0) - bottom.height() - (bottom.height() > 0.0 ? m_spacing.y() : 0.0)); return center ? y / 2.0 : y; } +void ChartLayout::rotateAxisTitles(PlotArea *plotarea) { + switch (plotarea->chartType()) { + case BarChartType: { + bool verticalXAxis = plotarea->isVertical(); + for (Axis *axis : plotarea->axes()) { + KoShape *title = axis->title(); + title->rotate(-title->rotation()); + switch (axis->actualAxisPosition()) { + case KChart::CartesianAxis::Bottom: + title->rotate(verticalXAxis ? -90 : 0); + break; + case KChart::CartesianAxis::Top: + title->rotate(verticalXAxis ? -90 : 0); + break; + case KChart::CartesianAxis::Left: + title->rotate(verticalXAxis ? 0 : -90); + break; + case KChart::CartesianAxis::Right: + title->rotate(verticalXAxis ? 0 : 90); + break; + } + } + break; + } + case LineChartType: + case AreaChartType: + case ScatterChartType: + case BubbleChartType: + case StockChartType: { + for (Axis *axis : plotarea->axes()) { + KoShape *title = axis->title(); + title->rotate(-title->rotation()); + switch (axis->actualAxisPosition()) { + case KChart::CartesianAxis::Left: + title->rotate(-90); + break; + case KChart::CartesianAxis::Right: + title->rotate(90); + break; + default: + break; + } + } + break; + } + default: + break; + } +} + void ChartLayout::calculateLayout() { QRectF area = m_containerRect; area.adjust(m_padding.left, m_padding.top, -m_padding.right, -m_padding.bottom); if (area.size().width() < 0 && area.size().height() < 0) { debugChartLayout<<"invalid size:"<(m_shapes.value(PlotAreaType)); QRectF plotareaRect = area; if (plotarea->chartType() == BarChartType && plotarea->isVertical()) debugChartLayout<<"Vertical bar chart"; QRectF titleRect; KoShape *title = m_shapes.value(TitleLabelType); if (title && title->isVisible()) { titleRect = itemRect(title); } QRectF subtitleRect; KoShape *subtitle = m_shapes.value(SubTitleLabelType); if (subtitle && subtitle->isVisible()) { subtitleRect = itemRect(subtitle); } QRectF footerRect; KoShape *footer = m_shapes.value(FooterLabelType); if (footer && footer->isVisible()) { footerRect = itemRect(footer); } QRectF legendRect; Legend *legend = dynamic_cast(m_shapes.value(LegendType)); if (legend && legend->isVisible()) { legendRect = itemRect(legend); debugChartLayout<<"legend rect:"< axisTitles; QRectF bottomTitleRect; // 1 QRectF leftTitleRect; // 2 QRectF topTitleRect; // 3 QRectF rightTitleRect; // 4 KoShape *xtitle = m_shapes.value(XAxisTitleType); if (xtitle && xtitle->isVisible()) { if (plotarea->chartType() == BarChartType && plotarea->isVertical()) { debugChartLayout<<"x-axis is vertical: position="<isVisible())<isVisible()) { if (plotarea->chartType() == BarChartType && plotarea->isVertical()) { debugChartLayout<<"secondary-x-axis is vertical: position="<isVisible()) { if (plotarea->chartType() == BarChartType && plotarea->isVertical()) { debugChartLayout<<"secondary-y-axis is horizontal: position="<legendPosition()) { case StartPosition: switch(legend->alignment()) { case Qt::AlignLeft: { // Align at top of the area to the left of plot area qreal offset = yOffset(topTitleRect, QRectF()); legendRect.moveTop(plotareaRect.top() + offset); break; } case Qt::AlignCenter: { qreal centerOffset = yOffset(topTitleRect, bottomTitleRect, true); legendRect.moveTop(plotareaRect.center().y() + centerOffset - legendRect.height() / 2.0); break; } case Qt::AlignRight: { // Align at bottom of the area to the left of plot area qreal offset = yOffset(QRectF(), bottomTitleRect); legendRect.moveBottom(plotareaRect.bottom() + offset); break; } } legendRect.moveLeft(plotareaRect.left()); plotareaRect.setLeft(legendRect.right() + m_spacing.x()); break; case TopPosition: switch(legend->alignment()) { case Qt::AlignLeft: { // Align at left of the area on top of plot area qreal offset = xOffset(leftTitleRect, QRectF()); legendRect.moveLeft(plotareaRect.left() + offset); break; } case Qt::AlignCenter: { qreal centerOffset = xOffset(leftTitleRect, rightTitleRect, true); legendRect.moveLeft(plotareaRect.center().x() + centerOffset - legendRect.width() / 2.0); debugChartLayout<<"legend top/center:"<<"to"<alignment()) { case Qt::AlignLeft: { // Align at top of the area right of plot area qreal offset = yOffset(topTitleRect, QRectF()); legendRect.moveTop(plotareaRect.top() + offset); debugChartLayout<<"legend end/top:"<<"to"<alignment()) { case Qt::AlignLeft: { // Align at left of the area below of plot area qreal offset = xOffset(leftTitleRect, QRectF()); legendRect.moveLeft(plotareaRect.left() + offset); break; } case Qt::AlignCenter: { qreal centerOffset = xOffset(leftTitleRect, rightTitleRect, true); legendRect.moveLeft(bottomcenter.x() + centerOffset - legendRect.width() / 2.0); break; } case Qt::AlignRight: { // Align at right of the area on below plot area qreal offset = xOffset(QRectF(), rightTitleRect); legendRect.moveRight(plotareaRect.right() + offset); break; } } legendRect.moveBottom(plotareaRect.bottom()); plotareaRect.setBottom(legendRect.top() - m_spacing.y()); break; case TopStartPosition: { qreal xoffset = xOffset(leftTitleRect, QRectF()); qreal yoffset = yOffset(topTitleRect, QRectF()); legendRect.moveTopLeft(area.topLeft()); if (legendRect.right() + m_spacing.x() - xoffset > plotareaRect.left()) { plotareaRect.setLeft(legendRect.right() + m_spacing.x() - xoffset); } if (legendRect.bottom() + m_spacing.y() - yoffset > plotareaRect.top()) { plotareaRect.setTop(legendRect.bottom() + m_spacing.y() - yoffset); } debugChartLayout<<"legend top/start"< plotareaRect.top()) { plotareaRect.setTop(legendRect.bottom() + m_spacing.y() - yoffset); } debugChartLayout<<"legend top/end"< plotareaRect.left()) { plotareaRect.setLeft(legendRect.right() + m_spacing.x() - xoffset); } if (legendRect.top() - m_spacing.y() + yoffset < plotareaRect.bottom()) { plotareaRect.setBottom(legendRect.top() - m_spacing.y() - yoffset); } debugChartLayout<<"legend bottom/start"< pr.right()) { plotareaRect.setRight(qMin(plotareaRect.right(), legendRect.left() - m_spacing.x())); } else if (legendRect.bottom() < pr.top()) { plotareaRect.setTop(qMax(plotareaRect.top(), legendRect.bottom() + m_spacing.y())); } else if (legendRect.top() > pr.bottom()) { plotareaRect.setBottom(qMin(plotareaRect.bottom(), legendRect.top() - m_spacing.y())); } debugChartLayout<<"legend manual:"<legendPosition()<<"align:"<alignment(); } debugChartLayout<<"axis titles:"<isVisible()) { m_layoutItems[title]->rect = titleRect; debugChartLayout<<"title"<isVisible()) { m_layoutItems[subtitle]->rect = subtitleRect; debugChartLayout<<"subtitle"<isVisible()) { m_layoutItems[footer]->rect = footerRect; debugChartLayout<<"footer"<isVisible()) { m_layoutItems[legend]->rect = legendRect; debugChartLayout<<"legend"<rect = bottomTitleRect; debugChartLayout<<"bottom title"<rect = leftTitleRect; debugChartLayout<<"left title"<rect = topTitleRect; debugChartLayout<<"top title"<rect = rightTitleRect; debugChartLayout<<"right title"<isVisible()) { m_layoutItems[plotarea]->rect = plotareaRect; debugChartLayout<<"plot area"<::const_iterator it; for (it = m_layoutItems.constBegin(); it != m_layoutItems.constEnd(); ++it) { if (it.key()->isVisible()) { setItemPosition(it.key(), it.value()->rect.topLeft()); debugChartLayout<rect.topLeft()<itemType == PlotAreaType) { setItemSize(it.key(), it.value()->rect.size()); debugChartLayout<rect.size()<size()); return shape->transformation().mapRect(boundingRect).topLeft(); } /*static*/ QSizeF ChartLayout::itemSize(const KoShape *shape) { const QRectF boundingRect = QRectF(QPointF(0, 0), shape->size()); return shape->transformation().mapRect(boundingRect).size(); } /*static*/ void ChartLayout::setItemSize(KoShape *shape, const QSizeF &size) { shape->setSize(size); } /*static*/ QRectF ChartLayout::itemRect(const KoShape *shape) { const QRectF boundingRect = QRectF(itemPosition(shape), itemSize(shape)); return boundingRect; } /*static*/ void ChartLayout::setItemPosition(KoShape *shape, const QPointF& pos) { const QPointF offset = shape->position() - itemPosition(shape); shape->setPosition(pos + offset); } QRectF ChartLayout::diagramArea(const KoShape *shape) { return diagramArea(shape, itemRect(shape)); } // FIXME: Get the actual plot area ex axis labels from KChart QRectF ChartLayout::diagramArea(const KoShape *shape, const QRectF &rect) { const PlotArea* plotArea = dynamic_cast(shape); if (!plotArea) { return rect; } qreal bottom = 0.0; qreal left = 0.0; qreal top = 0.0; qreal right = 0.0; // HACK: KChart has some spacing between axis and label qreal xspace = ScreenConversions::pxToPtX(6.0) * 2.0; qreal yspace = ScreenConversions::pxToPtY(6.0) * 2.0; if (plotArea->xAxis() && plotArea->xAxis()->showLabels()) { bottom = plotArea->xAxis()->fontSize(); bottom += yspace; } if (plotArea->yAxis() && plotArea->yAxis()->showLabels()) { left = plotArea->yAxis()->fontSize(); left += xspace; } if (plotArea->secondaryXAxis() && plotArea->secondaryXAxis()->showLabels()) { top = plotArea->secondaryXAxis()->fontSize(); top += yspace; } if (plotArea->secondaryYAxis() && plotArea->secondaryYAxis()->showLabels()) { right = plotArea->secondaryYAxis()->fontSize(); right += xspace; } return rect.adjusted(left, top, -right, -bottom); } QString ChartLayout::dbg(const KoShape *shape) const { QString s; LayoutData *data = m_layoutItems[const_cast(shape)]; switch(data->itemType) { case GenericItemType: s = "KoShape[Generic:"+ shape->shapeId() + "]"; break; case TitleLabelType: s = "KoShape[ChartTitle]"; break; case SubTitleLabelType: s = "KoShape[ChartSubTitle]"; break; case FooterLabelType: s = "KoShape[ChartFooter]"; break; case PlotAreaType: s = "KoShape[PlotArea]"; break; case LegendType: s = "KoShape[Legend"; switch(static_cast(shape)->alignment()) { case Qt::AlignLeft: s += ":Start"; break; case Qt::AlignCenter: s += ":Center"; break; case Qt::AlignRight: s += ":End"; break; default: s += ":Float"; break; } s += ']'; break; case XAxisTitleType: s = "KoShape[XAxisTitle]"; break; case YAxisTitleType: s = "KoShape[YAxisTitle]"; break; case SecondaryXAxisTitleType: s = "KoShape[SXAxisTitle]"; break; case SecondaryYAxisTitleType: s = "KoShape[SYAxisTitle]"; break; default: s = "KoShape[Unknown]"; break; } return s; } QString ChartLayout::dbg(const QList &shapes) const { QString s = "("; for (int i = 0; i < shapes.count(); ++i) { if (i > 0) s += ','; s += dbg(shapes.at(i)); } s += ')'; return s; } diff --git a/plugins/chartshape/ChartLayout.h b/plugins/chartshape/ChartLayout.h index 58257830ad5..62caaf3c7c8 100644 --- a/plugins/chartshape/ChartLayout.h +++ b/plugins/chartshape/ChartLayout.h @@ -1,227 +1,231 @@ /* This file is part of the KDE project Copyright 2017 Dag Andersen Copyright 2010 Johannes Simon This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KCHART_LAYOUT_H #define KCHART_LAYOUT_H // Qt #include #include #include // Calligra #include #include // KoChart #include "kochart_global.h" namespace KoChart { +class PlotArea; + /** * A generic chart-style layout with 10 possible positions: * * ---------------------- * | A | D | F | * |---|------------------| * | | | | * | B | I | G | (J) * | | | | * |---|------------------| * | C | E | H | * ---------------------- * * A - TopStartPosition * B - StartPosition * C - BottomStartPosition * D - TopPosition * E - BottomPosition * F - TopEndPosition * G - EndPosition * H - BottomEndPosition * I - CenterPosition * J - FloatingPosition * * A chart can consist of the following elements: * * Title: Positioned at the top in (D) * Sub-title: Positioned below Title in (D) * Footer: Position at the bottom in (E) * Legend: Positiond according to legend-position and alignmnent * Note that Legend is the only component with this level of layout control from odf * * Plot area: Positioned in the center (I) * Axes: Attached to the plot area * Axis title: Positioned beside the axis it describes * * Each of these elements may be manually positioned and sized, * but by default the layouting does the job. */ class ChartLayout : public KoShapeContainerModel { public: ChartLayout(); ~ChartLayout(); /** * Add @a shape and register it with GenericItemType * * This is normally called from the resource manager. * * Note that this shape will not be layed out until it gets a proper item type * set with setItemType() * * Abstract in KoShapeContainerModel so needs to be here. * * @see setItemType() */ void add(KoShape *shape) override; /** * Removes a shape from the layout. */ void remove(KoShape *shape) override; /** * Turns clipping of a shape on or off. */ void setClipped(const KoShape *shape, bool clipping) override; /** * @see setClipping */ bool isClipped(const KoShape *shape) const override; /// reimplemented void setInheritsTransform(const KoShape *shape, bool inherit) override; /// reimplemented bool inheritsTransform(const KoShape *shape) const override; /** * Returns the number of shapes in this layout. */ int count() const override; /** * Returns a list of shapes in this layout. */ QList shapes() const override; /** * Called whenever a property of the container (i.e. the ChartShape) is changed. */ void containerChanged(KoShapeContainer *container, KoShape::ChangeType type) override; /** * Returns whether a shape is locked for user modifications. */ bool isChildLocked(const KoShape *shape) const override; /** * Changes the item type of the shape to @p itemType * The @p itemType controls where the shape is layed out */ void setItemType(const KoShape *shape, ItemType itemType); void proposeMove(KoShape *child, QPointF &move) override; /** * Called whenever a property of a shape in this layout has changed. * * All layout items effected by this change will be re-layouted. */ void childChanged(KoShape *shape, KoShape::ChangeType type) override; /** * Does the layouting of all visible shapes * * Only does a relayout if one has been schedules previously through * scheduleRelayout(). * * \see scheduleRelayout */ void layout(); /// Calculates the layout used by @a layout() void calculateLayout(); /** * Schedules a relayout that is to be done when layout() is called. * * \see layout */ void scheduleRelayout(); /** * Sets the padding to @p padding that will be applied during layout */ void setPadding (const KoInsets &padding); /// Returns the padding defined for this layout KoInsets padding() const; /// Set spacing in points to @p hSpacing, @p vSpacing to be used for this layout void setSpacing(qreal hSpacing, qreal vSpacing); /// Returns the horizontal and vertical spacing in points defined for this layout QPointF spacing() const; /// Enable/disable layouting (ex: used during odf loading) void setLayoutingEnabled(bool value); private: /// Set the chart size to @p size and schedule relayout void setContainerRect(const QRectF &rect); qreal xOffset(const QRectF &left, const QRectF &right, bool center = false) const; qreal yOffset(const QRectF &top, const QRectF &bottom, bool center = false) const; + void rotateAxisTitles(PlotArea *plotarea); + #ifdef COMPILING_TESTS public: #endif QString dbg(const QList &shapes) const; QString dbg(const KoShape *shape) const; static qreal relativePosition(qreal start1, qreal length1, qreal start2, qreal length2, qreal start, qreal length); static QPointF itemPosition(const KoShape *shape); static QSizeF itemSize(const KoShape *shape); static void setItemSize(KoShape *shape, const QSizeF &size); static QRectF itemRect(const KoShape *shape); static void setItemPosition(KoShape *shape, const QPointF& pos); /// Returns the plot area minus axis labels static QRectF diagramArea(const KoShape *shape); /// Returns the @p rect minus axis labels static QRectF diagramArea(const KoShape *shape, const QRectF &rect); static bool autoPosition(const KoShape *shape); static bool autoSize(const KoShape *shape); private: bool m_doingLayout; bool m_relayoutScheduled; QRectF m_containerRect; KoInsets m_padding; QPointF m_spacing; bool m_layoutingEnabled; class LayoutData; QMap m_layoutItems; QMap m_shapes; }; } // namespace KoChart #endif // KCHART_CHARTLAYOUT_H diff --git a/plugins/chartshape/ChartTool.cpp b/plugins/chartshape/ChartTool.cpp index a3767eccf3f..33d9bb6d876 100644 --- a/plugins/chartshape/ChartTool.cpp +++ b/plugins/chartshape/ChartTool.cpp @@ -1,1158 +1,1157 @@ /* This file is part of the KDE project * * Copyright (C) 2007, 2010 Inge Wallin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // Own #include "ChartTool.h" // Qt #include #include #include #include #include #include #include #include // KF5 #include // Calligra #include #include #include #include #include #include #include // KChart #include #include #include #include #include #include #include // KoChart #include "Surface.h" #include "PlotArea.h" #include "ChartLayout.h" #include "Axis.h" #include "DataSet.h" #include "Legend.h" #include "ChartProxyModel.h" #include "TitlesConfigWidget.h" #include "LegendConfigWidget.h" #include "PlotAreaConfigWidget.h" #include "AxesConfigWidget.h" #include "DataSetConfigWidget.h" #include "PieConfigWidget.h" #include "RingConfigWidget.h" #include "RadarDataSetConfigWidget.h" #include "KChartConvertions.h" #include "commands/ChartTypeCommand.h" #include "commands/LegendCommand.h" #include "commands/AxisCommand.h" #include "commands/DatasetCommand.h" #include "commands/ChartTextShapeCommand.h" #include "commands/AddRemoveAxisCommand.h" #include "commands/GapCommand.h" #include "commands/PlotAreaCommand.h" #include "commands/DatasetCommand.h" #include "ChartDebug.h" using namespace KoChart; class ChartTool::Private { public: Private(); ~Private(); ChartShape *shape; QModelIndex datasetSelection; QPen datasetSelectionPen; QBrush datasetSelectionBrush; }; ChartTool::Private::Private() : shape(0) { } ChartTool::Private::~Private() { } ChartTool::ChartTool(KoCanvasBase *canvas) : KoToolBase(canvas), d(new Private()) { // Create QActions here. #if 0 QActionGroup *group = new QActionGroup(this); m_foo = new QAction(koIcon("this-action"), i18n("Do something"), this); m_foo->setCheckable(true); group->addAction(m_foo); connect(m_foo, SIGNAL(toggled(bool)), this, SLOT(catchFoo(bool))); m_bar = new QAction(koIcon("that-action"), i18n("Do something else"), this); m_bar->setCheckable(true); group->addAction(m_bar); connect(m_foo, SIGNAL(toggled(bool)), this, SLOT(catchBar(bool))); #endif connect(canvas->shapeManager(), SIGNAL(selectionChanged()), this, SLOT(shapeSelectionChanged())); } ChartTool::~ChartTool() { delete d; } void ChartTool::shapeSelectionChanged() { // When this chart tool is activated with one chart and a new chart is created, // the new chart is selected but the deactivate method is not called, // so this tool will still operate on the old chart. // We activate the default tool to rectify this. // FIXME: could probably be done in KoToolBase (or wherever appropriate) if (!d->shape) { return; } QList lst = canvas()->shapeManager()->selection()->selectedShapes(KoFlake::StrippedSelection); if (lst.contains(d->shape)) { return; } for (KoShape *s : lst) { ChartShape *chart = dynamic_cast(s); if (chart && chart != d->shape) { activateTool(KoInteractionTool_ID); } } } void ChartTool::paint(QPainter &painter, const KoViewConverter &converter) { if (d->shape) { QPen pen; //Use the #00adf5 color with 50% opacity pen.setColor(QColor(0, 173, 245, 127)); pen.setWidth(qMax((uint)1, handleRadius() / 2)); pen.setJoinStyle(Qt::RoundJoin); painter.setPen(pen); QTransform painterMatrix = painter.worldTransform(); painter.setWorldTransform(d->shape->absoluteTransformation(&converter) * painterMatrix); KoShape::applyConversion(painter, converter); painter.drawRect(QRectF(QPointF(), d->shape->size())); } } void ChartTool::mousePressEvent(KoPointerEvent *event) { #if 1 // disabled Q_UNUSED(event); return; #else // Select dataset if (!d->shape || !d->shape->kdChart() || ! d->shape->kdChart()->coordinatePlane() || !d->shape->kdChart()->coordinatePlane()->diagram()) return; QPointF point = event->point - d->shape->position(); QModelIndex selection = d->shape->kdChart()->coordinatePlane()->diagram()->indexAt(point.toPoint()); // Note: the dataset will always stay column() due to the transformations being // done internally by the ChartProxyModel int dataset = selection.column(); if (d->datasetSelection.isValid()) { d->shape->kdChart()->coordinatePlane()->diagram()->setPen(d->datasetSelection.column(), d->datasetSelectionPen); //d->shape->kdChart()->coordinatePlane()->diagram()->setBrush(d->datasetSelection, d->datasetSelectionBrush); } if (selection.isValid()) { d->datasetSelection = selection; QPen pen(Qt::DotLine); pen.setColor(Qt::darkGray); pen.setWidth(1); d->datasetSelectionBrush = d->shape->kdChart()->coordinatePlane()->diagram()->brush(selection); d->datasetSelectionPen = d->shape->kdChart()->coordinatePlane()->diagram()->pen(dataset); d->shape->kdChart()->coordinatePlane()->diagram()->setPen(dataset, pen); //d->shape->kdChart()->coordinatePlane()->diagram()->setBrush(selection, QBrush(Qt::lightGray)); } ((ChartConfigWidget*)optionWidget())->selectDataset(dataset); d->shape->update(); #endif } void ChartTool::mouseMoveEvent(KoPointerEvent *event) { event->ignore(); } void ChartTool::mouseReleaseEvent(KoPointerEvent *event) { event->ignore(); } void ChartTool::activate(ToolActivation, const QSet &shapes) { debugChartTool<shape = 0; for (KoShape *s : shapes) { d->shape = dynamic_cast(s); if (!d->shape) { for (KoShape *parent = s->parent(); parent; parent = parent->parent()) { d->shape = dynamic_cast(parent); if (d->shape) { break; } } } if (d->shape) { break; } } debugChartTool<shape; if (!d->shape) { emit done(); return; } useCursor(Qt::ArrowCursor); foreach (QWidget *w, optionWidgets()) { ConfigWidgetBase *widget = dynamic_cast(w); Q_ASSERT(widget); if (widget) { widget->open(d->shape); } } foreach (QWidget *w, optionWidgets()) { ConfigWidgetBase *widget = dynamic_cast(w); Q_ASSERT(widget); if (widget) { widget->updateData(); } } d->shape->update(); // to paint decoration } void ChartTool::deactivate() { debugChartTool<shape; foreach (QWidget *w, optionWidgets()) { ConfigWidgetBase *configWidget = dynamic_cast(w); if (configWidget) configWidget->deactivate(); } if (d->shape) { d->shape->update(); // to get rid of decoration } d->shape = 0; } QList > ChartTool::createOptionWidgets() { QList > widgets; TitlesConfigWidget *titles = new TitlesConfigWidget(); titles->setWindowTitle(i18n("Titles")); widgets.append(titles); connect(titles->ui.showTitle, SIGNAL(toggled(bool)), this, SLOT(setShowTitle(bool))); connect(titles->ui.titlePositioning, SIGNAL(currentIndexChanged(int)), this, SLOT(setTitlePositioning(int))); connect(titles->ui.titleResize, SIGNAL(currentIndexChanged(int)), this, SLOT(setTitleResize(int))); connect(titles->ui.showSubTitle, SIGNAL(toggled(bool)), this, SLOT(setShowSubTitle(bool))); connect(titles->ui.subtitlePositioning, SIGNAL(currentIndexChanged(int)), this, SLOT(setSubTitlePositioning(int))); connect(titles->ui.subtitleResize, SIGNAL(currentIndexChanged(int)), this, SLOT(setSubTitleResize(int))); connect(titles->ui.showFooter, SIGNAL(toggled(bool)), this, SLOT(setShowFooter(bool))); connect(titles->ui.footerPositioning, SIGNAL(currentIndexChanged(int)), this, SLOT(setFooterPositioning(int))); connect(titles->ui.footerResize, SIGNAL(currentIndexChanged(int)), this, SLOT(setFooterResize(int))); connect(d->shape, SIGNAL(updateConfigWidget()), titles, SLOT(updateData())); LegendConfigWidget *legend = new LegendConfigWidget(); legend->setWindowTitle(i18n("Legend")); widgets.append(legend); connect(legend, SIGNAL(showLegendChanged(bool)), this, SLOT(setShowLegend(bool))); connect(legend, SIGNAL(legendTitleChanged(QString)), this, SLOT(setLegendTitle(QString))); connect(legend, SIGNAL(legendFontChanged(QFont)), this, SLOT(setLegendFont(QFont))); connect(legend, SIGNAL(legendFontSizeChanged(int)), this, SLOT(setLegendFontSize(int))); connect(legend, SIGNAL(legendOrientationChanged(Qt::Orientation)), this, SLOT(setLegendOrientation(Qt::Orientation))); connect(legend, SIGNAL(legendPositionChanged(Position)), this, SLOT(setLegendPosition(Position))); connect(legend, SIGNAL(legendAlignmentChanged(Qt::Alignment)), this, SLOT(setLegendAlignment(Qt::Alignment))); connect(d->shape->legend(), SIGNAL(updateConfigWidget()), legend, SLOT(updateData())); PlotAreaConfigWidget *plotarea = new PlotAreaConfigWidget(); plotarea->setWindowTitle(i18n("Plot Area")); widgets.append(plotarea); connect(plotarea, SIGNAL(chartTypeChanged(ChartType,ChartSubtype)), this, SLOT(setChartType(ChartType,ChartSubtype))); connect(plotarea, SIGNAL(chartSubTypeChanged(ChartSubtype)), this, SLOT(setChartSubType(ChartSubtype))); connect(plotarea, SIGNAL(threeDModeToggled(bool)), this, SLOT(setThreeDMode(bool))); connect(plotarea, SIGNAL(chartOrientationChanged(Qt::Orientation)), this, SLOT(setChartOrientation(Qt::Orientation))); // data set edit dialog connect(plotarea, SIGNAL(dataSetXDataRegionChanged(DataSet*,CellRegion)), this, SLOT(setDataSetXDataRegion(DataSet*,CellRegion))); connect(plotarea, SIGNAL(dataSetYDataRegionChanged(DataSet*,CellRegion)), this, SLOT(setDataSetYDataRegion(DataSet*,CellRegion))); connect(plotarea, SIGNAL(dataSetCustomDataRegionChanged(DataSet*,CellRegion)), this, SLOT(setDataSetCustomDataRegion(DataSet*,CellRegion))); connect(plotarea, SIGNAL(dataSetLabelDataRegionChanged(DataSet*,CellRegion)), this, SLOT(setDataSetLabelDataRegion(DataSet*,CellRegion))); connect(plotarea, SIGNAL(dataSetCategoryDataRegionChanged(DataSet*,CellRegion)), this, SLOT(setDataSetCategoryDataRegion(DataSet*,CellRegion))); AxesConfigWidget *axes = plotarea->cartesianAxesConfigWidget(); connect(axes, SIGNAL(axisAdded(AxisDimension,QString)), this, SLOT(addAxis(AxisDimension,QString))); connect(axes, SIGNAL(axisRemoved(Axis*)), this, SLOT(removeAxis(Axis*))); connect(axes, SIGNAL(axisShowTitleChanged(Axis*,bool)), this, SLOT(setAxisShowTitle(Axis*,bool))); connect(axes, SIGNAL(axisShowChanged(Axis*,bool)), this, SLOT(setShowAxis(Axis*,bool))); connect(axes, SIGNAL(axisPositionChanged(Axis*,QString)), this, SLOT(setAxisPosition(Axis*,QString))); connect(axes, SIGNAL(axisLabelsPositionChanged(Axis*,QString)), this, SLOT(setAxisLabelsPosition(Axis*,QString))); connect(axes, SIGNAL(axisShowLabelsChanged(Axis*,bool)), this, SLOT(setAxisShowLabels(Axis*,bool))); connect(axes, SIGNAL(axisShowMajorGridLinesChanged(Axis*,bool)), this, SLOT(setAxisShowMajorGridLines(Axis*,bool))); connect(axes, SIGNAL(axisShowMinorGridLinesChanged(Axis*,bool)), this, SLOT(setAxisShowMinorGridLines(Axis*,bool))); // scaling dialog connect(axes, SIGNAL(axisUseLogarithmicScalingChanged(Axis*,bool)), this, SLOT(setAxisUseLogarithmicScaling(Axis*,bool))); connect(axes, SIGNAL(axisStepWidthChanged(Axis*,qreal)), this, SLOT(setAxisStepWidth(Axis*,qreal))); connect(axes, SIGNAL(axisSubStepWidthChanged(Axis*,qreal)), this, SLOT(setAxisSubStepWidth(Axis*,qreal))); connect(axes, SIGNAL(axisUseAutomaticStepWidthChanged(Axis*,bool)), this, SLOT(setAxisUseAutomaticStepWidth(Axis*,bool))); connect(axes, SIGNAL(axisUseAutomaticSubStepWidthChanged(Axis*,bool)), this, SLOT(setAxisUseAutomaticSubStepWidth(Axis*,bool))); // font dialog connect(axes, SIGNAL(axisLabelsFontChanged(Axis*,QFont)), this, SLOT(setAxisLabelsFont(Axis*,QFont))); connect(axes, SIGNAL(gapBetweenBarsChanged(Axis*,int)), this, SLOT(setGapBetweenBars(Axis*,int))); connect(axes, SIGNAL(gapBetweenSetsChanged(Axis*,int)), this, SLOT(setGapBetweenSets(Axis*,int))); DataSetConfigWidget *dataset = plotarea->cartesianDataSetConfigWidget(); connect(dataset, SIGNAL(dataSetChartTypeChanged(DataSet*,ChartType,ChartSubtype)), this, SLOT(setDataSetChartType(DataSet*,ChartType,ChartSubtype))); connect(dataset, SIGNAL(datasetBrushChanged(DataSet*,QColor,int)), this, SLOT(setDataSetBrush(DataSet*,QColor,int))); connect(dataset, SIGNAL(dataSetMarkerChanged(DataSet*,OdfSymbolType,OdfMarkerStyle)), this, SLOT(setDataSetMarker(DataSet*,OdfSymbolType,OdfMarkerStyle))); connect(dataset, SIGNAL(datasetPenChanged(DataSet*,QColor,int)), this, SLOT(setDataSetPen(DataSet*,QColor,int))); connect(dataset, SIGNAL(datasetShowCategoryChanged(DataSet*,bool,int)), this, SLOT(setDataSetShowCategory(DataSet*,bool,int))); connect(dataset, SIGNAL(dataSetShowNumberChanged(DataSet*,bool,int)), this, SLOT(setDataSetShowNumber(DataSet*,bool,int))); connect(dataset, SIGNAL(datasetShowPercentChanged(DataSet*,bool,int)), this, SLOT(setDataSetShowPercent(DataSet*,bool,int))); connect(dataset, SIGNAL(datasetShowSymbolChanged(DataSet*,bool,int)), this, SLOT(setDataSetShowSymbol(DataSet*,bool,int))); connect(dataset, SIGNAL(dataSetAxisChanged(DataSet*,Axis*)), this, SLOT(setDataSetAxis(DataSet*,Axis*))); connect(dataset, SIGNAL(axisAdded(AxisDimension,QString)), this, SLOT(addAxis(AxisDimension,QString))); PieConfigWidget *pie = plotarea->pieConfigWidget(); connect(pie, SIGNAL(explodeFactorChanged(DataSet*,int, int)), this, SLOT(setPieExplodeFactor(DataSet*,int, int))); connect(pie, SIGNAL(brushChanged(DataSet*,QColor,int)), this, SLOT(setDataSetBrush(DataSet*,QColor,int))); connect(pie, SIGNAL(penChanged(DataSet*,QColor,int)), this, SLOT(setDataSetPen(DataSet*,QColor,int))); connect(pie, SIGNAL(showCategoryChanged(DataSet*,bool,int)), this, SLOT(setDataSetShowCategory(DataSet*,bool,int))); connect(pie, SIGNAL(showNumberChanged(DataSet*,bool,int)), this, SLOT(setDataSetShowNumber(DataSet*,bool,int))); connect(pie, SIGNAL(showPercentChanged(DataSet*,bool,int)), this, SLOT(setDataSetShowPercent(DataSet*,bool,int))); RingConfigWidget *ring = plotarea->ringConfigWidget(); connect(ring, SIGNAL(explodeFactorChanged(DataSet*,int, int)), this, SLOT(setPieExplodeFactor(DataSet*,int, int))); connect(ring, SIGNAL(brushChanged(DataSet*,QColor,int)), this, SLOT(setDataSetBrush(DataSet*,QColor,int))); connect(ring, SIGNAL(penChanged(DataSet*,QColor,int)), this, SLOT(setDataSetPen(DataSet*,QColor,int))); connect(ring, SIGNAL(showCategoryChanged(DataSet*,bool,int)), this, SLOT(setDataSetShowCategory(DataSet*,bool,int))); connect(ring, SIGNAL(showNumberChanged(DataSet*,bool,int)), this, SLOT(setDataSetShowNumber(DataSet*,bool,int))); connect(ring, SIGNAL(showPercentChanged(DataSet*,bool,int)), this, SLOT(setDataSetShowPercent(DataSet*,bool,int))); axes = plotarea->stockAxesConfigWidget(); connect(axes, SIGNAL(axisAdded(AxisDimension,QString)), this, SLOT(addAxis(AxisDimension,QString))); connect(axes, SIGNAL(axisRemoved(Axis*)), this, SLOT(removeAxis(Axis*))); connect(axes, SIGNAL(axisShowTitleChanged(Axis*,bool)), this, SLOT(setAxisShowTitle(Axis*,bool))); connect(axes, SIGNAL(axisShowChanged(Axis*,bool)), this, SLOT(setShowAxis(Axis*,bool))); connect(axes, SIGNAL(axisPositionChanged(Axis*,QString)), this, SLOT(setAxisPosition(Axis*,QString))); connect(axes, SIGNAL(axisLabelsPositionChanged(Axis*,QString)), this, SLOT(setAxisLabelsPosition(Axis*,QString))); connect(axes, SIGNAL(axisShowLabelsChanged(Axis*,bool)), this, SLOT(setAxisShowLabels(Axis*,bool))); connect(axes, SIGNAL(axisShowMajorGridLinesChanged(Axis*,bool)), this, SLOT(setAxisShowMajorGridLines(Axis*,bool))); connect(axes, SIGNAL(axisShowMinorGridLinesChanged(Axis*,bool)), this, SLOT(setAxisShowMinorGridLines(Axis*,bool))); // scaling dialog connect(axes, SIGNAL(axisUseLogarithmicScalingChanged(Axis*,bool)), this, SLOT(setAxisUseLogarithmicScaling(Axis*,bool))); connect(axes, SIGNAL(axisStepWidthChanged(Axis*,qreal)), this, SLOT(setAxisStepWidth(Axis*,qreal))); connect(axes, SIGNAL(axisSubStepWidthChanged(Axis*,qreal)), this, SLOT(setAxisSubStepWidth(Axis*,qreal))); connect(axes, SIGNAL(axisUseAutomaticStepWidthChanged(Axis*,bool)), this, SLOT(setAxisUseAutomaticStepWidth(Axis*,bool))); connect(axes, SIGNAL(axisUseAutomaticSubStepWidthChanged(Axis*,bool)), this, SLOT(setAxisUseAutomaticSubStepWidth(Axis*,bool))); // font dialog connect(axes, SIGNAL(axisLabelsFontChanged(Axis*,QFont)), this, SLOT(setAxisLabelsFont(Axis*,QFont))); // Radar RadarDataSetConfigWidget *rdataset = plotarea->radarDataSetConfigWidget(); connect(rdataset, SIGNAL(datasetBrushChanged(DataSet*,QColor,int)), this, SLOT(setDataSetBrush(DataSet*,QColor,int))); connect(rdataset, SIGNAL(dataSetMarkerChanged(DataSet*,OdfSymbolType,OdfMarkerStyle)), this, SLOT(setDataSetMarker(DataSet*,OdfSymbolType,OdfMarkerStyle))); connect(rdataset, SIGNAL(datasetPenChanged(DataSet*,QColor,int)), this, SLOT(setDataSetPen(DataSet*,QColor,int))); connect(rdataset, SIGNAL(datasetShowCategoryChanged(DataSet*,bool,int)), this, SLOT(setDataSetShowCategory(DataSet*,bool,int))); connect(rdataset, SIGNAL(dataSetShowNumberChanged(DataSet*,bool,int)), this, SLOT(setDataSetShowNumber(DataSet*,bool,int))); connect(rdataset, SIGNAL(datasetShowPercentChanged(DataSet*,bool,int)), this, SLOT(setDataSetShowPercent(DataSet*,bool,int))); connect(rdataset, SIGNAL(datasetShowSymbolChanged(DataSet*,bool,int)), this, SLOT(setDataSetShowSymbol(DataSet*,bool,int))); connect(d->shape, SIGNAL(updateConfigWidget()), plotarea, SLOT(updateData())); return widgets; } void ChartTool::setChartType(ChartType type, ChartSubtype subtype) { Q_ASSERT(d->shape); if (!d->shape) { return; } ChartTypeCommand *command = new ChartTypeCommand(d->shape); if (command!=0) { command->setChartType(type, subtype); canvas()->addCommand(command); } foreach (QWidget *w, optionWidgets()) { ConfigWidgetBase *cw = dynamic_cast(w); if (cw) { cw->updateData(); } } } void ChartTool::setChartSubType(ChartSubtype subtype) { Q_ASSERT(d->shape); if (!d->shape) return; d->shape->setChartSubType(subtype); d->shape->update(); } void ChartTool::setDataSetXDataRegion(DataSet *dataSet, const CellRegion ®ion) { debugChartTool<setXDataRegion(region); d->shape->update(); } void ChartTool::setDataSetYDataRegion(DataSet *dataSet, const CellRegion ®ion) { if (!dataSet) return; dataSet->setYDataRegion(region); d->shape->update(); } void ChartTool::setDataSetCustomDataRegion(DataSet *dataSet, const CellRegion ®ion) { if (!dataSet) return; dataSet->setCustomDataRegion(region); } void ChartTool::setDataSetLabelDataRegion(DataSet *dataSet, const CellRegion ®ion) { if (!dataSet) return; dataSet->setLabelDataRegion(region); d->shape->update(); d->shape->legend()->update(); } void ChartTool::setDataSetCategoryDataRegion(DataSet *dataSet, const CellRegion ®ion) { if (!dataSet) { return; } if (isCartesian(d->shape->chartType())) { // FIXME: Seems strange the way things are stored in multiple places // Categories are labels on the categories axis dataSet->setCategoryDataRegion(region); // probably should not be stored here, as datasets cannot have individual categories d->shape->plotArea()->proxyModel()->setCategoryDataRegion(region); // this seems to be for odf only!? } else { // Categories are legend texts dataSet->setCategoryDataRegion(region); } d->shape->update(); d->shape->legend()->update(); } void ChartTool::setDataSetChartType(DataSet *dataSet, ChartType type, ChartSubtype subType) { Q_ASSERT(d->shape); Q_ASSERT(dataSet); if (dataSet) { DatasetCommand *cmd = new DatasetCommand(dataSet, d->shape); cmd->setDataSetChartType(type, subType); canvas()->addCommand(cmd); } d->shape->update(); d->shape->legend()->update(); } void ChartTool::setDataSetBrush(DataSet *dataSet, const QColor& color, int section) { Q_ASSERT(d->shape); Q_ASSERT(dataSet || section >= 0); debugChartTool< lst = d->shape->proxyModel()->dataSets(); if (lst.isEmpty()) { return; } // we set brush for section in all datasets KUndo2Command *command = new KUndo2Command(); for (int i = 0; i < lst.count(); ++i) { DatasetCommand *cmd = new DatasetCommand(lst.at(i), d->shape, section, command); cmd->setDataSetBrush(color); command->setText(cmd->text()); } canvas()->addCommand(command); } else { DatasetCommand *command = new DatasetCommand(dataSet, d->shape, section); command->setDataSetBrush(color); canvas()->addCommand(command); } } void ChartTool::setDataSetPen(DataSet *dataSet, const QColor& color, int section) { Q_ASSERT(d->shape); Q_ASSERT(dataSet || section >= 0); debugChartTool< lst = d->shape->proxyModel()->dataSets(); if (lst.isEmpty()) { return; } // we set brush for section in all datasets KUndo2Command *command = new KUndo2Command(); for (int i = 0; i < lst.count(); ++i) { DatasetCommand *cmd = new DatasetCommand(lst.at(i), d->shape, section, command); cmd->setDataSetPen(color); command->setText(cmd->text()); } canvas()->addCommand(command); } else { DatasetCommand *command = new DatasetCommand(dataSet, d->shape, section); command->setDataSetPen(color); canvas()->addCommand(command); } } void ChartTool::setDataSetMarker(DataSet *dataSet, OdfSymbolType type, OdfMarkerStyle style) { Q_ASSERT(d->shape); if (!dataSet) { return; } DatasetCommand *command = new DatasetCommand(dataSet, d->shape); command->setDataSetMarker(type, style); canvas()->addCommand(command); } void ChartTool::setDataSetAxis(DataSet *dataSet, Axis *axis) { Q_ASSERT(d->shape); if (!dataSet || !axis) return; DatasetCommand *command = new DatasetCommand(dataSet, d->shape); command->setDataSetAxis(axis); canvas()->addCommand(command); } void ChartTool::setDataSetShowCategory(DataSet *dataSet, bool b, int section) { Q_ASSERT(d->shape); Q_ASSERT(dataSet || section >= 0); if (!dataSet) { QList lst = d->shape->proxyModel()->dataSets(); if (lst.isEmpty()) { return; } // we set brush for section in all datasets KUndo2Command *command = new KUndo2Command(); for (int i = 0; i < lst.count(); ++i) { DatasetCommand *cmd = new DatasetCommand(lst.at(i), d->shape, section, command); cmd->setDataSetShowCategory(b); command->setText(cmd->text()); } canvas()->addCommand(command); } else { DatasetCommand *command = new DatasetCommand(dataSet, d->shape, section); command->setDataSetShowCategory(b); canvas()->addCommand(command); } debugChartTool<valueLabelType(section).category; } void ChartTool::setDataSetShowNumber(DataSet *dataSet, bool b, int section) { debugChartTool<shape); Q_ASSERT(dataSet || section >= 0); if (!dataSet) { QList lst = d->shape->proxyModel()->dataSets(); if (lst.isEmpty()) { return; } // we set brush for section in all datasets KUndo2Command *command = new KUndo2Command(); for (int i = 0; i < lst.count(); ++i) { DatasetCommand *cmd = new DatasetCommand(lst.at(i), d->shape, section, command); cmd->setDataSetShowNumber(b); command->setText(cmd->text()); } canvas()->addCommand(command); } else { DatasetCommand *command = new DatasetCommand(dataSet, d->shape, section); command->setDataSetShowNumber(b); canvas()->addCommand(command); } debugChartTool<valueLabelType(section).number; } void ChartTool::setDataSetShowPercent(DataSet *dataSet, bool b, int section) { Q_ASSERT(d->shape); Q_ASSERT(dataSet || section >= 0); if (!dataSet) { QList lst = d->shape->proxyModel()->dataSets(); if (lst.isEmpty()) { return; } // we set brush for section in all datasets KUndo2Command *command = new KUndo2Command(); for (int i = 0; i < lst.count(); ++i) { DatasetCommand *cmd = new DatasetCommand(lst.at(i), d->shape, section, command); cmd->setDataSetShowPercent(b); command->setText(cmd->text()); } canvas()->addCommand(command); } else { DatasetCommand *command = new DatasetCommand(dataSet, d->shape, section); command->setDataSetShowPercent(b); canvas()->addCommand(command); } debugChartTool<valueLabelType(section).percentage; } void ChartTool::setDataSetShowSymbol(DataSet *dataSet, bool b, int section) { Q_ASSERT(d->shape); Q_ASSERT(dataSet || section >= 0); if (!dataSet) { QList lst = d->shape->proxyModel()->dataSets(); if (lst.isEmpty()) { return; } // we set brush for section in all datasets KUndo2Command *command = new KUndo2Command(); for (int i = 0; i < lst.count(); ++i) { DatasetCommand *cmd = new DatasetCommand(lst.at(i), d->shape, section, command); cmd->setDataSetShowSymbol(b); command->setText(cmd->text()); } canvas()->addCommand(command); } else { DatasetCommand *command = new DatasetCommand(dataSet, d->shape, section); command->setDataSetShowSymbol(b); canvas()->addCommand(command); } debugChartTool<valueLabelType(section).symbol; } void ChartTool::setThreeDMode(bool threeD) { Q_ASSERT(d->shape); if (!d->shape) return; d->shape->setThreeD(threeD); d->shape->update(); } void ChartTool::setShowTitle(bool show) { Q_ASSERT(d->shape); if (!d->shape) return; ChartTextShapeCommand *command = new ChartTextShapeCommand(d->shape->title(), d->shape, show); canvas()->addCommand(command); } void ChartTool::setTitlePositioning(int index) { Q_ASSERT(d->shape); if (!d->shape) { return; } // TODD: undo command d->shape->title()->setAdditionalStyleAttribute("chart:auto-position", index == 0 ? "true" : "false"); d->shape->layout()->scheduleRelayout(); d->shape->layout()->layout(); d->shape->update(); } void ChartTool::setTitleResize(int index) { Q_ASSERT(d->shape); if (!d->shape) { return; } // TODD: undo command TextLabelData *labelData = dynamic_cast(d->shape->title()->userData()); if (labelData == 0) { return; } labelData->setResizeMethod(index == 0 ? KoTextShapeDataBase::AutoResize : KoTextShapeDataBase::NoResize); d->shape->layout()->scheduleRelayout(); d->shape->layout()->layout(); d->shape->update(); } void ChartTool::setShowSubTitle(bool show) { Q_ASSERT(d->shape); if (!d->shape) return; ChartTextShapeCommand *command = new ChartTextShapeCommand(d->shape->subTitle(), d->shape, show); canvas()->addCommand(command); } void ChartTool::setSubTitlePositioning(int index) { Q_ASSERT(d->shape); if (!d->shape) { return; } // TODD: undo command d->shape->subTitle()->setAdditionalStyleAttribute("chart:auto-position", index == 0 ? "true" : "false"); d->shape->layout()->scheduleRelayout(); d->shape->layout()->layout(); d->shape->update(); } void ChartTool::setSubTitleResize(int index) { Q_ASSERT(d->shape); if (!d->shape) { return; } // TODD: undo command TextLabelData *labelData = dynamic_cast(d->shape->subTitle()->userData()); if (labelData == 0) { return; } labelData->setResizeMethod(index == 0 ? KoTextShapeDataBase::AutoResize : KoTextShapeDataBase::NoResize); d->shape->layout()->scheduleRelayout(); d->shape->layout()->layout(); d->shape->update(); } void ChartTool::setShowFooter(bool show) { Q_ASSERT(d->shape); if (!d->shape) return; ChartTextShapeCommand *command = new ChartTextShapeCommand(d->shape->footer(), d->shape, show); canvas()->addCommand(command); } void ChartTool::setFooterPositioning(int index) { Q_ASSERT(d->shape); if (!d->shape) { return; } // TODD: undo command d->shape->footer()->setAdditionalStyleAttribute("chart:auto-position", index == 0 ? "true" : "false"); d->shape->layout()->scheduleRelayout(); d->shape->layout()->layout(); d->shape->update(); } void ChartTool::setFooterResize(int index) { Q_ASSERT(d->shape); if (!d->shape) { return; } // TODD: undo command TextLabelData *labelData = dynamic_cast(d->shape->footer()->userData()); if (labelData == 0) { return; } labelData->setResizeMethod(index == 0 ? KoTextShapeDataBase::AutoResize : KoTextShapeDataBase::NoResize); d->shape->layout()->scheduleRelayout(); d->shape->layout()->layout(); d->shape->update(); } void ChartTool::setDataDirection(Qt::Orientation direction) { Q_ASSERT(d->shape); if (!d->shape) return; d->shape->proxyModel()->setDataDirection(direction); d->shape->relayout(); } void ChartTool::setChartOrientation(Qt::Orientation direction) { Q_ASSERT(d->shape); if (!d->shape) { return; } PlotAreaCommand *command = new PlotAreaCommand(d->shape->plotArea()); command->setOrientation(direction); canvas()->addCommand(command); } void ChartTool::setLegendTitle(const QString &title) { Q_ASSERT(d->shape); Q_ASSERT(d->shape->legend()); LegendCommand *command = new LegendCommand(d->shape->legend()); command->setLegendTitle(title); canvas()->addCommand(command); } void ChartTool::setLegendFont(const QFont &font) { Q_ASSERT(d->shape); Q_ASSERT(d->shape->legend()); // There only is a general font, for the legend items and the legend title LegendCommand *command = new LegendCommand(d->shape->legend()); command->setLegendFont(font); canvas()->addCommand(command); } void ChartTool::setLegendFontSize(int size) { Q_ASSERT(d->shape); Q_ASSERT(d->shape->legend()); LegendCommand *command = new LegendCommand(d->shape->legend()); command->setLegendFontSize(size); canvas()->addCommand(command); } void ChartTool::setLegendOrientation(Qt::Orientation orientation) { Q_ASSERT(d->shape); Q_ASSERT(d->shape->legend()); LegendCommand *command = new LegendCommand(d->shape->legend()); command->setLegendExpansion(QtOrientationToLegendExpansion(orientation)); canvas()->addCommand(command); } void ChartTool::setLegendPosition(Position pos) { Q_ASSERT(d->shape); Q_ASSERT(d->shape->legend()); // TODO undo command d->shape->legend()->setLegendPosition(pos); d->shape->legend()->update(); d->shape->layout()->scheduleRelayout(); d->shape->layout()->layout(); } void ChartTool::setLegendAlignment(Qt::Alignment alignment) { Q_ASSERT(d->shape); Q_ASSERT(d->shape->legend()); // TODO undo command d->shape->legend()->setAlignment(alignment); d->shape->legend()->update(); d->shape->layout()->scheduleRelayout(); d->shape->layout()->layout(); } void ChartTool::addAxis(AxisDimension dimension, const QString& title) { Q_ASSERT(d->shape); Axis *axis = new Axis(d->shape->plotArea(), dimension); // automatically adds axis to plot area if (axis == d->shape->plotArea()->secondaryYAxis()) { - axis->title()->rotate(90); axis->setOdfAxisPosition("end"); // right } else if (axis == d->shape->plotArea()->secondaryXAxis()) { axis->setOdfAxisPosition("end"); // top axis->updateKChartAxisPosition(); } d->shape->plotArea()->takeAxis(axis); // so we remove it again, sigh axis->setTitleText(title); AddRemoveAxisCommand *command = new AddRemoveAxisCommand(axis, d->shape, true, canvas()->shapeManager()); canvas()->addCommand(command); } void ChartTool::removeAxis(Axis *axis) { Q_ASSERT(d->shape); AddRemoveAxisCommand *command = new AddRemoveAxisCommand(axis, d->shape, false, canvas()->shapeManager()); canvas()->addCommand(command); } void ChartTool::setAxisShowTitle(Axis *axis, bool show) { Q_ASSERT(d->shape); if (show && axis->titleText().isEmpty()) { axis->setTitleText(i18n("Axistitle")); } AxisCommand *command = new AxisCommand(axis, d->shape); command->setAxisShowTitle(show); canvas()->addCommand(command); } void ChartTool::setShowAxis(Axis *axis, bool show) { Q_ASSERT(d->shape); debugChartTool<shape); command->setShowAxis(show); canvas()->addCommand(command); } void ChartTool::setAxisPosition(Axis *axis, const QString &pos) { Q_ASSERT(d->shape); debugChartTool<shape); command->setAxisPosition(pos); canvas()->addCommand(command); } void ChartTool::setAxisLabelsPosition(Axis *axis, const QString &pos) { Q_ASSERT(d->shape); debugChartTool<shape); command->setAxisLabelsPosition(pos); canvas()->addCommand(command); } void ChartTool::setAxisShowLabels(Axis *axis, bool b) { Q_ASSERT(d->shape); AxisCommand *command = new AxisCommand(axis, d->shape); command->setAxisShowLabels(b); canvas()->addCommand(command); } void ChartTool::setAxisShowMajorGridLines(Axis *axis, bool b) { Q_ASSERT(d->shape); AxisCommand *command = new AxisCommand(axis, d->shape); command->setAxisShowMajorGridLines(b); canvas()->addCommand(command); } void ChartTool::setAxisShowMinorGridLines(Axis *axis, bool b) { Q_ASSERT(d->shape); AxisCommand *command = new AxisCommand(axis, d->shape); command->setAxisShowMinorGridLines(b); canvas()->addCommand(command); } void ChartTool::setAxisUseLogarithmicScaling(Axis *axis, bool b) { Q_ASSERT(d->shape); AxisCommand *command = new AxisCommand(axis, d->shape); command->setAxisUseLogarithmicScaling(b); canvas()->addCommand(command); } void ChartTool::setAxisStepWidth(Axis *axis, qreal width) { Q_ASSERT(d->shape); AxisCommand *command = new AxisCommand(axis, d->shape); command->setAxisStepWidth(width); canvas()->addCommand(command); } void ChartTool::setAxisSubStepWidth(Axis *axis, qreal width) { Q_ASSERT(d->shape); AxisCommand *command = new AxisCommand(axis, d->shape); command->setAxisSubStepWidth(width); canvas()->addCommand(command); } void ChartTool::setAxisUseAutomaticStepWidth(Axis *axis, bool automatic) { Q_ASSERT(d->shape); AxisCommand *command = new AxisCommand(axis, d->shape); command->setAxisUseAutomaticStepWidth(automatic); canvas()->addCommand(command); } void ChartTool::setAxisUseAutomaticSubStepWidth(Axis *axis, bool automatic) { Q_ASSERT(d->shape); AxisCommand *command = new AxisCommand(axis, d->shape); command->setAxisUseAutomaticSubStepWidth(automatic); canvas()->addCommand(command); } void ChartTool::setAxisLabelsFont(Axis *axis, const QFont &font) { Q_ASSERT(d->shape); AxisCommand *command = new AxisCommand(axis, d->shape); command->setAxisLabelsFont(font); canvas()->addCommand(command); } void ChartTool::setGapBetweenBars(Axis *axis, int percent) { Q_ASSERT(d->shape); debugChartTool<shape); command->setGapBetweenBars(percent); canvas()->addCommand(command); } void ChartTool::setGapBetweenSets(Axis *axis, int percent) { Q_ASSERT(d->shape); debugChartTool<shape); command->setGapBetweenSets(percent); canvas()->addCommand(command); } void ChartTool::setPieExplodeFactor(DataSet *dataSet, int section, int percent) { Q_ASSERT(d->shape); dataSet->setPieExplodeFactor(section, percent); d->shape->update(); } void ChartTool::setShowLegend(bool show) { Q_ASSERT(d->shape); ChartTextShapeCommand *command = new ChartTextShapeCommand(d->shape->legend(), d->shape, show); if (show) { command->setText(kundo2_i18n("Show Legend")); } else { command->setText(kundo2_i18n("Hide Legend")); } canvas()->addCommand(command); } diff --git a/plugins/chartshape/PlotArea.cpp b/plugins/chartshape/PlotArea.cpp index cce7630a419..b4b08d97ccb 100644 --- a/plugins/chartshape/PlotArea.cpp +++ b/plugins/chartshape/PlotArea.cpp @@ -1,1432 +1,1429 @@ /* This file is part of the KDE project Copyright 2007-2008 Johannes Simon Copyright 2009-2010 Inge Wallin Copyright 2018 Dag Andersen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Own #include "PlotArea.h" // Qt #include #include #include #include #include #include // Calligra #include #include #include #include #include #include #include #include #include #include #include #include #include // KChart #include #include #include #include #include #include #include #include #include // Attribute Classes #include #include #include #include #include // Diagram Classes #include #include #include #include #include // KoChart #include "Legend.h" #include "Surface.h" #include "Axis.h" #include "DataSet.h" #include "ChartProxyModel.h" #include "ScreenConversions.h" #include "ChartLayout.h" #include "ChartDebug.h" using namespace KoChart; const int MAX_PIXMAP_SIZE = 1000; Q_DECLARE_METATYPE(QPointer) typedef QList CoordinatePlaneList; class PlotArea::Private { public: Private(PlotArea *q, ChartShape *parent); ~Private(); void initAxes(); void updateAxesPosition(); CoordinatePlaneList coordinatePlanesForChartType(ChartType type); void autoHideAxisTitles(); PlotArea *q; // The parent chart shape ChartShape *shape; // ---------------------------------------------------------------- // Parts and properties of the chart ChartType chartType; ChartSubtype chartSubtype; Surface *wall; Surface *floor; // Only used in 3D charts // The axes QList axes; QList automaticallyHiddenAxisTitles; // 3D properties bool threeD; Ko3dScene *threeDScene; // ---------------------------------------------------------------- // Data specific to each chart type // 1. Bar charts // FIXME: OpenOffice stores these attributes in the axes' elements. // The specs don't say anything at all about what elements can have // these style attributes. // chart:vertical attribute: see ODF v1.2,19.63 bool vertical; // 2. Polar charts (pie/ring) qreal angleOffset; // in degrees qreal holeSize; // ---------------------------------------------------------------- // The embedded KD Chart // The KD Chart parts KChart::Chart *const kdChart; KChart::CartesianCoordinatePlane *const kdCartesianPlanePrimary; KChart::CartesianCoordinatePlane *const kdCartesianPlaneSecondary; KChart::PolarCoordinatePlane *const kdPolarPlane; KChart::RadarCoordinatePlane *const kdRadarPlane; QList kdDiagrams; // Caching: We can rerender faster if we cache KChart's output QImage image; bool paintPixmap; QPointF lastZoomLevel; QSizeF lastSize; mutable bool pixmapRepaintRequested; QPen stockRangeLinePen; QBrush stockGainBrush; QBrush stockLossBrush; QString symbolType; QString symbolName; DataSet::ValueLabelType valueLabelType; }; PlotArea::Private::Private(PlotArea *q, ChartShape *parent) : q(q) , shape(parent) // Default type: normal bar chart , chartType(BarChartType) , chartSubtype(NormalChartSubtype) , wall(0) , floor(0) , threeD(false) , threeDScene(0) // By default, x and y axes are not swapped. , vertical(false) // OpenOffice.org's default. It means the first pie slice starts at the // very top (and then going counter-clockwise). , angleOffset(90.0) , holeSize(50.0) // KCharts approx default // KD Chart stuff , kdChart(new KChart::Chart()) , kdCartesianPlanePrimary(new KChart::CartesianCoordinatePlane(kdChart)) , kdCartesianPlaneSecondary(new KChart::CartesianCoordinatePlane(kdChart)) , kdPolarPlane(new KChart::PolarCoordinatePlane(kdChart)) , kdRadarPlane(new KChart::RadarCoordinatePlane(kdChart)) // Cache , paintPixmap(true) , pixmapRepaintRequested(true) , symbolType("automatic") { kdCartesianPlanePrimary->setObjectName("primary"); kdCartesianPlaneSecondary->setObjectName("secondary"); // --- Prepare Primary Cartesian Coordinate Plane --- KChart::GridAttributes gridAttributes; gridAttributes.setGridVisible(false); gridAttributes.setGridGranularitySequence(KChartEnums::GranularitySequence_10_50); kdCartesianPlanePrimary->setGlobalGridAttributes(gridAttributes); // --- Prepare Secondary Cartesian Coordinate Plane --- kdCartesianPlaneSecondary->setGlobalGridAttributes(gridAttributes); // --- Prepare Polar Coordinate Plane --- KChart::GridAttributes polarGridAttributes; polarGridAttributes.setGridVisible(false); kdPolarPlane->setGlobalGridAttributes(polarGridAttributes); // --- Prepare Radar Coordinate Plane --- KChart::GridAttributes radarGridAttributes; polarGridAttributes.setGridVisible(true); kdRadarPlane->setGlobalGridAttributes(radarGridAttributes); // By default we use a cartesian chart (bar chart), so the polar planes // are not needed yet. They will be added on demand in setChartType(). kdChart->takeCoordinatePlane(kdPolarPlane); kdChart->takeCoordinatePlane(kdRadarPlane); shape->proxyModel()->setDataDimensions(1); stockRangeLinePen.setWidthF(2.0); stockGainBrush = QBrush(QColor(Qt::white)); stockLossBrush = QBrush(QColor(Qt::black)); } PlotArea::Private::~Private() { // remove first to avoid crash while (!kdChart->coordinatePlanes().isEmpty()) { kdChart->takeCoordinatePlane(kdChart->coordinatePlanes().last()); } qDeleteAll(axes); delete kdCartesianPlanePrimary; delete kdCartesianPlaneSecondary; delete kdPolarPlane; delete kdRadarPlane; delete kdChart; delete wall; delete floor; delete threeDScene; } void PlotArea::Private::initAxes() { // The category data region is anchored to an axis and will be set on addAxis if the // axis defines the Axis::categoryDataRegion(). So, clear it now. q->proxyModel()->setCategoryDataRegion(CellRegion()); // Remove all old axes while(!axes.isEmpty()) { Axis *axis = axes.takeLast(); Q_ASSERT(axis); if (axis->title()) automaticallyHiddenAxisTitles.removeAll(axis->title()); delete axis; } // There need to be at least these two axes. Their constructor will // automatically add them to the plot area as child shape. new Axis(q, XAxisDimension); Axis *yAxis = new Axis(q, YAxisDimension); yAxis->setShowMajorGrid(true); - yAxis->title()->rotate(-90); updateAxesPosition(); } void PlotArea::Private::updateAxesPosition() { debugChartAxis<updateKChartAxisPosition(); } } PlotArea::PlotArea(ChartShape *parent) : QObject() , KoShape() , d(new Private(this, parent)) { setShapeId("ChartShapePlotArea"); // NB! used by defaulttool/ChartResizeStrategy.cpp Q_ASSERT(d->shape); Q_ASSERT(d->shape->proxyModel()); setAdditionalStyleAttribute("chart:auto-position", "true"); setAdditionalStyleAttribute("chart:auto-size", "true"); connect(d->shape->proxyModel(), SIGNAL(modelReset()), this, SLOT(proxyModelStructureChanged())); connect(d->shape->proxyModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(proxyModelStructureChanged())); connect(d->shape->proxyModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(proxyModelStructureChanged())); connect(d->shape->proxyModel(), SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(proxyModelStructureChanged())); connect(d->shape->proxyModel(), SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(proxyModelStructureChanged())); connect(d->shape->proxyModel(), SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(plotAreaUpdate())); connect(d->shape->proxyModel(), SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(plotAreaUpdate())); connect(d->shape->proxyModel(), SIGNAL(dataChanged()), this, SLOT(plotAreaUpdate())); } PlotArea::~PlotArea() { delete d; } void PlotArea::plotAreaInit() { d->kdChart->resize(size().toSize()); d->kdChart->replaceCoordinatePlane(d->kdCartesianPlanePrimary); d->kdCartesianPlaneSecondary->setReferenceCoordinatePlane(d->kdCartesianPlanePrimary); d->kdChart->addCoordinatePlane(d->kdCartesianPlaneSecondary); KChart::FrameAttributes attr = d->kdChart->frameAttributes(); attr.setVisible(false); d->kdChart->setFrameAttributes(attr); d->wall = new Surface(this); //d->floor = new Surface(this); d->initAxes(); addAxesTitlesToLayout(); } void PlotArea::proxyModelStructureChanged() { if (proxyModel()->isLoading()) return; Q_ASSERT(xAxis()); Q_ASSERT(yAxis()); QMap attachedAxes; QList dataSets = proxyModel()->dataSets(); // Remember to what y axis each data set belongs foreach(DataSet *dataSet, dataSets) attachedAxes.insert(dataSet, dataSet->attachedAxis()); // Proxy structure and thus data sets changed, drop old state and // clear all axes of data sets foreach(Axis *axis, axes()) axis->clearDataSets(); // Now add the new list of data sets to the axis they belong to foreach(DataSet *dataSet, dataSets) { xAxis()->attachDataSet(dataSet); // If they weren't assigned to a y axis before, use default y axis if (attachedAxes[dataSet]) attachedAxes[dataSet]->attachDataSet(dataSet); else yAxis()->attachDataSet(dataSet); } } ChartProxyModel *PlotArea::proxyModel() const { return d->shape->proxyModel(); } QList PlotArea::axes() const { return d->axes; } QList PlotArea::dataSets() const { return proxyModel()->dataSets(); } Axis *PlotArea::xAxis() const { foreach(Axis *axis, d->axes) { if (axis->dimension() == XAxisDimension) return axis; } return 0; } Axis *PlotArea::yAxis() const { foreach(Axis *axis, d->axes) { if (axis->dimension() == YAxisDimension) return axis; } return 0; } Axis *PlotArea::secondaryXAxis() const { bool firstXAxisFound = false; foreach(Axis *axis, d->axes) { if (axis->dimension() == XAxisDimension) { if (firstXAxisFound) return axis; else firstXAxisFound = true; } } return 0; } Axis *PlotArea::secondaryYAxis() const { bool firstYAxisFound = false; foreach(Axis *axis, d->axes) { if (axis->dimension() == YAxisDimension) { if (firstYAxisFound) return axis; else firstYAxisFound = true; } } return 0; } ChartType PlotArea::chartType() const { return d->chartType; } ChartSubtype PlotArea::chartSubType() const { return d->chartSubtype; } bool PlotArea::isThreeD() const { return d->threeD; } bool PlotArea::isVertical() const { return d->chartType == BarChartType && d->vertical; } Ko3dScene *PlotArea::threeDScene() const { return d->threeDScene; } qreal PlotArea::angleOffset() const { return d->angleOffset; } qreal PlotArea::holeSize() const { return d->holeSize; } void PlotArea::setHoleSize(qreal value) { d->holeSize = value; } // FIXME: this should add the axxis as a child (set axis->parent()) bool PlotArea::addAxis(Axis *axis) { if (d->axes.contains(axis)) { warnChart << "PlotArea::addAxis(): Trying to add already added axis."; return false; } if (!axis) { warnChart << "PlotArea::addAxis(): Pointer to axis is NULL!"; return false; } d->axes.append(axis); if (axis->dimension() == XAxisDimension) { // let each axis know about the other axis foreach (Axis *_axis, d->axes) { if (_axis->isVisible()) _axis->registerAxis(axis); } } requestRepaint(); return true; } bool PlotArea::removeAxis(Axis *axis) { bool removed = takeAxis(axis); if (removed) { // This also removes the axis' title, which is a shape as well delete axis; } return removed; } // FIXME: this should remove the axis as a child (set axis->parent()) bool PlotArea::takeAxis(Axis *axis) { if (!d->axes.contains(axis)) { warnChart << "PlotArea::takeAxis(): Trying to remove non-added axis."; return false; } if (!axis) { warnChart << "PlotArea::takeAxis(): Pointer to axis is NULL!"; return false; } if (axis->title()) { d->automaticallyHiddenAxisTitles.removeAll(axis->title()); } d->axes.removeAll(axis); axis->removeAxisFromDiagrams(true); requestRepaint(); return true; } CoordinatePlaneList PlotArea::Private::coordinatePlanesForChartType(ChartType type) { CoordinatePlaneList result; switch (type) { case BarChartType: case LineChartType: case AreaChartType: case ScatterChartType: case GanttChartType: case SurfaceChartType: case StockChartType: case BubbleChartType: result.append(kdCartesianPlanePrimary); result.append(kdCartesianPlaneSecondary); break; case CircleChartType: case RingChartType: result.append(kdPolarPlane); break; case RadarChartType: case FilledRadarChartType: result.append(kdRadarPlane); break; case LastChartType: Q_ASSERT("There's no coordinate plane for LastChartType"); break; } Q_ASSERT(!result.isEmpty()); return result; } void PlotArea::Private::autoHideAxisTitles() { automaticallyHiddenAxisTitles.clear(); foreach (Axis *axis, axes) { if (axis->title()->isVisible()) { axis->title()->setVisible(false); automaticallyHiddenAxisTitles.append(axis->title()); } } } void PlotArea::setChartType(ChartType type) { if (d->chartType == type) return; // Lots of things to do if the old and new types of coordinate // systems don't match. if (!isPolar(d->chartType) && isPolar(type)) { d->autoHideAxisTitles(); } else if (isPolar(d->chartType) && !isPolar(type)) { foreach (KoShape *title, d->automaticallyHiddenAxisTitles) { title->setVisible(true); } d->automaticallyHiddenAxisTitles.clear(); } CellRegion region = d->shape->proxyModel()->cellRangeAddress(); if (type == CircleChartType || type == RingChartType) { d->shape->proxyModel()->setManualControl(false); xAxis()->clearDataSets(); yAxis()->clearDataSets(); if (secondaryYAxis()) { secondaryYAxis()->clearDataSets(); } if (secondaryXAxis()) { secondaryXAxis()->clearDataSets(); } } CoordinatePlaneList planesToRemove; // First remove secondary cartesian plane as it references the primary // plane, otherwise KChart will come down crashing on us. Note that // removing a plane that's not in the chart is not a problem. planesToRemove << d->kdCartesianPlaneSecondary << d->kdCartesianPlanePrimary << d->kdPolarPlane << d->kdRadarPlane; foreach(KChart::AbstractCoordinatePlane *plane, planesToRemove) d->kdChart->takeCoordinatePlane(plane); CoordinatePlaneList newPlanes = d->coordinatePlanesForChartType(type); foreach(KChart::AbstractCoordinatePlane *plane, newPlanes) d->kdChart->addCoordinatePlane(plane); Q_ASSERT(d->kdChart->coordinatePlanes() == newPlanes); d->chartType = type; foreach (Axis *axis, d->axes) { axis->plotAreaChartTypeChanged(type); } if (type == CircleChartType || type == RingChartType) { d->shape->proxyModel()->reset(region); } if (type != BarChartType) { setVertical(false); // Only supported by bar charts } requestRepaint(); } void PlotArea::setChartSubType(ChartSubtype subType) { d->chartSubtype = subType; foreach (Axis *axis, d->axes) { axis->plotAreaChartSubTypeChanged(subType); } } void PlotArea::setThreeD(bool threeD) { d->threeD = threeD; foreach(Axis *axis, d->axes) axis->setThreeD(threeD); requestRepaint(); } void PlotArea::setVertical(bool vertical) { d->vertical = vertical; foreach(Axis *axis, d->axes) axis->plotAreaIsVerticalChanged(); } // ---------------------------------------------------------------- // loading and saving bool PlotArea::loadOdf(const KoXmlElement &plotAreaElement, KoShapeLoadingContext &context) { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); KoOdfStylesReader &stylesReader = context.odfLoadingContext().stylesReader(); // The exact position defined in ODF overwrites the default layout position // NOTE: Do not do this as it means functionallity changes just because you save and load. // I don't think odf has an element/attribute that can hold this type of info. // Also afaics libreoffice do not do this. // if (plotAreaElement.hasAttributeNS(KoXmlNS::svg, "x") || // plotAreaElement.hasAttributeNS(KoXmlNS::svg, "y") || // plotAreaElement.hasAttributeNS(KoXmlNS::svg, "width") || // plotAreaElement.hasAttributeNS(KoXmlNS::svg, "height")) // { // parent()->layout()->setPosition(this, FloatingPosition); // } bool autoPosition = !(plotAreaElement.hasAttributeNS(KoXmlNS::svg, "x") && plotAreaElement.hasAttributeNS(KoXmlNS::svg, "y")); bool autoSize = !(plotAreaElement.hasAttributeNS(KoXmlNS::svg, "width") && plotAreaElement.hasAttributeNS(KoXmlNS::svg, "height")); context.odfLoadingContext().fillStyleStack(plotAreaElement, KoXmlNS::chart, "style-name", "chart"); loadOdfAttributes(plotAreaElement, context, OdfAllAttributes); // First step is to clear all old axis instances. while (!d->axes.isEmpty()) { Axis *axis = d->axes.takeLast(); Q_ASSERT(axis); // Clear this axis of all data sets, deleting any diagram associated with it. axis->clearDataSets(); if (axis->title()) d->automaticallyHiddenAxisTitles.removeAll(axis->title()); delete axis; } // Now find out about things that are in the plotarea style. // // These things include chart subtype, special things for some // chart types like line charts, stock charts, etc. // // Note that this has to happen BEFORE we create a axis and call // there loadOdf method cause the axis will evaluate settings // like the PlotArea::isVertical boolean. bool candleStick = false; if (plotAreaElement.hasAttributeNS(KoXmlNS::chart, "style-name")) { styleStack.clear(); context.odfLoadingContext().fillStyleStack(plotAreaElement, KoXmlNS::chart, "style-name", "chart"); styleStack.setTypeProperties("graphic"); styleStack.setTypeProperties("chart"); if (styleStack.hasProperty(KoXmlNS::chart, "auto-position")) { autoPosition |= styleStack.property(KoXmlNS::chart, "auto-position") == "true"; } else { // To be backwards compatible we set auto-position to true as this was the original behaviour // and is the way LO works autoPosition = true; } if (styleStack.hasProperty(KoXmlNS::chart, "auto-size")) { autoSize |= styleStack.property(KoXmlNS::chart, "auto-size") == "true" ; } else { // To be backwards compatible we set auto-size to true as this was the original behaviour // and is the way LO works autoSize = true; } // ring and pie if (styleStack.hasProperty(KoXmlNS::chart, "angle-offset")) { bool ok; const qreal angleOffset = styleStack.property(KoXmlNS::chart, "angle-offset").toDouble(&ok); if (ok) { setAngleOffset(angleOffset); } } // ring if (styleStack.hasProperty(KoXmlNS::chart, "hole-size")) { bool ok; const qreal value = styleStack.property(KoXmlNS::chart, "hole-size").toDouble(&ok); if (ok) { setHoleSize(value); } } // Check for 3D. if (styleStack.hasProperty(KoXmlNS::chart, "three-dimensional")) setThreeD(styleStack.property(KoXmlNS::chart, "three-dimensional") == "true"); d->threeDScene = load3dScene(plotAreaElement); // Set subtypes stacked or percent. // These are valid for Bar, Line, Area and Radar types. if (styleStack.hasProperty(KoXmlNS::chart, "percentage") && styleStack.property(KoXmlNS::chart, "percentage") == "true") { setChartSubType(PercentChartSubtype); } else if (styleStack.hasProperty(KoXmlNS::chart, "stacked") && styleStack.property(KoXmlNS::chart, "stacked") == "true") { setChartSubType(StackedChartSubtype); } // Data specific to bar charts if (styleStack.hasProperty(KoXmlNS::chart, "vertical")) setVertical(styleStack.property(KoXmlNS::chart, "vertical") == "true"); // Data specific to stock charts if (styleStack.hasProperty(KoXmlNS::chart, "japanese-candle-stick")) { candleStick = styleStack.property(KoXmlNS::chart, "japanese-candle-stick") == "true"; } // Special properties for various chart types #if 0 switch () { case BarChartType: if (styleStack) ; } #endif styleStack.clear(); context.odfLoadingContext().fillStyleStack(plotAreaElement, KoXmlNS::chart, "style-name", "chart"); } setAdditionalStyleAttribute("chart:auto-position", autoPosition ? "true" : "false"); setAdditionalStyleAttribute("chart:auto-size", autoSize ? "true" : "false"); // Now create and load the axis from the ODF. This needs to happen // AFTER we did set some of the basic settings above so the axis // can use those basic settings to evaluate it's own settings // depending on them. This is especially required for the // PlotArea::isVertical() boolean flag else things will go wrong. KoXmlElement n; forEachElement (n, plotAreaElement) { if (n.namespaceURI() != KoXmlNS::chart) continue; if (n.localName() == "axis") { if (!n.hasAttributeNS(KoXmlNS::chart, "dimension")) // We have to know what dimension the axis is supposed to be.. continue; const QString dimension = n.attributeNS(KoXmlNS::chart, "dimension", QString()); AxisDimension dim; if (dimension == "x") dim = XAxisDimension; else if (dimension == "y") dim = YAxisDimension; else if (dimension == "z") dim = ZAxisDimension; else continue; Axis *axis = new Axis(this, dim); if (dim == YAxisDimension) { if (axis == yAxis()) { - axis->title()->rotate(-90); } else if (axis == secondaryYAxis()) { - axis->title()->rotate(90); } } axis->loadOdf(n, context); } } // Two axes are mandatory, check that we have them. if (!xAxis()) { Axis *xAxis = new Axis(this, XAxisDimension); xAxis->setVisible(false); } if (!yAxis()) { Axis *yAxis = new Axis(this, YAxisDimension); yAxis->setVisible(false); } // Now, after the axes, load the datasets. // Note that this only contains properties of the datasets, the // actual data is not stored here. // // FIXME: Isn't the proxy model a strange place to store this data? proxyModel()->loadOdf(plotAreaElement, context, d->chartType); // Now load the surfaces (wall and possibly floor) // FIXME: Use named tags instead of looping? forEachElement (n, plotAreaElement) { if (n.namespaceURI() != KoXmlNS::chart) continue; if (n.localName() == "wall") { d->wall->loadOdf(n, context); } else if (n.localName() == "floor") { // The floor is not always present, so allocate it if needed. // FIXME: Load floor, even if we don't really support it yet // and save it back to ODF. //if (!d->floor) // d->floor = new Surface(this); //d->floor->loadOdf(n, context); } else if (n.localName() == "stock-gain-marker") { styleStack.clear(); context.odfLoadingContext().fillStyleStack(n, KoXmlNS::chart, "style-name", "chart"); styleStack.setTypeProperties("graphic"); if (styleStack.hasProperty(KoXmlNS::draw, "fill")) { d->stockGainBrush = KoOdfGraphicStyles::loadOdfFillStyle(styleStack, styleStack.property(KoXmlNS::draw, "fill"), stylesReader); debugChartOdf<stockGainBrush; } else { warnChartOdf<stockLossBrush = KoOdfGraphicStyles::loadOdfFillStyle(styleStack, styleStack.property(KoXmlNS::draw, "fill"), stylesReader); debugChartOdf<stockLossBrush; } else { warnChartOdf<stockRangeLinePen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, styleStack.property(KoXmlNS::draw, "stroke"), stylesReader); debugChartOdf<stockRangeLinePen; } else { warnChartOdf<chartType == StockChartType) { // The number of data sets determines stock chart subtype if (proxyModel()->rowCount() > 3) { if (candleStick) { setChartSubType(CandlestickChartSubtype); } else { setChartSubType(OpenHighLowCloseChartSubtype); } } } // Connect axes to datasets and cleanup foreach(DataSet *ds, d->shape->proxyModel()->dataSets()) { foreach(Axis *axis, d->axes) { if (axis->name() == ds->axisName()) { axis->attachDataSet(ds); } } } debugChartOdf<chartType<chartSubtype<axes; if (isPolar(d->chartType)) { d->autoHideAxisTitles(); } foreach(Axis *axis, d->axes) { axis->setName(QString()); } // update kchart axis position for all axes d->updateAxesPosition(); // add axes titles to layout addAxesTitlesToLayout(); return true; } void PlotArea::saveOdf(KoShapeSavingContext &context) const { KoXmlWriter &bodyWriter = context.xmlWriter(); //KoGenStyles &mainStyles = context.mainStyles(); bodyWriter.startElement("chart:plot-area"); KoGenStyle plotAreaStyle(KoGenStyle::ChartAutoStyle, "chart"); // Data direction const Qt::Orientation direction = proxyModel()->dataDirection(); plotAreaStyle.addProperty("chart:series-source", (direction == Qt::Horizontal) ? "rows" : "columns"); // Save chart subtype saveOdfSubType(bodyWriter, plotAreaStyle); // Save extra stuff (like auto-position) QMap::const_iterator it(additionalStyleAttributes().constBegin()); for (; it != additionalStyleAttributes().constEnd(); ++it) { plotAreaStyle.addProperty(it.key(), it.value(), KoGenStyle::ChartType); } // save graphic-properties and insert style bodyWriter.addAttribute("chart:style-name", saveStyle(plotAreaStyle, context)); const QSizeF s(size()); const QPointF p(position()); bodyWriter.addAttributePt("svg:width", s.width()); bodyWriter.addAttributePt("svg:height", s.height()); bodyWriter.addAttributePt("svg:x", p.x()); bodyWriter.addAttributePt("svg:y", p.y()); CellRegion cellRangeAddress = d->shape->proxyModel()->cellRangeAddress(); bodyWriter.addAttribute("table:cell-range-address", cellRangeAddress.toString()); // About the data: // Save if the first row / column contain headers. QString dataSourceHasLabels; if (proxyModel()->firstRowIsLabel()) { if (proxyModel()->firstColumnIsLabel()) dataSourceHasLabels = "both"; else dataSourceHasLabels = "row"; } else { if (proxyModel()->firstColumnIsLabel()) dataSourceHasLabels = "column"; else dataSourceHasLabels = "none"; } // Note: this is saved in the plotarea attributes and not the style. bodyWriter.addAttribute("chart:data-source-has-labels", dataSourceHasLabels); if (d->threeDScene) { d->threeDScene->saveOdfAttributes(bodyWriter); } if (d->chartType == StockChartType) { QString styleName; bodyWriter.startElement("chart:stock-gain-marker"); KoGenStyle stockGainStyle(KoGenStyle::ChartAutoStyle, "chart"); KoOdfGraphicStyles::saveOdfFillStyle(stockGainStyle, context.mainStyles(), d->stockGainBrush); styleName = context.mainStyles().insert(stockGainStyle, "ch"); bodyWriter.addAttribute("chart:style-name", styleName); bodyWriter.endElement(); // chart:stock-gain-marker bodyWriter.startElement("chart:stock-loss-marker"); KoGenStyle stockLossStyle(KoGenStyle::ChartAutoStyle, "chart"); KoOdfGraphicStyles::saveOdfFillStyle(stockLossStyle, context.mainStyles(), d->stockLossBrush); styleName = context.mainStyles().insert(stockLossStyle, "ch"); bodyWriter.addAttribute("chart:style-name", styleName); bodyWriter.endElement(); // chart:stock-loss-marker bodyWriter.startElement("chart:stock-range-line"); KoGenStyle stockRangeStyle(KoGenStyle::ChartAutoStyle, "chart"); KoOdfGraphicStyles::saveOdfStrokeStyle(stockRangeStyle, context.mainStyles(), d->stockRangeLinePen); styleName = context.mainStyles().insert(stockRangeStyle, "ch"); bodyWriter.addAttribute("chart:style-name", styleName); bodyWriter.endElement(); // chart:stock-range-line } // Done with the attributes, start writing the children. // Save the axes. foreach(Axis *axis, d->axes) { axis->saveOdf(context); } if (d->threeDScene) { d->threeDScene->saveOdfChildren(bodyWriter); } // Save data series d->shape->proxyModel()->saveOdf(context); // Save the floor and wall of the plotarea. d->wall->saveOdf(context, "chart:wall"); //if (d->floor) // d->floor->saveOdf(context, "chart:floor"); bodyWriter.endElement(); // chart:plot-area } void PlotArea::saveOdfSubType(KoXmlWriter& xmlWriter, KoGenStyle& plotAreaStyle) const { Q_UNUSED(xmlWriter); switch (d->chartType) { case BarChartType: switch(d->chartSubtype) { case NoChartSubtype: case NormalChartSubtype: break; case StackedChartSubtype: plotAreaStyle.addProperty("chart:stacked", "true"); break; case PercentChartSubtype: plotAreaStyle.addProperty("chart:percentage", "true"); break; } if (d->threeD) { plotAreaStyle.addProperty("chart:three-dimensional", "true"); } // Data specific to bar charts if (d->vertical) plotAreaStyle.addProperty("chart:vertical", "true"); // Don't save this if zero, because that's the default. //plotAreaStyle.addProperty("chart:lines-used", 0); // FIXME: for now break; case LineChartType: switch(d->chartSubtype) { case NoChartSubtype: case NormalChartSubtype: break; case StackedChartSubtype: plotAreaStyle.addProperty("chart:stacked", "true"); break; case PercentChartSubtype: plotAreaStyle.addProperty("chart:percentage", "true"); break; } if (d->threeD) { plotAreaStyle.addProperty("chart:three-dimensional", "true"); // FIXME: Save all 3D attributes too. } // FIXME: What does this mean? plotAreaStyle.addProperty("chart:symbol-type", "automatic"); break; case AreaChartType: switch(d->chartSubtype) { case NoChartSubtype: case NormalChartSubtype: break; case StackedChartSubtype: plotAreaStyle.addProperty("chart:stacked", "true"); break; case PercentChartSubtype: plotAreaStyle.addProperty("chart:percentage", "true"); break; } if (d->threeD) { plotAreaStyle.addProperty("chart:three-dimensional", "true"); // FIXME: Save all 3D attributes too. } break; case CircleChartType: plotAreaStyle.addProperty("chart:angle-offset", QString::number(d->angleOffset)); break; case RingChartType: plotAreaStyle.addProperty("chart:angle-offset", QString::number(d->angleOffset)); plotAreaStyle.addProperty("chart:hole-size", QString::number(d->holeSize)); break; case ScatterChartType: // FIXME break; case RadarChartType: case FilledRadarChartType: // Save subtype of the Radar chart. switch(d->chartSubtype) { case NoChartSubtype: case NormalChartSubtype: break; case StackedChartSubtype: plotAreaStyle.addProperty("chart:stacked", "true"); break; case PercentChartSubtype: plotAreaStyle.addProperty("chart:percentage", "true"); break; } break; case StockChartType: { switch(d->chartSubtype) { case NoChartSubtype: case HighLowCloseChartSubtype: case OpenHighLowCloseChartSubtype: plotAreaStyle.addProperty("chart:japanese-candle-stick", "false"); break; case CandlestickChartSubtype: plotAreaStyle.addProperty("chart:japanese-candle-stick", "true"); break; } } case BubbleChartType: case SurfaceChartType: case GanttChartType: // FIXME break; // This is not a valid type, but needs to be handled to avoid // a warning from gcc. case LastChartType: default: // FIXME break; } } void PlotArea::setAngleOffset(qreal angle) { d->angleOffset = angle; emit angleOffsetChanged(angle); } ChartShape *PlotArea::parent() const { // There has to be a valid parent Q_ASSERT(d->shape); return d->shape; } KChart::CartesianCoordinatePlane *PlotArea::kdCartesianPlane(Axis *axis) const { if (axis) { Q_ASSERT(d->axes.contains(axis)); // Only a secondary y axis gets the secondary plane if (axis->dimension() == YAxisDimension && axis != yAxis()) return d->kdCartesianPlaneSecondary; } return d->kdCartesianPlanePrimary; } KChart::PolarCoordinatePlane *PlotArea::kdPolarPlane() const { return d->kdPolarPlane; } KChart::RadarCoordinatePlane *PlotArea::kdRadarPlane() const { return d->kdRadarPlane; } KChart::Chart *PlotArea::kdChart() const { return d->kdChart; } bool PlotArea::registerKdDiagram(KChart::AbstractDiagram *diagram) { if (d->kdDiagrams.contains(diagram)) return false; d->kdDiagrams.append(diagram); return true; } bool PlotArea::deregisterKdDiagram(KChart::AbstractDiagram *diagram) { if (!d->kdDiagrams.contains(diagram)) return false; d->kdDiagrams.removeAll(diagram); return true; } // HACK to get kdChart to recognize secondary planes void PlotArea::registerKdPlane(KChart::AbstractCoordinatePlane *plane) { int pos = d->kdChart->coordinatePlanes().indexOf(plane); if (pos >= 1) { // secondary plane d->kdChart->takeCoordinatePlane(plane); d->kdChart->insertCoordinatePlane(pos, plane); } else if (pos < 0) { d->kdChart->addCoordinatePlane(plane); } } void PlotArea::plotAreaUpdate() { parent()->legend()->update(); if (d->chartType == StockChartType) { updateKChartStockAttributes(); } requestRepaint(); foreach(Axis* axis, d->axes) axis->update(); KoShape::update(); } void PlotArea::requestRepaint() const { d->pixmapRepaintRequested = true; } void PlotArea::paintPixmap(QPainter &painter, const KoViewConverter &converter) { // Adjust the size of the painting area to the current zoom level const QSize paintRectSize = converter.documentToView(size()).toSize(); const QSize plotAreaSize = size().toSize(); const int borderX = 4; const int borderY = 4; // Only use a pixmap with sane sizes d->paintPixmap = false;//paintRectSize.width() < MAX_PIXMAP_SIZE || paintRectSize.height() < MAX_PIXMAP_SIZE; if (d->paintPixmap) { d->image = QImage(paintRectSize, QImage::Format_RGB32); // Copy the painter's render hints, such as antialiasing QPainter pixmapPainter(&d->image); pixmapPainter.setRenderHints(painter.renderHints()); pixmapPainter.setRenderHint(QPainter::Antialiasing, false); // scale the painter's coordinate system to fit the current zoom level applyConversion(pixmapPainter, converter); d->kdChart->paint(&pixmapPainter, QRect(QPoint(borderX, borderY), QSize(plotAreaSize.width() - 2 * borderX, plotAreaSize.height() - 2 * borderY))); } else { d->kdChart->paint(&painter, QRect(QPoint(borderX, borderY), QSize(plotAreaSize.width() - 2 * borderX, plotAreaSize.height() - 2 * borderY))); } } void PlotArea::paint(QPainter& painter, const KoViewConverter& converter, KoShapePaintingContext &paintContext) { //painter.save(); // First of all, scale the painter's coordinate system to fit the current zoom level applyConversion(painter, converter); // Calculate the clipping rect QRectF paintRect = QRectF(QPointF(0, 0), size()); painter.setClipRect(paintRect, Qt::IntersectClip); // Paint the background if (background()) { QPainterPath p; p.addRect(paintRect); background()->paint(painter, converter, paintContext, p); } // Get the current zoom level QPointF zoomLevel; converter.zoom(&zoomLevel.rx(), &zoomLevel.ry()); // Only repaint the pixmap if it is scheduled, the zoom level // changed or the shape was resized. /*if ( d->pixmapRepaintRequested || d->lastZoomLevel != zoomLevel || d->lastSize != size() || !d->paintPixmap) { // TODO (js): What if two zoom levels are constantly being // requested? At the moment, this *is* the case, // due to the fact that the shape is also rendered // in the page overview in Stage. Every time // the window is hidden and shown again, a repaint // is requested --> laggy performance, especially // when quickly switching through windows. // // ANSWER (iw): what about having a small mapping between size // in pixels and pixmaps? The size could be 2 or // at most 3. We could manage the replacing // using LRU. paintPixmap(painter, converter); d->pixmapRepaintRequested = false; d->lastZoomLevel = zoomLevel; d->lastSize = size(); }*/ painter.setRenderHint(QPainter::Antialiasing, false); // KChart thinks in pixels, Calligra in pt ScreenConversions::scaleFromPtToPx(painter); // Only paint the actual chart if there is a certain minimal size, // because otherwise kdchart will crash. QRect kdchartRect = ScreenConversions::scaleFromPtToPx(paintRect, painter); // Turn off clipping so that border (or "frame") drawn by KChart::Chart // is not not cut off. painter.setClipping(false); if (kdchartRect.width() > 10 && kdchartRect.height() > 10) { #if KCHART_VERSION < 0x020689 painter.setPen(QPen()); // ruler line needs a pen to be shown #endif d->kdChart->paint(&painter, kdchartRect); } //painter.restore(); // Paint the cached pixmap if we got a GO from paintPixmap() //if (d->paintPixmap) // painter.drawImage(0, 0, d->image); } void PlotArea::relayout() const { d->kdCartesianPlanePrimary->relayout(); d->kdCartesianPlaneSecondary->relayout(); d->kdPolarPlane->relayout(); d->kdRadarPlane->relayout(); update(); } void PlotArea::addTitleToLayout() { addAxesTitlesToLayout(); // for now } void PlotArea::addAxesTitlesToLayout() { ChartLayout *layout = d->shape->layout(); Axis *axis = xAxis(); if (axis) { layout->remove(axis->title()); layout->setItemType(axis->title(), XAxisTitleType); } axis = yAxis(); if (axis) { layout->remove(axis->title()); layout->setItemType(axis->title(), YAxisTitleType); } axis = secondaryXAxis(); if (axis) { layout->remove(axis->title()); layout->setItemType(axis->title(), SecondaryXAxisTitleType); } axis = secondaryYAxis(); if (axis) { layout->remove(axis->title()); layout->setItemType(axis->title(), SecondaryYAxisTitleType); } } void PlotArea::setStockRangeLinePen(const QPen &pen) { d->stockRangeLinePen = pen; } QPen PlotArea::stockRangeLinePen() const { return d->stockRangeLinePen; } void PlotArea::setStockGainBrush(const QBrush &brush) { d->stockGainBrush = brush; } QBrush PlotArea::stockGainBrush() const { return d->stockGainBrush; } void PlotArea::setStockLossBrush(const QBrush &brush) { d->stockLossBrush = brush; } QBrush PlotArea::stockLossBrush() const { return d->stockLossBrush; } void PlotArea::updateKChartStockAttributes() { for (Axis *a : d->axes) { a->updateKChartStockAttributes(); } } DataSet::ValueLabelType PlotArea::valueLabelType() const { return d->valueLabelType; } QString PlotArea::symbolType() const { return d->symbolType; } void PlotArea::setSymbolType(const QString &type) { d->symbolType = type; } QString PlotArea::symbolName() const { return d->symbolName; } void PlotArea::setSymbolName(const QString &name) { d->symbolName = name; } void PlotArea::setValueLabelType(const DataSet::ValueLabelType &type) { d->valueLabelType = type; } diff --git a/plugins/chartshape/commands/PlotAreaCommand.cpp b/plugins/chartshape/commands/PlotAreaCommand.cpp index 7a9373d9786..0dd3965afd0 100644 --- a/plugins/chartshape/commands/PlotAreaCommand.cpp +++ b/plugins/chartshape/commands/PlotAreaCommand.cpp @@ -1,120 +1,76 @@ /* This file is part of the KDE project * Copyright 2018 Dag Andersen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "PlotAreaCommand.h" // KF5 #include // KChart #include "KChartCartesianAxis.h" // KoChart #include "ChartShape.h" #include "PlotArea.h" #include "Axis.h" #include "ChartDebug.h" using namespace KoChart; PlotAreaCommand::PlotAreaCommand(PlotArea *plotArea) : m_plotArea(plotArea) , m_chart(plotArea->parent()) { m_newOrientation = m_oldOrientation = plotArea->isVertical() ? Qt::Vertical : Qt::Horizontal; } PlotAreaCommand::~PlotAreaCommand() { } void PlotAreaCommand::redo() { KUndo2Command::redo(); if (m_oldOrientation != m_newOrientation) { m_plotArea->setVertical(m_newOrientation == Qt::Vertical); } - if (m_plotArea->chartType() == BarChartType) { - for (Axis *axis : m_plotArea->axes()) { - if (axis->title()->isVisible()) { - debugChartUiAxes<actualAxisPosition(); - int rotation = axis->title()->rotation(); - switch (axis->actualAxisPosition()) { - case KChart::CartesianAxis::Bottom: - case KChart::CartesianAxis::Top: - axis->title()->rotate(-rotation); - break; - case KChart::CartesianAxis::Left: - axis->title()->rotate(-rotation); - axis->title()->rotate(-90); - break; - case KChart::CartesianAxis::Right: - axis->title()->rotate(-rotation); - axis->title()->rotate(90); - break; - } - } - } - } m_chart->update(); m_chart->relayout(); } void PlotAreaCommand::undo() { if (m_oldOrientation != m_newOrientation) { m_plotArea->setVertical(m_oldOrientation == Qt::Vertical); } - if (m_plotArea->chartType() == BarChartType) { - for (Axis *axis : m_plotArea->axes()) { - if (axis->title()->isVisible()) { - debugChartUiAxes<actualAxisPosition(); - int rotation = axis->title()->rotation(); - switch (axis->actualAxisPosition()) { - case KChart::CartesianAxis::Bottom: - case KChart::CartesianAxis::Top: - axis->title()->rotate(-rotation); - break; - case KChart::CartesianAxis::Left: - axis->title()->rotate(-rotation); - axis->title()->rotate(-90); - break; - case KChart::CartesianAxis::Right: - axis->title()->rotate(-rotation); - axis->title()->rotate(90); - break; - } - } - } - } KUndo2Command::undo(); m_chart->update(); m_chart->relayout(); } void PlotAreaCommand::setOrientation(Qt::Orientation orientation) { m_newOrientation = orientation; if (orientation == Qt::Vertical) { setText(kundo2_i18n("Set Horizontal Bars")); } else { setText(kundo2_i18n("Set Vertical Bars")); } }