diff options
Diffstat (limited to 'chart2/source/view/axes/ScaleAutomatism.cxx')
-rw-r--r-- | chart2/source/view/axes/ScaleAutomatism.cxx | 989 |
1 files changed, 989 insertions, 0 deletions
diff --git a/chart2/source/view/axes/ScaleAutomatism.cxx b/chart2/source/view/axes/ScaleAutomatism.cxx new file mode 100644 index 000000000..24195c8fd --- /dev/null +++ b/chart2/source/view/axes/ScaleAutomatism.cxx @@ -0,0 +1,989 @@ +/* -*- 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 <ScaleAutomatism.hxx> +#include "Tickmarks_Equidistant.hxx" +#include <DateHelper.hxx> +#include "DateScaling.hxx" +#include <AxisHelper.hxx> +#include <com/sun/star/chart/TimeUnit.hpp> +#include <com/sun/star/chart2/AxisType.hpp> + +#include <rtl/math.hxx> +#include <tools/long.hxx> +#include <limits> + +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_SUB_INCREMENT_COUNT = 100; + +static sal_Int32 lcl_getMaximumAutoIncrementCount( sal_Int32 nAxisType ) +{ + sal_Int32 nMaximumAutoIncrementCount = 10; + if( nAxisType==AxisType::DATE ) + nMaximumAutoIncrementCount = MAXIMUM_MANUAL_INCREMENT_COUNT; + return nMaximumAutoIncrementCount; +} + +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(css::chart2::AxisOrientation_MATHEMATICAL) + , AxisType(css::chart2::AxisType::REALNUMBER) + , m_bShiftedCategoryPosition(false) + , TimeResolution(css::chart::TimeUnit::DAY) + , NullDate(30,12,1899) +{ +} + +ExplicitSubIncrement::ExplicitSubIncrement() + : IntervalCount(2) + , PostEquidistant(true) +{ +} + +ExplicitIncrementData::ExplicitIncrementData() + : MajorTimeInterval(1,css::chart::TimeUnit::DAY) + , MinorTimeInterval(1,css::chart::TimeUnit::DAY) + , Distance(1.0) + , PostEquidistant(true) + , BaseValue(0.0) +{ +} + +ScaleAutomatism::ScaleAutomatism( const ScaleData& rSourceScale, const Date& rNullDate ) + : m_aSourceScale( rSourceScale ) + , m_fValueMinimum( 0.0 ) + , m_fValueMaximum( 0.0 ) + , m_nMaximumAutoMainIncrementCount( lcl_getMaximumAutoIncrementCount( rSourceScale.AxisType ) ) + , m_bExpandBorderToIncrementRhythm( false ) + , m_bExpandIfValuesCloseToBorder( false ) + , m_bExpandWideValuesToZero( false ) + , m_bExpandNarrowValuesTowardZero( false ) + , m_nTimeResolution(css::chart::TimeUnit::DAY) + , m_aNullDate(rNullDate) +{ + resetValueRange(); + + double fExplicitOrigin = 0.0; + if( m_aSourceScale.Origin >>= fExplicitOrigin ) + expandValueRange( fExplicitOrigin, fExplicitOrigin); +} + +void ScaleAutomatism::resetValueRange( ) +{ + m_fValueMinimum = std::numeric_limits<double>::quiet_NaN(); + m_fValueMaximum = std::numeric_limits<double>::quiet_NaN(); +} + +void ScaleAutomatism::expandValueRange( double fMinimum, double fMaximum ) +{ + // if m_fValueMinimum and m_fValueMaximum == 0, it means that they were not determined. + // m_fValueMinimum == 0 makes impossible to determine real minimum, + // so they need to be reset tdf#96807 + if( (m_fValueMinimum == 0.0) && (m_fValueMaximum == 0.0) ) + resetValueRange(); + if( (fMinimum < m_fValueMinimum) || std::isnan( m_fValueMinimum ) ) + m_fValueMinimum = fMinimum; + if( (fMaximum > m_fValueMaximum) || std::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 > lcl_getMaximumAutoIncrementCount( m_aSourceScale.AxisType ) ) + m_nMaximumAutoMainIncrementCount = lcl_getMaximumAutoIncrementCount( m_aSourceScale.AxisType ); + 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( std::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( std::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.m_bShiftedCategoryPosition = 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; + } +} + +void ScaleAutomatism::calculateExplicitIncrementAndScaleForCategory( + ExplicitScaleData& rExplicitScale, + ExplicitIncrementData& rExplicitIncrement, + bool bAutoMinimum, bool bAutoMaximum ) const +{ + // no scaling for categories + rExplicitScale.Scaling.clear(); + + if( rExplicitScale.m_bShiftedCategoryPosition ) + 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 = 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 performance 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 = 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 = 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( !std::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( !std::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) ) + { + 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 == fMaximumFloor ) + { + /* if minimum and maximum are in one increment interval, expand + minimum toward 0 to make the 'shorter' data points visible. */ + 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( !std::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( !std::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( !std::isfinite( rExplicitScale.Minimum ) || rExplicitScale.Minimum <= 0) + { + rExplicitScale.Minimum = fInputMinimum; + if( !std::isfinite( rExplicitScale.Minimum ) || rExplicitScale.Minimum <= 0 ) + rExplicitScale.Minimum = 1.0; + } + if( !std::isfinite( rExplicitScale.Maximum) || rExplicitScale.Maximum <= 0 ) + { + rExplicitScale.Maximum= fInputMaximum; + if( !std::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 = false; + } + rExplicitIncrement.SubIncrements.push_back(aExplicitSubIncrement); + } +} + +void ScaleAutomatism::calculateExplicitIncrementAndScaleForDateTimeAxis( + ExplicitScaleData& rExplicitScale, + ExplicitIncrementData& rExplicitIncrement, + bool bAutoMinimum, bool bAutoMaximum ) const +{ + Date aMinDate(m_aNullDate); aMinDate.AddDays(::rtl::math::approxFloor(rExplicitScale.Minimum)); + Date aMaxDate(m_aNullDate); aMaxDate.AddDays(::rtl::math::approxFloor(rExplicitScale.Maximum)); + rExplicitIncrement.PostEquidistant = 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.m_bShiftedCategoryPosition ) + ++aMaxDate; //for explicit scales we need one interval more (maximum excluded) + break; + case MONTH: + aMinDate.SetDay(1); + aMaxDate.SetDay(1); + if( rExplicitScale.m_bShiftedCategoryPosition ) + 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.m_bShiftedCategoryPosition ) + 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: + tools::Long nDayCount = aMaxDate - aMinDate; + tools::Long nMainIncrementCount = 1; + if( !bAutoMajor ) + { + tools::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 ) + { + tools::Long nNumer = 1; + tools::Long nIntervalDays = nDayCount / nMaxMainIncrementCount; + double nDaysPerInterval = 1.0; + if( nIntervalDays>365 || rExplicitScale.TimeResolution==YEAR ) + { + 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 || rExplicitScale.TimeResolution==MONTH ) + { + 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::approxFloor( nIntervalDays/nDaysPerInterval ) ); + if(nNumer<=0) + nNumer=1; + if( rExplicitIncrement.MajorTimeInterval.TimeUnit == DAY ) + { + if( nNumer>2 && nNumer<7 ) + nNumer=7; + else if( nNumer>7 ) + { + rExplicitIncrement.MajorTimeInterval.TimeUnit = MONTH; + nDaysPerInterval = 31.0; + nNumer = static_cast<sal_Int32>( rtl::math::approxFloor( nIntervalDays/nDaysPerInterval ) ); + if(nNumer<=0) + nNumer=1; + } + } + rExplicitIncrement.MajorTimeInterval.Number = nNumer; + nMainIncrementCount = static_cast<tools::Long>(nDayCount/(nNumer*nDaysPerInterval)); + } + + //choose minor time interval: + if( !bAutoMinor ) + { + if( rExplicitIncrement.MinorTimeInterval.TimeUnit > rExplicitIncrement.MajorTimeInterval.TimeUnit ) + rExplicitIncrement.MinorTimeInterval.TimeUnit = rExplicitIncrement.MajorTimeInterval.TimeUnit; + tools::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 ) + return; + + 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 = 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 if ( !std::isfinite(fDistance) ) + { + // fdo#43703: Handle values bigger than limits correctly + fDistanceNormalized = 1.0; + fDistanceMagnitude = std::numeric_limits<double>::max(); + } + 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 = false; + } + rExplicitIncrement.SubIncrements.push_back(aExplicitSubIncrement); + } +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |