LCOV - code coverage report
Current view: top level - chart2/source/view/charttypes - PieChart.cxx (source / functions) Hit Total Coverage
Test: commit 0e63ca4fde4e446f346e35849c756a30ca294aab Lines: 194 455 42.6 %
Date: 2014-04-11 Functions: 23 32 71.9 %
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             : namespace chart
      38             : {
      39             : using namespace ::com::sun::star;
      40             : using namespace ::com::sun::star::chart2;
      41             : 
      42             : class PiePositionHelper : public PolarPlottingPositionHelper
      43             : {
      44             : public:
      45             :     PiePositionHelper( NormalAxis eNormalAxis, double fAngleDegreeOffset );
      46             :     virtual ~PiePositionHelper();
      47             : 
      48             :     bool    getInnerAndOuterRadius( double fCategoryX, double& fLogicInnerRadius, double& fLogicOuterRadius, bool bUseRings, double fMaxOffset ) const;
      49             : 
      50             : public:
      51             :     //Distance between different category rings, seen relative to width of a ring:
      52             :     double  m_fRingDistance; //>=0 m_fRingDistance=1 --> distance == width
      53             : };
      54             : 
      55          15 : PiePositionHelper::PiePositionHelper( NormalAxis eNormalAxis, double fAngleDegreeOffset )
      56             :         : PolarPlottingPositionHelper(eNormalAxis)
      57          15 :         , m_fRingDistance(0.0)
      58             : {
      59          15 :     m_fRadiusOffset = 0.0;
      60          15 :     m_fAngleDegreeOffset = fAngleDegreeOffset;
      61          15 : }
      62             : 
      63          30 : PiePositionHelper::~PiePositionHelper()
      64             : {
      65          30 : }
      66             : 
      67         118 : bool PiePositionHelper::getInnerAndOuterRadius( double fCategoryX
      68             :                                                , double& fLogicInnerRadius, double& fLogicOuterRadius
      69             :                                                , bool bUseRings, double fMaxOffset ) const
      70             : {
      71         118 :     if( !bUseRings )
      72         118 :         fCategoryX = 1.0;
      73             : 
      74         118 :     bool bIsVisible = true;
      75         118 :     double fLogicInner = fCategoryX -0.5+m_fRingDistance/2.0;
      76         118 :     double fLogicOuter = fCategoryX +0.5-m_fRingDistance/2.0;
      77             : 
      78         118 :     if( !isMathematicalOrientationRadius() )
      79             :     {
      80             :         //in this case the given getMaximumX() was not corrcect instead the minimum should have been smaller by fMaxOffset
      81             :         //but during getMaximumX and getMimumX we do not know the axis orientation
      82         104 :         fLogicInner += fMaxOffset;
      83         104 :         fLogicOuter += fMaxOffset;
      84             :     }
      85             : 
      86         118 :     if( fLogicInner >= getLogicMaxX() )
      87           0 :         return false;
      88         118 :     if( fLogicOuter <= getLogicMinX() )
      89           0 :         return false;
      90             : 
      91         118 :     if( fLogicInner < getLogicMinX() )
      92           0 :         fLogicInner = getLogicMinX();
      93         118 :     if( fLogicOuter > getLogicMaxX() )
      94           0 :         fLogicOuter = getLogicMaxX();
      95             : 
      96         118 :     fLogicInnerRadius = fLogicInner;
      97         118 :     fLogicOuterRadius = fLogicOuter;
      98         118 :     if( !isMathematicalOrientationRadius() )
      99         104 :         std::swap(fLogicInnerRadius,fLogicOuterRadius);
     100         118 :     return bIsVisible;
     101             : }
     102             : 
     103          15 : PieChart::PieChart( const uno::Reference<XChartType>& xChartTypeModel
     104             :                    , sal_Int32 nDimensionCount
     105             :                    , bool bExcludingPositioning )
     106             :         : VSeriesPlotter( xChartTypeModel, nDimensionCount )
     107          15 :         , m_pPosHelper( new PiePositionHelper( NormalAxis_Z, (m_nDimension==3)?0.0:90.0 ) )
     108             :         , m_bUseRings(false)
     109          30 :         , m_bSizeExcludesLabelsAndExplodedSegments(bExcludingPositioning)
     110             : {
     111          15 :     ::rtl::math::setNan(&m_fMaxOffset);
     112             : 
     113          15 :     PlotterBase::m_pPosHelper = m_pPosHelper;
     114          15 :     VSeriesPlotter::m_pMainPosHelper = m_pPosHelper;
     115          15 :     m_pPosHelper->m_fRadiusOffset = 0.0;
     116          15 :     m_pPosHelper->m_fRingDistance = 0.0;
     117             : 
     118          15 :     uno::Reference< beans::XPropertySet > xChartTypeProps( xChartTypeModel, uno::UNO_QUERY );
     119          15 :     if( xChartTypeProps.is() ) try
     120             :     {
     121          15 :         xChartTypeProps->getPropertyValue( "UseRings") >>= m_bUseRings;
     122          15 :         if( m_bUseRings )
     123             :         {
     124           0 :             m_pPosHelper->m_fRadiusOffset = 1.0;
     125           0 :             if( nDimensionCount==3 )
     126           0 :                 m_pPosHelper->m_fRingDistance = 0.1;
     127             :         }
     128             :     }
     129           0 :     catch( const uno::Exception& e )
     130             :     {
     131             :         ASSERT_EXCEPTION( e );
     132          15 :     }
     133          15 : }
     134             : 
     135          45 : PieChart::~PieChart()
     136             : {
     137          15 :     delete m_pPosHelper;
     138          30 : }
     139             : 
     140          15 : void PieChart::setScales( const std::vector< ExplicitScaleData >& rScales, bool /* bSwapXAndYAxis */ )
     141             : {
     142             :     OSL_ENSURE(m_nDimension<=static_cast<sal_Int32>(rScales.size()),"Dimension of Plotter does not fit two dimension of given scale sequence");
     143          15 :     m_pPosHelper->setScales( rScales, true );
     144          15 : }
     145             : 
     146          15 : drawing::Direction3D PieChart::getPreferredDiagramAspectRatio() const
     147             : {
     148          15 :     if( m_nDimension == 3 )
     149           1 :         return drawing::Direction3D(1,1,0.10);
     150          14 :     return drawing::Direction3D(1,1,1);
     151             : }
     152             : 
     153           0 : bool PieChart::keepAspectRatio() const
     154             : {
     155           0 :     if( m_nDimension == 3 )
     156           0 :         return false;
     157           0 :     return true;
     158             : }
     159             : 
     160          15 : bool PieChart::shouldSnapRectToUsedArea()
     161             : {
     162          15 :     return true;
     163             : }
     164             : 
     165         118 : uno::Reference< drawing::XShape > PieChart::createDataPoint(
     166             :           const uno::Reference< drawing::XShapes >& xTarget
     167             :         , const uno::Reference< beans::XPropertySet >& xObjectProperties
     168             :         , double fUnitCircleStartAngleDegree, double fUnitCircleWidthAngleDegree
     169             :         , double fUnitCircleInnerRadius, double fUnitCircleOuterRadius
     170             :         , double fLogicZ, double fDepth, double fExplodePercentage
     171             :         , tPropertyNameValueMap* pOverwritePropertiesMap )
     172             : {
     173             :     //transform position:
     174         118 :     drawing::Direction3D aOffset;
     175         118 :     if( !::rtl::math::approxEqual( fExplodePercentage, 0.0 ) )
     176             :     {
     177           0 :         double fAngle  = fUnitCircleStartAngleDegree + fUnitCircleWidthAngleDegree/2.0;
     178           0 :         double fRadius = (fUnitCircleOuterRadius-fUnitCircleInnerRadius)*fExplodePercentage;
     179           0 :         drawing::Position3D aOrigin = m_pPosHelper->transformUnitCircleToScene( 0, 0, fLogicZ );
     180           0 :         drawing::Position3D aNewOrigin = m_pPosHelper->transformUnitCircleToScene( fAngle, fRadius, fLogicZ );
     181           0 :         aOffset = aNewOrigin - aOrigin;
     182             :     }
     183             : 
     184             :     //create point
     185         118 :     uno::Reference< drawing::XShape > xShape(0);
     186         118 :     if(m_nDimension==3)
     187             :     {
     188          24 :         xShape = m_pShapeFactory->createPieSegment( xTarget
     189             :             , fUnitCircleStartAngleDegree, fUnitCircleWidthAngleDegree
     190             :             , fUnitCircleInnerRadius, fUnitCircleOuterRadius
     191          16 :             , aOffset, B3DHomMatrixToHomogenMatrix( m_pPosHelper->getUnitCartesianToScene() )
     192          16 :             , fDepth );
     193             :     }
     194             :     else
     195             :     {
     196         330 :         xShape = m_pShapeFactory->createPieSegment2D( xTarget
     197             :             , fUnitCircleStartAngleDegree, fUnitCircleWidthAngleDegree
     198             :             , fUnitCircleInnerRadius, fUnitCircleOuterRadius
     199         330 :             , aOffset, B3DHomMatrixToHomogenMatrix( m_pPosHelper->getUnitCartesianToScene() ) );
     200             :     }
     201         118 :     this->setMappedProperties( xShape, xObjectProperties, PropertyMapper::getPropertyNameMapForFilledSeriesProperties(), pOverwritePropertiesMap );
     202         118 :     return xShape;
     203             : }
     204             : 
     205          42 : void PieChart::addSeries( VDataSeries* pSeries, sal_Int32 /* zSlot */, sal_Int32 /* xSlot */, sal_Int32 /* ySlot */ )
     206             : {
     207          42 :     VSeriesPlotter::addSeries( pSeries, 0, -1, 0 );
     208          42 : }
     209             : 
     210          15 : double PieChart::getMinimumX()
     211             : {
     212          15 :     return 0.5;
     213             : }
     214         133 : double PieChart::getMaxOffset()
     215             : {
     216         133 :     if (!::rtl::math::isNan(m_fMaxOffset))
     217             :         // Value already cached.  Use it.
     218          88 :         return m_fMaxOffset;
     219             : 
     220          45 :     m_fMaxOffset = 0.0;
     221          45 :     if( m_aZSlots.size()<=0 )
     222           0 :         return m_fMaxOffset;
     223          45 :     if( m_aZSlots[0].size()<=0 )
     224           0 :         return m_fMaxOffset;
     225             : 
     226          45 :     const ::std::vector< VDataSeries* >& rSeriesList( m_aZSlots[0][0].m_aSeriesVector );
     227          45 :     if( rSeriesList.size()<=0 )
     228           0 :         return m_fMaxOffset;
     229             : 
     230          45 :     VDataSeries* pSeries = rSeriesList[0];
     231          45 :     uno::Reference< beans::XPropertySet > xSeriesProp( pSeries->getPropertiesOfSeries() );
     232          45 :     if( !xSeriesProp.is() )
     233           0 :         return m_fMaxOffset;
     234             : 
     235          45 :     double fExplodePercentage=0.0;
     236          45 :     xSeriesProp->getPropertyValue( "Offset") >>= fExplodePercentage;
     237          45 :     if(fExplodePercentage>m_fMaxOffset)
     238           0 :         m_fMaxOffset=fExplodePercentage;
     239             : 
     240          45 :     if(!m_bSizeExcludesLabelsAndExplodedSegments)
     241             :     {
     242          45 :         uno::Sequence< sal_Int32 > aAttributedDataPointIndexList;
     243          45 :         if( xSeriesProp->getPropertyValue( "AttributedDataPoints" ) >>= aAttributedDataPointIndexList )
     244             :         {
     245         111 :             for(sal_Int32 nN=aAttributedDataPointIndexList.getLength();nN--;)
     246             :             {
     247          21 :                 uno::Reference< beans::XPropertySet > xPointProp( pSeries->getPropertiesOfPoint(aAttributedDataPointIndexList[nN]) );
     248          21 :                 if(xPointProp.is())
     249             :                 {
     250          21 :                     fExplodePercentage=0.0;
     251          21 :                     xPointProp->getPropertyValue( "Offset") >>= fExplodePercentage;
     252          21 :                     if(fExplodePercentage>m_fMaxOffset)
     253           0 :                         m_fMaxOffset=fExplodePercentage;
     254             :                 }
     255          21 :             }
     256          45 :         }
     257             :     }
     258          45 :     return m_fMaxOffset;
     259             : }
     260          15 : double PieChart::getMaximumX()
     261             : {
     262          15 :     double fMaxOffset = getMaxOffset();
     263          15 :     if( m_aZSlots.size()>0 && m_bUseRings)
     264           0 :         return m_aZSlots[0].size()+0.5+fMaxOffset;
     265          15 :     return 1.5+fMaxOffset;
     266             : }
     267          15 : double PieChart::getMinimumYInRange( double /* fMinimumX */, double /* fMaximumX */, sal_Int32 /* nAxisIndex */ )
     268             : {
     269          15 :     return 0.0;
     270             : }
     271             : 
     272          15 : double PieChart::getMaximumYInRange( double /* fMinimumX */, double /* fMaximumX */, sal_Int32 /* nAxisIndex */ )
     273             : {
     274          15 :     return 1.0;
     275             : }
     276             : 
     277          31 : bool PieChart::isExpandBorderToIncrementRhythm( sal_Int32 /* nDimensionIndex */ )
     278             : {
     279          31 :     return false;
     280             : }
     281             : 
     282          31 : bool PieChart::isExpandIfValuesCloseToBorder( sal_Int32 /* nDimensionIndex */ )
     283             : {
     284          31 :     return false;
     285             : }
     286             : 
     287          31 : bool PieChart::isExpandWideValuesToZero( sal_Int32 /* nDimensionIndex */ )
     288             : {
     289          31 :     return false;
     290             : }
     291             : 
     292          31 : bool PieChart::isExpandNarrowValuesTowardZero( sal_Int32 /* nDimensionIndex */ )
     293             : {
     294          31 :     return false;
     295             : }
     296             : 
     297           0 : bool PieChart::isSeparateStackingForDifferentSigns( sal_Int32 /* nDimensionIndex */ )
     298             : {
     299           0 :     return false;
     300             : }
     301             : 
     302          30 : void PieChart::createShapes()
     303             : {
     304          30 :     if( m_aZSlots.begin() == m_aZSlots.end() ) //no series
     305           0 :         return;
     306             : 
     307             :     OSL_ENSURE(m_pShapeFactory&&m_xLogicTarget.is()&&m_xFinalTarget.is(),"PieChart is not proper initialized");
     308          30 :     if(!(m_pShapeFactory&&m_xLogicTarget.is()&&m_xFinalTarget.is()))
     309           0 :         return;
     310             : 
     311             :     //the text labels should be always on top of the other series shapes
     312             :     //therefore create an own group for the texts to move them to front
     313             :     //(because the text group is created after the series group the texts are displayed on top)
     314             :     uno::Reference< drawing::XShapes > xSeriesTarget(
     315          30 :         createGroupShape( m_xLogicTarget,OUString() ));
     316             :     uno::Reference< drawing::XShapes > xTextTarget(
     317          60 :         m_pShapeFactory->createGroup2D( m_xFinalTarget,OUString() ));
     318             :     //check necessary here that different Y axis can not be stacked in the same group? ... hm?
     319             : 
     320          30 :     ::std::vector< VDataSeriesGroup >::iterator             aXSlotIter = m_aZSlots[0].begin();
     321          30 :     const ::std::vector< VDataSeriesGroup >::const_iterator aXSlotEnd = m_aZSlots[0].end();
     322             : 
     323          30 :     ::std::vector< VDataSeriesGroup >::size_type nExplodeableSlot = 0;
     324          30 :     if( m_pPosHelper->isMathematicalOrientationRadius() && m_bUseRings )
     325           0 :         nExplodeableSlot = m_aZSlots[0].size()-1;
     326             : 
     327          30 :     m_aLabelInfoList.clear();
     328          30 :     ::rtl::math::setNan(&m_fMaxOffset);
     329          30 :     sal_Int32 n3DRelativeHeight = 100;
     330          60 :     uno::Reference< beans::XPropertySet > xPropertySet( m_xChartTypeModel, uno::UNO_QUERY );
     331          30 :     if ( (m_nDimension==3) && xPropertySet.is())
     332             :     {
     333             :         try
     334             :         {
     335           2 :             uno::Any aAny = xPropertySet->getPropertyValue( "3DRelativeHeight" );
     336           2 :             aAny >>= n3DRelativeHeight;
     337             :         }
     338           0 :         catch (const uno::Exception&) { }
     339             :     }
     340             : 
     341          60 :     for( double fSlotX=0; aXSlotIter != aXSlotEnd && (m_bUseRings||fSlotX<0.5 ); ++aXSlotIter, fSlotX+=1.0 )
     342             :     {
     343          30 :         ::std::vector< VDataSeries* >* pSeriesList = &(aXSlotIter->m_aSeriesVector);
     344          30 :         if( pSeriesList->size()<=0 )//there should be only one series in each x slot
     345           0 :             continue;
     346          30 :         VDataSeries* pSeries = (*pSeriesList)[0];
     347          30 :         if(!pSeries)
     348           0 :             continue;
     349             : 
     350          30 :         bool bHasFillColorMapping = pSeries->hasPropertyMapping("FillColor");
     351             : 
     352          30 :         m_pPosHelper->m_fAngleDegreeOffset = pSeries->getStartingAngle();
     353             : 
     354          30 :         double fLogicYSum = 0.0;
     355             :         //iterate through all points to get the sum
     356          30 :         sal_Int32 nPointIndex=0;
     357          30 :         sal_Int32 nPointCount=pSeries->getTotalPointCount();
     358         148 :         for( nPointIndex = 0; nPointIndex < nPointCount; nPointIndex++ )
     359             :         {
     360         118 :             double fY = pSeries->getYValue( nPointIndex );
     361             :             if(fY<0.0)
     362             :             {
     363             :                 //@todo warn somehow that negative values are treated as positive
     364             :             }
     365         118 :             if( ::rtl::math::isNan(fY) )
     366           0 :                 continue;
     367         118 :             fLogicYSum += fabs(fY);
     368             :         }
     369          30 :         if(fLogicYSum==0.0)
     370           0 :             continue;
     371          30 :         double fLogicYForNextPoint = 0.0;
     372             :         //iterate through all points to create shapes
     373         148 :         for( nPointIndex = 0; nPointIndex < nPointCount; nPointIndex++ )
     374             :         {
     375             :             double fLogicInnerRadius, fLogicOuterRadius;
     376         118 :             double fOffset = getMaxOffset();
     377         118 :             bool bIsVisible = m_pPosHelper->getInnerAndOuterRadius( fSlotX+1.0, fLogicInnerRadius, fLogicOuterRadius, m_bUseRings, fOffset );
     378         118 :             if( !bIsVisible )
     379           0 :                 continue;
     380             : 
     381         118 :             double fDepth  = this->getTransformedDepth() * (n3DRelativeHeight / 100.0);
     382             : 
     383         118 :             uno::Reference< drawing::XShapes > xSeriesGroupShape_Shapes = getSeriesGroupShape(pSeries, xSeriesTarget);
     384             :             //collect data point information (logic coordinates, style ):
     385         118 :             double fLogicYValue = fabs(pSeries->getYValue( nPointIndex ));
     386         118 :             if( ::rtl::math::isNan(fLogicYValue) )
     387           0 :                 continue;
     388         118 :             if(fLogicYValue==0.0)//@todo: continue also if the resolution to small
     389           0 :                 continue;
     390         118 :             double fLogicYPos = fLogicYForNextPoint;
     391         118 :             fLogicYForNextPoint += fLogicYValue;
     392             : 
     393         236 :             uno::Reference< beans::XPropertySet > xPointProperties = pSeries->getPropertiesOfPoint( nPointIndex );
     394             : 
     395             :             //iterate through all subsystems to create partial points
     396             :             {
     397             :                 //logic values on angle axis:
     398         118 :                 double fLogicStartAngleValue = fLogicYPos/fLogicYSum;
     399         118 :                 double fLogicEndAngleValue = (fLogicYPos+fLogicYValue)/fLogicYSum;
     400             : 
     401         118 :                 double fExplodePercentage=0.0;
     402         118 :                 bool bDoExplode = ( nExplodeableSlot == static_cast< ::std::vector< VDataSeriesGroup >::size_type >(fSlotX) );
     403         118 :                 if(bDoExplode) try
     404             :                 {
     405         118 :                     xPointProperties->getPropertyValue( "Offset") >>= fExplodePercentage;
     406             :                 }
     407           0 :                 catch( const uno::Exception& e )
     408             :                 {
     409             :                     ASSERT_EXCEPTION( e );
     410             :                 }
     411             : 
     412             :                 //transforme to unit circle:
     413         118 :                 double fUnitCircleWidthAngleDegree = m_pPosHelper->getWidthAngleDegree( fLogicStartAngleValue, fLogicEndAngleValue );
     414         118 :                 double fUnitCircleStartAngleDegree = m_pPosHelper->transformToAngleDegree( fLogicStartAngleValue );
     415         118 :                 double fUnitCircleInnerRadius = m_pPosHelper->transformToRadius( fLogicInnerRadius );
     416         118 :                 double fUnitCircleOuterRadius = m_pPosHelper->transformToRadius( fLogicOuterRadius );
     417             : 
     418             :                 //point color:
     419         118 :                 boost::scoped_ptr< tPropertyNameValueMap > apOverwritePropertiesMap(NULL);
     420             :                 {
     421         118 :                     if(!pSeries->hasPointOwnColor(nPointIndex) && m_xColorScheme.is())
     422             :                     {
     423         104 :                         apOverwritePropertiesMap.reset( new tPropertyNameValueMap() );
     424         208 :                         (*apOverwritePropertiesMap)["FillColor"] = uno::makeAny(
     425         208 :                             m_xColorScheme->getColorByIndex( nPointIndex ));
     426             :                     }
     427             :                 }
     428             : 
     429             :                 //create data point
     430         118 :                 double fLogicZ = -1.0; // For 3D pie chart label position
     431             :                 uno::Reference<drawing::XShape> xPointShape(
     432             :                     createDataPoint( xSeriesGroupShape_Shapes, xPointProperties
     433             :                                     , fUnitCircleStartAngleDegree, fUnitCircleWidthAngleDegree
     434             :                                     , fUnitCircleInnerRadius, fUnitCircleOuterRadius
     435         236 :                                     , fLogicZ, fDepth, fExplodePercentage, apOverwritePropertiesMap.get() ) );
     436             : 
     437         118 :                 if(bHasFillColorMapping)
     438             :                 {
     439           0 :                     uno::Reference< beans::XPropertySet > xProps( xPointShape, uno::UNO_QUERY_THROW );
     440           0 :                     xProps->setPropertyValue("FillColor", uno::makeAny(static_cast<sal_Int32>(
     441           0 :                                     pSeries->getValueByProperty(nPointIndex, "FillColor"))));
     442             :                 }
     443             : 
     444             :                 //create label
     445         118 :                 if( pSeries->getDataPointLabelIfLabel(nPointIndex) )
     446             :                 {
     447           0 :                     if( !::rtl::math::approxEqual( fExplodePercentage, 0.0 ) )
     448             :                     {
     449           0 :                         double fExplodeOffset = (fUnitCircleOuterRadius-fUnitCircleInnerRadius)*fExplodePercentage;
     450           0 :                         fUnitCircleInnerRadius += fExplodeOffset;
     451           0 :                         fUnitCircleOuterRadius += fExplodeOffset;
     452             :                     }
     453             : 
     454           0 :                     sal_Int32 nLabelPlacement = pSeries->getLabelPlacement( nPointIndex, m_xChartTypeModel, m_nDimension, m_pPosHelper->isSwapXAndY() );
     455             : 
     456             :                     // AVOID_OVERLAP is in fact "Best fit" in the UI.
     457           0 :                     bool bMovementAllowed = ( nLabelPlacement == ::com::sun::star::chart::DataLabelPlacement::AVOID_OVERLAP );
     458           0 :                     if( bMovementAllowed )
     459             :                         // Use center for "Best fit" for now. In the future we
     460             :                         // may want to implement a real best fit algorithm.
     461             :                         // But center is good enough, and close to what Excel
     462             :                         // does.
     463           0 :                         nLabelPlacement = ::com::sun::star::chart::DataLabelPlacement::CENTER;
     464             : 
     465           0 :                     LabelAlignment eAlignment(LABEL_ALIGN_CENTER);
     466           0 :                     sal_Int32 nScreenValueOffsetInRadiusDirection = 0 ;
     467           0 :                     if( nLabelPlacement == ::com::sun::star::chart::DataLabelPlacement::OUTSIDE )
     468           0 :                         nScreenValueOffsetInRadiusDirection = (3!=m_nDimension) ? 150 : 0;//todo maybe calculate this font height dependent
     469           0 :                     else if( nLabelPlacement == ::com::sun::star::chart::DataLabelPlacement::INSIDE )
     470           0 :                         nScreenValueOffsetInRadiusDirection = (3!=m_nDimension) ? -150 : 0;//todo maybe calculate this font height dependent
     471           0 :                     PolarLabelPositionHelper aPolarPosHelper(m_pPosHelper,m_nDimension,m_xLogicTarget,m_pShapeFactory);
     472             :                     awt::Point aScreenPosition2D(
     473             :                         aPolarPosHelper.getLabelScreenPositionAndAlignmentForUnitCircleValues(eAlignment, nLabelPlacement
     474             :                         , fUnitCircleStartAngleDegree, fUnitCircleWidthAngleDegree
     475           0 :                         , fUnitCircleInnerRadius, fUnitCircleOuterRadius, fLogicZ+0.5, 0 ));
     476             : 
     477           0 :                     PieLabelInfo aPieLabelInfo;
     478           0 :                     aPieLabelInfo.aFirstPosition = basegfx::B2IVector( aScreenPosition2D.X, aScreenPosition2D.Y );
     479           0 :                     awt::Point aOrigin( aPolarPosHelper.transformSceneToScreenPosition( m_pPosHelper->transformUnitCircleToScene( 0.0, 0.0, fLogicZ+1.0 ) ) );
     480           0 :                     aPieLabelInfo.aOrigin = basegfx::B2IVector( aOrigin.X, aOrigin.Y );
     481             : 
     482             :                     //add a scaling independent Offset if requested
     483           0 :                     if( nScreenValueOffsetInRadiusDirection != 0)
     484             :                     {
     485           0 :                         basegfx::B2IVector aDirection( aScreenPosition2D.X- aOrigin.X, aScreenPosition2D.Y- aOrigin.Y );
     486           0 :                         aDirection.setLength(nScreenValueOffsetInRadiusDirection);
     487           0 :                         aScreenPosition2D.X += aDirection.getX();
     488           0 :                         aScreenPosition2D.Y += aDirection.getY();
     489             :                     }
     490             : 
     491           0 :                     double nVal = pSeries->getYValue( nPointIndex );
     492           0 :                     aPieLabelInfo.xTextShape = this->createDataLabel( xTextTarget, *pSeries, nPointIndex
     493           0 :                                     , nVal, fLogicYSum, aScreenPosition2D, eAlignment );
     494             : 
     495           0 :                     uno::Reference< container::XChild > xChild( aPieLabelInfo.xTextShape, uno::UNO_QUERY );
     496           0 :                     if( xChild.is() )
     497           0 :                         aPieLabelInfo.xLabelGroupShape = uno::Reference<drawing::XShape>( xChild->getParent(), uno::UNO_QUERY );
     498           0 :                     aPieLabelInfo.fValue = nVal;
     499           0 :                     aPieLabelInfo.bMovementAllowed = bMovementAllowed;
     500           0 :                     aPieLabelInfo.bMoved= false;
     501           0 :                     aPieLabelInfo.xTextTarget = xTextTarget;
     502           0 :                     m_aLabelInfoList.push_back(aPieLabelInfo);
     503             :                 }
     504             : 
     505         118 :                 if(!bDoExplode)
     506             :                 {
     507             :                     AbstractShapeFactory::setShapeName( xPointShape
     508           0 :                                 , ObjectIdentifier::createPointCID( pSeries->getPointCID_Stub(), nPointIndex ) );
     509             :                 }
     510             :                 else try
     511             :                 {
     512             :                     //enable dragging of outer segments
     513             : 
     514         118 :                     double fAngle  = fUnitCircleStartAngleDegree + fUnitCircleWidthAngleDegree/2.0;
     515         118 :                     double fMaxDeltaRadius = fUnitCircleOuterRadius-fUnitCircleInnerRadius;
     516         118 :                     drawing::Position3D aOrigin = m_pPosHelper->transformUnitCircleToScene( fAngle, fUnitCircleOuterRadius, fLogicZ );
     517         118 :                     drawing::Position3D aNewOrigin = m_pPosHelper->transformUnitCircleToScene( fAngle, fUnitCircleOuterRadius + fMaxDeltaRadius, fLogicZ );
     518             : 
     519         118 :                     sal_Int32 nOffsetPercent( static_cast<sal_Int32>(fExplodePercentage * 100.0) );
     520             : 
     521             :                     awt::Point aMinimumPosition( PlottingPositionHelper::transformSceneToScreenPosition(
     522         118 :                         aOrigin, m_xLogicTarget, m_pShapeFactory, m_nDimension ) );
     523             :                     awt::Point aMaximumPosition( PlottingPositionHelper::transformSceneToScreenPosition(
     524         118 :                         aNewOrigin, m_xLogicTarget, m_pShapeFactory, m_nDimension ) );
     525             : 
     526             :                     //enable draging of piesegments
     527             :                     OUString aPointCIDStub( ObjectIdentifier::createSeriesSubObjectStub( OBJECTTYPE_DATA_POINT
     528             :                         , pSeries->getSeriesParticle()
     529         118 :                         , ObjectIdentifier::getPieSegmentDragMethodServiceName()
     530             :                         , ObjectIdentifier::createPieSegmentDragParameterString(
     531             :                             nOffsetPercent, aMinimumPosition, aMaximumPosition )
     532         236 :                         ) );
     533             : 
     534             :                     AbstractShapeFactory::setShapeName( xPointShape
     535         118 :                                 , ObjectIdentifier::createPointCID( aPointCIDStub, nPointIndex ) );
     536             :                 }
     537           0 :                 catch( const uno::Exception& e )
     538             :                 {
     539             :                     ASSERT_EXCEPTION( e );
     540         118 :                 }
     541             :             }//next series in x slot (next y slot)
     542         118 :         }//next category
     543          30 :     }//next x slot
     544             :     /* @todo remove series shapes if empty
     545             :     //remove and delete point-group-shape if empty
     546             :     if(!xSeriesGroupShape_Shapes->getCount())
     547             :     {
     548             :         (*aSeriesIter)->m_xShape.set(NULL);
     549             :         m_xLogicTarget->remove(xSeriesGroupShape_Shape);
     550             :     }
     551             :     */
     552             : 
     553             :     //remove and delete series-group-shape if empty
     554             : 
     555             :     //... todo
     556             : }
     557             : 
     558             : namespace
     559             : {
     560             : 
     561           0 : ::basegfx::B2IRectangle lcl_getRect( const uno::Reference< drawing::XShape >& xShape )
     562             : {
     563           0 :     ::basegfx::B2IRectangle aRect;
     564           0 :     if( xShape.is() )
     565           0 :         aRect = BaseGFXHelper::makeRectangle(xShape->getPosition(),xShape->getSize() );
     566           0 :     return aRect;
     567             : }
     568             : 
     569           0 : bool lcl_isInsidePage( const awt::Point& rPos, const awt::Size& rSize, const awt::Size& rPageSize )
     570             : {
     571           0 :     if( rPos.X < 0  || rPos.Y < 0 )
     572           0 :         return false;
     573           0 :     if( (rPos.X + rSize.Width) > rPageSize.Width  )
     574           0 :         return false;
     575           0 :     if( (rPos.Y + rSize.Height) > rPageSize.Height )
     576           0 :         return false;
     577           0 :     return true;
     578             : }
     579             : 
     580             : }//end anonymous namespace
     581             : 
     582           0 : PieChart::PieLabelInfo::PieLabelInfo()
     583             :     : xTextShape(0), xLabelGroupShape(0), aFirstPosition(), aOrigin(), fValue(0.0)
     584           0 :     , bMovementAllowed(false), bMoved(false), xTextTarget(0), pPrevious(0),pNext(0)
     585             : {
     586           0 : }
     587             : 
     588           0 : bool PieChart::PieLabelInfo::moveAwayFrom( const PieChart::PieLabelInfo* pFix, const awt::Size& rPageSize, bool bMoveHalfWay, bool bMoveClockwise, bool bAlternativeMoveDirection )
     589             : {
     590             :     //return true if the move was successful
     591           0 :     if(!this->bMovementAllowed)
     592           0 :         return false;
     593             : 
     594           0 :     const sal_Int32 nLabelDistanceX = rPageSize.Width/50;
     595           0 :     const sal_Int32 nLabelDistanceY = rPageSize.Height/50;
     596             : 
     597           0 :     ::basegfx::B2IRectangle aOverlap( lcl_getRect( this->xLabelGroupShape ) );
     598           0 :     aOverlap.intersect( lcl_getRect( pFix->xLabelGroupShape ) );
     599           0 :     if( !aOverlap.isEmpty() )
     600             :     {
     601             :         (void)bAlternativeMoveDirection;//todo
     602             : 
     603           0 :         basegfx::B2IVector aRadiusDirection = this->aFirstPosition - this->aOrigin;
     604           0 :         aRadiusDirection.setLength(1.0);
     605           0 :         basegfx::B2IVector aTangentialDirection( -aRadiusDirection.getY(), aRadiusDirection.getX() );
     606           0 :         bool bShiftHorizontal = abs(aTangentialDirection.getX()) > abs(aTangentialDirection.getY());
     607             : 
     608           0 :         sal_Int32 nShift = bShiftHorizontal ? static_cast<sal_Int32>(aOverlap.getWidth()) : static_cast<sal_Int32>(aOverlap.getHeight());
     609           0 :         nShift += (bShiftHorizontal ? nLabelDistanceX : nLabelDistanceY);
     610           0 :         if( bMoveHalfWay )
     611           0 :             nShift/=2;
     612           0 :         if(!bMoveClockwise)
     613           0 :             nShift*=-1;
     614           0 :         awt::Point aOldPos( this->xLabelGroupShape->getPosition() );
     615           0 :         basegfx::B2IVector aNewPos = basegfx::B2IVector( aOldPos.X, aOldPos.Y ) + nShift*aTangentialDirection;
     616             : 
     617             :         //check whether the new position is ok
     618           0 :         awt::Point aNewAWTPos( aNewPos.getX(), aNewPos.getY() );
     619           0 :         if( !lcl_isInsidePage( aNewAWTPos, this->xLabelGroupShape->getSize(), rPageSize ) )
     620           0 :             return false;
     621             : 
     622           0 :         this->xLabelGroupShape->setPosition( aNewAWTPos );
     623           0 :         this->bMoved = true;
     624             :     }
     625           0 :     return true;
     626             : }
     627             : 
     628           0 : void PieChart::resetLabelPositionsToPreviousState()
     629             : {
     630           0 :     std::vector< PieLabelInfo >::iterator aIt = m_aLabelInfoList.begin();
     631           0 :     std::vector< PieLabelInfo >::const_iterator aEnd = m_aLabelInfoList.end();
     632           0 :     for( ;aIt!=aEnd; ++aIt )
     633           0 :         aIt->xLabelGroupShape->setPosition(aIt->aPreviousPosition);
     634           0 : }
     635             : 
     636           0 : bool PieChart::detectLabelOverlapsAndMove( const awt::Size& rPageSize )
     637             : {
     638             :     //returns true when there might be more to do
     639             : 
     640             :     //find borders of a group of overlapping labels
     641           0 :     bool bOverlapFound = false;
     642           0 :     PieLabelInfo* pStart = &(*(m_aLabelInfoList.rbegin()));
     643           0 :     PieLabelInfo* pFirstBorder = 0;
     644           0 :     PieLabelInfo* pSecondBorder = 0;
     645           0 :     PieLabelInfo* pCurrent = pStart;
     646           0 :     do
     647             :     {
     648           0 :         ::basegfx::B2IRectangle aPreviousOverlap( lcl_getRect( pCurrent->xLabelGroupShape ) );
     649           0 :         ::basegfx::B2IRectangle aNextOverlap( aPreviousOverlap );
     650           0 :         aPreviousOverlap.intersect( lcl_getRect( pCurrent->pPrevious->xLabelGroupShape ) );
     651           0 :         aNextOverlap.intersect( lcl_getRect( pCurrent->pNext->xLabelGroupShape ) );
     652             : 
     653           0 :         bool bPreviousOverlap = !aPreviousOverlap.isEmpty();
     654           0 :         bool bNextOverlap = !aNextOverlap.isEmpty();
     655           0 :         if( bPreviousOverlap || bNextOverlap )
     656           0 :             bOverlapFound = true;
     657           0 :         if( !bPreviousOverlap && bNextOverlap )
     658             :         {
     659           0 :             pFirstBorder = pCurrent;
     660           0 :             break;
     661             :         }
     662           0 :         pCurrent = pCurrent->pNext;
     663             :     }
     664             :     while( pCurrent != pStart );
     665             : 
     666           0 :     if( !bOverlapFound )
     667           0 :         return false;
     668             : 
     669           0 :     if( pFirstBorder )
     670             :     {
     671           0 :         pCurrent = pFirstBorder;
     672           0 :         do
     673             :         {
     674           0 :             ::basegfx::B2IRectangle aPreviousOverlap( lcl_getRect( pCurrent->xLabelGroupShape ) );
     675           0 :             ::basegfx::B2IRectangle aNextOverlap( aPreviousOverlap );
     676           0 :             aPreviousOverlap.intersect( lcl_getRect( pCurrent->pPrevious->xLabelGroupShape ) );
     677           0 :             aNextOverlap.intersect( lcl_getRect( pCurrent->pNext->xLabelGroupShape ) );
     678             : 
     679           0 :             if( !aPreviousOverlap.isEmpty() && aNextOverlap.isEmpty() )
     680             :             {
     681           0 :                 pSecondBorder = pCurrent;
     682           0 :                 break;
     683             :             }
     684           0 :             pCurrent = pCurrent->pNext;
     685             :         }
     686             :         while( pCurrent != pFirstBorder );
     687             :     }
     688             : 
     689           0 :     if( !pFirstBorder || !pSecondBorder )
     690             :     {
     691           0 :         pFirstBorder = &(*(m_aLabelInfoList.rbegin()));
     692           0 :         pSecondBorder = &(*(m_aLabelInfoList.begin()));
     693             :     }
     694             : 
     695             :     //find center
     696           0 :     PieLabelInfo* pCenter = pFirstBorder;
     697           0 :     sal_Int32 nOverlapGroupCount = 1;
     698           0 :     for( pCurrent = pFirstBorder ;pCurrent != pSecondBorder; pCurrent = pCurrent->pNext )
     699           0 :         nOverlapGroupCount++;
     700           0 :     sal_Int32 nCenterPos = nOverlapGroupCount/2;
     701           0 :     bool bSingleCenter = nOverlapGroupCount%2 != 0;
     702           0 :     if( bSingleCenter )
     703           0 :         nCenterPos++;
     704           0 :     if(nCenterPos>1)
     705             :     {
     706           0 :         pCurrent = pFirstBorder;
     707           0 :         while( --nCenterPos )
     708           0 :             pCurrent = pCurrent->pNext;
     709           0 :         pCenter = pCurrent;
     710             :     }
     711             : 
     712             :     //remind current positions
     713           0 :     pCurrent = pStart;
     714           0 :     do
     715             :     {
     716           0 :         pCurrent->aPreviousPosition = pCurrent->xLabelGroupShape->getPosition();
     717           0 :         pCurrent = pCurrent->pNext;
     718             :     }
     719             :     while( pCurrent != pStart );
     720             : 
     721             : 
     722           0 :     bool bAlternativeMoveDirection = false;
     723           0 :     if( !tryMoveLabels( pFirstBorder, pSecondBorder, pCenter, bSingleCenter, bAlternativeMoveDirection, rPageSize ) )
     724           0 :         tryMoveLabels( pFirstBorder, pSecondBorder, pCenter, bSingleCenter, bAlternativeMoveDirection, rPageSize );
     725           0 :     return true;
     726             : }
     727             : 
     728           0 : bool PieChart::tryMoveLabels( PieLabelInfo* pFirstBorder, PieLabelInfo* pSecondBorder
     729             :                              , PieLabelInfo* pCenter
     730             :                              , bool bSingleCenter, bool& rbAlternativeMoveDirection, const awt::Size& rPageSize )
     731             : {
     732           0 :     PieLabelInfo* p1 = bSingleCenter ? pCenter->pPrevious : pCenter;
     733           0 :     PieLabelInfo* p2 = pCenter->pNext;
     734             :     //return true when successful
     735             : 
     736           0 :     bool bLabelOrderIsAntiClockWise = m_pPosHelper->isMathematicalOrientationAngle();
     737             : 
     738           0 :     PieLabelInfo* pCurrent = 0;
     739           0 :     for( pCurrent = p2 ;pCurrent->pPrevious != pSecondBorder; pCurrent = pCurrent->pNext )
     740             :     {
     741           0 :         PieLabelInfo* pFix = 0;
     742           0 :         for( pFix = p2->pPrevious ;pFix != pCurrent; pFix = pFix->pNext )
     743             :         {
     744           0 :             if( !pCurrent->moveAwayFrom( pFix, rPageSize, !bSingleCenter && pCurrent == p2, !bLabelOrderIsAntiClockWise, rbAlternativeMoveDirection ) )
     745             :             {
     746           0 :                 if( !rbAlternativeMoveDirection )
     747             :                 {
     748           0 :                     rbAlternativeMoveDirection = true;
     749           0 :                     resetLabelPositionsToPreviousState();
     750           0 :                     return false;
     751             :                 }
     752             :             }
     753             :         }
     754             :     }
     755           0 :     for( pCurrent = p1 ;pCurrent->pNext != pFirstBorder; pCurrent = pCurrent->pPrevious )
     756             :     {
     757           0 :         PieLabelInfo* pFix = 0;
     758           0 :         for( pFix = p2->pNext ;pFix != pCurrent; pFix = pFix->pPrevious )
     759             :         {
     760           0 :             if( !pCurrent->moveAwayFrom( pFix, rPageSize, false, bLabelOrderIsAntiClockWise, rbAlternativeMoveDirection ) )
     761             :             {
     762           0 :                 if( !rbAlternativeMoveDirection )
     763             :                 {
     764           0 :                     rbAlternativeMoveDirection = true;
     765           0 :                     resetLabelPositionsToPreviousState();
     766           0 :                     return false;
     767             :                 }
     768             :             }
     769             :         }
     770             :     }
     771           0 :     return true;
     772             : }
     773             : 
     774          15 : void PieChart::rearrangeLabelToAvoidOverlapIfRequested( const awt::Size& rPageSize )
     775             : {
     776             :     //check whether there are any labels that should be moved
     777          15 :     std::vector< PieLabelInfo >::iterator aIt1 = m_aLabelInfoList.begin();
     778          15 :     std::vector< PieLabelInfo >::const_iterator aEnd = m_aLabelInfoList.end();
     779          15 :     bool bMoveableFound = false;
     780          15 :     for( ;aIt1!=aEnd; ++aIt1 )
     781             :     {
     782           0 :         if(aIt1->bMovementAllowed)
     783             :         {
     784           0 :             bMoveableFound = true;
     785           0 :             break;
     786             :         }
     787             :     }
     788          15 :     if(!bMoveableFound)
     789          30 :         return;
     790             : 
     791           0 :     double fPageDiagonaleLength = sqrt( double( rPageSize.Width*rPageSize.Width + rPageSize.Height*rPageSize.Height) );
     792           0 :     if( ::rtl::math::approxEqual( fPageDiagonaleLength, 0.0 ) )
     793           0 :         return;
     794             : 
     795             :     //init next and previous
     796           0 :     aIt1 = m_aLabelInfoList.begin();
     797           0 :     std::vector< PieLabelInfo >::iterator aIt2 = aIt1;
     798           0 :     if( aIt1==aEnd )//no need to do anything when we only have one label
     799           0 :         return;
     800           0 :     aIt1->pPrevious = &(*(m_aLabelInfoList.rbegin()));
     801           0 :     ++aIt2;
     802           0 :     for( ;aIt2!=aEnd; ++aIt1, ++aIt2 )
     803             :     {
     804           0 :         PieLabelInfo& rInfo1( *aIt1 );
     805           0 :         PieLabelInfo& rInfo2( *aIt2 );
     806           0 :         rInfo1.pNext = &rInfo2;
     807           0 :         rInfo2.pPrevious = &rInfo1;
     808             :     }
     809           0 :     aIt1->pNext = &(*(m_aLabelInfoList.begin()));
     810             : 
     811             :     //detect overlaps and move
     812           0 :     sal_Int32 nMaxIterations = 50;
     813           0 :     while( detectLabelOverlapsAndMove( rPageSize ) && nMaxIterations > 0 )
     814           0 :         nMaxIterations--;
     815             : 
     816             :     //create connection lines for the moved labels
     817           0 :     aEnd = m_aLabelInfoList.end();
     818           0 :     VLineProperties aVLineProperties;
     819           0 :     for( aIt1 = m_aLabelInfoList.begin(); aIt1!=aEnd; ++aIt1 )
     820             :     {
     821           0 :         PieLabelInfo& rInfo( *aIt1 );
     822           0 :         if( rInfo.bMoved )
     823             :         {
     824           0 :             sal_Int32 nX1 = rInfo.aFirstPosition.getX();
     825           0 :             sal_Int32 nY1 = rInfo.aFirstPosition.getY();
     826           0 :             sal_Int32 nX2 = nX1;
     827           0 :             sal_Int32 nY2 = nY1;
     828           0 :             ::basegfx::B2IRectangle aRect( lcl_getRect( rInfo.xLabelGroupShape ) );
     829           0 :             if( nX1 < aRect.getMinX() )
     830           0 :                 nX2 = aRect.getMinX();
     831           0 :             else if( nX1 > aRect.getMaxX() )
     832           0 :                 nX2 = aRect.getMaxX();
     833             : 
     834           0 :             if( nY1 < aRect.getMinY() )
     835           0 :                 nY2 = aRect.getMinY();
     836           0 :             else if( nY1 > aRect.getMaxY() )
     837           0 :                 nY2 = aRect.getMaxY();
     838             : 
     839             :             //when the line is very short compared to the page size don't create one
     840           0 :             ::basegfx::B2DVector aLength(nX1-nX2, nY1-nY2);
     841           0 :             if( (aLength.getLength()/fPageDiagonaleLength) < 0.01 )
     842           0 :                 continue;
     843             : 
     844           0 :             drawing::PointSequenceSequence aPoints(1);
     845           0 :             aPoints[0].realloc(2);
     846           0 :             aPoints[0][0].X = nX1;
     847           0 :             aPoints[0][0].Y = nY1;
     848           0 :             aPoints[0][1].X = nX2;
     849           0 :             aPoints[0][1].Y = nY2;
     850             : 
     851           0 :             uno::Reference< beans::XPropertySet > xProp( rInfo.xTextShape, uno::UNO_QUERY);
     852           0 :             if( xProp.is() )
     853             :             {
     854           0 :                 sal_Int32 nColor = 0;
     855           0 :                 xProp->getPropertyValue("CharColor") >>= nColor;
     856           0 :                 if( nColor != -1 )//automatic font color does not work for lines -> fallback to black
     857           0 :                     aVLineProperties.Color = uno::makeAny(nColor);
     858             :             }
     859           0 :             m_pShapeFactory->createLine2D( rInfo.xTextTarget, aPoints, &aVLineProperties );
     860             :         }
     861           0 :     }
     862             : }
     863             : 
     864             : } //namespace chart
     865             : 
     866             : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Generated by: LCOV version 1.10