blob: e588d0373561213355caee53720dd6d31df3b18a [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the 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 PieChart::setScales( const std::vector< ExplicitScaleData >& rScales, bool /* bSwapXAndYAxis */ )
{
DBG_ASSERT(m_nDimension<=static_cast<sal_Int32>(rScales.size()),"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);
sal_Int32 n3DRelativeHeight = 100;
uno::Reference< beans::XPropertySet > xPropertySet( m_xChartTypeModel, uno::UNO_QUERY );
if ( (m_nDimension==3) && xPropertySet.is())
{
try
{
uno::Any aAny = xPropertySet->getPropertyValue( C2U("3DRelativeHeight") );
aAny >>= n3DRelativeHeight;
}
catch(const uno::Exception& e) {}
}
//=============================================================================
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 = -1.0;//as defined
double fDepth = this->getTransformedDepth() * (n3DRelativeHeight / 100.0);
//=============================================================================
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, fLogicZ+0.5, 0 ));
PieLabelInfo aPieLabelInfo;
aPieLabelInfo.aFirstPosition = basegfx::B2IVector( aScreenPosition2D.X, aScreenPosition2D.Y );
awt::Point aOrigin( aPolarPosHelper.transformSceneToScreenPosition( m_pPosHelper->transformUnitCircleToScene( 0.0, 0.0, fLogicZ+1.0 ) ) );
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
//.............................................................................