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