File: Common\Formulas\FormulaHelpers.cs
Project: ndp\fx\src\DataVisualization\System.Windows.Forms.DataVisualization.csproj (System.Windows.Forms.DataVisualization)
//-------------------------------------------------------------
// <copyright company=’Microsoft Corporation’>
//   Copyright © Microsoft Corporation. All Rights Reserved.
// </copyright>
//-------------------------------------------------------------
// @owner=victark, alexgor, deliant
using System;
using System.Collections.Generic;
using System.Text;
using System.Globalization;
using System.Diagnostics;
 
#if Microsoft_CONTROL
namespace System.Windows.Forms.DataVisualization.Charting.Formulas
#else
namespace System.Web.UI.DataVisualization.Charting.Formulas
#endif
{
 
    #region class FormulaHelper
    /// <summary>
    /// Formula helper is a static utility class implementing common formula related routines.
    /// </summary>
    internal static class FormulaHelper
    {
        #region Static
        /// <summary>
        /// Gets the formula info instance.
        /// </summary>
        /// <param name="formula">The formula.</param>
        /// <returns>FomulaInfo instance</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]
        internal static FormulaInfo GetFormulaInfo(FinancialFormula formula)
        {
            switch (formula)
            {
                //Price indicators
                case FinancialFormula.MovingAverage:
                    return new MovingAverageFormulaInfo();
                case FinancialFormula.ExponentialMovingAverage:
                    return new ExponentialMovingAverageFormulaInfo();
                case FinancialFormula.WeightedMovingAverage:
                    return new WeightedMovingAverageFormulaInfo();
                case FinancialFormula.TriangularMovingAverage:
                    return new TriangularMovingAverageFormulaInfo();
                case FinancialFormula.TripleExponentialMovingAverage:
                    return new TripleExponentialMovingAverageFormulaInfo();
                case FinancialFormula.BollingerBands:
                    return new BollingerBandsFormulaInfo();
                case FinancialFormula.TypicalPrice:
                    return new TypicalPriceFormulaInfo();
                case FinancialFormula.WeightedClose:
                    return new WeightedCloseFormulaInfo();
                case FinancialFormula.MedianPrice:
                    return new MedianPriceFormulaInfo();
                case FinancialFormula.Envelopes:
                    return new EnvelopesFormulaInfo();
                case FinancialFormula.StandardDeviation:
                    return new StandardDeviationFormulaInfo();
 
                // Oscilators
                case FinancialFormula.ChaikinOscillator:
                    return new ChaikinOscillatorFormulaInfo();
                case FinancialFormula.DetrendedPriceOscillator:
                    return new DetrendedPriceOscillatorFormulaInfo();
                case FinancialFormula.VolatilityChaikins:
                    return new VolatilityChaikinsFormulaInfo();
                case FinancialFormula.VolumeOscillator:
                    return new VolumeOscillatorFormulaInfo();
                case FinancialFormula.StochasticIndicator:
                    return new StochasticIndicatorFormulaInfo();
                case FinancialFormula.WilliamsR:
                    return new WilliamsRFormulaInfo();
 
                // General technical indicators
                case FinancialFormula.AverageTrueRange:
                    return new AverageTrueRangeFormulaInfo();
                case FinancialFormula.EaseOfMovement:
                    return new EaseOfMovementFormulaInfo();
                case FinancialFormula.MassIndex:
                    return new MassIndexFormulaInfo();
                case FinancialFormula.Performance:
                    return new PerformanceFormulaInfo();
                case FinancialFormula.RateOfChange:
                    return new RateOfChangeFormulaInfo();
                case FinancialFormula.RelativeStrengthIndex:
                    return new RelativeStrengthIndexFormulaInfo();
                case FinancialFormula.MovingAverageConvergenceDivergence:
                    return new MovingAverageConvergenceDivergenceFormulaInfo();
                case FinancialFormula.CommodityChannelIndex:
                    return new CommodityChannelIndexFormulaInfo();
 
                // Forecasting
                case FinancialFormula.Forecasting:
                    return new ForecastingFormulaInfo();
 
                // Volume Indicators
                case FinancialFormula.MoneyFlow:
                    return new MoneyFlowFormulaInfo();
                case FinancialFormula.PriceVolumeTrend:
                    return new PriceVolumeTrendFormulaInfo();
                case FinancialFormula.OnBalanceVolume:
                    return new OnBalanceVolumeFormulaInfo();
                case FinancialFormula.NegativeVolumeIndex:
                    return new NegativeVolumeIndexFormulaInfo();
                case FinancialFormula.PositiveVolumeIndex:
                    return new PositiveVolumeIndexFormulaInfo();
                case FinancialFormula.AccumulationDistribution:
                    return new AccumulationDistributionFormulaInfo();
 
                default:
                    Debug.Fail(String.Format(CultureInfo.InvariantCulture, "{0} case is not defined", formula));
                    return null;
            }
        }
 
        /// <summary>
        /// Gets the data fields of the specified chart type.
        /// </summary>
        /// <param name="chartType">Type of the chart.</param>
        /// <returns>Data fields</returns>
        internal static IList<DataField> GetDataFields(SeriesChartType chartType)
        {
            switch (chartType)
            {
                case SeriesChartType.BoxPlot:
                    return new DataField[] { 
                        DataField.LowerWisker, DataField.UpperWisker, 
                        DataField.LowerBox, DataField.UpperBox, 
                        DataField.Average, DataField.Median };
                case SeriesChartType.Bubble:
                    return new DataField[] { 
                        DataField.Bubble, DataField.BubbleSize };
                case SeriesChartType.Candlestick:
                case SeriesChartType.Stock:
                    return new DataField[] { 
                        DataField.High, DataField.Low,
                        DataField.Open, DataField.Close };
                case SeriesChartType.ErrorBar:
                    return new DataField[] { 
                        DataField.Center, 
                        DataField.LowerError, DataField.UpperError};
                case SeriesChartType.RangeBar:
                case SeriesChartType.Range:
                case SeriesChartType.RangeColumn:
                case SeriesChartType.SplineRange:
                    return new DataField[] { 
                        DataField.Top, DataField.Bottom };
                default:
                    return new DataField[] { DataField.Y };
            }
        }
 
        /// <summary>
        /// Gets the default type of the chart associated with this field name.
        /// </summary>
        /// <param name="field">The field.</param>
        /// <returns></returns>
        internal static SeriesChartType GetDefaultChartType(DataField field)
        {
            switch (field)
            {
                default:
                case DataField.Y:
                    return SeriesChartType.Line;
                case DataField.LowerWisker:
                case DataField.UpperWisker:
                case DataField.LowerBox:
                case DataField.UpperBox:
                case DataField.Average:
                case DataField.Median:
                    return SeriesChartType.BoxPlot;
                case DataField.Bubble:
                case DataField.BubbleSize:
                    return SeriesChartType.Bubble;
                case DataField.High:
                case DataField.Low:
                case DataField.Open:
                case DataField.Close:
                    return SeriesChartType.Stock;
                case DataField.Center:
                case DataField.LowerError:
                case DataField.UpperError:
                    return SeriesChartType.ErrorBar;
                case DataField.Top:
                case DataField.Bottom:
                    return SeriesChartType.Range;
            }
        }
 
        /// <summary>
        /// Maps formula data field to a chart type specific data field. 
        /// </summary>
        /// <param name="chartType">Type of the chart.</param>
        /// <param name="formulaField">The formula field to be mapped.</param>
        /// <returns>The series field</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
        internal static DataField? MapFormulaDataField(SeriesChartType chartType, DataField formulaField)
        {
            switch (formulaField)
            {
                case DataField.Top:
                case DataField.High:
                    switch (chartType)
                    {
                        default: return null;
                        case SeriesChartType.BoxPlot: return DataField.UpperBox;
                        case SeriesChartType.Candlestick:
                        case SeriesChartType.Stock: return DataField.High;
                        case SeriesChartType.ErrorBar: return DataField.UpperError;
                        case SeriesChartType.RangeBar:
                        case SeriesChartType.Range:
                        case SeriesChartType.RangeColumn:
                        case SeriesChartType.SplineRange: return DataField.Top;
                    }
 
                case DataField.Bottom:
                case DataField.Low:
                    switch (chartType)
                    {
                        default: return null;
                        case SeriesChartType.BoxPlot: return DataField.LowerBox;
                        case SeriesChartType.Candlestick:
                        case SeriesChartType.Stock: return DataField.Low;
                        case SeriesChartType.ErrorBar: return DataField.LowerError;
                        case SeriesChartType.RangeBar:
                        case SeriesChartType.Range:
                        case SeriesChartType.RangeColumn:
                        case SeriesChartType.SplineRange: return DataField.Bottom;
                    }
 
                case DataField.Open:
                    switch (chartType)
                    {
                        default: return null;
                        case SeriesChartType.BoxPlot: return DataField.Average;
                        case SeriesChartType.Candlestick:
                        case SeriesChartType.Stock: return DataField.Open;
                        case SeriesChartType.ErrorBar: return DataField.Center;
                        case SeriesChartType.RangeBar:
                        case SeriesChartType.Range:
                        case SeriesChartType.RangeColumn:
                        case SeriesChartType.SplineRange: return DataField.Bottom;
                    }
 
                case DataField.Close:
                case DataField.Y:
                    switch (chartType)
                    {
                        default: return DataField.Y;
                        case SeriesChartType.BoxPlot: return DataField.Average;
                        case SeriesChartType.Bubble: return DataField.Bubble;
                        case SeriesChartType.Candlestick:
                        case SeriesChartType.Stock: return DataField.Close;
                        case SeriesChartType.ErrorBar: return DataField.Center;
                        case SeriesChartType.RangeBar:
                        case SeriesChartType.Range:
                        case SeriesChartType.RangeColumn:
                        case SeriesChartType.SplineRange: return DataField.Top;
                    }
                default:
                    return null;
            }
        }
 
        #endregion
    }
    #endregion
 
    #region class FormulaInfo and inherited FormulaSpecific classes
 
    /// <summary>
    /// This a base class of the formula metainfo classes.
    /// </summary>
    internal abstract class FormulaInfo
    {
        #region Fields
        DataField[] _inputFields;
        DataField[] _outputFields;
        object[] _parameters;
        #endregion
 
        #region Properties
        /// <summary>
        /// Gets the input data fields of the formula.
        /// </summary>
        /// <value>The input fields.</value>
        public DataField[] InputFields 
        { 
            get { return _inputFields; } 
        }
 
        /// <summary>
        /// Gets the output data fields of the formula.
        /// </summary>
        /// <value>The output fields.</value>
        public DataField[] OutputFields 
        { 
            get { return _outputFields; } 
        }
 
        /// <summary>
        /// Gets the parameters of the formula.
        /// </summary>
        /// <value>The parameters.</value>
        public object[] Parameters 
        { 
            get { return _parameters; }
        }
        #endregion
 
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="FormulaInfo"/> class.
        /// </summary>
        /// <param name="inputFields">The input data fields.</param>
        /// <param name="outputFields">The output data fields.</param>
        /// <param name="defaultParams">The default formula params.</param>
        public FormulaInfo(DataField[] inputFields, DataField[] outputFields, params object[] defaultParams)
        {
            _inputFields = inputFields;
            _outputFields = outputFields;
            _parameters = defaultParams;
        }
        #endregion
 
        #region Methods
        /// <summary>
        /// Saves the formula parameters to a string.
        /// </summary>
        /// <returns>Csv string with parameters</returns>
        internal virtual string SaveParametersToString()
        {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < _parameters.Length; i++)
            {
                if (i > 0) sb.Append(',');
                sb.AppendFormat(CultureInfo.InvariantCulture, "{0}", _parameters[i]);
            }
            return sb.ToString();
        }
 
        /// <summary>
        /// Loads the formula parameters from string.
        /// </summary>
        /// <param name="parameters">Csv string with parameters.</param>
        internal virtual void LoadParametersFromString(string parameters)
        {
            if (String.IsNullOrEmpty(parameters))
                return;
 
            string[] paramStringList = parameters.Split(',');
            int paramStringIndex = 0;
            for (int i = 0; i < _parameters.Length && paramStringIndex < paramStringList.Length; i++)
            {
                string newParamValue = paramStringList[paramStringIndex++];
                if (!String.IsNullOrEmpty(newParamValue))
                {
                    _parameters[i] = ParseParameter(i, newParamValue);
                }
            }
        }
 
        /// <summary>
        /// Parses the formula parameter.
        /// </summary>
        /// <param name="index">The param index.</param>
        /// <param name="newParamValue">The parameter value string.</param>
        /// <returns>Parameter value.</returns>
        internal virtual object ParseParameter(int index, string newParamValue)
        {
            object param = _parameters[index];
            if (param is int)
            {
                return Convert.ToInt32(newParamValue, CultureInfo.InvariantCulture);
            }
            else if (param is bool)
            {
                return Convert.ToBoolean(newParamValue, CultureInfo.InvariantCulture);
            }
            else if (param is double)
            {
                return Convert.ToDouble(newParamValue, CultureInfo.InvariantCulture);
            }
            return null;
        }
 
        /// <summary>
        /// Checks the formula parameter string.
        /// </summary>
        /// <param name="parameters">The parameters.</param>
        internal virtual void CheckParameterString(string parameters)
        {
            if (String.IsNullOrEmpty(parameters))
                return;
 
            string[] paramStringList = parameters.Split(',');
            int paramStringIndex = 0;
            for (int i = 0; i < _parameters.Length && paramStringIndex < paramStringList.Length; i++)
            {
                string newParamValue = paramStringList[paramStringIndex++];
                if (!String.IsNullOrEmpty(newParamValue))
                {
                    try
                    {
                        ParseParameter(i, newParamValue);
                    }
                    catch (FormatException)
                    {
                        throw new ArgumentException(SR.ExceptionFormulaDataFormatInvalid(parameters));
                    }
                }
            }
        }
        #endregion
    }
 
    /// <summary>
    /// MovingAverage FormulaInfo
    /// </summary>
    internal class MovingAverageFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="MovingAverageFormulaInfo"/> class.
        /// </summary>
        public MovingAverageFormulaInfo()
            : this(2, false)                      //Defaults
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="MovingAverageFormulaInfo"/> class.
        /// </summary>
        /// <param name="period">The period.</param>
        /// <param name="startFromFirst">if set to <c>true</c> [start from first].</param>
        public MovingAverageFormulaInfo(int period, bool startFromFirst)
            : base(
                new DataField[] { DataField.Y }, //Input fields
                new DataField[] { DataField.Y }, //Output fields
                period, startFromFirst)
        {
        }
    }
 
    /// <summary>
    /// ExponentialMoving AverageFormulaInfo
    /// </summary>
    internal class ExponentialMovingAverageFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="ExponentialMovingAverageFormulaInfo"/> class.
        /// </summary>
        public ExponentialMovingAverageFormulaInfo()
            : this(2, false)                      //Defaults
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="ExponentialMovingAverageFormulaInfo"/> class.
        /// </summary>
        /// <param name="period">The period.</param>
        /// <param name="startFromFirst">if set to <c>true</c> [start from first].</param>
        public ExponentialMovingAverageFormulaInfo(int period, bool startFromFirst)
            : base(
                new DataField[] { DataField.Y }, //Input fields
                new DataField[] { DataField.Y }, //Output fields
                period, startFromFirst)
        {
        }
    }
 
    /// <summary>
    /// WeightedMovingAverageFormulaInfo
    /// </summary>
    internal class WeightedMovingAverageFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="WeightedMovingAverageFormulaInfo"/> class.
        /// </summary>
        public WeightedMovingAverageFormulaInfo()
            : this(2, false)                      //Defaults
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="WeightedMovingAverageFormulaInfo"/> class.
        /// </summary>
        /// <param name="period">The period.</param>
        /// <param name="startFromFirst">if set to <c>true</c> [start from first].</param>
        public WeightedMovingAverageFormulaInfo(int period, bool startFromFirst)
           : base(
                new DataField[] { DataField.Y }, //Input fields
                new DataField[] { DataField.Y }, //Output fields
                period, startFromFirst)
        {
        }
    }
 
    /// <summary>
    /// TriangularMovingAverage FormulaInfo
    /// </summary>
    internal class TriangularMovingAverageFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="TriangularMovingAverageFormulaInfo"/> class.
        /// </summary>
        public TriangularMovingAverageFormulaInfo()
            : this(2, false)                      //Defaults
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="TriangularMovingAverageFormulaInfo"/> class.
        /// </summary>
        /// <param name="period">The period.</param>
        /// <param name="startFromFirst">if set to <c>true</c> [start from first].</param>
        public TriangularMovingAverageFormulaInfo(int period, bool startFromFirst)
            : base(
                new DataField[] { DataField.Y }, //Input fields
                new DataField[] { DataField.Y }, //Output fields
                period, startFromFirst)
        {
        }
    }
 
    /// <summary>
    /// TripleExponentialMovingAverage FormulaInfo
    /// </summary>
    internal class TripleExponentialMovingAverageFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="TripleExponentialMovingAverageFormulaInfo"/> class.
        /// </summary>
        public TripleExponentialMovingAverageFormulaInfo()
            : this(12)                           //Defaults
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="TripleExponentialMovingAverageFormulaInfo"/> class.
        /// </summary>
        /// <param name="period">The period.</param>
        public TripleExponentialMovingAverageFormulaInfo(int period)
            : base(
                new DataField[] { DataField.Y }, //Input fields
                new DataField[] { DataField.Y }, //Output fields
                period)
        {
        }
    }
 
    /// <summary>
    /// BollingerBands FormulaInfo
    /// </summary>
    internal class BollingerBandsFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="BollingerBandsFormulaInfo"/> class.
        /// </summary>
        public BollingerBandsFormulaInfo()
            : this(3, 2, true)                                          //Defaults
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="BollingerBandsFormulaInfo"/> class.
        /// </summary>
        /// <param name="period">The period.</param>
        /// <param name="deviation">The deviation.</param>
        /// <param name="startFromFirst">if set to <c>true</c> [start from first].</param>
        public BollingerBandsFormulaInfo(int period, double deviation, bool startFromFirst)
            : base(
                new DataField[] { DataField.Y },                        //Input fields
                new DataField[] { DataField.Top, DataField.Bottom },    //Output fields
                period, deviation, startFromFirst)
        {
        }
    }
 
    /// <summary>
    /// TypicalPrice FormulaInfo
    /// </summary>
    internal class TypicalPriceFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="TypicalPriceFormulaInfo"/> class.
        /// </summary>
        public TypicalPriceFormulaInfo()
            : base(
                new DataField[] { DataField.Close, DataField.High, DataField.Low }, //Input fields
                new DataField[] { DataField.Y })                                    //Output fields
        {
        }
    }
 
    /// <summary>
    /// WeightedClose FormulaInfo
    /// </summary>
    internal class WeightedCloseFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="WeightedCloseFormulaInfo"/> class.
        /// </summary>
        public WeightedCloseFormulaInfo()
            : base(
                new DataField[] { DataField.Close, DataField.High, DataField.Low }, //Input fields
                new DataField[] { DataField.Y })                                    //Output fields
        {
        }
    }
 
    /// <summary>
    /// MedianPrice FormulaInfo
    /// </summary>
    internal class MedianPriceFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="MedianPriceFormulaInfo"/> class.
        /// </summary>
        public MedianPriceFormulaInfo()
            : base(
                new DataField[] { DataField.High, DataField.Low }, //Input fields
                new DataField[] { DataField.Y })                    //Output fields
        {
        }
    }
 
 
    /// <summary>
    /// Envelopes FormulaInfo
    /// </summary>
    internal class EnvelopesFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="EnvelopesFormulaInfo"/> class.
        /// </summary>
        public EnvelopesFormulaInfo()
            : this(2, 10, true)                                          //Defaults
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="EnvelopesFormulaInfo"/> class.
        /// </summary>
        /// <param name="period">The period.</param>
        /// <param name="shiftPercentage">The shift percentage.</param>
        /// <param name="startFromFirst">if set to <c>true</c> [start from first].</param>
        public EnvelopesFormulaInfo(int period, double shiftPercentage, bool startFromFirst)
            : base(
                new DataField[] { DataField.Y },                        //Input fields
                new DataField[] { DataField.Top, DataField.Bottom },    //Output fields
                period, shiftPercentage, startFromFirst)
        {
        }
    }
 
 
    /// <summary>
    /// StandardDeviation FormulaInfo
    /// </summary>
    internal class StandardDeviationFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="StandardDeviationFormulaInfo"/> class.
        /// </summary>
        public StandardDeviationFormulaInfo()
            : this(2, false)                      //Defaults
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="StandardDeviationFormulaInfo"/> class.
        /// </summary>
        /// <param name="period">The period.</param>
        /// <param name="startFromFirst">if set to <c>true</c> [start from first].</param>
        public StandardDeviationFormulaInfo(int period, bool startFromFirst)
            : base(
                new DataField[] { DataField.Y }, //Input fields
                new DataField[] { DataField.Y }, //Output fields
                period, startFromFirst)
        {
        }
    }
 
    /// <summary>
    /// ChaikinOscillatorFormulaInfo
    /// </summary>
    internal class ChaikinOscillatorFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="ChaikinOscillatorFormulaInfo"/> class.
        /// </summary>
        public ChaikinOscillatorFormulaInfo()
            : this(3, 10, false)                      //Defaults
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="ChaikinOscillatorFormulaInfo"/> class.
        /// </summary>
        /// <param name="shortPeriod">The short period.</param>
        /// <param name="longPeriod">The long period.</param>
        /// <param name="startFromFirst">if set to <c>true</c> [start from first].</param>
        public ChaikinOscillatorFormulaInfo(int shortPeriod, int longPeriod, bool startFromFirst)
            : base(
                new DataField[] { DataField.High, DataField.Low, DataField.Close, DataField.Y }, //Input fields
                new DataField[] { DataField.Y }, //Output fields
                shortPeriod, longPeriod, startFromFirst)
        {
        }
    }
 
    /// <summary>
    /// DetrendedPriceOscillator FormulaInfo
    /// </summary>
    internal class DetrendedPriceOscillatorFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="DetrendedPriceOscillatorFormulaInfo"/> class.
        /// </summary>
        public DetrendedPriceOscillatorFormulaInfo()
            : this(2, false)                      //Defaults
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="DetrendedPriceOscillatorFormulaInfo"/> class.
        /// </summary>
        /// <param name="period">The period.</param>
        /// <param name="startFromFirst">if set to <c>true</c> [start from first].</param>
        public DetrendedPriceOscillatorFormulaInfo(int period, bool startFromFirst)
            : base(
                new DataField[] { DataField.Y }, //Input fields
                new DataField[] { DataField.Y }, //Output fields
                period, startFromFirst)
        {
        }
    }
 
 
    /// <summary>
    /// VolatilityChaikins FormulaInfo
    /// </summary>
    internal class VolatilityChaikinsFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="VolatilityChaikinsFormulaInfo"/> class.
        /// </summary>
        public VolatilityChaikinsFormulaInfo()
            : this(10, 10)                      //Defaults
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="VolatilityChaikinsFormulaInfo"/> class.
        /// </summary>
        /// <param name="period">The period.</param>
        /// <param name="signalPeriod">The signal period.</param>
        public VolatilityChaikinsFormulaInfo(int period, int signalPeriod)
            : base(
                new DataField[] { DataField.High, DataField.Low }, //Input fields
                new DataField[] { DataField.Y }, //Output fields
                period, signalPeriod)
        {
        }
    }
 
    /// <summary>
    /// VolumeOscillator FormulaInfo
    /// </summary>
    internal class VolumeOscillatorFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="VolumeOscillatorFormulaInfo"/> class.
        /// </summary>
        public VolumeOscillatorFormulaInfo()
            : this(5, 10, true)                      //Defaults
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="VolumeOscillatorFormulaInfo"/> class.
        /// </summary>
        /// <param name="shortPeriod">The short period.</param>
        /// <param name="longPeriod">The long period.</param>
        /// <param name="percentage">if set to <c>true</c> [percentage].</param>
        public VolumeOscillatorFormulaInfo(int shortPeriod, int longPeriod, bool percentage)
            : base(
                new DataField[] { DataField.Y }, //Input fields
                new DataField[] { DataField.Y }, //Output fields
                shortPeriod, longPeriod, percentage)
        {
        }
    }
 
    /// <summary>
    /// StochasticIndicatorFormulaInfo
    /// </summary>
    internal class StochasticIndicatorFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="StochasticIndicatorFormulaInfo"/> class.
        /// </summary>
        public StochasticIndicatorFormulaInfo()
            : this(10, 10)                      //Defaults
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="StochasticIndicatorFormulaInfo"/> class.
        /// </summary>
        /// <param name="periodD">The period D.</param>
        /// <param name="periodK">The period K.</param>
        public StochasticIndicatorFormulaInfo(int periodD, int periodK)
            : base(
                new DataField[] { DataField.High, DataField.Low, DataField.Close }, //Input fields
                new DataField[] { DataField.Y, DataField.Y }, //Output fields
                periodD, periodK)
        {
        }
    }
 
    /// <summary>
    /// WilliamsRFormulaInfo
    /// </summary>
    internal class WilliamsRFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="WilliamsRFormulaInfo"/> class.
        /// </summary>
        public WilliamsRFormulaInfo()
            : this(14)                      //Defaults
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="WilliamsRFormulaInfo"/> class.
        /// </summary>
        /// <param name="period">The period.</param>
        public WilliamsRFormulaInfo(int period)
            : base(
                new DataField[] { DataField.High, DataField.Low, DataField.Close }, //Input fields
                new DataField[] { DataField.Y }, //Output fields
                period)
        {
        }
    }
 
    /// <summary>
    /// AverageTrueRange FormulaInfo
    /// </summary>
    internal class AverageTrueRangeFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="AverageTrueRangeFormulaInfo"/> class.
        /// </summary>
        public AverageTrueRangeFormulaInfo()
            : this(14)                      //Defaults
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="AverageTrueRangeFormulaInfo"/> class.
        /// </summary>
        /// <param name="period">The period.</param>
        public AverageTrueRangeFormulaInfo(int period)
            : base(
                new DataField[] { DataField.High, DataField.Low, DataField.Close }, //Input fields
                new DataField[] { DataField.Y }, //Output fields
                period)
        {
        }
    }
 
    /// <summary>
    /// EaseOfMovement FormulaInfo
    /// </summary>
    internal class EaseOfMovementFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="EaseOfMovementFormulaInfo"/> class.
        /// </summary>
        public EaseOfMovementFormulaInfo()
            : base(
                new DataField[] { DataField.High, DataField.Low, DataField.Close }, //Input fields
                new DataField[] { DataField.Y }) //Output fields
        {
        }
    }
 
    /// <summary>
    /// MassIndex FormulaInfo
    /// </summary>
    internal class MassIndexFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="MassIndexFormulaInfo"/> class.
        /// </summary>
        public MassIndexFormulaInfo()
            : this(25, 9)                      //Defaults
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="MassIndexFormulaInfo"/> class.
        /// </summary>
        /// <param name="period">The period.</param>
        /// <param name="averagePeriod">The average period.</param>
        public MassIndexFormulaInfo(int period, int averagePeriod)
            : base(
                new DataField[] { DataField.High, DataField.Low }, //Input fields
                new DataField[] { DataField.Y }, //Output fields
                period, averagePeriod)
        {
        }
    }
 
    /// <summary>
    /// Performance FormulaInfo
    /// </summary>
    internal class PerformanceFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="PerformanceFormulaInfo"/> class.
        /// </summary>
        public PerformanceFormulaInfo()
            : base(
                new DataField[] { DataField.Close }, //Input fields
                new DataField[] { DataField.Y }) //Output fields
        {
        }
    }
 
    /// <summary>
    /// RateOfChange FormulaInfo
    /// </summary>
    internal class RateOfChangeFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="RateOfChangeFormulaInfo"/> class.
        /// </summary>
        public RateOfChangeFormulaInfo()
            : this(10)                      //Defaults
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="RateOfChangeFormulaInfo"/> class.
        /// </summary>
        /// <param name="period">The period.</param>
        public RateOfChangeFormulaInfo(int period)
            : base(
                new DataField[] { DataField.Close }, //Input fields
                new DataField[] { DataField.Y }, //Output fields
                period)
        {
        }
    }
 
    /// <summary>
    /// RelativeStrengthIndex FormulaInfo
    /// </summary>
    internal class RelativeStrengthIndexFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="RelativeStrengthIndexFormulaInfo"/> class.
        /// </summary>
        public RelativeStrengthIndexFormulaInfo()
            : this(10)                      //Defaults
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="RelativeStrengthIndexFormulaInfo"/> class.
        /// </summary>
        /// <param name="period">The period.</param>
        public RelativeStrengthIndexFormulaInfo(int period)
            : base(
                new DataField[] { DataField.Close }, //Input fields
                new DataField[] { DataField.Y }, //Output fields
                period)
        {
        }
    }
 
    /// <summary>
    /// MovingAverageConvergenceDivergence FormulaInfo
    /// </summary>
    internal class MovingAverageConvergenceDivergenceFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="MovingAverageConvergenceDivergenceFormulaInfo"/> class.
        /// </summary>
        public MovingAverageConvergenceDivergenceFormulaInfo()
            : this(12, 26)                      //Defaults
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="MovingAverageConvergenceDivergenceFormulaInfo"/> class.
        /// </summary>
        /// <param name="shortPeriod">The short period.</param>
        /// <param name="longPeriod">The long period.</param>
        public MovingAverageConvergenceDivergenceFormulaInfo(int shortPeriod, int longPeriod)
            : base(
                new DataField[] { DataField.Close }, //Input fields
                new DataField[] { DataField.Y }, //Output fields
                shortPeriod, longPeriod)
        {
        }
    }
 
    /// <summary>
    /// CommodityChannelIndex FormulaInfo
    /// </summary>
    internal class CommodityChannelIndexFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="CommodityChannelIndexFormulaInfo"/> class.
        /// </summary>
        public CommodityChannelIndexFormulaInfo()
            : this(10)                      //Defaults
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="CommodityChannelIndexFormulaInfo"/> class.
        /// </summary>
        /// <param name="period">The period.</param>
        public CommodityChannelIndexFormulaInfo(int period)
            : base(
                new DataField[] { DataField.High, DataField.Low, DataField.Close }, //Input fields
                new DataField[] { DataField.Y }, //Output fields
                period)
        {
        }
    }
 
    /// <summary>
    /// Forecasting FormulaInfo
    /// </summary>
    internal class ForecastingFormulaInfo : FormulaInfo
    {
        //Fields
        string _parameters;
 
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="ForecastingFormulaInfo"/> class.
        /// </summary>
        public ForecastingFormulaInfo()
            : this(TimeSeriesAndForecasting.RegressionType.Polynomial, 2, 0, true, true)                      //Defaults
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="ForecastingFormulaInfo"/> class.
        /// </summary>
        /// <param name="regressionType">Type of the regression.</param>
        /// <param name="polynomialDegree">The polynomial degree.</param>
        /// <param name="forecastingPeriod">The forecasting period.</param>
        /// <param name="returnApproximationError">if set to <c>true</c> [return approximation error].</param>
        /// <param name="returnForecastingError">if set to <c>true</c> [return forecasting error].</param>
        public ForecastingFormulaInfo(TimeSeriesAndForecasting.RegressionType regressionType, int polynomialDegree, int forecastingPeriod, bool returnApproximationError, bool returnForecastingError)
            : base(
                new DataField[] { DataField.Close }, //Input fields
                new DataField[] { DataField.Close, DataField.High, DataField.Low }, //Output fields
                regressionType, polynomialDegree, forecastingPeriod, returnApproximationError, returnForecastingError)
        {
        }
 
        //Methods
        /// <summary>
        /// Loads the formula parameters from string.
        /// </summary>
        /// <param name="parameters">Csv string with parameters.</param>
        internal override void LoadParametersFromString(string parameters)
        {
            _parameters = parameters;
        }
 
        /// <summary>
        /// Checks the formula parameter string.
        /// </summary>
        /// <param name="parameters">The parameters.</param>
        internal override void CheckParameterString(string parameters)
        {
            if (String.IsNullOrEmpty(parameters))
                return;
 
            string[] paramStringList = parameters.Split(',');
            int paramStringIndex = 1;
            //Don't check the first param
            for (int i = 2; i < Parameters.Length && paramStringIndex < paramStringList.Length; i++)
            {
                string newParamValue = paramStringList[paramStringIndex++];
                if (!String.IsNullOrEmpty(newParamValue))
                {
                    try
                    {
                        ParseParameter(i, newParamValue);
                    }
                    catch (FormatException)
                    {
                        throw new ArgumentException(SR.ExceptionFormulaDataFormatInvalid(parameters));
                    }
                }
            }
        }
 
        /// <summary>
        /// Saves the formula parameters to a string.
        /// </summary>
        /// <returns>Csv string with parameters</returns>
        internal override string SaveParametersToString()
        {
            if (String.IsNullOrEmpty(_parameters))
                return _parameters;
            else
                return "2,0,true,true";
        }
    }
 
    /// <summary>
    /// MoneyFlow FormulaInfo
    /// </summary>
    internal class MoneyFlowFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="MoneyFlowFormulaInfo"/> class.
        /// </summary>
        public MoneyFlowFormulaInfo()
            : this(2)                      //Defaults
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="MoneyFlowFormulaInfo"/> class.
        /// </summary>
        /// <param name="period">The period.</param>
        public MoneyFlowFormulaInfo(int period)
            : base(
                new DataField[] { DataField.High, DataField.Low, DataField.Close, DataField.Y }, //Input fields: High,Low,Close,Volume
                new DataField[] { DataField.Y }, //Output fields
                period)
        {
        }
    }
 
    /// <summary>
    /// PriceVolumeTrend FormulaInfo
    /// </summary>
    internal class PriceVolumeTrendFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="PriceVolumeTrendFormulaInfo"/> class.
        /// </summary>
        public PriceVolumeTrendFormulaInfo()
            : base(
                new DataField[] { DataField.Close, DataField.Y }, //Input=Close,Volume
                new DataField[] { DataField.Y }) //Output fields
        {
        }
    }
 
    /// <summary>
    /// OnBalanceVolume FormulaInfo
    /// </summary>
    internal class OnBalanceVolumeFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="OnBalanceVolumeFormulaInfo"/> class.
        /// </summary>
        public OnBalanceVolumeFormulaInfo()
            : base(
                new DataField[] { DataField.Close, DataField.Y }, //Input=Close,Volume
                new DataField[] { DataField.Y }) //Output fields
        {
        }
    }
 
    /// <summary>
    /// NegativeVolumeIndex FormulaInfo
    /// </summary>
    internal class NegativeVolumeIndexFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="NegativeVolumeIndexFormulaInfo"/> class.
        /// </summary>
        public NegativeVolumeIndexFormulaInfo() //Note about parameters: Start value is mandatory so we don't provide the default
            : this(double.NaN)
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="NegativeVolumeIndexFormulaInfo"/> class.
        /// </summary>
        /// <param name="startValue">The start value.</param>
        public NegativeVolumeIndexFormulaInfo(double startValue)
            : base(
                new DataField[] { DataField.Close, DataField.Y }, //Input=Close,Volume
                new DataField[] { DataField.Y },
                startValue) //Output fields
        {
        }
    }
 
    /// <summary>
    /// PositiveVolumeIndex FormulaInfo
    /// </summary>
    internal class PositiveVolumeIndexFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="PositiveVolumeIndexFormulaInfo"/> class.
        /// </summary>
        public PositiveVolumeIndexFormulaInfo() //Note about parameters: Start value is mandatory so we don't provide the default
            : this(double.NaN)
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="PositiveVolumeIndexFormulaInfo"/> class.
        /// </summary>
        /// <param name="startValue">The start value.</param>
        public PositiveVolumeIndexFormulaInfo(double startValue)
            : base(
                new DataField[] { DataField.Close, DataField.Y }, //Input=Close,Volume
                new DataField[] { DataField.Y },
                startValue) //Output fields
        {
        }
    }
 
    /// <summary>
    /// AccumulationDistribution FormulaInfo
    /// </summary>
    internal class AccumulationDistributionFormulaInfo : FormulaInfo
    {
        //Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="AccumulationDistributionFormulaInfo"/> class.
        /// </summary>
        public AccumulationDistributionFormulaInfo() //Note about parameters: Start value is mandatory so we don't provide the default
            : base(
                new DataField[] { DataField.High, DataField.Low, DataField.Close, DataField.Y }, //Input=High, Low, Close, Volume
                new DataField[] { DataField.Y }) //Output fields
        {
        }
    }
 
    #endregion
 
    #region enum DataField
    /// <summary>
    /// Chart data fields
    /// </summary>
    internal enum DataField
    {
        X,
        Y,
        LowerWisker,
        UpperWisker,
        LowerBox,
        UpperBox,
        Average,
        Median,
        Bubble,
        BubbleSize,
        High,
        Low,
        Open,
        Close,
        Center,
        LowerError,
        UpperError,
        Top,
        Bottom
    }
    #endregion
 
    #region class SeriesFieldInfo
    /// <summary>
    /// SeriesFieldInfo class is a OO representation formula input/output data params ("Series1:Y2")
    /// </summary>
    internal class SeriesFieldInfo
    {
        #region Fields
        private Series _series;
        private string _seriesName;
        private DataField _dataField;
        #endregion
 
        #region Properties
        /// <summary>
        /// Gets the series.
        /// </summary>
        /// <value>The series.</value>
        public Series Series 
        { 
            get { return _series; }
        }
        /// <summary>
        /// Gets the name of the series.
        /// </summary>
        /// <value>The name of the series.</value>
        public string SeriesName
        {
            get { return _series != null ? _series.Name : _seriesName; }
        }
        /// <summary>
        /// Gets the data field.
        /// </summary>
        /// <value>The data field.</value>
        public DataField DataField 
        { 
            get { return _dataField; }
        }
        #endregion
 
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="SeriesFieldInfo"/> class.
        /// </summary>
        /// <param name="series">The series.</param>
        /// <param name="dataField">The data field.</param>
        public SeriesFieldInfo(Series series, DataField dataField)
        {
            _series = series;
            _dataField = dataField;
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="SeriesFieldInfo"/> class.
        /// </summary>
        /// <param name="seriesName">Name of the series.</param>
        /// <param name="dataField">The data field.</param>
        public SeriesFieldInfo(string seriesName, DataField dataField)
        {
            _seriesName = seriesName;
            _dataField = dataField;
        }
        #endregion
    }
    #endregion
 
    #region class SeriesFieldList
    /// <summary>
    /// SeriesFieldInfo class is a OO representation formula input/output data params ("Series1:Y2,Series2.Y4")
    /// </summary>
    internal class SeriesFieldList : List<SeriesFieldInfo>
    {
        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </returns>
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < this.Count; i++)
            {
                SeriesFieldInfo info = this[i];
 
                if (i > 0)
                    sb.Append(',');
 
                SeriesChartType seriesChartType = info.Series != null ?
                                                        info.Series.ChartType :
                                                        FormulaHelper.GetDefaultChartType(info.DataField);
 
                IList<DataField> dataFields = FormulaHelper.GetDataFields(seriesChartType);
 
                int dataFieldIndex = dataFields.IndexOf(info.DataField);
                if (dataFieldIndex == 0)
                    sb.AppendFormat(CultureInfo.InvariantCulture, "{0}:Y", info.SeriesName); //The string field descriptor is 1 based ;-(
                else
                    sb.AppendFormat(CultureInfo.InvariantCulture, "{0}:Y{1}", info.SeriesName, dataFieldIndex + 1); //The string field descriptor is 1 based ;-(
            }
            return sb.ToString();
        }
 
        //Static
        /// <summary>
        /// Parse the string defining the formula's input/output series and fields.
        /// </summary>
        /// <param name="chart">The chart.</param>
        /// <param name="seriesFields">The series fields list. The series name can be followed by the field names. For example: "Series1:Y,Series1:Y3,Series2:Close"</param>
        /// <param name="formulaFields">The formula fields list.</param>
        /// <returns></returns>
        public static SeriesFieldList FromString(Chart chart, string seriesFields, IList<DataField> formulaFields)
        {
            SeriesFieldList result = new SeriesFieldList();
            if (String.IsNullOrEmpty(seriesFields))
            {
                return result;
            }
 
            List<DataField> unmappedFormulaFields = new List<DataField>(formulaFields);
 
            //Loop through the series/field pairs
            foreach (string seriesField in seriesFields.Split(','))
            {
                //Stop processing if all the formula fields are mapped
                if (unmappedFormulaFields.Count == 0)
                    break;
 
                //Split a pair into a series + field
                string[] seriesFieldParts = seriesField.Split(':');
                if (seriesFieldParts.Length > 2)
                {
                    throw new ArgumentException(SR.ExceptionFormulaDataFormatInvalid(seriesField));
                }
 
                //Get the series and series fields
                string seriesName = seriesFieldParts[0].Trim();
                Series series = chart.Series.FindByName(seriesName);
                if (series != null)
                {
                    switch (seriesFieldParts.Length)
                    {
                        case 1: //Only series name is specified: "Series1"
                            AddSeriesFieldInfo(result, series, unmappedFormulaFields);
                            break;
                        case 2: //Series and field names are provided: "Series1:Y3"
                            AddSeriesFieldInfo(result, series, unmappedFormulaFields, seriesFieldParts[1]);
                            break;
                    }
                }
                else
                {
                    switch (seriesFieldParts.Length)
                    {
                        case 1: //Only series name is specified: "Series1"
                            AddSeriesFieldInfo(result, seriesName, unmappedFormulaFields);
                            break;
                        case 2: //Series and field names are provided: "Series1:Y3"
                            AddSeriesFieldInfo(result, seriesName, unmappedFormulaFields, seriesFieldParts[1]);
                            break;
                    }
                }
            }
            return result;
        }
 
        /// <summary>
        /// Adds the series field info.
        /// </summary>
        /// <param name="result">The result.</param>
        /// <param name="series">The series.</param>
        /// <param name="unmappedFormulaFields">The unmapped formula fields.</param>
        private static void AddSeriesFieldInfo(SeriesFieldList result, Series series, IList<DataField> unmappedFormulaFields)
        {
            List<DataField> seriesFields = new List<DataField>(FormulaHelper.GetDataFields(series.ChartType));
 
            for (int i = 0; i < unmappedFormulaFields.Count && seriesFields.Count > 0; )
            {
                DataField formulaField = unmappedFormulaFields[i];
                DataField? seriesField = null;
 
                // Case 1. Check if the formulaField is valid for this chart type
                if (seriesFields.Contains(formulaField))
                {
                    seriesField = formulaField;
                }
                // Case 2. Try to map the formula field to the series field
                if (seriesField == null)
                {
                    seriesField = FormulaHelper.MapFormulaDataField(series.ChartType, formulaField);
                }
 
                // If the seriesField is found - add it to the results
                if (seriesField != null)
                {
                    result.Add(new SeriesFieldInfo(series, (DataField)seriesField));
                    seriesFields.Remove((DataField)formulaField);
                    unmappedFormulaFields.Remove(formulaField);
                }
                else
                {
                    i++;
                }
            }
        }
        /// <summary>
        /// Adds the series field info.
        /// </summary>
        /// <param name="result">The result.</param>
        /// <param name="series">The series.</param>
        /// <param name="unmappedFormulaFields">The unmapped formula fields.</param>
        /// <param name="seriesFieldId">The series field id.</param>
        private static void AddSeriesFieldInfo(SeriesFieldList result, Series series, IList<DataField> unmappedFormulaFields, string seriesFieldId)
        {
            IList<DataField> seriesFields = FormulaHelper.GetDataFields(series.ChartType);
 
            DataField? seriesField = null;
 
            seriesFieldId = seriesFieldId.ToUpperInvariant().Trim();
            if (seriesFieldId == "Y")
            {
                seriesField = seriesFields[0];
            }
            else if (seriesFieldId.StartsWith("Y", StringComparison.Ordinal))
            {
                int id = 0;
                if (int.TryParse(seriesFieldId.Substring(1), out id))
                    if (id - 1 < seriesFields.Count)
                    {
                        seriesField = seriesFields[id - 1];
                    }
                    else
                    {
                        throw (new ArgumentException(SR.ExceptionFormulaYIndexInvalid, seriesFieldId));
                    }
            }
            else
            {
                seriesField = (DataField)Enum.Parse(typeof(DataField), seriesFieldId, true);
            }
 
            // Add the seriesField to the results
            if (seriesField != null)
            {
                result.Add(new SeriesFieldInfo(series, (DataField)seriesField));
                if (unmappedFormulaFields.Contains((DataField)seriesField))
                    unmappedFormulaFields.Remove((DataField)seriesField);
                else
                    unmappedFormulaFields.RemoveAt(0);
            }
            else
            {
                throw new ArgumentException(SR.ExceptionDataPointValueNameInvalid, seriesFieldId);
            }
 
        }
 
        /// <summary>
        /// Adds the series field info.
        /// </summary>
        /// <param name="result">The result.</param>
        /// <param name="seriesName">Name of the series.</param>
        /// <param name="unmappedFormulaFields">The unmapped formula fields.</param>
        private static void AddSeriesFieldInfo(SeriesFieldList result, string seriesName, IList<DataField> unmappedFormulaFields)
        {
            SeriesChartType chartType = FormulaHelper.GetDefaultChartType(unmappedFormulaFields[0]);
            List<DataField> seriesFields = new List<DataField>(FormulaHelper.GetDataFields(chartType));
 
            for (int i = 0; i < unmappedFormulaFields.Count && seriesFields.Count > 0; )
            {
                DataField formulaField = unmappedFormulaFields[i];
                DataField? seriesField = null;
 
                // Check if the formulaField is valid for this chart type
                if (seriesFields.Contains(formulaField))
                {
                    seriesField = formulaField;
                }
 
                // If the seriesField is found - add it to the results
                if (seriesField != null)
                {
                    result.Add(new SeriesFieldInfo(seriesName, (DataField)seriesField));
                    seriesFields.Remove((DataField)formulaField);
                    unmappedFormulaFields.Remove(formulaField);
                }
                else
                {
                    i++;
                }
            }
        }
        /// <summary>
        /// Adds the series field info.
        /// </summary>
        /// <param name="result">The result.</param>
        /// <param name="seriesName">Name of the series.</param>
        /// <param name="unmappedFormulaFields">The unmapped formula fields.</param>
        /// <param name="seriesFieldId">The series field id.</param>
        private static void AddSeriesFieldInfo(SeriesFieldList result, string seriesName, IList<DataField> unmappedFormulaFields, string seriesFieldId)
        {
            SeriesChartType chartType = FormulaHelper.GetDefaultChartType(unmappedFormulaFields[0]);
            IList<DataField> seriesFields = FormulaHelper.GetDataFields(chartType);
 
            //Find the field
            DataField? seriesField = null;
 
            seriesFieldId = seriesFieldId.ToUpperInvariant().Trim();
 
            if (seriesFieldId == "Y")
            {
                seriesField = seriesFields[0];
            }
            else if (seriesFieldId.StartsWith("Y", StringComparison.Ordinal))
            {
                int seriesFieldIndex = 0;
                if (int.TryParse(seriesFieldId.Substring(1), out seriesFieldIndex))
                    if (seriesFieldIndex < seriesFields.Count)
                    {
                        seriesField = seriesFields[seriesFieldIndex - 1];
                    }
                    else
                    {
                        throw (new ArgumentException(SR.ExceptionFormulaYIndexInvalid, seriesFieldId));
                    }
            }
            else
            {
                //Try parse the field name
                try
                {
                    seriesField = (DataField)Enum.Parse(typeof(DataField), seriesFieldId, true);
                }
                catch (ArgumentException)
                { }
            }
 
            if (seriesField != null)
            {
                result.Add(new SeriesFieldInfo(seriesName, (DataField)seriesField));
                unmappedFormulaFields.Remove((DataField)seriesField);
            }
            else
            {
                throw new ArgumentException(SR.ExceptionDataPointValueNameInvalid, seriesFieldId);
            }
        }
    }
    #endregion
 
}