forked from amazingfate/loongoffice
Implementing label support for of-pie charts. This involves using the existing pie label code with an added horizontal shift, and adding special (simple) handling for the bars in bar-of-pie. This also fixes tdf#161228 Change-Id: Ifc7c1f28548caf216aba5c7dc411d05a0c9d8726 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/169566 Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk> Tested-by: Jenkins
708 lines
26 KiB
C++
708 lines
26 KiB
C++
/* -*- 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 )
|
|
std::swap( fStartLogicValueOnAngleAxis, fEndLogicValueOnAngleAxis );
|
|
|
|
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 ::basegfx::B3DVector& aOffset) 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);
|
|
aPoint += aOffset;
|
|
::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: */
|