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