123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284 |
- // Licensed to the .NET Foundation under one or more agreements.
- // The .NET Foundation licenses this file to you under the MIT license.
- // See the LICENSE file in the project root for more information.
- //
- // 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.
- //
- using System;
- using System.Diagnostics.CodeAnalysis;
- using FastReport.DataVisualization.Charting.Formulas;
- namespace FastReport.DataVisualization.Charting
- {
- #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>
- 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 the disabled 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. --magreer 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.
- // --magreer 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
- }
- }
|