//------------------------------------------------------------- // // Copyright © Microsoft Corporation. All Rights Reserved. // //------------------------------------------------------------- // @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 /// /// An enumeration of financial formula names. /// public enum FinancialFormula { /// /// 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. /// AccumulationDistribution, /// /// Average True Range indicator. It measures commitment and compares /// the range between the High, Low and Close prices. /// AverageTrueRange, /// /// Bollinger Bands indicators. They are plotted at standard deviation levels /// above and below a simple moving average. /// BollingerBands, /// /// 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. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Chaikin")] ChaikinOscillator, /// /// Commodity Channel Index. It compares prices with their moving averages. /// CommodityChannelIndex, /// /// Detrended Price Oscillator. It attempts to remove trend from prices. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Detrended")] DetrendedPriceOscillator, /// /// Ease of Movement deals with the relationship between volume and price change, /// and uses volume to indicate how strong a trend is for prices. /// EaseOfMovement, /// /// Envelopes are plotted above and below a moving average using a specified percentage /// as the shift. /// Envelopes, /// /// An Exponential Moving Average is an average of data calculated over a period of time /// where the most recent days have more weight. /// ExponentialMovingAverage, /// /// Forecasting. It predicts future values using historical observations. /// Forecasting, /// /// 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. /// MovingAverageConvergenceDivergence, /// /// The Mass Index is used to predict trend reversal by comparing the /// difference and range between High and Low prices. /// MassIndex, /// /// Median prices are mid-point values of daily prices and can be used /// as a filter for trend indicators. /// MedianPrice, /// /// The Money Flow indicator compares upward changes and downward changes /// of volume-weighted typical prices. /// MoneyFlow, /// /// 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. /// NegativeVolumeIndex, /// /// The On Balance Volume indicator measures positive and negative volume flow. /// OnBalanceVolume, /// /// The Performance indicator compares a current closing price (or any other price) with /// the first closing value (from the first time period). /// Performance, /// /// 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. /// PositiveVolumeIndex, /// /// 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. /// PriceVolumeTrend, /// /// The Rate of Change indicator compares a specified closing price with the current price. /// RateOfChange, /// /// 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. /// RelativeStrengthIndex, /// /// 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. /// MovingAverage, /// /// Standard deviation is used to indicate volatility, and measures /// the difference between values (e.g. closing price) and their moving average. /// StandardDeviation, /// /// 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. /// StochasticIndicator, /// /// A Triangular Moving Average is an average of data calculated over a period of time /// where the middle portion of data has more weight. /// TriangularMovingAverage, /// /// 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. /// TripleExponentialMovingAverage, /// /// Typical price is the average value of daily prices, and can be used as a filter for trend indicators. /// TypicalPrice, /// /// The Volatility Chaikins indicator measures the difference between High and Low prices, /// and is used to indicate tops or bottoms of the market. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Chaikins")] VolatilityChaikins, /// /// 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. /// VolumeOscillator, /// /// 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. /// WeightedClose, /// /// 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. /// WeightedMovingAverage, /// /// William's %R is a momentum indicator, and is used to measure overbought and oversold levels. /// WilliamsR } #endregion // Financial Formula Name enumeration /// /// 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. /// #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; /// /// All X values are zero. /// private bool _zeroXValues = false; /// /// Utility class for Statistical formulas /// private StatisticFormula _statistics; /// /// Reference to the Common elements /// internal CommonElements Common; #endregion #region Data Formulas methods /// /// Default constructor /// public DataFormula() { _statistics = new StatisticFormula(this); _extraParameters = new string[1]; _extraParameters[0] = false.ToString(System.Globalization.CultureInfo.InvariantCulture); } /// /// This method calls a method from a formula module with /// specified name. /// /// Formula Name /// Formula parameters /// Comma separated input data series names and optional X and Y values names. /// Comma separated output data series names and optional X and Y values names. 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); } /// /// Copy axis labels from the original series into the calculated series /// /// array of input series /// array of output series 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++; } } /// /// This method will set series X and Y values from matrix of /// double values. /// /// Array of output series /// Array of Y value indexes /// Array of doubles which will be used to fill series /// Array of labels 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++; } } /// /// 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. /// /// A string with information about series and Y values /// Array of Data Series /// Array of Y value indexes /// Do not create new series if input series are used 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++; } } /// /// 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 /// /// Array of Data Series /// Array with indexes which represent value from data point: 0 = X, 1 = Y, 2 = Y2, 3 = Y3 /// Jagged Arrays of doubles private void GetDoubleArray(Series[] inputSeries, int[] valueIndex, out double[][] output) { GetDoubleArray(inputSeries, valueIndex, out output, false); } /// /// 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 /// /// Array of Data Series /// Array with indexes which represent value from data point: 0 = X, 1 = Y, 2 = Y2, 3 = Y3 /// Jagged Arrays of doubles /// Ignore Zero X values 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++; } } /// /// Merge, split or move Y values of the series. /// /// Comma separated list of input data series names and optional X and Y values names. /// Comma separated list of output data series names and optional X and Y values names. 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); } /// /// 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. /// /// Input matrix with empty values /// Output matrix without empty values 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++; } } } } /* /// /// 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. /// /// Matrix With input data /// Matrix without empty data points /// New Matrix with inserted data points */ //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++; } } */ //} /// /// This method splits a string with comma separated /// parameters to the array of strings with parameters. /// /// a string with comma separated parameters /// the array of strings with parameters 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(); } } /// /// Check if series have different number of series. /// /// Input series. /// true if there is different number of series. 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; } /// /// This method will check if X values from different series /// are aligned. /// /// Array of series 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 /// /// This method calls a method from a formula module with /// specified name. /// /// Formula Name /// Input series [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] public void FinancialFormula(FinancialFormula formulaName, Series inputSeries) { FinancialFormula(formulaName, inputSeries, inputSeries); } /// /// This method calls a method from a formula module with /// specified name. /// /// Formula Name /// Input series /// Output series [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] public void FinancialFormula(FinancialFormula formulaName, Series inputSeries, Series outputSeries) { FinancialFormula(formulaName, "", inputSeries, outputSeries); } /// /// This method calls a method from a formula module with /// specified name. /// /// Formula Name /// Formula parameters /// Input series /// Output series [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); } /// /// This method calls a method from a formula module with /// specified name. /// /// Formula Name /// Comma separated list of input series names and optional X and Y values names. [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] public void FinancialFormula(FinancialFormula formulaName, string inputSeries) { FinancialFormula(formulaName, inputSeries, inputSeries); } /// /// This method calls a method from a formula module with /// specified name. /// /// Formula Name /// Comma separated list of input series names and optional X and Y values names. /// Comma separated list of output series names and optional X and Y values names. [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] public void FinancialFormula(FinancialFormula formulaName, string inputSeries, string outputSeries) { FinancialFormula(formulaName, "", inputSeries, outputSeries); } /// /// This method calls a method from a formula module with /// specified name. /// /// Formula Name /// Formula parameters /// Comma separated list of input series names and optional X and Y values names. /// Comma separated list of output series names and optional X and Y values names. 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 /// /// Gets or sets a flag which indicates whether /// empty points are ignored while performing calculations; /// otherwise, empty points are treated as zeros. /// public bool IsEmptyPointIgnored { get { return _isEmptyPointIgnored; } set { _isEmptyPointIgnored = value; } } /// /// Gets or sets a flag which indicates whether /// to start formulas like rolling average from zero. /// 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); } } /// /// Returns a reference to the statistical utility class. /// public StatisticFormula Statistics { get { return _statistics; } } #endregion } }