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