1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include "PieChart.hxx"
21 #include "PlottingPositionHelper.hxx"
22 #include "AbstractShapeFactory.hxx"
23 #include "PolarLabelPositionHelper.hxx"
25 #include "CommonConverters.hxx"
26 #include "ViewDefines.hxx"
27 #include "ObjectIdentifier.hxx"
29 #include <com/sun/star/chart/DataLabelPlacement.hpp>
30 #include <com/sun/star/chart2/XColorScheme.hpp>
32 #include <com/sun/star/container/XChild.hpp>
33 #include <rtl/math.hxx>
35 #include <boost/scoped_ptr.hpp>
37 using namespace ::com::sun::star
;
38 using namespace ::com::sun::star::chart2
;
42 struct PieChart::ShapeParam
44 /** the start angle of the slice
46 double mfUnitCircleStartAngleDegree
;
48 /** the angle width of the slice
50 double mfUnitCircleWidthAngleDegree
;
52 /** the normalized outer radius of the ring the slice belongs to.
54 double mfUnitCircleOuterRadius
;
56 /** the normalized inner radius of the ring the slice belongs to
58 double mfUnitCircleInnerRadius
;
60 /** relative distance offset of a slice from the pie center;
61 * this parameter is used for instance when the user performs manual
62 * dragging of a slice (the drag operation is possible only for slices that
63 * belong to the outer ring and only along the ray bisecting the slice);
64 * the value for the given entry in the data series is obtained by the
65 * `Offset` property attached to each entry; note that the value
66 * provided by the `Offset` property is used both as a logical value in
67 * `PiePositionHelper::getInnerAndOuterRadius` and as a percentage value in
68 * the `PieChart::createDataPoint` and `PieChart::createTextLabelShape`
69 * methods; since the logical height of a ring is always 1, this duality
70 * does not cause any incorrect behavior;
72 double mfExplodePercentage
;
74 /** sum of all Y values in a single series
78 /** for 3D pie chart: label z coordinate
82 /** for 3D pie chart: height
87 mfUnitCircleStartAngleDegree(0.0),
88 mfUnitCircleWidthAngleDegree(0.0),
89 mfUnitCircleOuterRadius(0.0),
90 mfUnitCircleInnerRadius(0.0),
91 mfExplodePercentage(0.0),
97 class PiePositionHelper
: public PolarPlottingPositionHelper
100 PiePositionHelper( NormalAxis eNormalAxis
, double fAngleDegreeOffset
);
101 virtual ~PiePositionHelper();
103 bool getInnerAndOuterRadius( double fCategoryX
, double& fLogicInnerRadius
, double& fLogicOuterRadius
, bool bUseRings
, double fMaxOffset
) const;
106 //Distance between different category rings, seen relative to width of a ring:
107 double m_fRingDistance
; //>=0 m_fRingDistance=1 --> distance == width
110 PiePositionHelper::PiePositionHelper( NormalAxis eNormalAxis
, double fAngleDegreeOffset
)
111 : PolarPlottingPositionHelper(eNormalAxis
)
112 , m_fRingDistance(0.0)
114 m_fRadiusOffset
= 0.0;
115 m_fAngleDegreeOffset
= fAngleDegreeOffset
;
118 PiePositionHelper::~PiePositionHelper()
122 /** Compute the outer and the inner radius for the current ring (not for the
123 * whole donut!), in general it is:
124 * inner_radius = (ring_index + 1) - 0.5 + max_offset,
125 * outer_radius = (ring_index + 1) + 0.5 + max_offset.
126 * When orientation for the radius axis is reversed these values are swapped.
127 * (Indeed the the orientation for the radius axis is always reversed!
128 * See `PieChartTypeTemplate::adaptScales`.)
129 * The maximum relative offset (see notes for P`ieChart::getMaxOffset`) is
130 * added to both the inner and the outer radius.
131 * It returns true if the ring is visible (that is not out of the radius
134 bool PiePositionHelper::getInnerAndOuterRadius( double fCategoryX
135 , double& fLogicInnerRadius
, double& fLogicOuterRadius
136 , bool bUseRings
, double fMaxOffset
) const
141 bool bIsVisible
= true;
142 double fLogicInner
= fCategoryX
-0.5+m_fRingDistance
/2.0;
143 double fLogicOuter
= fCategoryX
+0.5-m_fRingDistance
/2.0;
145 if( !isMathematicalOrientationRadius() )
147 //in this case the given getMaximumX() was not corrcect instead the minimum should have been smaller by fMaxOffset
148 //but during getMaximumX and getMimumX we do not know the axis orientation
149 fLogicInner
+= fMaxOffset
;
150 fLogicOuter
+= fMaxOffset
;
153 if( fLogicInner
>= getLogicMaxX() )
155 if( fLogicOuter
<= getLogicMinX() )
158 if( fLogicInner
< getLogicMinX() )
159 fLogicInner
= getLogicMinX();
160 if( fLogicOuter
> getLogicMaxX() )
161 fLogicOuter
= getLogicMaxX();
163 fLogicInnerRadius
= fLogicInner
;
164 fLogicOuterRadius
= fLogicOuter
;
165 if( !isMathematicalOrientationRadius() )
166 std::swap(fLogicInnerRadius
,fLogicOuterRadius
);
170 PieChart::PieChart( const uno::Reference
<XChartType
>& xChartTypeModel
171 , sal_Int32 nDimensionCount
172 , bool bExcludingPositioning
)
173 : VSeriesPlotter( xChartTypeModel
, nDimensionCount
)
174 , m_pPosHelper( new PiePositionHelper( NormalAxis_Z
, (m_nDimension
==3)?0.0:90.0 ) )
176 , m_bSizeExcludesLabelsAndExplodedSegments(bExcludingPositioning
)
178 ::rtl::math::setNan(&m_fMaxOffset
);
180 PlotterBase::m_pPosHelper
= m_pPosHelper
;
181 VSeriesPlotter::m_pMainPosHelper
= m_pPosHelper
;
182 m_pPosHelper
->m_fRadiusOffset
= 0.0;
183 m_pPosHelper
->m_fRingDistance
= 0.0;
185 uno::Reference
< beans::XPropertySet
> xChartTypeProps( xChartTypeModel
, uno::UNO_QUERY
);
186 if( xChartTypeProps
.is() ) try
188 xChartTypeProps
->getPropertyValue( "UseRings") >>= m_bUseRings
;
191 m_pPosHelper
->m_fRadiusOffset
= 1.0;
192 if( nDimensionCount
==3 )
193 m_pPosHelper
->m_fRingDistance
= 0.1;
196 catch( const uno::Exception
& e
)
198 ASSERT_EXCEPTION( e
);
202 PieChart::~PieChart()
207 void PieChart::setScales( const std::vector
< ExplicitScaleData
>& rScales
, bool /* bSwapXAndYAxis */ )
209 OSL_ENSURE(m_nDimension
<=static_cast<sal_Int32
>(rScales
.size()),"Dimension of Plotter does not fit two dimension of given scale sequence");
210 m_pPosHelper
->setScales( rScales
, true );
213 drawing::Direction3D
PieChart::getPreferredDiagramAspectRatio() const
215 if( m_nDimension
== 3 )
216 return drawing::Direction3D(1,1,0.10);
217 return drawing::Direction3D(1,1,1);
220 bool PieChart::keepAspectRatio() const
222 if( m_nDimension
== 3 )
227 bool PieChart::shouldSnapRectToUsedArea()
232 uno::Reference
< drawing::XShape
> PieChart::createDataPoint(
233 const uno::Reference
<drawing::XShapes
>& xTarget
,
234 const uno::Reference
<beans::XPropertySet
>& xObjectProperties
,
235 tPropertyNameValueMap
* pOverwritePropertiesMap
,
236 const ShapeParam
& rParam
)
238 //transform position:
239 drawing::Direction3D aOffset
;
240 if (!::rtl::math::approxEqual(rParam
.mfExplodePercentage
, 0.0))
242 double fAngle
= rParam
.mfUnitCircleStartAngleDegree
+ rParam
.mfUnitCircleWidthAngleDegree
/2.0;
243 double fRadius
= (rParam
.mfUnitCircleOuterRadius
-rParam
.mfUnitCircleInnerRadius
)*rParam
.mfExplodePercentage
;
244 drawing::Position3D aOrigin
= m_pPosHelper
->transformUnitCircleToScene(0, 0, rParam
.mfLogicZ
);
245 drawing::Position3D aNewOrigin
= m_pPosHelper
->transformUnitCircleToScene(fAngle
, fRadius
, rParam
.mfLogicZ
);
246 aOffset
= aNewOrigin
- aOrigin
;
250 uno::Reference
< drawing::XShape
> xShape(0);
253 xShape
= m_pShapeFactory
->createPieSegment( xTarget
254 , rParam
.mfUnitCircleStartAngleDegree
, rParam
.mfUnitCircleWidthAngleDegree
255 , rParam
.mfUnitCircleInnerRadius
, rParam
.mfUnitCircleOuterRadius
256 , aOffset
, B3DHomMatrixToHomogenMatrix( m_pPosHelper
->getUnitCartesianToScene() )
261 xShape
= m_pShapeFactory
->createPieSegment2D( xTarget
262 , rParam
.mfUnitCircleStartAngleDegree
, rParam
.mfUnitCircleWidthAngleDegree
263 , rParam
.mfUnitCircleInnerRadius
, rParam
.mfUnitCircleOuterRadius
264 , aOffset
, B3DHomMatrixToHomogenMatrix( m_pPosHelper
->getUnitCartesianToScene() ) );
266 setMappedProperties( xShape
, xObjectProperties
, PropertyMapper::getPropertyNameMapForFilledSeriesProperties(), pOverwritePropertiesMap
);
270 void PieChart::createTextLabelShape(
271 const uno::Reference
<drawing::XShapes
>& xTextTarget
,
272 VDataSeries
& rSeries
, sal_Int32 nPointIndex
, ShapeParam
& rParam
)
274 if (!rSeries
.getDataPointLabelIfLabel(nPointIndex
))
275 // There is no text label for this data point. Nothing to do.
278 ///by using the `mfExplodePercentage` parameter a normalized offset is added
279 ///to both normalized radii. (See notes for
280 ///`PolarPlottingPositionHelper::transformToRadius`, especially example 3,
281 ///and related comments).
282 if (!rtl::math::approxEqual(rParam
.mfExplodePercentage
, 0.0))
284 double fExplodeOffset
= (rParam
.mfUnitCircleOuterRadius
-rParam
.mfUnitCircleInnerRadius
)*rParam
.mfExplodePercentage
;
285 rParam
.mfUnitCircleInnerRadius
+= fExplodeOffset
;
286 rParam
.mfUnitCircleOuterRadius
+= fExplodeOffset
;
289 ///get the required label placement type. Available placements are
290 ///`AVOID_OVERLAP`, `CENTER`, `OUTSIDE` and `INSIDE`;
291 sal_Int32 nLabelPlacement
= rSeries
.getLabelPlacement(
292 nPointIndex
, m_xChartTypeModel
, m_nDimension
, m_pPosHelper
->isSwapXAndY());
294 ///when the placement is of `AVOID_OVERLAP` type a later rearrangement of
295 ///the label position is allowed; the `createTextLabelShape` treats the
296 ///`AVOID_OVERLAP` as if it was of `CENTER` type;
298 //AVOID_OVERLAP is in fact "Best fit" in the UI.
299 bool bMovementAllowed
= ( nLabelPlacement
== ::com::sun::star::chart::DataLabelPlacement::AVOID_OVERLAP
);
300 if( bMovementAllowed
)
301 // Use center for "Best fit" for now. In the future we
302 // may want to implement a real best fit algorithm.
303 // But center is good enough, and close to what Excel
305 nLabelPlacement
= ::com::sun::star::chart::DataLabelPlacement::CENTER
;
307 ///for `OUTSIDE` (`INSIDE`) label placements an offset of 150 (-150), in the
308 ///radius direction, is added to the final screen position of the label
309 ///anchor point. This is required in order to ensure that the label is
310 ///completely outside (inside) the related slice. Indeed this value should
311 ///depend on the font height;
312 ///pay attention: 150 is not a big offset, in fact the screen position
313 ///coordinates for label anchor points are in the 10000-20000 range, hence
314 ///these are coordinates of a virtual screen and 150 is a small value;
315 LabelAlignment
eAlignment(LABEL_ALIGN_CENTER
);
316 sal_Int32 nScreenValueOffsetInRadiusDirection
= 0 ;
317 if( nLabelPlacement
== ::com::sun::star::chart::DataLabelPlacement::OUTSIDE
)
318 nScreenValueOffsetInRadiusDirection
= (3!=m_nDimension
) ? 150 : 0;//todo maybe calculate this font height dependent
319 else if( nLabelPlacement
== ::com::sun::star::chart::DataLabelPlacement::INSIDE
)
320 nScreenValueOffsetInRadiusDirection
= (3!=m_nDimension
) ? -150 : 0;//todo maybe calculate this font height dependent
322 ///the scene position of the label anchor point is calculated (see notes for
323 ///`PolarLabelPositionHelper::getLabelScreenPositionAndAlignmentForUnitCircleValues`),
324 ///and immediately transformed into the screen position.
325 PolarLabelPositionHelper
aPolarPosHelper(m_pPosHelper
,m_nDimension
,m_xLogicTarget
,m_pShapeFactory
);
326 awt::Point
aScreenPosition2D(
327 aPolarPosHelper
.getLabelScreenPositionAndAlignmentForUnitCircleValues(eAlignment
, nLabelPlacement
328 , rParam
.mfUnitCircleStartAngleDegree
, rParam
.mfUnitCircleWidthAngleDegree
329 , rParam
.mfUnitCircleInnerRadius
, rParam
.mfUnitCircleOuterRadius
, rParam
.mfLogicZ
+0.5, 0 ));
331 ///the screen position of the pie/donut center is calculated.
332 PieLabelInfo aPieLabelInfo
;
333 aPieLabelInfo
.aFirstPosition
= basegfx::B2IVector( aScreenPosition2D
.X
, aScreenPosition2D
.Y
);
334 awt::Point
aOrigin( aPolarPosHelper
.transformSceneToScreenPosition( m_pPosHelper
->transformUnitCircleToScene( 0.0, 0.0, rParam
.mfLogicZ
+1.0 ) ) );
335 aPieLabelInfo
.aOrigin
= basegfx::B2IVector( aOrigin
.X
, aOrigin
.Y
);
337 ///add a scaling independent Offset if requested
338 if( nScreenValueOffsetInRadiusDirection
!= 0)
340 basegfx::B2IVector
aDirection( aScreenPosition2D
.X
- aOrigin
.X
, aScreenPosition2D
.Y
- aOrigin
.Y
);
341 aDirection
.setLength(nScreenValueOffsetInRadiusDirection
);
342 aScreenPosition2D
.X
+= aDirection
.getX();
343 aScreenPosition2D
.Y
+= aDirection
.getY();
346 ///the text shape for the label is created
347 double nVal
= rSeries
.getYValue(nPointIndex
);
348 aPieLabelInfo
.xTextShape
= createDataLabel(
349 xTextTarget
, rSeries
, nPointIndex
, nVal
, rParam
.mfLogicYSum
, aScreenPosition2D
, eAlignment
);
351 ///a new `PieLabelInfo` instance is initialized with all the info related to
352 ///the current label in order to simplify later label position rearrangement;
353 uno::Reference
< container::XChild
> xChild( aPieLabelInfo
.xTextShape
, uno::UNO_QUERY
);
355 aPieLabelInfo
.xLabelGroupShape
= uno::Reference
<drawing::XShape
>( xChild
->getParent(), uno::UNO_QUERY
);
356 aPieLabelInfo
.fValue
= nVal
;
357 aPieLabelInfo
.bMovementAllowed
= bMovementAllowed
;
358 aPieLabelInfo
.bMoved
= false;
359 aPieLabelInfo
.xTextTarget
= xTextTarget
;
361 if (bMovementAllowed
)
363 performLabelBestFit(rParam
, aPieLabelInfo
);
367 m_aLabelInfoList
.push_back(aPieLabelInfo
);
371 void PieChart::addSeries( VDataSeries
* pSeries
, sal_Int32
/* zSlot */, sal_Int32
/* xSlot */, sal_Int32
/* ySlot */ )
373 VSeriesPlotter::addSeries( pSeries
, 0, -1, 0 );
376 double PieChart::getMinimumX()
380 double PieChart::getMaxOffset()
382 if (!::rtl::math::isNan(m_fMaxOffset
))
383 // Value already cached. Use it.
387 if( m_aZSlots
.size()<=0 )
389 if( m_aZSlots
[0].size()<=0 )
392 const ::std::vector
< VDataSeries
* >& rSeriesList( m_aZSlots
[0][0].m_aSeriesVector
);
393 if( rSeriesList
.size()<=0 )
396 VDataSeries
* pSeries
= rSeriesList
[0];
397 uno::Reference
< beans::XPropertySet
> xSeriesProp( pSeries
->getPropertiesOfSeries() );
398 if( !xSeriesProp
.is() )
401 double fExplodePercentage
=0.0;
402 xSeriesProp
->getPropertyValue( "Offset") >>= fExplodePercentage
;
403 if(fExplodePercentage
>m_fMaxOffset
)
404 m_fMaxOffset
=fExplodePercentage
;
406 if(!m_bSizeExcludesLabelsAndExplodedSegments
)
408 uno::Sequence
< sal_Int32
> aAttributedDataPointIndexList
;
409 if( xSeriesProp
->getPropertyValue( "AttributedDataPoints" ) >>= aAttributedDataPointIndexList
)
411 for(sal_Int32 nN
=aAttributedDataPointIndexList
.getLength();nN
--;)
413 uno::Reference
< beans::XPropertySet
> xPointProp( pSeries
->getPropertiesOfPoint(aAttributedDataPointIndexList
[nN
]) );
416 fExplodePercentage
=0.0;
417 xPointProp
->getPropertyValue( "Offset") >>= fExplodePercentage
;
418 if(fExplodePercentage
>m_fMaxOffset
)
419 m_fMaxOffset
=fExplodePercentage
;
426 double PieChart::getMaximumX()
428 double fMaxOffset
= getMaxOffset();
429 if( m_aZSlots
.size()>0 && m_bUseRings
)
430 return m_aZSlots
[0].size()+0.5+fMaxOffset
;
431 return 1.5+fMaxOffset
;
433 double PieChart::getMinimumYInRange( double /* fMinimumX */, double /* fMaximumX */, sal_Int32
/* nAxisIndex */ )
438 double PieChart::getMaximumYInRange( double /* fMinimumX */, double /* fMaximumX */, sal_Int32
/* nAxisIndex */ )
443 bool PieChart::isExpandBorderToIncrementRhythm( sal_Int32
/* nDimensionIndex */ )
448 bool PieChart::isExpandIfValuesCloseToBorder( sal_Int32
/* nDimensionIndex */ )
453 bool PieChart::isExpandWideValuesToZero( sal_Int32
/* nDimensionIndex */ )
458 bool PieChart::isExpandNarrowValuesTowardZero( sal_Int32
/* nDimensionIndex */ )
463 bool PieChart::isSeparateStackingForDifferentSigns( sal_Int32
/* nDimensionIndex */ )
468 void PieChart::createShapes()
470 ///a ZSlot is a vector< vector< VDataSeriesGroup > >. There is only one
471 ///ZSlot: m_aZSlots[0] which has a number of elements equal to the total
472 ///number of data series (in fact, even if m_aZSlots[0][i] is an object of
473 ///type `VDataSeriesGroup`, in the current implementation, there is only one
474 ///data series in each data series group).
475 if (m_aZSlots
.empty())
476 // No series to plot.
479 ///m_xLogicTarget is where the group of all data series shapes (e.g. a pie
480 ///slice) is added (xSeriesTarget);
482 ///m_xFinalTarget is where the group of all text shapes (labels) is added
485 ///both have been already created and added to the same root shape
486 ///( a member of a VDiagram object); this initialization occurs in
487 ///`ChartView::impl_createDiagramAndContent`.
489 OSL_ENSURE(m_pShapeFactory
&& m_xLogicTarget
.is() && m_xFinalTarget
.is(), "PieChart is not properly initialized.");
490 if (!m_pShapeFactory
|| !m_xLogicTarget
.is() || !m_xFinalTarget
.is())
493 ///the text labels should be always on top of the other series shapes
494 ///therefore create an own group for the texts to move them to front
495 ///(because the text group is created after the series group the texts are
497 uno::Reference
< drawing::XShapes
> xSeriesTarget(
498 createGroupShape( m_xLogicTarget
,OUString() ));
499 uno::Reference
< drawing::XShapes
> xTextTarget(
500 m_pShapeFactory
->createGroup2D( m_xFinalTarget
,OUString() ));
501 //check necessary here that different Y axis can not be stacked in the same group? ... hm?
503 ///pay attention that the `m_bSwapXAndY` parameter used by the polar
504 ///plotting position helper is always set to true for pie/donut charts
505 ///(see PieChart::setScales). This fact causes that `createShapes` expects
506 ///that the radius axis scale is the one with index 0 and the angle axis
507 ///scale is the one with index 1.
509 ::std::vector
< VDataSeriesGroup
>::iterator aXSlotIter
= m_aZSlots
[0].begin();
510 const ::std::vector
< VDataSeriesGroup
>::const_iterator aXSlotEnd
= m_aZSlots
[0].end();
512 ///m_bUseRings == true if chart type is `donut`, == false if chart type is
513 ///`pie`; if the chart is of `donut` type we have as many rings as many data
514 ///series, else we have a single ring (a pie) representing the first data
516 ///for what I can see the radius axis orientation is always reversed and
517 ///the angle axis orientation is always non-reversed;
518 ///the radius axis scale range is [0.5, number of rings + 0.5 + max_offset],
519 ///the angle axis scale range is [0, 1]. The max_offset parameter is used
520 ///for exploded pie chart and its value is 0.5.
522 ///the `explodeable` ring is the first one except when the radius axis
523 ///orientation is reversed (always!?) and we are dealing with a donut: in
524 ///such a case the `explodeable` ring is the last one.
525 ::std::vector
< VDataSeriesGroup
>::size_type nExplodeableSlot
= 0;
526 if( m_pPosHelper
->isMathematicalOrientationRadius() && m_bUseRings
)
527 nExplodeableSlot
= m_aZSlots
[0].size()-1;
529 m_aLabelInfoList
.clear();
530 ::rtl::math::setNan(&m_fMaxOffset
);
531 sal_Int32 n3DRelativeHeight
= 100;
532 uno::Reference
< beans::XPropertySet
> xPropertySet( m_xChartTypeModel
, uno::UNO_QUERY
);
533 if ( (m_nDimension
==3) && xPropertySet
.is())
537 uno::Any aAny
= xPropertySet
->getPropertyValue( "3DRelativeHeight" );
538 aAny
>>= n3DRelativeHeight
;
540 catch (const uno::Exception
&) { }
542 ///iterate over each xslot, that is on each data series (there is
543 ///only one data series in each data series group!); note that if the chart
544 ///type is a pie the loop iterates only over the first data series
545 ///(m_bUseRings||fSlotX<0.5)
546 for( double fSlotX
=0; aXSlotIter
!= aXSlotEnd
&& (m_bUseRings
||fSlotX
<0.5 ); ++aXSlotIter
, fSlotX
+=1.0 )
550 ::std::vector
< VDataSeries
* >* pSeriesList
= &(aXSlotIter
->m_aSeriesVector
);
551 if( pSeriesList
->size()<=0 )//there should be only one series in each x slot
553 VDataSeries
* pSeries
= (*pSeriesList
)[0];
557 bool bHasFillColorMapping
= pSeries
->hasPropertyMapping("FillColor");
559 /// The angle degree offset is set by the the same property of the
561 /// Counter-clockwise offset from the 3 o'clock position.
562 m_pPosHelper
->m_fAngleDegreeOffset
= pSeries
->getStartingAngle();
564 ///iterate through all points to get the sum of all entries of
565 ///the current data series
566 sal_Int32 nPointIndex
=0;
567 sal_Int32 nPointCount
=pSeries
->getTotalPointCount();
568 for( nPointIndex
= 0; nPointIndex
< nPointCount
; nPointIndex
++ )
570 double fY
= pSeries
->getYValue( nPointIndex
);
573 //@todo warn somehow that negative values are treated as positive
575 if( ::rtl::math::isNan(fY
) )
577 aParam
.mfLogicYSum
+= fabs(fY
);
580 if (aParam
.mfLogicYSum
== 0.0)
581 // Total sum of all Y values in this series is zero. Skip the whole series.
584 double fLogicYForNextPoint
= 0.0;
585 ///iterate through all points to create shapes
586 for( nPointIndex
= 0; nPointIndex
< nPointCount
; nPointIndex
++ )
588 double fLogicInnerRadius
, fLogicOuterRadius
;
590 ///compute the maximum relative distance offset of the current slice
591 ///from the pie center
592 ///it is worth noting that after the first invocation the maximum
593 ///offset value is cached, so it is evaluated only once per each
594 ///call to `createShapes`
595 double fOffset
= getMaxOffset();
597 ///compute the outer and the inner radius for the current ring slice
598 bool bIsVisible
= m_pPosHelper
->getInnerAndOuterRadius( fSlotX
+1.0, fLogicInnerRadius
, fLogicOuterRadius
, m_bUseRings
, fOffset
);
602 aParam
.mfDepth
= this->getTransformedDepth() * (n3DRelativeHeight
/ 100.0);
604 uno::Reference
< drawing::XShapes
> xSeriesGroupShape_Shapes
= getSeriesGroupShape(pSeries
, xSeriesTarget
);
605 ///collect data point information (logic coordinates, style ):
606 double fLogicYValue
= fabs(pSeries
->getYValue( nPointIndex
));
607 if( ::rtl::math::isNan(fLogicYValue
) )
609 if(fLogicYValue
==0.0)//@todo: continue also if the resolution to small
611 double fLogicYPos
= fLogicYForNextPoint
;
612 fLogicYForNextPoint
+= fLogicYValue
;
614 uno::Reference
< beans::XPropertySet
> xPointProperties
= pSeries
->getPropertiesOfPoint( nPointIndex
);
616 //iterate through all subsystems to create partial points
618 //logic values on angle axis:
619 double fLogicStartAngleValue
= fLogicYPos
/ aParam
.mfLogicYSum
;
620 double fLogicEndAngleValue
= (fLogicYPos
+fLogicYValue
) / aParam
.mfLogicYSum
;
622 ///note that the explode percentage is set to the `Offset`
623 ///property of the current data series entry only for slices
624 ///belonging to the outer ring
625 aParam
.mfExplodePercentage
= 0.0;
626 bool bDoExplode
= ( nExplodeableSlot
== static_cast< ::std::vector
< VDataSeriesGroup
>::size_type
>(fSlotX
) );
629 xPointProperties
->getPropertyValue( "Offset") >>= aParam
.mfExplodePercentage
;
631 catch( const uno::Exception
& e
)
633 ASSERT_EXCEPTION( e
);
636 ///see notes for `PolarPlottingPositionHelper` methods
637 ///transform to unit circle:
638 aParam
.mfUnitCircleWidthAngleDegree
= m_pPosHelper
->getWidthAngleDegree( fLogicStartAngleValue
, fLogicEndAngleValue
);
639 aParam
.mfUnitCircleStartAngleDegree
= m_pPosHelper
->transformToAngleDegree( fLogicStartAngleValue
);
640 aParam
.mfUnitCircleInnerRadius
= m_pPosHelper
->transformToRadius( fLogicInnerRadius
);
641 aParam
.mfUnitCircleOuterRadius
= m_pPosHelper
->transformToRadius( fLogicOuterRadius
);
644 boost::scoped_ptr
< tPropertyNameValueMap
> apOverwritePropertiesMap(NULL
);
645 if (!pSeries
->hasPointOwnColor(nPointIndex
) && m_xColorScheme
.is())
647 apOverwritePropertiesMap
.reset( new tPropertyNameValueMap() );
648 (*apOverwritePropertiesMap
)["FillColor"] = uno::makeAny(
649 m_xColorScheme
->getColorByIndex( nPointIndex
));
653 aParam
.mfLogicZ
= -1.0; // For 3D pie chart label position
654 uno::Reference
<drawing::XShape
> xPointShape
=
656 xSeriesGroupShape_Shapes
, xPointProperties
, apOverwritePropertiesMap
.get(), aParam
);
658 if(bHasFillColorMapping
)
660 double nPropVal
= pSeries
->getValueByProperty(nPointIndex
, "FillColor");
661 if(!rtl::math::isNan(nPropVal
))
663 uno::Reference
< beans::XPropertySet
> xProps( xPointShape
, uno::UNO_QUERY_THROW
);
664 xProps
->setPropertyValue("FillColor", uno::makeAny(static_cast<sal_Int32
>( nPropVal
)));
669 createTextLabelShape(xTextTarget
, *pSeries
, nPointIndex
, aParam
);
673 AbstractShapeFactory::setShapeName( xPointShape
674 , ObjectIdentifier::createPointCID( pSeries
->getPointCID_Stub(), nPointIndex
) );
678 ///enable dragging of outer segments
680 double fAngle
= aParam
.mfUnitCircleStartAngleDegree
+ aParam
.mfUnitCircleWidthAngleDegree
/2.0;
681 double fMaxDeltaRadius
= aParam
.mfUnitCircleOuterRadius
-aParam
.mfUnitCircleInnerRadius
;
682 drawing::Position3D aOrigin
= m_pPosHelper
->transformUnitCircleToScene( fAngle
, aParam
.mfUnitCircleOuterRadius
, aParam
.mfLogicZ
);
683 drawing::Position3D aNewOrigin
= m_pPosHelper
->transformUnitCircleToScene( fAngle
, aParam
.mfUnitCircleOuterRadius
+ fMaxDeltaRadius
, aParam
.mfLogicZ
);
685 sal_Int32
nOffsetPercent( static_cast<sal_Int32
>(aParam
.mfExplodePercentage
* 100.0) );
687 awt::Point
aMinimumPosition( PlottingPositionHelper::transformSceneToScreenPosition(
688 aOrigin
, m_xLogicTarget
, m_pShapeFactory
, m_nDimension
) );
689 awt::Point
aMaximumPosition( PlottingPositionHelper::transformSceneToScreenPosition(
690 aNewOrigin
, m_xLogicTarget
, m_pShapeFactory
, m_nDimension
) );
692 //enable draging of piesegments
693 OUString
aPointCIDStub( ObjectIdentifier::createSeriesSubObjectStub( OBJECTTYPE_DATA_POINT
694 , pSeries
->getSeriesParticle()
695 , ObjectIdentifier::getPieSegmentDragMethodServiceName()
696 , ObjectIdentifier::createPieSegmentDragParameterString(
697 nOffsetPercent
, aMinimumPosition
, aMaximumPosition
)
700 AbstractShapeFactory::setShapeName( xPointShape
701 , ObjectIdentifier::createPointCID( aPointCIDStub
, nPointIndex
) );
703 catch( const uno::Exception
& e
)
705 ASSERT_EXCEPTION( e
);
707 }//next series in x slot (next y slot)
715 ::basegfx::B2IRectangle
lcl_getRect( const uno::Reference
< drawing::XShape
>& xShape
)
717 ::basegfx::B2IRectangle aRect
;
719 aRect
= BaseGFXHelper::makeRectangle(xShape
->getPosition(),xShape
->getSize() );
723 bool lcl_isInsidePage( const awt::Point
& rPos
, const awt::Size
& rSize
, const awt::Size
& rPageSize
)
725 if( rPos
.X
< 0 || rPos
.Y
< 0 )
727 if( (rPos
.X
+ rSize
.Width
) > rPageSize
.Width
)
729 if( (rPos
.Y
+ rSize
.Height
) > rPageSize
.Height
)
735 double lcl_radToDeg(double fAngleRad
)
737 return (fAngleRad
/ M_PI
) * 180.0;
741 double lcl_degToRad(double fAngleDeg
)
743 return (fAngleDeg
/ 180) * M_PI
;
747 double lcl_getDegAngleInStandardRange(double fAngle
)
749 while( fAngle
< 0.0 )
751 while( fAngle
>= 360.0 )
756 }//end anonymous namespace
758 PieChart::PieLabelInfo::PieLabelInfo()
759 : xTextShape(0), xLabelGroupShape(0), aFirstPosition(), aOrigin(), fValue(0.0)
760 , bMovementAllowed(false), bMoved(false), xTextTarget(0), pPrevious(0),pNext(0)
764 /** In case this label and the passed label overlap the routine moves this
765 * label in order to fix the issue. After the label position has been
766 * rearranged it is checked that the moved label is still inside the page
767 * document, if the test is positive the routine returns true else returns
770 bool PieChart::PieLabelInfo::moveAwayFrom( const PieChart::PieLabelInfo
* pFix
, const awt::Size
& rPageSize
, bool bMoveHalfWay
, bool bMoveClockwise
, bool bAlternativeMoveDirection
)
772 //return true if the move was successful
773 if(!this->bMovementAllowed
)
776 const sal_Int32 nLabelDistanceX
= rPageSize
.Width
/50;
777 const sal_Int32 nLabelDistanceY
= rPageSize
.Height
/50;
779 ///compute the rectangle representing the intersection of the label bounding
780 ///boxes (`aOverlap`).
781 ::basegfx::B2IRectangle
aOverlap( lcl_getRect( this->xLabelGroupShape
) );
782 aOverlap
.intersect( lcl_getRect( pFix
->xLabelGroupShape
) );
783 if( !aOverlap
.isEmpty() )
785 (void)bAlternativeMoveDirection
;//todo
787 ///the label is shifted along the direction orthogonal to the vector
788 ///starting at the pie/donut center and ending at this label anchor
791 ///named `aTangentialDirection` the unit vector related to such a
792 ///direction, the magnitude of the shift along such a direction is
793 ///calculated in this way: if the horizontal component of
794 ///`aTangentialDirection` is greater than the vertical component,
795 ///the magnitude of the shift is equal to `aOverlap.Width` else to
796 ///`aOverlap.Height`;
797 basegfx::B2IVector aRadiusDirection
= this->aFirstPosition
- this->aOrigin
;
798 aRadiusDirection
.setLength(1.0);
799 basegfx::B2IVector
aTangentialDirection( -aRadiusDirection
.getY(), aRadiusDirection
.getX() );
800 bool bShiftHorizontal
= abs(aTangentialDirection
.getX()) > abs(aTangentialDirection
.getY());
801 sal_Int32 nShift
= bShiftHorizontal
? static_cast<sal_Int32
>(aOverlap
.getWidth()) : static_cast<sal_Int32
>(aOverlap
.getHeight());
802 ///the magnitude of the shift is also increased by 1/50-th of the width
803 ///or the height of the document page;
804 nShift
+= (bShiftHorizontal
? nLabelDistanceX
: nLabelDistanceY
);
805 ///in case the `bMoveHalfWay` parameter is true the magnitude of
806 ///the shift is halved.
809 ///in case the `bMoveClockwise` parameter is false the direction of
810 ///`aTangentialDirection` is reversed;
813 awt::Point
aOldPos( this->xLabelGroupShape
->getPosition() );
814 basegfx::B2IVector aNewPos
= basegfx::B2IVector( aOldPos
.X
, aOldPos
.Y
) + nShift
*aTangentialDirection
;
816 ///a final check is performed in order to be sure that the moved label
817 ///is still inside the page document;
818 awt::Point
aNewAWTPos( aNewPos
.getX(), aNewPos
.getY() );
819 if( !lcl_isInsidePage( aNewAWTPos
, this->xLabelGroupShape
->getSize(), rPageSize
) )
822 this->xLabelGroupShape
->setPosition( aNewAWTPos
);
827 ///note that no further test is performed in order to check that the
828 ///overlap is really fixed: this result is surely achieved if the shift
829 ///would occur in the horizontal or vertical direction (since, in such a
830 ///direction, the magnitude of the shift would be greater than the length
831 ///of the overlap), but in general this is not true;
832 ///adding a constant term equal to 1/50-th of the width or the height of
833 ///the document page increases the probability of success, anyway it is
834 ///worth noting that the method can return true even if the overlap issue
835 ///is not (completely) fixed;
838 void PieChart::resetLabelPositionsToPreviousState()
840 std::vector
< PieLabelInfo
>::iterator aIt
= m_aLabelInfoList
.begin();
841 std::vector
< PieLabelInfo
>::const_iterator aEnd
= m_aLabelInfoList
.end();
842 for( ;aIt
!=aEnd
; ++aIt
)
843 aIt
->xLabelGroupShape
->setPosition(aIt
->aPreviousPosition
);
846 bool PieChart::detectLabelOverlapsAndMove( const awt::Size
& rPageSize
)
848 ///the routine tries to individuate a chain of overlapping labels and
849 ///assigns the first and the last of them to `pFirstBorder` and
851 ///this result is achieved by performing two consecutive while loop.
853 ///find borders of a group of overlapping labels
855 ///a first while loop is started on the collection of `PieLabelInfo` objects;
856 ///the bounding box of each label is checked for overlap against the bounding
857 ///box of the previous and of the next label;
858 ///when an overlap is found `bOverlapFound` is set to true, however the
859 ///iteration is break only if the overlap occurs against only the next label
860 ///and not against the previous label: so we exit from the loop whenever an
861 ///overlap occurs except when the loop initial label overlaps with the
863 bool bOverlapFound
= false;
864 PieLabelInfo
* pStart
= &(*(m_aLabelInfoList
.rbegin()));
865 PieLabelInfo
* pFirstBorder
= 0;
866 PieLabelInfo
* pSecondBorder
= 0;
867 PieLabelInfo
* pCurrent
= pStart
;
870 ::basegfx::B2IRectangle
aPreviousOverlap( lcl_getRect( pCurrent
->xLabelGroupShape
) );
871 ::basegfx::B2IRectangle
aNextOverlap( aPreviousOverlap
);
872 aPreviousOverlap
.intersect( lcl_getRect( pCurrent
->pPrevious
->xLabelGroupShape
) );
873 aNextOverlap
.intersect( lcl_getRect( pCurrent
->pNext
->xLabelGroupShape
) );
875 bool bPreviousOverlap
= !aPreviousOverlap
.isEmpty();
876 bool bNextOverlap
= !aNextOverlap
.isEmpty();
877 if( bPreviousOverlap
|| bNextOverlap
)
878 bOverlapFound
= true;
879 if( !bPreviousOverlap
&& bNextOverlap
)
881 pFirstBorder
= pCurrent
;
884 pCurrent
= pCurrent
->pNext
;
886 while( pCurrent
!= pStart
);
891 ///in case we found a label (`pFirstBorder`) which overlaps with the next
892 ///label and not with the previous label a second while loop is started with
893 ///`pFirstBorder` as initial label; one more time the bounding box of each
894 ///label is checked for overlap against the bounding box of the previous and
895 ///of the next label, however this time we exit from the loop only if the
896 ///current label overlaps with the previous one but does not with the next
897 ///one (the opposite of what is required in the former loop);
898 ///in case such a label is found it is assigned to `pSecondBorder` and the
899 ///iteration is stopped; so in case there is a chain of overlapping labels
900 ///we end up having the first label of the chain pointed by `pFirstBorder`
901 ///and the last label of the chain pointed by `pSecondBorder`;
904 pCurrent
= pFirstBorder
;
907 ::basegfx::B2IRectangle
aPreviousOverlap( lcl_getRect( pCurrent
->xLabelGroupShape
) );
908 ::basegfx::B2IRectangle
aNextOverlap( aPreviousOverlap
);
909 aPreviousOverlap
.intersect( lcl_getRect( pCurrent
->pPrevious
->xLabelGroupShape
) );
910 aNextOverlap
.intersect( lcl_getRect( pCurrent
->pNext
->xLabelGroupShape
) );
912 if( !aPreviousOverlap
.isEmpty() && aNextOverlap
.isEmpty() )
914 pSecondBorder
= pCurrent
;
917 pCurrent
= pCurrent
->pNext
;
919 while( pCurrent
!= pFirstBorder
);
922 ///when two labels satisfying the required conditions are not found
923 ///(`pFirstBorder == 0 || pSecondBorder == 0`) but still an overlap occurs
924 ///(`bOverlapFound == true`) we are in the situation where each label
925 ///overlaps with both the previous and the next one; so `pFirstBorder` is
926 ///set to point to the last `PieLabelInfo` object in the collection and
927 ///`pSecondBorder` is set to point to the first one;
928 if( !pFirstBorder
|| !pSecondBorder
)
930 pFirstBorder
= &(*(m_aLabelInfoList
.rbegin()));
931 pSecondBorder
= &(*(m_aLabelInfoList
.begin()));
934 ///the total number of labels that made up the chain is calculated and used
935 ///for getting a pointer to the central label (`pCenter`);
936 PieLabelInfo
* pCenter
= pFirstBorder
;
937 sal_Int32 nOverlapGroupCount
= 1;
938 for( pCurrent
= pFirstBorder
;pCurrent
!= pSecondBorder
; pCurrent
= pCurrent
->pNext
)
939 nOverlapGroupCount
++;
940 sal_Int32 nCenterPos
= nOverlapGroupCount
/2;
941 bool bSingleCenter
= nOverlapGroupCount
%2 != 0;
946 pCurrent
= pFirstBorder
;
947 while( --nCenterPos
)
948 pCurrent
= pCurrent
->pNext
;
952 ///the current position of each label in the collection is saved in
953 ///`PieLabelInfo.aPreviousPosition`, so that it is possible to undo the label
954 ///move action if it is needed; the undo action is provided by the
955 ///`PieChart::resetLabelPositionsToPreviousState` method.
959 pCurrent
->aPreviousPosition
= pCurrent
->xLabelGroupShape
->getPosition();
960 pCurrent
= pCurrent
->pNext
;
962 while( pCurrent
!= pStart
);
964 ///the `PieChart::tryMoveLabels` method is invoked with
965 ///`rbAlternativeMoveDirection` boolean parameter set to false, such a method
966 ///tries to remove all overlaps that occur in the list of labels going from
967 ///`pFirstBorder` to `pSecondBorder`;
968 ///if the `PieChart::tryMoveLabels` returns true no further action is
969 ///performed, however it is worth noting that it does not mean that all
970 ///overlap issues have been surely fixed, but only that all moved labels are
971 ///at least completely inside the page document;
972 ///when `PieChart::tryMoveLabels` returns false, it means that the attempt
973 ///to fix one of the overlap issues caused that a label has been moved
974 ///(partially) outside the page document (anyway the `PieChart::tryMoveLabels`
975 ///method takes care to restore the position of all labels to their initial
976 ///position, and to set the `rbAlternativeMoveDirection` in/out parameter to
977 ///true); in such a case a second invocation of `PieChart::tryMoveLabels` is
978 ///performed (and this time the `rbAlternativeMoveDirection` boolean
979 ///parameter is true) and independently by what the `PieChart::tryMoveLabels`
980 ///method returns no further action is performed;
981 ///(see notes for `PieChart::tryMoveLabels`);
982 bool bAlternativeMoveDirection
= false;
983 if( !tryMoveLabels( pFirstBorder
, pSecondBorder
, pCenter
, bSingleCenter
, bAlternativeMoveDirection
, rPageSize
) )
984 tryMoveLabels( pFirstBorder
, pSecondBorder
, pCenter
, bSingleCenter
, bAlternativeMoveDirection
, rPageSize
);
986 ///in both cases (one or two invocations of `PieChart::tryMoveLabels`) the
987 ///`detectLabelOverlapsAndMove` method ends returning true.
992 /** Try to remove all overlaps that occur in the list of labels going from
993 * `pFirstBorder` to `pSecondBorder`
995 bool PieChart::tryMoveLabels( PieLabelInfo
* pFirstBorder
, PieLabelInfo
* pSecondBorder
996 , PieLabelInfo
* pCenter
997 , bool bSingleCenter
, bool& rbAlternativeMoveDirection
, const awt::Size
& rPageSize
)
1000 PieLabelInfo
* p1
= bSingleCenter
? pCenter
->pPrevious
: pCenter
;
1001 PieLabelInfo
* p2
= pCenter
->pNext
;
1002 //return true when successful
1004 bool bLabelOrderIsAntiClockWise
= m_pPosHelper
->isMathematicalOrientationAngle();
1006 ///two loops are performed simultaneously: the outer loop iterates on
1007 ///`PieLabelInfo` objects in the list starting from the central element
1008 ///(`pCenter`) and moving forward until the last element (`pSecondBorder`);
1009 ///the inner loop starts from the previous element of `pCenter` and moves
1010 ///forward until the current `PieLabelInfo` object of the outer loop is
1012 PieLabelInfo
* pCurrent
= 0;
1013 for( pCurrent
= p2
;pCurrent
->pPrevious
!= pSecondBorder
; pCurrent
= pCurrent
->pNext
)
1015 PieLabelInfo
* pFix
= 0;
1016 for( pFix
= p2
->pPrevious
;pFix
!= pCurrent
; pFix
= pFix
->pNext
)
1018 ///on the current `PieLabelInfo` object of the outer loop the
1019 ///`moveAwayFrom` method is invoked by passing the current
1020 ///`PieLabelInfo` object of the inner loop as argument.
1022 ///so each label going from the central one to the last one is
1023 ///checked for overlapping against all previous labels (that comes
1024 ///after the central label) and in case the overlap occurs the
1025 ///`moveAwayFrom` method tries to fix the issue;
1026 ///if `moveAwayFrom` returns true (pay attention: that does not
1027 ///mean that the overlap issue has been surely fixed but only that
1028 ///the moved label is at least completely inside the page document:
1029 ///see notes on `PieChart::PieLabelInfo::moveAwayFrom`), the inner
1030 ///loop starts a new iteration else the `rbAlternativeMoveDirection`
1031 ///boolean parameter is tested: if it is false the parameter is set
1032 ///to true, the position of all labels is restored to the initial
1033 ///one (through the `PieChart::resetLabelPositionsToPreviousState`
1034 ///method) and the method ends by returning false, else the inner
1035 ///loop starts a new iteration step;
1036 ///so when `rbAlternativeMoveDirection` is true the method goes on
1037 ///trying to fix left overlap issues even if the last `moveAwayFrom`
1038 ///invocation has moved a label in a position that it is not
1039 ///completely inside the page document
1041 if( !pCurrent
->moveAwayFrom( pFix
, rPageSize
, !bSingleCenter
&& pCurrent
== p2
, !bLabelOrderIsAntiClockWise
, rbAlternativeMoveDirection
) )
1043 if( !rbAlternativeMoveDirection
)
1045 rbAlternativeMoveDirection
= true;
1046 resetLabelPositionsToPreviousState();
1053 ///if the method does not return before ending the first pair of loops,
1054 ///a second pair of simultaneous loops is performed in the opposite
1055 ///direction (respect with the previous case): the outer loop iterates on
1056 ///`PieLabelInfo` objects in the list starting from the central element
1057 ///(`pCenter`) and moving backward until the first element (`pFirstBorder`);
1058 ///the inner loop starts from the next element of `pCenter` and moves
1059 ///backward until the current `PieLabelInfo` object of the outer loop is
1062 ///like in the previous case on the current `PieLabelInfo` object of
1063 ///the outer loop the `moveAwayFrom` method is invoked by passing
1064 ///the current `PieLabelInfo` object of the inner loop as argument
1066 ///so each label going from the central one to the first one is checked for
1067 ///overlapping on all subsequent labels (that come before the central label)
1068 ///and in case the overlap occurs the `moveAwayFrom` method tries to fix
1069 ///the issue. The subsequent actions performed after the invocation
1070 ///`moveAwayFrom` are the same detailed above for the first pair of loops
1072 for( pCurrent
= p1
;pCurrent
->pNext
!= pFirstBorder
; pCurrent
= pCurrent
->pPrevious
)
1074 PieLabelInfo
* pFix
= 0;
1075 for( pFix
= p2
->pNext
;pFix
!= pCurrent
; pFix
= pFix
->pPrevious
)
1077 if( !pCurrent
->moveAwayFrom( pFix
, rPageSize
, false, bLabelOrderIsAntiClockWise
, rbAlternativeMoveDirection
) )
1079 if( !rbAlternativeMoveDirection
)
1081 rbAlternativeMoveDirection
= true;
1082 resetLabelPositionsToPreviousState();
1091 void PieChart::rearrangeLabelToAvoidOverlapIfRequested( const awt::Size
& rPageSize
)
1093 ///this method is invoked by `ChartView::impl_createDiagramAndContent` for
1094 ///pie and donut charts after text label creation;
1095 ///it tries to rearrange labels only when the label placement type is
1099 ///check whether there are any labels that should be moved
1100 std::vector
< PieLabelInfo
>::iterator aIt1
= m_aLabelInfoList
.begin();
1101 std::vector
< PieLabelInfo
>::const_iterator aEnd
= m_aLabelInfoList
.end();
1102 bool bMoveableFound
= false;
1103 for( ;aIt1
!=aEnd
; ++aIt1
)
1105 if(aIt1
->bMovementAllowed
)
1107 bMoveableFound
= true;
1114 double fPageDiagonaleLength
= sqrt( double( rPageSize
.Width
*rPageSize
.Width
+ rPageSize
.Height
*rPageSize
.Height
) );
1115 if( ::rtl::math::approxEqual( fPageDiagonaleLength
, 0.0 ) )
1118 ///initialize next and previous member of `PieLabelInfo` objects
1119 aIt1
= m_aLabelInfoList
.begin();
1120 std::vector
< PieLabelInfo
>::iterator aIt2
= aIt1
;
1121 if( aIt1
==aEnd
)//no need to do anything when we only have one label
1123 aIt1
->pPrevious
= &(*(m_aLabelInfoList
.rbegin()));
1125 for( ;aIt2
!=aEnd
; ++aIt1
, ++aIt2
)
1127 PieLabelInfo
& rInfo1( *aIt1
);
1128 PieLabelInfo
& rInfo2( *aIt2
);
1129 rInfo1
.pNext
= &rInfo2
;
1130 rInfo2
.pPrevious
= &rInfo1
;
1132 aIt1
->pNext
= &(*(m_aLabelInfoList
.begin()));
1134 ///detect overlaps and move
1135 sal_Int32 nMaxIterations
= 50;
1136 while( detectLabelOverlapsAndMove( rPageSize
) && nMaxIterations
> 0 )
1139 ///create connection lines for the moved labels
1140 aEnd
= m_aLabelInfoList
.end();
1141 VLineProperties aVLineProperties
;
1142 for( aIt1
= m_aLabelInfoList
.begin(); aIt1
!=aEnd
; ++aIt1
)
1144 PieLabelInfo
& rInfo( *aIt1
);
1147 sal_Int32 nX1
= rInfo
.aFirstPosition
.getX();
1148 sal_Int32 nY1
= rInfo
.aFirstPosition
.getY();
1149 sal_Int32 nX2
= nX1
;
1150 sal_Int32 nY2
= nY1
;
1151 ::basegfx::B2IRectangle
aRect( lcl_getRect( rInfo
.xLabelGroupShape
) );
1152 if( nX1
< aRect
.getMinX() )
1153 nX2
= aRect
.getMinX();
1154 else if( nX1
> aRect
.getMaxX() )
1155 nX2
= aRect
.getMaxX();
1157 if( nY1
< aRect
.getMinY() )
1158 nY2
= aRect
.getMinY();
1159 else if( nY1
> aRect
.getMaxY() )
1160 nY2
= aRect
.getMaxY();
1162 //when the line is very short compared to the page size don't create one
1163 ::basegfx::B2DVector
aLength(nX1
-nX2
, nY1
-nY2
);
1164 if( (aLength
.getLength()/fPageDiagonaleLength
) < 0.01 )
1167 drawing::PointSequenceSequence
aPoints(1);
1168 aPoints
[0].realloc(2);
1169 aPoints
[0][0].X
= nX1
;
1170 aPoints
[0][0].Y
= nY1
;
1171 aPoints
[0][1].X
= nX2
;
1172 aPoints
[0][1].Y
= nY2
;
1174 uno::Reference
< beans::XPropertySet
> xProp( rInfo
.xTextShape
, uno::UNO_QUERY
);
1177 sal_Int32 nColor
= 0;
1178 xProp
->getPropertyValue("CharColor") >>= nColor
;
1179 if( nColor
!= -1 )//automatic font color does not work for lines -> fallback to black
1180 aVLineProperties
.Color
= uno::makeAny(nColor
);
1182 m_pShapeFactory
->createLine2D( rInfo
.xTextTarget
, aPoints
, &aVLineProperties
);
1188 /** Handle the placement of the label in the best fit case:
1189 * the routine try to place the label inside the related pie slice,
1190 * in case of success it returns true else returns false.
1194 * s: the bisector ray of the current pie slice
1195 * alpha: the angle between the horizontal axis and the bisector ray s
1196 * N: the vertex of the label b.b. which is nearest to C
1197 * F: the vertex of the label b.b. not adjacent to N; F lies on the pie border
1198 * P, Q: the intersection points between the label b.b. and the bisector ray s;
1199 * P is the one at minimum distance respect with C
1200 * e: the edge of the label b.b. where P lies (the nearest edge to C)
1201 * M: the vertex of e that is not N
1202 * G: the vertex of the label b.b. which is adjacent to N and that is not M
1203 * beta: the angle MPF
1204 * theta: the angle CPF
1211 * | G _________________________/____________________________ F
1225 * | | / . \ beta . |
1226 * | |__________/._\___|_______.____________________________|
1240 * __|/__|_____________________________________________________________
1245 * When alpha = 45k (k integer) s crosses the label b.b. at N exactly.
1246 * In such a case the nearest edge e is defined as the edge having N as the
1247 * start vertex and that is covered in the counterclockwise direction when
1248 * we move from N to the adjacent vertex.
1250 * The nearest vertex N is:
1251 * 1. the bottom left vertex when 0 < alpha < 90
1252 * 2. the bottom right vertex when 90 < alpha < 180
1253 * 3. the top right vertex when 180 < alpha < 270
1254 * 4. the top left vertex when 270 < alpha < 360.
1256 * The nearest edge e is:
1257 * 1. the left edge when −45 < alpha < 45
1258 * 2. the bottom edge when 45 < alpha <135
1259 * 3. the right edge when 135 < alpha < 225
1260 * 4. the top edge when 225 < alpha < 315.
1263 bool PieChart::performLabelBestFitInnerPlacement(ShapeParam
& rShapeParam
, PieLabelInfo
& rPieLabelInfo
)
1265 SAL_INFO( "chart2.pie.label.bestfit.inside",
1266 "** PieChart::performLabelBestFitInnerPlacement invoked **" );
1268 // get pie slice properties
1269 double fStartAngleDeg
= lcl_getDegAngleInStandardRange(rShapeParam
.mfUnitCircleStartAngleDegree
);
1270 double fWidthAngleDeg
= rShapeParam
.mfUnitCircleWidthAngleDegree
;
1271 double fHalfWidthAngleDeg
= fWidthAngleDeg
/ 2.0;
1272 double fBisectingRayAngleDeg
= lcl_getDegAngleInStandardRange(fStartAngleDeg
+ fHalfWidthAngleDeg
);
1274 // get the middle point of the arc representing the pie slice border
1275 double fLogicZ
= rShapeParam
.mfLogicZ
+ 1.0;
1276 awt::Point aMiddleArcPoint
= PlottingPositionHelper::transformSceneToScreenPosition(
1277 m_pPosHelper
->transformUnitCircleToScene(
1278 fBisectingRayAngleDeg
,
1279 rShapeParam
.mfUnitCircleOuterRadius
,
1281 m_xLogicTarget
, m_pShapeFactory
, m_nDimension
);
1283 // compute the pie radius
1284 basegfx::B2IVector aPieCenter
= rPieLabelInfo
.aOrigin
;
1285 basegfx::B2IVector
aRadiusVector(
1286 aMiddleArcPoint
.X
- aPieCenter
.getX(),
1287 aMiddleArcPoint
.Y
- aPieCenter
.getY() );
1288 double fSquaredPieRadius
= aRadiusVector
.scalar(aRadiusVector
);
1289 double fPieRadius
= sqrt( fSquaredPieRadius
);
1291 // the bb is moved as much as possible near to the border of the pie,
1292 // anyway a small offset from the border is present (0.025 * pie radius)
1293 const double fPieBorderOffset
= 0.025;
1294 fPieRadius
= fPieRadius
- fPieRadius
* fPieBorderOffset
;
1296 SAL_INFO( "chart2.pie.label.bestfit.inside",
1298 SAL_INFO( "chart2.pie.label.bestfit.inside",
1299 " start angle = " << fStartAngleDeg
);
1300 SAL_INFO( "chart2.pie.label.bestfit.inside",
1301 " angle width = " << fWidthAngleDeg
);
1302 SAL_INFO( "chart2.pie.label.bestfit.inside",
1303 " bisecting ray angle = " << fBisectingRayAngleDeg
);
1304 SAL_INFO( "chart2.pie.label.bestfit.inside",
1305 " pie radius = " << fPieRadius
);
1306 SAL_INFO( "chart2.pie.label.bestfit.inside",
1307 " pie center = " << rPieLabelInfo
.aOrigin
);
1308 SAL_INFO( "chart2.pie.label.bestfit.inside",
1309 " middle arc point = (" << aMiddleArcPoint
.X
<< ","
1310 << aMiddleArcPoint
.Y
<< ")" );
1311 SAL_INFO( "chart2.pie.label.bestfit.inside",
1312 " label bounding box:" );
1313 SAL_INFO( "chart2.pie.label.bestfit.inside",
1314 " old anchor point = " << rPieLabelInfo
.aFirstPosition
);
1317 if( ::rtl::math::approxEqual( fPieRadius
, 0.0 ) )
1320 // get label b.b. width and height
1321 ::basegfx::B2IRectangle
aBb( lcl_getRect( rPieLabelInfo
.xLabelGroupShape
) );
1322 double fLabelWidth
= aBb
.getWidth();
1323 double fLabelHeight
= aBb
.getHeight();
1325 // -45 <= fAlphaDeg < 315
1326 double fAlphaDeg
= lcl_getDegAngleInStandardRange(fBisectingRayAngleDeg
+ 45) - 45;
1327 double fAlphaRad
= lcl_degToRad(fAlphaDeg
);
1329 // compute nearest edge index
1334 int nSectorIndex
= floor( (fAlphaDeg
+ 45) / 45.0 );
1335 int nNearestEdgeIndex
= nSectorIndex
/ 2;
1337 // compute lengths of the nearest edge and of the orthogonal edges
1338 double fNearestEdgeLength
= fLabelWidth
;
1339 double fOrthogonalEdgeLength
= fLabelHeight
;
1341 int nOrthogonalAxisIndex
= 1;
1342 if( nNearestEdgeIndex
% 2 == 0 ) // nearest edge is vertical
1344 fNearestEdgeLength
= fLabelHeight
;
1345 fOrthogonalEdgeLength
= fLabelWidth
;
1347 nOrthogonalAxisIndex
= 0;
1350 // compute the distance between N and P
1351 // such a distance is piece wise linear respect with alpha:
1352 // given 45k <= alpha < 45(k+1) we have
1353 // when k is even: d(N,P) = (length(e) / 2) * (1 - (alpha - 45k)/45)
1354 // when k is odd: d(N,P) = (length(e) / 2) * (1 - (45(k+1) - alpha)/45)
1355 int nIndex
= nSectorIndex
-1; // nIndex = -1...6
1356 double fIndexMod2
= (nIndex
+ 8) % 2; // fIndexMod2 must be non negative
1357 double fSgn
= 2.0 * (fIndexMod2
- 0.5); // 0 -> -1, 1 -> 1
1358 double fDistanceNP
= (fNearestEdgeLength
/ 2.0) * (1 + fSgn
* ((fAlphaDeg
- 45 * (nIndex
+ fIndexMod2
)) / 45.0));
1359 double fDistancePM
= fNearestEdgeLength
- fDistanceNP
;
1361 // compute the length of the diagonal vector d,
1362 // that is the distance between P and F
1363 double fSquaredDistancePF
= fDistancePM
* fDistancePM
+ fOrthogonalEdgeLength
* fOrthogonalEdgeLength
;
1364 double fDistancePF
= sqrt( fSquaredDistancePF
);
1366 SAL_INFO( "chart2.pie.label.bestfit.inside",
1367 " width = " << fLabelWidth
);
1368 SAL_INFO( "chart2.pie.label.bestfit.inside",
1369 " height = " << fLabelHeight
);
1370 SAL_INFO( "chart2.pie.label.bestfit.inside",
1371 " nearest edge index = " << nNearestEdgeIndex
);
1372 SAL_INFO( "chart2.pie.label.bestfit.inside",
1373 " alpha = " << fAlphaDeg
);
1374 SAL_INFO( "chart2.pie.label.bestfit.inside",
1375 " distance(N,P) = " << fDistanceNP
);
1376 SAL_INFO( "chart2.pie.label.bestfit.inside",
1377 " nIndex = " << nIndex
);
1378 SAL_INFO( "chart2.pie.label.bestfit.inside",
1379 " fIndexMod2 = " << fIndexMod2
);
1380 SAL_INFO( "chart2.pie.label.bestfit.inside",
1381 " fSgn = " << fSgn
);
1382 SAL_INFO( "chart2.pie.label.bestfit.inside",
1383 " distance(P,F) = " << fDistancePF
);
1386 // we check that the condition length(d) <= pie radius holds
1387 if (fDistancePF
> fPieRadius
)
1392 // compute beta: the angle of the diagonal vector d,
1393 // that is, the angle in P respect with the triangle PMF;
1394 // since both arguments are non negative the returned value is in [0, PI/2]
1395 double fBetaRad
= atan2( fOrthogonalEdgeLength
, fDistancePM
);
1397 // compute the theta angle, that is the angle in P
1398 // respect with the triangle CFP;
1399 // when the second intersection edge is opposite to the nearest edge,
1400 // theta depends on alpha and beta according to the following relation:
1401 // theta = f(alpha, beta) = s * alpha + 90 * (1 - s * i) + beta
1402 // where i is the nearest edge index and s is the sign of (alpha' - 45),
1403 // with alpha' = (alpha + 45) mod 90;
1404 // when the second intersection edge is adjacent to the nearest edge,
1405 // we have theta = 360 - f(alpha, beta);
1406 // note that in the former case 0 <= f(alpha, beta) <= 180,
1407 // whilst in the latter case 180 <= f(alpha, beta) <= 360;
1408 double fAlphaMod90
= fmod( fAlphaDeg
+ 45, 90.0 ) - 45;
1409 double fSign
= ::rtl::math::approxEqual( fAlphaMod90
, 0.0 )
1411 : ( fAlphaMod90
< 0 ) ? -1.0 : 1.0;
1412 double fThetaRad
= fSign
* fAlphaRad
+ M_PI_2
* (1 - fSign
* nNearestEdgeIndex
) + fBetaRad
;
1413 if( fThetaRad
> M_PI
)
1415 fThetaRad
= 2 * M_PI
- fThetaRad
;
1418 // compute the length of the positional vector,
1419 // that is the distance between C and P
1421 // when the bisector ray intersects the b.b. in F we have theta mod 180 == 0
1422 if( ::rtl::math::approxEqual( fmod(fThetaRad
, M_PI
), 0.0 ))
1424 fDistanceCP
= fPieRadius
- fDistancePF
;
1426 else // general case
1428 // we can compute d(C,P) by applying some trigonometric formula to
1429 // the triangle CFP : we know length(d) and length(r) = r and we have
1430 // computed the angle in P (theta); so named delta the angle in C and
1431 // gamma the angle in F, by the relation:
1434 // --------- = --------- = ---------
1435 // sin theta sin delta sin gamma
1437 // we get the wanted distance
1438 double fSinTheta
= sin( fThetaRad
);
1439 double fSinDelta
= fDistancePF
* fSinTheta
/ fPieRadius
;
1440 double fDeltaRad
= asin( fSinDelta
);
1441 double fGammaRad
= M_PI
- (fThetaRad
+ fDeltaRad
);
1442 double fSinGamma
= sin( fGammaRad
);
1443 fDistanceCP
= fPieRadius
* fSinGamma
/ fSinTheta
;
1446 // define the positional vector
1447 basegfx::B2DVector
aPositionalVector( cos(fAlphaRad
), sin(fAlphaRad
) );
1448 aPositionalVector
.setLength(fDistanceCP
);
1450 // we define a direction vector in order to know
1451 // in which quadrant we are working
1452 basegfx::B2DVector
aDirection(1.0, 1.0);
1453 if( 90 <= fBisectingRayAngleDeg
&& fBisectingRayAngleDeg
< 270 )
1455 aDirection
.setX(-1.0);
1457 if( fBisectingRayAngleDeg
>= 180 )
1459 aDirection
.setY(-1.0);
1462 // compute vertices N, M and G respect with pie center C
1463 basegfx::B2DVector
aNearestVertex(aPositionalVector
);
1464 aNearestVertex
[nAxisIndex
] += -aDirection
[nAxisIndex
] * fDistanceNP
;
1465 basegfx::B2DVector
aVertexM(aNearestVertex
);
1466 aVertexM
[nAxisIndex
] += aDirection
[nAxisIndex
] * fNearestEdgeLength
;
1467 basegfx::B2DVector
aVertexG(aNearestVertex
);
1468 aVertexG
[nOrthogonalAxisIndex
] += aDirection
[nOrthogonalAxisIndex
] * fOrthogonalEdgeLength
;
1470 SAL_INFO( "chart2.pie.label.bestfit.inside",
1471 " beta = " << lcl_radToDeg(fBetaRad
) );
1472 SAL_INFO( "chart2.pie.label.bestfit.inside",
1473 " theta = " << lcl_radToDeg(fThetaRad
) );
1474 SAL_INFO( "chart2.pie.label.bestfit.inside",
1475 " fAlphaMod90 = " << fAlphaMod90
);
1476 SAL_INFO( "chart2.pie.label.bestfit.inside",
1477 " fSign = " << fSign
);
1478 SAL_INFO( "chart2.pie.label.bestfit.inside",
1479 " distance(C,P) = " << fDistanceCP
);
1480 SAL_INFO( "chart2.pie.label.bestfit.inside",
1481 " direction vector = " << aDirection
);
1482 SAL_INFO( "chart2.pie.label.bestfit.inside",
1483 " N = " << aNearestVertex
);
1484 SAL_INFO( "chart2.pie.label.bestfit.inside",
1485 " M = " << aVertexM
);
1486 SAL_INFO( "chart2.pie.label.bestfit.inside",
1487 " G = " << aVertexG
);
1489 // in order to be able to place the label inside the pie slice we need
1490 // to check that each angle between s and the ray starting from C and
1491 // passing through a b.b. vertex is less than half width of the pie slice;
1492 // when the nearest edge e crosses a Cartesian axis it is sufficient
1493 // to test only the vertices belonging to e, else we need to test
1494 // the 2 vertices that aren’t either N or F . Note that if a b.b. edge
1495 // crosses a Cartesian axis then it is the nearest edge to C
1497 // check the angle between CP and CM
1498 double fAngleRad
= aPositionalVector
.angle(aVertexM
);
1499 double fAngleDeg
= lcl_getDegAngleInStandardRange( lcl_radToDeg(fAngleRad
) );
1500 if( fAngleDeg
> 180 ) // in case the wrong angle has been computed
1501 fAngleDeg
= 360 - fAngleDeg
;
1502 SAL_INFO( "chart2.pie.label.bestfit.inside",
1503 " angle between CP and CM: " << fAngleDeg
);
1504 if( fAngleDeg
> fHalfWidthAngleDeg
)
1509 if( ( aNearestVertex
[nAxisIndex
] >= 0 && aVertexM
[nAxisIndex
] <= 0 )
1510 || ( aNearestVertex
[nAxisIndex
] <= 0 && aVertexM
[nAxisIndex
] >= 0 ) )
1512 // check the angle between CP and CN
1513 fAngleRad
= aPositionalVector
.angle(aNearestVertex
);
1514 fAngleDeg
= lcl_getDegAngleInStandardRange( lcl_radToDeg(fAngleRad
) );
1515 if( fAngleDeg
> 180 ) // in case the wrong angle has been computed
1516 fAngleDeg
= 360 - fAngleDeg
;
1517 SAL_INFO( "chart2.pie.label.bestfit.inside",
1518 " angle between CP and CN: " << fAngleDeg
);
1519 if( fAngleDeg
> fHalfWidthAngleDeg
)
1526 // check the angle between CP and CG
1527 fAngleRad
= aPositionalVector
.angle(aVertexG
);
1528 fAngleDeg
= lcl_getDegAngleInStandardRange( lcl_radToDeg(fAngleRad
) );
1529 if( fAngleDeg
> 180 ) // in case the wrong angle has been computed
1530 fAngleDeg
= 360 - fAngleDeg
;
1531 SAL_INFO( "chart2.pie.label.bestfit.inside",
1532 " angle between CP and CG: " << fAngleDeg
);
1533 if( fAngleDeg
> fHalfWidthAngleDeg
)
1539 // compute the b.b. center respect with the pie center
1540 basegfx::B2DVector
aBBCenter(aNearestVertex
);
1541 aBBCenter
[nAxisIndex
] += aDirection
[nAxisIndex
] * fNearestEdgeLength
/ 2;
1542 aBBCenter
[nOrthogonalAxisIndex
] += aDirection
[nOrthogonalAxisIndex
] * fOrthogonalEdgeLength
/ 2;
1544 // compute the b.b. anchor point
1545 basegfx::B2IVector aNewAnchorPoint
= aPieCenter
;
1546 aNewAnchorPoint
[0] += floor(aBBCenter
[0]);
1547 aNewAnchorPoint
[1] -= floor(aBBCenter
[1]); // the Y axis on the screen points downward
1549 // compute the translation vector for moving the label from the current
1550 // screen position to the new one
1551 basegfx::B2IVector aTranslationVector
= aNewAnchorPoint
- rPieLabelInfo
.aFirstPosition
;
1553 // compute the new screen position and move the label
1554 awt::Point
aNewPos( rPieLabelInfo
.xLabelGroupShape
->getPosition() );
1555 aNewPos
.X
+= aTranslationVector
.getX();
1556 aNewPos
.Y
+= aTranslationVector
.getY();
1557 rPieLabelInfo
.xLabelGroupShape
->setPosition(aNewPos
);
1559 SAL_INFO( "chart2.pie.label.bestfit.inside",
1560 " center = " << aBBCenter
);
1561 SAL_INFO( "chart2.pie.label.bestfit.inside",
1562 " new anchor point = " << aNewAnchorPoint
);
1563 SAL_INFO( "chart2.pie.label.bestfit.inside",
1564 " translation vector = " << aTranslationVector
);
1565 SAL_INFO( "chart2.pie.label.bestfit.inside",
1566 " new position = (" << aNewPos
.X
<< "," << aNewPos
.Y
<< ")" );
1571 /** Handle the outer placement of the labels in the best fit case.
1574 bool PieChart::performLabelBestFitOuterPlacement(ShapeParam
& /*rShapeParam*/, PieLabelInfo
& /*rPieLabelInfo*/)
1576 SAL_WARN( "chart2.pie.label.bestfit", "to be implemented" );
1580 /** Handle the placement of the label in the best fit case.
1581 * First off the routine try to place the label inside the related pie slice,
1582 * if this is not possible the label is placed outside.
1584 void PieChart::performLabelBestFit(ShapeParam
& rShapeParam
, PieLabelInfo
& rPieLabelInfo
)
1589 if( !performLabelBestFitInnerPlacement(rShapeParam
, rPieLabelInfo
) )
1591 performLabelBestFitOuterPlacement(rShapeParam
, rPieLabelInfo
);
1597 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */