diff options
Diffstat (limited to 'chart2/source/view/charttypes/PieChart.cxx')
-rw-r--r-- | chart2/source/view/charttypes/PieChart.cxx | 897 |
1 files changed, 897 insertions, 0 deletions
diff --git a/chart2/source/view/charttypes/PieChart.cxx b/chart2/source/view/charttypes/PieChart.cxx new file mode 100644 index 000000000000..eee13848b11b --- /dev/null +++ b/chart2/source/view/charttypes/PieChart.cxx @@ -0,0 +1,897 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_chart2.hxx" + +#include "PieChart.hxx" +#include "PlottingPositionHelper.hxx" +#include "ShapeFactory.hxx" +#include "PolarLabelPositionHelper.hxx" +#include "macros.hxx" +#include "CommonConverters.hxx" +#include "ViewDefines.hxx" +#include "ObjectIdentifier.hxx" + +#include <com/sun/star/chart/DataLabelPlacement.hpp> +#include <com/sun/star/chart2/XColorScheme.hpp> + +#include <com/sun/star/container/XChild.hpp> + +//#include "chartview/servicenames_charttypes.hxx" +//#include "servicenames_coosystems.hxx" +#include <tools/debug.hxx> +#include <rtl/math.hxx> + +//............................................................................. +namespace chart +{ +//............................................................................. +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; + +class PiePositionHelper : public PolarPlottingPositionHelper +{ +public: + PiePositionHelper( NormalAxis eNormalAxis, double fAngleDegreeOffset ); + virtual ~PiePositionHelper(); + + bool getInnerAndOuterRadius( double fCategoryX, double& fLogicInnerRadius, double& fLogicOuterRadius, bool bUseRings, double fMaxOffset ) const; + +public: + //Distance between different category rings, seen relative to width of a ring: + double m_fRingDistance; //>=0 m_fRingDistance=1 --> distance == width +}; + +PiePositionHelper::PiePositionHelper( NormalAxis eNormalAxis, double fAngleDegreeOffset ) + : PolarPlottingPositionHelper(eNormalAxis) + , m_fRingDistance(0.0) +{ + m_fRadiusOffset = 0.0; + m_fAngleDegreeOffset = fAngleDegreeOffset; +} + +PiePositionHelper::~PiePositionHelper() +{ +} + +bool PiePositionHelper::getInnerAndOuterRadius( double fCategoryX + , double& fLogicInnerRadius, double& fLogicOuterRadius + , bool bUseRings, double fMaxOffset ) const +{ + if( !bUseRings ) + fCategoryX = 1.0; + + bool bIsVisible = true; + double fLogicInner = fCategoryX -0.5+m_fRingDistance/2.0; + double fLogicOuter = fCategoryX +0.5-m_fRingDistance/2.0; + + if( !isMathematicalOrientationRadius() ) + { + //in this case the given getMaximumX() was not corrcect instead the minimum should have been smaller by fMaxOffset + //but during getMaximumX and getMimumX we do not know the axis orientation + fLogicInner += fMaxOffset; + fLogicOuter += fMaxOffset; + } + + if( fLogicInner >= getLogicMaxX() ) + return false; + if( fLogicOuter <= getLogicMinX() ) + return false; + + if( fLogicInner < getLogicMinX() ) + fLogicInner = getLogicMinX(); + if( fLogicOuter > getLogicMaxX() ) + fLogicOuter = getLogicMaxX(); + + fLogicInnerRadius = fLogicInner; + fLogicOuterRadius = fLogicOuter; + if( !isMathematicalOrientationRadius() ) + std::swap(fLogicInnerRadius,fLogicOuterRadius); + return bIsVisible; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +PieChart::PieChart( const uno::Reference<XChartType>& xChartTypeModel + , sal_Int32 nDimensionCount + , bool bExcludingPositioning ) + : VSeriesPlotter( xChartTypeModel, nDimensionCount ) + , m_pPosHelper( new PiePositionHelper( NormalAxis_Z, (m_nDimension==3)?0.0:90.0 ) ) + , m_bUseRings(false) + , m_bSizeExcludesLabelsAndExplodedSegments(bExcludingPositioning) +{ + ::rtl::math::setNan(&m_fMaxOffset); + + PlotterBase::m_pPosHelper = m_pPosHelper; + VSeriesPlotter::m_pMainPosHelper = m_pPosHelper; + m_pPosHelper->m_fRadiusOffset = 0.0; + m_pPosHelper->m_fRingDistance = 0.0; + + uno::Reference< beans::XPropertySet > xChartTypeProps( xChartTypeModel, uno::UNO_QUERY ); + if( xChartTypeProps.is() ) try + { + xChartTypeProps->getPropertyValue( C2U( "UseRings" )) >>= m_bUseRings; + if( m_bUseRings ) + { + m_pPosHelper->m_fRadiusOffset = 1.0; + if( nDimensionCount==3 ) + m_pPosHelper->m_fRingDistance = 0.1; + } + } + catch( uno::Exception& e ) + { + ASSERT_EXCEPTION( e ); + } +} + +PieChart::~PieChart() +{ + delete m_pPosHelper; +} + +//----------------------------------------------------------------- + +void SAL_CALL PieChart::setScales( const uno::Sequence< ExplicitScaleData >& rScales + , sal_Bool /* bSwapXAndYAxis */ ) + throw (uno::RuntimeException) +{ + DBG_ASSERT(m_nDimension<=rScales.getLength(),"Dimension of Plotter does not fit two dimension of given scale sequence"); + m_pPosHelper->setScales( rScales, true ); +} + +//----------------------------------------------------------------- + +drawing::Direction3D PieChart::getPreferredDiagramAspectRatio() const +{ + if( m_nDimension == 3 ) + return drawing::Direction3D(1,1,0.25); + return drawing::Direction3D(1,1,1); +} + +bool PieChart::keepAspectRatio() const +{ + if( m_nDimension == 3 ) + return false; + return true; +} + +bool PieChart::shouldSnapRectToUsedArea() +{ + return true; +} + +//----------------------------------------------------------------- +// lang::XServiceInfo +//----------------------------------------------------------------- +/* +APPHELPER_XSERVICEINFO_IMPL(PieChart,CHART2_VIEW_PIECHART_SERVICE_IMPLEMENTATION_NAME) + + uno::Sequence< rtl::OUString > PieChart +::getSupportedServiceNames_Static() +{ + uno::Sequence< rtl::OUString > aSNS( 1 ); + aSNS.getArray()[ 0 ] = CHART2_VIEW_PIECHART_SERVICE_NAME; + return aSNS; +} +*/ + +uno::Reference< drawing::XShape > PieChart::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 ) +{ + //--------------------------- + //transform position: + drawing::Direction3D aOffset; + if( !::rtl::math::approxEqual( fExplodePercentage, 0.0 ) ) + { + double fAngle = fUnitCircleStartAngleDegree + fUnitCircleWidthAngleDegree/2.0; + double fRadius = (fUnitCircleOuterRadius-fUnitCircleInnerRadius)*fExplodePercentage; + drawing::Position3D aOrigin = m_pPosHelper->transformUnitCircleToScene( 0, 0, fLogicZ ); + drawing::Position3D aNewOrigin = m_pPosHelper->transformUnitCircleToScene( fAngle, fRadius, fLogicZ ); + aOffset = aNewOrigin - aOrigin; + } + + //--------------------------- + //create point + uno::Reference< drawing::XShape > xShape(0); + if(m_nDimension==3) + { + xShape = m_pShapeFactory->createPieSegment( xTarget + , fUnitCircleStartAngleDegree, fUnitCircleWidthAngleDegree + , fUnitCircleInnerRadius, fUnitCircleOuterRadius + , aOffset, B3DHomMatrixToHomogenMatrix( m_pPosHelper->getUnitCartesianToScene() ) + , fDepth ); + } + else + { + xShape = m_pShapeFactory->createPieSegment2D( xTarget + , fUnitCircleStartAngleDegree, fUnitCircleWidthAngleDegree + , fUnitCircleInnerRadius, fUnitCircleOuterRadius + , aOffset, B3DHomMatrixToHomogenMatrix( m_pPosHelper->getUnitCartesianToScene() ) ); + } + this->setMappedProperties( xShape, xObjectProperties, PropertyMapper::getPropertyNameMapForFilledSeriesProperties(), pOverwritePropertiesMap ); + return xShape; +} + +void PieChart::addSeries( VDataSeries* pSeries, sal_Int32 /* zSlot */, sal_Int32 /* xSlot */, sal_Int32 /* ySlot */ ) +{ + VSeriesPlotter::addSeries( pSeries, 0, -1, 0 ); +} + +double PieChart::getMinimumX() +{ + return 0.5; +} +double PieChart::getMaxOffset() +{ + if (!::rtl::math::isNan(m_fMaxOffset)) + // Value already cached. Use it. + return m_fMaxOffset; + + m_fMaxOffset = 0.0; + if( m_aZSlots.size()<=0 ) + return m_fMaxOffset; + if( m_aZSlots[0].size()<=0 ) + return m_fMaxOffset; + + const ::std::vector< VDataSeries* >& rSeriesList( m_aZSlots[0][0].m_aSeriesVector ); + if( rSeriesList.size()<=0 ) + return m_fMaxOffset; + + VDataSeries* pSeries = rSeriesList[0]; + uno::Reference< beans::XPropertySet > xSeriesProp( pSeries->getPropertiesOfSeries() ); + if( !xSeriesProp.is() ) + return m_fMaxOffset; + + double fExplodePercentage=0.0; + xSeriesProp->getPropertyValue( C2U( "Offset" )) >>= fExplodePercentage; + if(fExplodePercentage>m_fMaxOffset) + m_fMaxOffset=fExplodePercentage; + + if(!m_bSizeExcludesLabelsAndExplodedSegments) + { + uno::Sequence< sal_Int32 > aAttributedDataPointIndexList; + if( xSeriesProp->getPropertyValue( C2U( "AttributedDataPoints" ) ) >>= aAttributedDataPointIndexList ) + { + for(sal_Int32 nN=aAttributedDataPointIndexList.getLength();nN--;) + { + uno::Reference< beans::XPropertySet > xPointProp( pSeries->getPropertiesOfPoint(aAttributedDataPointIndexList[nN]) ); + if(xPointProp.is()) + { + fExplodePercentage=0.0; + xPointProp->getPropertyValue( C2U( "Offset" )) >>= fExplodePercentage; + if(fExplodePercentage>m_fMaxOffset) + m_fMaxOffset=fExplodePercentage; + } + } + } + } + return m_fMaxOffset; +} +double PieChart::getMaximumX() +{ + double fMaxOffset = getMaxOffset(); + if( m_aZSlots.size()>0 && m_bUseRings) + return m_aZSlots[0].size()+0.5+fMaxOffset; + return 1.5+fMaxOffset; +} +double PieChart::getMinimumYInRange( double /* fMinimumX */, double /* fMaximumX */, sal_Int32 /* nAxisIndex */ ) +{ + return 0.0; +} + +double PieChart::getMaximumYInRange( double /* fMinimumX */, double /* fMaximumX */, sal_Int32 /* nAxisIndex */ ) +{ + return 1.0; +} + +bool PieChart::isExpandBorderToIncrementRhythm( sal_Int32 /* nDimensionIndex */ ) +{ + return false; +} + +bool PieChart::isExpandIfValuesCloseToBorder( sal_Int32 /* nDimensionIndex */ ) +{ + return false; +} + +bool PieChart::isExpandWideValuesToZero( sal_Int32 /* nDimensionIndex */ ) +{ + return false; +} + +bool PieChart::isExpandNarrowValuesTowardZero( sal_Int32 /* nDimensionIndex */ ) +{ + return false; +} + +bool PieChart::isSeperateStackingForDifferentSigns( sal_Int32 /* nDimensionIndex */ ) +{ + return false; +} + +void PieChart::createShapes() +{ + if( m_aZSlots.begin() == m_aZSlots.end() ) //no series + return; + + DBG_ASSERT(m_pShapeFactory&&m_xLogicTarget.is()&&m_xFinalTarget.is(),"PieChart is not proper initialized"); + if(!(m_pShapeFactory&&m_xLogicTarget.is()&&m_xFinalTarget.is())) + return; + + //the text labels should be always on top of the other series shapes + //therefore create an own group for the texts to move them to front + //(because the text group is created after the series group the texts are displayed on top) + uno::Reference< drawing::XShapes > xSeriesTarget( + createGroupShape( m_xLogicTarget,rtl::OUString() )); + uno::Reference< drawing::XShapes > xTextTarget( + m_pShapeFactory->createGroup2D( m_xFinalTarget,rtl::OUString() )); + //--------------------------------------------- + //check necessary here that different Y axis can not be stacked in the same group? ... hm? + +//============================================================================= + ::std::vector< VDataSeriesGroup >::iterator aXSlotIter = m_aZSlots[0].begin(); + const ::std::vector< VDataSeriesGroup >::const_iterator aXSlotEnd = m_aZSlots[0].end(); + + ::std::vector< VDataSeriesGroup >::size_type nExplodeableSlot = 0; + if( m_pPosHelper->isMathematicalOrientationRadius() && m_bUseRings ) + nExplodeableSlot = m_aZSlots[0].size()-1; + + m_aLabelInfoList.clear(); + ::rtl::math::setNan(&m_fMaxOffset); + +//============================================================================= + for( double fSlotX=0; aXSlotIter != aXSlotEnd && (m_bUseRings||fSlotX<0.5 ); aXSlotIter++, fSlotX+=1.0 ) + { + ::std::vector< VDataSeries* >* pSeriesList = &(aXSlotIter->m_aSeriesVector); + if( pSeriesList->size()<=0 )//there should be only one series in each x slot + continue; + VDataSeries* pSeries = (*pSeriesList)[0]; + if(!pSeries) + continue; + + m_pPosHelper->m_fAngleDegreeOffset = pSeries->getStartingAngle(); + + double fLogicYSum = 0.0; + //iterate through all points to get the sum + sal_Int32 nPointIndex=0; + sal_Int32 nPointCount=pSeries->getTotalPointCount(); + for( nPointIndex = 0; nPointIndex < nPointCount; nPointIndex++ ) + { + double fY = pSeries->getYValue( nPointIndex ); + if(fY<0.0) + { + //@todo warn somehow that negative values are treated as positive + } + if( ::rtl::math::isNan(fY) ) + continue; + fLogicYSum += fabs(fY); + } + if(fLogicYSum==0.0) + continue; + double fLogicYForNextPoint = 0.0; + //iterate through all points to create shapes + for( nPointIndex = 0; nPointIndex < nPointCount; nPointIndex++ ) + { + double fLogicInnerRadius, fLogicOuterRadius; + double fOffset = getMaxOffset(); + bool bIsVisible = m_pPosHelper->getInnerAndOuterRadius( fSlotX+1.0, fLogicInnerRadius, fLogicOuterRadius, m_bUseRings, fOffset ); + if( !bIsVisible ) + continue; + + double fLogicZ = -0.5;//as defined + double fDepth = this->getTransformedDepth(); +//============================================================================= + + uno::Reference< drawing::XShapes > xSeriesGroupShape_Shapes = getSeriesGroupShape(pSeries, xSeriesTarget); + //collect data point information (logic coordinates, style ): + double fLogicYValue = fabs(pSeries->getYValue( nPointIndex )); + if( ::rtl::math::isNan(fLogicYValue) ) + continue; + if(fLogicYValue==0.0)//@todo: continue also if the resolution to small + continue; + double fLogicYPos = fLogicYForNextPoint; + fLogicYForNextPoint += fLogicYValue; + + uno::Reference< beans::XPropertySet > xPointProperties = pSeries->getPropertiesOfPoint( nPointIndex ); + + //iterate through all subsystems to create partial points + { + //logic values on angle axis: + double fLogicStartAngleValue = fLogicYPos/fLogicYSum; + double fLogicEndAngleValue = (fLogicYPos+fLogicYValue)/fLogicYSum; + + double fExplodePercentage=0.0; + bool bDoExplode = ( nExplodeableSlot == static_cast< ::std::vector< VDataSeriesGroup >::size_type >(fSlotX) ); + if(bDoExplode) try + { + xPointProperties->getPropertyValue( C2U( "Offset" )) >>= fExplodePercentage; + } + catch( uno::Exception& e ) + { + ASSERT_EXCEPTION( e ); + } + + //--------------------------- + //transforme to unit circle: + double fUnitCircleWidthAngleDegree = m_pPosHelper->getWidthAngleDegree( fLogicStartAngleValue, fLogicEndAngleValue ); + double fUnitCircleStartAngleDegree = m_pPosHelper->transformToAngleDegree( fLogicStartAngleValue ); + double fUnitCircleInnerRadius = m_pPosHelper->transformToRadius( fLogicInnerRadius ); + double fUnitCircleOuterRadius = m_pPosHelper->transformToRadius( fLogicOuterRadius ); + + //--------------------------- + //point color: + std::auto_ptr< tPropertyNameValueMap > apOverwritePropertiesMap(0); + { + if(!pSeries->hasPointOwnColor(nPointIndex) && m_xColorScheme.is()) + { + apOverwritePropertiesMap = std::auto_ptr< tPropertyNameValueMap >( new tPropertyNameValueMap() ); + (*apOverwritePropertiesMap)[C2U("FillColor")] = uno::makeAny( + m_xColorScheme->getColorByIndex( nPointIndex )); + } + } + + //create data point + uno::Reference<drawing::XShape> xPointShape( + createDataPoint( xSeriesGroupShape_Shapes, xPointProperties + , fUnitCircleStartAngleDegree, fUnitCircleWidthAngleDegree + , fUnitCircleInnerRadius, fUnitCircleOuterRadius + , fLogicZ, fDepth, fExplodePercentage, apOverwritePropertiesMap.get() ) ); + + //create label + if( pSeries->getDataPointLabelIfLabel(nPointIndex) ) + { + if( !::rtl::math::approxEqual( fExplodePercentage, 0.0 ) ) + { + double fExplodeOffset = (fUnitCircleOuterRadius-fUnitCircleInnerRadius)*fExplodePercentage; + fUnitCircleInnerRadius += fExplodeOffset; + fUnitCircleOuterRadius += fExplodeOffset; + } + + sal_Int32 nLabelPlacement = pSeries->getLabelPlacement( nPointIndex, m_xChartTypeModel, m_nDimension, m_pPosHelper->isSwapXAndY() ); + bool bMovementAllowed = ( nLabelPlacement == ::com::sun::star::chart::DataLabelPlacement::AVOID_OVERLAP ); + if( bMovementAllowed ) + nLabelPlacement = ::com::sun::star::chart::DataLabelPlacement::OUTSIDE; + + LabelAlignment eAlignment(LABEL_ALIGN_CENTER); + sal_Int32 nScreenValueOffsetInRadiusDirection = 0 ; + if( nLabelPlacement == ::com::sun::star::chart::DataLabelPlacement::OUTSIDE ) + nScreenValueOffsetInRadiusDirection = (3!=m_nDimension) ? 150 : 0;//todo maybe calculate this font height dependent + else if( nLabelPlacement == ::com::sun::star::chart::DataLabelPlacement::INSIDE ) + nScreenValueOffsetInRadiusDirection = (3!=m_nDimension) ? -150 : 0;//todo maybe calculate this font height dependent + PolarLabelPositionHelper aPolarPosHelper(m_pPosHelper,m_nDimension,m_xLogicTarget,m_pShapeFactory); + awt::Point aScreenPosition2D( + aPolarPosHelper.getLabelScreenPositionAndAlignmentForUnitCircleValues(eAlignment, nLabelPlacement + , fUnitCircleStartAngleDegree, fUnitCircleWidthAngleDegree + , fUnitCircleInnerRadius, fUnitCircleOuterRadius, 0.0, 0 )); + + PieLabelInfo aPieLabelInfo; + aPieLabelInfo.aFirstPosition = basegfx::B2IVector( aScreenPosition2D.X, aScreenPosition2D.Y ); + awt::Point aOrigin( aPolarPosHelper.transformSceneToScreenPosition( m_pPosHelper->transformUnitCircleToScene( 0.0, 0.0, 0.5 ) ) ); + aPieLabelInfo.aOrigin = basegfx::B2IVector( aOrigin.X, aOrigin.Y ); + + //add a scaling independent Offset if requested + if( nScreenValueOffsetInRadiusDirection != 0) + { + basegfx::B2IVector aDirection( aScreenPosition2D.X- aOrigin.X, aScreenPosition2D.Y- aOrigin.Y ); + aDirection.setLength(nScreenValueOffsetInRadiusDirection); + aScreenPosition2D.X += aDirection.getX(); + aScreenPosition2D.Y += aDirection.getY(); + } + + aPieLabelInfo.xTextShape = this->createDataLabel( xTextTarget, *pSeries, nPointIndex + , fLogicYValue, fLogicYSum, aScreenPosition2D, eAlignment ); + + uno::Reference< container::XChild > xChild( aPieLabelInfo.xTextShape, uno::UNO_QUERY ); + if( xChild.is() ) + aPieLabelInfo.xLabelGroupShape = uno::Reference<drawing::XShape>( xChild->getParent(), uno::UNO_QUERY ); + aPieLabelInfo.fValue = fLogicYValue; + aPieLabelInfo.bMovementAllowed = bMovementAllowed; + aPieLabelInfo.bMoved= false; + aPieLabelInfo.xTextTarget = xTextTarget; + m_aLabelInfoList.push_back(aPieLabelInfo); + } + + if(!bDoExplode) + { + ShapeFactory::setShapeName( xPointShape + , ObjectIdentifier::createPointCID( pSeries->getPointCID_Stub(), nPointIndex ) ); + } + else try + { + //enable dragging of outer segments + + double fAngle = fUnitCircleStartAngleDegree + fUnitCircleWidthAngleDegree/2.0; + double fMaxDeltaRadius = fUnitCircleOuterRadius-fUnitCircleInnerRadius; + drawing::Position3D aOrigin = m_pPosHelper->transformUnitCircleToScene( fAngle, fUnitCircleOuterRadius, fLogicZ ); + drawing::Position3D aNewOrigin = m_pPosHelper->transformUnitCircleToScene( fAngle, fUnitCircleOuterRadius + fMaxDeltaRadius, fLogicZ ); + + sal_Int32 nOffsetPercent( static_cast<sal_Int32>(fExplodePercentage * 100.0) ); + + awt::Point aMinimumPosition( PlottingPositionHelper::transformSceneToScreenPosition( + aOrigin, m_xLogicTarget, m_pShapeFactory, m_nDimension ) ); + awt::Point aMaximumPosition( PlottingPositionHelper::transformSceneToScreenPosition( + aNewOrigin, m_xLogicTarget, m_pShapeFactory, m_nDimension ) ); + + //enable draging of piesegments + rtl::OUString aPointCIDStub( ObjectIdentifier::createSeriesSubObjectStub( OBJECTTYPE_DATA_POINT + , pSeries->getSeriesParticle() + , ObjectIdentifier::getPieSegmentDragMethodServiceName() + , ObjectIdentifier::createPieSegmentDragParameterString( + nOffsetPercent, aMinimumPosition, aMaximumPosition ) + ) ); + + ShapeFactory::setShapeName( xPointShape + , ObjectIdentifier::createPointCID( aPointCIDStub, nPointIndex ) ); + } + catch( uno::Exception& e ) + { + ASSERT_EXCEPTION( e ); + } + }//next series in x slot (next y slot) + }//next category + }//next x slot +//============================================================================= +//============================================================================= +//============================================================================= + /* @todo remove series shapes if empty + //remove and delete point-group-shape if empty + if(!xSeriesGroupShape_Shapes->getCount()) + { + (*aSeriesIter)->m_xShape.set(NULL); + m_xLogicTarget->remove(xSeriesGroupShape_Shape); + } + */ + + //remove and delete series-group-shape if empty + + //... todo +} + +namespace +{ + +::basegfx::B2IRectangle lcl_getRect( const uno::Reference< drawing::XShape >& xShape ) +{ + ::basegfx::B2IRectangle aRect; + if( xShape.is() ) + aRect = BaseGFXHelper::makeRectangle(xShape->getPosition(),xShape->getSize() ); + return aRect; +} + +bool lcl_isInsidePage( const awt::Point& rPos, const awt::Size& rSize, const awt::Size& rPageSize ) +{ + if( rPos.X < 0 || rPos.Y < 0 ) + return false; + if( (rPos.X + rSize.Width) > rPageSize.Width ) + return false; + if( (rPos.Y + rSize.Height) > rPageSize.Height ) + return false; + return true; +} + +}//end anonymous namespace + +PieChart::PieLabelInfo::PieLabelInfo() + : xTextShape(0), xLabelGroupShape(0), aFirstPosition(), aOrigin(), fValue(0.0) + , bMovementAllowed(false), bMoved(false), xTextTarget(0), pPrevious(0),pNext(0) +{ +} + +bool PieChart::PieLabelInfo::moveAwayFrom( const PieChart::PieLabelInfo* pFix, const awt::Size& rPageSize, bool bMoveHalfWay, bool bMoveClockwise, bool bAlternativeMoveDirection ) +{ + //return true if the move was successful + if(!this->bMovementAllowed) + return false; + + const sal_Int32 nLabelDistanceX = rPageSize.Width/50; + const sal_Int32 nLabelDistanceY = rPageSize.Height/50; + + ::basegfx::B2IRectangle aOverlap( lcl_getRect( this->xLabelGroupShape ) ); + aOverlap.intersect( lcl_getRect( pFix->xLabelGroupShape ) ); + if( !aOverlap.isEmpty() ) + { + (void)bAlternativeMoveDirection;//todo + + basegfx::B2IVector aRadiusDirection = this->aFirstPosition - this->aOrigin; + aRadiusDirection.setLength(1.0); + basegfx::B2IVector aTangentialDirection( -aRadiusDirection.getY(), aRadiusDirection.getX() ); + bool bShiftHorizontal = abs(aTangentialDirection.getX()) > abs(aTangentialDirection.getY()); + + sal_Int32 nShift = bShiftHorizontal ? static_cast<sal_Int32>(aOverlap.getWidth()) : static_cast<sal_Int32>(aOverlap.getHeight()); + nShift += (bShiftHorizontal ? nLabelDistanceX : nLabelDistanceY); + if( bMoveHalfWay ) + nShift/=2; + if(!bMoveClockwise) + nShift*=-1; + awt::Point aOldPos( this->xLabelGroupShape->getPosition() ); + basegfx::B2IVector aNewPos = basegfx::B2IVector( aOldPos.X, aOldPos.Y ) + nShift*aTangentialDirection; + + //check whether the new position is ok + awt::Point aNewAWTPos( aNewPos.getX(), aNewPos.getY() ); + if( !lcl_isInsidePage( aNewAWTPos, this->xLabelGroupShape->getSize(), rPageSize ) ) + return false; + + this->xLabelGroupShape->setPosition( aNewAWTPos ); + this->bMoved = true; + } + return true; +} + +void PieChart::resetLabelPositionsToPreviousState() +{ + std::vector< PieLabelInfo >::iterator aIt = m_aLabelInfoList.begin(); + std::vector< PieLabelInfo >::const_iterator aEnd = m_aLabelInfoList.end(); + for( ;aIt!=aEnd; ++aIt ) + aIt->xLabelGroupShape->setPosition(aIt->aPreviousPosition); +} + +bool PieChart::detectLabelOverlapsAndMove( const awt::Size& rPageSize ) +{ + //returns true when there might be more to do + + //find borders of a group of overlapping labels + bool bOverlapFound = false; + PieLabelInfo* pStart = &(*(m_aLabelInfoList.rbegin())); + PieLabelInfo* pFirstBorder = 0; + PieLabelInfo* pSecondBorder = 0; + PieLabelInfo* pCurrent = pStart; + do + { + ::basegfx::B2IRectangle aPreviousOverlap( lcl_getRect( pCurrent->xLabelGroupShape ) ); + ::basegfx::B2IRectangle aNextOverlap( aPreviousOverlap ); + aPreviousOverlap.intersect( lcl_getRect( pCurrent->pPrevious->xLabelGroupShape ) ); + aNextOverlap.intersect( lcl_getRect( pCurrent->pNext->xLabelGroupShape ) ); + + bool bPreviousOverlap = !aPreviousOverlap.isEmpty(); + bool bNextOverlap = !aNextOverlap.isEmpty(); + if( bPreviousOverlap || bNextOverlap ) + bOverlapFound = true; + if( !bPreviousOverlap && bNextOverlap ) + { + pFirstBorder = pCurrent; + break; + } + pCurrent = pCurrent->pNext; + } + while( pCurrent != pStart ); + + if( !bOverlapFound ) + return false; + + if( pFirstBorder ) + { + pCurrent = pFirstBorder; + do + { + ::basegfx::B2IRectangle aPreviousOverlap( lcl_getRect( pCurrent->xLabelGroupShape ) ); + ::basegfx::B2IRectangle aNextOverlap( aPreviousOverlap ); + aPreviousOverlap.intersect( lcl_getRect( pCurrent->pPrevious->xLabelGroupShape ) ); + aNextOverlap.intersect( lcl_getRect( pCurrent->pNext->xLabelGroupShape ) ); + + if( !aPreviousOverlap.isEmpty() && aNextOverlap.isEmpty() ) + { + pSecondBorder = pCurrent; + break; + } + pCurrent = pCurrent->pNext; + } + while( pCurrent != pFirstBorder ); + } + + if( !pFirstBorder || !pSecondBorder ) + { + pFirstBorder = &(*(m_aLabelInfoList.rbegin())); + pSecondBorder = &(*(m_aLabelInfoList.begin())); + } + + //find center + PieLabelInfo* pCenter = pFirstBorder; + sal_Int32 nOverlapGroupCount = 1; + for( pCurrent = pFirstBorder ;pCurrent != pSecondBorder; pCurrent = pCurrent->pNext ) + nOverlapGroupCount++; + sal_Int32 nCenterPos = nOverlapGroupCount/2; + bool bSingleCenter = nOverlapGroupCount%2 != 0; + if( bSingleCenter ) + nCenterPos++; + if(nCenterPos>1) + { + pCurrent = pFirstBorder; + while( --nCenterPos ) + pCurrent = pCurrent->pNext; + pCenter = pCurrent; + } + + //remind current positions + pCurrent = pStart; + do + { + pCurrent->aPreviousPosition = pCurrent->xLabelGroupShape->getPosition(); + pCurrent = pCurrent->pNext; + } + while( pCurrent != pStart ); + + // + bool bAlternativeMoveDirection = false; + if( !tryMoveLabels( pFirstBorder, pSecondBorder, pCenter, bSingleCenter, bAlternativeMoveDirection, rPageSize ) ) + tryMoveLabels( pFirstBorder, pSecondBorder, pCenter, bSingleCenter, bAlternativeMoveDirection, rPageSize ); + return true; +} + +bool PieChart::tryMoveLabels( PieLabelInfo* pFirstBorder, PieLabelInfo* pSecondBorder + , PieLabelInfo* pCenter + , bool bSingleCenter, bool& rbAlternativeMoveDirection, const awt::Size& rPageSize ) +{ + PieLabelInfo* p1 = bSingleCenter ? pCenter->pPrevious : pCenter; + PieLabelInfo* p2 = pCenter->pNext; + //return true when successful + + bool bLabelOrderIsAntiClockWise = m_pPosHelper->isMathematicalOrientationAngle(); + + PieLabelInfo* pCurrent = 0; + for( pCurrent = p2 ;pCurrent->pPrevious != pSecondBorder; pCurrent = pCurrent->pNext ) + { + PieLabelInfo* pFix = 0; + for( pFix = p2->pPrevious ;pFix != pCurrent; pFix = pFix->pNext ) + { + if( !pCurrent->moveAwayFrom( pFix, rPageSize, !bSingleCenter && pCurrent == p2, !bLabelOrderIsAntiClockWise, rbAlternativeMoveDirection ) ) + { + if( !rbAlternativeMoveDirection ) + { + rbAlternativeMoveDirection = true; + resetLabelPositionsToPreviousState(); + return false; + } + } + } + } + for( pCurrent = p1 ;pCurrent->pNext != pFirstBorder; pCurrent = pCurrent->pPrevious ) + { + PieLabelInfo* pFix = 0; + for( pFix = p2->pNext ;pFix != pCurrent; pFix = pFix->pPrevious ) + { + if( !pCurrent->moveAwayFrom( pFix, rPageSize, false, bLabelOrderIsAntiClockWise, rbAlternativeMoveDirection ) ) + { + if( !rbAlternativeMoveDirection ) + { + rbAlternativeMoveDirection = true; + resetLabelPositionsToPreviousState(); + return false; + } + } + } + } + return true; +} + +void PieChart::rearrangeLabelToAvoidOverlapIfRequested( const awt::Size& rPageSize ) +{ + //------------------------------------------------------------------ + //check whether there are any labels that should be moved + std::vector< PieLabelInfo >::iterator aIt1 = m_aLabelInfoList.begin(); + std::vector< PieLabelInfo >::const_iterator aEnd = m_aLabelInfoList.end(); + bool bMoveableFound = false; + for( ;aIt1!=aEnd; ++aIt1 ) + { + if(aIt1->bMovementAllowed) + { + bMoveableFound = true; + break; + } + } + if(!bMoveableFound) + return; + + double fPageDiagonaleLength = sqrt( double( rPageSize.Width*rPageSize.Width + rPageSize.Height*rPageSize.Height) ); + if( ::rtl::math::approxEqual( fPageDiagonaleLength, 0.0 ) ) + return; + + //------------------------------------------------------------------ + //init next and previous + aIt1 = m_aLabelInfoList.begin(); + std::vector< PieLabelInfo >::iterator aIt2 = aIt1; + if( aIt1==aEnd )//no need to do anything when we only have one label + return; + aIt1->pPrevious = &(*(m_aLabelInfoList.rbegin())); + ++aIt2; + for( ;aIt2!=aEnd; ++aIt1, ++aIt2 ) + { + PieLabelInfo& rInfo1( *aIt1 ); + PieLabelInfo& rInfo2( *aIt2 ); + rInfo1.pNext = &rInfo2; + rInfo2.pPrevious = &rInfo1; + } + aIt1->pNext = &(*(m_aLabelInfoList.begin())); + + + //------------------------------------------------------------------ + //detect overlaps and move + sal_Int32 nMaxIterations = 50; + while( detectLabelOverlapsAndMove( rPageSize ) && nMaxIterations > 0 ) + nMaxIterations--; + + //------------------------------------------------------------------ + //create connection lines for the moved labels + aEnd = m_aLabelInfoList.end(); + VLineProperties aVLineProperties; + for( aIt1 = m_aLabelInfoList.begin(); aIt1!=aEnd; ++aIt1 ) + { + PieLabelInfo& rInfo( *aIt1 ); + if( rInfo.bMoved ) + { + sal_Int32 nX1 = rInfo.aFirstPosition.getX(); + sal_Int32 nY1 = rInfo.aFirstPosition.getY(); + sal_Int32 nX2 = nX1; + sal_Int32 nY2 = nY1; + ::basegfx::B2IRectangle aRect( lcl_getRect( rInfo.xLabelGroupShape ) ); + if( nX1 < aRect.getMinX() ) + nX2 = aRect.getMinX(); + else if( nX1 > aRect.getMaxX() ) + nX2 = aRect.getMaxX(); + + if( nY1 < aRect.getMinY() ) + nY2 = aRect.getMinY(); + else if( nY1 > aRect.getMaxY() ) + nY2 = aRect.getMaxY(); + + + //when the line is very short compared to the page size don't create one + ::basegfx::B2DVector aLength(nX1-nX2, nY1-nY2); + if( (aLength.getLength()/fPageDiagonaleLength) < 0.01 ) + continue; + + drawing::PointSequenceSequence aPoints(1); + aPoints[0].realloc(2); + aPoints[0][0].X = nX1; + aPoints[0][0].Y = nY1; + aPoints[0][1].X = nX2; + aPoints[0][1].Y = nY2; + + uno::Reference< beans::XPropertySet > xProp( rInfo.xTextShape, uno::UNO_QUERY); + if( xProp.is() ) + { + sal_Int32 nColor = 0; + xProp->getPropertyValue(C2U("CharColor")) >>= nColor; + if( nColor != -1 )//automatic font color does not work for lines -> fallback to black + aVLineProperties.Color = uno::makeAny(nColor); + } + m_pShapeFactory->createLine2D( rInfo.xTextTarget, aPoints, &aVLineProperties ); + } + } +} + +//............................................................................. +} //namespace chart +//............................................................................. |