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 <PlottingPositionHelper.hxx>
21 #include <CommonConverters.hxx>
22 #include <Linear3DTransformation.hxx>
23 #include <VPolarTransformation.hxx>
24 #include <ShapeFactory.hxx>
25 #include <PropertyMapper.hxx>
26 #include <defines.hxx>
27
28 #include <com/sun/star/chart/TimeUnit.hpp>
29 #include <com/sun/star/chart2/AxisType.hpp>
30 #include <com/sun/star/drawing/DoubleSequence.hpp>
31 #include <com/sun/star/drawing/Position3D.hpp>
32 #include <com/sun/star/drawing/XShapes.hpp>
33
34 #include <rtl/math.hxx>
35
36 namespace chart
37 {
38 using namespace ::com::sun::star;
39 using namespace ::com::sun::star::chart2;
40
PlottingPositionHelper()41 PlottingPositionHelper::PlottingPositionHelper()
42 : m_aScales()
43 , m_aMatrixScreenToScene()
44 , m_bSwapXAndY( false )
45 , m_nXResolution( 1000 )
46 , m_nYResolution( 1000 )
47 , m_nZResolution( 1000 )
48 , m_bMaySkipPointsInRegressionCalculation( true )
49 , m_bDateAxis(false)
50 , m_nTimeResolution( css::chart::TimeUnit::DAY )
51 , m_aNullDate(30,12,1899)
52 , m_fScaledCategoryWidth(1.0)
53 , m_bAllowShiftXAxisPos(false)
54 , m_bAllowShiftZAxisPos(false)
55 {
56 }
PlottingPositionHelper(const PlottingPositionHelper & rSource)57 PlottingPositionHelper::PlottingPositionHelper( const PlottingPositionHelper& rSource )
58 : m_aScales( rSource.m_aScales )
59 , m_aMatrixScreenToScene( rSource.m_aMatrixScreenToScene )
60 // m_xTransformationLogicToScene( nullptr ) //should be recalculated
61 , m_bSwapXAndY( rSource.m_bSwapXAndY )
62 , m_nXResolution( rSource.m_nXResolution )
63 , m_nYResolution( rSource.m_nYResolution )
64 , m_nZResolution( rSource.m_nZResolution )
65 , m_bMaySkipPointsInRegressionCalculation( rSource.m_bMaySkipPointsInRegressionCalculation )
66 , m_bDateAxis( rSource.m_bDateAxis )
67 , m_nTimeResolution( rSource.m_nTimeResolution )
68 , m_aNullDate( rSource.m_aNullDate )
69 , m_fScaledCategoryWidth( rSource.m_fScaledCategoryWidth )
70 , m_bAllowShiftXAxisPos( rSource.m_bAllowShiftXAxisPos )
71 , m_bAllowShiftZAxisPos( rSource.m_bAllowShiftZAxisPos )
72 {
73 }
74
~PlottingPositionHelper()75 PlottingPositionHelper::~PlottingPositionHelper()
76 {
77
78 }
79
clone() const80 std::unique_ptr<PlottingPositionHelper> PlottingPositionHelper::clone() const
81 {
82 return std::make_unique<PlottingPositionHelper>(*this);
83 }
84
createSecondaryPosHelper(const ExplicitScaleData & rSecondaryScale)85 std::unique_ptr<PlottingPositionHelper> PlottingPositionHelper::createSecondaryPosHelper( const ExplicitScaleData& rSecondaryScale )
86 {
87 auto pRet = clone();
88 pRet->m_aScales[1]=rSecondaryScale;
89 return pRet;
90 }
91
setTransformationSceneToScreen(const drawing::HomogenMatrix & rMatrix)92 void PlottingPositionHelper::setTransformationSceneToScreen( const drawing::HomogenMatrix& rMatrix)
93 {
94 m_aMatrixScreenToScene = HomogenMatrixToB3DHomMatrix(rMatrix);
95 m_xTransformationLogicToScene = nullptr;
96 }
97
setScales(const std::vector<ExplicitScaleData> & rScales,bool bSwapXAndYAxis)98 void PlottingPositionHelper::setScales( const std::vector< ExplicitScaleData >& rScales, bool bSwapXAndYAxis )
99 {
100 m_aScales = rScales;
101 m_bSwapXAndY = bSwapXAndYAxis;
102 m_xTransformationLogicToScene = nullptr;
103 }
104
getTransformationScaledLogicToScene() const105 uno::Reference< XTransformation > PlottingPositionHelper::getTransformationScaledLogicToScene() const
106 {
107 //this is a standard transformation for a cartesian coordinate system
108
109 //transformation from 2) to 4) //@todo 2) and 4) need an ink to a document
110
111 //we need to apply this transformation to each geometric object because of a bug/problem
112 //of the old drawing layer (the UNO_NAME_3D_EXTRUDE_DEPTH is an integer value instead of a double )
113 if(!m_xTransformationLogicToScene.is())
114 {
115 ::basegfx::B3DHomMatrix aMatrix;
116 double MinX = getLogicMinX();
117 double MinY = getLogicMinY();
118 double MinZ = getLogicMinZ();
119 double MaxX = getLogicMaxX();
120 double MaxY = getLogicMaxY();
121 double MaxZ = getLogicMaxZ();
122
123 AxisOrientation nXAxisOrientation = m_aScales[0].Orientation;
124 AxisOrientation nYAxisOrientation = m_aScales[1].Orientation;
125 AxisOrientation nZAxisOrientation = m_aScales[2].Orientation;
126
127 //apply scaling
128 doUnshiftedLogicScaling( &MinX, &MinY, &MinZ );
129 doUnshiftedLogicScaling( &MaxX, &MaxY, &MaxZ);
130
131 if(m_bSwapXAndY)
132 {
133 std::swap(MinX,MinY);
134 std::swap(MaxX,MaxY);
135 std::swap(nXAxisOrientation,nYAxisOrientation);
136 }
137
138 double fWidthX = MaxX - MinX;
139 double fWidthY = MaxY - MinY;
140 double fWidthZ = MaxZ - MinZ;
141
142 double fScaleDirectionX = nXAxisOrientation==AxisOrientation_MATHEMATICAL ? 1.0 : -1.0;
143 double fScaleDirectionY = nYAxisOrientation==AxisOrientation_MATHEMATICAL ? 1.0 : -1.0;
144 double fScaleDirectionZ = nZAxisOrientation==AxisOrientation_MATHEMATICAL ? -1.0 : 1.0;
145
146 double fScaleX = fScaleDirectionX*FIXED_SIZE_FOR_3D_CHART_VOLUME/fWidthX;
147 double fScaleY = fScaleDirectionY*FIXED_SIZE_FOR_3D_CHART_VOLUME/fWidthY;
148 double fScaleZ = fScaleDirectionZ*FIXED_SIZE_FOR_3D_CHART_VOLUME/fWidthZ;
149
150 aMatrix.scale(fScaleX, fScaleY, fScaleZ);
151
152 if( nXAxisOrientation==AxisOrientation_MATHEMATICAL )
153 aMatrix.translate(-MinX*fScaleX, 0.0, 0.0);
154 else
155 aMatrix.translate(-MaxX*fScaleX, 0.0, 0.0);
156 if( nYAxisOrientation==AxisOrientation_MATHEMATICAL )
157 aMatrix.translate(0.0, -MinY*fScaleY, 0.0);
158 else
159 aMatrix.translate(0.0, -MaxY*fScaleY, 0.0);
160 if( nZAxisOrientation==AxisOrientation_MATHEMATICAL )
161 aMatrix.translate(0.0, 0.0, -MaxZ*fScaleZ);//z direction in draw is reverse mathematical direction
162 else
163 aMatrix.translate(0.0, 0.0, -MinZ*fScaleZ);
164
165 aMatrix = m_aMatrixScreenToScene*aMatrix;
166
167 m_xTransformationLogicToScene = new Linear3DTransformation(B3DHomMatrixToHomogenMatrix( aMatrix ),m_bSwapXAndY);
168 }
169 return m_xTransformationLogicToScene;
170 }
171
transformLogicToScene(double fX,double fY,double fZ,bool bClip) const172 drawing::Position3D PlottingPositionHelper::transformLogicToScene(
173 double fX, double fY, double fZ, bool bClip ) const
174 {
175 doLogicScaling( &fX,&fY,&fZ );
176 if(bClip)
177 clipScaledLogicValues( &fX,&fY,&fZ );
178
179 return transformScaledLogicToScene( fX, fY, fZ, false );
180 }
181
transformScaledLogicToScene(double fX,double fY,double fZ,bool bClip) const182 drawing::Position3D PlottingPositionHelper::transformScaledLogicToScene(
183 double fX, double fY, double fZ, bool bClip ) const
184 {
185 if( bClip )
186 clipScaledLogicValues( &fX,&fY,&fZ );
187
188 drawing::Position3D aPos( fX, fY, fZ);
189
190 uno::Reference< XTransformation > xTransformation =
191 getTransformationScaledLogicToScene();
192 uno::Sequence< double > aSeq =
193 xTransformation->transform( Position3DToSequence(aPos) );
194 return SequenceToPosition3D(aSeq);
195 }
196
transformSceneToScreenPosition(const drawing::Position3D & rScenePosition3D,const uno::Reference<drawing::XShapes> & xSceneTarget,ShapeFactory * pShapeFactory,sal_Int32 nDimensionCount)197 awt::Point PlottingPositionHelper::transformSceneToScreenPosition( const drawing::Position3D& rScenePosition3D
198 , const uno::Reference< drawing::XShapes >& xSceneTarget
199 , ShapeFactory* pShapeFactory
200 , sal_Int32 nDimensionCount )
201 {
202 //@todo would like to have a cheaper method to do this transformation
203 awt::Point aScreenPoint( static_cast<sal_Int32>(rScenePosition3D.PositionX), static_cast<sal_Int32>(rScenePosition3D.PositionY) );
204
205 //transformation from scene to screen (only necessary for 3D):
206 if(nDimensionCount==3)
207 {
208 //create 3D anchor shape
209 tPropertyNameMap aDummyPropertyNameMap;
210 uno::Reference< drawing::XShape > xShape3DAnchor = pShapeFactory->createCube( xSceneTarget
211 , rScenePosition3D,drawing::Direction3D(1,1,1)
212 , 0, nullptr, aDummyPropertyNameMap);
213 //get 2D position from xShape3DAnchor
214 aScreenPoint = xShape3DAnchor->getPosition();
215 xSceneTarget->remove(xShape3DAnchor);
216 }
217 return aScreenPoint;
218 }
219
transformScaledLogicToScene(drawing::PolyPolygonShape3D & rPolygon) const220 void PlottingPositionHelper::transformScaledLogicToScene( drawing::PolyPolygonShape3D& rPolygon ) const
221 {
222 drawing::Position3D aScenePosition;
223 for( sal_Int32 nS = rPolygon.SequenceX.getLength(); nS--;)
224 {
225 drawing::DoubleSequence& xValues = rPolygon.SequenceX[nS];
226 drawing::DoubleSequence& yValues = rPolygon.SequenceY[nS];
227 drawing::DoubleSequence& zValues = rPolygon.SequenceZ[nS];
228 for( sal_Int32 nP = xValues.getLength(); nP--; )
229 {
230 double& fX = xValues[nP];
231 double& fY = yValues[nP];
232 double& fZ = zValues[nP];
233 aScenePosition = transformScaledLogicToScene( fX,fY,fZ,true );
234 fX = aScenePosition.PositionX;
235 fY = aScenePosition.PositionY;
236 fZ = aScenePosition.PositionZ;
237 }
238 }
239 }
240
clipScaledLogicValues(double * pX,double * pY,double * pZ) const241 void PlottingPositionHelper::clipScaledLogicValues( double* pX, double* pY, double* pZ ) const
242 {
243 //get logic clip values:
244 double MinX = getLogicMinX();
245 double MinY = getLogicMinY();
246 double MinZ = getLogicMinZ();
247 double MaxX = getLogicMaxX();
248 double MaxY = getLogicMaxY();
249 double MaxZ = getLogicMaxZ();
250
251 //apply scaling
252 doUnshiftedLogicScaling( &MinX, &MinY, &MinZ );
253 doUnshiftedLogicScaling( &MaxX, &MaxY, &MaxZ);
254
255 if(pX)
256 {
257 if( *pX < MinX )
258 *pX = MinX;
259 else if( *pX > MaxX )
260 *pX = MaxX;
261 }
262 if(pY)
263 {
264 if( *pY < MinY )
265 *pY = MinY;
266 else if( *pY > MaxY )
267 *pY = MaxY;
268 }
269 if(pZ)
270 {
271 if( *pZ < MinZ )
272 *pZ = MinZ;
273 else if( *pZ > MaxZ )
274 *pZ = MaxZ;
275 }
276 }
277
getScaledLogicClipDoubleRect() const278 basegfx::B2DRectangle PlottingPositionHelper::getScaledLogicClipDoubleRect() const
279 {
280 //get logic clip values:
281 double MinX = getLogicMinX();
282 double MinY = getLogicMinY();
283 double MinZ = getLogicMinZ();
284 double MaxX = getLogicMaxX();
285 double MaxY = getLogicMaxY();
286 double MaxZ = getLogicMaxZ();
287
288 //apply scaling
289 doUnshiftedLogicScaling( &MinX, &MinY, &MinZ );
290 doUnshiftedLogicScaling( &MaxX, &MaxY, &MaxZ);
291
292 basegfx::B2DRectangle aRet( MinX, MaxY, MaxX, MinY );
293 return aRet;
294 }
295
getScaledLogicWidth() const296 drawing::Direction3D PlottingPositionHelper::getScaledLogicWidth() const
297 {
298 drawing::Direction3D aRet;
299
300 double MinX = getLogicMinX();
301 double MinY = getLogicMinY();
302 double MinZ = getLogicMinZ();
303 double MaxX = getLogicMaxX();
304 double MaxY = getLogicMaxY();
305 double MaxZ = getLogicMaxZ();
306
307 doLogicScaling( &MinX, &MinY, &MinZ );
308 doLogicScaling( &MaxX, &MaxY, &MaxZ);
309
310 aRet.DirectionX = MaxX - MinX;
311 aRet.DirectionY = MaxY - MinY;
312 aRet.DirectionZ = MaxZ - MinZ;
313 return aRet;
314 }
315
PolarPlottingPositionHelper()316 PolarPlottingPositionHelper::PolarPlottingPositionHelper()
317 : m_fRadiusOffset(0.0)
318 , m_fAngleDegreeOffset(90.0)
319 , m_aUnitCartesianToScene()
320 {
321 m_bMaySkipPointsInRegressionCalculation = false;
322 }
323
PolarPlottingPositionHelper(const PolarPlottingPositionHelper & rSource)324 PolarPlottingPositionHelper::PolarPlottingPositionHelper( const PolarPlottingPositionHelper& rSource )
325 : PlottingPositionHelper(rSource)
326 , m_fRadiusOffset( rSource.m_fRadiusOffset )
327 , m_fAngleDegreeOffset( rSource.m_fAngleDegreeOffset )
328 , m_aUnitCartesianToScene( rSource.m_aUnitCartesianToScene )
329 {
330 }
331
~PolarPlottingPositionHelper()332 PolarPlottingPositionHelper::~PolarPlottingPositionHelper()
333 {
334 }
335
clone() const336 std::unique_ptr<PlottingPositionHelper> PolarPlottingPositionHelper::clone() const
337 {
338 return std::make_unique<PolarPlottingPositionHelper>(*this);
339 }
340
setTransformationSceneToScreen(const drawing::HomogenMatrix & rMatrix)341 void PolarPlottingPositionHelper::setTransformationSceneToScreen( const drawing::HomogenMatrix& rMatrix)
342 {
343 PlottingPositionHelper::setTransformationSceneToScreen( rMatrix);
344 m_aUnitCartesianToScene =impl_calculateMatrixUnitCartesianToScene( m_aMatrixScreenToScene );
345 }
setScales(const std::vector<ExplicitScaleData> & rScales,bool bSwapXAndYAxis)346 void PolarPlottingPositionHelper::setScales( const std::vector< ExplicitScaleData >& rScales, bool bSwapXAndYAxis )
347 {
348 PlottingPositionHelper::setScales( rScales, bSwapXAndYAxis );
349 m_aUnitCartesianToScene =impl_calculateMatrixUnitCartesianToScene( m_aMatrixScreenToScene );
350 }
351
impl_calculateMatrixUnitCartesianToScene(const::basegfx::B3DHomMatrix & rMatrixScreenToScene) const352 ::basegfx::B3DHomMatrix PolarPlottingPositionHelper::impl_calculateMatrixUnitCartesianToScene( const ::basegfx::B3DHomMatrix& rMatrixScreenToScene ) const
353 {
354 ::basegfx::B3DHomMatrix aRet;
355
356 if( m_aScales.empty() )
357 return aRet;
358
359 double fTranslate =1.0;
360 double fScale =FIXED_SIZE_FOR_3D_CHART_VOLUME/2.0;
361
362 double fTranslateLogicZ;
363 double fScaleLogicZ;
364 {
365 double fScaleDirectionZ = m_aScales[2].Orientation==AxisOrientation_MATHEMATICAL ? 1.0 : -1.0;
366 double MinZ = getLogicMinZ();
367 double MaxZ = getLogicMaxZ();
368 doLogicScaling( nullptr, nullptr, &MinZ );
369 doLogicScaling( nullptr, nullptr, &MaxZ );
370 double fWidthZ = MaxZ - MinZ;
371
372 if( m_aScales[2].Orientation==AxisOrientation_MATHEMATICAL )
373 fTranslateLogicZ=MinZ;
374 else
375 fTranslateLogicZ=MaxZ;
376 fScaleLogicZ = fScaleDirectionZ*FIXED_SIZE_FOR_3D_CHART_VOLUME/fWidthZ;
377 }
378
379 double fTranslateX = fTranslate;
380 double fTranslateY = fTranslate;
381 double fTranslateZ = fTranslateLogicZ;
382
383 double fScaleX = fScale;
384 double fScaleY = fScale;
385 double fScaleZ = fScaleLogicZ;
386
387 aRet.translate(fTranslateX, fTranslateY, fTranslateZ);//x first
388 aRet.scale(fScaleX, fScaleY, fScaleZ);//x first
389
390 aRet = rMatrixScreenToScene * aRet;
391 return aRet;
392 }
393
getTransformationScaledLogicToScene() const394 uno::Reference< XTransformation > PolarPlottingPositionHelper::getTransformationScaledLogicToScene() const
395 {
396 if( !m_xTransformationLogicToScene.is() )
397 m_xTransformationLogicToScene = new VPolarTransformation(*this);
398 return m_xTransformationLogicToScene;
399 }
400
getWidthAngleDegree(double & fStartLogicValueOnAngleAxis,double & fEndLogicValueOnAngleAxis) const401 double PolarPlottingPositionHelper::getWidthAngleDegree( double& fStartLogicValueOnAngleAxis, double& fEndLogicValueOnAngleAxis ) const
402 {
403 const ExplicitScaleData& rAngleScale = m_bSwapXAndY ? m_aScales[1] : m_aScales[0];
404 if( rAngleScale.Orientation != AxisOrientation_MATHEMATICAL )
405 {
406 double fHelp = fEndLogicValueOnAngleAxis;
407 fEndLogicValueOnAngleAxis = fStartLogicValueOnAngleAxis;
408 fStartLogicValueOnAngleAxis = fHelp;
409 }
410
411 double fStartAngleDegree = transformToAngleDegree( fStartLogicValueOnAngleAxis );
412 double fEndAngleDegree = transformToAngleDegree( fEndLogicValueOnAngleAxis );
413 double fWidthAngleDegree = fEndAngleDegree - fStartAngleDegree;
414
415 if( ::rtl::math::approxEqual( fStartAngleDegree, fEndAngleDegree )
416 && !::rtl::math::approxEqual( fStartLogicValueOnAngleAxis, fEndLogicValueOnAngleAxis ) )
417 fWidthAngleDegree = 360.0;
418
419 // tdf#123504: both 0 and 360 are valid and different values here!
420 while (fWidthAngleDegree < 0.0)
421 fWidthAngleDegree += 360.0;
422 while (fWidthAngleDegree > 360.0)
423 fWidthAngleDegree -= 360.0;
424
425 return fWidthAngleDegree;
426 }
427
428 //This method does a lot of computation for understanding which scale to
429 //utilize and if reverse orientation should be used. Indeed, for a pie or donut,
430 //the final result is as simple as multiplying by 360 and adding
431 //`m_fAngleDegreeOffset`.
transformToAngleDegree(double fLogicValueOnAngleAxis,bool bDoScaling) const432 double PolarPlottingPositionHelper::transformToAngleDegree( double fLogicValueOnAngleAxis, bool bDoScaling ) const
433 {
434 double fRet=0.0;
435
436 double fAxisAngleScaleDirection = 1.0;
437 {
438 const ExplicitScaleData& rScale = m_bSwapXAndY ? m_aScales[1] : m_aScales[0];
439 if(rScale.Orientation != AxisOrientation_MATHEMATICAL)
440 fAxisAngleScaleDirection *= -1.0;
441 }
442
443 double MinAngleValue = 0.0;
444 double MaxAngleValue = 0.0;
445 {
446 double MinX = getLogicMinX();
447 double MinY = getLogicMinY();
448 double MaxX = getLogicMaxX();
449 double MaxY = getLogicMaxY();
450 double MinZ = getLogicMinZ();
451 double MaxZ = getLogicMaxZ();
452
453 doLogicScaling( &MinX, &MinY, &MinZ );
454 doLogicScaling( &MaxX, &MaxY, &MaxZ);
455
456 MinAngleValue = m_bSwapXAndY ? MinY : MinX;
457 MaxAngleValue = m_bSwapXAndY ? MaxY : MaxX;
458 }
459
460 double fScaledLogicAngleValue = 0.0;
461 if(bDoScaling)
462 {
463 double fX = m_bSwapXAndY ? getLogicMaxX() : fLogicValueOnAngleAxis;
464 double fY = m_bSwapXAndY ? fLogicValueOnAngleAxis : getLogicMaxY();
465 double fZ = getLogicMaxZ();
466 clipLogicValues( &fX, &fY, &fZ );
467 doLogicScaling( &fX, &fY, &fZ );
468 fScaledLogicAngleValue = m_bSwapXAndY ? fY : fX;
469 }
470 else
471 fScaledLogicAngleValue = fLogicValueOnAngleAxis;
472
473 fRet = m_fAngleDegreeOffset
474 + fAxisAngleScaleDirection*(fScaledLogicAngleValue-MinAngleValue)*360.0
475 /fabs(MaxAngleValue-MinAngleValue);
476 // tdf#123504: both 0 and 360 are valid and different values here!
477 while (fRet > 360.0)
478 fRet -= 360.0;
479 while (fRet < 0)
480 fRet += 360.0;
481 return fRet;
482 }
483
484 /**
485 * Given a value in the radius axis scale range, it returns, in the simplest
486 * case (that is when `m_fRadiusOffset` is zero), the normalized value; when
487 * `m_fRadiusOffset` is not zero (e.g. as in the case of a donut), the interval
488 * used for normalization is extended by `m_fRadiusOffset`: if the axis
489 * orientation is not reversed the new interval becomes
490 * [scale.Minimum - m_fRadiusOffset, scale.Maximum] else it becomes
491 * [scale.Minimum, scale.Maximum + m_fRadiusOffset].
492 * Pay attention here! For the latter case, since the axis orientation is
493 * reversed, the normalization is reversed too. Indeed, we have
494 * `transformToRadius(scale.Maximum + m_fRadiusOffset) = 0` and
495 * `transformToRadius(scale.Minimum) = 1`.
496 *
497 * For a pie chart the radius axis scale range is initialized by the
498 * `getMinimum` and `getMaximum` methods of the `PieChart` object (see notes
499 * for `VCoordinateSystem::prepareAutomaticAxisScaling`).
500 * So we have scale.Minimum = 0.5 (always constant!) and
501 * scale.Maximum = 0.5 + number_of_rings + max_offset
502 * (see notes for `PieChart::getMaxOffset`).
503 * Hence we get the following general formulas for computing normalized inner
504 * and outer radius:
505 *
506 * 1- transformToRadius(inner_radius) =
507 * (number_of_rings - (ring_index + 1) + m_fRadiusOffset)
508 * / (number_of_rings + max_offset + m_fRadiusOffset)
509 *
510 * 2- transformToRadius(outer_radius) =
511 * (1 + number_of_rings - (ring_index + 1) + m_fRadiusOffset)
512 * / (number_of_rings + max_offset + m_fRadiusOffset).
513 *
514 * Here you have to take into account that values for inner and outer radius
515 * are swapped since the radius axis is reversed (See notes for
516 * `PiePositionHelper::getInnerAndOuterRadius`). So indeed inner_radius is
517 * the outer and outer_radius is the inner. Anyway still because of the reverse
518 * orientation, the normalization performed by `transformToRadius` is reversed
519 * too, as we have seen above. Hence `transformToRadius(inner_radius)` is
520 * really the normalized inner radius and `transformToRadius(outer_radius)` is
521 * really the normalized outer radius.
522 *
523 * Some basic examples where we apply the above formulas:
524 * 1- For a non-exploded pie chart we have:
525 * `transformToRadius(inner_radius) = 0`,
526 * `transformToRadius(outer_radius) = 1`.
527 * 2- For a non-exploded donut with a single ring we have:
528 * `transformToRadius(inner_radius) =
529 * m_fRadiusOffset/(1 + m_fRadiusOffset)`,
530 * `transformToRadius(outer_radius) =
531 * (1 + m_fRadiusOffset)/(1 + m_fRadiusOffset) = 1`.
532 * 3- For an exploded pie chart we have:
533 * `transformToRadius(inner_radius) = 0/(1 + max_offset) = 0`,
534 * `transformToRadius(outer_radius) = 1/(1 + max_offset)`.
535 *
536 * The third example needs some remark. Both the logical inner and outer
537 * radius passed to `transformToRadius` are offset by `max_offset`.
538 * However the returned normalized values do not contain any (normalized)
539 * offset term at all, otherwise the returned values would be
540 * `max_offset/(1 + max_offset)` and `1`. Hence, for exploded pie/donut,
541 * `transformToRadius` returns the normalized value of radii without any
542 * offset term. These values are smaller than in the non-exploded case by an
543 * amount equals to the value of the normalized maximum offset
544 * (`max_offset/(1 + max_offset)` in the example above). That is due to the
545 * fact that the normalization keeps into account the space needed for the
546 * offset. This is the correct behavior, in fact the offset for the current
547 * slice could be different from the maximum offset.
548 * These remarks should clarify why the `PieChart::createDataPoint` and
549 * `PieChart::createTextLabelShape` methods add the normalized offset (for the
550 * current slice) to the normalized radii in order to achieve the correct
551 * placement of slice and text shapes.
552 */
transformToRadius(double fLogicValueOnRadiusAxis,bool bDoScaling) const553 double PolarPlottingPositionHelper::transformToRadius( double fLogicValueOnRadiusAxis, bool bDoScaling ) const
554 {
555 double fNormalRadius = 0.0;
556 {
557 double fScaledLogicRadiusValue = 0.0;
558 double fX = m_bSwapXAndY ? fLogicValueOnRadiusAxis: getLogicMaxX();
559 double fY = m_bSwapXAndY ? getLogicMaxY() : fLogicValueOnRadiusAxis;
560 if(bDoScaling)
561 doLogicScaling( &fX, &fY, nullptr );
562
563 fScaledLogicRadiusValue = m_bSwapXAndY ? fX : fY;
564
565 bool bMinIsInnerRadius = true;
566 const ExplicitScaleData& rScale = m_bSwapXAndY ? m_aScales[0] : m_aScales[1];
567 if(rScale.Orientation != AxisOrientation_MATHEMATICAL)
568 bMinIsInnerRadius = false;
569
570 double fInnerScaledLogicRadius=0.0;
571 double fOuterScaledLogicRadius=0.0;
572 {
573 double MinX = getLogicMinX();
574 double MinY = getLogicMinY();
575 doLogicScaling( &MinX, &MinY, nullptr );
576 double MaxX = getLogicMaxX();
577 double MaxY = getLogicMaxY();
578 doLogicScaling( &MaxX, &MaxY, nullptr );
579
580 double fMin = m_bSwapXAndY ? MinX : MinY;
581 double fMax = m_bSwapXAndY ? MaxX : MaxY;
582
583 fInnerScaledLogicRadius = bMinIsInnerRadius ? fMin : fMax;
584 fOuterScaledLogicRadius = bMinIsInnerRadius ? fMax : fMin;
585 }
586
587 if( bMinIsInnerRadius )
588 fInnerScaledLogicRadius -= fabs(m_fRadiusOffset);
589 else
590 fInnerScaledLogicRadius += fabs(m_fRadiusOffset);
591 fNormalRadius = (fScaledLogicRadiusValue-fInnerScaledLogicRadius)/(fOuterScaledLogicRadius-fInnerScaledLogicRadius);
592 }
593 return fNormalRadius;
594 }
595
transformLogicToScene(double fX,double fY,double fZ,bool bClip) const596 drawing::Position3D PolarPlottingPositionHelper::transformLogicToScene( double fX, double fY, double fZ, bool bClip ) const
597 {
598 if(bClip)
599 clipLogicValues( &fX,&fY,&fZ );
600 double fLogicValueOnAngleAxis = m_bSwapXAndY ? fY : fX;
601 double fLogicValueOnRadiusAxis = m_bSwapXAndY ? fX : fY;
602 return transformAngleRadiusToScene( fLogicValueOnAngleAxis, fLogicValueOnRadiusAxis, fZ );
603 }
604
transformScaledLogicToScene(double fX,double fY,double fZ,bool bClip) const605 drawing::Position3D PolarPlottingPositionHelper::transformScaledLogicToScene( double fX, double fY, double fZ, bool bClip ) const
606 {
607 if(bClip)
608 clipScaledLogicValues( &fX,&fY,&fZ );
609 double fLogicValueOnAngleAxis = m_bSwapXAndY ? fY : fX;
610 double fLogicValueOnRadiusAxis = m_bSwapXAndY ? fX : fY;
611 return transformAngleRadiusToScene( fLogicValueOnAngleAxis, fLogicValueOnRadiusAxis, fZ, false );
612 }
transformUnitCircleToScene(double fUnitAngleDegree,double fUnitRadius,double fLogicZ) const613 drawing::Position3D PolarPlottingPositionHelper::transformUnitCircleToScene( double fUnitAngleDegree, double fUnitRadius
614 , double fLogicZ ) const
615 {
616 double fAnglePi = basegfx::deg2rad(fUnitAngleDegree);
617
618 double fX=fUnitRadius*rtl::math::cos(fAnglePi);
619 double fY=fUnitRadius*rtl::math::sin(fAnglePi);
620 double fZ=fLogicZ;
621
622 //!! applying matrix to vector does ignore translation, so it is important to use a B3DPoint here instead of B3DVector
623 ::basegfx::B3DPoint aPoint(fX,fY,fZ);
624 ::basegfx::B3DPoint aRet = m_aUnitCartesianToScene * aPoint;
625 return B3DPointToPosition3D(aRet);
626 }
627
transformAngleRadiusToScene(double fLogicValueOnAngleAxis,double fLogicValueOnRadiusAxis,double fLogicZ,bool bDoScaling) const628 drawing::Position3D PolarPlottingPositionHelper::transformAngleRadiusToScene( double fLogicValueOnAngleAxis, double fLogicValueOnRadiusAxis, double fLogicZ, bool bDoScaling ) const
629 {
630 double fUnitAngleDegree = transformToAngleDegree(fLogicValueOnAngleAxis,bDoScaling);
631 double fUnitRadius = transformToRadius(fLogicValueOnRadiusAxis,bDoScaling);
632
633 return transformUnitCircleToScene( fUnitAngleDegree, fUnitRadius, fLogicZ );
634 }
635
getOuterLogicRadius() const636 double PolarPlottingPositionHelper::getOuterLogicRadius() const
637 {
638 const ExplicitScaleData& rScale = m_bSwapXAndY ? m_aScales[0] : m_aScales[1];
639 if( rScale.Orientation==AxisOrientation_MATHEMATICAL )
640 return rScale.Maximum;
641 else
642 return rScale.Minimum;
643 }
644
isPercentY() const645 bool PlottingPositionHelper::isPercentY() const
646 {
647 return m_aScales[1].AxisType==AxisType::PERCENT;
648 }
649
getBaseValueY() const650 double PlottingPositionHelper::getBaseValueY() const
651 {
652 return m_aScales[1].Origin;
653 }
654
setTimeResolution(tools::Long nTimeResolution,const Date & rNullDate)655 void PlottingPositionHelper::setTimeResolution( tools::Long nTimeResolution, const Date& rNullDate )
656 {
657 m_nTimeResolution = nTimeResolution;
658 m_aNullDate = rNullDate;
659
660 //adapt category width
661 double fCategoryWidth = 1.0;
662 if( !m_aScales.empty() )
663 {
664 if( m_aScales[0].AxisType == css::chart2::AxisType::DATE )
665 {
666 m_bDateAxis = true;
667 if( nTimeResolution == css::chart::TimeUnit::YEAR )
668 {
669 const double fMonthCount = 12.0;//todo: this depends on the DateScaling and must be adjusted in case we use more generic calendars in future
670 fCategoryWidth = fMonthCount;
671 }
672 }
673 }
674 setScaledCategoryWidth(fCategoryWidth);
675 }
676
setScaledCategoryWidth(double fScaledCategoryWidth)677 void PlottingPositionHelper::setScaledCategoryWidth( double fScaledCategoryWidth )
678 {
679 m_fScaledCategoryWidth = fScaledCategoryWidth;
680 }
AllowShiftXAxisPos(bool bAllowShift)681 void PlottingPositionHelper::AllowShiftXAxisPos( bool bAllowShift )
682 {
683 m_bAllowShiftXAxisPos = bAllowShift;
684 }
AllowShiftZAxisPos(bool bAllowShift)685 void PlottingPositionHelper::AllowShiftZAxisPos( bool bAllowShift )
686 {
687 m_bAllowShiftZAxisPos = bAllowShift;
688 }
689
690 }
691
692 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
693