tdf#50934: Restructuring for of-pie charts

Change-Id: I9682c314efb888d57e94f82f084cc6d0825a4408
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/160723
Tested-by: Jenkins
Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
diff --git a/chart2/source/view/charttypes/PieChart.cxx b/chart2/source/view/charttypes/PieChart.cxx
index 0f924de..49cf48d 100644
--- a/chart2/source/view/charttypes/PieChart.cxx
+++ b/chart2/source/view/charttypes/PieChart.cxx
@@ -790,13 +790,6 @@ void PieChart::createShapes()
    ///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_aPosHelper.isMathematicalOrientationRadius() && m_bUseRings )
        nExplodeableSlot = m_aZSlots.front().size()-1;

    m_aLabelInfoList.clear();
    m_fMaxOffset = std::numeric_limits<double>::quiet_NaN();
    sal_Int32 n3DRelativeHeight = 100;
@@ -815,8 +808,6 @@ void PieChart::createShapes()
    ///(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;
@@ -824,8 +815,6 @@ void PieChart::createShapes()
        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.
@@ -835,6 +824,8 @@ void PieChart::createShapes()
        ///the current data series
        sal_Int32 nPointIndex=0;
        sal_Int32 nPointCount=pSeries->getTotalPointCount();
        ShapeParam aParam;

        for( nPointIndex = 0; nPointIndex < nPointCount; nPointIndex++ )
        {
            double fY = pSeries->getYValue( nPointIndex );
@@ -851,136 +842,177 @@ void PieChart::createShapes()
            // 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_aPosHelper.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_aPosHelper.getWidthAngleDegree( fLogicStartAngleValue, fLogicEndAngleValue );
                aParam.mfUnitCircleStartAngleDegree = m_aPosHelper.transformToAngleDegree( fLogicStartAngleValue );
                aParam.mfUnitCircleInnerRadius = m_aPosHelper.transformToRadius( fLogicInnerRadius );
                aParam.mfUnitCircleOuterRadius = m_aPosHelper.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_aPosHelper.transformUnitCircleToScene( fAngle, aParam.mfUnitCircleOuterRadius, aParam.mfLogicZ );
                    drawing::Position3D aNewOrigin = m_aPosHelper.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
        switch (m_eSubType) {
        case PieChartSubType_NONE:
            createOneRing(SubPieType::NONE, fSlotX, aParam, xSeriesTarget, xTextTarget, pSeries, n3DRelativeHeight);
            break;
        case PieChartSubType_BAR:
            createOneRing(SubPieType::LEFT, fSlotX, aParam, xSeriesTarget, xTextTarget, pSeries, n3DRelativeHeight);
            break;
        case PieChartSubType_PIE:
            createOneRing(SubPieType::LEFT, fSlotX, aParam, xSeriesTarget, xTextTarget, pSeries, n3DRelativeHeight);
            break;
        default:
            assert(false); // this shouldn't happen
        }
    }//next x slot
}

void PieChart::createOneRing([[maybe_unused]]enum SubPieType eType,
        double fSlotX,
        ShapeParam& aParam,
        const rtl::Reference<SvxShapeGroupAnyD>& xSeriesTarget,
        const rtl::Reference<SvxShapeGroup>& xTextTarget,
        VDataSeries* pSeries,
        sal_Int32 n3DRelativeHeight)
{
    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_aPosHelper.m_fAngleDegreeOffset = pSeries->getStartingAngle();

    ///iterate through all points to get the sum of all entries of
    ///the current data series
    sal_Int32 nPointCount=pSeries->getTotalPointCount();

    ///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_aPosHelper.isMathematicalOrientationRadius() && m_bUseRings )
        nExplodeableSlot = m_aZSlots.front().size()-1;

    double fLogicYForNextPoint = 0.0;
    ///iterate through all points to create shapes
    for(sal_Int32 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_aPosHelper.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_aPosHelper.getWidthAngleDegree( fLogicStartAngleValue, fLogicEndAngleValue );
            aParam.mfUnitCircleStartAngleDegree = m_aPosHelper.transformToAngleDegree( fLogicStartAngleValue );
            aParam.mfUnitCircleInnerRadius = m_aPosHelper.transformToRadius( fLogicInnerRadius );
            aParam.mfUnitCircleOuterRadius = m_aPosHelper.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_aPosHelper.transformUnitCircleToScene( fAngle, aParam.mfUnitCircleOuterRadius, aParam.mfLogicZ );
                drawing::Position3D aNewOrigin = m_aPosHelper.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
}

PieChart::PieLabelInfo::PieLabelInfo()
    :  fValue(0.0)
    , bMovementAllowed(false), bMoved(false)
diff --git a/chart2/source/view/charttypes/PieChart.hxx b/chart2/source/view/charttypes/PieChart.hxx
index d6792f0..5c04ee0 100644
--- a/chart2/source/view/charttypes/PieChart.hxx
+++ b/chart2/source/view/charttypes/PieChart.hxx
@@ -75,6 +75,12 @@ public:
    virtual bool isExpandNarrowValuesTowardZero( sal_Int32 nDimensionIndex ) override;
    virtual bool isSeparateStackingForDifferentSigns( sal_Int32 nDimensionIndex ) override;

    enum class SubPieType {
        NONE,
        LEFT,
        RIGHT
    };

private: //methods
    rtl::Reference<SvxShape>
        createDataPoint(
@@ -122,6 +128,14 @@ struct PieLabelInfo;
    bool                performLabelBestFitInnerPlacement( ShapeParam& rShapeParam
                                , PieLabelInfo const & rPieLabelInfo );

    void                createOneRing([[maybe_unused]]enum SubPieType eType
                                , double fSlotX
                                , ShapeParam& aParam
                                , const rtl::Reference<SvxShapeGroupAnyD>& xSeriesTarget
                                , const rtl::Reference<SvxShapeGroup>& xTextTarget
                                , VDataSeries* pSeries
                                , sal_Int32 n3DRelativeHeight);

private: //member
    PiePositionHelper     m_aPosHelper;
    bool                  m_bUseRings;