LCOV - code coverage report
Current view: top level - chart2/source/view/charttypes - PieChart.cxx (source / functions) Hit Total Coverage
Test: commit c8344322a7af75b84dd3ca8f78b05543a976dfd5 Lines: 516 582 88.7 %
Date: 2015-06-13 12:38:46 Functions: 37 40 92.5 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
       2             : /*
       3             :  * This file is part of the LibreOffice project.
       4             :  *
       5             :  * This Source Code Form is subject to the terms of the Mozilla Public
       6             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       7             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
       8             :  *
       9             :  * This file incorporates work covered by the following license notice:
      10             :  *
      11             :  *   Licensed to the Apache Software Foundation (ASF) under one or more
      12             :  *   contributor license agreements. See the NOTICE file distributed
      13             :  *   with this work for additional information regarding copyright
      14             :  *   ownership. The ASF licenses this file to you under the Apache
      15             :  *   License, Version 2.0 (the "License"); you may not use this file
      16             :  *   except in compliance with the License. You may obtain a copy of
      17             :  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
      18             :  */
      19             : 
      20             : #include "PieChart.hxx"
      21             : #include "PlottingPositionHelper.hxx"
      22             : #include "AbstractShapeFactory.hxx"
      23             : #include "PolarLabelPositionHelper.hxx"
      24             : #include "macros.hxx"
      25             : #include "CommonConverters.hxx"
      26             : #include "ViewDefines.hxx"
      27             : #include "ObjectIdentifier.hxx"
      28             : 
      29             : #include <com/sun/star/chart/DataLabelPlacement.hpp>
      30             : #include <com/sun/star/chart2/XColorScheme.hpp>
      31             : 
      32             : #include <com/sun/star/container/XChild.hpp>
      33             : #include <rtl/math.hxx>
      34             : 
      35             : #include <boost/scoped_ptr.hpp>
      36             : 
      37             : using namespace ::com::sun::star;
      38             : using namespace ::com::sun::star::chart2;
      39             : 
      40             : namespace chart {
      41             : 
      42             : struct PieChart::ShapeParam
      43             : {
      44             :     /** the start angle of the slice
      45             :      */
      46             :     double mfUnitCircleStartAngleDegree;
      47             : 
      48             :     /** the angle width of the slice
      49             :      */
      50             :     double mfUnitCircleWidthAngleDegree;
      51             : 
      52             :     /** the normalized outer radius of the ring the slice belongs to.
      53             :      */
      54             :     double mfUnitCircleOuterRadius;
      55             : 
      56             :     /** the normalized inner radius of the ring the slice belongs to
      57             :      */
      58             :     double mfUnitCircleInnerRadius;
      59             : 
      60             :     /** relative distance offset of a slice from the pie center;
      61             :      *  this parameter is used for instance when the user performs manual
      62             :      *  dragging of a slice (the drag operation is possible only for slices that
      63             :      *  belong to the outer ring and only along the ray bisecting the slice);
      64             :      *  the value for the given entry in the data series is obtained by the
      65             :      *  `Offset` property attached to each entry; note that the value
      66             :      *  provided by the `Offset` property is used both as a logical value in
      67             :      *  `PiePositionHelper::getInnerAndOuterRadius` and as a percentage value in
      68             :      *  the `PieChart::createDataPoint` and `PieChart::createTextLabelShape`
      69             :      *  methods; since the logical height of a ring is always 1, this duality
      70             :      *  does not cause any incorrect behavior;
      71             :      */
      72             :     double mfExplodePercentage;
      73             : 
      74             :     /** sum of all Y values in a single series
      75             :      */
      76             :     double mfLogicYSum;
      77             : 
      78             :     /** for 3D pie chart: label z coordinate
      79             :      */
      80             :     double mfLogicZ;
      81             : 
      82             :     /** for 3D pie chart: height
      83             :      */
      84             :     double mfDepth;
      85             : 
      86          74 :     ShapeParam() :
      87             :         mfUnitCircleStartAngleDegree(0.0),
      88             :         mfUnitCircleWidthAngleDegree(0.0),
      89             :         mfUnitCircleOuterRadius(0.0),
      90             :         mfUnitCircleInnerRadius(0.0),
      91             :         mfExplodePercentage(0.0),
      92             :         mfLogicYSum(0.0),
      93             :         mfLogicZ(0.0),
      94          74 :         mfDepth(0.0) {}
      95             : };
      96             : 
      97             : class PiePositionHelper : public PolarPlottingPositionHelper
      98             : {
      99             : public:
     100             :     PiePositionHelper( NormalAxis eNormalAxis, double fAngleDegreeOffset );
     101             :     virtual ~PiePositionHelper();
     102             : 
     103             :     bool    getInnerAndOuterRadius( double fCategoryX, double& fLogicInnerRadius, double& fLogicOuterRadius, bool bUseRings, double fMaxOffset ) const;
     104             : 
     105             : public:
     106             :     //Distance between different category rings, seen relative to width of a ring:
     107             :     double  m_fRingDistance; //>=0 m_fRingDistance=1 --> distance == width
     108             : };
     109             : 
     110          37 : PiePositionHelper::PiePositionHelper( NormalAxis eNormalAxis, double fAngleDegreeOffset )
     111             :         : PolarPlottingPositionHelper(eNormalAxis)
     112          37 :         , m_fRingDistance(0.0)
     113             : {
     114          37 :     m_fRadiusOffset = 0.0;
     115          37 :     m_fAngleDegreeOffset = fAngleDegreeOffset;
     116          37 : }
     117             : 
     118          74 : PiePositionHelper::~PiePositionHelper()
     119             : {
     120          74 : }
     121             : 
     122             : /** Compute the outer and the inner radius for the current ring (not for the
     123             :  *  whole donut!), in general it is:
     124             :  *      inner_radius = (ring_index + 1) - 0.5 + max_offset,
     125             :  *      outer_radius = (ring_index + 1) + 0.5 + max_offset.
     126             :  *  When orientation for the radius axis is reversed these values are swapped.
     127             :  *  (Indeed the orientation for the radius axis is always reversed!
     128             :  *  See `PieChartTypeTemplate::adaptScales`.)
     129             :  *  The maximum relative offset (see notes for P`ieChart::getMaxOffset`) is
     130             :  *  added to both the inner and the outer radius.
     131             :  *  It returns true if the ring is visible (that is not out of the radius
     132             :  *  axis scale range).
     133             :  */
     134         336 : bool PiePositionHelper::getInnerAndOuterRadius( double fCategoryX
     135             :                                                , double& fLogicInnerRadius, double& fLogicOuterRadius
     136             :                                                , bool bUseRings, double fMaxOffset ) const
     137             : {
     138         336 :     if( !bUseRings )
     139         304 :         fCategoryX = 1.0;
     140             : 
     141         336 :     bool bIsVisible = true;
     142         336 :     double fLogicInner = fCategoryX -0.5+m_fRingDistance/2.0;
     143         336 :     double fLogicOuter = fCategoryX +0.5-m_fRingDistance/2.0;
     144             : 
     145         336 :     if( !isMathematicalOrientationRadius() )
     146             :     {
     147             :         //in this case the given getMaximumX() was not corrcect instead the minimum should have been smaller by fMaxOffset
     148             :         //but during getMaximumX and getMimumX we do not know the axis orientation
     149         112 :         fLogicInner += fMaxOffset;
     150         112 :         fLogicOuter += fMaxOffset;
     151             :     }
     152             : 
     153         336 :     if( fLogicInner >= getLogicMaxX() )
     154           0 :         return false;
     155         336 :     if( fLogicOuter <= getLogicMinX() )
     156           0 :         return false;
     157             : 
     158         336 :     if( fLogicInner < getLogicMinX() )
     159           0 :         fLogicInner = getLogicMinX();
     160         336 :     if( fLogicOuter > getLogicMaxX() )
     161           0 :         fLogicOuter = getLogicMaxX();
     162             : 
     163         336 :     fLogicInnerRadius = fLogicInner;
     164         336 :     fLogicOuterRadius = fLogicOuter;
     165         336 :     if( !isMathematicalOrientationRadius() )
     166         112 :         std::swap(fLogicInnerRadius,fLogicOuterRadius);
     167         336 :     return bIsVisible;
     168             : }
     169             : 
     170          37 : PieChart::PieChart( const uno::Reference<XChartType>& xChartTypeModel
     171             :                    , sal_Int32 nDimensionCount
     172             :                    , bool bExcludingPositioning )
     173             :         : VSeriesPlotter( xChartTypeModel, nDimensionCount )
     174          37 :         , m_pPosHelper( new PiePositionHelper( NormalAxis_Z, (m_nDimension==3)?0.0:90.0 ) )
     175             :         , m_bUseRings(false)
     176          74 :         , m_bSizeExcludesLabelsAndExplodedSegments(bExcludingPositioning)
     177             : {
     178          37 :     ::rtl::math::setNan(&m_fMaxOffset);
     179             : 
     180          37 :     PlotterBase::m_pPosHelper = m_pPosHelper;
     181          37 :     VSeriesPlotter::m_pMainPosHelper = m_pPosHelper;
     182          37 :     m_pPosHelper->m_fRadiusOffset = 0.0;
     183          37 :     m_pPosHelper->m_fRingDistance = 0.0;
     184             : 
     185          37 :     uno::Reference< beans::XPropertySet > xChartTypeProps( xChartTypeModel, uno::UNO_QUERY );
     186          37 :     if( xChartTypeProps.is() ) try
     187             :     {
     188          37 :         xChartTypeProps->getPropertyValue( "UseRings") >>= m_bUseRings;
     189          37 :         if( m_bUseRings )
     190             :         {
     191           4 :             m_pPosHelper->m_fRadiusOffset = 1.0;
     192           4 :             if( nDimensionCount==3 )
     193           0 :                 m_pPosHelper->m_fRingDistance = 0.1;
     194             :         }
     195             :     }
     196           0 :     catch( const uno::Exception& e )
     197             :     {
     198             :         ASSERT_EXCEPTION( e );
     199          37 :     }
     200          37 : }
     201             : 
     202         111 : PieChart::~PieChart()
     203             : {
     204          37 :     delete m_pPosHelper;
     205          74 : }
     206             : 
     207          37 : void PieChart::setScales( const std::vector< ExplicitScaleData >& rScales, bool /* bSwapXAndYAxis */ )
     208             : {
     209             :     OSL_ENSURE(m_nDimension<=static_cast<sal_Int32>(rScales.size()),"Dimension of Plotter does not fit two dimension of given scale sequence");
     210          37 :     m_pPosHelper->setScales( rScales, true );
     211          37 : }
     212             : 
     213          37 : drawing::Direction3D PieChart::getPreferredDiagramAspectRatio() const
     214             : {
     215          37 :     if( m_nDimension == 3 )
     216           6 :         return drawing::Direction3D(1,1,0.10);
     217          31 :     return drawing::Direction3D(1,1,1);
     218             : }
     219             : 
     220           0 : bool PieChart::keepAspectRatio() const
     221             : {
     222           0 :     if( m_nDimension == 3 )
     223           0 :         return false;
     224           0 :     return true;
     225             : }
     226             : 
     227          37 : bool PieChart::shouldSnapRectToUsedArea()
     228             : {
     229          37 :     return true;
     230             : }
     231             : 
     232         336 : uno::Reference< drawing::XShape > PieChart::createDataPoint(
     233             :     const uno::Reference<drawing::XShapes>& xTarget,
     234             :     const uno::Reference<beans::XPropertySet>& xObjectProperties,
     235             :     tPropertyNameValueMap* pOverwritePropertiesMap,
     236             :     const ShapeParam& rParam )
     237             : {
     238             :     //transform position:
     239         336 :     drawing::Direction3D aOffset;
     240         336 :     if (!::rtl::math::approxEqual(rParam.mfExplodePercentage, 0.0))
     241             :     {
     242          72 :         double fAngle  = rParam.mfUnitCircleStartAngleDegree + rParam.mfUnitCircleWidthAngleDegree/2.0;
     243          72 :         double fRadius = (rParam.mfUnitCircleOuterRadius-rParam.mfUnitCircleInnerRadius)*rParam.mfExplodePercentage;
     244          72 :         drawing::Position3D aOrigin = m_pPosHelper->transformUnitCircleToScene(0, 0, rParam.mfLogicZ);
     245          72 :         drawing::Position3D aNewOrigin = m_pPosHelper->transformUnitCircleToScene(fAngle, fRadius, rParam.mfLogicZ);
     246          72 :         aOffset = aNewOrigin - aOrigin;
     247             :     }
     248             : 
     249             :     //create point
     250         336 :     uno::Reference< drawing::XShape > xShape(0);
     251         336 :     if(m_nDimension==3)
     252             :     {
     253         336 :         xShape = m_pShapeFactory->createPieSegment( xTarget
     254             :             , rParam.mfUnitCircleStartAngleDegree, rParam.mfUnitCircleWidthAngleDegree
     255             :             , rParam.mfUnitCircleInnerRadius, rParam.mfUnitCircleOuterRadius
     256         168 :             , aOffset, B3DHomMatrixToHomogenMatrix( m_pPosHelper->getUnitCartesianToScene() )
     257         168 :             , rParam.mfDepth );
     258             :     }
     259             :     else
     260             :     {
     261         756 :         xShape = m_pShapeFactory->createPieSegment2D( xTarget
     262             :             , rParam.mfUnitCircleStartAngleDegree, rParam.mfUnitCircleWidthAngleDegree
     263             :             , rParam.mfUnitCircleInnerRadius, rParam.mfUnitCircleOuterRadius
     264         756 :             , aOffset, B3DHomMatrixToHomogenMatrix( m_pPosHelper->getUnitCartesianToScene() ) );
     265             :     }
     266         336 :     setMappedProperties( xShape, xObjectProperties, PropertyMapper::getPropertyNameMapForFilledSeriesProperties(), pOverwritePropertiesMap );
     267         336 :     return xShape;
     268             : }
     269             : 
     270         336 : void PieChart::createTextLabelShape(
     271             :     const uno::Reference<drawing::XShapes>& xTextTarget,
     272             :     VDataSeries& rSeries, sal_Int32 nPointIndex, ShapeParam& rParam )
     273             : {
     274         336 :     if (!rSeries.getDataPointLabelIfLabel(nPointIndex))
     275             :         // There is no text label for this data point.  Nothing to do.
     276         578 :         return;
     277             : 
     278             :     ///by using the `mfExplodePercentage` parameter a normalized offset is added
     279             :     ///to both normalized radii. (See notes for
     280             :     ///`PolarPlottingPositionHelper::transformToRadius`, especially example 3,
     281             :     ///and related comments).
     282          94 :     if (!rtl::math::approxEqual(rParam.mfExplodePercentage, 0.0))
     283             :     {
     284          52 :         double fExplodeOffset = (rParam.mfUnitCircleOuterRadius-rParam.mfUnitCircleInnerRadius)*rParam.mfExplodePercentage;
     285          52 :         rParam.mfUnitCircleInnerRadius += fExplodeOffset;
     286          52 :         rParam.mfUnitCircleOuterRadius += fExplodeOffset;
     287             :     }
     288             : 
     289             :     ///get the required label placement type. Available placements are
     290             :     ///`AVOID_OVERLAP`, `CENTER`, `OUTSIDE` and `INSIDE`;
     291             :     sal_Int32 nLabelPlacement = rSeries.getLabelPlacement(
     292          94 :         nPointIndex, m_xChartTypeModel, m_nDimension, m_pPosHelper->isSwapXAndY());
     293             : 
     294             :     ///when the placement is of `AVOID_OVERLAP` type a later rearrangement of
     295             :     ///the label position is allowed; the `createTextLabelShape` treats the
     296             :     ///`AVOID_OVERLAP` as if it was of `CENTER` type;
     297             : 
     298             :     //AVOID_OVERLAP is in fact "Best fit" in the UI.
     299          94 :     bool bMovementAllowed = ( nLabelPlacement == ::com::sun::star::chart::DataLabelPlacement::AVOID_OVERLAP );
     300          94 :     if( bMovementAllowed )
     301             :         // Use center for "Best fit" for now. In the future we
     302             :         // may want to implement a real best fit algorithm.
     303             :         // But center is good enough, and close to what Excel
     304             :         // does.
     305          72 :         nLabelPlacement = ::com::sun::star::chart::DataLabelPlacement::CENTER;
     306             : 
     307             :     ///for `OUTSIDE` (`INSIDE`) label placements an offset of 150 (-150), in the
     308             :     ///radius direction, is added to the final screen position of the label
     309             :     ///anchor point. This is required in order to ensure that the label is
     310             :     ///completely outside (inside) the related slice. Indeed this value should
     311             :     ///depend on the font height;
     312             :     ///pay attention: 150 is not a big offset, in fact the screen position
     313             :     ///coordinates for label anchor points are in the 10000-20000 range, hence
     314             :     ///these are coordinates of a virtual screen and 150 is a small value;
     315          94 :     LabelAlignment eAlignment(LABEL_ALIGN_CENTER);
     316          94 :     sal_Int32 nScreenValueOffsetInRadiusDirection = 0 ;
     317          94 :     if( nLabelPlacement == ::com::sun::star::chart::DataLabelPlacement::OUTSIDE )
     318           0 :         nScreenValueOffsetInRadiusDirection = (3!=m_nDimension) ? 150 : 0;//todo maybe calculate this font height dependent
     319          94 :     else if( nLabelPlacement == ::com::sun::star::chart::DataLabelPlacement::INSIDE )
     320           0 :         nScreenValueOffsetInRadiusDirection = (3!=m_nDimension) ? -150 : 0;//todo maybe calculate this font height dependent
     321             : 
     322             :     ///the scene position of the label anchor point is calculated (see notes for
     323             :     ///`PolarLabelPositionHelper::getLabelScreenPositionAndAlignmentForUnitCircleValues`),
     324             :     ///and immediately transformed into the screen position.
     325          94 :     PolarLabelPositionHelper aPolarPosHelper(m_pPosHelper,m_nDimension,m_xLogicTarget,m_pShapeFactory);
     326             :     awt::Point aScreenPosition2D(
     327             :         aPolarPosHelper.getLabelScreenPositionAndAlignmentForUnitCircleValues(eAlignment, nLabelPlacement
     328             :         , rParam.mfUnitCircleStartAngleDegree, rParam.mfUnitCircleWidthAngleDegree
     329          94 :         , rParam.mfUnitCircleInnerRadius, rParam.mfUnitCircleOuterRadius, rParam.mfLogicZ+0.5, 0 ));
     330             : 
     331             :     ///the screen position of the pie/donut center is calculated.
     332         188 :     PieLabelInfo aPieLabelInfo;
     333          94 :     aPieLabelInfo.aFirstPosition = basegfx::B2IVector( aScreenPosition2D.X, aScreenPosition2D.Y );
     334          94 :     awt::Point aOrigin( aPolarPosHelper.transformSceneToScreenPosition( m_pPosHelper->transformUnitCircleToScene( 0.0, 0.0, rParam.mfLogicZ+1.0 ) ) );
     335          94 :     aPieLabelInfo.aOrigin = basegfx::B2IVector( aOrigin.X, aOrigin.Y );
     336             : 
     337             :     ///add a scaling independent Offset if requested
     338          94 :     if( nScreenValueOffsetInRadiusDirection != 0)
     339             :     {
     340           0 :         basegfx::B2IVector aDirection( aScreenPosition2D.X- aOrigin.X, aScreenPosition2D.Y- aOrigin.Y );
     341           0 :         aDirection.setLength(nScreenValueOffsetInRadiusDirection);
     342           0 :         aScreenPosition2D.X += aDirection.getX();
     343           0 :         aScreenPosition2D.Y += aDirection.getY();
     344             :     }
     345             : 
     346             :     ///the text shape for the label is created
     347          94 :     double nVal = rSeries.getYValue(nPointIndex);
     348         188 :     aPieLabelInfo.xTextShape = createDataLabel(
     349          94 :         xTextTarget, rSeries, nPointIndex, nVal, rParam.mfLogicYSum, aScreenPosition2D, eAlignment);
     350             : 
     351             :     ///a new `PieLabelInfo` instance is initialized with all the info related to
     352             :     ///the current label in order to simplify later label position rearrangement;
     353         188 :     uno::Reference< container::XChild > xChild( aPieLabelInfo.xTextShape, uno::UNO_QUERY );
     354          94 :     if( xChild.is() )
     355          94 :         aPieLabelInfo.xLabelGroupShape = uno::Reference<drawing::XShape>( xChild->getParent(), uno::UNO_QUERY );
     356          94 :     aPieLabelInfo.fValue = nVal;
     357          94 :     aPieLabelInfo.bMovementAllowed = bMovementAllowed;
     358          94 :     aPieLabelInfo.bMoved= false;
     359          94 :     aPieLabelInfo.xTextTarget = xTextTarget;
     360             : 
     361          94 :     if (bMovementAllowed)
     362             :     {
     363          72 :         performLabelBestFit(rParam, aPieLabelInfo);
     364             :     }
     365             : 
     366             : 
     367         188 :     m_aLabelInfoList.push_back(aPieLabelInfo);
     368             : 
     369             : }
     370             : 
     371          66 : void PieChart::addSeries( VDataSeries* pSeries, sal_Int32 /* zSlot */, sal_Int32 /* xSlot */, sal_Int32 /* ySlot */ )
     372             : {
     373          66 :     VSeriesPlotter::addSeries( pSeries, 0, -1, 0 );
     374          66 : }
     375             : 
     376          37 : double PieChart::getMinimumX()
     377             : {
     378          37 :     return 0.5;
     379             : }
     380         373 : double PieChart::getMaxOffset()
     381             : {
     382         373 :     if (!::rtl::math::isNan(m_fMaxOffset))
     383             :         // Value already cached.  Use it.
     384         262 :         return m_fMaxOffset;
     385             : 
     386         111 :     m_fMaxOffset = 0.0;
     387         111 :     if( m_aZSlots.size()<=0 )
     388           0 :         return m_fMaxOffset;
     389         111 :     if( m_aZSlots[0].size()<=0 )
     390           0 :         return m_fMaxOffset;
     391             : 
     392         111 :     const ::std::vector< VDataSeries* >& rSeriesList( m_aZSlots[0][0].m_aSeriesVector );
     393         111 :     if( rSeriesList.size()<=0 )
     394           0 :         return m_fMaxOffset;
     395             : 
     396         111 :     VDataSeries* pSeries = rSeriesList[0];
     397         111 :     uno::Reference< beans::XPropertySet > xSeriesProp( pSeries->getPropertiesOfSeries() );
     398         111 :     if( !xSeriesProp.is() )
     399           0 :         return m_fMaxOffset;
     400             : 
     401         111 :     double fExplodePercentage=0.0;
     402         111 :     xSeriesProp->getPropertyValue( "Offset") >>= fExplodePercentage;
     403         111 :     if(fExplodePercentage>m_fMaxOffset)
     404          12 :         m_fMaxOffset=fExplodePercentage;
     405             : 
     406         111 :     if(!m_bSizeExcludesLabelsAndExplodedSegments)
     407             :     {
     408         111 :         uno::Sequence< sal_Int32 > aAttributedDataPointIndexList;
     409         111 :         if( xSeriesProp->getPropertyValue( "AttributedDataPoints" ) >>= aAttributedDataPointIndexList )
     410             :         {
     411         558 :             for(sal_Int32 nN=aAttributedDataPointIndexList.getLength();nN--;)
     412             :             {
     413         336 :                 uno::Reference< beans::XPropertySet > xPointProp( pSeries->getPropertiesOfPoint(aAttributedDataPointIndexList[nN]) );
     414         336 :                 if(xPointProp.is())
     415             :                 {
     416         336 :                     fExplodePercentage=0.0;
     417         336 :                     xPointProp->getPropertyValue( "Offset") >>= fExplodePercentage;
     418         336 :                     if(fExplodePercentage>m_fMaxOffset)
     419           3 :                         m_fMaxOffset=fExplodePercentage;
     420             :                 }
     421         336 :             }
     422         111 :         }
     423             :     }
     424         111 :     return m_fMaxOffset;
     425             : }
     426          37 : double PieChart::getMaximumX()
     427             : {
     428          37 :     double fMaxOffset = getMaxOffset();
     429          37 :     if( m_aZSlots.size()>0 && m_bUseRings)
     430           4 :         return m_aZSlots[0].size()+0.5+fMaxOffset;
     431          33 :     return 1.5+fMaxOffset;
     432             : }
     433          37 : double PieChart::getMinimumYInRange( double /* fMinimumX */, double /* fMaximumX */, sal_Int32 /* nAxisIndex */ )
     434             : {
     435          37 :     return 0.0;
     436             : }
     437             : 
     438          37 : double PieChart::getMaximumYInRange( double /* fMinimumX */, double /* fMaximumX */, sal_Int32 /* nAxisIndex */ )
     439             : {
     440          37 :     return 1.0;
     441             : }
     442             : 
     443          80 : bool PieChart::isExpandBorderToIncrementRhythm( sal_Int32 /* nDimensionIndex */ )
     444             : {
     445          80 :     return false;
     446             : }
     447             : 
     448          80 : bool PieChart::isExpandIfValuesCloseToBorder( sal_Int32 /* nDimensionIndex */ )
     449             : {
     450          80 :     return false;
     451             : }
     452             : 
     453          80 : bool PieChart::isExpandWideValuesToZero( sal_Int32 /* nDimensionIndex */ )
     454             : {
     455          80 :     return false;
     456             : }
     457             : 
     458          80 : bool PieChart::isExpandNarrowValuesTowardZero( sal_Int32 /* nDimensionIndex */ )
     459             : {
     460          80 :     return false;
     461             : }
     462             : 
     463           0 : bool PieChart::isSeparateStackingForDifferentSigns( sal_Int32 /* nDimensionIndex */ )
     464             : {
     465           0 :     return false;
     466             : }
     467             : 
     468          74 : void PieChart::createShapes()
     469             : {
     470             :     ///a ZSlot is a vector< vector< VDataSeriesGroup > >. There is only one
     471             :     ///ZSlot: m_aZSlots[0] which has a number of elements equal to the total
     472             :     ///number of data series (in fact, even if m_aZSlots[0][i] is an object of
     473             :     ///type `VDataSeriesGroup`, in the current implementation, there is only one
     474             :     ///data series in each data series group).
     475          74 :     if (m_aZSlots.empty())
     476             :         // No series to plot.
     477           0 :         return;
     478             : 
     479             :     ///m_xLogicTarget is where the group of all data series shapes (e.g. a pie
     480             :     ///slice) is added (xSeriesTarget);
     481             : 
     482             :     ///m_xFinalTarget is where the group of all text shapes (labels) is added
     483             :     ///(xTextTarget).
     484             : 
     485             :     ///both have been already created and added to the same root shape
     486             :     ///( a member of a VDiagram object); this initialization occurs in
     487             :     ///`ChartView::impl_createDiagramAndContent`.
     488             : 
     489             :     OSL_ENSURE(m_pShapeFactory && m_xLogicTarget.is() && m_xFinalTarget.is(), "PieChart is not properly initialized.");
     490          74 :     if (!m_pShapeFactory || !m_xLogicTarget.is() || !m_xFinalTarget.is())
     491           0 :         return;
     492             : 
     493             :     ///the text labels should be always on top of the other series shapes
     494             :     ///therefore create an own group for the texts to move them to front
     495             :     ///(because the text group is created after the series group the texts are
     496             :     ///displayed on top)
     497             :     uno::Reference< drawing::XShapes > xSeriesTarget(
     498          74 :         createGroupShape( m_xLogicTarget,OUString() ));
     499             :     uno::Reference< drawing::XShapes > xTextTarget(
     500         148 :         m_pShapeFactory->createGroup2D( m_xFinalTarget,OUString() ));
     501             :     //check necessary here that different Y axis can not be stacked in the same group? ... hm?
     502             : 
     503             :     ///pay attention that the `m_bSwapXAndY` parameter used by the polar
     504             :     ///plotting position helper is always set to true for pie/donut charts
     505             :     ///(see PieChart::setScales). This fact causes that `createShapes` expects
     506             :     ///that the radius axis scale is the one with index 0 and the angle axis
     507             :     ///scale is the one with index 1.
     508             : 
     509          74 :     ::std::vector< VDataSeriesGroup >::iterator             aXSlotIter = m_aZSlots[0].begin();
     510          74 :     const ::std::vector< VDataSeriesGroup >::const_iterator aXSlotEnd = m_aZSlots[0].end();
     511             : 
     512             :     ///m_bUseRings == true if chart type is `donut`, == false if chart type is
     513             :     ///`pie`; if the chart is of `donut` type we have as many rings as many data
     514             :     ///series, else we have a single ring (a pie) representing the first data
     515             :     ///series;
     516             :     ///for what I can see the radius axis orientation is always reversed and
     517             :     ///the angle axis orientation is always non-reversed;
     518             :     ///the radius axis scale range is [0.5, number of rings + 0.5 + max_offset],
     519             :     ///the angle axis scale range is [0, 1]. The max_offset parameter is used
     520             :     ///for exploded pie chart and its value is 0.5.
     521             : 
     522             :     ///the `explodeable` ring is the first one except when the radius axis
     523             :     ///orientation is reversed (always!?) and we are dealing with a donut: in
     524             :     ///such a case the `explodeable` ring is the last one.
     525          74 :     ::std::vector< VDataSeriesGroup >::size_type nExplodeableSlot = 0;
     526          74 :     if( m_pPosHelper->isMathematicalOrientationRadius() && m_bUseRings )
     527           8 :         nExplodeableSlot = m_aZSlots[0].size()-1;
     528             : 
     529          74 :     m_aLabelInfoList.clear();
     530          74 :     ::rtl::math::setNan(&m_fMaxOffset);
     531          74 :     sal_Int32 n3DRelativeHeight = 100;
     532         148 :     uno::Reference< beans::XPropertySet > xPropertySet( m_xChartTypeModel, uno::UNO_QUERY );
     533          74 :     if ( (m_nDimension==3) && xPropertySet.is())
     534             :     {
     535             :         try
     536             :         {
     537          12 :             uno::Any aAny = xPropertySet->getPropertyValue( "3DRelativeHeight" );
     538          12 :             aAny >>= n3DRelativeHeight;
     539             :         }
     540           0 :         catch (const uno::Exception&) { }
     541             :     }
     542             :     ///iterate over each xslot, that is on each data series (there is
     543             :     ///only one data series in each data series group!); note that if the chart
     544             :     ///type is a pie the loop iterates only over the first data series
     545             :     ///(m_bUseRings||fSlotX<0.5)
     546         148 :     for( double fSlotX=0; aXSlotIter != aXSlotEnd && (m_bUseRings||fSlotX<0.5 ); ++aXSlotIter, fSlotX+=1.0 )
     547             :     {
     548          74 :         ShapeParam aParam;
     549             : 
     550          74 :         ::std::vector< VDataSeries* >* pSeriesList = &(aXSlotIter->m_aSeriesVector);
     551          74 :         if( pSeriesList->size()<=0 )//there should be only one series in each x slot
     552           0 :             continue;
     553          74 :         VDataSeries* pSeries = (*pSeriesList)[0];
     554          74 :         if(!pSeries)
     555           0 :             continue;
     556             : 
     557          74 :         bool bHasFillColorMapping = pSeries->hasPropertyMapping("FillColor");
     558             : 
     559             :         /// The angle degree offset is set by the same property of the
     560             :         /// data series.
     561             :         /// Counter-clockwise offset from the 3 o'clock position.
     562          74 :         m_pPosHelper->m_fAngleDegreeOffset = pSeries->getStartingAngle();
     563             : 
     564             :         ///iterate through all points to get the sum of all entries of
     565             :         ///the current data series
     566          74 :         sal_Int32 nPointIndex=0;
     567          74 :         sal_Int32 nPointCount=pSeries->getTotalPointCount();
     568         410 :         for( nPointIndex = 0; nPointIndex < nPointCount; nPointIndex++ )
     569             :         {
     570         336 :             double fY = pSeries->getYValue( nPointIndex );
     571             :             if(fY<0.0)
     572             :             {
     573             :                 //@todo warn somehow that negative values are treated as positive
     574             :             }
     575         336 :             if( ::rtl::math::isNan(fY) )
     576           0 :                 continue;
     577         336 :             aParam.mfLogicYSum += fabs(fY);
     578             :         }
     579             : 
     580          74 :         if (aParam.mfLogicYSum == 0.0)
     581             :             // Total sum of all Y values in this series is zero. Skip the whole series.
     582           0 :             continue;
     583             : 
     584          74 :         double fLogicYForNextPoint = 0.0;
     585             :         ///iterate through all points to create shapes
     586         410 :         for( nPointIndex = 0; nPointIndex < nPointCount; nPointIndex++ )
     587             :         {
     588             :             double fLogicInnerRadius, fLogicOuterRadius;
     589             : 
     590             :             ///compute the maximum relative distance offset of the current slice
     591             :             ///from the pie center
     592             :             ///it is worth noting that after the first invocation the maximum
     593             :             ///offset value is cached, so it is evaluated only once per each
     594             :             ///call to `createShapes`
     595         336 :             double fOffset = getMaxOffset();
     596             : 
     597             :             ///compute the outer and the inner radius for the current ring slice
     598         336 :             bool bIsVisible = m_pPosHelper->getInnerAndOuterRadius( fSlotX+1.0, fLogicInnerRadius, fLogicOuterRadius, m_bUseRings, fOffset );
     599         336 :             if( !bIsVisible )
     600           0 :                 continue;
     601             : 
     602         336 :             aParam.mfDepth  = this->getTransformedDepth() * (n3DRelativeHeight / 100.0);
     603             : 
     604         336 :             uno::Reference< drawing::XShapes > xSeriesGroupShape_Shapes = getSeriesGroupShape(pSeries, xSeriesTarget);
     605             :             ///collect data point information (logic coordinates, style ):
     606         336 :             double fLogicYValue = fabs(pSeries->getYValue( nPointIndex ));
     607         336 :             if( ::rtl::math::isNan(fLogicYValue) )
     608           0 :                 continue;
     609         336 :             if(fLogicYValue==0.0)//@todo: continue also if the resolution to small
     610           0 :                 continue;
     611         336 :             double fLogicYPos = fLogicYForNextPoint;
     612         336 :             fLogicYForNextPoint += fLogicYValue;
     613             : 
     614         672 :             uno::Reference< beans::XPropertySet > xPointProperties = pSeries->getPropertiesOfPoint( nPointIndex );
     615             : 
     616             :             //iterate through all subsystems to create partial points
     617             :             {
     618             :                 //logic values on angle axis:
     619         336 :                 double fLogicStartAngleValue = fLogicYPos / aParam.mfLogicYSum;
     620         336 :                 double fLogicEndAngleValue = (fLogicYPos+fLogicYValue) / aParam.mfLogicYSum;
     621             : 
     622             :                 ///note that the explode percentage is set to the `Offset`
     623             :                 ///property of the current data series entry only for slices
     624             :                 ///belonging to the outer ring
     625         336 :                 aParam.mfExplodePercentage = 0.0;
     626         336 :                 bool bDoExplode = ( nExplodeableSlot == static_cast< ::std::vector< VDataSeriesGroup >::size_type >(fSlotX) );
     627         336 :                 if(bDoExplode) try
     628             :                 {
     629         336 :                     xPointProperties->getPropertyValue( "Offset") >>= aParam.mfExplodePercentage;
     630             :                 }
     631           0 :                 catch( const uno::Exception& e )
     632             :                 {
     633             :                     ASSERT_EXCEPTION( e );
     634             :                 }
     635             : 
     636             :                 ///see notes for `PolarPlottingPositionHelper` methods
     637             :                 ///transform to unit circle:
     638         336 :                 aParam.mfUnitCircleWidthAngleDegree = m_pPosHelper->getWidthAngleDegree( fLogicStartAngleValue, fLogicEndAngleValue );
     639         336 :                 aParam.mfUnitCircleStartAngleDegree = m_pPosHelper->transformToAngleDegree( fLogicStartAngleValue );
     640         336 :                 aParam.mfUnitCircleInnerRadius = m_pPosHelper->transformToRadius( fLogicInnerRadius );
     641         336 :                 aParam.mfUnitCircleOuterRadius = m_pPosHelper->transformToRadius( fLogicOuterRadius );
     642             : 
     643             :                 ///point color:
     644         336 :                 boost::scoped_ptr< tPropertyNameValueMap > apOverwritePropertiesMap(NULL);
     645         336 :                 if (!pSeries->hasPointOwnColor(nPointIndex) && m_xColorScheme.is())
     646             :                 {
     647         112 :                     apOverwritePropertiesMap.reset( new tPropertyNameValueMap() );
     648         224 :                     (*apOverwritePropertiesMap)["FillColor"] = uno::makeAny(
     649         224 :                         m_xColorScheme->getColorByIndex( nPointIndex ));
     650             :                 }
     651             : 
     652             :                 ///create data point
     653         336 :                 aParam.mfLogicZ = -1.0; // For 3D pie chart label position
     654             :                 uno::Reference<drawing::XShape> xPointShape =
     655             :                     createDataPoint(
     656         672 :                         xSeriesGroupShape_Shapes, xPointProperties, apOverwritePropertiesMap.get(), aParam);
     657             : 
     658         336 :                 if(bHasFillColorMapping)
     659             :                 {
     660           0 :                     double nPropVal = pSeries->getValueByProperty(nPointIndex, "FillColor");
     661           0 :                     if(!rtl::math::isNan(nPropVal))
     662             :                     {
     663           0 :                         uno::Reference< beans::XPropertySet > xProps( xPointShape, uno::UNO_QUERY_THROW );
     664           0 :                         xProps->setPropertyValue("FillColor", uno::makeAny(static_cast<sal_Int32>( nPropVal)));
     665             :                     }
     666             :                 }
     667             : 
     668             :                 ///create label
     669         336 :                 createTextLabelShape(xTextTarget, *pSeries, nPointIndex, aParam);
     670             : 
     671         336 :                 if(!bDoExplode)
     672             :                 {
     673             :                     AbstractShapeFactory::setShapeName( xPointShape
     674           0 :                                 , ObjectIdentifier::createPointCID( pSeries->getPointCID_Stub(), nPointIndex ) );
     675             :                 }
     676             :                 else try
     677             :                 {
     678             :                     ///enable dragging of outer segments
     679             : 
     680         336 :                     double fAngle  = aParam.mfUnitCircleStartAngleDegree + aParam.mfUnitCircleWidthAngleDegree/2.0;
     681         336 :                     double fMaxDeltaRadius = aParam.mfUnitCircleOuterRadius-aParam.mfUnitCircleInnerRadius;
     682         336 :                     drawing::Position3D aOrigin = m_pPosHelper->transformUnitCircleToScene( fAngle, aParam.mfUnitCircleOuterRadius, aParam.mfLogicZ );
     683         336 :                     drawing::Position3D aNewOrigin = m_pPosHelper->transformUnitCircleToScene( fAngle, aParam.mfUnitCircleOuterRadius + fMaxDeltaRadius, aParam.mfLogicZ );
     684             : 
     685         336 :                     sal_Int32 nOffsetPercent( static_cast<sal_Int32>(aParam.mfExplodePercentage * 100.0) );
     686             : 
     687             :                     awt::Point aMinimumPosition( PlottingPositionHelper::transformSceneToScreenPosition(
     688         336 :                         aOrigin, m_xLogicTarget, m_pShapeFactory, m_nDimension ) );
     689             :                     awt::Point aMaximumPosition( PlottingPositionHelper::transformSceneToScreenPosition(
     690         336 :                         aNewOrigin, m_xLogicTarget, m_pShapeFactory, m_nDimension ) );
     691             : 
     692             :                     //enable draging of piesegments
     693             :                     OUString aPointCIDStub( ObjectIdentifier::createSeriesSubObjectStub( OBJECTTYPE_DATA_POINT
     694             :                         , pSeries->getSeriesParticle()
     695         336 :                         , ObjectIdentifier::getPieSegmentDragMethodServiceName()
     696             :                         , ObjectIdentifier::createPieSegmentDragParameterString(
     697             :                             nOffsetPercent, aMinimumPosition, aMaximumPosition )
     698         672 :                         ) );
     699             : 
     700             :                     AbstractShapeFactory::setShapeName( xPointShape
     701         336 :                                 , ObjectIdentifier::createPointCID( aPointCIDStub, nPointIndex ) );
     702             :                 }
     703           0 :                 catch( const uno::Exception& e )
     704             :                 {
     705             :                     ASSERT_EXCEPTION( e );
     706         336 :                 }
     707             :             }//next series in x slot (next y slot)
     708         336 :         }//next category
     709          74 :     }//next x slot
     710             : }
     711             : 
     712             : namespace
     713             : {
     714             : 
     715         529 : ::basegfx::B2IRectangle lcl_getRect( const uno::Reference< drawing::XShape >& xShape )
     716             : {
     717         529 :     ::basegfx::B2IRectangle aRect;
     718         529 :     if( xShape.is() )
     719         529 :         aRect = BaseGFXHelper::makeRectangle(xShape->getPosition(),xShape->getSize() );
     720         529 :     return aRect;
     721             : }
     722             : 
     723          25 : bool lcl_isInsidePage( const awt::Point& rPos, const awt::Size& rSize, const awt::Size& rPageSize )
     724             : {
     725          25 :     if( rPos.X < 0  || rPos.Y < 0 )
     726           0 :         return false;
     727          25 :     if( (rPos.X + rSize.Width) > rPageSize.Width  )
     728           0 :         return false;
     729          25 :     if( (rPos.Y + rSize.Height) > rPageSize.Height )
     730           0 :         return false;
     731          25 :     return true;
     732             : }
     733             : 
     734             : inline
     735          95 : double lcl_radToDeg(double fAngleRad)
     736             : {
     737          95 :     return (fAngleRad / M_PI) * 180.0;
     738             : }
     739             : 
     740             : inline
     741          72 : double lcl_degToRad(double fAngleDeg)
     742             : {
     743          72 :     return (fAngleDeg / 180) * M_PI;
     744             : }
     745             : 
     746             : inline
     747         311 : double lcl_getDegAngleInStandardRange(double fAngle)
     748             : {
     749         656 :     while( fAngle < 0.0 )
     750          34 :         fAngle += 360.0;
     751         630 :     while( fAngle >= 360.0 )
     752           8 :         fAngle -= 360.0;
     753         311 :     return fAngle;
     754             : }
     755             : 
     756             : }//end anonymous namespace
     757             : 
     758          94 : PieChart::PieLabelInfo::PieLabelInfo()
     759             :     : xTextShape(0), xLabelGroupShape(0), aFirstPosition(), aOrigin(), fValue(0.0)
     760          94 :     , bMovementAllowed(false), bMoved(false), xTextTarget(0), pPrevious(0),pNext(0)
     761             : {
     762          94 : }
     763             : 
     764             : /** In case this label and the passed label overlap the routine moves this
     765             :  *  label in order to fix the issue. After the label position has been
     766             :  *  rearranged it is checked that the moved label is still inside the page
     767             :  *  document, if the test is positive the routine returns true else returns
     768             :  *  false.
     769             :  */
     770          46 : bool PieChart::PieLabelInfo::moveAwayFrom( const PieChart::PieLabelInfo* pFix, const awt::Size& rPageSize, bool bMoveHalfWay, bool bMoveClockwise, bool bAlternativeMoveDirection )
     771             : {
     772             :     //return true if the move was successful
     773          46 :     if(!this->bMovementAllowed)
     774           0 :         return false;
     775             : 
     776          46 :     const sal_Int32 nLabelDistanceX = rPageSize.Width/50;
     777          46 :     const sal_Int32 nLabelDistanceY = rPageSize.Height/50;
     778             : 
     779             :     ///compute the rectangle representing the intersection of the label bounding
     780             :     ///boxes (`aOverlap`).
     781          46 :     ::basegfx::B2IRectangle aOverlap( lcl_getRect( this->xLabelGroupShape ) );
     782          46 :     aOverlap.intersect( lcl_getRect( pFix->xLabelGroupShape ) );
     783          46 :     if( !aOverlap.isEmpty() )
     784             :     {
     785             :         (void)bAlternativeMoveDirection;//todo
     786             : 
     787             :         ///the label is shifted along the direction orthogonal to the vector
     788             :         ///starting at the pie/donut center and ending at this label anchor
     789             :         ///point;
     790             : 
     791             :         ///named `aTangentialDirection` the unit vector related to such a
     792             :         ///direction, the magnitude of the shift along such a direction is
     793             :         ///calculated in this way: if the horizontal component of
     794             :         ///`aTangentialDirection` is greater than the vertical component,
     795             :         ///the magnitude of the shift is equal to `aOverlap.Width` else to
     796             :         ///`aOverlap.Height`;
     797          25 :         basegfx::B2IVector aRadiusDirection = this->aFirstPosition - this->aOrigin;
     798          25 :         aRadiusDirection.setLength(1.0);
     799          50 :         basegfx::B2IVector aTangentialDirection( -aRadiusDirection.getY(), aRadiusDirection.getX() );
     800          25 :         bool bShiftHorizontal = abs(aTangentialDirection.getX()) > abs(aTangentialDirection.getY());
     801          25 :         sal_Int32 nShift = bShiftHorizontal ? static_cast<sal_Int32>(aOverlap.getWidth()) : static_cast<sal_Int32>(aOverlap.getHeight());
     802             :         ///the magnitude of the shift is also increased by 1/50-th of the width
     803             :         ///or the height of the document page;
     804          25 :         nShift += (bShiftHorizontal ? nLabelDistanceX : nLabelDistanceY);
     805             :         ///in case the `bMoveHalfWay` parameter is true the magnitude of
     806             :         ///the shift is halved.
     807          25 :         if( bMoveHalfWay )
     808           8 :             nShift/=2;
     809             :         ///in case the `bMoveClockwise` parameter is false the direction of
     810             :         ///`aTangentialDirection` is reversed;
     811          25 :         if(!bMoveClockwise)
     812          10 :             nShift*=-1;
     813          25 :         awt::Point aOldPos( this->xLabelGroupShape->getPosition() );
     814          50 :         basegfx::B2IVector aNewPos = basegfx::B2IVector( aOldPos.X, aOldPos.Y ) + nShift*aTangentialDirection;
     815             : 
     816             :         ///a final check is performed in order to be sure that the moved label
     817             :         ///is still inside the page document;
     818          25 :         awt::Point aNewAWTPos( aNewPos.getX(), aNewPos.getY() );
     819          25 :         if( !lcl_isInsidePage( aNewAWTPos, this->xLabelGroupShape->getSize(), rPageSize ) )
     820           0 :             return false;
     821             : 
     822          25 :         this->xLabelGroupShape->setPosition( aNewAWTPos );
     823          50 :         this->bMoved = true;
     824             :     }
     825          46 :     return true;
     826             : 
     827             :     ///note that no further test is performed in order to check that the
     828             :     ///overlap is really fixed: this result is surely achieved if the shift
     829             :     ///would occur in the horizontal or vertical direction (since, in such a
     830             :     ///direction, the magnitude of the shift would be greater than the length
     831             :     ///of the overlap), but in general this is not true;
     832             :     ///adding a constant term equal to 1/50-th of the width or the height of
     833             :     ///the document page increases the probability of success, anyway it is
     834             :     ///worth noting that the method can return true even if the overlap issue
     835             :     ///is not (completely) fixed;
     836             : }
     837             : 
     838           0 : void PieChart::resetLabelPositionsToPreviousState()
     839             : {
     840           0 :     std::vector< PieLabelInfo >::iterator aIt = m_aLabelInfoList.begin();
     841           0 :     std::vector< PieLabelInfo >::const_iterator aEnd = m_aLabelInfoList.end();
     842           0 :     for( ;aIt!=aEnd; ++aIt )
     843           0 :         aIt->xLabelGroupShape->setPosition(aIt->aPreviousPosition);
     844           0 : }
     845             : 
     846          18 : bool PieChart::detectLabelOverlapsAndMove( const awt::Size& rPageSize )
     847             : {
     848             :     ///the routine tries to individuate a chain of overlapping labels and
     849             :     ///assigns the first and the last of them to `pFirstBorder` and
     850             :     ///`pSecondBorder`;
     851             :     ///this result is achieved by performing two consecutive while loop.
     852             : 
     853             :     ///find borders of a group of overlapping labels
     854             : 
     855             :     ///a first while loop is started on the collection of `PieLabelInfo` objects;
     856             :     ///the bounding box of each label is checked for overlap against the bounding
     857             :     ///box of the previous and of the next label;
     858             :     ///when an overlap is found `bOverlapFound` is set to true, however the
     859             :     ///iteration is break only if the overlap occurs against only the next label
     860             :     ///and not against the previous label: so we exit from the loop whenever an
     861             :     ///overlap occurs except when the loop initial label overlaps with the
     862             :     ///previous one;
     863          18 :     bool bOverlapFound = false;
     864          18 :     PieLabelInfo* pStart = &(*(m_aLabelInfoList.rbegin()));
     865          18 :     PieLabelInfo* pFirstBorder = 0;
     866          18 :     PieLabelInfo* pSecondBorder = 0;
     867          18 :     PieLabelInfo* pCurrent = pStart;
     868          76 :     do
     869             :     {
     870          87 :         ::basegfx::B2IRectangle aPreviousOverlap( lcl_getRect( pCurrent->xLabelGroupShape ) );
     871          87 :         ::basegfx::B2IRectangle aNextOverlap( aPreviousOverlap );
     872          87 :         aPreviousOverlap.intersect( lcl_getRect( pCurrent->pPrevious->xLabelGroupShape ) );
     873          87 :         aNextOverlap.intersect( lcl_getRect( pCurrent->pNext->xLabelGroupShape ) );
     874             : 
     875          87 :         bool bPreviousOverlap = !aPreviousOverlap.isEmpty();
     876          87 :         bool bNextOverlap = !aNextOverlap.isEmpty();
     877          87 :         if( bPreviousOverlap || bNextOverlap )
     878          15 :             bOverlapFound = true;
     879          87 :         if( !bPreviousOverlap && bNextOverlap )
     880             :         {
     881          11 :             pFirstBorder = pCurrent;
     882          11 :             break;
     883             :         }
     884          76 :         pCurrent = pCurrent->pNext;
     885             :     }
     886             :     while( pCurrent != pStart );
     887             : 
     888          18 :     if( !bOverlapFound )
     889           7 :         return false;
     890             : 
     891             :     ///in case we found a label (`pFirstBorder`) which overlaps with the next
     892             :     ///label and not with the previous label a second while loop is started with
     893             :     ///`pFirstBorder` as initial label; one more time the bounding box of each
     894             :     ///label is checked for overlap against the bounding box of the previous and
     895             :     ///of the next label, however this time we exit from the loop only if the
     896             :     ///current label overlaps with the previous one but does not with the next
     897             :     ///one (the opposite of what is required in the former loop);
     898             :     ///in case such a label is found it is assigned to `pSecondBorder` and the
     899             :     ///iteration is stopped; so in case there is a chain of overlapping labels
     900             :     ///we end up having the first label of the chain pointed by `pFirstBorder`
     901             :     ///and the last label of the chain pointed by `pSecondBorder`;
     902          11 :     if( pFirstBorder )
     903             :     {
     904          11 :         pCurrent = pFirstBorder;
     905          18 :         do
     906             :         {
     907          29 :             ::basegfx::B2IRectangle aPreviousOverlap( lcl_getRect( pCurrent->xLabelGroupShape ) );
     908          29 :             ::basegfx::B2IRectangle aNextOverlap( aPreviousOverlap );
     909          29 :             aPreviousOverlap.intersect( lcl_getRect( pCurrent->pPrevious->xLabelGroupShape ) );
     910          29 :             aNextOverlap.intersect( lcl_getRect( pCurrent->pNext->xLabelGroupShape ) );
     911             : 
     912          29 :             if( !aPreviousOverlap.isEmpty() && aNextOverlap.isEmpty() )
     913             :             {
     914          11 :                 pSecondBorder = pCurrent;
     915          11 :                 break;
     916             :             }
     917          18 :             pCurrent = pCurrent->pNext;
     918             :         }
     919             :         while( pCurrent != pFirstBorder );
     920             :     }
     921             : 
     922             :     ///when two labels satisfying the required conditions are not found
     923             :     ///(`pFirstBorder == 0 || pSecondBorder == 0`) but still an overlap occurs
     924             :     ///(`bOverlapFound == true`) we are in the situation where each label
     925             :     ///overlaps with both the previous and the next one; so `pFirstBorder` is
     926             :     ///set to point to the last `PieLabelInfo` object in the collection and
     927             :     ///`pSecondBorder` is set to point to the first one;
     928          11 :     if( !pFirstBorder || !pSecondBorder )
     929             :     {
     930           0 :         pFirstBorder = &(*(m_aLabelInfoList.rbegin()));
     931           0 :         pSecondBorder = &(*(m_aLabelInfoList.begin()));
     932             :     }
     933             : 
     934             :     ///the total number of labels that made up the chain is calculated and used
     935             :     ///for getting a pointer to the central label (`pCenter`);
     936          11 :     PieLabelInfo* pCenter = pFirstBorder;
     937          11 :     sal_Int32 nOverlapGroupCount = 1;
     938          29 :     for( pCurrent = pFirstBorder ;pCurrent != pSecondBorder; pCurrent = pCurrent->pNext )
     939          18 :         nOverlapGroupCount++;
     940          11 :     sal_Int32 nCenterPos = nOverlapGroupCount/2;
     941          11 :     bool bSingleCenter = nOverlapGroupCount%2 != 0;
     942          11 :     if( bSingleCenter )
     943           3 :         nCenterPos++;
     944          11 :     if(nCenterPos>1)
     945             :     {
     946           5 :         pCurrent = pFirstBorder;
     947          15 :         while( --nCenterPos )
     948           5 :             pCurrent = pCurrent->pNext;
     949           5 :         pCenter = pCurrent;
     950             :     }
     951             : 
     952             :     ///the current position of each label in the collection is saved in
     953             :     ///`PieLabelInfo.aPreviousPosition`, so that it is possible to undo the label
     954             :     ///move action if it is needed; the undo action is provided by the
     955             :     ///`PieChart::resetLabelPositionsToPreviousState` method.
     956          11 :     pCurrent = pStart;
     957          73 :     do
     958             :     {
     959          73 :         pCurrent->aPreviousPosition = pCurrent->xLabelGroupShape->getPosition();
     960          73 :         pCurrent = pCurrent->pNext;
     961             :     }
     962             :     while( pCurrent != pStart );
     963             : 
     964             :     ///the `PieChart::tryMoveLabels` method is invoked with
     965             :     ///`rbAlternativeMoveDirection` boolean parameter set to false, such a method
     966             :     ///tries to remove all overlaps that occur in the list of labels going from
     967             :     ///`pFirstBorder` to `pSecondBorder`;
     968             :     ///if the `PieChart::tryMoveLabels` returns true no further action is
     969             :     ///performed, however it is worth noting that it does not mean that all
     970             :     ///overlap issues have been surely fixed, but only that all moved labels are
     971             :     ///at least completely inside the page document;
     972             :     ///when `PieChart::tryMoveLabels` returns false, it means that the attempt
     973             :     ///to fix one of the overlap issues caused that a label has been moved
     974             :     ///(partially) outside the page document (anyway the `PieChart::tryMoveLabels`
     975             :     ///method takes care to restore the position of all labels to their initial
     976             :     ///position, and to set the `rbAlternativeMoveDirection` in/out parameter to
     977             :     ///true); in such a case a second invocation of `PieChart::tryMoveLabels` is
     978             :     ///performed (and this time the `rbAlternativeMoveDirection` boolean
     979             :     ///parameter is true) and independently by what the `PieChart::tryMoveLabels`
     980             :     ///method returns no further action is performed;
     981             :     ///(see notes for `PieChart::tryMoveLabels`);
     982          11 :     bool bAlternativeMoveDirection = false;
     983          11 :     if( !tryMoveLabels( pFirstBorder, pSecondBorder, pCenter, bSingleCenter, bAlternativeMoveDirection, rPageSize ) )
     984           0 :         tryMoveLabels( pFirstBorder, pSecondBorder, pCenter, bSingleCenter, bAlternativeMoveDirection, rPageSize );
     985             : 
     986             :     ///in both cases (one or two invocations of `PieChart::tryMoveLabels`) the
     987             :     ///`detectLabelOverlapsAndMove` method ends returning true.
     988          11 :     return true;
     989             : }
     990             : 
     991             : 
     992             : /** Try to remove all overlaps that occur in the list of labels going from
     993             :  *  `pFirstBorder` to `pSecondBorder`
     994             :  */
     995          11 : bool PieChart::tryMoveLabels( PieLabelInfo* pFirstBorder, PieLabelInfo* pSecondBorder
     996             :                              , PieLabelInfo* pCenter
     997             :                              , bool bSingleCenter, bool& rbAlternativeMoveDirection, const awt::Size& rPageSize )
     998             : {
     999             : 
    1000          11 :     PieLabelInfo* p1 = bSingleCenter ? pCenter->pPrevious : pCenter;
    1001          11 :     PieLabelInfo* p2 = pCenter->pNext;
    1002             :     //return true when successful
    1003             : 
    1004          11 :     bool bLabelOrderIsAntiClockWise = m_pPosHelper->isMathematicalOrientationAngle();
    1005             : 
    1006             :     ///two loops are performed simultaneously: the outer loop iterates on
    1007             :     ///`PieLabelInfo` objects in the list starting from the central element
    1008             :     ///(`pCenter`) and moving forward until the last element (`pSecondBorder`);
    1009             :     ///the inner loop starts from the previous element of `pCenter` and moves
    1010             :     ///forward until the current `PieLabelInfo` object of the outer loop is
    1011             :     ///reached
    1012          11 :     PieLabelInfo* pCurrent = 0;
    1013          24 :     for( pCurrent = p2 ;pCurrent->pPrevious != pSecondBorder; pCurrent = pCurrent->pNext )
    1014             :     {
    1015          13 :         PieLabelInfo* pFix = 0;
    1016          28 :         for( pFix = p2->pPrevious ;pFix != pCurrent; pFix = pFix->pNext )
    1017             :         {
    1018             :             ///on the current `PieLabelInfo` object of the outer loop the
    1019             :             ///`moveAwayFrom` method is invoked by passing the current
    1020             :             ///`PieLabelInfo` object of the inner loop as argument.
    1021             : 
    1022             :             ///so each label going from the central one to the last one is
    1023             :             ///checked for overlapping against all previous labels (that comes
    1024             :             ///after the central label) and in case the overlap occurs the
    1025             :             ///`moveAwayFrom` method tries to fix the issue;
    1026             :             ///if `moveAwayFrom` returns true (pay attention: that does not
    1027             :             ///mean that the overlap issue has been surely fixed but only that
    1028             :             ///the moved label is at least completely inside the page document:
    1029             :             ///see notes on `PieChart::PieLabelInfo::moveAwayFrom`), the inner
    1030             :             ///loop starts a new iteration else the `rbAlternativeMoveDirection`
    1031             :             ///boolean parameter is tested: if it is false the parameter is set
    1032             :             ///to true, the position of all labels is restored to the initial
    1033             :             ///one (through the `PieChart::resetLabelPositionsToPreviousState`
    1034             :             ///method) and the method ends by returning false, else the inner
    1035             :             ///loop starts a new iteration step;
    1036             :             ///so when `rbAlternativeMoveDirection` is true the method goes on
    1037             :             ///trying to fix left overlap issues even if the last `moveAwayFrom`
    1038             :             ///invocation has moved a label in a position that it is not
    1039             :             ///completely inside the page document
    1040             : 
    1041          15 :             if( !pCurrent->moveAwayFrom( pFix, rPageSize, !bSingleCenter && pCurrent == p2, !bLabelOrderIsAntiClockWise, rbAlternativeMoveDirection ) )
    1042             :             {
    1043           0 :                 if( !rbAlternativeMoveDirection )
    1044             :                 {
    1045           0 :                     rbAlternativeMoveDirection = true;
    1046           0 :                     resetLabelPositionsToPreviousState();
    1047           0 :                     return false;
    1048             :                 }
    1049             :             }
    1050             :         }
    1051             :     }
    1052             : 
    1053             :     ///if the method does not return before ending the first pair of loops,
    1054             :     ///a second pair of simultaneous loops is performed in the opposite
    1055             :     ///direction (respect with the previous case): the outer loop iterates on
    1056             :     ///`PieLabelInfo` objects in the list starting from the central element
    1057             :     ///(`pCenter`) and moving backward until the first element (`pFirstBorder`);
    1058             :     ///the inner loop starts from the next element of `pCenter` and moves
    1059             :     ///backward until the current `PieLabelInfo` object of the outer loop is
    1060             :     ///reached
    1061             : 
    1062             :     ///like in the previous case on the current `PieLabelInfo` object of
    1063             :     ///the outer loop the `moveAwayFrom` method is invoked by passing
    1064             :     ///the current `PieLabelInfo` object of the inner loop as argument
    1065             : 
    1066             :     ///so each label going from the central one to the first one is checked for
    1067             :     ///overlapping on all subsequent labels (that come before the central label)
    1068             :     ///and in case the overlap occurs the `moveAwayFrom` method tries to fix
    1069             :     ///the issue. The subsequent actions performed after the invocation
    1070             :     ///`moveAwayFrom` are the same detailed above for the first pair of loops
    1071             : 
    1072          24 :     for( pCurrent = p1 ;pCurrent->pNext != pFirstBorder; pCurrent = pCurrent->pPrevious )
    1073             :     {
    1074          13 :         PieLabelInfo* pFix = 0;
    1075          44 :         for( pFix = p2->pNext ;pFix != pCurrent; pFix = pFix->pPrevious )
    1076             :         {
    1077          31 :             if( !pCurrent->moveAwayFrom( pFix, rPageSize, false, bLabelOrderIsAntiClockWise, rbAlternativeMoveDirection ) )
    1078             :             {
    1079           0 :                 if( !rbAlternativeMoveDirection )
    1080             :                 {
    1081           0 :                     rbAlternativeMoveDirection = true;
    1082           0 :                     resetLabelPositionsToPreviousState();
    1083           0 :                     return false;
    1084             :                 }
    1085             :             }
    1086             :         }
    1087             :     }
    1088          11 :     return true;
    1089             : }
    1090             : 
    1091          37 : void PieChart::rearrangeLabelToAvoidOverlapIfRequested( const awt::Size& rPageSize )
    1092             : {
    1093             :     ///this method is invoked by `ChartView::impl_createDiagramAndContent` for
    1094             :     ///pie and donut charts after text label creation;
    1095             :     ///it tries to rearrange labels only when the label placement type is
    1096             :     ///`AVOID_OVERLAP`.
    1097             : 
    1098             : 
    1099             :     ///check whether there are any labels that should be moved
    1100          37 :     std::vector< PieLabelInfo >::iterator aIt1 = m_aLabelInfoList.begin();
    1101          37 :     std::vector< PieLabelInfo >::const_iterator aEnd = m_aLabelInfoList.end();
    1102          37 :     bool bMoveableFound = false;
    1103          47 :     for( ;aIt1!=aEnd; ++aIt1 )
    1104             :     {
    1105          17 :         if(aIt1->bMovementAllowed)
    1106             :         {
    1107           7 :             bMoveableFound = true;
    1108           7 :             break;
    1109             :         }
    1110             :     }
    1111          37 :     if(!bMoveableFound)
    1112          60 :         return;
    1113             : 
    1114           7 :     double fPageDiagonaleLength = sqrt( double( rPageSize.Width*rPageSize.Width + rPageSize.Height*rPageSize.Height) );
    1115           7 :     if( ::rtl::math::approxEqual( fPageDiagonaleLength, 0.0 ) )
    1116           0 :         return;
    1117             : 
    1118             :     ///initialize next and previous member of `PieLabelInfo` objects
    1119           7 :     aIt1 = m_aLabelInfoList.begin();
    1120           7 :     std::vector< PieLabelInfo >::iterator aIt2 = aIt1;
    1121           7 :     if( aIt1==aEnd )//no need to do anything when we only have one label
    1122           0 :         return;
    1123           7 :     aIt1->pPrevious = &(*(m_aLabelInfoList.rbegin()));
    1124           7 :     ++aIt2;
    1125          39 :     for( ;aIt2!=aEnd; ++aIt1, ++aIt2 )
    1126             :     {
    1127          32 :         PieLabelInfo& rInfo1( *aIt1 );
    1128          32 :         PieLabelInfo& rInfo2( *aIt2 );
    1129          32 :         rInfo1.pNext = &rInfo2;
    1130          32 :         rInfo2.pPrevious = &rInfo1;
    1131             :     }
    1132           7 :     aIt1->pNext = &(*(m_aLabelInfoList.begin()));
    1133             : 
    1134             :     ///detect overlaps and move
    1135           7 :     sal_Int32 nMaxIterations = 50;
    1136          25 :     while( detectLabelOverlapsAndMove( rPageSize ) && nMaxIterations > 0 )
    1137          11 :         nMaxIterations--;
    1138             : 
    1139             :     ///create connection lines for the moved labels
    1140           7 :     aEnd = m_aLabelInfoList.end();
    1141           7 :     VLineProperties aVLineProperties;
    1142          46 :     for( aIt1 = m_aLabelInfoList.begin(); aIt1!=aEnd; ++aIt1 )
    1143             :     {
    1144          39 :         PieLabelInfo& rInfo( *aIt1 );
    1145          39 :         if( rInfo.bMoved )
    1146             :         {
    1147          17 :             sal_Int32 nX1 = rInfo.aFirstPosition.getX();
    1148          17 :             sal_Int32 nY1 = rInfo.aFirstPosition.getY();
    1149          17 :             sal_Int32 nX2 = nX1;
    1150          17 :             sal_Int32 nY2 = nY1;
    1151          17 :             ::basegfx::B2IRectangle aRect( lcl_getRect( rInfo.xLabelGroupShape ) );
    1152          17 :             if( nX1 < aRect.getMinX() )
    1153           4 :                 nX2 = aRect.getMinX();
    1154          13 :             else if( nX1 > aRect.getMaxX() )
    1155           2 :                 nX2 = aRect.getMaxX();
    1156             : 
    1157          17 :             if( nY1 < aRect.getMinY() )
    1158           2 :                 nY2 = aRect.getMinY();
    1159          15 :             else if( nY1 > aRect.getMaxY() )
    1160           6 :                 nY2 = aRect.getMaxY();
    1161             : 
    1162             :             //when the line is very short compared to the page size don't create one
    1163          17 :             ::basegfx::B2DVector aLength(nX1-nX2, nY1-nY2);
    1164          17 :             if( (aLength.getLength()/fPageDiagonaleLength) < 0.01 )
    1165          11 :                 continue;
    1166             : 
    1167          12 :             drawing::PointSequenceSequence aPoints(1);
    1168           6 :             aPoints[0].realloc(2);
    1169           6 :             aPoints[0][0].X = nX1;
    1170           6 :             aPoints[0][0].Y = nY1;
    1171           6 :             aPoints[0][1].X = nX2;
    1172           6 :             aPoints[0][1].Y = nY2;
    1173             : 
    1174          12 :             uno::Reference< beans::XPropertySet > xProp( rInfo.xTextShape, uno::UNO_QUERY);
    1175           6 :             if( xProp.is() )
    1176             :             {
    1177           6 :                 sal_Int32 nColor = 0;
    1178           6 :                 xProp->getPropertyValue("CharColor") >>= nColor;
    1179           6 :                 if( nColor != -1 )//automatic font color does not work for lines -> fallback to black
    1180           6 :                     aVLineProperties.Color = uno::makeAny(nColor);
    1181             :             }
    1182          12 :             m_pShapeFactory->createLine2D( rInfo.xTextTarget, aPoints, &aVLineProperties );
    1183             :         }
    1184           7 :     }
    1185             : }
    1186             : 
    1187             : 
    1188             : /** Handle the placement of the label in the best fit case:
    1189             :  *  the routine try to place the label inside the related pie slice,
    1190             :  *  in case of success it returns true else returns false.
    1191             :  *
    1192             :  *  Notation:
    1193             :  *  C: the pie center
    1194             :  *  s: the bisector ray of the current pie slice
    1195             :  *  alpha: the angle between the horizontal axis and the bisector ray s
    1196             :  *  N: the vertex of the label b.b. which is nearest to C
    1197             :  *  F: the vertex of the label b.b. not adjacent to N; F lies on the pie border
    1198             :  *  P, Q: the intersection points between the label b.b. and the bisector ray s;
    1199             :  *        P is the one at minimum distance respect with C
    1200             :  *  e: the edge of the label b.b. where P lies (the nearest edge to C)
    1201             :  *  M: the vertex of e that is not N
    1202             :  *  G: the vertex of the label b.b. which is adjacent to N and that is not M
    1203             :  *  beta: the angle MPF
    1204             :  *  theta: the angle CPF
    1205             :  *
    1206             :  *
    1207             :  *     |
    1208             :  *     |                                /s
    1209             :  *     |                               /
    1210             :  *     |                              /
    1211             :  *     |  G _________________________/____________________________ F
    1212             :  *     |   |                        /Q                          ..|
    1213             :  *     |   |                       /                         . .  |
    1214             :  *     |   |                      /                       .  .    |
    1215             :  *     |   |                     /                     .   .      |
    1216             :  *     |   |                    /                   .    .        |
    1217             :  *     |   |                   /                 .     .          |
    1218             :  *     |   |                  /              d.      .            |
    1219             :  *     |   |                 /             .       .              |
    1220             :  *     |   |                /           .        .                |
    1221             :  *     |   |               /         .         .                  |
    1222             :  *     |   |              /       .          .                    |
    1223             :  *     |   |             /     .           .                      |
    1224             :  *     |   |            /   .            .                        |
    1225             :  *     |   |           / .  \ beta     .                          |
    1226             :  *     |   |__________/._\___|_______.____________________________|
    1227             :  *     |  N          /P  /         .                               M
    1228             :  *     |            /___/theta   .
    1229             :  *     |           /           .
    1230             :  *     |          /          . r
    1231             :  *     |         /         .
    1232             :  *     |        /        .
    1233             :  *     |       /       .
    1234             :  *     |      /      .
    1235             :  *     |     /     .
    1236             :  *     |    /    .
    1237             :  *     |   /   .
    1238             :  *     |  /  .
    1239             :  *     | /\. alpha
    1240             :  *   __|/__|_____________________________________________________________
    1241             :  *     |C
    1242             :  *     |
    1243             :  *
    1244             :  *
    1245             :  *  When alpha = 45k (k integer) s crosses the label b.b. at N exactly.
    1246             :  *  In such a case the nearest edge e is defined as the edge having N as the
    1247             :  *  start vertex and that is covered in the counterclockwise direction when
    1248             :  *  we move from N to the adjacent vertex.
    1249             :  *
    1250             :  *  The nearest vertex N is:
    1251             :  *   1. the bottom left vertex when 0 < alpha < 90
    1252             :  *   2. the bottom right vertex when 90 < alpha < 180
    1253             :  *   3. the top right vertex when 180 < alpha < 270
    1254             :  *   4. the top left vertex when 270 < alpha < 360.
    1255             :  *
    1256             :  *  The nearest edge e is:
    1257             :  *   1. the left edge when −45 < alpha < 45
    1258             :  *   2. the bottom edge when 45 < alpha <135
    1259             :  *   3. the right edge when 135 < alpha < 225
    1260             :  *   4. the top edge when 225 < alpha < 315.
    1261             :  *
    1262             :  **/
    1263          72 : bool PieChart::performLabelBestFitInnerPlacement(ShapeParam& rShapeParam, PieLabelInfo& rPieLabelInfo)
    1264             : {
    1265             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1266             :               "** PieChart::performLabelBestFitInnerPlacement invoked **" );
    1267             : 
    1268             :     // get pie slice properties
    1269          72 :     double fStartAngleDeg = lcl_getDegAngleInStandardRange(rShapeParam.mfUnitCircleStartAngleDegree);
    1270          72 :     double fWidthAngleDeg = rShapeParam.mfUnitCircleWidthAngleDegree;
    1271          72 :     double fHalfWidthAngleDeg = fWidthAngleDeg / 2.0;
    1272          72 :     double fBisectingRayAngleDeg = lcl_getDegAngleInStandardRange(fStartAngleDeg + fHalfWidthAngleDeg);
    1273             : 
    1274             :     // get the middle point of the arc representing the pie slice border
    1275          72 :     double fLogicZ = rShapeParam.mfLogicZ + 1.0;
    1276             :     awt::Point aMiddleArcPoint = PlottingPositionHelper::transformSceneToScreenPosition(
    1277             :             m_pPosHelper->transformUnitCircleToScene(
    1278             :                     fBisectingRayAngleDeg,
    1279             :                     rShapeParam.mfUnitCircleOuterRadius,
    1280          72 :                     fLogicZ ),
    1281         144 :             m_xLogicTarget, m_pShapeFactory, m_nDimension );
    1282             : 
    1283             :     // compute the pie radius
    1284          72 :     basegfx::B2IVector aPieCenter = rPieLabelInfo.aOrigin;
    1285             :     basegfx::B2IVector aRadiusVector(
    1286          72 :             aMiddleArcPoint.X - aPieCenter.getX(),
    1287         216 :             aMiddleArcPoint.Y - aPieCenter.getY() );
    1288          72 :     double fSquaredPieRadius = aRadiusVector.scalar(aRadiusVector);
    1289          72 :     double fPieRadius = sqrt( fSquaredPieRadius );
    1290             : 
    1291             :     // the bb is moved as much as possible near to the border of the pie,
    1292             :     // anyway a small offset from the border is present (0.025 * pie radius)
    1293          72 :     const double fPieBorderOffset = 0.025;
    1294          72 :     fPieRadius = fPieRadius - fPieRadius * fPieBorderOffset;
    1295             : 
    1296             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1297             :               "    pie sector:" );
    1298             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1299             :               "      start angle = " << fStartAngleDeg );
    1300             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1301             :               "      angle width = " << fWidthAngleDeg );
    1302             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1303             :               "      bisecting ray angle = " << fBisectingRayAngleDeg );
    1304             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1305             :               "      pie radius = " << fPieRadius );
    1306             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1307             :               "      pie center = " << rPieLabelInfo.aOrigin );
    1308             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1309             :               "      middle arc point = (" << aMiddleArcPoint.X << ","
    1310             :                                            << aMiddleArcPoint.Y << ")" );
    1311             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1312             :               "    label bounding box:" );
    1313             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1314             :               "      old anchor point = " << rPieLabelInfo.aFirstPosition );
    1315             : 
    1316             : 
    1317          72 :     if( ::rtl::math::approxEqual( fPieRadius, 0.0 ) )
    1318           0 :         return false;
    1319             : 
    1320             :     // get label b.b. width and height
    1321          72 :     ::basegfx::B2IRectangle aBb( lcl_getRect( rPieLabelInfo.xLabelGroupShape ) );
    1322          72 :     double fLabelWidth = aBb.getWidth();
    1323          72 :     double fLabelHeight = aBb.getHeight();
    1324             : 
    1325             :     // -45 <= fAlphaDeg < 315
    1326          72 :     double fAlphaDeg = lcl_getDegAngleInStandardRange(fBisectingRayAngleDeg + 45) - 45;
    1327          72 :     double fAlphaRad = lcl_degToRad(fAlphaDeg);
    1328             : 
    1329             :     // compute nearest edge index
    1330             :     // 0 left
    1331             :     // 1 bottom
    1332             :     // 2 right
    1333             :     // 3 top
    1334          72 :     int nSectorIndex = floor( (fAlphaDeg + 45) / 45.0 );
    1335          72 :     int nNearestEdgeIndex = nSectorIndex / 2;
    1336             : 
    1337             :     // compute lengths of the nearest edge and of the orthogonal edges
    1338          72 :     double fNearestEdgeLength = fLabelWidth;
    1339          72 :     double fOrthogonalEdgeLength = fLabelHeight;
    1340          72 :     int nAxisIndex = 0;
    1341          72 :     int nOrthogonalAxisIndex = 1;
    1342          72 :     if( nNearestEdgeIndex % 2 == 0 ) // nearest edge is vertical
    1343             :     {
    1344          26 :         fNearestEdgeLength = fLabelHeight;
    1345          26 :         fOrthogonalEdgeLength = fLabelWidth;
    1346          26 :         nAxisIndex = 1;
    1347          26 :         nOrthogonalAxisIndex = 0;
    1348             :     }
    1349             : 
    1350             :     // compute the distance between N and P
    1351             :     // such a distance is piece wise linear respect with alpha:
    1352             :     // given 45k <= alpha < 45(k+1) we have
    1353             :     // when k is even: d(N,P) = (length(e) / 2) * (1 - (alpha - 45k)/45)
    1354             :     // when k is odd: d(N,P) = (length(e) / 2) * (1 - (45(k+1) - alpha)/45)
    1355          72 :     int nIndex = nSectorIndex -1;  // nIndex = -1...6
    1356          72 :     double fIndexMod2 = (nIndex + 8) % 2; // fIndexMod2 must be non negative
    1357          72 :     double fSgn = 2.0 * (fIndexMod2 - 0.5); // 0 -> -1, 1 -> 1
    1358          72 :     double fDistanceNP = (fNearestEdgeLength / 2.0) * (1 + fSgn * ((fAlphaDeg - 45 * (nIndex + fIndexMod2)) / 45.0));
    1359          72 :     double fDistancePM = fNearestEdgeLength - fDistanceNP;
    1360             : 
    1361             :     // compute the length of the diagonal vector d,
    1362             :     // that is the distance between P and F
    1363          72 :     double fSquaredDistancePF = fDistancePM * fDistancePM + fOrthogonalEdgeLength * fOrthogonalEdgeLength;
    1364          72 :     double fDistancePF = sqrt( fSquaredDistancePF );
    1365             : 
    1366             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1367             :               "      width = " << fLabelWidth );
    1368             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1369             :               "      height = " <<  fLabelHeight );
    1370             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1371             :               "      nearest edge index = " << nNearestEdgeIndex );
    1372             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1373             :               "      alpha = " << fAlphaDeg );
    1374             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1375             :               "      distance(N,P) = " << fDistanceNP );
    1376             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1377             :               "        nIndex = " << nIndex );
    1378             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1379             :               "        fIndexMod2 = " << fIndexMod2 );
    1380             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1381             :               "        fSgn = " << fSgn );
    1382             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1383             :               "      distance(P,F) = " << fDistancePF );
    1384             : 
    1385             : 
    1386             :     // we check that the condition length(d) <= pie radius holds
    1387          72 :     if (fDistancePF > fPieRadius)
    1388             :     {
    1389           7 :         return false;
    1390             :     }
    1391             : 
    1392             :     // compute beta: the angle of the diagonal vector d,
    1393             :     // that is, the angle in P respect with the triangle PMF;
    1394             :     // since both arguments are non negative the returned value is in [0, PI/2]
    1395          65 :     double fBetaRad = atan2( fOrthogonalEdgeLength, fDistancePM );
    1396             : 
    1397             :     // compute the theta angle, that is the angle in P
    1398             :     // respect with the triangle CFP;
    1399             :     // when the second intersection edge is opposite to the nearest edge,
    1400             :     // theta depends on alpha and beta according to the following relation:
    1401             :     // theta = f(alpha, beta) = s * alpha + 90 * (1 - s * i) + beta
    1402             :     // where i is the nearest edge index and s is the sign of (alpha' - 45),
    1403             :     // with alpha' = (alpha + 45) mod 90;
    1404             :     // when the second intersection edge is adjacent to the nearest edge,
    1405             :     // we have theta = 360 - f(alpha, beta);
    1406             :     // note that in the former case 0 <= f(alpha, beta) <= 180,
    1407             :     // whilst in the latter case 180 <= f(alpha, beta) <= 360;
    1408          65 :     double fAlphaMod90 = fmod( fAlphaDeg + 45, 90.0 ) - 45;
    1409          65 :     double fSign = ::rtl::math::approxEqual( fAlphaMod90, 0.0 )
    1410             :                        ? 0.0
    1411          65 :                        : ( fAlphaMod90 < 0 ) ? -1.0 : 1.0;
    1412          65 :     double fThetaRad = fSign * fAlphaRad + M_PI_2 * (1 - fSign * nNearestEdgeIndex) + fBetaRad;
    1413          65 :     if( fThetaRad > M_PI )
    1414             :     {
    1415          18 :         fThetaRad = 2 * M_PI - fThetaRad;
    1416             :     }
    1417             : 
    1418             :     // compute the length of the positional vector,
    1419             :     // that is the distance between C and P
    1420             :     double fDistanceCP;
    1421             :     // when the bisector ray intersects the b.b. in F we have theta mod 180 == 0
    1422          65 :     if( ::rtl::math::approxEqual( fmod(fThetaRad, M_PI), 0.0 ))
    1423             :     {
    1424           0 :         fDistanceCP = fPieRadius - fDistancePF;
    1425             :     }
    1426             :     else // general case
    1427             :     {
    1428             :         // we can compute d(C,P) by applying some trigonometric formula to
    1429             :         // the triangle CFP : we know length(d) and length(r) = r and we have
    1430             :         // computed the angle in P (theta); so named delta the angle in C and
    1431             :         // gamma the angle in F, by the relation:
    1432             :         //
    1433             :         //                r         d(P,F)     d(C,P)
    1434             :         //            --------- = --------- = ---------
    1435             :         //            sin theta   sin delta   sin gamma
    1436             :         //
    1437             :         // we get the wanted distance
    1438          65 :         double fSinTheta = sin( fThetaRad );
    1439          65 :         double fSinDelta = fDistancePF * fSinTheta / fPieRadius;
    1440          65 :         double fDeltaRad = asin( fSinDelta );
    1441          65 :         double fGammaRad = M_PI - (fThetaRad + fDeltaRad);
    1442          65 :         double fSinGamma = sin( fGammaRad );
    1443          65 :         fDistanceCP = fPieRadius * fSinGamma / fSinTheta;
    1444             :     }
    1445             : 
    1446             :     // define the positional vector
    1447         130 :     basegfx::B2DVector aPositionalVector( cos(fAlphaRad), sin(fAlphaRad) );
    1448          65 :     aPositionalVector.setLength(fDistanceCP);
    1449             : 
    1450             :     // we define a direction vector in order to know
    1451             :     // in which quadrant we are working
    1452         130 :     basegfx::B2DVector aDirection(1.0, 1.0);
    1453          65 :     if( 90 <= fBisectingRayAngleDeg && fBisectingRayAngleDeg < 270 )
    1454             :     {
    1455          52 :         aDirection.setX(-1.0);
    1456             :     }
    1457          65 :     if( fBisectingRayAngleDeg >= 180 )
    1458             :     {
    1459          23 :         aDirection.setY(-1.0);
    1460             :     }
    1461             : 
    1462             :     // compute vertices N, M and G respect with pie center C
    1463         130 :     basegfx::B2DVector aNearestVertex(aPositionalVector);
    1464          65 :     aNearestVertex[nAxisIndex] += -aDirection[nAxisIndex] * fDistanceNP;
    1465         130 :     basegfx::B2DVector aVertexM(aNearestVertex);
    1466          65 :     aVertexM[nAxisIndex] += aDirection[nAxisIndex] * fNearestEdgeLength;
    1467         130 :     basegfx::B2DVector aVertexG(aNearestVertex);
    1468          65 :     aVertexG[nOrthogonalAxisIndex] += aDirection[nOrthogonalAxisIndex] * fOrthogonalEdgeLength;
    1469             : 
    1470             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1471             :               "      beta = " << lcl_radToDeg(fBetaRad) );
    1472             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1473             :               "      theta = " << lcl_radToDeg(fThetaRad) );
    1474             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1475             :               "        fAlphaMod90 = " << fAlphaMod90 );
    1476             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1477             :               "        fSign = " << fSign );
    1478             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1479             :               "      distance(C,P) = " << fDistanceCP );
    1480             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1481             :               "      direction vector = " << aDirection );
    1482             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1483             :               "      N = " << aNearestVertex );
    1484             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1485             :               "      M = " << aVertexM );
    1486             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1487             :               "      G = " << aVertexG );
    1488             : 
    1489             :     // in order to be able to place the label inside the pie slice we need
    1490             :     // to check that each angle between s and the ray starting from C and
    1491             :     // passing through a b.b. vertex is less than half width of the pie slice;
    1492             :     // when the nearest edge e crosses a Cartesian axis it is sufficient
    1493             :     // to test only the vertices belonging to e, else we need to test
    1494             :     // the 2 vertices that aren’t either N or F . Note that if a b.b. edge
    1495             :     // crosses a Cartesian axis then it is the nearest edge to C
    1496             : 
    1497             :     // check the angle between CP and CM
    1498          65 :     double fAngleRad = aPositionalVector.angle(aVertexM);
    1499          65 :     double fAngleDeg = lcl_getDegAngleInStandardRange( lcl_radToDeg(fAngleRad) );
    1500          65 :     if( fAngleDeg > 180 )  // in case the wrong angle has been computed
    1501          20 :         fAngleDeg = 360 - fAngleDeg;
    1502             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1503             :               "      angle between CP and CM: " << fAngleDeg );
    1504          65 :     if( fAngleDeg > fHalfWidthAngleDeg )
    1505             :     {
    1506          35 :         return false;
    1507             :     }
    1508             : 
    1509          72 :     if( ( aNearestVertex[nAxisIndex] >= 0 && aVertexM[nAxisIndex] <= 0 )
    1510          60 :             || ( aNearestVertex[nAxisIndex] <= 0 && aVertexM[nAxisIndex] >= 0 ) )
    1511             :     {
    1512             :         // check the angle between CP and CN
    1513           4 :         fAngleRad = aPositionalVector.angle(aNearestVertex);
    1514           4 :         fAngleDeg = lcl_getDegAngleInStandardRange( lcl_radToDeg(fAngleRad) );
    1515           4 :         if( fAngleDeg > 180 )  // in case the wrong angle has been computed
    1516           2 :             fAngleDeg = 360 - fAngleDeg;
    1517             :         SAL_INFO( "chart2.pie.label.bestfit.inside",
    1518             :                   "      angle between CP and CN: " << fAngleDeg );
    1519           4 :         if( fAngleDeg > fHalfWidthAngleDeg )
    1520             :         {
    1521           0 :             return false;
    1522             :         }
    1523             :     }
    1524             :     else
    1525             :     {
    1526             :         // check the angle between CP and CG
    1527          26 :         fAngleRad = aPositionalVector.angle(aVertexG);
    1528          26 :         fAngleDeg = lcl_getDegAngleInStandardRange( lcl_radToDeg(fAngleRad) );
    1529          26 :         if( fAngleDeg > 180 )  // in case the wrong angle has been computed
    1530          12 :             fAngleDeg = 360 - fAngleDeg;
    1531             :         SAL_INFO( "chart2.pie.label.bestfit.inside",
    1532             :                   "      angle between CP and CG: " << fAngleDeg );
    1533          26 :         if( fAngleDeg > fHalfWidthAngleDeg )
    1534             :         {
    1535           4 :             return false;
    1536             :         }
    1537             :     }
    1538             : 
    1539             :     // compute the b.b. center respect with the pie center
    1540          52 :     basegfx::B2DVector aBBCenter(aNearestVertex);
    1541          26 :     aBBCenter[nAxisIndex] += aDirection[nAxisIndex] * fNearestEdgeLength / 2;
    1542          26 :     aBBCenter[nOrthogonalAxisIndex] += aDirection[nOrthogonalAxisIndex] * fOrthogonalEdgeLength / 2;
    1543             : 
    1544             :     // compute the b.b. anchor point
    1545          52 :     basegfx::B2IVector aNewAnchorPoint = aPieCenter;
    1546          26 :     aNewAnchorPoint[0] += floor(aBBCenter[0]);
    1547          26 :     aNewAnchorPoint[1] -= floor(aBBCenter[1]); // the Y axis on the screen points downward
    1548             : 
    1549             :     // compute the translation vector for moving the label from the current
    1550             :     // screen position to the new one
    1551          52 :     basegfx::B2IVector aTranslationVector = aNewAnchorPoint - rPieLabelInfo.aFirstPosition;
    1552             : 
    1553             :     // compute the new screen position and move the label
    1554          26 :     awt::Point aNewPos( rPieLabelInfo.xLabelGroupShape->getPosition() );
    1555          26 :     aNewPos.X += aTranslationVector.getX();
    1556          26 :     aNewPos.Y += aTranslationVector.getY();
    1557          26 :     rPieLabelInfo.xLabelGroupShape->setPosition(aNewPos);
    1558             : 
    1559             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1560             :               "      center = " <<  aBBCenter );
    1561             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1562             :               "      new anchor point = " << aNewAnchorPoint );
    1563             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1564             :               "      translation vector = " <<  aTranslationVector );
    1565             :     SAL_INFO( "chart2.pie.label.bestfit.inside",
    1566             :               "      new position = (" << aNewPos.X << "," << aNewPos.Y << ")" );
    1567             : 
    1568          98 :     return true;
    1569             : }
    1570             : 
    1571             : /** Handle the outer placement of the labels in the best fit case.
    1572             :  *
    1573             :  */
    1574          46 : bool PieChart::performLabelBestFitOuterPlacement(ShapeParam& /*rShapeParam*/, PieLabelInfo& /*rPieLabelInfo*/)
    1575             : {
    1576             :     SAL_WARN( "chart2.pie.label.bestfit", "to be implemented" );
    1577          46 :     return false;
    1578             : }
    1579             : 
    1580             : /** Handle the placement of the label in the best fit case.
    1581             :  *  First off the routine try to place the label inside the related pie slice,
    1582             :  *  if this is not possible the label is placed outside.
    1583             :  */
    1584          72 : void PieChart::performLabelBestFit(ShapeParam& rShapeParam, PieLabelInfo& rPieLabelInfo)
    1585             : {
    1586          72 :     if( m_bUseRings )
    1587          72 :         return;
    1588             : 
    1589          72 :     if( !performLabelBestFitInnerPlacement(rShapeParam, rPieLabelInfo) )
    1590             :     {
    1591          46 :         performLabelBestFitOuterPlacement(rShapeParam, rPieLabelInfo);
    1592             :     }
    1593             : }
    1594             : 
    1595             : } //namespace chart
    1596             : 
    1597             : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Generated by: LCOV version 1.11