|
//-------------------------------------------------------------
// <copyright company=’Microsoft Corporation’>
// Copyright © Microsoft Corporation. All Rights Reserved.
// </copyright>
//-------------------------------------------------------------
// @owner=alexgor, deliant
//=================================================================
// File: DataManipulator.cs
//
// Namespace: DataVisualization.Charting
//
// Classes: DataManipulator, IDataPointFilter
//
// Purpose: DataManipulator class exposes to the user methods
// to perform data filtering, grouping, inserting
// empty points, sorting and exporting data.
//
// It also expose financial and statistical formulas
// through the DataFormula base class.
//
// Reviewed: AG - Jul 31, 2002;
// GS - Aug 7, 2002
// AG - Microsoft 15, 2007
//
//===================================================================
#region Used namespaces
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Data;
using System.Drawing.Drawing2D;
using System.Drawing.Design;
#if Microsoft_CONTROL
using System.Windows.Forms.DataVisualization.Charting;
#else
using System.Web;
using System.Web.UI;
using System.Web.UI.DataVisualization.Charting;
#endif
#endregion
#if Microsoft_CONTROL
namespace System.Windows.Forms.DataVisualization.Charting
#else
namespace System.Web.UI.DataVisualization.Charting
#endif
{
#region Data manipulation enumerations
/// <summary>
/// Grouping functions types
/// </summary>
internal enum GroupingFunction
{
/// <summary>
/// Not defined
/// </summary>
None,
/// <summary>
/// Minimum value of the group
/// </summary>
Min,
/// <summary>
/// Maximum value of the group
/// </summary>
Max,
/// <summary>
/// Average value of the group
/// </summary>
Ave,
/// <summary>
/// Total of all values of the group
/// </summary>
Sum,
/// <summary>
/// Value of the first point in the group
/// </summary>
First,
/// <summary>
/// Value of the last point in the group
/// </summary>
Last,
/// <summary>
/// Value of the center point in the group
/// </summary>
Center,
/// <summary>
/// High, Low, Open, Close values in the group
/// </summary>
HiLoOpCl,
/// <summary>
/// High, Low values in the group
/// </summary>
HiLo,
/// <summary>
/// Number of points in the group
/// </summary>
Count,
/// <summary>
/// Number of unique points in the group
/// </summary>
DistinctCount,
/// <summary>
/// Variance of points in the group
/// </summary>
Variance,
/// <summary>
/// Deviation of points in the group
/// </summary>
Deviation
}
/// <summary>
/// An enumeration of units of measurement for intervals.
/// </summary>
public enum IntervalType
{
/// <summary>
/// Interval in numbers.
/// </summary>
Number,
/// <summary>
/// Interval in years.
/// </summary>
Years,
/// <summary>
/// Interval in months.
/// </summary>
Months,
/// <summary>
/// Interval in weeks.
/// </summary>
Weeks,
/// <summary>
/// Interval in days.
/// </summary>
Days,
/// <summary>
/// Interval in hours.
/// </summary>
Hours,
/// <summary>
/// Interval in minutes.
/// </summary>
Minutes,
/// <summary>
/// Interval in seconds.
/// </summary>
Seconds,
/// <summary>
/// Interval in milliseconds.
/// </summary>
Milliseconds
}
/// <summary>
/// An enumeration of units of measurement for date ranges.
/// </summary>
public enum DateRangeType
{
/// <summary>
/// Range defined in years.
/// </summary>
Year,
/// <summary>
/// Range defined in months.
/// </summary>
Month,
/// <summary>
/// Range defined in days of week.
/// </summary>
DayOfWeek,
/// <summary>
/// Range defined in days of month.
/// </summary>
DayOfMonth,
/// <summary>
/// Range defined in hours.
/// </summary>
Hour,
/// <summary>
/// Range defined in minutes.
/// </summary>
Minute
}
/// <summary>
/// An enumeration of methods of comparison.
/// </summary>
public enum CompareMethod
{
/// <summary>
/// One value is more than the other value.
/// </summary>
MoreThan,
/// <summary>
/// One value is less than the other value.
/// </summary>
LessThan,
/// <summary>
/// One value is equal the other value.
/// </summary>
EqualTo,
/// <summary>
/// One value is more or equal to the other value.
/// </summary>
MoreThanOrEqualTo,
/// <summary>
/// One value is less or equal to the other value.
/// </summary>
LessThanOrEqualTo,
/// <summary>
/// One value is not equal to the other value.
/// </summary>
NotEqualTo
}
#endregion
#region Data points filtering inteface
/// <summary>
/// The IDataPointFilter interface is used for filtering series data points.
/// </summary>
#if ASPPERM_35
[AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
#endif
public interface IDataPointFilter
{
/// <summary>
/// Checks if the specified data point must be filtered.
/// </summary>
/// <param name="point">Data point object.</param>
/// <param name="series">Series of the point.</param>
/// <param name="pointIndex">Index of the point in the series.</param>
/// <returns>True if point must be removed</returns>
bool FilterDataPoint(DataPoint point, Series series, int pointIndex);
}
#endregion
/// <summary>
/// The DataManipulator class is used at runtime to perform data manipulation
/// operations, and is exposed via the DataManipulator property of the
/// root Chart object.
/// </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 DataManipulator : DataFormula
{
#region Fields
// Indicates that filtering do not remove points, just mark them as empty
private bool _filterSetEmptyPoints = false;
// Indicates that points that match the criteria must be filtered out
private bool _filterMatchedPoints = true;
#endregion // Fields
#region Data manipulator helper functions
/// <summary>
/// Helper function that converts one series or a comma separated
/// list of series names into the Series array.
/// </summary>
/// <param name="obj">Series or string of series names.</param>
/// <param name="createNew">If series with this name do not exist - create new.</param>
/// <returns>Array of series.</returns>
internal Series[] ConvertToSeriesArray(object obj, bool createNew)
{
Series[] array = null;
if(obj == null)
{
return null;
}
// Parameter is one series
if(obj.GetType() == typeof(Series))
{
array = new Series[1];
array[0] = (Series)obj;
}
// Parameter is a string (comma separated series names)
else if(obj.GetType() == typeof(string))
{
string series = (string)obj;
int index = 0;
// "*" means process all series from the collection
if(series == "*")
{
// Create array of series
array = new Series[Common.DataManager.Series.Count];
// Add all series from the collection
foreach(Series s in Common.DataManager.Series)
{
array[index] = s;
++index;
}
}
// Comma separated list
else if(series.Length > 0)
{
// Replace commas in value string
series = series.Replace("\\,", "\\x45");
series = series.Replace("\\=", "\\x46");
// Split string by comma
string[] seriesNames = series.Split(',');
// Create array of series
array = new Series[seriesNames.Length];
// Find series by name
foreach(string s in seriesNames)
{
// Put pack a comma character
string seriesName = s.Replace("\\x45", ",");
seriesName = seriesName.Replace("\\x46", "=");
try
{
array[index] = Common.DataManager.Series[seriesName.Trim()];
}
catch(System.Exception)
{
if(createNew)
{
Series newSeries = new Series(seriesName.Trim());
Common.DataManager.Series.Add(newSeries);
array[index] = newSeries;
}
else
{
throw;
}
}
++index;
}
}
}
return array;
}
/// <summary>
/// Public constructor
/// </summary>
public DataManipulator()
{
}
#endregion
#region Series points sorting methods
/// <summary>
/// Sort series data points in specified order.
/// </summary>
/// <param name="pointSortOrder">Sorting order.</param>
/// <param name="sortBy">Value to sort by.</param>
/// <param name="series">Series array to sort.</param>
private void Sort(PointSortOrder pointSortOrder, string sortBy, Series[] series)
{
// Check arguments
if (sortBy == null)
throw new ArgumentNullException("sortBy");
if (series == null)
throw new ArgumentNullException("series");
// Check array of series
if(series.Length == 0)
{
return;
}
// Sort series
DataPointComparer comparer = new DataPointComparer(series[0], pointSortOrder, sortBy);
this.Sort(comparer, series);
}
/// <summary>
/// Sort series data points in specified order.
/// </summary>
/// <param name="comparer">Comparing interface.</param>
/// <param name="series">Series array to sort.</param>
private void Sort(IComparer<DataPoint> comparer, Series[] series)
{
// Check arguments
if (comparer == null)
throw new ArgumentNullException("comparer");
if (series == null)
throw new ArgumentNullException("series");
//**************************************************
//** Check array of series
//**************************************************
if(series.Length == 0)
{
return;
}
//**************************************************
//** If we sorting more than one series
//**************************************************
if(series.Length > 1)
{
// Check if series X values are aligned
this.CheckXValuesAlignment(series);
// Apply points indexes to the first series
int pointIndex = 0;
foreach(DataPoint point in series[0].Points)
{
point["_Index"] = pointIndex.ToString(System.Globalization.CultureInfo.InvariantCulture);
++pointIndex;
}
}
//**************************************************
//** Sort first series
//**************************************************
series[0].Sort(comparer);
//**************************************************
//** If we sorting more than one series
//**************************************************
if(series.Length > 1)
{
// Sort other series (depending on the first)
int toIndex = 0;
int fromIndex = 0;
foreach(DataPoint point in series[0].Points)
{
// Move point from index is stored in point attribute (as index before sorting)
fromIndex = int.Parse(point["_Index"], System.Globalization.CultureInfo.InvariantCulture);
// Move points in series
for(int seriesIndex = 1; seriesIndex < series.Length; seriesIndex++)
{
series[seriesIndex].Points.Insert(toIndex, series[seriesIndex].Points[toIndex + fromIndex]);
}
// Increase move point to index
++toIndex;
}
// Remove extra points from series
for(int seriesIndex = 1; seriesIndex < series.Length; seriesIndex++)
{
while(series[seriesIndex].Points.Count > series[0].Points.Count)
{
series[seriesIndex].Points.RemoveAt(series[seriesIndex].Points.Count - 1);
}
}
//**************************************************
//** Remove points index attribute
//**************************************************
foreach(DataPoint point in series[0].Points)
{
point.DeleteCustomProperty("_Index");
}
}
}
#endregion
#region Series points sorting overloaded methods
/// <summary>
/// Sort the series' data points in specified order.
/// </summary>
/// <param name="pointSortOrder">Sorting order.</param>
/// <param name="sortBy">Value to sort by.</param>
/// <param name="seriesName">Comma separated series names to sort.</param>
public void Sort(PointSortOrder pointSortOrder, string sortBy, string seriesName)
{
// Check arguments
if (seriesName == null)
throw new ArgumentNullException("seriesName");
Sort(pointSortOrder, sortBy, ConvertToSeriesArray(seriesName, false));
}
/// <summary>
/// Sort the series' data points in specified order.
/// </summary>
/// <param name="pointSortOrder">Sorting order.</param>
/// <param name="series">Series to sort.</param>
public void Sort(PointSortOrder pointSortOrder, Series series)
{
// Check arguments
if (series == null)
throw new ArgumentNullException("series");
Sort(pointSortOrder, "Y", ConvertToSeriesArray(series, false));
}
/// <summary>
/// Sort the series' data points in specified order.
/// </summary>
/// <param name="pointSortOrder">Sorting order.</param>
/// <param name="seriesName">Comma separated series names to sort.</param>
public void Sort(PointSortOrder pointSortOrder, string seriesName)
{
// Check arguments
if (seriesName == null)
throw new ArgumentNullException("seriesName");
Sort(pointSortOrder, "Y", ConvertToSeriesArray(seriesName, false));
}
/// <summary>
/// Sort the series' data points in specified order.
/// </summary>
/// <param name="pointSortOrder">Sorting order.</param>
/// <param name="sortBy">Value to sort by.</param>
/// <param name="series">Series to sort.</param>
public void Sort(PointSortOrder pointSortOrder, string sortBy, Series series)
{
// Check arguments
if (series == null)
throw new ArgumentNullException("series");
Sort(pointSortOrder, sortBy, ConvertToSeriesArray(series, false));
}
/// <summary>
/// Sort the series' data points in specified order.
/// </summary>
/// <param name="comparer">IComparer interface.</param>
/// <param name="series">Series to sort.</param>
public void Sort(IComparer<DataPoint> comparer, Series series)
{
// Check arguments - comparer is checked in the private override of Sort
if (series == null)
throw new ArgumentNullException("series");
Sort(comparer, ConvertToSeriesArray(series, false));
}
/// <summary>
/// Sort the series' data points in specified order.
/// </summary>
/// <param name="comparer">Comparing interface.</param>
/// <param name="seriesName">Comma separated series names to sort.</param>
public void Sort(IComparer<DataPoint> comparer, string seriesName)
{
// Check arguments - comparer is checked in the private override of Sort
if (seriesName == null)
throw new ArgumentNullException("seriesName");
Sort(comparer, ConvertToSeriesArray(seriesName, false));
}
#endregion
#region Insert empty data points method
/// <summary>
/// Insert empty data points using specified interval.
/// </summary>
/// <param name="interval">Interval size.</param>
/// <param name="intervalType">Interval type.</param>
/// <param name="intervalOffset">Interval offset size.</param>
/// <param name="intervalOffsetType">Interval offset type.</param>
/// <param name="fromXValue">Check intervals from this X value.</param>
/// <param name="toXValue">Check intervals until this X value.</param>
/// <param name="series">Series array.</param>
private void InsertEmptyPoints(
double interval,
IntervalType intervalType,
double intervalOffset,
IntervalType intervalOffsetType,
double fromXValue,
double toXValue,
Series[] series)
{
// Check the arguments
if (interval <= 0)
throw new ArgumentOutOfRangeException("interval");
//**************************************************
//** Automaticly detect minimum and maximum values
//**************************************************
double fromX = Math.Min(fromXValue, toXValue);
double toX = Math.Max(fromXValue, toXValue);
bool fromIsNaN = double.IsNaN(fromX);
bool toIsNaN = double.IsNaN(toX);
foreach(Series ser in series)
{
if(ser.Points.Count >= 1)
{
if(toIsNaN)
{
if(double.IsNaN(toX))
{
toX = ser.Points[ser.Points.Count - 1].XValue;
}
else
{
toX = Math.Max(toX, ser.Points[ser.Points.Count - 1].XValue);
}
}
if(fromIsNaN)
{
if(double.IsNaN(fromX))
{
fromX = ser.Points[0].XValue;
}
else
{
fromX = Math.Min(fromX, ser.Points[0].XValue);
}
}
if(fromX > toX)
{
double tempValue = fromX;
fromX = toX;
toX = tempValue;
}
}
}
//**************************************************
//** Automaticly adjust the beginning interval and
//** offset
//**************************************************
double nonAdjustedFromX = fromX;
fromX = ChartHelper.AlignIntervalStart(fromX, interval, ConvertIntervalType(intervalType));
// Add offset to the start position
if( intervalOffset != 0 )
{
fromX = fromX + ChartHelper.GetIntervalSize(fromX, intervalOffset, ConvertIntervalType(intervalOffsetType), null, 0, DateTimeIntervalType.Number, true, false);
}
//**************************************************
//** Loop through all series
//**************************************************
foreach(Series ser in series)
{
//**************************************************
//** Loop through all data points
//**************************************************
int numberOfPoints = 0;
int lastInsertPoint = 0;
double currentPointValue = fromX;
while(currentPointValue <= toX)
{
//**************************************************
//** Check that X value is in range
//**************************************************
bool outOfRange = false;
if(double.IsNaN(fromXValue) && currentPointValue < nonAdjustedFromX ||
!double.IsNaN(fromXValue) && currentPointValue < fromXValue)
{
outOfRange = true;
}
else if(currentPointValue > toXValue)
{
outOfRange = true;
}
// Current X value is in range of points values
if(!outOfRange)
{
//**************************************************
//** Find required X value
//**************************************************
int insertPosition = lastInsertPoint;
for(int pointIndex = lastInsertPoint; pointIndex < ser.Points.Count; pointIndex++)
{
// Value was found
if(ser.Points[pointIndex].XValue == currentPointValue)
{
insertPosition = -1;
break;
}
// Save point index where we should insert new empty point
if(ser.Points[pointIndex].XValue > currentPointValue)
{
insertPosition = pointIndex;
break;
}
// Insert as last point
if(pointIndex == (ser.Points.Count - 1))
{
insertPosition = ser.Points.Count;
}
}
//**************************************************
//** Required value was not found - insert empty data point
//**************************************************
if(insertPosition != -1)
{
lastInsertPoint = insertPosition;
++numberOfPoints;
DataPoint dataPoint = new DataPoint(ser);
dataPoint.XValue = currentPointValue;
dataPoint.IsEmpty = true;
ser.Points.Insert(insertPosition, dataPoint);
}
}
//**************************************************
//** Determine next required data point
//**************************************************
currentPointValue += ChartHelper.GetIntervalSize(currentPointValue,
interval,
ConvertIntervalType(intervalType));
//**************************************************
//** Check if we exceed number of empty points
//** we can add.
//**************************************************
if(numberOfPoints > 1000)
{
currentPointValue = toX + 1;
continue;
}
}
}
}
/// <summary>
/// Helper function which converts IntervalType enumeration
/// into DateTimeIntervalType enumeration.
/// </summary>
/// <param name="type">Interval type value.</param>
/// <returns>Date time interval type value.</returns>
private DateTimeIntervalType ConvertIntervalType(IntervalType type)
{
switch(type)
{
case(IntervalType.Milliseconds):
return DateTimeIntervalType.Milliseconds;
case(IntervalType.Seconds):
return DateTimeIntervalType.Seconds;
case(IntervalType.Days):
return DateTimeIntervalType.Days;
case(IntervalType.Hours):
return DateTimeIntervalType.Hours;
case(IntervalType.Minutes):
return DateTimeIntervalType.Minutes;
case(IntervalType.Months):
return DateTimeIntervalType.Months;
case(IntervalType.Number):
return DateTimeIntervalType.Number;
case(IntervalType.Weeks):
return DateTimeIntervalType.Weeks;
case(IntervalType.Years):
return DateTimeIntervalType.Years;
}
return DateTimeIntervalType.Auto;
}
#endregion
#region Insert empty data points overloaded methods
/// <summary>
/// Insert empty data points using the specified interval.
/// </summary>
/// <param name="interval">Interval size.</param>
/// <param name="intervalType">Interval type.</param>
/// <param name="series">Series to insert the empty points.</param>
public void InsertEmptyPoints(
double interval,
IntervalType intervalType,
Series series)
{
InsertEmptyPoints(interval, intervalType, 0, IntervalType.Number, series);
}
/// <summary>
/// Insert empty data points using the specified interval.
/// </summary>
/// <param name="interval">Interval size.</param>
/// <param name="intervalType">Interval type.</param>
/// <param name="seriesName">Name of series to insert the empty points.</param>
public void InsertEmptyPoints(
double interval,
IntervalType intervalType,
string seriesName)
{
InsertEmptyPoints(interval, intervalType, 0, IntervalType.Number, seriesName);
}
/// <summary>
/// Insert empty data points using the specified interval.
/// </summary>
/// <param name="interval">Interval size.</param>
/// <param name="intervalType">Interval type.</param>
/// <param name="intervalOffset">Interval offset size.</param>
/// <param name="intervalOffsetType">Interval offset type.</param>
/// <param name="seriesName">Name of series to insert the empty points.</param>
public void InsertEmptyPoints(
double interval,
IntervalType intervalType,
double intervalOffset,
IntervalType intervalOffsetType,
string seriesName)
{
InsertEmptyPoints(interval, intervalType, intervalOffset, intervalOffsetType, double.NaN, double.NaN, seriesName);
}
/// <summary>
/// Insert empty data points using the specified interval.
/// </summary>
/// <param name="interval">Interval size.</param>
/// <param name="intervalType">Interval type.</param>
/// <param name="intervalOffset">Interval offset size.</param>
/// <param name="intervalOffsetType">Interval offset type.</param>
/// <param name="series">Series to insert the empty points.</param>
public void InsertEmptyPoints(
double interval,
IntervalType intervalType,
double intervalOffset,
IntervalType intervalOffsetType,
Series series)
{
InsertEmptyPoints(interval, intervalType, intervalOffset, intervalOffsetType, double.NaN, double.NaN, series);
}
/// <summary>
/// Insert empty data points using the specified interval.
/// </summary>
/// <param name="interval">Interval size.</param>
/// <param name="intervalType">Interval type.</param>
/// <param name="intervalOffset">Interval offset size.</param>
/// <param name="intervalOffsetType">Interval offset type.</param>
/// <param name="fromXValue">Check intervals from this X value.</param>
/// <param name="toXValue">Check intervals until this X value.</param>
/// <param name="seriesName">Name of series to insert the empty points.</param>
public void InsertEmptyPoints(
double interval,
IntervalType intervalType,
double intervalOffset,
IntervalType intervalOffsetType,
double fromXValue,
double toXValue,
string seriesName)
{
// Check arguments
if (seriesName == null)
throw new ArgumentNullException("seriesName");
InsertEmptyPoints(
interval,
intervalType,
intervalOffset,
intervalOffsetType,
fromXValue,
toXValue,
ConvertToSeriesArray(seriesName, false));
}
/// <summary>
/// Insert empty data points using the specified interval.
/// </summary>
/// <param name="interval">Interval size.</param>
/// <param name="intervalType">Interval type.</param>
/// <param name="intervalOffset">Interval offset size.</param>
/// <param name="intervalOffsetType">Interval offset type.</param>
/// <param name="fromXValue">Check intervals from this X value.</param>
/// <param name="toXValue">Check intervals until this X value.</param>
/// <param name="series">Series to insert the empty points.</param>
public void InsertEmptyPoints(
double interval,
IntervalType intervalType,
double intervalOffset,
IntervalType intervalOffsetType,
double fromXValue,
double toXValue,
Series series)
{
// Check arguments
if (series == null)
throw new ArgumentNullException("series");
InsertEmptyPoints(
interval,
intervalType,
intervalOffset,
intervalOffsetType,
fromXValue,
toXValue,
ConvertToSeriesArray(series, false));
}
#endregion
#region Series data exporting methods
/// <summary>
/// Export series data into the DataSet object.
/// </summary>
/// <param name="series">Array of series which should be exported.</param>
/// <returns>Data set object with series data.</returns>
internal DataSet ExportSeriesValues(Series[] series)
{
//*****************************************************
//** Create DataSet object
//*****************************************************
DataSet dataSet = new DataSet();
dataSet.Locale = System.Globalization.CultureInfo.CurrentCulture;
// If input series are specified
if(series != null)
{
// Export each series in the loop
foreach(Series ser in series)
{
//*****************************************************
//** Check if all X values are zeros
//*****************************************************
bool zeroXValues = true;
foreach( DataPoint point in ser.Points )
{
if( point.XValue != 0.0 )
{
zeroXValues = false;
break;
}
}
// Added 10 May 2005, DT - dataset after databinding
// to string x value returns X as indexes
if (zeroXValues && ser.XValueType == ChartValueType.String)
{
zeroXValues = false;
}
//*****************************************************
//** Create new table for the series
//*****************************************************
DataTable seriesTable = new DataTable(ser.Name);
seriesTable.Locale = System.Globalization.CultureInfo.CurrentCulture;
//*****************************************************
//** Add X column into data table schema
//*****************************************************
Type columnType = typeof(double);
if(ser.IsXValueDateTime())
{
columnType = typeof(DateTime);
}
else if(ser.XValueType == ChartValueType.String)
{
columnType = typeof(string);
}
seriesTable.Columns.Add("X", columnType);
//*****************************************************
//** Add Y column(s) into data table schema
//*****************************************************
columnType = typeof(double);
if(ser.IsYValueDateTime())
{
columnType = typeof(DateTime);
}
else if(ser.YValueType == ChartValueType.String)
{
columnType = typeof(string);
}
for(int yIndex = 0; yIndex < ser.YValuesPerPoint; yIndex++)
{
if(yIndex == 0)
{
seriesTable.Columns.Add("Y", columnType);
}
else
{
seriesTable.Columns.Add("Y" + (yIndex + 1).ToString(System.Globalization.CultureInfo.InvariantCulture), columnType);
}
}
//*****************************************************
//** Fill data table's rows
//*****************************************************
double pointIndex = 1.0;
foreach(DataPoint point in ser.Points)
{
if(!point.IsEmpty || !this.IsEmptyPointIgnored)
{
DataRow dataRow = seriesTable.NewRow();
// Set row X value
object xValue = point.XValue;
if(ser.IsXValueDateTime())
{
if (Double.IsNaN(point.XValue))
xValue = DBNull.Value;
else
xValue = DateTime.FromOADate(point.XValue);
}
else if(ser.XValueType == ChartValueType.String)
{
xValue = point.AxisLabel;
}
dataRow["X"] = (zeroXValues) ? pointIndex : xValue;
// Set row Y value(s)
for(int yIndex = 0; yIndex < ser.YValuesPerPoint; yIndex++)
{
object yValue = point.YValues[yIndex];
if(!point.IsEmpty)
{
if(ser.IsYValueDateTime())
{
if (Double.IsNaN(point.YValues[yIndex]))
xValue = DBNull.Value;
else
yValue = DateTime.FromOADate(point.YValues[yIndex]);
}
else if(ser.YValueType == ChartValueType.String)
{
yValue = point.AxisLabel;
}
}
else if(!this.IsEmptyPointIgnored)
{
// Special handling of empty points
yValue = DBNull.Value;
}
if(yIndex == 0)
{
dataRow["Y"] = yValue;
}
else
{
dataRow["Y" + (yIndex + 1).ToString(System.Globalization.CultureInfo.InvariantCulture)] = yValue;
}
}
// Add row to the table
seriesTable.Rows.Add(dataRow);
++pointIndex;
}
}
// Accept changes
seriesTable.AcceptChanges();
//*****************************************************
//** Add data table into the data set
//*****************************************************
dataSet.Tables.Add(seriesTable);
}
}
return dataSet;
}
#endregion
#region Series data exporting overloaded methods
/// <summary>
/// Export all series from the collection into the DataSet object.
/// </summary>
/// <returns>Dataset object with series data.</returns>
public DataSet ExportSeriesValues()
{
return ExportSeriesValues("*");
}
/// <summary>
/// Export series data into the DataSet object.
/// </summary>
/// <param name="seriesNames">Comma separated list of series names to be exported.</param>
/// <returns>Dataset object with series data.</returns>
public DataSet ExportSeriesValues(string seriesNames)
{
// Check arguments
if (seriesNames == null)
throw new ArgumentNullException(seriesNames);
return ExportSeriesValues(ConvertToSeriesArray(seriesNames, false));
}
/// <summary>
/// Export series data into the DataSet object.
/// </summary>
/// <param name="series">Series to be exported.</param>
/// <returns>Dataset object with series data.</returns>
public DataSet ExportSeriesValues(Series series)
{
// Check arguments
if (series == null)
throw new ArgumentNullException("series");
return ExportSeriesValues(ConvertToSeriesArray(series, false));
}
#endregion
#region Filtering properties
/// <summary>
/// Gets or sets a flag which indicates whether points filtered by
/// the Filter or FilterTopN methods are removed or marked as empty.
/// If set to true, filtered points are marked as empty; otherwise they are removed.
/// This property defaults to be false.
/// </summary>
public bool FilterSetEmptyPoints
{
get
{
return _filterSetEmptyPoints;
}
set
{
_filterSetEmptyPoints = value;
}
}
/// <summary>
/// Gets or sets a value that determines if points are filtered
/// if they match criteria that is specified in Filter method calls.
/// If set to true, points that match specified criteria are filtered.
/// If set to false, points that do not match the criteria are filtered.
/// This property defaults to be true.
/// </summary>
public bool FilterMatchedPoints
{
get
{
return _filterMatchedPoints;
}
set
{
_filterMatchedPoints = value;
}
}
#endregion
#region Filtering methods
/// <summary>
/// Keeps only N top/bottom points of the series
/// </summary>
/// <param name="pointCount">Number of top/bottom points to return.</param>
/// <param name="inputSeries">Input series array.</param>
/// <param name="outputSeries">Output series array.</param>
/// <param name="usingValue">Defines which value of the point use in comparison (X, Y, Y2, ...).</param>
/// <param name="getTopValues">Indicate that N top values must be retrieved, otherwise N bottom values.</param>
private void FilterTopN(int pointCount,
Series[] inputSeries,
Series[] outputSeries,
string usingValue,
bool getTopValues)
{
// Check input/output series arrays
CheckSeriesArrays(inputSeries, outputSeries);
// Check input series alignment
CheckXValuesAlignment(inputSeries);
if(pointCount <= 0)
{
throw (new ArgumentOutOfRangeException("pointCount", SR.ExceptionDataManipulatorPointCountIsZero));
}
//**************************************************
//** Filter points in the first series and remove
//** in all
//**************************************************
// Define an output series array
Series[] output = new Series[inputSeries.Length];
for(int seriesIndex = 0; seriesIndex < inputSeries.Length; seriesIndex++)
{
output[seriesIndex] = inputSeries[seriesIndex];
if(outputSeries != null && outputSeries.Length > seriesIndex)
{
output[seriesIndex] = outputSeries[seriesIndex];
}
// Remove all points from the output series
if(output[seriesIndex] != inputSeries[seriesIndex])
{
output[seriesIndex].Points.Clear();
// Make sure there is enough Y values per point
output[seriesIndex].YValuesPerPoint = inputSeries[seriesIndex].YValuesPerPoint;
// Copy X values type
if(output[seriesIndex].XValueType == ChartValueType.Auto || output[seriesIndex].autoXValueType)
{
output[seriesIndex].XValueType = inputSeries[seriesIndex].XValueType;
output[seriesIndex].autoXValueType = true;
}
// Copy Y values type
if(output[seriesIndex].YValueType == ChartValueType.Auto || output[seriesIndex].autoYValueType)
{
output[seriesIndex].YValueType = inputSeries[seriesIndex].YValueType;
output[seriesIndex].autoYValueType = true;
}
// Copy input points into output
foreach(DataPoint point in inputSeries[seriesIndex].Points)
{
output[seriesIndex].Points.Add(point.Clone());
}
}
}
// No points to filter
if(inputSeries[0].Points.Count == 0)
{
return;
}
//**************************************************
//** Sort input data
//**************************************************
this.Sort((getTopValues) ? PointSortOrder.Descending : PointSortOrder.Ascending,
usingValue,
output);
//**************************************************
//** Get top/bottom points
//**************************************************
// Process all series
for(int seriesIndex = 0; seriesIndex < inputSeries.Length; seriesIndex++)
{
// Only keep N first points
while(output[seriesIndex].Points.Count > pointCount)
{
if(this.FilterSetEmptyPoints)
{
output[seriesIndex].Points[pointCount].IsEmpty = true;
++pointCount;
}
else
{
output[seriesIndex].Points.RemoveAt(pointCount);
}
}
}
}
/// <summary>
/// Filter data points using IDataPointFilter interface
/// </summary>
/// <param name="filterInterface">Data points filtering interface.</param>
/// <param name="inputSeries">Input series array.</param>
/// <param name="outputSeries">Output series array.</param>
private void Filter(IDataPointFilter filterInterface,
Series[] inputSeries,
Series[] outputSeries)
{
//**************************************************
//** Check input/output series arrays
//**************************************************
CheckSeriesArrays(inputSeries, outputSeries);
CheckXValuesAlignment(inputSeries);
if(filterInterface == null)
{
throw(new ArgumentNullException("filterInterface"));
}
//**************************************************
//** Filter points in the first series and remove
//** in all
//**************************************************
// Define an output series array
Series[] output = new Series[inputSeries.Length];
for(int seriesIndex = 0; seriesIndex < inputSeries.Length; seriesIndex++)
{
output[seriesIndex] = inputSeries[seriesIndex];
if(outputSeries != null && outputSeries.Length > seriesIndex)
{
output[seriesIndex] = outputSeries[seriesIndex];
}
// Remove all points from the output series
if(output[seriesIndex] != inputSeries[seriesIndex])
{
output[seriesIndex].Points.Clear();
// Make sure there is enough Y values per point
output[seriesIndex].YValuesPerPoint = inputSeries[seriesIndex].YValuesPerPoint;
// Copy X values type
if(output[seriesIndex].XValueType == ChartValueType.Auto || output[seriesIndex].autoXValueType)
{
output[seriesIndex].XValueType = inputSeries[seriesIndex].XValueType;
output[seriesIndex].autoXValueType = true;
}
// Copy Y values type
if(output[seriesIndex].YValueType == ChartValueType.Auto || output[seriesIndex].autoYValueType)
{
output[seriesIndex].YValueType = inputSeries[seriesIndex].YValueType;
output[seriesIndex].autoYValueType = true;
}
}
}
// No points to filter
if(inputSeries[0].Points.Count == 0)
{
return;
}
//**************************************************
//** Loop through all points of the first input series
//**************************************************
int originalPointIndex = 0;
for(int pointIndex = 0; pointIndex < inputSeries[0].Points.Count; pointIndex++, originalPointIndex++)
{
bool pointRemoved = false;
// Check if point match the criteria
bool matchCriteria = filterInterface.FilterDataPoint(
inputSeries[0].Points[pointIndex],
inputSeries[0],
originalPointIndex) == this.FilterMatchedPoints;
// Process all series
for(int seriesIndex = 0; seriesIndex < inputSeries.Length; seriesIndex++)
{
bool seriesMatchCriteria = matchCriteria;
if(output[seriesIndex] != inputSeries[seriesIndex])
{
if(seriesMatchCriteria && !this.FilterSetEmptyPoints)
{
// Don't do anything...
seriesMatchCriteria = false;
}
else
{
// Copy point into the output series for all series
output[seriesIndex].Points.Add(inputSeries[seriesIndex].Points[pointIndex].Clone());
}
}
// If point match the criteria
if(seriesMatchCriteria)
{
// Set point's empty flag
if(this.FilterSetEmptyPoints)
{
output[seriesIndex].Points[pointIndex].IsEmpty = true;
for(int valueIndex = 0; valueIndex < output[seriesIndex].Points[pointIndex].YValues.Length; valueIndex++)
{
output[seriesIndex].Points[pointIndex].YValues[valueIndex] = 0.0;
}
}
// Remove point
else
{
output[seriesIndex].Points.RemoveAt(pointIndex);
pointRemoved = true;
}
}
}
// Adjust index because of the removed point
if(pointRemoved)
{
--pointIndex;
}
}
}
/// <summary>
/// Data point filter.
/// Filters points using element type and index
/// </summary>
private class PointElementFilter : IDataPointFilter
{
// Private fields
private DataManipulator _dataManipulator = null;
private DateRangeType _dateRange;
private int[] _rangeElements = null;
// Default constructor is not accesiable
private PointElementFilter()
{
}
/// <summary>
/// Public constructor.
/// </summary>
/// <param name="dataManipulator">Data manipulator object.</param>
/// <param name="dateRange">Range type.</param>
/// <param name="rangeElements">Range elements to filter.</param>
public PointElementFilter(DataManipulator dataManipulator, DateRangeType dateRange, string rangeElements)
{
this._dataManipulator = dataManipulator;
this._dateRange = dateRange;
this._rangeElements = dataManipulator.ConvertElementIndexesToArray(rangeElements);
}
/// <summary>
/// Data points filtering method.
/// </summary>
/// <param name="point">Data point.</param>
/// <param name="series">Data point series.</param>
/// <param name="pointIndex">Data point index.</param>
/// <returns>Indicates that point should be filtered.</returns>
public bool FilterDataPoint(DataPoint point, Series series, int pointIndex)
{
return _dataManipulator.CheckFilterElementCriteria(
this._dateRange,
this._rangeElements,
point);
}
}
/// <summary>
/// Data point filter.
/// Filters points using point values
/// </summary>
private class PointValueFilter : IDataPointFilter
{
// Private fields
private CompareMethod _compareMethod;
private string _usingValue;
private double _compareValue;
/// <summary>
/// Default constructor is not accessible
/// </summary>
private PointValueFilter()
{
}
/// <summary>
/// Public constructor.
/// </summary>
/// <param name="compareMethod">Comparing method.</param>
/// <param name="compareValue">Comparing constant.</param>
/// <param name="usingValue">Value used in comparison.</param>
public PointValueFilter(CompareMethod compareMethod,
double compareValue,
string usingValue)
{
this._compareMethod = compareMethod;
this._usingValue = usingValue;
this._compareValue = compareValue;
}
/// <summary>
/// IDataPointFilter interface method implementation
/// </summary>
/// <param name="point">Data point.</param>
/// <param name="series">Data point series.</param>
/// <param name="pointIndex">Data point index.</param>
/// <returns>Indicates that point should be filtered.</returns>
public bool FilterDataPoint(DataPoint point, Series series, int pointIndex)
{
// Check if point match the criteria
bool matchCriteria = false;
switch(_compareMethod)
{
case(CompareMethod.EqualTo):
matchCriteria = point.GetValueByName(_usingValue)
== _compareValue;
break;
case(CompareMethod.LessThan):
matchCriteria = point.GetValueByName(_usingValue)
< _compareValue;
break;
case(CompareMethod.LessThanOrEqualTo):
matchCriteria = point.GetValueByName(_usingValue)
<= _compareValue;
break;
case(CompareMethod.MoreThan):
matchCriteria = point.GetValueByName(_usingValue)
> _compareValue;
break;
case(CompareMethod.MoreThanOrEqualTo):
matchCriteria = point.GetValueByName(_usingValue)
>= _compareValue;
break;
case(CompareMethod.NotEqualTo):
matchCriteria = point.GetValueByName(_usingValue)
!= _compareValue;
break;
}
return matchCriteria;
}
}
/// <summary>
/// Helper function to convert elements indexes from a string
/// into an array of integers
/// </summary>
/// <param name="rangeElements">Element indexes string. Ex:"3,5,6-9,15"</param>
/// <returns>Array of integer indexes.</returns>
private int[] ConvertElementIndexesToArray(string rangeElements)
{
// Split input string by comma
string[] indexes = rangeElements.Split(',');
// Check if there are items in the array
if(indexes.Length == 0)
{
throw (new ArgumentException(SR.ExceptionDataManipulatorIndexUndefined, "rangeElements"));
}
// Allocate memory for the result array
int[] result = new int[indexes.Length * 2];
// Process each element index
int index = 0;
foreach(string str in indexes)
{
// Check if it's a simple index or a range
if(str.IndexOf('-') != -1)
{
string[] rangeIndex = str.Split('-');
if(rangeIndex.Length == 2)
{
// Convert to integer
try
{
result[index] = Int32.Parse(rangeIndex[0], System.Globalization.CultureInfo.InvariantCulture);
result[index + 1] = Int32.Parse(rangeIndex[1], System.Globalization.CultureInfo.InvariantCulture);
if(result[index + 1] < result[index])
{
int temp = result[index];
result[index] = result[index + 1];
result[index + 1] = temp;
}
}
catch(System.Exception)
{
throw (new ArgumentException(SR.ExceptionDataManipulatorIndexFormatInvalid, "rangeElements"));
}
}
else
{
throw (new ArgumentException(SR.ExceptionDataManipulatorIndexFormatInvalid, "rangeElements"));
}
}
else
{
// Convert to integer
try
{
result[index] = Int32.Parse(str, System.Globalization.CultureInfo.InvariantCulture);
result[index + 1] = result[index];
}
catch(System.Exception)
{
throw (new ArgumentException(SR.ExceptionDataManipulatorIndexFormatInvalid, "rangeElements"));
}
}
index += 2;
}
return result;
}
/// <summary>
/// Helper function, which checks if specified point matches the criteria
/// </summary>
/// <param name="dateRange">Element type.</param>
/// <param name="rangeElements">Array of element indexes ranges (pairs).</param>
/// <param name="point">Data point to check.</param>
/// <returns>True if point matches the criteria.</returns>
private bool CheckFilterElementCriteria(
DateRangeType dateRange,
int[] rangeElements,
DataPoint point)
{
// Conver X value to DateTime
DateTime dateTimeValue = DateTime.FromOADate(point.XValue);
for(int index = 0; index < rangeElements.Length; index += 2)
{
switch(dateRange)
{
case(DateRangeType.Year):
if(dateTimeValue.Year >= rangeElements[index] &&
dateTimeValue.Year <= rangeElements[index+1])
return true;
break;
case(DateRangeType.Month):
if(dateTimeValue.Month >= rangeElements[index] &&
dateTimeValue.Month <= rangeElements[index+1])
return true;
break;
case(DateRangeType.DayOfWeek):
if((int)dateTimeValue.DayOfWeek >= rangeElements[index] &&
(int)dateTimeValue.DayOfWeek <= rangeElements[index+1])
return true;
break;
case(DateRangeType.DayOfMonth):
if(dateTimeValue.Day >= rangeElements[index] &&
dateTimeValue.Day <= rangeElements[index+1])
return true;
break;
case(DateRangeType.Hour):
if(dateTimeValue.Hour >= rangeElements[index] &&
dateTimeValue.Hour <= rangeElements[index+1])
return true;
break;
case(DateRangeType.Minute):
if(dateTimeValue.Minute >= rangeElements[index] &&
dateTimeValue.Minute <= rangeElements[index+1])
return true;
break;
}
}
return false;
}
#endregion
#region Filtering overloaded methods
/// <summary>
/// Filters a series' data points, either removing the specified points
/// or marking them as empty for the given date/time ranges.
/// </summary>
/// <param name="dateRange">Element type.</param>
/// <param name="rangeElements">Specifies the elements within the date/time range
/// (specified by the dateRange parameter) that will be filtered. Can be a single value (e.g. "7"),
/// comma-separated values (e.g. "5,6"), a range of values (e.g. 9-11),
/// or any variation thereof (e.g. "5,6,9-11").</param>
/// <param name="inputSeriesNames">Comma separated list of input series names.</param>
/// <param name="outputSeriesNames">Comma separated list of output series names, to store the output.</param>
public void Filter(DateRangeType dateRange,
string rangeElements,
string inputSeriesNames,
string outputSeriesNames)
{
// Check arguments
if (rangeElements == null)
throw new ArgumentNullException("rangeElements");
if (inputSeriesNames == null)
throw new ArgumentNullException("inputSeriesNames");
// Filter points using filtering interface
Filter(new PointElementFilter(this, dateRange, rangeElements),
ConvertToSeriesArray(inputSeriesNames, false),
ConvertToSeriesArray(outputSeriesNames, true));
}
/// <summary>
/// Filters a series' data points, either removing the specified points
/// or marking them as empty for the given date/time ranges.
/// The Series object that is filtered is used to store the modified data.
/// </summary>
/// <param name="dateRange">Element type.</param>
/// <param name="rangeElements">Specifies the elements within the date/time range
/// (specified by the dateRange parameter) that will be filtered. Can be a single value (e.g. "7"),
/// comma-separated values (e.g. "5,6"), a range of values (e.g. 9-11),
/// or any variation thereof (e.g. "5,6,9-11").</param>
/// <param name="inputSeries">Input series.</param>
public void Filter(DateRangeType dateRange,
string rangeElements,
Series inputSeries)
{
// Check arguments
if (rangeElements == null)
throw new ArgumentNullException("rangeElements");
if (inputSeries == null)
throw new ArgumentNullException("inputSeries");
Filter(dateRange, rangeElements, inputSeries, null);
}
/// <summary>
/// Filters a series' data points, either removing the specified points
/// or marking them as empty for the given date/time ranges.
/// </summary>
/// <param name="dateRange">Element type.</param>
/// <param name="rangeElements">Specifies the elements within the date/time range
/// (specified by the dateRange parameter) that will be filtered. Can be a single value (e.g. "7"),
/// comma-separated values (e.g. "5,6"), a range of values (e.g. 9-11),
/// or any variation thereof (e.g. "5,6,9-11").</param>
/// <param name="inputSeries">Input series.</param>
/// <param name="outputSeries">Output series.</param>
public void Filter(DateRangeType dateRange,
string rangeElements,
Series inputSeries,
Series outputSeries)
{
// Check arguments
if (rangeElements == null)
throw new ArgumentNullException("rangeElements");
if (inputSeries == null)
throw new ArgumentNullException("inputSeries");
// Filter points using filtering interface
Filter(new PointElementFilter(this, dateRange, rangeElements),
ConvertToSeriesArray(inputSeries, false),
ConvertToSeriesArray(outputSeries, false));
}
/// <summary>
/// Filters a series' data points, either removing the specified points
/// or marking them as empty for the given date/time ranges.
/// The filtered Series objects are used to store the modified data.
/// </summary>
/// <param name="dateRange">Element type.</param>
/// <param name="rangeElements">Specifies the elements within the date/time range
/// (specified by the dateRange parameter) that will be filtered. Can be a single value (e.g. "7"),
/// comma-separated values (e.g. "5,6"), a range of values (e.g. 9-11),
/// or any variation thereof (e.g. "5,6,9-11").</param>
/// <param name="inputSeriesNames">Comma separated list of input series names.</param>
public void Filter(DateRangeType dateRange,
string rangeElements,
string inputSeriesNames)
{
// Check arguments
if (rangeElements == null)
throw new ArgumentNullException("rangeElements");
if (inputSeriesNames == null)
throw new ArgumentNullException("inputSeriesNames");
Filter(dateRange,
rangeElements,
inputSeriesNames,
"");
}
/// <summary>
/// Filters a series' data points by applying a filtering rule to the first Y-value of data points.
/// The Series object that is filtered is used to store the modified data.
/// </summary>
/// <param name="compareMethod">Value comparing method.</param>
/// <param name="compareValue">Value to compare with.</param>
/// <param name="inputSeries">Input series.</param>
public void Filter(CompareMethod compareMethod,
double compareValue,
Series inputSeries)
{
// Check arguments
if (inputSeries == null)
throw new ArgumentNullException("inputSeries");
Filter(compareMethod,
compareValue,
inputSeries,
null,
"Y");
}
/// <summary>
/// Filters a series' data points by applying a filtering rule to the first Y-value of data points.
/// </summary>
/// <param name="compareMethod">Value comparing method.</param>
/// <param name="compareValue">Value to compare with.</param>
/// <param name="inputSeries">Input series.</param>
/// <param name="outputSeries">Output series.</param>
public void Filter(CompareMethod compareMethod,
double compareValue,
Series inputSeries,
Series outputSeries)
{
// Check arguments
if (inputSeries == null)
throw new ArgumentNullException("inputSeries");
// Filter points using filtering interface
Filter(new PointValueFilter(compareMethod, compareValue, "Y"),
ConvertToSeriesArray(inputSeries, false),
ConvertToSeriesArray(outputSeries, false));
}
/// <summary>
/// Filters a series' data points by applying a filtering rule to the specified value for comparison.
/// </summary>
/// <param name="compareMethod">Value comparing method.</param>
/// <param name="compareValue">Value to compare with.</param>
/// <param name="inputSeries">Input series.</param>
/// <param name="outputSeries">Output series.</param>
/// <param name="usingValue">The data point value that the filtering rule is applied to. Can be X, Y, Y2, Y3, etc.</param>
public void Filter(CompareMethod compareMethod,
double compareValue,
Series inputSeries,
Series outputSeries,
string usingValue)
{
// Check arguments
if (inputSeries == null)
throw new ArgumentNullException("inputSeries");
if (usingValue == null)
throw new ArgumentNullException("usingValue");
// Filter points using filtering interface
Filter(new PointValueFilter(compareMethod, compareValue, usingValue),
ConvertToSeriesArray(inputSeries, false),
ConvertToSeriesArray(outputSeries, false));
}
/// <summary>
/// Filters one or more series by applying a filtering rule to the first Y-value of the first series' data points.
/// The filtered Series objects are used to store the modified data.
/// </summary>
/// <param name="compareMethod">Value comparing method.</param>
/// <param name="compareValue">Value to compare with.</param>
/// <param name="inputSeriesNames">Comma separated list of input series names.</param>
public void Filter(CompareMethod compareMethod,
double compareValue,
string inputSeriesNames)
{
// Check arguments
if (inputSeriesNames == null)
throw new ArgumentNullException("inputSeriesNames");
Filter(compareMethod,
compareValue,
inputSeriesNames,
"",
"Y");
}
/// <summary>
/// Filters one or more series by applying a filtering rule to the first Y-value of the first series' data points.
/// </summary>
/// <param name="compareMethod">Value comparing method.</param>
/// <param name="compareValue">Value to compare with.</param>
/// <param name="inputSeriesNames">Comma separated list of input series names.</param>
/// <param name="outputSeriesNames">Comma separated list of output series names.</param>
public void Filter(CompareMethod compareMethod,
double compareValue,
string inputSeriesNames,
string outputSeriesNames)
{
// Check arguments
if (inputSeriesNames == null)
throw new ArgumentNullException("inputSeriesNames");
// Filter points using filtering interface
Filter(new PointValueFilter(compareMethod, compareValue, "Y"),
ConvertToSeriesArray(inputSeriesNames, false),
ConvertToSeriesArray(outputSeriesNames, true));
}
/// <summary>
/// Filters one or more series by applying a filtering rule to the specified value of the first series' data points.
/// </summary>
/// <param name="compareMethod">Value comparing method.</param>
/// <param name="compareValue">Value to compare with.</param>
/// <param name="inputSeriesNames">Comma separated input series names.</param>
/// <param name="outputSeriesNames">Comma separated output series names.</param>
/// <param name="usingValue">The data point value that the filtering rule is applied to. Can be X, Y, Y2, Y3, etc.</param>
public void Filter(CompareMethod compareMethod,
double compareValue,
string inputSeriesNames,
string outputSeriesNames,
string usingValue)
{
// Check arguments
if (inputSeriesNames == null)
throw new ArgumentNullException("inputSeriesNames");
if (usingValue == null)
throw new ArgumentNullException("usingValue");
// Filter points using filtering interface
Filter(new PointValueFilter(compareMethod, compareValue, usingValue),
ConvertToSeriesArray(inputSeriesNames, false),
ConvertToSeriesArray(outputSeriesNames, true));
}
/// <summary>
/// Filters all data points in one or more series except for a specified number of points.
/// The points that are not filtered correspond to points in the first input series that have the largest or smallest values.
/// </summary>
/// <param name="pointCount">The number of data points that the filtering operation will not remove.</param>
/// <param name="inputSeriesNames">Comma separated list of input series names.</param>
/// <param name="outputSeriesNames">Comma separated list of output series names.</param>
/// <param name="usingValue">The data point value that the filtering rule is applied to. Can be X, Y, Y2, Y3, etc.</param>
/// <param name="getTopValues">The largest values are kept if set to true; otherwise the smallest values are kept.</param>
public void FilterTopN(int pointCount,
string inputSeriesNames,
string outputSeriesNames,
string usingValue,
bool getTopValues)
{
// Check arguments
if (inputSeriesNames == null)
throw new ArgumentNullException("inputSeriesNames");
if (usingValue == null)
throw new ArgumentNullException("usingValue");
FilterTopN(pointCount,
ConvertToSeriesArray(inputSeriesNames, false),
ConvertToSeriesArray(outputSeriesNames, true),
usingValue,
getTopValues);
}
/// <summary>
/// Filters out all data points in a series except for a specified number of points with the largest (first) Y-values.
/// The Series object that is filtered is used to store the modified data.
/// </summary>
/// <param name="pointCount">The number of data points that the filtering operation will not remove.</param>
/// <param name="inputSeries">Input series.</param>
public void FilterTopN(int pointCount,
Series inputSeries)
{
// Check arguments
if (inputSeries == null)
throw new ArgumentNullException("inputSeries");
FilterTopN(pointCount,
ConvertToSeriesArray(inputSeries, false),
null,
"Y",
true);
}
/// <summary>
/// Filters all data points in a series except for a specified number of points with the largest first Y-values.
/// </summary>
/// <param name="pointCount">The number of data points that the filtering operation will not remove.</param>
/// <param name="inputSeries">Input series.</param>
/// <param name="outputSeries">Output series.</param>
public void FilterTopN(int pointCount,
Series inputSeries,
Series outputSeries)
{
// Check arguments
if (inputSeries == null)
throw new ArgumentNullException("inputSeries");
FilterTopN(pointCount,
ConvertToSeriesArray(inputSeries, false),
ConvertToSeriesArray(outputSeries, false),
"Y",
true);
}
/// <summary>
/// Filters all data points in a series except for a specified number of points with the largest values.
/// </summary>
/// <param name="pointCount">The number of data points that the filtering operation will not remove.</param>
/// <param name="inputSeries">Input series.</param>
/// <param name="outputSeries">Output series.</param>
/// <param name="usingValue">The data point value that the filtering rule is applied to. Can be X, Y, Y2, Y3, etc.</param>
public void FilterTopN(int pointCount,
Series inputSeries,
Series outputSeries,
string usingValue)
{
// Check arguments
if (inputSeries == null)
throw new ArgumentNullException("inputSeries");
if (usingValue == null)
throw new ArgumentNullException("usingValue");
FilterTopN(pointCount,
ConvertToSeriesArray(inputSeries, false),
ConvertToSeriesArray(outputSeries, false),
usingValue,
true);
}
/// <summary>
/// Filters all data points in a series except for a specified number of points with the smallest or largest values.
/// </summary>
/// <param name="pointCount">The number of data points that the filtering operation will not remove.</param>
/// <param name="inputSeries">Input series.</param>
/// <param name="outputSeries">Output series.</param>
/// <param name="usingValue">The data point value that the filtering rule is applied to. Can be X, Y, Y2, Y3, etc.</param>
/// <param name="getTopValues">The largest values are kept if set to true; otherwise the smallest values are kept.</param>
public void FilterTopN(int pointCount,
Series inputSeries,
Series outputSeries,
string usingValue,
bool getTopValues)
{
// Check arguments
if (inputSeries == null)
throw new ArgumentNullException("inputSeries");
if (usingValue == null)
throw new ArgumentNullException("usingValue");
FilterTopN(pointCount,
ConvertToSeriesArray(inputSeries, false),
ConvertToSeriesArray(outputSeries, false),
usingValue,
getTopValues);
}
/// <summary>
/// Filters all data points in one or more series except for a specified number of points.
/// The points that are not filtered correspond to points in the first series that have the largest first Y-values.
/// The Series objects that are filtered are used to store the modified data.
/// </summary>
/// <param name="pointCount">The number of data points that the filtering operation will not remove.</param>
/// <param name="inputSeriesNames">Comma separated list of input series names.</param>
public void FilterTopN(int pointCount,
string inputSeriesNames)
{
// Check arguments
if (inputSeriesNames == null)
throw new ArgumentNullException("inputSeriesNames");
FilterTopN(pointCount,
ConvertToSeriesArray(inputSeriesNames, false),
null,
"Y",
true);
}
/// <summary>
/// Filters out data points in one or more series except for a specified number of points.
/// The points that aren't filtered correspond to points in the first series that have the largest first Y-values.
/// </summary>
/// <param name="pointCount">The number of data points that the filtering operation will not remove.</param>
/// <param name="inputSeriesNames">Comma separated list of input series names.</param>
/// <param name="outputSeriesNames">Comma separated list of output series names.</param>
public void FilterTopN(int pointCount,
string inputSeriesNames,
string outputSeriesNames)
{
// Check arguments
if (inputSeriesNames == null)
throw new ArgumentNullException("inputSeriesNames");
FilterTopN(pointCount,
ConvertToSeriesArray(inputSeriesNames, false),
ConvertToSeriesArray(outputSeriesNames, true),
"Y",
true);
}
/// <summary>
/// Filters all data points in one or more series except for a specified number of points.
/// The points that are not filtered correspond to points in the first series that have the largest values.
/// </summary>
/// <param name="pointCount">The number of data points that the filtering operation will not remove.</param>
/// <param name="inputSeriesNames">Comma separated list of input series names.</param>
/// <param name="outputSeriesNames">Comma separated list of output series names.</param>
/// <param name="usingValue">The data point value that the filtering rule is applied to. Can be X, Y, Y2, Y3, etc.</param>
public void FilterTopN(int pointCount,
string inputSeriesNames,
string outputSeriesNames,
string usingValue)
{
// Check arguments
if (inputSeriesNames == null)
throw new ArgumentNullException("inputSeriesNames");
if (usingValue == null)
throw new ArgumentNullException("usingValue");
FilterTopN(pointCount,
ConvertToSeriesArray(inputSeriesNames, false),
ConvertToSeriesArray(outputSeriesNames, true),
usingValue,
true);
}
/// <summary>
/// Performs custom filtering on a series' data points.
/// The Series object that is filtered is used to store the modified data.
/// </summary>
/// <param name="filterInterface">Filtering interface.</param>
/// <param name="inputSeries">Input series.</param>
public void Filter(IDataPointFilter filterInterface,
Series inputSeries)
{
// Check arguments
if (filterInterface == null)
throw new ArgumentNullException("filterInterface");
if (inputSeries == null)
throw new ArgumentNullException("inputSeries");
Filter(filterInterface,
ConvertToSeriesArray(inputSeries, false),
null);
}
/// <summary>
/// Performs custom filtering on a series' data points.
/// </summary>
/// <param name="filterInterface">Filtering interface.</param>
/// <param name="inputSeries">Input series.</param>
/// <param name="outputSeries">Output series.</param>
public void Filter(IDataPointFilter filterInterface,
Series inputSeries,
Series outputSeries)
{
// Check arguments
if (filterInterface == null)
throw new ArgumentNullException("filterInterface");
if (inputSeries == null)
throw new ArgumentNullException("inputSeries");
Filter(filterInterface,
ConvertToSeriesArray(inputSeries, false),
ConvertToSeriesArray(outputSeries, false));
}
/// <summary>
/// Performs custom filtering on one or more series' data points, based on the first series' points.
/// The filtered series are also used to store the modified data.
/// </summary>
/// <param name="filterInterface">Filtering interface.</param>
/// <param name="inputSeriesNames">Comma separated list of input series names.</param>
public void Filter(IDataPointFilter filterInterface,
string inputSeriesNames)
{
// Check arguments
if (filterInterface == null)
throw new ArgumentNullException("filterInterface");
if (inputSeriesNames == null)
throw new ArgumentNullException("inputSeriesNames");
Filter(filterInterface,
ConvertToSeriesArray(inputSeriesNames, false),
null);
}
/// <summary>
/// Performs custom filtering on one or more series' data points, based on the first series' points.
/// </summary>
/// <param name="filterInterface">Filtering interface.</param>
/// <param name="inputSeriesNames">Comma separated list of input series names.</param>
/// <param name="outputSeriesNames">Comma separated list of output series names.</param>
public void Filter(IDataPointFilter filterInterface,
string inputSeriesNames,
string outputSeriesNames)
{
// Check arguments
if (filterInterface == null)
throw new ArgumentNullException("filterInterface");
if (inputSeriesNames == null)
throw new ArgumentNullException("inputSeriesNames");
Filter(filterInterface,
ConvertToSeriesArray(inputSeriesNames, false),
ConvertToSeriesArray(outputSeriesNames, true));
}
#endregion
#region Grouping methods
/// <summary>
/// Class stores information about the grouping function type and
/// index of output value.
/// </summary>
private class GroupingFunctionInfo
{
// AxisName of the grouping function
internal GroupingFunction function = GroupingFunction.None;
// Index of the Y value for storing results
internal int outputIndex = 0;
/// <summary>
/// Constructor.
/// </summary>
internal GroupingFunctionInfo()
{
}
}
/// <summary>
/// Grouping by X value, when it’s a string (stored in AxisLabel property).
/// </summary>
/// <param name="formula">Grouping formula.</param>
/// <param name="inputSeries">Array of input series.</param>
/// <param name="outputSeries">Array of output series.</param>
private void GroupByAxisLabel(string formula, Series[] inputSeries, Series[] outputSeries)
{
// Check arguments
if (formula == null)
throw new ArgumentNullException("formula");
//**************************************************
//** Check input/output series arrays
//**************************************************
CheckSeriesArrays(inputSeries, outputSeries);
//**************************************************
//** Check and parse formula
//**************************************************
int outputValuesNumber = 1;
GroupingFunctionInfo[] functions = GetGroupingFunctions(inputSeries, formula, out outputValuesNumber);
//**************************************************
//** Loop through all input series
//**************************************************
for(int seriesIndex = 0; seriesIndex < inputSeries.Length; seriesIndex++)
{
// Define an input and output series
Series input = inputSeries[seriesIndex];
Series output = input;
if(outputSeries != null && seriesIndex < outputSeries.Length)
{
output = outputSeries[seriesIndex];
// Remove all points from the output series
if(output.Name != input.Name)
{
output.Points.Clear();
// Copy X values type
if(output.XValueType == ChartValueType.Auto || output.autoXValueType)
{
output.XValueType = input.XValueType;
output.autoXValueType = true;
}
// Copy Y values type
if(output.YValueType == ChartValueType.Auto || output.autoYValueType)
{
output.YValueType = input.YValueType;
output.autoYValueType = true;
}
}
}
// Copy input data into temp storage
if(input != output)
{
Series inputTemp = new Series("Temp", input.YValuesPerPoint);
foreach(DataPoint point in input.Points)
{
DataPoint dp = new DataPoint(inputTemp);
dp.AxisLabel = point.AxisLabel;
dp.XValue = point.XValue;
point.YValues.CopyTo(dp.YValues, 0);
dp.IsEmpty = point.IsEmpty;
inputTemp.Points.Add(dp);
}
input = inputTemp;
}
// No points to group
if(input.Points.Count == 0)
{
continue;
}
// Make sure there is enough Y values per point
output.YValuesPerPoint = outputValuesNumber - 1;
//**************************************************
//** Sort input data by axis label
//**************************************************
input.Sort(PointSortOrder.Ascending, "AxisLabel");
//**************************************************
//** Initialize interval & value tracking variables
//**************************************************
int intervalFirstIndex = 0;
int intervalLastIndex = 0;
//**************************************************
//** Allocate array for storing temp.
//** values of the point
//**************************************************
double[] pointTempValues = new double[outputValuesNumber];
//**************************************************
//** Loop through the series points
//**************************************************
string currentLabel = null;
bool lastPoint = false;
int emptyPointsSkipped = 0;
for(int pointIndex = 0; pointIndex <= input.Points.Count && !lastPoint; pointIndex++)
{
bool endOfInterval = false;
//**************************************************
//** Check if it's the last point
//**************************************************
if(pointIndex == input.Points.Count)
{
// End of the group interval detected
lastPoint = true;
intervalLastIndex = pointIndex - 1;
pointIndex = intervalLastIndex;
endOfInterval = true;
}
// Set current axis label
if(!endOfInterval && currentLabel == null)
{
currentLabel = input.Points[pointIndex].AxisLabel;
}
//**************************************************
//** Check if current point X value is inside current group
//**************************************************
if(!endOfInterval && input.Points[pointIndex].AxisLabel != currentLabel)
{
// End of the group interval detected
intervalLastIndex = pointIndex - 1;
endOfInterval = true;
}
//**************************************************
//** Process data at end of the interval
//**************************************************
if(endOfInterval)
{
// Finalize the calculation
ProcessPointValues(
functions,
pointTempValues,
inputSeries[seriesIndex],
input.Points[pointIndex],
pointIndex,
intervalFirstIndex,
intervalLastIndex,
true,
ref emptyPointsSkipped);
//**************************************************
//** Calculate the X values
//**************************************************
if(functions[0].function == GroupingFunction.Center)
{
pointTempValues[0] =
(inputSeries[seriesIndex].Points[intervalFirstIndex].XValue +
inputSeries[seriesIndex].Points[intervalLastIndex].XValue) / 2.0;
}
else if(functions[0].function == GroupingFunction.First)
{
pointTempValues[0] =
inputSeries[seriesIndex].Points[intervalFirstIndex].XValue;
}
if(functions[0].function == GroupingFunction.Last)
{
pointTempValues[0] =
inputSeries[seriesIndex].Points[intervalLastIndex].XValue;
}
//**************************************************
//** Create new point object
//**************************************************
DataPoint newPoint = new DataPoint();
newPoint.ResizeYValueArray(outputValuesNumber - 1);
newPoint.XValue = pointTempValues[0];
newPoint.AxisLabel = currentLabel;
for(int i = 1; i < pointTempValues.Length; i++)
{
newPoint.YValues[i - 1] = pointTempValues[i];
}
//**************************************************
//** Remove grouped points if output and input
//** series are the same
//**************************************************
int newPointIndex = output.Points.Count;
if(output == input)
{
newPointIndex = intervalFirstIndex;
pointIndex = newPointIndex + 1;
// Remove grouped points
for(int removedPoint = intervalFirstIndex; removedPoint <= intervalLastIndex; removedPoint++)
{
output.Points.RemoveAt(intervalFirstIndex);
}
}
//**************************************************
//** Add point to the output series
//**************************************************
output.Points.Insert(newPointIndex, newPoint);
// Set new group interval indexes
intervalFirstIndex = pointIndex;
intervalLastIndex = pointIndex;
// Reset number of skipped points
emptyPointsSkipped = 0;
currentLabel = null;
// Process point once again
--pointIndex;
continue;
}
//**************************************************
//** Use current point values in the formula
//**************************************************
ProcessPointValues(
functions,
pointTempValues,
inputSeries[seriesIndex],
input.Points[pointIndex],
pointIndex,
intervalFirstIndex,
intervalLastIndex,
false,
ref emptyPointsSkipped);
}
}
}
/// <summary>
/// Groups series points in the interval with offset
/// </summary>
/// <param name="formula">Grouping formula.</param>
/// <param name="interval">Interval size.</param>
/// <param name="intervalType">Interval type.</param>
/// <param name="intervalOffset">Interval offset size.</param>
/// <param name="intervalOffsetType">Interval offset type.</param>
/// <param name="inputSeries">Array of input series.</param>
/// <param name="outputSeries">Array of output series.</param>
private void Group(string formula,
double interval,
IntervalType intervalType,
double intervalOffset,
IntervalType intervalOffsetType,
Series[] inputSeries,
Series[] outputSeries)
{
// Check arguments
if (formula == null)
throw new ArgumentNullException("formula");
//**************************************************
//** Check input/output series arrays
//**************************************************
CheckSeriesArrays(inputSeries, outputSeries);
//**************************************************
//** Check and parse formula
//**************************************************
int outputValuesNumber = 1;
GroupingFunctionInfo[] functions = GetGroupingFunctions(inputSeries, formula, out outputValuesNumber);
//**************************************************
//** Loop through all input series
//**************************************************
for(int seriesIndex = 0; seriesIndex < inputSeries.Length; seriesIndex++)
{
// Define an input and output series
Series input = inputSeries[seriesIndex];
Series output = input;
if(outputSeries != null && seriesIndex < outputSeries.Length)
{
output = outputSeries[seriesIndex];
// Remove all points from the output series
if(output.Name != input.Name)
{
output.Points.Clear();
// Copy X values type
if(output.XValueType == ChartValueType.Auto || output.autoXValueType)
{
output.XValueType = input.XValueType;
output.autoXValueType = true;
}
// Copy Y values type
if(output.YValueType == ChartValueType.Auto || output.autoYValueType)
{
output.YValueType = input.YValueType;
output.autoYValueType = true;
}
}
}
// No points to group
if(input.Points.Count == 0)
{
continue;
}
// Make sure there is enough Y values per point
output.YValuesPerPoint = outputValuesNumber - 1;
//**************************************************
//** Initialize interval & value tracking variables
//**************************************************
int intervalFirstIndex = 0;
int intervalLastIndex = 0;
double intervalFrom = 0;
double intervalTo = 0;
// Set interval start point
intervalFrom = input.Points[0].XValue;
// Adjust start point depending on the interval type
intervalFrom = ChartHelper.AlignIntervalStart(intervalFrom, interval, ConvertIntervalType(intervalType));
// Add offset to the start position
double offsetFrom = 0;
if( intervalOffset != 0 )
{
offsetFrom = intervalFrom + ChartHelper.GetIntervalSize(intervalFrom,
intervalOffset,
ConvertIntervalType(intervalOffsetType));
// Check if there are points left outside first group
if(input.Points[0].XValue < offsetFrom)
{
if(intervalType == IntervalType.Number)
{
intervalFrom = offsetFrom + ChartHelper.GetIntervalSize(offsetFrom,
-interval,
ConvertIntervalType(intervalType));
}
else
{
intervalFrom = offsetFrom - ChartHelper.GetIntervalSize(offsetFrom,
interval,
ConvertIntervalType(intervalType));
}
intervalTo = offsetFrom;
}
else
{
intervalFrom = offsetFrom;
intervalTo = intervalFrom + ChartHelper.GetIntervalSize(intervalFrom, interval, ConvertIntervalType(intervalType));
}
}
else
{
intervalTo = intervalFrom + ChartHelper.GetIntervalSize(intervalFrom, interval, ConvertIntervalType(intervalType));
}
//**************************************************
//** Allocate array for storing temp.
//** values of the point
//**************************************************
double[] pointTempValues = new double[outputValuesNumber];
//**************************************************
//** Loop through the series points
//**************************************************
bool lastPoint = false;
int emptyPointsSkipped = 0;
int pointsNumberInInterval = 0;
for(int pointIndex = 0; pointIndex <= input.Points.Count && !lastPoint; pointIndex++)
{
bool endOfInterval = false;
//**************************************************
//** Check if series is sorted by X value
//**************************************************
if(pointIndex > 0 && pointIndex < input.Points.Count)
{
if(input.Points[pointIndex].XValue < input.Points[pointIndex - 1].XValue)
{
throw (new InvalidOperationException(SR.ExceptionDataManipulatorGroupedSeriesNotSorted));
}
}
//**************************************************
//** Check if it's the last point
//**************************************************
if(pointIndex == input.Points.Count)
{
// End of the group interval detected
lastPoint = true;
intervalLastIndex = pointIndex - 1;
pointIndex = intervalLastIndex;
endOfInterval = true;
}
//**************************************************
//** Check if current point X value is inside current group
//**************************************************
if(!endOfInterval && input.Points[pointIndex].XValue >= intervalTo)
{
// End of the group interval detected
if(pointIndex == 0)
{
continue;
}
intervalLastIndex = pointIndex - 1;
endOfInterval = true;
}
//**************************************************
//** Process data at end of the interval
//**************************************************
if(endOfInterval)
{
// Add grouped point only if there are non empty points in the interval
if(pointsNumberInInterval > emptyPointsSkipped)
{
// Finalize the calculation
ProcessPointValues(
functions,
pointTempValues,
inputSeries[seriesIndex],
input.Points[pointIndex],
pointIndex,
intervalFirstIndex,
intervalLastIndex,
true,
ref emptyPointsSkipped);
//**************************************************
//** Calculate the X values
//**************************************************
if(functions[0].function == GroupingFunction.Center)
{
pointTempValues[0] = (intervalFrom + intervalTo) / 2.0;
}
else if(functions[0].function == GroupingFunction.First)
{
pointTempValues[0] = intervalFrom;
}
if(functions[0].function == GroupingFunction.Last)
{
pointTempValues[0] = intervalTo;
}
//**************************************************
//** Create new point object
//**************************************************
DataPoint newPoint = new DataPoint();
newPoint.ResizeYValueArray(outputValuesNumber - 1);
newPoint.XValue = pointTempValues[0];
for(int i = 1; i < pointTempValues.Length; i++)
{
newPoint.YValues[i - 1] = pointTempValues[i];
}
//**************************************************
//** Remove grouped points if output and input
//** series are the same
//**************************************************
int newPointIndex = output.Points.Count;
if(output == input)
{
newPointIndex = intervalFirstIndex;
pointIndex = newPointIndex + 1;
// Remove grouped points
for(int removedPoint = intervalFirstIndex; removedPoint <= intervalLastIndex; removedPoint++)
{
output.Points.RemoveAt(intervalFirstIndex);
}
}
//**************************************************
//** Add point to the output series
//**************************************************
output.Points.Insert(newPointIndex, newPoint);
}
// Set new From To values of the group interval
intervalFrom = intervalTo;
intervalTo = intervalFrom + ChartHelper.GetIntervalSize(intervalFrom, interval, ConvertIntervalType(intervalType));
// Set new group interval indexes
intervalFirstIndex = pointIndex;
intervalLastIndex = pointIndex;
// Reset number of points in the interval
pointsNumberInInterval = 0;
// Reset number of skipped points
emptyPointsSkipped = 0;
// Process point once again
--pointIndex;
continue;
}
//**************************************************
//** Use current point values in the formula
//**************************************************
ProcessPointValues(
functions,
pointTempValues,
inputSeries[seriesIndex],
input.Points[pointIndex],
pointIndex,
intervalFirstIndex,
intervalLastIndex,
false,
ref emptyPointsSkipped);
// Increase number of points in the group
++pointsNumberInInterval;
}
}
}
/// <summary>
/// Adds current point values to the temp. formula results.
/// </summary>
/// <param name="functions">Array of functions type.</param>
/// <param name="pointTempValues">Temp. point values.</param>
/// <param name="series">Point series.</param>
/// <param name="point">Current point.</param>
/// <param name="pointIndex">Current point index.</param>
/// <param name="intervalFirstIndex">Index of the first point in the interval.</param>
/// <param name="intervalLastIndex">Index of the last point in the interval.</param>
/// <param name="finalPass">Indicates that interval processing is finished.</param>
/// <param name="numberOfEmptyPoints">Number of skipped points in the interval.</param>
private void ProcessPointValues(
GroupingFunctionInfo[] functions,
double[] pointTempValues,
Series series,
DataPoint point,
int pointIndex,
int intervalFirstIndex,
int intervalLastIndex,
bool finalPass,
ref int numberOfEmptyPoints)
{
//*******************************************************************
//** Initialize temp data if it's the first point in the interval
//*******************************************************************
if(pointIndex == intervalFirstIndex && !finalPass)
{
// Initialize values depending on the function type
int funcIndex = 0;
foreach(GroupingFunctionInfo functionInfo in functions)
{
// Check that we do not exced number of input values
if(funcIndex > point.YValues.Length)
{
break;
}
// Initialize with zero
pointTempValues[functionInfo.outputIndex] = 0;
// Initialize with custom value depending on the formula
if(functionInfo.function == GroupingFunction.Min)
{
pointTempValues[functionInfo.outputIndex] = double.MaxValue;
}
else if(functionInfo.function == GroupingFunction.Max)
{
pointTempValues[functionInfo.outputIndex] = double.MinValue;
}
else if(functionInfo.function == GroupingFunction.First)
{
if(funcIndex == 0)
{
pointTempValues[0] = point.XValue;
}
else
{
pointTempValues[functionInfo.outputIndex] = point.YValues[funcIndex-1];
}
}
else if(functionInfo.function == GroupingFunction.HiLo ||
functionInfo.function == GroupingFunction.HiLoOpCl)
{
// Hi
pointTempValues[functionInfo.outputIndex] = double.MinValue;
//Lo
pointTempValues[functionInfo.outputIndex + 1] = double.MaxValue;
if(functionInfo.function == GroupingFunction.HiLoOpCl)
{
//Open
pointTempValues[functionInfo.outputIndex + 2] = point.YValues[funcIndex-1];
//Close
pointTempValues[functionInfo.outputIndex + 3] = 0;
}
}
// Increase current function index
++funcIndex;
}
}
//*******************************************************************
//** Add points values using formula
//*******************************************************************
if(!finalPass)
{
//*******************************************************************
//** Ignore empty points
//*******************************************************************
if(point.IsEmpty && this.IsEmptyPointIgnored)
{
++numberOfEmptyPoints;
return;
}
//*******************************************************************
//** Loop through each grouping function
//*******************************************************************
int funcIndex = 0;
foreach(GroupingFunctionInfo functionInfo in functions)
{
// Check that we do not exced number of input values
if(funcIndex > point.YValues.Length)
{
break;
}
// Process point values depending on the formula
if(functionInfo.function == GroupingFunction.Min &&
(!point.IsEmpty && this.IsEmptyPointIgnored))
{
pointTempValues[functionInfo.outputIndex] =
Math.Min(pointTempValues[functionInfo.outputIndex], point.YValues[funcIndex-1]);
}
else if(functionInfo.function == GroupingFunction.Max)
{
pointTempValues[functionInfo.outputIndex] =
Math.Max(pointTempValues[functionInfo.outputIndex], point.YValues[funcIndex-1]);
}
else if(functionInfo.function == GroupingFunction.Ave ||
functionInfo.function == GroupingFunction.Sum)
{
if(funcIndex == 0)
{
pointTempValues[0] += point.XValue;
}
else
{
pointTempValues[functionInfo.outputIndex] += point.YValues[funcIndex-1];
}
}
else if(functionInfo.function == GroupingFunction.Variance ||
functionInfo.function == GroupingFunction.Deviation)
{
pointTempValues[functionInfo.outputIndex] += point.YValues[funcIndex-1];
}
else if(functionInfo.function == GroupingFunction.Last)
{
if(funcIndex == 0)
{
pointTempValues[0] = point.XValue;
}
else
{
pointTempValues[functionInfo.outputIndex] = point.YValues[funcIndex-1];
}
}
else if(functionInfo.function == GroupingFunction.Count)
{
pointTempValues[functionInfo.outputIndex] += 1;
}
else if(functionInfo.function == GroupingFunction.HiLo ||
functionInfo.function == GroupingFunction.HiLoOpCl)
{
// Hi
pointTempValues[functionInfo.outputIndex] =
Math.Max(pointTempValues[functionInfo.outputIndex], point.YValues[funcIndex-1]);
// Lo
pointTempValues[functionInfo.outputIndex + 1] =
Math.Min(pointTempValues[functionInfo.outputIndex + 1], point.YValues[funcIndex-1]);
if(functionInfo.function == GroupingFunction.HiLoOpCl)
{
// Close
pointTempValues[functionInfo.outputIndex + 3] = point.YValues[funcIndex-1];
}
}
// Increase current function index
++funcIndex;
}
}
//*******************************************************************
//** Adjust formula results at final pass
//*******************************************************************
if(finalPass)
{
int funcIndex = 0;
foreach(GroupingFunctionInfo functionInfo in functions)
{
// Check that we do not exceed number of input values
if(funcIndex > point.YValues.Length)
{
break;
}
if(functionInfo.function == GroupingFunction.Ave)
{
pointTempValues[functionInfo.outputIndex] /= intervalLastIndex - intervalFirstIndex - numberOfEmptyPoints + 1;
}
if(functionInfo.function == GroupingFunction.DistinctCount)
{
// Initialize value with zero
pointTempValues[functionInfo.outputIndex] = 0;
// Create a list of uniques values
ArrayList uniqueValues = new ArrayList(intervalLastIndex - intervalFirstIndex + 1);
// Second pass through inteval points required for calculations
for(int secondPassIndex = intervalFirstIndex; secondPassIndex <= intervalLastIndex; secondPassIndex++)
{
// Ignore empty points
if(series.Points[secondPassIndex].IsEmpty && this.IsEmptyPointIgnored)
{
continue;
}
// Check if current value is in the unique list
if(!uniqueValues.Contains(series.Points[secondPassIndex].YValues[funcIndex-1]))
{
uniqueValues.Add(series.Points[secondPassIndex].YValues[funcIndex-1]);
}
}
// Get count of unique values
pointTempValues[functionInfo.outputIndex] = uniqueValues.Count;
}
else if(functionInfo.function == GroupingFunction.Variance ||
functionInfo.function == GroupingFunction.Deviation)
{
// Calculate average first
double average = pointTempValues[functionInfo.outputIndex] / (intervalLastIndex - intervalFirstIndex - numberOfEmptyPoints + 1);
// Second pass through inteval points required for calculations
pointTempValues[functionInfo.outputIndex] = 0;
for(int secondPassIndex = intervalFirstIndex; secondPassIndex <= intervalLastIndex; secondPassIndex++)
{
// Ignore empty points
if(series.Points[secondPassIndex].IsEmpty && this.IsEmptyPointIgnored)
{
continue;
}
pointTempValues[functionInfo.outputIndex] +=
Math.Pow(series.Points[secondPassIndex].YValues[funcIndex-1] - average, 2);
}
// Divide by points number
pointTempValues[functionInfo.outputIndex] /=
intervalLastIndex - intervalFirstIndex - numberOfEmptyPoints + 1;
// If calculating the deviation - take a square root of variance
if(functionInfo.function == GroupingFunction.Deviation)
{
pointTempValues[functionInfo.outputIndex] =
Math.Sqrt(pointTempValues[functionInfo.outputIndex]);
}
}
// Increase current function index
++funcIndex;
}
}
}
/// <summary>
/// Checks the formula format and returns an array of formula types
/// for each X and each Y value of the input series.
/// </summary>
/// <param name="inputSeries">Array of input series.</param>
/// <param name="formula">Formula string.</param>
/// <param name="outputValuesNumber">Number of values in output series.</param>
/// <returns>Array of functions for each Y value.</returns>
private GroupingFunctionInfo[] GetGroupingFunctions(Series[] inputSeries, string formula, out int outputValuesNumber)
{
// Get maximum number of Y values in all series
int numberOfYValues = 0;
foreach(Series series in inputSeries)
{
numberOfYValues = (int)Math.Max(numberOfYValues, series.YValuesPerPoint);
}
// Allocate memory for the result array for X and each Y values
GroupingFunctionInfo[] result = new GroupingFunctionInfo[numberOfYValues + 1];
for(int index = 0 ; index < result.Length; index++)
{
result[index] = new GroupingFunctionInfo();
}
// Split formula by comma
string[] valueFormulas = formula.Split(',');
// At least one formula must be specified
if(valueFormulas.Length == 0)
{
throw (new ArgumentException(SR.ExceptionDataManipulatorGroupingFormulaUndefined));
}
// Check each formula in the array
GroupingFunctionInfo defaultFormula = new GroupingFunctionInfo();
foreach(string s in valueFormulas)
{
// Trim white space and make upper case
string formulaString = s.Trim();
formulaString = formulaString.ToUpper(System.Globalization.CultureInfo.InvariantCulture);
// Get value index and formula type from the string
int valueIndex = 1;
GroupingFunction formulaType = ParseFormulaAndValueType(formulaString, out valueIndex);
// Save the default (first) formula
if(defaultFormula.function == GroupingFunction.None)
{
defaultFormula.function = formulaType;
}
// Check that value index do not exceed the max values number
if(valueIndex >= result.Length)
{
throw(new ArgumentException(SR.ExceptionDataManipulatorYValuesIndexExceeded( formulaString )));
}
// Check if formula for this value type was already set
if(result[valueIndex].function != GroupingFunction.None)
{
throw (new ArgumentException(SR.ExceptionDataManipulatorGroupingFormulaAlreadyDefined(formulaString)));
}
// Set formula type
result[valueIndex].function = formulaType;
}
// Apply default formula for non set X value
if(result[0].function == GroupingFunction.None)
{
result[0].function = GroupingFunction.First;
}
// Apply default formula for all non set Y values
for(int funcIndex = 1; funcIndex < result.Length; funcIndex++)
{
if(result[funcIndex].function == GroupingFunction.None)
{
result[funcIndex].function = defaultFormula.function;
}
}
// Specify output value index
outputValuesNumber = 0;
for(int funcIndex = 0; funcIndex < result.Length; funcIndex++)
{
result[funcIndex].outputIndex = outputValuesNumber;
if(result[funcIndex].function == GroupingFunction.HiLoOpCl)
{
outputValuesNumber += 3;
}
else if(result[funcIndex].function == GroupingFunction.HiLo)
{
outputValuesNumber += 1;
}
++outputValuesNumber;
}
// X value formula can be FIRST, LAST and AVE
if(result[0].function != GroupingFunction.First &&
result[0].function != GroupingFunction.Last &&
result[0].function != GroupingFunction.Center)
{
throw (new ArgumentException(SR.ExceptionDataManipulatorGroupingFormulaUnsupported));
}
return result;
}
/// <summary>
/// Parse one formula with optional value prefix.
/// Example: "Y2:MAX"
/// </summary>
/// <param name="formulaString">One formula name with optional value prefix.</param>
/// <param name="valueIndex">Return value index.</param>
/// <returns>Formula type.</returns>
private GroupingFunction ParseFormulaAndValueType(string formulaString, out int valueIndex)
{
// Initialize value index as first Y value (default)
valueIndex = 1;
// Split formula by optional ':' character
string[] formulaParts = formulaString.Split(':');
// There must be at least one and no more than two result strings
if(formulaParts.Length < 1 && formulaParts.Length > 2)
{
throw(new ArgumentException(SR.ExceptionDataManipulatorGroupingFormulaFormatInvalid( formulaString )));
}
// Check specified value type
if(formulaParts.Length == 2)
{
if(formulaParts[0] == "X")
{
valueIndex = 0;
}
else if(formulaParts[0].StartsWith("Y", StringComparison.Ordinal))
{
formulaParts[0] = formulaParts[0].TrimStart('Y');
if(formulaParts[0].Length == 0)
{
valueIndex = 1;
}
else
{
// Try to convert the rest of the string to integer
try
{
valueIndex = Int32.Parse(formulaParts[0], System.Globalization.CultureInfo.InvariantCulture);
}
catch(System.Exception)
{
throw (new ArgumentException(SR.ExceptionDataManipulatorGroupingFormulaFormatInvalid( formulaString )));
}
}
}
else
{
throw (new ArgumentException(SR.ExceptionDataManipulatorGroupingFormulaFormatInvalid( formulaString )));
}
}
// Check formula name
if(formulaParts[formulaParts.Length - 1] == "MIN")
return GroupingFunction.Min;
else if(formulaParts[formulaParts.Length - 1] == "MAX")
return GroupingFunction.Max;
else if(formulaParts[formulaParts.Length - 1] == "AVE")
return GroupingFunction.Ave;
else if(formulaParts[formulaParts.Length - 1] == "SUM")
return GroupingFunction.Sum;
else if(formulaParts[formulaParts.Length - 1] == "FIRST")
return GroupingFunction.First;
else if(formulaParts[formulaParts.Length - 1] == "LAST")
return GroupingFunction.Last;
else if(formulaParts[formulaParts.Length - 1] == "HILOOPCL")
return GroupingFunction.HiLoOpCl;
else if(formulaParts[formulaParts.Length - 1] == "HILO")
return GroupingFunction.HiLo;
else if(formulaParts[formulaParts.Length - 1] == "COUNT")
return GroupingFunction.Count;
else if(formulaParts[formulaParts.Length - 1] == "DISTINCTCOUNT")
return GroupingFunction.DistinctCount;
else if(formulaParts[formulaParts.Length - 1] == "VARIANCE")
return GroupingFunction.Variance;
else if(formulaParts[formulaParts.Length - 1] == "DEVIATION")
return GroupingFunction.Deviation;
else if(formulaParts[formulaParts.Length - 1] == "CENTER")
return GroupingFunction.Center;
// Invalid formula name
throw (new ArgumentException(SR.ExceptionDataManipulatorGroupingFormulaNameInvalid(formulaString)));
}
/// <summary>
/// Checks if input/output series parameters are correct.
/// If not - fires an exception
/// </summary>
/// <param name="inputSeries">Input series array.</param>
/// <param name="outputSeries">Output series array.</param>
private void CheckSeriesArrays(Series[] inputSeries, Series[] outputSeries)
{
// At least one series must be in the input series
if(inputSeries == null || inputSeries.Length == 0)
{
throw (new ArgumentException(SR.ExceptionDataManipulatorGroupingInputSeriesUndefined));
}
// Output series must be empty or have the same number of items
if(outputSeries != null && outputSeries.Length != inputSeries.Length)
{
throw (new ArgumentException(SR.ExceptionDataManipulatorGroupingInputOutputSeriesNumberMismatch));
}
}
#endregion
#region Grouping overloaded methods
/// <summary>
/// Groups data using one or more formulas.
/// The series that is grouped is cleared of its original data, and used to store the new data points.
/// </summary>
/// <param name="formula">Grouping formula.</param>
/// <param name="interval">Interval size.</param>
/// <param name="intervalType">Interval type.</param>
/// <param name="inputSeries">Input series.</param>
public void Group(string formula,
double interval,
IntervalType intervalType,
Series inputSeries)
{
// Check arguments
if (inputSeries == null)
throw new ArgumentNullException("inputSeries");
Group(formula, interval, intervalType, inputSeries, null);
}
/// <summary>
/// Groups data using one or more formulas.
/// Series are cleared of their original data and used to store the new data points.
/// </summary>
/// <param name="formula">Grouping formula.</param>
/// <param name="interval">Interval size.</param>
/// <param name="intervalType">Interval type.</param>
/// <param name="inputSeriesName">Comma separated list of input series names.</param>
public void Group(string formula,
double interval,
IntervalType intervalType,
string inputSeriesName)
{
// Check arguments
if (inputSeriesName == null)
throw new ArgumentNullException("inputSeriesName");
Group(formula, interval, intervalType, inputSeriesName, "");
}
/// <summary>
/// Groups data using one or more formulas.
/// The series that is grouped is cleared of its original data, and used to store the new data points.
/// </summary>
/// <param name="formula">Grouping formula.</param>
/// <param name="interval">Interval size.</param>
/// <param name="intervalType">Interval type.</param>
/// <param name="intervalOffset">Interval offset size.</param>
/// <param name="intervalOffsetType">Interval offset type.</param>
/// <param name="inputSeries">Input series.</param>
public void Group(string formula,
double interval,
IntervalType intervalType,
double intervalOffset,
IntervalType intervalOffsetType,
Series inputSeries)
{
// Check arguments
if (inputSeries == null)
throw new ArgumentNullException("inputSeries");
Group(formula, interval, intervalType, intervalOffset, intervalOffsetType, inputSeries, null);
}
/// <summary>
/// Groups data using one or more formulas.
/// Series are cleared of their original data and used to store the new data points.
/// </summary>
/// <param name="formula">Grouping formula.</param>
/// <param name="interval">Interval size.</param>
/// <param name="intervalType">Interval type.</param>
/// <param name="intervalOffset">Interval offset size.</param>
/// <param name="intervalOffsetType">Interval offset type.</param>
/// <param name="inputSeriesName">Comma separated list of input series names.</param>
public void Group(string formula,
double interval,
IntervalType intervalType,
double intervalOffset,
IntervalType intervalOffsetType,
string inputSeriesName)
{
// Check arguments
if (inputSeriesName == null)
throw new ArgumentNullException("inputSeriesName");
Group(formula, interval, intervalType, intervalOffset, intervalOffsetType, inputSeriesName, "");
}
/// <summary>
/// Groups series data by axis labels using one or more formulas.
/// Output series are used to store the grouped data points.
/// </summary>
/// <param name="formula">Grouping formula.</param>
/// <param name="inputSeriesName">Comma separated list of input series names.</param>
/// <param name="outputSeriesName">Comma separated list of output series names.</param>
public void GroupByAxisLabel(string formula, string inputSeriesName, string outputSeriesName)
{
// Check arguments
if (inputSeriesName == null)
throw new ArgumentNullException("inputSeriesName");
GroupByAxisLabel(formula,
ConvertToSeriesArray(inputSeriesName, false),
ConvertToSeriesArray(outputSeriesName, true));
}
/// <summary>
/// Groups a series' data by axis labels using one or more formulas.
/// The series is cleared of its original data, and then used to store the new data points.
/// </summary>
/// <param name="formula">Grouping formula.</param>
/// <param name="inputSeries">Input data series.</param>
public void GroupByAxisLabel(string formula, Series inputSeries)
{
// Check arguments
if (inputSeries == null)
throw new ArgumentNullException("inputSeries");
GroupByAxisLabel(formula, inputSeries, null);
}
/// <summary>
/// Groups series data by axis labels using one or more formulas.
/// Each series that is grouped is cleared of its original data, and used to store the new data points.
/// </summary>
/// <param name="formula">Grouping formula.</param>
/// <param name="inputSeriesName">Comma separated list of input series names.</param>
public void GroupByAxisLabel(string formula, string inputSeriesName)
{
// Check arguments
if (inputSeriesName == null)
throw new ArgumentNullException("inputSeriesName");
GroupByAxisLabel(formula, inputSeriesName, null);
}
/// <summary>
/// Groups series using one or more formulas.
/// Output series are used to store the grouped data points, and an offset can be used for intervals.
/// </summary>
/// <param name="formula">Grouping formula.</param>
/// <param name="interval">Interval size.</param>
/// <param name="intervalType">Interval type.</param>
/// <param name="intervalOffset">Interval offset size.</param>
/// <param name="intervalOffsetType">Interval offset type.</param>
/// <param name="inputSeriesName">Comma separated list of input series names.</param>
/// <param name="outputSeriesName">Comma separated list of output series names.</param>
public void Group(string formula,
double interval,
IntervalType intervalType,
double intervalOffset,
IntervalType intervalOffsetType,
string inputSeriesName,
string outputSeriesName)
{
// Check arguments
if (inputSeriesName == null)
throw new ArgumentNullException("inputSeriesName");
Group(formula,
interval,
intervalType,
intervalOffset,
intervalOffsetType,
ConvertToSeriesArray(inputSeriesName, false),
ConvertToSeriesArray(outputSeriesName, true));
}
/// <summary>
/// Groups a series' data using one or more formulas.
/// An output series is used to store the grouped data points.
/// </summary>
/// <param name="formula">Grouping formula.</param>
/// <param name="interval">Interval size.</param>
/// <param name="intervalType">Interval type.</param>
/// <param name="inputSeries">Input data series.</param>
/// <param name="outputSeries">Output data series.</param>
public void Group(string formula,
double interval,
IntervalType intervalType,
Series inputSeries,
Series outputSeries)
{
// Check arguments
if (inputSeries == null)
throw new ArgumentNullException("inputSeries");
Group(formula, interval, intervalType, 0, IntervalType.Number, inputSeries, outputSeries);
}
/// <summary>
/// Groups data for series using one or more formulas.
/// Output series are used to store the grouped data points.
/// </summary>
/// <param name="formula">Grouping formula.</param>
/// <param name="interval">Interval size.</param>
/// <param name="intervalType">Interval type.</param>
/// <param name="inputSeriesName">Comma separated list of input series names.</param>
/// <param name="outputSeriesName">Comma separated list of output series names.</param>
public void Group(string formula,
double interval,
IntervalType intervalType,
string inputSeriesName,
string outputSeriesName)
{
// Check arguments
if (inputSeriesName == null)
throw new ArgumentNullException("inputSeriesName");
Group(formula, interval, intervalType, 0, IntervalType.Number, inputSeriesName, outputSeriesName);
}
/// <summary>
/// Groups a series using one or more formulas.
/// An output series is used to store the grouped data points, and an offset can be used for intervals.
/// </summary>
/// <param name="formula">Grouping formula.</param>
/// <param name="interval">Interval size.</param>
/// <param name="intervalType">Interval type.</param>
/// <param name="intervalOffset">Interval offset size.</param>
/// <param name="intervalOffsetType">Interval offset type.</param>
/// <param name="inputSeries">Input data series.</param>
/// <param name="outputSeries">Output data series.</param>
public void Group(string formula,
double interval,
IntervalType intervalType,
double intervalOffset,
IntervalType intervalOffsetType,
Series inputSeries,
Series outputSeries)
{
// Check arguments
if (inputSeries == null)
throw new ArgumentNullException("inputSeries");
Group(formula,
interval,
intervalType,
intervalOffset,
intervalOffsetType,
ConvertToSeriesArray(inputSeries, false),
ConvertToSeriesArray(outputSeries, false));
}
/// <summary>
/// Groups a series' data by axis labels using one or more formulas.
/// An output series is used to store the grouped data points.
/// </summary>
/// <param name="formula">Grouping formula.</param>
/// <param name="inputSeries">Input data series.</param>
/// <param name="outputSeries">Output data series.</param>
public void GroupByAxisLabel(string formula, Series inputSeries, Series outputSeries)
{
// Check arguments
if (inputSeries == null)
throw new ArgumentNullException("inputSeries");
GroupByAxisLabel(formula,
ConvertToSeriesArray(inputSeries, false),
ConvertToSeriesArray(outputSeries, false));
}
#endregion
}
}
|