//------------------------------------------------------------- // // Copyright © Microsoft Corporation. All Rights Reserved. // //------------------------------------------------------------- // @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 /// /// An enumeration of the mode of automatically calculating intervals. /// public enum IntervalAutoMode { /// /// Fixed number of intervals always created on the axis. /// FixedCount, /// /// Number of axis intervals depends on the axis length. /// VariableCount } /// /// An enumeration of axis position. /// internal enum AxisPosition { /// /// Left position /// Left, /// /// Right position /// Right, /// /// Top position /// Top, /// /// Bottom position /// Bottom } /// /// An enumeration of axis arrow styles. /// public enum AxisArrowStyle { /// /// No arrow /// None, /// /// Triangle type /// Triangle, /// /// Sharp triangle type /// SharpTriangle, /// /// Lines type /// Lines } #endregion /// /// 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. /// 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; /// /// Axis position: Left, Right, Top Bottom /// private AxisPosition _axisPosition = AxisPosition.Left; /// /// Opposite Axis for this Axis. Necessary for Crossing. /// 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 _intervalsStore = new Stack(); #endregion #region Axis scale properties /// /// Axis position /// [ 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(); } } /// /// Gets or sets a flag which indicates whether the number of intervals /// on the axis is fixed or varies with the axis size. /// [ SRCategory("CategoryAttributeInterval"), DefaultValue(IntervalAutoMode.FixedCount), SRDescription("DescriptionAttributeIntervalAutoMode"), ] public IntervalAutoMode IntervalAutoMode { get { return this._intervalAutoMode; } set { this._intervalAutoMode = value; this.Invalidate(); } } /// /// 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. /// [ 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(); } } /// /// 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. /// [ 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(); } } /// /// 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. /// [ 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(); } } /// /// Date and time interval type. /// [ 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(); } } /// /// Sets auto interval values to grids, tick marks /// and labels /// 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(); } } /// /// Sets auto interval values to grids, tick marks /// and labels /// 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(); } /// /// Gets or sets the maximum axis value. /// [ 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(); } } /// /// Gets or sets the minimum axis value /// [ 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(); } } /// /// Gets or sets the point where axis is crossed by another axis. /// [ 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(); } } /// /// Enables or disables the axis. /// [ 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(); } } /// /// Gets or sets a flag which indicates whether the axis is logarithmic. /// Zeros or negative data values are not allowed on logarithmic charts. /// [ 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(); } } /// /// Base of the logarithm used in logarithmic scale. /// By default, this value is 10. /// [ 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; /// /// Gets or sets the style of scale breaks. /// [ 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; /// /// Axis scale segment collection. /// [ 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 /// /// Gets or sets the scale view settings of the axis. /// [ 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 /// /// Gets or sets the scroll bar settings of the axis. /// [ 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 /// /// Gets axis data scaleView minimum position. /// /// Axis data scaleView minimum position. internal double ViewMinimum { get { return _scaleView.ViewMinimum; } } /// /// Gets axis data scaleView minimum position. /// /// Axis data scaleView minimum position. internal double ViewMaximum { get { return _scaleView.ViewMaximum; } } /// /// Gets automatic maximum value (from data point values). /// internal bool AutoMaximum { get { return _autoMaximum; } } /// /// Gets automatic minimum value (from data point values). /// internal bool AutoMinimum { get { return _autoMinimum; } } #endregion #region Axis position converters methos /// /// 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. /// /// Value from axis. /// Relative position (0-100%). 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); } /// /// 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. /// /// Axis value. /// Relative position (0-100%). public double ValueToPosition( double axisValue ) { return GetPosition( axisValue ); } /// /// 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. /// /// Value from axis. /// Pixel position. 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; } /// /// 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. /// /// Relative position (0-100%). /// Axis value. public double PositionToValue( double position ) { return PositionToValue(position, true); } /// /// 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. /// /// Relative position (0-100%). /// Indicates if input value range should be checked. /// Axis value. 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; } /// /// 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. /// /// Pixel position. /// Axis value. 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 /// /// Sets axis position. Axis position depends /// on crossing and reversed value. /// 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; } } /// /// Sets temporary offset value. /// 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; } } /// /// Resets temporary offset value. /// 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; } } /// /// 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. /// /// Interval /// True if minimum scale value should start from zero. /// Maximum is auto /// Minimum is auto /// Minimum value /// Maximum value /// Interval 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; } /// /// Recalculates an intelligent interval from real interval. /// /// Real interval. /// Inteligent interval. 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 ); } /// /// Recalculates a intelligent interval from real interval /// obtained from maximum and minimum values /// /// Minimum /// Maximum /// Auto Interval private double CalcInterval( double min, double max ) { // Approximated interval value return CalcInterval( ( max - min ) / 5 ); } /// /// Recalculates a intelligent interval from real interval /// obtained from maximum, minimum and date type if /// the values is date-time value. /// /// Minimum value. /// Maximum value. /// True if date. /// Date time interval type. /// AxisName of date-time values. /// Auto Interval. 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 ); } /// /// Recalculates a intelligent interval for years /// /// Number of years /// Interval in years 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 ); } /// /// This method returns the number of units /// between min and max. /// /// Minimum. /// Maximum. /// Date type. /// Number of units. private int GetNumOfUnits( double min, double max, DateTimeIntervalType type ) { double current = ChartHelper.GetIntervalSize(min, 1, type); return (int)Math.Round((max - min) / current); } /// /// This method checks if value type is date-time. /// /// Date-time type or Auto. internal ChartValueType GetDateTimeType() { List 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; } /// /// This method removes "Auto", "min", "max" from crossing /// value and creates a double value. /// /// Crossing value 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; } /// /// Set auto minimum number. The minimum number /// which was sent to this function will be used to /// estimate a rounded minimum. /// /// This value is a recommendation for the minimum value. internal void SetAutoMinimum(double min) { // Set the minimum if( _autoMinimum ) { minimum = min; } } /// /// Set auto maximum number. The maximum number /// which was sent to this function will be used to /// estimate a rounded maximum. /// /// This value is a recommendation for the maximum value. internal void SetAutoMaximum(double max) { // Set the maximum if( _autoMaximum ) { maximum = max; } } /// /// 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. /// /// Opposite axis internal Axis GetOppositeAxis() { // Oppoiste axis found if (oppositeAxis != null) { return oppositeAxis; } List 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; } /// /// This function converts Values from Axes to /// linear relative positions. /// /// Value from axis. /// Relative position. 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 /// /// 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 /// 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; } } /// /// 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 /// /// Minimum /// Maximum /// Maximum value is Auto /// Minimum value is Auto /// Interval 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; } /// /// This function recalculates minimum maximum and interval for /// logarithmic axis. The function uses current values for minimum and /// maximum to find new rounding values. /// /// Current Minimum value /// Current Maximum value /// Crossing value /// Maximum value is Auto /// Minimum value is Auto /// Interval 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; } /// /// This function recalculates minimum maximum and interval for /// Date axis. The function uses current values for minimum and /// maximum to find new rounding values. /// /// Current Minimum value /// Current Maximum value /// Maximum value is Auto /// Minimum value is Auto /// AxisName of date-time values. /// Interval 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; } /// /// 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. /// /// Current Minimum value /// Current Maximum value /// Should start from zero flag. /// Preferred number of intervals. Can be set to zero for dynamic mode. /// Maximum value is Auto /// Minimum value is Auto /// Interval 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 } }