//------------------------------------------------------------- // // Copyright © Microsoft Corporation. All Rights Reserved. // //------------------------------------------------------------- // @owner=alexgor, deliant //================================================================= // File: AxisScaleSegments.cs // // Namespace: System.Web.UI.WebControls[Windows.Forms].Charting // // Classes: AxisScaleSegment, AxisScaleSegmentCollection // // Purpose: // // Reviewed: // //=================================================================== #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.Globalization; #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 { /// /// AxisScaleSegment class represents a single segment of the axis with /// it's own scale and intervals. /// [ SRDescription("DescriptionAttributeAxisScaleSegment_AxisScaleSegment"), ] internal class AxisScaleSegment { #region Fields // Associated axis internal Axis axis = null; // Axis segment position in percent of the axis size private double _position = 0.0; // Axis segment size in percent of the axis size private double _size = 0.0; // Axis segment spacing in percent of the axis size private double _spacing = 0.0; // Axis segment scale minimum value private double _scaleMinimum = 0.0; // Axis segment scale maximum value private double _scaleMaximum = 0.0; // Axis segment interval offset. private double _intervalOffset = 0; // Axis segment interval. private double _interval = 0; // Axis segment interval units type. private DateTimeIntervalType _intervalType = DateTimeIntervalType.Auto; // Axis segment interval offset units type. private DateTimeIntervalType _intervalOffsetType = DateTimeIntervalType.Auto; // Object associated with the segment private object _tag = null; // Stack used to save/load axis settings private Stack _oldAxisSettings = new Stack(); #endregion // Fields #region Constructor /// /// Default object constructor. /// public AxisScaleSegment() { } #endregion // Constructor #region Properties /// /// Axis segment position in axis size percentage. /// [ SRCategory("CategoryAttributeMisc"), DefaultValue(0.0), SRDescription("DescriptionAttributeAxisScaleSegment_Position"), ] public double Position { get { return this._position; } set { if(value < 0.0 || value > 100.0) { throw (new ArgumentOutOfRangeException("value", SR.ExceptionAxisScaleSegmentsPositionInvalid)); } this._position = value; } } /// /// Axis segment size in axis size percentage. /// [ SRCategory("CategoryAttributeMisc"), DefaultValue(0.0), SRDescription("DescriptionAttributeAxisScaleSegment_Size"), ] public double Size { get { return this._size; } set { if(value < 0.0 || value > 100.0) { throw (new ArgumentOutOfRangeException("value", SR.ExceptionAxisScaleSegmentsSizeInvalid)); } this._size = value; } } /// /// Axis segment spacing in axis size percentage. /// [ SRCategory("CategoryAttributeMisc"), DefaultValue(0.0), SRDescription("DescriptionAttributeAxisScaleSegment_Spacing"), ] public double Spacing { get { return this._spacing; } set { if(value < 0.0 || value > 100.0) { throw (new ArgumentOutOfRangeException("value", SR.ExceptionAxisScaleSegmentsSpacingInvalid)); } this._spacing = value; } } /// /// Axis segment scale maximum value. /// [ SRCategory("CategoryAttributeMisc"), DefaultValue(0.0), SRDescription("DescriptionAttributeAxisScaleSegment_ScaleMaximum"), ] public double ScaleMaximum { get { return this._scaleMaximum; } set { this._scaleMaximum = value; } } /// /// Axis segment scale minimum value. /// [ SRCategory("CategoryAttributeMisc"), DefaultValue(0.0), SRDescription("DescriptionAttributeAxisScaleSegment_ScaleMinimum"), ] public double ScaleMinimum { get { return this._scaleMinimum; } set { this._scaleMinimum = value; } } /// /// Axis segment interval size. /// [ SRCategory("CategoryAttributeInterval"), DefaultValue(0.0), SRDescription("DescriptionAttributeAxisScaleSegment_Interval"), TypeConverter(typeof(AxisIntervalValueConverter)), ] public double Interval { get { return this._interval; } set { // Axis interval properties must be set if(double.IsNaN(value)) { this._interval = 0; } else { this._interval = value; } } } /// /// Axis segment interval offset. /// [ SRCategory("CategoryAttributeInterval"), DefaultValue(0.0), SRDescription("DescriptionAttributeAxisScaleSegment_IntervalOffset"), TypeConverter(typeof(AxisIntervalValueConverter)) ] public double IntervalOffset { get { return _intervalOffset; } } /// /// Axis segment interval type. /// [ SRCategory("CategoryAttributeInterval"), DefaultValue(DateTimeIntervalType.Auto), SRDescription("DescriptionAttributeAxisScaleSegment_IntervalType"), ] public DateTimeIntervalType IntervalType { get { return this._intervalType; } set { // Axis interval properties must be set if(value == DateTimeIntervalType.NotSet) { this._intervalType = DateTimeIntervalType.Auto; } else { _intervalType = value; } } } /// /// Axis segment interval offset type. /// [ SRCategory("CategoryAttributeInterval"), DefaultValue(DateTimeIntervalType.Auto), SRDescription("DescriptionAttributeAxisScaleSegment_IntervalOffsetType"), ] public DateTimeIntervalType IntervalOffsetType { get { return this._intervalOffsetType; } } /// /// Object associated with axis scale segment. /// [ SRCategory("CategoryAttributeMisc"), Browsable(false), DefaultValue(null), SRDescription("DescriptionAttributeAxisScaleSegment_Tag"), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), SerializationVisibility(SerializationVisibility.Hidden), ] public object Tag { get { return this._tag; } set { this._tag = value; } } #endregion // Properties #region Break Line Painting Methods /// /// Paints the axis break line. /// /// Chart graphics to use. /// Axis scale segment next to current. internal void PaintBreakLine(ChartGraphics graph, AxisScaleSegment nextSegment) { // Get break line position RectangleF breakPosition = this.GetBreakLinePosition(graph, nextSegment); // Get top line graphics path GraphicsPath breakLinePathTop = this.GetBreakLinePath(breakPosition, true); GraphicsPath breakLinePathBottom = null; // Clear break line space using chart color behind the area if(breakPosition.Width > 0f && breakPosition.Height > 0f) { // Get bottom line graphics path breakLinePathBottom = this.GetBreakLinePath(breakPosition, false); // Clear plotting area background using(GraphicsPath fillPath = new GraphicsPath()) { // Create fill path out of top and bottom break lines fillPath.AddPath(breakLinePathTop, true); fillPath.Reverse(); fillPath.AddPath(breakLinePathBottom, true); fillPath.CloseAllFigures(); // Use chart back color to fill the area using(Brush fillBrush = this.GetChartFillBrush(graph)) { graph.FillPath(fillBrush, fillPath); // Check if shadow exsits in chart area if( this.axis.ChartArea.ShadowOffset != 0 && !this.axis.ChartArea.ShadowColor.IsEmpty) { // Clear shadow RectangleF shadowPartRect = breakPosition; if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left ) { shadowPartRect.Y += this.axis.ChartArea.ShadowOffset; shadowPartRect.Height -= this.axis.ChartArea.ShadowOffset; shadowPartRect.X = shadowPartRect.Right - 1; shadowPartRect.Width = this.axis.ChartArea.ShadowOffset + 2; } else { shadowPartRect.X += this.axis.ChartArea.ShadowOffset; shadowPartRect.Width -= this.axis.ChartArea.ShadowOffset; shadowPartRect.Y = shadowPartRect.Bottom - 1; shadowPartRect.Height = this.axis.ChartArea.ShadowOffset + 2; } graph.FillRectangle(fillBrush, shadowPartRect); // Draw new shadow using(GraphicsPath shadowPath = new GraphicsPath()) { shadowPath.AddPath(breakLinePathTop, false); // Define maximum size float size = this.axis.ChartArea.ShadowOffset; if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left ) { size = Math.Min(size, breakPosition.Height); } else { size = Math.Min(size, breakPosition.Width); } // Define step to increase transperancy int transparencyStep = (int)(this.axis.ChartArea.ShadowColor.A / size); // Set clip region to achieve spacing of the shadow // Start with the plotting rectangle position RectangleF clipRegion = graph.GetAbsoluteRectangle(this.axis.PlotAreaPosition.ToRectangleF()); if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left ) { clipRegion.X += this.axis.ChartArea.ShadowOffset; clipRegion.Width += this.axis.ChartArea.ShadowOffset; } else { clipRegion.Y += this.axis.ChartArea.ShadowOffset; clipRegion.Height += this.axis.ChartArea.ShadowOffset; } graph.SetClip(graph.GetRelativeRectangle(clipRegion)); // Draw several lines to form shadow for(int index = 0; index < size; index ++) { using(Matrix newMatrix = new Matrix()) { // Shift top break line by 1 pixel if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left ) { newMatrix.Translate(0f, 1f); } else { newMatrix.Translate(1f, 0f); } shadowPath.Transform(newMatrix); } // Get line color Color color = Color.FromArgb( this.axis.ChartArea.ShadowColor.A - transparencyStep * index, this.axis.ChartArea.ShadowColor); using(Pen shadowPen = new Pen(color, 1)) { // Draw shadow graph.DrawPath(shadowPen, shadowPath); } } graph.ResetClip(); } } } } } // Draw Separator Line(s) if(this.axis.ScaleBreakStyle.BreakLineStyle != BreakLineStyle.None) { using(Pen pen = new Pen(this.axis.ScaleBreakStyle.LineColor, this.axis.ScaleBreakStyle.LineWidth)) { // Set line style pen.DashStyle = graph.GetPenStyle(this.axis.ScaleBreakStyle.LineDashStyle); // Draw break lines graph.DrawPath(pen, breakLinePathTop); if(breakPosition.Width > 0f && breakPosition.Height > 0f) { graph.DrawPath(pen, breakLinePathBottom); } } } // Dispose break line paths breakLinePathTop.Dispose(); breakLinePathTop = null; if(breakLinePathBottom != null) { breakLinePathBottom.Dispose(); breakLinePathBottom = null; } } /// /// Get fill brush used to fill axis break lines spacing. /// /// chart graphics. /// Fill brush. private Brush GetChartFillBrush(ChartGraphics graph) { Chart chart = this.axis.ChartArea.Common.Chart; Brush brush = null; if( chart.BackGradientStyle == GradientStyle.None ) { brush = new SolidBrush(chart.BackColor); } else { // If a gradient type is set create a brush with gradient brush = graph.GetGradientBrush(new RectangleF(0, 0, chart.chartPicture.Width - 1, chart.chartPicture.Height - 1), chart.BackColor, chart.BackSecondaryColor, chart.BackGradientStyle); } if( chart.BackHatchStyle != ChartHatchStyle.None ) { brush = graph.GetHatchBrush( chart.BackHatchStyle, chart.BackColor, chart.BackSecondaryColor ); } if( chart.BackImage.Length > 0 && chart.BackImageWrapMode != ChartImageWrapMode.Unscaled && chart.BackImageWrapMode != ChartImageWrapMode.Scaled) { brush = graph.GetTextureBrush(chart.BackImage, chart.BackImageTransparentColor, chart.BackImageWrapMode, chart.BackColor ); } return brush; } /// /// Gets a path of the break line for specified position. /// /// Break line position. /// Indicates if top or bottom break line path should be retrieved. /// Graphics path with break line path. private GraphicsPath GetBreakLinePath(RectangleF breakLinePosition, bool top) { GraphicsPath path = new GraphicsPath(); if(this.axis.ScaleBreakStyle.BreakLineStyle == BreakLineStyle.Wave) { PointF[] points = null; int pointNumber = 0; if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left ) { float startX = breakLinePosition.X; float endX = breakLinePosition.Right; float y = (top) ? breakLinePosition.Y : breakLinePosition.Bottom; pointNumber = ((int)(endX - startX) / 40) * 2 ; if(pointNumber < 2) { pointNumber = 2; } float step = (endX - startX) / pointNumber; points = new PointF[pointNumber + 1]; for(int pointIndex = 1; pointIndex < pointNumber + 1; pointIndex++) { points[pointIndex] = new PointF( startX + pointIndex * step, y + ((pointIndex%2 == 0) ? -2f : 2f) ); } points[0] = new PointF(startX, y); points[points.Length - 1] = new PointF(endX, y); } else { float startY = breakLinePosition.Y; float endY = breakLinePosition.Bottom; float x = (top) ? breakLinePosition.X : breakLinePosition.Right; pointNumber = ((int)(endY - startY) / 40) * 2 ; if(pointNumber < 2) { pointNumber = 2; } float step = (endY - startY) / pointNumber; points = new PointF[pointNumber + 1]; for(int pointIndex = 1; pointIndex < pointNumber + 1; pointIndex++) { points[pointIndex] = new PointF( x + ((pointIndex%2 == 0) ? -2f : 2f), startY + pointIndex * step); } points[0] = new PointF(x, startY); points[points.Length - 1] = new PointF(x, endY); } path.AddCurve(points, 0, pointNumber, 0.8f); } else if(this.axis.ScaleBreakStyle.BreakLineStyle == BreakLineStyle.Ragged) { PointF[] points = null; Random rand = new Random(435657); if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left ) { float startX = breakLinePosition.X; float endX = breakLinePosition.Right; float y = (top) ? breakLinePosition.Y : breakLinePosition.Bottom; float step = 10f; int pointNumber = (int)((endX - startX) / step); if(pointNumber < 2) { pointNumber = 2; } points = new PointF[pointNumber]; for(int pointIndex = 1; pointIndex < pointNumber - 1; pointIndex++) { points[pointIndex] = new PointF( startX + pointIndex * step, y + rand.Next(-3, 3) ); } points[0] = new PointF(startX, y); points[points.Length - 1] = new PointF(endX, y); } else { float startY = breakLinePosition.Y; float endY = breakLinePosition.Bottom; float x = (top) ? breakLinePosition.X : breakLinePosition.Right; float step = 10f; int pointNumber = (int)((endY - startY) / step); if(pointNumber < 2) { pointNumber = 2; } points = new PointF[pointNumber]; for(int pointIndex = 1; pointIndex < pointNumber - 1; pointIndex++) { points[pointIndex] = new PointF( x + rand.Next(-3, 3), startY + pointIndex * step ); } points[0] = new PointF(x, startY); points[points.Length - 1] = new PointF(x, endY); } path.AddLines(points); } else { if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left ) { if(top) { path.AddLine(breakLinePosition.X, breakLinePosition.Y, breakLinePosition.Right, breakLinePosition.Y); } else { path.AddLine(breakLinePosition.X, breakLinePosition.Bottom, breakLinePosition.Right, breakLinePosition.Bottom); } } else { if(top) { path.AddLine(breakLinePosition.X, breakLinePosition.Y, breakLinePosition.X, breakLinePosition.Bottom); } else { path.AddLine(breakLinePosition.Right, breakLinePosition.Y, breakLinePosition.Right, breakLinePosition.Bottom); } } } return path; } /// /// Gets position of the axis break line. Break line may be shown as a single /// line or two lines separated with a spacing. /// /// Chart graphics. /// Next segment reference. /// Position of the axis break line in pixel coordinates. internal RectangleF GetBreakLinePosition(ChartGraphics graph, AxisScaleSegment nextSegment) { // Start with the plotting rectangle position RectangleF breakPosition = this.axis.PlotAreaPosition.ToRectangleF(); // Find maximum scale value of the current segment and minimuj of the next double from = this.axis.GetLinearPosition(nextSegment.ScaleMinimum); double to = this.axis.GetLinearPosition(this.ScaleMaximum); if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left ) { breakPosition.Y = (float)Math.Min(from, to); breakPosition.Height = (float)Math.Max(from, to); } else { breakPosition.X = (float)Math.Min(from, to); breakPosition.Width = (float)Math.Max(from, to);; } // Convert to pixels breakPosition = Rectangle.Round(graph.GetAbsoluteRectangle(breakPosition)); // Add border width if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left ) { breakPosition.Height = (float)Math.Abs(breakPosition.Y - breakPosition.Height); breakPosition.X -= this.axis.ChartArea.BorderWidth; breakPosition.Width += 2 * this.axis.ChartArea.BorderWidth; } else { breakPosition.Width = (float)Math.Abs(breakPosition.X - breakPosition.Width); breakPosition.Y -= this.axis.ChartArea.BorderWidth; breakPosition.Height += 2 * this.axis.ChartArea.BorderWidth; } return breakPosition; } #endregion // Break Line Painting Methods #region Helper Methods /// /// Gets segment scale position and size in relative coordinates. /// Method takes in consideration segment spacing and space required fro separatorType. /// /// Plotting area size in relative coordinates. /// Returns scale position. /// Returns scale size. internal void GetScalePositionAndSize(double plotAreaSize, out double scalePosition, out double scaleSize) { scaleSize = (this.Size - this.Spacing) * (plotAreaSize / 100.0); scalePosition = this.Position * (plotAreaSize / 100.0); } /// /// Saves axis settings and set them from the current segment. /// internal void SetTempAxisScaleAndInterval() { // Save current axis settings if(_oldAxisSettings.Count == 0) { _oldAxisSettings.Push(this.axis.maximum); _oldAxisSettings.Push(this.axis.minimum); _oldAxisSettings.Push(this.axis.majorGrid.interval); _oldAxisSettings.Push(this.axis.majorGrid.intervalType); _oldAxisSettings.Push(this.axis.majorGrid.intervalOffset); _oldAxisSettings.Push(this.axis.majorGrid.intervalOffsetType); _oldAxisSettings.Push(this.axis.majorTickMark.interval); _oldAxisSettings.Push(this.axis.majorTickMark.intervalType); _oldAxisSettings.Push(this.axis.majorTickMark.intervalOffset); _oldAxisSettings.Push(this.axis.majorTickMark.intervalOffsetType); _oldAxisSettings.Push(this.axis.LabelStyle.interval); _oldAxisSettings.Push(this.axis.LabelStyle.intervalType); _oldAxisSettings.Push(this.axis.LabelStyle.intervalOffset); _oldAxisSettings.Push(this.axis.LabelStyle.intervalOffsetType); } // Copy settings from the segment into the axis this.axis.maximum = this.ScaleMaximum; this.axis.minimum = this.ScaleMinimum; this.axis.majorGrid.interval = this.Interval; this.axis.majorGrid.intervalType = this.IntervalType; this.axis.majorGrid.intervalOffset = this.IntervalOffset; this.axis.majorGrid.intervalOffsetType = this.IntervalOffsetType; this.axis.majorTickMark.interval = this.Interval; this.axis.majorTickMark.intervalType = this.IntervalType; this.axis.majorTickMark.intervalOffset = this.IntervalOffset; this.axis.majorTickMark.intervalOffsetType = this.IntervalOffsetType; this.axis.LabelStyle.interval = this.Interval; this.axis.LabelStyle.intervalType = this.IntervalType; this.axis.LabelStyle.intervalOffset = this.IntervalOffset; this.axis.LabelStyle.intervalOffsetType = this.IntervalOffsetType; } /// /// Restore previously saved axis settings. /// internal void RestoreAxisScaleAndInterval() { if(_oldAxisSettings.Count > 0) { this.axis.LabelStyle.intervalOffsetType = (DateTimeIntervalType)_oldAxisSettings.Pop(); this.axis.LabelStyle.intervalOffset = (double)_oldAxisSettings.Pop(); this.axis.LabelStyle.intervalType = (DateTimeIntervalType)_oldAxisSettings.Pop(); this.axis.LabelStyle.interval = (double)_oldAxisSettings.Pop(); this.axis.majorTickMark.intervalOffsetType = (DateTimeIntervalType)_oldAxisSettings.Pop(); this.axis.majorTickMark.intervalOffset = (double)_oldAxisSettings.Pop(); this.axis.majorTickMark.intervalType = (DateTimeIntervalType)_oldAxisSettings.Pop(); this.axis.majorTickMark.interval = (double)_oldAxisSettings.Pop(); this.axis.majorGrid.intervalOffsetType = (DateTimeIntervalType)_oldAxisSettings.Pop(); this.axis.majorGrid.intervalOffset = (double)_oldAxisSettings.Pop(); this.axis.majorGrid.intervalType = (DateTimeIntervalType)_oldAxisSettings.Pop(); this.axis.majorGrid.interval = (double)_oldAxisSettings.Pop(); this.axis.minimum = (double)_oldAxisSettings.Pop(); this.axis.maximum = (double)_oldAxisSettings.Pop(); } } #endregion // Helper Methods } /// /// AxisScaleSegmentCollection is a class that stores collection of axis segments. /// [ SRDescription("DescriptionAttributeAxisScaleSegmentCollection_AxisScaleSegmentCollection"), ] internal class AxisScaleSegmentCollection : CollectionBase { #region Fields // Axis this segment collection belongs to. private Axis _axis = null; // Segment which is always used to convert scale values. // This value is set tmporarly when only one segment has // to handle all the values. private AxisScaleSegment _enforcedSegment = null; // Indicates that values allowed to be outside of the scale segment. // Otherwise they will be rounded to Min and Max values. internal bool AllowOutOfScaleValues = false; #endregion // Fields #region Construction and Initialization /// /// Default public constructor. /// /// /// This constructor is for internal use and should not be part of documentation. /// public AxisScaleSegmentCollection() { } /// /// Default public constructor. /// /// /// This constructor is for internal use and should not be part of documentation. /// /// /// Chart axis this collection belongs to /// internal AxisScaleSegmentCollection(Axis axis) { this._axis = axis; } #endregion // Construction and Initialization #region Indexer /// /// Axis scale segment collection indexer. /// /// /// The AxisScaleSegment object index can be provided as a parameter. Returns the object. /// [ SRDescription("DescriptionAttributeAxisScaleSegmentCollection_Item"), ] public AxisScaleSegment this[int index] { get { return (AxisScaleSegment)this.List[(int)index]; } } #endregion // Indexer #region Collection Add and Insert methods /// /// Adds a segment to the end of the collection. /// /// /// object to add. /// /// /// Index of the newly added object. /// public int Add(AxisScaleSegment segment) { return this.List.Add(segment); } #endregion // Collection Add and Insert methods #region Items Inserting and Removing Notification methods /// /// After new item inserted. /// /// Item index. /// Item object. /// /// This is an internal method and should not be part of the documentation. /// protected override void OnInsertComplete(int index, object value) { ((AxisScaleSegment)value).axis = this._axis; } /// /// After items is set. /// /// The zero-based index at which oldValue can be found. /// The value to replace with newValue. /// The new value of the element at index. /// /// This is an internal method and should not be part of the documentation. /// protected override void OnSetComplete(int index, object oldValue, object newValue) { ((AxisScaleSegment)newValue).axis = this._axis; } #endregion #region Helper Methods /// /// Ensures that specified axis scale segment is used for all coordinate transformations. /// Set tot NULL to reset. /// /// internal void EnforceSegment(AxisScaleSegment segment) { this._enforcedSegment = segment; } /// /// Find axis scale segment that should be used to translate axis value to relative coordinates. /// /// Axis value to convert. /// Scale segment to use for the convertion. public AxisScaleSegment FindScaleSegmentForAxisValue(double axisValue) { // Check if no segments defined if(this.List.Count == 0) { return null; } // Check if segment enforcment is enabled if(_enforcedSegment != null) { return _enforcedSegment; } // Iterate through all segments for(int index = 0; index < this.Count; index++) { if(axisValue < this[index].ScaleMinimum) { if(index == 0) { return this[index]; } else { // Find the segment which is "closer" to the value if( Math.Abs(this[index].ScaleMinimum - axisValue) < Math.Abs(axisValue - this[index - 1].ScaleMaximum)) { return this[index]; } else { return this[index - 1]; } } } if(axisValue <= this[index].ScaleMaximum) { return this[index]; } else if(index == this.Count - 1) { return this[index]; } } return null; } #endregion // Helper Methods } }