/* -*- 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 .
*/
namespace chart
{ usingnamespace ::com::sun::star; usingnamespace ::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;
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;
// 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
// 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();
/* 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 ***
/* 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; elseif( !bAutoMaximum )
rExplicitIncrement.BaseValue = fTempMaximum; else
rExplicitIncrement.BaseValue = 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;
// round to entire multiples of the distance and add additional space if( bAutoMinimum && m_bExpandBorderToIncrementRhythm )
{
fAxisMinimum = EquidistantTickFactory::getMinimumAtIncrement( fAxisMinimum, rExplicitIncrement );
// 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 );
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;
//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 ...
} elseif( 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;
}
//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;
// 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 ***
/* 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; elseif( !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;
// 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;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.