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 <BaseGFXHelper.hxx>
21 #include <VLineProperties.hxx>
22 #include "PieChart.hxx"
23 #include <PlottingPositionHelper.hxx>
24 #include <ShapeFactory.hxx>
25 #include <PolarLabelPositionHelper.hxx>
26 #include <CommonConverters.hxx>
27 #include <ObjectIdentifier.hxx>
29 #include <com/sun/star/chart/DataLabelPlacement.hpp>
30 #include <com/sun/star/chart2/XChartType.hpp>
31 #include <com/sun/star/chart2/XColorScheme.hpp>
33 #include <com/sun/star/container/XChild.hpp>
34 #include <com/sun/star/drawing/XShape.hpp>
35 #include <com/sun/star/drawing/XShapes.hpp>
36 #include <com/sun/star/beans/XPropertySet.hpp>
37 #include <rtl/math.hxx>
38 #include <sal/log.hxx>
39 #include <osl/diagnose.h>
40 #include <tools/diagnose_ex.h>
41 #include <tools/helpers.hxx>
45 using namespace ::com::sun::star
;
46 using namespace ::com::sun::star::chart2
;
50 struct PieChart::ShapeParam
52 /** the start angle of the slice
54 double mfUnitCircleStartAngleDegree
;
56 /** the angle width of the slice
58 double mfUnitCircleWidthAngleDegree
;
60 /** the normalized outer radius of the ring the slice belongs to.
62 double mfUnitCircleOuterRadius
;
64 /** the normalized inner radius of the ring the slice belongs to
66 double mfUnitCircleInnerRadius
;
68 /** relative distance offset of a slice from the pie center;
69 * this parameter is used for instance when the user performs manual
70 * dragging of a slice (the drag operation is possible only for slices that
71 * belong to the outer ring and only along the ray bisecting the slice);
72 * the value for the given entry in the data series is obtained by the
73 * `Offset` property attached to each entry; note that the value
74 * provided by the `Offset` property is used both as a logical value in
75 * `PiePositionHelper::getInnerAndOuterRadius` and as a percentage value in
76 * the `PieChart::createDataPoint` and `PieChart::createTextLabelShape`
77 * methods; since the logical height of a ring is always 1, this duality
78 * does not cause any incorrect behavior;
80 double mfExplodePercentage
;
82 /** sum of all Y values in a single series
86 /** for 3D pie chart: label z coordinate
90 /** for 3D pie chart: height
95 mfUnitCircleStartAngleDegree(0.0),
96 mfUnitCircleWidthAngleDegree(0.0),
97 mfUnitCircleOuterRadius(0.0),
98 mfUnitCircleInnerRadius(0.0),
99 mfExplodePercentage(0.0),
107 ::basegfx::B2IRectangle
lcl_getRect(const uno::Reference
<drawing::XShape
>& xShape
)
109 ::basegfx::B2IRectangle aRect
;
111 aRect
= BaseGFXHelper::makeRectangle(xShape
->getPosition(), xShape
->getSize());
115 bool lcl_isInsidePage(const awt::Point
& rPos
, const awt::Size
& rSize
, const awt::Size
& rPageSize
)
117 if (rPos
.X
< 0 || rPos
.Y
< 0)
119 if ((rPos
.X
+ rSize
.Width
) > rPageSize
.Width
)
121 if ((rPos
.Y
+ rSize
.Height
) > rPageSize
.Height
)
126 } //end anonymous namespace
128 class PiePositionHelper
: public PolarPlottingPositionHelper
131 PiePositionHelper( double fAngleDegreeOffset
);
133 bool getInnerAndOuterRadius( double fCategoryX
, double& fLogicInnerRadius
, double& fLogicOuterRadius
, bool bUseRings
, double fMaxOffset
) const;
136 //Distance between different category rings, seen relative to width of a ring:
137 double m_fRingDistance
; //>=0 m_fRingDistance=1 --> distance == width
140 PiePositionHelper::PiePositionHelper( double fAngleDegreeOffset
)
141 : m_fRingDistance(0.0)
143 m_fRadiusOffset
= 0.0;
144 m_fAngleDegreeOffset
= fAngleDegreeOffset
;
147 /** Compute the outer and the inner radius for the current ring (not for the
148 * whole donut!), in general it is:
149 * inner_radius = (ring_index + 1) - 0.5 + max_offset,
150 * outer_radius = (ring_index + 1) + 0.5 + max_offset.
151 * When orientation for the radius axis is reversed these values are swapped.
152 * (Indeed the orientation for the radius axis is always reversed!
153 * See `PieChartTypeTemplate::adaptScales`.)
154 * The maximum relative offset (see notes for `PieChart::getMaxOffset`) is
155 * added to both the inner and the outer radius.
156 * It returns true if the ring is visible (that is not out of the radius
159 bool PiePositionHelper::getInnerAndOuterRadius( double fCategoryX
160 , double& fLogicInnerRadius
, double& fLogicOuterRadius
161 , bool bUseRings
, double fMaxOffset
) const
166 double fLogicInner
= fCategoryX
-0.5+m_fRingDistance
/2.0;
167 double fLogicOuter
= fCategoryX
+0.5-m_fRingDistance
/2.0;
169 if( !isMathematicalOrientationRadius() )
171 //in this case the given getMaximumX() was not correct instead the minimum should have been smaller by fMaxOffset
172 //but during getMaximumX and getMimumX we do not know the axis orientation
173 fLogicInner
+= fMaxOffset
;
174 fLogicOuter
+= fMaxOffset
;
177 if( fLogicInner
>= getLogicMaxX() )
179 if( fLogicOuter
<= getLogicMinX() )
182 if( fLogicInner
< getLogicMinX() )
183 fLogicInner
= getLogicMinX();
184 if( fLogicOuter
> getLogicMaxX() )
185 fLogicOuter
= getLogicMaxX();
187 fLogicInnerRadius
= fLogicInner
;
188 fLogicOuterRadius
= fLogicOuter
;
189 if( !isMathematicalOrientationRadius() )
190 std::swap(fLogicInnerRadius
,fLogicOuterRadius
);
194 PieChart::PieChart( const uno::Reference
<XChartType
>& xChartTypeModel
195 , sal_Int32 nDimensionCount
196 , bool bExcludingPositioning
)
197 : VSeriesPlotter( xChartTypeModel
, nDimensionCount
)
198 , m_pPosHelper( new PiePositionHelper( (m_nDimension
==3) ? 0.0 : 90.0 ) )
200 , m_bSizeExcludesLabelsAndExplodedSegments(bExcludingPositioning
)
202 ::rtl::math::setNan(&m_fMaxOffset
);
204 PlotterBase::m_pPosHelper
= m_pPosHelper
.get();
205 VSeriesPlotter::m_pMainPosHelper
= m_pPosHelper
.get();
206 m_pPosHelper
->m_fRadiusOffset
= 0.0;
207 m_pPosHelper
->m_fRingDistance
= 0.0;
209 uno::Reference
< beans::XPropertySet
> xChartTypeProps( xChartTypeModel
, uno::UNO_QUERY
);
210 if( !xChartTypeProps
.is() )
215 xChartTypeProps
->getPropertyValue( "UseRings") >>= m_bUseRings
;
218 m_pPosHelper
->m_fRadiusOffset
= 1.0;
219 if( nDimensionCount
==3 )
220 m_pPosHelper
->m_fRingDistance
= 0.1;
223 catch( const uno::Exception
& )
225 TOOLS_WARN_EXCEPTION("chart2", "" );
229 PieChart::~PieChart()
233 void PieChart::setScales( const std::vector
< ExplicitScaleData
>& rScales
, bool /* bSwapXAndYAxis */ )
235 OSL_ENSURE(m_nDimension
<=static_cast<sal_Int32
>(rScales
.size()),"Dimension of Plotter does not fit two dimension of given scale sequence");
236 m_pPosHelper
->setScales( rScales
, true );
239 drawing::Direction3D
PieChart::getPreferredDiagramAspectRatio() const
241 if( m_nDimension
== 3 )
242 return drawing::Direction3D(1,1,0.10);
243 return drawing::Direction3D(1,1,1);
246 bool PieChart::shouldSnapRectToUsedArea()
251 uno::Reference
< drawing::XShape
> PieChart::createDataPoint(
252 const uno::Reference
<drawing::XShapes
>& xTarget
,
253 const uno::Reference
<beans::XPropertySet
>& xObjectProperties
,
254 tPropertyNameValueMap
const * pOverwritePropertiesMap
,
255 const ShapeParam
& rParam
)
257 //transform position:
258 drawing::Direction3D aOffset
;
259 if (rParam
.mfExplodePercentage
!= 0.0)
261 double fAngle
= rParam
.mfUnitCircleStartAngleDegree
+ rParam
.mfUnitCircleWidthAngleDegree
/2.0;
262 double fRadius
= (rParam
.mfUnitCircleOuterRadius
-rParam
.mfUnitCircleInnerRadius
)*rParam
.mfExplodePercentage
;
263 drawing::Position3D aOrigin
= m_pPosHelper
->transformUnitCircleToScene(0, 0, rParam
.mfLogicZ
);
264 drawing::Position3D aNewOrigin
= m_pPosHelper
->transformUnitCircleToScene(fAngle
, fRadius
, rParam
.mfLogicZ
);
265 aOffset
= aNewOrigin
- aOrigin
;
269 uno::Reference
< drawing::XShape
> xShape
;
272 xShape
= m_pShapeFactory
->createPieSegment( xTarget
273 , rParam
.mfUnitCircleStartAngleDegree
, rParam
.mfUnitCircleWidthAngleDegree
274 , rParam
.mfUnitCircleInnerRadius
, rParam
.mfUnitCircleOuterRadius
275 , aOffset
, B3DHomMatrixToHomogenMatrix( m_pPosHelper
->getUnitCartesianToScene() )
280 xShape
= m_pShapeFactory
->createPieSegment2D( xTarget
281 , rParam
.mfUnitCircleStartAngleDegree
, rParam
.mfUnitCircleWidthAngleDegree
282 , rParam
.mfUnitCircleInnerRadius
, rParam
.mfUnitCircleOuterRadius
283 , aOffset
, B3DHomMatrixToHomogenMatrix( m_pPosHelper
->getUnitCartesianToScene() ) );
285 setMappedProperties( xShape
, xObjectProperties
, PropertyMapper::getPropertyNameMapForFilledSeriesProperties(), pOverwritePropertiesMap
);
289 void PieChart::createTextLabelShape(
290 const uno::Reference
<drawing::XShapes
>& xTextTarget
,
291 VDataSeries
& rSeries
, sal_Int32 nPointIndex
, ShapeParam
& rParam
)
293 if (!rSeries
.getDataPointLabelIfLabel(nPointIndex
))
294 // There is no text label for this data point. Nothing to do.
297 ///by using the `mfExplodePercentage` parameter a normalized offset is added
298 ///to both normalized radii. (See notes for
299 ///`PolarPlottingPositionHelper::transformToRadius`, especially example 3,
300 ///and related comments).
301 if (rParam
.mfExplodePercentage
!= 0.0)
303 double fExplodeOffset
= (rParam
.mfUnitCircleOuterRadius
-rParam
.mfUnitCircleInnerRadius
)*rParam
.mfExplodePercentage
;
304 rParam
.mfUnitCircleInnerRadius
+= fExplodeOffset
;
305 rParam
.mfUnitCircleOuterRadius
+= fExplodeOffset
;
308 ///get the required label placement type. Available placements are
309 ///`AVOID_OVERLAP`, `CENTER`, `OUTSIDE` and `INSIDE`;
310 sal_Int32 nLabelPlacement
= rSeries
.getLabelPlacement(
311 nPointIndex
, m_xChartTypeModel
, m_pPosHelper
->isSwapXAndY());
313 ///when the placement is of `AVOID_OVERLAP` type a later rearrangement of
314 ///the label position is allowed; the `createTextLabelShape` treats the
315 ///`AVOID_OVERLAP` as if it was of `CENTER` type;
317 double nVal
= rSeries
.getYValue(nPointIndex
);
318 //AVOID_OVERLAP is in fact "Best fit" in the UI.
319 bool bMovementAllowed
= nLabelPlacement
== css::chart::DataLabelPlacement::AVOID_OVERLAP
320 || nLabelPlacement
== css::chart::DataLabelPlacement::CUSTOM
;
321 if( bMovementAllowed
)
322 nLabelPlacement
= css::chart::DataLabelPlacement::CENTER
;
324 ///for `OUTSIDE` (`INSIDE`) label placements an offset of 150 (-150), in the
325 ///radius direction, is added to the final screen position of the label
326 ///anchor point. This is required in order to ensure that the label is
327 ///completely outside (inside) the related slice. Indeed this value should
328 ///depend on the font height;
329 ///pay attention: 150 is not a big offset, in fact the screen position
330 ///coordinates for label anchor points are in the 10000-20000 range, hence
331 ///these are coordinates of a virtual screen and 150 is a small value;
332 LabelAlignment
eAlignment(LABEL_ALIGN_CENTER
);
333 sal_Int32 nScreenValueOffsetInRadiusDirection
= 0 ;
334 if( nLabelPlacement
== css::chart::DataLabelPlacement::OUTSIDE
)
335 nScreenValueOffsetInRadiusDirection
= (m_nDimension
!=3) ? 150 : 0;//todo maybe calculate this font height dependent
336 else if( nLabelPlacement
== css::chart::DataLabelPlacement::INSIDE
)
337 nScreenValueOffsetInRadiusDirection
= (m_nDimension
!=3) ? -150 : 0;//todo maybe calculate this font height dependent
339 ///the scene position of the label anchor point is calculated (see notes for
340 ///`PolarLabelPositionHelper::getLabelScreenPositionAndAlignmentForUnitCircleValues`),
341 ///and immediately transformed into the screen position.
342 PolarLabelPositionHelper
aPolarPosHelper(m_pPosHelper
.get(),m_nDimension
,m_xLogicTarget
,m_pShapeFactory
);
343 awt::Point
aScreenPosition2D(
344 aPolarPosHelper
.getLabelScreenPositionAndAlignmentForUnitCircleValues(eAlignment
, nLabelPlacement
345 , rParam
.mfUnitCircleStartAngleDegree
, rParam
.mfUnitCircleWidthAngleDegree
346 , rParam
.mfUnitCircleInnerRadius
, rParam
.mfUnitCircleOuterRadius
, rParam
.mfLogicZ
+0.5, 0 ));
348 ///the screen position of the pie/donut center is calculated.
349 PieLabelInfo aPieLabelInfo
;
350 aPieLabelInfo
.aFirstPosition
= basegfx::B2IVector( aScreenPosition2D
.X
, aScreenPosition2D
.Y
);
351 awt::Point
aOrigin( aPolarPosHelper
.transformSceneToScreenPosition( m_pPosHelper
->transformUnitCircleToScene( 0.0, 0.0, rParam
.mfLogicZ
+1.0 ) ) );
352 aPieLabelInfo
.aOrigin
= basegfx::B2IVector( aOrigin
.X
, aOrigin
.Y
);
354 ///add a scaling independent Offset if requested
355 if( nScreenValueOffsetInRadiusDirection
!= 0)
357 basegfx::B2IVector
aDirection( aScreenPosition2D
.X
- aOrigin
.X
, aScreenPosition2D
.Y
- aOrigin
.Y
);
358 aDirection
.setLength(nScreenValueOffsetInRadiusDirection
);
359 aScreenPosition2D
.X
+= aDirection
.getX();
360 aScreenPosition2D
.Y
+= aDirection
.getY();
363 // compute outer pie radius
364 awt::Point aOuterCirclePoint
= PlottingPositionHelper::transformSceneToScreenPosition(
365 m_pPosHelper
->transformUnitCircleToScene(
367 rParam
.mfUnitCircleOuterRadius
,
369 m_xLogicTarget
, m_pShapeFactory
, m_nDimension
);
370 basegfx::B2IVector
aRadiusVector(
371 aOuterCirclePoint
.X
- aPieLabelInfo
.aOrigin
.getX(),
372 aOuterCirclePoint
.Y
- aPieLabelInfo
.aOrigin
.getY() );
373 double fSquaredPieRadius
= aRadiusVector
.scalar(aRadiusVector
);
374 double fPieRadius
= sqrt( fSquaredPieRadius
);
376 = rParam
.mfUnitCircleStartAngleDegree
+ rParam
.mfUnitCircleWidthAngleDegree
/ 2.0;
377 while (fAngleDegree
> 360.0)
378 fAngleDegree
-= 360.0;
379 while (fAngleDegree
< 0.0)
380 fAngleDegree
+= 360.0;
382 awt::Point aOuterPosition
= PlottingPositionHelper::transformSceneToScreenPosition(
383 m_pPosHelper
->transformUnitCircleToScene(fAngleDegree
, rParam
.mfUnitCircleOuterRadius
, 0),
384 m_xLogicTarget
, m_pShapeFactory
, m_nDimension
);
385 aPieLabelInfo
.aOuterPosition
= basegfx::B2IVector(aOuterPosition
.X
, aOuterPosition
.Y
);
387 // set the maximum text width to be used when text wrapping is enabled
388 double fTextMaximumFrameWidth
= 0.8 * fPieRadius
;
389 if( nLabelPlacement
== css::chart::DataLabelPlacement::OUTSIDE
&& m_aAvailableOuterRect
.getWidth() )
391 if (fAngleDegree
< 67.5 || fAngleDegree
>= 292.5)
392 fTextMaximumFrameWidth
= m_aAvailableOuterRect
.getMaxX() - aPieLabelInfo
.aFirstPosition
.getX();
393 else if (fAngleDegree
< 112.5 || fAngleDegree
>= 247.5)
394 fTextMaximumFrameWidth
= m_aAvailableOuterRect
.getWidth();
396 fTextMaximumFrameWidth
= aPieLabelInfo
.aFirstPosition
.getX() - m_aAvailableOuterRect
.getMinX();
398 sal_Int32 nTextMaximumFrameWidth
= ceil(fTextMaximumFrameWidth
);
400 ///the text shape for the label is created
401 aPieLabelInfo
.xTextShape
= createDataLabel(
402 xTextTarget
, rSeries
, nPointIndex
, nVal
, rParam
.mfLogicYSum
,
403 aScreenPosition2D
, eAlignment
, 0, nTextMaximumFrameWidth
);
405 ///a new `PieLabelInfo` instance is initialized with all the info related to
406 ///the current label in order to simplify later label position rearrangement;
407 uno::Reference
< container::XChild
> xChild( aPieLabelInfo
.xTextShape
, uno::UNO_QUERY
);
409 ///text shape could be empty; in that case there is no need to add label info
413 aPieLabelInfo
.xLabelGroupShape
.set( xChild
->getParent(), uno::UNO_QUERY
);
415 if (bMovementAllowed
&& !m_bUseRings
)
417 /** Handle the placement of the label in the best fit case.
418 * First off the routine try to place the label inside the related pie slice,
419 * if this is not possible the label is placed outside.
421 if (rSeries
.getLabelPlacement(nPointIndex
, m_xChartTypeModel
, m_pPosHelper
->isSwapXAndY())
422 == css::chart::DataLabelPlacement::CUSTOM
423 || !performLabelBestFitInnerPlacement(rParam
, aPieLabelInfo
))
425 if (m_aAvailableOuterRect
.getWidth())
427 if (fAngleDegree
< 67.5 || fAngleDegree
>= 292.5)
428 fTextMaximumFrameWidth
430 * (m_aAvailableOuterRect
.getMaxX() - aPieLabelInfo
.aFirstPosition
.getX());
431 else if (fAngleDegree
< 112.5 || fAngleDegree
>= 247.5)
432 fTextMaximumFrameWidth
= 0.8 * m_aAvailableOuterRect
.getWidth();
434 fTextMaximumFrameWidth
436 * (aPieLabelInfo
.aFirstPosition
.getX() - m_aAvailableOuterRect
.getMinX());
438 nTextMaximumFrameWidth
= ceil(fTextMaximumFrameWidth
);
441 nScreenValueOffsetInRadiusDirection
= (m_nDimension
!= 3) ? 150 : 0;
443 = aPolarPosHelper
.getLabelScreenPositionAndAlignmentForUnitCircleValues(
444 eAlignment
, css::chart::DataLabelPlacement::OUTSIDE
,
445 rParam
.mfUnitCircleStartAngleDegree
,
446 rParam
.mfUnitCircleWidthAngleDegree
, rParam
.mfUnitCircleInnerRadius
,
447 rParam
.mfUnitCircleOuterRadius
, rParam
.mfLogicZ
+ 0.5, 0);
448 aPieLabelInfo
.aFirstPosition
449 = basegfx::B2IVector(aScreenPosition2D
.X
, aScreenPosition2D
.Y
);
451 //add a scaling independent Offset if requested
452 if (nScreenValueOffsetInRadiusDirection
!= 0)
454 basegfx::B2IVector
aDirection(aScreenPosition2D
.X
- aOrigin
.X
,
455 aScreenPosition2D
.Y
- aOrigin
.Y
);
456 aDirection
.setLength(nScreenValueOffsetInRadiusDirection
);
457 aScreenPosition2D
.X
+= aDirection
.getX();
458 aScreenPosition2D
.Y
+= aDirection
.getY();
461 uno::Reference
<drawing::XShapes
> xShapes(xChild
->getParent(), uno::UNO_QUERY
);
462 xShapes
->remove(aPieLabelInfo
.xTextShape
);
463 aPieLabelInfo
.xTextShape
464 = createDataLabel(xTextTarget
, rSeries
, nPointIndex
, nVal
, rParam
.mfLogicYSum
,
465 aScreenPosition2D
, eAlignment
, 0, nTextMaximumFrameWidth
);
467 xChild
.set(uno::Reference
<container::XChild
>(aPieLabelInfo
.xTextShape
, uno::UNO_QUERY
));
471 aPieLabelInfo
.xLabelGroupShape
.set(xChild
->getParent(), uno::UNO_QUERY
);
475 bool bShowLeaderLine
= rSeries
.getPropertiesOfSeries()
476 ->getPropertyValue("ShowCustomLeaderLines")
478 if (m_bPieLabelsAllowToMove
&& rSeries
.isLabelCustomPos(nPointIndex
) && bShowLeaderLine
)
480 sal_Int32 nX1
= aPieLabelInfo
.aOuterPosition
.getX();
481 sal_Int32 nY1
= aPieLabelInfo
.aOuterPosition
.getY();
484 ::basegfx::B2IRectangle
aRect(lcl_getRect(aPieLabelInfo
.xLabelGroupShape
));
485 if (nX1
< aRect
.getMinX())
486 nX2
= aRect
.getMinX();
487 else if (nX1
> aRect
.getMaxX())
488 nX2
= aRect
.getMaxX();
490 if (nY1
< aRect
.getMinY())
491 nY2
= aRect
.getMinY();
492 else if (nY1
> aRect
.getMaxY())
493 nY2
= aRect
.getMaxY();
495 sal_Int32 nSquaredDistanceFromOrigin
496 = (nX2
- aOrigin
.X
) * (nX2
- aOrigin
.X
) + (nY2
- aOrigin
.Y
) * (nY2
- aOrigin
.Y
);
498 // tdf#138018 Don't show leader line when custom positioned data label is inside pie chart
499 if (nSquaredDistanceFromOrigin
> fSquaredPieRadius
)
501 drawing::PointSequenceSequence
aPoints(1);
502 aPoints
[0].realloc(2);
503 aPoints
[0][0].X
= nX1
;
504 aPoints
[0][0].Y
= nY1
;
505 aPoints
[0][1].X
= nX2
;
506 aPoints
[0][1].Y
= nY2
;
508 uno::Reference
<beans::XPropertySet
> xProp(aPieLabelInfo
.xTextShape
, uno::UNO_QUERY
);
509 VLineProperties aVLineProperties
;
512 sal_Int32 nColor
= 0;
513 xProp
->getPropertyValue("CharColor") >>= nColor
;
514 //automatic font color does not work for lines -> fallback to black
516 aVLineProperties
.Color
<<= nColor
;
518 m_pShapeFactory
->createLine2D(xTextTarget
, aPoints
, &aVLineProperties
);
522 aPieLabelInfo
.fValue
= nVal
;
523 aPieLabelInfo
.bMovementAllowed
= bMovementAllowed
;
524 aPieLabelInfo
.bMoved
= false;
525 aPieLabelInfo
.xTextTarget
= xTextTarget
;
526 aPieLabelInfo
.bShowLeaderLine
= bShowLeaderLine
&& !rSeries
.isLabelCustomPos(nPointIndex
);
528 m_aLabelInfoList
.push_back(aPieLabelInfo
);
531 void PieChart::addSeries( std::unique_ptr
<VDataSeries
> pSeries
, sal_Int32
/* zSlot */, sal_Int32
/* xSlot */, sal_Int32
/* ySlot */ )
533 VSeriesPlotter::addSeries( std::move(pSeries
), 0, -1, 0 );
536 double PieChart::getMinimumX()
540 double PieChart::getMaxOffset()
542 if (!std::isnan(m_fMaxOffset
))
543 // Value already cached. Use it.
547 if( m_aZSlots
.empty() )
549 if( m_aZSlots
.front().empty() )
552 const std::vector
< std::unique_ptr
<VDataSeries
> >& rSeriesList( m_aZSlots
.front().front().m_aSeriesVector
);
553 if(rSeriesList
.empty())
556 VDataSeries
* pSeries
= rSeriesList
.front().get();
557 uno::Reference
< beans::XPropertySet
> xSeriesProp( pSeries
->getPropertiesOfSeries() );
558 if( !xSeriesProp
.is() )
561 double fExplodePercentage
=0.0;
562 xSeriesProp
->getPropertyValue( "Offset") >>= fExplodePercentage
;
563 if(fExplodePercentage
>m_fMaxOffset
)
564 m_fMaxOffset
=fExplodePercentage
;
566 if(!m_bSizeExcludesLabelsAndExplodedSegments
)
568 uno::Sequence
< sal_Int32
> aAttributedDataPointIndexList
;
569 if( xSeriesProp
->getPropertyValue( "AttributedDataPoints" ) >>= aAttributedDataPointIndexList
)
571 for(sal_Int32 nN
=aAttributedDataPointIndexList
.getLength();nN
--;)
573 uno::Reference
< beans::XPropertySet
> xPointProp( pSeries
->getPropertiesOfPoint(aAttributedDataPointIndexList
[nN
]) );
576 fExplodePercentage
=0.0;
577 xPointProp
->getPropertyValue( "Offset") >>= fExplodePercentage
;
578 if(fExplodePercentage
>m_fMaxOffset
)
579 m_fMaxOffset
=fExplodePercentage
;
586 double PieChart::getMaximumX()
588 double fMaxOffset
= getMaxOffset();
589 if( !m_aZSlots
.empty() && m_bUseRings
)
590 return m_aZSlots
.front().size()+0.5+fMaxOffset
;
591 return 1.5+fMaxOffset
;
593 double PieChart::getMinimumYInRange( double /* fMinimumX */, double /* fMaximumX */, sal_Int32
/* nAxisIndex */ )
598 double PieChart::getMaximumYInRange( double /* fMinimumX */, double /* fMaximumX */, sal_Int32
/* nAxisIndex */ )
603 bool PieChart::isExpandBorderToIncrementRhythm( sal_Int32
/* nDimensionIndex */ )
608 bool PieChart::isExpandIfValuesCloseToBorder( sal_Int32
/* nDimensionIndex */ )
613 bool PieChart::isExpandWideValuesToZero( sal_Int32
/* nDimensionIndex */ )
618 bool PieChart::isExpandNarrowValuesTowardZero( sal_Int32
/* nDimensionIndex */ )
623 bool PieChart::isSeparateStackingForDifferentSigns( sal_Int32
/* nDimensionIndex */ )
628 void PieChart::createShapes()
630 ///a ZSlot is a vector< vector< VDataSeriesGroup > >. There is only one
631 ///ZSlot: m_aZSlots[0] which has a number of elements equal to the total
632 ///number of data series (in fact, even if m_aZSlots[0][i] is an object of
633 ///type `VDataSeriesGroup`, in the current implementation, there is only one
634 ///data series in each data series group).
635 if (m_aZSlots
.empty())
636 // No series to plot.
639 ///m_xLogicTarget is where the group of all data series shapes (e.g. a pie
640 ///slice) is added (xSeriesTarget);
642 ///m_xFinalTarget is where the group of all text shapes (labels) is added
645 ///both have been already created and added to the same root shape
646 ///( a member of a VDiagram object); this initialization occurs in
647 ///`ChartView::impl_createDiagramAndContent`.
649 OSL_ENSURE(m_pShapeFactory
&& m_xLogicTarget
.is() && m_xFinalTarget
.is(), "PieChart is not properly initialized.");
650 if (!m_pShapeFactory
|| !m_xLogicTarget
.is() || !m_xFinalTarget
.is())
653 ///the text labels should be always on top of the other series shapes
654 ///therefore create an own group for the texts to move them to front
655 ///(because the text group is created after the series group the texts are
657 uno::Reference
< drawing::XShapes
> xSeriesTarget(
658 createGroupShape( m_xLogicTarget
));
659 uno::Reference
< drawing::XShapes
> xTextTarget(
660 m_pShapeFactory
->createGroup2D( m_xFinalTarget
));
661 //check necessary here that different Y axis can not be stacked in the same group? ... hm?
663 ///pay attention that the `m_bSwapXAndY` parameter used by the polar
664 ///plotting position helper is always set to true for pie/donut charts
665 ///(see PieChart::setScales). This fact causes that `createShapes` expects
666 ///that the radius axis scale is the one with index 0 and the angle axis
667 ///scale is the one with index 1.
669 std::vector
< VDataSeriesGroup
>::iterator aXSlotIter
= m_aZSlots
.front().begin();
670 const std::vector
< VDataSeriesGroup
>::const_iterator aXSlotEnd
= m_aZSlots
.front().end();
672 ///m_bUseRings == true if chart type is `donut`, == false if chart type is
673 ///`pie`; if the chart is of `donut` type we have as many rings as many data
674 ///series, else we have a single ring (a pie) representing the first data
676 ///for what I can see the radius axis orientation is always reversed and
677 ///the angle axis orientation is always non-reversed;
678 ///the radius axis scale range is [0.5, number of rings + 0.5 + max_offset],
679 ///the angle axis scale range is [0, 1]. The max_offset parameter is used
680 ///for exploded pie chart and its value is 0.5.
682 ///the `explodeable` ring is the first one except when the radius axis
683 ///orientation is reversed (always!?) and we are dealing with a donut: in
684 ///such a case the `explodeable` ring is the last one.
685 std::vector
< VDataSeriesGroup
>::size_type nExplodeableSlot
= 0;
686 if( m_pPosHelper
->isMathematicalOrientationRadius() && m_bUseRings
)
687 nExplodeableSlot
= m_aZSlots
.front().size()-1;
689 m_aLabelInfoList
.clear();
690 ::rtl::math::setNan(&m_fMaxOffset
);
691 sal_Int32 n3DRelativeHeight
= 100;
692 uno::Reference
< beans::XPropertySet
> xPropertySet( m_xChartTypeModel
, uno::UNO_QUERY
);
693 if ( (m_nDimension
==3) && xPropertySet
.is())
697 uno::Any aAny
= xPropertySet
->getPropertyValue( "3DRelativeHeight" );
698 aAny
>>= n3DRelativeHeight
;
700 catch (const uno::Exception
&) { }
702 ///iterate over each xslot, that is on each data series (there is
703 ///only one data series in each data series group!); note that if the chart
704 ///type is a pie the loop iterates only over the first data series
705 ///(m_bUseRings||fSlotX<0.5)
706 for( double fSlotX
=0; aXSlotIter
!= aXSlotEnd
&& (m_bUseRings
||fSlotX
<0.5 ); ++aXSlotIter
, fSlotX
+=1.0 )
710 std::vector
< std::unique_ptr
<VDataSeries
> >* pSeriesList
= &(aXSlotIter
->m_aSeriesVector
);
711 if(pSeriesList
->empty())//there should be only one series in each x slot
713 VDataSeries
* pSeries
= pSeriesList
->front().get();
717 bool bHasFillColorMapping
= pSeries
->hasPropertyMapping("FillColor");
719 /// The angle degree offset is set by the same property of the
721 /// Counter-clockwise offset from the 3 o'clock position.
722 m_pPosHelper
->m_fAngleDegreeOffset
= pSeries
->getStartingAngle();
724 ///iterate through all points to get the sum of all entries of
725 ///the current data series
726 sal_Int32 nPointIndex
=0;
727 sal_Int32 nPointCount
=pSeries
->getTotalPointCount();
728 for( nPointIndex
= 0; nPointIndex
< nPointCount
; nPointIndex
++ )
730 double fY
= pSeries
->getYValue( nPointIndex
);
733 //@todo warn somehow that negative values are treated as positive
737 aParam
.mfLogicYSum
+= fabs(fY
);
740 if (aParam
.mfLogicYSum
== 0.0)
741 // Total sum of all Y values in this series is zero. Skip the whole series.
744 double fLogicYForNextPoint
= 0.0;
745 ///iterate through all points to create shapes
746 for( nPointIndex
= 0; nPointIndex
< nPointCount
; nPointIndex
++ )
748 double fLogicInnerRadius
, fLogicOuterRadius
;
750 ///compute the maximum relative distance offset of the current slice
751 ///from the pie center
752 ///it is worth noting that after the first invocation the maximum
753 ///offset value is cached, so it is evaluated only once per each
754 ///call to `createShapes`
755 double fOffset
= getMaxOffset();
757 ///compute the outer and the inner radius for the current ring slice
758 bool bIsVisible
= m_pPosHelper
->getInnerAndOuterRadius( fSlotX
+1.0, fLogicInnerRadius
, fLogicOuterRadius
, m_bUseRings
, fOffset
);
762 aParam
.mfDepth
= getTransformedDepth() * (n3DRelativeHeight
/ 100.0);
764 uno::Reference
< drawing::XShapes
> xSeriesGroupShape_Shapes
= getSeriesGroupShape(pSeries
, xSeriesTarget
);
765 ///collect data point information (logic coordinates, style ):
766 double fLogicYValue
= fabs(pSeries
->getYValue( nPointIndex
));
767 if( std::isnan(fLogicYValue
) )
769 if(fLogicYValue
==0.0)//@todo: continue also if the resolution is too small
771 double fLogicYPos
= fLogicYForNextPoint
;
772 fLogicYForNextPoint
+= fLogicYValue
;
774 uno::Reference
< beans::XPropertySet
> xPointProperties
= pSeries
->getPropertiesOfPoint( nPointIndex
);
776 //iterate through all subsystems to create partial points
778 //logic values on angle axis:
779 double fLogicStartAngleValue
= fLogicYPos
/ aParam
.mfLogicYSum
;
780 double fLogicEndAngleValue
= (fLogicYPos
+fLogicYValue
) / aParam
.mfLogicYSum
;
782 ///note that the explode percentage is set to the `Offset`
783 ///property of the current data series entry only for slices
784 ///belonging to the outer ring
785 aParam
.mfExplodePercentage
= 0.0;
786 bool bDoExplode
= ( nExplodeableSlot
== static_cast< std::vector
< VDataSeriesGroup
>::size_type
>(fSlotX
) );
789 xPointProperties
->getPropertyValue( "Offset") >>= aParam
.mfExplodePercentage
;
791 catch( const uno::Exception
& )
793 TOOLS_WARN_EXCEPTION("chart2", "" );
796 ///see notes for `PolarPlottingPositionHelper` methods
797 ///transform to unit circle:
798 aParam
.mfUnitCircleWidthAngleDegree
= m_pPosHelper
->getWidthAngleDegree( fLogicStartAngleValue
, fLogicEndAngleValue
);
799 aParam
.mfUnitCircleStartAngleDegree
= m_pPosHelper
->transformToAngleDegree( fLogicStartAngleValue
);
800 aParam
.mfUnitCircleInnerRadius
= m_pPosHelper
->transformToRadius( fLogicInnerRadius
);
801 aParam
.mfUnitCircleOuterRadius
= m_pPosHelper
->transformToRadius( fLogicOuterRadius
);
804 std::unique_ptr
< tPropertyNameValueMap
> apOverwritePropertiesMap
;
805 if (!pSeries
->hasPointOwnColor(nPointIndex
) && m_xColorScheme
.is())
807 apOverwritePropertiesMap
.reset( new tPropertyNameValueMap
);
808 (*apOverwritePropertiesMap
)["FillColor"] <<=
809 m_xColorScheme
->getColorByIndex( nPointIndex
);
813 aParam
.mfLogicZ
= -1.0; // For 3D pie chart label position
814 uno::Reference
<drawing::XShape
> xPointShape
=
816 xSeriesGroupShape_Shapes
, xPointProperties
, apOverwritePropertiesMap
.get(), aParam
);
818 if(bHasFillColorMapping
)
820 double nPropVal
= pSeries
->getValueByProperty(nPointIndex
, "FillColor");
821 if(!std::isnan(nPropVal
))
823 uno::Reference
< beans::XPropertySet
> xProps( xPointShape
, uno::UNO_QUERY_THROW
);
824 xProps
->setPropertyValue("FillColor", uno::Any(static_cast<sal_Int32
>( nPropVal
)));
829 createTextLabelShape(xTextTarget
, *pSeries
, nPointIndex
, aParam
);
833 ShapeFactory::setShapeName( xPointShape
834 , ObjectIdentifier::createPointCID( pSeries
->getPointCID_Stub(), nPointIndex
) );
838 ///enable dragging of outer segments
840 double fAngle
= aParam
.mfUnitCircleStartAngleDegree
+ aParam
.mfUnitCircleWidthAngleDegree
/2.0;
841 double fMaxDeltaRadius
= aParam
.mfUnitCircleOuterRadius
-aParam
.mfUnitCircleInnerRadius
;
842 drawing::Position3D aOrigin
= m_pPosHelper
->transformUnitCircleToScene( fAngle
, aParam
.mfUnitCircleOuterRadius
, aParam
.mfLogicZ
);
843 drawing::Position3D aNewOrigin
= m_pPosHelper
->transformUnitCircleToScene( fAngle
, aParam
.mfUnitCircleOuterRadius
+ fMaxDeltaRadius
, aParam
.mfLogicZ
);
845 sal_Int32
nOffsetPercent( static_cast<sal_Int32
>(aParam
.mfExplodePercentage
* 100.0) );
847 awt::Point
aMinimumPosition( PlottingPositionHelper::transformSceneToScreenPosition(
848 aOrigin
, m_xLogicTarget
, m_pShapeFactory
, m_nDimension
) );
849 awt::Point
aMaximumPosition( PlottingPositionHelper::transformSceneToScreenPosition(
850 aNewOrigin
, m_xLogicTarget
, m_pShapeFactory
, m_nDimension
) );
852 //enable dragging of piesegments
853 OUString
aPointCIDStub( ObjectIdentifier::createSeriesSubObjectStub( OBJECTTYPE_DATA_POINT
854 , pSeries
->getSeriesParticle()
855 , ObjectIdentifier::getPieSegmentDragMethodServiceName()
856 , ObjectIdentifier::createPieSegmentDragParameterString(
857 nOffsetPercent
, aMinimumPosition
, aMaximumPosition
)
860 ShapeFactory::setShapeName( xPointShape
861 , ObjectIdentifier::createPointCID( aPointCIDStub
, nPointIndex
) );
863 catch( const uno::Exception
& )
865 TOOLS_WARN_EXCEPTION("chart2", "" );
867 }//next series in x slot (next y slot)
872 PieChart::PieLabelInfo::PieLabelInfo()
873 : aFirstPosition(), aOrigin(), fValue(0.0)
874 , bMovementAllowed(false), bMoved(false)
875 , bShowLeaderLine(false), pPrevious(nullptr)
880 /** In case this label and the passed label overlap the routine moves this
881 * label in order to fix the issue. After the label position has been
882 * rearranged it is checked that the moved label is still inside the page
883 * document, if the test is positive the routine returns true else returns
886 bool PieChart::PieLabelInfo::moveAwayFrom( const PieChart::PieLabelInfo
* pFix
, const awt::Size
& rPageSize
, bool bMoveHalfWay
, bool bMoveClockwise
)
888 //return true if the move was successful
889 if(!bMovementAllowed
)
892 const sal_Int32 nLabelDistanceX
= rPageSize
.Width
/50;
893 const sal_Int32 nLabelDistanceY
= rPageSize
.Height
/50;
895 ///compute the rectangle representing the intersection of the label bounding
896 ///boxes (`aOverlap`).
897 ::basegfx::B2IRectangle
aOverlap( lcl_getRect( xLabelGroupShape
) );
898 aOverlap
.intersect( lcl_getRect( pFix
->xLabelGroupShape
) );
899 if( !aOverlap
.isEmpty() )
901 //TODO: alternative move direction
903 ///the label is shifted along the direction orthogonal to the vector
904 ///starting at the pie/donut center and ending at this label anchor
907 ///named `aTangentialDirection` the unit vector related to such a
908 ///direction, the magnitude of the shift along such a direction is
909 ///calculated in this way: if the horizontal component of
910 ///`aTangentialDirection` is greater than the vertical component,
911 ///the magnitude of the shift is equal to `aOverlap.Width` else to
912 ///`aOverlap.Height`;
913 basegfx::B2IVector aRadiusDirection
= aFirstPosition
- aOrigin
;
914 aRadiusDirection
.setLength(1.0);
915 basegfx::B2IVector
aTangentialDirection( -aRadiusDirection
.getY(), aRadiusDirection
.getX() );
916 bool bShiftHorizontal
= abs(aTangentialDirection
.getX()) > abs(aTangentialDirection
.getY());
917 sal_Int32 nShift
= bShiftHorizontal
? static_cast<sal_Int32
>(aOverlap
.getWidth()) : static_cast<sal_Int32
>(aOverlap
.getHeight());
918 ///the magnitude of the shift is also increased by 1/50-th of the width
919 ///or the height of the document page;
920 nShift
+= (bShiftHorizontal
? nLabelDistanceX
: nLabelDistanceY
);
921 ///in case the `bMoveHalfWay` parameter is true the magnitude of
922 ///the shift is halved.
925 ///in case the `bMoveClockwise` parameter is false the direction of
926 ///`aTangentialDirection` is reversed;
929 awt::Point
aOldPos( xLabelGroupShape
->getPosition() );
930 basegfx::B2IVector aNewPos
= basegfx::B2IVector( aOldPos
.X
, aOldPos
.Y
) + nShift
*aTangentialDirection
;
932 ///a final check is performed in order to be sure that the moved label
933 ///is still inside the page document;
934 awt::Point
aNewAWTPos( aNewPos
.getX(), aNewPos
.getY() );
935 if( !lcl_isInsidePage( aNewAWTPos
, xLabelGroupShape
->getSize(), rPageSize
) )
938 xLabelGroupShape
->setPosition( aNewAWTPos
);
943 ///note that no further test is performed in order to check that the
944 ///overlap is really fixed: this result is surely achieved if the shift
945 ///would occur in the horizontal or vertical direction (since, in such a
946 ///direction, the magnitude of the shift would be greater than the length
947 ///of the overlap), but in general this is not true;
948 ///adding a constant term equal to 1/50-th of the width or the height of
949 ///the document page increases the probability of success, anyway it is
950 ///worth noting that the method can return true even if the overlap issue
951 ///is not (completely) fixed;
954 void PieChart::resetLabelPositionsToPreviousState()
956 for (auto const& labelInfo
: m_aLabelInfoList
)
957 labelInfo
.xLabelGroupShape
->setPosition(labelInfo
.aPreviousPosition
);
960 bool PieChart::detectLabelOverlapsAndMove( const awt::Size
& rPageSize
)
962 ///the routine tries to individuate a chain of overlapping labels and
963 ///assigns the first and the last of them to `pFirstBorder` and
965 ///this result is achieved by performing two consecutive while loop.
967 ///find borders of a group of overlapping labels
969 ///a first while loop is started on the collection of `PieLabelInfo` objects;
970 ///the bounding box of each label is checked for overlap against the bounding
971 ///box of the previous and of the next label;
972 ///when an overlap is found `bOverlapFound` is set to true, however the
973 ///iteration is break only if the overlap occurs against only the next label
974 ///and not against the previous label: so we exit from the loop whenever an
975 ///overlap occurs except when the loop initial label overlaps with the
977 bool bOverlapFound
= false;
978 PieLabelInfo
* pStart
= &(*(m_aLabelInfoList
.rbegin()));
979 PieLabelInfo
* pFirstBorder
= nullptr;
980 PieLabelInfo
* pSecondBorder
= nullptr;
981 PieLabelInfo
* pCurrent
= pStart
;
984 ::basegfx::B2IRectangle
aPreviousOverlap( lcl_getRect( pCurrent
->xLabelGroupShape
) );
985 ::basegfx::B2IRectangle
aNextOverlap( aPreviousOverlap
);
986 aPreviousOverlap
.intersect( lcl_getRect( pCurrent
->pPrevious
->xLabelGroupShape
) );
987 aNextOverlap
.intersect( lcl_getRect( pCurrent
->pNext
->xLabelGroupShape
) );
989 bool bPreviousOverlap
= !aPreviousOverlap
.isEmpty();
990 bool bNextOverlap
= !aNextOverlap
.isEmpty();
991 if( bPreviousOverlap
|| bNextOverlap
)
992 bOverlapFound
= true;
993 if( !bPreviousOverlap
&& bNextOverlap
)
995 pFirstBorder
= pCurrent
;
998 pCurrent
= pCurrent
->pNext
;
1000 while( pCurrent
!= pStart
);
1002 if( !bOverlapFound
)
1005 ///in case we found a label (`pFirstBorder`) which overlaps with the next
1006 ///label and not with the previous label a second while loop is started with
1007 ///`pFirstBorder` as initial label; one more time the bounding box of each
1008 ///label is checked for overlap against the bounding box of the previous and
1009 ///of the next label, however this time we exit from the loop only if the
1010 ///current label overlaps with the previous one but does not with the next
1011 ///one (the opposite of what is required in the former loop);
1012 ///in case such a label is found it is assigned to `pSecondBorder` and the
1013 ///iteration is stopped; so in case there is a chain of overlapping labels
1014 ///we end up having the first label of the chain pointed by `pFirstBorder`
1015 ///and the last label of the chain pointed by `pSecondBorder`;
1018 pCurrent
= pFirstBorder
;
1021 ::basegfx::B2IRectangle
aPreviousOverlap( lcl_getRect( pCurrent
->xLabelGroupShape
) );
1022 ::basegfx::B2IRectangle
aNextOverlap( aPreviousOverlap
);
1023 aPreviousOverlap
.intersect( lcl_getRect( pCurrent
->pPrevious
->xLabelGroupShape
) );
1024 aNextOverlap
.intersect( lcl_getRect( pCurrent
->pNext
->xLabelGroupShape
) );
1026 if( !aPreviousOverlap
.isEmpty() && aNextOverlap
.isEmpty() )
1028 pSecondBorder
= pCurrent
;
1031 pCurrent
= pCurrent
->pNext
;
1033 while( pCurrent
!= pFirstBorder
);
1036 ///when two labels satisfying the required conditions are not found
1037 ///(`pFirstBorder == 0 || pSecondBorder == 0`) but still an overlap occurs
1038 ///(`bOverlapFound == true`) we are in the situation where each label
1039 ///overlaps with both the previous and the next one; so `pFirstBorder` is
1040 ///set to point to the last `PieLabelInfo` object in the collection and
1041 ///`pSecondBorder` is set to point to the first one;
1042 if( !pFirstBorder
|| !pSecondBorder
)
1044 pFirstBorder
= &(*(m_aLabelInfoList
.rbegin()));
1045 pSecondBorder
= &(*(m_aLabelInfoList
.begin()));
1048 ///the total number of labels that made up the chain is calculated and used
1049 ///for getting a pointer to the central label (`pCenter`);
1050 PieLabelInfo
* pCenter
= pFirstBorder
;
1051 sal_Int32 nOverlapGroupCount
= 1;
1052 for( pCurrent
= pFirstBorder
;pCurrent
!= pSecondBorder
; pCurrent
= pCurrent
->pNext
)
1053 nOverlapGroupCount
++;
1054 sal_Int32 nCenterPos
= nOverlapGroupCount
/2;
1055 bool bSingleCenter
= nOverlapGroupCount
%2 != 0;
1060 pCurrent
= pFirstBorder
;
1061 while( --nCenterPos
)
1062 pCurrent
= pCurrent
->pNext
;
1066 ///the current position of each label in the collection is saved in
1067 ///`PieLabelInfo.aPreviousPosition`, so that it is possible to undo the label
1068 ///move action if it is needed; the undo action is provided by the
1069 ///`PieChart::resetLabelPositionsToPreviousState` method.
1073 pCurrent
->aPreviousPosition
= pCurrent
->xLabelGroupShape
->getPosition();
1074 pCurrent
= pCurrent
->pNext
;
1076 while( pCurrent
!= pStart
);
1078 ///the `PieChart::tryMoveLabels` method is invoked with
1079 ///`rbAlternativeMoveDirection` boolean parameter set to false, such a method
1080 ///tries to remove all overlaps that occur in the list of labels going from
1081 ///`pFirstBorder` to `pSecondBorder`;
1082 ///if the `PieChart::tryMoveLabels` returns true no further action is
1083 ///performed, however it is worth noting that it does not mean that all
1084 ///overlap issues have been surely fixed, but only that all moved labels are
1085 ///at least completely inside the page document;
1086 ///when `PieChart::tryMoveLabels` returns false, it means that the attempt
1087 ///to fix one of the overlap issues caused that a label has been moved
1088 ///(partially) outside the page document (anyway the `PieChart::tryMoveLabels`
1089 ///method takes care to restore the position of all labels to their initial
1090 ///position, and to set the `rbAlternativeMoveDirection` in/out parameter to
1091 ///true); in such a case a second invocation of `PieChart::tryMoveLabels` is
1092 ///performed (and this time the `rbAlternativeMoveDirection` boolean
1093 ///parameter is true) and independently by what the `PieChart::tryMoveLabels`
1094 ///method returns no further action is performed;
1095 ///(see notes for `PieChart::tryMoveLabels`);
1096 bool bAlternativeMoveDirection
= false;
1097 if( !tryMoveLabels( pFirstBorder
, pSecondBorder
, pCenter
, bSingleCenter
, bAlternativeMoveDirection
, rPageSize
) )
1098 tryMoveLabels( pFirstBorder
, pSecondBorder
, pCenter
, bSingleCenter
, bAlternativeMoveDirection
, rPageSize
);
1100 ///in both cases (one or two invocations of `PieChart::tryMoveLabels`) the
1101 ///`detectLabelOverlapsAndMove` method ends returning true.
1106 /** Try to remove all overlaps that occur in the list of labels going from
1107 * `pFirstBorder` to `pSecondBorder`
1109 bool PieChart::tryMoveLabels( PieLabelInfo
const * pFirstBorder
, PieLabelInfo
const * pSecondBorder
1110 , PieLabelInfo
* pCenter
1111 , bool bSingleCenter
, bool& rbAlternativeMoveDirection
, const awt::Size
& rPageSize
)
1114 PieLabelInfo
* p1
= bSingleCenter
? pCenter
->pPrevious
: pCenter
;
1115 PieLabelInfo
* p2
= pCenter
->pNext
;
1116 //return true when successful
1118 bool bLabelOrderIsAntiClockWise
= m_pPosHelper
->isMathematicalOrientationAngle();
1120 ///two loops are performed simultaneously: the outer loop iterates on
1121 ///`PieLabelInfo` objects in the list starting from the central element
1122 ///(`pCenter`) and moving forward until the last element (`pSecondBorder`);
1123 ///the inner loop starts from the previous element of `pCenter` and moves
1124 ///forward until the current `PieLabelInfo` object of the outer loop is
1126 PieLabelInfo
* pCurrent
= nullptr;
1127 for( pCurrent
= p2
;pCurrent
->pPrevious
!= pSecondBorder
; pCurrent
= pCurrent
->pNext
)
1129 PieLabelInfo
* pFix
= nullptr;
1130 for( pFix
= p2
->pPrevious
;pFix
!= pCurrent
; pFix
= pFix
->pNext
)
1132 ///on the current `PieLabelInfo` object of the outer loop the
1133 ///`moveAwayFrom` method is invoked by passing the current
1134 ///`PieLabelInfo` object of the inner loop as argument.
1136 ///so each label going from the central one to the last one is
1137 ///checked for overlapping against all previous labels (that comes
1138 ///after the central label) and in case the overlap occurs the
1139 ///`moveAwayFrom` method tries to fix the issue;
1140 ///if `moveAwayFrom` returns true (pay attention: that does not
1141 ///mean that the overlap issue has been surely fixed but only that
1142 ///the moved label is at least completely inside the page document:
1143 ///see notes on `PieChart::PieLabelInfo::moveAwayFrom`), the inner
1144 ///loop starts a new iteration else the `rbAlternativeMoveDirection`
1145 ///boolean parameter is tested: if it is false the parameter is set
1146 ///to true, the position of all labels is restored to the initial
1147 ///one (through the `PieChart::resetLabelPositionsToPreviousState`
1148 ///method) and the method ends by returning false, else the inner
1149 ///loop starts a new iteration step;
1150 ///so when `rbAlternativeMoveDirection` is true the method goes on
1151 ///trying to fix left overlap issues even if the last `moveAwayFrom`
1152 ///invocation has moved a label in a position that it is not
1153 ///completely inside the page document
1155 if( !pCurrent
->moveAwayFrom( pFix
, rPageSize
, !bSingleCenter
&& pCurrent
== p2
, !bLabelOrderIsAntiClockWise
) )
1157 if( !rbAlternativeMoveDirection
)
1159 rbAlternativeMoveDirection
= true;
1160 resetLabelPositionsToPreviousState();
1167 ///if the method does not return before ending the first pair of loops,
1168 ///a second pair of simultaneous loops is performed in the opposite
1169 ///direction (respect with the previous case): the outer loop iterates on
1170 ///`PieLabelInfo` objects in the list starting from the central element
1171 ///(`pCenter`) and moving backward until the first element (`pFirstBorder`);
1172 ///the inner loop starts from the next element of `pCenter` and moves
1173 ///backward until the current `PieLabelInfo` object of the outer loop is
1176 ///like in the previous case on the current `PieLabelInfo` object of
1177 ///the outer loop the `moveAwayFrom` method is invoked by passing
1178 ///the current `PieLabelInfo` object of the inner loop as argument
1180 ///so each label going from the central one to the first one is checked for
1181 ///overlapping on all subsequent labels (that come before the central label)
1182 ///and in case the overlap occurs the `moveAwayFrom` method tries to fix
1183 ///the issue. The subsequent actions performed after the invocation
1184 ///`moveAwayFrom` are the same detailed above for the first pair of loops
1186 for( pCurrent
= p1
;pCurrent
->pNext
!= pFirstBorder
; pCurrent
= pCurrent
->pPrevious
)
1188 PieLabelInfo
* pFix
= nullptr;
1189 for( pFix
= p2
->pNext
;pFix
!= pCurrent
; pFix
= pFix
->pPrevious
)
1191 if( !pCurrent
->moveAwayFrom( pFix
, rPageSize
, false, bLabelOrderIsAntiClockWise
) )
1193 if( !rbAlternativeMoveDirection
)
1195 rbAlternativeMoveDirection
= true;
1196 resetLabelPositionsToPreviousState();
1205 void PieChart::rearrangeLabelToAvoidOverlapIfRequested( const awt::Size
& rPageSize
)
1207 ///this method is invoked by `ChartView::impl_createDiagramAndContent` for
1208 ///pie and donut charts after text label creation;
1209 ///it tries to rearrange labels only when the label placement type is
1211 // no need to do anything when we only have one label
1212 if (m_aLabelInfoList
.size() < 2)
1215 ///check whether there are any labels that should be moved
1216 bool bMoveableFound
= false;
1217 for (auto const& labelInfo
: m_aLabelInfoList
)
1219 if(labelInfo
.bMovementAllowed
)
1221 bMoveableFound
= true;
1228 double fPageDiagonaleLength
= sqrt( double(rPageSize
.Width
)*double(rPageSize
.Width
) + double(rPageSize
.Height
)*double(rPageSize
.Height
) );
1229 if( fPageDiagonaleLength
== 0.0 )
1232 ///initialize next and previous member of `PieLabelInfo` objects
1233 auto aIt1
= m_aLabelInfoList
.begin();
1234 auto aEnd
= m_aLabelInfoList
.end();
1235 std::vector
< PieLabelInfo
>::iterator aIt2
= aIt1
;
1236 aIt1
->pPrevious
= &(*(m_aLabelInfoList
.rbegin()));
1238 for( ;aIt2
!=aEnd
; ++aIt1
, ++aIt2
)
1240 PieLabelInfo
& rInfo1( *aIt1
);
1241 PieLabelInfo
& rInfo2( *aIt2
);
1242 rInfo1
.pNext
= &rInfo2
;
1243 rInfo2
.pPrevious
= &rInfo1
;
1245 aIt1
->pNext
= &(*(m_aLabelInfoList
.begin()));
1247 ///detect overlaps and move
1248 sal_Int32 nMaxIterations
= 50;
1249 while( detectLabelOverlapsAndMove( rPageSize
) && nMaxIterations
> 0 )
1252 ///create connection lines for the moved labels
1253 VLineProperties aVLineProperties
;
1254 for (auto const& labelInfo
: m_aLabelInfoList
)
1256 if( labelInfo
.bMoved
&& labelInfo
.bShowLeaderLine
)
1258 sal_Int32 nX1
= labelInfo
.aOuterPosition
.getX();
1259 sal_Int32 nY1
= labelInfo
.aOuterPosition
.getY();
1260 sal_Int32 nX2
= nX1
;
1261 sal_Int32 nY2
= nY1
;
1262 ::basegfx::B2IRectangle
aRect( lcl_getRect( labelInfo
.xLabelGroupShape
) );
1263 if( nX1
< aRect
.getMinX() )
1264 nX2
= aRect
.getMinX();
1265 else if( nX1
> aRect
.getMaxX() )
1266 nX2
= aRect
.getMaxX();
1268 if( nY1
< aRect
.getMinY() )
1269 nY2
= aRect
.getMinY();
1270 else if( nY1
> aRect
.getMaxY() )
1271 nY2
= aRect
.getMaxY();
1273 //when the line is very short compared to the page size don't create one
1274 ::basegfx::B2DVector
aLength(nX1
-nX2
, nY1
-nY2
);
1275 if( (aLength
.getLength()/fPageDiagonaleLength
) < 0.01 )
1278 drawing::PointSequenceSequence
aPoints(1);
1279 aPoints
[0].realloc(2);
1280 aPoints
[0][0].X
= nX1
;
1281 aPoints
[0][0].Y
= nY1
;
1282 aPoints
[0][1].X
= nX2
;
1283 aPoints
[0][1].Y
= nY2
;
1285 uno::Reference
< beans::XPropertySet
> xProp( labelInfo
.xTextShape
, uno::UNO_QUERY
);
1288 sal_Int32 nColor
= 0;
1289 xProp
->getPropertyValue("CharColor") >>= nColor
;
1290 if( nColor
!= -1 )//automatic font color does not work for lines -> fallback to black
1291 aVLineProperties
.Color
<<= nColor
;
1293 m_pShapeFactory
->createLine2D( labelInfo
.xTextTarget
, aPoints
, &aVLineProperties
);
1299 /** Handle the placement of the label in the best fit case:
1300 * the routine try to place the label inside the related pie slice,
1301 * in case of success it returns true else returns false.
1305 * s: the bisector ray of the current pie slice
1306 * alpha: the angle between the horizontal axis and the bisector ray s
1307 * N: the vertex of the label b.b. which is nearest to C
1308 * F: the vertex of the label b.b. not adjacent to N; F lies on the pie border
1309 * P, Q: the intersection points between the label b.b. and the bisector ray s;
1310 * P is the one at minimum distance respect with C
1311 * e: the edge of the label b.b. where P lies (the nearest edge to C)
1312 * M: the vertex of e that is not N
1313 * G: the vertex of the label b.b. which is adjacent to N and that is not M
1314 * beta: the angle MPF
1315 * theta: the angle CPF
1322 * | G _________________________/____________________________ F
1336 * | | / . \ beta . |
1337 * | |__________/._\___|_______.____________________________|
1351 * __|/__|_____________________________________________________________
1356 * When alpha = 45k (k integer) s crosses the label b.b. at N exactly.
1357 * In such a case the nearest edge e is defined as the edge having N as the
1358 * start vertex and that is covered in the counterclockwise direction when
1359 * we move from N to the adjacent vertex.
1361 * The nearest vertex N is:
1362 * 1. the bottom left vertex when 0 < alpha < 90
1363 * 2. the bottom right vertex when 90 < alpha < 180
1364 * 3. the top right vertex when 180 < alpha < 270
1365 * 4. the top left vertex when 270 < alpha < 360.
1367 * The nearest edge e is:
1368 * 1. the left edge when −45 < alpha < 45
1369 * 2. the bottom edge when 45 < alpha <135
1370 * 3. the right edge when 135 < alpha < 225
1371 * 4. the top edge when 225 < alpha < 315.
1374 bool PieChart::performLabelBestFitInnerPlacement(ShapeParam
& rShapeParam
, PieLabelInfo
const & rPieLabelInfo
)
1376 SAL_INFO( "chart2.pie.label.bestfit.inside",
1377 "** PieChart::performLabelBestFitInnerPlacement invoked **" );
1379 // get pie slice properties
1380 double fStartAngleDeg
= NormAngle360(rShapeParam
.mfUnitCircleStartAngleDegree
);
1381 double fWidthAngleDeg
= rShapeParam
.mfUnitCircleWidthAngleDegree
;
1382 double fHalfWidthAngleDeg
= fWidthAngleDeg
/ 2.0;
1383 double fBisectingRayAngleDeg
= NormAngle360(fStartAngleDeg
+ fHalfWidthAngleDeg
);
1385 // get the middle point of the arc representing the pie slice border
1386 double fLogicZ
= rShapeParam
.mfLogicZ
+ 1.0;
1387 awt::Point aMiddleArcPoint
= PlottingPositionHelper::transformSceneToScreenPosition(
1388 m_pPosHelper
->transformUnitCircleToScene(
1389 fBisectingRayAngleDeg
,
1390 rShapeParam
.mfUnitCircleOuterRadius
,
1392 m_xLogicTarget
, m_pShapeFactory
, m_nDimension
);
1394 // compute the pie radius
1395 basegfx::B2IVector aPieCenter
= rPieLabelInfo
.aOrigin
;
1396 basegfx::B2IVector
aRadiusVector(
1397 aMiddleArcPoint
.X
- aPieCenter
.getX(),
1398 aMiddleArcPoint
.Y
- aPieCenter
.getY() );
1399 double fSquaredPieRadius
= aRadiusVector
.scalar(aRadiusVector
);
1400 double fPieRadius
= sqrt( fSquaredPieRadius
);
1402 // the bb is moved as much as possible near to the border of the pie,
1403 // anyway a small offset from the border is present (0.025 * pie radius)
1404 const double fPieBorderOffset
= 0.025;
1405 fPieRadius
= fPieRadius
- fPieRadius
* fPieBorderOffset
;
1407 SAL_INFO( "chart2.pie.label.bestfit.inside",
1409 SAL_INFO( "chart2.pie.label.bestfit.inside",
1410 " start angle = " << fStartAngleDeg
);
1411 SAL_INFO( "chart2.pie.label.bestfit.inside",
1412 " angle width = " << fWidthAngleDeg
);
1413 SAL_INFO( "chart2.pie.label.bestfit.inside",
1414 " bisecting ray angle = " << fBisectingRayAngleDeg
);
1415 SAL_INFO( "chart2.pie.label.bestfit.inside",
1416 " pie radius = " << fPieRadius
);
1417 SAL_INFO( "chart2.pie.label.bestfit.inside",
1418 " pie center = " << rPieLabelInfo
.aOrigin
);
1419 SAL_INFO( "chart2.pie.label.bestfit.inside",
1420 " middle arc point = (" << aMiddleArcPoint
.X
<< ","
1421 << aMiddleArcPoint
.Y
<< ")" );
1422 SAL_INFO( "chart2.pie.label.bestfit.inside",
1423 " label bounding box:" );
1424 SAL_INFO( "chart2.pie.label.bestfit.inside",
1425 " old anchor point = " << rPieLabelInfo
.aFirstPosition
);
1428 if( fPieRadius
== 0.0 )
1431 // get label b.b. width and height
1432 ::basegfx::B2IRectangle
aBb( lcl_getRect( rPieLabelInfo
.xLabelGroupShape
) );
1433 double fLabelWidth
= aBb
.getWidth();
1434 double fLabelHeight
= aBb
.getHeight();
1436 // -45 <= fAlphaDeg < 315
1437 double fAlphaDeg
= NormAngle360(fBisectingRayAngleDeg
+ 45) - 45;
1438 double fAlphaRad
= basegfx::deg2rad(fAlphaDeg
);
1440 // compute nearest edge index
1445 int nSectorIndex
= floor( (fAlphaDeg
+ 45) / 45.0 );
1446 int nNearestEdgeIndex
= nSectorIndex
/ 2;
1448 // compute lengths of the nearest edge and of the orthogonal edges
1449 double fNearestEdgeLength
= fLabelWidth
;
1450 double fOrthogonalEdgeLength
= fLabelHeight
;
1451 basegfx::Axis2D eAxis
= basegfx::Axis2D::X
;
1452 basegfx::Axis2D eOrthogonalAxis
= basegfx::Axis2D::Y
;
1453 if( nNearestEdgeIndex
% 2 == 0 ) // nearest edge is vertical
1455 fNearestEdgeLength
= fLabelHeight
;
1456 fOrthogonalEdgeLength
= fLabelWidth
;
1457 eAxis
= basegfx::Axis2D::Y
;
1458 eOrthogonalAxis
= basegfx::Axis2D::X
;
1461 // compute the distance between N and P
1462 // such a distance is piece wise linear respect with alpha:
1463 // given 45k <= alpha < 45(k+1) we have
1464 // when k is even: d(N,P) = (length(e) / 2) * (1 - (alpha - 45k)/45)
1465 // when k is odd: d(N,P) = (length(e) / 2) * (1 - (45(k+1) - alpha)/45)
1466 int nIndex
= nSectorIndex
-1; // nIndex = -1...6
1467 double fIndexMod2
= (nIndex
+ 8) % 2; // fIndexMod2 must be non negative
1468 double fSgn
= 2.0 * (fIndexMod2
- 0.5); // 0 -> -1, 1 -> 1
1469 double fDistanceNP
= (fNearestEdgeLength
/ 2.0) * (1 + fSgn
* ((fAlphaDeg
- 45 * (nIndex
+ fIndexMod2
)) / 45.0));
1470 double fDistancePM
= fNearestEdgeLength
- fDistanceNP
;
1472 // compute the length of the diagonal vector d,
1473 // that is the distance between P and F
1474 double fSquaredDistancePF
= fDistancePM
* fDistancePM
+ fOrthogonalEdgeLength
* fOrthogonalEdgeLength
;
1475 double fDistancePF
= sqrt( fSquaredDistancePF
);
1477 SAL_INFO( "chart2.pie.label.bestfit.inside",
1478 " width = " << fLabelWidth
);
1479 SAL_INFO( "chart2.pie.label.bestfit.inside",
1480 " height = " << fLabelHeight
);
1481 SAL_INFO( "chart2.pie.label.bestfit.inside",
1482 " nearest edge index = " << nNearestEdgeIndex
);
1483 SAL_INFO( "chart2.pie.label.bestfit.inside",
1484 " alpha = " << fAlphaDeg
);
1485 SAL_INFO( "chart2.pie.label.bestfit.inside",
1486 " distance(N,P) = " << fDistanceNP
);
1487 SAL_INFO( "chart2.pie.label.bestfit.inside",
1488 " nIndex = " << nIndex
);
1489 SAL_INFO( "chart2.pie.label.bestfit.inside",
1490 " fIndexMod2 = " << fIndexMod2
);
1491 SAL_INFO( "chart2.pie.label.bestfit.inside",
1492 " fSgn = " << fSgn
);
1493 SAL_INFO( "chart2.pie.label.bestfit.inside",
1494 " distance(P,F) = " << fDistancePF
);
1497 // we check that the condition length(d) <= pie radius holds
1498 if (fDistancePF
> fPieRadius
)
1503 // compute beta: the angle of the diagonal vector d,
1504 // that is, the angle in P respect with the triangle PMF;
1505 // since both arguments are non negative the returned value is in [0, PI/2]
1506 double fBetaRad
= atan2( fOrthogonalEdgeLength
, fDistancePM
);
1508 // compute the theta angle, that is the angle in P
1509 // respect with the triangle CFP;
1510 // when the second intersection edge is opposite to the nearest edge,
1511 // theta depends on alpha and beta according to the following relation:
1512 // theta = f(alpha, beta) = s * alpha + 90 * (1 - s * i) + beta
1513 // where i is the nearest edge index and s is the sign of (alpha' - 45),
1514 // with alpha' = (alpha + 45) mod 90;
1515 // when the second intersection edge is adjacent to the nearest edge,
1516 // we have theta = 360 - f(alpha, beta);
1517 // note that in the former case 0 <= f(alpha, beta) <= 180,
1518 // whilst in the latter case 180 <= f(alpha, beta) <= 360;
1519 double fAlphaMod90
= fmod( fAlphaDeg
+ 45, 90.0 ) - 45;
1520 double fSign
= fAlphaMod90
== 0.0
1522 : ( fAlphaMod90
< 0 ) ? -1.0 : 1.0;
1523 double fThetaRad
= fSign
* fAlphaRad
+ M_PI_2
* (1 - fSign
* nNearestEdgeIndex
) + fBetaRad
;
1524 if( fThetaRad
> M_PI
)
1526 fThetaRad
= 2 * M_PI
- fThetaRad
;
1529 // compute the length of the positional vector,
1530 // that is the distance between C and P
1532 // when the bisector ray intersects the b.b. in F we have theta mod 180 == 0
1533 if( fmod(fThetaRad
, M_PI
) == 0.0 )
1535 fDistanceCP
= fPieRadius
- fDistancePF
;
1537 else // general case
1539 // we can compute d(C,P) by applying some trigonometric formula to
1540 // the triangle CFP : we know length(d) and length(r) = r and we have
1541 // computed the angle in P (theta); so named delta the angle in C and
1542 // gamma the angle in F, by the relation:
1545 // --------- = --------- = ---------
1546 // sin theta sin delta sin gamma
1548 // we get the wanted distance
1549 double fSinTheta
= sin( fThetaRad
);
1550 double fSinDelta
= fDistancePF
* fSinTheta
/ fPieRadius
;
1551 double fDeltaRad
= asin( fSinDelta
);
1552 double fGammaRad
= M_PI
- (fThetaRad
+ fDeltaRad
);
1553 double fSinGamma
= sin( fGammaRad
);
1554 fDistanceCP
= fPieRadius
* fSinGamma
/ fSinTheta
;
1557 // define the positional vector
1558 basegfx::B2DVector
aPositionalVector( cos(fAlphaRad
), sin(fAlphaRad
) );
1559 aPositionalVector
.setLength(fDistanceCP
);
1561 // we define a direction vector in order to know
1562 // in which quadrant we are working
1563 basegfx::B2DVector
aDirection(1.0, 1.0);
1564 if( 90 <= fBisectingRayAngleDeg
&& fBisectingRayAngleDeg
< 270 )
1566 aDirection
.setX(-1.0);
1568 if( fBisectingRayAngleDeg
>= 180 )
1570 aDirection
.setY(-1.0);
1573 // compute vertices N, M and G respect with pie center C
1574 basegfx::B2DVector
aNearestVertex(aPositionalVector
);
1575 aNearestVertex
.set(eAxis
, aNearestVertex
.get(eAxis
) - aDirection
.get(eAxis
) * fDistanceNP
);
1576 basegfx::B2DVector
aVertexM(aNearestVertex
);
1577 aVertexM
.set(eAxis
, aVertexM
.get(eAxis
) + aDirection
.get(eAxis
) * fNearestEdgeLength
);
1578 basegfx::B2DVector
aVertexG(aNearestVertex
);
1579 aVertexG
.set(eOrthogonalAxis
, aVertexG
.get(eOrthogonalAxis
) + aDirection
.get(eOrthogonalAxis
) * fOrthogonalEdgeLength
);
1581 SAL_INFO( "chart2.pie.label.bestfit.inside",
1582 " beta = " << basegfx::rad2deg(fBetaRad
) );
1583 SAL_INFO( "chart2.pie.label.bestfit.inside",
1584 " theta = " << basegfx::rad2deg(fThetaRad
) );
1585 SAL_INFO( "chart2.pie.label.bestfit.inside",
1586 " fAlphaMod90 = " << fAlphaMod90
);
1587 SAL_INFO( "chart2.pie.label.bestfit.inside",
1588 " fSign = " << fSign
);
1589 SAL_INFO( "chart2.pie.label.bestfit.inside",
1590 " distance(C,P) = " << fDistanceCP
);
1591 SAL_INFO( "chart2.pie.label.bestfit.inside",
1592 " direction vector = " << aDirection
);
1593 SAL_INFO( "chart2.pie.label.bestfit.inside",
1594 " N = " << aNearestVertex
);
1595 SAL_INFO( "chart2.pie.label.bestfit.inside",
1596 " M = " << aVertexM
);
1597 SAL_INFO( "chart2.pie.label.bestfit.inside",
1598 " G = " << aVertexG
);
1600 // in order to be able to place the label inside the pie slice we need
1601 // to check that each angle between s and the ray starting from C and
1602 // passing through a b.b. vertex is less than half width of the pie slice;
1603 // when the nearest edge e crosses a Cartesian axis it is sufficient
1604 // to test only the vertices belonging to e, else we need to test
1605 // the 2 vertices that aren't either N or F. Note that if a b.b. edge
1606 // crosses a Cartesian axis then it is the nearest edge to C
1608 // check the angle between CP and CM
1609 double fAngleRad
= aPositionalVector
.angle(aVertexM
);
1610 double fAngleDeg
= NormAngle360(basegfx::rad2deg(fAngleRad
));
1611 if( fAngleDeg
> 180 ) // in case the wrong angle has been computed
1612 fAngleDeg
= 360 - fAngleDeg
;
1613 SAL_INFO( "chart2.pie.label.bestfit.inside",
1614 " angle between CP and CM: " << fAngleDeg
);
1615 if( fAngleDeg
> fHalfWidthAngleDeg
)
1620 if( ( aNearestVertex
.get(eAxis
) >= 0 && aVertexM
.get(eAxis
) <= 0 )
1621 || ( aNearestVertex
.get(eAxis
) <= 0 && aVertexM
.get(eAxis
) >= 0 ) )
1623 // check the angle between CP and CN
1624 fAngleRad
= aPositionalVector
.angle(aNearestVertex
);
1625 fAngleDeg
= NormAngle360(basegfx::rad2deg(fAngleRad
));
1626 if( fAngleDeg
> 180 ) // in case the wrong angle has been computed
1627 fAngleDeg
= 360 - fAngleDeg
;
1628 SAL_INFO( "chart2.pie.label.bestfit.inside",
1629 " angle between CP and CN: " << fAngleDeg
);
1630 if( fAngleDeg
> fHalfWidthAngleDeg
)
1637 // check the angle between CP and CG
1638 fAngleRad
= aPositionalVector
.angle(aVertexG
);
1639 fAngleDeg
= NormAngle360(basegfx::rad2deg(fAngleRad
));
1640 if( fAngleDeg
> 180 ) // in case the wrong angle has been computed
1641 fAngleDeg
= 360 - fAngleDeg
;
1642 SAL_INFO( "chart2.pie.label.bestfit.inside",
1643 " angle between CP and CG: " << fAngleDeg
);
1644 if( fAngleDeg
> fHalfWidthAngleDeg
)
1650 // compute the b.b. center respect with the pie center
1651 basegfx::B2DVector
aBBCenter(aNearestVertex
);
1652 aBBCenter
.set(eAxis
, aBBCenter
.get(eAxis
) + aDirection
.get(eAxis
) * fNearestEdgeLength
/ 2);
1653 aBBCenter
.set(eOrthogonalAxis
, aBBCenter
.get(eOrthogonalAxis
) + aDirection
.get(eOrthogonalAxis
) * fOrthogonalEdgeLength
/ 2);
1655 // compute the b.b. anchor point
1656 basegfx::B2IVector aNewAnchorPoint
= aPieCenter
;
1657 aNewAnchorPoint
.setX(aNewAnchorPoint
.getX() + floor(aBBCenter
.getX()));
1658 aNewAnchorPoint
.setY(aNewAnchorPoint
.getY() - floor(aBBCenter
.getY())); // the Y axis on the screen points downward
1660 // compute the translation vector for moving the label from the current
1661 // screen position to the new one
1662 basegfx::B2IVector aTranslationVector
= aNewAnchorPoint
- rPieLabelInfo
.aFirstPosition
;
1664 // compute the new screen position and move the label
1665 // XShape::getPosition returns the top left vertex of the b.b. of the shape
1666 awt::Point
aOldPos( rPieLabelInfo
.xLabelGroupShape
->getPosition() );
1667 awt::Point
aNewPos( aOldPos
.X
+ aTranslationVector
.getX(),
1668 aOldPos
.Y
+ aTranslationVector
.getY() );
1669 rPieLabelInfo
.xLabelGroupShape
->setPosition(aNewPos
);
1671 SAL_INFO( "chart2.pie.label.bestfit.inside",
1672 " center = " << aBBCenter
);
1673 SAL_INFO( "chart2.pie.label.bestfit.inside",
1674 " new anchor point = " << aNewAnchorPoint
);
1675 SAL_INFO( "chart2.pie.label.bestfit.inside",
1676 " translation vector = " << aTranslationVector
);
1677 SAL_INFO( "chart2.pie.label.bestfit.inside",
1678 " old position = (" << aOldPos
.X
<< "," << aOldPos
.Y
<< ")" );
1679 SAL_INFO( "chart2.pie.label.bestfit.inside",
1680 " new position = (" << aNewPos
.X
<< "," << aNewPos
.Y
<< ")" );
1687 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */