summaryrefslogtreecommitdiffstats
path: root/chart2/source/view/main/PlottingPositionHelper.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /chart2/source/view/main/PlottingPositionHelper.cxx
parentInitial commit. (diff)
downloadlibreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz
libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--chart2/source/view/main/PlottingPositionHelper.cxx708
1 files changed, 708 insertions, 0 deletions
diff --git a/chart2/source/view/main/PlottingPositionHelper.cxx b/chart2/source/view/main/PlottingPositionHelper.cxx
new file mode 100644
index 000000000..eab2f69e7
--- /dev/null
+++ b/chart2/source/view/main/PlottingPositionHelper.cxx
@@ -0,0 +1,708 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <PlottingPositionHelper.hxx>
+#include <CommonConverters.hxx>
+#include <Linear3DTransformation.hxx>
+#include <VPolarTransformation.hxx>
+#include <ShapeFactory.hxx>
+#include <PropertyMapper.hxx>
+#include <defines.hxx>
+
+#include <com/sun/star/chart/TimeUnit.hpp>
+#include <com/sun/star/chart2/AxisType.hpp>
+#include <com/sun/star/drawing/Position3D.hpp>
+
+#include <rtl/math.hxx>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::chart2;
+
+XTransformation2::~XTransformation2() {}
+
+PlottingPositionHelper::PlottingPositionHelper()
+ : m_bSwapXAndY( false )
+ , m_nXResolution( 1000 )
+ , m_nYResolution( 1000 )
+ , m_nZResolution( 1000 )
+ , m_bMaySkipPointsInRegressionCalculation( true )
+ , m_bDateAxis(false)
+ , m_nTimeResolution( css::chart::TimeUnit::DAY )
+ , m_aNullDate(30,12,1899)
+ , m_fScaledCategoryWidth(1.0)
+ , m_bAllowShiftXAxisPos(false)
+ , m_bAllowShiftZAxisPos(false)
+{
+}
+PlottingPositionHelper::PlottingPositionHelper( const PlottingPositionHelper& rSource )
+ : m_aScales( rSource.m_aScales )
+ , m_aMatrixScreenToScene( rSource.m_aMatrixScreenToScene )
+ // m_xTransformationLogicToScene( nullptr ) //should be recalculated
+ , m_bSwapXAndY( rSource.m_bSwapXAndY )
+ , m_nXResolution( rSource.m_nXResolution )
+ , m_nYResolution( rSource.m_nYResolution )
+ , m_nZResolution( rSource.m_nZResolution )
+ , m_bMaySkipPointsInRegressionCalculation( rSource.m_bMaySkipPointsInRegressionCalculation )
+ , m_bDateAxis( rSource.m_bDateAxis )
+ , m_nTimeResolution( rSource.m_nTimeResolution )
+ , m_aNullDate( rSource.m_aNullDate )
+ , m_fScaledCategoryWidth( rSource.m_fScaledCategoryWidth )
+ , m_bAllowShiftXAxisPos( rSource.m_bAllowShiftXAxisPos )
+ , m_bAllowShiftZAxisPos( rSource.m_bAllowShiftZAxisPos )
+{
+}
+
+PlottingPositionHelper::~PlottingPositionHelper()
+{
+
+}
+
+std::unique_ptr<PlottingPositionHelper> PlottingPositionHelper::clone() const
+{
+ return std::make_unique<PlottingPositionHelper>(*this);
+}
+
+std::unique_ptr<PlottingPositionHelper> PlottingPositionHelper::createSecondaryPosHelper( const ExplicitScaleData& rSecondaryScale )
+{
+ auto pRet = clone();
+ pRet->m_aScales[1]=rSecondaryScale;
+ return pRet;
+}
+
+void PlottingPositionHelper::setTransformationSceneToScreen( const drawing::HomogenMatrix& rMatrix)
+{
+ m_aMatrixScreenToScene = HomogenMatrixToB3DHomMatrix(rMatrix);
+ m_xTransformationLogicToScene = nullptr;
+}
+
+void PlottingPositionHelper::setScales( std::vector< ExplicitScaleData >&& rScales, bool bSwapXAndYAxis )
+{
+ m_aScales = std::move(rScales);
+ m_bSwapXAndY = bSwapXAndYAxis;
+ m_xTransformationLogicToScene = nullptr;
+}
+
+::chart::XTransformation2* PlottingPositionHelper::getTransformationScaledLogicToScene() const
+{
+ //this is a standard transformation for a cartesian coordinate system
+
+ //transformation from 2) to 4) //@todo 2) and 4) need an ink to a document
+
+ //we need to apply this transformation to each geometric object because of a bug/problem
+ //of the old drawing layer (the UNO_NAME_3D_EXTRUDE_DEPTH is an integer value instead of a double )
+ if(!m_xTransformationLogicToScene)
+ {
+ ::basegfx::B3DHomMatrix aMatrix;
+ double MinX = getLogicMinX();
+ double MinY = getLogicMinY();
+ double MinZ = getLogicMinZ();
+ double MaxX = getLogicMaxX();
+ double MaxY = getLogicMaxY();
+ double MaxZ = getLogicMaxZ();
+
+ AxisOrientation nXAxisOrientation = m_aScales[0].Orientation;
+ AxisOrientation nYAxisOrientation = m_aScales[1].Orientation;
+ AxisOrientation nZAxisOrientation = m_aScales[2].Orientation;
+
+ //apply scaling
+ doUnshiftedLogicScaling( &MinX, &MinY, &MinZ );
+ doUnshiftedLogicScaling( &MaxX, &MaxY, &MaxZ);
+
+ if(m_bSwapXAndY)
+ {
+ std::swap(MinX,MinY);
+ std::swap(MaxX,MaxY);
+ std::swap(nXAxisOrientation,nYAxisOrientation);
+ }
+
+ double fWidthX = MaxX - MinX;
+ double fWidthY = MaxY - MinY;
+ double fWidthZ = MaxZ - MinZ;
+
+ double fScaleDirectionX = nXAxisOrientation==AxisOrientation_MATHEMATICAL ? 1.0 : -1.0;
+ double fScaleDirectionY = nYAxisOrientation==AxisOrientation_MATHEMATICAL ? 1.0 : -1.0;
+ double fScaleDirectionZ = nZAxisOrientation==AxisOrientation_MATHEMATICAL ? -1.0 : 1.0;
+
+ double fScaleX = fScaleDirectionX*FIXED_SIZE_FOR_3D_CHART_VOLUME/fWidthX;
+ double fScaleY = fScaleDirectionY*FIXED_SIZE_FOR_3D_CHART_VOLUME/fWidthY;
+ double fScaleZ = fScaleDirectionZ*FIXED_SIZE_FOR_3D_CHART_VOLUME/fWidthZ;
+
+ aMatrix.scale(fScaleX, fScaleY, fScaleZ);
+
+ if( nXAxisOrientation==AxisOrientation_MATHEMATICAL )
+ aMatrix.translate(-MinX*fScaleX, 0.0, 0.0);
+ else
+ aMatrix.translate(-MaxX*fScaleX, 0.0, 0.0);
+ if( nYAxisOrientation==AxisOrientation_MATHEMATICAL )
+ aMatrix.translate(0.0, -MinY*fScaleY, 0.0);
+ else
+ aMatrix.translate(0.0, -MaxY*fScaleY, 0.0);
+ if( nZAxisOrientation==AxisOrientation_MATHEMATICAL )
+ aMatrix.translate(0.0, 0.0, -MaxZ*fScaleZ);//z direction in draw is reverse mathematical direction
+ else
+ aMatrix.translate(0.0, 0.0, -MinZ*fScaleZ);
+
+ aMatrix = m_aMatrixScreenToScene*aMatrix;
+
+ m_xTransformationLogicToScene.reset(new Linear3DTransformation(B3DHomMatrixToHomogenMatrix( aMatrix ), m_bSwapXAndY));
+ }
+ return m_xTransformationLogicToScene.get();
+}
+
+drawing::Position3D PlottingPositionHelper::transformLogicToScene(
+ double fX, double fY, double fZ, bool bClip ) const
+{
+ doLogicScaling( &fX,&fY,&fZ );
+ if(bClip)
+ clipScaledLogicValues( &fX,&fY,&fZ );
+
+ return transformScaledLogicToScene( fX, fY, fZ, false );
+}
+
+drawing::Position3D PlottingPositionHelper::transformScaledLogicToScene(
+ double fX, double fY, double fZ, bool bClip ) const
+{
+ if( bClip )
+ clipScaledLogicValues( &fX,&fY,&fZ );
+
+ drawing::Position3D aPos( fX, fY, fZ);
+
+ ::chart::XTransformation2* pTransformation =
+ getTransformationScaledLogicToScene();
+ return pTransformation->transform( aPos );
+}
+
+awt::Point PlottingPositionHelper::transformSceneToScreenPosition( const drawing::Position3D& rScenePosition3D
+ , const rtl::Reference<SvxShapeGroupAnyD>& xSceneTarget
+ , sal_Int32 nDimensionCount )
+{
+ //@todo would like to have a cheaper method to do this transformation
+ awt::Point aScreenPoint( static_cast<sal_Int32>(rScenePosition3D.PositionX), static_cast<sal_Int32>(rScenePosition3D.PositionY) );
+
+ //transformation from scene to screen (only necessary for 3D):
+ if(nDimensionCount==3)
+ {
+ //create 3D anchor shape
+ tPropertyNameMap aDummyPropertyNameMap;
+ rtl::Reference<Svx3DExtrudeObject> xShape3DAnchor = ShapeFactory::createCube( xSceneTarget
+ , rScenePosition3D,drawing::Direction3D(1,1,1)
+ , 0, nullptr, aDummyPropertyNameMap);
+ //get 2D position from xShape3DAnchor
+ aScreenPoint = xShape3DAnchor->getPosition();
+ xSceneTarget->remove(xShape3DAnchor);
+ }
+ return aScreenPoint;
+}
+
+void PlottingPositionHelper::transformScaledLogicToScene( drawing::PolyPolygonShape3D& rPolygon ) const
+{
+ drawing::Position3D aScenePosition;
+ auto SequenceXRange = asNonConstRange(rPolygon.SequenceX);
+ auto SequenceYRange = asNonConstRange(rPolygon.SequenceY);
+ auto SequenceZRange = asNonConstRange(rPolygon.SequenceZ);
+ for( sal_Int32 nS = rPolygon.SequenceX.getLength(); nS--;)
+ {
+ auto xValuesRange = asNonConstRange(SequenceXRange[nS]);
+ auto yValuesRange = asNonConstRange(SequenceYRange[nS]);
+ auto zValuesRange = asNonConstRange(SequenceZRange[nS]);
+ for( sal_Int32 nP = SequenceXRange[nS].getLength(); nP--; )
+ {
+ double& fX = xValuesRange[nP];
+ double& fY = yValuesRange[nP];
+ double& fZ = zValuesRange[nP];
+ aScenePosition = transformScaledLogicToScene( fX,fY,fZ,true );
+ fX = aScenePosition.PositionX;
+ fY = aScenePosition.PositionY;
+ fZ = aScenePosition.PositionZ;
+ }
+ }
+}
+
+void PlottingPositionHelper::transformScaledLogicToScene( std::vector<std::vector<css::drawing::Position3D>>& rPolygon ) const
+{
+ drawing::Position3D aScenePosition;
+ for( sal_Int32 nS = static_cast<sal_Int32>(rPolygon.size()); nS--;)
+ {
+ auto valuesRange = rPolygon[nS].data();
+ for( sal_Int32 nP = rPolygon[nS].size(); nP--; )
+ {
+ double& fX = valuesRange[nP].PositionX;
+ double& fY = valuesRange[nP].PositionY;
+ double& fZ = valuesRange[nP].PositionZ;
+ aScenePosition = transformScaledLogicToScene( fX,fY,fZ,true );
+ fX = aScenePosition.PositionX;
+ fY = aScenePosition.PositionY;
+ fZ = aScenePosition.PositionZ;
+ }
+ }
+}
+
+void PlottingPositionHelper::clipScaledLogicValues( double* pX, double* pY, double* pZ ) const
+{
+ //get logic clip values:
+ double MinX = getLogicMinX();
+ double MinY = getLogicMinY();
+ double MinZ = getLogicMinZ();
+ double MaxX = getLogicMaxX();
+ double MaxY = getLogicMaxY();
+ double MaxZ = getLogicMaxZ();
+
+ //apply scaling
+ doUnshiftedLogicScaling( &MinX, &MinY, &MinZ );
+ doUnshiftedLogicScaling( &MaxX, &MaxY, &MaxZ);
+
+ if(pX)
+ {
+ if( *pX < MinX )
+ *pX = MinX;
+ else if( *pX > MaxX )
+ *pX = MaxX;
+ }
+ if(pY)
+ {
+ if( *pY < MinY )
+ *pY = MinY;
+ else if( *pY > MaxY )
+ *pY = MaxY;
+ }
+ if(pZ)
+ {
+ if( *pZ < MinZ )
+ *pZ = MinZ;
+ else if( *pZ > MaxZ )
+ *pZ = MaxZ;
+ }
+}
+
+basegfx::B2DRectangle PlottingPositionHelper::getScaledLogicClipDoubleRect() const
+{
+ //get logic clip values:
+ double MinX = getLogicMinX();
+ double MinY = getLogicMinY();
+ double MinZ = getLogicMinZ();
+ double MaxX = getLogicMaxX();
+ double MaxY = getLogicMaxY();
+ double MaxZ = getLogicMaxZ();
+
+ //apply scaling
+ doUnshiftedLogicScaling( &MinX, &MinY, &MinZ );
+ doUnshiftedLogicScaling( &MaxX, &MaxY, &MaxZ);
+
+ basegfx::B2DRectangle aRet( MinX, MaxY, MaxX, MinY );
+ return aRet;
+}
+
+drawing::Direction3D PlottingPositionHelper::getScaledLogicWidth() const
+{
+ drawing::Direction3D aRet;
+
+ double MinX = getLogicMinX();
+ double MinY = getLogicMinY();
+ double MinZ = getLogicMinZ();
+ double MaxX = getLogicMaxX();
+ double MaxY = getLogicMaxY();
+ double MaxZ = getLogicMaxZ();
+
+ doLogicScaling( &MinX, &MinY, &MinZ );
+ doLogicScaling( &MaxX, &MaxY, &MaxZ);
+
+ aRet.DirectionX = MaxX - MinX;
+ aRet.DirectionY = MaxY - MinY;
+ aRet.DirectionZ = MaxZ - MinZ;
+ return aRet;
+}
+
+PolarPlottingPositionHelper::PolarPlottingPositionHelper()
+ : m_fRadiusOffset(0.0)
+ , m_fAngleDegreeOffset(90.0)
+{
+ m_bMaySkipPointsInRegressionCalculation = false;
+}
+
+PolarPlottingPositionHelper::PolarPlottingPositionHelper( const PolarPlottingPositionHelper& rSource )
+ : PlottingPositionHelper(rSource)
+ , m_fRadiusOffset( rSource.m_fRadiusOffset )
+ , m_fAngleDegreeOffset( rSource.m_fAngleDegreeOffset )
+ , m_aUnitCartesianToScene( rSource.m_aUnitCartesianToScene )
+{
+}
+
+PolarPlottingPositionHelper::~PolarPlottingPositionHelper()
+{
+}
+
+std::unique_ptr<PlottingPositionHelper> PolarPlottingPositionHelper::clone() const
+{
+ return std::make_unique<PolarPlottingPositionHelper>(*this);
+}
+
+void PolarPlottingPositionHelper::setTransformationSceneToScreen( const drawing::HomogenMatrix& rMatrix)
+{
+ PlottingPositionHelper::setTransformationSceneToScreen( rMatrix);
+ m_aUnitCartesianToScene =impl_calculateMatrixUnitCartesianToScene( m_aMatrixScreenToScene );
+}
+void PolarPlottingPositionHelper::setScales( std::vector< ExplicitScaleData >&& rScales, bool bSwapXAndYAxis )
+{
+ PlottingPositionHelper::setScales( std::move(rScales), bSwapXAndYAxis );
+ m_aUnitCartesianToScene =impl_calculateMatrixUnitCartesianToScene( m_aMatrixScreenToScene );
+}
+
+::basegfx::B3DHomMatrix PolarPlottingPositionHelper::impl_calculateMatrixUnitCartesianToScene( const ::basegfx::B3DHomMatrix& rMatrixScreenToScene ) const
+{
+ ::basegfx::B3DHomMatrix aRet;
+
+ if( m_aScales.empty() )
+ return aRet;
+
+ double fTranslate =1.0;
+ double fScale =FIXED_SIZE_FOR_3D_CHART_VOLUME/2.0;
+
+ double fTranslateLogicZ;
+ double fScaleLogicZ;
+ {
+ double fScaleDirectionZ = m_aScales[2].Orientation==AxisOrientation_MATHEMATICAL ? 1.0 : -1.0;
+ double MinZ = getLogicMinZ();
+ double MaxZ = getLogicMaxZ();
+ doLogicScaling( nullptr, nullptr, &MinZ );
+ doLogicScaling( nullptr, nullptr, &MaxZ );
+ double fWidthZ = MaxZ - MinZ;
+
+ if( m_aScales[2].Orientation==AxisOrientation_MATHEMATICAL )
+ fTranslateLogicZ=MinZ;
+ else
+ fTranslateLogicZ=MaxZ;
+ fScaleLogicZ = fScaleDirectionZ*FIXED_SIZE_FOR_3D_CHART_VOLUME/fWidthZ;
+ }
+
+ double fTranslateX = fTranslate;
+ double fTranslateY = fTranslate;
+ double fTranslateZ = fTranslateLogicZ;
+
+ double fScaleX = fScale;
+ double fScaleY = fScale;
+ double fScaleZ = fScaleLogicZ;
+
+ aRet.translate(fTranslateX, fTranslateY, fTranslateZ);//x first
+ aRet.scale(fScaleX, fScaleY, fScaleZ);//x first
+
+ aRet = rMatrixScreenToScene * aRet;
+ return aRet;
+}
+
+::chart::XTransformation2* PolarPlottingPositionHelper::getTransformationScaledLogicToScene() const
+{
+ if( !m_xTransformationLogicToScene )
+ m_xTransformationLogicToScene.reset(new VPolarTransformation(*this));
+ return m_xTransformationLogicToScene.get();
+}
+
+double PolarPlottingPositionHelper::getWidthAngleDegree( double& fStartLogicValueOnAngleAxis, double& fEndLogicValueOnAngleAxis ) const
+{
+ const ExplicitScaleData& rAngleScale = m_bSwapXAndY ? m_aScales[1] : m_aScales[0];
+ if( rAngleScale.Orientation != AxisOrientation_MATHEMATICAL )
+ {
+ double fHelp = fEndLogicValueOnAngleAxis;
+ fEndLogicValueOnAngleAxis = fStartLogicValueOnAngleAxis;
+ fStartLogicValueOnAngleAxis = fHelp;
+ }
+
+ double fStartAngleDegree = transformToAngleDegree( fStartLogicValueOnAngleAxis );
+ double fEndAngleDegree = transformToAngleDegree( fEndLogicValueOnAngleAxis );
+ double fWidthAngleDegree = fEndAngleDegree - fStartAngleDegree;
+
+ if( ::rtl::math::approxEqual( fStartAngleDegree, fEndAngleDegree )
+ && !::rtl::math::approxEqual( fStartLogicValueOnAngleAxis, fEndLogicValueOnAngleAxis ) )
+ fWidthAngleDegree = 360.0;
+
+ // tdf#123504: both 0 and 360 are valid and different values here!
+ while (fWidthAngleDegree < 0.0)
+ fWidthAngleDegree += 360.0;
+ while (fWidthAngleDegree > 360.0)
+ fWidthAngleDegree -= 360.0;
+
+ return fWidthAngleDegree;
+}
+
+//This method does a lot of computation for understanding which scale to
+//utilize and if reverse orientation should be used. Indeed, for a pie or donut,
+//the final result is as simple as multiplying by 360 and adding
+//`m_fAngleDegreeOffset`.
+double PolarPlottingPositionHelper::transformToAngleDegree( double fLogicValueOnAngleAxis, bool bDoScaling ) const
+{
+ double fRet=0.0;
+
+ double fAxisAngleScaleDirection = 1.0;
+ {
+ const ExplicitScaleData& rScale = m_bSwapXAndY ? m_aScales[1] : m_aScales[0];
+ if(rScale.Orientation != AxisOrientation_MATHEMATICAL)
+ fAxisAngleScaleDirection *= -1.0;
+ }
+
+ double MinAngleValue = 0.0;
+ double MaxAngleValue = 0.0;
+ {
+ double MinX = getLogicMinX();
+ double MinY = getLogicMinY();
+ double MaxX = getLogicMaxX();
+ double MaxY = getLogicMaxY();
+ double MinZ = getLogicMinZ();
+ double MaxZ = getLogicMaxZ();
+
+ doLogicScaling( &MinX, &MinY, &MinZ );
+ doLogicScaling( &MaxX, &MaxY, &MaxZ);
+
+ MinAngleValue = m_bSwapXAndY ? MinY : MinX;
+ MaxAngleValue = m_bSwapXAndY ? MaxY : MaxX;
+ }
+
+ double fScaledLogicAngleValue = 0.0;
+ if(bDoScaling)
+ {
+ double fX = m_bSwapXAndY ? getLogicMaxX() : fLogicValueOnAngleAxis;
+ double fY = m_bSwapXAndY ? fLogicValueOnAngleAxis : getLogicMaxY();
+ double fZ = getLogicMaxZ();
+ clipLogicValues( &fX, &fY, &fZ );
+ doLogicScaling( &fX, &fY, &fZ );
+ fScaledLogicAngleValue = m_bSwapXAndY ? fY : fX;
+ }
+ else
+ fScaledLogicAngleValue = fLogicValueOnAngleAxis;
+
+ fRet = m_fAngleDegreeOffset
+ + fAxisAngleScaleDirection*(fScaledLogicAngleValue-MinAngleValue)*360.0
+ /fabs(MaxAngleValue-MinAngleValue);
+ // tdf#123504: both 0 and 360 are valid and different values here!
+ while (fRet > 360.0)
+ fRet -= 360.0;
+ while (fRet < 0)
+ fRet += 360.0;
+ return fRet;
+}
+
+/**
+ * Given a value in the radius axis scale range, it returns, in the simplest
+ * case (that is when `m_fRadiusOffset` is zero), the normalized value; when
+ * `m_fRadiusOffset` is not zero (e.g. as in the case of a donut), the interval
+ * used for normalization is extended by `m_fRadiusOffset`: if the axis
+ * orientation is not reversed the new interval becomes
+ * [scale.Minimum - m_fRadiusOffset, scale.Maximum] else it becomes
+ * [scale.Minimum, scale.Maximum + m_fRadiusOffset].
+ * Pay attention here! For the latter case, since the axis orientation is
+ * reversed, the normalization is reversed too. Indeed, we have
+ * `transformToRadius(scale.Maximum + m_fRadiusOffset) = 0` and
+ * `transformToRadius(scale.Minimum) = 1`.
+ *
+ * For a pie chart the radius axis scale range is initialized by the
+ * `getMinimum` and `getMaximum` methods of the `PieChart` object (see notes
+ * for `VCoordinateSystem::prepareAutomaticAxisScaling`).
+ * So we have scale.Minimum = 0.5 (always constant!) and
+ * scale.Maximum = 0.5 + number_of_rings + max_offset
+ * (see notes for `PieChart::getMaxOffset`).
+ * Hence we get the following general formulas for computing normalized inner
+ * and outer radius:
+ *
+ * 1- transformToRadius(inner_radius) =
+ * (number_of_rings - (ring_index + 1) + m_fRadiusOffset)
+ * / (number_of_rings + max_offset + m_fRadiusOffset)
+ *
+ * 2- transformToRadius(outer_radius) =
+ * (1 + number_of_rings - (ring_index + 1) + m_fRadiusOffset)
+ * / (number_of_rings + max_offset + m_fRadiusOffset).
+ *
+ * Here you have to take into account that values for inner and outer radius
+ * are swapped since the radius axis is reversed (See notes for
+ * `PiePositionHelper::getInnerAndOuterRadius`). So indeed inner_radius is
+ * the outer and outer_radius is the inner. Anyway still because of the reverse
+ * orientation, the normalization performed by `transformToRadius` is reversed
+ * too, as we have seen above. Hence `transformToRadius(inner_radius)` is
+ * really the normalized inner radius and `transformToRadius(outer_radius)` is
+ * really the normalized outer radius.
+ *
+ * Some basic examples where we apply the above formulas:
+ * 1- For a non-exploded pie chart we have:
+ * `transformToRadius(inner_radius) = 0`,
+ * `transformToRadius(outer_radius) = 1`.
+ * 2- For a non-exploded donut with a single ring we have:
+ * `transformToRadius(inner_radius) =
+ * m_fRadiusOffset/(1 + m_fRadiusOffset)`,
+ * `transformToRadius(outer_radius) =
+ * (1 + m_fRadiusOffset)/(1 + m_fRadiusOffset) = 1`.
+ * 3- For an exploded pie chart we have:
+ * `transformToRadius(inner_radius) = 0/(1 + max_offset) = 0`,
+ * `transformToRadius(outer_radius) = 1/(1 + max_offset)`.
+ *
+ * The third example needs some remark. Both the logical inner and outer
+ * radius passed to `transformToRadius` are offset by `max_offset`.
+ * However the returned normalized values do not contain any (normalized)
+ * offset term at all, otherwise the returned values would be
+ * `max_offset/(1 + max_offset)` and `1`. Hence, for exploded pie/donut,
+ * `transformToRadius` returns the normalized value of radii without any
+ * offset term. These values are smaller than in the non-exploded case by an
+ * amount equals to the value of the normalized maximum offset
+ * (`max_offset/(1 + max_offset)` in the example above). That is due to the
+ * fact that the normalization keeps into account the space needed for the
+ * offset. This is the correct behavior, in fact the offset for the current
+ * slice could be different from the maximum offset.
+ * These remarks should clarify why the `PieChart::createDataPoint` and
+ * `PieChart::createTextLabelShape` methods add the normalized offset (for the
+ * current slice) to the normalized radii in order to achieve the correct
+ * placement of slice and text shapes.
+ */
+double PolarPlottingPositionHelper::transformToRadius( double fLogicValueOnRadiusAxis, bool bDoScaling ) const
+{
+ double fNormalRadius = 0.0;
+ {
+ double fScaledLogicRadiusValue = 0.0;
+ double fX = m_bSwapXAndY ? fLogicValueOnRadiusAxis: getLogicMaxX();
+ double fY = m_bSwapXAndY ? getLogicMaxY() : fLogicValueOnRadiusAxis;
+ if(bDoScaling)
+ doLogicScaling( &fX, &fY, nullptr );
+
+ fScaledLogicRadiusValue = m_bSwapXAndY ? fX : fY;
+
+ bool bMinIsInnerRadius = true;
+ const ExplicitScaleData& rScale = m_bSwapXAndY ? m_aScales[0] : m_aScales[1];
+ if(rScale.Orientation != AxisOrientation_MATHEMATICAL)
+ bMinIsInnerRadius = false;
+
+ double fInnerScaledLogicRadius=0.0;
+ double fOuterScaledLogicRadius=0.0;
+ {
+ double MinX = getLogicMinX();
+ double MinY = getLogicMinY();
+ doLogicScaling( &MinX, &MinY, nullptr );
+ double MaxX = getLogicMaxX();
+ double MaxY = getLogicMaxY();
+ doLogicScaling( &MaxX, &MaxY, nullptr );
+
+ double fMin = m_bSwapXAndY ? MinX : MinY;
+ double fMax = m_bSwapXAndY ? MaxX : MaxY;
+
+ fInnerScaledLogicRadius = bMinIsInnerRadius ? fMin : fMax;
+ fOuterScaledLogicRadius = bMinIsInnerRadius ? fMax : fMin;
+ }
+
+ if( bMinIsInnerRadius )
+ fInnerScaledLogicRadius -= fabs(m_fRadiusOffset);
+ else
+ fInnerScaledLogicRadius += fabs(m_fRadiusOffset);
+ fNormalRadius = (fScaledLogicRadiusValue-fInnerScaledLogicRadius)/(fOuterScaledLogicRadius-fInnerScaledLogicRadius);
+ }
+ return fNormalRadius;
+}
+
+drawing::Position3D PolarPlottingPositionHelper::transformLogicToScene( double fX, double fY, double fZ, bool bClip ) const
+{
+ if(bClip)
+ clipLogicValues( &fX,&fY,&fZ );
+ double fLogicValueOnAngleAxis = m_bSwapXAndY ? fY : fX;
+ double fLogicValueOnRadiusAxis = m_bSwapXAndY ? fX : fY;
+ return transformAngleRadiusToScene( fLogicValueOnAngleAxis, fLogicValueOnRadiusAxis, fZ );
+}
+
+drawing::Position3D PolarPlottingPositionHelper::transformScaledLogicToScene( double fX, double fY, double fZ, bool bClip ) const
+{
+ if(bClip)
+ clipScaledLogicValues( &fX,&fY,&fZ );
+ double fLogicValueOnAngleAxis = m_bSwapXAndY ? fY : fX;
+ double fLogicValueOnRadiusAxis = m_bSwapXAndY ? fX : fY;
+ return transformAngleRadiusToScene( fLogicValueOnAngleAxis, fLogicValueOnRadiusAxis, fZ, false );
+}
+drawing::Position3D PolarPlottingPositionHelper::transformUnitCircleToScene( double fUnitAngleDegree, double fUnitRadius
+ , double fLogicZ ) const
+{
+ double fAnglePi = basegfx::deg2rad(fUnitAngleDegree);
+
+ double fX=fUnitRadius*std::cos(fAnglePi);
+ double fY=fUnitRadius*std::sin(fAnglePi);
+ double fZ=fLogicZ;
+
+ //!! applying matrix to vector does ignore translation, so it is important to use a B3DPoint here instead of B3DVector
+ ::basegfx::B3DPoint aPoint(fX,fY,fZ);
+ ::basegfx::B3DPoint aRet = m_aUnitCartesianToScene * aPoint;
+ return B3DPointToPosition3D(aRet);
+}
+
+drawing::Position3D PolarPlottingPositionHelper::transformAngleRadiusToScene( double fLogicValueOnAngleAxis, double fLogicValueOnRadiusAxis, double fLogicZ, bool bDoScaling ) const
+{
+ double fUnitAngleDegree = transformToAngleDegree(fLogicValueOnAngleAxis,bDoScaling);
+ double fUnitRadius = transformToRadius(fLogicValueOnRadiusAxis,bDoScaling);
+
+ return transformUnitCircleToScene( fUnitAngleDegree, fUnitRadius, fLogicZ );
+}
+
+double PolarPlottingPositionHelper::getOuterLogicRadius() const
+{
+ const ExplicitScaleData& rScale = m_bSwapXAndY ? m_aScales[0] : m_aScales[1];
+ if( rScale.Orientation==AxisOrientation_MATHEMATICAL )
+ return rScale.Maximum;
+ else
+ return rScale.Minimum;
+}
+
+bool PlottingPositionHelper::isPercentY() const
+{
+ return m_aScales[1].AxisType==AxisType::PERCENT;
+}
+
+double PlottingPositionHelper::getBaseValueY() const
+{
+ return m_aScales[1].Origin;
+}
+
+void PlottingPositionHelper::setTimeResolution( tools::Long nTimeResolution, const Date& rNullDate )
+{
+ m_nTimeResolution = nTimeResolution;
+ m_aNullDate = rNullDate;
+
+ //adapt category width
+ double fCategoryWidth = 1.0;
+ if( !m_aScales.empty() )
+ {
+ if( m_aScales[0].AxisType == css::chart2::AxisType::DATE )
+ {
+ m_bDateAxis = true;
+ if( nTimeResolution == css::chart::TimeUnit::YEAR )
+ {
+ const double fMonthCount = 12.0;//todo: this depends on the DateScaling and must be adjusted in case we use more generic calendars in future
+ fCategoryWidth = fMonthCount;
+ }
+ }
+ }
+ setScaledCategoryWidth(fCategoryWidth);
+}
+
+void PlottingPositionHelper::setScaledCategoryWidth( double fScaledCategoryWidth )
+{
+ m_fScaledCategoryWidth = fScaledCategoryWidth;
+}
+void PlottingPositionHelper::AllowShiftXAxisPos( bool bAllowShift )
+{
+ m_bAllowShiftXAxisPos = bAllowShift;
+}
+void PlottingPositionHelper::AllowShiftZAxisPos( bool bAllowShift )
+{
+ m_bAllowShiftZAxisPos = bAllowShift;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */