using System;
using System.Globalization;
namespace Visifire.Charts
{
///
/// AxisManager class calculates the max value, min value, interval and number of intervals
/// of the axis.
///
internal class AxisManager
{
#region Public Methods
#region "Constructor"
///
/// Initializes a new instance of the Visifire.Charts.AxisManager class
///
/// Maximum Value.
/// Minimum Value.
/// Makes sure that the zero is included in the axis range
/// Applies limits so that axis range doesn't cross it
public AxisManager(Double maxValue, Double minValue, Boolean startFromZero, Boolean allowLimitOverflow, Boolean stackingOverride, AxisRepresentations axisRepresentation)
{
if (maxValue < minValue)
throw (new ArgumentException("Invalid Argument:: Maximum Data value should be always greater than the minimum data value."));
this._max = (Decimal)maxValue;
this._min = (Decimal)minValue;
AxisRepresentation = axisRepresentation;
if (startFromZero)
{
if (minValue >= 0) AxisMinimumValue = 0;
else if (maxValue <= 0) AxisMaximumValue = 0;
}
if (!allowLimitOverflow)
{
if (minValue == 0) AxisMinimumValue = 0;
if (maxValue == 0) AxisMaximumValue = 0;
}
if (!allowLimitOverflow & stackingOverride)
{
AxisMaximumValue = maxValue;
AxisMinimumValue = minValue;
}
}
#endregion
///
/// Function calculates the max value, min value, interval and number of intervals of the axis.
///
public void Calculate()
{
Int32 loop = 0; // No of iteration.
Int32 maxMagnitude; // Magnitude of max data value.
Int32 minMagnitude; // Magnitude of min data value.
Int32 magnitude; // Magnitude of max/min data value.
Decimal nextInterval; // Next calculated interval size from the old interval size.
Decimal tempAxisMaximumValue; // Calculated maximum value of the axis.
Decimal tempAxisMinimumValue; // Calculated minimum value of the axis.
// Handle values less than 10 and Greater than 2
if (AxisRepresentation == AxisRepresentations.AxisX)
{
if (this._max < 10 && this._min >= 0)
this._maxNoOfInterval = (Int32)(_max + 1);
}
else
{
if (_max > 2 && this._max < 10 && this._min >= 0)
this._maxNoOfInterval = (Int32)(_max + 1);
}
// Only one value presents to calculate the range.
if (this._max == this._min)
{
CalculateSingle(); // Calculation for single value.
return;
}
// Max is rounded to the nearest power of 10.
maxMagnitude = OrderOfMagnitude(this._max);
// Min is rounded to the nearest power of 10.
minMagnitude = OrderOfMagnitude(this._min);
// The maximum magnitude need to chose, in order to calculate the initial
// interval size having maximum the value.
magnitude = (maxMagnitude > minMagnitude) ? maxMagnitude : minMagnitude;
// Interval needs to be sinking towards the power of 10.
// Initially maximum interval is chosen.
if (this._overrideInterval)
nextInterval = this._interval;
else
nextInterval = (Decimal)Math.Pow(10, magnitude + 1);
// Rounding down the axisMaximumValue if necessary.
if (this._overrideAxisMaximumValue)
tempAxisMaximumValue = this._axisMaximumValue;
else
tempAxisMaximumValue = RoundAxisMaximumValue(this._max, nextInterval);
// Rounding up the axisMinimumValue if necessary.
if (this._overrideAxisMinimumValue)
tempAxisMinimumValue = this._axisMinimumValue;
else
tempAxisMinimumValue = RoundAxisMinimumValue(this._min, nextInterval);
this._interval = nextInterval;
this._axisMaximumValue = tempAxisMaximumValue;
this._axisMinimumValue = tempAxisMinimumValue;
// Next intervals will be calculated inside loop in iterative way.
// Top value will be rounded down as much as possible.
// Bottom value will be rounded up as much as possible.
// So, In each pass in while loop calculates the new reduced interval.
// which helps to calculate the new maximum and minimum value for axis.
while (++loop < 100)
{
Int32 nextNoOfInterval; // Number of interval increased in iterative way.
// Try to minimize the Interval Value if possible.
if (!this._overrideInterval)
nextInterval = ReduceInterval(nextInterval);
// If next interval is undesirable then stop further calculation.
if (nextInterval == 0)
break;
// Rounding down the axisMaximumValue if necessary.
if (!this._overrideAxisMaximumValue)
tempAxisMaximumValue = RoundAxisMaximumValue(this._max, nextInterval);
// Rounding down the axisMinimumValue if necessary.
if (!this._overrideAxisMinimumValue)
tempAxisMinimumValue = RoundAxisMinimumValue(this._min, nextInterval);
// Calculate the number of interval.
nextNoOfInterval = (Int32)((tempAxisMaximumValue - tempAxisMinimumValue) / nextInterval);
// Number of interval cannot exceed the user expected no of interval.
if (nextNoOfInterval > this._maxNoOfInterval)
break;
this._axisMaximumValue = tempAxisMaximumValue;
this._axisMinimumValue = tempAxisMinimumValue;
this._interval = nextInterval;
}
}
#endregion
#region Public Properties
public AxisRepresentations AxisRepresentation
{
get;
set;
}
///
/// Get or set the maximum number of intervals.
///
public Int32 MaximumNoOfInterval
{
set
{
if (value < 0)
throw (new Exception("Invalid property value:: Expected number of intervals should be positive."));
else
if (value > 100)
throw (new Exception("Property out of range:: Expected number of intervals should be less than or equals to 1000."));
this._maxNoOfInterval = value;
}
get
{
return GetNoOfIntervals();
}
}
private Boolean _includeZero;
///
/// Write-only property used to include zero in the axis range.
///
public Boolean IncludeZero
{
get
{
return _includeZero;
}
set
{
_includeZero = true;
// If zero is included need to set min value as 0.
if (value == true && this._min > 0)
this._min = 0;
}
}
///
/// Get or set the axis maximum value.
///
public Double AxisMaximumValue
{
get
{
return (Double)this._axisMaximumValue;
}
set
{
this._axisMaximumValue = (Decimal)value;
this._overrideAxisMaximumValue = true;
}
}
///
/// Get or set the interval.
///
public Double Interval
{
get
{
return (Double)_interval;
}
set
{
if (value < 0)
throw (new Exception("Invalid property value:: Interval size should be positive always."));
this._interval = (Decimal)value;
this._overrideInterval = true;
}
}
///
/// Get or set the axis minimum value.
///
public Double AxisMinimumValue
{
set
{
this._axisMinimumValue = (Decimal)value;
this._overrideAxisMinimumValue = true;
}
get
{
return (Double)this._axisMinimumValue;
}
}
///
/// Get or set minimum data value
///
public Double MinimumValue
{
get
{
return (Double)_min;
}
set
{
_min = (Decimal)value;
}
}
///
/// Get or set maximum data value
///
public Double MaximumValue
{
get
{
return (Double)_max;
}
set
{
_max = (Decimal)value;
}
}
#endregion
#region Private Methods
///
/// Returns the number of Intervals in the calculated range.
///
private Int32 GetNoOfIntervals()
{
return (Int32)((this._axisMaximumValue - this._axisMinimumValue) / this._interval);
}
///
/// Removes decimal point from a decimal number.
///
/// Number used for calculation.
/// Returns an integer.
private Int64 RemoveDecimalPoint(Decimal number)
{
// Number is already an integer.
if ((Int64)(number) == number)
return (Int64)(number);
else
// Multiply 10 to move the decimal point to one digit right.
while ((Int64)(number) != number)
number = number * 10;
return (Int64)(number);
}
///
/// Finds the position of the decimal point in decimal number.
///
/// Number used for calculation.
/// Returns an integer.
private Int32 IndexOfDecimalPoint(Decimal number)
{
Int32 count = 0; // local variable as counter.
// While number is not an integer.
while ((Int64)(number) != number)
{
count++;
number = number * 10;
}
return count;
}
///
/// Remove trailing from an integer.
///
/// Number used for calculation.
/// Returns an integer.
private Int64 RemoveZeroFromInt(Int64 number)
{
// While the number is divided by 10.
while ((number % 10) == 0)
number = number / 10;
return number;
}
///
/// Calculate the number of zeros at the end of a number.
///
/// Number used for calculation.
/// Returns an integer.
private Int32 NoOfZeroAtEndInInt(Int64 number)
{
Int32 count = 0; // Keep track the no of zeros.
while ((number % 10) == 0)
{
count++;
number = number / 10;
}
return count;
}
///
/// Calculate the mantissa or exponent of decimal number.
///
/// According to the argument mantissa or exponent will be returned.
/// Number used for calculation.
/// Returns mantissa or exponent.
private Int64 GetMantissaOrExponent(MantissaOrExponent mantissaOrExponent, Decimal number)
{
if (mantissaOrExponent == MantissaOrExponent.Exponent)
{
Int32 exponent;
exponent = NoOfZeroAtEndInInt(RemoveDecimalPoint(number));
exponent -= IndexOfDecimalPoint(number);
return (Int64)exponent;
}
else
{
Int64 mantissa;
mantissa = RemoveZeroFromInt(RemoveDecimalPoint(number));
return mantissa;
}
}
///
/// Finds the order of magnitude of a number.
/// Note: A number rounded to the nearest power of 10 is called an order of magnitude.
///
/// Number used for calculation.
/// Returns an integer.
private Int32 OrderOfMagnitude(Decimal number)
{
Int64 mantissa; // Mantissa of number.
Int64 exponent; // Exponent of number.
if (number == 0)
return 0;
mantissa = GetMantissaOrExponent(MantissaOrExponent.Mantissa, number);
exponent = GetMantissaOrExponent(MantissaOrExponent.Exponent, number);
return mantissa.ToString(CultureInfo.InvariantCulture).Length + (Int32)(exponent - 1);
}
///
/// Rounding down the value of axis maximum value.
///
/// Axis maximum value.
/// Interval value.
///
private Decimal RoundAxisMaximumValue(Decimal axisMaxValue, Decimal intervalValue)
{
axisMaxValue = axisMaxValue / intervalValue;
axisMaxValue = Decimal.Floor(axisMaxValue);
axisMaxValue = (axisMaxValue + 1) * intervalValue;
return axisMaxValue;
}
///
/// Rounding Up the value of axis minimum value.
///
/// Axis minimum value.
/// Interval value.
private Decimal RoundAxisMinimumValue(Decimal axisMinValue, Decimal intervalValue)
{
axisMinValue = axisMinValue / intervalValue;
axisMinValue = (Decimal)Math.Ceiling((Double)axisMinValue);
axisMinValue = (axisMinValue - 1) * intervalValue;
return axisMinValue;
}
///
/// Try to minimize the Interval Value if possible.
///
/// Interval value.
/// Reduced interval.
private Decimal ReduceInterval(Decimal intervalValue)
{
Int64 mantissa; // Mantissa of interval value.
mantissa = GetMantissaOrExponent(MantissaOrExponent.Mantissa, intervalValue);
// Easily understandable numbers by human brain are 5, 2 and 1 or power of 5 or 2 or 1.
// Point to be noted: A number is divisible by its own mantissa.
if (mantissa == 5)
return (intervalValue * 2 / 5);
else if (mantissa == 2)
return (intervalValue * 1 / 2);
else if (mantissa == 1)
return (intervalValue * 5 / 10);
else
return 0;
}
///
/// Function calculates the max value, min value, interval and number of intervals of the axis
/// for a single value range.
///
private void CalculateSingle()
{
Int32 loop = 0; // No of iteration.
Int64 magnitude; // Magnitude of max/min value.
Decimal nextInterval; // Next Calculated interval from the old interval.
// If the max and min both are same and equals to zero then the best range is 0 to 1.
if (_max == 0)
{
this._axisMaximumValue = 1;
this._axisMinimumValue = 0;
this._interval = 1;
return;
}
// Max is rounded to the nearest power of 10.
magnitude = OrderOfMagnitude(this._max);
// Interval needs to be sinking towards the power of 10.
// Initially maximum interval is chosen.
if (this._overrideInterval)
nextInterval = this._interval;
else
nextInterval = (Decimal)Math.Pow(10, magnitude);
// Rounding down the axisMaximumValue if necessary.
if (!this._overrideAxisMaximumValue)
this._axisMaximumValue = RoundAxisMaximumValue(this._max, nextInterval);
// Rounding down the axisMaximumValue if necessary.
if (!this._overrideAxisMinimumValue)
this._axisMinimumValue = RoundAxisMinimumValue(this._max, nextInterval);
this._interval = nextInterval;
// Next intervals will be calculated inside loop in iterative way.
while (loop++ < 100)
{
Int32 nextNoOfInterval; // Number of interval.
// Try to minimize the Interval Value if possible.
if (!this._overrideInterval)
nextInterval = ReduceInterval(nextInterval);
// If next interval is undesirable then stop further calculation.
if (nextInterval == 0)
break;
// Calculate number of interval.
nextNoOfInterval = (Int32)((this._axisMaximumValue - this._axisMinimumValue) / nextInterval);
// Number of interval cannot exceed the user expected no of interval.
if (nextNoOfInterval > _maxNoOfInterval)
break;
this._interval = nextInterval;
}
}
#endregion
#region Data
// Input parameters.
private Decimal _min; // Min data value.
private Decimal _max; // Max data value.
private Int32 _maxNoOfInterval = 10; // Maximum number of intervals.
// Values calculated by this class.
private Decimal _interval; // The interval size.
private Decimal _axisMaximumValue; // Calculated Maximum value of the axis.
private Decimal _axisMinimumValue; // Calculated Minimum value of the axis.
// Member variables used to keep track about override operation.
private Boolean _overrideAxisMaximumValue = false;
private Boolean _overrideAxisMinimumValue = false;
private Boolean _overrideInterval = false;
#endregion Data
}
}