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 : double mfUnitCircleStartAngleDegree;
45 : double mfUnitCircleWidthAngleDegree;
46 : double mfUnitCircleOuterRadius;
47 : double mfUnitCircleInnerRadius;
48 : double mfExplodePercentage;
49 : double mfLogicYSum; // sum of all Y values in a single series.
50 : double mfLogicZ;
51 : double mfDepth;
52 :
53 40 : ShapeParam() :
54 : mfUnitCircleStartAngleDegree(0.0),
55 : mfUnitCircleWidthAngleDegree(0.0),
56 : mfUnitCircleOuterRadius(0.0),
57 : mfUnitCircleInnerRadius(0.0),
58 : mfExplodePercentage(0.0),
59 : mfLogicYSum(0.0),
60 : mfLogicZ(0.0),
61 40 : mfDepth(0.0) {}
62 : };
63 :
64 : class PiePositionHelper : public PolarPlottingPositionHelper
65 : {
66 : public:
67 : PiePositionHelper( NormalAxis eNormalAxis, double fAngleDegreeOffset );
68 : virtual ~PiePositionHelper();
69 :
70 : bool getInnerAndOuterRadius( double fCategoryX, double& fLogicInnerRadius, double& fLogicOuterRadius, bool bUseRings, double fMaxOffset ) const;
71 :
72 : public:
73 : //Distance between different category rings, seen relative to width of a ring:
74 : double m_fRingDistance; //>=0 m_fRingDistance=1 --> distance == width
75 : };
76 :
77 20 : PiePositionHelper::PiePositionHelper( NormalAxis eNormalAxis, double fAngleDegreeOffset )
78 : : PolarPlottingPositionHelper(eNormalAxis)
79 20 : , m_fRingDistance(0.0)
80 : {
81 20 : m_fRadiusOffset = 0.0;
82 20 : m_fAngleDegreeOffset = fAngleDegreeOffset;
83 20 : }
84 :
85 40 : PiePositionHelper::~PiePositionHelper()
86 : {
87 40 : }
88 :
89 152 : bool PiePositionHelper::getInnerAndOuterRadius( double fCategoryX
90 : , double& fLogicInnerRadius, double& fLogicOuterRadius
91 : , bool bUseRings, double fMaxOffset ) const
92 : {
93 152 : if( !bUseRings )
94 152 : fCategoryX = 1.0;
95 :
96 152 : bool bIsVisible = true;
97 152 : double fLogicInner = fCategoryX -0.5+m_fRingDistance/2.0;
98 152 : double fLogicOuter = fCategoryX +0.5-m_fRingDistance/2.0;
99 :
100 152 : if( !isMathematicalOrientationRadius() )
101 : {
102 : //in this case the given getMaximumX() was not corrcect instead the minimum should have been smaller by fMaxOffset
103 : //but during getMaximumX and getMimumX we do not know the axis orientation
104 80 : fLogicInner += fMaxOffset;
105 80 : fLogicOuter += fMaxOffset;
106 : }
107 :
108 152 : if( fLogicInner >= getLogicMaxX() )
109 0 : return false;
110 152 : if( fLogicOuter <= getLogicMinX() )
111 0 : return false;
112 :
113 152 : if( fLogicInner < getLogicMinX() )
114 0 : fLogicInner = getLogicMinX();
115 152 : if( fLogicOuter > getLogicMaxX() )
116 0 : fLogicOuter = getLogicMaxX();
117 :
118 152 : fLogicInnerRadius = fLogicInner;
119 152 : fLogicOuterRadius = fLogicOuter;
120 152 : if( !isMathematicalOrientationRadius() )
121 80 : std::swap(fLogicInnerRadius,fLogicOuterRadius);
122 152 : return bIsVisible;
123 : }
124 :
125 20 : PieChart::PieChart( const uno::Reference<XChartType>& xChartTypeModel
126 : , sal_Int32 nDimensionCount
127 : , bool bExcludingPositioning )
128 : : VSeriesPlotter( xChartTypeModel, nDimensionCount )
129 20 : , m_pPosHelper( new PiePositionHelper( NormalAxis_Z, (m_nDimension==3)?0.0:90.0 ) )
130 : , m_bUseRings(false)
131 40 : , m_bSizeExcludesLabelsAndExplodedSegments(bExcludingPositioning)
132 : {
133 20 : ::rtl::math::setNan(&m_fMaxOffset);
134 :
135 20 : PlotterBase::m_pPosHelper = m_pPosHelper;
136 20 : VSeriesPlotter::m_pMainPosHelper = m_pPosHelper;
137 20 : m_pPosHelper->m_fRadiusOffset = 0.0;
138 20 : m_pPosHelper->m_fRingDistance = 0.0;
139 :
140 20 : uno::Reference< beans::XPropertySet > xChartTypeProps( xChartTypeModel, uno::UNO_QUERY );
141 20 : if( xChartTypeProps.is() ) try
142 : {
143 20 : xChartTypeProps->getPropertyValue( "UseRings") >>= m_bUseRings;
144 20 : if( m_bUseRings )
145 : {
146 0 : m_pPosHelper->m_fRadiusOffset = 1.0;
147 0 : if( nDimensionCount==3 )
148 0 : m_pPosHelper->m_fRingDistance = 0.1;
149 : }
150 : }
151 0 : catch( const uno::Exception& e )
152 : {
153 : ASSERT_EXCEPTION( e );
154 20 : }
155 20 : }
156 :
157 60 : PieChart::~PieChart()
158 : {
159 20 : delete m_pPosHelper;
160 40 : }
161 :
162 20 : void PieChart::setScales( const std::vector< ExplicitScaleData >& rScales, bool /* bSwapXAndYAxis */ )
163 : {
164 : OSL_ENSURE(m_nDimension<=static_cast<sal_Int32>(rScales.size()),"Dimension of Plotter does not fit two dimension of given scale sequence");
165 20 : m_pPosHelper->setScales( rScales, true );
166 20 : }
167 :
168 20 : drawing::Direction3D PieChart::getPreferredDiagramAspectRatio() const
169 : {
170 20 : if( m_nDimension == 3 )
171 2 : return drawing::Direction3D(1,1,0.10);
172 18 : return drawing::Direction3D(1,1,1);
173 : }
174 :
175 0 : bool PieChart::keepAspectRatio() const
176 : {
177 0 : if( m_nDimension == 3 )
178 0 : return false;
179 0 : return true;
180 : }
181 :
182 20 : bool PieChart::shouldSnapRectToUsedArea()
183 : {
184 20 : return true;
185 : }
186 :
187 152 : uno::Reference< drawing::XShape > PieChart::createDataPoint(
188 : const uno::Reference<drawing::XShapes>& xTarget,
189 : const uno::Reference<beans::XPropertySet>& xObjectProperties,
190 : tPropertyNameValueMap* pOverwritePropertiesMap,
191 : const ShapeParam& rParam )
192 : {
193 : //transform position:
194 152 : drawing::Direction3D aOffset;
195 152 : if (!::rtl::math::approxEqual(rParam.mfExplodePercentage, 0.0))
196 : {
197 0 : double fAngle = rParam.mfUnitCircleStartAngleDegree + rParam.mfUnitCircleWidthAngleDegree/2.0;
198 0 : double fRadius = (rParam.mfUnitCircleOuterRadius-rParam.mfUnitCircleInnerRadius)*rParam.mfExplodePercentage;
199 0 : drawing::Position3D aOrigin = m_pPosHelper->transformUnitCircleToScene(0, 0, rParam.mfLogicZ);
200 0 : drawing::Position3D aNewOrigin = m_pPosHelper->transformUnitCircleToScene(fAngle, fRadius, rParam.mfLogicZ);
201 0 : aOffset = aNewOrigin - aOrigin;
202 : }
203 :
204 : //create point
205 152 : uno::Reference< drawing::XShape > xShape(0);
206 152 : if(m_nDimension==3)
207 : {
208 64 : xShape = m_pShapeFactory->createPieSegment( xTarget
209 : , rParam.mfUnitCircleStartAngleDegree, rParam.mfUnitCircleWidthAngleDegree
210 : , rParam.mfUnitCircleInnerRadius, rParam.mfUnitCircleOuterRadius
211 32 : , aOffset, B3DHomMatrixToHomogenMatrix( m_pPosHelper->getUnitCartesianToScene() )
212 32 : , rParam.mfDepth );
213 : }
214 : else
215 : {
216 408 : xShape = m_pShapeFactory->createPieSegment2D( xTarget
217 : , rParam.mfUnitCircleStartAngleDegree, rParam.mfUnitCircleWidthAngleDegree
218 : , rParam.mfUnitCircleInnerRadius, rParam.mfUnitCircleOuterRadius
219 408 : , aOffset, B3DHomMatrixToHomogenMatrix( m_pPosHelper->getUnitCartesianToScene() ) );
220 : }
221 152 : this->setMappedProperties( xShape, xObjectProperties, PropertyMapper::getPropertyNameMapForFilledSeriesProperties(), pOverwritePropertiesMap );
222 152 : return xShape;
223 : }
224 :
225 152 : void PieChart::createTextLabelShape(
226 : const uno::Reference<drawing::XShapes>& xTextTarget,
227 : VDataSeries& rSeries, sal_Int32 nPointIndex, ShapeParam& rParam )
228 : {
229 152 : if (!rSeries.getDataPointLabelIfLabel(nPointIndex))
230 : // There is no text label for this data point. Nothing to do.
231 276 : return;
232 :
233 28 : if (!rtl::math::approxEqual(rParam.mfExplodePercentage, 0.0))
234 : {
235 0 : double fExplodeOffset = (rParam.mfUnitCircleOuterRadius-rParam.mfUnitCircleInnerRadius)*rParam.mfExplodePercentage;
236 0 : rParam.mfUnitCircleInnerRadius += fExplodeOffset;
237 0 : rParam.mfUnitCircleOuterRadius += fExplodeOffset;
238 : }
239 :
240 : sal_Int32 nLabelPlacement = rSeries.getLabelPlacement(
241 28 : nPointIndex, m_xChartTypeModel, m_nDimension, m_pPosHelper->isSwapXAndY());
242 :
243 : // AVOID_OVERLAP is in fact "Best fit" in the UI.
244 28 : bool bMovementAllowed = ( nLabelPlacement == ::com::sun::star::chart::DataLabelPlacement::AVOID_OVERLAP );
245 28 : if( bMovementAllowed )
246 : // Use center for "Best fit" for now. In the future we
247 : // may want to implement a real best fit algorithm.
248 : // But center is good enough, and close to what Excel
249 : // does.
250 16 : nLabelPlacement = ::com::sun::star::chart::DataLabelPlacement::CENTER;
251 :
252 28 : LabelAlignment eAlignment(LABEL_ALIGN_CENTER);
253 28 : sal_Int32 nScreenValueOffsetInRadiusDirection = 0 ;
254 28 : if( nLabelPlacement == ::com::sun::star::chart::DataLabelPlacement::OUTSIDE )
255 0 : nScreenValueOffsetInRadiusDirection = (3!=m_nDimension) ? 150 : 0;//todo maybe calculate this font height dependent
256 28 : else if( nLabelPlacement == ::com::sun::star::chart::DataLabelPlacement::INSIDE )
257 0 : nScreenValueOffsetInRadiusDirection = (3!=m_nDimension) ? -150 : 0;//todo maybe calculate this font height dependent
258 :
259 28 : PolarLabelPositionHelper aPolarPosHelper(m_pPosHelper,m_nDimension,m_xLogicTarget,m_pShapeFactory);
260 : awt::Point aScreenPosition2D(
261 : aPolarPosHelper.getLabelScreenPositionAndAlignmentForUnitCircleValues(eAlignment, nLabelPlacement
262 : , rParam.mfUnitCircleStartAngleDegree, rParam.mfUnitCircleWidthAngleDegree
263 28 : , rParam.mfUnitCircleInnerRadius, rParam.mfUnitCircleOuterRadius, rParam.mfLogicZ+0.5, 0 ));
264 :
265 56 : PieLabelInfo aPieLabelInfo;
266 28 : aPieLabelInfo.aFirstPosition = basegfx::B2IVector( aScreenPosition2D.X, aScreenPosition2D.Y );
267 28 : awt::Point aOrigin( aPolarPosHelper.transformSceneToScreenPosition( m_pPosHelper->transformUnitCircleToScene( 0.0, 0.0, rParam.mfLogicZ+1.0 ) ) );
268 28 : aPieLabelInfo.aOrigin = basegfx::B2IVector( aOrigin.X, aOrigin.Y );
269 :
270 : //add a scaling independent Offset if requested
271 28 : if( nScreenValueOffsetInRadiusDirection != 0)
272 : {
273 0 : basegfx::B2IVector aDirection( aScreenPosition2D.X- aOrigin.X, aScreenPosition2D.Y- aOrigin.Y );
274 0 : aDirection.setLength(nScreenValueOffsetInRadiusDirection);
275 0 : aScreenPosition2D.X += aDirection.getX();
276 0 : aScreenPosition2D.Y += aDirection.getY();
277 : }
278 :
279 28 : double nVal = rSeries.getYValue(nPointIndex);
280 56 : aPieLabelInfo.xTextShape = createDataLabel(
281 28 : xTextTarget, rSeries, nPointIndex, nVal, rParam.mfLogicYSum, aScreenPosition2D, eAlignment);
282 :
283 56 : uno::Reference< container::XChild > xChild( aPieLabelInfo.xTextShape, uno::UNO_QUERY );
284 28 : if( xChild.is() )
285 28 : aPieLabelInfo.xLabelGroupShape = uno::Reference<drawing::XShape>( xChild->getParent(), uno::UNO_QUERY );
286 28 : aPieLabelInfo.fValue = nVal;
287 28 : aPieLabelInfo.bMovementAllowed = bMovementAllowed;
288 28 : aPieLabelInfo.bMoved= false;
289 28 : aPieLabelInfo.xTextTarget = xTextTarget;
290 56 : m_aLabelInfoList.push_back(aPieLabelInfo);
291 : }
292 :
293 42 : void PieChart::addSeries( VDataSeries* pSeries, sal_Int32 /* zSlot */, sal_Int32 /* xSlot */, sal_Int32 /* ySlot */ )
294 : {
295 42 : VSeriesPlotter::addSeries( pSeries, 0, -1, 0 );
296 42 : }
297 :
298 20 : double PieChart::getMinimumX()
299 : {
300 20 : return 0.5;
301 : }
302 172 : double PieChart::getMaxOffset()
303 : {
304 172 : if (!::rtl::math::isNan(m_fMaxOffset))
305 : // Value already cached. Use it.
306 112 : return m_fMaxOffset;
307 :
308 60 : m_fMaxOffset = 0.0;
309 60 : if( m_aZSlots.size()<=0 )
310 0 : return m_fMaxOffset;
311 60 : if( m_aZSlots[0].size()<=0 )
312 0 : return m_fMaxOffset;
313 :
314 60 : const ::std::vector< VDataSeries* >& rSeriesList( m_aZSlots[0][0].m_aSeriesVector );
315 60 : if( rSeriesList.size()<=0 )
316 0 : return m_fMaxOffset;
317 :
318 60 : VDataSeries* pSeries = rSeriesList[0];
319 60 : uno::Reference< beans::XPropertySet > xSeriesProp( pSeries->getPropertiesOfSeries() );
320 60 : if( !xSeriesProp.is() )
321 0 : return m_fMaxOffset;
322 :
323 60 : double fExplodePercentage=0.0;
324 60 : xSeriesProp->getPropertyValue( "Offset") >>= fExplodePercentage;
325 60 : if(fExplodePercentage>m_fMaxOffset)
326 0 : m_fMaxOffset=fExplodePercentage;
327 :
328 60 : if(!m_bSizeExcludesLabelsAndExplodedSegments)
329 : {
330 60 : uno::Sequence< sal_Int32 > aAttributedDataPointIndexList;
331 60 : if( xSeriesProp->getPropertyValue( "AttributedDataPoints" ) >>= aAttributedDataPointIndexList )
332 : {
333 228 : for(sal_Int32 nN=aAttributedDataPointIndexList.getLength();nN--;)
334 : {
335 108 : uno::Reference< beans::XPropertySet > xPointProp( pSeries->getPropertiesOfPoint(aAttributedDataPointIndexList[nN]) );
336 108 : if(xPointProp.is())
337 : {
338 108 : fExplodePercentage=0.0;
339 108 : xPointProp->getPropertyValue( "Offset") >>= fExplodePercentage;
340 108 : if(fExplodePercentage>m_fMaxOffset)
341 0 : m_fMaxOffset=fExplodePercentage;
342 : }
343 108 : }
344 60 : }
345 : }
346 60 : return m_fMaxOffset;
347 : }
348 20 : double PieChart::getMaximumX()
349 : {
350 20 : double fMaxOffset = getMaxOffset();
351 20 : if( m_aZSlots.size()>0 && m_bUseRings)
352 0 : return m_aZSlots[0].size()+0.5+fMaxOffset;
353 20 : return 1.5+fMaxOffset;
354 : }
355 20 : double PieChart::getMinimumYInRange( double /* fMinimumX */, double /* fMaximumX */, sal_Int32 /* nAxisIndex */ )
356 : {
357 20 : return 0.0;
358 : }
359 :
360 20 : double PieChart::getMaximumYInRange( double /* fMinimumX */, double /* fMaximumX */, sal_Int32 /* nAxisIndex */ )
361 : {
362 20 : return 1.0;
363 : }
364 :
365 42 : bool PieChart::isExpandBorderToIncrementRhythm( sal_Int32 /* nDimensionIndex */ )
366 : {
367 42 : return false;
368 : }
369 :
370 42 : bool PieChart::isExpandIfValuesCloseToBorder( sal_Int32 /* nDimensionIndex */ )
371 : {
372 42 : return false;
373 : }
374 :
375 42 : bool PieChart::isExpandWideValuesToZero( sal_Int32 /* nDimensionIndex */ )
376 : {
377 42 : return false;
378 : }
379 :
380 42 : bool PieChart::isExpandNarrowValuesTowardZero( sal_Int32 /* nDimensionIndex */ )
381 : {
382 42 : return false;
383 : }
384 :
385 0 : bool PieChart::isSeparateStackingForDifferentSigns( sal_Int32 /* nDimensionIndex */ )
386 : {
387 0 : return false;
388 : }
389 :
390 40 : void PieChart::createShapes()
391 : {
392 40 : if (m_aZSlots.empty())
393 : // No series to plot.
394 0 : return;
395 :
396 : OSL_ENSURE(m_pShapeFactory && m_xLogicTarget.is() && m_xFinalTarget.is(), "PieChart is not properly initialized.");
397 40 : if (!m_pShapeFactory || !m_xLogicTarget.is() || !m_xFinalTarget.is())
398 0 : return;
399 :
400 : //the text labels should be always on top of the other series shapes
401 : //therefore create an own group for the texts to move them to front
402 : //(because the text group is created after the series group the texts are displayed on top)
403 : uno::Reference< drawing::XShapes > xSeriesTarget(
404 40 : createGroupShape( m_xLogicTarget,OUString() ));
405 : uno::Reference< drawing::XShapes > xTextTarget(
406 80 : m_pShapeFactory->createGroup2D( m_xFinalTarget,OUString() ));
407 : //check necessary here that different Y axis can not be stacked in the same group? ... hm?
408 :
409 40 : ::std::vector< VDataSeriesGroup >::iterator aXSlotIter = m_aZSlots[0].begin();
410 40 : const ::std::vector< VDataSeriesGroup >::const_iterator aXSlotEnd = m_aZSlots[0].end();
411 :
412 40 : ::std::vector< VDataSeriesGroup >::size_type nExplodeableSlot = 0;
413 40 : if( m_pPosHelper->isMathematicalOrientationRadius() && m_bUseRings )
414 0 : nExplodeableSlot = m_aZSlots[0].size()-1;
415 :
416 40 : m_aLabelInfoList.clear();
417 40 : ::rtl::math::setNan(&m_fMaxOffset);
418 40 : sal_Int32 n3DRelativeHeight = 100;
419 80 : uno::Reference< beans::XPropertySet > xPropertySet( m_xChartTypeModel, uno::UNO_QUERY );
420 40 : if ( (m_nDimension==3) && xPropertySet.is())
421 : {
422 : try
423 : {
424 4 : uno::Any aAny = xPropertySet->getPropertyValue( "3DRelativeHeight" );
425 4 : aAny >>= n3DRelativeHeight;
426 : }
427 0 : catch (const uno::Exception&) { }
428 : }
429 :
430 80 : for( double fSlotX=0; aXSlotIter != aXSlotEnd && (m_bUseRings||fSlotX<0.5 ); ++aXSlotIter, fSlotX+=1.0 )
431 : {
432 40 : ShapeParam aParam;
433 :
434 40 : ::std::vector< VDataSeries* >* pSeriesList = &(aXSlotIter->m_aSeriesVector);
435 40 : if( pSeriesList->size()<=0 )//there should be only one series in each x slot
436 0 : continue;
437 40 : VDataSeries* pSeries = (*pSeriesList)[0];
438 40 : if(!pSeries)
439 0 : continue;
440 :
441 40 : bool bHasFillColorMapping = pSeries->hasPropertyMapping("FillColor");
442 :
443 : // Counter-clockwise offset from the 3 o'clock position.
444 40 : m_pPosHelper->m_fAngleDegreeOffset = pSeries->getStartingAngle();
445 :
446 : //iterate through all points to get the sum
447 40 : sal_Int32 nPointIndex=0;
448 40 : sal_Int32 nPointCount=pSeries->getTotalPointCount();
449 192 : for( nPointIndex = 0; nPointIndex < nPointCount; nPointIndex++ )
450 : {
451 152 : double fY = pSeries->getYValue( nPointIndex );
452 : if(fY<0.0)
453 : {
454 : //@todo warn somehow that negative values are treated as positive
455 : }
456 152 : if( ::rtl::math::isNan(fY) )
457 0 : continue;
458 152 : aParam.mfLogicYSum += fabs(fY);
459 : }
460 :
461 40 : if (aParam.mfLogicYSum == 0.0)
462 : // Total sum of all Y values in this series is zero. Skip the whole series.
463 0 : continue;
464 :
465 40 : double fLogicYForNextPoint = 0.0;
466 : //iterate through all points to create shapes
467 192 : for( nPointIndex = 0; nPointIndex < nPointCount; nPointIndex++ )
468 : {
469 : double fLogicInnerRadius, fLogicOuterRadius;
470 152 : double fOffset = getMaxOffset();
471 152 : bool bIsVisible = m_pPosHelper->getInnerAndOuterRadius( fSlotX+1.0, fLogicInnerRadius, fLogicOuterRadius, m_bUseRings, fOffset );
472 152 : if( !bIsVisible )
473 0 : continue;
474 :
475 152 : aParam.mfDepth = this->getTransformedDepth() * (n3DRelativeHeight / 100.0);
476 :
477 152 : uno::Reference< drawing::XShapes > xSeriesGroupShape_Shapes = getSeriesGroupShape(pSeries, xSeriesTarget);
478 : //collect data point information (logic coordinates, style ):
479 152 : double fLogicYValue = fabs(pSeries->getYValue( nPointIndex ));
480 152 : if( ::rtl::math::isNan(fLogicYValue) )
481 0 : continue;
482 152 : if(fLogicYValue==0.0)//@todo: continue also if the resolution to small
483 0 : continue;
484 152 : double fLogicYPos = fLogicYForNextPoint;
485 152 : fLogicYForNextPoint += fLogicYValue;
486 :
487 304 : uno::Reference< beans::XPropertySet > xPointProperties = pSeries->getPropertiesOfPoint( nPointIndex );
488 :
489 : //iterate through all subsystems to create partial points
490 : {
491 : //logic values on angle axis:
492 152 : double fLogicStartAngleValue = fLogicYPos / aParam.mfLogicYSum;
493 152 : double fLogicEndAngleValue = (fLogicYPos+fLogicYValue) / aParam.mfLogicYSum;
494 :
495 152 : aParam.mfExplodePercentage = 0.0;
496 152 : bool bDoExplode = ( nExplodeableSlot == static_cast< ::std::vector< VDataSeriesGroup >::size_type >(fSlotX) );
497 152 : if(bDoExplode) try
498 : {
499 152 : xPointProperties->getPropertyValue( "Offset") >>= aParam.mfExplodePercentage;
500 : }
501 0 : catch( const uno::Exception& e )
502 : {
503 : ASSERT_EXCEPTION( e );
504 : }
505 :
506 : //transforme to unit circle:
507 152 : aParam.mfUnitCircleWidthAngleDegree = m_pPosHelper->getWidthAngleDegree( fLogicStartAngleValue, fLogicEndAngleValue );
508 152 : aParam.mfUnitCircleStartAngleDegree = m_pPosHelper->transformToAngleDegree( fLogicStartAngleValue );
509 152 : aParam.mfUnitCircleInnerRadius = m_pPosHelper->transformToRadius( fLogicInnerRadius );
510 152 : aParam.mfUnitCircleOuterRadius = m_pPosHelper->transformToRadius( fLogicOuterRadius );
511 :
512 : //point color:
513 152 : boost::scoped_ptr< tPropertyNameValueMap > apOverwritePropertiesMap(NULL);
514 152 : if (!pSeries->hasPointOwnColor(nPointIndex) && m_xColorScheme.is())
515 : {
516 80 : apOverwritePropertiesMap.reset( new tPropertyNameValueMap() );
517 160 : (*apOverwritePropertiesMap)["FillColor"] = uno::makeAny(
518 160 : m_xColorScheme->getColorByIndex( nPointIndex ));
519 : }
520 :
521 : //create data point
522 152 : aParam.mfLogicZ = -1.0; // For 3D pie chart label position
523 : uno::Reference<drawing::XShape> xPointShape =
524 : createDataPoint(
525 304 : xSeriesGroupShape_Shapes, xPointProperties, apOverwritePropertiesMap.get(), aParam);
526 :
527 152 : if(bHasFillColorMapping)
528 : {
529 0 : double nPropVal = pSeries->getValueByProperty(nPointIndex, "FillColor");
530 0 : if(!rtl::math::isNan(nPropVal))
531 : {
532 0 : uno::Reference< beans::XPropertySet > xProps( xPointShape, uno::UNO_QUERY_THROW );
533 0 : xProps->setPropertyValue("FillColor", uno::makeAny(static_cast<sal_Int32>( nPropVal)));
534 : }
535 : }
536 :
537 : //create label
538 152 : createTextLabelShape(xTextTarget, *pSeries, nPointIndex, aParam);
539 :
540 152 : if(!bDoExplode)
541 : {
542 : AbstractShapeFactory::setShapeName( xPointShape
543 0 : , ObjectIdentifier::createPointCID( pSeries->getPointCID_Stub(), nPointIndex ) );
544 : }
545 : else try
546 : {
547 : //enable dragging of outer segments
548 :
549 152 : double fAngle = aParam.mfUnitCircleStartAngleDegree + aParam.mfUnitCircleWidthAngleDegree/2.0;
550 152 : double fMaxDeltaRadius = aParam.mfUnitCircleOuterRadius-aParam.mfUnitCircleInnerRadius;
551 152 : drawing::Position3D aOrigin = m_pPosHelper->transformUnitCircleToScene( fAngle, aParam.mfUnitCircleOuterRadius, aParam.mfLogicZ );
552 152 : drawing::Position3D aNewOrigin = m_pPosHelper->transformUnitCircleToScene( fAngle, aParam.mfUnitCircleOuterRadius + fMaxDeltaRadius, aParam.mfLogicZ );
553 :
554 152 : sal_Int32 nOffsetPercent( static_cast<sal_Int32>(aParam.mfExplodePercentage * 100.0) );
555 :
556 : awt::Point aMinimumPosition( PlottingPositionHelper::transformSceneToScreenPosition(
557 152 : aOrigin, m_xLogicTarget, m_pShapeFactory, m_nDimension ) );
558 : awt::Point aMaximumPosition( PlottingPositionHelper::transformSceneToScreenPosition(
559 152 : aNewOrigin, m_xLogicTarget, m_pShapeFactory, m_nDimension ) );
560 :
561 : //enable draging of piesegments
562 : OUString aPointCIDStub( ObjectIdentifier::createSeriesSubObjectStub( OBJECTTYPE_DATA_POINT
563 : , pSeries->getSeriesParticle()
564 152 : , ObjectIdentifier::getPieSegmentDragMethodServiceName()
565 : , ObjectIdentifier::createPieSegmentDragParameterString(
566 : nOffsetPercent, aMinimumPosition, aMaximumPosition )
567 304 : ) );
568 :
569 : AbstractShapeFactory::setShapeName( xPointShape
570 152 : , ObjectIdentifier::createPointCID( aPointCIDStub, nPointIndex ) );
571 : }
572 0 : catch( const uno::Exception& e )
573 : {
574 : ASSERT_EXCEPTION( e );
575 152 : }
576 : }//next series in x slot (next y slot)
577 152 : }//next category
578 40 : }//next x slot
579 : }
580 :
581 : namespace
582 : {
583 :
584 42 : ::basegfx::B2IRectangle lcl_getRect( const uno::Reference< drawing::XShape >& xShape )
585 : {
586 42 : ::basegfx::B2IRectangle aRect;
587 42 : if( xShape.is() )
588 42 : aRect = BaseGFXHelper::makeRectangle(xShape->getPosition(),xShape->getSize() );
589 42 : return aRect;
590 : }
591 :
592 0 : bool lcl_isInsidePage( const awt::Point& rPos, const awt::Size& rSize, const awt::Size& rPageSize )
593 : {
594 0 : if( rPos.X < 0 || rPos.Y < 0 )
595 0 : return false;
596 0 : if( (rPos.X + rSize.Width) > rPageSize.Width )
597 0 : return false;
598 0 : if( (rPos.Y + rSize.Height) > rPageSize.Height )
599 0 : return false;
600 0 : return true;
601 : }
602 :
603 : }//end anonymous namespace
604 :
605 28 : PieChart::PieLabelInfo::PieLabelInfo()
606 : : xTextShape(0), xLabelGroupShape(0), aFirstPosition(), aOrigin(), fValue(0.0)
607 28 : , bMovementAllowed(false), bMoved(false), xTextTarget(0), pPrevious(0),pNext(0)
608 : {
609 28 : }
610 :
611 0 : bool PieChart::PieLabelInfo::moveAwayFrom( const PieChart::PieLabelInfo* pFix, const awt::Size& rPageSize, bool bMoveHalfWay, bool bMoveClockwise, bool bAlternativeMoveDirection )
612 : {
613 : //return true if the move was successful
614 0 : if(!this->bMovementAllowed)
615 0 : return false;
616 :
617 0 : const sal_Int32 nLabelDistanceX = rPageSize.Width/50;
618 0 : const sal_Int32 nLabelDistanceY = rPageSize.Height/50;
619 :
620 0 : ::basegfx::B2IRectangle aOverlap( lcl_getRect( this->xLabelGroupShape ) );
621 0 : aOverlap.intersect( lcl_getRect( pFix->xLabelGroupShape ) );
622 0 : if( !aOverlap.isEmpty() )
623 : {
624 : (void)bAlternativeMoveDirection;//todo
625 :
626 0 : basegfx::B2IVector aRadiusDirection = this->aFirstPosition - this->aOrigin;
627 0 : aRadiusDirection.setLength(1.0);
628 0 : basegfx::B2IVector aTangentialDirection( -aRadiusDirection.getY(), aRadiusDirection.getX() );
629 0 : bool bShiftHorizontal = abs(aTangentialDirection.getX()) > abs(aTangentialDirection.getY());
630 :
631 0 : sal_Int32 nShift = bShiftHorizontal ? static_cast<sal_Int32>(aOverlap.getWidth()) : static_cast<sal_Int32>(aOverlap.getHeight());
632 0 : nShift += (bShiftHorizontal ? nLabelDistanceX : nLabelDistanceY);
633 0 : if( bMoveHalfWay )
634 0 : nShift/=2;
635 0 : if(!bMoveClockwise)
636 0 : nShift*=-1;
637 0 : awt::Point aOldPos( this->xLabelGroupShape->getPosition() );
638 0 : basegfx::B2IVector aNewPos = basegfx::B2IVector( aOldPos.X, aOldPos.Y ) + nShift*aTangentialDirection;
639 :
640 : //check whether the new position is ok
641 0 : awt::Point aNewAWTPos( aNewPos.getX(), aNewPos.getY() );
642 0 : if( !lcl_isInsidePage( aNewAWTPos, this->xLabelGroupShape->getSize(), rPageSize ) )
643 0 : return false;
644 :
645 0 : this->xLabelGroupShape->setPosition( aNewAWTPos );
646 0 : this->bMoved = true;
647 : }
648 0 : return true;
649 : }
650 :
651 0 : void PieChart::resetLabelPositionsToPreviousState()
652 : {
653 0 : std::vector< PieLabelInfo >::iterator aIt = m_aLabelInfoList.begin();
654 0 : std::vector< PieLabelInfo >::const_iterator aEnd = m_aLabelInfoList.end();
655 0 : for( ;aIt!=aEnd; ++aIt )
656 0 : aIt->xLabelGroupShape->setPosition(aIt->aPreviousPosition);
657 0 : }
658 :
659 4 : bool PieChart::detectLabelOverlapsAndMove( const awt::Size& rPageSize )
660 : {
661 : //returns true when there might be more to do
662 :
663 : //find borders of a group of overlapping labels
664 4 : bool bOverlapFound = false;
665 4 : PieLabelInfo* pStart = &(*(m_aLabelInfoList.rbegin()));
666 4 : PieLabelInfo* pFirstBorder = 0;
667 4 : PieLabelInfo* pSecondBorder = 0;
668 4 : PieLabelInfo* pCurrent = pStart;
669 14 : do
670 : {
671 14 : ::basegfx::B2IRectangle aPreviousOverlap( lcl_getRect( pCurrent->xLabelGroupShape ) );
672 14 : ::basegfx::B2IRectangle aNextOverlap( aPreviousOverlap );
673 14 : aPreviousOverlap.intersect( lcl_getRect( pCurrent->pPrevious->xLabelGroupShape ) );
674 14 : aNextOverlap.intersect( lcl_getRect( pCurrent->pNext->xLabelGroupShape ) );
675 :
676 14 : bool bPreviousOverlap = !aPreviousOverlap.isEmpty();
677 14 : bool bNextOverlap = !aNextOverlap.isEmpty();
678 14 : if( bPreviousOverlap || bNextOverlap )
679 0 : bOverlapFound = true;
680 14 : if( !bPreviousOverlap && bNextOverlap )
681 : {
682 0 : pFirstBorder = pCurrent;
683 0 : break;
684 : }
685 14 : pCurrent = pCurrent->pNext;
686 : }
687 : while( pCurrent != pStart );
688 :
689 4 : if( !bOverlapFound )
690 4 : return false;
691 :
692 0 : if( pFirstBorder )
693 : {
694 0 : pCurrent = pFirstBorder;
695 0 : do
696 : {
697 0 : ::basegfx::B2IRectangle aPreviousOverlap( lcl_getRect( pCurrent->xLabelGroupShape ) );
698 0 : ::basegfx::B2IRectangle aNextOverlap( aPreviousOverlap );
699 0 : aPreviousOverlap.intersect( lcl_getRect( pCurrent->pPrevious->xLabelGroupShape ) );
700 0 : aNextOverlap.intersect( lcl_getRect( pCurrent->pNext->xLabelGroupShape ) );
701 :
702 0 : if( !aPreviousOverlap.isEmpty() && aNextOverlap.isEmpty() )
703 : {
704 0 : pSecondBorder = pCurrent;
705 0 : break;
706 : }
707 0 : pCurrent = pCurrent->pNext;
708 : }
709 : while( pCurrent != pFirstBorder );
710 : }
711 :
712 0 : if( !pFirstBorder || !pSecondBorder )
713 : {
714 0 : pFirstBorder = &(*(m_aLabelInfoList.rbegin()));
715 0 : pSecondBorder = &(*(m_aLabelInfoList.begin()));
716 : }
717 :
718 : //find center
719 0 : PieLabelInfo* pCenter = pFirstBorder;
720 0 : sal_Int32 nOverlapGroupCount = 1;
721 0 : for( pCurrent = pFirstBorder ;pCurrent != pSecondBorder; pCurrent = pCurrent->pNext )
722 0 : nOverlapGroupCount++;
723 0 : sal_Int32 nCenterPos = nOverlapGroupCount/2;
724 0 : bool bSingleCenter = nOverlapGroupCount%2 != 0;
725 0 : if( bSingleCenter )
726 0 : nCenterPos++;
727 0 : if(nCenterPos>1)
728 : {
729 0 : pCurrent = pFirstBorder;
730 0 : while( --nCenterPos )
731 0 : pCurrent = pCurrent->pNext;
732 0 : pCenter = pCurrent;
733 : }
734 :
735 : //remind current positions
736 0 : pCurrent = pStart;
737 0 : do
738 : {
739 0 : pCurrent->aPreviousPosition = pCurrent->xLabelGroupShape->getPosition();
740 0 : pCurrent = pCurrent->pNext;
741 : }
742 : while( pCurrent != pStart );
743 :
744 0 : bool bAlternativeMoveDirection = false;
745 0 : if( !tryMoveLabels( pFirstBorder, pSecondBorder, pCenter, bSingleCenter, bAlternativeMoveDirection, rPageSize ) )
746 0 : tryMoveLabels( pFirstBorder, pSecondBorder, pCenter, bSingleCenter, bAlternativeMoveDirection, rPageSize );
747 0 : return true;
748 : }
749 :
750 0 : bool PieChart::tryMoveLabels( PieLabelInfo* pFirstBorder, PieLabelInfo* pSecondBorder
751 : , PieLabelInfo* pCenter
752 : , bool bSingleCenter, bool& rbAlternativeMoveDirection, const awt::Size& rPageSize )
753 : {
754 0 : PieLabelInfo* p1 = bSingleCenter ? pCenter->pPrevious : pCenter;
755 0 : PieLabelInfo* p2 = pCenter->pNext;
756 : //return true when successful
757 :
758 0 : bool bLabelOrderIsAntiClockWise = m_pPosHelper->isMathematicalOrientationAngle();
759 :
760 0 : PieLabelInfo* pCurrent = 0;
761 0 : for( pCurrent = p2 ;pCurrent->pPrevious != pSecondBorder; pCurrent = pCurrent->pNext )
762 : {
763 0 : PieLabelInfo* pFix = 0;
764 0 : for( pFix = p2->pPrevious ;pFix != pCurrent; pFix = pFix->pNext )
765 : {
766 0 : if( !pCurrent->moveAwayFrom( pFix, rPageSize, !bSingleCenter && pCurrent == p2, !bLabelOrderIsAntiClockWise, rbAlternativeMoveDirection ) )
767 : {
768 0 : if( !rbAlternativeMoveDirection )
769 : {
770 0 : rbAlternativeMoveDirection = true;
771 0 : resetLabelPositionsToPreviousState();
772 0 : return false;
773 : }
774 : }
775 : }
776 : }
777 0 : for( pCurrent = p1 ;pCurrent->pNext != pFirstBorder; pCurrent = pCurrent->pPrevious )
778 : {
779 0 : PieLabelInfo* pFix = 0;
780 0 : for( pFix = p2->pNext ;pFix != pCurrent; pFix = pFix->pPrevious )
781 : {
782 0 : if( !pCurrent->moveAwayFrom( pFix, rPageSize, false, bLabelOrderIsAntiClockWise, rbAlternativeMoveDirection ) )
783 : {
784 0 : if( !rbAlternativeMoveDirection )
785 : {
786 0 : rbAlternativeMoveDirection = true;
787 0 : resetLabelPositionsToPreviousState();
788 0 : return false;
789 : }
790 : }
791 : }
792 : }
793 0 : return true;
794 : }
795 :
796 20 : void PieChart::rearrangeLabelToAvoidOverlapIfRequested( const awt::Size& rPageSize )
797 : {
798 : //check whether there are any labels that should be moved
799 20 : std::vector< PieLabelInfo >::iterator aIt1 = m_aLabelInfoList.begin();
800 20 : std::vector< PieLabelInfo >::const_iterator aEnd = m_aLabelInfoList.end();
801 20 : bool bMoveableFound = false;
802 24 : for( ;aIt1!=aEnd; ++aIt1 )
803 : {
804 8 : if(aIt1->bMovementAllowed)
805 : {
806 4 : bMoveableFound = true;
807 4 : break;
808 : }
809 : }
810 20 : if(!bMoveableFound)
811 32 : return;
812 :
813 4 : double fPageDiagonaleLength = sqrt( double( rPageSize.Width*rPageSize.Width + rPageSize.Height*rPageSize.Height) );
814 4 : if( ::rtl::math::approxEqual( fPageDiagonaleLength, 0.0 ) )
815 0 : return;
816 :
817 : //init next and previous
818 4 : aIt1 = m_aLabelInfoList.begin();
819 4 : std::vector< PieLabelInfo >::iterator aIt2 = aIt1;
820 4 : if( aIt1==aEnd )//no need to do anything when we only have one label
821 0 : return;
822 4 : aIt1->pPrevious = &(*(m_aLabelInfoList.rbegin()));
823 4 : ++aIt2;
824 14 : for( ;aIt2!=aEnd; ++aIt1, ++aIt2 )
825 : {
826 10 : PieLabelInfo& rInfo1( *aIt1 );
827 10 : PieLabelInfo& rInfo2( *aIt2 );
828 10 : rInfo1.pNext = &rInfo2;
829 10 : rInfo2.pPrevious = &rInfo1;
830 : }
831 4 : aIt1->pNext = &(*(m_aLabelInfoList.begin()));
832 :
833 : //detect overlaps and move
834 4 : sal_Int32 nMaxIterations = 50;
835 8 : while( detectLabelOverlapsAndMove( rPageSize ) && nMaxIterations > 0 )
836 0 : nMaxIterations--;
837 :
838 : //create connection lines for the moved labels
839 4 : aEnd = m_aLabelInfoList.end();
840 4 : VLineProperties aVLineProperties;
841 18 : for( aIt1 = m_aLabelInfoList.begin(); aIt1!=aEnd; ++aIt1 )
842 : {
843 14 : PieLabelInfo& rInfo( *aIt1 );
844 14 : if( rInfo.bMoved )
845 : {
846 0 : sal_Int32 nX1 = rInfo.aFirstPosition.getX();
847 0 : sal_Int32 nY1 = rInfo.aFirstPosition.getY();
848 0 : sal_Int32 nX2 = nX1;
849 0 : sal_Int32 nY2 = nY1;
850 0 : ::basegfx::B2IRectangle aRect( lcl_getRect( rInfo.xLabelGroupShape ) );
851 0 : if( nX1 < aRect.getMinX() )
852 0 : nX2 = aRect.getMinX();
853 0 : else if( nX1 > aRect.getMaxX() )
854 0 : nX2 = aRect.getMaxX();
855 :
856 0 : if( nY1 < aRect.getMinY() )
857 0 : nY2 = aRect.getMinY();
858 0 : else if( nY1 > aRect.getMaxY() )
859 0 : nY2 = aRect.getMaxY();
860 :
861 : //when the line is very short compared to the page size don't create one
862 0 : ::basegfx::B2DVector aLength(nX1-nX2, nY1-nY2);
863 0 : if( (aLength.getLength()/fPageDiagonaleLength) < 0.01 )
864 0 : continue;
865 :
866 0 : drawing::PointSequenceSequence aPoints(1);
867 0 : aPoints[0].realloc(2);
868 0 : aPoints[0][0].X = nX1;
869 0 : aPoints[0][0].Y = nY1;
870 0 : aPoints[0][1].X = nX2;
871 0 : aPoints[0][1].Y = nY2;
872 :
873 0 : uno::Reference< beans::XPropertySet > xProp( rInfo.xTextShape, uno::UNO_QUERY);
874 0 : if( xProp.is() )
875 : {
876 0 : sal_Int32 nColor = 0;
877 0 : xProp->getPropertyValue("CharColor") >>= nColor;
878 0 : if( nColor != -1 )//automatic font color does not work for lines -> fallback to black
879 0 : aVLineProperties.Color = uno::makeAny(nColor);
880 : }
881 0 : m_pShapeFactory->createLine2D( rInfo.xTextTarget, aPoints, &aVLineProperties );
882 : }
883 4 : }
884 : }
885 :
886 108 : } //namespace chart
887 :
888 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|