//================================================================= // File: SpikeRemoval.cs // // Namespace: System.Web.UI.DataVisualization.Charting.Utilities // // Classes: SpikeRemoval // // Purpose: Removes spikes from data // // //=================================================================== // Chart Control for ASP.Net // Copyright ?Microsoft Corporation, all rights reserved //=================================================================== using System; using System.Data; using System.Configuration; using System.Drawing; using System.Web.UI.DataVisualization.Charting; namespace System.Web.UI.DataVisualization.Charting.Utilities { /// /// Spike removal is a utility used to remove high and low spikes from a graph. This means that the /// chart axis scaling will be changed so that data that was difficult to see and analyze will be easier /// to see after anomaly spikes have been removed. /// public class SpikeRemoval { #region Members private bool mySetCutoffLabels; private MarkerStyle myRemovedPointStyle; private float myMaximum; private float myMinimum; #endregion #region Properties /// /// Sets whether or not labels are set on each cut off point. If they are, they will show up on the chart and provide /// extra clarification if the tooltip is not enough. /// public bool SetCutoffLabels { get { return mySetCutoffLabels; } set { mySetCutoffLabels = value; } } /// /// Holds the style that is used for the marker of any deleted points. /// public MarkerStyle RemovedPointStyle { get { return myRemovedPointStyle; } set { myRemovedPointStyle = value; } } /// /// Contains the maximum value of the data after it has had the spikes removed. This value also has /// the tolerance factored in. /// public float Maximum { get { return myMaximum; } } /// /// Contains the minimum value of the data after it has had the spikes removed. This value also has /// the tolerance factored in. /// public float Minimum { get { return myMinimum; } } #endregion #region Constructors /// /// Default Constructor. /// public SpikeRemoval() { //Default removed point style to a diamond. myRemovedPointStyle = MarkerStyle.Diamond; //Default labels to off. mySetCutoffLabels = false; } #endregion #region Public Methods /// /// RemoveSpikes will remove the high and low spikes off of a graph. The data within the series /// provided will be modified, and for best results, the chart containing it should have axis /// scaled automatically. /// /// The series which contains the data to be analyzed and modified. It is assumed that /// Y-values are contained in YValues[0]. Cases contrary to this will produce unexpected results. /// The percentage range of data to be kept. Anything that lies outside of the range /// will be considered a spike. /// The percentage a spike can be outside of the range but still included. The percentage /// is based on the maximum or minimum value in the range. public void RemoveSpikes(Series dataseries, int range, int tolerance) { //Assure range and tolerance are a percentage. //Range is more strict in that at least 1% of the data must be included in the range, whereas it is //possible to have 0% tolerance. if ((range < 1 || range > 100)) throw new ArgumentOutOfRangeException("range", "Range must be a percentage between 1 and 100"); if ((tolerance < 0 || tolerance > 100)) throw new ArgumentOutOfRangeException("tolerance", "Tolerance must be a percentage between 0 and 100"); //Data values and indices hold the y values and the indices of the points in arrays. float[] datavalues = new float[dataseries.Points.Count]; int[] indices = new int[dataseries.Points.Count]; //Percent and number hold the actual values calculated from the range. float percent = 0.0f; int number = 0; //Copy all y values into an array and store the indices. for (int i = 0; i < dataseries.Points.Count; i++) { datavalues[i] = (float)dataseries.Points[i].YValues[0]; indices[i] = i; } //Sort the array and indices. Array.Sort(datavalues, indices); //Calculate the percent that has to come off each side of the data. //ie. With a range of 80%, 20% of the data is being cut off, and 10% is coming off each side. percent = ((100 - (float)range) / 2) / 100; //Calculate the actual number of points coming off of each side. //ie. With a percent of 10% and 100 data points, 10 points are coming off of each side. number = (int)System.Math.Round(dataseries.Points.Count * percent, 0); //Set the maximum and minimum values. myMinimum = (float)(dataseries.Points[indices[number]].YValues[0] - System.Math.Abs(dataseries.Points[indices[number]].YValues[0] * (((float)tolerance / 100)))); myMaximum = (float)(dataseries.Points[indices[dataseries.Points.Count - number - 1]].YValues[0] + System.Math.Abs(dataseries.Points[indices[dataseries.Points.Count - number - 1]].YValues[0] * (((float)tolerance / 100)))); //Cut the low spikes off. for (int i = 0; i < number; i++) { //Don't cut the spike if it's within the tolerance value. if (dataseries.Points[indices[i]].YValues[0] < (myMinimum)) { //Assign the tooltip to the point dataseries.Points[indices[i]].ToolTip = "Value: " + dataseries.Points[indices[i]].YValues[0]; //Assign the label to the point if (mySetCutoffLabels) dataseries.Points[indices[i]].Label = "Value: " + dataseries.Points[indices[i]].YValues[0]; //Reassign the value to the minimum number allowed dataseries.Points[indices[i]].YValues[0] = myMinimum; //Set the marker point dataseries.Points[indices[i]].MarkerStyle = myRemovedPointStyle; } } //Cut the high spikes off. for (int i = dataseries.Points.Count - number; i < dataseries.Points.Count; i++) { //Don't cut the spike if it's within the tolerance value. if (dataseries.Points[indices[i]].YValues[0] > (myMaximum)) { //Assign the tooltip to the point dataseries.Points[indices[i]].ToolTip = "Value: " + dataseries.Points[indices[i]].YValues[0]; //Assign the label to the point if (mySetCutoffLabels) dataseries.Points[indices[i]].Label = "Value: " + dataseries.Points[indices[i]].YValues[0]; //Reassign the value to the maximum number allowed dataseries.Points[indices[i]].YValues[0] = myMaximum; //Set the marker point dataseries.Points[indices[i]].MarkerStyle = myRemovedPointStyle; } } } #endregion } }