| /************************************************************** |
| * |
| * 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 |
| //............................................................................. |