Files
Pat Tullmann 0cb742dafb binfmt-detector-cli: rewrite to support PE32+ binaries (#38)
Rewrite with hard-coded offsets into the PE file format to discern
if a binary is PE32 or PE32+, and then to determine if it contains
a "CLR Data Directory" entry that looks valid.

Tested with PE32 and PE32+ compiled Mono binaries, PE32 and PE32+ native
binaries, and a random assortment of garbage files.

Former-commit-id: 9e7ac86ec84f653a2f79b87183efd5b0ebda001b
2023-10-16 20:16:47 +02:00

3496 lines
114 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//-------------------------------------------------------------
// <copyright company=Microsoft Corporation>
// Copyright © Microsoft Corporation. All Rights Reserved.
// </copyright>
//-------------------------------------------------------------
// @owner=alexgor, deliant
//=================================================================
// File: 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 its 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
}
}