diff options
Diffstat (limited to 'chart2/source/view/axes/ScaleAutomatism.cxx')
-rw-r--r-- | chart2/source/view/axes/ScaleAutomatism.cxx | 1008 |
1 files changed, 1008 insertions, 0 deletions
diff --git a/chart2/source/view/axes/ScaleAutomatism.cxx b/chart2/source/view/axes/ScaleAutomatism.cxx new file mode 100644 index 000000000000..075ad16347c2 --- /dev/null +++ b/chart2/source/view/axes/ScaleAutomatism.cxx @@ -0,0 +1,1008 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_chart2.hxx" +#include "ScaleAutomatism.hxx" +#include "macros.hxx" +#include "Tickmarks_Equidistant.hxx" +#include "DateHelper.hxx" +#include "DateScaling.hxx" +#include "AxisHelper.hxx" +#include <com/sun/star/chart/TimeUnit.hpp> + +#include <rtl/math.hxx> +#include <tools/debug.hxx> + +//............................................................................. +namespace chart +{ +//............................................................................. +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; +using ::com::sun::star::chart::TimeUnit::DAY; +using ::com::sun::star::chart::TimeUnit::MONTH; +using ::com::sun::star::chart::TimeUnit::YEAR; + +const sal_Int32 MAXIMUM_MANUAL_INCREMENT_COUNT = 500; +const sal_Int32 MAXIMUM_AUTO_INCREMENT_COUNT = 10; +const sal_Int32 MAXIMUM_SUB_INCREMENT_COUNT = 100; + +namespace +{ + +void lcl_ensureMaximumSubIncrementCount( sal_Int32& rnSubIntervalCount ) +{ + if( rnSubIntervalCount > MAXIMUM_SUB_INCREMENT_COUNT ) + rnSubIntervalCount = MAXIMUM_SUB_INCREMENT_COUNT; +} + +}//end anonymous namespace + + +//............................................................................. + +ExplicitScaleData::ExplicitScaleData() + : Minimum(0.0) + , Maximum(10.0) + , Origin(0.0) + , Orientation(::com::sun::star::chart2::AxisOrientation_MATHEMATICAL) + , Scaling() + , AxisType(::com::sun::star::chart2::AxisType::REALNUMBER) + , ShiftedCategoryPosition(false) + , TimeResolution(::com::sun::star::chart::TimeUnit::DAY) + , NullDate(30,12,1899) +{ +} + +ExplicitSubIncrement::ExplicitSubIncrement() + : IntervalCount(2) + , PostEquidistant(true) +{ +} + + +ExplicitIncrementData::ExplicitIncrementData() + : MajorTimeInterval(1,::com::sun::star::chart::TimeUnit::DAY) + , MinorTimeInterval(1,::com::sun::star::chart::TimeUnit::DAY) + , Distance(1.0) + , PostEquidistant(true) + , BaseValue(0.0) + , SubIncrements() +{ +} + +//............................................................................. + +ScaleAutomatism::ScaleAutomatism( const ScaleData& rSourceScale, const Date& rNullDate ) + : m_aSourceScale( rSourceScale ) + , m_fValueMinimum( 0.0 ) + , m_fValueMaximum( 0.0 ) + , m_nMaximumAutoMainIncrementCount( MAXIMUM_AUTO_INCREMENT_COUNT ) + , m_bExpandBorderToIncrementRhythm( false ) + , m_bExpandIfValuesCloseToBorder( false ) + , m_bExpandWideValuesToZero( false ) + , m_bExpandNarrowValuesTowardZero( false ) + , m_nTimeResolution(::com::sun::star::chart::TimeUnit::DAY) + , m_aNullDate(rNullDate) +{ + ::rtl::math::setNan( &m_fValueMinimum ); + ::rtl::math::setNan( &m_fValueMaximum ); + + double fExplicitOrigin = 0.0; + if( m_aSourceScale.Origin >>= fExplicitOrigin ) + expandValueRange( fExplicitOrigin, fExplicitOrigin); +} +ScaleAutomatism::~ScaleAutomatism() +{ +} + +void ScaleAutomatism::expandValueRange( double fMinimum, double fMaximum ) +{ + if( (fMinimum < m_fValueMinimum) || ::rtl::math::isNan( m_fValueMinimum ) ) + m_fValueMinimum = fMinimum; + if( (fMaximum > m_fValueMaximum) || ::rtl::math::isNan( m_fValueMaximum ) ) + m_fValueMaximum = fMaximum; +} + +void ScaleAutomatism::setAutoScalingOptions( + bool bExpandBorderToIncrementRhythm, + bool bExpandIfValuesCloseToBorder, + bool bExpandWideValuesToZero, + bool bExpandNarrowValuesTowardZero ) +{ + // if called multiple times, enable an option, if it is set in at least one call + m_bExpandBorderToIncrementRhythm |= bExpandBorderToIncrementRhythm; + m_bExpandIfValuesCloseToBorder |= bExpandIfValuesCloseToBorder; + m_bExpandWideValuesToZero |= bExpandWideValuesToZero; + m_bExpandNarrowValuesTowardZero |= bExpandNarrowValuesTowardZero; + + if( m_aSourceScale.AxisType==AxisType::PERCENT ) + m_bExpandIfValuesCloseToBorder = false; +} + +void ScaleAutomatism::setMaximumAutoMainIncrementCount( sal_Int32 nMaximumAutoMainIncrementCount ) +{ + if( nMaximumAutoMainIncrementCount < 2 ) + m_nMaximumAutoMainIncrementCount = 2; //#i82006 + else if( nMaximumAutoMainIncrementCount > MAXIMUM_AUTO_INCREMENT_COUNT ) + m_nMaximumAutoMainIncrementCount = MAXIMUM_AUTO_INCREMENT_COUNT; + else + m_nMaximumAutoMainIncrementCount = nMaximumAutoMainIncrementCount; +} + +void ScaleAutomatism::setAutomaticTimeResolution( sal_Int32 nTimeResolution ) +{ + m_nTimeResolution = nTimeResolution; +} + +void ScaleAutomatism::calculateExplicitScaleAndIncrement( + ExplicitScaleData& rExplicitScale, ExplicitIncrementData& rExplicitIncrement ) const +{ + // fill explicit scale + rExplicitScale.Orientation = m_aSourceScale.Orientation; + rExplicitScale.Scaling = m_aSourceScale.Scaling; + rExplicitScale.AxisType = m_aSourceScale.AxisType; + rExplicitScale.NullDate = m_aNullDate; + + bool bAutoMinimum = !(m_aSourceScale.Minimum >>= rExplicitScale.Minimum); + bool bAutoMaximum = !(m_aSourceScale.Maximum >>= rExplicitScale.Maximum); + bool bAutoOrigin = !(m_aSourceScale.Origin >>= rExplicitScale.Origin); + + // automatic scale minimum + if( bAutoMinimum ) + { + if( m_aSourceScale.AxisType==AxisType::PERCENT ) + rExplicitScale.Minimum = 0.0; + else if( ::rtl::math::isNan( m_fValueMinimum ) ) + { + if( m_aSourceScale.AxisType==AxisType::DATE ) + rExplicitScale.Minimum = 36526.0; //1.1.2000 + else + rExplicitScale.Minimum = 0.0; //@todo get Minimum from scaling or from plotter???? + } + else + rExplicitScale.Minimum = m_fValueMinimum; + } + + // automatic scale maximum + if( bAutoMaximum ) + { + if( m_aSourceScale.AxisType==AxisType::PERCENT ) + rExplicitScale.Maximum = 1.0; + else if( ::rtl::math::isNan( m_fValueMaximum ) ) + { + if( m_aSourceScale.AxisType==AxisType::DATE ) + rExplicitScale.Maximum = 40179.0; //1.1.2010 + else + rExplicitScale.Maximum = 10.0; //@todo get Maximum from scaling or from plotter???? + } + else + rExplicitScale.Maximum = m_fValueMaximum; + } + + //--------------------------------------------------------------- + //fill explicit increment + + rExplicitScale.ShiftedCategoryPosition = m_aSourceScale.ShiftedCategoryPosition; + bool bIsLogarithm = false; + + //minimum and maximum of the ExplicitScaleData may be changed if allowed + if( m_aSourceScale.AxisType==AxisType::DATE ) + calculateExplicitIncrementAndScaleForDateTimeAxis( rExplicitScale, rExplicitIncrement, bAutoMinimum, bAutoMaximum ); + else if( m_aSourceScale.AxisType==AxisType::CATEGORY || m_aSourceScale.AxisType==AxisType::SERIES ) + calculateExplicitIncrementAndScaleForCategory( rExplicitScale, rExplicitIncrement, bAutoMinimum, bAutoMaximum ); + else + { + bIsLogarithm = AxisHelper::isLogarithmic( rExplicitScale.Scaling ); + if( bIsLogarithm ) + calculateExplicitIncrementAndScaleForLogarithmic( rExplicitScale, rExplicitIncrement, bAutoMinimum, bAutoMaximum ); + else + calculateExplicitIncrementAndScaleForLinear( rExplicitScale, rExplicitIncrement, bAutoMinimum, bAutoMaximum ); + } + + // automatic origin + if( bAutoOrigin ) + { + // #i71415# automatic origin for logarithmic axis + double fDefaulOrigin = bIsLogarithm ? 1.0 : 0.0; + + if( fDefaulOrigin < rExplicitScale.Minimum ) + fDefaulOrigin = rExplicitScale.Minimum; + else if( fDefaulOrigin > rExplicitScale.Maximum ) + fDefaulOrigin = rExplicitScale.Maximum; + + rExplicitScale.Origin = fDefaulOrigin; + } +} + +ScaleData ScaleAutomatism::getScale() const +{ + return m_aSourceScale; +} + +Date ScaleAutomatism::getNullDate() const +{ + return m_aNullDate; +} + +// private -------------------------------------------------------------------- + +void ScaleAutomatism::calculateExplicitIncrementAndScaleForCategory( + ExplicitScaleData& rExplicitScale, + ExplicitIncrementData& rExplicitIncrement, + bool bAutoMinimum, bool bAutoMaximum ) const +{ + // no scaling for categories + rExplicitScale.Scaling.clear(); + + if( rExplicitScale.ShiftedCategoryPosition ) + rExplicitScale.Maximum += 1.0; + + // ensure that at least one category is visible + if( rExplicitScale.Maximum <= rExplicitScale.Minimum ) + rExplicitScale.Maximum = rExplicitScale.Minimum + 1.0; + + // default increment settings + rExplicitIncrement.PostEquidistant = sal_True; // does not matter anyhow + rExplicitIncrement.Distance = 1.0; // category axis always have a main increment of 1 + rExplicitIncrement.BaseValue = 0.0; // category axis always have a base of 0 + + // automatic minimum and maximum + if( bAutoMinimum && m_bExpandBorderToIncrementRhythm ) + rExplicitScale.Minimum = EquidistantTickFactory::getMinimumAtIncrement( rExplicitScale.Minimum, rExplicitIncrement ); + if( bAutoMaximum && m_bExpandBorderToIncrementRhythm ) + rExplicitScale.Maximum = EquidistantTickFactory::getMaximumAtIncrement( rExplicitScale.Maximum, rExplicitIncrement ); + + //prevent performace killover + double fDistanceCount = ::rtl::math::approxFloor( (rExplicitScale.Maximum-rExplicitScale.Minimum) / rExplicitIncrement.Distance ); + if( static_cast< sal_Int32 >( fDistanceCount ) > MAXIMUM_MANUAL_INCREMENT_COUNT ) + { + double fMinimumFloor = ::rtl::math::approxFloor( rExplicitScale.Minimum ); + double fMaximumCeil = ::rtl::math::approxCeil( rExplicitScale.Maximum ); + rExplicitIncrement.Distance = ::rtl::math::approxCeil( (fMaximumCeil - fMinimumFloor) / MAXIMUM_MANUAL_INCREMENT_COUNT ); + } + + //--------------------------------------------------------------- + //fill explicit sub increment + sal_Int32 nSubCount = m_aSourceScale.IncrementData.SubIncrements.getLength(); + for( sal_Int32 nN=0; nN<nSubCount; nN++ ) + { + ExplicitSubIncrement aExplicitSubIncrement; + const SubIncrement& rSubIncrement= m_aSourceScale.IncrementData.SubIncrements[nN]; + if(!(rSubIncrement.IntervalCount>>=aExplicitSubIncrement.IntervalCount)) + { + //scaling dependent + //@todo autocalculate IntervalCount dependent on MainIncrement and scaling + aExplicitSubIncrement.IntervalCount = 2; + } + lcl_ensureMaximumSubIncrementCount( aExplicitSubIncrement.IntervalCount ); + if(!(rSubIncrement.PostEquidistant>>=aExplicitSubIncrement.PostEquidistant)) + { + //scaling dependent + aExplicitSubIncrement.PostEquidistant = sal_False; + } + rExplicitIncrement.SubIncrements.push_back(aExplicitSubIncrement); + } +} + +//----------------------------------------------------------------------------------------- + +void ScaleAutomatism::calculateExplicitIncrementAndScaleForLogarithmic( + ExplicitScaleData& rExplicitScale, + ExplicitIncrementData& rExplicitIncrement, + bool bAutoMinimum, bool bAutoMaximum ) const +{ + // *** STEP 1: initialize the range data *** + + const double fInputMinimum = rExplicitScale.Minimum; + const double fInputMaximum = rExplicitScale.Maximum; + + double fSourceMinimum = rExplicitScale.Minimum; + double fSourceMaximum = rExplicitScale.Maximum; + + // set automatic PostEquidistant to true (maybe scaling dependent?) + // Note: scaling with PostEquidistant==false is untested and needs review + if( !(m_aSourceScale.IncrementData.PostEquidistant >>= rExplicitIncrement.PostEquidistant) ) + rExplicitIncrement.PostEquidistant = sal_True; + + /* All following scaling code will operate on the logarithms of the source + values. In the last step, the original values will be restored. */ + uno::Reference< XScaling > xScaling = rExplicitScale.Scaling; + if( !xScaling.is() ) + xScaling.set( AxisHelper::createLogarithmicScaling() ); + uno::Reference< XScaling > xInverseScaling = xScaling->getInverseScaling(); + + fSourceMinimum = xScaling->doScaling( fSourceMinimum ); + if( !::rtl::math::isFinite( fSourceMinimum ) ) + fSourceMinimum = 0.0; + else if( ::rtl::math::approxEqual( fSourceMinimum, ::rtl::math::approxFloor( fSourceMinimum ) ) ) + fSourceMinimum = ::rtl::math::approxFloor( fSourceMinimum ); + + fSourceMaximum = xScaling->doScaling( fSourceMaximum ); + if( !::rtl::math::isFinite( fSourceMaximum ) ) + fSourceMaximum = 0.0; + else if( ::rtl::math::approxEqual( fSourceMaximum, ::rtl::math::approxFloor( fSourceMaximum ) ) ) + fSourceMaximum = ::rtl::math::approxFloor( fSourceMaximum ); + + /* If range is invalid (minimum greater than maximum), change one of the + variable limits to validate the range. In this step, a zero-sized range + is still allowed. */ + if( fSourceMinimum > fSourceMaximum ) + { + // force changing the maximum, if both limits are fixed + if( bAutoMaximum || !bAutoMinimum ) + fSourceMaximum = fSourceMinimum; + else + fSourceMinimum = fSourceMaximum; + } + + /* If maximum is less than 0 (and therefore minimum too), minimum and + maximum will be negated and swapped to make the following algorithms + easier. Example: Both ranges [2,5] and [-5,-2] will be processed as + [2,5], and the latter will be swapped back later. The range [0,0] is + explicitly excluded from swapping (this would result in [-1,0] instead + of the expected [0,1]). */ + bool bSwapAndNegateRange = (fSourceMinimum < 0.0) && (fSourceMaximum <= 0.0); + if( bSwapAndNegateRange ) + { + double fTempValue = fSourceMinimum; + fSourceMinimum = -fSourceMaximum; + fSourceMaximum = -fTempValue; + ::std::swap( bAutoMinimum, bAutoMaximum ); + } + + // *** STEP 2: find temporary (unrounded) axis minimum and maximum *** + + double fTempMinimum = fSourceMinimum; + double fTempMaximum = fSourceMaximum; + + /* If minimum is variable and greater than 0 (and therefore maximum too), + means all original values are greater than 1 (or all values are less + than 1, and the range has been swapped above), then: */ + if( bAutoMinimum && (fTempMinimum > 0.0) ) + { + /* If minimum is less than 5 (i.e. original source values less than + B^5, B being the base of the scaling), or if minimum and maximum + are in different increment intervals (means, if minimum and maximum + are not both in the range [B^n,B^(n+1)] for a whole number n), set + minimum to 0, which results in B^0=1 on the axis. */ + double fMinimumFloor = ::rtl::math::approxFloor( fTempMinimum ); + double fMaximumFloor = ::rtl::math::approxFloor( fTempMaximum ); + // handle the exact value B^(n+1) to be in the range [B^n,B^(n+1)] + if( ::rtl::math::approxEqual( fTempMaximum, fMaximumFloor ) ) + fMaximumFloor -= 1.0; + + if( (fMinimumFloor < 5.0) || (fMinimumFloor < fMaximumFloor) ) + { + if( m_bExpandWideValuesToZero ) + fTempMinimum = 0.0; + } + /* Else (minimum and maximum are in one increment interval), expand + minimum toward 0 to make the 'shorter' data points visible. */ + else + { + if( m_bExpandNarrowValuesTowardZero ) + fTempMinimum -= 1.0; + } + } + + /* If range is still zero-sized (e.g. when minimum is fixed), set minimum + to 0, which makes the axis start/stop at the value 1. */ + if( fTempMinimum == fTempMaximum ) + { + if( bAutoMinimum && (fTempMaximum > 0.0) ) + fTempMinimum = 0.0; + else + fTempMaximum += 1.0; // always add one interval, even if maximum is fixed + } + + // *** STEP 3: calculate main interval size *** + + // base value (anchor position of the intervals), already scaled + if( !(m_aSourceScale.IncrementData.BaseValue >>= rExplicitIncrement.BaseValue) ) + { + //scaling dependent + //@maybe todo is this default also plotter dependent ?? + if( !bAutoMinimum ) + rExplicitIncrement.BaseValue = fTempMinimum; + else if( !bAutoMaximum ) + rExplicitIncrement.BaseValue = fTempMaximum; + else + rExplicitIncrement.BaseValue = 0.0; + } + + // calculate automatic interval + bool bAutoDistance = !(m_aSourceScale.IncrementData.Distance >>= rExplicitIncrement.Distance); + if( bAutoDistance ) + rExplicitIncrement.Distance = 0.0; + + /* Restrict number of allowed intervals with user-defined distance to + MAXIMUM_MANUAL_INCREMENT_COUNT. */ + sal_Int32 nMaxMainIncrementCount = bAutoDistance ? + m_nMaximumAutoMainIncrementCount : MAXIMUM_MANUAL_INCREMENT_COUNT; + + // repeat calculation until number of intervals are valid + bool bNeedIteration = true; + bool bHasCalculatedDistance = false; + while( bNeedIteration ) + { + if( bAutoDistance ) + { + // first iteration: calculate interval size from axis limits + if( !bHasCalculatedDistance ) + { + double fMinimumFloor = ::rtl::math::approxFloor( fTempMinimum ); + double fMaximumCeil = ::rtl::math::approxCeil( fTempMaximum ); + rExplicitIncrement.Distance = ::rtl::math::approxCeil( (fMaximumCeil - fMinimumFloor) / nMaxMainIncrementCount ); + } + else + { + // following iterations: increase distance + rExplicitIncrement.Distance += 1.0; + } + + // for next iteration: distance calculated -> use else path to increase + bHasCalculatedDistance = true; + } + + // *** STEP 4: additional space above or below the data points *** + + double fAxisMinimum = fTempMinimum; + double fAxisMaximum = fTempMaximum; + + // round to entire multiples of the distance and add additional space + if( bAutoMinimum && m_bExpandBorderToIncrementRhythm ) + { + fAxisMinimum = EquidistantTickFactory::getMinimumAtIncrement( fAxisMinimum, rExplicitIncrement ); + + //ensure valid values after scaling #i100995# + if( !bAutoDistance ) + { + double fCheck = xInverseScaling->doScaling( fAxisMinimum ); + if( !::rtl::math::isFinite( fCheck ) || fCheck <= 0 ) + { + bAutoDistance = true; + bHasCalculatedDistance = false; + continue; + } + } + } + if( bAutoMaximum && m_bExpandBorderToIncrementRhythm ) + { + fAxisMaximum = EquidistantTickFactory::getMaximumAtIncrement( fAxisMaximum, rExplicitIncrement ); + + //ensure valid values after scaling #i100995# + if( !bAutoDistance ) + { + double fCheck = xInverseScaling->doScaling( fAxisMaximum ); + if( !::rtl::math::isFinite( fCheck ) || fCheck <= 0 ) + { + bAutoDistance = true; + bHasCalculatedDistance = false; + continue; + } + } + } + + // set the resulting limits (swap back to negative range if needed) + if( bSwapAndNegateRange ) + { + rExplicitScale.Minimum = -fAxisMaximum; + rExplicitScale.Maximum = -fAxisMinimum; + } + else + { + rExplicitScale.Minimum = fAxisMinimum; + rExplicitScale.Maximum = fAxisMaximum; + } + + /* If the number of intervals is too high (e.g. due to invalid fixed + distance or due to added space above or below data points), + calculate again with increased distance. */ + double fDistanceCount = ::rtl::math::approxFloor( (fAxisMaximum - fAxisMinimum) / rExplicitIncrement.Distance ); + bNeedIteration = static_cast< sal_Int32 >( fDistanceCount ) > nMaxMainIncrementCount; + // if manual distance is invalid, trigger automatic calculation + if( bNeedIteration ) + bAutoDistance = true; + + // convert limits back to logarithmic scale + rExplicitScale.Minimum = xInverseScaling->doScaling( rExplicitScale.Minimum ); + rExplicitScale.Maximum = xInverseScaling->doScaling( rExplicitScale.Maximum ); + + //ensure valid values after scaling #i100995# + if( !::rtl::math::isFinite( rExplicitScale.Minimum ) || rExplicitScale.Minimum <= 0) + { + rExplicitScale.Minimum = fInputMinimum; + if( !::rtl::math::isFinite( rExplicitScale.Minimum ) || rExplicitScale.Minimum <= 0 ) + rExplicitScale.Minimum = 1.0; + } + if( !::rtl::math::isFinite( rExplicitScale.Maximum) || rExplicitScale.Maximum <= 0 ) + { + rExplicitScale.Maximum= fInputMaximum; + if( !::rtl::math::isFinite( rExplicitScale.Maximum) || rExplicitScale.Maximum <= 0 ) + rExplicitScale.Maximum = 10.0; + } + if( rExplicitScale.Maximum < rExplicitScale.Minimum ) + ::std::swap( rExplicitScale.Maximum, rExplicitScale.Minimum ); + } + + //--------------------------------------------------------------- + //fill explicit sub increment + sal_Int32 nSubCount = m_aSourceScale.IncrementData.SubIncrements.getLength(); + for( sal_Int32 nN=0; nN<nSubCount; nN++ ) + { + ExplicitSubIncrement aExplicitSubIncrement; + const SubIncrement& rSubIncrement = m_aSourceScale.IncrementData.SubIncrements[nN]; + if(!(rSubIncrement.IntervalCount>>=aExplicitSubIncrement.IntervalCount)) + { + //scaling dependent + //@todo autocalculate IntervalCount dependent on MainIncrement and scaling + aExplicitSubIncrement.IntervalCount = 9; + } + lcl_ensureMaximumSubIncrementCount( aExplicitSubIncrement.IntervalCount ); + if(!(rSubIncrement.PostEquidistant>>=aExplicitSubIncrement.PostEquidistant)) + { + //scaling dependent + aExplicitSubIncrement.PostEquidistant = sal_False; + } + rExplicitIncrement.SubIncrements.push_back(aExplicitSubIncrement); + } +} + +//----------------------------------------------------------------------------------------- + +void ScaleAutomatism::calculateExplicitIncrementAndScaleForDateTimeAxis( + ExplicitScaleData& rExplicitScale, + ExplicitIncrementData& rExplicitIncrement, + bool bAutoMinimum, bool bAutoMaximum ) const +{ + Date aMinDate(m_aNullDate); aMinDate += static_cast<long>(::rtl::math::approxFloor(rExplicitScale.Minimum)); + Date aMaxDate(m_aNullDate); aMaxDate += static_cast<long>(::rtl::math::approxFloor(rExplicitScale.Maximum)); + rExplicitIncrement.PostEquidistant = sal_False; + + if( aMinDate > aMaxDate ) + { + std::swap(aMinDate,aMaxDate); + } + + if( !(m_aSourceScale.TimeIncrement.TimeResolution >>= rExplicitScale.TimeResolution) ) + rExplicitScale.TimeResolution = m_nTimeResolution; + + rExplicitScale.Scaling = new DateScaling(m_aNullDate,rExplicitScale.TimeResolution,false); + + // choose min and max suitable to time resolution + switch( rExplicitScale.TimeResolution ) + { + case DAY: + if( rExplicitScale.ShiftedCategoryPosition ) + aMaxDate++;//for explicit scales we need one interval more (maximum excluded) + break; + case MONTH: + aMinDate.SetDay(1); + aMaxDate.SetDay(1); + if( rExplicitScale.ShiftedCategoryPosition ) + aMaxDate = DateHelper::GetDateSomeMonthsAway(aMaxDate,1);//for explicit scales we need one interval more (maximum excluded) + if( DateHelper::IsLessThanOneMonthAway( aMinDate, aMaxDate ) ) + { + if( bAutoMaximum || !bAutoMinimum ) + aMaxDate = DateHelper::GetDateSomeMonthsAway(aMinDate,1); + else + aMinDate = DateHelper::GetDateSomeMonthsAway(aMaxDate,-1); + } + break; + case YEAR: + aMinDate.SetDay(1); + aMinDate.SetMonth(1); + aMaxDate.SetDay(1); + aMaxDate.SetMonth(1); + if( rExplicitScale.ShiftedCategoryPosition ) + aMaxDate = DateHelper::GetDateSomeYearsAway(aMaxDate,1);//for explicit scales we need one interval more (maximum excluded) + if( DateHelper::IsLessThanOneYearAway( aMinDate, aMaxDate ) ) + { + if( bAutoMaximum || !bAutoMinimum ) + aMaxDate = DateHelper::GetDateSomeYearsAway(aMinDate,1); + else + aMinDate = DateHelper::GetDateSomeYearsAway(aMaxDate,-1); + } + break; + } + + // set the resulting limits (swap back to negative range if needed) + rExplicitScale.Minimum = aMinDate - m_aNullDate; + rExplicitScale.Maximum = aMaxDate - m_aNullDate; + + bool bAutoMajor = !(m_aSourceScale.TimeIncrement.MajorTimeInterval >>= rExplicitIncrement.MajorTimeInterval); + bool bAutoMinor = !(m_aSourceScale.TimeIncrement.MinorTimeInterval >>= rExplicitIncrement.MinorTimeInterval); + + sal_Int32 nMaxMainIncrementCount = bAutoMajor ? + m_nMaximumAutoMainIncrementCount : MAXIMUM_MANUAL_INCREMENT_COUNT; + if( nMaxMainIncrementCount > 1 ) + nMaxMainIncrementCount--; + + + //choose major time interval: + long nDayCount = (aMaxDate-aMinDate); + long nMainIncrementCount = 1; + if( !bAutoMajor ) + { + long nIntervalDayCount = rExplicitIncrement.MajorTimeInterval.Number; + if( rExplicitIncrement.MajorTimeInterval.TimeUnit < rExplicitScale.TimeResolution ) + rExplicitIncrement.MajorTimeInterval.TimeUnit = rExplicitScale.TimeResolution; + switch( rExplicitIncrement.MajorTimeInterval.TimeUnit ) + { + case DAY: + break; + case MONTH: + nIntervalDayCount*=31;//todo: maybe different for other calendars... get localized calendar according to set number format at axis ... + break; + case YEAR: + nIntervalDayCount*=365;//todo: maybe different for other calendars... get localized calendar according to set number format at axis ... + break; + } + nMainIncrementCount = nDayCount/nIntervalDayCount; + if( nMainIncrementCount > nMaxMainIncrementCount ) + bAutoMajor = true; + } + if( bAutoMajor ) + { + long nNumer = 1; + long nIntervalDays = nDayCount / nMaxMainIncrementCount; + double nDaysPerInterval = 1.0; + if( nIntervalDays>365 || YEAR==rExplicitScale.TimeResolution ) + { + rExplicitIncrement.MajorTimeInterval.TimeUnit = YEAR; + nDaysPerInterval = 365.0;//todo: maybe different for other calendars... get localized calendar according to set number format at axis ... + } + else if( nIntervalDays>31 || MONTH==rExplicitScale.TimeResolution ) + { + rExplicitIncrement.MajorTimeInterval.TimeUnit = MONTH; + nDaysPerInterval = 31.0;//todo: maybe different for other calendars... get localized calendar according to set number format at axis ... + } + else + { + rExplicitIncrement.MajorTimeInterval.TimeUnit = DAY; + nDaysPerInterval = 1.0; + } + + nNumer = static_cast<sal_Int32>( rtl::math::approxCeil( nIntervalDays/nDaysPerInterval ) ); + if(nNumer<=0) + nNumer=1; + rExplicitIncrement.MajorTimeInterval.Number = nNumer; + nMainIncrementCount = nDayCount/(nNumer*nDaysPerInterval); + } + + //choose minor time interval: + if( !bAutoMinor ) + { + if( rExplicitIncrement.MinorTimeInterval.TimeUnit > rExplicitIncrement.MajorTimeInterval.TimeUnit ) + rExplicitIncrement.MinorTimeInterval.TimeUnit = rExplicitIncrement.MajorTimeInterval.TimeUnit; + long nIntervalDayCount = rExplicitIncrement.MinorTimeInterval.Number; + switch( rExplicitIncrement.MinorTimeInterval.TimeUnit ) + { + case DAY: + break; + case MONTH: + nIntervalDayCount*=31;//todo: maybe different for other calendars... get localized calendar according to set number format at axis ... + break; + case YEAR: + nIntervalDayCount*=365;//todo: maybe different for other calendars... get localized calendar according to set number format at axis ... + break; + } + if( nDayCount/nIntervalDayCount > nMaxMainIncrementCount ) + bAutoMinor = true; + } + if( bAutoMinor ) + { + rExplicitIncrement.MinorTimeInterval.TimeUnit = rExplicitIncrement.MajorTimeInterval.TimeUnit; + rExplicitIncrement.MinorTimeInterval.Number = 1; + if( nMainIncrementCount > 100 ) + rExplicitIncrement.MinorTimeInterval.Number = rExplicitIncrement.MajorTimeInterval.Number; + else + { + if( rExplicitIncrement.MajorTimeInterval.Number >= 2 ) + { + if( !(rExplicitIncrement.MajorTimeInterval.Number%2) ) + rExplicitIncrement.MinorTimeInterval.Number = rExplicitIncrement.MajorTimeInterval.Number/2; + else if( !(rExplicitIncrement.MajorTimeInterval.Number%3) ) + rExplicitIncrement.MinorTimeInterval.Number = rExplicitIncrement.MajorTimeInterval.Number/3; + else if( !(rExplicitIncrement.MajorTimeInterval.Number%5) ) + rExplicitIncrement.MinorTimeInterval.Number = rExplicitIncrement.MajorTimeInterval.Number/5; + else if( rExplicitIncrement.MajorTimeInterval.Number > 50 ) + rExplicitIncrement.MinorTimeInterval.Number = rExplicitIncrement.MajorTimeInterval.Number; + } + else + { + switch( rExplicitIncrement.MajorTimeInterval.TimeUnit ) + { + case DAY: + break; + case MONTH: + if( rExplicitScale.TimeResolution == DAY ) + rExplicitIncrement.MinorTimeInterval.TimeUnit = DAY; + break; + case YEAR: + if( rExplicitScale.TimeResolution <= MONTH ) + rExplicitIncrement.MinorTimeInterval.TimeUnit = MONTH; + break; + } + } + } + } + +} + +//----------------------------------------------------------------------------------------- + +void ScaleAutomatism::calculateExplicitIncrementAndScaleForLinear( + ExplicitScaleData& rExplicitScale, + ExplicitIncrementData& rExplicitIncrement, + bool bAutoMinimum, bool bAutoMaximum ) const +{ + // *** STEP 1: initialize the range data *** + + double fSourceMinimum = rExplicitScale.Minimum; + double fSourceMaximum = rExplicitScale.Maximum; + + // set automatic PostEquidistant to true (maybe scaling dependent?) + if( !(m_aSourceScale.IncrementData.PostEquidistant >>= rExplicitIncrement.PostEquidistant) ) + rExplicitIncrement.PostEquidistant = sal_True; + + /* If range is invalid (minimum greater than maximum), change one of the + variable limits to validate the range. In this step, a zero-sized range + is still allowed. */ + if( fSourceMinimum > fSourceMaximum ) + { + // force changing the maximum, if both limits are fixed + if( bAutoMaximum || !bAutoMinimum ) + fSourceMaximum = fSourceMinimum; + else + fSourceMinimum = fSourceMaximum; + } + + /* If maximum is zero or negative (and therefore minimum too), minimum and + maximum will be negated and swapped to make the following algorithms + easier. Example: Both ranges [2,5] and [-5,-2] will be processed as + [2,5], and the latter will be swapped back later. The range [0,0] is + explicitly excluded from swapping (this would result in [-1,0] instead + of the expected [0,1]). */ + bool bSwapAndNegateRange = (fSourceMinimum < 0.0) && (fSourceMaximum <= 0.0); + if( bSwapAndNegateRange ) + { + double fTempValue = fSourceMinimum; + fSourceMinimum = -fSourceMaximum; + fSourceMaximum = -fTempValue; + ::std::swap( bAutoMinimum, bAutoMaximum ); + } + + // *** STEP 2: find temporary (unrounded) axis minimum and maximum *** + + double fTempMinimum = fSourceMinimum; + double fTempMaximum = fSourceMaximum; + + /* If minimum is variable and greater than 0 (and therefore maximum too), + means all values are positive (or all values are negative, and the + range has been swapped above), then: */ + if( bAutoMinimum && (fTempMinimum > 0.0) ) + { + /* If minimum equals maximum, or if minimum is less than 5/6 of + maximum, set minimum to 0. */ + if( (fTempMinimum == fTempMaximum) || (fTempMinimum / fTempMaximum < 5.0 / 6.0) ) + { + if( m_bExpandWideValuesToZero ) + fTempMinimum = 0.0; + } + /* Else (minimum is greater than or equal to 5/6 of maximum), add half + of the visible range (expand minimum toward 0) to make the + 'shorter' data points visible. */ + else + { + if( m_bExpandNarrowValuesTowardZero ) + fTempMinimum -= (fTempMaximum - fTempMinimum) / 2.0; + } + } + + /* If range is still zero-sized (e.g. when minimum is fixed), add some + space to a variable limit. */ + if( fTempMinimum == fTempMaximum ) + { + if( bAutoMaximum || !bAutoMinimum ) + { + // change 0 to 1, otherwise double the value + if( fTempMaximum == 0.0 ) + fTempMaximum = 1.0; + else + fTempMaximum *= 2.0; + } + else + { + // change 0 to -1, otherwise halve the value + if( fTempMinimum == 0.0 ) + fTempMinimum = -1.0; + else + fTempMinimum /= 2.0; + } + } + + // *** STEP 3: calculate main interval size *** + + // base value (anchor position of the intervals) + if( !(m_aSourceScale.IncrementData.BaseValue >>= rExplicitIncrement.BaseValue) ) + { + if( !bAutoMinimum ) + rExplicitIncrement.BaseValue = fTempMinimum; + else if( !bAutoMaximum ) + rExplicitIncrement.BaseValue = fTempMaximum; + else + rExplicitIncrement.BaseValue = 0.0; + } + + // calculate automatic interval + bool bAutoDistance = !(m_aSourceScale.IncrementData.Distance >>= rExplicitIncrement.Distance); + /* Restrict number of allowed intervals with user-defined distance to + MAXIMUM_MANUAL_INCREMENT_COUNT. */ + sal_Int32 nMaxMainIncrementCount = bAutoDistance ? + m_nMaximumAutoMainIncrementCount : MAXIMUM_MANUAL_INCREMENT_COUNT; + + double fDistanceMagnitude = 0.0; + double fDistanceNormalized = 0.0; + bool bHasNormalizedDistance = false; + + // repeat calculation until number of intervals are valid + bool bNeedIteration = true; + while( bNeedIteration ) + { + if( bAutoDistance ) + { + // first iteration: calculate interval size from axis limits + if( !bHasNormalizedDistance ) + { + // raw size of an interval + double fDistance = (fTempMaximum - fTempMinimum) / nMaxMainIncrementCount; + + // if distance of is less than 1e-307, do not do anything + if( fDistance <= 1.0e-307 ) + { + fDistanceNormalized = 1.0; + fDistanceMagnitude = 1.0e-307; + } + else + { + // distance magnitude (a power of 10) + int nExponent = static_cast< int >( ::rtl::math::approxFloor( log10( fDistance ) ) ); + fDistanceMagnitude = ::rtl::math::pow10Exp( 1.0, nExponent ); + + // stick normalized distance to a few predefined values + fDistanceNormalized = fDistance / fDistanceMagnitude; + if( fDistanceNormalized <= 1.0 ) + fDistanceNormalized = 1.0; + else if( fDistanceNormalized <= 2.0 ) + fDistanceNormalized = 2.0; + else if( fDistanceNormalized <= 5.0 ) + fDistanceNormalized = 5.0; + else + { + fDistanceNormalized = 1.0; + fDistanceMagnitude *= 10; + } + } + // for next iteration: distance is normalized -> use else path to increase distance + bHasNormalizedDistance = true; + } + // following iterations: increase distance, use only allowed values + else + { + if( fDistanceNormalized == 1.0 ) + fDistanceNormalized = 2.0; + else if( fDistanceNormalized == 2.0 ) + fDistanceNormalized = 5.0; + else + { + fDistanceNormalized = 1.0; + fDistanceMagnitude *= 10; + } + } + + // set the resulting distance + rExplicitIncrement.Distance = fDistanceNormalized * fDistanceMagnitude; + } + + // *** STEP 4: additional space above or below the data points *** + + double fAxisMinimum = fTempMinimum; + double fAxisMaximum = fTempMaximum; + + // round to entire multiples of the distance and add additional space + if( bAutoMinimum ) + { + // round to entire multiples of the distance, based on the base value + if( m_bExpandBorderToIncrementRhythm ) + fAxisMinimum = EquidistantTickFactory::getMinimumAtIncrement( fAxisMinimum, rExplicitIncrement ); + // additional space, if source minimum is to near at axis minimum + if( m_bExpandIfValuesCloseToBorder ) + if( (fAxisMinimum != 0.0) && ((fAxisMaximum - fSourceMinimum) / (fAxisMaximum - fAxisMinimum) > 20.0 / 21.0) ) + fAxisMinimum -= rExplicitIncrement.Distance; + } + if( bAutoMaximum ) + { + // round to entire multiples of the distance, based on the base value + if( m_bExpandBorderToIncrementRhythm ) + fAxisMaximum = EquidistantTickFactory::getMaximumAtIncrement( fAxisMaximum, rExplicitIncrement ); + // additional space, if source maximum is to near at axis maximum + if( m_bExpandIfValuesCloseToBorder ) + if( (fAxisMaximum != 0.0) && ((fSourceMaximum - fAxisMinimum) / (fAxisMaximum - fAxisMinimum) > 20.0 / 21.0) ) + fAxisMaximum += rExplicitIncrement.Distance; + } + + // set the resulting limits (swap back to negative range if needed) + if( bSwapAndNegateRange ) + { + rExplicitScale.Minimum = -fAxisMaximum; + rExplicitScale.Maximum = -fAxisMinimum; + } + else + { + rExplicitScale.Minimum = fAxisMinimum; + rExplicitScale.Maximum = fAxisMaximum; + } + + /* If the number of intervals is too high (e.g. due to invalid fixed + distance or due to added space above or below data points), + calculate again with increased distance. */ + double fDistanceCount = ::rtl::math::approxFloor( (fAxisMaximum - fAxisMinimum) / rExplicitIncrement.Distance ); + bNeedIteration = static_cast< sal_Int32 >( fDistanceCount ) > nMaxMainIncrementCount; + // if manual distance is invalid, trigger automatic calculation + if( bNeedIteration ) + bAutoDistance = true; + } + + //--------------------------------------------------------------- + //fill explicit sub increment + sal_Int32 nSubCount = m_aSourceScale.IncrementData.SubIncrements.getLength(); + for( sal_Int32 nN=0; nN<nSubCount; nN++ ) + { + ExplicitSubIncrement aExplicitSubIncrement; + const SubIncrement& rSubIncrement= m_aSourceScale.IncrementData.SubIncrements[nN]; + if(!(rSubIncrement.IntervalCount>>=aExplicitSubIncrement.IntervalCount)) + { + //scaling dependent + //@todo autocalculate IntervalCount dependent on MainIncrement and scaling + aExplicitSubIncrement.IntervalCount = 2; + } + lcl_ensureMaximumSubIncrementCount( aExplicitSubIncrement.IntervalCount ); + if(!(rSubIncrement.PostEquidistant>>=aExplicitSubIncrement.PostEquidistant)) + { + //scaling dependent + aExplicitSubIncrement.PostEquidistant = sal_False; + } + rExplicitIncrement.SubIncrements.push_back(aExplicitSubIncrement); + } +} + +//............................................................................. +} //namespace chart +//............................................................................. |