File: Common\General\FormulaData.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=alexgor, deliant
//=================================================================
//  File:		DataFormula.cs
//
//  Namespace:	DataVisualization.Charting
//
//	Classes:	DataFormula
//
//  Purpose:	DataFormula class provides properties and methods, 
//				which prepare series data for technical analyses 
//				and time series and forecasting formulas and prepare 
//				output data to be displayed as a chart.
//
//	Reviewed:	GS - August 6, 2002
//				AG - August 7, 2002
//              AG - Microsoft 15, 2007
//
//===================================================================
 
#region Used Namespace
using System;
using System.Drawing;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Collections.Generic;
#endregion
 
#if Microsoft_CONTROL
using System.Windows.Forms.DataVisualization.Charting.Formulas;
 
namespace System.Windows.Forms.DataVisualization.Charting
#else
using System.Web.UI.DataVisualization.Charting.Formulas;
    
namespace System.Web.UI.DataVisualization.Charting
#endif
{
    #region Financial Formula Name enumeration
 
    /// <summary>
    /// An enumeration of financial formula names.
    /// </summary>
    public enum FinancialFormula
    {
        /// <summary>
        /// Accumulation Distribution formula. This indicator uses a relationship 
        /// between volume and prices to estimate the strength of price movements, 
        /// and if volume is increased, there is a high probability that prices will go up.
        /// </summary>
        AccumulationDistribution,
 
        /// <summary>
        /// Average True Range indicator.  It measures commitment and compares 
        /// the range between the High, Low and Close prices. 
        /// </summary>
        AverageTrueRange,
 
        /// <summary>
        /// Bollinger Bands indicators.  They are plotted at standard deviation levels 
        /// above and below a simple moving average.
        /// </summary>
        BollingerBands,
 
        /// <summary>
        /// Chaikin Oscillator indicator. It is the difference between a 3-day 
        /// exponential moving average and a 10-day exponential moving average 
        /// applied to the Accumulation Distribution.
        /// </summary>
        [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Chaikin")]
        ChaikinOscillator,
 
        /// <summary>
        /// Commodity Channel Index. It compares prices with their moving averages.
        /// </summary>
        CommodityChannelIndex,
 
        /// <summary>
        /// Detrended Price Oscillator.  It attempts to remove trend from prices. 
        /// </summary>
        [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Detrended")]
        DetrendedPriceOscillator,
 
        /// <summary>
        /// Ease of Movement deals with the relationship between volume and price change, 
        /// and uses volume to indicate how strong a trend is for prices.
        /// </summary>
        EaseOfMovement,
 
        /// <summary>
        /// Envelopes are plotted above and below a moving average using a specified percentage 
        /// as the shift.
        /// </summary>
        Envelopes,
 
        /// <summary>
        /// An Exponential Moving Average is an average of data calculated over a period of time 
        /// where the most recent days have more weight.  
        /// </summary>
        ExponentialMovingAverage,
 
        /// <summary>
        /// Forecasting.  It predicts future values using historical observations.
        /// </summary>
        Forecasting,
 
        /// <summary>
        /// Moving Average Convergence/Divergence indicator.  It compares two 
        /// moving averages of prices and is used with a 9-day Exponential 
        /// Moving average as a signal, which indicates buying and selling moments.
        /// </summary>
        MovingAverageConvergenceDivergence,
 
        /// <summary>
        /// The Mass Index is used to predict trend reversal by comparing the 
        /// difference and range between High and Low prices. 
        /// </summary>
        MassIndex,
 
        /// <summary>
        /// Median prices are mid-point values of daily prices and can be used 
        /// as a filter for trend indicators. 
        /// </summary>
        MedianPrice,
 
        /// <summary>
        /// The Money Flow indicator compares upward changes and downward changes
        /// of volume-weighted typical prices. 
        /// </summary>
        MoneyFlow,
 
        /// <summary>
        /// The Negative Volume Index should be used together with the Positive Volume index, 
        /// and the Negative Volume Index only changes if the volume decreases from the previous day.
        /// </summary>
        NegativeVolumeIndex,
 
        /// <summary>
        /// The On Balance Volume indicator measures positive and negative volume flow.
        /// </summary>
        OnBalanceVolume,
 
        /// <summary>
        /// The Performance indicator compares a current closing price (or any other price) with 
        /// the first closing value (from the first time period).
        /// </summary>
        Performance,
 
        /// <summary>
        /// The Positive Volume Index should be used together with the Negative Volume index. 
        /// The Positive volume index only changes if the volume decreases from the previous day. 
        /// </summary>
        PositiveVolumeIndex,
 
        /// <summary>
        /// The Price Volume Trend is a cumulative volume total that is calculated using 
        /// relative changes of the closing price, and should be used with other indicators.  
        /// </summary>
        PriceVolumeTrend,
 
        /// <summary>
        /// The Rate of Change indicator compares a specified closing price with the current price. 
        /// </summary>
        RateOfChange,
 
        /// <summary>
        /// The Relative Strength Index is a momentum oscillator that compares upward movements 
        /// of the closing price with downward movements, and results in values that range from 0 to 100.
        /// </summary>
        RelativeStrengthIndex,
 
        /// <summary>
        /// A Simple Moving Average is an average of data calculated over a period of time. 
        /// The moving average is the most popular price indicator used in technical analysis, 
        /// and can be used with any price (e.g. Hi, Low, Open and Close) 
        /// or it can be applied to other indicators. 
        /// </summary>
        MovingAverage,
 
        /// <summary>
        /// Standard deviation is used to indicate volatility, and measures 
        /// the difference between values (e.g. closing price) and their moving average.  
        /// </summary>
        StandardDeviation,
 
        /// <summary>
        /// The Stochastic Indicator helps to find trend reversal by searching in a period for
        /// when the closing prices are close to low prices in an upward trending market
        /// and for when the closing prices are close to high prices in a downward trending market.
        /// </summary>
        StochasticIndicator,
 
        /// <summary>
        /// A Triangular Moving Average is an average of data calculated over a period of time 
        /// where the middle portion of data has more weight.
        /// </summary>
        TriangularMovingAverage,
 
        /// <summary>
        /// The Triple Exponential Moving Average is based on a triple moving average of the closing Price.
        /// Its purpose is to eliminate short cycles.  This indicator keeps the closing price 
        /// in trends that are shorter than the specified period. 
        /// </summary>
        TripleExponentialMovingAverage,
 
        /// <summary>
        /// Typical price is the average value of daily prices, and can be used as a filter for trend indicators.
        /// </summary>
        TypicalPrice,
 
        /// <summary>
        /// The Volatility Chaikins indicator measures the difference between High and Low prices, 
        /// and is used to indicate tops or bottoms of the market.
        /// </summary>
        [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Chaikins")]
        VolatilityChaikins,
 
        /// <summary>
        /// The Volume oscillator attempts to identify trends in volume by comparing two moving averages: 
        /// one with a short period and another with a longer period.
        /// </summary>
        VolumeOscillator,
 
        /// <summary>
        /// The Weighted Close formula calculates the average value of daily prices. 
        /// The only difference between Typical Price and the Weighted Close is that the closing price 
        /// has extra weight, and is considered the most important price. 
        /// </summary>
        WeightedClose,
 
        /// <summary>
        /// A Weighted Moving Average is an average of data calculated over a period of time, 
        /// where greater weight is attached to the most recent data. 
        /// </summary>
        WeightedMovingAverage,
 
        /// <summary>
        /// William's %R is a momentum indicator, and is used to measure overbought and oversold levels. 
        /// </summary>
        WilliamsR
    }
 
    #endregion  // Financial Formula Name enumeration
 
    /// <summary>
    /// The DataFormula class provides properties and methods, which prepare series 
    /// data for technical analysis, apply formulas on the series data 
    /// and prepare output data to be displayed as a chart.
    /// </summary>
#if ASPPERM_35
	[AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
        [AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
#endif
    public class DataFormula
    {
        #region Data Formulas fields
 
        internal const string IndexedSeriesLabelsSourceAttr = "__IndexedSeriesLabelsSource__";
 
        //***********************************************************
        //** Private data members, which store properties values
        //***********************************************************
        private bool _isEmptyPointIgnored = true;
 
        private string[] _extraParameters;
 
        /// <summary>
        /// All X values are zero.
        /// </summary>
        private bool _zeroXValues = false;
 
        /// <summary>
        /// Utility class for Statistical formulas
        /// </summary>
        private StatisticFormula _statistics;
 
        /// <summary>
        /// Reference to the Common elements
        /// </summary>
        internal CommonElements Common;
 
 
        #endregion
 
        #region Data Formulas methods
 
        /// <summary>
        /// Default constructor
        /// </summary>
        public DataFormula()
        {
            _statistics = new StatisticFormula(this);
 
            _extraParameters = new string[1];
            _extraParameters[0] = false.ToString(System.Globalization.CultureInfo.InvariantCulture);
        }
 
        /// <summary>
        /// This method calls a method from a formula module with 
        /// specified name.
        /// </summary>
        /// <param name="formulaName">Formula Name</param>
        /// <param name="parameters">Formula parameters</param>
        /// <param name="inputSeries">Comma separated input data series names and optional X and Y values names.</param>
        /// <param name="outputSeries">Comma separated output data series names and optional X and Y values names.</param>
        internal void Formula(string formulaName, string parameters, string inputSeries, string outputSeries)
        {            
            // Array of series
            Series[] inSeries;
            Series[] outSeries;
 
            // Commented out as InsertEmptyDataPoints is currently commented out.
            // This field is not used anywhere else, but we might need it if we uncomment all ---- code parts in this method. (krisztb 4/29/08)
            // True if formulas are statistical
            //bool statisticalFormulas = false;
 
            // Array of Y value indexes
            int[] inValueIndexes;
            int[] outValueIndexes;
 
            // Matrix with double values ( used in formula modules )
            double[][] inValues;
            double[][] inNoEmptyValues;
            double[][] outValues = null;
            string[][] outLabels = null;
 
            // Array with parameters
            string[] parameterList;
 
            // Split comma separated parameter list in the array of strings.
            SplitParameters(parameters, out parameterList);
 
            // Split comma separated series and Y values list in the array of 
            // Series and indexes to Y values.
            ConvertToArrays(inputSeries, out inSeries, out inValueIndexes, true);
            ConvertToArrays(outputSeries, out outSeries, out outValueIndexes, false);
 
            // Create indexes if all x values are 0
            //ConvertZeroXToIndex( ref inSeries );
 
            // Set X value AxisName for output series. 
            foreach (Series outSeriesItem in outSeries)
            {
                if (inSeries[0] != null)
                {
                    outSeriesItem.XValueType = inSeries[0].XValueType;
                }
            }
 
            // This method will convert array of Series and array of Y value 
            // indexes to matrix of double values.
            GetDoubleArray(inSeries, inValueIndexes, out inValues);
 
            // Remove columns with empty values from matrix
            if (!DifferentNumberOfSeries(inValues))
            {
                RemoveEmptyValues(inValues, out inNoEmptyValues);
            }
            else
            {
                inNoEmptyValues = inValues;
            }
 
            // Call a formula from formula modules
            string moduleName = null;
            for (int module = 0; module < Common.FormulaRegistry.Count; module++)
            {
                moduleName = Common.FormulaRegistry.GetModuleName(module);
                Common.FormulaRegistry.GetFormulaModule(moduleName).Formula(formulaName, inNoEmptyValues, out outValues, parameterList, _extraParameters, out outLabels);
 
                // Commented out as InsertEmptyDataPoints is currently commented out (see next block).
                // It set the statisticalFormulas field that was used to test whether to insert empty data points. (krisztb 4/29/08)
                //if( outValues != null )
                //{
                //    if (moduleName == SR.FormulaNameStatisticalAnalysis)
                //    {
                //        statisticalFormulas = true;
                //    }
                //    break;
                //}
 
                // Check if formula was found by detecting output 
                if (outValues != null)
                {
                    // Exit the loop
                    break;
                }
            }
 
            if (outValues == null)
                throw new ArgumentException(SR.ExceptionFormulaNotFound(formulaName));
 
            // Insert empty data points
 
            //
            // This has been commented out as InsertEmptyDataPoints is currently commented out.
            // In its current implementation it didn't do anything other than assign the second
            // parameter to the third, so ultimately it was a no op. --Microsoft 4/21/08
            //
            //if( !statisticalFormulas )
            //{
            //    InsertEmptyDataPoints( inValues, outValues, out outValues );
            //}
 
            // Fill Series with results from matrix with double values using Y value indexes.
            SetDoubleArray(outSeries, outValueIndexes, outValues, outLabels);
 
            if (_zeroXValues)
            {
                // we have indexed series : proceed to align output series.
                foreach (Series series in outSeries)
                {
                    if (series.Points.Count > 0)
                    {
                        // get the last xValue: the formula processing is 
                        double topXValue = series.Points[series.Points.Count - 1].XValue;
                        this.Common.Chart.DataManipulator.InsertEmptyPoints(1, IntervalType.Number, 0, IntervalType.Number, 1, topXValue, series);
                        foreach (DataPoint point in series.Points)
                        {
                            point.XValue = 0;
                        }
                    }
                }
            }
            // Copy axis labels from the original series into the calculated series            
            CopyAxisLabels(inSeries, outSeries);
        }
 
        /// <summary>
        /// Copy axis labels from the original series into the calculated series 
        /// </summary>
        /// <param name="inSeries">array of input series</param>
        /// <param name="outSeries">array of output series</param>
        private void CopyAxisLabels(Series[] inSeries, Series[] outSeries)
        {
            //Loop through the pairs of input and output series
            int seriesIndex = 0;
            while (seriesIndex < inSeries.Length && seriesIndex < outSeries.Length)
            {
                Series inputSeries = inSeries[seriesIndex];
                Series outputSeries = outSeries[seriesIndex];
 
                //Depending on whether or not the source series has X Values we need to use two different search algorithms
                if (_zeroXValues)
                {   //If we have the empty XValues then the source series should have all the AxisLabels
                    // -- set the indexed series labels source 
                    outputSeries[DataFormula.IndexedSeriesLabelsSourceAttr] = inputSeries.Name;
                }
                else
                {   //If the source series has XValues - loop through the input series points looking for the points with AxisLabels set
                    int outIndex = 0;
                    foreach (DataPoint inputPoint in inputSeries.Points)
                    {
                        if (!String.IsNullOrEmpty(inputPoint.AxisLabel))
                        {
                            //If the Axis label is set we need to find the corresponding point by the X value
                            //Most probably the points are in the same order so lets first try the corresponding point in the output series
                            if (outIndex < outputSeries.Points.Count && inputPoint.XValue == outputSeries.Points[outIndex].XValue)
                            {   // Yes, the corresponding point in the outputSeries has the same XValue as inputPoint -> copy axis label
                                outputSeries.Points[outIndex].AxisLabel = inputPoint.AxisLabel;
                            }
                            else
                            {
                                //The correspong point has a different x value -> lets go through output series and find the value with the same X
                                outIndex = 0;
                                foreach (DataPoint outputPoint in outputSeries.Points)
                                {
                                    if (inputPoint.XValue == outputPoint.XValue)
                                    {   //Found the point with the same XValue - copy axis label and break
                                        outputPoint.AxisLabel = inputPoint.AxisLabel;
                                        break;
                                    }
                                    outIndex++;
                                }
                            }
                        }
                        outIndex++;
                    }
                }
                //Sync next pair of input and output series...
                seriesIndex++;
            }
        }
 
 
        /// <summary>
        /// This method will set series X and Y values from matrix of 
        /// double values.
        /// </summary>
        /// <param name="outputSeries">Array of output series</param>
        /// <param name="valueIndex">Array of Y value indexes</param>
        /// <param name="outputValues">Array of doubles which will be used to fill series</param>
        /// <param name="outputLabels">Array of labels</param>
        internal void SetDoubleArray(Series[] outputSeries, int[] valueIndex, double[][] outputValues, string[][] outputLabels)
        {
            // Validation
            if (outputSeries.Length != valueIndex.Length)
            {
                throw new ArgumentException(SR.ExceptionFormulaDataItemsNumberMismatch);
            }
 
            // Number of output series is not correct
            if (outputSeries.Length < outputValues.Length - 1)
            {
                throw new ArgumentException(SR.ExceptionFormulaDataOutputSeriesNumberYValuesIncorrect);
            }
 
            int seriesIndex = 0;
            foreach (Series series in outputSeries)
            {
                if (seriesIndex + 1 > outputValues.Length - 1)
                {
                    break;
                }
 
                // If there is different number of data points.
                if (series.Points.Count != outputValues[seriesIndex].Length)
                {
                    // Delete all points
                    series.Points.Clear();
                }
 
                // Set the number of y values
                if (series.YValuesPerPoint < valueIndex[seriesIndex])
                {
                    series.YValuesPerPoint = valueIndex[seriesIndex];
                }
 
                for (int pointIndex = 0; pointIndex < outputValues[0].Length; pointIndex++)
                {
                    // Create a new series and fill data
                    if (series.Points.Count != outputValues[seriesIndex].Length)
                    {
                        // Add data points to series.
                        series.Points.AddXY(outputValues[0][pointIndex], 0);
 
                        // Set Labels
                        if (outputLabels != null)
                        {
                            series.Points[pointIndex].Label = outputLabels[seriesIndex][pointIndex];
                        }
 
                        // Set empty data points or Y values
                        if (Double.IsNaN(outputValues[seriesIndex + 1][pointIndex]))
                            series.Points[pointIndex].IsEmpty = true;
                        else
                            series.Points[pointIndex].YValues[valueIndex[seriesIndex] - 1] = outputValues[seriesIndex + 1][pointIndex];
                    }
                    // Use existing series and set Y values.
                    else
                    {
                        if (series.Points[pointIndex].XValue != outputValues[0][pointIndex] && !_zeroXValues)
                        {
                            throw new InvalidOperationException(SR.ExceptionFormulaXValuesNotAligned);
                        }
 
                        // Set empty data points or Y values
                        if (Double.IsNaN(outputValues[seriesIndex + 1][pointIndex]))
                            series.Points[pointIndex].IsEmpty = true;
                        else
                        {
                            series.Points[pointIndex].YValues[valueIndex[seriesIndex] - 1] = outputValues[seriesIndex + 1][pointIndex];
 
                            // Set Labels
                            if (outputLabels != null)
                            {
                                series.Points[pointIndex].Label = outputLabels[seriesIndex][pointIndex];
                            }
                        }
                    }
                }
                seriesIndex++;
            }
        }
 
        /// <summary>
        /// This method will convert a string with information about 
        /// series and y values to two arrays. The first array will 
        /// contain series and the second array will contain 
        /// corresponding indexes to y values for every series. 
        /// The arrays have to have the same number of items.
        /// </summary>
        /// <param name="inputString">A string with information about series and Y values</param>
        /// <param name="seiesArray">Array of Data Series</param>
        /// <param name="valueArray">Array of Y value indexes</param>
        /// <param name="inputSeries">Do not create new series if input series are used</param>
        private void ConvertToArrays(string inputString, out Series[] seiesArray, out int[] valueArray, bool inputSeries)
        {
            // Split string by comma
            string[] subStrings = inputString.Split(',');
 
            // Create array of series
            seiesArray = new Series[subStrings.Length];
 
            // Create array of integers - values
            valueArray = new int[subStrings.Length];
 
            int index = 0;
 
            foreach (string str in subStrings)
            {
                string[] parts = str.Split(':');
 
 
                // There must be at least one and no more than two result strings
                if (parts.Length < 1 && parts.Length > 2)
                {
                    throw (new ArgumentException(SR.ExceptionFormulaDataFormatInvalid(str)));
                }
 
                // Initialize value index as first Y value (default)
                int valueIndex = 1;
 
                // Check specified value type
                if (parts.Length == 2)
                {
                    if (parts[1].StartsWith("Y", StringComparison.Ordinal))
                    {
                        parts[1] = parts[1].TrimStart('Y');
 
                        if (parts[1].Length == 0)
                        {
                            valueIndex = 1;
                        }
                        else
                        {
                            // Try to convert the rest of the string to integer
                            try
                            {
                                valueIndex = Int32.Parse(parts[1], System.Globalization.CultureInfo.InvariantCulture);
                            }
                            catch (System.Exception)
                            {
                                throw (new ArgumentException(SR.ExceptionFormulaDataFormatInvalid(str)));
                            }
                        }
                    }
                    else
                    {
                        throw (new ArgumentException(SR.ExceptionFormulaDataSeriesNameNotFound(str)));
                    }
                }
 
                // Set Y value indexes
                valueArray[index] = valueIndex;
 
                // Set series
                try
                {
                    seiesArray[index] = Common.DataManager.Series[parts[0].Trim()];
                }
                catch (System.Exception)
                {
                    // Series doesn't exist.
                    if (!inputSeries)
                    {
                        // Create a new series if output series
                        Common.DataManager.Series.Add(new Series(parts[0]));
                        seiesArray[index] = Common.DataManager.Series[parts[0]];
                    }
                    else
                        throw (new ArgumentException(SR.ExceptionFormulaDataSeriesNameNotFoundInCollection(str)));
                }
                index++;
            }
        }
 
 
        /// <summary>
        /// Returns Jagged Arrays of doubles from array of series. 
        /// A jagged array is merely an array of arrays and
        /// it doesn't have to be square. The first item is array of 
        /// X values from the first series
        /// </summary>
        /// <param name="inputSeries">Array of Data Series</param>
        /// <param name="valueIndex">Array with indexes which represent value from data point: 0 = X, 1 = Y, 2 = Y2, 3 = Y3</param>
        /// <param name="output">Jagged Arrays of doubles</param>
        private void GetDoubleArray(Series[] inputSeries, int[] valueIndex, out double[][] output)
        {
            GetDoubleArray(inputSeries, valueIndex, out output, false);
        }
 
        /// <summary>
        /// Returns Jagged Arrays of doubles from array of series. 
        /// A jagged array is merely an array of arrays and
        /// it doesn't have to be square. The first item is array of 
        /// X values from the first series
        /// </summary>
        /// <param name="inputSeries">Array of Data Series</param>
        /// <param name="valueIndex">Array with indexes which represent value from data point: 0 = X, 1 = Y, 2 = Y2, 3 = Y3</param>
        /// <param name="output">Jagged Arrays of doubles</param>
        /// <param name="ignoreZeroX">Ignore Zero X values</param>
        private void GetDoubleArray(Series[] inputSeries, int[] valueIndex, out double[][] output, bool ignoreZeroX)
        {
            // Allocate a memory.
            output = new double[inputSeries.Length + 1][];
 
            // Check the length of the array of series and array of value indexes.
            if (inputSeries.Length != valueIndex.Length)
            {
                throw new ArgumentException(SR.ExceptionFormulaDataItemsNumberMismatch2);
            }
 
            // Find Maximum number of data points
            int maxNumOfPoints = int.MinValue;
            Series seriesWidthMaxPoints = null;
            foreach (Series series in inputSeries)
            {
                if (maxNumOfPoints < series.Points.Count)
                {
                    maxNumOfPoints = series.Points.Count;
                    seriesWidthMaxPoints = series;
                }
            }
 
            // *********************************************************
            // Set X values
            // *********************************************************
 
            // Check if all X values are zero
            foreach (DataPoint point in inputSeries[0].Points)
            {
                _zeroXValues = true;
 
                if (point.XValue != 0.0)
                {
                    _zeroXValues = false;
                    break;
                }
            }
 
            if (_zeroXValues && !ignoreZeroX)
            {
                // Check X values input alignment
                CheckXValuesAlignment(inputSeries);
            }
 
 
            // Data point index
            int indexPoint = 0;
 
            // Allocate memory for X values.
            output[0] = new double[maxNumOfPoints];
 
            // Data Points loop
            foreach (DataPoint point in seriesWidthMaxPoints.Points)
            {
                // Set X value
                if (_zeroXValues)
                    output[0][indexPoint] = (double)indexPoint + 1.0;
                else
                    output[0][indexPoint] = point.XValue;
 
                // Increase data point index.
                indexPoint++;
            }
 
            // *********************************************************
            // Set Y values
            // *********************************************************
            // Data Series Loop
            int indexSeries = 1;
            foreach (Series series in inputSeries)
            {
                output[indexSeries] = new double[series.Points.Count];
                indexPoint = 0;
 
                // Data Points loop
                foreach (DataPoint point in series.Points)
                {
                    // Set Y values
                    if (point.IsEmpty)
                        // IsEmpty data point
                        output[indexSeries][indexPoint] = double.NaN;
                    else
                    {
                        try
                        {
                            output[indexSeries][indexPoint] = point.YValues[valueIndex[indexSeries - 1] - 1];
                        }
                        catch (System.Exception)
                        {
                            throw new ArgumentException(SR.ExceptionFormulaYIndexInvalid);
                        }
                    }
 
                    // Increase data point index.
                    indexPoint++;
                }
                // Increase data series index.
                indexSeries++;
            }
        }
 
        /// <summary>
        /// Merge, split or move Y values of the series. 
        /// </summary>
        /// <param name="inputSeries">Comma separated list of input data series names and optional X and Y values names.</param>
        /// <param name="outputSeries">Comma separated list of output data series names and optional X and Y values names.</param>
        public void CopySeriesValues(string inputSeries, string outputSeries)
        {
            if (inputSeries == null)
                throw new ArgumentNullException("inputSeries");
            if (outputSeries == null)
                throw new ArgumentNullException("outputSeries");
 
            Series[] inSeries;
            Series[] outSeries;
            int[] inValueIndexes;
            int[] outValueIndexes;
            double[][] inValues;
            double[][] outValues;
 
            // Convert string with information about series and Y values 
            // to array of series and indexes to Y values.
            ConvertToArrays(inputSeries, out inSeries, out inValueIndexes, true);
            ConvertToArrays(outputSeries, out outSeries, out outValueIndexes, false);
 
            // The number of input and output series are different.
            if (inSeries.Length != outSeries.Length)
            {
                throw new ArgumentException(SR.ExceptionFormulaInputOutputSeriesMismatch);
            }
 
            // Check if output series points exist. If they do not exist 
            // create data points which are copy of Input series data points
            for (int indexSeries = 0; indexSeries < inSeries.Length; indexSeries++)
            {
                Series[] series = new Series[2];
                series[0] = inSeries[indexSeries];
                series[1] = outSeries[indexSeries];
                if (series[1].Points.Count == 0)
                {
                    foreach (DataPoint point in series[0].Points)
                    {
                        DataPoint clonePoint = point.Clone();
                        clonePoint.series = series[1];
                        series[1].Points.Add(clonePoint);
                    }
                }
            }
 
            // Check alignment of X values.
            for (int indexSeries = 0; indexSeries < inSeries.Length; indexSeries++)
            {
                Series[] series = new Series[2];
                series[0] = inSeries[indexSeries];
                series[1] = outSeries[indexSeries];
                CheckXValuesAlignment(series);
            }
 
            // Covert Series X and Y values to arrays of doubles
            GetDoubleArray(inSeries, inValueIndexes, out inValues, true);
 
            outValues = new double[inValues.Length][];
 
            // Copy Series X and Y values.
            for (int seriesIndex = 0; seriesIndex < inValues.Length; seriesIndex++)
            {
                outValues[seriesIndex] = new double[inValues[seriesIndex].Length];
                for (int pointIndex = 0; pointIndex < inValues[seriesIndex].Length; pointIndex++)
                {
                    outValues[seriesIndex][pointIndex] = inValues[seriesIndex][pointIndex];
                }
            }
 
            // Copy Series X and Y value Types.
            for (int seriesIndx = 0; seriesIndx < inSeries.Length; seriesIndx++)
            {
                // X value type
                if (outSeries[seriesIndx].XValueType == ChartValueType.Auto)
                {
                    outSeries[seriesIndx].XValueType = inSeries[seriesIndx].XValueType;
                    outSeries[seriesIndx].autoXValueType = inSeries[seriesIndx].autoXValueType;
                }
 
                // Y value type.
                if (outSeries[seriesIndx].YValueType == ChartValueType.Auto)
                {
                    outSeries[seriesIndx].YValueType = inSeries[seriesIndx].YValueType;
                    outSeries[seriesIndx].autoYValueType = inSeries[seriesIndx].autoYValueType;
                }
 
                seriesIndx++;
            }
 
            SetDoubleArray(outSeries, outValueIndexes, outValues, null);
        }
 
 
        /// <summary>
        /// This method will first copy input matrix to output matrix 
        /// then will remove columns, which have 
        /// one or more empty values (NaN) from the output matrix. This 
        /// method will set all values from column of input matrix 
        /// to be empty (NaN) if one or more values of that column 
        /// are empty.
        /// </summary>
        /// <param name="input">Input matrix with empty values</param>
        /// <param name="output">Output matrix without empty values</param>
        private void RemoveEmptyValues(double[][] input, out double[][] output)
        {
            // Allocate memory
            output = new double[input.Length][];
            int seriesIndex = 0;
 
            int numberOfRows = 0;
 
            // Set Nan for all data points with same index in input array
            // Data point loop
            for (int pointIndex = 0; pointIndex < input[0].Length; pointIndex++)
            {
                bool isEmpty = false;
                // Series loop
                // Find empty data point with same point index
                for (seriesIndex = 0; seriesIndex < input.Length; seriesIndex++)
                {
                    if (seriesIndex >= input[seriesIndex].Length)
                        continue;
                    if (Double.IsNaN(input[seriesIndex][pointIndex]))
                        isEmpty = true;
                }
 
                if (!isEmpty)
                {
                    numberOfRows++;
                }
 
                // There is empty data point
                if (isEmpty)
                {
                    // Set all points with same index to be empty
                    for (seriesIndex = 1; seriesIndex < input.Length; seriesIndex++)
                    {
                        input[seriesIndex][pointIndex] = Double.NaN;
                    }
                }
            }
 
            // Copy input matrix to output matrix without empty columns.
            for (seriesIndex = 0; seriesIndex < input.Length; seriesIndex++)
            {
                output[seriesIndex] = new double[numberOfRows];
                int outPointIndex = 0;
                for (int pointIndex = 0; pointIndex < input[0].Length; pointIndex++)
                {
                    if (pointIndex >= input[seriesIndex].Length)
                        continue;
                    if (!double.IsNaN(input[1][pointIndex]))
                    {
                        output[seriesIndex][outPointIndex] = input[seriesIndex][pointIndex];
                        outPointIndex++;
                    }
                }
            }
        }
 
 
        /*
		/// <summary>
		/// This method will compare a input matrix with empty data 
		/// points and output matrix without empty data points and 
		/// add empty data points to output matrix according to 
		/// input matrix empty data point positions.
		/// </summary>
		/// <param name="input">Matrix With input data</param>
		/// <param name="inputWithoutEmpty">Matrix without empty data points</param>
		/// <param name="output">New Matrix with inserted data points</param>
         */
        //private void InsertEmptyDataPoints( double [][] input, double [][] inputWithoutEmpty, out double [][] output )
        //{
 
 
        // *** NOTE ***
        //
        //
        // This method is only called in one location as of this writing.
        // Therefore the entire method is being commented out for now. We wish
        // to preserve the code itself as it may be re-implemented in the future.
        // --Microsoft 4/21/08
        //
        // ************
 
 
 
        //output = inputWithoutEmpty;
        //return;
 
        //
        // NOTE: Inserting empty points in the result data after applying the formula
        // causes issues. The algorithm below do not cover most of the common spzces
        // and as a result the formula data is completly destroyed.
        // 
        // By removing this code the result data set will have "missing" points instaed 
        // of empty. 
        //   - AG
        //
 
        /*
 
        // Input matrix can have only empty rows. If one value 
        // is empty all values from a row have to be empty.
 
        // Find the number of empty rows
        int NumberOfEmptyRows = 0;
        foreach( double val in input[1] )
        {
            if( Double.IsNaN( val ) )
            {
                NumberOfEmptyRows++;
            }
        }
 
        if( NumberOfEmptyRows == 0 ||
            inputWithoutEmpty[0].Length > input[0].Length)
        {
            output = inputWithoutEmpty;
            return;
        }
 
        output = new double[input.Length][];
        // Series loop
        for( int seriesIndex = 0; seriesIndex < input.Length; seriesIndex++ )
        {
            int inputPointIndex = 0;
            int emptyPointIndex = 0;
 
            // Skip input index if points are not aligned .
            while( input[0][inputPointIndex] != inputWithoutEmpty[0][0] && inputPointIndex < input[0].Length )
            {
                inputPointIndex++;
            }
 
            output[seriesIndex] = new double[inputWithoutEmpty[0].Length + NumberOfEmptyRows - inputPointIndex];
 
            // Data Point loop
            for( int pointIndex = 0; pointIndex < output[seriesIndex].Length; pointIndex++ )
            {
                if( inputPointIndex < input[0].Length &&
                    inputPointIndex < input[1].Length )
                {
                    // If the point Y value is empty (NaN) insert empty (NaN) for all values.
                    if( double.IsNaN( input[1][inputPointIndex] ) )
                    {
                        output[seriesIndex][pointIndex] = input[seriesIndex][inputPointIndex];
                        emptyPointIndex--;
                    }
                    else if( input[0][inputPointIndex] == inputWithoutEmpty[0][emptyPointIndex] )
                    {
                        output[seriesIndex][pointIndex] = inputWithoutEmpty[seriesIndex][emptyPointIndex];
                    }
                    else
                    {
                        output[0][pointIndex] = inputWithoutEmpty[0][emptyPointIndex];
                        output[seriesIndex][pointIndex] = inputWithoutEmpty[seriesIndex][emptyPointIndex];
                    }
                }
                else
                {
                    output[seriesIndex][pointIndex] = inputWithoutEmpty[seriesIndex][emptyPointIndex];
                }
 
                inputPointIndex++;
                emptyPointIndex++;
            }
        }
        */
        //}
 
 
        /// <summary>
        /// This method splits a string with comma separated 
        /// parameters to the array of strings with parameters.
        /// </summary>
        /// <param name="parameters">a string with comma separated parameters</param>
        /// <param name="parameterList">the array of strings with parameters</param>
        private void SplitParameters(string parameters, out string[] parameterList)
        {
            // Split string by comma
            parameterList = parameters.Split(',');
 
            for (Int32 i = 0; i < parameterList.Length; i++)
            {
                parameterList[i] = parameterList[i].Trim();
            }
 
        }
 
        /// <summary>
        /// Check if series have different number of series.
        /// </summary>
        /// <param name="input">Input series.</param>
        /// <returns>true if there is different number of series.</returns>
        private static bool DifferentNumberOfSeries(double[][] input)
        {
            for (int index = 0; index < input.Length - 1; index++)
            {
                if (input[index].Length != input[index + 1].Length)
                {
                    return true;
                }
            }
            return false;
        }
 
        /// <summary>
        /// This method will check if X values from different series 
        /// are aligned.
        /// </summary>
        /// <param name="series">Array of series</param>
        internal void CheckXValuesAlignment(Series[] series)
        {
            // Check aligment only if more than 1 series provided
            if (series.Length > 1)
            {
                // Series loop
                for (int seriesIndex = 0; seriesIndex < series.Length - 1; seriesIndex++)
                {
                    // Check the number of data points
                    if (series[seriesIndex].Points.Count != series[seriesIndex + 1].Points.Count)
                    {
                        throw new ArgumentException(SR.ExceptionFormulaDataSeriesAreNotAlignedDifferentDataPoints(series[seriesIndex].Name, series[seriesIndex + 1].Name));
                    }
 
                    // Data points loop
                    for (int pointIndex = 0; pointIndex < series[seriesIndex].Points.Count; pointIndex++)
                    {
                        // Check X values.
                        if (series[seriesIndex].Points[pointIndex].XValue != series[seriesIndex + 1].Points[pointIndex].XValue)
                            throw new ArgumentException(SR.ExceptionFormulaDataSeriesAreNotAlignedDifferentXValues(series[seriesIndex].Name, series[seriesIndex + 1].Name));
 
                    }
                }
            }
        }
 
 
        #endregion
 
        #region Data Formulas Financial methods
        /// <summary>
        /// This method calls a method from a formula module with 
        /// specified name.
        /// </summary>
        /// <param name="formulaName">Formula Name</param>
        /// <param name="inputSeries">Input series</param>
        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
        public void FinancialFormula(FinancialFormula formulaName, Series inputSeries)        
        {
            FinancialFormula(formulaName, inputSeries, inputSeries);
        }
 
        /// <summary>
        /// This method calls a method from a formula module with 
        /// specified name.
        /// </summary>
        /// <param name="formulaName">Formula Name</param>
        /// <param name="inputSeries">Input series</param>
        /// <param name="outputSeries">Output series</param>
        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
        public void FinancialFormula(FinancialFormula formulaName, Series inputSeries, Series outputSeries)
        {
            FinancialFormula(formulaName, "", inputSeries, outputSeries);
        }
 
 
        /// <summary>
        /// This method calls a method from a formula module with 
        /// specified name.
        /// </summary>
        /// <param name="formulaName">Formula Name</param>
        /// <param name="parameters">Formula parameters</param>
        /// <param name="inputSeries">Input series</param>
        /// <param name="outputSeries">Output series</param>
        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
        public void FinancialFormula(FinancialFormula formulaName, string parameters, Series inputSeries, Series outputSeries)
        {
            if (inputSeries == null)
                throw new ArgumentNullException("inputSeries");
            if (outputSeries == null)
                throw new ArgumentNullException("outputSeries");
            FinancialFormula(formulaName, parameters, inputSeries.Name, outputSeries.Name);
        }
 
 
        /// <summary>
        /// This method calls a method from a formula module with 
        /// specified name.
        /// </summary>
        /// <param name="formulaName">Formula Name</param>
        /// <param name="inputSeries">Comma separated list of input series names and optional X and Y values names.</param>
        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
        public void FinancialFormula(FinancialFormula formulaName, string inputSeries)
        {
            FinancialFormula(formulaName, inputSeries, inputSeries);
        }
 
 
        /// <summary>
        /// This method calls a method from a formula module with 
        /// specified name.
        /// </summary>
        /// <param name="formulaName">Formula Name</param>
        /// <param name="inputSeries">Comma separated list of input series names and optional X and Y values names.</param>
        /// <param name="outputSeries">Comma separated list of output series names and optional X and Y values names.</param>
        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
        public void FinancialFormula(FinancialFormula formulaName, string inputSeries, string outputSeries)
        {
            FinancialFormula(formulaName, "", inputSeries, outputSeries);
        }
 
        /// <summary>
        /// This method calls a method from a formula module with 
        /// specified name.
        /// </summary>
        /// <param name="formulaName">Formula Name</param>
        /// <param name="parameters">Formula parameters</param>
        /// <param name="inputSeries">Comma separated list of input series names and optional X and Y values names.</param>
        /// <param name="outputSeries">Comma separated list of output series names and optional X and Y values names.</param>
        public void FinancialFormula(FinancialFormula formulaName, string parameters, string inputSeries, string outputSeries)
        {
            if (inputSeries == null)
                throw new ArgumentNullException("inputSeries");
            if (outputSeries == null)
                throw new ArgumentNullException("outputSeries");
 
            // Get formula info
            FormulaInfo formulaInfo = FormulaHelper.GetFormulaInfo(formulaName);
 
            // Provide default parameters if necessary
            if (string.IsNullOrEmpty(parameters))
            {
                parameters = formulaInfo.SaveParametersToString();
            }
            else 
            {
                formulaInfo.CheckParameterString(parameters);
            }
 
            // Fix the InputSeries and Outputseries for cases when the series field names are not provided
            SeriesFieldList inputFields = SeriesFieldList.FromString(this.Common.Chart, inputSeries, formulaInfo.InputFields);
            SeriesFieldList outputFields = SeriesFieldList.FromString(this.Common.Chart, outputSeries, formulaInfo.OutputFields);
 
            if (inputFields != null) inputSeries = inputFields.ToString();
            if (outputFields != null) outputSeries = outputFields.ToString();
 
            Formula(formulaName.ToString(), parameters, inputSeries, outputSeries);
        }
        #endregion
 
        #region Data Formulas properties
 
        /// <summary>
        /// Gets or sets a flag which indicates whether 
        /// empty points are ignored while performing calculations; 
        /// otherwise, empty points are treated as zeros. 
        /// </summary>
        public bool IsEmptyPointIgnored
        {
            get
            {
                return _isEmptyPointIgnored;
            }
            set
            {
                _isEmptyPointIgnored = value;
            }
        }
 
 
 
        /// <summary>
        /// Gets or sets a flag which indicates whether 
        /// to start formulas like rolling average from zero.
        /// </summary>
        public bool IsStartFromFirst
        {
            get
            {
                return bool.Parse(_extraParameters[0]);
            }
            set
            {
                if (value)
                    _extraParameters[0] = true.ToString(System.Globalization.CultureInfo.InvariantCulture);
                else
                    _extraParameters[0] = false.ToString(System.Globalization.CultureInfo.InvariantCulture);
            }
        }
 
        /// <summary>
        /// Returns a reference to the statistical utility class.
        /// </summary>
        public StatisticFormula Statistics
        {
            get
            {
                return _statistics;
            }
        }
 
 
        #endregion
    }
 
 
 
}