|
//-------------------------------------------------------------
// <copyright company=’Microsoft Corporation’>
// Copyright © Microsoft Corporation. All Rights Reserved.
// </copyright>
//-------------------------------------------------------------
// @owner=alexgor, deliant
//=================================================================
// File: AxisScale.cs
//
// Namespace: System.Web.UI.WebControls[Windows.Forms].Charting
//
// Classes: AxisScale
//
// Purpose: Base class for the Axis class which defines axis
// csale related properties and methods.
//
// Reviewed: GS Aug 8, 2002
// AG Aug 8, 2002
//
//===================================================================
#region Used namespaces
using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Data;
using System.Drawing;
using System.Drawing.Design;
using System.Drawing.Drawing2D;
using System.Collections.Generic;
#if Microsoft_CONTROL
using System.Windows.Forms.DataVisualization.Charting.Data;
using System.Windows.Forms.DataVisualization.Charting.ChartTypes;
using System.Windows.Forms.DataVisualization.Charting.Utilities;
using System.Windows.Forms.DataVisualization.Charting.Borders3D;
using System.Windows.Forms.DataVisualization.Charting;
#else
using System.Web;
using System.Web.UI;
using System.Web.UI.DataVisualization.Charting;
using System.Web.UI.DataVisualization.Charting.Data;
using System.Web.UI.DataVisualization.Charting.ChartTypes;
using System.Web.UI.DataVisualization.Charting.Utilities;
#endif
#endregion
#if Microsoft_CONTROL
namespace System.Windows.Forms.DataVisualization.Charting
#else
namespace System.Web.UI.DataVisualization.Charting
#endif
{
#region Axis enumerations
/// <summary>
/// An enumeration of the mode of automatically calculating intervals.
/// </summary>
public enum IntervalAutoMode
{
/// <summary>
/// Fixed number of intervals always created on the axis.
/// </summary>
FixedCount,
/// <summary>
/// Number of axis intervals depends on the axis length.
/// </summary>
VariableCount
}
/// <summary>
/// An enumeration of axis position.
/// </summary>
internal enum AxisPosition
{
/// <summary>
/// Left position
/// </summary>
Left,
/// <summary>
/// Right position
/// </summary>
Right,
/// <summary>
/// Top position
/// </summary>
Top,
/// <summary>
/// Bottom position
/// </summary>
Bottom
}
/// <summary>
/// An enumeration of axis arrow styles.
/// </summary>
public enum AxisArrowStyle
{
/// <summary>
/// No arrow
/// </summary>
None,
/// <summary>
/// Triangle type
/// </summary>
Triangle,
/// <summary>
/// Sharp triangle type
/// </summary>
SharpTriangle,
/// <summary>
/// Lines type
/// </summary>
Lines
}
#endregion
/// <summary>
/// The Axis class keeps information about minimum, maximum
/// and interval values and it is responsible for setting
/// these values automatically. It also handles
/// logarithmic and reversed axis.
/// </summary>
public partial class Axis
{
#region Axis scale fields
// Represents the distance between the data points and its
// chart area margin, Measured as a percentage of default
// margin size.
internal double margin = 100.0;
internal double marginView = 0.0;
internal bool offsetTempSet = false;
// Used for column chart margin
internal double marginTemp = 0.0;
private ArrayList _stripLineOffsets = new ArrayList();
// Data members, which store properties values
private bool _isLogarithmic = false;
internal double logarithmBase = 10.0;
internal bool isReversed = false;
internal bool isStartedFromZero = true;
internal TickMark minorTickMark = null;
internal TickMark majorTickMark = null;
internal Grid minorGrid = null;
internal Grid majorGrid = null;
internal bool enabled = false;
internal bool autoEnabled = true;
internal LabelStyle labelStyle = null;
private DateTimeIntervalType _internalIntervalType = DateTimeIntervalType.Auto;
internal double maximum = Double.NaN;
internal double crossing = Double.NaN;
internal double minimum = Double.NaN;
// Temporary Minimum and maximum values.
internal double tempMaximum = Double.NaN;
internal double tempMinimum = Double.NaN;
internal double tempCrossing = Double.NaN;
internal CustomLabelsCollection tempLabels;
internal bool tempAutoMaximum = true;
internal bool tempAutoMinimum = true;
internal double tempMajorGridInterval = Double.NaN;
internal double tempMinorGridInterval = 0.0;
internal double tempMajorTickMarkInterval = Double.NaN;
internal double tempMinorTickMarkInterval = 0.0;
internal double tempLabelInterval = Double.NaN;
internal DateTimeIntervalType tempGridIntervalType = DateTimeIntervalType.NotSet;
internal DateTimeIntervalType tempTickMarkIntervalType = DateTimeIntervalType.NotSet;
internal DateTimeIntervalType tempLabelIntervalType = DateTimeIntervalType.NotSet;
// Paint mode
internal bool paintMode = false;
// Axis type (X, Y, X2, Y2)
internal AxisName axisType = AxisName.X;
// Automatic maximum value (from data point values).
private bool _autoMaximum = true;
// Automatic minimum value (from data point values).
private bool _autoMinimum = true;
/// <summary>
/// Axis position: Left, Right, Top Bottom
/// </summary>
private AxisPosition _axisPosition = AxisPosition.Left;
/// <summary>
/// Opposite Axis for this Axis. Necessary for Crossing.
/// </summary>
internal Axis oppositeAxis = null;
// Axis data scaleView
private AxisScaleView _scaleView = null;
#if Microsoft_CONTROL
// Axis scroll bar class
internal AxisScrollBar scrollBar = null;
#endif // Microsoft_CONTROL
// For scater chart X values could be rounded.
internal bool roundedXValues = false;
// If Axis is logarithmic value shoud be converted to
// linear only once.
internal bool logarithmicConvertedToLinear = false;
// IsLogarithmic minimum value
internal double logarithmicMinimum;
// IsLogarithmic maximum value
internal double logarithmicMaximum;
// Correction of interval because of
// 3D Rotation and perspective
internal double interval3DCorrection = Double.NaN;
// Axis coordinate convertion optimization fields
internal bool optimizedGetPosition = false;
internal double paintViewMax = 0.0;
internal double paintViewMin = 0.0;
internal double paintRange = 0.0;
internal double valueMultiplier = 0.0;
internal RectangleF paintAreaPosition = RectangleF.Empty;
internal double paintAreaPositionBottom = 0.0;
internal double paintAreaPositionRight = 0.0;
internal double paintChartAreaSize = 0.0;
// Determines how number of intervals automatically calculated
private IntervalAutoMode _intervalAutoMode = IntervalAutoMode.FixedCount;
// True if scale segments are used
internal bool scaleSegmentsUsed = false;
// Preffered number of intervals on the axis
internal int prefferedNumberofIntervals = 5;
private Stack<Double> _intervalsStore = new Stack<Double>();
#endregion
#region Axis scale properties
/// <summary>
/// Axis position
/// </summary>
[
Bindable(true),
DefaultValue(AxisPosition.Left),
NotifyParentPropertyAttribute(true),
SRDescription("DescriptionAttributeReverse"),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute),
#endif
DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden),
SerializationVisibilityAttribute(SerializationVisibility.Hidden)
]
virtual internal AxisPosition AxisPosition
{
get
{
return this._axisPosition;
}
set
{
this._axisPosition = value;
#if SUBAXES
// Update axis position of the sub axis
if( !((Axis)this).IsSubAxis )
{
foreach(SubAxis subAxis in ((Axis)this).SubAxes)
{
subAxis._axisPosition = value;
}
}
#endif // SUBAXES
this.Invalidate();
}
}
/// <summary>
/// Gets or sets a flag which indicates whether the number of intervals
/// on the axis is fixed or varies with the axis size.
/// </summary>
[
SRCategory("CategoryAttributeInterval"),
DefaultValue(IntervalAutoMode.FixedCount),
SRDescription("DescriptionAttributeIntervalAutoMode"),
]
public IntervalAutoMode IntervalAutoMode
{
get
{
return this._intervalAutoMode;
}
set
{
this._intervalAutoMode = value;
this.Invalidate();
}
}
/// <summary>
/// Gets or sets a flag which indicates whether the axis is reversed.
/// If set to reversed, the values on the axis are in reversed sort order
/// and the direction of values on the axis is flipped.
/// </summary>
[
SRCategory("CategoryAttributeScale"),
Bindable(true),
DefaultValue(false),
NotifyParentPropertyAttribute(true),
SRDescription("DescriptionAttributeReverse"),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public bool IsReversed
{
get
{
return isReversed;
}
set
{
isReversed = value;
this.Invalidate();
}
}
/// <summary>
/// Gets or sets a flag which indicates whether the minimum value
/// of the axis will be automatically set to zero if all data point
/// values are positive. If there are negative data point values,
/// the minimum value of the data points will be used.
/// </summary>
[
SRCategory("CategoryAttributeScale"),
Bindable(true),
DefaultValue(true),
NotifyParentPropertyAttribute(true),
SRDescription("DescriptionAttributeStartFromZero3"),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public bool IsStartedFromZero
{
get
{
return isStartedFromZero;
}
set
{
isStartedFromZero = value;
this.Invalidate();
}
}
/// <summary>
/// Gets or sets a flag to add a margin to the axis.
/// If true, a space is added between the first/last data
/// point and the border of chart area.
/// </summary>
[
SRCategory("CategoryAttributeScale"),
Bindable(true),
DefaultValue(true),
NotifyParentPropertyAttribute(true),
SRDescription("DescriptionAttributeMargin"),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public bool IsMarginVisible
{
get
{
if( margin > 0 )
return true;
else
return false;
}
set
{
if( value == true )
margin = 100;
else
margin = 0;
this.Invalidate();
}
}
/// <summary>
/// Date and time interval type.
/// </summary>
[
SRCategory("CategoryAttributeScale"),
Bindable(true),
DefaultValue(DateTimeIntervalType.Auto),
NotifyParentPropertyAttribute(true),
SRDescription("DescriptionAttributeInternalIntervalType"),
RefreshPropertiesAttribute(RefreshProperties.All),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
internal DateTimeIntervalType InternalIntervalType
{
get
{
return _internalIntervalType;
}
set
{
// Set intervals for labels, grids and tick marks. ( Auto interval type )
if( tempMajorGridInterval <= 0.0 ||
(double.IsNaN(tempMajorGridInterval) && ((Axis)this).Interval <= 0.0) )
{
majorGrid.intervalType = value;
}
if( this.tempMajorTickMarkInterval <= 0.0 ||
(double.IsNaN(tempMajorTickMarkInterval) && ((Axis)this).Interval <= 0.0) )
{
majorTickMark.intervalType = value;
}
if( this.tempLabelInterval <= 0.0 ||
(double.IsNaN(tempLabelInterval) && ((Axis)this).Interval <= 0.0) )
{
labelStyle.intervalType = value;
}
_internalIntervalType = value;
this.Invalidate();
}
}
/// <summary>
/// Sets auto interval values to grids, tick marks
/// and labels
/// </summary>
internal double SetInterval
{
set
{
if( tempMajorGridInterval <= 0.0 ||
(double.IsNaN(tempMajorGridInterval) && ((Axis)this).Interval <= 0.0) )
{
majorGrid.interval = value;
}
if( tempMajorTickMarkInterval <= 0.0 ||
(double.IsNaN(tempMajorTickMarkInterval) && ((Axis)this).Interval <= 0.0) )
{
majorTickMark.interval = value;
}
if( tempLabelInterval <= 0.0 ||
(double.IsNaN(tempLabelInterval) && ((Axis)this).Interval <= 0.0) )
{
labelStyle.interval = value;
}
this.Invalidate();
}
}
/// <summary>
/// Sets auto interval values to grids, tick marks
/// and labels
/// </summary>
internal void SetIntervalAndType(double newInterval, DateTimeIntervalType newIntervalType)
{
if( tempMajorGridInterval <= 0.0 ||
(double.IsNaN(tempMajorGridInterval) && ((Axis)this).Interval <= 0.0) )
{
majorGrid.interval = newInterval;
majorGrid.intervalType = newIntervalType;
}
if( tempMajorTickMarkInterval <= 0.0 ||
(double.IsNaN(tempMajorTickMarkInterval) && ((Axis)this).Interval <= 0.0) )
{
majorTickMark.interval = newInterval;
majorTickMark.intervalType = newIntervalType;
}
if( tempLabelInterval <= 0.0 ||
(double.IsNaN(tempLabelInterval) && ((Axis)this).Interval <= 0.0) )
{
labelStyle.interval = newInterval;
labelStyle.intervalType = newIntervalType;
}
this.Invalidate();
}
/// <summary>
/// Gets or sets the maximum axis value.
/// </summary>
[
SRCategory("CategoryAttributeScale"),
Bindable(true),
DefaultValue(Double.NaN),
NotifyParentPropertyAttribute(true),
SRDescription("DescriptionAttributeMaximum"),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute),
#endif
TypeConverter(typeof(AxisMinMaxAutoValueConverter))
]
public double Maximum
{
get
{
// Get maximum
if (_isLogarithmic && logarithmicConvertedToLinear && !Double.IsNaN(maximum))
return logarithmicMaximum;
else
return maximum;
}
set
{
// Split a value to maximum and auto maximum
if( Double.IsNaN(value) )
{
_autoMaximum = true;
maximum = Double.NaN;
}
else
{
// Set maximum
maximum = value;
// Set non linearized Maximum for logarithmic scale
logarithmicMaximum = value;
_autoMaximum = false;
}
// Reset original property value fields
((Axis)this).tempMaximum = maximum;
// This line is added because of Save ScaleView State August 29, 2003
// in Web Forms. This place could cause problems with Reset Auto Values.
((Axis)this).tempAutoMaximum = _autoMaximum;
this.Invalidate();
}
}
/// <summary>
/// Gets or sets the minimum axis value
/// </summary>
[
SRCategory("CategoryAttributeScale"),
Bindable(true),
DefaultValue(Double.NaN),
NotifyParentPropertyAttribute(true),
SRDescription("DescriptionAttributeMinimum"),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute),
#endif
TypeConverter(typeof(AxisMinMaxAutoValueConverter))
]
public double Minimum
{
get
{
// Get minimum
if (_isLogarithmic && logarithmicConvertedToLinear && !Double.IsNaN(maximum))
return logarithmicMinimum;
else
return minimum;
}
set
{
// Split a value to minimum and auto minimum
if( Double.IsNaN(value) )
{
_autoMinimum = true;
minimum = Double.NaN;
}
else
{
// Set maximum
minimum = value;
_autoMinimum = false;
// Set non linearized Minimum for logarithmic scale
logarithmicMinimum = value;
}
// Reset original property value fields
((Axis)this).tempMinimum = minimum;
// This line is added because of Save ScaleView State August 29, 2003
// in Web Forms. This place could cause problems with Reset Auto Values.
((Axis)this).tempAutoMinimum = _autoMinimum;
this.Invalidate();
}
}
/// <summary>
/// Gets or sets the point where axis is crossed by another axis.
/// </summary>
[
SRCategory("CategoryAttributeScale"),
Bindable(true),
DefaultValue(Double.NaN),
NotifyParentPropertyAttribute(true),
SRDescription("DescriptionAttributeCrossing"),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute),
#endif
TypeConverter(typeof(AxisCrossingValueConverter))
]
virtual public double Crossing
{
get
{
if( paintMode )
if (_isLogarithmic)
return Math.Pow( this.logarithmBase, GetCrossing() );
else
return GetCrossing();
else
return crossing;
}
set
{
crossing = value;
// Reset original property value fields
((Axis)this).tempCrossing = crossing;
this.Invalidate();
}
}
/// <summary>
/// Enables or disables the axis.
/// </summary>
[
SRCategory("CategoryAttributeMisc"),
Bindable(true),
DefaultValue(typeof(AxisEnabled), "Auto"),
NotifyParentPropertyAttribute(true),
SRDescription("DescriptionAttributeEnabled7"),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public AxisEnabled Enabled
{
get
{
// Take Enabled from two fields: enabled and auto enabled
if( autoEnabled )
{
return AxisEnabled.Auto;
}
else if( enabled )
{
return AxisEnabled.True;
}
else
{
return AxisEnabled.False;
}
}
set
{ // Split Enabled to two fields: enabled and auto enabled
if( value == AxisEnabled.Auto )
{
autoEnabled = true;
}
else if( value == AxisEnabled.True )
{
enabled = true;
autoEnabled = false;
}
else
{
enabled = false;
autoEnabled = false;
}
this.Invalidate();
}
}
/// <summary>
/// Gets or sets a flag which indicates whether the axis is logarithmic.
/// Zeros or negative data values are not allowed on logarithmic charts.
/// </summary>
[
SRCategory("CategoryAttributeScale"),
Bindable(true),
DefaultValue(false),
NotifyParentPropertyAttribute(true),
SRDescription("DescriptionAttributeLogarithmic"),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public bool IsLogarithmic
{
get
{
return _isLogarithmic;
}
set
{
_isLogarithmic = value;
this.Invalidate();
}
}
/// <summary>
/// Base of the logarithm used in logarithmic scale.
/// By default, this value is 10.
/// </summary>
[
SRCategory("CategoryAttributeScale"),
Bindable(true),
DefaultValue(10.0),
NotifyParentPropertyAttribute(true),
SRDescription("DescriptionAttributeLogarithmBase"),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public double LogarithmBase
{
get
{
return logarithmBase;
}
set
{
if( value < 2.0 )
{
throw (new ArgumentOutOfRangeException("value", SR.ExceptionAxisScaleLogarithmBaseInvalid));
}
logarithmBase = value;
this.Invalidate();
}
}
#endregion
#region Axis Segments and Scale Breaks Properties
// Field that stores Axis automatic scale breaks style.
internal AxisScaleBreakStyle axisScaleBreakStyle = null;
/// <summary>
/// Gets or sets the style of scale breaks.
/// </summary>
[
SRCategory("CategoryAttributeScale"),
SRDescription("DescriptionAttributeScaleBreakStyle"),
TypeConverter(typeof(NoNameExpandableObjectConverter)),
NotifyParentPropertyAttribute(true),
#if Microsoft_CONTROL
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
#else
PersistenceMode(PersistenceMode.InnerProperty),
#endif
]
virtual public AxisScaleBreakStyle ScaleBreakStyle
{
get
{
return this.axisScaleBreakStyle;
}
set
{
this.axisScaleBreakStyle = value;
this.axisScaleBreakStyle.axis = (Axis)this;
//this.Invalidate();
}
}
// Field that stores axis scale segments
internal AxisScaleSegmentCollection scaleSegments = null;
/// <summary>
/// Axis scale segment collection.
/// </summary>
[
SRCategory("CategoryAttributeScale"),
Browsable(false),
EditorBrowsable(EditorBrowsableState.Never),
SRDescription("DescriptionAttributeAxisScaleSegmentCollection_AxisScaleSegmentCollection"),
SerializationVisibilityAttribute(SerializationVisibility.Hidden),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
Editor(Editors.ChartCollectionEditor.Editor, Editors.ChartCollectionEditor.Base)
]
internal AxisScaleSegmentCollection ScaleSegments
{
get
{
return this.scaleSegments;
}
}
#endregion // Axis Segments and Scale Breaks Properties
#region Axis data scaleView properies and methods
/// <summary>
/// Gets or sets the scale view settings of the axis.
/// </summary>
[
SRCategory("CategoryAttributeDataView"),
Bindable(true),
SRDescription("DescriptionAttributeView"),
#if Microsoft_CONTROL
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
#else
PersistenceMode(PersistenceMode.InnerProperty),
#endif
TypeConverter(typeof(NoNameExpandableObjectConverter))
]
public AxisScaleView ScaleView
{
get
{
return _scaleView;
}
set
{
_scaleView = value;
_scaleView.axis = (Axis)this;
this.Invalidate();
}
}
#if Microsoft_CONTROL
/// <summary>
/// Gets or sets the scroll bar settings of the axis.
/// </summary>
[
SRCategory("CategoryAttributeDataView"),
Bindable(true),
SRDescription("DescriptionAttributeScrollBar"),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
TypeConverter(typeof(NoNameExpandableObjectConverter))
]
public AxisScrollBar ScrollBar
{
get
{
return scrollBar;
}
set
{
scrollBar = value;
scrollBar.axis = (Axis)this;
this.Invalidate();
}
}
#endif // Microsoft_CONTROL
/// <summary>
/// Gets axis data scaleView minimum position.
/// </summary>
/// <returns>Axis data scaleView minimum position.</returns>
internal double ViewMinimum
{
get { return _scaleView.ViewMinimum; }
}
/// <summary>
/// Gets axis data scaleView minimum position.
/// </summary>
/// <returns>Axis data scaleView minimum position.</returns>
internal double ViewMaximum
{
get { return _scaleView.ViewMaximum; }
}
/// <summary>
/// Gets automatic maximum value (from data point values).
/// </summary>
internal bool AutoMaximum
{
get { return _autoMaximum; }
}
/// <summary>
/// Gets automatic minimum value (from data point values).
/// </summary>
internal bool AutoMinimum
{
get { return _autoMinimum; }
}
#endregion
#region Axis position converters methos
/// <summary>
/// This function converts axis value to relative position (0-100%).
/// If an axis has a logarithmic scale, the value is converted to a linear scale.
/// </summary>
/// <param name="axisValue">Value from axis.</param>
/// <returns>Relative position (0-100%).</returns>
public double GetPosition( double axisValue )
{
// Adjust for the IsLogarithmic axis
if (_isLogarithmic && axisValue != 0.0)
{
axisValue = Math.Log( axisValue, this.logarithmBase );
}
// Get linear position
return GetLinearPosition(axisValue);
}
/// <summary>
/// This function converts an axis value to relative position (0-100%).
/// If an axis has a logarithmic scale, the value is converted to a linear scale.
/// </summary>
/// <param name="axisValue">Axis value.</param>
/// <returns>Relative position (0-100%).</returns>
public double ValueToPosition( double axisValue )
{
return GetPosition( axisValue );
}
/// <summary>
/// This function converts an axis value to a pixel position.
/// If an axis has a logarithmic scale, the value is converted to a linear scale.
/// </summary>
/// <param name="axisValue">Value from axis.</param>
/// <returns>Pixel position.</returns>
public double ValueToPixelPosition( double axisValue )
{
// Get relative value
double val = ValueToPosition(axisValue);
// Convert it to pixels
if( AxisPosition == AxisPosition.Top || AxisPosition == AxisPosition.Bottom )
{
val *= (this.Common.ChartPicture.Width - 1) / 100F;
}
else
{
val *= (this.Common.ChartPicture.Height - 1) / 100F;
}
return val;
}
/// <summary>
/// This function converts a relative position to an axis value.
/// If an axis has a logarithmic scale, the value is converted to a linear scale.
/// </summary>
/// <param name="position">Relative position (0-100%).</param>
/// <returns>Axis value.</returns>
public double PositionToValue( double position )
{
return PositionToValue(position, true);
}
/// <summary>
/// This function converts a relative position to an axis value.
/// If an axis has a logarithmic scale, the value is converted to a linear scale.
/// </summary>
/// <param name="position">Relative position (0-100%).</param>
/// <param name="validateInput">Indicates if input value range should be checked.</param>
/// <returns>Axis value.</returns>
internal double PositionToValue( double position, bool validateInput)
{
// Check parameters
if(validateInput &&
(position < 0 || position > 100) )
{
throw (new ArgumentException(SR.ExceptionAxisScalePositionInvalid, "position"));
}
// Check if plot area position was already calculated
if(PlotAreaPosition == null)
{
throw (new InvalidOperationException(SR.ExceptionAxisScalePositionToValueCallFailed));
}
// Convert chart picture position to plotting position
if( AxisPosition == AxisPosition.Top || AxisPosition == AxisPosition.Bottom )
position = position - PlotAreaPosition.X;
else
position = PlotAreaPosition.Bottom - position;
// The Chart area size
double ChartArea;
if( AxisPosition == AxisPosition.Top || AxisPosition == AxisPosition.Bottom )
ChartArea = PlotAreaPosition.Width;
else
ChartArea = PlotAreaPosition.Height;
// The Real range as double
double viewMax = ViewMaximum;
double viewMin = ViewMinimum;
double range = viewMax - viewMin;
// Avoid division by zero
double axisValue = 0;
if( range != 0 )
{
// Find axis value from position
axisValue = range / ChartArea * position;
}
// Corrected axis value for reversed
if( isReversed )
axisValue = viewMax - axisValue;
else
axisValue = viewMin + axisValue;
return axisValue;
}
/// <summary>
/// This function converts a pixel position to an axis value.
/// If an axis has a logarithmic scale, the value is converted to a linear scale.
/// </summary>
/// <param name="position">Pixel position.</param>
/// <returns>Axis value.</returns>
public double PixelPositionToValue( double position )
{
// Convert it to pixels
double val = position;
if( AxisPosition == AxisPosition.Top || AxisPosition == AxisPosition.Bottom )
{
val *= 100F / ((float)(this.Common.ChartPicture.Width - 1));
}
else
{
val *= 100F / ((float)(this.Common.ChartPicture.Height - 1));
}
// Get from relative position
return PositionToValue(val);
}
#endregion
#region Axis scale methods
/// <summary>
/// Sets axis position. Axis position depends
/// on crossing and reversed value.
/// </summary>
internal void SetAxisPosition()
{
// Change position of the axis
if( GetOppositeAxis().isReversed )
{
if( AxisPosition == AxisPosition.Left )
AxisPosition = AxisPosition.Right;
else if( AxisPosition == AxisPosition.Right )
AxisPosition = AxisPosition.Left;
else if( AxisPosition == AxisPosition.Top )
AxisPosition = AxisPosition.Bottom;
else if( AxisPosition == AxisPosition.Bottom )
AxisPosition = AxisPosition.Top;
}
}
/// <summary>
/// Sets temporary offset value.
/// </summary>
internal void SetTempAxisOffset( )
{
if (ChartArea.Series.Count == 0)
{
return;
}
// Conditions when this code changes margin size: Column chart,
// margin is turned off, Interval offset is not used for
// gridlines, tick marks and labels.
Series ser = ChartArea.GetFirstSeries();
if( ( ser.ChartType == SeriesChartType.Column ||
ser.ChartType == SeriesChartType.StackedColumn ||
ser.ChartType == SeriesChartType.StackedColumn100 ||
ser.ChartType == SeriesChartType.Bar ||
ser.ChartType == SeriesChartType.RangeBar ||
ser.ChartType == SeriesChartType.RangeColumn ||
ser.ChartType == SeriesChartType.StackedBar ||
ser.ChartType == SeriesChartType.StackedBar100 ) &&
margin != 100.0 && !offsetTempSet &&
this._autoMinimum)
{
// Find offset correction for Column chart margin.
double offset;
marginTemp = margin;
// Find point width
// Check if series provide custom value for point width
double pointWidthSize;
string strWidth = ser[CustomPropertyName.PointWidth];
if(strWidth != null)
{
pointWidthSize = CommonElements.ParseDouble(strWidth);
}
else
{
pointWidthSize = 0.8;
}
margin = ( pointWidthSize / 2 ) * 100;
offset = ( margin ) / 100;
double contraOffset = ( 100 - margin ) / 100;
if (this._intervalsStore.Count == 0)
{
this._intervalsStore.Push(this.labelStyle.intervalOffset);
this._intervalsStore.Push(this.majorGrid.intervalOffset);
this._intervalsStore.Push(this.majorTickMark.intervalOffset);
this._intervalsStore.Push(this.minorGrid.intervalOffset);
this._intervalsStore.Push(this.minorTickMark.intervalOffset);
}
this.labelStyle.intervalOffset = Double.IsNaN(this.labelStyle.intervalOffset) ? offset : this.labelStyle.intervalOffset + offset;
this.majorGrid.intervalOffset = Double.IsNaN(this.majorGrid.intervalOffset) ? offset : this.majorGrid.intervalOffset + offset;
this.majorTickMark.intervalOffset = Double.IsNaN(this.majorTickMark.intervalOffset) ? offset : this.majorTickMark.intervalOffset + offset;
this.minorGrid.intervalOffset = Double.IsNaN(this.minorGrid.intervalOffset) ? offset : this.minorGrid.intervalOffset + offset;
this.minorTickMark.intervalOffset = Double.IsNaN(this.minorTickMark.intervalOffset) ? offset : this.minorTickMark.intervalOffset + offset;
foreach( StripLine strip in ((Axis)(this)).StripLines )
{
_stripLineOffsets.Add( strip.IntervalOffset );
strip.IntervalOffset -= contraOffset;
}
offsetTempSet = true;
}
}
/// <summary>
/// Resets temporary offset value.
/// </summary>
internal void ResetTempAxisOffset( )
{
if( this.offsetTempSet )
{
System.Diagnostics.Debug.Assert(this._intervalsStore.Count == 5, "Fail in interval store count");
this.minorTickMark.intervalOffset = this._intervalsStore.Pop();
this.minorGrid.intervalOffset = this._intervalsStore.Pop();
this.majorTickMark.intervalOffset = this._intervalsStore.Pop();
this.majorGrid.intervalOffset = this._intervalsStore.Pop();
this.labelStyle.intervalOffset = this._intervalsStore.Pop();
int index = 0;
foreach( StripLine strip in ((Axis)(this)).StripLines )
{
if( _stripLineOffsets.Count > index )
{
strip.IntervalOffset = (double)_stripLineOffsets[index];
}
index++;
}
_stripLineOffsets.Clear();
offsetTempSet = false;
margin = marginTemp;
}
}
/// <summary>
/// This function will create auto maximum and minimum values
/// using the interval. This function will make a gap between
/// data points and border of the chart area.
/// </summary>
/// <param name="inter">Interval</param>
/// <param name="shouldStartFromZero">True if minimum scale value should start from zero.</param>
/// <param name="autoMax">Maximum is auto</param>
/// <param name="autoMin">Minimum is auto</param>
/// <param name="min">Minimum value</param>
/// <param name="max">Maximum value</param>
/// <returns>Interval</returns>
internal double RoundedValues(
double inter,
bool shouldStartFromZero,
bool autoMax,
bool autoMin,
ref double min,
ref double max )
{
// For X Axes
if( axisType == AxisName.X || axisType == AxisName.X2 )
{
if( margin == 0.0 && !this.roundedXValues )
{
return inter;
}
}
else // For Y Axes
{
// Avoid dividing with 0. There is no gap.
if( margin == 0.0 )
{
return inter;
}
}
if( autoMin )
{ // Set minimum value
if( min < 0.0 || ( !shouldStartFromZero && !ChartArea.stacked ) )
{
min = (double)( ((decimal)Math.Ceiling( min / inter ) - 1m ) * (decimal)inter );
}
else
{
min = 0.0;
}
}
if( autoMax )
{// Set maximum value
if( max <= 0.0 && shouldStartFromZero )
{
max = 0.0;
}
else
{
max = (double)( ((decimal)Math.Floor( max / inter ) + 1m ) * (decimal)inter );
}
}
return inter;
}
/// <summary>
/// Recalculates an intelligent interval from real interval.
/// </summary>
/// <param name="diff">Real interval.</param>
/// <returns>Inteligent interval.</returns>
internal double CalcInterval( double diff )
{
// If the interval is zero return error
if( diff == 0.0 )
{
throw (new ArgumentOutOfRangeException("diff", SR.ExceptionAxisScaleIntervalIsZero));
}
// If the real interval is > 1.0
double step = -1;
double temp = diff;
while( temp > 1.0 )
{
step ++;
temp = temp / 10.0;
if( step > 1000 )
{
throw (new InvalidOperationException(SR.ExceptionAxisScaleMinimumMaximumInvalid));
}
}
// If the real interval is < 1.0
temp = diff;
if( temp < 1.0 )
{
step = 0;
}
while( temp < 1.0 )
{
step --;
temp = temp * 10.0;
if( step < -1000 )
{
throw (new InvalidOperationException(SR.ExceptionAxisScaleMinimumMaximumInvalid));
}
}
double power = (this.IsLogarithmic) ? this.logarithmBase : 10.0;
double tempDiff = diff / Math.Pow( power, step );
if( tempDiff < 3 )
tempDiff = 2;
else if( tempDiff < 7 )
tempDiff = 5;
else
tempDiff = 10;
// Make a correction of the real interval
return tempDiff * Math.Pow( power, step );
}
/// <summary>
/// Recalculates a intelligent interval from real interval
/// obtained from maximum and minimum values
/// </summary>
/// <param name="min">Minimum</param>
/// <param name="max">Maximum</param>
/// <returns>Auto Interval</returns>
private double CalcInterval( double min, double max )
{
// Approximated interval value
return CalcInterval( ( max - min ) / 5 );
}
/// <summary>
/// Recalculates a intelligent interval from real interval
/// obtained from maximum, minimum and date type if
/// the values is date-time value.
/// </summary>
/// <param name="min">Minimum value.</param>
/// <param name="max">Maximum value.</param>
/// <param name="date">True if date.</param>
/// <param name="type">Date time interval type.</param>
/// <param name="valuesType">AxisName of date-time values.</param>
/// <returns>Auto Interval.</returns>
internal double CalcInterval(
double min,
double max,
bool date,
out DateTimeIntervalType type,
ChartValueType valuesType)
{
// AxisName is date time
if( date )
{
DateTime dateTimeMin = DateTime.FromOADate( min );
DateTime dateTimeMax = DateTime.FromOADate( max );
TimeSpan timeSpan = dateTimeMax.Subtract( dateTimeMin );
// Minutes
double inter = timeSpan.TotalMinutes;
// For Range less than 60 seconds interval is 5 sec
if( inter <= 1.0 && valuesType != ChartValueType.Date)
{
// Milli Seconds
double mlSeconds = timeSpan.TotalMilliseconds;
if(mlSeconds <= 10)
{
type = DateTimeIntervalType.Milliseconds;
return 1;
}
if(mlSeconds <= 50)
{
type = DateTimeIntervalType.Milliseconds;
return 4;
}
if(mlSeconds <= 200)
{
type = DateTimeIntervalType.Milliseconds;
return 20;
}
if(mlSeconds <= 500)
{
type = DateTimeIntervalType.Milliseconds;
return 50;
}
// Seconds
double seconds = timeSpan.TotalSeconds;
if(seconds <= 7)
{
type = DateTimeIntervalType.Seconds;
return 1;
}
else if(seconds <= 15)
{
type = DateTimeIntervalType.Seconds;
return 2;
}
else if(seconds <= 30)
{
type = DateTimeIntervalType.Seconds;
return 5;
}
else if(seconds <= 60)
{
type = DateTimeIntervalType.Seconds;
return 10;
}
}// For Range less than 120 seconds interval is 10 sec
else if( inter <= 2.0 && valuesType != ChartValueType.Date)
{
type = DateTimeIntervalType.Seconds;
return 20;
}// For Range less than 180 seconds interval is 30 sec
else if( inter <= 3.0 && valuesType != ChartValueType.Date)
{
type = DateTimeIntervalType.Seconds;
return 30;
}
// For Range less than 10 minutes interval is 1 min
else if( inter <= 10 && valuesType != ChartValueType.Date)
{
type = DateTimeIntervalType.Minutes;
return 1;
}
// For Range less than 20 minutes interval is 1 min
else if( inter <= 20 && valuesType != ChartValueType.Date)
{
type = DateTimeIntervalType.Minutes;
return 2;
}// For Range less than 60 minutes interval is 5 min
else if( inter <= 60 && valuesType != ChartValueType.Date)
{
type = DateTimeIntervalType.Minutes;
return 5;
}// For Range less than 120 minutes interval is 10 min
else if( inter <= 120 && valuesType != ChartValueType.Date)
{
type = DateTimeIntervalType.Minutes;
return 10;
}// For Range less than 180 minutes interval is 30 min
else if( inter <= 180 && valuesType != ChartValueType.Date)
{
type = DateTimeIntervalType.Minutes;
return 30;
}
// For Range less than 12 hours interval is 1 hour
else if( inter <= 60*12 && valuesType != ChartValueType.Date)
{
type = DateTimeIntervalType.Hours;
return 1;
}
// For Range less than 24 hours interval is 4 hour
else if( inter <= 60*24 && valuesType != ChartValueType.Date)
{
type = DateTimeIntervalType.Hours;
return 4;
}
// For Range less than 2 days interval is 6 hour
else if( inter <= 60*24*2 && valuesType != ChartValueType.Date)
{
type = DateTimeIntervalType.Hours;
return 6;
}
// For Range less than 3 days interval is 12 hour
else if( inter <= 60*24*3 && valuesType != ChartValueType.Date)
{
type = DateTimeIntervalType.Hours;
return 12;
}
// For Range less than 10 days interval is 1 day
else if( inter <= 60*24*10 )
{
type = DateTimeIntervalType.Days;
return 1;
}
// For Range less than 20 days interval is 2 day
else if( inter <= 60*24*20 )
{
type = DateTimeIntervalType.Days;
return 2;
}
// For Range less than 30 days interval is 3 day
else if( inter <= 60*24*30 )
{
type = DateTimeIntervalType.Days;
return 3;
}
// For Range less than 2 months interval is 1 week
else if( inter <= 60*24*30.5*2 )
{
type = DateTimeIntervalType.Weeks;
return 1;
}
// For Range less than 5 months interval is 2weeks
else if( inter <= 60*24*30.5*5 )
{
type = DateTimeIntervalType.Weeks;
return 2;
}
// For Range less than 12 months interval is 1 month
else if( inter <= 60*24*30.5*12 )
{
type = DateTimeIntervalType.Months;
return 1;
}
// For Range less than 24 months interval is 3 month
else if( inter <= 60*24*30.5*24 )
{
type = DateTimeIntervalType.Months;
return 3;
}
// For Range less than 48 months interval is 6 months
else if( inter <= 60*24*30.5*48 )
{
type = DateTimeIntervalType.Months;
return 6;
}
// For Range more than 48 months interval is year
else if( inter >= 60*24*30.5*48 )
{
type = DateTimeIntervalType.Years;
return CalcYearInterval( inter / 60 / 24 / 365 );
}
}
// Else numbers
type = DateTimeIntervalType.Number;
return CalcInterval( min, max );
}
/// <summary>
/// Recalculates a intelligent interval for years
/// </summary>
/// <param name="years">Number of years</param>
/// <returns>Interval in years</returns>
private double CalcYearInterval( double years )
{
// If the interval is zero return error
if( years <= 1.0 )
{
throw (new ArgumentOutOfRangeException("years", SR.ExceptionAxisScaleIntervalIsLessThen1Year));
}
if( years < 5 )
return 1;
else if( years < 10 )
return 2;
// Make a correction of the interval
return Math.Floor( years / 5 );
}
/// <summary>
/// This method returns the number of units
/// between min and max.
/// </summary>
/// <param name="min">Minimum.</param>
/// <param name="max">Maximum.</param>
/// <param name="type">Date type.</param>
/// <returns>Number of units.</returns>
private int GetNumOfUnits( double min, double max, DateTimeIntervalType type )
{
double current = ChartHelper.GetIntervalSize(min, 1, type);
return (int)Math.Round((max - min) / current);
}
/// <summary>
/// This method checks if value type is date-time.
/// </summary>
/// <returns>Date-time type or Auto.</returns>
internal ChartValueType GetDateTimeType()
{
List<string> list = null;
ChartValueType dateType = ChartValueType.Auto;
// Check if Value type is date from first series in the axis
if( axisType == AxisName.X )
{
// Check X axes type
list = ChartArea.GetXAxesSeries( AxisType.Primary, ((Axis)this).SubAxisName );
if( list.Count == 0 )
{
return ChartValueType.Auto;
}
if( Common.DataManager.Series[list[0]].IsXValueDateTime() )
{
dateType = Common.DataManager.Series[list[0]].XValueType;
}
}
else if( axisType == AxisName.X2 )
{
// Check X2 axes type
list = ChartArea.GetXAxesSeries( AxisType.Secondary, ((Axis)this).SubAxisName );
if( list.Count == 0 )
{
return ChartValueType.Auto;
}
if( Common.DataManager.Series[list[0]].IsXValueDateTime() )
{
dateType = Common.DataManager.Series[list[0]].XValueType;
}
}
else if( axisType == AxisName.Y )
{
// Check Y axes type
list = ChartArea.GetYAxesSeries( AxisType.Primary, ((Axis)this).SubAxisName );
if( list.Count == 0 )
{
return ChartValueType.Auto;
}
if( Common.DataManager.Series[list[0]].IsYValueDateTime() )
{
dateType = Common.DataManager.Series[list[0]].YValueType;
}
}
else if( axisType == AxisName.Y2 )
{
// Check Y2 axes type
list = ChartArea.GetYAxesSeries( AxisType.Secondary, ((Axis)this).SubAxisName );
if( list.Count == 0 )
{
return ChartValueType.Auto;
}
if( Common.DataManager.Series[list[0]].IsYValueDateTime() )
{
dateType = Common.DataManager.Series[list[0]].YValueType;
}
}
return dateType;
}
/// <summary>
/// This method removes "Auto", "min", "max" from crossing
/// value and creates a double value.
/// </summary>
/// <returns>Crossing value</returns>
private double GetCrossing()
{
if( Double.IsNaN(crossing) )
{
if( Common.ChartTypeRegistry.GetChartType( (string)ChartArea.ChartTypes[0] ).ZeroCrossing )
{
if( ViewMinimum > 0.0 )
{
return ViewMinimum;
}
else if( ViewMaximum < 0.0 )
{
return ViewMaximum;
}
else
{
return 0.0;
}
}
else
{
return ViewMinimum;
}
}
else if( crossing == Double.MaxValue )
{
return ViewMaximum;
}
else if( crossing == Double.MinValue )
{
return ViewMinimum;
}
return crossing;
}
/// <summary>
/// Set auto minimum number. The minimum number
/// which was sent to this function will be used to
/// estimate a rounded minimum.
/// </summary>
/// <param name="min"> This value is a recommendation for the minimum value. </param>
internal void SetAutoMinimum(double min)
{
// Set the minimum
if( _autoMinimum )
{
minimum = min;
}
}
/// <summary>
/// Set auto maximum number. The maximum number
/// which was sent to this function will be used to
/// estimate a rounded maximum.
/// </summary>
/// <param name="max">This value is a recommendation for the maximum value.</param>
internal void SetAutoMaximum(double max)
{
// Set the maximum
if( _autoMaximum )
{
maximum = max;
}
}
/// <summary>
/// Find opposite axis of this axis. What is opposite
/// axis depend on first series in chart area and primary
/// and secondary X and Y axes for the first series.
/// </summary>
/// <returns>Opposite axis</returns>
internal Axis GetOppositeAxis()
{
// Oppoiste axis found
if (oppositeAxis != null)
{
return oppositeAxis;
}
List<string> list;
switch( axisType )
{
// X Axis
case AxisName.X:
list = ChartArea.GetXAxesSeries( AxisType.Primary, ((Axis)this).SubAxisName );
// There aren't data series
if( list.Count == 0 )
oppositeAxis = ChartArea.AxisY;
// Take opposite axis from the first series from chart area
else if( Common.DataManager.Series[list[0]].YAxisType == AxisType.Primary )
oppositeAxis = ChartArea.AxisY.GetSubAxis(Common.DataManager.Series[list[0]].YSubAxisName);
else
oppositeAxis = ChartArea.AxisY2.GetSubAxis(Common.DataManager.Series[list[0]].YSubAxisName);
break;
// X2 Axis
case AxisName.X2:
list = ChartArea.GetXAxesSeries( AxisType.Secondary, ((Axis)this).SubAxisName );
// There aren't data series
if( list.Count == 0 )
oppositeAxis = ChartArea.AxisY2;
// Take opposite axis from the first series from chart area
else if( Common.DataManager.Series[list[0]].YAxisType == AxisType.Primary)
oppositeAxis = ChartArea.AxisY.GetSubAxis(Common.DataManager.Series[list[0]].YSubAxisName);
else
oppositeAxis = ChartArea.AxisY2.GetSubAxis(Common.DataManager.Series[list[0]].YSubAxisName);
break;
// Y Axis
case AxisName.Y:
list = ChartArea.GetYAxesSeries( AxisType.Primary, ((Axis)this).SubAxisName );
// There aren't data series
if( list.Count == 0 )
oppositeAxis = ChartArea.AxisX;
// Take opposite axis from the first series from chart area
else if( Common.DataManager.Series[list[0]].XAxisType == AxisType.Primary )
oppositeAxis = ChartArea.AxisX.GetSubAxis(Common.DataManager.Series[list[0]].XSubAxisName);
else
oppositeAxis = ChartArea.AxisX2.GetSubAxis(Common.DataManager.Series[list[0]].XSubAxisName);
break;
// Y2 Axis
case AxisName.Y2:
list = ChartArea.GetYAxesSeries( AxisType.Secondary, ((Axis)this).SubAxisName );
// There aren't data series
if( list.Count == 0 )
oppositeAxis = ChartArea.AxisX2;
// Take opposite axis from the first series from chart area
else if( Common.DataManager.Series[list[0]].XAxisType == AxisType.Primary )
oppositeAxis = ChartArea.AxisX.GetSubAxis(Common.DataManager.Series[list[0]].XSubAxisName);
else
oppositeAxis = ChartArea.AxisX2.GetSubAxis(Common.DataManager.Series[list[0]].XSubAxisName);
break;
}
return oppositeAxis;
}
/// <summary>
/// This function converts Values from Axes to
/// linear relative positions.
/// </summary>
/// <param name="axisValue">Value from axis.</param>
/// <returns>Relative position.</returns>
internal double GetLinearPosition( double axisValue )
{
bool circularArea = (ChartArea == null || !ChartArea.chartAreaIsCurcular) ?
false : true;
// Check if some value calculation is optimized
if(!this.optimizedGetPosition)
{
paintViewMax = ViewMaximum;
paintViewMin = ViewMinimum;
paintRange = paintViewMax - paintViewMin;
paintAreaPosition = PlotAreaPosition.ToRectangleF();
// Update position for circular chart area
if(circularArea)
{
paintAreaPosition.Width /= 2.0f;
paintAreaPosition.Height /= 2.0f;
}
paintAreaPositionBottom = paintAreaPosition.Y + paintAreaPosition.Height;
paintAreaPositionRight = paintAreaPosition.X + paintAreaPosition.Width;
// The Chart area size
if( AxisPosition == AxisPosition.Top || AxisPosition == AxisPosition.Bottom )
paintChartAreaSize = paintAreaPosition.Width;
else
paintChartAreaSize = paintAreaPosition.Height;
valueMultiplier = 0.0;
if( paintRange != 0 )
{
valueMultiplier = paintChartAreaSize / paintRange;
}
}
// The Chart area pixel size
double position = valueMultiplier * ( axisValue - paintViewMin);
// Check if axis scale segments are enabled
if(this.scaleSegmentsUsed)
{
AxisScaleSegment scaleSegment = this.ScaleSegments.FindScaleSegmentForAxisValue(axisValue);
if(scaleSegment != null)
{
double segmentSize = 0.0;
double segmentPosition = 0.0;
scaleSegment.GetScalePositionAndSize(paintChartAreaSize, out segmentPosition, out segmentSize);
// Make sure value do not exceed max possible
if(!this.ScaleSegments.AllowOutOfScaleValues)
{
if(axisValue > scaleSegment.ScaleMaximum)
{
axisValue = scaleSegment.ScaleMaximum;
}
else if(axisValue < scaleSegment.ScaleMinimum)
{
axisValue = scaleSegment.ScaleMinimum;
}
}
double segmentScaleRange = scaleSegment.ScaleMaximum - scaleSegment.ScaleMinimum;
position = (segmentSize / segmentScaleRange) * (axisValue - scaleSegment.ScaleMinimum);
position += segmentPosition;
}
}
// Window position
// (Do Not use .Right or .Bottom methods below) - rounding issue!
if( isReversed )
{
if( AxisPosition == AxisPosition.Top || AxisPosition == AxisPosition.Bottom )
position = paintAreaPositionRight - position;
else
position = paintAreaPosition.Y + position;
}
else
{
if( AxisPosition == AxisPosition.Top || AxisPosition == AxisPosition.Bottom )
position = paintAreaPosition.X + position;
else
position = paintAreaPositionBottom - position;
}
return position;
}
#endregion
#region Axis estimate axis methods
/// <summary>
/// This function recalculates minimum maximum and interval.
/// The function uses current values for minimum and maximum to
/// find rounding values. If the value from the data source for the
/// maximum value is 376.5 this function will return 380. This function
/// also set interval type for date
/// </summary>
internal void EstimateAxis()
{
double axisInterval;
// Check if veiw size specified without scaleView position
if(!Double.IsNaN(this.ScaleView.Size))
{
// If size set only use axis minimum for scaleView position
if(Double.IsNaN(this.ScaleView.Position))
{
this.ScaleView.Position = this.Minimum;
}
}
// Zooming Mode
if( !Double.IsNaN(_scaleView.Position) && !Double.IsNaN(_scaleView.Size) )
{
double viewMaximum = ViewMaximum;
double viewMinimum = ViewMinimum;
// IsLogarithmic axes
if (this._isLogarithmic)
{
viewMaximum = Math.Pow( this.logarithmBase, viewMaximum );
viewMinimum = Math.Pow( this.logarithmBase, viewMinimum );
}
else
{
// Add rounding and gap for maximum and minimum
EstimateAxis( ref this.minimum, ref this.maximum, _autoMaximum, _autoMinimum );
}
// Find Interval for Zoom
axisInterval = EstimateAxis( ref viewMinimum, ref viewMaximum, true, true );
}
else // No Zooming mode
{
// Estimate axis shoud be always called for non logarithmic axis
axisInterval = EstimateAxis( ref this.minimum, ref this.maximum, _autoMaximum, _autoMinimum );
}
// Set intervals for grids, tick marks and labels
if( axisInterval <= 0.0 )
{
throw (new InvalidOperationException(SR.ExceptionAxisScaleAutoIntervalInvalid));
}
else
{
// This code checks if all series in the chart area have “integer type”
// for specified axes, which means int, uint, long and ulong and rounds interval.
#if SUBAXES
if( ChartArea.SeriesIntegerType( this.axisType, ((Axis)this).SubAxisName ) )
#else // SUBAXES
if ( ChartArea.SeriesIntegerType( this.axisType, string.Empty ))
#endif // SUBAXES
{
axisInterval = Math.Round( axisInterval );
if( axisInterval == 0.0 )
{
axisInterval = 1.0;
}
// Round Minimum to floor value if type is integer
minimum = Math.Floor( minimum );
}
SetInterval = axisInterval;
}
}
/// <summary>
/// This function recalculates minimum maximum and interval.
/// The function uses current values for minimum and maximum to
/// find rounding values. If the value from the data source for the
/// maximum value is 376.5 this function will return 380. This function
/// also set interval type for date
/// </summary>
/// <param name="minimumValue">Minimum</param>
/// <param name="maximumValue">Maximum</param>
/// <param name="autoMaximum">Maximum value is Auto</param>
/// <param name="autoMinimum">Minimum value is Auto</param>
/// <returns>Interval</returns>
internal double EstimateAxis( ref double minimumValue, ref double maximumValue, bool autoMaximum, bool autoMinimum )
{
double axisInterval;
// The axis minimum value is greater than the maximum value.
if( maximumValue < minimumValue )
{
if(!this.Common.ChartPicture.SuppressExceptions)
{
throw (new InvalidOperationException(SR.ExceptionAxisScaleMinimumValueIsGreaterThenMaximumDataPoint));
}
else
{
// Max axis scale should be always bigger
double tempValue = maximumValue;
maximumValue = minimumValue;
minimumValue = tempValue;
}
}
// Take Value type
ChartValueType dateType = GetDateTimeType();
// Axis type is logarithmic
if (_isLogarithmic)
{
axisInterval = EstimateLogarithmicAxis( ref minimumValue, ref maximumValue, crossing, autoMaximum, autoMinimum );
}
// Axis type is date
else if( dateType != ChartValueType.Auto)
{
axisInterval = EstimateDateAxis( ref minimumValue, ref maximumValue, autoMaximum, autoMinimum, dateType );
}
// Axis type is number
else
{
axisInterval = EstimateNumberAxis( ref minimumValue, ref maximumValue, this.IsStartedFromZero, this.prefferedNumberofIntervals, autoMaximum, autoMinimum );
}
// Set intervals for grids, tick marks and labels
if( axisInterval <= 0.0 )
{
throw (new InvalidOperationException(SR.ExceptionAxisScaleAutoIntervalInvalid));
}
else
{
// Set interval for Grid lines Tick Marks and labels
SetInterval = axisInterval;
}
return axisInterval;
}
/// <summary>
/// This function recalculates minimum maximum and interval for
/// logarithmic axis. The function uses current values for minimum and
/// maximum to find new rounding values.
/// </summary>
/// <param name="minimumValue">Current Minimum value</param>
/// <param name="maximumValue">Current Maximum value</param>
/// <param name="crossingValue">Crossing value</param>
/// <param name="autoMaximum">Maximum value is Auto</param>
/// <param name="autoMinimum">Minimum value is Auto</param>
/// <returns>Interval</returns>
private double EstimateLogarithmicAxis( ref double minimumValue, ref double maximumValue, double crossingValue, bool autoMaximum, bool autoMinimum )
{
double axisInterval;
if( !logarithmicConvertedToLinear )
{
// Remember values. Do not use POW function because of rounding.
this.logarithmicMinimum = this.minimum;
this.logarithmicMaximum = this.maximum;
}
// For log axis margin always turn on.
margin = 100;
// Supress zero and negative values with logarithmic axis exceptions
if(this.Common != null && this.Common.Chart != null && this.Common.Chart.chartPicture.SuppressExceptions)
{
if (minimumValue <= 0.0 )
{
minimumValue = 1.0;
}
if (maximumValue <= 0.0 )
{
maximumValue = 1.0;
}
if (crossingValue <= 0.0 && crossingValue != Double.MinValue )
{
crossingValue = 1.0;
}
}
// The logarithmic axes can not show negative values.
if( minimumValue <= 0.0 || maximumValue <= 0.0 || crossingValue <= 0.0 )
{
if (minimumValue <= 0.0 )
throw (new ArgumentOutOfRangeException("minimumValue", SR.ExceptionAxisScaleLogarithmicNegativeValues));
if (maximumValue <= 0.0 )
throw (new ArgumentOutOfRangeException("maximumValue", SR.ExceptionAxisScaleLogarithmicNegativeValues));
}
// Change crossing to linear scale
crossingValue = Math.Log( crossingValue, this.logarithmBase );
// Change minimum and maximum to linear scale
minimumValue = Math.Log( minimumValue, this.logarithmBase );
maximumValue = Math.Log( maximumValue, this.logarithmBase );
logarithmicConvertedToLinear = true;
// Find interval - Make approximately 5 grid lines and labels.
double diff = ( maximumValue - minimumValue ) / 5;
// Make good interval for logarithmic scale
axisInterval = Math.Floor( diff );
if( axisInterval == 0 ) axisInterval = 1;
if( autoMinimum && autoMaximum )
{
// The maximum and minimum rounding with interval
RoundedValues( axisInterval, this.IsStartedFromZero, autoMaximum, autoMinimum, ref minimumValue, ref maximumValue );
}
// Do not allow min/max values more than a hundred
if(ChartArea.hundredPercent)
{
if(autoMinimum)
{
if(minimumValue < 0)
minimumValue = 0;
}
if(autoMaximum)
{
if(maximumValue > 2)
maximumValue = 2;
}
}
// Set interval for Grid lines Tick Marks and labels
return axisInterval;
}
/// <summary>
/// This function recalculates minimum maximum and interval for
/// Date axis. The function uses current values for minimum and
/// maximum to find new rounding values.
/// </summary>
/// <param name="minimumValue">Current Minimum value</param>
/// <param name="maximumValue">Current Maximum value</param>
/// <param name="autoMaximum">Maximum value is Auto</param>
/// <param name="autoMinimum">Minimum value is Auto</param>
/// <param name="valuesType">AxisName of date-time values.</param>
/// <returns>Interval</returns>
private double EstimateDateAxis(
ref double minimumValue,
ref double maximumValue,
bool autoMaximum,
bool autoMinimum,
ChartValueType valuesType)
{
double axisInterval;
double min = minimumValue;
double max = maximumValue;
// Find interval for this date type
axisInterval = CalcInterval(min, max, true, out _internalIntervalType, valuesType);
// For 3D Charts interval could be changed. After rotation
// projection of axis could be very small.
if( !double.IsNaN( this.interval3DCorrection ) &&
ChartArea.Area3DStyle.Enable3D &&
!ChartArea.chartAreaIsCurcular)
{
axisInterval = Math.Floor( axisInterval / this.interval3DCorrection );
this.interval3DCorrection = double.NaN;
}
// Find number of units between minimum and maximum
int numberOfUnits = GetNumOfUnits( min, max, _internalIntervalType );
// Make a gap between max point and axis for Y axes
if( axisType == AxisName.Y || axisType == AxisName.Y2 )
{
if (autoMinimum && minimumValue > ChartHelper.GetIntervalSize(min, axisInterval, _internalIntervalType))
{
// Add gap to the minimum value from the series
// equal half of the interval
minimumValue += ChartHelper.GetIntervalSize(
min,
- (axisInterval / 2.0) * margin / 100,
_internalIntervalType,
null,
0.0,
DateTimeIntervalType.Number,
false,
false);
// Align minimum sacale value on the interval
minimumValue = ChartHelper.AlignIntervalStart(
minimumValue,
axisInterval * margin / 100,
_internalIntervalType);
}
// Increase maximum if not zero. Make a space between chart type
// and the end of the chart area.
if( autoMaximum && max > 0 && margin != 0.0 )
{
maximumValue = minimumValue + ChartHelper.GetIntervalSize(
minimumValue,
(double)((Math.Floor(numberOfUnits / axisInterval / margin * 100)+2) * axisInterval * margin / 100),
_internalIntervalType);
}
}
InternalIntervalType = _internalIntervalType;
// Set interval for Grid lines Tick Marks and labels
return axisInterval;
}
/// <summary>
/// This function recalculates minimum maximum and interval for
/// number type axis. The function uses current values for minimum and
/// maximum to find new rounding values.
/// </summary>
/// <param name="minimumValue">Current Minimum value</param>
/// <param name="maximumValue">Current Maximum value</param>
/// <param name="shouldStartFromZero">Should start from zero flag.</param>
/// <param name="preferredNumberOfIntervals">Preferred number of intervals. Can be set to zero for dynamic mode.</param>
/// <param name="autoMaximum">Maximum value is Auto</param>
/// <param name="autoMinimum">Minimum value is Auto</param>
/// <returns>Interval</returns>
internal double EstimateNumberAxis(
ref double minimumValue,
ref double maximumValue,
bool shouldStartFromZero,
int preferredNumberOfIntervals,
bool autoMaximum,
bool autoMinimum )
{
double axisInterval;
double min = minimumValue;
double max = maximumValue;
double diff;
if( !roundedXValues && ( axisType == AxisName.X || axisType == AxisName.X2 ) )
{
diff = ChartArea.GetPointsInterval( false, 10 );
if( diff == 0 || ( max - min ) / diff > 20 )
{
diff = ( max - min ) / preferredNumberOfIntervals;
}
}
else
{
diff = ( max - min ) / preferredNumberOfIntervals;
}
// For 3D Charts interval could be changed. After rotation
// projection of axis could be very small.
if( !double.IsNaN( this.interval3DCorrection ) &&
ChartArea.Area3DStyle.Enable3D &&
!ChartArea.chartAreaIsCurcular)
{
diff = diff / this.interval3DCorrection;
// Do not change minimum and maximum with 3D correction.
if( max - min < diff )
{
diff = max - min;
}
this.interval3DCorrection = double.NaN;
if( diff != 0.0 )
{
diff = CalcInterval( diff );
}
}
if( autoMaximum || autoMinimum )
{
if( diff == 0 )
{
// Can not find interval. Minimum and maximum are same
max = min + 1;
diff = 0.2;
axisInterval = 0.2;
}
else
{
axisInterval = CalcInterval( diff );
}
}
else
{
axisInterval = diff;
}
// Case when minimum or maximum is set and interval is > maximum.
// Reasons overflow exception.
if( ((Axis)this).interval != 0 && ((Axis)this).interval > axisInterval && minimumValue + ((Axis)this).interval > maximumValue )
{
axisInterval = ((Axis)this).interval;
if( autoMaximum )
{
maximumValue = minimumValue + axisInterval;
}
if( autoMinimum )
{
minimumValue = maximumValue - axisInterval;
}
}
// The maximum and minimum rounding for Y Axes
if( axisType == AxisName.Y || axisType == AxisName.Y2 || ( roundedXValues && ( axisType == AxisName.X || axisType == AxisName.X2 )))
{
// Start from zero for the 100% chart types
bool minIsZero = false;
bool maxIsZero = false;
if(ChartArea.hundredPercent)
{
minIsZero = (minimumValue == 0.0);
maxIsZero = (maximumValue == 0.0);
}
// Round min/max values
RoundedValues( axisInterval, shouldStartFromZero, autoMaximum, autoMinimum, ref minimumValue, ref maximumValue );
// Do not allow min/max values more than a hundred
if(ChartArea.hundredPercent)
{
if(autoMinimum)
{
if(minimumValue < -100)
minimumValue = -100;
if(minIsZero)
minimumValue = 0;
}
if(autoMaximum)
{
if(maximumValue > 100)
maximumValue = 100;
if(maxIsZero)
maximumValue = 0;
}
}
}
// Set interval for Grid lines Tick Marks and labels
return axisInterval;
}
#endregion
}
}
|