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