Xamarin Public Jenkins (auto-signing) 536cd135cc Imported Upstream version 5.4.0.167
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
2017-08-21 15:34:15 +00:00

1313 lines
52 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//-------------------------------------------------------------
// <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
}
}