summaryrefslogtreecommitdiffstats
path: root/chart2/source/view/charttypes
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /chart2/source/view/charttypes
parentInitial commit. (diff)
downloadlibreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz
libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'chart2/source/view/charttypes')
-rw-r--r--chart2/source/view/charttypes/AreaChart.cxx952
-rw-r--r--chart2/source/view/charttypes/AreaChart.hxx86
-rw-r--r--chart2/source/view/charttypes/BarChart.cxx976
-rw-r--r--chart2/source/view/charttypes/BarChart.hxx118
-rw-r--r--chart2/source/view/charttypes/BarPositionHelper.cxx73
-rw-r--r--chart2/source/view/charttypes/BarPositionHelper.hxx44
-rw-r--r--chart2/source/view/charttypes/BubbleChart.cxx362
-rw-r--r--chart2/source/view/charttypes/BubbleChart.hxx60
-rw-r--r--chart2/source/view/charttypes/CandleStickChart.cxx314
-rw-r--r--chart2/source/view/charttypes/CandleStickChart.hxx54
-rw-r--r--chart2/source/view/charttypes/CategoryPositionHelper.cxx82
-rw-r--r--chart2/source/view/charttypes/CategoryPositionHelper.hxx57
-rw-r--r--chart2/source/view/charttypes/ConfigAccess.cxx74
-rw-r--r--chart2/source/view/charttypes/NetChart.cxx642
-rw-r--r--chart2/source/view/charttypes/NetChart.hxx74
-rw-r--r--chart2/source/view/charttypes/PieChart.cxx1719
-rw-r--r--chart2/source/view/charttypes/PieChart.hxx145
-rw-r--r--chart2/source/view/charttypes/Splines.cxx872
-rw-r--r--chart2/source/view/charttypes/Splines.hxx48
-rw-r--r--chart2/source/view/charttypes/VSeriesPlotter.cxx2847
20 files changed, 9599 insertions, 0 deletions
diff --git a/chart2/source/view/charttypes/AreaChart.cxx b/chart2/source/view/charttypes/AreaChart.cxx
new file mode 100644
index 000000000..9cb2e06ba
--- /dev/null
+++ b/chart2/source/view/charttypes/AreaChart.cxx
@@ -0,0 +1,952 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "AreaChart.hxx"
+#include <PlottingPositionHelper.hxx>
+#include <ShapeFactory.hxx>
+#include <CommonConverters.hxx>
+#include <ExplicitCategoriesProvider.hxx>
+#include <ObjectIdentifier.hxx>
+#include "Splines.hxx"
+#include <ChartType.hxx>
+#include <ChartTypeHelper.hxx>
+#include <LabelPositionHelper.hxx>
+#include <Clipping.hxx>
+#include <Stripe.hxx>
+#include <DateHelper.hxx>
+#include <unonames.hxx>
+#include <ConfigAccess.hxx>
+
+#include <com/sun/star/chart2/Symbol.hpp>
+#include <com/sun/star/chart/DataLabelPlacement.hpp>
+#include <com/sun/star/chart/MissingValueTreatment.hpp>
+
+#include <sal/log.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <officecfg/Office/Compatibility.hxx>
+
+#include <limits>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::chart2;
+
+AreaChart::AreaChart( const rtl::Reference<ChartType>& xChartTypeModel
+ , sal_Int32 nDimensionCount
+ , bool bCategoryXAxis
+ , bool bNoArea
+ )
+ : VSeriesPlotter( xChartTypeModel, nDimensionCount, bCategoryXAxis )
+ , m_pMainPosHelper(new PlottingPositionHelper())
+ , m_bArea(!bNoArea)
+ , m_bLine(bNoArea)
+ , m_bSymbol( ChartTypeHelper::isSupportingSymbolProperties(xChartTypeModel,nDimensionCount) )
+ , m_eCurveStyle(CurveStyle_LINES)
+ , m_nCurveResolution(20)
+ , m_nSplineOrder(3)
+{
+ m_pMainPosHelper->AllowShiftXAxisPos(true);
+ m_pMainPosHelper->AllowShiftZAxisPos(true);
+
+ PlotterBase::m_pPosHelper = m_pMainPosHelper.get();
+ VSeriesPlotter::m_pMainPosHelper = m_pMainPosHelper.get();
+
+ try
+ {
+ if( m_xChartTypeModel.is() )
+ {
+ m_xChartTypeModel->getPropertyValue(CHART_UNONAME_CURVE_STYLE) >>= m_eCurveStyle;
+ m_xChartTypeModel->getPropertyValue(CHART_UNONAME_CURVE_RESOLUTION) >>= m_nCurveResolution;
+ m_xChartTypeModel->getPropertyValue(CHART_UNONAME_SPLINE_ORDER) >>= m_nSplineOrder;
+ }
+ }
+ catch( uno::Exception& e )
+ {
+ //the above properties are not supported by all charttypes supported by this class (e.g. area or net chart)
+ //in that cases this exception is ok
+ e.Context.is();//to have debug information without compilation warnings
+ }
+}
+
+AreaChart::~AreaChart()
+{
+}
+
+bool AreaChart::isSeparateStackingForDifferentSigns( sal_Int32 /*nDimensionIndex*/ )
+{
+ // no separate stacking in all types of line/area charts
+ return false;
+}
+
+LegendSymbolStyle AreaChart::getLegendSymbolStyle()
+{
+ if( m_bArea || m_nDimension == 3 )
+ return LegendSymbolStyle::Box;
+ return LegendSymbolStyle::Line;
+}
+
+uno::Any AreaChart::getExplicitSymbol( const VDataSeries& rSeries, sal_Int32 nPointIndex )
+{
+ uno::Any aRet;
+
+ Symbol* pSymbolProperties = rSeries.getSymbolProperties( nPointIndex );
+ if( pSymbolProperties )
+ {
+ aRet <<= *pSymbolProperties;
+ }
+
+ return aRet;
+}
+
+drawing::Direction3D AreaChart::getPreferredDiagramAspectRatio() const
+{
+ drawing::Direction3D aRet(1,-1,1);
+ if( m_nDimension == 2 )
+ aRet = drawing::Direction3D(-1,-1,-1);
+ else if (m_pPosHelper)
+ {
+ drawing::Direction3D aScale( m_pPosHelper->getScaledLogicWidth() );
+ aRet.DirectionZ = aScale.DirectionZ*0.2;
+ if(aRet.DirectionZ>1.0)
+ aRet.DirectionZ=1.0;
+ if(aRet.DirectionZ>10)
+ aRet.DirectionZ=10;
+ }
+ return aRet;
+}
+
+void AreaChart::addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 zSlot, sal_Int32 xSlot, sal_Int32 ySlot )
+{
+ if( m_bArea && pSeries )
+ {
+ sal_Int32 nMissingValueTreatment = pSeries->getMissingValueTreatment();
+ if( nMissingValueTreatment == css::chart::MissingValueTreatment::LEAVE_GAP )
+ pSeries->setMissingValueTreatment( css::chart::MissingValueTreatment::USE_ZERO );
+ }
+ if( m_nDimension == 3 && !m_bCategoryXAxis )
+ {
+ //3D xy always deep
+ OSL_ENSURE( zSlot==-1,"3D xy charts should be deep stacked in model also" );
+ zSlot=-1;
+ xSlot=0;
+ ySlot=0;
+ }
+ VSeriesPlotter::addSeries( std::move(pSeries), zSlot, xSlot, ySlot );
+}
+
+static void lcl_removeDuplicatePoints( std::vector<std::vector<css::drawing::Position3D>>& rPolyPoly, PlottingPositionHelper& rPosHelper )
+{
+ sal_Int32 nPolyCount = rPolyPoly.size();
+ if(!nPolyCount)
+ return;
+
+ // TODO we could do with without a temporary array
+ std::vector<std::vector<css::drawing::Position3D>> aTmp;
+ aTmp.resize(nPolyCount);
+
+ for( sal_Int32 nPolygonIndex = 0; nPolygonIndex<nPolyCount; nPolygonIndex++ )
+ {
+ std::vector<css::drawing::Position3D>* pOuterSource = &rPolyPoly[nPolygonIndex];
+ std::vector<css::drawing::Position3D>* pOuterTarget = &aTmp[nPolygonIndex];
+
+ sal_Int32 nPointCount = pOuterSource->size();
+ if( !nPointCount )
+ continue;
+
+ pOuterTarget->resize(nPointCount);
+
+ css::drawing::Position3D* pSource = pOuterSource->data();
+ css::drawing::Position3D* pTarget = pOuterTarget->data();
+
+ //copy first point
+ *pTarget=*pSource++;
+ sal_Int32 nTargetPointCount=1;
+
+ for( sal_Int32 nSource=1; nSource<nPointCount; nSource++ )
+ {
+ if( !rPosHelper.isSameForGivenResolution( pTarget->PositionX, pTarget->PositionY, pTarget->PositionZ
+ , pSource->PositionX, pSource->PositionY, pSource->PositionZ ) )
+ {
+ pTarget++;
+ *pTarget=*pSource;
+ nTargetPointCount++;
+ }
+ pSource++;
+ }
+
+ //free unused space
+ if( nTargetPointCount<nPointCount )
+ {
+ pOuterTarget->resize(nTargetPointCount);
+ }
+
+ pOuterSource->clear();
+ }
+
+ //free space
+ rPolyPoly.resize(nPolyCount);
+
+ rPolyPoly = std::move(aTmp);
+}
+
+bool AreaChart::create_stepped_line(
+ std::vector<std::vector<css::drawing::Position3D>> aStartPoly,
+ chart2::CurveStyle eCurveStyle,
+ PlottingPositionHelper const * pPosHelper,
+ std::vector<std::vector<css::drawing::Position3D>> &aPoly )
+{
+ sal_uInt32 nOuterCount = aStartPoly.size();
+ if ( !nOuterCount )
+ return false;
+
+ std::vector<std::vector<css::drawing::Position3D>> aSteppedPoly;
+ aSteppedPoly.resize(nOuterCount);
+
+ auto pSequence = aSteppedPoly.data();
+
+ for( sal_uInt32 nOuter = 0; nOuter < nOuterCount; ++nOuter )
+ {
+ if( aStartPoly[nOuter].size() <= 1 )
+ continue; //we need at least two points
+
+ sal_uInt32 nMaxIndexPoints = aStartPoly[nOuter].size()-1; // is >1
+ sal_uInt32 nNewIndexPoints = 0;
+ if ( eCurveStyle==CurveStyle_STEP_START || eCurveStyle==CurveStyle_STEP_END)
+ nNewIndexPoints = nMaxIndexPoints * 2 + 1;
+ else
+ nNewIndexPoints = nMaxIndexPoints * 3 + 1;
+
+ const css::drawing::Position3D* pOld = aStartPoly[nOuter].data();
+
+ pSequence[nOuter].resize( nNewIndexPoints );
+
+ css::drawing::Position3D* pNew = pSequence[nOuter].data();
+
+ pNew[0] = pOld[0];
+ for( sal_uInt32 oi = 0; oi < nMaxIndexPoints; oi++ )
+ {
+ switch ( eCurveStyle )
+ {
+ case CurveStyle_STEP_START:
+ /** O
+ |
+ |
+ |
+ O-----+
+ */
+ // create the intermediate point
+ pNew[1+oi*2].PositionX = pOld[oi+1].PositionX;
+ pNew[1+oi*2].PositionY = pOld[oi].PositionY;
+ pNew[1+oi*2].PositionZ = pOld[oi].PositionZ;
+ // and now the normal one
+ pNew[1+oi*2+1] = pOld[oi+1];
+ break;
+ case CurveStyle_STEP_END:
+ /** +------O
+ |
+ |
+ |
+ O
+ */
+ // create the intermediate point
+ pNew[1+oi*2].PositionX = pOld[oi].PositionX;
+ pNew[1+oi*2].PositionY = pOld[oi+1].PositionY;
+ pNew[1+oi*2].PositionZ = pOld[oi].PositionZ;
+ // and now the normal one
+ pNew[1+oi*2+1] = pOld[oi+1];
+ break;
+ case CurveStyle_STEP_CENTER_X:
+ /** +--O
+ |
+ |
+ |
+ O--+
+ */
+ // create the first intermediate point
+ pNew[1+oi*3].PositionX = (pOld[oi].PositionX + pOld[oi+1].PositionX) / 2;
+ pNew[1+oi*3].PositionY = pOld[oi].PositionY;
+ pNew[1+oi*3].PositionZ = pOld[oi].PositionZ;
+ // create the second intermediate point
+ pNew[1+oi*3+1].PositionX = (pOld[oi].PositionX + pOld[oi+1].PositionX) / 2;
+ pNew[1+oi*3+1].PositionY = pOld[oi+1].PositionY;
+ pNew[1+oi*3+1].PositionZ = pOld[oi].PositionZ;
+ // and now the normal one
+ pNew[1+oi*3+2] = pOld[oi+1];
+ break;
+ case CurveStyle_STEP_CENTER_Y:
+ /** O
+ |
+ +-----+
+ |
+ O
+ */
+ // create the first intermediate point
+ pNew[1+oi*3].PositionX = pOld[oi].PositionX;
+ pNew[1+oi*3].PositionY = (pOld[oi].PositionY + pOld[oi+1].PositionY) / 2;
+ pNew[1+oi*3].PositionZ = pOld[oi].PositionZ;
+ // create the second intermediate point
+ pNew[1+oi*3+1].PositionX = pOld[oi+1].PositionX;
+ pNew[1+oi*3+1].PositionY = (pOld[oi].PositionY + pOld[oi+1].PositionY) / 2;
+ pNew[1+oi*3+1].PositionZ = pOld[oi].PositionZ;
+ // and now the normal one
+ pNew[1+oi*3+2] = pOld[oi+1];
+ break;
+ default:
+ // this should never be executed
+ OSL_FAIL("Unknown curvestyle in AreaChart::create_stepped_line");
+ }
+ }
+ }
+ Clipping::clipPolygonAtRectangle( aSteppedPoly, pPosHelper->getScaledLogicClipDoubleRect(), aPoly );
+
+ return true;
+}
+
+bool AreaChart::impl_createLine( VDataSeries* pSeries
+ , std::vector<std::vector<css::drawing::Position3D>> const * pSeriesPoly
+ , PlottingPositionHelper* pPosHelper )
+{
+ //return true if a line was created successfully
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes = getSeriesGroupShapeBackChild(pSeries, m_xSeriesTarget);
+
+ std::vector<std::vector<css::drawing::Position3D>> aPoly;
+ if(m_eCurveStyle==CurveStyle_CUBIC_SPLINES)
+ {
+ std::vector<std::vector<css::drawing::Position3D>> aSplinePoly;
+ SplineCalculater::CalculateCubicSplines( *pSeriesPoly, aSplinePoly, m_nCurveResolution );
+ lcl_removeDuplicatePoints( aSplinePoly, *pPosHelper );
+ Clipping::clipPolygonAtRectangle( aSplinePoly, pPosHelper->getScaledLogicClipDoubleRect(), aPoly );
+ }
+ else if(m_eCurveStyle==CurveStyle_B_SPLINES)
+ {
+ std::vector<std::vector<css::drawing::Position3D>> aSplinePoly;
+ SplineCalculater::CalculateBSplines( *pSeriesPoly, aSplinePoly, m_nCurveResolution, m_nSplineOrder );
+ lcl_removeDuplicatePoints( aSplinePoly, *pPosHelper );
+ Clipping::clipPolygonAtRectangle( aSplinePoly, pPosHelper->getScaledLogicClipDoubleRect(), aPoly );
+ }
+ else if (m_eCurveStyle==CurveStyle_STEP_START ||
+ m_eCurveStyle==CurveStyle_STEP_END ||
+ m_eCurveStyle==CurveStyle_STEP_CENTER_Y ||
+ m_eCurveStyle==CurveStyle_STEP_CENTER_X
+ )
+ {
+ if (!create_stepped_line(*pSeriesPoly, m_eCurveStyle, pPosHelper, aPoly))
+ {
+ return false;
+ }
+ }
+ else
+ { // default to creating a straight line
+ SAL_WARN_IF(m_eCurveStyle != CurveStyle_LINES, "chart2.areachart", "Unknown curve style");
+ Clipping::clipPolygonAtRectangle( *pSeriesPoly, pPosHelper->getScaledLogicClipDoubleRect(), aPoly );
+ }
+
+ if(!ShapeFactory::hasPolygonAnyLines(aPoly))
+ return false;
+
+ //transformation 3) -> 4)
+ pPosHelper->transformScaledLogicToScene( aPoly );
+
+ //create line:
+ rtl::Reference< SvxShape > xShape;
+ if(m_nDimension==3)
+ {
+ double fDepth = getTransformedDepth();
+ sal_Int32 nPolyCount = aPoly.size();
+ for(sal_Int32 nPoly=0;nPoly<nPolyCount;nPoly++)
+ {
+ sal_Int32 nPointCount = aPoly[nPoly].size();
+ for(sal_Int32 nPoint=0;nPoint<nPointCount-1;nPoint++)
+ {
+ drawing::Position3D aPoint1, aPoint2;
+ aPoint1 = aPoly[nPoly][nPoint+1];
+ aPoint2 = aPoly[nPoly][nPoint];
+
+ ShapeFactory::createStripe(xSeriesGroupShape_Shapes
+ , Stripe( aPoint1, aPoint2, fDepth )
+ , pSeries->getPropertiesOfSeries(), PropertyMapper::getPropertyNameMapForFilledSeriesProperties(), true, 1 );
+ }
+ }
+ }
+ else //m_nDimension!=3
+ {
+ xShape = ShapeFactory::createLine2D( xSeriesGroupShape_Shapes, aPoly );
+ PropertyMapper::setMappedProperties( *xShape
+ , pSeries->getPropertiesOfSeries()
+ , PropertyMapper::getPropertyNameMapForLineSeriesProperties() );
+ //because of this name this line will be used for marking
+ ::chart::ShapeFactory::setShapeName(xShape, "MarkHandles");
+ }
+ return true;
+}
+
+bool AreaChart::impl_createArea( VDataSeries* pSeries
+ , std::vector<std::vector<css::drawing::Position3D>> const * pSeriesPoly
+ , std::vector<std::vector<css::drawing::Position3D>> const * pPreviousSeriesPoly
+ , PlottingPositionHelper const * pPosHelper )
+{
+ //return true if an area was created successfully
+
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes = getSeriesGroupShapeBackChild(pSeries, m_xSeriesTarget);
+ double zValue = pSeries->m_fLogicZPos;
+
+ std::vector<std::vector<css::drawing::Position3D>> aPoly( *pSeriesPoly );
+ //add second part to the polygon (grounding points or previous series points)
+ if(!pPreviousSeriesPoly)
+ {
+ double fMinX = pSeries->m_fLogicMinX;
+ double fMaxX = pSeries->m_fLogicMaxX;
+ double fY = pPosHelper->getBaseValueY();//logic grounding
+ if( m_nDimension==3 )
+ fY = pPosHelper->getLogicMinY();
+
+ //clip to scale
+ if(fMaxX<pPosHelper->getLogicMinX() || fMinX>pPosHelper->getLogicMaxX())
+ return false;//no visible shape needed
+ pPosHelper->clipLogicValues( &fMinX, &fY, nullptr );
+ pPosHelper->clipLogicValues( &fMaxX, nullptr, nullptr );
+
+ //apply scaling
+ {
+ pPosHelper->doLogicScaling( &fMinX, &fY, &zValue );
+ pPosHelper->doLogicScaling( &fMaxX, nullptr, nullptr );
+ }
+
+ AddPointToPoly( aPoly, drawing::Position3D( fMaxX,fY,zValue) );
+ AddPointToPoly( aPoly, drawing::Position3D( fMinX,fY,zValue) );
+ }
+ else
+ {
+ appendPoly( aPoly, *pPreviousSeriesPoly );
+ }
+ ShapeFactory::closePolygon(aPoly);
+
+ //apply clipping
+ {
+ std::vector<std::vector<css::drawing::Position3D>> aClippedPoly;
+ Clipping::clipPolygonAtRectangle( aPoly, pPosHelper->getScaledLogicClipDoubleRect(), aClippedPoly, false );
+ ShapeFactory::closePolygon(aClippedPoly); //again necessary after clipping
+ aPoly = aClippedPoly;
+ }
+
+ if(!ShapeFactory::hasPolygonAnyLines(aPoly))
+ return false;
+
+ //transformation 3) -> 4)
+ pPosHelper->transformScaledLogicToScene( aPoly );
+
+ //create area:
+ rtl::Reference< SvxShape > xShape;
+ if(m_nDimension==3)
+ {
+ xShape = ShapeFactory::createArea3D( xSeriesGroupShape_Shapes
+ , aPoly, getTransformedDepth() );
+ }
+ else //m_nDimension!=3
+ {
+ xShape = ShapeFactory::createArea2D( xSeriesGroupShape_Shapes
+ , aPoly );
+ }
+ PropertyMapper::setMappedProperties( *xShape
+ , pSeries->getPropertiesOfSeries()
+ , PropertyMapper::getPropertyNameMapForFilledSeriesProperties() );
+ //because of this name this line will be used for marking
+ ::chart::ShapeFactory::setShapeName(xShape, "MarkHandles");
+ return true;
+}
+
+void AreaChart::impl_createSeriesShapes()
+{
+ //the polygon shapes for each series need to be created before
+
+ //iterate through all series again to create the series shapes
+ for( auto const& rZSlot : m_aZSlots )
+ {
+ for( auto const& rXSlot : rZSlot )
+ {
+ std::map< sal_Int32, std::vector<std::vector<css::drawing::Position3D>>* > aPreviousSeriesPolyMap;//a PreviousSeriesPoly for each different nAttachedAxisIndex
+ std::vector<std::vector<css::drawing::Position3D>>* pSeriesPoly = nullptr;
+
+ //iterate through all series
+ for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
+ {
+ sal_Int32 nAttachedAxisIndex = pSeries->getAttachedAxisIndex();
+ PlottingPositionHelper& rPosHelper = getPlottingPositionHelper(nAttachedAxisIndex);
+ m_pPosHelper = &rPosHelper;
+
+ createRegressionCurvesShapes( *pSeries, m_xErrorBarTarget, m_xRegressionCurveEquationTarget,
+ m_pPosHelper->maySkipPointsInRegressionCalculation());
+
+ pSeriesPoly = &pSeries->m_aPolyPolygonShape3D;
+ if( m_bArea )
+ {
+ if (!impl_createArea(pSeries.get(), pSeriesPoly,
+ aPreviousSeriesPolyMap[nAttachedAxisIndex], &rPosHelper))
+ continue;
+ }
+ if( m_bLine )
+ {
+ if (!impl_createLine(pSeries.get(), pSeriesPoly, &rPosHelper))
+ continue;
+ }
+ aPreviousSeriesPolyMap[nAttachedAxisIndex] = pSeriesPoly;
+ }//next series in x slot (next y slot)
+ }//next x slot
+ }//next z slot
+}
+
+namespace
+{
+
+void lcl_reorderSeries( std::vector< std::vector< VDataSeriesGroup > >& rZSlots )
+{
+ std::vector< std::vector< VDataSeriesGroup > > aRet;
+ aRet.reserve( rZSlots.size() );
+
+ std::vector< std::vector< VDataSeriesGroup > >::reverse_iterator aZIt( rZSlots.rbegin() );
+ std::vector< std::vector< VDataSeriesGroup > >::reverse_iterator aZEnd( rZSlots.rend() );
+ for( ; aZIt != aZEnd; ++aZIt )
+ {
+ std::vector< VDataSeriesGroup > aXSlot;
+ aXSlot.reserve( aZIt->size() );
+
+ std::vector< VDataSeriesGroup >::reverse_iterator aXIt( aZIt->rbegin() );
+ std::vector< VDataSeriesGroup >::reverse_iterator aXEnd( aZIt->rend() );
+ for( ; aXIt != aXEnd; ++aXIt )
+ aXSlot.push_back(std::move(*aXIt));
+
+ aRet.push_back(std::move(aXSlot));
+ }
+
+ rZSlots = std::move(aRet);
+}
+
+//better performance for big data
+struct FormerPoint
+{
+ FormerPoint( double fX, double fY, double fZ )
+ : m_fX(fX), m_fY(fY), m_fZ(fZ)
+ {}
+ FormerPoint()
+ : m_fX(std::numeric_limits<double>::quiet_NaN())
+ , m_fY(std::numeric_limits<double>::quiet_NaN())
+ , m_fZ(std::numeric_limits<double>::quiet_NaN())
+ {
+ }
+
+ double m_fX;
+ double m_fY;
+ double m_fZ;
+};
+
+}//anonymous namespace
+
+void AreaChart::createShapes()
+{
+ if( m_aZSlots.empty() ) //no series
+ return;
+
+ //tdf#127813 Don't reverse the series in OOXML-heavy environments
+ if( officecfg::Office::Compatibility::View::ReverseSeriesOrderAreaAndNetChart::get() && m_nDimension == 2 && ( m_bArea || !m_bCategoryXAxis ) )
+ lcl_reorderSeries( m_aZSlots );
+
+ OSL_ENSURE(m_xLogicTarget.is()&&m_xFinalTarget.is(),"AreaChart is not proper initialized");
+ if(!(m_xLogicTarget.is()&&m_xFinalTarget.is()))
+ return;
+
+ //the text labels should be always on top of the other series shapes
+ //for area chart the error bars should be always on top of the other series shapes
+
+ //therefore create an own group for the texts and the error bars to move them to front
+ //(because the text group is created after the series group the texts are displayed on top)
+ m_xSeriesTarget = createGroupShape( m_xLogicTarget );
+ if( m_bArea )
+ m_xErrorBarTarget = createGroupShape( m_xLogicTarget );
+ else
+ m_xErrorBarTarget = m_xSeriesTarget;
+ m_xTextTarget = ShapeFactory::createGroup2D( m_xFinalTarget );
+ m_xRegressionCurveEquationTarget = ShapeFactory::createGroup2D( m_xFinalTarget );
+
+ //check necessary here that different Y axis can not be stacked in the same group? ... hm?
+
+ //update/create information for current group
+ double fLogicZ = 1.0;//as defined
+
+ sal_Int32 nStartIndex = 0; // inclusive ;..todo get somehow from x scale
+ sal_Int32 nEndIndex = VSeriesPlotter::getPointCount();
+ if(nEndIndex<=0)
+ nEndIndex=1;
+
+ //better performance for big data
+ std::map< VDataSeries*, FormerPoint > aSeriesFormerPointMap;
+ m_bPointsWereSkipped = false;
+ sal_Int32 nSkippedPoints = 0;
+ sal_Int32 nCreatedPoints = 0;
+
+ bool bDateCategory = (m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis());
+
+ std::vector<std::map< sal_Int32, double > > aLogicYSumMapByX(nEndIndex);//one for each different nAttachedAxisIndex
+ for( auto const& rZSlot : m_aZSlots )
+ {
+ //iterate through all x slots in this category to get 100percent sum
+ for( auto const& rXSlot : rZSlot )
+ {
+ for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
+ {
+ if(!pSeries)
+ continue;
+
+ if (bDateCategory)
+ pSeries->doSortByXValues();
+
+ for( sal_Int32 nIndex = nStartIndex; nIndex < nEndIndex; nIndex++ )
+ {
+ std::map< sal_Int32, double >& rLogicYSumMap = aLogicYSumMapByX[nIndex];
+ sal_Int32 nAttachedAxisIndex = pSeries->getAttachedAxisIndex();
+ rLogicYSumMap.insert({nAttachedAxisIndex, 0.0});
+
+ m_pPosHelper = &getPlottingPositionHelper(nAttachedAxisIndex);
+
+ double fAdd = pSeries->getYValue( nIndex );
+ if( !std::isnan(fAdd) && !std::isinf(fAdd) )
+ rLogicYSumMap[nAttachedAxisIndex] += fabs( fAdd );
+ }
+ }
+ }
+ }
+
+ const bool bUseErrorRectangle = ConfigAccess::getUseErrorRectangle();
+
+ sal_Int32 nZ=1;
+ for( auto const& rZSlot : m_aZSlots )
+ {
+ //for the area chart there should be at most one x slot (no side by side stacking available)
+ //attention different: xSlots are always interpreted as independent areas one behind the other: @todo this doesn't work why not???
+ for( auto const& rXSlot : rZSlot )
+ {
+ std::vector<std::map< sal_Int32, double > > aLogicYForNextSeriesMapByX(nEndIndex); //one for each different nAttachedAxisIndex
+ //iterate through all series
+ for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
+ {
+ if(!pSeries)
+ continue;
+
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes = getSeriesGroupShapeFrontChild(pSeries.get(), m_xSeriesTarget);
+
+ sal_Int32 nAttachedAxisIndex = pSeries->getAttachedAxisIndex();
+ double fXMin, fXMax;
+ pSeries->getMinMaxXValue(fXMin, fXMax);
+ PlottingPositionHelper& rPosHelper = getPlottingPositionHelper(nAttachedAxisIndex);
+ m_pPosHelper = &rPosHelper;
+
+ if(m_nDimension==3)
+ fLogicZ = nZ+0.5;
+ pSeries->m_fLogicZPos = fLogicZ;
+
+ for( sal_Int32 nIndex = nStartIndex; nIndex < nEndIndex; nIndex++ )
+ {
+
+ /* #i70133# ignore points outside of series length in standard area
+ charts. Stacked area charts will use missing points as zeros. In
+ standard charts, pSeriesList contains only one series. */
+ if( m_bArea && (rXSlot.m_aSeriesVector.size() == 1) && (nIndex >= pSeries->getTotalPointCount()) )
+ continue;
+
+ //collect data point information (logic coordinates, style ):
+ double fLogicX = pSeries->getXValue(nIndex);
+ if (bDateCategory)
+ {
+ if (std::isnan(fLogicX) || (fLogicX < fXMin || fLogicX > fXMax))
+ continue;
+
+ fLogicX = DateHelper::RasterizeDateValue( fLogicX, m_aNullDate, m_nTimeResolution );
+ }
+ double fLogicY = pSeries->getYValue(nIndex);
+
+ if( m_nDimension==3 && m_bArea && rXSlot.m_aSeriesVector.size()!=1 )
+ fLogicY = fabs( fLogicY );
+
+ double fLogicValueForLabeDisplay = fLogicY;
+ std::map< sal_Int32, double >& rLogicYSumMap = aLogicYSumMapByX[nIndex];
+ if (rPosHelper.isPercentY() && rLogicYSumMap[nAttachedAxisIndex] != 0.0)
+ {
+ fLogicY = fabs( fLogicY )/rLogicYSumMap[nAttachedAxisIndex];
+ }
+
+ if( std::isnan(fLogicX) || std::isinf(fLogicX)
+ || std::isnan(fLogicY) || std::isinf(fLogicY)
+ || std::isnan(fLogicZ) || std::isinf(fLogicZ) )
+ {
+ if( pSeries->getMissingValueTreatment() == css::chart::MissingValueTreatment::LEAVE_GAP )
+ {
+ std::vector<std::vector<css::drawing::Position3D>>& rPolygon = pSeries->m_aPolyPolygonShape3D;
+ sal_Int32& rIndex = pSeries->m_nPolygonIndex;
+ if( 0<= rIndex && o3tl::make_unsigned(rIndex) < rPolygon.size() )
+ {
+ if( !rPolygon[ rIndex ].empty() )
+ rIndex++; //start a new polygon for the next point if the current poly is not empty
+ }
+ }
+ continue;
+ }
+
+ std::map< sal_Int32, double >& rLogicYForNextSeriesMap = aLogicYForNextSeriesMapByX[nIndex];
+ rLogicYForNextSeriesMap.try_emplace(nAttachedAxisIndex, 0.0);
+
+ double fPreviousYValue = rLogicYForNextSeriesMap[nAttachedAxisIndex];
+ fLogicY += rLogicYForNextSeriesMap[nAttachedAxisIndex];
+ rLogicYForNextSeriesMap[nAttachedAxisIndex] = fLogicY;
+
+ bool bIsVisible = rPosHelper.isLogicVisible(fLogicX, fLogicY, fLogicZ);
+
+ //remind minimal and maximal x values for area 'grounding' points
+ //only for filled area
+ {
+ double& rfMinX = pSeries->m_fLogicMinX;
+ if(!nIndex||fLogicX<rfMinX)
+ rfMinX=fLogicX;
+ double& rfMaxX = pSeries->m_fLogicMaxX;
+ if(!nIndex||fLogicX>rfMaxX)
+ rfMaxX=fLogicX;
+ }
+
+ drawing::Position3D aUnscaledLogicPosition( fLogicX, fLogicY, fLogicZ );
+ drawing::Position3D aScaledLogicPosition(aUnscaledLogicPosition);
+ rPosHelper.doLogicScaling(aScaledLogicPosition);
+
+ //transformation 3) -> 4)
+ drawing::Position3D aScenePosition(
+ rPosHelper.transformLogicToScene(fLogicX, fLogicY, fLogicZ, false));
+
+ //better performance for big data
+ FormerPoint aFormerPoint( aSeriesFormerPointMap[pSeries.get()] );
+ rPosHelper.setCoordinateSystemResolution(m_aCoordinateSystemResolution);
+ if (!pSeries->isAttributedDataPoint(nIndex)
+ && rPosHelper.isSameForGivenResolution(
+ aFormerPoint.m_fX, aFormerPoint.m_fY, aFormerPoint.m_fZ,
+ aScaledLogicPosition.PositionX, aScaledLogicPosition.PositionY,
+ aScaledLogicPosition.PositionZ))
+ {
+ ++nSkippedPoints;
+ m_bPointsWereSkipped = true;
+ continue;
+ }
+ aSeriesFormerPointMap[pSeries.get()] = FormerPoint(aScaledLogicPosition.PositionX, aScaledLogicPosition.PositionY, aScaledLogicPosition.PositionZ);
+
+ //store point information for series polygon
+ //for area and/or line (symbols only do not need this)
+ if( isValidPosition(aScaledLogicPosition) )
+ {
+ AddPointToPoly( pSeries->m_aPolyPolygonShape3D, aScaledLogicPosition, pSeries->m_nPolygonIndex );
+ }
+
+ //create a single datapoint if point is visible
+ //apply clipping:
+ if( !bIsVisible )
+ continue;
+
+ bool bCreateYErrorBar = false, bCreateXErrorBar = false;
+ {
+ uno::Reference< beans::XPropertySet > xErrorBarProp(pSeries->getYErrorBarProperties(nIndex));
+ if( xErrorBarProp.is() )
+ {
+ bool bShowPositive = false;
+ bool bShowNegative = false;
+ xErrorBarProp->getPropertyValue("ShowPositiveError") >>= bShowPositive;
+ xErrorBarProp->getPropertyValue("ShowNegativeError") >>= bShowNegative;
+ bCreateYErrorBar = bShowPositive || bShowNegative;
+ }
+
+ xErrorBarProp = pSeries->getXErrorBarProperties(nIndex);
+ if ( xErrorBarProp.is() )
+ {
+ bool bShowPositive = false;
+ bool bShowNegative = false;
+ xErrorBarProp->getPropertyValue("ShowPositiveError") >>= bShowPositive;
+ xErrorBarProp->getPropertyValue("ShowNegativeError") >>= bShowNegative;
+ bCreateXErrorBar = bShowPositive || bShowNegative;
+ }
+ }
+
+ Symbol* pSymbolProperties = m_bSymbol ? pSeries->getSymbolProperties( nIndex ) : nullptr;
+ bool bCreateSymbol = pSymbolProperties && (pSymbolProperties->Style != SymbolStyle_NONE);
+
+ if( !bCreateSymbol && !bCreateYErrorBar &&
+ !bCreateXErrorBar && !pSeries->getDataPointLabelIfLabel(nIndex) )
+ continue;
+
+ {
+ nCreatedPoints++;
+
+ //create data point
+ drawing::Direction3D aSymbolSize(0,0,0);
+ if( bCreateSymbol )
+ {
+ if(m_nDimension!=3)
+ {
+ //create a group shape for this point and add to the series shape:
+ OUString aPointCID = ObjectIdentifier::createPointCID(
+ pSeries->getPointCID_Stub(), nIndex );
+ rtl::Reference<SvxShapeGroupAnyD> xPointGroupShape_Shapes;
+ if (pSymbolProperties->Style == SymbolStyle_STANDARD || pSymbolProperties->Style == SymbolStyle_GRAPHIC)
+ xPointGroupShape_Shapes = createGroupShape(xSeriesGroupShape_Shapes,aPointCID);
+
+ if (pSymbolProperties->Style != SymbolStyle_NONE)
+ {
+ aSymbolSize.DirectionX = pSymbolProperties->Size.Width;
+ aSymbolSize.DirectionY = pSymbolProperties->Size.Height;
+ }
+
+ if (pSymbolProperties->Style == SymbolStyle_STANDARD)
+ {
+ sal_Int32 nSymbol = pSymbolProperties->StandardSymbol;
+ ShapeFactory::createSymbol2D(
+ xPointGroupShape_Shapes, aScenePosition, aSymbolSize,
+ nSymbol, pSymbolProperties->BorderColor,
+ pSymbolProperties->FillColor);
+ }
+ else if (pSymbolProperties->Style == SymbolStyle_GRAPHIC)
+ {
+ ShapeFactory::createGraphic2D(xPointGroupShape_Shapes,
+ aScenePosition, aSymbolSize,
+ pSymbolProperties->Graphic);
+ }
+ //@todo other symbol styles
+ }
+ }
+ //create error bars or rectangles, depending on configuration
+ if ( bUseErrorRectangle )
+ {
+ if ( bCreateXErrorBar || bCreateYErrorBar )
+ {
+ createErrorRectangle(
+ aUnscaledLogicPosition,
+ *pSeries,
+ nIndex,
+ m_xErrorBarTarget,
+ bCreateXErrorBar,
+ bCreateYErrorBar );
+ }
+ }
+ else
+ {
+ if (bCreateXErrorBar)
+ createErrorBar_X( aUnscaledLogicPosition, *pSeries, nIndex, m_xErrorBarTarget );
+
+ if (bCreateYErrorBar)
+ createErrorBar_Y( aUnscaledLogicPosition, *pSeries, nIndex, m_xErrorBarTarget, nullptr );
+ }
+
+ //create data point label
+ if( pSeries->getDataPointLabelIfLabel(nIndex) )
+ {
+ LabelAlignment eAlignment = LABEL_ALIGN_TOP;
+ sal_Int32 nLabelPlacement = pSeries->getLabelPlacement(
+ nIndex, m_xChartTypeModel, rPosHelper.isSwapXAndY());
+
+ if (m_bArea && nLabelPlacement == css::chart::DataLabelPlacement::CENTER)
+ {
+ if (fPreviousYValue)
+ fLogicY -= (fLogicY - fPreviousYValue) / 2.0;
+ else
+ fLogicY = (fLogicY + rPosHelper.getLogicMinY()) / 2.0;
+ aScenePosition = rPosHelper.transformLogicToScene(fLogicX, fLogicY, fLogicZ, false);
+ }
+
+ drawing::Position3D aScenePosition3D( aScenePosition.PositionX
+ , aScenePosition.PositionY
+ , aScenePosition.PositionZ+getTransformedDepth() );
+
+ switch(nLabelPlacement)
+ {
+ case css::chart::DataLabelPlacement::TOP:
+ aScenePosition3D.PositionY -= (aSymbolSize.DirectionY/2+1);
+ eAlignment = LABEL_ALIGN_TOP;
+ break;
+ case css::chart::DataLabelPlacement::BOTTOM:
+ aScenePosition3D.PositionY += (aSymbolSize.DirectionY/2+1);
+ eAlignment = LABEL_ALIGN_BOTTOM;
+ break;
+ case css::chart::DataLabelPlacement::LEFT:
+ aScenePosition3D.PositionX -= (aSymbolSize.DirectionX/2+1);
+ eAlignment = LABEL_ALIGN_LEFT;
+ break;
+ case css::chart::DataLabelPlacement::RIGHT:
+ aScenePosition3D.PositionX += (aSymbolSize.DirectionX/2+1);
+ eAlignment = LABEL_ALIGN_RIGHT;
+ break;
+ case css::chart::DataLabelPlacement::CENTER:
+ eAlignment = LABEL_ALIGN_CENTER;
+ break;
+ default:
+ OSL_FAIL("this label alignment is not implemented yet");
+ aScenePosition3D.PositionY -= (aSymbolSize.DirectionY/2+1);
+ eAlignment = LABEL_ALIGN_TOP;
+ break;
+ }
+
+ awt::Point aScreenPosition2D;//get the screen position for the labels
+ sal_Int32 nOffset = 100; //todo maybe calculate this font height dependent
+ {
+ if(eAlignment==LABEL_ALIGN_CENTER || m_nDimension == 3 )
+ nOffset = 0;
+ aScreenPosition2D = LabelPositionHelper(m_nDimension,m_xLogicTarget)
+ .transformSceneToScreenPosition( aScenePosition3D );
+ }
+
+ createDataLabel( m_xTextTarget, *pSeries, nIndex
+ , fLogicValueForLabeDisplay
+ , rLogicYSumMap[nAttachedAxisIndex], aScreenPosition2D, eAlignment, nOffset );
+ }
+ }
+ }
+
+ }//next series in x slot (next y slot)
+ }//next x slot
+ ++nZ;
+ }//next z slot
+
+ impl_createSeriesShapes();
+
+ /* @todo remove series shapes if empty
+ //remove and delete point-group-shape if empty
+ if(!xSeriesGroupShape_Shapes->getCount())
+ {
+ pSeries->m_xShape.set(NULL);
+ m_xLogicTarget->remove(xSeriesGroupShape_Shape);
+ }
+ */
+
+ //remove and delete series-group-shape if empty
+
+ //... todo
+
+ SAL_INFO(
+ "chart2",
+ "skipped points: " << nSkippedPoints << " created points: "
+ << nCreatedPoints);
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/AreaChart.hxx b/chart2/source/view/charttypes/AreaChart.hxx
new file mode 100644
index 000000000..2f7434f3c
--- /dev/null
+++ b/chart2/source/view/charttypes/AreaChart.hxx
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <memory>
+#include <VSeriesPlotter.hxx>
+#include <com/sun/star/chart2/CurveStyle.hpp>
+
+namespace chart
+{
+
+class AreaChart : public VSeriesPlotter
+{
+ // public methods
+public:
+ AreaChart() = delete;
+
+ AreaChart( const rtl::Reference< ::chart::ChartType >& xChartTypeModel
+ , sal_Int32 nDimensionCount
+ , bool bCategoryXAxis, bool bNoArea=false
+ );
+ virtual ~AreaChart() override;
+
+ virtual void createShapes() override;
+ virtual void addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 zSlot, sal_Int32 xSlot, sal_Int32 ySlot ) override;
+
+ virtual css::drawing::Direction3D getPreferredDiagramAspectRatio() const override;
+
+ // MinimumAndMaximumSupplier
+ virtual bool isSeparateStackingForDifferentSigns( sal_Int32 nDimensionIndex ) override;
+
+ virtual LegendSymbolStyle getLegendSymbolStyle() override;
+ virtual css::uno::Any getExplicitSymbol( const VDataSeries& rSeries, sal_Int32 nPointIndex/*-1 for series symbol*/ ) override;
+
+private: //methods
+ void impl_createSeriesShapes();
+ bool impl_createArea( VDataSeries* pSeries
+ , std::vector<std::vector<css::drawing::Position3D>> const * pSeriesPoly
+ , std::vector<std::vector<css::drawing::Position3D>> const * pPreviousSeriesPoly
+ , PlottingPositionHelper const * pPosHelper );
+ bool impl_createLine( VDataSeries* pSeries
+ , std::vector<std::vector<css::drawing::Position3D>> const * pSeriesPoly
+ , PlottingPositionHelper* pPosHelper );
+ static bool create_stepped_line( std::vector<std::vector<css::drawing::Position3D>> aStartPoly
+ , css::chart2::CurveStyle eCurveStyle
+ , PlottingPositionHelper const * pPosHelper
+ , std::vector<std::vector<css::drawing::Position3D>> &aPoly );
+
+private: //member
+ std::unique_ptr<PlottingPositionHelper>
+ m_pMainPosHelper;
+
+ bool m_bArea;//false -> line or symbol only
+ bool m_bLine;
+ bool m_bSymbol;
+
+ //Properties for splines:
+ css::chart2::CurveStyle m_eCurveStyle;
+ sal_Int32 m_nCurveResolution;
+ sal_Int32 m_nSplineOrder;
+
+ rtl::Reference<SvxShapeGroupAnyD> m_xSeriesTarget;
+ rtl::Reference<SvxShapeGroupAnyD> m_xErrorBarTarget;
+ rtl::Reference<SvxShapeGroupAnyD> m_xTextTarget;
+ rtl::Reference<SvxShapeGroupAnyD> m_xRegressionCurveEquationTarget;
+};
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/BarChart.cxx b/chart2/source/view/charttypes/BarChart.cxx
new file mode 100644
index 000000000..b15706d76
--- /dev/null
+++ b/chart2/source/view/charttypes/BarChart.cxx
@@ -0,0 +1,976 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "BarChart.hxx"
+#include "BarPositionHelper.hxx"
+
+#include <ChartType.hxx>
+#include <ShapeFactory.hxx>
+#include <CommonConverters.hxx>
+#include <ObjectIdentifier.hxx>
+#include <LabelPositionHelper.hxx>
+#include <AxisIndexDefines.hxx>
+#include <Clipping.hxx>
+#include <DateHelper.hxx>
+#include <svx/scene3d.hxx>
+#include <comphelper/scopeguard.hxx>
+
+#include <com/sun/star/chart/DataLabelPlacement.hpp>
+
+#include <com/sun/star/chart2/DataPointGeometry3D.hpp>
+#include <rtl/math.hxx>
+#include <tools/diagnose_ex.h>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+using namespace ::rtl::math;
+using namespace ::com::sun::star::chart2;
+
+BarChart::BarChart( const rtl::Reference<ChartType>& xChartTypeModel
+ , sal_Int32 nDimensionCount )
+ : VSeriesPlotter( xChartTypeModel, nDimensionCount )
+ , m_pMainPosHelper( new BarPositionHelper() )
+{
+ PlotterBase::m_pPosHelper = m_pMainPosHelper.get();
+ VSeriesPlotter::m_pMainPosHelper = m_pMainPosHelper.get();
+
+ try
+ {
+ if( m_xChartTypeModel.is() )
+ {
+ m_xChartTypeModel->getPropertyValue( "OverlapSequence" ) >>= m_aOverlapSequence;
+ m_xChartTypeModel->getPropertyValue( "GapwidthSequence" ) >>= m_aGapwidthSequence;
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+}
+
+BarChart::~BarChart()
+{
+}
+
+PlottingPositionHelper& BarChart::getPlottingPositionHelper( sal_Int32 nAxisIndex ) const
+{
+ PlottingPositionHelper& rPosHelper = VSeriesPlotter::getPlottingPositionHelper( nAxisIndex );
+ BarPositionHelper* pBarPosHelper = dynamic_cast<BarPositionHelper*>(&rPosHelper);
+ if( pBarPosHelper && nAxisIndex >= 0 )
+ {
+ if( nAxisIndex < m_aOverlapSequence.getLength() )
+ pBarPosHelper->setInnerDistance( -m_aOverlapSequence[nAxisIndex]/100.0 );
+ if( nAxisIndex < m_aGapwidthSequence.getLength() )
+ pBarPosHelper->setOuterDistance( m_aGapwidthSequence[nAxisIndex]/100.0 );
+ }
+ return rPosHelper;
+}
+
+drawing::Direction3D BarChart::getPreferredDiagramAspectRatio() const
+{
+ drawing::Direction3D aRet(1.0,1.0,1.0);
+ if( m_nDimension == 3 )
+ {
+ aRet = drawing::Direction3D(1.0,-1.0,1.0);
+ BarPositionHelper* pPosHelper = dynamic_cast<BarPositionHelper*>(&( getPlottingPositionHelper( MAIN_AXIS_INDEX) ) );
+ if (pPosHelper)
+ {
+ drawing::Direction3D aScale( pPosHelper->getScaledLogicWidth() );
+ if(aScale.DirectionX!=0.0)
+ {
+ double fXSlotCount = 1.0;
+ if(!m_aZSlots.empty())
+ {
+ fXSlotCount = m_aZSlots.begin()->size();
+ }
+ aRet.DirectionZ = aScale.DirectionZ /
+ (aScale.DirectionX + aScale.DirectionX * (fXSlotCount-1.0) * pPosHelper->getScaledSlotWidth());
+ }
+ else
+ {
+ return VSeriesPlotter::getPreferredDiagramAspectRatio();
+ }
+ }
+ else
+ {
+ return VSeriesPlotter::getPreferredDiagramAspectRatio();
+ }
+
+ if(aRet.DirectionZ<0.05)
+ {
+ aRet.DirectionZ=0.05;
+ }
+ else if(aRet.DirectionZ>10)
+ {
+ aRet.DirectionZ=10;
+ }
+ if( m_pMainPosHelper && m_pMainPosHelper->isSwapXAndY() )
+ {
+ double fTemp = aRet.DirectionX;
+ aRet.DirectionX = aRet.DirectionY;
+ aRet.DirectionY = fTemp;
+ }
+ }
+ else
+ aRet = drawing::Direction3D(-1,-1,-1);
+ return aRet;
+}
+
+awt::Point BarChart::getLabelScreenPositionAndAlignment(
+ LabelAlignment& rAlignment, sal_Int32 nLabelPlacement
+ , double fScaledX, double fScaledLowerYValue, double fScaledUpperYValue, double fScaledZ
+ , double fScaledLowerBarDepth, double fScaledUpperBarDepth, double fBaseValue
+ , BarPositionHelper const * pPosHelper
+ ) const
+{
+ double fX = fScaledX;
+ double fY = fScaledUpperYValue;
+ double fZ = fScaledZ;
+ bool bReverse = !pPosHelper->isMathematicalOrientationY();
+ bool bNormalOutside = (!bReverse == (fBaseValue < fScaledUpperYValue));
+ double fDepth = fScaledUpperBarDepth;
+
+ switch(nLabelPlacement)
+ {
+ case css::chart::DataLabelPlacement::TOP:
+ {
+ if( !pPosHelper->isSwapXAndY() )
+ {
+ fY = bReverse ? fScaledLowerYValue : fScaledUpperYValue;
+ rAlignment = LABEL_ALIGN_TOP;
+ if(m_nDimension==3)
+ fDepth = bReverse ? fabs(fScaledLowerBarDepth) : fabs(fScaledUpperBarDepth);
+ }
+ else
+ {
+ fY -= (fScaledUpperYValue-fScaledLowerYValue)/2.0;
+ rAlignment = LABEL_ALIGN_CENTER;
+ OSL_FAIL( "top label placement is not really supported by horizontal bar charts" );
+ }
+ }
+ break;
+ case css::chart::DataLabelPlacement::BOTTOM:
+ {
+ if(!pPosHelper->isSwapXAndY())
+ {
+ fY = bReverse ? fScaledUpperYValue : fScaledLowerYValue;
+ rAlignment = LABEL_ALIGN_BOTTOM;
+ if(m_nDimension==3)
+ fDepth = bReverse ? fabs(fScaledUpperBarDepth) : fabs(fScaledLowerBarDepth);
+ }
+ else
+ {
+ fY -= (fScaledUpperYValue-fScaledLowerYValue)/2.0;
+ rAlignment = LABEL_ALIGN_CENTER;
+ OSL_FAIL( "bottom label placement is not supported by horizontal bar charts" );
+ }
+ }
+ break;
+ case css::chart::DataLabelPlacement::LEFT:
+ {
+ if( pPosHelper->isSwapXAndY() )
+ {
+ fY = bReverse ? fScaledUpperYValue : fScaledLowerYValue;
+ rAlignment = LABEL_ALIGN_LEFT;
+ if(m_nDimension==3)
+ fDepth = bReverse ? fabs(fScaledUpperBarDepth) : fabs(fScaledLowerBarDepth);
+ }
+ else
+ {
+ fY -= (fScaledUpperYValue-fScaledLowerYValue)/2.0;
+ rAlignment = LABEL_ALIGN_CENTER;
+ OSL_FAIL( "left label placement is not supported by column charts" );
+ }
+ }
+ break;
+ case css::chart::DataLabelPlacement::RIGHT:
+ {
+ if( pPosHelper->isSwapXAndY() )
+ {
+ fY = bReverse ? fScaledLowerYValue : fScaledUpperYValue;
+ rAlignment = LABEL_ALIGN_RIGHT;
+ if(m_nDimension==3)
+ fDepth = bReverse ? fabs(fScaledLowerBarDepth) : fabs(fScaledUpperBarDepth);
+ }
+ else
+ {
+ fY -= (fScaledUpperYValue-fScaledLowerYValue)/2.0;
+ rAlignment = LABEL_ALIGN_CENTER;
+ OSL_FAIL( "right label placement is not supported by column charts" );
+ }
+ }
+ break;
+ case css::chart::DataLabelPlacement::OUTSIDE:
+ {
+ fY = (fBaseValue < fScaledUpperYValue) ? fScaledUpperYValue : fScaledLowerYValue;
+ if( pPosHelper->isSwapXAndY() )
+ // if datapoint value is 0 the label will appear RIGHT in case of Bar Chart
+ if( fBaseValue == fScaledUpperYValue && fBaseValue == fScaledLowerYValue )
+ rAlignment = LABEL_ALIGN_RIGHT;
+ else
+ rAlignment = bNormalOutside ? LABEL_ALIGN_RIGHT : LABEL_ALIGN_LEFT;
+ else
+ // if datapoint value is 0 the label will appear TOP in case of Column Chart
+ if( fBaseValue == fScaledUpperYValue && fBaseValue == fScaledLowerYValue )
+ rAlignment = LABEL_ALIGN_TOP;
+ else
+ rAlignment = bNormalOutside ? LABEL_ALIGN_TOP : LABEL_ALIGN_BOTTOM;
+ if(m_nDimension==3)
+ fDepth = (fBaseValue < fScaledUpperYValue) ? fabs(fScaledUpperBarDepth) : fabs(fScaledLowerBarDepth);
+ }
+ break;
+ case css::chart::DataLabelPlacement::INSIDE:
+ {
+ fY = (fBaseValue < fScaledUpperYValue) ? fScaledUpperYValue : fScaledLowerYValue;
+ if( pPosHelper->isSwapXAndY() )
+ rAlignment = bNormalOutside ? LABEL_ALIGN_LEFT : LABEL_ALIGN_RIGHT;
+ else
+ rAlignment = bNormalOutside ? LABEL_ALIGN_BOTTOM : LABEL_ALIGN_TOP;
+ if(m_nDimension==3)
+ fDepth = (fBaseValue < fScaledUpperYValue) ? fabs(fScaledUpperBarDepth) : fabs(fScaledLowerBarDepth);
+ }
+ break;
+ case css::chart::DataLabelPlacement::NEAR_ORIGIN:
+ {
+ fY = (fBaseValue < fScaledUpperYValue) ? fScaledLowerYValue : fScaledUpperYValue;
+ if( pPosHelper->isSwapXAndY() )
+ // if datapoint value is 0 the label will appear RIGHT in case of Bar Chart
+ if( fBaseValue == fScaledUpperYValue && fBaseValue == fScaledLowerYValue )
+ rAlignment = LABEL_ALIGN_RIGHT;
+ else
+ rAlignment = bNormalOutside ? LABEL_ALIGN_RIGHT : LABEL_ALIGN_LEFT;
+ else
+ // if datapoint value is 0 the label will appear TOP in case of Column Chart
+ if( fBaseValue == fScaledUpperYValue && fBaseValue == fScaledLowerYValue )
+ rAlignment = LABEL_ALIGN_TOP;
+ else
+ rAlignment = bNormalOutside ? LABEL_ALIGN_TOP : LABEL_ALIGN_BOTTOM;
+ if(m_nDimension==3)
+ fDepth = (fBaseValue < fScaledUpperYValue) ? fabs(fScaledLowerBarDepth) : fabs(fScaledUpperBarDepth);
+ }
+ break;
+ case css::chart::DataLabelPlacement::CENTER:
+ fY -= (fScaledUpperYValue-fScaledLowerYValue)/2.0;
+ // if datapoint value is 0 the label will appear TOP/RIGHT in case of Column/Bar Charts
+ if( fBaseValue == fScaledUpperYValue && fBaseValue == fScaledLowerYValue )
+ if( pPosHelper->isSwapXAndY() )
+ rAlignment = LABEL_ALIGN_RIGHT;
+ else
+ rAlignment = LABEL_ALIGN_TOP;
+ else
+ rAlignment = LABEL_ALIGN_CENTER;
+ if(m_nDimension==3)
+ fDepth = fabs(fScaledUpperBarDepth-fScaledLowerBarDepth)/2.0;
+ break;
+ default:
+ OSL_FAIL("this label alignment is not implemented yet");
+
+ break;
+ }
+ if(m_nDimension==3)
+ fZ -= fDepth/2.0;
+
+ drawing::Position3D aScenePosition3D( pPosHelper->
+ transformScaledLogicToScene( fX, fY, fZ, true ) );
+ return LabelPositionHelper(m_nDimension,m_xLogicTarget)
+ .transformSceneToScreenPosition( aScenePosition3D );
+}
+
+rtl::Reference< SvxShape > BarChart::createDataPoint3D_Bar(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const drawing::Position3D& rPosition, const drawing::Direction3D& rSize
+ , double fTopHeight, sal_Int32 nRotateZAngleHundredthDegree
+ , const uno::Reference< beans::XPropertySet >& xObjectProperties
+ , sal_Int32 nGeometry3D )
+{
+ bool bRoundedEdges = true;
+ try
+ {
+ if( xObjectProperties.is() )
+ {
+ sal_Int16 nPercentDiagonal = 0;
+ xObjectProperties->getPropertyValue( "PercentDiagonal" ) >>= nPercentDiagonal;
+ if( nPercentDiagonal < 5 )
+ bRoundedEdges = false;
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+
+ rtl::Reference< SvxShape > xShape;
+ switch( nGeometry3D )
+ {
+ case DataPointGeometry3D::CYLINDER:
+ xShape = ShapeFactory::createCylinder( xTarget, rPosition, rSize, nRotateZAngleHundredthDegree );
+ break;
+ case DataPointGeometry3D::CONE:
+ xShape = ShapeFactory::createCone( xTarget, rPosition, rSize, fTopHeight, nRotateZAngleHundredthDegree );
+ break;
+ case DataPointGeometry3D::PYRAMID:
+ xShape = ShapeFactory::createPyramid( xTarget, rPosition, rSize, fTopHeight, nRotateZAngleHundredthDegree>0
+ , xObjectProperties, PropertyMapper::getPropertyNameMapForFilledSeriesProperties() );
+ break;
+ case DataPointGeometry3D::CUBOID:
+ default:
+ xShape = ShapeFactory::createCube( xTarget, rPosition, rSize
+ , nRotateZAngleHundredthDegree, xObjectProperties
+ , PropertyMapper::getPropertyNameMapForFilledSeriesProperties(), bRoundedEdges );
+ return xShape;
+ }
+ if( nGeometry3D != DataPointGeometry3D::PYRAMID )
+ PropertyMapper::setMappedProperties( *xShape, xObjectProperties, PropertyMapper::getPropertyNameMapForFilledSeriesProperties() );
+ return xShape;
+}
+
+namespace
+{
+bool lcl_hasGeometry3DVariableWidth( sal_Int32 nGeometry3D )
+{
+ bool bRet = false;
+ switch( nGeometry3D )
+ {
+ case DataPointGeometry3D::PYRAMID:
+ case DataPointGeometry3D::CONE:
+ bRet = true;
+ break;
+ case DataPointGeometry3D::CUBOID:
+ case DataPointGeometry3D::CYLINDER:
+ default:
+ bRet = false;
+ break;
+ }
+ return bRet;
+}
+}// end anonymous namespace
+
+void BarChart::addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 zSlot, sal_Int32 xSlot, sal_Int32 ySlot )
+{
+ if( !pSeries )
+ return;
+ if(m_nDimension==2)
+ {
+ //2ND_AXIS_IN_BARS put series on second scales to different z slot as temporary workaround
+ //this needs to be redesigned if 3d bars are also able to display secondary axes
+
+ sal_Int32 nAxisIndex = pSeries->getAttachedAxisIndex();
+ zSlot = nAxisIndex;
+
+ if( !pSeries->getGroupBarsPerAxis() )
+ zSlot = 0;
+ if(zSlot>=static_cast<sal_Int32>(m_aZSlots.size()))
+ m_aZSlots.resize(zSlot+1);
+ }
+ VSeriesPlotter::addSeries( std::move(pSeries), zSlot, xSlot, ySlot );
+}
+
+void BarChart::adaptOverlapAndGapwidthForGroupBarsPerAxis()
+{
+ //adapt m_aOverlapSequence and m_aGapwidthSequence for the groupBarsPerAxis feature
+ //thus the different series use the same settings
+
+ VDataSeries* pFirstSeries = getFirstSeries();
+ if(!pFirstSeries || pFirstSeries->getGroupBarsPerAxis())
+ return;
+
+ sal_Int32 nAxisIndex = pFirstSeries->getAttachedAxisIndex();
+ sal_Int32 nN = 0;
+ sal_Int32 nUseThisIndex = nAxisIndex;
+ if( nUseThisIndex < 0 || nUseThisIndex >= m_aOverlapSequence.getLength() )
+ nUseThisIndex = 0;
+ auto aOverlapSequenceRange = asNonConstRange(m_aOverlapSequence);
+ for( nN = 0; nN < m_aOverlapSequence.getLength(); nN++ )
+ {
+ if(nN!=nUseThisIndex)
+ aOverlapSequenceRange[nN] = m_aOverlapSequence[nUseThisIndex];
+ }
+
+ nUseThisIndex = nAxisIndex;
+ if( nUseThisIndex < 0 || nUseThisIndex >= m_aGapwidthSequence.getLength() )
+ nUseThisIndex = 0;
+ auto aGapwidthSequenceRange = asNonConstRange(m_aGapwidthSequence);
+ for( nN = 0; nN < m_aGapwidthSequence.getLength(); nN++ )
+ {
+ if(nN!=nUseThisIndex)
+ aGapwidthSequenceRange[nN] = m_aGapwidthSequence[nUseThisIndex];
+ }
+}
+
+void BarChart::createShapes()
+{
+ if( m_aZSlots.empty() ) //no series
+ return;
+
+ OSL_ENSURE(m_xLogicTarget.is()&&m_xFinalTarget.is(),"BarChart is not proper initialized");
+ if(!(m_xLogicTarget.is()&&m_xFinalTarget.is()))
+ return;
+
+ //the text labels should be always on top of the other series shapes
+ //therefore create an own group for the texts to move them to front
+ //(because the text group is created after the series group the texts are displayed on top)
+
+ //the regression curves should always be on top of the bars but beneath the text labels
+ //to achieve this the regression curve target is created after the series target and before the text target
+
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesTarget = createGroupShape( m_xLogicTarget );
+ rtl::Reference<SvxShapeGroupAnyD> xRegressionCurveTarget = createGroupShape( m_xLogicTarget );
+ rtl::Reference<SvxShapeGroupAnyD> xTextTarget = ShapeFactory::createGroup2D( m_xFinalTarget );
+
+ rtl::Reference<SvxShapeGroupAnyD> xRegressionCurveEquationTarget =
+ ShapeFactory::createGroup2D( m_xFinalTarget );
+ //check necessary here that different Y axis can not be stacked in the same group? ... hm?
+
+ bool bDrawConnectionLines = false;
+ bool bDrawConnectionLinesInited = false;
+
+ std::unordered_set<rtl::Reference<SvxShape>> aShapeSet;
+
+ const comphelper::ScopeGuard aGuard([aShapeSet]() {
+
+ std::unordered_set<E3dScene*> aSceneSet;
+
+ for (rtl::Reference<SvxShape> const & rShape : aShapeSet)
+ {
+ E3dScene* pScene = dynamic_cast<E3dScene*>(rShape->GetSdrObject());
+ if(nullptr != pScene)
+ {
+ aSceneSet.insert(pScene->getRootE3dSceneFromE3dObject());
+ }
+ }
+ for (E3dScene* pScene : aSceneSet)
+ {
+ pScene->ResumeReportingDirtyRects();
+ pScene->SetAllSceneRectsDirty();
+ }
+ });
+
+ adaptOverlapAndGapwidthForGroupBarsPerAxis();
+
+ //better performance for big data
+ std::map< VDataSeries*, FormerBarPoint > aSeriesFormerPointMap;
+ m_bPointsWereSkipped = false;
+
+ sal_Int32 nStartIndex = 0;
+ sal_Int32 nEndIndex = VSeriesPlotter::getPointCount();
+ //iterate through all x values per indices
+ for( sal_Int32 nPointIndex = nStartIndex; nPointIndex < nEndIndex; nPointIndex++ )
+ {
+ //sum up the values for all series in a complete z slot per attached axis
+ std::map< sal_Int32, double > aLogicYSumMap;
+ for( auto& rZSlot : m_aZSlots )
+ {
+ for( auto& rXSlot : rZSlot )
+ {
+ sal_Int32 nAttachedAxisIndex = rXSlot.getAttachedAxisIndexForFirstSeries();
+ aLogicYSumMap.insert({nAttachedAxisIndex, 0.0});
+
+ const sal_Int32 nSlotPoints = rXSlot.getPointCount();
+ if( nPointIndex >= nSlotPoints )
+ continue;
+
+ double fMinimumY = 0.0, fMaximumY = 0.0;
+ rXSlot.calculateYMinAndMaxForCategory( nPointIndex
+ , isSeparateStackingForDifferentSigns( 1 ), fMinimumY, fMaximumY, nAttachedAxisIndex );
+
+ if( !std::isnan( fMaximumY ) && fMaximumY > 0)
+ aLogicYSumMap[nAttachedAxisIndex] += fMaximumY;
+ if( !std::isnan( fMinimumY ) && fMinimumY < 0)
+ aLogicYSumMap[nAttachedAxisIndex] += fabs(fMinimumY);
+ }
+ }
+
+ sal_Int32 nZ=1;
+ for( auto& rZSlot : m_aZSlots )
+ {
+ doZSlot(bDrawConnectionLines, bDrawConnectionLinesInited, rZSlot, nZ, nPointIndex, nStartIndex,
+ xSeriesTarget, xRegressionCurveTarget, xRegressionCurveEquationTarget, xTextTarget,
+ aShapeSet, aSeriesFormerPointMap, aLogicYSumMap);
+ ++nZ;
+ }//next z slot
+ }//next category
+ if( bDrawConnectionLines )
+ {
+ for( auto const& rZSlot : m_aZSlots )
+ {
+ BarPositionHelper* pPosHelper = m_pMainPosHelper.get();
+ if( !rZSlot.empty() )
+ {
+ sal_Int32 nAttachedAxisIndex = rZSlot.front().getAttachedAxisIndexForFirstSeries();
+ //2ND_AXIS_IN_BARS so far one can assume to have the same plotter for each z slot
+ pPosHelper = dynamic_cast<BarPositionHelper*>(&( getPlottingPositionHelper( nAttachedAxisIndex ) ) );
+ if(!pPosHelper)
+ pPosHelper = m_pMainPosHelper.get();
+ }
+ PlotterBase::m_pPosHelper = pPosHelper;
+
+ //iterate through all x slots in this category
+ for( auto const& rXSlot : rZSlot )
+ {
+ //iterate through all series in this x slot
+ for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
+ {
+ if(!pSeries)
+ continue;
+ std::vector<std::vector<css::drawing::Position3D>>* pSeriesPoly = &pSeries->m_aPolyPolygonShape3D;
+ if(!ShapeFactory::hasPolygonAnyLines(*pSeriesPoly))
+ continue;
+
+ std::vector<std::vector<css::drawing::Position3D>> aPoly;
+ Clipping::clipPolygonAtRectangle( *pSeriesPoly, pPosHelper->getScaledLogicClipDoubleRect(), aPoly );
+
+ if(!ShapeFactory::hasPolygonAnyLines(aPoly))
+ continue;
+
+ //transformation 3) -> 4)
+ pPosHelper->transformScaledLogicToScene( aPoly );
+
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes(
+ getSeriesGroupShape(pSeries.get(), xSeriesTarget) );
+ rtl::Reference<SvxShapePolyPolygon> xShape( ShapeFactory::createLine2D(
+ xSeriesGroupShape_Shapes, aPoly ) );
+ PropertyMapper::setMappedProperties( *xShape, pSeries->getPropertiesOfSeries()
+ , PropertyMapper::getPropertyNameMapForFilledSeriesProperties() );
+ }
+ }
+ }
+ }
+
+ /* @todo remove series shapes if empty
+ */
+}
+
+void BarChart::doZSlot(
+ bool& bDrawConnectionLines, bool& bDrawConnectionLinesInited,
+ const std::vector< VDataSeriesGroup >& rZSlot,
+ const sal_Int32 nZ, const sal_Int32 nPointIndex, const sal_Int32 nStartIndex,
+ const rtl::Reference<SvxShapeGroupAnyD>& xSeriesTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveEquationTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xTextTarget,
+ std::unordered_set<rtl::Reference<SvxShape>>& aShapeSet,
+ std::map< VDataSeries*, FormerBarPoint >& aSeriesFormerPointMap,
+ std::map< sal_Int32, double >& aLogicYSumMap)
+{
+ //iterate through all x slots in this category
+ double fSlotX=0;
+ for( auto& rXSlot : rZSlot )
+ {
+ sal_Int32 nAttachedAxisIndex = rXSlot.getAttachedAxisIndexForFirstSeries();
+ //2ND_AXIS_IN_BARS so far one can assume to have the same plotter for each z slot
+ BarPositionHelper* pPosHelper = dynamic_cast<BarPositionHelper*>(&( getPlottingPositionHelper( nAttachedAxisIndex ) ) );
+ if(!pPosHelper)
+ pPosHelper = m_pMainPosHelper.get();
+
+ PlotterBase::m_pPosHelper = pPosHelper;
+
+ //update/create information for current group
+ pPosHelper->updateSeriesCount( rZSlot.size() );
+ double fLogicBaseWidth = pPosHelper->getScaledSlotWidth();
+
+ // get distance from base value to maximum and minimum
+
+ double fMinimumY = 0.0, fMaximumY = 0.0;
+ if( nPointIndex < rXSlot.getPointCount())
+ rXSlot.calculateYMinAndMaxForCategory( nPointIndex
+ , isSeparateStackingForDifferentSigns( 1 ), fMinimumY, fMaximumY, nAttachedAxisIndex );
+
+ double fLogicPositiveYSum = 0.0;
+ if( !std::isnan( fMaximumY ) )
+ fLogicPositiveYSum = fMaximumY;
+
+ double fLogicNegativeYSum = 0.0;
+ if( !std::isnan( fMinimumY ) )
+ fLogicNegativeYSum = fMinimumY;
+
+ if( pPosHelper->isPercentY() )
+ {
+ /* #i70395# fLogicPositiveYSum contains sum of all positive
+ values, if any, otherwise the highest negative value.
+ fLogicNegativeYSum contains sum of all negative values,
+ if any, otherwise the lowest positive value.
+ Afterwards, fLogicPositiveYSum will contain the maximum
+ (positive) value that is related to 100%. */
+
+ // do nothing if there are positive values only
+ if( fLogicNegativeYSum < 0.0 )
+ {
+ // fLogicPositiveYSum<0 => negative values only, use absolute of negative sum
+ if( fLogicPositiveYSum < 0.0 )
+ fLogicPositiveYSum = -fLogicNegativeYSum;
+ // otherwise there are positive and negative values, calculate total distance
+ else
+ fLogicPositiveYSum -= fLogicNegativeYSum;
+ }
+ fLogicNegativeYSum = 0.0;
+ }
+
+ doXSlot(rXSlot, bDrawConnectionLines, bDrawConnectionLinesInited, nZ, nPointIndex, nStartIndex,
+ xSeriesTarget, xRegressionCurveTarget, xRegressionCurveEquationTarget, xTextTarget,
+ aShapeSet, aSeriesFormerPointMap, aLogicYSumMap,
+ fLogicBaseWidth, fSlotX, pPosHelper, fLogicPositiveYSum, fLogicNegativeYSum, nAttachedAxisIndex);
+
+ fSlotX+=1.0;
+ }//next x slot
+}
+
+
+void BarChart::doXSlot(
+ const VDataSeriesGroup& rXSlot,
+ bool& bDrawConnectionLines, bool& bDrawConnectionLinesInited,
+ const sal_Int32 nZ, const sal_Int32 nPointIndex, const sal_Int32 nStartIndex,
+ const rtl::Reference<SvxShapeGroupAnyD>& xSeriesTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveEquationTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xTextTarget,
+ std::unordered_set<rtl::Reference<SvxShape>>& aShapeSet,
+ std::map< VDataSeries*, FormerBarPoint >& aSeriesFormerPointMap,
+ std::map< sal_Int32, double >& aLogicYSumMap,
+ const double fLogicBaseWidth, const double fSlotX,
+ BarPositionHelper* const pPosHelper,
+ const double fLogicPositiveYSum, const double fLogicNegativeYSum,
+ const sal_Int32 nAttachedAxisIndex)
+{
+ double fBaseValue = 0.0;
+ if( !pPosHelper->isPercentY() && rXSlot.m_aSeriesVector.size()<=1 )
+ fBaseValue = pPosHelper->getBaseValueY();
+ double fPositiveLogicYForNextSeries = fBaseValue;
+ double fNegativeLogicYForNextSeries = fBaseValue;
+
+ //iterate through all series in this x slot
+ for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
+ {
+ if(!pSeries)
+ continue;
+
+ bool bHasFillColorMapping = pSeries->hasPropertyMapping("FillColor");
+
+ bool bOnlyConnectionLinesForThisPoint = false;
+
+ if(nPointIndex==nStartIndex)//do not create a regression line for each point
+ createRegressionCurvesShapes( *pSeries, xRegressionCurveTarget, xRegressionCurveEquationTarget,
+ m_pPosHelper->maySkipPointsInRegressionCalculation());
+
+ if( !bDrawConnectionLinesInited )
+ {
+ bDrawConnectionLines = pSeries->getConnectBars();
+ if( m_nDimension==3 )
+ bDrawConnectionLines = false;
+ if( bDrawConnectionLines && rXSlot.m_aSeriesVector.size()==1 )
+ {
+ //detect whether we have a stacked chart or not:
+ StackingDirection eDirection = pSeries->getStackingDirection();
+ if( eDirection != StackingDirection_Y_STACKING )
+ bDrawConnectionLines = false;
+ }
+ bDrawConnectionLinesInited = true;
+ }
+
+ // Use another XShapes for background, so we can avoid needing to set the Z-order on all of them,
+ // which is expensive in bulk.
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes(getSeriesGroupShape(pSeries.get(), xSeriesTarget));
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesBackgroundShape_Shapes(getSeriesGroupShape(pSeries.get(), xSeriesTarget));
+ aShapeSet.insert(xSeriesGroupShape_Shapes);
+ aShapeSet.insert(xSeriesBackgroundShape_Shapes);
+ // Suspend setting rects dirty for the duration of this call
+ E3dScene* pScene = dynamic_cast<E3dScene*>(xSeriesGroupShape_Shapes->GetSdrObject());
+ if (pScene)
+ pScene->SuspendReportingDirtyRects();
+ pScene = dynamic_cast<E3dScene*>(xSeriesBackgroundShape_Shapes->GetSdrObject());
+ if (pScene)
+ pScene->SuspendReportingDirtyRects();
+
+ //collect data point information (logic coordinates, style ):
+ double fUnscaledLogicX = pSeries->getXValue( nPointIndex );
+ fUnscaledLogicX = DateHelper::RasterizeDateValue( fUnscaledLogicX, m_aNullDate, m_nTimeResolution );
+ if(std::isnan(fUnscaledLogicX))
+ continue;//point not visible
+ if(fUnscaledLogicX<pPosHelper->getLogicMinX())
+ continue;//point not visible
+ if(fUnscaledLogicX>pPosHelper->getLogicMaxX())
+ continue;//point not visible
+ if(pPosHelper->isStrongLowerRequested(0) && fUnscaledLogicX==pPosHelper->getLogicMaxX())
+ continue;//point not visible
+ double fLogicX = pPosHelper->getScaledSlotPos( fUnscaledLogicX, fSlotX );
+
+ double fLogicBarHeight = pSeries->getYValue( nPointIndex );
+ if( std::isnan( fLogicBarHeight )) //no value at this category
+ continue;
+
+ double fLogicValueForLabeDisplay = fLogicBarHeight;
+ fLogicBarHeight-=fBaseValue;
+
+ if( pPosHelper->isPercentY() )
+ {
+ if(fLogicPositiveYSum!=0.0)
+ fLogicBarHeight = fabs( fLogicBarHeight )/fLogicPositiveYSum;
+ else
+ fLogicBarHeight = 0.0;
+ }
+
+ // tdf#114141 to draw the top of the zero height 3D bar
+ // we set a small positive value, here the smallest one for the type double (DBL_MIN)
+ if( fLogicBarHeight == 0.0 )
+ fLogicBarHeight = DBL_MIN;
+
+ //sort negative and positive values, to display them on different sides of the x axis
+ bool bPositive = fLogicBarHeight >= 0.0;
+ double fLowerYValue = bPositive ? fPositiveLogicYForNextSeries : fNegativeLogicYForNextSeries;
+ double fUpperYValue = fLowerYValue+fLogicBarHeight;
+ if( bPositive )
+ fPositiveLogicYForNextSeries += fLogicBarHeight;
+ else
+ fNegativeLogicYForNextSeries += fLogicBarHeight;
+
+ double fLogicZ = 1.0;//as defined
+ if(m_nDimension==3)
+ fLogicZ = nZ+0.5;
+
+ drawing::Position3D aUnscaledLogicPosition( fUnscaledLogicX, fUpperYValue, fLogicZ );
+
+ //@todo ... start an iteration over the different breaks of the axis
+ //each subsystem may add an additional shape to form the whole point
+ //create a group shape for this point and add to the series shape:
+// uno::Reference< drawing::XShapes > xPointGroupShape_Shapes( createGroupShape(xSeriesGroupShape_Shapes) );
+// uno::Reference<drawing::XShape> xPointGroupShape_Shape =
+// uno::Reference<drawing::XShape>( xPointGroupShape_Shapes, uno::UNO_QUERY );
+ //as long as we do not iterate we do not need to create an additional group for each point
+ uno::Reference< beans::XPropertySet > xDataPointProperties( pSeries->getPropertiesOfPoint( nPointIndex ) );
+ sal_Int32 nGeometry3D = DataPointGeometry3D::CUBOID;
+ if(m_nDimension==3) try
+ {
+ xDataPointProperties->getPropertyValue( "Geometry3D") >>= nGeometry3D;
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+
+ //@todo iterate through all subsystems to create partial points
+ {
+ //@todo select a suitable PositionHelper for this subsystem
+ BarPositionHelper* pSubPosHelper = pPosHelper;
+
+ double fUnclippedUpperYValue = fUpperYValue;
+
+ //apply clipping to Y
+ if( !pPosHelper->clipYRange(fLowerYValue,fUpperYValue) )
+ {
+ if( bDrawConnectionLines )
+ bOnlyConnectionLinesForThisPoint = true;
+ else
+ continue;
+ }
+ //@todo clipping of X and Z is not fully integrated so far, as there is a need to create different objects
+
+ //apply scaling to Y before calculating width (necessary to maintain gradient in clipped objects)
+ pSubPosHelper->doLogicScaling(nullptr,&fLowerYValue,nullptr);
+ pSubPosHelper->doLogicScaling(nullptr,&fUpperYValue,nullptr);
+ //scaling of X and Z is not provided as the created objects should be symmetric in that dimensions
+
+ pSubPosHelper->doLogicScaling(nullptr,&fUnclippedUpperYValue,nullptr);
+
+ //calculate resulting width
+ double fCompleteHeight = bPositive ? fLogicPositiveYSum : fLogicNegativeYSum;
+ if( pPosHelper->isPercentY() )
+ fCompleteHeight = 1.0;
+ double fLogicBarWidth = fLogicBaseWidth;
+ double fTopHeight=approxSub(fCompleteHeight,fUpperYValue);
+ if(!bPositive)
+ fTopHeight=approxSub(fCompleteHeight,fLowerYValue);
+ double fLogicYStart = bPositive ? fLowerYValue : fUpperYValue;
+ double fMiddleHeight = fUpperYValue-fLowerYValue;
+ if(!bPositive)
+ fMiddleHeight*=-1.0;
+ double fLogicBarDepth = 0.5;
+ if(m_nDimension==3)
+ {
+ if( lcl_hasGeometry3DVariableWidth(nGeometry3D) && fCompleteHeight!=0.0 )
+ {
+ double fHeight = fCompleteHeight-fLowerYValue;
+ if(!bPositive)
+ fHeight = fCompleteHeight-fUpperYValue;
+ fLogicBarWidth = fLogicBaseWidth*fHeight/fCompleteHeight;
+ if(fLogicBarWidth<=0.0)
+ fLogicBarWidth=fLogicBaseWidth;
+ fLogicBarDepth = fLogicBarDepth*fHeight/fCompleteHeight;
+ if(fLogicBarDepth<=0.0)
+ fLogicBarDepth*=-1.0;
+ }
+ }
+
+ //better performance for big data
+ FormerBarPoint aFormerPoint( aSeriesFormerPointMap[pSeries.get()] );
+ pPosHelper->setCoordinateSystemResolution( m_aCoordinateSystemResolution );
+ if( !pSeries->isAttributedDataPoint(nPointIndex)
+ &&
+ pPosHelper->isSameForGivenResolution( aFormerPoint.m_fX, aFormerPoint.m_fUpperY, aFormerPoint.m_fZ
+ , fLogicX, fUpperYValue, fLogicZ )
+ &&
+ pPosHelper->isSameForGivenResolution( aFormerPoint.m_fX, aFormerPoint.m_fLowerY, aFormerPoint.m_fZ
+ , fLogicX, fLowerYValue, fLogicZ )
+ )
+ {
+ m_bPointsWereSkipped = true;
+ continue;
+ }
+ aSeriesFormerPointMap[pSeries.get()] = FormerBarPoint(fLogicX,fUpperYValue,fLowerYValue,fLogicZ);
+
+ if( bDrawConnectionLines )
+ {
+ //store point information for connection lines
+
+ drawing::Position3D aLeftUpperPoint( fLogicX-fLogicBarWidth/2.0,fUnclippedUpperYValue,fLogicZ );
+ drawing::Position3D aRightUpperPoint( fLogicX+fLogicBarWidth/2.0,fUnclippedUpperYValue,fLogicZ );
+
+ if( isValidPosition(aLeftUpperPoint) )
+ AddPointToPoly( pSeries->m_aPolyPolygonShape3D, aLeftUpperPoint );
+ if( isValidPosition(aRightUpperPoint) )
+ AddPointToPoly( pSeries->m_aPolyPolygonShape3D, aRightUpperPoint );
+ }
+
+ if( bOnlyConnectionLinesForThisPoint )
+ continue;
+
+ //maybe additional possibility for performance improvement
+ //bool bCreateLineInsteadOfComplexGeometryDueToMissingSpace = false;
+ //pPosHelper->isSameForGivenResolution( fLogicX-fLogicBarWidth/2.0, fLowerYValue, fLogicZ
+ // , fLogicX+fLogicBarWidth/2.0, fLowerYValue, fLogicZ );
+
+ //create partial point
+ if( !approxEqual(fLowerYValue,fUpperYValue) )
+ {
+ rtl::Reference< SvxShape > xShape;
+ if( m_nDimension==3 )
+ {
+ drawing::Position3D aLogicBottom (fLogicX,fLogicYStart,fLogicZ);
+ drawing::Position3D aLogicLeftBottomFront (fLogicX+fLogicBarWidth/2.0,fLogicYStart,fLogicZ-fLogicBarDepth/2.0);
+ drawing::Position3D aLogicRightDeepTop (fLogicX-fLogicBarWidth/2.0,fLogicYStart+fMiddleHeight,fLogicZ+fLogicBarDepth/2.0);
+ drawing::Position3D aLogicTopTop (fLogicX,fLogicYStart+fMiddleHeight+fTopHeight,fLogicZ);
+
+ ::chart::XTransformation2* pTransformation = pSubPosHelper->getTransformationScaledLogicToScene();
+
+ //transformation 3) -> 4)
+ drawing::Position3D aTransformedBottom ( pTransformation->transform( aLogicBottom ) );
+ drawing::Position3D aTransformedLeftBottomFront ( pTransformation->transform( aLogicLeftBottomFront ) );
+ drawing::Position3D aTransformedRightDeepTop ( pTransformation->transform( aLogicRightDeepTop ) );
+ drawing::Position3D aTransformedTopTop ( pTransformation->transform( aLogicTopTop ) );
+
+ drawing::Direction3D aSize = aTransformedRightDeepTop - aTransformedLeftBottomFront;
+ drawing::Direction3D aTopSize( aTransformedTopTop - aTransformedRightDeepTop );
+ fTopHeight = aTopSize.DirectionY;
+
+ sal_Int32 nRotateZAngleHundredthDegree = 0;
+ if( pPosHelper->isSwapXAndY() )
+ {
+ fTopHeight = aTopSize.DirectionX;
+ nRotateZAngleHundredthDegree = 90*100;
+ aSize = drawing::Direction3D(aSize.DirectionY,aSize.DirectionX,aSize.DirectionZ);
+ }
+
+ if( aSize.DirectionX < 0 )
+ aSize.DirectionX *= -1.0;
+ if( aSize.DirectionZ < 0 )
+ aSize.DirectionZ *= -1.0;
+ if( fTopHeight < 0 )
+ fTopHeight *= -1.0;
+
+ xShape = createDataPoint3D_Bar(
+ xSeriesGroupShape_Shapes, aTransformedBottom, aSize, fTopHeight, nRotateZAngleHundredthDegree
+ , xDataPointProperties, nGeometry3D );
+ }
+ else //m_nDimension!=3
+ {
+ drawing::Position3D aLeftUpperPoint( fLogicX-fLogicBarWidth/2.0,fUpperYValue,fLogicZ );
+ drawing::Position3D aRightUpperPoint( fLogicX+fLogicBarWidth/2.0,fUpperYValue,fLogicZ );
+ std::vector<std::vector<css::drawing::Position3D>> aPoly
+ {
+ { // inner vector
+ drawing::Position3D( fLogicX-fLogicBarWidth/2.0,fLowerYValue,fLogicZ),
+ drawing::Position3D( fLogicX+fLogicBarWidth/2.0,fLowerYValue,fLogicZ),
+ aRightUpperPoint,
+ aLeftUpperPoint,
+ drawing::Position3D( fLogicX-fLogicBarWidth/2.0,fLowerYValue,fLogicZ)
+ }
+ };
+ pPosHelper->transformScaledLogicToScene( aPoly );
+ xShape = ShapeFactory::createArea2D( xSeriesGroupShape_Shapes, aPoly );
+ PropertyMapper::setMappedProperties( *xShape, xDataPointProperties, PropertyMapper::getPropertyNameMapForFilledSeriesProperties() );
+ }
+
+ if(bHasFillColorMapping)
+ {
+ double nPropVal = pSeries->getValueByProperty(nPointIndex, "FillColor");
+ if(!std::isnan(nPropVal))
+ {
+ xShape->setPropertyValue("FillColor", uno::Any(static_cast<sal_Int32>(nPropVal)));
+ }
+ }
+ //set name/classified ObjectID (CID)
+ ShapeFactory::setShapeName(xShape
+ , ObjectIdentifier::createPointCID(
+ pSeries->getPointCID_Stub(),nPointIndex) );
+ }
+
+ //create error bar
+ createErrorBar_Y( aUnscaledLogicPosition, *pSeries, nPointIndex, m_xLogicTarget, &fLogicX );
+
+ //create data point label
+ if( pSeries->getDataPointLabelIfLabel(nPointIndex) )
+ {
+ double fLogicSum = aLogicYSumMap[nAttachedAxisIndex];
+
+ LabelAlignment eAlignment(LABEL_ALIGN_CENTER);
+ sal_Int32 nLabelPlacement = pSeries->getLabelPlacement( nPointIndex, m_xChartTypeModel, pPosHelper->isSwapXAndY() );
+
+ double fLowerBarDepth = fLogicBarDepth;
+ double fUpperBarDepth = fLogicBarDepth;
+ {
+ if( lcl_hasGeometry3DVariableWidth(nGeometry3D) && fCompleteHeight!=0.0 )
+ {
+ double fOuterBarDepth = fLogicBarDepth * fTopHeight/(fabs(fCompleteHeight));
+ fLowerBarDepth = (fBaseValue < fUpperYValue) ? fabs(fLogicBarDepth) : fabs(fOuterBarDepth);
+ fUpperBarDepth = (fBaseValue < fUpperYValue) ? fabs(fOuterBarDepth) : fabs(fLogicBarDepth);
+ }
+ }
+
+ awt::Point aScreenPosition2D = getLabelScreenPositionAndAlignment(
+ eAlignment, nLabelPlacement, fLogicX, fLowerYValue, fUpperYValue, fLogicZ,
+ fLowerBarDepth, fUpperBarDepth, fBaseValue, pPosHelper);
+ sal_Int32 nOffset = 0;
+ if(eAlignment!=LABEL_ALIGN_CENTER)
+ {
+ nOffset = 100;//add some spacing //@todo maybe get more intelligent values
+ if( m_nDimension == 3 )
+ nOffset = 260;
+ }
+ createDataLabel(
+ xTextTarget, *pSeries, nPointIndex,
+ fLogicValueForLabeDisplay, fLogicSum, aScreenPosition2D, eAlignment, nOffset);
+ }
+
+ }//end iteration through partial points
+
+ }//next series in x slot (next y slot)
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/BarChart.hxx b/chart2/source/view/charttypes/BarChart.hxx
new file mode 100644
index 000000000..52c3b6177
--- /dev/null
+++ b/chart2/source/view/charttypes/BarChart.hxx
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <memory>
+#include <VSeriesPlotter.hxx>
+
+namespace chart
+{
+class BarPositionHelper;
+
+class BarChart : public VSeriesPlotter
+{
+ // public methods
+public:
+ BarChart() = delete;
+
+ BarChart( const rtl::Reference< ::chart::ChartType >& xChartTypeModel
+ , sal_Int32 nDimensionCount );
+ virtual ~BarChart() override;
+
+ virtual void createShapes() override;
+ virtual void addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 zSlot, sal_Int32 xSlot, sal_Int32 ySlot ) override;
+
+ virtual css::drawing::Direction3D getPreferredDiagramAspectRatio() const override;
+
+private: //methods
+ static rtl::Reference< SvxShape >
+ createDataPoint3D_Bar(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const css::drawing::Position3D& rPosition
+ , const css::drawing::Direction3D& rSize
+ , double fTopHeight, sal_Int32 nRotateZAngleHundredthDegree
+ , const css::uno::Reference< css::beans::XPropertySet >& xObjectProperties
+ , sal_Int32 nGeometry3D );
+
+ css::awt::Point getLabelScreenPositionAndAlignment(
+ LabelAlignment& rAlignment, sal_Int32 nLabelPlacement
+ , double fScaledX, double fScaledLowerYValue, double fScaledUpperYValue, double fScaledZ
+ , double fScaledLowerBarDepth, double fScaledUpperBarDepth, double fBaseValue
+ , BarPositionHelper const * pPosHelper ) const;
+
+ virtual PlottingPositionHelper& getPlottingPositionHelper( sal_Int32 nAxisIndex ) const override;//nAxisIndex indicates whether the position belongs to the main axis ( nAxisIndex==0 ) or secondary axis ( nAxisIndex==1 )
+
+ void adaptOverlapAndGapwidthForGroupBarsPerAxis();
+
+ //better performance for big data
+ struct FormerBarPoint
+ {
+ FormerBarPoint( double fX, double fUpperY, double fLowerY, double fZ )
+ : m_fX(fX), m_fUpperY(fUpperY), m_fLowerY(fLowerY), m_fZ(fZ)
+ {}
+ FormerBarPoint()
+ : m_fX(std::numeric_limits<double>::quiet_NaN())
+ , m_fUpperY(std::numeric_limits<double>::quiet_NaN())
+ , m_fLowerY(std::numeric_limits<double>::quiet_NaN())
+ , m_fZ(std::numeric_limits<double>::quiet_NaN())
+ {
+ }
+
+ double m_fX;
+ double m_fUpperY;
+ double m_fLowerY;
+ double m_fZ;
+ };
+
+ void doZSlot(
+ bool& bDrawConnectionLines, bool& bDrawConnectionLinesInited, const std::vector< VDataSeriesGroup >& rZSlot,
+ sal_Int32 nZ, sal_Int32 nPointIndex, sal_Int32 nStartIndex,
+ const rtl::Reference<SvxShapeGroupAnyD>& xSeriesTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveEquationTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xTextTarget,
+ std::unordered_set<rtl::Reference<SvxShape>>& aShapeSet,
+ std::map< VDataSeries*, FormerBarPoint >& aSeriesFormerPointMap,
+ std::map< sal_Int32, double >& aLogicYSumMap);
+
+ void doXSlot(
+ const VDataSeriesGroup& rXSlot,
+ bool& bDrawConnectionLines, bool& bDrawConnectionLinesInited,
+ sal_Int32 nZ, sal_Int32 nPointIndex, sal_Int32 nStartIndex,
+ const rtl::Reference<SvxShapeGroupAnyD>& xSeriesTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveEquationTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xTextTarget,
+ std::unordered_set<rtl::Reference<SvxShape>>& aShapeSet,
+ std::map< VDataSeries*, FormerBarPoint >& aSeriesFormerPointMap,
+ std::map< sal_Int32, double >& aLogicYSumMap,
+ double fLogicBaseWidth, double fSlotX,
+ BarPositionHelper* const pPosHelper,
+ double fLogicPositiveYSum, double fLogicNegativeYSum,
+ sal_Int32 nAttachedAxisIndex);
+
+private: //member
+ std::unique_ptr<BarPositionHelper> m_pMainPosHelper;
+ css::uno::Sequence< sal_Int32 > m_aOverlapSequence;
+ css::uno::Sequence< sal_Int32 > m_aGapwidthSequence;
+};
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/BarPositionHelper.cxx b/chart2/source/view/charttypes/BarPositionHelper.cxx
new file mode 100644
index 000000000..f8ac3e7d6
--- /dev/null
+++ b/chart2/source/view/charttypes/BarPositionHelper.cxx
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "BarPositionHelper.hxx"
+#include <DateHelper.hxx>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::chart2;
+
+BarPositionHelper::BarPositionHelper()
+ : CategoryPositionHelper( 1 )
+{
+ AllowShiftXAxisPos(true);
+ AllowShiftZAxisPos(true);
+}
+
+BarPositionHelper::BarPositionHelper( const BarPositionHelper& rSource )
+ : CategoryPositionHelper( rSource )
+ , PlottingPositionHelper( rSource )
+{
+}
+
+BarPositionHelper::~BarPositionHelper()
+{
+}
+
+std::unique_ptr<PlottingPositionHelper> BarPositionHelper::clone() const
+{
+ return std::make_unique<BarPositionHelper>(*this);
+}
+
+void BarPositionHelper::updateSeriesCount( double fSeriesCount )
+{
+ m_fSeriesCount = fSeriesCount;
+}
+
+double BarPositionHelper::getScaledSlotPos( double fUnscaledLogicX, double fSeriesNumber ) const
+{
+ if( m_bDateAxis )
+ fUnscaledLogicX = DateHelper::RasterizeDateValue( fUnscaledLogicX, m_aNullDate, m_nTimeResolution );
+ double fScaledLogicX(fUnscaledLogicX);
+ doLogicScaling(&fScaledLogicX,nullptr,nullptr);
+ fScaledLogicX = CategoryPositionHelper::getScaledSlotPos( fScaledLogicX, fSeriesNumber );
+ return fScaledLogicX;
+
+}
+
+void BarPositionHelper::setScaledCategoryWidth( double fScaledCategoryWidth )
+{
+ m_fScaledCategoryWidth = fScaledCategoryWidth;
+ CategoryPositionHelper::setCategoryWidth( m_fScaledCategoryWidth );
+}
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/BarPositionHelper.hxx b/chart2/source/view/charttypes/BarPositionHelper.hxx
new file mode 100644
index 000000000..961ea988f
--- /dev/null
+++ b/chart2/source/view/charttypes/BarPositionHelper.hxx
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <PlottingPositionHelper.hxx>
+#include "CategoryPositionHelper.hxx"
+
+namespace chart
+{
+class BarPositionHelper : public CategoryPositionHelper, public PlottingPositionHelper
+{
+public:
+ explicit BarPositionHelper();
+ BarPositionHelper(const BarPositionHelper& rSource);
+ virtual ~BarPositionHelper() override;
+
+ virtual std::unique_ptr<PlottingPositionHelper> clone() const override;
+
+ void updateSeriesCount(double fSeriesCount); /*only enter the size of x stacked series*/
+
+ virtual double getScaledSlotPos(double fCategoryX, double fSeriesNumber) const override;
+ virtual void setScaledCategoryWidth(double fScaledCategoryWidth) override;
+};
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/BubbleChart.cxx b/chart2/source/view/charttypes/BubbleChart.cxx
new file mode 100644
index 000000000..803cf73b2
--- /dev/null
+++ b/chart2/source/view/charttypes/BubbleChart.cxx
@@ -0,0 +1,362 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "BubbleChart.hxx"
+#include <PlottingPositionHelper.hxx>
+#include <ShapeFactory.hxx>
+#include <ObjectIdentifier.hxx>
+#include <LabelPositionHelper.hxx>
+#include <ChartType.hxx>
+
+#include <com/sun/star/chart/DataLabelPlacement.hpp>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <limits>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::chart2;
+
+BubbleChart::BubbleChart( const rtl::Reference<ChartType>& xChartTypeModel
+ , sal_Int32 nDimensionCount )
+ : VSeriesPlotter( xChartTypeModel, nDimensionCount, false )
+ , m_fMaxLogicBubbleSize( 0.0 )
+ , m_fBubbleSizeFactorToScreen( 1.0 )
+{
+ // We only support 2 dimensional bubble charts
+ assert(nDimensionCount == 2);
+
+ if( !m_pMainPosHelper )
+ m_pMainPosHelper = new PlottingPositionHelper();
+ PlotterBase::m_pPosHelper = m_pMainPosHelper;
+}
+
+BubbleChart::~BubbleChart()
+{
+ delete m_pMainPosHelper;
+}
+
+void BubbleChart::calculateMaximumLogicBubbleSize()
+{
+ double fMaxSize = 0.0;
+
+ sal_Int32 nEndIndex = VSeriesPlotter::getPointCount();
+ for( sal_Int32 nIndex = 0; nIndex < nEndIndex; nIndex++ )
+ {
+ for( auto const& rZSlot : m_aZSlots )
+ {
+ for( auto const& rXSlot : rZSlot )
+ {
+ for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
+ {
+ if(!pSeries)
+ continue;
+
+ double fSize = pSeries->getBubble_Size( nIndex );
+ if( fSize > fMaxSize )
+ fMaxSize = fSize;
+ }
+ }
+ }
+ }
+
+ m_fMaxLogicBubbleSize = fMaxSize;
+}
+
+void BubbleChart::calculateBubbleSizeScalingFactor()
+{
+ double fLogicZ=1.0;
+ drawing::Position3D aSceneMinPos( m_pMainPosHelper->transformLogicToScene( m_pMainPosHelper->getLogicMinX(),m_pMainPosHelper->getLogicMinY(),fLogicZ, false ) );
+ drawing::Position3D aSceneMaxPos( m_pMainPosHelper->transformLogicToScene( m_pMainPosHelper->getLogicMaxX(),m_pMainPosHelper->getLogicMaxY(),fLogicZ, false ) );
+
+ awt::Point aScreenMinPos( LabelPositionHelper(m_nDimension,m_xLogicTarget).transformSceneToScreenPosition( aSceneMinPos ) );
+ awt::Point aScreenMaxPos( LabelPositionHelper(m_nDimension,m_xLogicTarget).transformSceneToScreenPosition( aSceneMaxPos ) );
+
+ sal_Int32 nWidth = abs( aScreenMaxPos.X - aScreenMinPos.X );
+ sal_Int32 nHeight = abs( aScreenMaxPos.Y - aScreenMinPos.Y );
+
+ sal_Int32 nMinExtend = std::min( nWidth, nHeight );
+ m_fBubbleSizeFactorToScreen = nMinExtend * 0.25;//max bubble size is 25 percent of diagram size
+}
+
+drawing::Direction3D BubbleChart::transformToScreenBubbleSize( double fLogicSize )
+{
+ drawing::Direction3D aRet(0,0,0);
+
+ if( std::isnan(fLogicSize) || std::isinf(fLogicSize) )
+ return aRet;
+
+ double fMaxSize = m_fMaxLogicBubbleSize;
+
+ double fMaxRadius = sqrt( fMaxSize / M_PI );
+ double fRadius = sqrt( fLogicSize / M_PI );
+
+ aRet.DirectionX = m_fBubbleSizeFactorToScreen * fRadius / fMaxRadius;
+ aRet.DirectionY = aRet.DirectionX;
+
+ return aRet;
+}
+
+bool BubbleChart::isExpandIfValuesCloseToBorder( sal_Int32 /*nDimensionIndex*/ )
+{
+ return true;
+}
+
+bool BubbleChart::isSeparateStackingForDifferentSigns( sal_Int32 /*nDimensionIndex*/ )
+{
+ return false;
+}
+
+LegendSymbolStyle BubbleChart::getLegendSymbolStyle()
+{
+ return LegendSymbolStyle::Circle;
+}
+
+drawing::Direction3D BubbleChart::getPreferredDiagramAspectRatio() const
+{
+ return drawing::Direction3D(-1,-1,-1);
+}
+
+namespace {
+
+//better performance for big data
+struct FormerPoint
+{
+ FormerPoint( double fX, double fY, double fZ )
+ : m_fX(fX), m_fY(fY), m_fZ(fZ)
+ {}
+ FormerPoint()
+ : m_fX(std::numeric_limits<double>::quiet_NaN())
+ , m_fY(std::numeric_limits<double>::quiet_NaN())
+ , m_fZ(std::numeric_limits<double>::quiet_NaN())
+ {
+ }
+
+ double m_fX;
+ double m_fY;
+ double m_fZ;
+};
+
+}
+
+void BubbleChart::createShapes()
+{
+ if( m_aZSlots.empty() ) //no series
+ return;
+
+ OSL_ENSURE(m_xLogicTarget.is()&&m_xFinalTarget.is(),"BubbleChart is not proper initialized");
+ if(!(m_xLogicTarget.is()&&m_xFinalTarget.is()))
+ return;
+
+ //therefore create an own group for the texts and the error bars to move them to front
+ //(because the text group is created after the series group the texts are displayed on top)
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesTarget = createGroupShape( m_xLogicTarget );
+ rtl::Reference< SvxShapeGroup > xTextTarget = ShapeFactory::createGroup2D( m_xFinalTarget );
+
+ //update/create information for current group
+ double fLogicZ = 1.0;//as defined
+
+ sal_Int32 const nStartIndex = 0; // inclusive ;..todo get somehow from x scale
+ sal_Int32 nEndIndex = VSeriesPlotter::getPointCount();
+ if(nEndIndex<=0)
+ nEndIndex=1;
+
+ //better performance for big data
+ std::map< VDataSeries*, FormerPoint > aSeriesFormerPointMap;
+ m_bPointsWereSkipped = false;
+ sal_Int32 nSkippedPoints = 0;
+ sal_Int32 nCreatedPoints = 0;
+
+ calculateMaximumLogicBubbleSize();
+ calculateBubbleSizeScalingFactor();
+ if( m_fMaxLogicBubbleSize <= 0 || m_fBubbleSizeFactorToScreen <= 0 )
+ return;
+
+ //iterate through all x values per indices
+ for( sal_Int32 nIndex = nStartIndex; nIndex < nEndIndex; nIndex++ )
+ {
+ for( auto const& rZSlot : m_aZSlots )
+ {
+ for( auto const& rXSlot : rZSlot )
+ {
+ //iterate through all series
+ for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
+ {
+ if(!pSeries)
+ continue;
+
+ bool bHasFillColorMapping = pSeries->hasPropertyMapping("FillColor");
+ bool bHasBorderColorMapping = pSeries->hasPropertyMapping("LineColor");
+
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes = getSeriesGroupShape(pSeries.get(), xSeriesTarget);
+
+ sal_Int32 nAttachedAxisIndex = pSeries->getAttachedAxisIndex();
+ PlottingPositionHelper& rPosHelper
+ = getPlottingPositionHelper(nAttachedAxisIndex);
+ m_pPosHelper = &rPosHelper;
+
+ //collect data point information (logic coordinates, style ):
+ double fLogicX = pSeries->getXValue(nIndex);
+ double fLogicY = pSeries->getYValue(nIndex);
+ double fBubbleSize = pSeries->getBubble_Size( nIndex );
+
+ if( fBubbleSize<0.0 )
+ continue;
+
+ if( fBubbleSize == 0.0 || std::isnan(fBubbleSize) )
+ continue;
+
+ if( std::isnan(fLogicX) || std::isinf(fLogicX)
+ || std::isnan(fLogicY) || std::isinf(fLogicY) )
+ continue;
+
+ bool bIsVisible = rPosHelper.isLogicVisible(fLogicX, fLogicY, fLogicZ);
+
+ drawing::Position3D aUnscaledLogicPosition( fLogicX, fLogicY, fLogicZ );
+ drawing::Position3D aScaledLogicPosition(aUnscaledLogicPosition);
+ rPosHelper.doLogicScaling(aScaledLogicPosition);
+
+ //transformation 3) -> 4)
+ drawing::Position3D aScenePosition(
+ rPosHelper.transformLogicToScene(fLogicX, fLogicY, fLogicZ, false));
+
+ //better performance for big data
+ FormerPoint aFormerPoint( aSeriesFormerPointMap[pSeries.get()] );
+ rPosHelper.setCoordinateSystemResolution(m_aCoordinateSystemResolution);
+ if (!pSeries->isAttributedDataPoint(nIndex)
+ && rPosHelper.isSameForGivenResolution(
+ aFormerPoint.m_fX, aFormerPoint.m_fY, aFormerPoint.m_fZ,
+ aScaledLogicPosition.PositionX, aScaledLogicPosition.PositionY,
+ aScaledLogicPosition.PositionZ))
+ {
+ nSkippedPoints++;
+ m_bPointsWereSkipped = true;
+ continue;
+ }
+ aSeriesFormerPointMap[pSeries.get()] = FormerPoint(aScaledLogicPosition.PositionX, aScaledLogicPosition.PositionY, aScaledLogicPosition.PositionZ);
+
+ //create a single datapoint if point is visible
+ if( !bIsVisible )
+ continue;
+
+ //create a group shape for this point and add to the series shape:
+ OUString aPointCID = ObjectIdentifier::createPointCID(
+ pSeries->getPointCID_Stub(), nIndex );
+ rtl::Reference<SvxShapeGroupAnyD> xPointGroupShape_Shapes(
+ createGroupShape(xSeriesGroupShape_Shapes,aPointCID) );
+
+ {
+ nCreatedPoints++;
+
+ //create data point
+ drawing::Direction3D aSymbolSize = transformToScreenBubbleSize( fBubbleSize );
+ rtl::Reference<SvxShapeCircle> xShape = ShapeFactory::createCircle2D( xPointGroupShape_Shapes
+ , aScenePosition, aSymbolSize );
+
+ PropertyMapper::setMappedProperties( *xShape
+ , pSeries->getPropertiesOfPoint( nIndex )
+ , PropertyMapper::getPropertyNameMapForFilledSeriesProperties() );
+
+ if(bHasFillColorMapping)
+ {
+ double nPropVal = pSeries->getValueByProperty(nIndex, "FillColor");
+ if(!std::isnan(nPropVal))
+ {
+ xShape->SvxShape::setPropertyValue("FillColor", uno::Any(static_cast<sal_Int32>(nPropVal)));
+ }
+ }
+ if(bHasBorderColorMapping)
+ {
+ double nPropVal = pSeries->getValueByProperty(nIndex, "LineColor");
+ if(!std::isnan(nPropVal))
+ {
+ xShape->SvxShape::setPropertyValue("LineColor", uno::Any(static_cast<sal_Int32>(nPropVal)));
+ }
+ }
+
+ ::chart::ShapeFactory::setShapeName( xShape, "MarkHandles" );
+
+ //create data point label
+ if( pSeries->getDataPointLabelIfLabel(nIndex) )
+ {
+ LabelAlignment eAlignment = LABEL_ALIGN_TOP;
+ drawing::Position3D aScenePosition3D( aScenePosition.PositionX
+ , aScenePosition.PositionY
+ , aScenePosition.PositionZ+getTransformedDepth() );
+
+ sal_Int32 nLabelPlacement = pSeries->getLabelPlacement(
+ nIndex, m_xChartTypeModel, rPosHelper.isSwapXAndY());
+
+ switch(nLabelPlacement)
+ {
+ case css::chart::DataLabelPlacement::TOP:
+ aScenePosition3D.PositionY -= (aSymbolSize.DirectionY/2+1);
+ eAlignment = LABEL_ALIGN_TOP;
+ break;
+ case css::chart::DataLabelPlacement::BOTTOM:
+ aScenePosition3D.PositionY += (aSymbolSize.DirectionY/2+1);
+ eAlignment = LABEL_ALIGN_BOTTOM;
+ break;
+ case css::chart::DataLabelPlacement::LEFT:
+ aScenePosition3D.PositionX -= (aSymbolSize.DirectionX/2+1);
+ eAlignment = LABEL_ALIGN_LEFT;
+ break;
+ case css::chart::DataLabelPlacement::RIGHT:
+ aScenePosition3D.PositionX += (aSymbolSize.DirectionX/2+1);
+ eAlignment = LABEL_ALIGN_RIGHT;
+ break;
+ case css::chart::DataLabelPlacement::CENTER:
+ eAlignment = LABEL_ALIGN_CENTER;
+ break;
+ default:
+ OSL_FAIL("this label alignment is not implemented yet");
+ aScenePosition3D.PositionY -= (aSymbolSize.DirectionY/2+1);
+ eAlignment = LABEL_ALIGN_TOP;
+ break;
+ }
+
+ awt::Point aScreenPosition2D( LabelPositionHelper(m_nDimension,m_xLogicTarget)
+ .transformSceneToScreenPosition( aScenePosition3D ) );
+ sal_Int32 nOffset = 0;
+ if(eAlignment!=LABEL_ALIGN_CENTER)
+ nOffset = 100;//add some spacing //@todo maybe get more intelligent values
+ createDataLabel( xTextTarget, *pSeries, nIndex
+ , fBubbleSize, fBubbleSize, aScreenPosition2D, eAlignment, nOffset );
+ }
+ }
+
+ //remove PointGroupShape if empty
+ if(!xPointGroupShape_Shapes->getCount())
+ xSeriesGroupShape_Shapes->remove(xPointGroupShape_Shapes);
+
+ }//next series in x slot (next y slot)
+ }//next x slot
+ }//next z slot
+ }//next category
+ SAL_INFO(
+ "chart2",
+ "skipped points: " << nSkippedPoints << " created points: "
+ << nCreatedPoints);
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/BubbleChart.hxx b/chart2/source/view/charttypes/BubbleChart.hxx
new file mode 100644
index 000000000..c25e5b634
--- /dev/null
+++ b/chart2/source/view/charttypes/BubbleChart.hxx
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <VSeriesPlotter.hxx>
+#include <com/sun/star/drawing/Direction3D.hpp>
+
+namespace chart
+{
+
+class BubbleChart : public VSeriesPlotter
+{
+ // public methods
+public:
+ BubbleChart() = delete;
+
+ BubbleChart( const rtl::Reference< ::chart::ChartType >& xChartTypeModel
+ , sal_Int32 nDimensionCount );
+ virtual ~BubbleChart() override;
+
+ virtual void createShapes() override;
+
+ virtual css::drawing::Direction3D getPreferredDiagramAspectRatio() const override;
+
+ // MinimumAndMaximumSupplier
+ virtual bool isExpandIfValuesCloseToBorder( sal_Int32 nDimensionIndex ) override;
+ virtual bool isSeparateStackingForDifferentSigns( sal_Int32 nDimensionIndex ) override;
+
+ virtual LegendSymbolStyle getLegendSymbolStyle() override;
+
+private: //methods
+ void calculateMaximumLogicBubbleSize();
+ void calculateBubbleSizeScalingFactor();
+
+ css::drawing::Direction3D transformToScreenBubbleSize( double fLogicSize );
+
+private: //member
+
+ double m_fMaxLogicBubbleSize;//calculated values
+ double m_fBubbleSizeFactorToScreen;//calculated values
+};
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/CandleStickChart.cxx b/chart2/source/view/charttypes/CandleStickChart.cxx
new file mode 100644
index 000000000..5c8497a5d
--- /dev/null
+++ b/chart2/source/view/charttypes/CandleStickChart.cxx
@@ -0,0 +1,314 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "CandleStickChart.hxx"
+#include <ChartType.hxx>
+#include <ShapeFactory.hxx>
+#include <CommonConverters.hxx>
+#include <ExplicitCategoriesProvider.hxx>
+#include <ObjectIdentifier.hxx>
+#include "BarPositionHelper.hxx"
+#include <DateHelper.hxx>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <tools/diagnose_ex.h>
+#include <osl/diagnose.h>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::chart2;
+
+CandleStickChart::CandleStickChart( const rtl::Reference<ChartType>& xChartTypeModel
+ , sal_Int32 nDimensionCount )
+ : VSeriesPlotter( xChartTypeModel, nDimensionCount )
+ , m_pMainPosHelper( new BarPositionHelper() )
+{
+ PlotterBase::m_pPosHelper = m_pMainPosHelper.get();
+ VSeriesPlotter::m_pMainPosHelper = m_pMainPosHelper.get();
+}
+
+CandleStickChart::~CandleStickChart()
+{
+}
+
+// MinimumAndMaximumSupplier
+
+bool CandleStickChart::isSeparateStackingForDifferentSigns( sal_Int32 /* nDimensionIndex */ )
+{
+ return false;
+}
+
+LegendSymbolStyle CandleStickChart::getLegendSymbolStyle()
+{
+ return LegendSymbolStyle::Line;
+}
+
+drawing::Direction3D CandleStickChart::getPreferredDiagramAspectRatio() const
+{
+ return drawing::Direction3D(-1,-1,-1);
+}
+
+void CandleStickChart::addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 /* zSlot */, sal_Int32 xSlot, sal_Int32 ySlot )
+{
+ //ignore y stacking for candle stick chart
+ VSeriesPlotter::addSeries( std::move(pSeries), 0, xSlot, ySlot );
+}
+
+void CandleStickChart::createShapes()
+{
+ if( m_aZSlots.empty() ) //no series
+ return;
+
+ if( m_nDimension!=2 )
+ return;
+
+ OSL_ENSURE(m_xLogicTarget.is()&&m_xFinalTarget.is(),"CandleStickChart is not proper initialized");
+ if(!(m_xLogicTarget.is()&&m_xFinalTarget.is()))
+ return;
+
+ //the text labels should be always on top of the other series shapes
+ //therefore create an own group for the texts to move them to front
+ //(because the text group is created after the series group the texts are displayed on top)
+
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesTarget =
+ createGroupShape( m_xLogicTarget );
+ rtl::Reference<SvxShapeGroupAnyD> xLossTarget =
+ createGroupShape( m_xLogicTarget, ObjectIdentifier::createClassifiedIdentifier(
+ OBJECTTYPE_DATA_STOCK_LOSS, u"" ));
+ rtl::Reference<SvxShapeGroupAnyD> xGainTarget =
+ createGroupShape( m_xLogicTarget, ObjectIdentifier::createClassifiedIdentifier(
+ OBJECTTYPE_DATA_STOCK_GAIN, u"" ));
+ rtl::Reference< SvxShapeGroup > xTextTarget =
+ ShapeFactory::createGroup2D( m_xFinalTarget );
+
+ //check necessary here that different Y axis can not be stacked in the same group? ... hm?
+
+ bool bJapaneseStyle=true;//@todo is this the correct default?
+ bool bShowFirst = true;//is only important if bJapaneseStyle == false
+ tNameSequence aWhiteBox_Names, aBlackBox_Names;
+ tAnySequence aWhiteBox_Values, aBlackBox_Values;
+ try
+ {
+ if( m_xChartTypeModel.is() )
+ {
+ m_xChartTypeModel->getPropertyValue( "ShowFirst" ) >>= bShowFirst;
+
+ uno::Reference< beans::XPropertySet > xWhiteDayProps;
+ uno::Reference< beans::XPropertySet > xBlackDayProps;
+ m_xChartTypeModel->getPropertyValue( "Japanese" ) >>= bJapaneseStyle;
+ m_xChartTypeModel->getPropertyValue( "WhiteDay" ) >>= xWhiteDayProps;
+ m_xChartTypeModel->getPropertyValue( "BlackDay" ) >>= xBlackDayProps;
+
+ tPropertyNameValueMap aWhiteBox_Map;
+ PropertyMapper::getValueMap( aWhiteBox_Map, PropertyMapper::getPropertyNameMapForFillAndLineProperties(), xWhiteDayProps );
+ PropertyMapper::getMultiPropertyListsFromValueMap( aWhiteBox_Names, aWhiteBox_Values, aWhiteBox_Map );
+
+ tPropertyNameValueMap aBlackBox_Map;
+ PropertyMapper::getValueMap( aBlackBox_Map, PropertyMapper::getPropertyNameMapForFillAndLineProperties(), xBlackDayProps );
+ PropertyMapper::getMultiPropertyListsFromValueMap( aBlackBox_Names, aBlackBox_Values, aBlackBox_Map );
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+
+ //(@todo maybe different iteration for breaks in axis ?)
+ sal_Int32 nEndIndex = VSeriesPlotter::getPointCount();
+ double fLogicZ = 1.5;//as defined
+ //iterate through all x values per indices
+ for( sal_Int32 nIndex = 0; nIndex < nEndIndex; nIndex++ )
+ {
+ for( auto const& rZSlot : m_aZSlots )
+ {
+ BarPositionHelper* pPosHelper = m_pMainPosHelper.get();
+ if( !rZSlot.empty() )
+ {
+ sal_Int32 nAttachedAxisIndex = rZSlot.front().getAttachedAxisIndexForFirstSeries();
+ //2ND_AXIS_IN_BARS so far one can assume to have the same plotter for each z slot
+ pPosHelper = dynamic_cast<BarPositionHelper*>(&( getPlottingPositionHelper( nAttachedAxisIndex ) ) );
+ if(!pPosHelper)
+ pPosHelper = m_pMainPosHelper.get();
+ }
+ PlotterBase::m_pPosHelper = pPosHelper;
+
+ //update/create information for current group
+ pPosHelper->updateSeriesCount( rZSlot.size() );
+ double fSlotX=0;
+ //iterate through all x slots in this category
+ for( auto const& rXSlot : rZSlot )
+ {
+ //iterate through all series in this x slot
+ for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
+ {
+ //collect data point information (logic coordinates, style ):
+ double fUnscaledX = pSeries->getXValue( nIndex );
+ if( m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis() )
+ fUnscaledX = DateHelper::RasterizeDateValue( fUnscaledX, m_aNullDate, m_nTimeResolution );
+ if(fUnscaledX<pPosHelper->getLogicMinX() || fUnscaledX>pPosHelper->getLogicMaxX())
+ continue;//point not visible
+ double fScaledX = pPosHelper->getScaledSlotPos( fUnscaledX, fSlotX );
+
+ double fUnscaledY_First = pSeries->getY_First( nIndex );
+ double fUnscaledY_Last = pSeries->getY_Last( nIndex );
+ double fUnscaledY_Min = pSeries->getY_Min( nIndex );
+ double fUnscaledY_Max = pSeries->getY_Max( nIndex );
+
+ bool bBlack=false;
+ if(fUnscaledY_Last<=fUnscaledY_First)
+ {
+ std::swap(fUnscaledY_First,fUnscaledY_Last);
+ bBlack=true;
+ }
+ if(fUnscaledY_Max<fUnscaledY_Min)
+ std::swap(fUnscaledY_Min,fUnscaledY_Max);
+ //transformation 3) -> 4)
+ double fHalfScaledWidth = pPosHelper->getScaledSlotWidth()/2.0;
+
+ double fScaledY_First(fUnscaledY_First);
+ double fScaledY_Last(fUnscaledY_Last);
+ double fScaledY_Min(fUnscaledY_Min);
+ double fScaledY_Max(fUnscaledY_Max);
+ pPosHelper->clipLogicValues( nullptr,&fScaledY_First,nullptr );
+ pPosHelper->clipLogicValues( nullptr,&fScaledY_Last,nullptr );
+ pPosHelper->clipLogicValues( nullptr,&fScaledY_Min,nullptr );
+ pPosHelper->clipLogicValues( nullptr,&fScaledY_Max,nullptr );
+ pPosHelper->doLogicScaling( nullptr,&fScaledY_First,nullptr );
+ pPosHelper->doLogicScaling( nullptr,&fScaledY_Last,nullptr );
+ pPosHelper->doLogicScaling( nullptr,&fScaledY_Min,nullptr );
+ pPosHelper->doLogicScaling( nullptr,&fScaledY_Max,nullptr );
+
+ drawing::Position3D aPosLeftFirst( pPosHelper->transformScaledLogicToScene( fScaledX-fHalfScaledWidth, fScaledY_First ,0 ,true ) );
+ drawing::Position3D aPosRightLast( pPosHelper->transformScaledLogicToScene( fScaledX+fHalfScaledWidth, fScaledY_Last ,0 ,true ) );
+ drawing::Position3D aPosMiddleFirst( pPosHelper->transformScaledLogicToScene( fScaledX, fScaledY_First ,0 ,true ) );
+ drawing::Position3D aPosMiddleLast( pPosHelper->transformScaledLogicToScene( fScaledX, fScaledY_Last ,0 ,true ) );
+ drawing::Position3D aPosMiddleMinimum( pPosHelper->transformScaledLogicToScene( fScaledX, fScaledY_Min ,0 ,true ) );
+ drawing::Position3D aPosMiddleMaximum( pPosHelper->transformScaledLogicToScene( fScaledX, fScaledY_Max ,0 ,true ) );
+
+ rtl::Reference<SvxShapeGroupAnyD> xLossGainTarget( xGainTarget );
+ if(bBlack)
+ xLossGainTarget = xLossTarget;
+
+ uno::Reference< beans::XPropertySet > xPointProp( pSeries->getPropertiesOfPoint( nIndex ));
+ rtl::Reference<SvxShapeGroupAnyD> xPointGroupShape_Shapes;
+ {
+ OUString aPointCID = ObjectIdentifier::createPointCID( pSeries->getPointCID_Stub(), nIndex );
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes( getSeriesGroupShape(pSeries.get(), xSeriesTarget) );
+ xPointGroupShape_Shapes = createGroupShape(xSeriesGroupShape_Shapes,aPointCID);
+ }
+
+ //create min-max line
+ if( isValidPosition(aPosMiddleMinimum) && isValidPosition(aPosMiddleMaximum) )
+ {
+ std::vector<std::vector<css::drawing::Position3D>> aPoly
+ {
+ { aPosMiddleMinimum, aPosMiddleMaximum }
+ };
+
+ rtl::Reference<SvxShapePolyPolygon> xShape =
+ ShapeFactory::createLine2D( xPointGroupShape_Shapes, aPoly);
+ PropertyMapper::setMappedProperties( *xShape, xPointProp, PropertyMapper::getPropertyNameMapForLineSeriesProperties() );
+ }
+
+ //create first-last shape
+ if(bJapaneseStyle && isValidPosition(aPosLeftFirst) && isValidPosition(aPosRightLast) )
+ {
+ drawing::Direction3D aDiff = aPosRightLast-aPosLeftFirst;
+ awt::Size aAWTSize( Direction3DToAWTSize( aDiff ));
+ // workaround for bug in drawing: if height is 0 the box gets infinitely large
+ if( aAWTSize.Height == 0 )
+ aAWTSize.Height = 1;
+
+ tNameSequence aNames;
+ tAnySequence aValues;
+
+ rtl::Reference<SvxShapeRect> xShape =
+ ShapeFactory::createRectangle( xLossGainTarget,
+ aAWTSize, Position3DToAWTPoint( aPosLeftFirst ),
+ aNames, aValues);
+
+ if(bBlack)
+ PropertyMapper::setMultiProperties( aBlackBox_Names, aBlackBox_Values, *xShape );
+ else
+ PropertyMapper::setMultiProperties( aWhiteBox_Names, aWhiteBox_Values, *xShape );
+ }
+ else
+ {
+ std::vector<std::vector<css::drawing::Position3D>> aPoly;
+
+ sal_Int32 nLineIndex = 0;
+ if( bShowFirst && pPosHelper->isLogicVisible( fUnscaledX, fUnscaledY_First ,fLogicZ )
+ && isValidPosition(aPosLeftFirst) && isValidPosition(aPosMiddleFirst) )
+ {
+ AddPointToPoly( aPoly, aPosLeftFirst, nLineIndex );
+ AddPointToPoly( aPoly, aPosMiddleFirst, nLineIndex++ );
+ }
+ if( pPosHelper->isLogicVisible( fUnscaledX, fUnscaledY_Last ,fLogicZ )
+ && isValidPosition(aPosMiddleLast) && isValidPosition(aPosRightLast) )
+ {
+ AddPointToPoly( aPoly, aPosMiddleLast, nLineIndex );
+ AddPointToPoly( aPoly, aPosRightLast, nLineIndex );
+ }
+
+ if( !aPoly.empty() )
+ {
+ rtl::Reference<SvxShapePolyPolygon> xShape =
+ ShapeFactory::createLine2D( xPointGroupShape_Shapes, aPoly );
+ PropertyMapper::setMappedProperties( *xShape, xPointProp, PropertyMapper::getPropertyNameMapForLineSeriesProperties() );
+ }
+ }
+
+ //create data point label
+ if( pSeries->getDataPointLabelIfLabel(nIndex) )
+ {
+ if(isValidPosition(aPosMiddleFirst))
+ createDataLabel( xTextTarget, *pSeries, nIndex
+ , fUnscaledY_First, 1.0, Position3DToAWTPoint(aPosMiddleFirst), LABEL_ALIGN_LEFT_BOTTOM );
+ if(isValidPosition(aPosMiddleLast))
+ createDataLabel( xTextTarget, *pSeries, nIndex
+ , fUnscaledY_Last, 1.0, Position3DToAWTPoint(aPosMiddleLast), LABEL_ALIGN_RIGHT_TOP );
+ if(isValidPosition(aPosMiddleMinimum))
+ createDataLabel( xTextTarget, *pSeries, nIndex
+ , fUnscaledY_Min, 1.0, Position3DToAWTPoint(aPosMiddleMinimum), LABEL_ALIGN_BOTTOM );
+ if(isValidPosition(aPosMiddleMaximum))
+ createDataLabel( xTextTarget, *pSeries, nIndex
+ , fUnscaledY_Max, 1.0, Position3DToAWTPoint(aPosMiddleMaximum), LABEL_ALIGN_TOP );
+ }
+ }//next series in x slot (next y slot)
+ fSlotX+=1.0;
+ }//next x slot
+ }//next z slot
+ }//next category
+ /* @todo remove series shapes if empty
+ //remove and delete point-group-shape if empty
+ if(!xSeriesGroupShape_Shapes->getCount())
+ {
+ pSeries->m_xShape.set(NULL);
+ m_xLogicTarget->remove(xSeriesGroupShape_Shape);
+ }
+ */
+
+ //remove and delete series-group-shape if empty
+
+ //... todo
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/CandleStickChart.hxx b/chart2/source/view/charttypes/CandleStickChart.hxx
new file mode 100644
index 000000000..93571889e
--- /dev/null
+++ b/chart2/source/view/charttypes/CandleStickChart.hxx
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <memory>
+#include <VSeriesPlotter.hxx>
+
+namespace chart
+{
+class BarPositionHelper;
+
+class CandleStickChart : public VSeriesPlotter
+{
+ // public methods
+public:
+ CandleStickChart() = delete;
+
+ CandleStickChart( const rtl::Reference< ::chart::ChartType >& xChartTypeModel
+ , sal_Int32 nDimensionCount );
+ virtual ~CandleStickChart() override;
+
+ virtual void createShapes() override;
+ virtual void addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 zSlot, sal_Int32 xSlot, sal_Int32 ySlot ) override;
+
+ virtual css::drawing::Direction3D getPreferredDiagramAspectRatio() const override;
+
+ // MinimumAndMaximumSupplier
+ virtual bool isSeparateStackingForDifferentSigns( sal_Int32 nDimensionIndex ) override;
+
+ virtual LegendSymbolStyle getLegendSymbolStyle() override;
+
+private: //member
+ std::unique_ptr<BarPositionHelper> m_pMainPosHelper;
+};
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/CategoryPositionHelper.cxx b/chart2/source/view/charttypes/CategoryPositionHelper.cxx
new file mode 100644
index 000000000..d7412d3cb
--- /dev/null
+++ b/chart2/source/view/charttypes/CategoryPositionHelper.cxx
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "CategoryPositionHelper.hxx"
+
+namespace chart
+{
+
+CategoryPositionHelper::CategoryPositionHelper( double fSeriesCount, double fCategoryWidth )
+ : m_fSeriesCount(fSeriesCount)
+ , m_fCategoryWidth(fCategoryWidth)
+ , m_fInnerDistance(0.0)
+ , m_fOuterDistance(1.0)
+{
+}
+
+CategoryPositionHelper::~CategoryPositionHelper()
+{
+}
+
+double CategoryPositionHelper::getScaledSlotWidth() const
+{
+ double fWidth = m_fCategoryWidth /
+ ( m_fSeriesCount
+ + m_fOuterDistance
+ + m_fInnerDistance*( m_fSeriesCount - 1.0) );
+ return fWidth;
+}
+
+double CategoryPositionHelper::getScaledSlotPos( double fScaledXPos, double fSeriesNumber ) const
+{
+ //the returned position is in the middle of the rect
+ //fSeriesNumber 0...n-1
+ double fPos = fScaledXPos
+ - (m_fCategoryWidth/2.0)
+ + (m_fOuterDistance/2.0 + fSeriesNumber*(1.0+m_fInnerDistance)) * getScaledSlotWidth()
+ + getScaledSlotWidth()/2.0;
+ return fPos;
+}
+
+void CategoryPositionHelper::setInnerDistance( double fInnerDistance )
+{
+ if( fInnerDistance < -1.0 )
+ fInnerDistance = -1.0;
+ if( fInnerDistance > 1.0 )
+ fInnerDistance = 1.0;
+ m_fInnerDistance = fInnerDistance;
+}
+
+void CategoryPositionHelper::setOuterDistance( double fOuterDistance )
+{
+ if( fOuterDistance < 0.0 )
+ fOuterDistance = 0.0;
+ if( fOuterDistance > 6.0 )
+ fOuterDistance = 6.0;
+ m_fOuterDistance = fOuterDistance;
+}
+
+void CategoryPositionHelper::setCategoryWidth( double fCategoryWidth )
+{
+ m_fCategoryWidth = fCategoryWidth;
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/CategoryPositionHelper.hxx b/chart2/source/view/charttypes/CategoryPositionHelper.hxx
new file mode 100644
index 000000000..ebc784ef6
--- /dev/null
+++ b/chart2/source/view/charttypes/CategoryPositionHelper.hxx
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+namespace chart
+{
+class CategoryPositionHelper
+{
+public:
+ CategoryPositionHelper(double fSeriesCount, double CategoryWidth = 1.0);
+ virtual ~CategoryPositionHelper();
+
+ CategoryPositionHelper(CategoryPositionHelper const&) = default;
+ CategoryPositionHelper(CategoryPositionHelper&&) = default;
+ CategoryPositionHelper& operator=(CategoryPositionHelper const&) = default;
+ CategoryPositionHelper& operator=(CategoryPositionHelper&&) = default;
+
+ double getScaledSlotWidth() const;
+ virtual double getScaledSlotPos(double fCategoryX, double fSeriesNumber) const;
+ void setCategoryWidth(double fCategoryWidth);
+
+ //Distance between two neighboring bars in same category, seen relative to width of the bar
+ void setInnerDistance(double fInnerDistance);
+
+ //Distance between two neighboring bars in different category, seen relative to width of the bar:
+ void setOuterDistance(double fOuterDistance);
+
+protected:
+ double m_fSeriesCount;
+ double m_fCategoryWidth;
+ //Distance between two neighboring bars in same category, seen relative to width of the bar:
+ double
+ m_fInnerDistance; //[-1,1] m_fInnerDistance=1 --> distance == width; m_fInnerDistance=-1-->all rects are painted on the same position
+ //Distance between two neighboring bars in different category, seen relative to width of the bar:
+ double m_fOuterDistance; //>=0 m_fOuterDistance=1 --> distance == width
+};
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/ConfigAccess.cxx b/chart2/source/view/charttypes/ConfigAccess.cxx
new file mode 100644
index 000000000..ce02c4817
--- /dev/null
+++ b/chart2/source/view/charttypes/ConfigAccess.cxx
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <ConfigAccess.hxx>
+
+#include <unotools/configitem.hxx>
+#include <o3tl/any.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+
+namespace
+{
+class ChartConfigItem : public ::utl::ConfigItem
+{
+private:
+ virtual void ImplCommit() override;
+
+public:
+ ChartConfigItem();
+
+ bool getUseErrorRectangle();
+ virtual void Notify(const uno::Sequence<OUString>& aPropertyNames) override;
+};
+}
+
+ChartConfigItem::ChartConfigItem()
+ : ConfigItem("Office.Chart/ErrorProperties")
+{
+}
+
+void ChartConfigItem::ImplCommit() {}
+void ChartConfigItem::Notify(const uno::Sequence<OUString>&) {}
+
+bool ChartConfigItem::getUseErrorRectangle()
+{
+ uno::Sequence<OUString> aNames{ "ErrorRectangle" };
+
+ auto b = o3tl::tryAccess<bool>(GetProperties(aNames)[0]);
+ return b && *b;
+}
+
+namespace ConfigAccess
+{
+bool getUseErrorRectangle()
+{
+ //a ChartConfigItem Singleton
+ static ChartConfigItem SINGLETON;
+ bool bResult(SINGLETON.getUseErrorRectangle());
+ return bResult;
+}
+} //namespace ConfigAccess
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/NetChart.cxx b/chart2/source/view/charttypes/NetChart.cxx
new file mode 100644
index 000000000..5b8f1db34
--- /dev/null
+++ b/chart2/source/view/charttypes/NetChart.cxx
@@ -0,0 +1,642 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "NetChart.hxx"
+#include <PlottingPositionHelper.hxx>
+#include <ShapeFactory.hxx>
+#include <ExplicitCategoriesProvider.hxx>
+#include <CommonConverters.hxx>
+#include <ObjectIdentifier.hxx>
+#include <LabelPositionHelper.hxx>
+#include <Clipping.hxx>
+#include <PolarLabelPositionHelper.hxx>
+#include <DateHelper.hxx>
+#include <ChartType.hxx>
+
+#include <com/sun/star/chart2/Symbol.hpp>
+#include <com/sun/star/chart/DataLabelPlacement.hpp>
+#include <com/sun/star/chart/MissingValueTreatment.hpp>
+
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+
+#include <officecfg/Office/Compatibility.hxx>
+
+#include <limits>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::chart2;
+
+NetChart::NetChart( const rtl::Reference<ChartType>& xChartTypeModel
+ , sal_Int32 nDimensionCount
+ , bool bNoArea
+ , std::unique_ptr<PlottingPositionHelper> pPlottingPositionHelper
+ )
+ : VSeriesPlotter( xChartTypeModel, nDimensionCount, true )
+ , m_pMainPosHelper(std::move(pPlottingPositionHelper))
+ , m_bArea(!bNoArea)
+ , m_bLine(bNoArea)
+{
+ // we only support 2D Net charts
+ assert(nDimensionCount == 2);
+
+ m_pMainPosHelper->AllowShiftXAxisPos(true);
+ m_pMainPosHelper->AllowShiftZAxisPos(true);
+
+ PlotterBase::m_pPosHelper = m_pMainPosHelper.get();
+ VSeriesPlotter::m_pMainPosHelper = m_pMainPosHelper.get();
+}
+
+NetChart::~NetChart()
+{
+}
+
+double NetChart::getMaximumX()
+{
+ double fMax = VSeriesPlotter::getMaximumX() + 1.0;
+ return fMax;
+}
+
+bool NetChart::isExpandIfValuesCloseToBorder( sal_Int32 )
+{
+ return false;
+}
+
+bool NetChart::isSeparateStackingForDifferentSigns( sal_Int32 /*nDimensionIndex*/ )
+{
+ // no separate stacking in all types of line/area charts
+ return false;
+}
+
+LegendSymbolStyle NetChart::getLegendSymbolStyle()
+{
+ if( m_bArea )
+ return LegendSymbolStyle::Box;
+ return LegendSymbolStyle::Line;
+}
+
+uno::Any NetChart::getExplicitSymbol( const VDataSeries& rSeries, sal_Int32 nPointIndex )
+{
+ uno::Any aRet;
+
+ Symbol* pSymbolProperties = rSeries.getSymbolProperties( nPointIndex );
+ if( pSymbolProperties )
+ {
+ aRet <<= *pSymbolProperties;
+ }
+
+ return aRet;
+}
+
+drawing::Direction3D NetChart::getPreferredDiagramAspectRatio() const
+{
+ return drawing::Direction3D(1,1,1);
+}
+
+bool NetChart::impl_createLine( VDataSeries* pSeries
+ , const std::vector<std::vector<css::drawing::Position3D>>* pSeriesPoly
+ , PlottingPositionHelper const * pPosHelper )
+{
+ //return true if a line was created successfully
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes = getSeriesGroupShapeBackChild(pSeries, m_xSeriesTarget);
+
+ std::vector<std::vector<css::drawing::Position3D>> aPoly;
+ {
+ bool bIsClipped = false;
+ if( !ShapeFactory::isPolygonEmptyOrSinglePoint(*pSeriesPoly) )
+ {
+ // do NOT connect last and first point, if one is NAN, and NAN handling is NAN_AS_GAP
+ double fFirstY = pSeries->getYValue( 0 );
+ double fLastY = pSeries->getYValue( VSeriesPlotter::getPointCount() - 1 );
+ if( (pSeries->getMissingValueTreatment() != css::chart::MissingValueTreatment::LEAVE_GAP)
+ || (std::isfinite( fFirstY ) && std::isfinite( fLastY )) )
+ {
+ // connect last point in last polygon with first point in first polygon
+ ::basegfx::B2DRectangle aScaledLogicClipDoubleRect( pPosHelper->getScaledLogicClipDoubleRect() );
+ std::vector<std::vector<css::drawing::Position3D>> aTmpPoly(*pSeriesPoly);
+ drawing::Position3D aLast(aScaledLogicClipDoubleRect.getMaxX(),aTmpPoly[0][0].PositionY,aTmpPoly[0][0].PositionZ);
+ // add connector line to last polygon
+ AddPointToPoly( aTmpPoly, aLast, pSeriesPoly->size() - 1 );
+ Clipping::clipPolygonAtRectangle( aTmpPoly, aScaledLogicClipDoubleRect, aPoly );
+ bIsClipped = true;
+ }
+ }
+
+ if( !bIsClipped )
+ Clipping::clipPolygonAtRectangle( *pSeriesPoly, pPosHelper->getScaledLogicClipDoubleRect(), aPoly );
+ }
+
+ if(!ShapeFactory::hasPolygonAnyLines(aPoly))
+ return false;
+
+ //transformation 3) -> 4)
+ pPosHelper->transformScaledLogicToScene( aPoly );
+
+ //create line:
+ rtl::Reference<SvxShapePolyPolygon> xShape;
+ {
+ xShape = ShapeFactory::createLine2D( xSeriesGroupShape_Shapes, aPoly );
+ PropertyMapper::setMappedProperties( *xShape
+ , pSeries->getPropertiesOfSeries()
+ , PropertyMapper::getPropertyNameMapForLineSeriesProperties() );
+ //because of this name this line will be used for marking
+ ::chart::ShapeFactory::setShapeName(xShape, "MarkHandles");
+ }
+ return true;
+}
+
+bool NetChart::impl_createArea( VDataSeries* pSeries
+ , const std::vector<std::vector<css::drawing::Position3D>>* pSeriesPoly
+ , std::vector<std::vector<css::drawing::Position3D>> const * pPreviousSeriesPoly
+ , PlottingPositionHelper const * pPosHelper )
+{
+ //return true if an area was created successfully
+
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes = getSeriesGroupShapeBackChild(pSeries, m_xSeriesTarget);
+ double zValue = pSeries->m_fLogicZPos;
+
+ std::vector<std::vector<css::drawing::Position3D>> aPoly( *pSeriesPoly );
+ //add second part to the polygon (grounding points or previous series points)
+ if( !ShapeFactory::isPolygonEmptyOrSinglePoint(*pSeriesPoly) )
+ {
+ if( pPreviousSeriesPoly )
+ addPolygon( aPoly, *pPreviousSeriesPoly );
+ }
+ else if(!pPreviousSeriesPoly)
+ {
+ double fMinX = pSeries->m_fLogicMinX;
+ double fMaxX = pSeries->m_fLogicMaxX;
+ double fY = pPosHelper->getBaseValueY();//logic grounding
+
+ //clip to scale
+ if(fMaxX<pPosHelper->getLogicMinX() || fMinX>pPosHelper->getLogicMaxX())
+ return false;//no visible shape needed
+ pPosHelper->clipLogicValues( &fMinX, &fY, nullptr );
+ pPosHelper->clipLogicValues( &fMaxX, nullptr, nullptr );
+
+ //apply scaling
+ {
+ pPosHelper->doLogicScaling( &fMinX, &fY, &zValue );
+ pPosHelper->doLogicScaling( &fMaxX, nullptr, nullptr );
+ }
+
+ AddPointToPoly( aPoly, drawing::Position3D( fMaxX,fY,zValue) );
+ AddPointToPoly( aPoly, drawing::Position3D( fMinX,fY,zValue) );
+ }
+ else
+ {
+ appendPoly( aPoly, *pPreviousSeriesPoly );
+ }
+ ShapeFactory::closePolygon(aPoly);
+
+ //apply clipping
+ {
+ std::vector<std::vector<css::drawing::Position3D>> aClippedPoly;
+ Clipping::clipPolygonAtRectangle( aPoly, pPosHelper->getScaledLogicClipDoubleRect(), aClippedPoly, false );
+ ShapeFactory::closePolygon(aClippedPoly); //again necessary after clipping
+ aPoly = aClippedPoly;
+ }
+
+ if(!ShapeFactory::hasPolygonAnyLines(aPoly))
+ return false;
+
+ //transformation 3) -> 4)
+ pPosHelper->transformScaledLogicToScene( aPoly );
+
+ //create area:
+ rtl::Reference<SvxShapePolyPolygon>
+ xShape = ShapeFactory::createArea2D( xSeriesGroupShape_Shapes
+ , aPoly );
+ PropertyMapper::setMappedProperties( *xShape
+ , pSeries->getPropertiesOfSeries()
+ , PropertyMapper::getPropertyNameMapForFilledSeriesProperties() );
+ //because of this name this line will be used for marking
+ ::chart::ShapeFactory::setShapeName(xShape, "MarkHandles");
+ return true;
+}
+
+void NetChart::impl_createSeriesShapes()
+{
+ //the polygon shapes for each series need to be created before
+
+ //iterate through all series again to create the series shapes
+ for( auto const& rZSlot : m_aZSlots )
+ {
+ for( auto const& rXSlot : rZSlot )
+ {
+ std::map< sal_Int32, std::vector<std::vector<css::drawing::Position3D>>* > aPreviousSeriesPolyMap;//a PreviousSeriesPoly for each different nAttachedAxisIndex
+ std::vector<std::vector<css::drawing::Position3D>>* pSeriesPoly = nullptr;
+
+ //iterate through all series
+ for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
+ {
+ sal_Int32 nAttachedAxisIndex = pSeries->getAttachedAxisIndex();
+ m_pPosHelper = &getPlottingPositionHelper(nAttachedAxisIndex);
+
+ pSeriesPoly = &pSeries->m_aPolyPolygonShape3D;
+ if( m_bArea )
+ {
+ if (!impl_createArea(pSeries.get(), pSeriesPoly,
+ aPreviousSeriesPolyMap[nAttachedAxisIndex], m_pPosHelper))
+ continue;
+ }
+ if( m_bLine )
+ {
+ if (!impl_createLine(pSeries.get(), pSeriesPoly, m_pPosHelper))
+ continue;
+ }
+ aPreviousSeriesPolyMap[nAttachedAxisIndex] = pSeriesPoly;
+ }//next series in x slot (next y slot)
+ }//next x slot
+ }//next z slot
+}
+
+namespace
+{
+
+void lcl_reorderSeries( std::vector< std::vector< VDataSeriesGroup > >& rZSlots )
+{
+ std::vector< std::vector< VDataSeriesGroup > > aRet;
+ aRet.reserve( rZSlots.size() );
+
+ std::vector< std::vector< VDataSeriesGroup > >::reverse_iterator aZIt( rZSlots.rbegin() );
+ std::vector< std::vector< VDataSeriesGroup > >::reverse_iterator aZEnd( rZSlots.rend() );
+ for( ; aZIt != aZEnd; ++aZIt )
+ {
+ std::vector< VDataSeriesGroup > aXSlot;
+
+ std::vector< VDataSeriesGroup >::reverse_iterator aXIt( aZIt->rbegin() );
+ std::vector< VDataSeriesGroup >::reverse_iterator aXEnd( aZIt->rend() );
+ for( ; aXIt != aXEnd; ++aXIt )
+ aXSlot.push_back(std::move(*aXIt));
+
+ aRet.push_back(std::move(aXSlot));
+ }
+
+ rZSlots = std::move(aRet);
+}
+
+//better performance for big data
+struct FormerPoint
+{
+ FormerPoint( double fX, double fY, double fZ )
+ : m_fX(fX), m_fY(fY), m_fZ(fZ)
+ {}
+ FormerPoint()
+ : m_fX(std::numeric_limits<double>::quiet_NaN())
+ , m_fY(std::numeric_limits<double>::quiet_NaN())
+ , m_fZ(std::numeric_limits<double>::quiet_NaN())
+
+ {
+ }
+
+ double m_fX;
+ double m_fY;
+ double m_fZ;
+};
+
+}//anonymous namespace
+
+void NetChart::createShapes()
+{
+ if( m_aZSlots.empty() ) //no series
+ return;
+
+ //tdf#127813 Don't reverse the series in OOXML-heavy environments
+ if (officecfg::Office::Compatibility::View::ReverseSeriesOrderAreaAndNetChart::get() && m_bArea)
+ lcl_reorderSeries( m_aZSlots );
+
+ OSL_ENSURE(m_xLogicTarget.is()&&m_xFinalTarget.is(),"NetChart is not proper initialized");
+ if(!(m_xLogicTarget.is()&&m_xFinalTarget.is()))
+ return;
+
+ //the text labels should be always on top of the other series shapes
+ //for area chart the error bars should be always on top of the other series shapes
+
+ //therefore create an own group for the texts and the error bars to move them to front
+ //(because the text group is created after the series group the texts are displayed on top)
+ m_xSeriesTarget = createGroupShape( m_xLogicTarget );
+ m_xTextTarget = ShapeFactory::createGroup2D( m_xFinalTarget );
+
+ //check necessary here that different Y axis can not be stacked in the same group? ... hm?
+
+ //update/create information for current group
+ double fLogicZ = 1.0;//as defined
+
+ sal_Int32 const nStartIndex = 0; // inclusive ;..todo get somehow from x scale
+ sal_Int32 nEndIndex = VSeriesPlotter::getPointCount();
+ if(nEndIndex<=0)
+ nEndIndex=1;
+
+ //better performance for big data
+ std::map< VDataSeries*, FormerPoint > aSeriesFormerPointMap;
+ m_bPointsWereSkipped = false;
+
+ bool bDateCategory = (m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis());
+
+ //iterate through all x values per indices
+ for( sal_Int32 nIndex = nStartIndex; nIndex < nEndIndex; nIndex++ )
+ {
+ std::map< sal_Int32, double > aLogicYSumMap;//one for each different nAttachedAxisIndex
+ for( auto const& rZSlot : m_aZSlots )
+ {
+ //iterate through all x slots in this category to get 100percent sum
+ for( auto const& rXSlot : rZSlot )
+ {
+ for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
+ {
+ if(!pSeries)
+ continue;
+
+ if (bDateCategory)
+ pSeries->doSortByXValues();
+
+ sal_Int32 nAttachedAxisIndex = pSeries->getAttachedAxisIndex();
+ aLogicYSumMap.insert({nAttachedAxisIndex, 0.0});
+
+ m_pPosHelper = &getPlottingPositionHelper(nAttachedAxisIndex);
+
+ double fAdd = pSeries->getYValue( nIndex );
+ if( !std::isnan(fAdd) && !std::isinf(fAdd) )
+ aLogicYSumMap[nAttachedAxisIndex] += fabs( fAdd );
+ }
+ }
+ }
+
+ for( auto const& rZSlot : m_aZSlots )
+ {
+ //for the area chart there should be at most one x slot (no side by side stacking available)
+ //attention different: xSlots are always interpreted as independent areas one behind the other: @todo this doesn't work why not???
+ for( auto const& rXSlot : rZSlot )
+ {
+ std::map< sal_Int32, double > aLogicYForNextSeriesMap;//one for each different nAttachedAxisIndex
+ //iterate through all series
+ for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
+ {
+ if(!pSeries)
+ continue;
+
+ /* #i70133# ignore points outside of series length in standard area
+ charts. Stacked area charts will use missing points as zeros. In
+ standard charts, pSeriesList contains only one series. */
+ if( m_bArea && (rXSlot.m_aSeriesVector.size() == 1) && (nIndex >= pSeries->getTotalPointCount()) )
+ continue;
+
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes = getSeriesGroupShapeFrontChild(pSeries.get(), m_xSeriesTarget);
+
+ sal_Int32 nAttachedAxisIndex = pSeries->getAttachedAxisIndex();
+ m_pPosHelper = &getPlottingPositionHelper(nAttachedAxisIndex);
+
+ pSeries->m_fLogicZPos = fLogicZ;
+
+ //collect data point information (logic coordinates, style ):
+ double fLogicX = pSeries->getXValue(nIndex);
+ if (bDateCategory)
+ fLogicX = DateHelper::RasterizeDateValue( fLogicX, m_aNullDate, m_nTimeResolution );
+ double fLogicY = pSeries->getYValue(nIndex);
+
+ if( m_bArea && ( std::isnan(fLogicY) || std::isinf(fLogicY) ) )
+ {
+ if( pSeries->getMissingValueTreatment() == css::chart::MissingValueTreatment::LEAVE_GAP )
+ {
+ if( rXSlot.m_aSeriesVector.size() == 1 || pSeries == rXSlot.m_aSeriesVector.front() )
+ {
+ fLogicY = m_pPosHelper->getLogicMinY();
+ if (!m_pPosHelper->isMathematicalOrientationY())
+ fLogicY = m_pPosHelper->getLogicMaxY();
+ }
+ else
+ fLogicY = 0.0;
+ }
+ }
+
+ if (m_pPosHelper->isPercentY() && aLogicYSumMap[nAttachedAxisIndex] != 0.0)
+ {
+ fLogicY = fabs( fLogicY )/aLogicYSumMap[nAttachedAxisIndex];
+ }
+
+ if( std::isnan(fLogicX) || std::isinf(fLogicX)
+ || std::isnan(fLogicY) || std::isinf(fLogicY)
+ || std::isnan(fLogicZ) || std::isinf(fLogicZ) )
+ {
+ if( pSeries->getMissingValueTreatment() == css::chart::MissingValueTreatment::LEAVE_GAP )
+ {
+ std::vector<std::vector<css::drawing::Position3D>>& rPolygon = pSeries->m_aPolyPolygonShape3D;
+ sal_Int32& rIndex = pSeries->m_nPolygonIndex;
+ if( 0<= rIndex && o3tl::make_unsigned(rIndex) < rPolygon.size() )
+ {
+ if( !rPolygon[ rIndex ].empty() )
+ rIndex++; //start a new polygon for the next point if the current poly is not empty
+ }
+ }
+ continue;
+ }
+
+ aLogicYForNextSeriesMap.try_emplace(nAttachedAxisIndex, 0.0);
+
+ double fLogicValueForLabeDisplay = fLogicY;
+
+ fLogicY += aLogicYForNextSeriesMap[nAttachedAxisIndex];
+ aLogicYForNextSeriesMap[nAttachedAxisIndex] = fLogicY;
+
+ bool bIsVisible = m_pPosHelper->isLogicVisible(fLogicX, fLogicY, fLogicZ);
+
+ //remind minimal and maximal x values for area 'grounding' points
+ //only for filled area
+ {
+ double& rfMinX = pSeries->m_fLogicMinX;
+ if(!nIndex||fLogicX<rfMinX)
+ rfMinX=fLogicX;
+ double& rfMaxX = pSeries->m_fLogicMaxX;
+ if(!nIndex||fLogicX>rfMaxX)
+ rfMaxX=fLogicX;
+ }
+
+ drawing::Position3D aUnscaledLogicPosition( fLogicX, fLogicY, fLogicZ );
+ drawing::Position3D aScaledLogicPosition(aUnscaledLogicPosition);
+ m_pPosHelper->doLogicScaling(aScaledLogicPosition);
+
+ //transformation 3) -> 4)
+ drawing::Position3D aScenePosition(
+ m_pPosHelper->transformLogicToScene(fLogicX, fLogicY, fLogicZ, false));
+
+ //better performance for big data
+ FormerPoint aFormerPoint( aSeriesFormerPointMap[pSeries.get()] );
+ m_pPosHelper->setCoordinateSystemResolution(m_aCoordinateSystemResolution);
+ if( !pSeries->isAttributedDataPoint(nIndex)
+ && m_pPosHelper->isSameForGivenResolution(
+ aFormerPoint.m_fX, aFormerPoint.m_fY, aFormerPoint.m_fZ
+ , aScaledLogicPosition.PositionX, aScaledLogicPosition.PositionY, aScaledLogicPosition.PositionZ ) )
+ {
+ m_bPointsWereSkipped = true;
+ continue;
+ }
+ aSeriesFormerPointMap[pSeries.get()] = FormerPoint(aScaledLogicPosition.PositionX, aScaledLogicPosition.PositionY, aScaledLogicPosition.PositionZ);
+
+ //store point information for series polygon
+ //for area and/or line (symbols only do not need this)
+ if( isValidPosition(aScaledLogicPosition) )
+ {
+ AddPointToPoly( pSeries->m_aPolyPolygonShape3D, aScaledLogicPosition, pSeries->m_nPolygonIndex );
+
+ //prepare clipping for filled net charts
+ if( !bIsVisible && m_bArea )
+ {
+ drawing::Position3D aClippedPos(aScaledLogicPosition);
+ m_pPosHelper->clipScaledLogicValues(nullptr, &aClippedPos.PositionY,
+ nullptr);
+ if (m_pPosHelper->isLogicVisible(aClippedPos.PositionX,
+ aClippedPos.PositionY,
+ aClippedPos.PositionZ))
+ {
+ AddPointToPoly( pSeries->m_aPolyPolygonShape3D, aClippedPos, pSeries->m_nPolygonIndex );
+ AddPointToPoly( pSeries->m_aPolyPolygonShape3D, aScaledLogicPosition, pSeries->m_nPolygonIndex );
+ }
+ }
+ }
+
+ //create a single datapoint if point is visible
+ //apply clipping:
+ if( !bIsVisible )
+ continue;
+
+ Symbol* pSymbolProperties = pSeries->getSymbolProperties( nIndex );
+ bool bCreateSymbol = pSymbolProperties && (pSymbolProperties->Style != SymbolStyle_NONE);
+
+ if( !bCreateSymbol && !pSeries->getDataPointLabelIfLabel(nIndex) )
+ continue;
+
+ //create a group shape for this point and add to the series shape:
+ OUString aPointCID = ObjectIdentifier::createPointCID(
+ pSeries->getPointCID_Stub(), nIndex );
+ rtl::Reference<SvxShapeGroupAnyD> xPointGroupShape_Shapes(
+ createGroupShape(xSeriesGroupShape_Shapes,aPointCID) );
+
+ {
+ //create data point
+ drawing::Direction3D aSymbolSize(0,0,0);
+ if (bCreateSymbol) // implies pSymbolProperties
+ {
+ if (pSymbolProperties->Style != SymbolStyle_NONE)
+ {
+ aSymbolSize.DirectionX = pSymbolProperties->Size.Width;
+ aSymbolSize.DirectionY = pSymbolProperties->Size.Height;
+ }
+
+ if (pSymbolProperties->Style == SymbolStyle_STANDARD)
+ {
+ sal_Int32 nSymbol = pSymbolProperties->StandardSymbol;
+ ShapeFactory::createSymbol2D(
+ xPointGroupShape_Shapes, aScenePosition, aSymbolSize, nSymbol,
+ pSymbolProperties->BorderColor, pSymbolProperties->FillColor);
+ }
+ else if (pSymbolProperties->Style == SymbolStyle_GRAPHIC)
+ {
+ ShapeFactory::createGraphic2D(xPointGroupShape_Shapes,
+ aScenePosition, aSymbolSize,
+ pSymbolProperties->Graphic);
+ }
+ //@todo other symbol styles
+ }
+
+ //create data point label
+ if( pSeries->getDataPointLabelIfLabel(nIndex) )
+ {
+ LabelAlignment eAlignment = LABEL_ALIGN_TOP;
+ drawing::Position3D aScenePosition3D( aScenePosition.PositionX
+ , aScenePosition.PositionY
+ , aScenePosition.PositionZ+getTransformedDepth() );
+
+ sal_Int32 nLabelPlacement = pSeries->getLabelPlacement(
+ nIndex, m_xChartTypeModel, m_pPosHelper->isSwapXAndY());
+
+ switch(nLabelPlacement)
+ {
+ case css::chart::DataLabelPlacement::TOP:
+ aScenePosition3D.PositionY -= (aSymbolSize.DirectionY/2+1);
+ eAlignment = LABEL_ALIGN_TOP;
+ break;
+ case css::chart::DataLabelPlacement::BOTTOM:
+ aScenePosition3D.PositionY += (aSymbolSize.DirectionY/2+1);
+ eAlignment = LABEL_ALIGN_BOTTOM;
+ break;
+ case css::chart::DataLabelPlacement::LEFT:
+ aScenePosition3D.PositionX -= (aSymbolSize.DirectionX/2+1);
+ eAlignment = LABEL_ALIGN_LEFT;
+ break;
+ case css::chart::DataLabelPlacement::RIGHT:
+ aScenePosition3D.PositionX += (aSymbolSize.DirectionX/2+1);
+ eAlignment = LABEL_ALIGN_RIGHT;
+ break;
+ case css::chart::DataLabelPlacement::CENTER:
+ eAlignment = LABEL_ALIGN_CENTER;
+ //todo implement this different for area charts
+ break;
+ default:
+ OSL_FAIL("this label alignment is not implemented yet");
+ aScenePosition3D.PositionY -= (aSymbolSize.DirectionY/2+1);
+ eAlignment = LABEL_ALIGN_TOP;
+ break;
+ }
+
+ awt::Point aScreenPosition2D;//get the screen position for the labels
+ sal_Int32 nOffset = 100; //todo maybe calculate this font height dependent
+ if( nLabelPlacement == css::chart::DataLabelPlacement::OUTSIDE )
+ {
+ PolarPlottingPositionHelper* pPolarPosHelper
+ = dynamic_cast<PolarPlottingPositionHelper*>(m_pPosHelper);
+ if( pPolarPosHelper )
+ {
+ PolarLabelPositionHelper aPolarLabelPositionHelper(pPolarPosHelper,m_nDimension,m_xLogicTarget);
+ aScreenPosition2D = aPolarLabelPositionHelper.getLabelScreenPositionAndAlignmentForLogicValues(
+ eAlignment, fLogicX, fLogicY, fLogicZ, nOffset );
+ }
+ }
+ else
+ {
+ if(eAlignment==LABEL_ALIGN_CENTER )
+ nOffset = 0;
+ aScreenPosition2D = LabelPositionHelper(m_nDimension,m_xLogicTarget)
+ .transformSceneToScreenPosition( aScenePosition3D );
+ }
+
+ createDataLabel( m_xTextTarget, *pSeries, nIndex
+ , fLogicValueForLabeDisplay
+ , aLogicYSumMap[nAttachedAxisIndex], aScreenPosition2D, eAlignment, nOffset );
+ }
+ }
+
+ //remove PointGroupShape if empty
+ if(!xPointGroupShape_Shapes->getCount())
+ xSeriesGroupShape_Shapes->remove(xPointGroupShape_Shapes);
+
+ }//next series in x slot (next y slot)
+ }//next x slot
+ }//next z slot
+ }//next category
+
+ impl_createSeriesShapes();
+
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/NetChart.hxx b/chart2/source/view/charttypes/NetChart.hxx
new file mode 100644
index 000000000..a536daf15
--- /dev/null
+++ b/chart2/source/view/charttypes/NetChart.hxx
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <memory>
+#include <VSeriesPlotter.hxx>
+
+namespace chart
+{
+
+class NetChart : public VSeriesPlotter
+{
+ // public methods
+public:
+ NetChart() = delete;
+
+ NetChart( const rtl::Reference< ::chart::ChartType >& xChartTypeModel
+ , sal_Int32 nDimensionCount
+ , bool bNoArea
+ , std::unique_ptr<PlottingPositionHelper> pPlottingPositionHelper //takes ownership
+ );
+ virtual ~NetChart() override;
+
+ virtual void createShapes() override;
+
+ virtual css::drawing::Direction3D getPreferredDiagramAspectRatio() const override;
+
+ // MinimumAndMaximumSupplier
+ virtual double getMaximumX() override;
+ virtual bool isExpandIfValuesCloseToBorder( sal_Int32 nDimensionIndex ) override;
+ virtual bool isSeparateStackingForDifferentSigns( sal_Int32 nDimensionIndex ) override;
+
+ virtual LegendSymbolStyle getLegendSymbolStyle() override;
+ virtual css::uno::Any getExplicitSymbol( const VDataSeries& rSeries, sal_Int32 nPointIndex/*-1 for series symbol*/ ) override;
+
+private: //methods
+ void impl_createSeriesShapes();
+ bool impl_createArea( VDataSeries* pSeries
+ , const std::vector<std::vector<css::drawing::Position3D>>* pSeriesPoly
+ , std::vector<std::vector<css::drawing::Position3D>> const * pPreviousSeriesPoly
+ , PlottingPositionHelper const * pPosHelper );
+ bool impl_createLine( VDataSeries* pSeries
+ , const std::vector<std::vector<css::drawing::Position3D>>* pSeriesPoly
+ , PlottingPositionHelper const * pPosHelper );
+
+private: //member
+ std::unique_ptr<PlottingPositionHelper> m_pMainPosHelper;
+
+ bool m_bArea;//false -> line or symbol only
+ bool m_bLine;
+
+ rtl::Reference<SvxShapeGroupAnyD> m_xSeriesTarget;
+ rtl::Reference<SvxShapeGroupAnyD> m_xTextTarget;
+};
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/PieChart.cxx b/chart2/source/view/charttypes/PieChart.cxx
new file mode 100644
index 000000000..a81428c24
--- /dev/null
+++ b/chart2/source/view/charttypes/PieChart.cxx
@@ -0,0 +1,1719 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <BaseGFXHelper.hxx>
+#include <VLineProperties.hxx>
+#include "PieChart.hxx"
+#include <PlottingPositionHelper.hxx>
+#include <ShapeFactory.hxx>
+#include <PolarLabelPositionHelper.hxx>
+#include <CommonConverters.hxx>
+#include <ObjectIdentifier.hxx>
+#include <ChartType.hxx>
+
+#include <com/sun/star/chart/DataLabelPlacement.hpp>
+#include <com/sun/star/chart2/XColorScheme.hpp>
+
+#include <com/sun/star/drawing/XShapes.hpp>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <tools/diagnose_ex.h>
+#include <tools/helpers.hxx>
+
+#include <limits>
+#include <memory>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::chart2;
+
+namespace chart {
+
+struct PieChart::ShapeParam
+{
+ /** the start angle of the slice
+ */
+ double mfUnitCircleStartAngleDegree;
+
+ /** the angle width of the slice
+ */
+ double mfUnitCircleWidthAngleDegree;
+
+ /** the normalized outer radius of the ring the slice belongs to.
+ */
+ double mfUnitCircleOuterRadius;
+
+ /** the normalized inner radius of the ring the slice belongs to
+ */
+ double mfUnitCircleInnerRadius;
+
+ /** relative distance offset of a slice from the pie center;
+ * this parameter is used for instance when the user performs manual
+ * dragging of a slice (the drag operation is possible only for slices that
+ * belong to the outer ring and only along the ray bisecting the slice);
+ * the value for the given entry in the data series is obtained by the
+ * `Offset` property attached to each entry; note that the value
+ * provided by the `Offset` property is used both as a logical value in
+ * `PiePositionHelper::getInnerAndOuterRadius` and as a percentage value in
+ * the `PieChart::createDataPoint` and `PieChart::createTextLabelShape`
+ * methods; since the logical height of a ring is always 1, this duality
+ * does not cause any incorrect behavior;
+ */
+ double mfExplodePercentage;
+
+ /** sum of all Y values in a single series
+ */
+ double mfLogicYSum;
+
+ /** for 3D pie chart: label z coordinate
+ */
+ double mfLogicZ;
+
+ /** for 3D pie chart: height
+ */
+ double mfDepth;
+
+ ShapeParam() :
+ mfUnitCircleStartAngleDegree(0.0),
+ mfUnitCircleWidthAngleDegree(0.0),
+ mfUnitCircleOuterRadius(0.0),
+ mfUnitCircleInnerRadius(0.0),
+ mfExplodePercentage(0.0),
+ mfLogicYSum(0.0),
+ mfLogicZ(0.0),
+ mfDepth(0.0) {}
+};
+
+namespace
+{
+::basegfx::B2IRectangle lcl_getRect(const rtl::Reference<SvxShape>& xShape)
+{
+ ::basegfx::B2IRectangle aRect;
+ if (xShape.is())
+ aRect = BaseGFXHelper::makeRectangle(xShape->getPosition(), xShape->getSize());
+ return aRect;
+}
+
+bool lcl_isInsidePage(const awt::Point& rPos, const awt::Size& rSize, const awt::Size& rPageSize)
+{
+ if (rPos.X < 0 || rPos.Y < 0)
+ return false;
+ if ((rPos.X + rSize.Width) > rPageSize.Width)
+ return false;
+ if ((rPos.Y + rSize.Height) > rPageSize.Height)
+ return false;
+ return true;
+}
+
+} //end anonymous namespace
+
+class PiePositionHelper : public PolarPlottingPositionHelper
+{
+public:
+ PiePositionHelper( double fAngleDegreeOffset );
+
+ bool getInnerAndOuterRadius( double fCategoryX, double& fLogicInnerRadius, double& fLogicOuterRadius, bool bUseRings, double fMaxOffset ) const;
+
+public:
+ //Distance between different category rings, seen relative to width of a ring:
+ double m_fRingDistance; //>=0 m_fRingDistance=1 --> distance == width
+};
+
+PiePositionHelper::PiePositionHelper( double fAngleDegreeOffset )
+ : m_fRingDistance(0.0)
+{
+ m_fRadiusOffset = 0.0;
+ m_fAngleDegreeOffset = fAngleDegreeOffset;
+}
+
+/** Compute the outer and the inner radius for the current ring (not for the
+ * whole donut!), in general it is:
+ * inner_radius = (ring_index + 1) - 0.5 + max_offset,
+ * outer_radius = (ring_index + 1) + 0.5 + max_offset.
+ * When orientation for the radius axis is reversed these values are swapped.
+ * (Indeed the orientation for the radius axis is always reversed!
+ * See `PieChartTypeTemplate::adaptScales`.)
+ * The maximum relative offset (see notes for `PieChart::getMaxOffset`) is
+ * added to both the inner and the outer radius.
+ * It returns true if the ring is visible (that is not out of the radius
+ * axis scale range).
+ */
+bool PiePositionHelper::getInnerAndOuterRadius( double fCategoryX
+ , double& fLogicInnerRadius, double& fLogicOuterRadius
+ , bool bUseRings, double fMaxOffset ) const
+{
+ if( !bUseRings )
+ fCategoryX = 1.0;
+
+ double fLogicInner = fCategoryX -0.5+m_fRingDistance/2.0;
+ double fLogicOuter = fCategoryX +0.5-m_fRingDistance/2.0;
+
+ if( !isMathematicalOrientationRadius() )
+ {
+ //in this case the given getMaximumX() was not correct instead the minimum should have been smaller by fMaxOffset
+ //but during getMaximumX and getMimumX we do not know the axis orientation
+ fLogicInner += fMaxOffset;
+ fLogicOuter += fMaxOffset;
+ }
+
+ if( fLogicInner >= getLogicMaxX() )
+ return false;
+ if( fLogicOuter <= getLogicMinX() )
+ return false;
+
+ if( fLogicInner < getLogicMinX() )
+ fLogicInner = getLogicMinX();
+ if( fLogicOuter > getLogicMaxX() )
+ fLogicOuter = getLogicMaxX();
+
+ fLogicInnerRadius = fLogicInner;
+ fLogicOuterRadius = fLogicOuter;
+ if( !isMathematicalOrientationRadius() )
+ std::swap(fLogicInnerRadius,fLogicOuterRadius);
+ return true;
+}
+
+PieChart::PieChart( const rtl::Reference<ChartType>& xChartTypeModel
+ , sal_Int32 nDimensionCount
+ , bool bExcludingPositioning )
+ : VSeriesPlotter( xChartTypeModel, nDimensionCount )
+ , m_pPosHelper( new PiePositionHelper( (m_nDimension==3) ? 0.0 : 90.0 ) )
+ , m_bUseRings(false)
+ , m_bSizeExcludesLabelsAndExplodedSegments(bExcludingPositioning)
+ , m_fMaxOffset(std::numeric_limits<double>::quiet_NaN())
+{
+ PlotterBase::m_pPosHelper = m_pPosHelper.get();
+ VSeriesPlotter::m_pMainPosHelper = m_pPosHelper.get();
+ m_pPosHelper->m_fRadiusOffset = 0.0;
+ m_pPosHelper->m_fRingDistance = 0.0;
+
+ if( !xChartTypeModel.is() )
+ return;
+
+ try
+ {
+ xChartTypeModel->getPropertyValue( "UseRings") >>= m_bUseRings;
+ if( m_bUseRings )
+ {
+ m_pPosHelper->m_fRadiusOffset = 1.0;
+ if( nDimensionCount==3 )
+ m_pPosHelper->m_fRingDistance = 0.1;
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+}
+
+PieChart::~PieChart()
+{
+}
+
+void PieChart::setScales( std::vector< ExplicitScaleData >&& rScales, bool /* bSwapXAndYAxis */ )
+{
+ OSL_ENSURE(m_nDimension<=static_cast<sal_Int32>(rScales.size()),"Dimension of Plotter does not fit two dimension of given scale sequence");
+ m_pPosHelper->setScales( std::move(rScales), true );
+}
+
+drawing::Direction3D PieChart::getPreferredDiagramAspectRatio() const
+{
+ if( m_nDimension == 3 )
+ return drawing::Direction3D(1,1,0.10);
+ return drawing::Direction3D(1,1,1);
+}
+
+bool PieChart::shouldSnapRectToUsedArea()
+{
+ return true;
+}
+
+rtl::Reference<SvxShape> PieChart::createDataPoint(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget,
+ const uno::Reference<beans::XPropertySet>& xObjectProperties,
+ const ShapeParam& rParam,
+ const sal_Int32 nPointCount,
+ const bool bConcentricExplosion)
+{
+ //transform position:
+ drawing::Direction3D aOffset;
+ double fExplodedInnerRadius = rParam.mfUnitCircleInnerRadius;
+ double fExplodedOuterRadius = rParam.mfUnitCircleOuterRadius;
+ double fStartAngle = rParam.mfUnitCircleStartAngleDegree;
+ double fWidthAngle = rParam.mfUnitCircleWidthAngleDegree;
+
+ if (rParam.mfExplodePercentage != 0.0) {
+ double fRadius = (fExplodedOuterRadius-fExplodedInnerRadius)*rParam.mfExplodePercentage;
+
+ if (bConcentricExplosion) {
+
+ // For concentric explosion, increase the radius but retain the original
+ // arc length of all ring segments together. This results in a gap
+ // that's evenly divided among all segments, assuming they all have
+ // the same explosion percentage
+ assert(fExplodedInnerRadius >= 0 && fExplodedOuterRadius > 0);
+ double fAngleRatio = (fExplodedInnerRadius + fExplodedOuterRadius) /
+ (fExplodedInnerRadius + fExplodedOuterRadius + 2 * fRadius);
+
+ assert(nPointCount > 0);
+ double fAngleGap = 360 * (1.0 - fAngleRatio) / nPointCount;
+ fStartAngle += fAngleGap / 2;
+ fWidthAngle -= fAngleGap;
+
+ fExplodedInnerRadius += fRadius;
+ fExplodedOuterRadius += fRadius;
+
+ } else {
+ // For the non-concentric explosion case, keep the original radius
+ // but shift the circle origin
+ double fAngle = fStartAngle + fWidthAngle/2.0;
+
+ drawing::Position3D aOrigin = m_pPosHelper->transformUnitCircleToScene(0, 0, rParam.mfLogicZ);
+ drawing::Position3D aNewOrigin = m_pPosHelper->transformUnitCircleToScene(fAngle, fRadius, rParam.mfLogicZ);
+ aOffset = aNewOrigin - aOrigin;
+ }
+ }
+
+ //create point
+ rtl::Reference<SvxShape> xShape;
+ if(m_nDimension==3)
+ {
+ xShape = ShapeFactory::createPieSegment( xTarget
+ , fStartAngle, fWidthAngle
+ , fExplodedInnerRadius, fExplodedOuterRadius
+ , aOffset, B3DHomMatrixToHomogenMatrix( m_pPosHelper->getUnitCartesianToScene() )
+ , rParam.mfDepth );
+ }
+ else
+ {
+ xShape = ShapeFactory::createPieSegment2D( xTarget
+ , fStartAngle, fWidthAngle
+ , fExplodedInnerRadius, fExplodedOuterRadius
+ , aOffset, B3DHomMatrixToHomogenMatrix( m_pPosHelper->getUnitCartesianToScene() ) );
+ }
+ PropertyMapper::setMappedProperties( *xShape, xObjectProperties, PropertyMapper::getPropertyNameMapForFilledSeriesProperties() );
+ return xShape;
+}
+
+void PieChart::createTextLabelShape(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTextTarget,
+ VDataSeries& rSeries, sal_Int32 nPointIndex, ShapeParam& rParam )
+{
+ if (!rSeries.getDataPointLabelIfLabel(nPointIndex))
+ // There is no text label for this data point. Nothing to do.
+ return;
+
+ ///by using the `mfExplodePercentage` parameter a normalized offset is added
+ ///to both normalized radii. (See notes for
+ ///`PolarPlottingPositionHelper::transformToRadius`, especially example 3,
+ ///and related comments).
+ if (rParam.mfExplodePercentage != 0.0)
+ {
+ double fExplodeOffset = (rParam.mfUnitCircleOuterRadius-rParam.mfUnitCircleInnerRadius)*rParam.mfExplodePercentage;
+ rParam.mfUnitCircleInnerRadius += fExplodeOffset;
+ rParam.mfUnitCircleOuterRadius += fExplodeOffset;
+ }
+
+ ///get the required label placement type. Available placements are
+ ///`AVOID_OVERLAP`, `CENTER`, `OUTSIDE` and `INSIDE`;
+ sal_Int32 nLabelPlacement = rSeries.getLabelPlacement(
+ nPointIndex, m_xChartTypeModel, m_pPosHelper->isSwapXAndY());
+
+ ///when the placement is of `AVOID_OVERLAP` type a later rearrangement of
+ ///the label position is allowed; the `createTextLabelShape` treats the
+ ///`AVOID_OVERLAP` as if it was of `CENTER` type;
+
+ double nVal = rSeries.getYValue(nPointIndex);
+ //AVOID_OVERLAP is in fact "Best fit" in the UI.
+ bool bMovementAllowed = nLabelPlacement == css::chart::DataLabelPlacement::AVOID_OVERLAP
+ || nLabelPlacement == css::chart::DataLabelPlacement::CUSTOM;
+ if( bMovementAllowed )
+ nLabelPlacement = css::chart::DataLabelPlacement::CENTER;
+
+ ///for `OUTSIDE` (`INSIDE`) label placements an offset of 150 (-150), in the
+ ///radius direction, is added to the final screen position of the label
+ ///anchor point. This is required in order to ensure that the label is
+ ///completely outside (inside) the related slice. Indeed this value should
+ ///depend on the font height;
+ ///pay attention: 150 is not a big offset, in fact the screen position
+ ///coordinates for label anchor points are in the 10000-20000 range, hence
+ ///these are coordinates of a virtual screen and 150 is a small value;
+ LabelAlignment eAlignment(LABEL_ALIGN_CENTER);
+ sal_Int32 nScreenValueOffsetInRadiusDirection = 0 ;
+ if( nLabelPlacement == css::chart::DataLabelPlacement::OUTSIDE )
+ nScreenValueOffsetInRadiusDirection = (m_nDimension!=3) ? 150 : 0;//todo maybe calculate this font height dependent
+ else if( nLabelPlacement == css::chart::DataLabelPlacement::INSIDE )
+ nScreenValueOffsetInRadiusDirection = (m_nDimension!=3) ? -150 : 0;//todo maybe calculate this font height dependent
+
+ ///the scene position of the label anchor point is calculated (see notes for
+ ///`PolarLabelPositionHelper::getLabelScreenPositionAndAlignmentForUnitCircleValues`),
+ ///and immediately transformed into the screen position.
+ PolarLabelPositionHelper aPolarPosHelper(m_pPosHelper.get(),m_nDimension,m_xLogicTarget);
+ awt::Point aScreenPosition2D(
+ aPolarPosHelper.getLabelScreenPositionAndAlignmentForUnitCircleValues(eAlignment, nLabelPlacement
+ , rParam.mfUnitCircleStartAngleDegree, rParam.mfUnitCircleWidthAngleDegree
+ , rParam.mfUnitCircleInnerRadius, rParam.mfUnitCircleOuterRadius, rParam.mfLogicZ+0.5, 0 ));
+
+ ///the screen position of the pie/donut center is calculated.
+ PieLabelInfo aPieLabelInfo;
+ aPieLabelInfo.aFirstPosition = basegfx::B2IVector( aScreenPosition2D.X, aScreenPosition2D.Y );
+ awt::Point aOrigin( aPolarPosHelper.transformSceneToScreenPosition( m_pPosHelper->transformUnitCircleToScene( 0.0, 0.0, rParam.mfLogicZ+1.0 ) ) );
+ aPieLabelInfo.aOrigin = basegfx::B2IVector( aOrigin.X, aOrigin.Y );
+
+ ///add a scaling independent Offset if requested
+ if( nScreenValueOffsetInRadiusDirection != 0)
+ {
+ basegfx::B2IVector aDirection( aScreenPosition2D.X- aOrigin.X, aScreenPosition2D.Y- aOrigin.Y );
+ aDirection.setLength(nScreenValueOffsetInRadiusDirection);
+ aScreenPosition2D.X += aDirection.getX();
+ aScreenPosition2D.Y += aDirection.getY();
+ }
+
+ // compute outer pie radius
+ awt::Point aOuterCirclePoint = PlottingPositionHelper::transformSceneToScreenPosition(
+ m_pPosHelper->transformUnitCircleToScene(
+ 0,
+ rParam.mfUnitCircleOuterRadius,
+ 0 ),
+ m_xLogicTarget, m_nDimension );
+ basegfx::B2IVector aRadiusVector(
+ aOuterCirclePoint.X - aPieLabelInfo.aOrigin.getX(),
+ aOuterCirclePoint.Y - aPieLabelInfo.aOrigin.getY() );
+ double fSquaredPieRadius = aRadiusVector.scalar(aRadiusVector);
+ double fPieRadius = sqrt( fSquaredPieRadius );
+ double fAngleDegree
+ = rParam.mfUnitCircleStartAngleDegree + rParam.mfUnitCircleWidthAngleDegree / 2.0;
+ while (fAngleDegree > 360.0)
+ fAngleDegree -= 360.0;
+ while (fAngleDegree < 0.0)
+ fAngleDegree += 360.0;
+
+ awt::Point aOuterPosition = PlottingPositionHelper::transformSceneToScreenPosition(
+ m_pPosHelper->transformUnitCircleToScene(fAngleDegree, rParam.mfUnitCircleOuterRadius, 0),
+ m_xLogicTarget, m_nDimension);
+ aPieLabelInfo.aOuterPosition = basegfx::B2IVector(aOuterPosition.X, aOuterPosition.Y);
+
+ // set the maximum text width to be used when text wrapping is enabled
+ double fTextMaximumFrameWidth = 0.8 * fPieRadius;
+ if (nLabelPlacement == css::chart::DataLabelPlacement::OUTSIDE
+ && m_aAvailableOuterRect.getWidth())
+ {
+ if ((fAngleDegree >= 67.5 && fAngleDegree <= 112.5)
+ || (fAngleDegree >= 247.5 && fAngleDegree <= 292.5))
+ fTextMaximumFrameWidth = m_aAvailableOuterRect.getWidth() / 3.0;
+ else
+ fTextMaximumFrameWidth = 0.85 * (m_aAvailableOuterRect.getWidth() / 2.0 - fPieRadius);
+ }
+ sal_Int32 nTextMaximumFrameWidth = ceil(fTextMaximumFrameWidth);
+
+ ///the text shape for the label is created
+ aPieLabelInfo.xTextShape = createDataLabel(
+ xTextTarget, rSeries, nPointIndex, nVal, rParam.mfLogicYSum,
+ aScreenPosition2D, eAlignment, 0, nTextMaximumFrameWidth);
+
+ ///a new `PieLabelInfo` instance is initialized with all the info related to
+ ///the current label in order to simplify later label position rearrangement;
+ rtl::Reference< SvxShape > xChild = aPieLabelInfo.xTextShape;
+
+ ///text shape could be empty; in that case there is no need to add label info
+ if( !xChild.is() )
+ return;
+
+ aPieLabelInfo.xLabelGroupShape = dynamic_cast<SvxShapeGroupAnyD*>(xChild->getParent().get());
+
+ if (bMovementAllowed && !m_bUseRings)
+ {
+ /** Handle the placement of the label in the best fit case.
+ * First off the routine try to place the label inside the related pie slice,
+ * if this is not possible the label is placed outside.
+ */
+ if (rSeries.getLabelPlacement(nPointIndex, m_xChartTypeModel, m_pPosHelper->isSwapXAndY())
+ == css::chart::DataLabelPlacement::CUSTOM
+ || !performLabelBestFitInnerPlacement(rParam, aPieLabelInfo))
+ {
+ if (m_aAvailableOuterRect.getWidth())
+ {
+ if ((fAngleDegree >= 67.5 && fAngleDegree <= 112.5)
+ || (fAngleDegree >= 247.5 && fAngleDegree <= 292.5))
+ fTextMaximumFrameWidth = m_aAvailableOuterRect.getWidth() / 3.0;
+ else
+ fTextMaximumFrameWidth
+ = 0.85 * (m_aAvailableOuterRect.getWidth() / 2.0 - fPieRadius);
+ nTextMaximumFrameWidth = ceil(fTextMaximumFrameWidth);
+ }
+
+ nScreenValueOffsetInRadiusDirection = (m_nDimension != 3) ? 150 : 0;
+ aScreenPosition2D
+ = aPolarPosHelper.getLabelScreenPositionAndAlignmentForUnitCircleValues(
+ eAlignment, css::chart::DataLabelPlacement::OUTSIDE,
+ rParam.mfUnitCircleStartAngleDegree,
+ rParam.mfUnitCircleWidthAngleDegree, rParam.mfUnitCircleInnerRadius,
+ rParam.mfUnitCircleOuterRadius, rParam.mfLogicZ + 0.5, 0);
+ aPieLabelInfo.aFirstPosition
+ = basegfx::B2IVector(aScreenPosition2D.X, aScreenPosition2D.Y);
+
+ //add a scaling independent Offset if requested
+ if (nScreenValueOffsetInRadiusDirection != 0)
+ {
+ basegfx::B2IVector aDirection(aScreenPosition2D.X - aOrigin.X,
+ aScreenPosition2D.Y - aOrigin.Y);
+ aDirection.setLength(nScreenValueOffsetInRadiusDirection);
+ aScreenPosition2D.X += aDirection.getX();
+ aScreenPosition2D.Y += aDirection.getY();
+ }
+
+ uno::Reference<drawing::XShapes> xShapes(xChild->getParent(), uno::UNO_QUERY);
+ xShapes->remove(aPieLabelInfo.xTextShape);
+ aPieLabelInfo.xTextShape
+ = createDataLabel(xTextTarget, rSeries, nPointIndex, nVal, rParam.mfLogicYSum,
+ aScreenPosition2D, eAlignment, 0, nTextMaximumFrameWidth);
+ xChild = aPieLabelInfo.xTextShape;
+ if (!xChild.is())
+ return;
+
+ aPieLabelInfo.xLabelGroupShape = dynamic_cast<SvxShapeGroupAnyD*>(xChild->getParent().get());
+ }
+ }
+
+ bool bShowLeaderLine = rSeries.getPropertiesOfSeries()
+ ->getPropertyValue("ShowCustomLeaderLines")
+ .get<sal_Bool>();
+ if (m_bPieLabelsAllowToMove)
+ {
+ ::basegfx::B2IRectangle aRect(lcl_getRect(aPieLabelInfo.xLabelGroupShape));
+ sal_Int32 nPageWidth = m_aPageReferenceSize.Width;
+ sal_Int32 nPageHeight = m_aPageReferenceSize.Height;
+
+ // the data label should be inside the chart area
+ awt::Point aShapePos = aPieLabelInfo.xLabelGroupShape->getPosition();
+ if (aRect.getMinX() < 0)
+ aPieLabelInfo.xLabelGroupShape->setPosition(
+ awt::Point(aShapePos.X - aRect.getMinX(), aShapePos.Y));
+ if (aRect.getMinY() < 0)
+ aPieLabelInfo.xLabelGroupShape->setPosition(
+ awt::Point(aShapePos.X, aShapePos.Y - aRect.getMinY()));
+ if (aRect.getMaxX() > nPageWidth)
+ aPieLabelInfo.xLabelGroupShape->setPosition(
+ awt::Point(aShapePos.X - (aRect.getMaxX() - nPageWidth), aShapePos.Y));
+ if (aRect.getMaxY() > nPageHeight)
+ aPieLabelInfo.xLabelGroupShape->setPosition(
+ awt::Point(aShapePos.X, aShapePos.Y - (aRect.getMaxY() - nPageHeight)));
+
+ if (rSeries.isLabelCustomPos(nPointIndex) && bShowLeaderLine)
+ {
+ sal_Int32 nX1 = aPieLabelInfo.aOuterPosition.getX();
+ sal_Int32 nY1 = aPieLabelInfo.aOuterPosition.getY();
+ sal_Int32 nX2 = nX1;
+ sal_Int32 nY2 = nY1;
+ if (nX1 < aRect.getMinX())
+ nX2 = aRect.getMinX();
+ else if (nX1 > aRect.getMaxX())
+ nX2 = aRect.getMaxX();
+
+ if (nY1 < aRect.getMinY())
+ nY2 = aRect.getMinY();
+ else if (nY1 > aRect.getMaxY())
+ nY2 = aRect.getMaxY();
+
+ sal_Int32 nSquaredDistanceFromOrigin
+ = (nX2 - aOrigin.X) * (nX2 - aOrigin.X) + (nY2 - aOrigin.Y) * (nY2 - aOrigin.Y);
+
+ // tdf#138018 Don't show leader line when custom positioned data label is inside pie chart
+ if (nSquaredDistanceFromOrigin > fSquaredPieRadius)
+ {
+ //when the line is very short compared to the page size don't create one
+ ::basegfx::B2DVector aLength(nX1 - nX2, nY1 - nY2);
+ double fPageDiagonaleLength = std::hypot(nPageWidth, nPageHeight);
+ if ((aLength.getLength() / fPageDiagonaleLength) >= 0.01)
+ {
+ drawing::PointSequenceSequence aPoints{ { {nX1, nY1}, {nX2, nY2} } };
+
+ VLineProperties aVLineProperties;
+ if (aPieLabelInfo.xTextShape.is())
+ {
+ sal_Int32 nColor = 0;
+ aPieLabelInfo.xTextShape->SvxShape::getPropertyValue("CharColor") >>= nColor;
+ //automatic font color does not work for lines -> fallback to black
+ if (nColor != -1)
+ aVLineProperties.Color <<= nColor;
+ }
+ ShapeFactory::createLine2D(xTextTarget, aPoints, &aVLineProperties);
+ }
+ }
+ }
+ }
+
+ aPieLabelInfo.fValue = nVal;
+ aPieLabelInfo.bMovementAllowed = bMovementAllowed;
+ aPieLabelInfo.bMoved = false;
+ aPieLabelInfo.xTextTarget = xTextTarget;
+ aPieLabelInfo.bShowLeaderLine = bShowLeaderLine && !rSeries.isLabelCustomPos(nPointIndex);
+
+ m_aLabelInfoList.push_back(aPieLabelInfo);
+}
+
+void PieChart::addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 /* zSlot */, sal_Int32 /* xSlot */, sal_Int32 /* ySlot */ )
+{
+ VSeriesPlotter::addSeries( std::move(pSeries), 0, -1, 0 );
+}
+
+double PieChart::getMinimumX()
+{
+ return 0.5;
+}
+double PieChart::getMaxOffset()
+{
+ if (!std::isnan(m_fMaxOffset))
+ // Value already cached. Use it.
+ return m_fMaxOffset;
+
+ m_fMaxOffset = 0.0;
+ if( m_aZSlots.empty() )
+ return m_fMaxOffset;
+ if( m_aZSlots.front().empty() )
+ return m_fMaxOffset;
+
+ const std::vector< std::unique_ptr<VDataSeries> >& rSeriesList( m_aZSlots.front().front().m_aSeriesVector );
+ if(rSeriesList.empty())
+ return m_fMaxOffset;
+
+ VDataSeries* pSeries = rSeriesList.front().get();
+ uno::Reference< beans::XPropertySet > xSeriesProp( pSeries->getPropertiesOfSeries() );
+ if( !xSeriesProp.is() )
+ return m_fMaxOffset;
+
+ double fExplodePercentage=0.0;
+ xSeriesProp->getPropertyValue( "Offset") >>= fExplodePercentage;
+ if(fExplodePercentage>m_fMaxOffset)
+ m_fMaxOffset=fExplodePercentage;
+
+ if(!m_bSizeExcludesLabelsAndExplodedSegments)
+ {
+ uno::Sequence< sal_Int32 > aAttributedDataPointIndexList;
+ if( xSeriesProp->getPropertyValue( "AttributedDataPoints" ) >>= aAttributedDataPointIndexList )
+ {
+ for(sal_Int32 nN=aAttributedDataPointIndexList.getLength();nN--;)
+ {
+ uno::Reference< beans::XPropertySet > xPointProp( pSeries->getPropertiesOfPoint(aAttributedDataPointIndexList[nN]) );
+ if(xPointProp.is())
+ {
+ fExplodePercentage=0.0;
+ xPointProp->getPropertyValue( "Offset") >>= fExplodePercentage;
+ if(fExplodePercentage>m_fMaxOffset)
+ m_fMaxOffset=fExplodePercentage;
+ }
+ }
+ }
+ }
+ return m_fMaxOffset;
+}
+double PieChart::getMaximumX()
+{
+ double fMaxOffset = getMaxOffset();
+ if( !m_aZSlots.empty() && m_bUseRings)
+ return m_aZSlots.front().size()+0.5+fMaxOffset;
+ return 1.5+fMaxOffset;
+}
+double PieChart::getMinimumYInRange( double /* fMinimumX */, double /* fMaximumX */, sal_Int32 /* nAxisIndex */ )
+{
+ return 0.0;
+}
+
+double PieChart::getMaximumYInRange( double /* fMinimumX */, double /* fMaximumX */, sal_Int32 /* nAxisIndex */ )
+{
+ return 1.0;
+}
+
+bool PieChart::isExpandBorderToIncrementRhythm( sal_Int32 /* nDimensionIndex */ )
+{
+ return false;
+}
+
+bool PieChart::isExpandIfValuesCloseToBorder( sal_Int32 /* nDimensionIndex */ )
+{
+ return false;
+}
+
+bool PieChart::isExpandWideValuesToZero( sal_Int32 /* nDimensionIndex */ )
+{
+ return false;
+}
+
+bool PieChart::isExpandNarrowValuesTowardZero( sal_Int32 /* nDimensionIndex */ )
+{
+ return false;
+}
+
+bool PieChart::isSeparateStackingForDifferentSigns( sal_Int32 /* nDimensionIndex */ )
+{
+ return false;
+}
+
+void PieChart::createShapes()
+{
+ ///a ZSlot is a vector< vector< VDataSeriesGroup > >. There is only one
+ ///ZSlot: m_aZSlots[0] which has a number of elements equal to the total
+ ///number of data series (in fact, even if m_aZSlots[0][i] is an object of
+ ///type `VDataSeriesGroup`, in the current implementation, there is only one
+ ///data series in each data series group).
+ if (m_aZSlots.empty())
+ // No series to plot.
+ return;
+
+ ///m_xLogicTarget is where the group of all data series shapes (e.g. a pie
+ ///slice) is added (xSeriesTarget);
+
+ ///m_xFinalTarget is where the group of all text shapes (labels) is added
+ ///(xTextTarget).
+
+ ///both have been already created and added to the same root shape
+ ///( a member of a VDiagram object); this initialization occurs in
+ ///`ChartView::impl_createDiagramAndContent`.
+
+ OSL_ENSURE(m_xLogicTarget.is() && m_xFinalTarget.is(), "PieChart is not properly initialized.");
+ if (!m_xLogicTarget.is() || !m_xFinalTarget.is())
+ return;
+
+ ///the text labels should be always on top of the other series shapes
+ ///therefore create an own group for the texts to move them to front
+ ///(because the text group is created after the series group the texts are
+ ///displayed on top)
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesTarget = createGroupShape( m_xLogicTarget );
+ rtl::Reference<SvxShapeGroup> xTextTarget = ShapeFactory::createGroup2D( m_xFinalTarget );
+ //check necessary here that different Y axis can not be stacked in the same group? ... hm?
+
+ ///pay attention that the `m_bSwapXAndY` parameter used by the polar
+ ///plotting position helper is always set to true for pie/donut charts
+ ///(see PieChart::setScales). This fact causes that `createShapes` expects
+ ///that the radius axis scale is the one with index 0 and the angle axis
+ ///scale is the one with index 1.
+
+ std::vector< VDataSeriesGroup >::iterator aXSlotIter = m_aZSlots.front().begin();
+ const std::vector< VDataSeriesGroup >::const_iterator aXSlotEnd = m_aZSlots.front().end();
+
+ ///m_bUseRings == true if chart type is `donut`, == false if chart type is
+ ///`pie`; if the chart is of `donut` type we have as many rings as many data
+ ///series, else we have a single ring (a pie) representing the first data
+ ///series;
+ ///for what I can see the radius axis orientation is always reversed and
+ ///the angle axis orientation is always non-reversed;
+ ///the radius axis scale range is [0.5, number of rings + 0.5 + max_offset],
+ ///the angle axis scale range is [0, 1]. The max_offset parameter is used
+ ///for exploded pie chart and its value is 0.5.
+
+ ///the `explodeable` ring is the first one except when the radius axis
+ ///orientation is reversed (always!?) and we are dealing with a donut: in
+ ///such a case the `explodeable` ring is the last one.
+ std::vector< VDataSeriesGroup >::size_type nExplodeableSlot = 0;
+ if( m_pPosHelper->isMathematicalOrientationRadius() && m_bUseRings )
+ nExplodeableSlot = m_aZSlots.front().size()-1;
+
+ m_aLabelInfoList.clear();
+ m_fMaxOffset = std::numeric_limits<double>::quiet_NaN();
+ sal_Int32 n3DRelativeHeight = 100;
+ if ( (m_nDimension==3) && m_xChartTypeModel.is())
+ {
+ try
+ {
+ uno::Any aAny = m_xChartTypeModel->getPropertyValue( "3DRelativeHeight" );
+ aAny >>= n3DRelativeHeight;
+ }
+ catch (const uno::Exception&) { }
+ }
+ ///iterate over each xslot, that is on each data series (there is
+ ///only one data series in each data series group!); note that if the chart
+ ///type is a pie the loop iterates only over the first data series
+ ///(m_bUseRings||fSlotX<0.5)
+ for( double fSlotX=0; aXSlotIter != aXSlotEnd && (m_bUseRings||fSlotX<0.5 ); ++aXSlotIter, fSlotX+=1.0 )
+ {
+ ShapeParam aParam;
+
+ std::vector< std::unique_ptr<VDataSeries> >* pSeriesList = &(aXSlotIter->m_aSeriesVector);
+ if(pSeriesList->empty())//there should be only one series in each x slot
+ continue;
+ VDataSeries* pSeries = pSeriesList->front().get();
+ if(!pSeries)
+ continue;
+
+ bool bHasFillColorMapping = pSeries->hasPropertyMapping("FillColor");
+
+ /// The angle degree offset is set by the same property of the
+ /// data series.
+ /// Counter-clockwise offset from the 3 o'clock position.
+ m_pPosHelper->m_fAngleDegreeOffset = pSeries->getStartingAngle();
+
+ ///iterate through all points to get the sum of all entries of
+ ///the current data series
+ sal_Int32 nPointIndex=0;
+ sal_Int32 nPointCount=pSeries->getTotalPointCount();
+ for( nPointIndex = 0; nPointIndex < nPointCount; nPointIndex++ )
+ {
+ double fY = pSeries->getYValue( nPointIndex );
+ if(fY<0.0)
+ {
+ //@todo warn somehow that negative values are treated as positive
+ }
+ if( std::isnan(fY) )
+ continue;
+ aParam.mfLogicYSum += fabs(fY);
+ }
+
+ if (aParam.mfLogicYSum == 0.0)
+ // Total sum of all Y values in this series is zero. Skip the whole series.
+ continue;
+
+ double fLogicYForNextPoint = 0.0;
+ ///iterate through all points to create shapes
+ for( nPointIndex = 0; nPointIndex < nPointCount; nPointIndex++ )
+ {
+ double fLogicInnerRadius, fLogicOuterRadius;
+
+ ///compute the maximum relative distance offset of the current slice
+ ///from the pie center
+ ///it is worth noting that after the first invocation the maximum
+ ///offset value is cached, so it is evaluated only once per each
+ ///call to `createShapes`
+ double fOffset = getMaxOffset();
+
+ ///compute the outer and the inner radius for the current ring slice
+ bool bIsVisible = m_pPosHelper->getInnerAndOuterRadius( fSlotX+1.0, fLogicInnerRadius, fLogicOuterRadius, m_bUseRings, fOffset );
+ if( !bIsVisible )
+ continue;
+
+ aParam.mfDepth = getTransformedDepth() * (n3DRelativeHeight / 100.0);
+
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes = getSeriesGroupShape(pSeries, xSeriesTarget);
+ ///collect data point information (logic coordinates, style ):
+ double fLogicYValue = fabs(pSeries->getYValue( nPointIndex ));
+ if( std::isnan(fLogicYValue) )
+ continue;
+ if(fLogicYValue==0.0)//@todo: continue also if the resolution is too small
+ continue;
+ double fLogicYPos = fLogicYForNextPoint;
+ fLogicYForNextPoint += fLogicYValue;
+
+ uno::Reference< beans::XPropertySet > xPointProperties = pSeries->getPropertiesOfPoint( nPointIndex );
+
+ //iterate through all subsystems to create partial points
+ {
+ //logic values on angle axis:
+ double fLogicStartAngleValue = fLogicYPos / aParam.mfLogicYSum;
+ double fLogicEndAngleValue = (fLogicYPos+fLogicYValue) / aParam.mfLogicYSum;
+
+ ///note that the explode percentage is set to the `Offset`
+ ///property of the current data series entry only for slices
+ ///belonging to the outer ring
+ aParam.mfExplodePercentage = 0.0;
+ bool bDoExplode = ( nExplodeableSlot == static_cast< std::vector< VDataSeriesGroup >::size_type >(fSlotX) );
+ if(bDoExplode) try
+ {
+ xPointProperties->getPropertyValue( "Offset") >>= aParam.mfExplodePercentage;
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+
+ ///see notes for `PolarPlottingPositionHelper` methods
+ ///transform to unit circle:
+ aParam.mfUnitCircleWidthAngleDegree = m_pPosHelper->getWidthAngleDegree( fLogicStartAngleValue, fLogicEndAngleValue );
+ aParam.mfUnitCircleStartAngleDegree = m_pPosHelper->transformToAngleDegree( fLogicStartAngleValue );
+ aParam.mfUnitCircleInnerRadius = m_pPosHelper->transformToRadius( fLogicInnerRadius );
+ aParam.mfUnitCircleOuterRadius = m_pPosHelper->transformToRadius( fLogicOuterRadius );
+
+ ///create data point
+ aParam.mfLogicZ = -1.0; // For 3D pie chart label position
+
+ // Do concentric explosion if it's a donut chart with more than one series
+ const bool bConcentricExplosion = m_bUseRings && (m_aZSlots.front().size() > 1);
+ rtl::Reference<SvxShape> xPointShape =
+ createDataPoint(
+ xSeriesGroupShape_Shapes, xPointProperties, aParam, nPointCount,
+ bConcentricExplosion);
+
+ ///point color:
+ if (!pSeries->hasPointOwnColor(nPointIndex) && m_xColorScheme.is())
+ {
+ xPointShape->setPropertyValue("FillColor",
+ uno::Any(m_xColorScheme->getColorByIndex( nPointIndex )));
+ }
+
+
+ if(bHasFillColorMapping)
+ {
+ double nPropVal = pSeries->getValueByProperty(nPointIndex, "FillColor");
+ if(!std::isnan(nPropVal))
+ {
+ xPointShape->setPropertyValue("FillColor", uno::Any(static_cast<sal_Int32>( nPropVal)));
+ }
+ }
+
+ ///create label
+ createTextLabelShape(xTextTarget, *pSeries, nPointIndex, aParam);
+
+ if(!bDoExplode)
+ {
+ ShapeFactory::setShapeName( xPointShape
+ , ObjectIdentifier::createPointCID( pSeries->getPointCID_Stub(), nPointIndex ) );
+ }
+ else try
+ {
+ ///enable dragging of outer segments
+
+ double fAngle = aParam.mfUnitCircleStartAngleDegree + aParam.mfUnitCircleWidthAngleDegree/2.0;
+ double fMaxDeltaRadius = aParam.mfUnitCircleOuterRadius-aParam.mfUnitCircleInnerRadius;
+ drawing::Position3D aOrigin = m_pPosHelper->transformUnitCircleToScene( fAngle, aParam.mfUnitCircleOuterRadius, aParam.mfLogicZ );
+ drawing::Position3D aNewOrigin = m_pPosHelper->transformUnitCircleToScene( fAngle, aParam.mfUnitCircleOuterRadius + fMaxDeltaRadius, aParam.mfLogicZ );
+
+ sal_Int32 nOffsetPercent( static_cast<sal_Int32>(aParam.mfExplodePercentage * 100.0) );
+
+ awt::Point aMinimumPosition( PlottingPositionHelper::transformSceneToScreenPosition(
+ aOrigin, m_xLogicTarget, m_nDimension ) );
+ awt::Point aMaximumPosition( PlottingPositionHelper::transformSceneToScreenPosition(
+ aNewOrigin, m_xLogicTarget, m_nDimension ) );
+
+ //enable dragging of piesegments
+ OUString aPointCIDStub( ObjectIdentifier::createSeriesSubObjectStub( OBJECTTYPE_DATA_POINT
+ , pSeries->getSeriesParticle()
+ , ObjectIdentifier::getPieSegmentDragMethodServiceName()
+ , ObjectIdentifier::createPieSegmentDragParameterString(
+ nOffsetPercent, aMinimumPosition, aMaximumPosition )
+ ) );
+
+ ShapeFactory::setShapeName( xPointShape
+ , ObjectIdentifier::createPointCID( aPointCIDStub, nPointIndex ) );
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ }//next series in x slot (next y slot)
+ }//next category
+ }//next x slot
+}
+
+PieChart::PieLabelInfo::PieLabelInfo()
+ : fValue(0.0)
+ , bMovementAllowed(false), bMoved(false)
+ , bShowLeaderLine(false), pPrevious(nullptr)
+ , pNext(nullptr)
+{
+}
+
+/** In case this label and the passed label overlap the routine moves this
+ * label in order to fix the issue. After the label position has been
+ * rearranged it is checked that the moved label is still inside the page
+ * document, if the test is positive the routine returns true else returns
+ * false.
+ */
+bool PieChart::PieLabelInfo::moveAwayFrom( const PieChart::PieLabelInfo* pFix, const awt::Size& rPageSize, bool bMoveHalfWay, bool bMoveClockwise )
+{
+ //return true if the move was successful
+ if(!bMovementAllowed)
+ return false;
+
+ const sal_Int32 nLabelDistanceX = rPageSize.Width/50;
+ const sal_Int32 nLabelDistanceY = rPageSize.Height/50;
+
+ ///compute the rectangle representing the intersection of the label bounding
+ ///boxes (`aOverlap`).
+ ::basegfx::B2IRectangle aOverlap( lcl_getRect( xLabelGroupShape ) );
+ aOverlap.intersect( lcl_getRect( pFix->xLabelGroupShape ) );
+ if( aOverlap.isEmpty() )
+ return true;
+
+ //TODO: alternative move direction
+
+ ///the label is shifted along the direction orthogonal to the vector
+ ///starting at the pie/donut center and ending at this label anchor
+ ///point;
+
+ ///named `aTangentialDirection` the unit vector related to such a
+ ///direction, the magnitude of the shift along such a direction is
+ ///calculated in this way: if the horizontal component of
+ ///`aTangentialDirection` is greater than the vertical component,
+ ///the magnitude of the shift is equal to `aOverlap.Width` else to
+ ///`aOverlap.Height`;
+ basegfx::B2IVector aRadiusDirection = aFirstPosition - aOrigin;
+ aRadiusDirection.setLength(1.0);
+ basegfx::B2IVector aTangentialDirection( -aRadiusDirection.getY(), aRadiusDirection.getX() );
+ bool bShiftHorizontal = abs(aTangentialDirection.getX()) > abs(aTangentialDirection.getY());
+ sal_Int32 nShift = bShiftHorizontal ? static_cast<sal_Int32>(aOverlap.getWidth()) : static_cast<sal_Int32>(aOverlap.getHeight());
+ ///the magnitude of the shift is also increased by 1/50-th of the width
+ ///or the height of the document page;
+ nShift += (bShiftHorizontal ? nLabelDistanceX : nLabelDistanceY);
+ ///in case the `bMoveHalfWay` parameter is true the magnitude of
+ ///the shift is halved.
+ if( bMoveHalfWay )
+ nShift/=2;
+ ///in case the `bMoveClockwise` parameter is false the direction of
+ ///`aTangentialDirection` is reversed;
+ if(!bMoveClockwise)
+ nShift*=-1;
+ awt::Point aOldPos( xLabelGroupShape->getPosition() );
+ basegfx::B2IVector aNewPos = basegfx::B2IVector( aOldPos.X, aOldPos.Y ) + nShift*aTangentialDirection;
+
+ ///a final check is performed in order to be sure that the moved label
+ ///is still inside the page document;
+ awt::Point aNewAWTPos( aNewPos.getX(), aNewPos.getY() );
+ if( !lcl_isInsidePage( aNewAWTPos, xLabelGroupShape->getSize(), rPageSize ) )
+ return false;
+
+ xLabelGroupShape->setPosition( aNewAWTPos );
+ bMoved = true;
+
+ return true;
+
+ ///note that no further test is performed in order to check that the
+ ///overlap is really fixed: this result is surely achieved if the shift
+ ///would occur in the horizontal or vertical direction (since, in such a
+ ///direction, the magnitude of the shift would be greater than the length
+ ///of the overlap), but in general this is not true;
+ ///adding a constant term equal to 1/50-th of the width or the height of
+ ///the document page increases the probability of success, anyway it is
+ ///worth noting that the method can return true even if the overlap issue
+ ///is not (completely) fixed;
+}
+
+void PieChart::resetLabelPositionsToPreviousState()
+{
+ for (auto const& labelInfo : m_aLabelInfoList)
+ labelInfo.xLabelGroupShape->setPosition(labelInfo.aPreviousPosition);
+}
+
+bool PieChart::detectLabelOverlapsAndMove( const awt::Size& rPageSize )
+{
+ ///the routine tries to individuate a chain of overlapping labels and
+ ///assigns the first and the last of them to `pFirstBorder` and
+ ///`pSecondBorder`;
+ ///this result is achieved by performing two consecutive while loop.
+
+ ///find borders of a group of overlapping labels
+
+ ///a first while loop is started on the collection of `PieLabelInfo` objects;
+ ///the bounding box of each label is checked for overlap against the bounding
+ ///box of the previous and of the next label;
+ ///when an overlap is found `bOverlapFound` is set to true, however the
+ ///iteration is break only if the overlap occurs against only the next label
+ ///and not against the previous label: so we exit from the loop whenever an
+ ///overlap occurs except when the loop initial label overlaps with the
+ ///previous one;
+ bool bOverlapFound = false;
+ PieLabelInfo* pStart = &(*(m_aLabelInfoList.rbegin()));
+ PieLabelInfo* pFirstBorder = nullptr;
+ PieLabelInfo* pSecondBorder = nullptr;
+ PieLabelInfo* pCurrent = pStart;
+ do
+ {
+ ::basegfx::B2IRectangle aPreviousOverlap( lcl_getRect( pCurrent->xLabelGroupShape ) );
+ ::basegfx::B2IRectangle aNextOverlap( aPreviousOverlap );
+ aPreviousOverlap.intersect( lcl_getRect( pCurrent->pPrevious->xLabelGroupShape ) );
+ aNextOverlap.intersect( lcl_getRect( pCurrent->pNext->xLabelGroupShape ) );
+
+ bool bPreviousOverlap = !aPreviousOverlap.isEmpty();
+ bool bNextOverlap = !aNextOverlap.isEmpty();
+ if( bPreviousOverlap || bNextOverlap )
+ bOverlapFound = true;
+ if( !bPreviousOverlap && bNextOverlap )
+ {
+ pFirstBorder = pCurrent;
+ break;
+ }
+ pCurrent = pCurrent->pNext;
+ }
+ while( pCurrent != pStart );
+
+ if( !bOverlapFound )
+ return false;
+
+ ///in case we found a label (`pFirstBorder`) which overlaps with the next
+ ///label and not with the previous label a second while loop is started with
+ ///`pFirstBorder` as initial label; one more time the bounding box of each
+ ///label is checked for overlap against the bounding box of the previous and
+ ///of the next label, however this time we exit from the loop only if the
+ ///current label overlaps with the previous one but does not with the next
+ ///one (the opposite of what is required in the former loop);
+ ///in case such a label is found it is assigned to `pSecondBorder` and the
+ ///iteration is stopped; so in case there is a chain of overlapping labels
+ ///we end up having the first label of the chain pointed by `pFirstBorder`
+ ///and the last label of the chain pointed by `pSecondBorder`;
+ if( pFirstBorder )
+ {
+ pCurrent = pFirstBorder;
+ do
+ {
+ ::basegfx::B2IRectangle aPreviousOverlap( lcl_getRect( pCurrent->xLabelGroupShape ) );
+ ::basegfx::B2IRectangle aNextOverlap( aPreviousOverlap );
+ aPreviousOverlap.intersect( lcl_getRect( pCurrent->pPrevious->xLabelGroupShape ) );
+ aNextOverlap.intersect( lcl_getRect( pCurrent->pNext->xLabelGroupShape ) );
+
+ if( !aPreviousOverlap.isEmpty() && aNextOverlap.isEmpty() )
+ {
+ pSecondBorder = pCurrent;
+ break;
+ }
+ pCurrent = pCurrent->pNext;
+ }
+ while( pCurrent != pFirstBorder );
+ }
+
+ ///when two labels satisfying the required conditions are not found
+ ///(`pFirstBorder == 0 || pSecondBorder == 0`) but still an overlap occurs
+ ///(`bOverlapFound == true`) we are in the situation where each label
+ ///overlaps with both the previous and the next one; so `pFirstBorder` is
+ ///set to point to the last `PieLabelInfo` object in the collection and
+ ///`pSecondBorder` is set to point to the first one;
+ if( !pFirstBorder || !pSecondBorder )
+ {
+ pFirstBorder = &(*(m_aLabelInfoList.rbegin()));
+ pSecondBorder = &(*(m_aLabelInfoList.begin()));
+ }
+
+ ///the total number of labels that made up the chain is calculated and used
+ ///for getting a pointer to the central label (`pCenter`);
+ PieLabelInfo* pCenter = pFirstBorder;
+ sal_Int32 nOverlapGroupCount = 1;
+ for( pCurrent = pFirstBorder ;pCurrent != pSecondBorder; pCurrent = pCurrent->pNext )
+ nOverlapGroupCount++;
+ sal_Int32 nCenterPos = nOverlapGroupCount/2;
+ bool bSingleCenter = nOverlapGroupCount%2 != 0;
+ if( bSingleCenter )
+ nCenterPos++;
+ if(nCenterPos>1)
+ {
+ pCurrent = pFirstBorder;
+ while( --nCenterPos )
+ pCurrent = pCurrent->pNext;
+ pCenter = pCurrent;
+ }
+
+ ///the current position of each label in the collection is saved in
+ ///`PieLabelInfo.aPreviousPosition`, so that it is possible to undo the label
+ ///move action if it is needed; the undo action is provided by the
+ ///`PieChart::resetLabelPositionsToPreviousState` method.
+ pCurrent = pStart;
+ do
+ {
+ pCurrent->aPreviousPosition = pCurrent->xLabelGroupShape->getPosition();
+ pCurrent = pCurrent->pNext;
+ }
+ while( pCurrent != pStart );
+
+ ///the `PieChart::tryMoveLabels` method is invoked with
+ ///`rbAlternativeMoveDirection` boolean parameter set to false, such a method
+ ///tries to remove all overlaps that occur in the list of labels going from
+ ///`pFirstBorder` to `pSecondBorder`;
+ ///if the `PieChart::tryMoveLabels` returns true no further action is
+ ///performed, however it is worth noting that it does not mean that all
+ ///overlap issues have been surely fixed, but only that all moved labels are
+ ///at least completely inside the page document;
+ ///when `PieChart::tryMoveLabels` returns false, it means that the attempt
+ ///to fix one of the overlap issues caused that a label has been moved
+ ///(partially) outside the page document (anyway the `PieChart::tryMoveLabels`
+ ///method takes care to restore the position of all labels to their initial
+ ///position, and to set the `rbAlternativeMoveDirection` in/out parameter to
+ ///true); in such a case a second invocation of `PieChart::tryMoveLabels` is
+ ///performed (and this time the `rbAlternativeMoveDirection` boolean
+ ///parameter is true) and independently by what the `PieChart::tryMoveLabels`
+ ///method returns no further action is performed;
+ ///(see notes for `PieChart::tryMoveLabels`);
+ bool bAlternativeMoveDirection = false;
+ if( !tryMoveLabels( pFirstBorder, pSecondBorder, pCenter, bSingleCenter, bAlternativeMoveDirection, rPageSize ) )
+ tryMoveLabels( pFirstBorder, pSecondBorder, pCenter, bSingleCenter, bAlternativeMoveDirection, rPageSize );
+
+ ///in both cases (one or two invocations of `PieChart::tryMoveLabels`) the
+ ///`detectLabelOverlapsAndMove` method ends returning true.
+ return true;
+}
+
+
+/** Try to remove all overlaps that occur in the list of labels going from
+ * `pFirstBorder` to `pSecondBorder`
+ */
+bool PieChart::tryMoveLabels( PieLabelInfo const * pFirstBorder, PieLabelInfo const * pSecondBorder
+ , PieLabelInfo* pCenter
+ , bool bSingleCenter, bool& rbAlternativeMoveDirection, const awt::Size& rPageSize )
+{
+
+ PieLabelInfo* p1 = bSingleCenter ? pCenter->pPrevious : pCenter;
+ PieLabelInfo* p2 = pCenter->pNext;
+ //return true when successful
+
+ bool bLabelOrderIsAntiClockWise = m_pPosHelper->isMathematicalOrientationAngle();
+
+ ///two loops are performed simultaneously: the outer loop iterates on
+ ///`PieLabelInfo` objects in the list starting from the central element
+ ///(`pCenter`) and moving forward until the last element (`pSecondBorder`);
+ ///the inner loop starts from the previous element of `pCenter` and moves
+ ///forward until the current `PieLabelInfo` object of the outer loop is
+ ///reached
+ PieLabelInfo* pCurrent = nullptr;
+ for( pCurrent = p2 ;pCurrent->pPrevious != pSecondBorder; pCurrent = pCurrent->pNext )
+ {
+ PieLabelInfo* pFix = nullptr;
+ for( pFix = p2->pPrevious ;pFix != pCurrent; pFix = pFix->pNext )
+ {
+ ///on the current `PieLabelInfo` object of the outer loop the
+ ///`moveAwayFrom` method is invoked by passing the current
+ ///`PieLabelInfo` object of the inner loop as argument.
+
+ ///so each label going from the central one to the last one is
+ ///checked for overlapping against all previous labels (that comes
+ ///after the central label) and in case the overlap occurs the
+ ///`moveAwayFrom` method tries to fix the issue;
+ ///if `moveAwayFrom` returns true (pay attention: that does not
+ ///mean that the overlap issue has been surely fixed but only that
+ ///the moved label is at least completely inside the page document:
+ ///see notes on `PieChart::PieLabelInfo::moveAwayFrom`), the inner
+ ///loop starts a new iteration else the `rbAlternativeMoveDirection`
+ ///boolean parameter is tested: if it is false the parameter is set
+ ///to true, the position of all labels is restored to the initial
+ ///one (through the `PieChart::resetLabelPositionsToPreviousState`
+ ///method) and the method ends by returning false, else the inner
+ ///loop starts a new iteration step;
+ ///so when `rbAlternativeMoveDirection` is true the method goes on
+ ///trying to fix left overlap issues even if the last `moveAwayFrom`
+ ///invocation has moved a label in a position that it is not
+ ///completely inside the page document
+
+ if( !pCurrent->moveAwayFrom( pFix, rPageSize, !bSingleCenter && pCurrent == p2, !bLabelOrderIsAntiClockWise ) )
+ {
+ if( !rbAlternativeMoveDirection )
+ {
+ rbAlternativeMoveDirection = true;
+ resetLabelPositionsToPreviousState();
+ return false;
+ }
+ }
+ }
+ }
+
+ ///if the method does not return before ending the first pair of loops,
+ ///a second pair of simultaneous loops is performed in the opposite
+ ///direction (respect with the previous case): the outer loop iterates on
+ ///`PieLabelInfo` objects in the list starting from the central element
+ ///(`pCenter`) and moving backward until the first element (`pFirstBorder`);
+ ///the inner loop starts from the next element of `pCenter` and moves
+ ///backward until the current `PieLabelInfo` object of the outer loop is
+ ///reached
+
+ ///like in the previous case on the current `PieLabelInfo` object of
+ ///the outer loop the `moveAwayFrom` method is invoked by passing
+ ///the current `PieLabelInfo` object of the inner loop as argument
+
+ ///so each label going from the central one to the first one is checked for
+ ///overlapping on all subsequent labels (that come before the central label)
+ ///and in case the overlap occurs the `moveAwayFrom` method tries to fix
+ ///the issue. The subsequent actions performed after the invocation
+ ///`moveAwayFrom` are the same detailed above for the first pair of loops
+
+ for( pCurrent = p1 ;pCurrent->pNext != pFirstBorder; pCurrent = pCurrent->pPrevious )
+ {
+ PieLabelInfo* pFix = nullptr;
+ for( pFix = p2->pNext ;pFix != pCurrent; pFix = pFix->pPrevious )
+ {
+ if( !pCurrent->moveAwayFrom( pFix, rPageSize, false, bLabelOrderIsAntiClockWise ) )
+ {
+ if( !rbAlternativeMoveDirection )
+ {
+ rbAlternativeMoveDirection = true;
+ resetLabelPositionsToPreviousState();
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+void PieChart::rearrangeLabelToAvoidOverlapIfRequested( const awt::Size& rPageSize )
+{
+ ///this method is invoked by `ChartView::impl_createDiagramAndContent` for
+ ///pie and donut charts after text label creation;
+ ///it tries to rearrange labels only when the label placement type is
+ ///`AVOID_OVERLAP`.
+ // no need to do anything when we only have one label
+ if (m_aLabelInfoList.size() < 2)
+ return;
+
+ ///check whether there are any labels that should be moved
+ bool bMoveableFound = false;
+ for (auto const& labelInfo : m_aLabelInfoList)
+ {
+ if(labelInfo.bMovementAllowed)
+ {
+ bMoveableFound = true;
+ break;
+ }
+ }
+ if(!bMoveableFound)
+ return;
+
+ double fPageDiagonaleLength = std::hypot(rPageSize.Width, rPageSize.Height);
+ if( fPageDiagonaleLength == 0.0 )
+ return;
+
+ ///initialize next and previous member of `PieLabelInfo` objects
+ auto aIt1 = m_aLabelInfoList.begin();
+ auto aEnd = m_aLabelInfoList.end();
+ std::vector< PieLabelInfo >::iterator aIt2 = aIt1;
+ aIt1->pPrevious = &(*(m_aLabelInfoList.rbegin()));
+ ++aIt2;
+ for( ;aIt2!=aEnd; ++aIt1, ++aIt2 )
+ {
+ PieLabelInfo& rInfo1( *aIt1 );
+ PieLabelInfo& rInfo2( *aIt2 );
+ rInfo1.pNext = &rInfo2;
+ rInfo2.pPrevious = &rInfo1;
+ }
+ aIt1->pNext = &(*(m_aLabelInfoList.begin()));
+
+ ///detect overlaps and move
+ sal_Int32 nMaxIterations = 50;
+ while( detectLabelOverlapsAndMove( rPageSize ) && nMaxIterations > 0 )
+ nMaxIterations--;
+
+ ///create connection lines for the moved labels
+ VLineProperties aVLineProperties;
+ for (auto const& labelInfo : m_aLabelInfoList)
+ {
+ if( labelInfo.bMoved && labelInfo.bShowLeaderLine )
+ {
+ sal_Int32 nX1 = labelInfo.aOuterPosition.getX();
+ sal_Int32 nY1 = labelInfo.aOuterPosition.getY();
+ sal_Int32 nX2 = nX1;
+ sal_Int32 nY2 = nY1;
+ ::basegfx::B2IRectangle aRect( lcl_getRect( labelInfo.xLabelGroupShape ) );
+ if( nX1 < aRect.getMinX() )
+ nX2 = aRect.getMinX();
+ else if( nX1 > aRect.getMaxX() )
+ nX2 = aRect.getMaxX();
+
+ if( nY1 < aRect.getMinY() )
+ nY2 = aRect.getMinY();
+ else if( nY1 > aRect.getMaxY() )
+ nY2 = aRect.getMaxY();
+
+ //when the line is very short compared to the page size don't create one
+ ::basegfx::B2DVector aLength(nX1-nX2, nY1-nY2);
+ if( (aLength.getLength()/fPageDiagonaleLength) < 0.01 )
+ continue;
+
+ drawing::PointSequenceSequence aPoints{ { {nX1, nY1}, {nX2, nY2} } };
+
+ if( labelInfo.xTextShape.is() )
+ {
+ sal_Int32 nColor = 0;
+ labelInfo.xTextShape->SvxShape::getPropertyValue("CharColor") >>= nColor;
+ if( nColor != -1 )//automatic font color does not work for lines -> fallback to black
+ aVLineProperties.Color <<= nColor;
+ }
+ ShapeFactory::createLine2D( labelInfo.xTextTarget, aPoints, &aVLineProperties );
+ }
+ }
+}
+
+
+/** Handle the placement of the label in the best fit case:
+ * the routine try to place the label inside the related pie slice,
+ * in case of success it returns true else returns false.
+ *
+ * Notation:
+ * C: the pie center
+ * s: the bisector ray of the current pie slice
+ * alpha: the angle between the horizontal axis and the bisector ray s
+ * N: the vertex of the label b.b. which is nearest to C
+ * F: the vertex of the label b.b. not adjacent to N; F lies on the pie border
+ * P, Q: the intersection points between the label b.b. and the bisector ray s;
+ * P is the one at minimum distance respect with C
+ * e: the edge of the label b.b. where P lies (the nearest edge to C)
+ * M: the vertex of e that is not N
+ * G: the vertex of the label b.b. which is adjacent to N and that is not M
+ * beta: the angle MPF
+ * theta: the angle CPF
+ *
+ *
+ * |
+ * | /s
+ * | /
+ * | /
+ * | G _________________________/____________________________ F
+ * | | /Q ..|
+ * | | / . . |
+ * | | / . . |
+ * | | / . . |
+ * | | / . . |
+ * | | / . . |
+ * | | / d. . |
+ * | | / . . |
+ * | | / . . |
+ * | | / . . |
+ * | | / . . |
+ * | | / . . |
+ * | | / . . |
+ * | | / . \ beta . |
+ * | |__________/._\___|_______.____________________________|
+ * | N /P / . M
+ * | /___/theta .
+ * | / .
+ * | / . r
+ * | / .
+ * | / .
+ * | / .
+ * | / .
+ * | / .
+ * | / .
+ * | / .
+ * | / .
+ * | /\. alpha
+ * __|/__|_____________________________________________________________
+ * |C
+ * |
+ *
+ *
+ * When alpha = 45k (k integer) s crosses the label b.b. at N exactly.
+ * In such a case the nearest edge e is defined as the edge having N as the
+ * start vertex and that is covered in the counterclockwise direction when
+ * we move from N to the adjacent vertex.
+ *
+ * The nearest vertex N is:
+ * 1. the bottom left vertex when 0 < alpha < 90
+ * 2. the bottom right vertex when 90 < alpha < 180
+ * 3. the top right vertex when 180 < alpha < 270
+ * 4. the top left vertex when 270 < alpha < 360.
+ *
+ * The nearest edge e is:
+ * 1. the left edge when −45 < alpha < 45
+ * 2. the bottom edge when 45 < alpha <135
+ * 3. the right edge when 135 < alpha < 225
+ * 4. the top edge when 225 < alpha < 315.
+ *
+ **/
+bool PieChart::performLabelBestFitInnerPlacement(ShapeParam& rShapeParam, PieLabelInfo const & rPieLabelInfo)
+{
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ "** PieChart::performLabelBestFitInnerPlacement invoked **" );
+
+ // get pie slice properties
+ double fStartAngleDeg = NormAngle360(rShapeParam.mfUnitCircleStartAngleDegree);
+ double fWidthAngleDeg = rShapeParam.mfUnitCircleWidthAngleDegree;
+ double fHalfWidthAngleDeg = fWidthAngleDeg / 2.0;
+ double fBisectingRayAngleDeg = NormAngle360(fStartAngleDeg + fHalfWidthAngleDeg);
+
+ // get the middle point of the arc representing the pie slice border
+ double fLogicZ = rShapeParam.mfLogicZ + 1.0;
+ awt::Point aMiddleArcPoint = PlottingPositionHelper::transformSceneToScreenPosition(
+ m_pPosHelper->transformUnitCircleToScene(
+ fBisectingRayAngleDeg,
+ rShapeParam.mfUnitCircleOuterRadius,
+ fLogicZ ),
+ m_xLogicTarget, m_nDimension );
+
+ // compute the pie radius
+ basegfx::B2IVector aPieCenter = rPieLabelInfo.aOrigin;
+ basegfx::B2IVector aRadiusVector(
+ aMiddleArcPoint.X - aPieCenter.getX(),
+ aMiddleArcPoint.Y - aPieCenter.getY() );
+ double fSquaredPieRadius = aRadiusVector.scalar(aRadiusVector);
+ double fPieRadius = sqrt( fSquaredPieRadius );
+
+ // the bb is moved as much as possible near to the border of the pie,
+ // anyway a small offset from the border is present (0.025 * pie radius)
+ const double fPieBorderOffset = 0.025;
+ fPieRadius = fPieRadius - fPieRadius * fPieBorderOffset;
+
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " pie sector:" );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " start angle = " << fStartAngleDeg );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " angle width = " << fWidthAngleDeg );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " bisecting ray angle = " << fBisectingRayAngleDeg );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " pie radius = " << fPieRadius );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " pie center = " << rPieLabelInfo.aOrigin );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " middle arc point = (" << aMiddleArcPoint.X << ","
+ << aMiddleArcPoint.Y << ")" );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " label bounding box:" );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " old anchor point = " << rPieLabelInfo.aFirstPosition );
+
+
+ if( fPieRadius == 0.0 )
+ return false;
+
+ // get label b.b. width and height
+ ::basegfx::B2IRectangle aBb( lcl_getRect( rPieLabelInfo.xLabelGroupShape ) );
+ double fLabelWidth = aBb.getWidth();
+ double fLabelHeight = aBb.getHeight();
+
+ // -45 <= fAlphaDeg < 315
+ double fAlphaDeg = NormAngle360(fBisectingRayAngleDeg + 45) - 45;
+ double fAlphaRad = basegfx::deg2rad(fAlphaDeg);
+
+ // compute nearest edge index
+ // 0 left
+ // 1 bottom
+ // 2 right
+ // 3 top
+ int nSectorIndex = floor( (fAlphaDeg + 45) / 45.0 );
+ int nNearestEdgeIndex = nSectorIndex / 2;
+
+ // compute lengths of the nearest edge and of the orthogonal edges
+ double fNearestEdgeLength = fLabelWidth;
+ double fOrthogonalEdgeLength = fLabelHeight;
+ basegfx::Axis2D eAxis = basegfx::Axis2D::X;
+ basegfx::Axis2D eOrthogonalAxis = basegfx::Axis2D::Y;
+ if( nNearestEdgeIndex % 2 == 0 ) // nearest edge is vertical
+ {
+ fNearestEdgeLength = fLabelHeight;
+ fOrthogonalEdgeLength = fLabelWidth;
+ eAxis = basegfx::Axis2D::Y;
+ eOrthogonalAxis = basegfx::Axis2D::X;
+ }
+
+ // compute the distance between N and P
+ // such a distance is piece wise linear respect with alpha:
+ // given 45k <= alpha < 45(k+1) we have
+ // when k is even: d(N,P) = (length(e) / 2) * (1 - (alpha - 45k)/45)
+ // when k is odd: d(N,P) = (length(e) / 2) * (1 - (45(k+1) - alpha)/45)
+ int nIndex = nSectorIndex -1; // nIndex = -1...6
+ double fIndexMod2 = (nIndex + 8) % 2; // fIndexMod2 must be non negative
+ double fSgn = 2.0 * (fIndexMod2 - 0.5); // 0 -> -1, 1 -> 1
+ double fDistanceNP = (fNearestEdgeLength / 2.0) * (1 + fSgn * ((fAlphaDeg - 45 * (nIndex + fIndexMod2)) / 45.0));
+ double fDistancePM = fNearestEdgeLength - fDistanceNP;
+
+ // compute the length of the diagonal vector d,
+ // that is the distance between P and F
+ double fDistancePF = std::hypot(fDistancePM, fOrthogonalEdgeLength);
+
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " width = " << fLabelWidth );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " height = " << fLabelHeight );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " nearest edge index = " << nNearestEdgeIndex );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " alpha = " << fAlphaDeg );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " distance(N,P) = " << fDistanceNP );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " nIndex = " << nIndex );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " fIndexMod2 = " << fIndexMod2 );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " fSgn = " << fSgn );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " distance(P,F) = " << fDistancePF );
+
+
+ // we check that the condition length(d) <= pie radius holds
+ if (fDistancePF > fPieRadius)
+ {
+ return false;
+ }
+
+ // compute beta: the angle of the diagonal vector d,
+ // that is, the angle in P respect with the triangle PMF;
+ // since both arguments are non negative the returned value is in [0, PI/2]
+ double fBetaRad = atan2( fOrthogonalEdgeLength, fDistancePM );
+
+ // compute the theta angle, that is the angle in P
+ // respect with the triangle CFP;
+ // when the second intersection edge is opposite to the nearest edge,
+ // theta depends on alpha and beta according to the following relation:
+ // theta = f(alpha, beta) = s * alpha + 90 * (1 - s * i) + beta
+ // where i is the nearest edge index and s is the sign of (alpha' - 45),
+ // with alpha' = (alpha + 45) mod 90;
+ // when the second intersection edge is adjacent to the nearest edge,
+ // we have theta = 360 - f(alpha, beta);
+ // note that in the former case 0 <= f(alpha, beta) <= 180,
+ // whilst in the latter case 180 <= f(alpha, beta) <= 360;
+ double fAlphaMod90 = fmod( fAlphaDeg + 45, 90.0 ) - 45;
+ double fSign = fAlphaMod90 == 0.0
+ ? 0.0
+ : ( fAlphaMod90 < 0 ) ? -1.0 : 1.0;
+ double fThetaRad = fSign * fAlphaRad + M_PI_2 * (1 - fSign * nNearestEdgeIndex) + fBetaRad;
+ if( fThetaRad > M_PI )
+ {
+ fThetaRad = 2 * M_PI - fThetaRad;
+ }
+
+ // compute the length of the positional vector,
+ // that is the distance between C and P
+ double fDistanceCP;
+ // when the bisector ray intersects the b.b. in F we have theta mod 180 == 0
+ if( fmod(fThetaRad, M_PI) == 0.0 )
+ {
+ fDistanceCP = fPieRadius - fDistancePF;
+ }
+ else // general case
+ {
+ // we can compute d(C,P) by applying some trigonometric formula to
+ // the triangle CFP : we know length(d) and length(r) = r and we have
+ // computed the angle in P (theta); so named delta the angle in C and
+ // gamma the angle in F, by the relation:
+ //
+ // r d(P,F) d(C,P)
+ // --------- = --------- = ---------
+ // sin theta sin delta sin gamma
+ //
+ // we get the wanted distance
+ double fSinTheta = sin( fThetaRad );
+ double fSinDelta = fDistancePF * fSinTheta / fPieRadius;
+ double fDeltaRad = asin( fSinDelta );
+ double fGammaRad = M_PI - (fThetaRad + fDeltaRad);
+ double fSinGamma = sin( fGammaRad );
+ fDistanceCP = fPieRadius * fSinGamma / fSinTheta;
+ }
+
+ // define the positional vector
+ basegfx::B2DVector aPositionalVector( cos(fAlphaRad), sin(fAlphaRad) );
+ aPositionalVector.setLength(fDistanceCP);
+
+ // we define a direction vector in order to know
+ // in which quadrant we are working
+ basegfx::B2DVector aDirection(1.0, 1.0);
+ if( 90 <= fBisectingRayAngleDeg && fBisectingRayAngleDeg < 270 )
+ {
+ aDirection.setX(-1.0);
+ }
+ if( fBisectingRayAngleDeg >= 180 )
+ {
+ aDirection.setY(-1.0);
+ }
+
+ // compute vertices N, M and G respect with pie center C
+ basegfx::B2DVector aNearestVertex(aPositionalVector);
+ aNearestVertex.set(eAxis, aNearestVertex.get(eAxis) - aDirection.get(eAxis) * fDistanceNP);
+ basegfx::B2DVector aVertexM(aNearestVertex);
+ aVertexM.set(eAxis, aVertexM.get(eAxis) + aDirection.get(eAxis) * fNearestEdgeLength);
+ basegfx::B2DVector aVertexG(aNearestVertex);
+ aVertexG.set(eOrthogonalAxis, aVertexG.get(eOrthogonalAxis) + aDirection.get(eOrthogonalAxis) * fOrthogonalEdgeLength);
+
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " beta = " << basegfx::rad2deg(fBetaRad) );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " theta = " << basegfx::rad2deg(fThetaRad) );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " fAlphaMod90 = " << fAlphaMod90 );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " fSign = " << fSign );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " distance(C,P) = " << fDistanceCP );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " direction vector = " << aDirection );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " N = " << aNearestVertex );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " M = " << aVertexM );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " G = " << aVertexG );
+
+ // in order to be able to place the label inside the pie slice we need
+ // to check that each angle between s and the ray starting from C and
+ // passing through a b.b. vertex is less than half width of the pie slice;
+ // when the nearest edge e crosses a Cartesian axis it is sufficient
+ // to test only the vertices belonging to e, else we need to test
+ // the 2 vertices that aren't either N or F. Note that if a b.b. edge
+ // crosses a Cartesian axis then it is the nearest edge to C
+
+ // check the angle between CP and CM
+ double fAngleRad = aPositionalVector.angle(aVertexM);
+ double fAngleDeg = NormAngle360(basegfx::rad2deg(fAngleRad));
+ if( fAngleDeg > 180 ) // in case the wrong angle has been computed
+ fAngleDeg = 360 - fAngleDeg;
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " angle between CP and CM: " << fAngleDeg );
+ if( fAngleDeg > fHalfWidthAngleDeg )
+ {
+ return false;
+ }
+
+ if( ( aNearestVertex.get(eAxis) >= 0 && aVertexM.get(eAxis) <= 0 )
+ || ( aNearestVertex.get(eAxis) <= 0 && aVertexM.get(eAxis) >= 0 ) )
+ {
+ // check the angle between CP and CN
+ fAngleRad = aPositionalVector.angle(aNearestVertex);
+ fAngleDeg = NormAngle360(basegfx::rad2deg(fAngleRad));
+ if( fAngleDeg > 180 ) // in case the wrong angle has been computed
+ fAngleDeg = 360 - fAngleDeg;
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " angle between CP and CN: " << fAngleDeg );
+ if( fAngleDeg > fHalfWidthAngleDeg )
+ {
+ return false;
+ }
+ }
+ else
+ {
+ // check the angle between CP and CG
+ fAngleRad = aPositionalVector.angle(aVertexG);
+ fAngleDeg = NormAngle360(basegfx::rad2deg(fAngleRad));
+ if( fAngleDeg > 180 ) // in case the wrong angle has been computed
+ fAngleDeg = 360 - fAngleDeg;
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " angle between CP and CG: " << fAngleDeg );
+ if( fAngleDeg > fHalfWidthAngleDeg )
+ {
+ return false;
+ }
+ }
+
+ // compute the b.b. center respect with the pie center
+ basegfx::B2DVector aBBCenter(aNearestVertex);
+ aBBCenter.set(eAxis, aBBCenter.get(eAxis) + aDirection.get(eAxis) * fNearestEdgeLength / 2);
+ aBBCenter.set(eOrthogonalAxis, aBBCenter.get(eOrthogonalAxis) + aDirection.get(eOrthogonalAxis) * fOrthogonalEdgeLength / 2);
+
+ // compute the b.b. anchor point
+ basegfx::B2IVector aNewAnchorPoint = aPieCenter;
+ aNewAnchorPoint.setX(aNewAnchorPoint.getX() + floor(aBBCenter.getX()));
+ aNewAnchorPoint.setY(aNewAnchorPoint.getY() - floor(aBBCenter.getY())); // the Y axis on the screen points downward
+
+ // compute the translation vector for moving the label from the current
+ // screen position to the new one
+ basegfx::B2IVector aTranslationVector = aNewAnchorPoint - rPieLabelInfo.aFirstPosition;
+
+ // compute the new screen position and move the label
+ // XShape::getPosition returns the top left vertex of the b.b. of the shape
+ awt::Point aOldPos( rPieLabelInfo.xLabelGroupShape->getPosition() );
+ awt::Point aNewPos( aOldPos.X + aTranslationVector.getX(),
+ aOldPos.Y + aTranslationVector.getY() );
+ rPieLabelInfo.xLabelGroupShape->setPosition(aNewPos);
+
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " center = " << aBBCenter );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " new anchor point = " << aNewAnchorPoint );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " translation vector = " << aTranslationVector );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " old position = (" << aOldPos.X << "," << aOldPos.Y << ")" );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " new position = (" << aNewPos.X << "," << aNewPos.Y << ")" );
+
+ return true;
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/PieChart.hxx b/chart2/source/view/charttypes/PieChart.hxx
new file mode 100644
index 000000000..9a5b7fb4c
--- /dev/null
+++ b/chart2/source/view/charttypes/PieChart.hxx
@@ -0,0 +1,145 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <memory>
+#include <VSeriesPlotter.hxx>
+#include <basegfx/vector/b2ivector.hxx>
+#include <com/sun/star/awt/Point.hpp>
+
+namespace chart
+{
+class PiePositionHelper;
+
+class PieChart : public VSeriesPlotter
+{
+ struct ShapeParam;
+
+public:
+ PieChart() = delete;
+
+ PieChart( const rtl::Reference< ::chart::ChartType >& xChartTypeModel
+ , sal_Int32 nDimensionCount, bool bExcludingPositioning );
+ virtual ~PieChart() override;
+
+ /** This method creates all shapes needed for representing the pie chart.
+ */
+ virtual void createShapes() override;
+ virtual void rearrangeLabelToAvoidOverlapIfRequested( const css::awt::Size& rPageSize ) override;
+
+ virtual void setScales( std::vector< ExplicitScaleData >&& rScales, bool bSwapXAndYAxis ) override;
+ virtual void addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 zSlot, sal_Int32 xSlot, sal_Int32 ySlot ) override;
+
+ virtual css::drawing::Direction3D getPreferredDiagramAspectRatio() const override;
+ virtual bool shouldSnapRectToUsedArea() override;
+
+ //MinimumAndMaximumSupplier
+ virtual double getMinimumX() override;
+ virtual double getMaximumX() override;
+ virtual double getMinimumYInRange( double fMinimumX, double fMaximumX, sal_Int32 nAxisIndex ) override;
+ virtual double getMaximumYInRange( double fMinimumX, double fMaximumX, sal_Int32 nAxisIndex ) override;
+
+ virtual bool isExpandBorderToIncrementRhythm( sal_Int32 nDimensionIndex ) override;
+ virtual bool isExpandIfValuesCloseToBorder( sal_Int32 nDimensionIndex ) override;
+ virtual bool isExpandWideValuesToZero( sal_Int32 nDimensionIndex ) override;
+ virtual bool isExpandNarrowValuesTowardZero( sal_Int32 nDimensionIndex ) override;
+ virtual bool isSeparateStackingForDifferentSigns( sal_Int32 nDimensionIndex ) override;
+
+private: //methods
+ rtl::Reference<SvxShape>
+ createDataPoint(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget,
+ const css::uno::Reference<css::beans::XPropertySet>& xObjectProperties,
+ const ShapeParam& rParam,
+ const sal_Int32 nPointCount,
+ const bool bConcentricExplosion);
+
+ /** This method creates a text shape for a label of a data point.
+ *
+ * @param xTextTarget
+ * where to append the new created text shape.
+ * @param rSeries
+ * the data series, the data point belongs to.
+ * @param nPointIndex
+ * the index of the data point the label is related to.
+ * @param rParam
+ * ShapeParam object.
+ */
+ void createTextLabelShape(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTextTarget,
+ VDataSeries& rSeries, sal_Int32 nPointIndex, ShapeParam& rParam );
+
+ /** This method sets `m_fMaxOffset` to the maximum `Offset` property and
+ * returns it. There is a `Offset` property for each entry in a data
+ * series, moreover there exists a shared `Offset` property attached to
+ * the whole data series. The `Offset` property represents the
+ * relative distance offset of a slice from the pie center.
+ * The shared property is used for exploded pie chart, while the property
+ * attached to single data series entries is used for manual dragging of
+ * a slice.
+ * `m_fMaxOffset` is used by `PiePositionHelper::getInnerAndOuterRadius`.
+ * Note that only the `Offset` properties of the first (x slot) data series
+ * and its entries are utilized for computing the maximum offset.
+ */
+ double getMaxOffset();
+ bool detectLabelOverlapsAndMove(const css::awt::Size& rPageSize);//returns true when there might be more to do
+ void resetLabelPositionsToPreviousState();
+struct PieLabelInfo;
+ bool tryMoveLabels( PieLabelInfo const * pFirstBorder, PieLabelInfo const * pSecondBorder
+ , PieLabelInfo* pCenter, bool bSingleCenter, bool& rbAlternativeMoveDirection
+ , const css::awt::Size& rPageSize );
+
+ bool performLabelBestFitInnerPlacement( ShapeParam& rShapeParam
+ , PieLabelInfo const & rPieLabelInfo );
+
+private: //member
+ std::unique_ptr<PiePositionHelper>
+ m_pPosHelper;
+ bool m_bUseRings;
+ bool m_bSizeExcludesLabelsAndExplodedSegments;
+
+ struct PieLabelInfo
+ {
+ PieLabelInfo();
+ bool moveAwayFrom( const PieLabelInfo* pFix, const css::awt::Size& rPageSize
+ , bool bMoveHalfWay, bool bMoveClockwise );
+
+ rtl::Reference< SvxShapeText > xTextShape;
+ rtl::Reference< SvxShapeGroupAnyD > xLabelGroupShape;
+ ::basegfx::B2IVector aFirstPosition;
+ ::basegfx::B2IVector aOuterPosition;
+ ::basegfx::B2IVector aOrigin;
+ double fValue;
+ bool bMovementAllowed;
+ bool bMoved;
+ bool bShowLeaderLine;
+ rtl::Reference<SvxShapeGroupAnyD> xTextTarget;
+ PieLabelInfo* pPrevious;
+ PieLabelInfo* pNext;
+ css::awt::Point aPreviousPosition;
+ };
+
+ std::vector< PieLabelInfo > m_aLabelInfoList;
+
+ double m_fMaxOffset; /// cached max offset value (init'ed to NaN)
+};
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/Splines.cxx b/chart2/source/view/charttypes/Splines.cxx
new file mode 100644
index 000000000..2aac96929
--- /dev/null
+++ b/chart2/source/view/charttypes/Splines.cxx
@@ -0,0 +1,872 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "Splines.hxx"
+#include <osl/diagnose.h>
+#include <com/sun/star/drawing/Position3D.hpp>
+
+#include <vector>
+#include <algorithm>
+#include <memory>
+#include <cmath>
+#include <limits>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+
+namespace
+{
+
+typedef std::pair< double, double > tPointType;
+typedef std::vector< tPointType > tPointVecType;
+typedef tPointVecType::size_type lcl_tSizeType;
+
+class lcl_SplineCalculation
+{
+public:
+ /** @descr creates an object that calculates cubic splines on construction
+
+ @param rSortedPoints the points for which splines shall be calculated, they need to be sorted in x values
+ @param fY1FirstDerivation the resulting spline should have the first
+ derivation equal to this value at the x-value of the first point
+ of rSortedPoints. If fY1FirstDerivation is set to infinity, a natural
+ spline is calculated.
+ @param fYnFirstDerivation the resulting spline should have the first
+ derivation equal to this value at the x-value of the last point
+ of rSortedPoints
+ */
+ lcl_SplineCalculation( tPointVecType && rSortedPoints,
+ double fY1FirstDerivation,
+ double fYnFirstDerivation );
+
+ /** @descr creates an object that calculates cubic splines on construction
+ for the special case of periodic cubic spline
+
+ @param rSortedPoints the points for which splines shall be calculated,
+ they need to be sorted in x values. First and last y value must be equal
+ */
+ explicit lcl_SplineCalculation( tPointVecType && rSortedPoints);
+
+ /** @descr this function corresponds to the function splint in [1].
+
+ [1] Numerical Recipes in C, 2nd edition
+ William H. Press, et al.,
+ Section 3.3, page 116
+ */
+ double GetInterpolatedValue( double x );
+
+private:
+ /// a copy of the points given in the CTOR
+ tPointVecType m_aPoints;
+
+ /// the result of the Calculate() method
+ std::vector< double > m_aSecDerivY;
+
+ double m_fYp1;
+ double m_fYpN;
+
+ // these values are cached for performance reasons
+ lcl_tSizeType m_nKLow;
+ lcl_tSizeType m_nKHigh;
+ double m_fLastInterpolatedValue;
+
+ /** @descr this function corresponds to the function spline in [1].
+
+ [1] Numerical Recipes in C, 2nd edition
+ William H. Press, et al.,
+ Section 3.3, page 115
+ */
+ void Calculate();
+
+ /** @descr this function corresponds to the algorithm 4.76 in [2] and
+ theorem 5.3.7 in [3]
+
+ [2] Engeln-Müllges, Gisela: Numerik-Algorithmen: Verfahren, Beispiele, Anwendungen
+ Springer, Berlin; Auflage: 9., überarb. und erw. A. (8. Dezember 2004)
+ Section 4.10.2, page 175
+
+ [3] Hanrath, Wilhelm: Mathematik III / Numerik, Vorlesungsskript zur
+ Veranstaltung im WS 2007/2008
+ Fachhochschule Aachen, 2009-09-19
+ Numerik_01.pdf, downloaded 2011-04-19 via
+ http://www.fh-aachen.de/index.php?id=11424&no_cache=1&file=5016&uid=44191
+ Section 5.3, page 129
+ */
+ void CalculatePeriodic();
+};
+
+lcl_SplineCalculation::lcl_SplineCalculation(
+ tPointVecType && rSortedPoints,
+ double fY1FirstDerivation,
+ double fYnFirstDerivation )
+ : m_aPoints( std::move(rSortedPoints) ),
+ m_fYp1( fY1FirstDerivation ),
+ m_fYpN( fYnFirstDerivation ),
+ m_nKLow( 0 ),
+ m_nKHigh( m_aPoints.size() - 1 ),
+ m_fLastInterpolatedValue(std::numeric_limits<double>::infinity())
+{
+ Calculate();
+}
+
+lcl_SplineCalculation::lcl_SplineCalculation(
+ tPointVecType && rSortedPoints)
+ : m_aPoints( std::move(rSortedPoints) ),
+ m_fYp1( 0.0 ), /*dummy*/
+ m_fYpN( 0.0 ), /*dummy*/
+ m_nKLow( 0 ),
+ m_nKHigh( m_aPoints.size() - 1 ),
+ m_fLastInterpolatedValue(std::numeric_limits<double>::infinity())
+{
+ CalculatePeriodic();
+}
+
+void lcl_SplineCalculation::Calculate()
+{
+ if( m_aPoints.size() <= 1 )
+ return;
+
+ // n is the last valid index to m_aPoints
+ const lcl_tSizeType n = m_aPoints.size() - 1;
+ std::vector< double > u( n );
+ m_aSecDerivY.resize( n + 1, 0.0 );
+
+ if( std::isinf( m_fYp1 ) )
+ {
+ // natural spline
+ m_aSecDerivY[ 0 ] = 0.0;
+ u[ 0 ] = 0.0;
+ }
+ else
+ {
+ m_aSecDerivY[ 0 ] = -0.5;
+ double xDiff = m_aPoints[ 1 ].first - m_aPoints[ 0 ].first;
+ u[ 0 ] = ( 3.0 / xDiff ) *
+ ((( m_aPoints[ 1 ].second - m_aPoints[ 0 ].second ) / xDiff ) - m_fYp1 );
+ }
+
+ for( lcl_tSizeType i = 1; i < n; ++i )
+ {
+ tPointType
+ p_i = m_aPoints[ i ],
+ p_im1 = m_aPoints[ i - 1 ],
+ p_ip1 = m_aPoints[ i + 1 ];
+
+ double sig = ( p_i.first - p_im1.first ) /
+ ( p_ip1.first - p_im1.first );
+ double p = sig * m_aSecDerivY[ i - 1 ] + 2.0;
+
+ m_aSecDerivY[ i ] = ( sig - 1.0 ) / p;
+ u[ i ] =
+ ( ( p_ip1.second - p_i.second ) /
+ ( p_ip1.first - p_i.first ) ) -
+ ( ( p_i.second - p_im1.second ) /
+ ( p_i.first - p_im1.first ) );
+ u[ i ] =
+ ( 6.0 * u[ i ] / ( p_ip1.first - p_im1.first )
+ - sig * u[ i - 1 ] ) / p;
+ }
+
+ // initialize to values for natural splines (used for m_fYpN equal to
+ // infinity)
+ double qn = 0.0;
+ double un = 0.0;
+
+ if( ! std::isinf( m_fYpN ) )
+ {
+ qn = 0.5;
+ double xDiff = m_aPoints[ n ].first - m_aPoints[ n - 1 ].first;
+ un = ( 3.0 / xDiff ) *
+ ( m_fYpN - ( m_aPoints[ n ].second - m_aPoints[ n - 1 ].second ) / xDiff );
+ }
+
+ m_aSecDerivY[ n ] = ( un - qn * u[ n - 1 ] ) / ( qn * m_aSecDerivY[ n - 1 ] + 1.0 );
+
+ // note: the algorithm in [1] iterates from n-1 to 0, but as size_type
+ // may be (usually is) an unsigned type, we can not write k >= 0, as this
+ // is always true.
+ for( lcl_tSizeType k = n; k > 0; --k )
+ {
+ m_aSecDerivY[ k - 1 ] = (m_aSecDerivY[ k - 1 ] * m_aSecDerivY[ k ] ) + u[ k - 1 ];
+ }
+}
+
+void lcl_SplineCalculation::CalculatePeriodic()
+{
+ if( m_aPoints.size() <= 1 )
+ return;
+
+ // n is the last valid index to m_aPoints
+ const lcl_tSizeType n = m_aPoints.size() - 1;
+
+ // u is used for vector f in A*c=f in [3], vector a in Ax=a in [2],
+ // vector z in Rtranspose z = a and Dr=z in [2]
+ std::vector< double > u( n + 1, 0.0 );
+
+ // used for vector c in A*c=f and vector x in Ax=a in [2]
+ m_aSecDerivY.resize( n + 1, 0.0 );
+
+ // diagonal of matrix A, used index 1 to n
+ std::vector< double > Adiag( n + 1, 0.0 );
+
+ // secondary diagonal of matrix A with index 1 to n-1 and upper right element in A[n]
+ std::vector< double > Aupper( n + 1, 0.0 );
+
+ // diagonal of matrix D in A=(R transpose)*D*R in [2], used index 1 to n
+ std::vector< double > Ddiag( n+1, 0.0 );
+
+ // right column of matrix R, used index 1 to n-2
+ std::vector< double > Rright( n-1, 0.0 );
+
+ // secondary diagonal of matrix R, used index 1 to n-1
+ std::vector< double > Rupper( n, 0.0 );
+
+ if (n<4)
+ {
+ if (n==3)
+ { // special handling of three polynomials, that are four points
+ double xDiff0 = m_aPoints[ 1 ].first - m_aPoints[ 0 ].first ;
+ double xDiff1 = m_aPoints[ 2 ].first - m_aPoints[ 1 ].first ;
+ double xDiff2 = m_aPoints[ 3 ].first - m_aPoints[ 2 ].first ;
+ double xDiff2p1 = xDiff2 + xDiff1;
+ double xDiff0p2 = xDiff0 + xDiff2;
+ double xDiff1p0 = xDiff1 + xDiff0;
+ double fFactor = 1.5 / (xDiff0*xDiff1 + xDiff1*xDiff2 + xDiff2*xDiff0);
+ double yDiff0 = (m_aPoints[ 1 ].second - m_aPoints[ 0 ].second) / xDiff0;
+ double yDiff1 = (m_aPoints[ 2 ].second - m_aPoints[ 1 ].second) / xDiff1;
+ double yDiff2 = (m_aPoints[ 0 ].second - m_aPoints[ 2 ].second) / xDiff2;
+ m_aSecDerivY[ 1 ] = fFactor * (yDiff1*xDiff2p1 - yDiff0*xDiff0p2);
+ m_aSecDerivY[ 2 ] = fFactor * (yDiff2*xDiff0p2 - yDiff1*xDiff1p0);
+ m_aSecDerivY[ 3 ] = fFactor * (yDiff0*xDiff1p0 - yDiff2*xDiff2p1);
+ m_aSecDerivY[ 0 ] = m_aSecDerivY[ 3 ];
+ }
+ else if (n==2)
+ {
+ // special handling of two polynomials, that are three points
+ double xDiff0 = m_aPoints[ 1 ].first - m_aPoints[ 0 ].first;
+ double xDiff1 = m_aPoints[ 2 ].first - m_aPoints[ 1 ].first;
+ double fHelp = 3.0 * (m_aPoints[ 0 ].second - m_aPoints[ 1 ].second) / (xDiff0*xDiff1);
+ m_aSecDerivY[ 1 ] = fHelp ;
+ m_aSecDerivY[ 2 ] = -fHelp ;
+ m_aSecDerivY[ 0 ] = m_aSecDerivY[ 2 ] ;
+ }
+ else
+ {
+ // should be handled with natural spline, periodic not possible.
+ }
+ }
+ else
+ {
+ double xDiff_i =1.0; // values are dummy;
+ double xDiff_im1 =1.0;
+ double yDiff_i = 1.0;
+ double yDiff_im1 = 1.0;
+ // fill matrix A and fill right side vector u
+ for( lcl_tSizeType i=1; i<n; ++i )
+ {
+ xDiff_im1 = m_aPoints[ i ].first - m_aPoints[ i-1 ].first;
+ xDiff_i = m_aPoints[ i+1 ].first - m_aPoints[ i ].first;
+ yDiff_im1 = (m_aPoints[ i ].second - m_aPoints[ i-1 ].second) / xDiff_im1;
+ yDiff_i = (m_aPoints[ i+1 ].second - m_aPoints[ i ].second) / xDiff_i;
+ Adiag[ i ] = 2 * (xDiff_im1 + xDiff_i);
+ Aupper[ i ] = xDiff_i;
+ u [ i ] = 3 * (yDiff_i - yDiff_im1);
+ }
+ xDiff_im1 = m_aPoints[ n ].first - m_aPoints[ n-1 ].first;
+ xDiff_i = m_aPoints[ 1 ].first - m_aPoints[ 0 ].first;
+ yDiff_im1 = (m_aPoints[ n ].second - m_aPoints[ n-1 ].second) / xDiff_im1;
+ yDiff_i = (m_aPoints[ 1 ].second - m_aPoints[ 0 ].second) / xDiff_i;
+ Adiag[ n ] = 2 * (xDiff_im1 + xDiff_i);
+ Aupper[ n ] = xDiff_i;
+ u [ n ] = 3 * (yDiff_i - yDiff_im1);
+
+ // decomposite A=(R transpose)*D*R
+ Ddiag[1] = Adiag[1];
+ Rupper[1] = Aupper[1] / Ddiag[1];
+ Rright[1] = Aupper[n] / Ddiag[1];
+ for( lcl_tSizeType i=2; i<=n-2; ++i )
+ {
+ Ddiag[i] = Adiag[i] - Aupper[ i-1 ] * Rupper[ i-1 ];
+ Rupper[ i ] = Aupper[ i ] / Ddiag[ i ];
+ Rright[ i ] = - Rright[ i-1 ] * Aupper[ i-1 ] / Ddiag[ i ];
+ }
+ Ddiag[ n-1 ] = Adiag[ n-1 ] - Aupper[ n-2 ] * Rupper[ n-2 ];
+ Rupper[ n-1 ] = ( Aupper[ n-1 ] - Aupper[ n-2 ] * Rright[ n-2] ) / Ddiag[ n-1 ];
+ double fSum = 0.0;
+ for ( lcl_tSizeType i=1; i<=n-2; ++i )
+ {
+ fSum += Ddiag[ i ] * Rright[ i ] * Rright[ i ];
+ }
+ Ddiag[ n ] = Adiag[ n ] - fSum - Ddiag[ n-1 ] * Rupper[ n-1 ] * Rupper[ n-1 ]; // bug in [2]!
+
+ // solve forward (R transpose)*z=u, overwrite u with z
+ for ( lcl_tSizeType i=2; i<=n-1; ++i )
+ {
+ u[ i ] -= u[ i-1 ]* Rupper[ i-1 ];
+ }
+ fSum = 0.0;
+ for ( lcl_tSizeType i=1; i<=n-2; ++i )
+ {
+ fSum += Rright[ i ] * u[ i ];
+ }
+ u[ n ] = u[ n ] - fSum - Rupper[ n - 1] * u[ n-1 ];
+
+ // solve forward D*r=z, z is in u, overwrite u with r
+ for ( lcl_tSizeType i=1; i<=n; ++i )
+ {
+ u[ i ] = u[i] / Ddiag[ i ];
+ }
+
+ // solve backward R*x= r, r is in u
+ m_aSecDerivY[ n ] = u[ n ];
+ m_aSecDerivY[ n-1 ] = u[ n-1 ] - Rupper[ n-1 ] * m_aSecDerivY[ n ];
+ for ( lcl_tSizeType i=n-2; i>=1; --i)
+ {
+ m_aSecDerivY[ i ] = u[ i ] - Rupper[ i ] * m_aSecDerivY[ i+1 ] - Rright[ i ] * m_aSecDerivY[ n ];
+ }
+ // periodic
+ m_aSecDerivY[ 0 ] = m_aSecDerivY[ n ];
+ }
+
+ // adapt m_aSecDerivY for usage in GetInterpolatedValue()
+ for( lcl_tSizeType i = 0; i <= n ; ++i )
+ {
+ m_aSecDerivY[ i ] *= 2.0;
+ }
+
+}
+
+double lcl_SplineCalculation::GetInterpolatedValue( double x )
+{
+ OSL_PRECOND( ( m_aPoints[ 0 ].first <= x ) &&
+ ( x <= m_aPoints[ m_aPoints.size() - 1 ].first ),
+ "Trying to extrapolate" );
+
+ const lcl_tSizeType n = m_aPoints.size() - 1;
+ if( x < m_fLastInterpolatedValue )
+ {
+ m_nKLow = 0;
+ m_nKHigh = n;
+
+ // calculate m_nKLow and m_nKHigh
+ // first initialization is done in CTOR
+ while( m_nKHigh - m_nKLow > 1 )
+ {
+ lcl_tSizeType k = ( m_nKHigh + m_nKLow ) / 2;
+ if( m_aPoints[ k ].first > x )
+ m_nKHigh = k;
+ else
+ m_nKLow = k;
+ }
+ }
+ else
+ {
+ while( ( m_nKHigh <= n ) &&
+ ( m_aPoints[ m_nKHigh ].first < x ) )
+ {
+ ++m_nKHigh;
+ ++m_nKLow;
+ }
+ OSL_ENSURE( m_nKHigh <= n, "Out of Bounds" );
+ }
+ m_fLastInterpolatedValue = x;
+
+ double h = m_aPoints[ m_nKHigh ].first - m_aPoints[ m_nKLow ].first;
+ OSL_ENSURE( h != 0, "Bad input to GetInterpolatedValue()" );
+
+ double a = ( m_aPoints[ m_nKHigh ].first - x ) / h;
+ double b = ( x - m_aPoints[ m_nKLow ].first ) / h;
+
+ return ( a * m_aPoints[ m_nKLow ].second +
+ b * m_aPoints[ m_nKHigh ].second +
+ (( a*a*a - a ) * m_aSecDerivY[ m_nKLow ] +
+ ( b*b*b - b ) * m_aSecDerivY[ m_nKHigh ] ) *
+ ( h*h ) / 6.0 );
+}
+
+// helper methods for B-spline
+
+// Create parameter t_0 to t_n using the centripetal method with a power of 0.5
+bool createParameterT(const tPointVecType& rUniquePoints, double* t)
+{ // precondition: no adjacent identical points
+ // postcondition: 0 = t_0 < t_1 < ... < t_n = 1
+ bool bIsSuccessful = true;
+ const lcl_tSizeType n = rUniquePoints.size() - 1;
+ t[0]=0.0;
+ double fDenominator = 0.0; // initialized for summing up
+ for (lcl_tSizeType i=1; i<=n ; ++i)
+ { // 4th root(dx^2+dy^2)
+ double dx = rUniquePoints[i].first - rUniquePoints[i-1].first;
+ double dy = rUniquePoints[i].second - rUniquePoints[i-1].second;
+ if (dx == 0 && dy == 0)
+ {
+ bIsSuccessful = false;
+ break;
+ }
+ else
+ {
+ fDenominator += sqrt(std::hypot(dx, dy));
+ }
+ }
+ if (fDenominator == 0.0)
+ {
+ bIsSuccessful = false;
+ }
+ if (bIsSuccessful)
+ {
+ for (lcl_tSizeType j=1; j<=n ; ++j)
+ {
+ double fNumerator = 0.0;
+ for (lcl_tSizeType i=1; i<=j ; ++i)
+ {
+ double dx = rUniquePoints[i].first - rUniquePoints[i-1].first;
+ double dy = rUniquePoints[i].second - rUniquePoints[i-1].second;
+ fNumerator += sqrt(std::hypot(dx, dy));
+ }
+ t[j] = fNumerator / fDenominator;
+
+ }
+ // postcondition check
+ t[n] = 1.0;
+ double fPrevious = 0.0;
+ for (lcl_tSizeType i=1; i <= n && bIsSuccessful ; ++i)
+ {
+ if (fPrevious >= t[i])
+ {
+ bIsSuccessful = false;
+ }
+ else
+ {
+ fPrevious = t[i];
+ }
+ }
+ }
+ return bIsSuccessful;
+}
+
+void createKnotVector(const lcl_tSizeType n, const sal_uInt32 p, const double* t, double* u)
+{ // precondition: 0 = t_0 < t_1 < ... < t_n = 1
+ for (lcl_tSizeType j = 0; j <= p; ++j)
+ {
+ u[j] = 0.0;
+ }
+ for (lcl_tSizeType j = 1; j <= n-p; ++j )
+ {
+ double fSum = 0.0;
+ for (lcl_tSizeType i = j; i <= j+p-1; ++i)
+ {
+ fSum += t[i];
+ }
+ assert(p != 0);
+ u[j+p] = fSum / p ;
+ }
+ for (lcl_tSizeType j = n+1; j <= n+1+p; ++j)
+ {
+ u[j] = 1.0;
+ }
+}
+
+void applyNtoParameterT(const lcl_tSizeType i,const double tk,const sal_uInt32 p,const double* u, double* rowN)
+{
+ // get N_p(t_k) recursively, only N_(i-p) till N_(i) are relevant, all other N_# are zero
+
+ // initialize with indicator function degree 0
+ rowN[p] = 1.0; // all others are zero
+
+ // calculate up to degree p
+ for (sal_uInt32 s = 1; s <= p; ++s)
+ {
+ // first element
+ double fLeftFactor = 0.0;
+ double fRightFactor = ( u[i+1] - tk ) / ( u[i+1]- u[i-s+1] );
+ // i-s "true index" - (i-p)"shift" = p-s
+ rowN[p-s] = fRightFactor * rowN[p-s+1];
+
+ // middle elements
+ for (sal_uInt32 j = s-1; j>=1 ; --j)
+ {
+ fLeftFactor = ( tk - u[i-j] ) / ( u[i-j+s] - u[i-j] ) ;
+ fRightFactor = ( u[i-j+s+1] - tk ) / ( u[i-j+s+1] - u[i-j+1] );
+ // i-j "true index" - (i-p)"shift" = p-j
+ rowN[p-j] = fLeftFactor * rowN[p-j] + fRightFactor * rowN[p-j+1];
+ }
+
+ // last element
+ fLeftFactor = ( tk - u[i] ) / ( u[i+s] - u[i] );
+ // i "true index" - (i-p)"shift" = p
+ rowN[p] = fLeftFactor * rowN[p];
+ }
+}
+
+} // anonymous namespace
+
+// Calculates uniform parametric splines with subinterval length 1,
+// according ODF1.2 part 1, chapter 'chart interpolation'.
+void SplineCalculater::CalculateCubicSplines(
+ const std::vector<std::vector<css::drawing::Position3D>>& rInput
+ , std::vector<std::vector<css::drawing::Position3D>>& rResult
+ , sal_uInt32 nGranularity )
+{
+ OSL_PRECOND( nGranularity > 0, "Granularity is invalid" );
+
+ sal_uInt32 nOuterCount = rInput.size();
+
+ rResult.resize(nOuterCount);
+
+ auto pSequence = rResult.data();
+
+ if( !nOuterCount )
+ return;
+
+ for( sal_uInt32 nOuter = 0; nOuter < nOuterCount; ++nOuter )
+ {
+ if( rInput[nOuter].size() <= 1 )
+ continue; //we need at least two points
+
+ sal_uInt32 nMaxIndexPoints = rInput[nOuter].size()-1; // is >=1
+ const css::drawing::Position3D* pOld = rInput[nOuter].data();
+
+ std::vector < double > aParameter(nMaxIndexPoints+1);
+ aParameter[0]=0.0;
+ for( sal_uInt32 nIndex=1; nIndex<=nMaxIndexPoints; nIndex++ )
+ {
+ aParameter[nIndex]=aParameter[nIndex-1]+1;
+ }
+
+ // Split the calculation to X, Y and Z coordinate
+ tPointVecType aInputX;
+ aInputX.resize(nMaxIndexPoints+1);
+ tPointVecType aInputY;
+ aInputY.resize(nMaxIndexPoints+1);
+ tPointVecType aInputZ;
+ aInputZ.resize(nMaxIndexPoints+1);
+ for (sal_uInt32 nN=0;nN<=nMaxIndexPoints; nN++ )
+ {
+ aInputX[ nN ].first=aParameter[nN];
+ aInputX[ nN ].second=pOld[ nN ].PositionX;
+ aInputY[ nN ].first=aParameter[nN];
+ aInputY[ nN ].second=pOld[ nN ].PositionY;
+ aInputZ[ nN ].first=aParameter[nN];
+ aInputZ[ nN ].second=pOld[ nN ].PositionZ;
+ }
+
+ // generate a spline for each coordinate. It holds the complete
+ // information to calculate each point of the curve
+ std::unique_ptr<lcl_SplineCalculation> aSplineX;
+ std::unique_ptr<lcl_SplineCalculation> aSplineY;
+ // lcl_SplineCalculation* aSplineZ; the z-coordinates of all points in
+ // a data series are equal. No spline calculation needed, but copy
+ // coordinate to output
+
+ if( pOld[ 0 ].PositionX == pOld[nMaxIndexPoints].PositionX &&
+ pOld[ 0 ].PositionY == pOld[nMaxIndexPoints].PositionY &&
+ pOld[ 0 ].PositionZ == pOld[nMaxIndexPoints].PositionZ &&
+ nMaxIndexPoints >=2 )
+ { // periodic spline
+ aSplineX = std::make_unique<lcl_SplineCalculation>(std::move(aInputX));
+ aSplineY = std::make_unique<lcl_SplineCalculation>(std::move(aInputY));
+ }
+ else // generate the kind "natural spline"
+ {
+ double fXDerivation = std::numeric_limits<double>::infinity();
+ double fYDerivation = std::numeric_limits<double>::infinity();
+ aSplineX = std::make_unique<lcl_SplineCalculation>(std::move(aInputX), fXDerivation, fXDerivation);
+ aSplineY = std::make_unique<lcl_SplineCalculation>(std::move(aInputY), fYDerivation, fYDerivation);
+ }
+
+ // fill result polygon with calculated values
+ pSequence[nOuter].resize( nMaxIndexPoints*nGranularity + 1);
+
+ css::drawing::Position3D* pNew = pSequence[nOuter].data();
+
+ sal_uInt32 nNewPointIndex = 0; // Index in result points
+
+ for( sal_uInt32 ni = 0; ni < nMaxIndexPoints; ni++ )
+ {
+ // given point is surely a curve point
+ pNew[nNewPointIndex].PositionX = pOld[ni].PositionX;
+ pNew[nNewPointIndex].PositionY = pOld[ni].PositionY;
+ pNew[nNewPointIndex].PositionZ = pOld[ni].PositionZ;
+ nNewPointIndex++;
+
+ // calculate intermediate points
+ double fInc = ( aParameter[ ni+1 ] - aParameter[ni] ) / static_cast< double >( nGranularity );
+ for(sal_uInt32 nj = 1; nj < nGranularity; nj++)
+ {
+ double fParam = aParameter[ni] + ( fInc * static_cast< double >( nj ) );
+
+ pNew[nNewPointIndex].PositionX = aSplineX->GetInterpolatedValue( fParam );
+ pNew[nNewPointIndex].PositionY = aSplineY->GetInterpolatedValue( fParam );
+ // pNewZ[nNewPointIndex]=aSplineZ->GetInterpolatedValue( fParam );
+ pNew[nNewPointIndex].PositionZ = pOld[ni].PositionZ;
+ nNewPointIndex++;
+ }
+ }
+ // add last point
+ pNew[nNewPointIndex] = pOld[nMaxIndexPoints];
+ }
+}
+
+void SplineCalculater::CalculateBSplines(
+ const std::vector<std::vector<css::drawing::Position3D>>& rInput
+ , std::vector<std::vector<css::drawing::Position3D>>& rResult
+ , sal_uInt32 nResolution
+ , sal_uInt32 nDegree )
+{
+ // nResolution is ODF1.2 file format attribute chart:spline-resolution and
+ // ODF1.2 spec variable k. Causion, k is used as index in the spec in addition.
+ // nDegree is ODF1.2 file format attribute chart:spline-order and
+ // ODF1.2 spec variable p
+ OSL_ASSERT( nResolution > 1 );
+ OSL_ASSERT( nDegree >= 1 );
+
+ // limit the b-spline degree at 15 to prevent insanely large sets of points
+ sal_uInt32 p = std::min<sal_uInt32>(nDegree, 15);
+
+ sal_Int32 nOuterCount = rInput.size();
+
+ rResult.resize(nOuterCount);
+
+ auto pSequence = rResult.data();
+
+ if( !nOuterCount )
+ return; // no input
+
+ for( sal_Int32 nOuter = 0; nOuter < nOuterCount; ++nOuter )
+ {
+ if( rInput[nOuter].size() <= 1 )
+ continue; // need at least 2 points, next piece of the series
+
+ // Copy input to vector of points and remove adjacent double points. The
+ // Z-coordinate is equal for all points in a series and holds the depth
+ // in 3D mode, simple copying is enough.
+ lcl_tSizeType nMaxIndexPoints = rInput[nOuter].size()-1; // is >=1
+ const css::drawing::Position3D* pOld = rInput[nOuter].data();
+ double fZCoordinate = pOld[0].PositionZ;
+ tPointVecType aPointsIn;
+ aPointsIn.resize(nMaxIndexPoints+1);
+ for (lcl_tSizeType i = 0; i <= nMaxIndexPoints; ++i )
+ {
+ aPointsIn[ i ].first = pOld[i].PositionX;
+ aPointsIn[ i ].second = pOld[i].PositionY;
+ }
+ aPointsIn.erase( std::unique( aPointsIn.begin(), aPointsIn.end()),
+ aPointsIn.end() );
+
+ // n is the last valid index to the reduced aPointsIn
+ // There are n+1 valid data points.
+ const lcl_tSizeType n = aPointsIn.size() - 1;
+ if (n < 1 || p > n)
+ continue; // need at least 2 points, degree p needs at least n+1 points
+ // next piece of series
+
+ std::vector<double> t(n + 1);
+ if (!createParameterT(aPointsIn, t.data()))
+ {
+ continue; // next piece of series
+ }
+
+ lcl_tSizeType m = n + p + 1;
+ std::vector<double> u(m + 1);
+ createKnotVector(n, p, t.data(), u.data());
+
+ // The matrix N contains the B-spline basis functions applied to parameters.
+ // In each row only p+1 adjacent elements are non-zero. The starting
+ // column in a higher row is equal or greater than in the lower row.
+ // To store this matrix the non-zero elements are shifted to column 0
+ // and the amount of shifting is remembered in an array.
+ std::vector<std::vector<double>> aMatN(n + 1, std::vector<double>(p + 1));
+ std::vector<lcl_tSizeType> aShift(n + 1);
+ aMatN[0][0] = 1.0; //all others are zero
+ aShift[0] = 0;
+ aMatN[n][0] = 1.0;
+ aShift[n] = n;
+ for (lcl_tSizeType k = 1; k<=n-1; ++k)
+ { // all basis functions are applied to t_k,
+ // results are elements in row k in matrix N
+
+ // find the one interval with u_i <= t_k < u_(i+1)
+ // remember u_0 = ... = u_p = 0.0 and u_(m-p) = ... u_m = 1.0 and 0<t_k<1
+ lcl_tSizeType i = p;
+ while (u[i] > t[k] || t[k] >= u[i+1])
+ {
+ ++i;
+ }
+
+ // index in reduced matrix aMatN = (index in full matrix N) - (i-p)
+ aShift[k] = i - p;
+
+ applyNtoParameterT(i, t[k], p, u.data(), aMatN[k].data());
+ } // next row k
+
+ // Get matrix C of control points from the matrix equation aMatN * C = aPointsIn
+ // aPointsIn is overwritten with C.
+ // Gaussian elimination is possible without pivoting, see reference
+ lcl_tSizeType r = 0; // true row index
+ lcl_tSizeType c = 0; // true column index
+ double fDivisor = 1.0; // used for diagonal element
+ double fEliminate = 1.0; // used for the element, that will become zero
+ bool bIsSuccessful = true;
+ for (c = 0 ; c <= n && bIsSuccessful; ++c)
+ {
+ // search for first non-zero downwards
+ r = c;
+ while ( r < n && aMatN[r][c-aShift[r]] == 0 )
+ {
+ ++r;
+ }
+ if (aMatN[r][c-aShift[r]] == 0.0)
+ {
+ // Matrix N is singular, although this is mathematically impossible
+ bIsSuccessful = false;
+ }
+ else
+ {
+ // exchange total row r with total row c if necessary
+ if (r != c)
+ {
+ std::swap( aMatN[r], aMatN[c] );
+ std::swap( aPointsIn[r], aPointsIn[c] );
+ std::swap( aShift[r], aShift[c] );
+ }
+
+ // divide row c, so that element(c,c) becomes 1
+ fDivisor = aMatN[c][c-aShift[c]]; // not zero, see above
+ for (sal_uInt32 i = 0; i <= p; ++i)
+ {
+ aMatN[c][i] /= fDivisor;
+ }
+ aPointsIn[c].first /= fDivisor;
+ aPointsIn[c].second /= fDivisor;
+
+ // eliminate forward, examine row c+1 to n-1 (worst case)
+ // stop if first non-zero element in row has an higher column as c
+ // look at nShift for that, elements in nShift are equal or increasing
+ for ( r = c+1; r < n && aShift[r]<=c ; ++r)
+ {
+ fEliminate = aMatN[r][0];
+ if (fEliminate != 0.0) // else accidentally zero, nothing to do
+ {
+ for (sal_uInt32 i = 1; i <= p; ++i)
+ {
+ aMatN[r][i-1] = aMatN[r][i] - fEliminate * aMatN[c][i];
+ }
+ aMatN[r][p]=0;
+ aPointsIn[r].first -= fEliminate * aPointsIn[c].first;
+ aPointsIn[r].second -= fEliminate * aPointsIn[c].second;
+ ++aShift[r];
+ }
+ }
+ }
+ }// upper triangle form is reached
+ if( bIsSuccessful)
+ {
+ // eliminate backwards, begin with last column
+ for (lcl_tSizeType cc = n; cc >= 1; --cc )
+ {
+ // In row cc the diagonal element(cc,cc) == 1 and all elements left from
+ // diagonal are zero and do not influence other rows.
+ // Full matrix N has semibandwidth < p, therefore element(r,c) is
+ // zero, if abs(r-cc)>=p. abs(r-cc)=cc-r, because r<cc.
+ r = cc - 1;
+ while ( r !=0 && cc-r < p )
+ {
+ fEliminate = aMatN[r][ cc - aShift[r] ];
+ if ( fEliminate != 0.0) // else element is accidentally zero, no action needed
+ {
+ // row r -= fEliminate * row cc only relevant for right side
+ aMatN[r][cc - aShift[r]] = 0.0;
+ aPointsIn[r].first -= fEliminate * aPointsIn[cc].first;
+ aPointsIn[r].second -= fEliminate * aPointsIn[cc].second;
+ }
+ --r;
+ }
+ }
+ // aPointsIn contains the control points now.
+
+ // calculate the intermediate points according given resolution
+ // using deBoor-Cox algorithm
+ lcl_tSizeType nNewSize = nResolution * n + 1;
+ pSequence[nOuter].resize(nNewSize);
+ css::drawing::Position3D* pNew = pSequence[nOuter].data();
+ pNew[0].PositionX = aPointsIn[0].first;
+ pNew[0].PositionY = aPointsIn[0].second;
+ pNew[0].PositionZ = fZCoordinate; // Precondition: z-coordinates of all points of a series are equal
+ pNew[nNewSize -1 ].PositionX = aPointsIn[n].first;
+ pNew[nNewSize -1 ].PositionY = aPointsIn[n].second;
+ pNew[nNewSize -1 ].PositionZ = fZCoordinate;
+ std::vector<double> aP(m + 1);
+ lcl_tSizeType nLow = 0;
+ for ( lcl_tSizeType nTIndex = 0; nTIndex <= n-1; ++nTIndex)
+ {
+ for (sal_uInt32 nResolutionStep = 1;
+ nResolutionStep <= nResolution && ( nTIndex != n-1 || nResolutionStep != nResolution);
+ ++nResolutionStep)
+ {
+ lcl_tSizeType nNewIndex = nTIndex * nResolution + nResolutionStep;
+ double ux = t[nTIndex] + nResolutionStep * ( t[nTIndex+1] - t[nTIndex]) /nResolution;
+
+ // get index nLow, so that u[nLow]<= ux < u[nLow +1]
+ // continue from previous nLow
+ while ( u[nLow] <= ux)
+ {
+ ++nLow;
+ }
+ --nLow;
+
+ // x-coordinate
+ for (lcl_tSizeType i = nLow-p; i <= nLow; ++i)
+ {
+ aP[i] = aPointsIn[i].first;
+ }
+ for (sal_uInt32 lcl_Degree = 1; lcl_Degree <= p; ++lcl_Degree)
+ {
+ for (lcl_tSizeType i = nLow; i >= nLow + lcl_Degree - p; --i)
+ {
+ double fFactor = ( ux - u[i] ) / ( u[i+p+1-lcl_Degree] - u[i]);
+ aP[i] = (1 - fFactor)* aP[i-1] + fFactor * aP[i];
+ }
+ }
+ pNew[nNewIndex].PositionX = aP[nLow];
+
+ // y-coordinate
+ for (lcl_tSizeType i = nLow - p; i <= nLow; ++i)
+ {
+ aP[i] = aPointsIn[i].second;
+ }
+ for (sal_uInt32 lcl_Degree = 1; lcl_Degree <= p; ++lcl_Degree)
+ {
+ for (lcl_tSizeType i = nLow; i >= nLow +lcl_Degree - p; --i)
+ {
+ double fFactor = ( ux - u[i] ) / ( u[i+p+1-lcl_Degree] - u[i]);
+ aP[i] = (1 - fFactor)* aP[i-1] + fFactor * aP[i];
+ }
+ }
+ pNew[nNewIndex].PositionY = aP[nLow];
+ pNew[nNewIndex].PositionZ = fZCoordinate;
+ }
+ }
+ }
+ } // next piece of the series
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/Splines.hxx b/chart2/source/view/charttypes/Splines.hxx
new file mode 100644
index 000000000..b83c13931
--- /dev/null
+++ b/chart2/source/view/charttypes/Splines.hxx
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/types.h>
+#include <vector>
+
+namespace com::sun::star::drawing { struct PolyPolygonShape3D; }
+namespace com::sun::star::drawing { struct Position3D; }
+
+namespace chart
+{
+
+class SplineCalculater
+{
+public:
+ static void CalculateCubicSplines(
+ const std::vector<std::vector<css::drawing::Position3D>>& rPoints
+ , std::vector<std::vector<css::drawing::Position3D>>& rResult
+ , sal_uInt32 nGranularity );
+
+ static void CalculateBSplines(
+ const std::vector<std::vector<css::drawing::Position3D>>& rPoints
+ , std::vector<std::vector<css::drawing::Position3D>>& rResult
+ , sal_uInt32 nGranularity
+ , sal_uInt32 nSplineDepth );
+};
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/VSeriesPlotter.cxx b/chart2/source/view/charttypes/VSeriesPlotter.cxx
new file mode 100644
index 000000000..fd3a1fe1f
--- /dev/null
+++ b/chart2/source/view/charttypes/VSeriesPlotter.cxx
@@ -0,0 +1,2847 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <cstddef>
+#include <limits>
+#include <memory>
+#include <VSeriesPlotter.hxx>
+#include <BaseGFXHelper.hxx>
+#include <VLineProperties.hxx>
+#include <ShapeFactory.hxx>
+#include <Diagram.hxx>
+#include <BaseCoordinateSystem.hxx>
+#include <DataSeries.hxx>
+
+#include <CommonConverters.hxx>
+#include <ExplicitCategoriesProvider.hxx>
+#include <ObjectIdentifier.hxx>
+#include <StatisticsHelper.hxx>
+#include <PlottingPositionHelper.hxx>
+#include <LabelPositionHelper.hxx>
+#include <ChartType.hxx>
+#include <ChartTypeHelper.hxx>
+#include <Clipping.hxx>
+#include <servicenames_charttypes.hxx>
+#include <NumberFormatterWrapper.hxx>
+#include <DataSeriesHelper.hxx>
+#include <RegressionCurveModel.hxx>
+#include <RegressionCurveHelper.hxx>
+#include <VLegendSymbolFactory.hxx>
+#include <FormattedStringHelper.hxx>
+#include <RelativePositionHelper.hxx>
+#include <DateHelper.hxx>
+#include <DiagramHelper.hxx>
+#include <defines.hxx>
+#include <ChartModel.hxx>
+
+//only for creation: @todo remove if all plotter are uno components and instantiated via servicefactory
+#include "BarChart.hxx"
+#include "PieChart.hxx"
+#include "AreaChart.hxx"
+#include "CandleStickChart.hxx"
+#include "BubbleChart.hxx"
+#include "NetChart.hxx"
+#include <unonames.hxx>
+#include <SpecialCharacters.hxx>
+
+#include <com/sun/star/chart2/DataPointLabel.hpp>
+#include <com/sun/star/chart/ErrorBarStyle.hpp>
+#include <com/sun/star/chart/TimeUnit.hpp>
+#include <com/sun/star/chart2/MovingAverageType.hpp>
+#include <com/sun/star/chart2/XDataPointCustomLabelField.hpp>
+#include <com/sun/star/container/XChild.hpp>
+#include <com/sun/star/chart2/RelativePosition.hpp>
+#include <o3tl/safeint.hxx>
+#include <tools/color.hxx>
+#include <tools/UnitConversion.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/math.hxx>
+#include <basegfx/vector/b2dvector.hxx>
+#include <com/sun/star/drawing/LineStyle.hpp>
+#include <com/sun/star/util/XCloneable.hpp>
+
+#include <unotools/localedatawrapper.hxx>
+#include <comphelper/sequence.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <tools/diagnose_ex.h>
+#include <sal/log.hxx>
+
+#include <functional>
+#include <map>
+#include <unordered_map>
+
+
+namespace chart {
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::chart;
+using namespace ::com::sun::star::chart2;
+using ::com::sun::star::uno::Reference;
+using ::com::sun::star::uno::Sequence;
+
+VDataSeriesGroup::CachedYValues::CachedYValues()
+ : m_bValuesDirty(true)
+ , m_fMinimumY(0.0)
+ , m_fMaximumY(0.0)
+{
+}
+
+VDataSeriesGroup::VDataSeriesGroup( std::unique_ptr<VDataSeries> pSeries )
+ : m_aSeriesVector(1)
+ , m_bMaxPointCountDirty(true)
+ , m_nMaxPointCount(0)
+{
+ m_aSeriesVector[0] = std::move(pSeries);
+}
+
+VDataSeriesGroup::VDataSeriesGroup(VDataSeriesGroup&& other) noexcept
+ : m_aSeriesVector( std::move(other.m_aSeriesVector) )
+ , m_bMaxPointCountDirty( other.m_bMaxPointCountDirty )
+ , m_nMaxPointCount( other.m_nMaxPointCount )
+ , m_aListOfCachedYValues( std::move(other.m_aListOfCachedYValues) )
+{
+}
+
+VDataSeriesGroup::~VDataSeriesGroup()
+{
+}
+
+void VDataSeriesGroup::deleteSeries()
+{
+ //delete all data series help objects:
+ m_aSeriesVector.clear();
+}
+
+void VDataSeriesGroup::addSeries( std::unique_ptr<VDataSeries> pSeries )
+{
+ m_aSeriesVector.push_back(std::move(pSeries));
+ m_bMaxPointCountDirty=true;
+}
+
+sal_Int32 VDataSeriesGroup::getSeriesCount() const
+{
+ return m_aSeriesVector.size();
+}
+
+VSeriesPlotter::VSeriesPlotter( const rtl::Reference<ChartType>& xChartTypeModel
+ , sal_Int32 nDimensionCount, bool bCategoryXAxis )
+ : PlotterBase( nDimensionCount )
+ , m_pMainPosHelper( nullptr )
+ , m_xChartTypeModel(xChartTypeModel)
+ , m_bCategoryXAxis(bCategoryXAxis)
+ , m_nTimeResolution(css::chart::TimeUnit::DAY)
+ , m_aNullDate(30,12,1899)
+ , m_pExplicitCategoriesProvider(nullptr)
+ , m_bPointsWereSkipped(false)
+ , m_bPieLabelsAllowToMove(false)
+ , m_aAvailableOuterRect(0, 0, 0, 0)
+{
+ SAL_WARN_IF(!m_xChartTypeModel.is(),"chart2","no XChartType available in view, fallback to default values may be wrong");
+}
+
+VSeriesPlotter::~VSeriesPlotter()
+{
+ //delete all data series help objects:
+ for (std::vector<VDataSeriesGroup> & rGroupVector : m_aZSlots)
+ {
+ for (VDataSeriesGroup & rGroup : rGroupVector)
+ {
+ rGroup.deleteSeries();
+ }
+ rGroupVector.clear();
+ }
+ m_aZSlots.clear();
+
+ m_aSecondaryPosHelperMap.clear();
+
+ m_aSecondaryValueScales.clear();
+}
+
+void VSeriesPlotter::addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 zSlot, sal_Int32 xSlot, sal_Int32 ySlot )
+{
+ //take ownership of pSeries
+
+ OSL_PRECOND( pSeries, "series to add is NULL" );
+ if(!pSeries)
+ return;
+
+ if(m_bCategoryXAxis)
+ {
+ if( m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis() )
+ pSeries->setXValues( m_pExplicitCategoriesProvider->getOriginalCategories() );
+ else
+ pSeries->setCategoryXAxis();
+ }
+ else
+ {
+ if( m_pExplicitCategoriesProvider )
+ pSeries->setXValuesIfNone( m_pExplicitCategoriesProvider->getOriginalCategories() );
+ }
+
+ if(zSlot<0 || o3tl::make_unsigned(zSlot)>=m_aZSlots.size())
+ {
+ //new z slot
+ std::vector< VDataSeriesGroup > aZSlot;
+ aZSlot.emplace_back( std::move(pSeries) );
+ m_aZSlots.push_back( std::move(aZSlot) );
+ }
+ else
+ {
+ //existing zslot
+ std::vector< VDataSeriesGroup >& rXSlots = m_aZSlots[zSlot];
+
+ if(xSlot<0 || o3tl::make_unsigned(xSlot)>=rXSlots.size())
+ {
+ //append the series to already existing x series
+ rXSlots.emplace_back( std::move(pSeries) );
+ }
+ else
+ {
+ //x slot is already occupied
+ //y slot decides what to do:
+
+ VDataSeriesGroup& rYSlots = rXSlots[xSlot];
+ sal_Int32 nYSlotCount = rYSlots.getSeriesCount();
+
+ if( ySlot < -1 )
+ {
+ //move all existing series in the xSlot to next slot
+ //@todo
+ OSL_FAIL( "Not implemented yet");
+ }
+ else if( ySlot == -1 || ySlot >= nYSlotCount)
+ {
+ //append the series to already existing y series
+ rYSlots.addSeries( std::move(pSeries) );
+ }
+ else
+ {
+ //y slot is already occupied
+ //insert at given y and x position
+
+ //@todo
+ OSL_FAIL( "Not implemented yet");
+ }
+ }
+ }
+}
+
+drawing::Direction3D VSeriesPlotter::getPreferredDiagramAspectRatio() const
+{
+ drawing::Direction3D aRet(1.0,1.0,1.0);
+ if (!m_pPosHelper)
+ return aRet;
+
+ drawing::Direction3D aScale( m_pPosHelper->getScaledLogicWidth() );
+ aRet.DirectionZ = aScale.DirectionZ*0.2;
+ if(aRet.DirectionZ>1.0)
+ aRet.DirectionZ=1.0;
+ if(aRet.DirectionZ>10)
+ aRet.DirectionZ=10;
+ return aRet;
+}
+
+void VSeriesPlotter::releaseShapes()
+{
+ for (std::vector<VDataSeriesGroup> const & rGroupVector : m_aZSlots)
+ {
+ for (VDataSeriesGroup const & rGroup : rGroupVector)
+ {
+ //iterate through all series in this x slot
+ for (std::unique_ptr<VDataSeries> const & pSeries : rGroup.m_aSeriesVector)
+ {
+ pSeries->releaseShapes();
+ }
+ }
+ }
+}
+
+rtl::Reference<SvxShapeGroupAnyD> VSeriesPlotter::getSeriesGroupShape( VDataSeries* pDataSeries
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
+{
+ if( !pDataSeries->m_xGroupShape )
+ //create a group shape for this series and add to logic target:
+ pDataSeries->m_xGroupShape = createGroupShape( xTarget,pDataSeries->getCID() );
+ return pDataSeries->m_xGroupShape;
+}
+
+rtl::Reference<SvxShapeGroupAnyD> VSeriesPlotter::getSeriesGroupShapeFrontChild( VDataSeries* pDataSeries
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
+{
+ if(!pDataSeries->m_xFrontSubGroupShape)
+ {
+ //ensure that the series group shape is already created
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesShapes( getSeriesGroupShape( pDataSeries, xTarget ) );
+ //ensure that the back child is created first
+ getSeriesGroupShapeBackChild( pDataSeries, xTarget );
+ //use series group shape as parent for the new created front group shape
+ pDataSeries->m_xFrontSubGroupShape = createGroupShape( xSeriesShapes );
+ }
+ return pDataSeries->m_xFrontSubGroupShape;
+}
+
+rtl::Reference<SvxShapeGroupAnyD> VSeriesPlotter::getSeriesGroupShapeBackChild( VDataSeries* pDataSeries
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
+{
+ if(!pDataSeries->m_xBackSubGroupShape)
+ {
+ //ensure that the series group shape is already created
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesShapes( getSeriesGroupShape( pDataSeries, xTarget ) );
+ //use series group shape as parent for the new created back group shape
+ pDataSeries->m_xBackSubGroupShape = createGroupShape( xSeriesShapes );
+ }
+ return pDataSeries->m_xBackSubGroupShape;
+}
+
+rtl::Reference<SvxShapeGroup> VSeriesPlotter::getLabelsGroupShape( VDataSeries& rDataSeries
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTextTarget )
+{
+ //xTextTarget needs to be a 2D shape container always!
+ if(!rDataSeries.m_xLabelsGroupShape)
+ {
+ //create a 2D group shape for texts of this series and add to text target:
+ rDataSeries.m_xLabelsGroupShape = ShapeFactory::createGroup2D( xTextTarget, rDataSeries.getLabelsCID() );
+ }
+ return rDataSeries.m_xLabelsGroupShape;
+}
+
+rtl::Reference<SvxShapeGroupAnyD> VSeriesPlotter::getErrorBarsGroupShape( VDataSeries& rDataSeries
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , bool bYError )
+{
+ rtl::Reference<SvxShapeGroupAnyD> &rShapeGroup =
+ bYError ? rDataSeries.m_xErrorYBarsGroupShape : rDataSeries.m_xErrorXBarsGroupShape;
+
+ if(!rShapeGroup)
+ {
+ //create a group shape for this series and add to logic target:
+ rShapeGroup = createGroupShape( xTarget,rDataSeries.getErrorBarsCID(bYError) );
+ }
+ return rShapeGroup;
+
+}
+
+OUString VSeriesPlotter::getLabelTextForValue( VDataSeries const & rDataSeries
+ , sal_Int32 nPointIndex
+ , double fValue
+ , bool bAsPercentage )
+{
+ OUString aNumber;
+
+ if (m_apNumberFormatterWrapper)
+ {
+ sal_Int32 nNumberFormatKey = 0;
+ if( rDataSeries.hasExplicitNumberFormat(nPointIndex,bAsPercentage) )
+ nNumberFormatKey = rDataSeries.getExplicitNumberFormat(nPointIndex,bAsPercentage);
+ else if( bAsPercentage )
+ {
+ sal_Int32 nPercentFormat = DiagramHelper::getPercentNumberFormat( m_apNumberFormatterWrapper->getNumberFormatsSupplier() );
+ if( nPercentFormat != -1 )
+ nNumberFormatKey = nPercentFormat;
+ }
+ else
+ {
+ nNumberFormatKey = rDataSeries.detectNumberFormatKey( nPointIndex );
+ }
+ if(nNumberFormatKey<0)
+ nNumberFormatKey=0;
+
+ Color nLabelCol;
+ bool bColChanged;
+ aNumber = m_apNumberFormatterWrapper->getFormattedString(
+ nNumberFormatKey, fValue, nLabelCol, bColChanged );
+ //@todo: change color of label if bColChanged is true
+ }
+ else
+ {
+ const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
+ const OUString& aNumDecimalSep = rLocaleDataWrapper.getNumDecimalSep();
+ assert(aNumDecimalSep.getLength() > 0);
+ sal_Unicode cDecSeparator = aNumDecimalSep[0];
+ aNumber = ::rtl::math::doubleToUString( fValue, rtl_math_StringFormat_G /*rtl_math_StringFormat*/
+ , 3/*DecPlaces*/ , cDecSeparator );
+ }
+ return aNumber;
+}
+
+rtl::Reference<SvxShapeText> VSeriesPlotter::createDataLabel( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , VDataSeries& rDataSeries
+ , sal_Int32 nPointIndex
+ , double fValue
+ , double fSumValue
+ , const awt::Point& rScreenPosition2D
+ , LabelAlignment eAlignment
+ , sal_Int32 nOffset
+ , sal_Int32 nTextWidth )
+{
+ rtl::Reference<SvxShapeText> xTextShape;
+ Sequence<uno::Reference<XDataPointCustomLabelField>> aCustomLabels;
+
+ try
+ {
+ const uno::Reference< css::beans::XPropertySet >& xPropertySet(
+ rDataSeries.getPropertiesOfPoint( nPointIndex ) );
+ if( xPropertySet.is() )
+ {
+ uno::Any aAny = xPropertySet->getPropertyValue( CHART_UNONAME_CUSTOM_LABEL_FIELDS );
+ if( aAny.hasValue() )
+ {
+ aAny >>= aCustomLabels;
+ }
+ }
+
+ awt::Point aScreenPosition2D(rScreenPosition2D);
+ if(eAlignment==LABEL_ALIGN_LEFT)
+ aScreenPosition2D.X -= nOffset;
+ else if(eAlignment==LABEL_ALIGN_RIGHT)
+ aScreenPosition2D.X += nOffset;
+ else if(eAlignment==LABEL_ALIGN_TOP)
+ aScreenPosition2D.Y -= nOffset;
+ else if(eAlignment==LABEL_ALIGN_BOTTOM)
+ aScreenPosition2D.Y += nOffset;
+
+ rtl::Reference<SvxShapeGroup> xTarget_ =
+ ShapeFactory::createGroup2D(
+ getLabelsGroupShape(rDataSeries, xTarget),
+ ObjectIdentifier::createPointCID( rDataSeries.getLabelCID_Stub(), nPointIndex));
+
+ //check whether the label needs to be created and how:
+ DataPointLabel* pLabel = rDataSeries.getDataPointLabelIfLabel( nPointIndex );
+
+ if( !pLabel )
+ return xTextShape;
+
+ //prepare legend symbol
+
+ // get the font size for the label through the "CharHeight" property
+ // attached to the passed data series entry.
+ // (By tracing font height values it results that for pie chart the
+ // font size is not the same for all labels, but here no font size
+ // modification occurs).
+ float fViewFontSize( 10.0 );
+ {
+ uno::Reference< beans::XPropertySet > xProps( rDataSeries.getPropertiesOfPoint( nPointIndex ) );
+ if( xProps.is() )
+ xProps->getPropertyValue( "CharHeight") >>= fViewFontSize;
+ fViewFontSize = convertPointToMm100(fViewFontSize);
+ }
+
+ // the font height is used for computing the size of an optional legend
+ // symbol to be prepended to the text label.
+ rtl::Reference< SvxShapeGroup > xSymbol;
+ if(pLabel->ShowLegendSymbol)
+ {
+ sal_Int32 nSymbolHeight = static_cast< sal_Int32 >( fViewFontSize * 0.6 );
+ awt::Size aCurrentRatio = getPreferredLegendKeyAspectRatio();
+ sal_Int32 nSymbolWidth = aCurrentRatio.Width;
+ if( aCurrentRatio.Height > 0 )
+ {
+ nSymbolWidth = nSymbolHeight* aCurrentRatio.Width/aCurrentRatio.Height;
+ }
+ awt::Size aMaxSymbolExtent( nSymbolWidth, nSymbolHeight );
+
+ if( rDataSeries.isVaryColorsByPoint() )
+ xSymbol = VSeriesPlotter::createLegendSymbolForPoint( aMaxSymbolExtent, rDataSeries, nPointIndex, xTarget_ );
+ else
+ xSymbol = VSeriesPlotter::createLegendSymbolForSeries( aMaxSymbolExtent, rDataSeries, xTarget_ );
+ }
+
+ //prepare text
+ bool bTextWrap = false;
+ OUString aSeparator(" ");
+ double fRotationDegrees = 0.0;
+ try
+ {
+ uno::Reference< beans::XPropertySet > xPointProps( rDataSeries.getPropertiesOfPoint( nPointIndex ) );
+ if(xPointProps.is())
+ {
+ xPointProps->getPropertyValue( "TextWordWrap" ) >>= bTextWrap;
+ xPointProps->getPropertyValue( "LabelSeparator" ) >>= aSeparator;
+ // Extract the optional text rotation through the
+ // "TextRotation" property attached to the passed data point.
+ xPointProps->getPropertyValue( "TextRotation" ) >>= fRotationDegrees;
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+
+ sal_Int32 nLineCountForSymbolsize = 0;
+ sal_uInt32 nTextListLength = 4;
+ sal_uInt32 nCustomLabelsCount = aCustomLabels.getLength();
+ Sequence< OUString > aTextList( nTextListLength );
+
+ bool bUseCustomLabel = nCustomLabelsCount > 0;
+ if( bUseCustomLabel )
+ {
+ nTextListLength = ( nCustomLabelsCount > 3 ) ? nCustomLabelsCount : 3;
+ aSeparator = "";
+ aTextList = Sequence< OUString >( nTextListLength );
+ auto pTextList = aTextList.getArray();
+ for( sal_uInt32 i = 0; i < nCustomLabelsCount; ++i )
+ {
+ switch( aCustomLabels[i]->getFieldType() )
+ {
+ case DataPointCustomLabelFieldType_VALUE:
+ {
+ pTextList[i] = getLabelTextForValue( rDataSeries, nPointIndex, fValue, false );
+ break;
+ }
+ case DataPointCustomLabelFieldType_CATEGORYNAME:
+ {
+ pTextList[i] = getCategoryName( nPointIndex );
+ break;
+ }
+ case DataPointCustomLabelFieldType_SERIESNAME:
+ {
+ OUString aRole;
+ if ( m_xChartTypeModel )
+ aRole = m_xChartTypeModel->getRoleOfSequenceForSeriesLabel();
+ const rtl::Reference< DataSeries >& xSeries( rDataSeries.getModel() );
+ pTextList[i] = DataSeriesHelper::getDataSeriesLabel( xSeries, aRole );
+ break;
+ }
+ case DataPointCustomLabelFieldType_PERCENTAGE:
+ {
+ if(fSumValue == 0.0)
+ fSumValue = 1.0;
+ fValue /= fSumValue;
+ if(fValue < 0)
+ fValue *= -1.0;
+
+ pTextList[i] = getLabelTextForValue(rDataSeries, nPointIndex, fValue, true);
+ break;
+ }
+ case DataPointCustomLabelFieldType_CELLRANGE:
+ {
+ if (aCustomLabels[i]->getDataLabelsRange())
+ pTextList[i] = aCustomLabels[i]->getString();
+ else
+ pTextList[i] = OUString();
+ break;
+ }
+ case DataPointCustomLabelFieldType_CELLREF:
+ {
+ // TODO: for now doesn't show placeholder
+ pTextList[i] = OUString();
+ break;
+ }
+ case DataPointCustomLabelFieldType_TEXT:
+ {
+ pTextList[i] = aCustomLabels[i]->getString();
+ break;
+ }
+ case DataPointCustomLabelFieldType_NEWLINE:
+ {
+ pTextList[i] = "\n";
+ break;
+ }
+ default:
+ break;
+ }
+ aCustomLabels[i]->setString( aTextList[i] );
+ }
+ }
+ else
+ {
+ auto pTextList = aTextList.getArray();
+ if( pLabel->ShowCategoryName )
+ {
+ pTextList[0] = getCategoryName( nPointIndex );
+ }
+
+ if( pLabel->ShowSeriesName )
+ {
+ OUString aRole;
+ if ( m_xChartTypeModel )
+ aRole = m_xChartTypeModel->getRoleOfSequenceForSeriesLabel();
+ const rtl::Reference< DataSeries >& xSeries( rDataSeries.getModel() );
+ pTextList[1] = DataSeriesHelper::getDataSeriesLabel( xSeries, aRole );
+ }
+
+ if( pLabel->ShowNumber )
+ {
+ pTextList[2] = getLabelTextForValue(rDataSeries, nPointIndex, fValue, false);
+ }
+
+ if( pLabel->ShowNumberInPercent )
+ {
+ if(fSumValue==0.0)
+ fSumValue=1.0;
+ fValue /= fSumValue;
+ if( fValue < 0 )
+ fValue*=-1.0;
+
+ pTextList[3] = getLabelTextForValue(rDataSeries, nPointIndex, fValue, true);
+ }
+ }
+
+ for( auto const & line : std::as_const(aTextList) )
+ {
+ if( !line.isEmpty() )
+ {
+ ++nLineCountForSymbolsize;
+ }
+ }
+
+ //prepare properties for multipropertyset-interface of shape
+ tNameSequence* pPropNames;
+ tAnySequence* pPropValues;
+ if( !rDataSeries.getTextLabelMultiPropertyLists( nPointIndex, pPropNames, pPropValues ) )
+ return xTextShape;
+
+ // set text alignment for the text shape to be created.
+ LabelPositionHelper::changeTextAdjustment( *pPropValues, *pPropNames, eAlignment );
+
+ // check if data series entry percent value and absolute value have to
+ // be appended to the text label, and what should be the separator
+ // character (comma, space, new line). In case it is a new line we get
+ // a multi-line label.
+ bool bMultiLineLabel = ( aSeparator == "\n" );
+
+ if( bUseCustomLabel )
+ {
+ Sequence< uno::Reference< XFormattedString > > aFormattedLabels(
+ comphelper::containerToSequence<uno::Reference<XFormattedString>>(aCustomLabels));
+
+ // create text shape
+ xTextShape = ShapeFactory::
+ createText( xTarget_, aFormattedLabels, *pPropNames, *pPropValues,
+ ShapeFactory::makeTransformation( aScreenPosition2D ) );
+ }
+ else
+ {
+ // join text list elements
+ OUStringBuffer aText;
+ for( sal_uInt32 nN = 0; nN < nTextListLength; ++nN)
+ {
+ if( !aTextList[nN].isEmpty() )
+ {
+ if( !aText.isEmpty() )
+ {
+ aText.append(aSeparator);
+ }
+ aText.append( aTextList[nN] );
+ }
+ }
+
+ //create text shape
+ xTextShape = ShapeFactory::
+ createText( xTarget_, aText.makeStringAndClear(), *pPropNames, *pPropValues,
+ ShapeFactory::makeTransformation( aScreenPosition2D ) );
+ }
+
+ if( !xTextShape.is() )
+ return xTextShape;
+
+ // we need to use a default value for the maximum width property ?
+ if( nTextWidth == 0 && bTextWrap )
+ {
+ sal_Int32 nMinSize =
+ (m_aPageReferenceSize.Height < m_aPageReferenceSize.Width)
+ ? m_aPageReferenceSize.Height
+ : m_aPageReferenceSize.Width;
+ nTextWidth = nMinSize / 3;
+ }
+
+ // in case text must be wrapped set the maximum width property
+ // for the text shape
+ if( nTextWidth != 0 && bTextWrap )
+ {
+ // compute the height of a line of text
+ if( !bMultiLineLabel || nLineCountForSymbolsize <= 0 )
+ {
+ nLineCountForSymbolsize = 1;
+ }
+ awt::Size aTextSize = xTextShape->getSize();
+ sal_Int32 aTextLineHeight = aTextSize.Height / nLineCountForSymbolsize;
+
+ // set maximum text width
+ uno::Any aTextMaximumFrameWidth( nTextWidth );
+ xTextShape->SvxShape::setPropertyValue( "TextMaximumFrameWidth", aTextMaximumFrameWidth );
+
+ // compute the total lines of text
+ aTextSize = xTextShape->getSize();
+ nLineCountForSymbolsize = aTextSize.Height / aTextLineHeight;
+ }
+
+ // in case text is rotated, the transformation property of the text
+ // shape is modified.
+ if( fRotationDegrees != 0.0 )
+ {
+ const double fDegreesPi( -basegfx::deg2rad(fRotationDegrees) );
+ xTextShape->SvxShape::setPropertyValue( "Transformation", ShapeFactory::makeTransformation( aScreenPosition2D, fDegreesPi ) );
+ LabelPositionHelper::correctPositionForRotation( xTextShape, eAlignment, fRotationDegrees, true /*bRotateAroundCenter*/ );
+ }
+
+ awt::Point aTextShapePos(xTextShape->getPosition());
+ if( m_bPieLabelsAllowToMove && rDataSeries.isLabelCustomPos(nPointIndex) )
+ {
+ awt::Point aRelPos = rDataSeries.getLabelPosition(aTextShapePos, nPointIndex);
+ if( aRelPos.X != -1 )
+ {
+ xTextShape->setPosition(aRelPos);
+ if( !m_xChartTypeModel->getChartType().equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_PIE) &&
+ rDataSeries.getPropertiesOfSeries()->getPropertyValue( "ShowCustomLeaderLines" ).get<sal_Bool>())
+ {
+ sal_Int32 nX1 = rScreenPosition2D.X;
+ sal_Int32 nY1 = rScreenPosition2D.Y;
+ sal_Int32 nX2 = nX1;
+ sal_Int32 nY2 = nY1;
+ ::basegfx::B2IRectangle aRect(BaseGFXHelper::makeRectangle(aRelPos, xTextShape->getSize()));
+ if (nX1 < aRelPos.X)
+ nX2 = aRelPos.X;
+ else if (nX1 > aRect.getMaxX())
+ nX2 = aRect.getMaxX();
+
+ if (nY1 < aRect.getMinY())
+ nY2 = aRect.getMinY();
+ else if (nY1 > aRect.getMaxY())
+ nY2 = aRect.getMaxY();
+
+ //when the line is very short compared to the page size don't create one
+ ::basegfx::B2DVector aLength(nX1 - nX2, nY1 - nY2);
+ double fPageDiagonaleLength
+ = std::hypot(m_aPageReferenceSize.Width, m_aPageReferenceSize.Height);
+ if ((aLength.getLength() / fPageDiagonaleLength) >= 0.01)
+ {
+ drawing::PointSequenceSequence aPoints{ { {nX1, nY1}, {nX2, nY2} } };
+
+ VLineProperties aVLineProperties;
+ ShapeFactory::createLine2D(xTarget, aPoints, &aVLineProperties);
+ }
+ }
+ }
+ }
+
+ // in case legend symbol has to be displayed, text shape position is
+ // slightly changed.
+ const awt::Point aUnrotatedTextPos(xTextShape->getPosition());
+ if( xSymbol.is() )
+ {
+ const awt::Point aOldTextPos( xTextShape->getPosition() );
+ awt::Point aNewTextPos( aOldTextPos );
+
+ awt::Point aSymbolPosition( aUnrotatedTextPos );
+ awt::Size aSymbolSize( xSymbol->getSize() );
+ awt::Size aTextSize = xTextShape->getSize();
+
+ sal_Int32 nXDiff = aSymbolSize.Width + static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.22 ) );//minimum 1mm
+ if( !bMultiLineLabel || nLineCountForSymbolsize <= 0 )
+ nLineCountForSymbolsize = 1;
+ aSymbolPosition.Y += ((aTextSize.Height/nLineCountForSymbolsize)/4);
+
+ if(eAlignment==LABEL_ALIGN_LEFT
+ || eAlignment==LABEL_ALIGN_LEFT_TOP
+ || eAlignment==LABEL_ALIGN_LEFT_BOTTOM)
+ {
+ aSymbolPosition.X -= nXDiff;
+ }
+ else if(eAlignment==LABEL_ALIGN_RIGHT
+ || eAlignment==LABEL_ALIGN_RIGHT_TOP
+ || eAlignment==LABEL_ALIGN_RIGHT_BOTTOM )
+ {
+ aNewTextPos.X += nXDiff;
+ }
+ else if(eAlignment==LABEL_ALIGN_TOP
+ || eAlignment==LABEL_ALIGN_BOTTOM
+ || eAlignment==LABEL_ALIGN_CENTER )
+ {
+ aSymbolPosition.X -= nXDiff/2;
+ aNewTextPos.X += nXDiff/2;
+ }
+
+ xSymbol->setPosition( aSymbolPosition );
+ xTextShape->setPosition( aNewTextPos );
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+
+ return xTextShape;
+}
+
+namespace
+{
+double lcl_getErrorBarLogicLength(
+ const uno::Sequence< double > & rData,
+ const uno::Reference< beans::XPropertySet >& xProp,
+ sal_Int32 nErrorBarStyle,
+ sal_Int32 nIndex,
+ bool bPositive,
+ bool bYError )
+{
+ double fResult = std::numeric_limits<double>::quiet_NaN();
+ try
+ {
+ switch( nErrorBarStyle )
+ {
+ case css::chart::ErrorBarStyle::NONE:
+ break;
+ case css::chart::ErrorBarStyle::VARIANCE:
+ fResult = StatisticsHelper::getVariance( rData );
+ break;
+ case css::chart::ErrorBarStyle::STANDARD_DEVIATION:
+ fResult = StatisticsHelper::getStandardDeviation( rData );
+ break;
+ case css::chart::ErrorBarStyle::RELATIVE:
+ {
+ double fPercent = 0;
+ if( xProp->getPropertyValue( bPositive
+ ? OUString("PositiveError")
+ : OUString("NegativeError") ) >>= fPercent )
+ {
+ if( nIndex >=0 && nIndex < rData.getLength() &&
+ ! std::isnan( rData[nIndex] ) &&
+ ! std::isnan( fPercent ))
+ {
+ fResult = rData[nIndex] * fPercent / 100.0;
+ }
+ }
+ }
+ break;
+ case css::chart::ErrorBarStyle::ABSOLUTE:
+ xProp->getPropertyValue( bPositive
+ ? OUString("PositiveError")
+ : OUString("NegativeError") ) >>= fResult;
+ break;
+ case css::chart::ErrorBarStyle::ERROR_MARGIN:
+ {
+ // todo: check if this is really what's called error-margin
+ double fPercent = 0;
+ if( xProp->getPropertyValue( bPositive
+ ? OUString("PositiveError")
+ : OUString("NegativeError") ) >>= fPercent )
+ {
+ double fMaxValue = -std::numeric_limits<double>::infinity();
+ for(double d : rData)
+ {
+ if(fMaxValue < d)
+ fMaxValue = d;
+ }
+ if( std::isfinite( fMaxValue ) &&
+ std::isfinite( fPercent ))
+ {
+ fResult = fMaxValue * fPercent / 100.0;
+ }
+ }
+ }
+ break;
+ case css::chart::ErrorBarStyle::STANDARD_ERROR:
+ fResult = StatisticsHelper::getStandardError( rData );
+ break;
+ case css::chart::ErrorBarStyle::FROM_DATA:
+ {
+ uno::Reference< chart2::data::XDataSource > xErrorBarData( xProp, uno::UNO_QUERY );
+ if( xErrorBarData.is())
+ fResult = StatisticsHelper::getErrorFromDataSource(
+ xErrorBarData, nIndex, bPositive, bYError);
+ }
+ break;
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+
+ return fResult;
+}
+
+void lcl_AddErrorBottomLine( const drawing::Position3D& rPosition, ::basegfx::B2DVector aMainDirection
+ , std::vector<std::vector<css::drawing::Position3D>>& rPoly, sal_Int32 nSequenceIndex )
+{
+ double fFixedWidth = 200.0;
+
+ aMainDirection.normalize();
+ ::basegfx::B2DVector aOrthoDirection(-aMainDirection.getY(),aMainDirection.getX());
+ aOrthoDirection.normalize();
+
+ ::basegfx::B2DVector aAnchor( rPosition.PositionX, rPosition.PositionY );
+ ::basegfx::B2DVector aStart = aAnchor + aOrthoDirection*fFixedWidth/2.0;
+ ::basegfx::B2DVector aEnd = aAnchor - aOrthoDirection*fFixedWidth/2.0;
+
+ AddPointToPoly( rPoly, drawing::Position3D( aStart.getX(), aStart.getY(), rPosition.PositionZ), nSequenceIndex );
+ AddPointToPoly( rPoly, drawing::Position3D( aEnd.getX(), aEnd.getY(), rPosition.PositionZ), nSequenceIndex );
+}
+
+::basegfx::B2DVector lcl_getErrorBarMainDirection(
+ const drawing::Position3D& rStart
+ , const drawing::Position3D& rBottomEnd
+ , PlottingPositionHelper const * pPosHelper
+ , const drawing::Position3D& rUnscaledLogicPosition
+ , bool bYError )
+{
+ ::basegfx::B2DVector aMainDirection( rStart.PositionX - rBottomEnd.PositionX
+ , rStart.PositionY - rBottomEnd.PositionY );
+ if( !aMainDirection.getLength() )
+ {
+ //get logic clip values:
+ double MinX = pPosHelper->getLogicMinX();
+ double MinY = pPosHelper->getLogicMinY();
+ double MaxX = pPosHelper->getLogicMaxX();
+ double MaxY = pPosHelper->getLogicMaxY();
+ double fZ = pPosHelper->getLogicMinZ();
+
+ if( bYError )
+ {
+ //main direction has constant x value
+ MinX = rUnscaledLogicPosition.PositionX;
+ MaxX = rUnscaledLogicPosition.PositionX;
+ }
+ else
+ {
+ //main direction has constant y value
+ MinY = rUnscaledLogicPosition.PositionY;
+ MaxY = rUnscaledLogicPosition.PositionY;
+ }
+
+ drawing::Position3D aStart = pPosHelper->transformLogicToScene( MinX, MinY, fZ, false );
+ drawing::Position3D aEnd = pPosHelper->transformLogicToScene( MaxX, MaxY, fZ, false );
+
+ aMainDirection = ::basegfx::B2DVector( aStart.PositionX - aEnd.PositionX
+ , aStart.PositionY - aEnd.PositionY );
+ }
+ if( !aMainDirection.getLength() )
+ {
+ //@todo
+ }
+ return aMainDirection;
+}
+
+drawing::Position3D lcl_transformMixedToScene( PlottingPositionHelper const * pPosHelper
+ , double fX /*scaled*/, double fY /*unscaled*/, double fZ /*unscaled*/ )
+{
+ if(!pPosHelper)
+ return drawing::Position3D(0,0,0);
+ pPosHelper->doLogicScaling( nullptr,&fY,&fZ );
+ pPosHelper->clipScaledLogicValues( &fX,&fY,&fZ );
+ return pPosHelper->transformScaledLogicToScene( fX, fY, fZ, false );
+}
+
+} // anonymous namespace
+
+void VSeriesPlotter::createErrorBar(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const drawing::Position3D& rUnscaledLogicPosition
+ , const uno::Reference< beans::XPropertySet > & xErrorBarProperties
+ , const VDataSeries& rVDataSeries
+ , sal_Int32 nIndex
+ , bool bYError /* = true */
+ , const double* pfScaledLogicX
+ )
+{
+ if( !ChartTypeHelper::isSupportingStatisticProperties( m_xChartTypeModel, m_nDimension ) )
+ return;
+
+ if( ! xErrorBarProperties.is())
+ return;
+
+ try
+ {
+ bool bShowPositive = false;
+ bool bShowNegative = false;
+ sal_Int32 nErrorBarStyle = css::chart::ErrorBarStyle::VARIANCE;
+
+ xErrorBarProperties->getPropertyValue( "ShowPositiveError") >>= bShowPositive;
+ xErrorBarProperties->getPropertyValue( "ShowNegativeError") >>= bShowNegative;
+ xErrorBarProperties->getPropertyValue( "ErrorBarStyle") >>= nErrorBarStyle;
+
+ if(!bShowPositive && !bShowNegative)
+ return;
+
+ if(nErrorBarStyle==css::chart::ErrorBarStyle::NONE)
+ return;
+
+ if (!m_pPosHelper)
+ return;
+
+ drawing::Position3D aUnscaledLogicPosition(rUnscaledLogicPosition);
+ if(nErrorBarStyle==css::chart::ErrorBarStyle::STANDARD_DEVIATION)
+ {
+ if (bYError)
+ aUnscaledLogicPosition.PositionY = rVDataSeries.getYMeanValue();
+ else
+ aUnscaledLogicPosition.PositionX = rVDataSeries.getXMeanValue();
+ }
+
+ bool bCreateNegativeBorder = false;//make a vertical line at the negative end of the error bar
+ bool bCreatePositiveBorder = false;//make a vertical line at the positive end of the error bar
+ drawing::Position3D aMiddle(aUnscaledLogicPosition);
+ const double fX = aUnscaledLogicPosition.PositionX;
+ const double fY = aUnscaledLogicPosition.PositionY;
+ const double fZ = aUnscaledLogicPosition.PositionZ;
+ double fScaledX = fX;
+ if( pfScaledLogicX )
+ fScaledX = *pfScaledLogicX;
+ else
+ m_pPosHelper->doLogicScaling( &fScaledX, nullptr, nullptr );
+
+ aMiddle = lcl_transformMixedToScene( m_pPosHelper, fScaledX, fY, fZ );
+
+ drawing::Position3D aNegative(aMiddle);
+ drawing::Position3D aPositive(aMiddle);
+
+ uno::Sequence< double > aData( bYError ? rVDataSeries.getAllY() : rVDataSeries.getAllX() );
+
+ if( bShowPositive )
+ {
+ double fLength = lcl_getErrorBarLogicLength( aData, xErrorBarProperties, nErrorBarStyle, nIndex, true, bYError );
+ if( std::isfinite( fLength ) )
+ {
+ double fLocalX = fX;
+ double fLocalY = fY;
+ if( bYError )
+ {
+ fLocalY+=fLength;
+ aPositive = lcl_transformMixedToScene( m_pPosHelper, fScaledX, fLocalY, fZ );
+ }
+ else
+ {
+ fLocalX+=fLength;
+ aPositive = m_pPosHelper->transformLogicToScene( fLocalX, fLocalY, fZ, true );
+ }
+ bCreatePositiveBorder = m_pPosHelper->isLogicVisible(fLocalX, fLocalY, fZ);
+ }
+ else
+ bShowPositive = false;
+ }
+
+ if( bShowNegative )
+ {
+ double fLength = lcl_getErrorBarLogicLength( aData, xErrorBarProperties, nErrorBarStyle, nIndex, false, bYError );
+ if( std::isfinite( fLength ) )
+ {
+ double fLocalX = fX;
+ double fLocalY = fY;
+ if( bYError )
+ {
+ fLocalY-=fLength;
+ aNegative = lcl_transformMixedToScene( m_pPosHelper, fScaledX, fLocalY, fZ );
+ }
+ else
+ {
+ fLocalX-=fLength;
+ aNegative = m_pPosHelper->transformLogicToScene( fLocalX, fLocalY, fZ, true );
+ }
+ if (std::isfinite(aNegative.PositionX) &&
+ std::isfinite(aNegative.PositionY) &&
+ std::isfinite(aNegative.PositionZ)) {
+ bCreateNegativeBorder = m_pPosHelper->isLogicVisible( fLocalX, fLocalY, fZ);
+ } else {
+ // If error bars result in a numerical problem (e.g., an
+ // error bar on a logarithmic chart that results in a point
+ // <= 0) then just turn off the error bar.
+ //
+ // TODO: This perhaps should display a warning, so the user
+ // knows why a bar is not appearing.
+ // TODO: This test could also be added to the positive case,
+ // though a numerical overflow there is less likely.
+ bShowNegative = false;
+ }
+ }
+ else
+ bShowNegative = false;
+ }
+
+ if(!bShowPositive && !bShowNegative)
+ return;
+
+ std::vector<std::vector<css::drawing::Position3D>> aPoly;
+
+ sal_Int32 nSequenceIndex=0;
+ if( bShowNegative )
+ AddPointToPoly( aPoly, aNegative, nSequenceIndex );
+ AddPointToPoly( aPoly, aMiddle, nSequenceIndex );
+ if( bShowPositive )
+ AddPointToPoly( aPoly, aPositive, nSequenceIndex );
+
+ if( bShowNegative && bCreateNegativeBorder )
+ {
+ ::basegfx::B2DVector aMainDirection = lcl_getErrorBarMainDirection( aMiddle, aNegative, m_pPosHelper, aUnscaledLogicPosition, bYError );
+ nSequenceIndex++;
+ lcl_AddErrorBottomLine( aNegative, aMainDirection, aPoly, nSequenceIndex );
+ }
+ if( bShowPositive && bCreatePositiveBorder )
+ {
+ ::basegfx::B2DVector aMainDirection = lcl_getErrorBarMainDirection( aMiddle, aPositive, m_pPosHelper, aUnscaledLogicPosition, bYError );
+ nSequenceIndex++;
+ lcl_AddErrorBottomLine( aPositive, aMainDirection, aPoly, nSequenceIndex );
+ }
+
+ rtl::Reference<SvxShapePolyPolygon> xShape = ShapeFactory::createLine2D( xTarget, aPoly );
+ PropertyMapper::setMappedProperties( *xShape, xErrorBarProperties, PropertyMapper::getPropertyNameMapForLineProperties() );
+ }
+ catch( const uno::Exception & )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+
+}
+
+void VSeriesPlotter::addErrorBorder(
+ const drawing::Position3D& rPos0
+ ,const drawing::Position3D& rPos1
+ ,const rtl::Reference<SvxShapeGroupAnyD>& rTarget
+ ,const uno::Reference< beans::XPropertySet >& rErrorBorderProp )
+{
+ std::vector<std::vector<css::drawing::Position3D>> aPoly { { rPos0, rPos1} };
+ rtl::Reference<SvxShapePolyPolygon> xShape = ShapeFactory::createLine2D(
+ rTarget, aPoly );
+ PropertyMapper::setMappedProperties( *xShape, rErrorBorderProp,
+ PropertyMapper::getPropertyNameMapForLineProperties() );
+}
+
+void VSeriesPlotter::createErrorRectangle(
+ const drawing::Position3D& rUnscaledLogicPosition
+ ,VDataSeries& rVDataSeries
+ ,sal_Int32 nIndex
+ ,const rtl::Reference<SvxShapeGroupAnyD>& rTarget
+ ,bool bUseXErrorData
+ ,bool bUseYErrorData )
+{
+ if ( m_nDimension != 2 )
+ return;
+
+ // error border properties
+ Reference< beans::XPropertySet > xErrorBorderPropX, xErrorBorderPropY;
+ if ( bUseXErrorData )
+ {
+ xErrorBorderPropX = rVDataSeries.getXErrorBarProperties( nIndex );
+ if ( !xErrorBorderPropX.is() )
+ return;
+ }
+ rtl::Reference<SvxShapeGroupAnyD> xErrorBorder_ShapesX =
+ getErrorBarsGroupShape( rVDataSeries, rTarget, false );
+
+ if ( bUseYErrorData )
+ {
+ xErrorBorderPropY = rVDataSeries.getYErrorBarProperties( nIndex );
+ if ( !xErrorBorderPropY.is() )
+ return;
+ }
+ rtl::Reference<SvxShapeGroupAnyD> xErrorBorder_ShapesY =
+ getErrorBarsGroupShape( rVDataSeries, rTarget, true );
+
+ if( !ChartTypeHelper::isSupportingStatisticProperties( m_xChartTypeModel, m_nDimension ) )
+ return;
+
+ try
+ {
+ bool bShowXPositive = false;
+ bool bShowXNegative = false;
+ bool bShowYPositive = false;
+ bool bShowYNegative = false;
+
+ sal_Int32 nErrorBorderStyleX = css::chart::ErrorBarStyle::VARIANCE;
+ sal_Int32 nErrorBorderStyleY = css::chart::ErrorBarStyle::VARIANCE;
+
+ if ( bUseXErrorData )
+ {
+ xErrorBorderPropX->getPropertyValue( "ErrorBarStyle" ) >>= nErrorBorderStyleX;
+ xErrorBorderPropX->getPropertyValue( "ShowPositiveError") >>= bShowXPositive;
+ xErrorBorderPropX->getPropertyValue( "ShowNegativeError") >>= bShowXNegative;
+ }
+ if ( bUseYErrorData )
+ {
+ xErrorBorderPropY->getPropertyValue( "ErrorBarStyle" ) >>= nErrorBorderStyleY;
+ xErrorBorderPropY->getPropertyValue( "ShowPositiveError") >>= bShowYPositive;
+ xErrorBorderPropY->getPropertyValue( "ShowNegativeError") >>= bShowYNegative;
+ }
+
+ if ( bUseXErrorData && nErrorBorderStyleX == css::chart::ErrorBarStyle::NONE )
+ bUseXErrorData = false;
+ if ( bUseYErrorData && nErrorBorderStyleY == css::chart::ErrorBarStyle::NONE )
+ bUseYErrorData = false;
+
+ if ( !bShowXPositive && !bShowXNegative && !bShowYPositive && !bShowYNegative )
+ return;
+
+ if ( !m_pPosHelper )
+ return;
+
+ drawing::Position3D aUnscaledLogicPosition( rUnscaledLogicPosition );
+ if ( bUseXErrorData && nErrorBorderStyleX == css::chart::ErrorBarStyle::STANDARD_DEVIATION )
+ aUnscaledLogicPosition.PositionX = rVDataSeries.getXMeanValue();
+ if ( bUseYErrorData && nErrorBorderStyleY == css::chart::ErrorBarStyle::STANDARD_DEVIATION )
+ aUnscaledLogicPosition.PositionY = rVDataSeries.getYMeanValue();
+
+ const double fX = aUnscaledLogicPosition.PositionX;
+ const double fY = aUnscaledLogicPosition.PositionY;
+ const double fZ = aUnscaledLogicPosition.PositionZ;
+ double fScaledX = fX;
+ m_pPosHelper->doLogicScaling( &fScaledX, nullptr, nullptr );
+
+ uno::Sequence< double > aDataX( rVDataSeries.getAllX() );
+ uno::Sequence< double > aDataY( rVDataSeries.getAllY() );
+
+ double fPosX = 0.0;
+ double fPosY = 0.0;
+ double fNegX = 0.0;
+ double fNegY = 0.0;
+ if ( bUseXErrorData )
+ {
+ if ( bShowXPositive )
+ fPosX = lcl_getErrorBarLogicLength( aDataX, xErrorBorderPropX,
+ nErrorBorderStyleX, nIndex, true, false );
+ if ( bShowXNegative )
+ fNegX = lcl_getErrorBarLogicLength( aDataX, xErrorBorderPropX,
+ nErrorBorderStyleX, nIndex, false, false );
+ }
+
+ if ( bUseYErrorData )
+ {
+ if ( bShowYPositive )
+ fPosY = lcl_getErrorBarLogicLength( aDataY, xErrorBorderPropY,
+ nErrorBorderStyleY, nIndex, true, true );
+ if ( bShowYNegative )
+ fNegY = lcl_getErrorBarLogicLength( aDataY, xErrorBorderPropY,
+ nErrorBorderStyleY, nIndex, false, true );
+ }
+
+ if ( !( std::isfinite( fPosX ) &&
+ std::isfinite( fPosY ) &&
+ std::isfinite( fNegX ) &&
+ std::isfinite( fNegY ) ) )
+ return;
+
+ drawing::Position3D aBottomLeft( lcl_transformMixedToScene( m_pPosHelper,
+ fX - fNegX, fY - fNegY, fZ ) );
+ drawing::Position3D aTopLeft( lcl_transformMixedToScene( m_pPosHelper,
+ fX - fNegX, fY + fPosY, fZ ) );
+ drawing::Position3D aTopRight( lcl_transformMixedToScene( m_pPosHelper,
+ fX + fPosX, fY + fPosY, fZ ) );
+ drawing::Position3D aBottomRight( lcl_transformMixedToScene( m_pPosHelper,
+ fX + fPosX, fY - fNegY, fZ ) );
+ if ( bUseXErrorData )
+ {
+ // top border
+ addErrorBorder( aTopLeft, aTopRight, xErrorBorder_ShapesX, xErrorBorderPropX );
+
+ // bottom border
+ addErrorBorder( aBottomRight, aBottomLeft, xErrorBorder_ShapesX, xErrorBorderPropX );
+ }
+
+ if ( bUseYErrorData )
+ {
+ // left border
+ addErrorBorder( aBottomLeft, aTopLeft, xErrorBorder_ShapesY, xErrorBorderPropY );
+
+ // right border
+ addErrorBorder( aTopRight, aBottomRight, xErrorBorder_ShapesY, xErrorBorderPropY );
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION("chart2", "Exception in createErrorRectangle(). ");
+ }
+}
+
+void VSeriesPlotter::createErrorBar_X( const drawing::Position3D& rUnscaledLogicPosition
+ , VDataSeries& rVDataSeries, sal_Int32 nPointIndex
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
+{
+ if(m_nDimension!=2)
+ return;
+ // error bars
+ uno::Reference< beans::XPropertySet > xErrorBarProp(rVDataSeries.getXErrorBarProperties(nPointIndex));
+ if( xErrorBarProp.is())
+ {
+ rtl::Reference<SvxShapeGroupAnyD> xErrorBarsGroup_Shapes =
+ getErrorBarsGroupShape(rVDataSeries, xTarget, false);
+
+ createErrorBar( xErrorBarsGroup_Shapes
+ , rUnscaledLogicPosition, xErrorBarProp
+ , rVDataSeries, nPointIndex
+ , false /* bYError */
+ , nullptr );
+ }
+}
+
+void VSeriesPlotter::createErrorBar_Y( const drawing::Position3D& rUnscaledLogicPosition
+ , VDataSeries& rVDataSeries, sal_Int32 nPointIndex
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , double const * pfScaledLogicX )
+{
+ if(m_nDimension!=2)
+ return;
+ // error bars
+ uno::Reference< beans::XPropertySet > xErrorBarProp(rVDataSeries.getYErrorBarProperties(nPointIndex));
+ if( xErrorBarProp.is())
+ {
+ rtl::Reference<SvxShapeGroupAnyD> xErrorBarsGroup_Shapes =
+ getErrorBarsGroupShape(rVDataSeries, xTarget, true);
+
+ createErrorBar( xErrorBarsGroup_Shapes
+ , rUnscaledLogicPosition, xErrorBarProp
+ , rVDataSeries, nPointIndex
+ , true /* bYError */
+ , pfScaledLogicX );
+ }
+}
+
+void VSeriesPlotter::createRegressionCurvesShapes( VDataSeries const & rVDataSeries,
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xEquationTarget,
+ bool bMaySkipPoints )
+{
+ if(m_nDimension!=2)
+ return;
+ rtl::Reference< DataSeries > xContainer( rVDataSeries.getModel() );
+ if(!xContainer.is())
+ return;
+
+ if (!m_pPosHelper)
+ return;
+
+ const std::vector< rtl::Reference< ::chart::RegressionCurveModel > > & aCurveList = xContainer->getRegressionCurves2();
+
+ for(std::size_t nN=0; nN<aCurveList.size(); nN++)
+ {
+ const auto & rCurve = aCurveList[nN];
+ uno::Reference< XRegressionCurveCalculator > xCalculator( rCurve->getCalculator() );
+ if( !xCalculator.is())
+ continue;
+
+ bool bAverageLine = RegressionCurveHelper::isMeanValueLine( rCurve );
+
+ sal_Int32 aDegree = 2;
+ sal_Int32 aPeriod = 2;
+ sal_Int32 aMovingAverageType = css::chart2::MovingAverageType::Prior;
+ double aExtrapolateForward = 0.0;
+ double aExtrapolateBackward = 0.0;
+ bool bForceIntercept = false;
+ double aInterceptValue = 0.0;
+
+ if ( !bAverageLine )
+ {
+ rCurve->getPropertyValue( "PolynomialDegree") >>= aDegree;
+ rCurve->getPropertyValue( "MovingAveragePeriod") >>= aPeriod;
+ rCurve->getPropertyValue( "MovingAverageType") >>= aMovingAverageType;
+ rCurve->getPropertyValue( "ExtrapolateForward") >>= aExtrapolateForward;
+ rCurve->getPropertyValue( "ExtrapolateBackward") >>= aExtrapolateBackward;
+ rCurve->getPropertyValue( "ForceIntercept") >>= bForceIntercept;
+ if (bForceIntercept)
+ rCurve->getPropertyValue( "InterceptValue") >>= aInterceptValue;
+ }
+
+ double fChartMinX = m_pPosHelper->getLogicMinX();
+ double fChartMaxX = m_pPosHelper->getLogicMaxX();
+
+ double fMinX = fChartMinX;
+ double fMaxX = fChartMaxX;
+
+ double fPointScale = 1.0;
+
+ if( !bAverageLine )
+ {
+ rVDataSeries.getMinMaxXValue(fMinX, fMaxX);
+ fMaxX += aExtrapolateForward;
+ fMinX -= aExtrapolateBackward;
+
+ fPointScale = (fMaxX - fMinX) / (fChartMaxX - fChartMinX);
+ // sanitize the value, tdf#119922
+ fPointScale = std::min(fPointScale, 1000.0);
+ }
+
+ xCalculator->setRegressionProperties(aDegree, bForceIntercept, aInterceptValue, aPeriod,
+ aMovingAverageType);
+ xCalculator->recalculateRegression(rVDataSeries.getAllX(), rVDataSeries.getAllY());
+ sal_Int32 nPointCount = 100 * fPointScale;
+
+ if ( nPointCount < 2 )
+ nPointCount = 2;
+
+ std::vector< ExplicitScaleData > aScales( m_pPosHelper->getScales());
+ uno::Reference< chart2::XScaling > xScalingX;
+ uno::Reference< chart2::XScaling > xScalingY;
+ if( aScales.size() >= 2 )
+ {
+ xScalingX.set( aScales[0].Scaling );
+ xScalingY.set( aScales[1].Scaling );
+ }
+
+ const uno::Sequence< geometry::RealPoint2D > aCalculatedPoints(
+ xCalculator->getCurveValues(
+ fMinX, fMaxX, nPointCount,
+ xScalingX, xScalingY, bMaySkipPoints ));
+
+ nPointCount = aCalculatedPoints.getLength();
+
+ drawing::PolyPolygonShape3D aRegressionPoly;
+ aRegressionPoly.SequenceX.realloc(1);
+ aRegressionPoly.SequenceY.realloc(1);
+ aRegressionPoly.SequenceZ.realloc(1);
+ auto pSequenceX = aRegressionPoly.SequenceX.getArray();
+ auto pSequenceY = aRegressionPoly.SequenceY.getArray();
+ auto pSequenceZ = aRegressionPoly.SequenceZ.getArray();
+ pSequenceX[0].realloc(nPointCount);
+ pSequenceY[0].realloc(nPointCount);
+ auto pSequenceX0 = pSequenceX[0].getArray();
+ auto pSequenceY0 = pSequenceY[0].getArray();
+
+ sal_Int32 nRealPointCount = 0;
+
+ for(geometry::RealPoint2D const & p : aCalculatedPoints)
+ {
+ double fLogicX = p.X;
+ double fLogicY = p.Y;
+ double fLogicZ = 0.0; //dummy
+
+ // fdo#51656: don't scale mean value lines
+ if(!bAverageLine)
+ m_pPosHelper->doLogicScaling( &fLogicX, &fLogicY, &fLogicZ );
+
+ if(!std::isnan(fLogicX) && !std::isinf(fLogicX) &&
+ !std::isnan(fLogicY) && !std::isinf(fLogicY) &&
+ !std::isnan(fLogicZ) && !std::isinf(fLogicZ) )
+ {
+ pSequenceX0[nRealPointCount] = fLogicX;
+ pSequenceY0[nRealPointCount] = fLogicY;
+ nRealPointCount++;
+ }
+ }
+ pSequenceX[0].realloc(nRealPointCount);
+ pSequenceY[0].realloc(nRealPointCount);
+ pSequenceZ[0].realloc(nRealPointCount);
+
+ drawing::PolyPolygonShape3D aClippedPoly;
+ Clipping::clipPolygonAtRectangle( aRegressionPoly, m_pPosHelper->getScaledLogicClipDoubleRect(), aClippedPoly );
+ aRegressionPoly = aClippedPoly;
+ m_pPosHelper->transformScaledLogicToScene( aRegressionPoly );
+
+ awt::Point aDefaultPos;
+ if( aRegressionPoly.SequenceX.hasElements() && aRegressionPoly.SequenceX[0].hasElements() )
+ {
+ VLineProperties aVLineProperties;
+ aVLineProperties.initFromPropertySet( rCurve );
+
+ //create an extra group shape for each curve for selection handling
+ rtl::Reference<SvxShapeGroupAnyD> xRegressionGroupShapes =
+ createGroupShape( xTarget, rVDataSeries.getDataCurveCID( nN, bAverageLine ) );
+ rtl::Reference<SvxShapePolyPolygon> xShape = ShapeFactory::createLine2D(
+ xRegressionGroupShapes, PolyToPointSequence( aRegressionPoly ), &aVLineProperties );
+ ShapeFactory::setShapeName( xShape, "MarkHandles" );
+ aDefaultPos = xShape->getPosition();
+ }
+
+ // curve equation and correlation coefficient
+ uno::Reference< beans::XPropertySet > xEquationProperties( rCurve->getEquationProperties());
+ if( xEquationProperties.is())
+ {
+ createRegressionCurveEquationShapes(
+ rVDataSeries.getDataCurveEquationCID( nN ),
+ xEquationProperties, xEquationTarget, xCalculator,
+ aDefaultPos );
+ }
+ }
+}
+
+static sal_Int32 lcl_getOUStringMaxLineLength ( OUStringBuffer const & aString )
+{
+ const sal_Int32 nStringLength = aString.getLength();
+ sal_Int32 nMaxLineLength = 0;
+
+ for ( sal_Int32 i=0; i<nStringLength; i++ )
+ {
+ sal_Int32 indexSep = aString.indexOf( "\n", i );
+ if ( indexSep < 0 )
+ indexSep = nStringLength;
+ sal_Int32 nLineLength = indexSep - i;
+ if ( nLineLength > nMaxLineLength )
+ nMaxLineLength = nLineLength;
+ i = indexSep;
+ }
+
+ return nMaxLineLength;
+}
+
+void VSeriesPlotter::createRegressionCurveEquationShapes(
+ const OUString & rEquationCID,
+ const uno::Reference< beans::XPropertySet > & xEquationProperties,
+ const rtl::Reference<SvxShapeGroupAnyD>& xEquationTarget,
+ const uno::Reference< chart2::XRegressionCurveCalculator > & xRegressionCurveCalculator,
+ awt::Point aDefaultPos )
+{
+ OSL_ASSERT( xEquationProperties.is());
+ if( !xEquationProperties.is())
+ return;
+
+ bool bShowEquation = false;
+ bool bShowCorrCoeff = false;
+ if(!(( xEquationProperties->getPropertyValue( "ShowEquation") >>= bShowEquation ) &&
+ ( xEquationProperties->getPropertyValue( "ShowCorrelationCoefficient") >>= bShowCorrCoeff )))
+ return;
+
+ if( ! (bShowEquation || bShowCorrCoeff))
+ return;
+
+ OUStringBuffer aFormula;
+ sal_Int32 nNumberFormatKey = 0;
+ sal_Int32 nFormulaWidth = 0;
+ xEquationProperties->getPropertyValue(CHART_UNONAME_NUMFMT) >>= nNumberFormatKey;
+ bool bResizeEquation = true;
+ sal_Int32 nMaxIteration = 2;
+ if ( bShowEquation )
+ {
+ OUString aXName, aYName;
+ if ( !(xEquationProperties->getPropertyValue( "XName" ) >>= aXName) )
+ aXName = OUString( "x" );
+ if ( !(xEquationProperties->getPropertyValue( "YName" ) >>= aYName) )
+ aYName = OUString( "f(x)" );
+ xRegressionCurveCalculator->setXYNames( aXName, aYName );
+ }
+
+ for ( sal_Int32 nCountIteration = 0; bResizeEquation && nCountIteration < nMaxIteration ; nCountIteration++ )
+ {
+ bResizeEquation = false;
+ if( bShowEquation )
+ {
+ if (m_apNumberFormatterWrapper)
+ { // iteration 0: default representation (no wrap)
+ // iteration 1: expected width (nFormulaWidth) is calculated
+ aFormula = xRegressionCurveCalculator->getFormattedRepresentation(
+ m_apNumberFormatterWrapper->getNumberFormatsSupplier(),
+ nNumberFormatKey, nFormulaWidth );
+ nFormulaWidth = lcl_getOUStringMaxLineLength( aFormula );
+ }
+ else
+ {
+ aFormula = xRegressionCurveCalculator->getRepresentation();
+ }
+
+ if( bShowCorrCoeff )
+ {
+ aFormula.append( "\n" );
+ }
+ }
+ if( bShowCorrCoeff )
+ {
+ aFormula.append( "R" + OUStringChar( aSuperscriptFigures[2] ) + " = " );
+ double fR( xRegressionCurveCalculator->getCorrelationCoefficient());
+ if (m_apNumberFormatterWrapper)
+ {
+ Color nLabelCol;
+ bool bColChanged;
+ aFormula.append(
+ m_apNumberFormatterWrapper->getFormattedString(
+ nNumberFormatKey, fR*fR, nLabelCol, bColChanged ));
+ //@todo: change color of label if bColChanged is true
+ }
+ else
+ {
+ const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
+ const OUString& aNumDecimalSep = rLocaleDataWrapper.getNumDecimalSep();
+ sal_Unicode aDecimalSep = aNumDecimalSep[0];
+ aFormula.append( ::rtl::math::doubleToUString(
+ fR*fR, rtl_math_StringFormat_G, 4, aDecimalSep, true ));
+ }
+ }
+
+ awt::Point aScreenPosition2D;
+ chart2::RelativePosition aRelativePosition;
+ if( xEquationProperties->getPropertyValue( "RelativePosition") >>= aRelativePosition )
+ {
+ //@todo decide whether x is primary or secondary
+ double fX = aRelativePosition.Primary*m_aPageReferenceSize.Width;
+ double fY = aRelativePosition.Secondary*m_aPageReferenceSize.Height;
+ aScreenPosition2D.X = static_cast< sal_Int32 >( ::rtl::math::round( fX ));
+ aScreenPosition2D.Y = static_cast< sal_Int32 >( ::rtl::math::round( fY ));
+ }
+ else
+ aScreenPosition2D = aDefaultPos;
+
+ if( !aFormula.isEmpty())
+ {
+ // set fill and line properties on creation
+ tNameSequence aNames;
+ tAnySequence aValues;
+ PropertyMapper::getPreparedTextShapePropertyLists( xEquationProperties, aNames, aValues );
+
+ rtl::Reference<SvxShapeText> xTextShape = ShapeFactory::createText(
+ xEquationTarget, aFormula.makeStringAndClear(),
+ aNames, aValues, ShapeFactory::makeTransformation( aScreenPosition2D ));
+
+ ShapeFactory::setShapeName( xTextShape, rEquationCID );
+ awt::Size aSize( xTextShape->getSize() );
+ awt::Point aPos( RelativePositionHelper::getUpperLeftCornerOfAnchoredObject(
+ aScreenPosition2D, aSize, aRelativePosition.Anchor ) );
+ //ensure that the equation is fully placed within the page (if possible)
+ if( (aPos.X + aSize.Width) > m_aPageReferenceSize.Width )
+ aPos.X = m_aPageReferenceSize.Width - aSize.Width;
+ if( aPos.X < 0 )
+ {
+ aPos.X = 0;
+ if ( nFormulaWidth > 0 )
+ {
+ bResizeEquation = true;
+ if ( nCountIteration < nMaxIteration-1 )
+ xEquationTarget->remove( xTextShape ); // remove equation
+ nFormulaWidth *= m_aPageReferenceSize.Width / static_cast< double >(aSize.Width);
+ nFormulaWidth -= nCountIteration;
+ if ( nFormulaWidth < 0 )
+ nFormulaWidth = 0;
+ }
+ }
+ if( (aPos.Y + aSize.Height) > m_aPageReferenceSize.Height )
+ aPos.Y = m_aPageReferenceSize.Height - aSize.Height;
+ if( aPos.Y < 0 )
+ aPos.Y = 0;
+ if ( !bResizeEquation || nCountIteration == nMaxIteration-1 )
+ xTextShape->setPosition(aPos); // if equation was not removed
+ }
+ }
+}
+
+void VSeriesPlotter::setTimeResolutionOnXAxis( tools::Long TimeResolution, const Date& rNullDate )
+{
+ m_nTimeResolution = TimeResolution;
+ m_aNullDate = rNullDate;
+}
+
+// MinimumAndMaximumSupplier
+tools::Long VSeriesPlotter::calculateTimeResolutionOnXAxis()
+{
+ tools::Long nRet = css::chart::TimeUnit::YEAR;
+ if (!m_pExplicitCategoriesProvider)
+ return nRet;
+
+ const std::vector<double>& rDateCategories = m_pExplicitCategoriesProvider->getDateCategories();
+ if (rDateCategories.empty())
+ return nRet;
+
+ std::vector<double>::const_iterator aIt = rDateCategories.begin(), aEnd = rDateCategories.end();
+
+ aIt = std::find_if(aIt, aEnd, [](const double& rDateCategory) { return !std::isnan(rDateCategory); });
+ if (aIt == aEnd)
+ return nRet;
+
+ Date aNullDate(30,12,1899);
+ if (m_apNumberFormatterWrapper)
+ aNullDate = m_apNumberFormatterWrapper->getNullDate();
+
+ Date aPrevious(aNullDate); aPrevious.AddDays(rtl::math::approxFloor(*aIt));
+ ++aIt;
+ for(;aIt!=aEnd;++aIt)
+ {
+ if (std::isnan(*aIt))
+ continue;
+
+ Date aCurrent(aNullDate); aCurrent.AddDays(rtl::math::approxFloor(*aIt));
+ if( nRet == css::chart::TimeUnit::YEAR )
+ {
+ if( DateHelper::IsInSameYear( aPrevious, aCurrent ) )
+ nRet = css::chart::TimeUnit::MONTH;
+ }
+ if( nRet == css::chart::TimeUnit::MONTH )
+ {
+ if( DateHelper::IsInSameMonth( aPrevious, aCurrent ) )
+ nRet = css::chart::TimeUnit::DAY;
+ }
+ if( nRet == css::chart::TimeUnit::DAY )
+ break;
+ aPrevious=aCurrent;
+ }
+
+ return nRet;
+}
+double VSeriesPlotter::getMinimumX()
+{
+ double fMinimum, fMaximum;
+ getMinimumAndMaximumX( fMinimum, fMaximum );
+ return fMinimum;
+}
+double VSeriesPlotter::getMaximumX()
+{
+ double fMinimum, fMaximum;
+ getMinimumAndMaximumX( fMinimum, fMaximum );
+ return fMaximum;
+}
+
+double VSeriesPlotter::getMinimumYInRange( double fMinimumX, double fMaximumX, sal_Int32 nAxisIndex )
+{
+ if( !m_bCategoryXAxis || ( m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis() ) )
+ {
+ double fMinY, fMaxY;
+ getMinimumAndMaximumYInContinuousXRange( fMinY, fMaxY, fMinimumX, fMaximumX, nAxisIndex );
+ return fMinY;
+ }
+
+ double fMinimum = std::numeric_limits<double>::infinity();
+ double fMaximum = -std::numeric_limits<double>::infinity();
+ for(std::vector<VDataSeriesGroup> & rXSlots : m_aZSlots)
+ {
+ for(VDataSeriesGroup & rXSlot : rXSlots)
+ {
+ double fLocalMinimum, fLocalMaximum;
+ rXSlot.calculateYMinAndMaxForCategoryRange(
+ static_cast<sal_Int32>(fMinimumX-1.0) //first category (index 0) matches with real number 1.0
+ , static_cast<sal_Int32>(fMaximumX-1.0) //first category (index 0) matches with real number 1.0
+ , isSeparateStackingForDifferentSigns( 1 )
+ , fLocalMinimum, fLocalMaximum, nAxisIndex );
+ if(fMaximum<fLocalMaximum)
+ fMaximum=fLocalMaximum;
+ if(fMinimum>fLocalMinimum)
+ fMinimum=fLocalMinimum;
+ }
+ }
+ if(std::isinf(fMinimum))
+ return std::numeric_limits<double>::quiet_NaN();
+ return fMinimum;
+}
+
+double VSeriesPlotter::getMaximumYInRange( double fMinimumX, double fMaximumX, sal_Int32 nAxisIndex )
+{
+ if( !m_bCategoryXAxis || ( m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis() ) )
+ {
+ double fMinY, fMaxY;
+ getMinimumAndMaximumYInContinuousXRange( fMinY, fMaxY, fMinimumX, fMaximumX, nAxisIndex );
+ return fMaxY;
+ }
+
+ double fMinimum = std::numeric_limits<double>::infinity();
+ double fMaximum = -std::numeric_limits<double>::infinity();
+ for( std::vector< VDataSeriesGroup > & rXSlots : m_aZSlots)
+ {
+ for(VDataSeriesGroup & rXSlot : rXSlots)
+ {
+ double fLocalMinimum, fLocalMaximum;
+ rXSlot.calculateYMinAndMaxForCategoryRange(
+ static_cast<sal_Int32>(fMinimumX-1.0) //first category (index 0) matches with real number 1.0
+ , static_cast<sal_Int32>(fMaximumX-1.0) //first category (index 0) matches with real number 1.0
+ , isSeparateStackingForDifferentSigns( 1 )
+ , fLocalMinimum, fLocalMaximum, nAxisIndex );
+ if(fMaximum<fLocalMaximum)
+ fMaximum=fLocalMaximum;
+ if(fMinimum>fLocalMinimum)
+ fMinimum=fLocalMinimum;
+ }
+ }
+ if(std::isinf(fMaximum))
+ return std::numeric_limits<double>::quiet_NaN();
+ return fMaximum;
+}
+
+double VSeriesPlotter::getMinimumZ()
+{
+ //this is the default for all charts without a meaningful z axis
+ return 1.0;
+}
+double VSeriesPlotter::getMaximumZ()
+{
+ if( m_nDimension!=3 || m_aZSlots.empty() )
+ return getMinimumZ()+1;
+ return m_aZSlots.size();
+}
+
+namespace
+{
+ bool lcl_isValueAxis( sal_Int32 nDimensionIndex, bool bCategoryXAxis )
+ {
+ // default implementation: true for Y axes, and for value X axis
+ if( nDimensionIndex == 0 )
+ return !bCategoryXAxis;
+ return nDimensionIndex == 1;
+ }
+}
+
+bool VSeriesPlotter::isExpandBorderToIncrementRhythm( sal_Int32 nDimensionIndex )
+{
+ return lcl_isValueAxis( nDimensionIndex, m_bCategoryXAxis );
+}
+
+bool VSeriesPlotter::isExpandIfValuesCloseToBorder( sal_Int32 nDimensionIndex )
+{
+ // do not expand axes in 3D charts
+ return (m_nDimension < 3) && lcl_isValueAxis( nDimensionIndex, m_bCategoryXAxis );
+}
+
+bool VSeriesPlotter::isExpandWideValuesToZero( sal_Int32 nDimensionIndex )
+{
+ // default implementation: only for Y axis
+ return nDimensionIndex == 1;
+}
+
+bool VSeriesPlotter::isExpandNarrowValuesTowardZero( sal_Int32 nDimensionIndex )
+{
+ // default implementation: only for Y axis
+ return nDimensionIndex == 1;
+}
+
+bool VSeriesPlotter::isSeparateStackingForDifferentSigns( sal_Int32 nDimensionIndex )
+{
+ // default implementation: only for Y axis
+ return nDimensionIndex == 1;
+}
+
+void VSeriesPlotter::getMinimumAndMaximumX( double& rfMinimum, double& rfMaximum ) const
+{
+ rfMinimum = std::numeric_limits<double>::infinity();
+ rfMaximum = -std::numeric_limits<double>::infinity();
+
+ for (auto const& ZSlot : m_aZSlots)
+ {
+ for (auto const& XSlot : ZSlot)
+ {
+ double fLocalMinimum, fLocalMaximum;
+ XSlot.getMinimumAndMaximumX( fLocalMinimum, fLocalMaximum );
+ if( !std::isnan(fLocalMinimum) && fLocalMinimum< rfMinimum )
+ rfMinimum = fLocalMinimum;
+ if( !std::isnan(fLocalMaximum) && fLocalMaximum> rfMaximum )
+ rfMaximum = fLocalMaximum;
+ }
+ }
+ if(std::isinf(rfMinimum))
+ rfMinimum = std::numeric_limits<double>::quiet_NaN();
+ if(std::isinf(rfMaximum))
+ rfMaximum = std::numeric_limits<double>::quiet_NaN();
+}
+
+void VSeriesPlotter::getMinimumAndMaximumYInContinuousXRange( double& rfMinY, double& rfMaxY, double fMinX, double fMaxX, sal_Int32 nAxisIndex ) const
+{
+ rfMinY = std::numeric_limits<double>::infinity();
+ rfMaxY = -std::numeric_limits<double>::infinity();
+
+ for (auto const& ZSlot : m_aZSlots)
+ {
+ for (auto const& XSlot : ZSlot)
+ {
+ double fLocalMinimum, fLocalMaximum;
+ XSlot.getMinimumAndMaximumYInContinuousXRange( fLocalMinimum, fLocalMaximum, fMinX, fMaxX, nAxisIndex );
+ if( !std::isnan(fLocalMinimum) && fLocalMinimum< rfMinY )
+ rfMinY = fLocalMinimum;
+ if( !std::isnan(fLocalMaximum) && fLocalMaximum> rfMaxY )
+ rfMaxY = fLocalMaximum;
+ }
+ }
+ if(std::isinf(rfMinY))
+ rfMinY = std::numeric_limits<double>::quiet_NaN();
+ if(std::isinf(rfMaxY))
+ rfMaxY = std::numeric_limits<double>::quiet_NaN();
+}
+
+sal_Int32 VSeriesPlotter::getPointCount() const
+{
+ sal_Int32 nRet = 0;
+
+ for (auto const& ZSlot : m_aZSlots)
+ {
+ for (auto const& XSlot : ZSlot)
+ {
+ sal_Int32 nPointCount = XSlot.getPointCount();
+ if( nPointCount>nRet )
+ nRet = nPointCount;
+ }
+ }
+ return nRet;
+}
+
+void VSeriesPlotter::setNumberFormatsSupplier(
+ const uno::Reference< util::XNumberFormatsSupplier > & xNumFmtSupplier )
+{
+ m_apNumberFormatterWrapper.reset( new NumberFormatterWrapper( xNumFmtSupplier ));
+}
+
+void VSeriesPlotter::setColorScheme( const uno::Reference< XColorScheme >& xColorScheme )
+{
+ m_xColorScheme = xColorScheme;
+}
+
+void VSeriesPlotter::setExplicitCategoriesProvider( ExplicitCategoriesProvider* pExplicitCategoriesProvider )
+{
+ m_pExplicitCategoriesProvider = pExplicitCategoriesProvider;
+}
+
+sal_Int32 VDataSeriesGroup::getPointCount() const
+{
+ if(!m_bMaxPointCountDirty)
+ return m_nMaxPointCount;
+
+ sal_Int32 nRet = 0;
+
+ for (std::unique_ptr<VDataSeries> const & pSeries : m_aSeriesVector)
+ {
+ sal_Int32 nPointCount = pSeries->getTotalPointCount();
+ if( nPointCount>nRet )
+ nRet = nPointCount;
+ }
+ m_nMaxPointCount=nRet;
+ m_aListOfCachedYValues.clear();
+ m_aListOfCachedYValues.resize(m_nMaxPointCount);
+ m_bMaxPointCountDirty=false;
+ return nRet;
+}
+
+sal_Int32 VDataSeriesGroup::getAttachedAxisIndexForFirstSeries() const
+{
+ sal_Int32 nRet = 0;
+
+ if (!m_aSeriesVector.empty())
+ nRet = m_aSeriesVector[0]->getAttachedAxisIndex();
+
+ return nRet;
+}
+
+void VDataSeriesGroup::getMinimumAndMaximumX( double& rfMinimum, double& rfMaximum ) const
+{
+
+ rfMinimum = std::numeric_limits<double>::infinity();
+ rfMaximum = -std::numeric_limits<double>::infinity();
+
+ for (std::unique_ptr<VDataSeries> const & pSeries : m_aSeriesVector)
+ {
+ sal_Int32 nPointCount = pSeries->getTotalPointCount();
+ for(sal_Int32 nN=0;nN<nPointCount;nN++)
+ {
+ double fX = pSeries->getXValue( nN );
+ if( std::isnan(fX) )
+ continue;
+ if(rfMaximum<fX)
+ rfMaximum=fX;
+ if(rfMinimum>fX)
+ rfMinimum=fX;
+ }
+ }
+ if(std::isinf(rfMinimum))
+ rfMinimum = std::numeric_limits<double>::quiet_NaN();
+ if(std::isinf(rfMaximum))
+ rfMaximum = std::numeric_limits<double>::quiet_NaN();
+}
+
+namespace {
+
+/**
+ * Keep track of minimum and maximum Y values for one or more data series.
+ * When multiple data series exist, that indicates that the data series are
+ * stacked.
+ *
+ * <p>For each X value, we calculate separate Y value ranges for each data
+ * series in the first pass. In the second pass, we calculate the minimum Y
+ * value by taking the absolute minimum value of all data series, whereas
+ * the maximum Y value is the sum of all the series maximum Y values.</p>
+ *
+ * <p>Once that's done for all X values, the final min / max Y values get
+ * calculated by taking the absolute min / max Y values across all the X
+ * values.</p>
+ */
+class PerXMinMaxCalculator
+{
+ typedef std::pair<double, double> MinMaxType;
+ typedef std::map<size_t, MinMaxType> SeriesMinMaxType;
+ typedef std::map<double, SeriesMinMaxType> GroupMinMaxType;
+ typedef std::unordered_map<double, MinMaxType> TotalStoreType;
+ GroupMinMaxType m_SeriesGroup;
+ size_t mnCurSeries;
+
+public:
+ PerXMinMaxCalculator() : mnCurSeries(0) {}
+
+ void nextSeries() { ++mnCurSeries; }
+
+ void setValue(double fX, double fY)
+ {
+ SeriesMinMaxType* pStore = getByXValue(fX); // get storage for given X value.
+ if (!pStore)
+ // This shouldn't happen!
+ return;
+
+ SeriesMinMaxType::iterator it = pStore->lower_bound(mnCurSeries);
+ if (it != pStore->end() && !pStore->key_comp()(mnCurSeries, it->first))
+ {
+ MinMaxType& r = it->second;
+ // A min-max pair already exists for this series. Update it.
+ if (fY < r.first)
+ r.first = fY;
+ if (r.second < fY)
+ r.second = fY;
+ }
+ else
+ {
+ // No existing pair. Insert a new one.
+ pStore->insert(
+ it, SeriesMinMaxType::value_type(
+ mnCurSeries, MinMaxType(fY,fY)));
+ }
+ }
+
+ void getTotalRange(double& rfMin, double& rfMax) const
+ {
+ TotalStoreType aStore;
+ getTotalStore(aStore);
+
+ if (aStore.empty())
+ {
+ rfMin = std::numeric_limits<double>::quiet_NaN();
+ rfMax = std::numeric_limits<double>::quiet_NaN();
+ return;
+ }
+
+ TotalStoreType::const_iterator it = aStore.begin(), itEnd = aStore.end();
+ rfMin = it->second.first;
+ rfMax = it->second.second;
+ for (++it; it != itEnd; ++it)
+ {
+ if (rfMin > it->second.first)
+ rfMin = it->second.first;
+ if (rfMax < it->second.second)
+ rfMax = it->second.second;
+ }
+ }
+
+private:
+ /**
+ * Parse all data and reduce them into a set of global Y value ranges per
+ * X value.
+ */
+ void getTotalStore(TotalStoreType& rStore) const
+ {
+ TotalStoreType aStore;
+ for (auto const& it : m_SeriesGroup)
+ {
+ double fX = it.first;
+
+ const SeriesMinMaxType& rSeries = it.second;
+ for (auto const& series : rSeries)
+ {
+ double fYMin = series.second.first, fYMax = series.second.second;
+ TotalStoreType::iterator itr = aStore.find(fX);
+ if (itr == aStore.end())
+ // New min-max pair for give X value.
+ aStore.emplace(fX, std::pair<double,double>(fYMin,fYMax));
+ else
+ {
+ MinMaxType& r = itr->second;
+ if (fYMin < r.first)
+ r.first = fYMin; // min y-value
+
+ r.second += fYMax; // accumulative max y-value.
+ }
+ }
+ }
+ rStore.swap(aStore);
+ }
+
+ SeriesMinMaxType* getByXValue(double fX)
+ {
+ GroupMinMaxType::iterator it = m_SeriesGroup.find(fX);
+ if (it == m_SeriesGroup.end())
+ {
+ std::pair<GroupMinMaxType::iterator,bool> r =
+ m_SeriesGroup.insert(std::make_pair(fX, SeriesMinMaxType{}));
+
+ if (!r.second)
+ // insertion failed.
+ return nullptr;
+
+ it = r.first;
+ }
+
+ return &it->second;
+ }
+};
+
+}
+
+void VDataSeriesGroup::getMinimumAndMaximumYInContinuousXRange(
+ double& rfMinY, double& rfMaxY, double fMinX, double fMaxX, sal_Int32 nAxisIndex ) const
+{
+ rfMinY = std::numeric_limits<double>::quiet_NaN();
+ rfMaxY = std::numeric_limits<double>::quiet_NaN();
+
+ if (m_aSeriesVector.empty())
+ // No data series. Bail out.
+ return;
+
+ PerXMinMaxCalculator aRangeCalc;
+ for (const std::unique_ptr<VDataSeries> & pSeries : m_aSeriesVector)
+ {
+ if (!pSeries)
+ continue;
+
+ for (sal_Int32 i = 0, n = pSeries->getTotalPointCount(); i < n; ++i)
+ {
+ if (nAxisIndex != pSeries->getAttachedAxisIndex())
+ continue;
+
+ double fX = pSeries->getXValue(i);
+ if (std::isnan(fX))
+ continue;
+
+ if (fX < fMinX || fX > fMaxX)
+ // Outside specified X range. Skip it.
+ continue;
+
+ double fY = pSeries->getYValue(i);
+ if (std::isnan(fY))
+ continue;
+
+ aRangeCalc.setValue(fX, fY);
+ }
+ aRangeCalc.nextSeries();
+ }
+
+ aRangeCalc.getTotalRange(rfMinY, rfMaxY);
+}
+
+void VDataSeriesGroup::calculateYMinAndMaxForCategory( sal_Int32 nCategoryIndex
+ , bool bSeparateStackingForDifferentSigns
+ , double& rfMinimumY, double& rfMaximumY, sal_Int32 nAxisIndex ) const
+{
+ assert(nCategoryIndex >= 0);
+ assert(nCategoryIndex < getPointCount());
+
+ rfMinimumY = std::numeric_limits<double>::infinity();
+ rfMaximumY = -std::numeric_limits<double>::infinity();
+
+ if(m_aSeriesVector.empty())
+ return;
+
+ CachedYValues aCachedYValues = m_aListOfCachedYValues[nCategoryIndex][nAxisIndex];
+ if( !aCachedYValues.m_bValuesDirty )
+ {
+ //return cached values
+ rfMinimumY = aCachedYValues.m_fMinimumY;
+ rfMaximumY = aCachedYValues.m_fMaximumY;
+ return;
+ }
+
+ double fTotalSum = std::numeric_limits<double>::quiet_NaN();
+ double fPositiveSum = std::numeric_limits<double>::quiet_NaN();
+ double fNegativeSum = std::numeric_limits<double>::quiet_NaN();
+ double fFirstPositiveY = std::numeric_limits<double>::quiet_NaN();
+ double fFirstNegativeY = std::numeric_limits<double>::quiet_NaN();
+
+ if( bSeparateStackingForDifferentSigns )
+ {
+ for (const std::unique_ptr<VDataSeries> & pSeries: m_aSeriesVector)
+ {
+ if( nAxisIndex != pSeries->getAttachedAxisIndex() )
+ continue;
+
+ double fValueMinY = pSeries->getMinimumofAllDifferentYValues( nCategoryIndex );
+ double fValueMaxY = pSeries->getMaximumofAllDifferentYValues( nCategoryIndex );
+
+ if( fValueMaxY >= 0 )
+ {
+ if( std::isnan( fPositiveSum ) )
+ fPositiveSum = fFirstPositiveY = fValueMaxY;
+ else
+ fPositiveSum += fValueMaxY;
+ }
+ if( fValueMinY < 0 )
+ {
+ if(std::isnan( fNegativeSum ))
+ fNegativeSum = fFirstNegativeY = fValueMinY;
+ else
+ fNegativeSum += fValueMinY;
+ }
+ }
+ rfMinimumY = std::isnan( fNegativeSum ) ? fFirstPositiveY : fNegativeSum;
+ rfMaximumY = std::isnan( fPositiveSum ) ? fFirstNegativeY : fPositiveSum;
+ }
+ else
+ {
+ for (const std::unique_ptr<VDataSeries> & pSeries: m_aSeriesVector)
+ {
+ if( nAxisIndex != pSeries->getAttachedAxisIndex() )
+ continue;
+
+ double fValueMinY = pSeries->getMinimumofAllDifferentYValues( nCategoryIndex );
+ double fValueMaxY = pSeries->getMaximumofAllDifferentYValues( nCategoryIndex );
+
+ if( std::isnan( fTotalSum ) )
+ {
+ rfMinimumY = fValueMinY;
+ rfMaximumY = fTotalSum = fValueMaxY;
+ }
+ else
+ {
+ fTotalSum += fValueMaxY;
+ if( rfMinimumY > fTotalSum )
+ rfMinimumY = fTotalSum;
+ if( rfMaximumY < fTotalSum )
+ rfMaximumY = fTotalSum;
+ }
+ }
+ }
+
+ aCachedYValues.m_fMinimumY = rfMinimumY;
+ aCachedYValues.m_fMaximumY = rfMaximumY;
+ aCachedYValues.m_bValuesDirty = false;
+ m_aListOfCachedYValues[nCategoryIndex][nAxisIndex]=aCachedYValues;
+}
+
+void VDataSeriesGroup::calculateYMinAndMaxForCategoryRange(
+ sal_Int32 nStartCategoryIndex, sal_Int32 nEndCategoryIndex
+ , bool bSeparateStackingForDifferentSigns
+ , double& rfMinimumY, double& rfMaximumY, sal_Int32 nAxisIndex )
+{
+ //@todo maybe cache these values
+ rfMinimumY = std::numeric_limits<double>::infinity();
+ rfMaximumY = -std::numeric_limits<double>::infinity();
+
+ //iterate through the given categories
+ if(nStartCategoryIndex<0)
+ nStartCategoryIndex=0;
+ const sal_Int32 nPointCount = getPointCount();//necessary to create m_aListOfCachedYValues
+ if(nPointCount <= 0)
+ return;
+ if (nEndCategoryIndex >= nPointCount)
+ nEndCategoryIndex = nPointCount - 1;
+ if(nEndCategoryIndex<0)
+ nEndCategoryIndex=0;
+ for( sal_Int32 nCatIndex = nStartCategoryIndex; nCatIndex <= nEndCategoryIndex; nCatIndex++ )
+ {
+ double fMinimumY = std::numeric_limits<double>::quiet_NaN();
+ double fMaximumY = std::numeric_limits<double>::quiet_NaN();
+
+ calculateYMinAndMaxForCategory( nCatIndex
+ , bSeparateStackingForDifferentSigns, fMinimumY, fMaximumY, nAxisIndex );
+
+ if(rfMinimumY > fMinimumY)
+ rfMinimumY = fMinimumY;
+ if(rfMaximumY < fMaximumY)
+ rfMaximumY = fMaximumY;
+ }
+}
+
+double VSeriesPlotter::getTransformedDepth() const
+{
+ double MinZ = m_pMainPosHelper->getLogicMinZ();
+ double MaxZ = m_pMainPosHelper->getLogicMaxZ();
+ m_pMainPosHelper->doLogicScaling( nullptr, nullptr, &MinZ );
+ m_pMainPosHelper->doLogicScaling( nullptr, nullptr, &MaxZ );
+ return FIXED_SIZE_FOR_3D_CHART_VOLUME/(MaxZ-MinZ);
+}
+
+void VSeriesPlotter::addSecondaryValueScale( const ExplicitScaleData& rScale, sal_Int32 nAxisIndex )
+{
+ if( nAxisIndex<1 )
+ return;
+
+ m_aSecondaryValueScales[nAxisIndex]=rScale;
+}
+
+PlottingPositionHelper& VSeriesPlotter::getPlottingPositionHelper( sal_Int32 nAxisIndex ) const
+{
+ PlottingPositionHelper* pRet = nullptr;
+ if(nAxisIndex>0)
+ {
+ tSecondaryPosHelperMap::const_iterator aPosIt = m_aSecondaryPosHelperMap.find( nAxisIndex );
+ if( aPosIt != m_aSecondaryPosHelperMap.end() )
+ {
+ pRet = aPosIt->second.get();
+ }
+ else if (m_pPosHelper)
+ {
+ tSecondaryValueScales::const_iterator aScaleIt = m_aSecondaryValueScales.find( nAxisIndex );
+ if( aScaleIt != m_aSecondaryValueScales.end() )
+ {
+ m_aSecondaryPosHelperMap[nAxisIndex] = m_pPosHelper->createSecondaryPosHelper( aScaleIt->second );
+ pRet = m_aSecondaryPosHelperMap[nAxisIndex].get();
+ }
+ }
+ }
+ if( !pRet )
+ pRet = m_pMainPosHelper;
+ pRet->setTimeResolution( m_nTimeResolution, m_aNullDate );
+ return *pRet;
+}
+
+void VSeriesPlotter::rearrangeLabelToAvoidOverlapIfRequested( const awt::Size& /*rPageSize*/ )
+{
+}
+
+VDataSeries* VSeriesPlotter::getFirstSeries() const
+{
+ for (std::vector<VDataSeriesGroup> const & rGroup : m_aZSlots)
+ {
+ if (!rGroup.empty())
+ {
+ if (!rGroup[0].m_aSeriesVector.empty())
+ {
+ VDataSeries* pSeries = rGroup[0].m_aSeriesVector[0].get();
+ if (pSeries)
+ return pSeries;
+ }
+ }
+ }
+ return nullptr;
+}
+
+OUString VSeriesPlotter::getCategoryName( sal_Int32 nPointIndex ) const
+{
+ if (m_pExplicitCategoriesProvider)
+ {
+ Sequence< OUString > aCategories(m_pExplicitCategoriesProvider->getSimpleCategories());
+ if (nPointIndex >= 0 && nPointIndex < aCategories.getLength())
+ {
+ return aCategories[nPointIndex];
+ }
+ }
+ return OUString();
+}
+
+std::vector<VDataSeries const*> VSeriesPlotter::getAllSeries() const
+{
+ std::vector<VDataSeries const*> aAllSeries;
+ for (std::vector<VDataSeriesGroup> const & rXSlot : m_aZSlots)
+ {
+ for(VDataSeriesGroup const & rGroup : rXSlot)
+ {
+ for (std::unique_ptr<VDataSeries> const & p : rGroup.m_aSeriesVector)
+ aAllSeries.push_back(p.get());
+ }
+ }
+ return aAllSeries;
+}
+
+std::vector<VDataSeries*> VSeriesPlotter::getAllSeries()
+{
+ std::vector<VDataSeries*> aAllSeries;
+ for (std::vector<VDataSeriesGroup> const & rXSlot : m_aZSlots)
+ {
+ for(VDataSeriesGroup const & rGroup : rXSlot)
+ {
+ for (std::unique_ptr<VDataSeries> const & p : rGroup.m_aSeriesVector)
+ aAllSeries.push_back(p.get());
+ }
+ }
+ return aAllSeries;
+}
+
+uno::Sequence<OUString> VSeriesPlotter::getSeriesNames() const
+{
+ std::vector<OUString> aRetVector;
+
+ OUString aRole;
+ if (m_xChartTypeModel.is())
+ aRole = m_xChartTypeModel->getRoleOfSequenceForSeriesLabel();
+
+ for (auto const& rGroup : m_aZSlots)
+ {
+ if (!rGroup.empty())
+ {
+ VDataSeriesGroup const & rSeriesGroup(rGroup[0]);
+ if (!rSeriesGroup.m_aSeriesVector.empty())
+ {
+ VDataSeries const * pSeries = rSeriesGroup.m_aSeriesVector[0].get();
+ rtl::Reference< DataSeries > xSeries( pSeries ? pSeries->getModel() : nullptr );
+ if( xSeries.is() )
+ {
+ OUString aSeriesName( DataSeriesHelper::getDataSeriesLabel( xSeries, aRole ) );
+ aRetVector.push_back( aSeriesName );
+ }
+ }
+ }
+ }
+ return comphelper::containerToSequence( aRetVector );
+}
+
+void VSeriesPlotter::setPageReferenceSize( const css::awt::Size & rPageRefSize )
+{
+ m_aPageReferenceSize = rPageRefSize;
+
+ // set reference size also at all data series
+
+ for (auto const & outer : m_aZSlots)
+ for (VDataSeriesGroup const & rGroup : outer)
+ {
+ for (std::unique_ptr<VDataSeries> const & pSeries : rGroup.m_aSeriesVector)
+ {
+ pSeries->setPageReferenceSize(m_aPageReferenceSize);
+ }
+ }
+}
+
+//better performance for big data
+void VSeriesPlotter::setCoordinateSystemResolution( const Sequence< sal_Int32 >& rCoordinateSystemResolution )
+{
+ m_aCoordinateSystemResolution = rCoordinateSystemResolution;
+}
+
+bool VSeriesPlotter::WantToPlotInFrontOfAxisLine()
+{
+ return ChartTypeHelper::isSeriesInFrontOfAxisLine( m_xChartTypeModel );
+}
+
+bool VSeriesPlotter::shouldSnapRectToUsedArea()
+{
+ return m_nDimension != 3;
+}
+
+std::vector< ViewLegendEntry > VSeriesPlotter::createLegendEntries(
+ const awt::Size& rEntryKeyAspectRatio
+ , LegendPosition eLegendPosition
+ , const Reference< beans::XPropertySet >& xTextProperties
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const Reference< uno::XComponentContext >& xContext
+ , ChartModel& rModel
+ )
+{
+ std::vector< ViewLegendEntry > aResult;
+
+ if( xTarget.is() )
+ {
+ rtl::Reference< Diagram > xDiagram = rModel.getFirstChartDiagram();
+ rtl::Reference< BaseCoordinateSystem > xCooSys(xDiagram->getBaseCoordinateSystems()[0]);
+ bool bSwapXAndY = false;
+
+ try
+ {
+ xCooSys->getPropertyValue( "SwapXAndYAxis" ) >>= bSwapXAndY;
+ }
+ catch( const uno::Exception& )
+ {
+ }
+
+ //iterate through all series
+ bool bBreak = false;
+ bool bFirstSeries = true;
+
+
+ for (std::vector<VDataSeriesGroup> const & rGroupVector : m_aZSlots)
+ {
+ for (VDataSeriesGroup const & rGroup : rGroupVector)
+ {
+ for (std::unique_ptr<VDataSeries> const & pSeries : rGroup.m_aSeriesVector)
+ {
+ if (!pSeries)
+ continue;
+
+ if (!pSeries->getPropertiesOfSeries()->getPropertyValue("ShowLegendEntry").get<sal_Bool>())
+ {
+ continue;
+ }
+
+ std::vector<ViewLegendEntry> aSeriesEntries(
+ createLegendEntriesForSeries(
+ rEntryKeyAspectRatio, *pSeries, xTextProperties,
+ xTarget, xContext));
+
+ //add series entries to the result now
+
+ // use only the first series if VaryColorsByPoint is set for the first series
+ if (bFirstSeries && pSeries->isVaryColorsByPoint())
+ bBreak = true;
+ bFirstSeries = false;
+
+ // add entries reverse if chart is stacked in y-direction and the legend position is right or left.
+ // If the legend is top or bottom and we have a stacked bar-chart the normal order
+ // is the correct one, unless the chart type is horizontal bar-chart.
+ bool bReverse = false;
+ if ( bSwapXAndY )
+ {
+ StackingDirection eStackingDirection( pSeries->getStackingDirection() );
+ bReverse = ( eStackingDirection != StackingDirection_Y_STACKING );
+ }
+ else if ( eLegendPosition == LegendPosition_LINE_START || eLegendPosition == LegendPosition_LINE_END )
+ {
+ StackingDirection eStackingDirection( pSeries->getStackingDirection() );
+ bReverse = ( eStackingDirection == StackingDirection_Y_STACKING );
+ }
+
+ if (bReverse)
+ aResult.insert( aResult.begin(), aSeriesEntries.begin(), aSeriesEntries.end() );
+ else
+ aResult.insert( aResult.end(), aSeriesEntries.begin(), aSeriesEntries.end() );
+ }
+ if (bBreak)
+ return aResult;
+ }
+ }
+ }
+
+ return aResult;
+}
+
+namespace
+{
+bool lcl_HasVisibleLine( const uno::Reference< beans::XPropertySet >& xProps, bool& rbHasDashedLine )
+{
+ bool bHasVisibleLine = false;
+ rbHasDashedLine = false;
+ drawing::LineStyle aLineStyle = drawing::LineStyle_NONE;
+ if( xProps.is() && ( xProps->getPropertyValue( "LineStyle") >>= aLineStyle ) )
+ {
+ if( aLineStyle != drawing::LineStyle_NONE )
+ bHasVisibleLine = true;
+ if( aLineStyle == drawing::LineStyle_DASH )
+ rbHasDashedLine = true;
+ }
+ return bHasVisibleLine;
+}
+
+bool lcl_HasRegressionCurves( const VDataSeries& rSeries, bool& rbHasDashedLine )
+{
+ bool bHasRegressionCurves = false;
+ rtl::Reference< DataSeries > xRegrCont( rSeries.getModel() );
+ for( const rtl::Reference< RegressionCurveModel > & rCurve : xRegrCont->getRegressionCurves2() )
+ {
+ bHasRegressionCurves = true;
+ lcl_HasVisibleLine( rCurve, rbHasDashedLine );
+ }
+ return bHasRegressionCurves;
+}
+}
+LegendSymbolStyle VSeriesPlotter::getLegendSymbolStyle()
+{
+ return LegendSymbolStyle::Box;
+}
+
+awt::Size VSeriesPlotter::getPreferredLegendKeyAspectRatio()
+{
+ awt::Size aRet(1000,1000);
+ if( m_nDimension==3 )
+ return aRet;
+
+ bool bSeriesAllowsLines = (getLegendSymbolStyle() == LegendSymbolStyle::Line);
+ bool bHasLines = false;
+ bool bHasDashedLines = false;
+ //iterate through all series
+ for (VDataSeries* pSeries : getAllSeries())
+ {
+ if( bSeriesAllowsLines )
+ {
+ bool bCurrentDashed = false;
+ if( lcl_HasVisibleLine( pSeries->getPropertiesOfSeries(), bCurrentDashed ) )
+ {
+ bHasLines = true;
+ if( bCurrentDashed )
+ {
+ bHasDashedLines = true;
+ break;
+ }
+ }
+ }
+ bool bRegressionHasDashedLines=false;
+ if( lcl_HasRegressionCurves( *pSeries, bRegressionHasDashedLines ) )
+ {
+ bHasLines = true;
+ if( bRegressionHasDashedLines )
+ {
+ bHasDashedLines = true;
+ break;
+ }
+ }
+ }
+ if( bHasLines )
+ {
+ if( bHasDashedLines )
+ aRet = awt::Size(1600,-1);
+ else
+ aRet = awt::Size(800,-1);
+ }
+ return aRet;
+}
+
+uno::Any VSeriesPlotter::getExplicitSymbol( const VDataSeries& /*rSeries*/, sal_Int32 /*nPointIndex*/ )
+{
+ return uno::Any();
+}
+
+rtl::Reference<SvxShapeGroup> VSeriesPlotter::createLegendSymbolForSeries(
+ const awt::Size& rEntryKeyAspectRatio
+ , const VDataSeries& rSeries
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
+{
+
+ LegendSymbolStyle eLegendSymbolStyle = getLegendSymbolStyle();
+ uno::Any aExplicitSymbol( getExplicitSymbol( rSeries, -1 ) );
+
+ VLegendSymbolFactory::PropertyType ePropType =
+ VLegendSymbolFactory::PropertyType::FilledSeries;
+
+ // todo: maybe the property-style does not solely depend on the
+ // legend-symbol type
+ switch( eLegendSymbolStyle )
+ {
+ case LegendSymbolStyle::Line:
+ ePropType = VLegendSymbolFactory::PropertyType::LineSeries;
+ break;
+ default:
+ break;
+ }
+ rtl::Reference<SvxShapeGroup> xShape = VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio,
+ xTarget, eLegendSymbolStyle,
+ rSeries.getPropertiesOfSeries(), ePropType, aExplicitSymbol );
+
+ return xShape;
+}
+
+rtl::Reference< SvxShapeGroup > VSeriesPlotter::createLegendSymbolForPoint(
+ const awt::Size& rEntryKeyAspectRatio
+ , const VDataSeries& rSeries
+ , sal_Int32 nPointIndex
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
+{
+
+ LegendSymbolStyle eLegendSymbolStyle = getLegendSymbolStyle();
+ uno::Any aExplicitSymbol( getExplicitSymbol(rSeries,nPointIndex) );
+
+ VLegendSymbolFactory::PropertyType ePropType =
+ VLegendSymbolFactory::PropertyType::FilledSeries;
+
+ // todo: maybe the property-style does not solely depend on the
+ // legend-symbol type
+ switch( eLegendSymbolStyle )
+ {
+ case LegendSymbolStyle::Line:
+ ePropType = VLegendSymbolFactory::PropertyType::LineSeries;
+ break;
+ default:
+ break;
+ }
+
+ // the default properties for the data point are the data series properties.
+ // If a data point has own attributes overwrite them
+ Reference< beans::XPropertySet > xSeriesProps( rSeries.getPropertiesOfSeries() );
+ Reference< beans::XPropertySet > xPointSet( xSeriesProps );
+ if( rSeries.isAttributedDataPoint( nPointIndex ) )
+ xPointSet.set( rSeries.getPropertiesOfPoint( nPointIndex ));
+
+ // if a data point has no own color use a color from the diagram's color scheme
+ if( ! rSeries.hasPointOwnColor( nPointIndex ))
+ {
+ Reference< util::XCloneable > xCloneable( xPointSet,uno::UNO_QUERY );
+ if( xCloneable.is() && m_xColorScheme.is() )
+ {
+ xPointSet.set( xCloneable->createClone(), uno::UNO_QUERY );
+ Reference< container::XChild > xChild( xPointSet, uno::UNO_QUERY );
+ if( xChild.is())
+ xChild->setParent( xSeriesProps );
+
+ OSL_ASSERT( xPointSet.is());
+ xPointSet->setPropertyValue(
+ "Color", uno::Any( m_xColorScheme->getColorByIndex( nPointIndex )));
+ }
+ }
+
+ rtl::Reference< SvxShapeGroup > xShape = VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio,
+ xTarget, eLegendSymbolStyle, xPointSet, ePropType, aExplicitSymbol );
+
+ return xShape;
+}
+
+std::vector< ViewLegendEntry > VSeriesPlotter::createLegendEntriesForSeries(
+ const awt::Size& rEntryKeyAspectRatio
+ , const VDataSeries& rSeries
+ , const Reference< beans::XPropertySet >& xTextProperties
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const Reference< uno::XComponentContext >& xContext
+ )
+{
+ std::vector< ViewLegendEntry > aResult;
+
+ if( ! ( xTarget.is() && xContext.is() ) )
+ return aResult;
+
+ try
+ {
+ ViewLegendEntry aEntry;
+ OUString aLabelText;
+ bool bVaryColorsByPoint = rSeries.isVaryColorsByPoint();
+ bool bIsPie = m_xChartTypeModel->getChartType().equalsIgnoreAsciiCase(
+ CHART2_SERVICE_NAME_CHARTTYPE_PIE);
+ try
+ {
+ if (bIsPie)
+ {
+ bool bDonut = false;
+ if ((m_xChartTypeModel->getPropertyValue("UseRings") >>= bDonut) && bDonut)
+ bIsPie = false;
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ }
+
+ if (bVaryColorsByPoint || bIsPie)
+ {
+ Sequence< OUString > aCategoryNames;
+ if( m_pExplicitCategoriesProvider )
+ aCategoryNames = m_pExplicitCategoriesProvider->getSimpleCategories();
+ Sequence<sal_Int32> deletedLegendEntries;
+ try
+ {
+ rSeries.getPropertiesOfSeries()->getPropertyValue("DeletedLegendEntries") >>= deletedLegendEntries;
+ }
+ catch (const uno::Exception&)
+ {
+ }
+ for( sal_Int32 nIdx=0; nIdx<aCategoryNames.getLength(); ++nIdx )
+ {
+ bool deletedLegendEntry = false;
+ for (const auto& deletedLegendEntryIdx : std::as_const(deletedLegendEntries))
+ {
+ if (nIdx == deletedLegendEntryIdx)
+ {
+ deletedLegendEntry = true;
+ break;
+ }
+ }
+ if (deletedLegendEntry)
+ continue;
+
+ // symbol
+ rtl::Reference< SvxShapeGroup > xSymbolGroup(ShapeFactory::createGroup2D( xTarget ));
+
+ // create the symbol
+ rtl::Reference< SvxShapeGroup > xShape = createLegendSymbolForPoint( rEntryKeyAspectRatio,
+ rSeries, nIdx, xSymbolGroup );
+
+ // set CID to symbol for selection
+ if( xShape.is() )
+ {
+ aEntry.xSymbol = xSymbolGroup;
+
+ OUString aChildParticle( ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_DATA_POINT, nIdx ) );
+ aChildParticle = ObjectIdentifier::addChildParticle( aChildParticle, ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_LEGEND_ENTRY, 0 ) );
+ OUString aCID = ObjectIdentifier::createClassifiedIdentifierForParticles( rSeries.getSeriesParticle(), aChildParticle );
+ ShapeFactory::setShapeName( xShape, aCID );
+ }
+
+ // label
+ aLabelText = aCategoryNames[nIdx];
+ if( xShape.is() || !aLabelText.isEmpty() )
+ {
+ aEntry.aLabel = FormattedStringHelper::createFormattedStringSequence( xContext, aLabelText, xTextProperties );
+ aResult.push_back(aEntry);
+ }
+ }
+ }
+ else
+ {
+ // symbol
+ rtl::Reference< SvxShapeGroup > xSymbolGroup(ShapeFactory::createGroup2D( xTarget ));
+
+ // create the symbol
+ rtl::Reference<SvxShapeGroup> xShape = createLegendSymbolForSeries(
+ rEntryKeyAspectRatio, rSeries, xSymbolGroup );
+
+ // set CID to symbol for selection
+ if( xShape.is())
+ {
+ aEntry.xSymbol = xSymbolGroup;
+
+ OUString aChildParticle( ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_LEGEND_ENTRY, 0 ) );
+ OUString aCID = ObjectIdentifier::createClassifiedIdentifierForParticles( rSeries.getSeriesParticle(), aChildParticle );
+ ShapeFactory::setShapeName( xShape, aCID );
+ }
+
+ // label
+ aLabelText = DataSeriesHelper::getDataSeriesLabel( rSeries.getModel(), m_xChartTypeModel.is() ? m_xChartTypeModel->getRoleOfSequenceForSeriesLabel() : "values-y");
+ aEntry.aLabel = FormattedStringHelper::createFormattedStringSequence( xContext, aLabelText, xTextProperties );
+
+ aResult.push_back(aEntry);
+ }
+
+ // don't show legend entry of regression curve & friends if this type of chart
+ // doesn't support statistics #i63016#, fdo#37197
+ if (!ChartTypeHelper::isSupportingStatisticProperties( m_xChartTypeModel, m_nDimension ))
+ return aResult;
+
+ rtl::Reference< DataSeries > xRegrCont = rSeries.getModel();
+ if( xRegrCont.is())
+ {
+ const std::vector< rtl::Reference< RegressionCurveModel > > & aCurves = xRegrCont->getRegressionCurves2();
+ sal_Int32 i = 0, nCount = aCurves.size();
+ for( i=0; i<nCount; ++i )
+ {
+ //label
+ OUString aResStr( RegressionCurveHelper::getUINameForRegressionCurve( aCurves[i] ) );
+ replaceParamterInString( aResStr, "%SERIESNAME", aLabelText );
+ aEntry.aLabel = FormattedStringHelper::createFormattedStringSequence( xContext, aResStr, xTextProperties );
+
+ // symbol
+ rtl::Reference<SvxShapeGroup> xSymbolGroup(ShapeFactory::createGroup2D( xTarget ));
+
+ // create the symbol
+ rtl::Reference<SvxShapeGroup> xShape = VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio,
+ xSymbolGroup, LegendSymbolStyle::Line,
+ aCurves[i],
+ VLegendSymbolFactory::PropertyType::Line, uno::Any() );
+
+ // set CID to symbol for selection
+ if( xShape.is())
+ {
+ aEntry.xSymbol = xSymbolGroup;
+
+ bool bAverageLine = RegressionCurveHelper::isMeanValueLine( aCurves[i] );
+ ObjectType eObjectType = bAverageLine ? OBJECTTYPE_DATA_AVERAGE_LINE : OBJECTTYPE_DATA_CURVE;
+ OUString aChildParticle( ObjectIdentifier::createChildParticleWithIndex( eObjectType, i ) );
+ aChildParticle = ObjectIdentifier::addChildParticle( aChildParticle, ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_LEGEND_ENTRY, 0 ) );
+ OUString aCID = ObjectIdentifier::createClassifiedIdentifierForParticles( rSeries.getSeriesParticle(), aChildParticle );
+ ShapeFactory::setShapeName( xShape, aCID );
+ }
+
+ aResult.push_back(aEntry);
+ }
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION("chart2" );
+ }
+ return aResult;
+}
+
+VSeriesPlotter* VSeriesPlotter::createSeriesPlotter(
+ const rtl::Reference<ChartType>& xChartTypeModel
+ , sal_Int32 nDimensionCount
+ , bool bExcludingPositioning )
+{
+ if (!xChartTypeModel.is())
+ return nullptr;
+
+ OUString aChartType = xChartTypeModel->getChartType();
+
+ VSeriesPlotter* pRet=nullptr;
+ if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_COLUMN ) )
+ pRet = new BarChart(xChartTypeModel,nDimensionCount);
+ else if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_BAR ) )
+ pRet = new BarChart(xChartTypeModel,nDimensionCount);
+ else if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_AREA ) )
+ pRet = new AreaChart(xChartTypeModel,nDimensionCount,true);
+ else if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_LINE ) )
+ pRet = new AreaChart(xChartTypeModel,nDimensionCount,true,true);
+ else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_SCATTER) )
+ pRet = new AreaChart(xChartTypeModel,nDimensionCount,false,true);
+ else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_BUBBLE) )
+ pRet = new BubbleChart(xChartTypeModel,nDimensionCount);
+ else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_PIE) )
+ pRet = new PieChart(xChartTypeModel,nDimensionCount, bExcludingPositioning );
+ else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_NET) )
+ pRet = new NetChart(xChartTypeModel,nDimensionCount,true,std::make_unique<PolarPlottingPositionHelper>());
+ else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_FILLED_NET) )
+ pRet = new NetChart(xChartTypeModel,nDimensionCount,false,std::make_unique<PolarPlottingPositionHelper>());
+ else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_CANDLESTICK) )
+ pRet = new CandleStickChart(xChartTypeModel,nDimensionCount);
+ else
+ pRet = new AreaChart(xChartTypeModel,nDimensionCount,false,true);
+ return pRet;
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */