// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // // Purpose: 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. // using System; using System.Collections; using System.Collections.Generic; using System.Data; namespace FastReport.DataVisualization.Charting { #region Data manipulation enumerations /// /// Grouping functions types /// internal enum GroupingFunction { /// /// Not defined /// None, /// /// Minimum value of the group /// Min, /// /// Maximum value of the group /// Max, /// /// Average value of the group /// Ave, /// /// Total of all values of the group /// Sum, /// /// Value of the first point in the group /// First, /// /// Value of the last point in the group /// Last, /// /// Value of the center point in the group /// Center, /// /// High, Low, Open, Close values in the group /// HiLoOpCl, /// /// High, Low values in the group /// HiLo, /// /// Number of points in the group /// Count, /// /// Number of unique points in the group /// DistinctCount, /// /// Variance of points in the group /// Variance, /// /// Deviation of points in the group /// Deviation } /// /// An enumeration of units of measurement for intervals. /// public enum IntervalType { /// /// Interval in numbers. /// Number, /// /// Interval in years. /// Years, /// /// Interval in months. /// Months, /// /// Interval in weeks. /// Weeks, /// /// Interval in days. /// Days, /// /// Interval in hours. /// Hours, /// /// Interval in minutes. /// Minutes, /// /// Interval in seconds. /// Seconds, /// /// Interval in milliseconds. /// Milliseconds } /// /// An enumeration of units of measurement for date ranges. /// public enum DateRangeType { /// /// Range defined in years. /// Year, /// /// Range defined in months. /// Month, /// /// Range defined in days of week. /// DayOfWeek, /// /// Range defined in days of month. /// DayOfMonth, /// /// Range defined in hours. /// Hour, /// /// Range defined in minutes. /// Minute } /// /// An enumeration of methods of comparison. /// public enum CompareMethod { /// /// One value is more than the other value. /// MoreThan, /// /// One value is less than the other value. /// LessThan, /// /// One value is equal the other value. /// EqualTo, /// /// One value is more or equal to the other value. /// MoreThanOrEqualTo, /// /// One value is less or equal to the other value. /// LessThanOrEqualTo, /// /// One value is not equal to the other value. /// NotEqualTo } #endregion #region Data points filtering inteface /// /// The IDataPointFilter interface is used for filtering series data points. /// public interface IDataPointFilter { /// /// Checks if the specified data point must be filtered. /// /// Data point object. /// Series of the point. /// Index of the point in the series. /// True if point must be removed bool FilterDataPoint(DataPoint point, Series series, int pointIndex); } #endregion /// /// The DataManipulator class is used at runtime to perform data manipulation /// operations, and is exposed via the DataManipulator property of the /// root Chart object. /// 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 /// /// Helper function that converts one series or a comma separated /// list of series names into the Series array. /// /// Series or string of series names. /// If series with this name do not exist - create new. /// Array of series. 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; } /// /// Public constructor /// public DataManipulator() { } #endregion #region Series points sorting methods /// /// Sort series data points in specified order. /// /// Sorting order. /// Value to sort by. /// Series array to sort. 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); } /// /// Sort series data points in specified order. /// /// Comparing interface. /// Series array to sort. private void Sort(IComparer 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 /// /// Sort the series' data points in specified order. /// /// Sorting order. /// Value to sort by. /// Comma separated series names to sort. public void Sort(PointSortOrder pointSortOrder, string sortBy, string seriesName) { // Check arguments if (seriesName == null) throw new ArgumentNullException("seriesName"); Sort(pointSortOrder, sortBy, ConvertToSeriesArray(seriesName, false)); } /// /// Sort the series' data points in specified order. /// /// Sorting order. /// Series to sort. public void Sort(PointSortOrder pointSortOrder, Series series) { // Check arguments if (series == null) throw new ArgumentNullException("series"); Sort(pointSortOrder, "Y", ConvertToSeriesArray(series, false)); } /// /// Sort the series' data points in specified order. /// /// Sorting order. /// Comma separated series names to sort. public void Sort(PointSortOrder pointSortOrder, string seriesName) { // Check arguments if (seriesName == null) throw new ArgumentNullException("seriesName"); Sort(pointSortOrder, "Y", ConvertToSeriesArray(seriesName, false)); } /// /// Sort the series' data points in specified order. /// /// Sorting order. /// Value to sort by. /// Series to sort. public void Sort(PointSortOrder pointSortOrder, string sortBy, Series series) { // Check arguments if (series == null) throw new ArgumentNullException("series"); Sort(pointSortOrder, sortBy, ConvertToSeriesArray(series, false)); } /// /// Sort the series' data points in specified order. /// /// IComparer interface. /// Series to sort. public void Sort(IComparer 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)); } /// /// Sort the series' data points in specified order. /// /// Comparing interface. /// Comma separated series names to sort. public void Sort(IComparer 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 /// /// Insert empty data points using specified interval. /// /// Interval size. /// Interval type. /// Interval offset size. /// Interval offset type. /// Check intervals from this X value. /// Check intervals until this X value. /// Series array. 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; } } } } /// /// Helper function which converts IntervalType enumeration /// into DateTimeIntervalType enumeration. /// /// Interval type value. /// Date time interval type value. 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 /// /// Insert empty data points using the specified interval. /// /// Interval size. /// Interval type. /// Series to insert the empty points. public void InsertEmptyPoints( double interval, IntervalType intervalType, Series series) { InsertEmptyPoints(interval, intervalType, 0, IntervalType.Number, series); } /// /// Insert empty data points using the specified interval. /// /// Interval size. /// Interval type. /// Name of series to insert the empty points. public void InsertEmptyPoints( double interval, IntervalType intervalType, string seriesName) { InsertEmptyPoints(interval, intervalType, 0, IntervalType.Number, seriesName); } /// /// Insert empty data points using the specified interval. /// /// Interval size. /// Interval type. /// Interval offset size. /// Interval offset type. /// Name of series to insert the empty points. public void InsertEmptyPoints( double interval, IntervalType intervalType, double intervalOffset, IntervalType intervalOffsetType, string seriesName) { InsertEmptyPoints(interval, intervalType, intervalOffset, intervalOffsetType, double.NaN, double.NaN, seriesName); } /// /// Insert empty data points using the specified interval. /// /// Interval size. /// Interval type. /// Interval offset size. /// Interval offset type. /// Series to insert the empty points. public void InsertEmptyPoints( double interval, IntervalType intervalType, double intervalOffset, IntervalType intervalOffsetType, Series series) { InsertEmptyPoints(interval, intervalType, intervalOffset, intervalOffsetType, double.NaN, double.NaN, series); } /// /// Insert empty data points using the specified interval. /// /// Interval size. /// Interval type. /// Interval offset size. /// Interval offset type. /// Check intervals from this X value. /// Check intervals until this X value. /// Name of series to insert the empty points. 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)); } /// /// Insert empty data points using the specified interval. /// /// Interval size. /// Interval type. /// Interval offset size. /// Interval offset type. /// Check intervals from this X value. /// Check intervals until this X value. /// Series to insert the empty points. 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 /// /// Export series data into the DataSet object. /// /// Array of series which should be exported. /// Data set object with series data. 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 /// /// Export all series from the collection into the DataSet object. /// /// Dataset object with series data. public DataSet ExportSeriesValues() { return ExportSeriesValues("*"); } /// /// Export series data into the DataSet object. /// /// Comma separated list of series names to be exported. /// Dataset object with series data. public DataSet ExportSeriesValues(string seriesNames) { // Check arguments if (seriesNames == null) throw new ArgumentNullException(seriesNames); return ExportSeriesValues(ConvertToSeriesArray(seriesNames, false)); } /// /// Export series data into the DataSet object. /// /// Series to be exported. /// Dataset object with series data. public DataSet ExportSeriesValues(Series series) { // Check arguments if (series == null) throw new ArgumentNullException("series"); return ExportSeriesValues(ConvertToSeriesArray(series, false)); } #endregion #region Filtering properties /// /// 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. /// public bool FilterSetEmptyPoints { get { return _filterSetEmptyPoints; } set { _filterSetEmptyPoints = value; } } /// /// 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. /// public bool FilterMatchedPoints { get { return _filterMatchedPoints; } set { _filterMatchedPoints = value; } } #endregion #region Filtering methods /// /// Keeps only N top/bottom points of the series /// /// Number of top/bottom points to return. /// Input series array. /// Output series array. /// Defines which value of the point use in comparison (X, Y, Y2, ...). /// Indicate that N top values must be retrieved, otherwise N bottom values. 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); } } } } /// /// Filter data points using IDataPointFilter interface /// /// Data points filtering interface. /// Input series array. /// Output series array. 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; } } } /// /// Data point filter. /// Filters points using element type and index /// private class PointElementFilter : IDataPointFilter { // Private fields private DataManipulator _dataManipulator = null; private DateRangeType _dateRange; private int[] _rangeElements = null; // Default constructor is not accesiable private PointElementFilter() { } /// /// Public constructor. /// /// Data manipulator object. /// Range type. /// Range elements to filter. public PointElementFilter(DataManipulator dataManipulator, DateRangeType dateRange, string rangeElements) { this._dataManipulator = dataManipulator; this._dateRange = dateRange; this._rangeElements = dataManipulator.ConvertElementIndexesToArray(rangeElements); } /// /// Data points filtering method. /// /// Data point. /// Data point series. /// Data point index. /// Indicates that point should be filtered. public bool FilterDataPoint(DataPoint point, Series series, int pointIndex) { return _dataManipulator.CheckFilterElementCriteria( this._dateRange, this._rangeElements, point); } } /// /// Data point filter. /// Filters points using point values /// private class PointValueFilter : IDataPointFilter { // Private fields private CompareMethod _compareMethod; private string _usingValue; private double _compareValue; /// /// Default constructor is not accessible /// private PointValueFilter() { } /// /// Public constructor. /// /// Comparing method. /// Comparing constant. /// Value used in comparison. public PointValueFilter(CompareMethod compareMethod, double compareValue, string usingValue) { this._compareMethod = compareMethod; this._usingValue = usingValue; this._compareValue = compareValue; } /// /// IDataPointFilter interface method implementation /// /// Data point. /// Data point series. /// Data point index. /// Indicates that point should be filtered. 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; } } /// /// Helper function to convert elements indexes from a string /// into an array of integers /// /// Element indexes string. Ex:"3,5,6-9,15" /// Array of integer indexes. 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; } /// /// Helper function, which checks if specified point matches the criteria /// /// Element type. /// Array of element indexes ranges (pairs). /// Data point to check. /// True if point matches the criteria. 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 /// /// Filters a series' data points, either removing the specified points /// or marking them as empty for the given date/time ranges. /// /// Element type. /// 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"). /// Comma separated list of input series names. /// Comma separated list of output series names, to store the output. 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)); } /// /// 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. /// /// Element type. /// 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"). /// Input series. 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); } /// /// Filters a series' data points, either removing the specified points /// or marking them as empty for the given date/time ranges. /// /// Element type. /// 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"). /// Input series. /// Output series. 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)); } /// /// 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. /// /// Element type. /// 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"). /// Comma separated list of input series names. 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, ""); } /// /// 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. /// /// Value comparing method. /// Value to compare with. /// Input series. public void Filter(CompareMethod compareMethod, double compareValue, Series inputSeries) { // Check arguments if (inputSeries == null) throw new ArgumentNullException("inputSeries"); Filter(compareMethod, compareValue, inputSeries, null, "Y"); } /// /// Filters a series' data points by applying a filtering rule to the first Y-value of data points. /// /// Value comparing method. /// Value to compare with. /// Input series. /// Output series. 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)); } /// /// Filters a series' data points by applying a filtering rule to the specified value for comparison. /// /// Value comparing method. /// Value to compare with. /// Input series. /// Output series. /// The data point value that the filtering rule is applied to. Can be X, Y, Y2, Y3, etc. 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)); } /// /// 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. /// /// Value comparing method. /// Value to compare with. /// Comma separated list of input series names. public void Filter(CompareMethod compareMethod, double compareValue, string inputSeriesNames) { // Check arguments if (inputSeriesNames == null) throw new ArgumentNullException("inputSeriesNames"); Filter(compareMethod, compareValue, inputSeriesNames, "", "Y"); } /// /// Filters one or more series by applying a filtering rule to the first Y-value of the first series' data points. /// /// Value comparing method. /// Value to compare with. /// Comma separated list of input series names. /// Comma separated list of output series names. 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)); } /// /// Filters one or more series by applying a filtering rule to the specified value of the first series' data points. /// /// Value comparing method. /// Value to compare with. /// Comma separated input series names. /// Comma separated output series names. /// The data point value that the filtering rule is applied to. Can be X, Y, Y2, Y3, etc. 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)); } /// /// 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. /// /// The number of data points that the filtering operation will not remove. /// Comma separated list of input series names. /// Comma separated list of output series names. /// The data point value that the filtering rule is applied to. Can be X, Y, Y2, Y3, etc. /// The largest values are kept if set to true; otherwise the smallest values are kept. 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); } /// /// 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. /// /// The number of data points that the filtering operation will not remove. /// Input series. public void FilterTopN(int pointCount, Series inputSeries) { // Check arguments if (inputSeries == null) throw new ArgumentNullException("inputSeries"); FilterTopN(pointCount, ConvertToSeriesArray(inputSeries, false), null, "Y", true); } /// /// Filters all data points in a series except for a specified number of points with the largest first Y-values. /// /// The number of data points that the filtering operation will not remove. /// Input series. /// Output series. 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); } /// /// Filters all data points in a series except for a specified number of points with the largest values. /// /// The number of data points that the filtering operation will not remove. /// Input series. /// Output series. /// The data point value that the filtering rule is applied to. Can be X, Y, Y2, Y3, etc. 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); } /// /// Filters all data points in a series except for a specified number of points with the smallest or largest values. /// /// The number of data points that the filtering operation will not remove. /// Input series. /// Output series. /// The data point value that the filtering rule is applied to. Can be X, Y, Y2, Y3, etc. /// The largest values are kept if set to true; otherwise the smallest values are kept. 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); } /// /// 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. /// /// The number of data points that the filtering operation will not remove. /// Comma separated list of input series names. public void FilterTopN(int pointCount, string inputSeriesNames) { // Check arguments if (inputSeriesNames == null) throw new ArgumentNullException("inputSeriesNames"); FilterTopN(pointCount, ConvertToSeriesArray(inputSeriesNames, false), null, "Y", true); } /// /// 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. /// /// The number of data points that the filtering operation will not remove. /// Comma separated list of input series names. /// Comma separated list of output series names. 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); } /// /// 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. /// /// The number of data points that the filtering operation will not remove. /// Comma separated list of input series names. /// Comma separated list of output series names. /// The data point value that the filtering rule is applied to. Can be X, Y, Y2, Y3, etc. 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); } /// /// Performs custom filtering on a series' data points. /// The Series object that is filtered is used to store the modified data. /// /// Filtering interface. /// Input series. 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); } /// /// Performs custom filtering on a series' data points. /// /// Filtering interface. /// Input series. /// Output series. 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)); } /// /// 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. /// /// Filtering interface. /// Comma separated list of input series names. 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); } /// /// Performs custom filtering on one or more series' data points, based on the first series' points. /// /// Filtering interface. /// Comma separated list of input series names. /// Comma separated list of output series names. 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 /// /// Class stores information about the grouping function type and /// index of output value. /// 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; /// /// Constructor. /// internal GroupingFunctionInfo() { } } /// /// Grouping by X value, when it’s a string (stored in AxisLabel property). /// /// Grouping formula. /// Array of input series. /// Array of output series. 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); } } } /// /// Groups series points in the interval with offset /// /// Grouping formula. /// Interval size. /// Interval type. /// Interval offset size. /// Interval offset type. /// Array of input series. /// Array of output series. 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; } } } /// /// Adds current point values to the temp. formula results. /// /// Array of functions type. /// Temp. point values. /// Point series. /// Current point. /// Current point index. /// Index of the first point in the interval. /// Index of the last point in the interval. /// Indicates that interval processing is finished. /// Number of skipped points in the interval. 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; } } } /// /// Checks the formula format and returns an array of formula types /// for each X and each Y value of the input series. /// /// Array of input series. /// Formula string. /// Number of values in output series. /// Array of functions for each Y value. 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; } /// /// Parse one formula with optional value prefix. /// Example: "Y2:MAX" /// /// One formula name with optional value prefix. /// Return value index. /// Formula type. 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))); } /// /// Checks if input/output series parameters are correct. /// If not - fires an exception /// /// Input series array. /// Output series array. 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 /// /// 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. /// /// Grouping formula. /// Interval size. /// Interval type. /// Input series. 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); } /// /// Groups data using one or more formulas. /// Series are cleared of their original data and used to store the new data points. /// /// Grouping formula. /// Interval size. /// Interval type. /// Comma separated list of input series names. 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, ""); } /// /// 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. /// /// Grouping formula. /// Interval size. /// Interval type. /// Interval offset size. /// Interval offset type. /// Input series. 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); } /// /// Groups data using one or more formulas. /// Series are cleared of their original data and used to store the new data points. /// /// Grouping formula. /// Interval size. /// Interval type. /// Interval offset size. /// Interval offset type. /// Comma separated list of input series names. 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, ""); } /// /// Groups series data by axis labels using one or more formulas. /// Output series are used to store the grouped data points. /// /// Grouping formula. /// Comma separated list of input series names. /// Comma separated list of output series names. 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)); } /// /// 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. /// /// Grouping formula. /// Input data series. public void GroupByAxisLabel(string formula, Series inputSeries) { // Check arguments if (inputSeries == null) throw new ArgumentNullException("inputSeries"); GroupByAxisLabel(formula, inputSeries, null); } /// /// 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. /// /// Grouping formula. /// Comma separated list of input series names. public void GroupByAxisLabel(string formula, string inputSeriesName) { // Check arguments if (inputSeriesName == null) throw new ArgumentNullException("inputSeriesName"); GroupByAxisLabel(formula, inputSeriesName, null); } /// /// 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. /// /// Grouping formula. /// Interval size. /// Interval type. /// Interval offset size. /// Interval offset type. /// Comma separated list of input series names. /// Comma separated list of output series names. 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)); } /// /// Groups a series' data using one or more formulas. /// An output series is used to store the grouped data points. /// /// Grouping formula. /// Interval size. /// Interval type. /// Input data series. /// Output data series. 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); } /// /// Groups data for series using one or more formulas. /// Output series are used to store the grouped data points. /// /// Grouping formula. /// Interval size. /// Interval type. /// Comma separated list of input series names. /// Comma separated list of output series names. 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); } /// /// 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. /// /// Grouping formula. /// Interval size. /// Interval type. /// Interval offset size. /// Interval offset type. /// Input data series. /// Output data series. 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)); } /// /// Groups a series' data by axis labels using one or more formulas. /// An output series is used to store the grouped data points. /// /// Grouping formula. /// Input data series. /// Output data series. 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 } }