File: Common\ChartTypes\ErrorBarChart.cs
Project: ndp\fx\src\DataVisualization\System.Web.DataVisualization.csproj (System.Web.DataVisualization)
//-------------------------------------------------------------
// <copyright company=’Microsoft Corporation’>
//   Copyright © Microsoft Corporation. All Rights Reserved.
// </copyright>
//-------------------------------------------------------------
// @owner=alexgor, deliant
//=================================================================
//  File:		ErrorBarChart.cs
//
//  Namespace:	DataVisualization.Charting.ChartTypes
//
//	Classes:	ErrorBarChart
//
//  Purpose:	Provides 2D and 3D drawing and hit testing of the 
//              ErrorBar chart.
//
//  Error Bar Chart Overview:
//  -------------------------
//  Error bar charts consist of lines with markers that are used to 
//  display statistical information about the data displayed in a 
//  graph. An Error Bar chart type is a series that has 3 Y values. 
//  While it is true that these values can be assigned to each point 
//  in an Error Bar chart, in most cases, the values will be 
//  calculated from the data present in another series. 
//  
//  The order of the Y values is important because each position in 
//  the array of values represents a value on the Error Bar:
//      0 - Center or Average point value
//      1 - Lower Error value
//      2 - Upper Error value
//  
//	Reviewed:	GS - Jul 15, 2003
//              AG - Microsoft 6, 2007
//
//===================================================================
 
#region Used namespaces
 
using System;
using System.Collections;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.ComponentModel.Design;
using System.Globalization;
using System.Collections.Generic;
 
#if Microsoft_CONTROL
	using System.Windows.Forms.DataVisualization.Charting.Utilities;
#else
    using System.Web.UI.DataVisualization.Charting;
    using System.Web.UI.DataVisualization.Charting.Utilities;
#endif
 
#endregion
 
#if Microsoft_CONTROL
    namespace System.Windows.Forms.DataVisualization.Charting.ChartTypes
#else
    namespace System.Web.UI.DataVisualization.Charting.ChartTypes
#endif
{
	#region Enumerations
 
	/// <summary>
	/// Defines the way error amount is calculated.
	/// </summary>
	internal enum ErrorBarType
	{
		/// <summary>
		/// Error is a fixed value.
		/// </summary>
		FixedValue,
 
		/// <summary>
		/// Error is percentage of the center value.
		/// </summary>
		Percentage,
 
		/// <summary>
		/// Error is standard deviation of all center values in series.
		/// </summary>
		StandardDeviation,
 
		/// <summary>
		/// Error is standard error of all center values in series.
		/// </summary>
		StandardError
	}
 
	/// <summary>
	/// Error bars drawing styles.
	/// </summary>
	internal enum ErrorBarStyle
	{
		/// <summary>
		/// Draws both lower and upper error bar.
		/// </summary>
		Both,
 
		/// <summary>
		/// Draws only upper error bar.
		/// </summary>
		UpperError,
 
		/// <summary>
		/// Draws only lower error bar.
		/// </summary>
		LowerError
	}
 
	#endregion
 
	/// <summary>
    /// ErrorBarChart class provides 2D and 3D drawing and hit testing of
    /// the ErrorBar chart.
	/// </summary>
	internal class ErrorBarChart : IChartType
	{
		#region Fields
 
		/// <summary>
		/// Vertical axis
		/// </summary>
		protected	Axis	vAxis = null;	
 
		/// <summary>
		/// Horizontal axis
		/// </summary>
		protected	Axis	hAxis = null;
 
		#endregion
 
		#region Constructor
 
		/// <summary>
		/// Error bar chart constructor.
		/// </summary>
		public ErrorBarChart()
		{
		}
 
		#endregion
 
		#region IChartType interface implementation
 
		/// <summary>
		/// Chart type name
		/// </summary>
		virtual public string Name			{ get{ return ChartTypeNames.ErrorBar;}}
 
		/// <summary>
		/// True if chart type is stacked
		/// </summary>
		virtual public bool Stacked		{ get{ return false;}}
 
 
		/// <summary>
		/// True if stacked chart type supports groups
		/// </summary>
		virtual public bool SupportStackedGroups	{ get { return false; } }
 
 
		/// <summary>
		/// True if stacked chart type should draw separately positive and 
		/// negative data points ( Bar and column Stacked types ).
		/// </summary>
		public bool StackSign		{ get{ return false;}}
 
		/// <summary>
		/// True if chart type supports axes
		/// </summary>
		virtual public bool RequireAxes	{ get{ return true;} }
 
		/// <summary>
		/// Chart type with two y values used for scale ( bubble chart type )
		/// </summary>
		public bool SecondYScale{ get{ return false;} }
 
		/// <summary>
		/// True if chart type requires circular chart area.
		/// </summary>
		public bool CircularChartArea	{ get{ return false;} }
 
		/// <summary>
		/// True if chart type supports logarithmic axes
		/// </summary>
		virtual public bool SupportLogarithmicAxes	{ get{ return true;} }
 
		/// <summary>
		/// True if chart type requires to switch the value (Y) axes position
		/// </summary>
		virtual public bool SwitchValueAxes	{ get{ return false;} }
 
		/// <summary>
		/// True if chart series can be placed side-by-side.
		/// </summary>
		public bool SideBySideSeries { get{ return false;} }
 
		/// <summary>
		/// True if each data point of a chart must be represented in the legend
		/// </summary>
		virtual public bool DataPointsInLegend	{ get{ return false;} }
 
		/// <summary>
		/// If the crossing value is auto Crossing value ZeroCrossing should be 
		/// automatically set to zero for some chart 
		/// types (Bar, column, area etc.)
		/// </summary>
		virtual public bool ZeroCrossing { get{ return false;} }
 
		/// <summary>
		/// True if palette colors should be applied for each data paoint.
		/// Otherwise the color is applied to the series.
		/// </summary>
		virtual public bool ApplyPaletteColorsToPoints	{ get { return false; } }
 
		/// <summary>
		/// Indicates that extra Y values are connected to the scale of the Y axis
		/// </summary>
		virtual public bool ExtraYValuesConnectedToYAxis{ get { return true; } }
		
		/// <summary>
		/// Indicates that this is a one hundred percent chart.
		/// Axis scale from 0 to 100 percent should be used.
		/// </summary>
		virtual public bool HundredPercent{ get{return false;} }
 
		/// <summary>
		/// Indicates that this is a one hundred percent chart.
		/// Axis scale from 0 to 100 percent should be used.
		/// </summary>
		virtual public bool HundredPercentSupportNegative{ get{return false;} }
 
		/// <summary>
		/// How to draw series/points in legend:
		/// Filled rectangle, Line or Marker
		/// </summary>
		/// <param name="series">Legend item series.</param>
		/// <returns>Legend item style.</returns>
		virtual public LegendImageStyle GetLegendImageStyle(Series series)
		{
			return LegendImageStyle.Line;
		}
	
		/// <summary>
		/// Number of supported Y value(s) per point 
		/// </summary>
		virtual public int YValuesPerPoint	{ get { return 3; } }
 
		/// <summary>
		/// Gets chart type image.
		/// </summary>
		/// <param name="registry">Chart types registry object.</param>
		/// <returns>Chart type image.</returns>
        virtual public System.Drawing.Image GetImage(ChartTypeRegistry registry)
		{
			return (System.Drawing.Image)registry.ResourceManager.GetObject(this.Name + "ChartType");
		}
 
		#endregion
 
		#region Painting and Selection methods
 
		/// <summary>
		/// Paint error bar chart.
		/// </summary>
		/// <param name="graph">The Chart Graphics object.</param>
		/// <param name="common">The Common elements object.</param>
		/// <param name="area">Chart area for this chart.</param>
		/// <param name="seriesToDraw">Chart series to draw.</param>
		virtual public void Paint( ChartGraphics graph, CommonElements common, ChartArea area, Series seriesToDraw )
		{	
			ProcessChartType( false, graph, common, area, seriesToDraw );
		}
 
		/// <summary>
		/// This method recalculates size of the bars. This method is used 
		/// from Paint or Select method.
		/// </summary>
		/// <param name="selection">If True selection mode is active, otherwise paint mode is active.</param>
		/// <param name="graph">The Chart Graphics object.</param>
		/// <param name="common">The Common elements object.</param>
		/// <param name="area">Chart area for this chart.</param>
		/// <param name="seriesToDraw">Chart series to draw.</param>
		virtual protected void ProcessChartType( 
			bool selection, 
			ChartGraphics graph, 
			CommonElements common, 
			ChartArea area, 
			Series seriesToDraw )
		{
			// Prosess 3D chart type
			if(area.Area3DStyle.Enable3D)
			{
				ProcessChartType3D( selection, graph, common, area, seriesToDraw );
				return;
			}
 
			// All data series from chart area which have Error bar chart type
			List<string>	typeSeries = area.GetSeriesFromChartType(this.Name);
 
			// Zero X values mode.
            bool indexedSeries = ChartHelper.IndexedSeries(common, typeSeries.ToArray());
 
			//************************************************************
			//** Loop through all series
			//************************************************************
			int seriesIndex = 0;
			foreach( Series ser in common.DataManager.Series )
			{
				// Process non empty series of the area with error bar chart type
				if( String.Compare( ser.ChartTypeName, this.Name, StringComparison.OrdinalIgnoreCase ) != 0 
					|| ser.ChartArea != area.Name || !ser.IsVisible())
				{
					continue;
				}
 
				// Set active horizontal/vertical axis
				hAxis = area.GetAxis(AxisName.X, ser.XAxisType, ser.XSubAxisName);
				vAxis = area.GetAxis(AxisName.Y, ser.YAxisType, ser.YSubAxisName);
 
				// Get interval between points
				double interval = (indexedSeries) ? 1 : area.GetPointsInterval( hAxis.IsLogarithmic, hAxis.logarithmBase );
 
				// Calculates points width
				float	width = (float)(ser.GetPointWidth(graph, hAxis, interval, 0.4));
 
 
				// Align error bar X position with linked series				
				float	sideBySideWidth = width;
				int		numberOfLinkedSeries = 1;
				int		indexOfLinkedSeries = 0;
				bool	showSideBySide = false;
				string	linkedSeriesName = string.Empty;
				bool	currentDrawSeriesSideBySide = false;
				if(ser.IsCustomPropertySet(CustomPropertyName.ErrorBarSeries))
				{
					// Get series name
					linkedSeriesName = ser[CustomPropertyName.ErrorBarSeries];
					int valueTypeIndex = linkedSeriesName.IndexOf(":", StringComparison.Ordinal);
					if(valueTypeIndex >= 0)
					{
						linkedSeriesName = linkedSeriesName.Substring(0, valueTypeIndex);
					}
 
					// All linked data series from chart area which have Error bar chart type
					string linkedSeriesChartType = common.DataManager.Series[linkedSeriesName].ChartTypeName;
                    ChartArea linkedSeriesArea = common.ChartPicture.ChartAreas[common.DataManager.Series[linkedSeriesName].ChartArea];
                    List<string> typeLinkedSeries = linkedSeriesArea.GetSeriesFromChartType(linkedSeriesChartType);
 
					// Get index of linked serries
					foreach(string name in typeLinkedSeries)
					{
						if(name == linkedSeriesName)
						{
							break;
						}
						++indexOfLinkedSeries;
					}
 
					
					currentDrawSeriesSideBySide = false;
					if(String.Compare(linkedSeriesChartType, ChartTypeNames.Column, StringComparison.OrdinalIgnoreCase) ==0 
                        || String.Compare(linkedSeriesChartType, ChartTypeNames.RangeColumn, StringComparison.OrdinalIgnoreCase) == 0
                        )
					{
						currentDrawSeriesSideBySide = true;
					}
					foreach(string seriesName in typeLinkedSeries)
					{
						if(common.DataManager.Series[seriesName].IsCustomPropertySet(CustomPropertyName.DrawSideBySide))
						{
							string attribValue = common.DataManager.Series[seriesName][CustomPropertyName.DrawSideBySide];
							if(String.Compare(attribValue, "False", StringComparison.OrdinalIgnoreCase) == 0)
							{
								currentDrawSeriesSideBySide = false;
							}
							else if(String.Compare(attribValue, "True", StringComparison.OrdinalIgnoreCase) == 0)
							{
								currentDrawSeriesSideBySide = true;
							}
							else if(String.Compare(attribValue, "Auto", StringComparison.OrdinalIgnoreCase) == 0)
							{
								// Do nothing
							}
							else
							{
                                throw (new InvalidOperationException(SR.ExceptionAttributeDrawSideBySideInvalid));
							}
						}
					}
 
					if(currentDrawSeriesSideBySide)
					{
						// Find the number of linked data series
						numberOfLinkedSeries = typeLinkedSeries.Count;
						width /= numberOfLinkedSeries;
						showSideBySide = true;
 
						// Check if side by side
						if(!indexedSeries)
						{
							area.GetPointsInterval( typeLinkedSeries, hAxis.IsLogarithmic, hAxis.logarithmBase, true, out showSideBySide );
						}
 
						sideBySideWidth = (float)(common.DataManager.Series[linkedSeriesName].GetPointWidth(graph, hAxis, interval, 0.8)) / numberOfLinkedSeries;
					}
				}
 
				// Check if side-by-side attribute is set
				if(!currentDrawSeriesSideBySide && ser.IsCustomPropertySet(CustomPropertyName.DrawSideBySide))
				{
					string attribValue = ser[CustomPropertyName.DrawSideBySide];
					if(String.Compare(attribValue, "False", StringComparison.OrdinalIgnoreCase) ==0)
					{
						showSideBySide = false;
					}
					else if(String.Compare(attribValue, "True", StringComparison.OrdinalIgnoreCase) ==0)
					{
						showSideBySide = true;
						numberOfLinkedSeries = typeSeries.Count;
						indexOfLinkedSeries = seriesIndex;
						width /= numberOfLinkedSeries;
 
						// NOTE: Lines of code below were added to fix issue #4048
						sideBySideWidth = (float)(ser.GetPointWidth(graph, hAxis, interval, 0.8)) / numberOfLinkedSeries;
					}
					else if(String.Compare(attribValue, "Auto", StringComparison.OrdinalIgnoreCase) ==0)
					{
					}
					else
					{
                        throw (new InvalidOperationException(SR.ExceptionAttributeDrawSideBySideInvalid));
					}
				}
 
 
				// Call Back Paint event
				if( !selection )
				{
                    common.Chart.CallOnPrePaint(new ChartPaintEventArgs(ser, graph, common, area.PlotAreaPosition));
				}
 
 
				//************************************************************
				//** Series data points loop
				//************************************************************
				int	index = 1;
				foreach( DataPoint point in ser.Points )
				{
					// Check required Y values number
					if(point.YValues.Length < this.YValuesPerPoint)
					{
						throw(new InvalidOperationException(SR.ExceptionChartTypeRequiresYValues(this.Name, this.YValuesPerPoint.ToString(CultureInfo.InvariantCulture))));
					}
 
					// Reset pre-calculated point position
					point.positionRel = new PointF(float.NaN, float.NaN);
 
					// Get point X position
					float xPosition = 0f;
					double	xValue = point.XValue;
					if( indexedSeries )
					{
						xValue = (double)index;
					//	xPosition = (float)(hAxis.GetPosition( (double)index ) - sideBySideWidth * ((double) numberOfLinkedSeries) / 2.0 + sideBySideWidth/2 + indexOfLinkedSeries * sideBySideWidth);
					}
					
					if( showSideBySide )
					{
						xPosition = (float)(hAxis.GetPosition( xValue ) - sideBySideWidth * ((double) numberOfLinkedSeries) / 2.0 + sideBySideWidth/2 + indexOfLinkedSeries * sideBySideWidth);
					}
					else
					{
						xPosition = (float)hAxis.GetPosition( xValue );
					}
						
					double yValue0 = vAxis.GetLogValue( point.YValues[1] );
					double yValue1 = vAxis.GetLogValue( point.YValues[2] );
					xValue = hAxis.GetLogValue(xValue);
					
					// Check if chart is completly out of the data scaleView
					if(xValue < hAxis.ViewMinimum || 
						xValue > hAxis.ViewMaximum ||
						(yValue0 < vAxis.ViewMinimum && yValue1 < vAxis.ViewMinimum) ||
						(yValue0 > vAxis.ViewMaximum && yValue1 > vAxis.ViewMaximum) )
					{
						++index;
						continue;
					}
						
					// Make sure High/Low values are in data scaleView range						
					double	low = vAxis.GetLogValue( point.YValues[1] );
					double	high = vAxis.GetLogValue( point.YValues[2] );
 
					// Check if lower and/or upper error bar are drawn
					ErrorBarStyle	barStyle = ErrorBarStyle.Both;
					if(point.IsCustomPropertySet(CustomPropertyName.ErrorBarStyle) || ser.IsCustomPropertySet(CustomPropertyName.ErrorBarStyle))
					{
						string errorBarStyle = ser[CustomPropertyName.ErrorBarStyle];
						if(point.IsCustomPropertySet(CustomPropertyName.ErrorBarStyle))
						{
							errorBarStyle = point[CustomPropertyName.ErrorBarStyle];
						}
 
                        if(String.Compare( errorBarStyle, "Both", StringComparison.OrdinalIgnoreCase ) == 0)
						{
							// default - do nothing
						}
						else if(String.Compare(errorBarStyle, "UpperError", StringComparison.OrdinalIgnoreCase ) == 0)
						{
							barStyle = ErrorBarStyle.UpperError;
							low = vAxis.GetLogValue( point.YValues[0] );
							high = vAxis.GetLogValue( point.YValues[2] );
						}
						else if(String.Compare(errorBarStyle, "LowerError", StringComparison.OrdinalIgnoreCase ) == 0)
						{
							barStyle = ErrorBarStyle.LowerError;
							low = vAxis.GetLogValue( point.YValues[1] );
							high = vAxis.GetLogValue( point.YValues[0] );
						}
						else
						{
							throw(new InvalidOperationException( SR.ExceptionCustomAttributeValueInvalid( point[CustomPropertyName.ErrorBarStyle], "ErrorBarStyle")));
						}
					}
 
					// Check if values are in range
					if( high > vAxis.ViewMaximum )
					{
						high = vAxis.ViewMaximum;
					}
					if( high < vAxis.ViewMinimum )
					{
						high = vAxis.ViewMinimum;
					}
					high = (float)vAxis.GetLinearPosition(high);
					
					if( low > vAxis.ViewMaximum )
					{
						low = vAxis.ViewMaximum;
					}
					if( low < vAxis.ViewMinimum )
					{
						low = vAxis.ViewMinimum;
					}
					low = vAxis.GetLinearPosition(low);
 
					// Remeber pre-calculated point position
					point.positionRel = new PointF((float)xPosition, (float)Math.Min(high, low));
 
					if( common.ProcessModePaint )
					{
 
						// Check if chart is partialy in the data scaleView
						bool	clipRegionSet = false;
						if(xValue == hAxis.ViewMinimum || xValue == hAxis.ViewMaximum )
						{
							// Set clipping region for line drawing 
							graph.SetClip( area.PlotAreaPosition.ToRectangleF() );
							clipRegionSet = true;
						}
 
                        // Start Svg Selection mode
						graph.StartHotRegion( point );
 
						// Draw error bar line
						graph.DrawLineRel( 
							point.Color, 
							point.BorderWidth, 
							point.BorderDashStyle, 
							new PointF(xPosition, (float)high), 
							new PointF(xPosition, (float)low),
							ser.ShadowColor, 
							ser.ShadowOffset );
 
						// Draw Error Bar marks
						DrawErrorBarMarks(graph, barStyle, area, ser, point, xPosition, width);
 
						// End Svg Selection mode
						graph.EndHotRegion( );
 
						// Reset Clip Region
						if(clipRegionSet)
						{
							graph.ResetClip();
						}
					}
 
					if( common.ProcessModeRegions )
					{
						// Calculate rect around the error bar marks
						RectangleF	areaRect = RectangleF.Empty;
						areaRect.X = xPosition - width / 2f;
						areaRect.Y = (float)Math.Min(high, low);
						areaRect.Width = width;
						areaRect.Height = (float)Math.Max(high, low) - areaRect.Y;
 
						// Add area
						common.HotRegionsList.AddHotRegion( areaRect, point, ser.Name, index - 1 );
					}
					++index;
				}
 
				//************************************************************
				//** Second series data points loop, when labels are drawn.
				//************************************************************
				if( !selection )
				{
					index = 1;
					foreach( DataPoint point in ser.Points )
					{
						// Get point X position
						float xPosition = 0f;
						double	xValue = point.XValue;
						if( indexedSeries )
						{
							xValue = (double)index;
							xPosition = (float)(hAxis.GetPosition( (double)index ) - sideBySideWidth * ((double) numberOfLinkedSeries) / 2.0 + sideBySideWidth/2 + indexOfLinkedSeries * sideBySideWidth);
						}
						else if( showSideBySide )
						{
							xPosition = (float)(hAxis.GetPosition( xValue ) - sideBySideWidth * ((double) numberOfLinkedSeries) / 2.0 + sideBySideWidth/2 + indexOfLinkedSeries * sideBySideWidth);
						}
						else
						{
							xPosition = (float)hAxis.GetPosition( xValue );
						}
 
						double yValue0 = vAxis.GetLogValue( point.YValues[1] );
						double yValue1 = vAxis.GetLogValue( point.YValues[2] );
						xValue = hAxis.GetLogValue(xValue);
					
						// Check if chart is completly out of the data scaleView
						if(xValue < hAxis.ViewMinimum || 
							xValue > hAxis.ViewMaximum ||
							(yValue0 < vAxis.ViewMinimum && yValue1 < vAxis.ViewMinimum) ||
							(yValue0 > vAxis.ViewMaximum && yValue1 > vAxis.ViewMaximum) )
						{
							++index;
							continue;
						}
 
						// Make sure High/Low values are in data scaleView range						
						double	high = vAxis.GetLogValue( point.YValues[1] );
						double	low = vAxis.GetLogValue( point.YValues[2] );
					
						// Check if lower and/or upper error bar are drawn
						if(point.IsCustomPropertySet(CustomPropertyName.ErrorBarStyle) || ser.IsCustomPropertySet(CustomPropertyName.ErrorBarStyle))
						{
							string errorBarStyle = ser[CustomPropertyName.ErrorBarStyle];
							if(point.IsCustomPropertySet(CustomPropertyName.ErrorBarStyle))
							{
								errorBarStyle = point[CustomPropertyName.ErrorBarStyle];
							}
							if(String.Compare(errorBarStyle, "Both", StringComparison.OrdinalIgnoreCase) == 0)
							{
								// default - do nothing
							}
							else if(String.Compare(errorBarStyle, "UpperError", StringComparison.OrdinalIgnoreCase) == 0)
							{
								low = vAxis.GetLogValue( point.YValues[0] );
								high = vAxis.GetLogValue( point.YValues[2] );
							}
							else if(String.Compare(errorBarStyle, "LowerError", StringComparison.OrdinalIgnoreCase) == 0)
							{
								low = vAxis.GetLogValue( point.YValues[1] );
								high = vAxis.GetLogValue( point.YValues[0] );
							}
							else
							{
								throw(new InvalidOperationException(SR.ExceptionCustomAttributeValueInvalid(point[CustomPropertyName.ErrorBarStyle], "ErrorBarStyle")));
							}
						}
 
 
						if( high > vAxis.ViewMaximum )
						{
							high = vAxis.ViewMaximum;
						}
						if( high < vAxis.ViewMinimum )
						{
							high = vAxis.ViewMinimum;
						}
						high = (float)vAxis.GetLinearPosition(high);
					
						if( low > vAxis.ViewMaximum )
						{
							low = vAxis.ViewMaximum;
						}
						if( low < vAxis.ViewMinimum )
						{
							low = vAxis.ViewMinimum;
						}
						low = vAxis.GetLinearPosition(low);
 
						// Start Svg Selection mode
						graph.StartHotRegion( point, true );
 
						// Draw label
						DrawLabel(common, area, graph, ser, point, new PointF(xPosition, (float)Math.Min(high, low)), index);
 
						// End Svg Selection mode
						graph.EndHotRegion( );
 
						++index;
					}
				}
				
				// Call Paint event
				if( !selection )
				{
                    common.Chart.CallOnPostPaint(new ChartPaintEventArgs(ser, graph, common, area.PlotAreaPosition));
				}
 
				++seriesIndex;
			}
		}
 
		/// <summary>
		/// Draws error bar markers.
		/// </summary>
		/// <param name="graph">Chart graphics object.</param>
		/// <param name="barStyle">Style of the error bar.</param>
		/// <param name="area">Chart area.</param>
		/// <param name="ser">Data point series.</param>
		/// <param name="point">Data point to draw.</param>
		/// <param name="xPosition">X position.</param>
		/// <param name="width">Point width.</param>
		virtual protected void DrawErrorBarMarks(
			ChartGraphics graph, 
			ErrorBarStyle barStyle,
			ChartArea area,
			Series ser, 
			DataPoint point, 
			float xPosition, 
			float width)
		{
			double	yPosition = 0.0;
			string	markerStyle = String.Empty;
 
			// Draw lower error marker
			if(barStyle == ErrorBarStyle.Both || barStyle == ErrorBarStyle.LowerError)
			{
				// Get Y position
				yPosition = vAxis.GetLogValue( point.YValues[1] );
 
				// Get marker style name
				markerStyle = "LINE";
				if(point.MarkerStyle != MarkerStyle.None)
				{
					markerStyle = point.MarkerStyle.ToString();
				}
 
				// Draw marker
				DrawErrorBarSingleMarker(graph, area, point, markerStyle, xPosition, (float)yPosition, 0f, width, false);
			}
 
			// Draw upper error marker
			if(barStyle == ErrorBarStyle.Both || barStyle == ErrorBarStyle.UpperError)
			{
				// Get Y position
				yPosition = vAxis.GetLogValue( point.YValues[2] );
 
				// Get marker style name
				markerStyle = "LINE";
				if(point.MarkerStyle != MarkerStyle.None)
				{
					markerStyle = point.MarkerStyle.ToString();
				}
 
				// Draw marker
				DrawErrorBarSingleMarker(graph, area, point, markerStyle, xPosition, (float)yPosition, 0f, width, false);
			}
 
			// Draw center value marker
			if(point.IsCustomPropertySet(CustomPropertyName.ErrorBarCenterMarkerStyle) || point.series.IsCustomPropertySet(CustomPropertyName.ErrorBarCenterMarkerStyle))
			{
				// Get Y position
				yPosition = vAxis.GetLogValue( point.YValues[0] );
 
				// Get marker style name
				markerStyle = point.series[CustomPropertyName.ErrorBarCenterMarkerStyle];
				if(point.IsCustomPropertySet(CustomPropertyName.ErrorBarCenterMarkerStyle))
				{
					markerStyle = point[CustomPropertyName.ErrorBarCenterMarkerStyle];
				}
                markerStyle = markerStyle.ToUpper(System.Globalization.CultureInfo.InvariantCulture);
 
				// Draw marker
				DrawErrorBarSingleMarker(graph, area, point, markerStyle, xPosition, (float)yPosition, 0f, width, false);
			}
		}
 
		/// <summary>
		/// Draws single marker on the error bar.
		/// </summary>
		/// <param name="graph">Chart graphics.</param>
		/// <param name="area">Chart area.</param>
		/// <param name="point">Series point.</param>
		/// <param name="markerStyle">Marker style name.</param>
		/// <param name="xPosition">X position.</param>
		/// <param name="yPosition">Y position.</param>
		/// <param name="zPosition">Z position.</param>
		/// <param name="width">Point width.</param>
		/// <param name="draw3D">Used for 3d drawing.</param>
		private void DrawErrorBarSingleMarker(
			ChartGraphics graph, 
			ChartArea area,
			DataPoint point, 
			string markerStyle,
			float xPosition, 
			float yPosition, 
			float zPosition, 
			float width,
			bool draw3D)
		{
			markerStyle = markerStyle.ToUpper(CultureInfo.InvariantCulture);
			if(markerStyle.Length > 0 && String.Compare(markerStyle, "None", StringComparison.OrdinalIgnoreCase) != 0)
			{
				// Make sure Y value is in range
				if( yPosition > vAxis.ViewMaximum || yPosition < vAxis.ViewMinimum)
				{
					return;
				}
				yPosition = (float)vAxis.GetLinearPosition(yPosition);
 
				// 3D Transform coordinates
				if(draw3D)
				{
					Point3D[] points = new Point3D[1];
					points[0] = new Point3D(xPosition, yPosition, zPosition);
					area.matrix3D.TransformPoints(points);
					xPosition = points[0].X;
					yPosition = points[0].Y;
				}
 
				// Draw horizontal line marker
				if(String.Compare(markerStyle, "Line", StringComparison.OrdinalIgnoreCase) == 0)
				{
					graph.DrawLineRel(
						point.Color, 
						point.BorderWidth, 
						point.BorderDashStyle, 
						new PointF(xPosition - width/2f, yPosition), 
						new PointF(xPosition + width/2f, yPosition),
						(point.series != null) ? point.series.ShadowColor : Color.Empty, 
						(point.series != null) ? point.series.ShadowOffset : 0 );
				}
 
				// Draw standard marker
				else
				{
					MarkerStyle marker = (MarkerStyle)Enum.Parse(typeof(MarkerStyle), markerStyle, true);
 
					// Get marker size
					SizeF markerSize = GetMarkerSize(
						graph, 
						area.Common, 
						area, 
						point, 
						point.MarkerSize, 
						point.MarkerImage);
 
					// Get marker color
					Color markerColor = (point.MarkerColor == Color.Empty) ? point.Color : point.MarkerColor;
 
					// Draw the marker
					graph.DrawMarkerRel(
						new PointF(xPosition, yPosition), 
						marker,
						point.MarkerSize,
						markerColor,
						point.MarkerBorderColor,
						point.MarkerBorderWidth,
						point.MarkerImage,
						point.MarkerImageTransparentColor,
						(point.series != null) ? point.series.ShadowOffset : 0,
						(point.series != null) ? point.series.ShadowColor : Color.Empty,
						new RectangleF(xPosition, yPosition, markerSize.Width, markerSize.Height));
				}
			}
		}
 
		/// <summary>
		/// Returns marker size.
		/// </summary>
		/// <param name="graph">The Chart Graphics object.</param>
		/// <param name="common">The Common elements object.</param>
		/// <param name="area">Chart area for this chart.</param>
		/// <param name="point">Data point.</param>
		/// <param name="markerSize">Marker size.</param>
		/// <param name="markerImage">Marker image.</param>
		/// <returns>Marker width and height.</returns>
		virtual protected SizeF GetMarkerSize(
			ChartGraphics graph, 
			CommonElements common, 
			ChartArea area, 
			DataPoint point, 
			int markerSize, 
			string markerImage)
		{
			SizeF size = new SizeF(markerSize, markerSize);
            if (graph != null && graph.Graphics != null)
            {
                // Marker size is in pixels and we do the mapping for higher DPIs
                size.Width = markerSize * graph.Graphics.DpiX / 96;
                size.Height = markerSize * graph.Graphics.DpiY / 96;
            }
 
            if(markerImage.Length > 0)
			    common.ImageLoader.GetAdjustedImageSize(markerImage, graph.Graphics, ref size);
			
            return size;
		}
 
 
		/// <summary>
		/// Draws error bar chart data point label.
		/// </summary>
		/// <param name="common">The Common elements object</param>
		/// <param name="area">Chart area for this chart</param>
		/// <param name="graph">Chart graphics object.</param>
		/// <param name="ser">Data point series.</param>
		/// <param name="point">Data point to draw.</param>
		/// <param name="position">Label position.</param>
		/// <param name="pointIndex">Data point index.</param>
		virtual protected void DrawLabel(
			CommonElements common, 
			ChartArea area,
			ChartGraphics graph, 
			Series ser, 
			DataPoint point, 
			PointF position,
			int pointIndex)
		{
			if(ser.IsValueShownAsLabel || point.IsValueShownAsLabel || point.Label.Length > 0)
			{
				// Label text format
                using (StringFormat format = new StringFormat())
                {
                    format.Alignment = StringAlignment.Near;
                    format.LineAlignment = StringAlignment.Center;
                    if (point.LabelAngle == 0)
                    {
                        format.Alignment = StringAlignment.Center;
                        format.LineAlignment = StringAlignment.Far;
                    }
 
                    // Get label text
                    string text;
                    if (point.Label.Length == 0)
                    {
                        text = ValueConverter.FormatValue(
                            ser.Chart,
                            point,
                            point.Tag,
                            point.YValues[0],
                            point.LabelFormat,
                            ser.YValueType,
                            ChartElementType.DataPoint);
                    }
                    else
                    {
                        text = point.ReplaceKeywords(point.Label);
                    }
 
                    // Adjust label positio to the marker size
                    SizeF markerSizes = new SizeF(0f, 0f);
                    if (point.MarkerStyle != MarkerStyle.None)
                    {
                        markerSizes = graph.GetRelativeSize(new SizeF(point.MarkerSize, point.MarkerSize));
                        position.Y -= markerSizes.Height / 2f;
                    }
 
                    // Get text angle
                    int textAngle = point.LabelAngle;
 
                    // Check if text contains white space only
                    if (text.Trim().Length != 0)
                    {
                        SizeF sizeFont = SizeF.Empty;
 
 
                        // Check if Smart Labels are enabled
                        if (ser.SmartLabelStyle.Enabled)
                        {
                            // Get text size
                            sizeFont = graph.GetRelativeSize(
                                graph.MeasureString(text, point.Font, new SizeF(1000f, 1000f), StringFormat.GenericTypographic));
 
                            // Adjust label position using SmartLabelStyle algorithm
                            position = area.smartLabels.AdjustSmartLabelPosition(
                                common,
                                graph,
                                area,
                                ser.SmartLabelStyle,
                                position,
                                sizeFont,
                                format,
                                position,
                                markerSizes,
                                LabelAlignmentStyles.Top);
 
                            // Smart labels always use 0 degrees text angle
                            textAngle = 0;
                        }
 
 
 
                        // Draw label
                        if (!position.IsEmpty)
                        {
                            // Get text size
                            if (sizeFont.IsEmpty)
                            {
                                sizeFont = graph.GetRelativeSize(
                                    graph.MeasureString(text, point.Font, new SizeF(1000f, 1000f), StringFormat.GenericTypographic));
                            }
 
                            // Get label background position
                            RectangleF labelBackPosition = RectangleF.Empty;
                            SizeF sizeLabel = new SizeF(sizeFont.Width, sizeFont.Height);
                            sizeLabel.Height += sizeFont.Height / 8;
                            sizeLabel.Width += sizeLabel.Width / text.Length;
                            labelBackPosition = PointChart.GetLabelPosition(
                                graph,
                                position,
                                sizeLabel,
                                format,
                                true);
 
                            // Draw label text
                            using (Brush brush = new SolidBrush(point.LabelForeColor))
                            {
                                graph.DrawPointLabelStringRel(
                                    common,
                                    text,
                                    point.Font,
                                    brush,
                                    position,
                                    format,
                                    textAngle,
                                    labelBackPosition,
                                    point.LabelBackColor,
                                    point.LabelBorderColor,
                                    point.LabelBorderWidth,
                                    point.LabelBorderDashStyle,
                                    ser,
                                    point,
                                    pointIndex - 1);
                            }
                        }
                    }
                }
			}
		}
 
		#endregion
 
		#region 3D Drawing and Selection methods
 
		/// <summary>
		/// This method recalculates size of the bars. This method is used 
		/// from Paint or Select method.
		/// </summary>
		/// <param name="selection">If True selection mode is active, otherwise paint mode is active.</param>
		/// <param name="graph">The Chart Graphics object.</param>
		/// <param name="common">The Common elements object.</param>
		/// <param name="area">Chart area for this chart.</param>
		/// <param name="seriesToDraw">Chart series to draw.</param>
		virtual protected void ProcessChartType3D( 
			bool selection, 
			ChartGraphics graph, 
			CommonElements common, 
			ChartArea area, 
			Series seriesToDraw )
		{
			// All data series from chart area which have Error Bar chart type
			List<string>	typeSeries = area.GetSeriesFromChartType(this.Name);
 
			// Zero X values mode.
            bool indexedSeries = ChartHelper.IndexedSeries(common, typeSeries.ToArray());
 
			//************************************************************
			//** Loop through all series
			//************************************************************
			foreach( Series ser in common.DataManager.Series )
			{
				// Process non empty series of the area with stock chart type
				if( String.Compare( ser.ChartTypeName, this.Name, StringComparison.OrdinalIgnoreCase ) != 0 
					|| ser.ChartArea != area.Name || !ser.IsVisible())
				{
					continue;
				}
 
				// Check that we have at least 4 Y values
				if(ser.YValuesPerPoint < 3)
				{
					throw(new ArgumentException(SR.ExceptionChartTypeRequiresYValues( ChartTypeNames.ErrorBar, ((int)(3)).ToString(CultureInfo.CurrentCulture))));
				}
 
				// Set active horizontal/vertical axis
				hAxis = area.GetAxis(AxisName.X, ser.XAxisType, ser.XSubAxisName);
				vAxis = area.GetAxis(AxisName.Y, ser.YAxisType, ser.YSubAxisName);
 
				// Get interval between points
				double interval = (indexedSeries) ? 1 : area.GetPointsInterval( hAxis.IsLogarithmic, hAxis.logarithmBase );
 
				// Calculates the width of the candles.
				float	width = (float)(ser.GetPointWidth(graph, hAxis, interval, 0.4));
 
				// Align error bar X position with linked series				
				float	sideBySideWidth = width;
				int		numberOfLinkedSeries = 1;
				int		indexOfLinkedSeries = 0;
				bool	showSideBySide = false;
				if(ser.IsCustomPropertySet(CustomPropertyName.ErrorBarSeries))
				{
					// Get series name
					string attribValue = ser[CustomPropertyName.ErrorBarSeries];
                    int valueTypeIndex = attribValue.IndexOf(":", StringComparison.Ordinal);
					if(valueTypeIndex >= 0)
					{
						attribValue = attribValue.Substring(0, valueTypeIndex);
					}
 
					// All linked data series from chart area which have Error bar chart type
					string linkedSeriesChartType = common.DataManager.Series[attribValue].ChartTypeName;
					List<string>	typeLinkedSeries = area.GetSeriesFromChartType(linkedSeriesChartType);
 
					// Get index of linked serries
					foreach(string name in typeLinkedSeries)
					{
						if(name == attribValue)
						{
							break;
						}
						++indexOfLinkedSeries;
					}
 
					bool	currentDrawSeriesSideBySide = false;
					if(String.Compare(linkedSeriesChartType, ChartTypeNames.Column, StringComparison.OrdinalIgnoreCase ) == 0
                        || String.Compare(linkedSeriesChartType, ChartTypeNames.RangeColumn, StringComparison.OrdinalIgnoreCase) == 0
                        )
					{
						currentDrawSeriesSideBySide = true;
					}
					foreach(string seriesName in typeLinkedSeries)
					{
						if(common.DataManager.Series[seriesName].IsCustomPropertySet(CustomPropertyName.DrawSideBySide))
						{
							attribValue = common.DataManager.Series[seriesName][CustomPropertyName.DrawSideBySide];
							if(String.Compare(attribValue, "False", StringComparison.OrdinalIgnoreCase ) == 0)
							{
								currentDrawSeriesSideBySide = false;
							}
							else if(String.Compare(attribValue, "True", StringComparison.OrdinalIgnoreCase ) == 0)
							{
								currentDrawSeriesSideBySide = true;
							}
							else if(String.Compare(attribValue, "Auto", StringComparison.OrdinalIgnoreCase ) == 0)
							{
								// Do nothing
							}
							else
							{
                                throw (new InvalidOperationException(SR.ExceptionAttributeDrawSideBySideInvalid));
							}
						}
					}
 
					if(currentDrawSeriesSideBySide)
					{
						// Find the number of linked data series
						numberOfLinkedSeries = typeLinkedSeries.Count;
						width /= numberOfLinkedSeries;
 
						// Check if side by side
						if(!indexedSeries)
						{
							area.GetPointsInterval( typeLinkedSeries, hAxis.IsLogarithmic, hAxis.logarithmBase, true, out showSideBySide );
						}
					}
				}
 
				// Call Back Paint event
				if( !selection )
				{
                    common.Chart.CallOnPrePaint(new ChartPaintEventArgs(ser, graph, common, area.PlotAreaPosition));
				}
 
				//************************************************************
				//** Get series depth and Z position
				//************************************************************
				float seriesDepth, seriesZPosition;
				area.GetSeriesZPositionAndDepth(ser, out seriesDepth, out seriesZPosition);
 
				//************************************************************
				//** Series data points loop
				//************************************************************
				int	index = 1;
				foreach( DataPoint point in ser.Points )
				{
					// Check required Y values number
					if(point.YValues.Length < this.YValuesPerPoint)
					{
						throw(new InvalidOperationException(SR.ExceptionChartTypeRequiresYValues( this.Name, this.YValuesPerPoint.ToString(CultureInfo.InvariantCulture))));
					}
 
					// Reset pre-calculated point position
					point.positionRel = new PointF(float.NaN, float.NaN);
 
					// Get point X position
					float xPosition = 0f;
					double	xValue = point.XValue;
					if( indexedSeries )
					{
						xValue = (double)index;
						xPosition = (float)(hAxis.GetPosition( (double)index ) - sideBySideWidth * ((double) numberOfLinkedSeries) / 2.0 + sideBySideWidth/2 + indexOfLinkedSeries * sideBySideWidth);
					}
					else if( showSideBySide )
					{
						xPosition = (float)(hAxis.GetPosition( xValue ) - sideBySideWidth * ((double) numberOfLinkedSeries) / 2.0 + sideBySideWidth/2 + indexOfLinkedSeries * sideBySideWidth);
					}
					else
					{
						xPosition = (float)hAxis.GetPosition( xValue );
					}
 
					double yValue0 = vAxis.GetLogValue( point.YValues[1] );
					double yValue1 = vAxis.GetLogValue( point.YValues[2] );
					xValue = hAxis.GetLogValue(xValue);
					
					// Check if chart is completly out of the data scaleView
					if(xValue < hAxis.ViewMinimum || 
						xValue > hAxis.ViewMaximum ||
						(yValue0 < vAxis.ViewMinimum && yValue1 < vAxis.ViewMinimum) ||
						(yValue0 > vAxis.ViewMaximum && yValue1 > vAxis.ViewMaximum) )
					{
						++index;
						continue;
					}
 
					// Make sure High/Low values are in data scaleView range						
					double	high = vAxis.GetLogValue( point.YValues[2] );
					double	low = vAxis.GetLogValue( point.YValues[1] );
 
					// Check if lower and/or upper error bar are drawn
					ErrorBarStyle	barStyle = ErrorBarStyle.Both;
					if(point.IsCustomPropertySet(CustomPropertyName.ErrorBarStyle) || ser.IsCustomPropertySet(CustomPropertyName.ErrorBarStyle))
					{
						string errorBarStyle = ser[CustomPropertyName.ErrorBarStyle];
						if(point.IsCustomPropertySet(CustomPropertyName.ErrorBarStyle))
						{
							errorBarStyle = point[CustomPropertyName.ErrorBarStyle];
						}
						if(String.Compare(errorBarStyle, "Both", StringComparison.OrdinalIgnoreCase) == 0)
						{
							// default - do nothing
						}
						else if(String.Compare(errorBarStyle, "UpperError", StringComparison.OrdinalIgnoreCase) == 0)
						{
							barStyle = ErrorBarStyle.UpperError;
							low = vAxis.GetLogValue( point.YValues[0] );
							high = vAxis.GetLogValue( point.YValues[2] );
						}
						else if(String.Compare(errorBarStyle, "LowerError", StringComparison.OrdinalIgnoreCase) == 0)
						{
							barStyle = ErrorBarStyle.LowerError;
							low = vAxis.GetLogValue( point.YValues[1] );
							high = vAxis.GetLogValue( point.YValues[0] );
						}
						else
						{
							throw(new InvalidOperationException(SR.ExceptionCustomAttributeValueInvalid(point[CustomPropertyName.ErrorBarStyle], "ErrorBarStyle")));
						}
					}
				
					if( high > vAxis.ViewMaximum )
					{
						high = vAxis.ViewMaximum;
					}
					if( high < vAxis.ViewMinimum )
					{
						high = vAxis.ViewMinimum;
					}
					high = (float)vAxis.GetLinearPosition(high);
					
					if( low > vAxis.ViewMaximum )
					{
						low = vAxis.ViewMaximum;
					}
					if( low < vAxis.ViewMinimum )
					{
						low = vAxis.ViewMinimum;
					}
					low = vAxis.GetLinearPosition(low);
 
					// Remeber pre-calculated point position
					point.positionRel = new PointF((float)xPosition, (float)Math.Min(high, low));
 
					// 3D Transform coordinates
					Point3D[] points = new Point3D[2];
					points[0] = new Point3D(xPosition, (float)high, seriesZPosition+seriesDepth/2f);
					points[1] = new Point3D(xPosition, (float)low, seriesZPosition+seriesDepth/2f);
					area.matrix3D.TransformPoints(points);
 
					if( common.ProcessModePaint )
					{
 
						// Check if chart is partialy in the data scaleView
						bool	clipRegionSet = false;
						if(xValue == hAxis.ViewMinimum || xValue == hAxis.ViewMaximum )
						{
							// Set clipping region for line drawing 
							graph.SetClip( area.PlotAreaPosition.ToRectangleF() );
							clipRegionSet = true;
						}
 
                        // Start Svg Selection mode
						graph.StartHotRegion( point );
 
						// Draw error bar line
						graph.DrawLineRel( 
							point.Color, 
							point.BorderWidth, 
							point.BorderDashStyle, 
							points[0].PointF, 
							points[1].PointF,
							ser.ShadowColor, 
							ser.ShadowOffset );
 
						// Draw Error Bar marks
						DrawErrorBarMarks3D(graph, barStyle, area, ser, point, xPosition, width, seriesZPosition, seriesDepth);
						xPosition = points[0].X;
						high = points[0].Y;
						low = points[1].Y;
 
						// End Svg Selection mode
						graph.EndHotRegion( );
 
						// Reset Clip Region
						if(clipRegionSet)
						{
							graph.ResetClip();
						}
					}
 
					if( common.ProcessModeRegions )
					{
						xPosition = points[0].X;
						high = points[0].Y;
						low = points[1].Y;
 
						// Calculate rect around the error bar marks
						RectangleF	areaRect = RectangleF.Empty;
						areaRect.X = xPosition - width / 2f;
						areaRect.Y = (float)Math.Min(high, low);
						areaRect.Width = width;
						areaRect.Height = (float)Math.Max(high, low) - areaRect.Y;
 
						// Add area
						common.HotRegionsList.AddHotRegion( 
							areaRect, 
							point, 
							ser.Name, 
							index - 1 );
					}
				
					++index;
				}
 
				//************************************************************
				//** Second series data points loop, when labels are drawn.
				//************************************************************
				if( !selection )
				{
					index = 1;
					foreach( DataPoint point in ser.Points )
					{
						// Get point X position
						float xPosition = 0f;
						double	xValue = point.XValue;
						if( indexedSeries )
						{
							xValue = (double)index;
							xPosition = (float)(hAxis.GetPosition( (double)index ) - sideBySideWidth * ((double) numberOfLinkedSeries) / 2.0 + sideBySideWidth/2 + indexOfLinkedSeries * sideBySideWidth);
						}
						else if( showSideBySide )
						{
							xPosition = (float)(hAxis.GetPosition( xValue ) - sideBySideWidth * ((double) numberOfLinkedSeries) / 2.0 + sideBySideWidth/2 + indexOfLinkedSeries * sideBySideWidth);
						}
						else
						{
							xPosition = (float)hAxis.GetPosition( xValue );
						}
 
 
						double yValue0 = vAxis.GetLogValue( point.YValues[1] );
						double yValue1 = vAxis.GetLogValue( point.YValues[2] );
						xValue = hAxis.GetLogValue(xValue);
					
						// Check if chart is completly out of the data scaleView
						if(xValue < hAxis.ViewMinimum || 
							xValue > hAxis.ViewMaximum ||
							(yValue0 < vAxis.ViewMinimum && yValue1 < vAxis.ViewMinimum) ||
							(yValue0 > vAxis.ViewMaximum && yValue1 > vAxis.ViewMaximum) )
						{
							++index;
							continue;
						}
 
						// Make sure High/Low values are in data scaleView range						
						double	high = vAxis.GetLogValue( point.YValues[2] );
						double	low = vAxis.GetLogValue( point.YValues[1] );
 
						// Check if lower and/or upper error bar are drawn
						if(point.IsCustomPropertySet(CustomPropertyName.ErrorBarStyle) || ser.IsCustomPropertySet(CustomPropertyName.ErrorBarStyle))
						{
							string errorBarStyle = ser[CustomPropertyName.ErrorBarStyle];
							if(point.IsCustomPropertySet(CustomPropertyName.ErrorBarStyle))
							{
								errorBarStyle = point[CustomPropertyName.ErrorBarStyle];
							}
							if(String.Compare(errorBarStyle, "Both", StringComparison.OrdinalIgnoreCase ) == 0)
							{
								// default - do nothing
							}
							else if(String.Compare(errorBarStyle, "UpperError", StringComparison.OrdinalIgnoreCase ) == 0)
							{
								low = vAxis.GetLogValue( point.YValues[0] );
								high = vAxis.GetLogValue( point.YValues[2] );
							}
							else if(String.Compare(errorBarStyle, "LowerError", StringComparison.OrdinalIgnoreCase ) == 0)
							{
								low = vAxis.GetLogValue( point.YValues[1] );
								high = vAxis.GetLogValue( point.YValues[0] );
							}
							else
							{
								throw(new InvalidOperationException(SR.ExceptionCustomAttributeValueInvalid( point[CustomPropertyName.ErrorBarStyle], "ErrorBarStyle")));
							}
						}
					
						if( high > vAxis.ViewMaximum )
						{
							high = vAxis.ViewMaximum;
						}
						if( high < vAxis.ViewMinimum )
						{
							high = vAxis.ViewMinimum;
						}
						high = (float)vAxis.GetLinearPosition(high);
					
						if( low > vAxis.ViewMaximum )
						{
							low = vAxis.ViewMaximum;
						}
						if( low < vAxis.ViewMinimum )
						{
							low = vAxis.ViewMinimum;
						}
						low = vAxis.GetLinearPosition(low);
 
 
						// 3D Transform coordinates
						Point3D[] points = new Point3D[2];
						points[0] = new Point3D(xPosition, (float)high, seriesZPosition+seriesDepth/2f);
						points[1] = new Point3D(xPosition, (float)low, seriesZPosition+seriesDepth/2f);
						area.matrix3D.TransformPoints(points);
						xPosition = points[0].X;
						high = points[0].Y;
						low = points[1].Y;
 
						// Draw label
						DrawLabel(common, area, graph, ser, point, new PointF(xPosition, (float)Math.Min(high, low)), index);
				
						++index;
					}
				}
				
				// Call Paint event
				if( !selection )
				{
                    common.Chart.CallOnPostPaint(new ChartPaintEventArgs(ser, graph, common, area.PlotAreaPosition));
				}
			}
		}
 
		/// <summary>
		/// Draws stock chart open-close marks depending on selected style.
		/// </summary>
		/// <param name="graph">Chart graphics object.</param>
		/// <param name="barStyle">Style of the error bar.</param>
		/// <param name="area">Chart area.</param>
		/// <param name="ser">Data point series.</param>
		/// <param name="point">Data point to draw.</param>
		/// <param name="xPosition">X position.</param>
		/// <param name="width">Point width.</param>
		/// <param name="zPosition">Series Z position.</param>
		/// <param name="depth">Series depth.</param>
		virtual protected void DrawErrorBarMarks3D(
			ChartGraphics graph, 
			ErrorBarStyle barStyle,
			ChartArea area,
			Series ser, 
			DataPoint point, 
			float xPosition, 
			float width,
			float zPosition,
			float depth)
		{
			float	yPosition = 0f;
			string	markerStyle = String.Empty;
 
			// Draw lower error marker
			if(barStyle == ErrorBarStyle.Both || barStyle == ErrorBarStyle.LowerError)
			{
				// Get Y position
				yPosition = (float)vAxis.GetLogValue( point.YValues[1] );
 
				// Get marker style name
				markerStyle = "LINE";
				if(point.MarkerStyle != MarkerStyle.None)
				{
					markerStyle = point.MarkerStyle.ToString();
				}
 
				// Draw marker
				DrawErrorBarSingleMarker(graph, area, point, markerStyle, xPosition, yPosition, zPosition+depth/2f, width, true);
			}
 
			// Draw upper error marker
			if(barStyle == ErrorBarStyle.Both || barStyle == ErrorBarStyle.UpperError)
			{
				// Get Y position
				yPosition = (float)vAxis.GetLogValue( point.YValues[2] );
 
				// Get marker style name
				markerStyle = "LINE";
				if(point.MarkerStyle != MarkerStyle.None)
				{
					markerStyle = point.MarkerStyle.ToString();
				}
 
				// Draw marker
				DrawErrorBarSingleMarker(graph, area, point, markerStyle, xPosition, yPosition, zPosition+depth/2f, width, true);
			}
 
			// Draw center value marker
			if(point.IsCustomPropertySet(CustomPropertyName.ErrorBarCenterMarkerStyle) || point.series.IsCustomPropertySet(CustomPropertyName.ErrorBarCenterMarkerStyle))
			{
				// Get Y position
				yPosition = (float)vAxis.GetLogValue( point.YValues[0] );
 
				// Get marker style name
				markerStyle = point.series[CustomPropertyName.ErrorBarCenterMarkerStyle];
				if(point.IsCustomPropertySet(CustomPropertyName.ErrorBarCenterMarkerStyle))
				{
					markerStyle = point[CustomPropertyName.ErrorBarCenterMarkerStyle];
				}
				markerStyle = markerStyle.ToUpper(CultureInfo.InvariantCulture);
 
				// Draw marker
				DrawErrorBarSingleMarker(graph, area, point, markerStyle, xPosition, yPosition, zPosition+depth/2f, width, true);
			}
		}
 
		#endregion
 
		#region Y values related methods
 
		/// <summary>
		/// Helper function that returns the Y value of the point.
		/// </summary>
		/// <param name="common">Chart common elements.</param>
		/// <param name="area">Chart area the series belongs to.</param>
		/// <param name="series">Sereis of the point.</param>
		/// <param name="point">Point object.</param>
		/// <param name="pointIndex">Index of the point.</param>
		/// <param name="yValueIndex">Index of the Y value to get.</param>
		/// <returns>Y value of the point.</returns>
		virtual public double GetYValue(
			CommonElements common, 
			ChartArea area, 
			Series series, 
			DataPoint point, 
			int pointIndex, 
			int yValueIndex)
		{
			return point.YValues[yValueIndex];
		}
 
		#endregion
 
		#region Automatic Values Calculation methods
 
		/// <summary>
		/// Calculates lower and upper error amount using specified formula.
		/// </summary>
		/// <param name="errorBarSeries">Error bar chart type series.</param>
		internal static void CalculateErrorAmount(Series errorBarSeries)
		{
			// Check input parameters
			if(String.Compare(errorBarSeries.ChartTypeName, ChartTypeNames.ErrorBar, StringComparison.OrdinalIgnoreCase) != 0 )
			{
				return;
			}
 
			// Check if "ErrorBarType" custom attribute is set
			if(!errorBarSeries.IsCustomPropertySet(CustomPropertyName.ErrorBarType) && 
				!errorBarSeries.IsCustomPropertySet(CustomPropertyName.ErrorBarSeries))
			{
				return;
			}
 
			// Parase the value of the ErrorBarType attribute.
			double param = double.NaN;
			ErrorBarType errorBarType = ErrorBarType.StandardError;
			if(errorBarSeries.IsCustomPropertySet(CustomPropertyName.ErrorBarType))
			{
				string	typeName = errorBarSeries[CustomPropertyName.ErrorBarType];
				if(typeName.StartsWith("FixedValue", StringComparison.OrdinalIgnoreCase))
				{
					errorBarType = ErrorBarType.FixedValue;
				}
				else if(typeName.StartsWith("Percentage", StringComparison.OrdinalIgnoreCase))
				{
					errorBarType = ErrorBarType.Percentage;
				}
				else if(typeName.StartsWith("StandardDeviation", StringComparison.OrdinalIgnoreCase))
				{
					errorBarType = ErrorBarType.StandardDeviation;
				}
				else if(typeName.StartsWith("StandardError", StringComparison.OrdinalIgnoreCase))
				{
					errorBarType = ErrorBarType.StandardError;
				}
				else if(typeName.StartsWith("None", StringComparison.OrdinalIgnoreCase))
				{
					return;
				}
				else
				{
					throw(new InvalidOperationException(SR.ExceptionErrorBarTypeInvalid(errorBarSeries[CustomPropertyName.ErrorBarType])));
				}
		
				// Check if parameter is specified
				typeName = typeName.Substring(errorBarType.ToString().Length);
				if(typeName.Length > 0)
				{
					// Must be followed by '(' and ends with ')'
                    if (!typeName.StartsWith("(", StringComparison.Ordinal) || !typeName.EndsWith(")", StringComparison.Ordinal))
					{
						throw(new InvalidOperationException(SR.ExceptionErrorBarTypeFormatInvalid(errorBarSeries[CustomPropertyName.ErrorBarType])));
					}
					typeName = typeName.Substring(1, typeName.Length - 2);
 
 
					if(typeName.Length > 0)
					{
                        if (!double.TryParse(typeName, NumberStyles.Any, CultureInfo.InvariantCulture, out param))
                        {
                            throw (new InvalidOperationException(SR.ExceptionErrorBarTypeFormatInvalid(errorBarSeries[CustomPropertyName.ErrorBarType])));
                        }
					}
				}
			}
 
			// Points number
			int	pointNumber = errorBarSeries.Points.Count;
 
			// Find number of empty data points
			int numberOfEmptyPoints = 0;
			foreach(DataPoint point in errorBarSeries.Points)
			{
				if( point.IsEmpty )
				{
					numberOfEmptyPoints++;
				}
			}
 
			// Number of poist without empty points
			pointNumber -= numberOfEmptyPoints;
 
            if (double.IsNaN(param))
            {
                param = DefaultErrorBarTypeValue(errorBarType);
            }
 
			// Calculate error amount
			double	errorAmount = 0.0;
			if(errorBarType == ErrorBarType.FixedValue)
			{
			    errorAmount = param;
			}
			else if(errorBarType == ErrorBarType.Percentage)
			{
                // no processing or errorAmount
			}
			else if( errorBarType == ErrorBarType.StandardDeviation )
			{
				// Formula for standard deviation need 
				// more then one data point
				if( pointNumber > 1 )
				{
				
					// Calculate series mean value
					double mean = 0.0;
					foreach(DataPoint point in errorBarSeries.Points)
					{
						mean += point.YValues[0];
					}
					mean /= pointNumber;
 
					// Calculate series variance
					errorAmount = 0.0;
					foreach(DataPoint point in errorBarSeries.Points)
					{
						if( !point.IsEmpty )
						{
							errorAmount += Math.Pow(point.YValues[0] - mean, 2);
						}
					}
				
					errorAmount = param * Math.Sqrt(errorAmount/ ( pointNumber - 1 ) );
				}
				else
				{
					errorAmount = 0;
				}
			}
			else if( errorBarType == ErrorBarType.StandardError )
			{
				// Formula for standard deviation need 
				// more then one data point
				if( pointNumber > 1 )
				{
					// Calculate standard error
					errorAmount = 0.0;
					foreach(DataPoint point in errorBarSeries.Points)
					{
						if( !point.IsEmpty )
						{
							errorAmount += Math.Pow(point.YValues[0], 2);
						}
					}
				
					errorAmount = param * Math.Sqrt( errorAmount/( pointNumber * ( pointNumber - 1 ) ) ) / 2.0;
				}
				else
				{
					errorAmount = 0;
				}
			}
 
 
			// Loop through all points to calculate error amount
			foreach(DataPoint point in errorBarSeries.Points)
			{
				if(errorBarType == ErrorBarType.Percentage)
				{
					point.YValues[1] = point.YValues[0] - point.YValues[0] * param / 100.0;
					point.YValues[2] = point.YValues[0] + point.YValues[0] * param / 100.0;
				}
				else
				{
					point.YValues[1] = point.YValues[0] - errorAmount;
					point.YValues[2] = point.YValues[0] + errorAmount;
				}
			}
 
		}
 
        internal static double DefaultErrorBarTypeValue(ErrorBarType errorBarType)
        {
            switch (errorBarType)
            {
                case ErrorBarType.FixedValue:
                case ErrorBarType.Percentage:
                    return 10.0;
                case ErrorBarType.StandardDeviation:
                case ErrorBarType.StandardError:
                    return 1.0;
                default:
                    System.Diagnostics.Debug.Fail("Unknown ErrorBarType=" + errorBarType.ToString());
                    break;
            }
            return 10.0;
        }
 
		/// <summary>
		/// Populates error bar center value using the linked series specified by 
		/// "ErrorBarSeries" custom attribute.
		/// </summary>
		/// <param name="errorBarSeries">Error bar chart type series.</param>
		internal static void GetDataFromLinkedSeries(Series errorBarSeries)
		{
			// Check input parameters
			if(String.Compare(errorBarSeries.ChartTypeName, ChartTypeNames.ErrorBar, StringComparison.OrdinalIgnoreCase) != 0 || errorBarSeries.Chart == null)
			{
				return;
			}
 
			// Check if "ErrorBarSeries" custom attribute is set
			if(!errorBarSeries.IsCustomPropertySet(CustomPropertyName.ErrorBarSeries))
			{
				return;
			}
 
			// Get series and value name
			string	linkedSeriesName = errorBarSeries[CustomPropertyName.ErrorBarSeries];
			String	valueName = "Y";
			int valueTypeIndex = linkedSeriesName.IndexOf(":", StringComparison.Ordinal);
			if(valueTypeIndex >= 0)
			{
				valueName = linkedSeriesName.Substring(valueTypeIndex + 1);
				linkedSeriesName = linkedSeriesName.Substring(0, valueTypeIndex);
			}
 
			// Get reference to the chart control
			Chart control = errorBarSeries.Chart;
			if(control != null)
			{
				// Get linked series and check existance
				if(control.Series.IndexOf(linkedSeriesName) == -1)
				{
                    throw (new InvalidOperationException(SR.ExceptionDataSeriesNameNotFound(linkedSeriesName)));
				}
				Series linkedSeries = control.Series[linkedSeriesName];
 
				// Make sure we use the same X and Y axis as the linked series
				errorBarSeries.XAxisType = linkedSeries.XAxisType;
				errorBarSeries.YAxisType = linkedSeries.YAxisType;
 
				// Get cennter values from the linked series
				errorBarSeries.Points.Clear();
				foreach(DataPoint point in linkedSeries.Points)
				{
					// Add new point into the collection
					errorBarSeries.Points.AddXY(point.XValue, point.GetValueByName(valueName));
				}
			}
 
		}
 
		#endregion
 
		#region SmartLabelStyle methods
 
		/// <summary>
		/// Adds markers position to the list. Used to check SmartLabelStyle overlapping.
		/// </summary>
		/// <param name="common">Common chart elements.</param>
		/// <param name="area">Chart area.</param>
		/// <param name="series">Series values to be used.</param>
		/// <param name="list">List to add to.</param>
		public void AddSmartLabelMarkerPositions(CommonElements common, ChartArea area, Series series, ArrayList list)		
		{
			// No data point markers supported for SmartLabelStyle
		}
 
		#endregion
 
        #region IDisposable interface implementation
        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            //Nothing to dispose at the base class. 
        }
 
        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion
	}
}