nss: upgrade to release 3.73
[LibreOffice.git] / chart2 / source / view / charttypes / PieChart.cxx
blob7bf2eaa2a89b6fac24679072f43a5024886bea2c
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
43 #include <memory>
45 using namespace ::com::sun::star;
46 using namespace ::com::sun::star::chart2;
48 namespace chart {
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
84 double mfLogicYSum;
86 /** for 3D pie chart: label z coordinate
88 double mfLogicZ;
90 /** for 3D pie chart: height
92 double mfDepth;
94 ShapeParam() :
95 mfUnitCircleStartAngleDegree(0.0),
96 mfUnitCircleWidthAngleDegree(0.0),
97 mfUnitCircleOuterRadius(0.0),
98 mfUnitCircleInnerRadius(0.0),
99 mfExplodePercentage(0.0),
100 mfLogicYSum(0.0),
101 mfLogicZ(0.0),
102 mfDepth(0.0) {}
105 namespace
107 ::basegfx::B2IRectangle lcl_getRect(const uno::Reference<drawing::XShape>& xShape)
109 ::basegfx::B2IRectangle aRect;
110 if (xShape.is())
111 aRect = BaseGFXHelper::makeRectangle(xShape->getPosition(), xShape->getSize());
112 return aRect;
115 bool lcl_isInsidePage(const awt::Point& rPos, const awt::Size& rSize, const awt::Size& rPageSize)
117 if (rPos.X < 0 || rPos.Y < 0)
118 return false;
119 if ((rPos.X + rSize.Width) > rPageSize.Width)
120 return false;
121 if ((rPos.Y + rSize.Height) > rPageSize.Height)
122 return false;
123 return true;
126 } //end anonymous namespace
128 class PiePositionHelper : public PolarPlottingPositionHelper
130 public:
131 PiePositionHelper( double fAngleDegreeOffset );
133 bool getInnerAndOuterRadius( double fCategoryX, double& fLogicInnerRadius, double& fLogicOuterRadius, bool bUseRings, double fMaxOffset ) const;
135 public:
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
157 * axis scale range).
159 bool PiePositionHelper::getInnerAndOuterRadius( double fCategoryX
160 , double& fLogicInnerRadius, double& fLogicOuterRadius
161 , bool bUseRings, double fMaxOffset ) const
163 if( !bUseRings )
164 fCategoryX = 1.0;
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() )
178 return false;
179 if( fLogicOuter <= getLogicMinX() )
180 return false;
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);
191 return true;
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 ) )
199 , m_bUseRings(false)
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() )
211 return;
215 xChartTypeProps->getPropertyValue( "UseRings") >>= m_bUseRings;
216 if( 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()
248 return true;
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;
268 //create point
269 uno::Reference< drawing::XShape > xShape;
270 if(m_nDimension==3)
272 xShape = m_pShapeFactory->createPieSegment( xTarget
273 , rParam.mfUnitCircleStartAngleDegree, rParam.mfUnitCircleWidthAngleDegree
274 , rParam.mfUnitCircleInnerRadius, rParam.mfUnitCircleOuterRadius
275 , aOffset, B3DHomMatrixToHomogenMatrix( m_pPosHelper->getUnitCartesianToScene() )
276 , rParam.mfDepth );
278 else
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 );
286 return xShape;
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.
295 return;
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,
368 0 ),
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 );
375 double fAngleDegree
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();
395 else
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
410 if( !xChild.is() )
411 return;
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
429 = 0.8
430 * (m_aAvailableOuterRect.getMaxX() - aPieLabelInfo.aFirstPosition.getX());
431 else if (fAngleDegree < 112.5 || fAngleDegree >= 247.5)
432 fTextMaximumFrameWidth = 0.8 * m_aAvailableOuterRect.getWidth();
433 else
434 fTextMaximumFrameWidth
435 = 0.8
436 * (aPieLabelInfo.aFirstPosition.getX() - m_aAvailableOuterRect.getMinX());
438 nTextMaximumFrameWidth = ceil(fTextMaximumFrameWidth);
441 nScreenValueOffsetInRadiusDirection = (m_nDimension != 3) ? 150 : 0;
442 aScreenPosition2D
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);
466 xChild.clear();
467 xChild.set(uno::Reference<container::XChild>(aPieLabelInfo.xTextShape, uno::UNO_QUERY));
468 if (!xChild.is())
469 return;
471 aPieLabelInfo.xLabelGroupShape.set(xChild->getParent(), uno::UNO_QUERY);
475 bool bShowLeaderLine = rSeries.getPropertiesOfSeries()
476 ->getPropertyValue("ShowCustomLeaderLines")
477 .get<sal_Bool>();
478 if (m_bPieLabelsAllowToMove && rSeries.isLabelCustomPos(nPointIndex) && bShowLeaderLine)
480 sal_Int32 nX1 = aPieLabelInfo.aOuterPosition.getX();
481 sal_Int32 nY1 = aPieLabelInfo.aOuterPosition.getY();
482 sal_Int32 nX2 = nX1;
483 sal_Int32 nY2 = nY1;
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;
510 if (xProp.is())
512 sal_Int32 nColor = 0;
513 xProp->getPropertyValue("CharColor") >>= nColor;
514 //automatic font color does not work for lines -> fallback to black
515 if (nColor != -1)
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()
538 return 0.5;
540 double PieChart::getMaxOffset()
542 if (!std::isnan(m_fMaxOffset))
543 // Value already cached. Use it.
544 return m_fMaxOffset;
546 m_fMaxOffset = 0.0;
547 if( m_aZSlots.empty() )
548 return m_fMaxOffset;
549 if( m_aZSlots.front().empty() )
550 return m_fMaxOffset;
552 const std::vector< std::unique_ptr<VDataSeries> >& rSeriesList( m_aZSlots.front().front().m_aSeriesVector );
553 if(rSeriesList.empty())
554 return m_fMaxOffset;
556 VDataSeries* pSeries = rSeriesList.front().get();
557 uno::Reference< beans::XPropertySet > xSeriesProp( pSeries->getPropertiesOfSeries() );
558 if( !xSeriesProp.is() )
559 return m_fMaxOffset;
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]) );
574 if(xPointProp.is())
576 fExplodePercentage=0.0;
577 xPointProp->getPropertyValue( "Offset") >>= fExplodePercentage;
578 if(fExplodePercentage>m_fMaxOffset)
579 m_fMaxOffset=fExplodePercentage;
584 return m_fMaxOffset;
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 */ )
595 return 0.0;
598 double PieChart::getMaximumYInRange( double /* fMinimumX */, double /* fMaximumX */, sal_Int32 /* nAxisIndex */ )
600 return 1.0;
603 bool PieChart::isExpandBorderToIncrementRhythm( sal_Int32 /* nDimensionIndex */ )
605 return false;
608 bool PieChart::isExpandIfValuesCloseToBorder( sal_Int32 /* nDimensionIndex */ )
610 return false;
613 bool PieChart::isExpandWideValuesToZero( sal_Int32 /* nDimensionIndex */ )
615 return false;
618 bool PieChart::isExpandNarrowValuesTowardZero( sal_Int32 /* nDimensionIndex */ )
620 return false;
623 bool PieChart::isSeparateStackingForDifferentSigns( sal_Int32 /* nDimensionIndex */ )
625 return false;
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.
637 return;
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
643 ///(xTextTarget).
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())
651 return;
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
656 ///displayed on top)
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
675 ///series;
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 )
708 ShapeParam aParam;
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
712 continue;
713 VDataSeries* pSeries = pSeriesList->front().get();
714 if(!pSeries)
715 continue;
717 bool bHasFillColorMapping = pSeries->hasPropertyMapping("FillColor");
719 /// The angle degree offset is set by the same property of the
720 /// data series.
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 );
731 if(fY<0.0)
733 //@todo warn somehow that negative values are treated as positive
735 if( std::isnan(fY) )
736 continue;
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.
742 continue;
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 );
759 if( !bIsVisible )
760 continue;
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) )
768 continue;
769 if(fLogicYValue==0.0)//@todo: continue also if the resolution is too small
770 continue;
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) );
787 if(bDoExplode) try
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 );
803 ///point color:
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 );
812 ///create data point
813 aParam.mfLogicZ = -1.0; // For 3D pie chart label position
814 uno::Reference<drawing::XShape> xPointShape =
815 createDataPoint(
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)));
828 ///create label
829 createTextLabelShape(xTextTarget, *pSeries, nPointIndex, aParam);
831 if(!bDoExplode)
833 ShapeFactory::setShapeName( xPointShape
834 , ObjectIdentifier::createPointCID( pSeries->getPointCID_Stub(), nPointIndex ) );
836 else try
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 )
858 ) );
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)
868 }//next category
869 }//next x slot
872 PieChart::PieLabelInfo::PieLabelInfo()
873 : aFirstPosition(), aOrigin(), fValue(0.0)
874 , bMovementAllowed(false), bMoved(false)
875 , bShowLeaderLine(false), pPrevious(nullptr)
876 , pNext(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
884 * false.
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)
890 return false;
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
905 ///point;
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.
923 if( bMoveHalfWay )
924 nShift/=2;
925 ///in case the `bMoveClockwise` parameter is false the direction of
926 ///`aTangentialDirection` is reversed;
927 if(!bMoveClockwise)
928 nShift*=-1;
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 ) )
936 return false;
938 xLabelGroupShape->setPosition( aNewAWTPos );
939 bMoved = true;
941 return true;
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
964 ///`pSecondBorder`;
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
976 ///previous one;
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;
996 break;
998 pCurrent = pCurrent->pNext;
1000 while( pCurrent != pStart );
1002 if( !bOverlapFound )
1003 return false;
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`;
1016 if( pFirstBorder )
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;
1029 break;
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;
1056 if( bSingleCenter )
1057 nCenterPos++;
1058 if(nCenterPos>1)
1060 pCurrent = pFirstBorder;
1061 while( --nCenterPos )
1062 pCurrent = pCurrent->pNext;
1063 pCenter = pCurrent;
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.
1070 pCurrent = pStart;
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.
1102 return 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
1125 ///reached
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();
1161 return false;
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
1174 ///reached
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();
1197 return false;
1202 return true;
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
1210 ///`AVOID_OVERLAP`.
1211 // no need to do anything when we only have one label
1212 if (m_aLabelInfoList.size() < 2)
1213 return;
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;
1222 break;
1225 if(!bMoveableFound)
1226 return;
1228 double fPageDiagonaleLength = sqrt( double(rPageSize.Width)*double(rPageSize.Width) + double(rPageSize.Height)*double(rPageSize.Height) );
1229 if( fPageDiagonaleLength == 0.0 )
1230 return;
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()));
1237 ++aIt2;
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 )
1250 nMaxIterations--;
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 )
1276 continue;
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);
1286 if( xProp.is() )
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.
1303 * Notation:
1304 * C: the pie center
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
1319 * | /s
1320 * | /
1321 * | /
1322 * | G _________________________/____________________________ F
1323 * | | /Q ..|
1324 * | | / . . |
1325 * | | / . . |
1326 * | | / . . |
1327 * | | / . . |
1328 * | | / . . |
1329 * | | / d. . |
1330 * | | / . . |
1331 * | | / . . |
1332 * | | / . . |
1333 * | | / . . |
1334 * | | / . . |
1335 * | | / . . |
1336 * | | / . \ beta . |
1337 * | |__________/._\___|_______.____________________________|
1338 * | N /P / . M
1339 * | /___/theta .
1340 * | / .
1341 * | / . r
1342 * | / .
1343 * | / .
1344 * | / .
1345 * | / .
1346 * | / .
1347 * | / .
1348 * | / .
1349 * | / .
1350 * | /\. alpha
1351 * __|/__|_____________________________________________________________
1352 * |C
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,
1391 fLogicZ ),
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",
1408 " pie sector:" );
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 )
1429 return false;
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
1441 // 0 left
1442 // 1 bottom
1443 // 2 right
1444 // 3 top
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)
1500 return false;
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
1521 ? 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
1531 double fDistanceCP;
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:
1544 // r d(P,F) d(C,P)
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 )
1617 return false;
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 )
1632 return false;
1635 else
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 )
1646 return false;
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 << ")" );
1682 return true;
1685 } //namespace chart
1687 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */