Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
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/.
8 : *
9 : * This file incorporates work covered by the following license notice:
10 : *
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 .
18 : */
19 :
20 : #include "PieChart.hxx"
21 : #include "PlottingPositionHelper.hxx"
22 : #include "AbstractShapeFactory.hxx"
23 : #include "PolarLabelPositionHelper.hxx"
24 : #include "macros.hxx"
25 : #include "CommonConverters.hxx"
26 : #include "ViewDefines.hxx"
27 : #include "ObjectIdentifier.hxx"
28 :
29 : #include <com/sun/star/chart/DataLabelPlacement.hpp>
30 : #include <com/sun/star/chart2/XColorScheme.hpp>
31 :
32 : #include <com/sun/star/container/XChild.hpp>
33 : #include <rtl/math.hxx>
34 :
35 : #include <boost/scoped_ptr.hpp>
36 :
37 : using namespace ::com::sun::star;
38 : using namespace ::com::sun::star::chart2;
39 :
40 : namespace chart {
41 :
42 : struct PieChart::ShapeParam
43 : {
44 : /** the start angle of the slice
45 : */
46 : double mfUnitCircleStartAngleDegree;
47 :
48 : /** the angle width of the slice
49 : */
50 : double mfUnitCircleWidthAngleDegree;
51 :
52 : /** the normalized outer radius of the ring the slice belongs to.
53 : */
54 : double mfUnitCircleOuterRadius;
55 :
56 : /** the normalized inner radius of the ring the slice belongs to
57 : */
58 : double mfUnitCircleInnerRadius;
59 :
60 : /** relative distance offset of a slice from the pie center;
61 : * this parameter is used for instance when the user performs manual
62 : * dragging of a slice (the drag operation is possible only for slices that
63 : * belong to the outer ring and only along the ray bisecting the slice);
64 : * the value for the given entry in the data series is obtained by the
65 : * `Offset` property attached to each entry; note that the value
66 : * provided by the `Offset` property is used both as a logical value in
67 : * `PiePositionHelper::getInnerAndOuterRadius` and as a percentage value in
68 : * the `PieChart::createDataPoint` and `PieChart::createTextLabelShape`
69 : * methods; since the logical height of a ring is always 1, this duality
70 : * does not cause any incorrect behavior;
71 : */
72 : double mfExplodePercentage;
73 :
74 : /** sum of all Y values in a single series
75 : */
76 : double mfLogicYSum;
77 :
78 : /** for 3D pie chart: label z coordinate
79 : */
80 : double mfLogicZ;
81 :
82 : /** for 3D pie chart: height
83 : */
84 : double mfDepth;
85 :
86 74 : ShapeParam() :
87 : mfUnitCircleStartAngleDegree(0.0),
88 : mfUnitCircleWidthAngleDegree(0.0),
89 : mfUnitCircleOuterRadius(0.0),
90 : mfUnitCircleInnerRadius(0.0),
91 : mfExplodePercentage(0.0),
92 : mfLogicYSum(0.0),
93 : mfLogicZ(0.0),
94 74 : mfDepth(0.0) {}
95 : };
96 :
97 : class PiePositionHelper : public PolarPlottingPositionHelper
98 : {
99 : public:
100 : PiePositionHelper( NormalAxis eNormalAxis, double fAngleDegreeOffset );
101 : virtual ~PiePositionHelper();
102 :
103 : bool getInnerAndOuterRadius( double fCategoryX, double& fLogicInnerRadius, double& fLogicOuterRadius, bool bUseRings, double fMaxOffset ) const;
104 :
105 : public:
106 : //Distance between different category rings, seen relative to width of a ring:
107 : double m_fRingDistance; //>=0 m_fRingDistance=1 --> distance == width
108 : };
109 :
110 37 : PiePositionHelper::PiePositionHelper( NormalAxis eNormalAxis, double fAngleDegreeOffset )
111 : : PolarPlottingPositionHelper(eNormalAxis)
112 37 : , m_fRingDistance(0.0)
113 : {
114 37 : m_fRadiusOffset = 0.0;
115 37 : m_fAngleDegreeOffset = fAngleDegreeOffset;
116 37 : }
117 :
118 74 : PiePositionHelper::~PiePositionHelper()
119 : {
120 74 : }
121 :
122 : /** Compute the outer and the inner radius for the current ring (not for the
123 : * whole donut!), in general it is:
124 : * inner_radius = (ring_index + 1) - 0.5 + max_offset,
125 : * outer_radius = (ring_index + 1) + 0.5 + max_offset.
126 : * When orientation for the radius axis is reversed these values are swapped.
127 : * (Indeed the orientation for the radius axis is always reversed!
128 : * See `PieChartTypeTemplate::adaptScales`.)
129 : * The maximum relative offset (see notes for P`ieChart::getMaxOffset`) is
130 : * added to both the inner and the outer radius.
131 : * It returns true if the ring is visible (that is not out of the radius
132 : * axis scale range).
133 : */
134 336 : bool PiePositionHelper::getInnerAndOuterRadius( double fCategoryX
135 : , double& fLogicInnerRadius, double& fLogicOuterRadius
136 : , bool bUseRings, double fMaxOffset ) const
137 : {
138 336 : if( !bUseRings )
139 304 : fCategoryX = 1.0;
140 :
141 336 : bool bIsVisible = true;
142 336 : double fLogicInner = fCategoryX -0.5+m_fRingDistance/2.0;
143 336 : double fLogicOuter = fCategoryX +0.5-m_fRingDistance/2.0;
144 :
145 336 : if( !isMathematicalOrientationRadius() )
146 : {
147 : //in this case the given getMaximumX() was not corrcect instead the minimum should have been smaller by fMaxOffset
148 : //but during getMaximumX and getMimumX we do not know the axis orientation
149 112 : fLogicInner += fMaxOffset;
150 112 : fLogicOuter += fMaxOffset;
151 : }
152 :
153 336 : if( fLogicInner >= getLogicMaxX() )
154 0 : return false;
155 336 : if( fLogicOuter <= getLogicMinX() )
156 0 : return false;
157 :
158 336 : if( fLogicInner < getLogicMinX() )
159 0 : fLogicInner = getLogicMinX();
160 336 : if( fLogicOuter > getLogicMaxX() )
161 0 : fLogicOuter = getLogicMaxX();
162 :
163 336 : fLogicInnerRadius = fLogicInner;
164 336 : fLogicOuterRadius = fLogicOuter;
165 336 : if( !isMathematicalOrientationRadius() )
166 112 : std::swap(fLogicInnerRadius,fLogicOuterRadius);
167 336 : return bIsVisible;
168 : }
169 :
170 37 : PieChart::PieChart( const uno::Reference<XChartType>& xChartTypeModel
171 : , sal_Int32 nDimensionCount
172 : , bool bExcludingPositioning )
173 : : VSeriesPlotter( xChartTypeModel, nDimensionCount )
174 37 : , m_pPosHelper( new PiePositionHelper( NormalAxis_Z, (m_nDimension==3)?0.0:90.0 ) )
175 : , m_bUseRings(false)
176 74 : , m_bSizeExcludesLabelsAndExplodedSegments(bExcludingPositioning)
177 : {
178 37 : ::rtl::math::setNan(&m_fMaxOffset);
179 :
180 37 : PlotterBase::m_pPosHelper = m_pPosHelper;
181 37 : VSeriesPlotter::m_pMainPosHelper = m_pPosHelper;
182 37 : m_pPosHelper->m_fRadiusOffset = 0.0;
183 37 : m_pPosHelper->m_fRingDistance = 0.0;
184 :
185 37 : uno::Reference< beans::XPropertySet > xChartTypeProps( xChartTypeModel, uno::UNO_QUERY );
186 37 : if( xChartTypeProps.is() ) try
187 : {
188 37 : xChartTypeProps->getPropertyValue( "UseRings") >>= m_bUseRings;
189 37 : if( m_bUseRings )
190 : {
191 4 : m_pPosHelper->m_fRadiusOffset = 1.0;
192 4 : if( nDimensionCount==3 )
193 0 : m_pPosHelper->m_fRingDistance = 0.1;
194 : }
195 : }
196 0 : catch( const uno::Exception& e )
197 : {
198 : ASSERT_EXCEPTION( e );
199 37 : }
200 37 : }
201 :
202 111 : PieChart::~PieChart()
203 : {
204 37 : delete m_pPosHelper;
205 74 : }
206 :
207 37 : void PieChart::setScales( const std::vector< ExplicitScaleData >& rScales, bool /* bSwapXAndYAxis */ )
208 : {
209 : OSL_ENSURE(m_nDimension<=static_cast<sal_Int32>(rScales.size()),"Dimension of Plotter does not fit two dimension of given scale sequence");
210 37 : m_pPosHelper->setScales( rScales, true );
211 37 : }
212 :
213 37 : drawing::Direction3D PieChart::getPreferredDiagramAspectRatio() const
214 : {
215 37 : if( m_nDimension == 3 )
216 6 : return drawing::Direction3D(1,1,0.10);
217 31 : return drawing::Direction3D(1,1,1);
218 : }
219 :
220 0 : bool PieChart::keepAspectRatio() const
221 : {
222 0 : if( m_nDimension == 3 )
223 0 : return false;
224 0 : return true;
225 : }
226 :
227 37 : bool PieChart::shouldSnapRectToUsedArea()
228 : {
229 37 : return true;
230 : }
231 :
232 336 : uno::Reference< drawing::XShape > PieChart::createDataPoint(
233 : const uno::Reference<drawing::XShapes>& xTarget,
234 : const uno::Reference<beans::XPropertySet>& xObjectProperties,
235 : tPropertyNameValueMap* pOverwritePropertiesMap,
236 : const ShapeParam& rParam )
237 : {
238 : //transform position:
239 336 : drawing::Direction3D aOffset;
240 336 : if (!::rtl::math::approxEqual(rParam.mfExplodePercentage, 0.0))
241 : {
242 72 : double fAngle = rParam.mfUnitCircleStartAngleDegree + rParam.mfUnitCircleWidthAngleDegree/2.0;
243 72 : double fRadius = (rParam.mfUnitCircleOuterRadius-rParam.mfUnitCircleInnerRadius)*rParam.mfExplodePercentage;
244 72 : drawing::Position3D aOrigin = m_pPosHelper->transformUnitCircleToScene(0, 0, rParam.mfLogicZ);
245 72 : drawing::Position3D aNewOrigin = m_pPosHelper->transformUnitCircleToScene(fAngle, fRadius, rParam.mfLogicZ);
246 72 : aOffset = aNewOrigin - aOrigin;
247 : }
248 :
249 : //create point
250 336 : uno::Reference< drawing::XShape > xShape(0);
251 336 : if(m_nDimension==3)
252 : {
253 336 : xShape = m_pShapeFactory->createPieSegment( xTarget
254 : , rParam.mfUnitCircleStartAngleDegree, rParam.mfUnitCircleWidthAngleDegree
255 : , rParam.mfUnitCircleInnerRadius, rParam.mfUnitCircleOuterRadius
256 168 : , aOffset, B3DHomMatrixToHomogenMatrix( m_pPosHelper->getUnitCartesianToScene() )
257 168 : , rParam.mfDepth );
258 : }
259 : else
260 : {
261 756 : xShape = m_pShapeFactory->createPieSegment2D( xTarget
262 : , rParam.mfUnitCircleStartAngleDegree, rParam.mfUnitCircleWidthAngleDegree
263 : , rParam.mfUnitCircleInnerRadius, rParam.mfUnitCircleOuterRadius
264 756 : , aOffset, B3DHomMatrixToHomogenMatrix( m_pPosHelper->getUnitCartesianToScene() ) );
265 : }
266 336 : setMappedProperties( xShape, xObjectProperties, PropertyMapper::getPropertyNameMapForFilledSeriesProperties(), pOverwritePropertiesMap );
267 336 : return xShape;
268 : }
269 :
270 336 : void PieChart::createTextLabelShape(
271 : const uno::Reference<drawing::XShapes>& xTextTarget,
272 : VDataSeries& rSeries, sal_Int32 nPointIndex, ShapeParam& rParam )
273 : {
274 336 : if (!rSeries.getDataPointLabelIfLabel(nPointIndex))
275 : // There is no text label for this data point. Nothing to do.
276 578 : return;
277 :
278 : ///by using the `mfExplodePercentage` parameter a normalized offset is added
279 : ///to both normalized radii. (See notes for
280 : ///`PolarPlottingPositionHelper::transformToRadius`, especially example 3,
281 : ///and related comments).
282 94 : if (!rtl::math::approxEqual(rParam.mfExplodePercentage, 0.0))
283 : {
284 52 : double fExplodeOffset = (rParam.mfUnitCircleOuterRadius-rParam.mfUnitCircleInnerRadius)*rParam.mfExplodePercentage;
285 52 : rParam.mfUnitCircleInnerRadius += fExplodeOffset;
286 52 : rParam.mfUnitCircleOuterRadius += fExplodeOffset;
287 : }
288 :
289 : ///get the required label placement type. Available placements are
290 : ///`AVOID_OVERLAP`, `CENTER`, `OUTSIDE` and `INSIDE`;
291 : sal_Int32 nLabelPlacement = rSeries.getLabelPlacement(
292 94 : nPointIndex, m_xChartTypeModel, m_nDimension, m_pPosHelper->isSwapXAndY());
293 :
294 : ///when the placement is of `AVOID_OVERLAP` type a later rearrangement of
295 : ///the label position is allowed; the `createTextLabelShape` treats the
296 : ///`AVOID_OVERLAP` as if it was of `CENTER` type;
297 :
298 : //AVOID_OVERLAP is in fact "Best fit" in the UI.
299 94 : bool bMovementAllowed = ( nLabelPlacement == ::com::sun::star::chart::DataLabelPlacement::AVOID_OVERLAP );
300 94 : if( bMovementAllowed )
301 : // Use center for "Best fit" for now. In the future we
302 : // may want to implement a real best fit algorithm.
303 : // But center is good enough, and close to what Excel
304 : // does.
305 72 : nLabelPlacement = ::com::sun::star::chart::DataLabelPlacement::CENTER;
306 :
307 : ///for `OUTSIDE` (`INSIDE`) label placements an offset of 150 (-150), in the
308 : ///radius direction, is added to the final screen position of the label
309 : ///anchor point. This is required in order to ensure that the label is
310 : ///completely outside (inside) the related slice. Indeed this value should
311 : ///depend on the font height;
312 : ///pay attention: 150 is not a big offset, in fact the screen position
313 : ///coordinates for label anchor points are in the 10000-20000 range, hence
314 : ///these are coordinates of a virtual screen and 150 is a small value;
315 94 : LabelAlignment eAlignment(LABEL_ALIGN_CENTER);
316 94 : sal_Int32 nScreenValueOffsetInRadiusDirection = 0 ;
317 94 : if( nLabelPlacement == ::com::sun::star::chart::DataLabelPlacement::OUTSIDE )
318 0 : nScreenValueOffsetInRadiusDirection = (3!=m_nDimension) ? 150 : 0;//todo maybe calculate this font height dependent
319 94 : else if( nLabelPlacement == ::com::sun::star::chart::DataLabelPlacement::INSIDE )
320 0 : nScreenValueOffsetInRadiusDirection = (3!=m_nDimension) ? -150 : 0;//todo maybe calculate this font height dependent
321 :
322 : ///the scene position of the label anchor point is calculated (see notes for
323 : ///`PolarLabelPositionHelper::getLabelScreenPositionAndAlignmentForUnitCircleValues`),
324 : ///and immediately transformed into the screen position.
325 94 : PolarLabelPositionHelper aPolarPosHelper(m_pPosHelper,m_nDimension,m_xLogicTarget,m_pShapeFactory);
326 : awt::Point aScreenPosition2D(
327 : aPolarPosHelper.getLabelScreenPositionAndAlignmentForUnitCircleValues(eAlignment, nLabelPlacement
328 : , rParam.mfUnitCircleStartAngleDegree, rParam.mfUnitCircleWidthAngleDegree
329 94 : , rParam.mfUnitCircleInnerRadius, rParam.mfUnitCircleOuterRadius, rParam.mfLogicZ+0.5, 0 ));
330 :
331 : ///the screen position of the pie/donut center is calculated.
332 188 : PieLabelInfo aPieLabelInfo;
333 94 : aPieLabelInfo.aFirstPosition = basegfx::B2IVector( aScreenPosition2D.X, aScreenPosition2D.Y );
334 94 : awt::Point aOrigin( aPolarPosHelper.transformSceneToScreenPosition( m_pPosHelper->transformUnitCircleToScene( 0.0, 0.0, rParam.mfLogicZ+1.0 ) ) );
335 94 : aPieLabelInfo.aOrigin = basegfx::B2IVector( aOrigin.X, aOrigin.Y );
336 :
337 : ///add a scaling independent Offset if requested
338 94 : if( nScreenValueOffsetInRadiusDirection != 0)
339 : {
340 0 : basegfx::B2IVector aDirection( aScreenPosition2D.X- aOrigin.X, aScreenPosition2D.Y- aOrigin.Y );
341 0 : aDirection.setLength(nScreenValueOffsetInRadiusDirection);
342 0 : aScreenPosition2D.X += aDirection.getX();
343 0 : aScreenPosition2D.Y += aDirection.getY();
344 : }
345 :
346 : ///the text shape for the label is created
347 94 : double nVal = rSeries.getYValue(nPointIndex);
348 188 : aPieLabelInfo.xTextShape = createDataLabel(
349 94 : xTextTarget, rSeries, nPointIndex, nVal, rParam.mfLogicYSum, aScreenPosition2D, eAlignment);
350 :
351 : ///a new `PieLabelInfo` instance is initialized with all the info related to
352 : ///the current label in order to simplify later label position rearrangement;
353 188 : uno::Reference< container::XChild > xChild( aPieLabelInfo.xTextShape, uno::UNO_QUERY );
354 94 : if( xChild.is() )
355 94 : aPieLabelInfo.xLabelGroupShape = uno::Reference<drawing::XShape>( xChild->getParent(), uno::UNO_QUERY );
356 94 : aPieLabelInfo.fValue = nVal;
357 94 : aPieLabelInfo.bMovementAllowed = bMovementAllowed;
358 94 : aPieLabelInfo.bMoved= false;
359 94 : aPieLabelInfo.xTextTarget = xTextTarget;
360 :
361 94 : if (bMovementAllowed)
362 : {
363 72 : performLabelBestFit(rParam, aPieLabelInfo);
364 : }
365 :
366 :
367 188 : m_aLabelInfoList.push_back(aPieLabelInfo);
368 :
369 : }
370 :
371 66 : void PieChart::addSeries( VDataSeries* pSeries, sal_Int32 /* zSlot */, sal_Int32 /* xSlot */, sal_Int32 /* ySlot */ )
372 : {
373 66 : VSeriesPlotter::addSeries( pSeries, 0, -1, 0 );
374 66 : }
375 :
376 37 : double PieChart::getMinimumX()
377 : {
378 37 : return 0.5;
379 : }
380 373 : double PieChart::getMaxOffset()
381 : {
382 373 : if (!::rtl::math::isNan(m_fMaxOffset))
383 : // Value already cached. Use it.
384 262 : return m_fMaxOffset;
385 :
386 111 : m_fMaxOffset = 0.0;
387 111 : if( m_aZSlots.size()<=0 )
388 0 : return m_fMaxOffset;
389 111 : if( m_aZSlots[0].size()<=0 )
390 0 : return m_fMaxOffset;
391 :
392 111 : const ::std::vector< VDataSeries* >& rSeriesList( m_aZSlots[0][0].m_aSeriesVector );
393 111 : if( rSeriesList.size()<=0 )
394 0 : return m_fMaxOffset;
395 :
396 111 : VDataSeries* pSeries = rSeriesList[0];
397 111 : uno::Reference< beans::XPropertySet > xSeriesProp( pSeries->getPropertiesOfSeries() );
398 111 : if( !xSeriesProp.is() )
399 0 : return m_fMaxOffset;
400 :
401 111 : double fExplodePercentage=0.0;
402 111 : xSeriesProp->getPropertyValue( "Offset") >>= fExplodePercentage;
403 111 : if(fExplodePercentage>m_fMaxOffset)
404 12 : m_fMaxOffset=fExplodePercentage;
405 :
406 111 : if(!m_bSizeExcludesLabelsAndExplodedSegments)
407 : {
408 111 : uno::Sequence< sal_Int32 > aAttributedDataPointIndexList;
409 111 : if( xSeriesProp->getPropertyValue( "AttributedDataPoints" ) >>= aAttributedDataPointIndexList )
410 : {
411 558 : for(sal_Int32 nN=aAttributedDataPointIndexList.getLength();nN--;)
412 : {
413 336 : uno::Reference< beans::XPropertySet > xPointProp( pSeries->getPropertiesOfPoint(aAttributedDataPointIndexList[nN]) );
414 336 : if(xPointProp.is())
415 : {
416 336 : fExplodePercentage=0.0;
417 336 : xPointProp->getPropertyValue( "Offset") >>= fExplodePercentage;
418 336 : if(fExplodePercentage>m_fMaxOffset)
419 3 : m_fMaxOffset=fExplodePercentage;
420 : }
421 336 : }
422 111 : }
423 : }
424 111 : return m_fMaxOffset;
425 : }
426 37 : double PieChart::getMaximumX()
427 : {
428 37 : double fMaxOffset = getMaxOffset();
429 37 : if( m_aZSlots.size()>0 && m_bUseRings)
430 4 : return m_aZSlots[0].size()+0.5+fMaxOffset;
431 33 : return 1.5+fMaxOffset;
432 : }
433 37 : double PieChart::getMinimumYInRange( double /* fMinimumX */, double /* fMaximumX */, sal_Int32 /* nAxisIndex */ )
434 : {
435 37 : return 0.0;
436 : }
437 :
438 37 : double PieChart::getMaximumYInRange( double /* fMinimumX */, double /* fMaximumX */, sal_Int32 /* nAxisIndex */ )
439 : {
440 37 : return 1.0;
441 : }
442 :
443 80 : bool PieChart::isExpandBorderToIncrementRhythm( sal_Int32 /* nDimensionIndex */ )
444 : {
445 80 : return false;
446 : }
447 :
448 80 : bool PieChart::isExpandIfValuesCloseToBorder( sal_Int32 /* nDimensionIndex */ )
449 : {
450 80 : return false;
451 : }
452 :
453 80 : bool PieChart::isExpandWideValuesToZero( sal_Int32 /* nDimensionIndex */ )
454 : {
455 80 : return false;
456 : }
457 :
458 80 : bool PieChart::isExpandNarrowValuesTowardZero( sal_Int32 /* nDimensionIndex */ )
459 : {
460 80 : return false;
461 : }
462 :
463 0 : bool PieChart::isSeparateStackingForDifferentSigns( sal_Int32 /* nDimensionIndex */ )
464 : {
465 0 : return false;
466 : }
467 :
468 74 : void PieChart::createShapes()
469 : {
470 : ///a ZSlot is a vector< vector< VDataSeriesGroup > >. There is only one
471 : ///ZSlot: m_aZSlots[0] which has a number of elements equal to the total
472 : ///number of data series (in fact, even if m_aZSlots[0][i] is an object of
473 : ///type `VDataSeriesGroup`, in the current implementation, there is only one
474 : ///data series in each data series group).
475 74 : if (m_aZSlots.empty())
476 : // No series to plot.
477 0 : return;
478 :
479 : ///m_xLogicTarget is where the group of all data series shapes (e.g. a pie
480 : ///slice) is added (xSeriesTarget);
481 :
482 : ///m_xFinalTarget is where the group of all text shapes (labels) is added
483 : ///(xTextTarget).
484 :
485 : ///both have been already created and added to the same root shape
486 : ///( a member of a VDiagram object); this initialization occurs in
487 : ///`ChartView::impl_createDiagramAndContent`.
488 :
489 : OSL_ENSURE(m_pShapeFactory && m_xLogicTarget.is() && m_xFinalTarget.is(), "PieChart is not properly initialized.");
490 74 : if (!m_pShapeFactory || !m_xLogicTarget.is() || !m_xFinalTarget.is())
491 0 : return;
492 :
493 : ///the text labels should be always on top of the other series shapes
494 : ///therefore create an own group for the texts to move them to front
495 : ///(because the text group is created after the series group the texts are
496 : ///displayed on top)
497 : uno::Reference< drawing::XShapes > xSeriesTarget(
498 74 : createGroupShape( m_xLogicTarget,OUString() ));
499 : uno::Reference< drawing::XShapes > xTextTarget(
500 148 : m_pShapeFactory->createGroup2D( m_xFinalTarget,OUString() ));
501 : //check necessary here that different Y axis can not be stacked in the same group? ... hm?
502 :
503 : ///pay attention that the `m_bSwapXAndY` parameter used by the polar
504 : ///plotting position helper is always set to true for pie/donut charts
505 : ///(see PieChart::setScales). This fact causes that `createShapes` expects
506 : ///that the radius axis scale is the one with index 0 and the angle axis
507 : ///scale is the one with index 1.
508 :
509 74 : ::std::vector< VDataSeriesGroup >::iterator aXSlotIter = m_aZSlots[0].begin();
510 74 : const ::std::vector< VDataSeriesGroup >::const_iterator aXSlotEnd = m_aZSlots[0].end();
511 :
512 : ///m_bUseRings == true if chart type is `donut`, == false if chart type is
513 : ///`pie`; if the chart is of `donut` type we have as many rings as many data
514 : ///series, else we have a single ring (a pie) representing the first data
515 : ///series;
516 : ///for what I can see the radius axis orientation is always reversed and
517 : ///the angle axis orientation is always non-reversed;
518 : ///the radius axis scale range is [0.5, number of rings + 0.5 + max_offset],
519 : ///the angle axis scale range is [0, 1]. The max_offset parameter is used
520 : ///for exploded pie chart and its value is 0.5.
521 :
522 : ///the `explodeable` ring is the first one except when the radius axis
523 : ///orientation is reversed (always!?) and we are dealing with a donut: in
524 : ///such a case the `explodeable` ring is the last one.
525 74 : ::std::vector< VDataSeriesGroup >::size_type nExplodeableSlot = 0;
526 74 : if( m_pPosHelper->isMathematicalOrientationRadius() && m_bUseRings )
527 8 : nExplodeableSlot = m_aZSlots[0].size()-1;
528 :
529 74 : m_aLabelInfoList.clear();
530 74 : ::rtl::math::setNan(&m_fMaxOffset);
531 74 : sal_Int32 n3DRelativeHeight = 100;
532 148 : uno::Reference< beans::XPropertySet > xPropertySet( m_xChartTypeModel, uno::UNO_QUERY );
533 74 : if ( (m_nDimension==3) && xPropertySet.is())
534 : {
535 : try
536 : {
537 12 : uno::Any aAny = xPropertySet->getPropertyValue( "3DRelativeHeight" );
538 12 : aAny >>= n3DRelativeHeight;
539 : }
540 0 : catch (const uno::Exception&) { }
541 : }
542 : ///iterate over each xslot, that is on each data series (there is
543 : ///only one data series in each data series group!); note that if the chart
544 : ///type is a pie the loop iterates only over the first data series
545 : ///(m_bUseRings||fSlotX<0.5)
546 148 : for( double fSlotX=0; aXSlotIter != aXSlotEnd && (m_bUseRings||fSlotX<0.5 ); ++aXSlotIter, fSlotX+=1.0 )
547 : {
548 74 : ShapeParam aParam;
549 :
550 74 : ::std::vector< VDataSeries* >* pSeriesList = &(aXSlotIter->m_aSeriesVector);
551 74 : if( pSeriesList->size()<=0 )//there should be only one series in each x slot
552 0 : continue;
553 74 : VDataSeries* pSeries = (*pSeriesList)[0];
554 74 : if(!pSeries)
555 0 : continue;
556 :
557 74 : bool bHasFillColorMapping = pSeries->hasPropertyMapping("FillColor");
558 :
559 : /// The angle degree offset is set by the same property of the
560 : /// data series.
561 : /// Counter-clockwise offset from the 3 o'clock position.
562 74 : m_pPosHelper->m_fAngleDegreeOffset = pSeries->getStartingAngle();
563 :
564 : ///iterate through all points to get the sum of all entries of
565 : ///the current data series
566 74 : sal_Int32 nPointIndex=0;
567 74 : sal_Int32 nPointCount=pSeries->getTotalPointCount();
568 410 : for( nPointIndex = 0; nPointIndex < nPointCount; nPointIndex++ )
569 : {
570 336 : double fY = pSeries->getYValue( nPointIndex );
571 : if(fY<0.0)
572 : {
573 : //@todo warn somehow that negative values are treated as positive
574 : }
575 336 : if( ::rtl::math::isNan(fY) )
576 0 : continue;
577 336 : aParam.mfLogicYSum += fabs(fY);
578 : }
579 :
580 74 : if (aParam.mfLogicYSum == 0.0)
581 : // Total sum of all Y values in this series is zero. Skip the whole series.
582 0 : continue;
583 :
584 74 : double fLogicYForNextPoint = 0.0;
585 : ///iterate through all points to create shapes
586 410 : for( nPointIndex = 0; nPointIndex < nPointCount; nPointIndex++ )
587 : {
588 : double fLogicInnerRadius, fLogicOuterRadius;
589 :
590 : ///compute the maximum relative distance offset of the current slice
591 : ///from the pie center
592 : ///it is worth noting that after the first invocation the maximum
593 : ///offset value is cached, so it is evaluated only once per each
594 : ///call to `createShapes`
595 336 : double fOffset = getMaxOffset();
596 :
597 : ///compute the outer and the inner radius for the current ring slice
598 336 : bool bIsVisible = m_pPosHelper->getInnerAndOuterRadius( fSlotX+1.0, fLogicInnerRadius, fLogicOuterRadius, m_bUseRings, fOffset );
599 336 : if( !bIsVisible )
600 0 : continue;
601 :
602 336 : aParam.mfDepth = this->getTransformedDepth() * (n3DRelativeHeight / 100.0);
603 :
604 336 : uno::Reference< drawing::XShapes > xSeriesGroupShape_Shapes = getSeriesGroupShape(pSeries, xSeriesTarget);
605 : ///collect data point information (logic coordinates, style ):
606 336 : double fLogicYValue = fabs(pSeries->getYValue( nPointIndex ));
607 336 : if( ::rtl::math::isNan(fLogicYValue) )
608 0 : continue;
609 336 : if(fLogicYValue==0.0)//@todo: continue also if the resolution to small
610 0 : continue;
611 336 : double fLogicYPos = fLogicYForNextPoint;
612 336 : fLogicYForNextPoint += fLogicYValue;
613 :
614 672 : uno::Reference< beans::XPropertySet > xPointProperties = pSeries->getPropertiesOfPoint( nPointIndex );
615 :
616 : //iterate through all subsystems to create partial points
617 : {
618 : //logic values on angle axis:
619 336 : double fLogicStartAngleValue = fLogicYPos / aParam.mfLogicYSum;
620 336 : double fLogicEndAngleValue = (fLogicYPos+fLogicYValue) / aParam.mfLogicYSum;
621 :
622 : ///note that the explode percentage is set to the `Offset`
623 : ///property of the current data series entry only for slices
624 : ///belonging to the outer ring
625 336 : aParam.mfExplodePercentage = 0.0;
626 336 : bool bDoExplode = ( nExplodeableSlot == static_cast< ::std::vector< VDataSeriesGroup >::size_type >(fSlotX) );
627 336 : if(bDoExplode) try
628 : {
629 336 : xPointProperties->getPropertyValue( "Offset") >>= aParam.mfExplodePercentage;
630 : }
631 0 : catch( const uno::Exception& e )
632 : {
633 : ASSERT_EXCEPTION( e );
634 : }
635 :
636 : ///see notes for `PolarPlottingPositionHelper` methods
637 : ///transform to unit circle:
638 336 : aParam.mfUnitCircleWidthAngleDegree = m_pPosHelper->getWidthAngleDegree( fLogicStartAngleValue, fLogicEndAngleValue );
639 336 : aParam.mfUnitCircleStartAngleDegree = m_pPosHelper->transformToAngleDegree( fLogicStartAngleValue );
640 336 : aParam.mfUnitCircleInnerRadius = m_pPosHelper->transformToRadius( fLogicInnerRadius );
641 336 : aParam.mfUnitCircleOuterRadius = m_pPosHelper->transformToRadius( fLogicOuterRadius );
642 :
643 : ///point color:
644 336 : boost::scoped_ptr< tPropertyNameValueMap > apOverwritePropertiesMap(NULL);
645 336 : if (!pSeries->hasPointOwnColor(nPointIndex) && m_xColorScheme.is())
646 : {
647 112 : apOverwritePropertiesMap.reset( new tPropertyNameValueMap() );
648 224 : (*apOverwritePropertiesMap)["FillColor"] = uno::makeAny(
649 224 : m_xColorScheme->getColorByIndex( nPointIndex ));
650 : }
651 :
652 : ///create data point
653 336 : aParam.mfLogicZ = -1.0; // For 3D pie chart label position
654 : uno::Reference<drawing::XShape> xPointShape =
655 : createDataPoint(
656 672 : xSeriesGroupShape_Shapes, xPointProperties, apOverwritePropertiesMap.get(), aParam);
657 :
658 336 : if(bHasFillColorMapping)
659 : {
660 0 : double nPropVal = pSeries->getValueByProperty(nPointIndex, "FillColor");
661 0 : if(!rtl::math::isNan(nPropVal))
662 : {
663 0 : uno::Reference< beans::XPropertySet > xProps( xPointShape, uno::UNO_QUERY_THROW );
664 0 : xProps->setPropertyValue("FillColor", uno::makeAny(static_cast<sal_Int32>( nPropVal)));
665 : }
666 : }
667 :
668 : ///create label
669 336 : createTextLabelShape(xTextTarget, *pSeries, nPointIndex, aParam);
670 :
671 336 : if(!bDoExplode)
672 : {
673 : AbstractShapeFactory::setShapeName( xPointShape
674 0 : , ObjectIdentifier::createPointCID( pSeries->getPointCID_Stub(), nPointIndex ) );
675 : }
676 : else try
677 : {
678 : ///enable dragging of outer segments
679 :
680 336 : double fAngle = aParam.mfUnitCircleStartAngleDegree + aParam.mfUnitCircleWidthAngleDegree/2.0;
681 336 : double fMaxDeltaRadius = aParam.mfUnitCircleOuterRadius-aParam.mfUnitCircleInnerRadius;
682 336 : drawing::Position3D aOrigin = m_pPosHelper->transformUnitCircleToScene( fAngle, aParam.mfUnitCircleOuterRadius, aParam.mfLogicZ );
683 336 : drawing::Position3D aNewOrigin = m_pPosHelper->transformUnitCircleToScene( fAngle, aParam.mfUnitCircleOuterRadius + fMaxDeltaRadius, aParam.mfLogicZ );
684 :
685 336 : sal_Int32 nOffsetPercent( static_cast<sal_Int32>(aParam.mfExplodePercentage * 100.0) );
686 :
687 : awt::Point aMinimumPosition( PlottingPositionHelper::transformSceneToScreenPosition(
688 336 : aOrigin, m_xLogicTarget, m_pShapeFactory, m_nDimension ) );
689 : awt::Point aMaximumPosition( PlottingPositionHelper::transformSceneToScreenPosition(
690 336 : aNewOrigin, m_xLogicTarget, m_pShapeFactory, m_nDimension ) );
691 :
692 : //enable draging of piesegments
693 : OUString aPointCIDStub( ObjectIdentifier::createSeriesSubObjectStub( OBJECTTYPE_DATA_POINT
694 : , pSeries->getSeriesParticle()
695 336 : , ObjectIdentifier::getPieSegmentDragMethodServiceName()
696 : , ObjectIdentifier::createPieSegmentDragParameterString(
697 : nOffsetPercent, aMinimumPosition, aMaximumPosition )
698 672 : ) );
699 :
700 : AbstractShapeFactory::setShapeName( xPointShape
701 336 : , ObjectIdentifier::createPointCID( aPointCIDStub, nPointIndex ) );
702 : }
703 0 : catch( const uno::Exception& e )
704 : {
705 : ASSERT_EXCEPTION( e );
706 336 : }
707 : }//next series in x slot (next y slot)
708 336 : }//next category
709 74 : }//next x slot
710 : }
711 :
712 : namespace
713 : {
714 :
715 529 : ::basegfx::B2IRectangle lcl_getRect( const uno::Reference< drawing::XShape >& xShape )
716 : {
717 529 : ::basegfx::B2IRectangle aRect;
718 529 : if( xShape.is() )
719 529 : aRect = BaseGFXHelper::makeRectangle(xShape->getPosition(),xShape->getSize() );
720 529 : return aRect;
721 : }
722 :
723 25 : bool lcl_isInsidePage( const awt::Point& rPos, const awt::Size& rSize, const awt::Size& rPageSize )
724 : {
725 25 : if( rPos.X < 0 || rPos.Y < 0 )
726 0 : return false;
727 25 : if( (rPos.X + rSize.Width) > rPageSize.Width )
728 0 : return false;
729 25 : if( (rPos.Y + rSize.Height) > rPageSize.Height )
730 0 : return false;
731 25 : return true;
732 : }
733 :
734 : inline
735 95 : double lcl_radToDeg(double fAngleRad)
736 : {
737 95 : return (fAngleRad / M_PI) * 180.0;
738 : }
739 :
740 : inline
741 72 : double lcl_degToRad(double fAngleDeg)
742 : {
743 72 : return (fAngleDeg / 180) * M_PI;
744 : }
745 :
746 : inline
747 311 : double lcl_getDegAngleInStandardRange(double fAngle)
748 : {
749 656 : while( fAngle < 0.0 )
750 34 : fAngle += 360.0;
751 630 : while( fAngle >= 360.0 )
752 8 : fAngle -= 360.0;
753 311 : return fAngle;
754 : }
755 :
756 : }//end anonymous namespace
757 :
758 94 : PieChart::PieLabelInfo::PieLabelInfo()
759 : : xTextShape(0), xLabelGroupShape(0), aFirstPosition(), aOrigin(), fValue(0.0)
760 94 : , bMovementAllowed(false), bMoved(false), xTextTarget(0), pPrevious(0),pNext(0)
761 : {
762 94 : }
763 :
764 : /** In case this label and the passed label overlap the routine moves this
765 : * label in order to fix the issue. After the label position has been
766 : * rearranged it is checked that the moved label is still inside the page
767 : * document, if the test is positive the routine returns true else returns
768 : * false.
769 : */
770 46 : bool PieChart::PieLabelInfo::moveAwayFrom( const PieChart::PieLabelInfo* pFix, const awt::Size& rPageSize, bool bMoveHalfWay, bool bMoveClockwise, bool bAlternativeMoveDirection )
771 : {
772 : //return true if the move was successful
773 46 : if(!this->bMovementAllowed)
774 0 : return false;
775 :
776 46 : const sal_Int32 nLabelDistanceX = rPageSize.Width/50;
777 46 : const sal_Int32 nLabelDistanceY = rPageSize.Height/50;
778 :
779 : ///compute the rectangle representing the intersection of the label bounding
780 : ///boxes (`aOverlap`).
781 46 : ::basegfx::B2IRectangle aOverlap( lcl_getRect( this->xLabelGroupShape ) );
782 46 : aOverlap.intersect( lcl_getRect( pFix->xLabelGroupShape ) );
783 46 : if( !aOverlap.isEmpty() )
784 : {
785 : (void)bAlternativeMoveDirection;//todo
786 :
787 : ///the label is shifted along the direction orthogonal to the vector
788 : ///starting at the pie/donut center and ending at this label anchor
789 : ///point;
790 :
791 : ///named `aTangentialDirection` the unit vector related to such a
792 : ///direction, the magnitude of the shift along such a direction is
793 : ///calculated in this way: if the horizontal component of
794 : ///`aTangentialDirection` is greater than the vertical component,
795 : ///the magnitude of the shift is equal to `aOverlap.Width` else to
796 : ///`aOverlap.Height`;
797 25 : basegfx::B2IVector aRadiusDirection = this->aFirstPosition - this->aOrigin;
798 25 : aRadiusDirection.setLength(1.0);
799 50 : basegfx::B2IVector aTangentialDirection( -aRadiusDirection.getY(), aRadiusDirection.getX() );
800 25 : bool bShiftHorizontal = abs(aTangentialDirection.getX()) > abs(aTangentialDirection.getY());
801 25 : sal_Int32 nShift = bShiftHorizontal ? static_cast<sal_Int32>(aOverlap.getWidth()) : static_cast<sal_Int32>(aOverlap.getHeight());
802 : ///the magnitude of the shift is also increased by 1/50-th of the width
803 : ///or the height of the document page;
804 25 : nShift += (bShiftHorizontal ? nLabelDistanceX : nLabelDistanceY);
805 : ///in case the `bMoveHalfWay` parameter is true the magnitude of
806 : ///the shift is halved.
807 25 : if( bMoveHalfWay )
808 8 : nShift/=2;
809 : ///in case the `bMoveClockwise` parameter is false the direction of
810 : ///`aTangentialDirection` is reversed;
811 25 : if(!bMoveClockwise)
812 10 : nShift*=-1;
813 25 : awt::Point aOldPos( this->xLabelGroupShape->getPosition() );
814 50 : basegfx::B2IVector aNewPos = basegfx::B2IVector( aOldPos.X, aOldPos.Y ) + nShift*aTangentialDirection;
815 :
816 : ///a final check is performed in order to be sure that the moved label
817 : ///is still inside the page document;
818 25 : awt::Point aNewAWTPos( aNewPos.getX(), aNewPos.getY() );
819 25 : if( !lcl_isInsidePage( aNewAWTPos, this->xLabelGroupShape->getSize(), rPageSize ) )
820 0 : return false;
821 :
822 25 : this->xLabelGroupShape->setPosition( aNewAWTPos );
823 50 : this->bMoved = true;
824 : }
825 46 : return true;
826 :
827 : ///note that no further test is performed in order to check that the
828 : ///overlap is really fixed: this result is surely achieved if the shift
829 : ///would occur in the horizontal or vertical direction (since, in such a
830 : ///direction, the magnitude of the shift would be greater than the length
831 : ///of the overlap), but in general this is not true;
832 : ///adding a constant term equal to 1/50-th of the width or the height of
833 : ///the document page increases the probability of success, anyway it is
834 : ///worth noting that the method can return true even if the overlap issue
835 : ///is not (completely) fixed;
836 : }
837 :
838 0 : void PieChart::resetLabelPositionsToPreviousState()
839 : {
840 0 : std::vector< PieLabelInfo >::iterator aIt = m_aLabelInfoList.begin();
841 0 : std::vector< PieLabelInfo >::const_iterator aEnd = m_aLabelInfoList.end();
842 0 : for( ;aIt!=aEnd; ++aIt )
843 0 : aIt->xLabelGroupShape->setPosition(aIt->aPreviousPosition);
844 0 : }
845 :
846 18 : bool PieChart::detectLabelOverlapsAndMove( const awt::Size& rPageSize )
847 : {
848 : ///the routine tries to individuate a chain of overlapping labels and
849 : ///assigns the first and the last of them to `pFirstBorder` and
850 : ///`pSecondBorder`;
851 : ///this result is achieved by performing two consecutive while loop.
852 :
853 : ///find borders of a group of overlapping labels
854 :
855 : ///a first while loop is started on the collection of `PieLabelInfo` objects;
856 : ///the bounding box of each label is checked for overlap against the bounding
857 : ///box of the previous and of the next label;
858 : ///when an overlap is found `bOverlapFound` is set to true, however the
859 : ///iteration is break only if the overlap occurs against only the next label
860 : ///and not against the previous label: so we exit from the loop whenever an
861 : ///overlap occurs except when the loop initial label overlaps with the
862 : ///previous one;
863 18 : bool bOverlapFound = false;
864 18 : PieLabelInfo* pStart = &(*(m_aLabelInfoList.rbegin()));
865 18 : PieLabelInfo* pFirstBorder = 0;
866 18 : PieLabelInfo* pSecondBorder = 0;
867 18 : PieLabelInfo* pCurrent = pStart;
868 76 : do
869 : {
870 87 : ::basegfx::B2IRectangle aPreviousOverlap( lcl_getRect( pCurrent->xLabelGroupShape ) );
871 87 : ::basegfx::B2IRectangle aNextOverlap( aPreviousOverlap );
872 87 : aPreviousOverlap.intersect( lcl_getRect( pCurrent->pPrevious->xLabelGroupShape ) );
873 87 : aNextOverlap.intersect( lcl_getRect( pCurrent->pNext->xLabelGroupShape ) );
874 :
875 87 : bool bPreviousOverlap = !aPreviousOverlap.isEmpty();
876 87 : bool bNextOverlap = !aNextOverlap.isEmpty();
877 87 : if( bPreviousOverlap || bNextOverlap )
878 15 : bOverlapFound = true;
879 87 : if( !bPreviousOverlap && bNextOverlap )
880 : {
881 11 : pFirstBorder = pCurrent;
882 11 : break;
883 : }
884 76 : pCurrent = pCurrent->pNext;
885 : }
886 : while( pCurrent != pStart );
887 :
888 18 : if( !bOverlapFound )
889 7 : return false;
890 :
891 : ///in case we found a label (`pFirstBorder`) which overlaps with the next
892 : ///label and not with the previous label a second while loop is started with
893 : ///`pFirstBorder` as initial label; one more time the bounding box of each
894 : ///label is checked for overlap against the bounding box of the previous and
895 : ///of the next label, however this time we exit from the loop only if the
896 : ///current label overlaps with the previous one but does not with the next
897 : ///one (the opposite of what is required in the former loop);
898 : ///in case such a label is found it is assigned to `pSecondBorder` and the
899 : ///iteration is stopped; so in case there is a chain of overlapping labels
900 : ///we end up having the first label of the chain pointed by `pFirstBorder`
901 : ///and the last label of the chain pointed by `pSecondBorder`;
902 11 : if( pFirstBorder )
903 : {
904 11 : pCurrent = pFirstBorder;
905 18 : do
906 : {
907 29 : ::basegfx::B2IRectangle aPreviousOverlap( lcl_getRect( pCurrent->xLabelGroupShape ) );
908 29 : ::basegfx::B2IRectangle aNextOverlap( aPreviousOverlap );
909 29 : aPreviousOverlap.intersect( lcl_getRect( pCurrent->pPrevious->xLabelGroupShape ) );
910 29 : aNextOverlap.intersect( lcl_getRect( pCurrent->pNext->xLabelGroupShape ) );
911 :
912 29 : if( !aPreviousOverlap.isEmpty() && aNextOverlap.isEmpty() )
913 : {
914 11 : pSecondBorder = pCurrent;
915 11 : break;
916 : }
917 18 : pCurrent = pCurrent->pNext;
918 : }
919 : while( pCurrent != pFirstBorder );
920 : }
921 :
922 : ///when two labels satisfying the required conditions are not found
923 : ///(`pFirstBorder == 0 || pSecondBorder == 0`) but still an overlap occurs
924 : ///(`bOverlapFound == true`) we are in the situation where each label
925 : ///overlaps with both the previous and the next one; so `pFirstBorder` is
926 : ///set to point to the last `PieLabelInfo` object in the collection and
927 : ///`pSecondBorder` is set to point to the first one;
928 11 : if( !pFirstBorder || !pSecondBorder )
929 : {
930 0 : pFirstBorder = &(*(m_aLabelInfoList.rbegin()));
931 0 : pSecondBorder = &(*(m_aLabelInfoList.begin()));
932 : }
933 :
934 : ///the total number of labels that made up the chain is calculated and used
935 : ///for getting a pointer to the central label (`pCenter`);
936 11 : PieLabelInfo* pCenter = pFirstBorder;
937 11 : sal_Int32 nOverlapGroupCount = 1;
938 29 : for( pCurrent = pFirstBorder ;pCurrent != pSecondBorder; pCurrent = pCurrent->pNext )
939 18 : nOverlapGroupCount++;
940 11 : sal_Int32 nCenterPos = nOverlapGroupCount/2;
941 11 : bool bSingleCenter = nOverlapGroupCount%2 != 0;
942 11 : if( bSingleCenter )
943 3 : nCenterPos++;
944 11 : if(nCenterPos>1)
945 : {
946 5 : pCurrent = pFirstBorder;
947 15 : while( --nCenterPos )
948 5 : pCurrent = pCurrent->pNext;
949 5 : pCenter = pCurrent;
950 : }
951 :
952 : ///the current position of each label in the collection is saved in
953 : ///`PieLabelInfo.aPreviousPosition`, so that it is possible to undo the label
954 : ///move action if it is needed; the undo action is provided by the
955 : ///`PieChart::resetLabelPositionsToPreviousState` method.
956 11 : pCurrent = pStart;
957 73 : do
958 : {
959 73 : pCurrent->aPreviousPosition = pCurrent->xLabelGroupShape->getPosition();
960 73 : pCurrent = pCurrent->pNext;
961 : }
962 : while( pCurrent != pStart );
963 :
964 : ///the `PieChart::tryMoveLabels` method is invoked with
965 : ///`rbAlternativeMoveDirection` boolean parameter set to false, such a method
966 : ///tries to remove all overlaps that occur in the list of labels going from
967 : ///`pFirstBorder` to `pSecondBorder`;
968 : ///if the `PieChart::tryMoveLabels` returns true no further action is
969 : ///performed, however it is worth noting that it does not mean that all
970 : ///overlap issues have been surely fixed, but only that all moved labels are
971 : ///at least completely inside the page document;
972 : ///when `PieChart::tryMoveLabels` returns false, it means that the attempt
973 : ///to fix one of the overlap issues caused that a label has been moved
974 : ///(partially) outside the page document (anyway the `PieChart::tryMoveLabels`
975 : ///method takes care to restore the position of all labels to their initial
976 : ///position, and to set the `rbAlternativeMoveDirection` in/out parameter to
977 : ///true); in such a case a second invocation of `PieChart::tryMoveLabels` is
978 : ///performed (and this time the `rbAlternativeMoveDirection` boolean
979 : ///parameter is true) and independently by what the `PieChart::tryMoveLabels`
980 : ///method returns no further action is performed;
981 : ///(see notes for `PieChart::tryMoveLabels`);
982 11 : bool bAlternativeMoveDirection = false;
983 11 : if( !tryMoveLabels( pFirstBorder, pSecondBorder, pCenter, bSingleCenter, bAlternativeMoveDirection, rPageSize ) )
984 0 : tryMoveLabels( pFirstBorder, pSecondBorder, pCenter, bSingleCenter, bAlternativeMoveDirection, rPageSize );
985 :
986 : ///in both cases (one or two invocations of `PieChart::tryMoveLabels`) the
987 : ///`detectLabelOverlapsAndMove` method ends returning true.
988 11 : return true;
989 : }
990 :
991 :
992 : /** Try to remove all overlaps that occur in the list of labels going from
993 : * `pFirstBorder` to `pSecondBorder`
994 : */
995 11 : bool PieChart::tryMoveLabels( PieLabelInfo* pFirstBorder, PieLabelInfo* pSecondBorder
996 : , PieLabelInfo* pCenter
997 : , bool bSingleCenter, bool& rbAlternativeMoveDirection, const awt::Size& rPageSize )
998 : {
999 :
1000 11 : PieLabelInfo* p1 = bSingleCenter ? pCenter->pPrevious : pCenter;
1001 11 : PieLabelInfo* p2 = pCenter->pNext;
1002 : //return true when successful
1003 :
1004 11 : bool bLabelOrderIsAntiClockWise = m_pPosHelper->isMathematicalOrientationAngle();
1005 :
1006 : ///two loops are performed simultaneously: the outer loop iterates on
1007 : ///`PieLabelInfo` objects in the list starting from the central element
1008 : ///(`pCenter`) and moving forward until the last element (`pSecondBorder`);
1009 : ///the inner loop starts from the previous element of `pCenter` and moves
1010 : ///forward until the current `PieLabelInfo` object of the outer loop is
1011 : ///reached
1012 11 : PieLabelInfo* pCurrent = 0;
1013 24 : for( pCurrent = p2 ;pCurrent->pPrevious != pSecondBorder; pCurrent = pCurrent->pNext )
1014 : {
1015 13 : PieLabelInfo* pFix = 0;
1016 28 : for( pFix = p2->pPrevious ;pFix != pCurrent; pFix = pFix->pNext )
1017 : {
1018 : ///on the current `PieLabelInfo` object of the outer loop the
1019 : ///`moveAwayFrom` method is invoked by passing the current
1020 : ///`PieLabelInfo` object of the inner loop as argument.
1021 :
1022 : ///so each label going from the central one to the last one is
1023 : ///checked for overlapping against all previous labels (that comes
1024 : ///after the central label) and in case the overlap occurs the
1025 : ///`moveAwayFrom` method tries to fix the issue;
1026 : ///if `moveAwayFrom` returns true (pay attention: that does not
1027 : ///mean that the overlap issue has been surely fixed but only that
1028 : ///the moved label is at least completely inside the page document:
1029 : ///see notes on `PieChart::PieLabelInfo::moveAwayFrom`), the inner
1030 : ///loop starts a new iteration else the `rbAlternativeMoveDirection`
1031 : ///boolean parameter is tested: if it is false the parameter is set
1032 : ///to true, the position of all labels is restored to the initial
1033 : ///one (through the `PieChart::resetLabelPositionsToPreviousState`
1034 : ///method) and the method ends by returning false, else the inner
1035 : ///loop starts a new iteration step;
1036 : ///so when `rbAlternativeMoveDirection` is true the method goes on
1037 : ///trying to fix left overlap issues even if the last `moveAwayFrom`
1038 : ///invocation has moved a label in a position that it is not
1039 : ///completely inside the page document
1040 :
1041 15 : if( !pCurrent->moveAwayFrom( pFix, rPageSize, !bSingleCenter && pCurrent == p2, !bLabelOrderIsAntiClockWise, rbAlternativeMoveDirection ) )
1042 : {
1043 0 : if( !rbAlternativeMoveDirection )
1044 : {
1045 0 : rbAlternativeMoveDirection = true;
1046 0 : resetLabelPositionsToPreviousState();
1047 0 : return false;
1048 : }
1049 : }
1050 : }
1051 : }
1052 :
1053 : ///if the method does not return before ending the first pair of loops,
1054 : ///a second pair of simultaneous loops is performed in the opposite
1055 : ///direction (respect with the previous case): the outer loop iterates on
1056 : ///`PieLabelInfo` objects in the list starting from the central element
1057 : ///(`pCenter`) and moving backward until the first element (`pFirstBorder`);
1058 : ///the inner loop starts from the next element of `pCenter` and moves
1059 : ///backward until the current `PieLabelInfo` object of the outer loop is
1060 : ///reached
1061 :
1062 : ///like in the previous case on the current `PieLabelInfo` object of
1063 : ///the outer loop the `moveAwayFrom` method is invoked by passing
1064 : ///the current `PieLabelInfo` object of the inner loop as argument
1065 :
1066 : ///so each label going from the central one to the first one is checked for
1067 : ///overlapping on all subsequent labels (that come before the central label)
1068 : ///and in case the overlap occurs the `moveAwayFrom` method tries to fix
1069 : ///the issue. The subsequent actions performed after the invocation
1070 : ///`moveAwayFrom` are the same detailed above for the first pair of loops
1071 :
1072 24 : for( pCurrent = p1 ;pCurrent->pNext != pFirstBorder; pCurrent = pCurrent->pPrevious )
1073 : {
1074 13 : PieLabelInfo* pFix = 0;
1075 44 : for( pFix = p2->pNext ;pFix != pCurrent; pFix = pFix->pPrevious )
1076 : {
1077 31 : if( !pCurrent->moveAwayFrom( pFix, rPageSize, false, bLabelOrderIsAntiClockWise, rbAlternativeMoveDirection ) )
1078 : {
1079 0 : if( !rbAlternativeMoveDirection )
1080 : {
1081 0 : rbAlternativeMoveDirection = true;
1082 0 : resetLabelPositionsToPreviousState();
1083 0 : return false;
1084 : }
1085 : }
1086 : }
1087 : }
1088 11 : return true;
1089 : }
1090 :
1091 37 : void PieChart::rearrangeLabelToAvoidOverlapIfRequested( const awt::Size& rPageSize )
1092 : {
1093 : ///this method is invoked by `ChartView::impl_createDiagramAndContent` for
1094 : ///pie and donut charts after text label creation;
1095 : ///it tries to rearrange labels only when the label placement type is
1096 : ///`AVOID_OVERLAP`.
1097 :
1098 :
1099 : ///check whether there are any labels that should be moved
1100 37 : std::vector< PieLabelInfo >::iterator aIt1 = m_aLabelInfoList.begin();
1101 37 : std::vector< PieLabelInfo >::const_iterator aEnd = m_aLabelInfoList.end();
1102 37 : bool bMoveableFound = false;
1103 47 : for( ;aIt1!=aEnd; ++aIt1 )
1104 : {
1105 17 : if(aIt1->bMovementAllowed)
1106 : {
1107 7 : bMoveableFound = true;
1108 7 : break;
1109 : }
1110 : }
1111 37 : if(!bMoveableFound)
1112 60 : return;
1113 :
1114 7 : double fPageDiagonaleLength = sqrt( double( rPageSize.Width*rPageSize.Width + rPageSize.Height*rPageSize.Height) );
1115 7 : if( ::rtl::math::approxEqual( fPageDiagonaleLength, 0.0 ) )
1116 0 : return;
1117 :
1118 : ///initialize next and previous member of `PieLabelInfo` objects
1119 7 : aIt1 = m_aLabelInfoList.begin();
1120 7 : std::vector< PieLabelInfo >::iterator aIt2 = aIt1;
1121 7 : if( aIt1==aEnd )//no need to do anything when we only have one label
1122 0 : return;
1123 7 : aIt1->pPrevious = &(*(m_aLabelInfoList.rbegin()));
1124 7 : ++aIt2;
1125 39 : for( ;aIt2!=aEnd; ++aIt1, ++aIt2 )
1126 : {
1127 32 : PieLabelInfo& rInfo1( *aIt1 );
1128 32 : PieLabelInfo& rInfo2( *aIt2 );
1129 32 : rInfo1.pNext = &rInfo2;
1130 32 : rInfo2.pPrevious = &rInfo1;
1131 : }
1132 7 : aIt1->pNext = &(*(m_aLabelInfoList.begin()));
1133 :
1134 : ///detect overlaps and move
1135 7 : sal_Int32 nMaxIterations = 50;
1136 25 : while( detectLabelOverlapsAndMove( rPageSize ) && nMaxIterations > 0 )
1137 11 : nMaxIterations--;
1138 :
1139 : ///create connection lines for the moved labels
1140 7 : aEnd = m_aLabelInfoList.end();
1141 7 : VLineProperties aVLineProperties;
1142 46 : for( aIt1 = m_aLabelInfoList.begin(); aIt1!=aEnd; ++aIt1 )
1143 : {
1144 39 : PieLabelInfo& rInfo( *aIt1 );
1145 39 : if( rInfo.bMoved )
1146 : {
1147 17 : sal_Int32 nX1 = rInfo.aFirstPosition.getX();
1148 17 : sal_Int32 nY1 = rInfo.aFirstPosition.getY();
1149 17 : sal_Int32 nX2 = nX1;
1150 17 : sal_Int32 nY2 = nY1;
1151 17 : ::basegfx::B2IRectangle aRect( lcl_getRect( rInfo.xLabelGroupShape ) );
1152 17 : if( nX1 < aRect.getMinX() )
1153 4 : nX2 = aRect.getMinX();
1154 13 : else if( nX1 > aRect.getMaxX() )
1155 2 : nX2 = aRect.getMaxX();
1156 :
1157 17 : if( nY1 < aRect.getMinY() )
1158 2 : nY2 = aRect.getMinY();
1159 15 : else if( nY1 > aRect.getMaxY() )
1160 6 : nY2 = aRect.getMaxY();
1161 :
1162 : //when the line is very short compared to the page size don't create one
1163 17 : ::basegfx::B2DVector aLength(nX1-nX2, nY1-nY2);
1164 17 : if( (aLength.getLength()/fPageDiagonaleLength) < 0.01 )
1165 11 : continue;
1166 :
1167 12 : drawing::PointSequenceSequence aPoints(1);
1168 6 : aPoints[0].realloc(2);
1169 6 : aPoints[0][0].X = nX1;
1170 6 : aPoints[0][0].Y = nY1;
1171 6 : aPoints[0][1].X = nX2;
1172 6 : aPoints[0][1].Y = nY2;
1173 :
1174 12 : uno::Reference< beans::XPropertySet > xProp( rInfo.xTextShape, uno::UNO_QUERY);
1175 6 : if( xProp.is() )
1176 : {
1177 6 : sal_Int32 nColor = 0;
1178 6 : xProp->getPropertyValue("CharColor") >>= nColor;
1179 6 : if( nColor != -1 )//automatic font color does not work for lines -> fallback to black
1180 6 : aVLineProperties.Color = uno::makeAny(nColor);
1181 : }
1182 12 : m_pShapeFactory->createLine2D( rInfo.xTextTarget, aPoints, &aVLineProperties );
1183 : }
1184 7 : }
1185 : }
1186 :
1187 :
1188 : /** Handle the placement of the label in the best fit case:
1189 : * the routine try to place the label inside the related pie slice,
1190 : * in case of success it returns true else returns false.
1191 : *
1192 : * Notation:
1193 : * C: the pie center
1194 : * s: the bisector ray of the current pie slice
1195 : * alpha: the angle between the horizontal axis and the bisector ray s
1196 : * N: the vertex of the label b.b. which is nearest to C
1197 : * F: the vertex of the label b.b. not adjacent to N; F lies on the pie border
1198 : * P, Q: the intersection points between the label b.b. and the bisector ray s;
1199 : * P is the one at minimum distance respect with C
1200 : * e: the edge of the label b.b. where P lies (the nearest edge to C)
1201 : * M: the vertex of e that is not N
1202 : * G: the vertex of the label b.b. which is adjacent to N and that is not M
1203 : * beta: the angle MPF
1204 : * theta: the angle CPF
1205 : *
1206 : *
1207 : * |
1208 : * | /s
1209 : * | /
1210 : * | /
1211 : * | G _________________________/____________________________ F
1212 : * | | /Q ..|
1213 : * | | / . . |
1214 : * | | / . . |
1215 : * | | / . . |
1216 : * | | / . . |
1217 : * | | / . . |
1218 : * | | / d. . |
1219 : * | | / . . |
1220 : * | | / . . |
1221 : * | | / . . |
1222 : * | | / . . |
1223 : * | | / . . |
1224 : * | | / . . |
1225 : * | | / . \ beta . |
1226 : * | |__________/._\___|_______.____________________________|
1227 : * | N /P / . M
1228 : * | /___/theta .
1229 : * | / .
1230 : * | / . r
1231 : * | / .
1232 : * | / .
1233 : * | / .
1234 : * | / .
1235 : * | / .
1236 : * | / .
1237 : * | / .
1238 : * | / .
1239 : * | /\. alpha
1240 : * __|/__|_____________________________________________________________
1241 : * |C
1242 : * |
1243 : *
1244 : *
1245 : * When alpha = 45k (k integer) s crosses the label b.b. at N exactly.
1246 : * In such a case the nearest edge e is defined as the edge having N as the
1247 : * start vertex and that is covered in the counterclockwise direction when
1248 : * we move from N to the adjacent vertex.
1249 : *
1250 : * The nearest vertex N is:
1251 : * 1. the bottom left vertex when 0 < alpha < 90
1252 : * 2. the bottom right vertex when 90 < alpha < 180
1253 : * 3. the top right vertex when 180 < alpha < 270
1254 : * 4. the top left vertex when 270 < alpha < 360.
1255 : *
1256 : * The nearest edge e is:
1257 : * 1. the left edge when −45 < alpha < 45
1258 : * 2. the bottom edge when 45 < alpha <135
1259 : * 3. the right edge when 135 < alpha < 225
1260 : * 4. the top edge when 225 < alpha < 315.
1261 : *
1262 : **/
1263 72 : bool PieChart::performLabelBestFitInnerPlacement(ShapeParam& rShapeParam, PieLabelInfo& rPieLabelInfo)
1264 : {
1265 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1266 : "** PieChart::performLabelBestFitInnerPlacement invoked **" );
1267 :
1268 : // get pie slice properties
1269 72 : double fStartAngleDeg = lcl_getDegAngleInStandardRange(rShapeParam.mfUnitCircleStartAngleDegree);
1270 72 : double fWidthAngleDeg = rShapeParam.mfUnitCircleWidthAngleDegree;
1271 72 : double fHalfWidthAngleDeg = fWidthAngleDeg / 2.0;
1272 72 : double fBisectingRayAngleDeg = lcl_getDegAngleInStandardRange(fStartAngleDeg + fHalfWidthAngleDeg);
1273 :
1274 : // get the middle point of the arc representing the pie slice border
1275 72 : double fLogicZ = rShapeParam.mfLogicZ + 1.0;
1276 : awt::Point aMiddleArcPoint = PlottingPositionHelper::transformSceneToScreenPosition(
1277 : m_pPosHelper->transformUnitCircleToScene(
1278 : fBisectingRayAngleDeg,
1279 : rShapeParam.mfUnitCircleOuterRadius,
1280 72 : fLogicZ ),
1281 144 : m_xLogicTarget, m_pShapeFactory, m_nDimension );
1282 :
1283 : // compute the pie radius
1284 72 : basegfx::B2IVector aPieCenter = rPieLabelInfo.aOrigin;
1285 : basegfx::B2IVector aRadiusVector(
1286 72 : aMiddleArcPoint.X - aPieCenter.getX(),
1287 216 : aMiddleArcPoint.Y - aPieCenter.getY() );
1288 72 : double fSquaredPieRadius = aRadiusVector.scalar(aRadiusVector);
1289 72 : double fPieRadius = sqrt( fSquaredPieRadius );
1290 :
1291 : // the bb is moved as much as possible near to the border of the pie,
1292 : // anyway a small offset from the border is present (0.025 * pie radius)
1293 72 : const double fPieBorderOffset = 0.025;
1294 72 : fPieRadius = fPieRadius - fPieRadius * fPieBorderOffset;
1295 :
1296 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1297 : " pie sector:" );
1298 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1299 : " start angle = " << fStartAngleDeg );
1300 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1301 : " angle width = " << fWidthAngleDeg );
1302 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1303 : " bisecting ray angle = " << fBisectingRayAngleDeg );
1304 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1305 : " pie radius = " << fPieRadius );
1306 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1307 : " pie center = " << rPieLabelInfo.aOrigin );
1308 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1309 : " middle arc point = (" << aMiddleArcPoint.X << ","
1310 : << aMiddleArcPoint.Y << ")" );
1311 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1312 : " label bounding box:" );
1313 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1314 : " old anchor point = " << rPieLabelInfo.aFirstPosition );
1315 :
1316 :
1317 72 : if( ::rtl::math::approxEqual( fPieRadius, 0.0 ) )
1318 0 : return false;
1319 :
1320 : // get label b.b. width and height
1321 72 : ::basegfx::B2IRectangle aBb( lcl_getRect( rPieLabelInfo.xLabelGroupShape ) );
1322 72 : double fLabelWidth = aBb.getWidth();
1323 72 : double fLabelHeight = aBb.getHeight();
1324 :
1325 : // -45 <= fAlphaDeg < 315
1326 72 : double fAlphaDeg = lcl_getDegAngleInStandardRange(fBisectingRayAngleDeg + 45) - 45;
1327 72 : double fAlphaRad = lcl_degToRad(fAlphaDeg);
1328 :
1329 : // compute nearest edge index
1330 : // 0 left
1331 : // 1 bottom
1332 : // 2 right
1333 : // 3 top
1334 72 : int nSectorIndex = floor( (fAlphaDeg + 45) / 45.0 );
1335 72 : int nNearestEdgeIndex = nSectorIndex / 2;
1336 :
1337 : // compute lengths of the nearest edge and of the orthogonal edges
1338 72 : double fNearestEdgeLength = fLabelWidth;
1339 72 : double fOrthogonalEdgeLength = fLabelHeight;
1340 72 : int nAxisIndex = 0;
1341 72 : int nOrthogonalAxisIndex = 1;
1342 72 : if( nNearestEdgeIndex % 2 == 0 ) // nearest edge is vertical
1343 : {
1344 26 : fNearestEdgeLength = fLabelHeight;
1345 26 : fOrthogonalEdgeLength = fLabelWidth;
1346 26 : nAxisIndex = 1;
1347 26 : nOrthogonalAxisIndex = 0;
1348 : }
1349 :
1350 : // compute the distance between N and P
1351 : // such a distance is piece wise linear respect with alpha:
1352 : // given 45k <= alpha < 45(k+1) we have
1353 : // when k is even: d(N,P) = (length(e) / 2) * (1 - (alpha - 45k)/45)
1354 : // when k is odd: d(N,P) = (length(e) / 2) * (1 - (45(k+1) - alpha)/45)
1355 72 : int nIndex = nSectorIndex -1; // nIndex = -1...6
1356 72 : double fIndexMod2 = (nIndex + 8) % 2; // fIndexMod2 must be non negative
1357 72 : double fSgn = 2.0 * (fIndexMod2 - 0.5); // 0 -> -1, 1 -> 1
1358 72 : double fDistanceNP = (fNearestEdgeLength / 2.0) * (1 + fSgn * ((fAlphaDeg - 45 * (nIndex + fIndexMod2)) / 45.0));
1359 72 : double fDistancePM = fNearestEdgeLength - fDistanceNP;
1360 :
1361 : // compute the length of the diagonal vector d,
1362 : // that is the distance between P and F
1363 72 : double fSquaredDistancePF = fDistancePM * fDistancePM + fOrthogonalEdgeLength * fOrthogonalEdgeLength;
1364 72 : double fDistancePF = sqrt( fSquaredDistancePF );
1365 :
1366 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1367 : " width = " << fLabelWidth );
1368 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1369 : " height = " << fLabelHeight );
1370 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1371 : " nearest edge index = " << nNearestEdgeIndex );
1372 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1373 : " alpha = " << fAlphaDeg );
1374 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1375 : " distance(N,P) = " << fDistanceNP );
1376 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1377 : " nIndex = " << nIndex );
1378 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1379 : " fIndexMod2 = " << fIndexMod2 );
1380 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1381 : " fSgn = " << fSgn );
1382 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1383 : " distance(P,F) = " << fDistancePF );
1384 :
1385 :
1386 : // we check that the condition length(d) <= pie radius holds
1387 72 : if (fDistancePF > fPieRadius)
1388 : {
1389 7 : return false;
1390 : }
1391 :
1392 : // compute beta: the angle of the diagonal vector d,
1393 : // that is, the angle in P respect with the triangle PMF;
1394 : // since both arguments are non negative the returned value is in [0, PI/2]
1395 65 : double fBetaRad = atan2( fOrthogonalEdgeLength, fDistancePM );
1396 :
1397 : // compute the theta angle, that is the angle in P
1398 : // respect with the triangle CFP;
1399 : // when the second intersection edge is opposite to the nearest edge,
1400 : // theta depends on alpha and beta according to the following relation:
1401 : // theta = f(alpha, beta) = s * alpha + 90 * (1 - s * i) + beta
1402 : // where i is the nearest edge index and s is the sign of (alpha' - 45),
1403 : // with alpha' = (alpha + 45) mod 90;
1404 : // when the second intersection edge is adjacent to the nearest edge,
1405 : // we have theta = 360 - f(alpha, beta);
1406 : // note that in the former case 0 <= f(alpha, beta) <= 180,
1407 : // whilst in the latter case 180 <= f(alpha, beta) <= 360;
1408 65 : double fAlphaMod90 = fmod( fAlphaDeg + 45, 90.0 ) - 45;
1409 65 : double fSign = ::rtl::math::approxEqual( fAlphaMod90, 0.0 )
1410 : ? 0.0
1411 65 : : ( fAlphaMod90 < 0 ) ? -1.0 : 1.0;
1412 65 : double fThetaRad = fSign * fAlphaRad + M_PI_2 * (1 - fSign * nNearestEdgeIndex) + fBetaRad;
1413 65 : if( fThetaRad > M_PI )
1414 : {
1415 18 : fThetaRad = 2 * M_PI - fThetaRad;
1416 : }
1417 :
1418 : // compute the length of the positional vector,
1419 : // that is the distance between C and P
1420 : double fDistanceCP;
1421 : // when the bisector ray intersects the b.b. in F we have theta mod 180 == 0
1422 65 : if( ::rtl::math::approxEqual( fmod(fThetaRad, M_PI), 0.0 ))
1423 : {
1424 0 : fDistanceCP = fPieRadius - fDistancePF;
1425 : }
1426 : else // general case
1427 : {
1428 : // we can compute d(C,P) by applying some trigonometric formula to
1429 : // the triangle CFP : we know length(d) and length(r) = r and we have
1430 : // computed the angle in P (theta); so named delta the angle in C and
1431 : // gamma the angle in F, by the relation:
1432 : //
1433 : // r d(P,F) d(C,P)
1434 : // --------- = --------- = ---------
1435 : // sin theta sin delta sin gamma
1436 : //
1437 : // we get the wanted distance
1438 65 : double fSinTheta = sin( fThetaRad );
1439 65 : double fSinDelta = fDistancePF * fSinTheta / fPieRadius;
1440 65 : double fDeltaRad = asin( fSinDelta );
1441 65 : double fGammaRad = M_PI - (fThetaRad + fDeltaRad);
1442 65 : double fSinGamma = sin( fGammaRad );
1443 65 : fDistanceCP = fPieRadius * fSinGamma / fSinTheta;
1444 : }
1445 :
1446 : // define the positional vector
1447 130 : basegfx::B2DVector aPositionalVector( cos(fAlphaRad), sin(fAlphaRad) );
1448 65 : aPositionalVector.setLength(fDistanceCP);
1449 :
1450 : // we define a direction vector in order to know
1451 : // in which quadrant we are working
1452 130 : basegfx::B2DVector aDirection(1.0, 1.0);
1453 65 : if( 90 <= fBisectingRayAngleDeg && fBisectingRayAngleDeg < 270 )
1454 : {
1455 52 : aDirection.setX(-1.0);
1456 : }
1457 65 : if( fBisectingRayAngleDeg >= 180 )
1458 : {
1459 23 : aDirection.setY(-1.0);
1460 : }
1461 :
1462 : // compute vertices N, M and G respect with pie center C
1463 130 : basegfx::B2DVector aNearestVertex(aPositionalVector);
1464 65 : aNearestVertex[nAxisIndex] += -aDirection[nAxisIndex] * fDistanceNP;
1465 130 : basegfx::B2DVector aVertexM(aNearestVertex);
1466 65 : aVertexM[nAxisIndex] += aDirection[nAxisIndex] * fNearestEdgeLength;
1467 130 : basegfx::B2DVector aVertexG(aNearestVertex);
1468 65 : aVertexG[nOrthogonalAxisIndex] += aDirection[nOrthogonalAxisIndex] * fOrthogonalEdgeLength;
1469 :
1470 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1471 : " beta = " << lcl_radToDeg(fBetaRad) );
1472 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1473 : " theta = " << lcl_radToDeg(fThetaRad) );
1474 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1475 : " fAlphaMod90 = " << fAlphaMod90 );
1476 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1477 : " fSign = " << fSign );
1478 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1479 : " distance(C,P) = " << fDistanceCP );
1480 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1481 : " direction vector = " << aDirection );
1482 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1483 : " N = " << aNearestVertex );
1484 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1485 : " M = " << aVertexM );
1486 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1487 : " G = " << aVertexG );
1488 :
1489 : // in order to be able to place the label inside the pie slice we need
1490 : // to check that each angle between s and the ray starting from C and
1491 : // passing through a b.b. vertex is less than half width of the pie slice;
1492 : // when the nearest edge e crosses a Cartesian axis it is sufficient
1493 : // to test only the vertices belonging to e, else we need to test
1494 : // the 2 vertices that aren’t either N or F . Note that if a b.b. edge
1495 : // crosses a Cartesian axis then it is the nearest edge to C
1496 :
1497 : // check the angle between CP and CM
1498 65 : double fAngleRad = aPositionalVector.angle(aVertexM);
1499 65 : double fAngleDeg = lcl_getDegAngleInStandardRange( lcl_radToDeg(fAngleRad) );
1500 65 : if( fAngleDeg > 180 ) // in case the wrong angle has been computed
1501 20 : fAngleDeg = 360 - fAngleDeg;
1502 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1503 : " angle between CP and CM: " << fAngleDeg );
1504 65 : if( fAngleDeg > fHalfWidthAngleDeg )
1505 : {
1506 35 : return false;
1507 : }
1508 :
1509 72 : if( ( aNearestVertex[nAxisIndex] >= 0 && aVertexM[nAxisIndex] <= 0 )
1510 60 : || ( aNearestVertex[nAxisIndex] <= 0 && aVertexM[nAxisIndex] >= 0 ) )
1511 : {
1512 : // check the angle between CP and CN
1513 4 : fAngleRad = aPositionalVector.angle(aNearestVertex);
1514 4 : fAngleDeg = lcl_getDegAngleInStandardRange( lcl_radToDeg(fAngleRad) );
1515 4 : if( fAngleDeg > 180 ) // in case the wrong angle has been computed
1516 2 : fAngleDeg = 360 - fAngleDeg;
1517 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1518 : " angle between CP and CN: " << fAngleDeg );
1519 4 : if( fAngleDeg > fHalfWidthAngleDeg )
1520 : {
1521 0 : return false;
1522 : }
1523 : }
1524 : else
1525 : {
1526 : // check the angle between CP and CG
1527 26 : fAngleRad = aPositionalVector.angle(aVertexG);
1528 26 : fAngleDeg = lcl_getDegAngleInStandardRange( lcl_radToDeg(fAngleRad) );
1529 26 : if( fAngleDeg > 180 ) // in case the wrong angle has been computed
1530 12 : fAngleDeg = 360 - fAngleDeg;
1531 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1532 : " angle between CP and CG: " << fAngleDeg );
1533 26 : if( fAngleDeg > fHalfWidthAngleDeg )
1534 : {
1535 4 : return false;
1536 : }
1537 : }
1538 :
1539 : // compute the b.b. center respect with the pie center
1540 52 : basegfx::B2DVector aBBCenter(aNearestVertex);
1541 26 : aBBCenter[nAxisIndex] += aDirection[nAxisIndex] * fNearestEdgeLength / 2;
1542 26 : aBBCenter[nOrthogonalAxisIndex] += aDirection[nOrthogonalAxisIndex] * fOrthogonalEdgeLength / 2;
1543 :
1544 : // compute the b.b. anchor point
1545 52 : basegfx::B2IVector aNewAnchorPoint = aPieCenter;
1546 26 : aNewAnchorPoint[0] += floor(aBBCenter[0]);
1547 26 : aNewAnchorPoint[1] -= floor(aBBCenter[1]); // the Y axis on the screen points downward
1548 :
1549 : // compute the translation vector for moving the label from the current
1550 : // screen position to the new one
1551 52 : basegfx::B2IVector aTranslationVector = aNewAnchorPoint - rPieLabelInfo.aFirstPosition;
1552 :
1553 : // compute the new screen position and move the label
1554 26 : awt::Point aNewPos( rPieLabelInfo.xLabelGroupShape->getPosition() );
1555 26 : aNewPos.X += aTranslationVector.getX();
1556 26 : aNewPos.Y += aTranslationVector.getY();
1557 26 : rPieLabelInfo.xLabelGroupShape->setPosition(aNewPos);
1558 :
1559 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1560 : " center = " << aBBCenter );
1561 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1562 : " new anchor point = " << aNewAnchorPoint );
1563 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1564 : " translation vector = " << aTranslationVector );
1565 : SAL_INFO( "chart2.pie.label.bestfit.inside",
1566 : " new position = (" << aNewPos.X << "," << aNewPos.Y << ")" );
1567 :
1568 98 : return true;
1569 : }
1570 :
1571 : /** Handle the outer placement of the labels in the best fit case.
1572 : *
1573 : */
1574 46 : bool PieChart::performLabelBestFitOuterPlacement(ShapeParam& /*rShapeParam*/, PieLabelInfo& /*rPieLabelInfo*/)
1575 : {
1576 : SAL_WARN( "chart2.pie.label.bestfit", "to be implemented" );
1577 46 : return false;
1578 : }
1579 :
1580 : /** Handle the placement of the label in the best fit case.
1581 : * First off the routine try to place the label inside the related pie slice,
1582 : * if this is not possible the label is placed outside.
1583 : */
1584 72 : void PieChart::performLabelBestFit(ShapeParam& rShapeParam, PieLabelInfo& rPieLabelInfo)
1585 : {
1586 72 : if( m_bUseRings )
1587 72 : return;
1588 :
1589 72 : if( !performLabelBestFitInnerPlacement(rShapeParam, rPieLabelInfo) )
1590 : {
1591 46 : performLabelBestFitOuterPlacement(rShapeParam, rPieLabelInfo);
1592 : }
1593 : }
1594 :
1595 : } //namespace chart
1596 :
1597 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|