bump product version to 4.1.6.2
[LibreOffice.git] / chart2 / source / view / charttypes / PieChart.cxx
blobddfbc364a7243d87cd450a379161fef8e9150aa7
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include "PieChart.hxx"
21 #include "PlottingPositionHelper.hxx"
22 #include "ShapeFactory.hxx"
23 #include "PolarLabelPositionHelper.hxx"
24 #include "macros.hxx"
25 #include "CommonConverters.hxx"
26 #include "ViewDefines.hxx"
27 #include "ObjectIdentifier.hxx"
29 #include <com/sun/star/chart/DataLabelPlacement.hpp>
30 #include <com/sun/star/chart2/XColorScheme.hpp>
32 #include <com/sun/star/container/XChild.hpp>
33 #include <rtl/math.hxx>
35 #include <boost/scoped_ptr.hpp>
37 //.............................................................................
38 namespace chart
40 //.............................................................................
41 using namespace ::com::sun::star;
42 using namespace ::com::sun::star::chart2;
44 class PiePositionHelper : public PolarPlottingPositionHelper
46 public:
47 PiePositionHelper( NormalAxis eNormalAxis, double fAngleDegreeOffset );
48 virtual ~PiePositionHelper();
50 bool getInnerAndOuterRadius( double fCategoryX, double& fLogicInnerRadius, double& fLogicOuterRadius, bool bUseRings, double fMaxOffset ) const;
52 public:
53 //Distance between different category rings, seen relative to width of a ring:
54 double m_fRingDistance; //>=0 m_fRingDistance=1 --> distance == width
57 PiePositionHelper::PiePositionHelper( NormalAxis eNormalAxis, double fAngleDegreeOffset )
58 : PolarPlottingPositionHelper(eNormalAxis)
59 , m_fRingDistance(0.0)
61 m_fRadiusOffset = 0.0;
62 m_fAngleDegreeOffset = fAngleDegreeOffset;
65 PiePositionHelper::~PiePositionHelper()
69 bool PiePositionHelper::getInnerAndOuterRadius( double fCategoryX
70 , double& fLogicInnerRadius, double& fLogicOuterRadius
71 , bool bUseRings, double fMaxOffset ) const
73 if( !bUseRings )
74 fCategoryX = 1.0;
76 bool bIsVisible = true;
77 double fLogicInner = fCategoryX -0.5+m_fRingDistance/2.0;
78 double fLogicOuter = fCategoryX +0.5-m_fRingDistance/2.0;
80 if( !isMathematicalOrientationRadius() )
82 //in this case the given getMaximumX() was not corrcect instead the minimum should have been smaller by fMaxOffset
83 //but during getMaximumX and getMimumX we do not know the axis orientation
84 fLogicInner += fMaxOffset;
85 fLogicOuter += fMaxOffset;
88 if( fLogicInner >= getLogicMaxX() )
89 return false;
90 if( fLogicOuter <= getLogicMinX() )
91 return false;
93 if( fLogicInner < getLogicMinX() )
94 fLogicInner = getLogicMinX();
95 if( fLogicOuter > getLogicMaxX() )
96 fLogicOuter = getLogicMaxX();
98 fLogicInnerRadius = fLogicInner;
99 fLogicOuterRadius = fLogicOuter;
100 if( !isMathematicalOrientationRadius() )
101 std::swap(fLogicInnerRadius,fLogicOuterRadius);
102 return bIsVisible;
105 //-----------------------------------------------------------------------------
106 //-----------------------------------------------------------------------------
107 //-----------------------------------------------------------------------------
109 PieChart::PieChart( const uno::Reference<XChartType>& xChartTypeModel
110 , sal_Int32 nDimensionCount
111 , bool bExcludingPositioning )
112 : VSeriesPlotter( xChartTypeModel, nDimensionCount )
113 , m_pPosHelper( new PiePositionHelper( NormalAxis_Z, (m_nDimension==3)?0.0:90.0 ) )
114 , m_bUseRings(false)
115 , m_bSizeExcludesLabelsAndExplodedSegments(bExcludingPositioning)
117 ::rtl::math::setNan(&m_fMaxOffset);
119 PlotterBase::m_pPosHelper = m_pPosHelper;
120 VSeriesPlotter::m_pMainPosHelper = m_pPosHelper;
121 m_pPosHelper->m_fRadiusOffset = 0.0;
122 m_pPosHelper->m_fRingDistance = 0.0;
124 uno::Reference< beans::XPropertySet > xChartTypeProps( xChartTypeModel, uno::UNO_QUERY );
125 if( xChartTypeProps.is() ) try
127 xChartTypeProps->getPropertyValue( "UseRings") >>= m_bUseRings;
128 if( m_bUseRings )
130 m_pPosHelper->m_fRadiusOffset = 1.0;
131 if( nDimensionCount==3 )
132 m_pPosHelper->m_fRingDistance = 0.1;
135 catch( const uno::Exception& e )
137 ASSERT_EXCEPTION( e );
141 PieChart::~PieChart()
143 delete m_pPosHelper;
146 //-----------------------------------------------------------------
148 void PieChart::setScales( const std::vector< ExplicitScaleData >& rScales, bool /* bSwapXAndYAxis */ )
150 OSL_ENSURE(m_nDimension<=static_cast<sal_Int32>(rScales.size()),"Dimension of Plotter does not fit two dimension of given scale sequence");
151 m_pPosHelper->setScales( rScales, true );
154 //-----------------------------------------------------------------
156 drawing::Direction3D PieChart::getPreferredDiagramAspectRatio() const
158 if( m_nDimension == 3 )
159 return drawing::Direction3D(1,1,0.10);
160 return drawing::Direction3D(1,1,1);
163 bool PieChart::keepAspectRatio() const
165 if( m_nDimension == 3 )
166 return false;
167 return true;
170 bool PieChart::shouldSnapRectToUsedArea()
172 return true;
175 uno::Reference< drawing::XShape > PieChart::createDataPoint(
176 const uno::Reference< drawing::XShapes >& xTarget
177 , const uno::Reference< beans::XPropertySet >& xObjectProperties
178 , double fUnitCircleStartAngleDegree, double fUnitCircleWidthAngleDegree
179 , double fUnitCircleInnerRadius, double fUnitCircleOuterRadius
180 , double fLogicZ, double fDepth, double fExplodePercentage
181 , tPropertyNameValueMap* pOverwritePropertiesMap )
183 //---------------------------
184 //transform position:
185 drawing::Direction3D aOffset;
186 if( !::rtl::math::approxEqual( fExplodePercentage, 0.0 ) )
188 double fAngle = fUnitCircleStartAngleDegree + fUnitCircleWidthAngleDegree/2.0;
189 double fRadius = (fUnitCircleOuterRadius-fUnitCircleInnerRadius)*fExplodePercentage;
190 drawing::Position3D aOrigin = m_pPosHelper->transformUnitCircleToScene( 0, 0, fLogicZ );
191 drawing::Position3D aNewOrigin = m_pPosHelper->transformUnitCircleToScene( fAngle, fRadius, fLogicZ );
192 aOffset = aNewOrigin - aOrigin;
195 //---------------------------
196 //create point
197 uno::Reference< drawing::XShape > xShape(0);
198 if(m_nDimension==3)
200 xShape = m_pShapeFactory->createPieSegment( xTarget
201 , fUnitCircleStartAngleDegree, fUnitCircleWidthAngleDegree
202 , fUnitCircleInnerRadius, fUnitCircleOuterRadius
203 , aOffset, B3DHomMatrixToHomogenMatrix( m_pPosHelper->getUnitCartesianToScene() )
204 , fDepth );
206 else
208 xShape = m_pShapeFactory->createPieSegment2D( xTarget
209 , fUnitCircleStartAngleDegree, fUnitCircleWidthAngleDegree
210 , fUnitCircleInnerRadius, fUnitCircleOuterRadius
211 , aOffset, B3DHomMatrixToHomogenMatrix( m_pPosHelper->getUnitCartesianToScene() ) );
213 this->setMappedProperties( xShape, xObjectProperties, PropertyMapper::getPropertyNameMapForFilledSeriesProperties(), pOverwritePropertiesMap );
214 return xShape;
217 void PieChart::addSeries( VDataSeries* pSeries, sal_Int32 /* zSlot */, sal_Int32 /* xSlot */, sal_Int32 /* ySlot */ )
219 VSeriesPlotter::addSeries( pSeries, 0, -1, 0 );
222 double PieChart::getMinimumX()
224 return 0.5;
226 double PieChart::getMaxOffset()
228 if (!::rtl::math::isNan(m_fMaxOffset))
229 // Value already cached. Use it.
230 return m_fMaxOffset;
232 m_fMaxOffset = 0.0;
233 if( m_aZSlots.size()<=0 )
234 return m_fMaxOffset;
235 if( m_aZSlots[0].size()<=0 )
236 return m_fMaxOffset;
238 const ::std::vector< VDataSeries* >& rSeriesList( m_aZSlots[0][0].m_aSeriesVector );
239 if( rSeriesList.size()<=0 )
240 return m_fMaxOffset;
242 VDataSeries* pSeries = rSeriesList[0];
243 uno::Reference< beans::XPropertySet > xSeriesProp( pSeries->getPropertiesOfSeries() );
244 if( !xSeriesProp.is() )
245 return m_fMaxOffset;
247 double fExplodePercentage=0.0;
248 xSeriesProp->getPropertyValue( "Offset") >>= fExplodePercentage;
249 if(fExplodePercentage>m_fMaxOffset)
250 m_fMaxOffset=fExplodePercentage;
252 if(!m_bSizeExcludesLabelsAndExplodedSegments)
254 uno::Sequence< sal_Int32 > aAttributedDataPointIndexList;
255 if( xSeriesProp->getPropertyValue( "AttributedDataPoints" ) >>= aAttributedDataPointIndexList )
257 for(sal_Int32 nN=aAttributedDataPointIndexList.getLength();nN--;)
259 uno::Reference< beans::XPropertySet > xPointProp( pSeries->getPropertiesOfPoint(aAttributedDataPointIndexList[nN]) );
260 if(xPointProp.is())
262 fExplodePercentage=0.0;
263 xPointProp->getPropertyValue( "Offset") >>= fExplodePercentage;
264 if(fExplodePercentage>m_fMaxOffset)
265 m_fMaxOffset=fExplodePercentage;
270 return m_fMaxOffset;
272 double PieChart::getMaximumX()
274 double fMaxOffset = getMaxOffset();
275 if( m_aZSlots.size()>0 && m_bUseRings)
276 return m_aZSlots[0].size()+0.5+fMaxOffset;
277 return 1.5+fMaxOffset;
279 double PieChart::getMinimumYInRange( double /* fMinimumX */, double /* fMaximumX */, sal_Int32 /* nAxisIndex */ )
281 return 0.0;
284 double PieChart::getMaximumYInRange( double /* fMinimumX */, double /* fMaximumX */, sal_Int32 /* nAxisIndex */ )
286 return 1.0;
289 bool PieChart::isExpandBorderToIncrementRhythm( sal_Int32 /* nDimensionIndex */ )
291 return false;
294 bool PieChart::isExpandIfValuesCloseToBorder( sal_Int32 /* nDimensionIndex */ )
296 return false;
299 bool PieChart::isExpandWideValuesToZero( sal_Int32 /* nDimensionIndex */ )
301 return false;
304 bool PieChart::isExpandNarrowValuesTowardZero( sal_Int32 /* nDimensionIndex */ )
306 return false;
309 bool PieChart::isSeparateStackingForDifferentSigns( sal_Int32 /* nDimensionIndex */ )
311 return false;
314 void PieChart::createShapes()
316 if( m_aZSlots.begin() == m_aZSlots.end() ) //no series
317 return;
319 OSL_ENSURE(m_pShapeFactory&&m_xLogicTarget.is()&&m_xFinalTarget.is(),"PieChart is not proper initialized");
320 if(!(m_pShapeFactory&&m_xLogicTarget.is()&&m_xFinalTarget.is()))
321 return;
323 //the text labels should be always on top of the other series shapes
324 //therefore create an own group for the texts to move them to front
325 //(because the text group is created after the series group the texts are displayed on top)
326 uno::Reference< drawing::XShapes > xSeriesTarget(
327 createGroupShape( m_xLogicTarget,OUString() ));
328 uno::Reference< drawing::XShapes > xTextTarget(
329 m_pShapeFactory->createGroup2D( m_xFinalTarget,OUString() ));
330 //---------------------------------------------
331 //check necessary here that different Y axis can not be stacked in the same group? ... hm?
333 //=============================================================================
334 ::std::vector< VDataSeriesGroup >::iterator aXSlotIter = m_aZSlots[0].begin();
335 const ::std::vector< VDataSeriesGroup >::const_iterator aXSlotEnd = m_aZSlots[0].end();
337 ::std::vector< VDataSeriesGroup >::size_type nExplodeableSlot = 0;
338 if( m_pPosHelper->isMathematicalOrientationRadius() && m_bUseRings )
339 nExplodeableSlot = m_aZSlots[0].size()-1;
341 m_aLabelInfoList.clear();
342 ::rtl::math::setNan(&m_fMaxOffset);
343 sal_Int32 n3DRelativeHeight = 100;
344 uno::Reference< beans::XPropertySet > xPropertySet( m_xChartTypeModel, uno::UNO_QUERY );
345 if ( (m_nDimension==3) && xPropertySet.is())
349 uno::Any aAny = xPropertySet->getPropertyValue( "3DRelativeHeight" );
350 aAny >>= n3DRelativeHeight;
352 catch (const uno::Exception&) { }
355 //=============================================================================
356 for( double fSlotX=0; aXSlotIter != aXSlotEnd && (m_bUseRings||fSlotX<0.5 ); ++aXSlotIter, fSlotX+=1.0 )
358 ::std::vector< VDataSeries* >* pSeriesList = &(aXSlotIter->m_aSeriesVector);
359 if( pSeriesList->size()<=0 )//there should be only one series in each x slot
360 continue;
361 VDataSeries* pSeries = (*pSeriesList)[0];
362 if(!pSeries)
363 continue;
365 m_pPosHelper->m_fAngleDegreeOffset = pSeries->getStartingAngle();
367 double fLogicYSum = 0.0;
368 //iterate through all points to get the sum
369 sal_Int32 nPointIndex=0;
370 sal_Int32 nPointCount=pSeries->getTotalPointCount();
371 for( nPointIndex = 0; nPointIndex < nPointCount; nPointIndex++ )
373 double fY = pSeries->getYValue( nPointIndex );
374 if(fY<0.0)
376 //@todo warn somehow that negative values are treated as positive
378 if( ::rtl::math::isNan(fY) )
379 continue;
380 fLogicYSum += fabs(fY);
382 if(fLogicYSum==0.0)
383 continue;
384 double fLogicYForNextPoint = 0.0;
385 //iterate through all points to create shapes
386 for( nPointIndex = 0; nPointIndex < nPointCount; nPointIndex++ )
388 double fLogicInnerRadius, fLogicOuterRadius;
389 double fOffset = getMaxOffset();
390 bool bIsVisible = m_pPosHelper->getInnerAndOuterRadius( fSlotX+1.0, fLogicInnerRadius, fLogicOuterRadius, m_bUseRings, fOffset );
391 if( !bIsVisible )
392 continue;
394 double fDepth = this->getTransformedDepth() * (n3DRelativeHeight / 100.0);
396 uno::Reference< drawing::XShapes > xSeriesGroupShape_Shapes = getSeriesGroupShape(pSeries, xSeriesTarget);
397 //collect data point information (logic coordinates, style ):
398 double fLogicYValue = fabs(pSeries->getYValue( nPointIndex ));
399 if( ::rtl::math::isNan(fLogicYValue) )
400 continue;
401 if(fLogicYValue==0.0)//@todo: continue also if the resolution to small
402 continue;
403 double fLogicYPos = fLogicYForNextPoint;
404 fLogicYForNextPoint += fLogicYValue;
406 uno::Reference< beans::XPropertySet > xPointProperties = pSeries->getPropertiesOfPoint( nPointIndex );
408 //iterate through all subsystems to create partial points
410 //logic values on angle axis:
411 double fLogicStartAngleValue = fLogicYPos/fLogicYSum;
412 double fLogicEndAngleValue = (fLogicYPos+fLogicYValue)/fLogicYSum;
414 double fExplodePercentage=0.0;
415 bool bDoExplode = ( nExplodeableSlot == static_cast< ::std::vector< VDataSeriesGroup >::size_type >(fSlotX) );
416 if(bDoExplode) try
418 xPointProperties->getPropertyValue( "Offset") >>= fExplodePercentage;
420 catch( const uno::Exception& e )
422 ASSERT_EXCEPTION( e );
425 //---------------------------
426 //transforme to unit circle:
427 double fUnitCircleWidthAngleDegree = m_pPosHelper->getWidthAngleDegree( fLogicStartAngleValue, fLogicEndAngleValue );
428 double fUnitCircleStartAngleDegree = m_pPosHelper->transformToAngleDegree( fLogicStartAngleValue );
429 double fUnitCircleInnerRadius = m_pPosHelper->transformToRadius( fLogicInnerRadius );
430 double fUnitCircleOuterRadius = m_pPosHelper->transformToRadius( fLogicOuterRadius );
432 //---------------------------
433 //point color:
434 boost::scoped_ptr< tPropertyNameValueMap > apOverwritePropertiesMap(NULL);
436 if(!pSeries->hasPointOwnColor(nPointIndex) && m_xColorScheme.is())
438 apOverwritePropertiesMap.reset( new tPropertyNameValueMap() );
439 (*apOverwritePropertiesMap)["FillColor"] = uno::makeAny(
440 m_xColorScheme->getColorByIndex( nPointIndex ));
444 //create data point
445 double fLogicZ = -1.0; // For 3D pie chart label position
446 uno::Reference<drawing::XShape> xPointShape(
447 createDataPoint( xSeriesGroupShape_Shapes, xPointProperties
448 , fUnitCircleStartAngleDegree, fUnitCircleWidthAngleDegree
449 , fUnitCircleInnerRadius, fUnitCircleOuterRadius
450 , fLogicZ, fDepth, fExplodePercentage, apOverwritePropertiesMap.get() ) );
452 //create label
453 if( pSeries->getDataPointLabelIfLabel(nPointIndex) )
455 if( !::rtl::math::approxEqual( fExplodePercentage, 0.0 ) )
457 double fExplodeOffset = (fUnitCircleOuterRadius-fUnitCircleInnerRadius)*fExplodePercentage;
458 fUnitCircleInnerRadius += fExplodeOffset;
459 fUnitCircleOuterRadius += fExplodeOffset;
462 sal_Int32 nLabelPlacement = pSeries->getLabelPlacement( nPointIndex, m_xChartTypeModel, m_nDimension, m_pPosHelper->isSwapXAndY() );
464 // AVOID_OVERLAP is in fact "Best fit" in the UI.
465 bool bMovementAllowed = ( nLabelPlacement == ::com::sun::star::chart::DataLabelPlacement::AVOID_OVERLAP );
466 if( bMovementAllowed )
467 // Use center for "Best fit" for now. In the future we
468 // may want to implement a real best fit algorithm.
469 // But center is good enough, and close to what Excel
470 // does.
471 nLabelPlacement = ::com::sun::star::chart::DataLabelPlacement::CENTER;
473 LabelAlignment eAlignment(LABEL_ALIGN_CENTER);
474 sal_Int32 nScreenValueOffsetInRadiusDirection = 0 ;
475 if( nLabelPlacement == ::com::sun::star::chart::DataLabelPlacement::OUTSIDE )
476 nScreenValueOffsetInRadiusDirection = (3!=m_nDimension) ? 150 : 0;//todo maybe calculate this font height dependent
477 else if( nLabelPlacement == ::com::sun::star::chart::DataLabelPlacement::INSIDE )
478 nScreenValueOffsetInRadiusDirection = (3!=m_nDimension) ? -150 : 0;//todo maybe calculate this font height dependent
479 PolarLabelPositionHelper aPolarPosHelper(m_pPosHelper,m_nDimension,m_xLogicTarget,m_pShapeFactory);
480 awt::Point aScreenPosition2D(
481 aPolarPosHelper.getLabelScreenPositionAndAlignmentForUnitCircleValues(eAlignment, nLabelPlacement
482 , fUnitCircleStartAngleDegree, fUnitCircleWidthAngleDegree
483 , fUnitCircleInnerRadius, fUnitCircleOuterRadius, fLogicZ+0.5, 0 ));
485 PieLabelInfo aPieLabelInfo;
486 aPieLabelInfo.aFirstPosition = basegfx::B2IVector( aScreenPosition2D.X, aScreenPosition2D.Y );
487 awt::Point aOrigin( aPolarPosHelper.transformSceneToScreenPosition( m_pPosHelper->transformUnitCircleToScene( 0.0, 0.0, fLogicZ+1.0 ) ) );
488 aPieLabelInfo.aOrigin = basegfx::B2IVector( aOrigin.X, aOrigin.Y );
490 //add a scaling independent Offset if requested
491 if( nScreenValueOffsetInRadiusDirection != 0)
493 basegfx::B2IVector aDirection( aScreenPosition2D.X- aOrigin.X, aScreenPosition2D.Y- aOrigin.Y );
494 aDirection.setLength(nScreenValueOffsetInRadiusDirection);
495 aScreenPosition2D.X += aDirection.getX();
496 aScreenPosition2D.Y += aDirection.getY();
499 aPieLabelInfo.xTextShape = this->createDataLabel( xTextTarget, *pSeries, nPointIndex
500 , fLogicYValue, fLogicYSum, aScreenPosition2D, eAlignment );
502 uno::Reference< container::XChild > xChild( aPieLabelInfo.xTextShape, uno::UNO_QUERY );
503 if( xChild.is() )
504 aPieLabelInfo.xLabelGroupShape = uno::Reference<drawing::XShape>( xChild->getParent(), uno::UNO_QUERY );
505 aPieLabelInfo.fValue = fLogicYValue;
506 aPieLabelInfo.bMovementAllowed = bMovementAllowed;
507 aPieLabelInfo.bMoved= false;
508 aPieLabelInfo.xTextTarget = xTextTarget;
509 m_aLabelInfoList.push_back(aPieLabelInfo);
512 if(!bDoExplode)
514 ShapeFactory::setShapeName( xPointShape
515 , ObjectIdentifier::createPointCID( pSeries->getPointCID_Stub(), nPointIndex ) );
517 else try
519 //enable dragging of outer segments
521 double fAngle = fUnitCircleStartAngleDegree + fUnitCircleWidthAngleDegree/2.0;
522 double fMaxDeltaRadius = fUnitCircleOuterRadius-fUnitCircleInnerRadius;
523 drawing::Position3D aOrigin = m_pPosHelper->transformUnitCircleToScene( fAngle, fUnitCircleOuterRadius, fLogicZ );
524 drawing::Position3D aNewOrigin = m_pPosHelper->transformUnitCircleToScene( fAngle, fUnitCircleOuterRadius + fMaxDeltaRadius, fLogicZ );
526 sal_Int32 nOffsetPercent( static_cast<sal_Int32>(fExplodePercentage * 100.0) );
528 awt::Point aMinimumPosition( PlottingPositionHelper::transformSceneToScreenPosition(
529 aOrigin, m_xLogicTarget, m_pShapeFactory, m_nDimension ) );
530 awt::Point aMaximumPosition( PlottingPositionHelper::transformSceneToScreenPosition(
531 aNewOrigin, m_xLogicTarget, m_pShapeFactory, m_nDimension ) );
533 //enable draging of piesegments
534 OUString aPointCIDStub( ObjectIdentifier::createSeriesSubObjectStub( OBJECTTYPE_DATA_POINT
535 , pSeries->getSeriesParticle()
536 , ObjectIdentifier::getPieSegmentDragMethodServiceName()
537 , ObjectIdentifier::createPieSegmentDragParameterString(
538 nOffsetPercent, aMinimumPosition, aMaximumPosition )
539 ) );
541 ShapeFactory::setShapeName( xPointShape
542 , ObjectIdentifier::createPointCID( aPointCIDStub, nPointIndex ) );
544 catch( const uno::Exception& e )
546 ASSERT_EXCEPTION( e );
548 }//next series in x slot (next y slot)
549 }//next category
550 }//next x slot
551 //=============================================================================
552 //=============================================================================
553 //=============================================================================
554 /* @todo remove series shapes if empty
555 //remove and delete point-group-shape if empty
556 if(!xSeriesGroupShape_Shapes->getCount())
558 (*aSeriesIter)->m_xShape.set(NULL);
559 m_xLogicTarget->remove(xSeriesGroupShape_Shape);
563 //remove and delete series-group-shape if empty
565 //... todo
568 namespace
571 ::basegfx::B2IRectangle lcl_getRect( const uno::Reference< drawing::XShape >& xShape )
573 ::basegfx::B2IRectangle aRect;
574 if( xShape.is() )
575 aRect = BaseGFXHelper::makeRectangle(xShape->getPosition(),xShape->getSize() );
576 return aRect;
579 bool lcl_isInsidePage( const awt::Point& rPos, const awt::Size& rSize, const awt::Size& rPageSize )
581 if( rPos.X < 0 || rPos.Y < 0 )
582 return false;
583 if( (rPos.X + rSize.Width) > rPageSize.Width )
584 return false;
585 if( (rPos.Y + rSize.Height) > rPageSize.Height )
586 return false;
587 return true;
590 }//end anonymous namespace
592 PieChart::PieLabelInfo::PieLabelInfo()
593 : xTextShape(0), xLabelGroupShape(0), aFirstPosition(), aOrigin(), fValue(0.0)
594 , bMovementAllowed(false), bMoved(false), xTextTarget(0), pPrevious(0),pNext(0)
598 bool PieChart::PieLabelInfo::moveAwayFrom( const PieChart::PieLabelInfo* pFix, const awt::Size& rPageSize, bool bMoveHalfWay, bool bMoveClockwise, bool bAlternativeMoveDirection )
600 //return true if the move was successful
601 if(!this->bMovementAllowed)
602 return false;
604 const sal_Int32 nLabelDistanceX = rPageSize.Width/50;
605 const sal_Int32 nLabelDistanceY = rPageSize.Height/50;
607 ::basegfx::B2IRectangle aOverlap( lcl_getRect( this->xLabelGroupShape ) );
608 aOverlap.intersect( lcl_getRect( pFix->xLabelGroupShape ) );
609 if( !aOverlap.isEmpty() )
611 (void)bAlternativeMoveDirection;//todo
613 basegfx::B2IVector aRadiusDirection = this->aFirstPosition - this->aOrigin;
614 aRadiusDirection.setLength(1.0);
615 basegfx::B2IVector aTangentialDirection( -aRadiusDirection.getY(), aRadiusDirection.getX() );
616 bool bShiftHorizontal = abs(aTangentialDirection.getX()) > abs(aTangentialDirection.getY());
618 sal_Int32 nShift = bShiftHorizontal ? static_cast<sal_Int32>(aOverlap.getWidth()) : static_cast<sal_Int32>(aOverlap.getHeight());
619 nShift += (bShiftHorizontal ? nLabelDistanceX : nLabelDistanceY);
620 if( bMoveHalfWay )
621 nShift/=2;
622 if(!bMoveClockwise)
623 nShift*=-1;
624 awt::Point aOldPos( this->xLabelGroupShape->getPosition() );
625 basegfx::B2IVector aNewPos = basegfx::B2IVector( aOldPos.X, aOldPos.Y ) + nShift*aTangentialDirection;
627 //check whether the new position is ok
628 awt::Point aNewAWTPos( aNewPos.getX(), aNewPos.getY() );
629 if( !lcl_isInsidePage( aNewAWTPos, this->xLabelGroupShape->getSize(), rPageSize ) )
630 return false;
632 this->xLabelGroupShape->setPosition( aNewAWTPos );
633 this->bMoved = true;
635 return true;
638 void PieChart::resetLabelPositionsToPreviousState()
640 std::vector< PieLabelInfo >::iterator aIt = m_aLabelInfoList.begin();
641 std::vector< PieLabelInfo >::const_iterator aEnd = m_aLabelInfoList.end();
642 for( ;aIt!=aEnd; ++aIt )
643 aIt->xLabelGroupShape->setPosition(aIt->aPreviousPosition);
646 bool PieChart::detectLabelOverlapsAndMove( const awt::Size& rPageSize )
648 //returns true when there might be more to do
650 //find borders of a group of overlapping labels
651 bool bOverlapFound = false;
652 PieLabelInfo* pStart = &(*(m_aLabelInfoList.rbegin()));
653 PieLabelInfo* pFirstBorder = 0;
654 PieLabelInfo* pSecondBorder = 0;
655 PieLabelInfo* pCurrent = pStart;
658 ::basegfx::B2IRectangle aPreviousOverlap( lcl_getRect( pCurrent->xLabelGroupShape ) );
659 ::basegfx::B2IRectangle aNextOverlap( aPreviousOverlap );
660 aPreviousOverlap.intersect( lcl_getRect( pCurrent->pPrevious->xLabelGroupShape ) );
661 aNextOverlap.intersect( lcl_getRect( pCurrent->pNext->xLabelGroupShape ) );
663 bool bPreviousOverlap = !aPreviousOverlap.isEmpty();
664 bool bNextOverlap = !aNextOverlap.isEmpty();
665 if( bPreviousOverlap || bNextOverlap )
666 bOverlapFound = true;
667 if( !bPreviousOverlap && bNextOverlap )
669 pFirstBorder = pCurrent;
670 break;
672 pCurrent = pCurrent->pNext;
674 while( pCurrent != pStart );
676 if( !bOverlapFound )
677 return false;
679 if( pFirstBorder )
681 pCurrent = pFirstBorder;
684 ::basegfx::B2IRectangle aPreviousOverlap( lcl_getRect( pCurrent->xLabelGroupShape ) );
685 ::basegfx::B2IRectangle aNextOverlap( aPreviousOverlap );
686 aPreviousOverlap.intersect( lcl_getRect( pCurrent->pPrevious->xLabelGroupShape ) );
687 aNextOverlap.intersect( lcl_getRect( pCurrent->pNext->xLabelGroupShape ) );
689 if( !aPreviousOverlap.isEmpty() && aNextOverlap.isEmpty() )
691 pSecondBorder = pCurrent;
692 break;
694 pCurrent = pCurrent->pNext;
696 while( pCurrent != pFirstBorder );
699 if( !pFirstBorder || !pSecondBorder )
701 pFirstBorder = &(*(m_aLabelInfoList.rbegin()));
702 pSecondBorder = &(*(m_aLabelInfoList.begin()));
705 //find center
706 PieLabelInfo* pCenter = pFirstBorder;
707 sal_Int32 nOverlapGroupCount = 1;
708 for( pCurrent = pFirstBorder ;pCurrent != pSecondBorder; pCurrent = pCurrent->pNext )
709 nOverlapGroupCount++;
710 sal_Int32 nCenterPos = nOverlapGroupCount/2;
711 bool bSingleCenter = nOverlapGroupCount%2 != 0;
712 if( bSingleCenter )
713 nCenterPos++;
714 if(nCenterPos>1)
716 pCurrent = pFirstBorder;
717 while( --nCenterPos )
718 pCurrent = pCurrent->pNext;
719 pCenter = pCurrent;
722 //remind current positions
723 pCurrent = pStart;
726 pCurrent->aPreviousPosition = pCurrent->xLabelGroupShape->getPosition();
727 pCurrent = pCurrent->pNext;
729 while( pCurrent != pStart );
732 bool bAlternativeMoveDirection = false;
733 if( !tryMoveLabels( pFirstBorder, pSecondBorder, pCenter, bSingleCenter, bAlternativeMoveDirection, rPageSize ) )
734 tryMoveLabels( pFirstBorder, pSecondBorder, pCenter, bSingleCenter, bAlternativeMoveDirection, rPageSize );
735 return true;
738 bool PieChart::tryMoveLabels( PieLabelInfo* pFirstBorder, PieLabelInfo* pSecondBorder
739 , PieLabelInfo* pCenter
740 , bool bSingleCenter, bool& rbAlternativeMoveDirection, const awt::Size& rPageSize )
742 PieLabelInfo* p1 = bSingleCenter ? pCenter->pPrevious : pCenter;
743 PieLabelInfo* p2 = pCenter->pNext;
744 //return true when successful
746 bool bLabelOrderIsAntiClockWise = m_pPosHelper->isMathematicalOrientationAngle();
748 PieLabelInfo* pCurrent = 0;
749 for( pCurrent = p2 ;pCurrent->pPrevious != pSecondBorder; pCurrent = pCurrent->pNext )
751 PieLabelInfo* pFix = 0;
752 for( pFix = p2->pPrevious ;pFix != pCurrent; pFix = pFix->pNext )
754 if( !pCurrent->moveAwayFrom( pFix, rPageSize, !bSingleCenter && pCurrent == p2, !bLabelOrderIsAntiClockWise, rbAlternativeMoveDirection ) )
756 if( !rbAlternativeMoveDirection )
758 rbAlternativeMoveDirection = true;
759 resetLabelPositionsToPreviousState();
760 return false;
765 for( pCurrent = p1 ;pCurrent->pNext != pFirstBorder; pCurrent = pCurrent->pPrevious )
767 PieLabelInfo* pFix = 0;
768 for( pFix = p2->pNext ;pFix != pCurrent; pFix = pFix->pPrevious )
770 if( !pCurrent->moveAwayFrom( pFix, rPageSize, false, bLabelOrderIsAntiClockWise, rbAlternativeMoveDirection ) )
772 if( !rbAlternativeMoveDirection )
774 rbAlternativeMoveDirection = true;
775 resetLabelPositionsToPreviousState();
776 return false;
781 return true;
784 void PieChart::rearrangeLabelToAvoidOverlapIfRequested( const awt::Size& rPageSize )
786 //------------------------------------------------------------------
787 //check whether there are any labels that should be moved
788 std::vector< PieLabelInfo >::iterator aIt1 = m_aLabelInfoList.begin();
789 std::vector< PieLabelInfo >::const_iterator aEnd = m_aLabelInfoList.end();
790 bool bMoveableFound = false;
791 for( ;aIt1!=aEnd; ++aIt1 )
793 if(aIt1->bMovementAllowed)
795 bMoveableFound = true;
796 break;
799 if(!bMoveableFound)
800 return;
802 double fPageDiagonaleLength = sqrt( double( rPageSize.Width*rPageSize.Width + rPageSize.Height*rPageSize.Height) );
803 if( ::rtl::math::approxEqual( fPageDiagonaleLength, 0.0 ) )
804 return;
806 //------------------------------------------------------------------
807 //init next and previous
808 aIt1 = m_aLabelInfoList.begin();
809 std::vector< PieLabelInfo >::iterator aIt2 = aIt1;
810 if( aIt1==aEnd )//no need to do anything when we only have one label
811 return;
812 aIt1->pPrevious = &(*(m_aLabelInfoList.rbegin()));
813 ++aIt2;
814 for( ;aIt2!=aEnd; ++aIt1, ++aIt2 )
816 PieLabelInfo& rInfo1( *aIt1 );
817 PieLabelInfo& rInfo2( *aIt2 );
818 rInfo1.pNext = &rInfo2;
819 rInfo2.pPrevious = &rInfo1;
821 aIt1->pNext = &(*(m_aLabelInfoList.begin()));
824 //------------------------------------------------------------------
825 //detect overlaps and move
826 sal_Int32 nMaxIterations = 50;
827 while( detectLabelOverlapsAndMove( rPageSize ) && nMaxIterations > 0 )
828 nMaxIterations--;
830 //------------------------------------------------------------------
831 //create connection lines for the moved labels
832 aEnd = m_aLabelInfoList.end();
833 VLineProperties aVLineProperties;
834 for( aIt1 = m_aLabelInfoList.begin(); aIt1!=aEnd; ++aIt1 )
836 PieLabelInfo& rInfo( *aIt1 );
837 if( rInfo.bMoved )
839 sal_Int32 nX1 = rInfo.aFirstPosition.getX();
840 sal_Int32 nY1 = rInfo.aFirstPosition.getY();
841 sal_Int32 nX2 = nX1;
842 sal_Int32 nY2 = nY1;
843 ::basegfx::B2IRectangle aRect( lcl_getRect( rInfo.xLabelGroupShape ) );
844 if( nX1 < aRect.getMinX() )
845 nX2 = aRect.getMinX();
846 else if( nX1 > aRect.getMaxX() )
847 nX2 = aRect.getMaxX();
849 if( nY1 < aRect.getMinY() )
850 nY2 = aRect.getMinY();
851 else if( nY1 > aRect.getMaxY() )
852 nY2 = aRect.getMaxY();
855 //when the line is very short compared to the page size don't create one
856 ::basegfx::B2DVector aLength(nX1-nX2, nY1-nY2);
857 if( (aLength.getLength()/fPageDiagonaleLength) < 0.01 )
858 continue;
860 drawing::PointSequenceSequence aPoints(1);
861 aPoints[0].realloc(2);
862 aPoints[0][0].X = nX1;
863 aPoints[0][0].Y = nY1;
864 aPoints[0][1].X = nX2;
865 aPoints[0][1].Y = nY2;
867 uno::Reference< beans::XPropertySet > xProp( rInfo.xTextShape, uno::UNO_QUERY);
868 if( xProp.is() )
870 sal_Int32 nColor = 0;
871 xProp->getPropertyValue("CharColor") >>= nColor;
872 if( nColor != -1 )//automatic font color does not work for lines -> fallback to black
873 aVLineProperties.Color = uno::makeAny(nColor);
875 m_pShapeFactory->createLine2D( rInfo.xTextTarget, aPoints, &aVLineProperties );
880 //.............................................................................
881 } //namespace chart
882 //.............................................................................
884 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */