Initial commit for WC-SPC project
This commit is contained in:
@@ -0,0 +1,623 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Web.UI.DataVisualization.Charting;
|
||||
using System.Collections;
|
||||
|
||||
namespace System.Web.UI.DataVisualization.Charting.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// .
|
||||
/// </summary>
|
||||
public class ChartDataTableHelper
|
||||
{
|
||||
#region Members
|
||||
protected System.Web.UI.DataVisualization.Charting.Chart ChartObj = null;
|
||||
protected ArrayList ChartAreas = null;
|
||||
protected bool AddTableTotals = false;
|
||||
protected System.Drawing.Color tableColor = Color.White;
|
||||
protected System.Drawing.Color borderColor = Color.Black;
|
||||
protected bool enabled = true;
|
||||
protected bool Initialized = false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Enables or Disables the painting of the Data Table.
|
||||
/// </summary>
|
||||
public bool Enabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return Enabled;
|
||||
}
|
||||
set
|
||||
{
|
||||
enabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets or gets the Chart object.
|
||||
/// </summary>
|
||||
public System.Web.UI.DataVisualization.Charting.Chart Chart
|
||||
{
|
||||
get
|
||||
{
|
||||
return ChartObj;
|
||||
}
|
||||
set
|
||||
{
|
||||
ChartObj = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets or gets the Table Color that will be painted.
|
||||
/// </summary>
|
||||
public System.Drawing.Color TableColor
|
||||
{
|
||||
get
|
||||
{
|
||||
return tableColor;
|
||||
}
|
||||
set
|
||||
{
|
||||
tableColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets or gets the Table Border Color that will be painted.
|
||||
/// </summary>
|
||||
public System.Drawing.Color BorderColor
|
||||
{
|
||||
get
|
||||
{
|
||||
return borderColor;
|
||||
}
|
||||
set
|
||||
{
|
||||
borderColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
/// <summary>
|
||||
/// Construct a ChartDataTableHelper instance.
|
||||
/// </summary>
|
||||
public ChartDataTableHelper()
|
||||
{
|
||||
ChartObj = null;
|
||||
ChartAreas = new ArrayList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a ChartDataTableHelper instance and Initialize all ChartAreas with a table.
|
||||
/// </summary>
|
||||
public ChartDataTableHelper(System.Web.UI.DataVisualization.Charting.Chart chartObj)
|
||||
{
|
||||
ChartAreas = new ArrayList();
|
||||
Initialize(chartObj);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Construct a ChartDataTableHelper instance and Initialize the specified ChartArea with a table.
|
||||
/// </summary>
|
||||
public ChartDataTableHelper(System.Web.UI.DataVisualization.Charting.Chart chartObj, string chartAreaName)
|
||||
{
|
||||
ChartAreas = new ArrayList();
|
||||
Initialize(chartObj, chartAreaName);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a ChartDataTableHelper instance, Initialize the specified ChartArea with a table and
|
||||
/// set a boolean to show or hide total columns.
|
||||
/// </summary>
|
||||
public ChartDataTableHelper(System.Web.UI.DataVisualization.Charting.Chart chartObj, string chartAreaName, bool addTableTotals)
|
||||
{
|
||||
ChartAreas = new ArrayList();
|
||||
Initialize(chartObj, chartAreaName, addTableTotals);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Initialization Methods
|
||||
/// <summary>
|
||||
/// Initialize all ChartAreas with a table.
|
||||
/// </summary>
|
||||
public void Initialize(System.Web.UI.DataVisualization.Charting.Chart chartObj)
|
||||
{
|
||||
ChartObj = chartObj;
|
||||
|
||||
foreach(ChartArea area in ChartObj.ChartAreas)
|
||||
{
|
||||
AddDataTable(area.Name);
|
||||
}
|
||||
|
||||
if(!Initialized)
|
||||
ChartObj.PostPaint +=new EventHandler<ChartPaintEventArgs>(this.Chart_PostPaint);
|
||||
|
||||
Initialized = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initialize all ChartAreas with a table and
|
||||
/// set a boolean to show or hide total columns.
|
||||
/// </summary>
|
||||
public void Initialize(System.Web.UI.DataVisualization.Charting.Chart chartObj, bool addTableTotals)
|
||||
{
|
||||
AddTableTotals = addTableTotals;
|
||||
Initialize(chartObj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the specified ChartArea with a table.
|
||||
/// </summary>
|
||||
public void Initialize(System.Web.UI.DataVisualization.Charting.Chart chartObj, string chartAreaName)
|
||||
{
|
||||
ChartObj = chartObj;
|
||||
|
||||
AddDataTable(chartAreaName);
|
||||
|
||||
if(!Initialized)
|
||||
ChartObj.PostPaint +=new EventHandler<ChartPaintEventArgs>(this.Chart_PostPaint);
|
||||
|
||||
Initialized = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the specified ChartArea with a table and
|
||||
/// set a boolean to show or hide total columns.
|
||||
/// </summary>
|
||||
public void Initialize(System.Web.UI.DataVisualization.Charting.Chart chartObj, string chartAreaName, bool addTableTotals)
|
||||
{
|
||||
ChartObj = chartObj;
|
||||
AddTableTotals = addTableTotals;
|
||||
|
||||
AddDataTable(chartAreaName);
|
||||
|
||||
if(!Initialized)
|
||||
ChartObj.PostPaint +=new EventHandler<ChartPaintEventArgs>(this.Chart_PostPaint);
|
||||
|
||||
Initialized = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the specified ChartArea with a table.
|
||||
/// </summary>
|
||||
public void AddDataTable(string chartAreaName)
|
||||
{
|
||||
if(ChartObj == null || ChartAreas.IndexOf(chartAreaName) >= 0)
|
||||
return;
|
||||
|
||||
// add this chart area to the list of chart areas that need to
|
||||
// have a data table attached
|
||||
ChartAreas.Add(chartAreaName);
|
||||
|
||||
int Row = 0;
|
||||
|
||||
|
||||
if(AddTableTotals)
|
||||
{
|
||||
// create a dummy series that will not be shown but is used for
|
||||
// showing the totals in the data table
|
||||
ChartObj.Series.Add("DUMMY");
|
||||
ChartObj.Series["DUMMY"].ChartArea = chartAreaName;
|
||||
ChartObj.Series["DUMMY"].Enabled = false;
|
||||
ChartObj.Series["DUMMY"].Color = Color.Gainsboro;
|
||||
}
|
||||
|
||||
|
||||
// for each of the series that are attached to this
|
||||
// named chart area, create a custom axis label.
|
||||
// All tables lines will be drawn on a paint event.
|
||||
// ****************************************************
|
||||
// NOTE: ALL SERIES MUST HAVE THE SAME NUMBER OF POINTS!
|
||||
// ****************************************************
|
||||
foreach(Series ser in ChartObj.Series)
|
||||
{
|
||||
if(chartAreaName == ser.ChartArea)
|
||||
{
|
||||
if(AddTableTotals)
|
||||
{
|
||||
// shadows must be turned off otherwise
|
||||
// they will still show up for the transparent points
|
||||
ser.ShadowOffset = 0;
|
||||
|
||||
// adjust the series values to ensure they are not
|
||||
// indexed and they are sorted... plus adding each point
|
||||
// to make the dummy series data
|
||||
AdjustXValues(ser);
|
||||
}
|
||||
|
||||
|
||||
Row++;
|
||||
double From = 0.0;
|
||||
double To = 0.0;
|
||||
bool firstPoint = true;
|
||||
|
||||
double YValueTotal = 0;
|
||||
|
||||
foreach(DataPoint dp in ser.Points)
|
||||
{
|
||||
if(AddTableTotals)
|
||||
YValueTotal += dp.YValues[0];
|
||||
|
||||
if(firstPoint && dp.XValue == 0)
|
||||
From = 0.5;
|
||||
else if(firstPoint)
|
||||
From = dp.XValue - 0.5;
|
||||
|
||||
if(firstPoint)
|
||||
{
|
||||
ChartObj.ChartAreas[chartAreaName].AxisX.Minimum = From;
|
||||
ChartObj.ChartAreas[chartAreaName].AxisX.MajorGrid.Interval = 1;
|
||||
ChartObj.ChartAreas[chartAreaName].AxisX.MajorTickMark.Interval = 1;
|
||||
ChartObj.ChartAreas[chartAreaName].AxisX.LabelStyle.Interval = 1;
|
||||
ChartObj.ChartAreas[chartAreaName].AxisX.MajorGrid.IntervalOffset = 0.5;
|
||||
ChartObj.ChartAreas[chartAreaName].AxisX.MajorTickMark.IntervalOffset = 0.5;
|
||||
ChartObj.ChartAreas[chartAreaName].AxisX.LabelStyle.IntervalOffset = 0.5;
|
||||
}
|
||||
|
||||
To = From + 1;
|
||||
|
||||
ChartObj.ChartAreas[chartAreaName].AxisX.CustomLabels.Add(
|
||||
From, To,
|
||||
" ", // space used as a placeholder
|
||||
Row, LabelMarkStyle.None, GridTickTypes.None
|
||||
);
|
||||
|
||||
firstPoint = false;
|
||||
From += 1;
|
||||
}
|
||||
|
||||
if(AddTableTotals)
|
||||
ser.Points[ser.Points.Count-1].YValues[0] = YValueTotal;
|
||||
|
||||
ChartObj.ChartAreas[chartAreaName].AxisX.Maximum = To;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if(AddTableTotals)
|
||||
AdjustYMaximum(chartAreaName);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// With the addition of Totals, the chart will try to set the maximum
|
||||
/// values according to these totals. This will cause some series to be
|
||||
/// barely visible. Since the points are transparent this is a poor behavior.
|
||||
/// This method will find and explicitly set the YAxis maximum to something
|
||||
/// a little more with the user expectations.
|
||||
/// </summary>
|
||||
private void AdjustYMaximum(string chartAreaName)
|
||||
{
|
||||
double MaxYValue = 0;
|
||||
|
||||
// find the max YValue from all points in all series
|
||||
foreach(Series ser in ChartObj.Series)
|
||||
{
|
||||
if(chartAreaName == ser.ChartArea && ser.Enabled)
|
||||
{
|
||||
// check agains all points except the last point
|
||||
// which is the totals column
|
||||
for(int index = 0; index < ser.Points.Count-1; index++)
|
||||
{
|
||||
DataPoint pt = ser.Points[index];
|
||||
if(pt.YValues[0] > MaxYValue)
|
||||
MaxYValue = pt.YValues[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double LogValue = (int)(Math.Log10(MaxYValue)) + 1;
|
||||
double NewMaxYValue = Math.Pow(10, LogValue);
|
||||
double ratio = MaxYValue / NewMaxYValue;
|
||||
double divisor = 1;
|
||||
|
||||
if(ratio <= 0.1)
|
||||
divisor = 10;
|
||||
else if(ratio < 0.2)
|
||||
divisor = 5;
|
||||
else if(ratio < 0.25)
|
||||
divisor = 4;
|
||||
else if(ratio < 0.4)
|
||||
divisor = 2.5;
|
||||
else if(ratio < 0.5)
|
||||
divisor = 2;
|
||||
else if(ratio < 0.8)
|
||||
divisor = 1.25;
|
||||
|
||||
ChartObj.ChartAreas[chartAreaName].AxisY.Maximum = NewMaxYValue / divisor;
|
||||
ChartObj.ChartAreas[chartAreaName].AxisY.RoundAxisValues();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A cleanup method that ensures the XValues are sorted accordingly and set explicitly.
|
||||
/// It will also create the totals for the DUMMY series.
|
||||
/// </summary>
|
||||
private void AdjustXValues(System.Web.UI.DataVisualization.Charting.Series series)
|
||||
{
|
||||
bool AddDummyPoints = true;
|
||||
|
||||
if(series.Name == "DUMMY")
|
||||
return;
|
||||
else if(ChartObj.Series["DUMMY"].Points.Count > 0)
|
||||
AddDummyPoints = false;
|
||||
|
||||
// sort the series
|
||||
series.Sort(PointSortOrder.Ascending, "X");
|
||||
|
||||
bool IsIndexed = false;
|
||||
|
||||
if(series.IsXValueIndexed)
|
||||
IsIndexed = true;
|
||||
else
|
||||
{
|
||||
bool IsFirstPoint = true;
|
||||
bool IsLastPointZero = false;
|
||||
|
||||
// the series X values must be set and greater than zero
|
||||
foreach(DataPoint pt in series.Points)
|
||||
{
|
||||
if(pt.XValue == 0 && !IsFirstPoint && IsLastPointZero)
|
||||
{
|
||||
IsIndexed = true;
|
||||
break;
|
||||
}
|
||||
else if (pt.XValue == 0 && IsFirstPoint)
|
||||
IsLastPointZero = true;
|
||||
|
||||
IsFirstPoint = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(IsIndexed)
|
||||
{
|
||||
series.IsXValueIndexed = false;
|
||||
int XValue = 0;
|
||||
|
||||
foreach(DataPoint pt in series.Points)
|
||||
pt.XValue = ++XValue;
|
||||
}
|
||||
|
||||
series.Points.AddXY(series.Points[series.Points.Count-1].XValue + 1, 0);
|
||||
series.Points[series.Points.Count-1].AxisLabel = "Total";
|
||||
series.Points[series.Points.Count-1].Color = Color.Transparent;
|
||||
series.Points[series.Points.Count-1].BorderColor = Color.Transparent;
|
||||
|
||||
int index = 0;
|
||||
foreach(DataPoint pt in series.Points)
|
||||
{
|
||||
if(AddDummyPoints)
|
||||
{
|
||||
ChartObj.Series["DUMMY"].Points.AddXY(pt.XValue, pt.YValues[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
ChartObj.Series["DUMMY"].Points[index].YValues[0] += pt.YValues[0];
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Remove Table
|
||||
|
||||
public void RemoveDataTable(string chartAreaName)
|
||||
{
|
||||
if (ChartObj.ChartAreas.IndexOf(chartAreaName) >= 0)
|
||||
ChartObj.ChartAreas[chartAreaName].AxisX.CustomLabels.Clear();
|
||||
|
||||
if(ChartAreas.IndexOf(chartAreaName) >= 0)
|
||||
{
|
||||
ChartAreas.RemoveAt(ChartAreas.IndexOf(chartAreaName));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Paint Event Handling
|
||||
|
||||
/// <summary>
|
||||
/// Chart Paint event handler.
|
||||
/// </summary>
|
||||
private void Chart_PostPaint(object sender, System.Web.UI.DataVisualization.Charting.ChartPaintEventArgs e)
|
||||
{
|
||||
if( e.ChartElement is ChartArea )
|
||||
{
|
||||
ChartArea area = (ChartArea)e.ChartElement;
|
||||
// call the paint method.
|
||||
if(ChartAreas.IndexOf(area.Name) >= 0 && enabled)
|
||||
{
|
||||
PaintDataTable(sender, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method does all the work for the painting of the data table.
|
||||
/// </summary>
|
||||
private void PaintDataTable(object sender, System.Web.UI.DataVisualization.Charting.ChartPaintEventArgs e)
|
||||
{
|
||||
ChartArea area = (ChartArea)e.ChartElement;
|
||||
|
||||
// get the rect of the chart area
|
||||
RectangleF rect = e.ChartGraphics.GetAbsoluteRectangle( area.Position.ToRectangleF() );
|
||||
|
||||
// get the inner plot position
|
||||
ElementPosition elemPos = area.InnerPlotPosition;
|
||||
|
||||
// find the coordinates of the inner plot position
|
||||
float x = rect.X + (rect.Width / 100 * elemPos.X);
|
||||
float y = rect.Y + (rect.Height / 100 * elemPos.Y);
|
||||
float ChartAreaBottomY = rect.Y + rect.Height;
|
||||
|
||||
float width = (rect.Width / 100 * elemPos.Width);
|
||||
float height = (rect.Height / 100 * elemPos.Height);
|
||||
|
||||
// find the height of the font that will be used
|
||||
Font axisFont = area.AxisX.LabelStyle.Font;
|
||||
string testString = "ForFontHeight";
|
||||
SizeF axisFontSize = e.ChartGraphics.Graphics.MeasureString(testString, axisFont);
|
||||
|
||||
// find the height of the font that will be used
|
||||
Font titleFont = area.AxisX.TitleFont;
|
||||
testString = area.AxisX.Title;
|
||||
SizeF titleFontSize = e.ChartGraphics.Graphics.MeasureString(testString, titleFont);
|
||||
|
||||
int seriesCount = 0;
|
||||
|
||||
// for each series that is attached to the chart area,
|
||||
// draw some boxes around the labels in the color provided
|
||||
for(int i = e.Chart.Series.Count-1; i >= 0; i--)
|
||||
{
|
||||
if(area.Name == e.Chart.Series[i].ChartArea)
|
||||
{
|
||||
seriesCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// now, if a box was actually drawn, then draw
|
||||
// the verticle lines to separate the columns of the table.
|
||||
if(seriesCount > 0)
|
||||
{
|
||||
for(int i = 0; i < e.Chart.Series.Count; i++)
|
||||
{
|
||||
if(area.Name == e.Chart.Series[i].ChartArea)
|
||||
{
|
||||
double min = area.AxisX.Minimum;
|
||||
double max = area.AxisX.Maximum;
|
||||
|
||||
// modify the min value for the current axis view
|
||||
if(area.AxisX.ScaleView.Position-1 > min)
|
||||
min = area.AxisX.ScaleView.Position-1;
|
||||
|
||||
// modify the max value for the currect axis view
|
||||
if( (area.AxisX.ScaleView.Position + area.AxisX.ScaleView.Size + 0.5) < max)
|
||||
max = area.AxisX.ScaleView.Position + area.AxisX.ScaleView.Size + 0.5;
|
||||
|
||||
|
||||
// find the starting point that will be display.
|
||||
// this is dependent on the current axis view.
|
||||
// this sample assumes the same number of points in each
|
||||
// series so always take from the zeroth series
|
||||
int pointIndex = 0;
|
||||
foreach(DataPoint pt in ChartObj.Series[0].Points)
|
||||
{
|
||||
if(pt.XValue > min)
|
||||
break;
|
||||
|
||||
pointIndex++;
|
||||
}
|
||||
|
||||
bool TableLegendDrawn = false;
|
||||
|
||||
for(double AxisValue = min; AxisValue < max; AxisValue++)
|
||||
{
|
||||
float pixelX = (float)e.ChartGraphics.GetPositionFromAxis(area.Name, AxisName.X, AxisValue);
|
||||
float nextPixelX = (float)e.ChartGraphics.GetPositionFromAxis(area.Name, AxisName.X, AxisValue + 1);
|
||||
float pixelY = ChartAreaBottomY - titleFontSize.Height - (seriesCount * axisFontSize.Height);
|
||||
|
||||
PointF point1 = PointF.Empty;
|
||||
PointF point2 = PointF.Empty;
|
||||
|
||||
// Set Maximum and minimum points
|
||||
point1.X = pixelX;
|
||||
point1.Y = 0;
|
||||
|
||||
// Convert relative coordinates to absolute coordinates.
|
||||
point1 = e.ChartGraphics.GetAbsolutePoint(point1);
|
||||
point2.X = point1.X;
|
||||
point2.Y = ChartAreaBottomY - titleFontSize.Height;
|
||||
point1.Y = pixelY;
|
||||
|
||||
// Draw connection line
|
||||
e.ChartGraphics.Graphics.DrawLine(new Pen(borderColor), point1,point2);
|
||||
|
||||
|
||||
point2.X = nextPixelX;
|
||||
point2.Y = 0;
|
||||
point2 = e.ChartGraphics.GetAbsolutePoint(point2);
|
||||
|
||||
StringFormat format = new StringFormat();
|
||||
format.Alignment = StringAlignment.Center;
|
||||
format.LineAlignment = StringAlignment.Center;
|
||||
|
||||
// for each series draw one value in the column
|
||||
int row = 0;
|
||||
foreach(Series ser in ChartObj.Series)
|
||||
{
|
||||
if(area.Name == ser.ChartArea)
|
||||
{
|
||||
if(!TableLegendDrawn)
|
||||
{
|
||||
// draw the series color box
|
||||
e.ChartGraphics.Graphics.FillRectangle(new SolidBrush(ser.Color),
|
||||
x-10, row*(axisFont.Height)+(point1.Y), 10, axisFontSize.Height);
|
||||
|
||||
e.ChartGraphics.Graphics.DrawRectangle(new Pen(borderColor),
|
||||
x-10, row*(axisFont.Height)+(point1.Y), 10, axisFontSize.Height);
|
||||
|
||||
e.ChartGraphics.Graphics.FillRectangle(new SolidBrush(tableColor),
|
||||
x,
|
||||
row*(axisFont.Height)+(point1.Y),
|
||||
width,
|
||||
axisFontSize.Height);
|
||||
|
||||
e.ChartGraphics.Graphics.DrawRectangle(new Pen(borderColor),
|
||||
x,
|
||||
row*(axisFont.Height)+(point1.Y),
|
||||
width,
|
||||
axisFontSize.Height);
|
||||
|
||||
}
|
||||
|
||||
if(pointIndex < ser.Points.Count)
|
||||
{
|
||||
string label = ser.Points[pointIndex].YValues[0].ToString();
|
||||
RectangleF textRect = new RectangleF(point1.X, row*(axisFont.Height)+(point1.Y+1), point2.X-point1.X, axisFont.Height);
|
||||
e.ChartGraphics.Graphics.DrawString(label, axisFont, new SolidBrush(area.AxisX.LabelStyle.ForeColor), textRect, format);
|
||||
}
|
||||
|
||||
row++;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
TableLegendDrawn = true;
|
||||
|
||||
pointIndex++;
|
||||
}
|
||||
|
||||
// do this only once so break!
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
BIN
MES_Manage/App_code/Utilities/ChartDataTableHelper/vssver2.scc
Normal file
BIN
MES_Manage/App_code/Utilities/ChartDataTableHelper/vssver2.scc
Normal file
Binary file not shown.
@@ -0,0 +1,258 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Web.UI.DataVisualization.Charting;
|
||||
using System.Collections;
|
||||
|
||||
namespace System.Web.UI.DataVisualization.Charting.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class that creates a histogram chart. Histogram is a data
|
||||
/// distribution chart which shows how many values, from the data series,
|
||||
/// are inside each segment interval.
|
||||
///
|
||||
/// You can define how many intervals you want to have using the SegmentIntervalNumber
|
||||
/// field or the exact length of the interval using the SegmentIntervalWidth
|
||||
/// field. Actual segment interval number can be slightly different due
|
||||
/// to the automatic interval rounding.
|
||||
/// </summary>
|
||||
public class HistogramChartHelper
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Number of class intervals the data range is devided in.
|
||||
/// This property only has affect when "SegmentIntervalWidth" is
|
||||
/// set to double.NaN.
|
||||
/// </summary>
|
||||
public int SegmentIntervalNumber = 20;
|
||||
|
||||
/// <summary>
|
||||
/// Histogram class interval width. Setting this value to "double.NaN"
|
||||
/// will result in automatic width calculation based on the data range
|
||||
/// and number of required interval specified in "SegmentIntervalNumber".
|
||||
/// </summary>
|
||||
public double SegmentIntervalWidth = double.NaN;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that percent frequency should be shown on the right axis
|
||||
/// </summary>
|
||||
public bool ShowPercentOnSecondaryYAxis = true;
|
||||
|
||||
#endregion // Fields
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Creates a histogram chart.
|
||||
/// </summary>
|
||||
/// <param name="chartControl">Chart control reference.</param>
|
||||
/// <param name="dataSeriesName">Name of the series which stores the original data.</param>
|
||||
/// <param name="histogramSeriesName">Name of the histogram series.</param>
|
||||
public void CreateHistogram(
|
||||
Chart chartControl,
|
||||
string dataSeriesName,
|
||||
string histogramSeriesName)
|
||||
{
|
||||
// Validate input
|
||||
if (chartControl == null)
|
||||
{
|
||||
throw (new ArgumentNullException("chartControl"));
|
||||
}
|
||||
if (chartControl.Series.IndexOf(dataSeriesName) < 0)
|
||||
{
|
||||
throw (new ArgumentException("Series with name'" + dataSeriesName + "' was not found.", "dataSeriesName"));
|
||||
}
|
||||
|
||||
// Make data series invisible
|
||||
chartControl.Series[dataSeriesName].Enabled = false;
|
||||
|
||||
// Check if histogram series exsists
|
||||
Series histogramSeries = null;
|
||||
if (chartControl.Series.IndexOf(histogramSeriesName) < 0)
|
||||
{
|
||||
// Add new series
|
||||
histogramSeries = chartControl.Series.Add(histogramSeriesName);
|
||||
|
||||
// Set new series chart type and other attributes
|
||||
histogramSeries.ChartType = SeriesChartType.Column;
|
||||
histogramSeries.BorderColor = Color.Black;
|
||||
histogramSeries.BorderWidth = 1;
|
||||
histogramSeries.BorderDashStyle = ChartDashStyle.Solid;
|
||||
}
|
||||
else
|
||||
{
|
||||
histogramSeries = chartControl.Series[histogramSeriesName];
|
||||
histogramSeries.Points.Clear();
|
||||
}
|
||||
|
||||
// Get data series minimum and maximum values
|
||||
double minValue = double.MaxValue;
|
||||
double maxValue = double.MinValue;
|
||||
int pointCount = 0;
|
||||
foreach (DataPoint dataPoint in chartControl.Series[dataSeriesName].Points)
|
||||
{
|
||||
// Process only non-empty data points
|
||||
if (!dataPoint.IsEmpty)
|
||||
{
|
||||
if (dataPoint.YValues[0] > maxValue)
|
||||
{
|
||||
maxValue = dataPoint.YValues[0];
|
||||
}
|
||||
if (dataPoint.YValues[0] < minValue)
|
||||
{
|
||||
minValue = dataPoint.YValues[0];
|
||||
}
|
||||
++pointCount;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate interval width if it's not set
|
||||
if (double.IsNaN(this.SegmentIntervalWidth))
|
||||
{
|
||||
this.SegmentIntervalWidth = (maxValue - minValue) / SegmentIntervalNumber;
|
||||
this.SegmentIntervalWidth = RoundInterval(this.SegmentIntervalWidth);
|
||||
}
|
||||
|
||||
// Round minimum and maximum values
|
||||
minValue = Math.Floor(minValue / this.SegmentIntervalWidth) * this.SegmentIntervalWidth;
|
||||
maxValue = Math.Ceiling(maxValue / this.SegmentIntervalWidth) * this.SegmentIntervalWidth;
|
||||
|
||||
// Create histogram series points
|
||||
double currentPosition = minValue;
|
||||
for (currentPosition = minValue; currentPosition <= maxValue; currentPosition += this.SegmentIntervalWidth)
|
||||
{
|
||||
// Count all points from data series that are in current interval
|
||||
int count = 0;
|
||||
foreach (DataPoint dataPoint in chartControl.Series[dataSeriesName].Points)
|
||||
{
|
||||
if (!dataPoint.IsEmpty)
|
||||
{
|
||||
double endPosition = currentPosition + this.SegmentIntervalWidth;
|
||||
if (dataPoint.YValues[0] >= currentPosition &&
|
||||
dataPoint.YValues[0] < endPosition)
|
||||
{
|
||||
++count;
|
||||
}
|
||||
|
||||
// Last segment includes point values on both segment boundaries
|
||||
else if (endPosition >= maxValue)
|
||||
{
|
||||
if (dataPoint.YValues[0] >= currentPosition &&
|
||||
dataPoint.YValues[0] <= endPosition)
|
||||
{
|
||||
++count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Add data point into the histogram series
|
||||
histogramSeries.Points.AddXY(currentPosition + this.SegmentIntervalWidth / 2.0, count);
|
||||
//histogramSeries.Points.AddXY("", count);
|
||||
//histogramSeries.Points.AddY( count);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Adjust series attributes
|
||||
histogramSeries["PointWidth"] = "1";
|
||||
|
||||
// Adjust chart area
|
||||
ChartArea chartArea = chartControl.ChartAreas[histogramSeries.ChartArea];
|
||||
chartArea.AxisY.Title = "ƵÊý";
|
||||
chartArea.AxisX.Minimum = minValue;
|
||||
chartArea.AxisX.Maximum = maxValue;
|
||||
|
||||
// Set axis interval based on the histogram class interval
|
||||
// and do not allow more than 10 labels on the axis.
|
||||
double axisInterval = this.SegmentIntervalWidth;
|
||||
while ((maxValue - minValue) / axisInterval > 10.0)
|
||||
{
|
||||
axisInterval *= 2.0;
|
||||
}
|
||||
chartArea.AxisX.Interval = axisInterval;
|
||||
|
||||
// Set chart area secondary Y axis
|
||||
chartArea.AxisY2.Enabled = AxisEnabled.Auto;
|
||||
if (this.ShowPercentOnSecondaryYAxis)
|
||||
{
|
||||
chartArea.RecalculateAxesScale();
|
||||
|
||||
chartArea.AxisY2.Enabled = AxisEnabled.True;
|
||||
chartArea.AxisY2.LabelStyle.Format = "P0";
|
||||
chartArea.AxisY2.MajorGrid.Enabled = false;
|
||||
chartArea.AxisY2.Title = "Percent of Total";
|
||||
|
||||
chartArea.AxisY2.Minimum = 0;
|
||||
chartArea.AxisY2.Maximum = chartArea.AxisY.Maximum / (pointCount / 100.0);
|
||||
double minStep = (chartArea.AxisY2.Maximum > 20.0) ? 5.0 : 1.0;
|
||||
chartArea.AxisY2.Interval = Math.Ceiling((chartArea.AxisY2.Maximum / 5.0 / minStep)) * minStep;
|
||||
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Helper method which rounds specified axsi interval.
|
||||
/// </summary>
|
||||
/// <param name="interval">Calculated axis interval.</param>
|
||||
/// <returns>Rounded axis interval.</returns>
|
||||
public double RoundInterval( double interval )
|
||||
{
|
||||
// If the interval is zero return error
|
||||
if( interval == 0.0 )
|
||||
{
|
||||
throw( new ArgumentOutOfRangeException("interval", "Interval can not be zero."));
|
||||
}
|
||||
|
||||
// If the real interval is > 1.0
|
||||
double step = -1;
|
||||
double tempValue = interval;
|
||||
while( tempValue > 1.0 )
|
||||
{
|
||||
step ++;
|
||||
tempValue = tempValue / 10.0;
|
||||
if( step > 1000 )
|
||||
{
|
||||
throw( new InvalidOperationException( "Auto interval error due to invalid point values or axis minimum/maximum." ) );
|
||||
}
|
||||
}
|
||||
|
||||
// If the real interval is < 1.0
|
||||
tempValue = interval;
|
||||
if( tempValue < 1.0 )
|
||||
{
|
||||
step = 0;
|
||||
}
|
||||
|
||||
while( tempValue < 1.0 )
|
||||
{
|
||||
step --;
|
||||
tempValue = tempValue * 10.0;
|
||||
if( step < -1000 )
|
||||
{
|
||||
throw( new InvalidOperationException( "Auto interval error due to invalid point values or axis minimum/maximum." ) );
|
||||
}
|
||||
}
|
||||
|
||||
double tempDiff = interval / Math.Pow( 10.0, step );
|
||||
if( tempDiff < 3.0 )
|
||||
{
|
||||
tempDiff = 2.0;
|
||||
}
|
||||
else if( tempDiff < 7.0 )
|
||||
{
|
||||
tempDiff = 5.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
tempDiff = 10.0;
|
||||
}
|
||||
|
||||
// Make a correction of the real interval
|
||||
return tempDiff * Math.Pow( 10.0, step );
|
||||
}
|
||||
|
||||
#endregion // Methods
|
||||
}
|
||||
}
|
||||
BIN
MES_Manage/App_code/Utilities/HistogramchartHelper/vssver2.scc
Normal file
BIN
MES_Manage/App_code/Utilities/HistogramchartHelper/vssver2.scc
Normal file
Binary file not shown.
326
MES_Manage/App_code/Utilities/PassBandFilter/FFT.cs
Normal file
326
MES_Manage/App_code/Utilities/PassBandFilter/FFT.cs
Normal file
@@ -0,0 +1,326 @@
|
||||
//=================================================================
|
||||
// File: FFT.cs
|
||||
//
|
||||
// Namespace: System.Web.UI.DataVisualization.Charting.Utilities
|
||||
//
|
||||
// Classes: FFT
|
||||
//
|
||||
// Purpose: Used for the fast fourier transformation algorithm
|
||||
//
|
||||
//===================================================================
|
||||
// Chart Control for ASP.Net
|
||||
//===================================================================
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace System.Web.UI.DataVisualization.Charting.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class which implements the various window functions for determination of the filter
|
||||
/// coefficients.
|
||||
/// </summary>
|
||||
class FFT
|
||||
{
|
||||
#region Members
|
||||
/// <summary>
|
||||
/// Filter type enumeration for identification of what type of filter we want coefficients
|
||||
/// for.
|
||||
/// </summary>
|
||||
public enum FilterType { HighPass, LowPass, BandPass };
|
||||
|
||||
/// <summary>
|
||||
/// Algorithm enumeration for choice of algorithm
|
||||
/// </summary>
|
||||
public enum Algorithm { Kaiser, Hann, Hamming, Blackman, Rectangular };
|
||||
|
||||
private float myRate;
|
||||
private float myFreqFrom;
|
||||
private float myFreqTo;
|
||||
private float myAttenuation;
|
||||
private float myBand;
|
||||
private float myAlpha;
|
||||
private int myOrder;
|
||||
|
||||
/// <summary>
|
||||
/// Shannon sampling frequency
|
||||
/// </summary>
|
||||
private float myFS;
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
/// <summary>
|
||||
/// Sampling rate
|
||||
/// </summary>
|
||||
public float Rate
|
||||
{
|
||||
get { return myRate; }
|
||||
set
|
||||
{
|
||||
myRate = value;
|
||||
myFS = 0.5f * myRate;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starting frequency for passband. Must be lower than the ending frequency.
|
||||
/// </summary>
|
||||
public float FreqFrom
|
||||
{
|
||||
get { return myFreqFrom; }
|
||||
set { myFreqFrom = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ending frequency for passband. Must be higher than the starting frequency.
|
||||
/// </summary>
|
||||
public float FreqTo
|
||||
{
|
||||
get { return myFreqTo; }
|
||||
set { myFreqTo = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stopband attenuation.
|
||||
/// </summary>
|
||||
public float StopBandAttenuation
|
||||
{
|
||||
get { return myAttenuation; }
|
||||
set { myAttenuation = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transition band.
|
||||
/// </summary>
|
||||
public float TransitionBand
|
||||
{
|
||||
get { return myBand; }
|
||||
set { myBand = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Alpha value used for the Kaiser algorithm.
|
||||
/// </summary>
|
||||
public float Alpha
|
||||
{
|
||||
get { return myAlpha; }
|
||||
set { myAlpha = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter order. Must be an even number.
|
||||
/// </summary>
|
||||
public int Order
|
||||
{
|
||||
get { return myOrder; }
|
||||
set { myOrder = value; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
/// <summary>
|
||||
/// Construct a FFT instance and initialize with default values.
|
||||
/// </summary>
|
||||
public FFT()
|
||||
{
|
||||
//default rate to 8000
|
||||
Rate = 8000;
|
||||
|
||||
//default attenuation to 60db
|
||||
this.myAttenuation = 60;
|
||||
|
||||
//default transition band to 500hz
|
||||
this.myBand = 500;
|
||||
|
||||
//default order to 0 so that we'll know if it was changed by the user or not
|
||||
this.myOrder = 0;
|
||||
|
||||
//default Alpha to 4
|
||||
this.myAlpha = 4;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Mathematical Functions
|
||||
/// <summary>
|
||||
/// Bessel is the zeroth order Bessel function which is used in the Kaiser window.
|
||||
/// This is a polynomial approximation of the zeroth order modified Bessel function found in:
|
||||
/// W.H. Press, B.P. Flannery, S.A. Teukolsky, and W.T. Vetterling.
|
||||
/// Numerical Recipes in C: The Art of Scientific Computing.
|
||||
/// Cambridge UP, 1988.
|
||||
/// P. 237
|
||||
/// </summary>
|
||||
/// <param name="x">Input number which the Bessel will be performed on</param>
|
||||
private float Bessel(float x)
|
||||
{
|
||||
double ax, ans;
|
||||
double y;
|
||||
|
||||
ax = System.Math.Abs(x);
|
||||
if (ax < 3.75)
|
||||
{
|
||||
y = x / 3.75;
|
||||
y *= y;
|
||||
ans = 1.0 + y * (3.5156229 + y * (3.0899424 + y * (1.2067492
|
||||
+ y * (0.2659732 + y * (0.360768e-1 + y * 0.45813e-2)))));
|
||||
}
|
||||
else
|
||||
{
|
||||
y = 3.75 / ax;
|
||||
ans = (System.Math.Exp(ax) / System.Math.Sqrt(ax)) * (0.39894228 + y * (0.1328592e-1
|
||||
+ y * (0.225319e-2 + y * (-0.157565e-2 + y * (0.916281e-2
|
||||
+ y * (-0.2057706e-1 + y * (0.2635537e-1 + y * (-0.1647633e-1
|
||||
+ y * 0.392377e-2))))))));
|
||||
}
|
||||
|
||||
return (float)ans;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Generate Coefficients
|
||||
/// <summary>
|
||||
/// Calculate the coefficients to be used by the filter function.
|
||||
/// </summary>
|
||||
/// <param name="filterType">Enum type which specifies the filter to be performed.</param>
|
||||
/// <param name="alg">Enum type which specifies which algorithm to be used for the window
|
||||
/// algorithm.</param>
|
||||
public float[] GenerateCoefficients(FilterType filterType, Algorithm alg)
|
||||
{
|
||||
//Calculate order if it hasn't been set
|
||||
if (this.myOrder == 0)
|
||||
this.myOrder = (int)(((this.myAttenuation - 7.95f) / (this.myBand * 14.36f / this.myFS) + 1.0f) * 2.0f) - 1;
|
||||
|
||||
float[] window = new float[(this.myOrder / 2) + 1];
|
||||
float[] coEff = new float[this.myOrder + 1];
|
||||
float ps;
|
||||
float pe;
|
||||
const float PI = (float)System.Math.PI;
|
||||
int o2 = this.myOrder / 2;
|
||||
|
||||
//Switch based on algorithm
|
||||
switch (alg)
|
||||
{
|
||||
case Algorithm.Kaiser:
|
||||
//Kaiser Window function
|
||||
for (int i = 1; i <= o2; i++)
|
||||
{
|
||||
window[i] = Bessel(this.myAlpha * (float)System.Math.Sqrt(1.0f - (float)System.Math.Pow((float)i / o2, 2))) / Bessel(this.myAlpha);
|
||||
}
|
||||
|
||||
//Stopband attenuation and transition band should be set by the user
|
||||
break;
|
||||
|
||||
case Algorithm.Hann:
|
||||
//Hann window function
|
||||
for (int i = 1; i <= o2; i++)
|
||||
{
|
||||
window[i] = 0.5f + 0.5f * (float)System.Math.Cos((PI / (o2 + 1)) * i);
|
||||
}
|
||||
|
||||
//Set the min stopband attenuation
|
||||
this.StopBandAttenuation = 44.0f;
|
||||
|
||||
//Set the transition band
|
||||
this.TransitionBand = 6.22f * this.myFS / this.myOrder;
|
||||
break;
|
||||
|
||||
case Algorithm.Hamming:
|
||||
//Hamming window function
|
||||
for (int i = 1; i <= o2; i++)
|
||||
{
|
||||
window[i] = 0.54f + 0.46f * (float)System.Math.Cos((PI / o2) * i);
|
||||
}
|
||||
|
||||
//Set the min stopband attenuation
|
||||
this.StopBandAttenuation = 53.0f;
|
||||
|
||||
//Set the transition band
|
||||
this.TransitionBand = 6.64f * this.myFS / this.myOrder;
|
||||
break;
|
||||
|
||||
case Algorithm.Blackman:
|
||||
//Blackman window function
|
||||
for (int i = 1; i <= o2; i++)
|
||||
{
|
||||
window[i] = 0.42f + 0.5f * (float)Math.Cos((PI / o2) * i) + 0.08f * (float)Math.Cos(2.0f * (PI / o2) * i);
|
||||
}
|
||||
|
||||
//Set the min stopband attenuation
|
||||
this.StopBandAttenuation = 74.0f;
|
||||
|
||||
//Set the transition band
|
||||
this.TransitionBand = 11.13f * this.myFS / this.myOrder;
|
||||
break;
|
||||
|
||||
case Algorithm.Rectangular:
|
||||
//Rectangular window function
|
||||
for (int i = 1; i <= o2; i++)
|
||||
{
|
||||
window[i] = 1.0f;
|
||||
}
|
||||
|
||||
//Set the min stopband attenuation
|
||||
this.StopBandAttenuation = 21.0f;
|
||||
|
||||
//Set the transition band
|
||||
this.TransitionBand = 1.84f * this.myFS / this.myOrder;
|
||||
break;
|
||||
|
||||
default:
|
||||
//Zero all values if nothing was set (error)
|
||||
for (int i = 1; i <= o2; i++)
|
||||
{
|
||||
window[i] = 0.0f;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//Switch based on filtertype
|
||||
switch (filterType)
|
||||
{
|
||||
case FilterType.BandPass:
|
||||
pe = PI / 2 * (this.FreqTo - this.FreqFrom + this.myBand) / this.myFS;
|
||||
ps = PI / 2 * (this.FreqFrom + this.FreqTo) / this.myFS;
|
||||
break;
|
||||
|
||||
case FilterType.LowPass:
|
||||
pe = PI * (this.FreqTo + this.myBand / 2) / this.myFS;
|
||||
ps = 0.0f;
|
||||
break;
|
||||
|
||||
case FilterType.HighPass:
|
||||
pe = PI * (1.0f - (this.FreqFrom - this.myBand / 2) / this.myFS);
|
||||
ps = PI;
|
||||
break;
|
||||
|
||||
default:
|
||||
pe = 0.0f;
|
||||
ps = 0.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
//Set first coefficient value
|
||||
coEff[0] = pe / PI;
|
||||
|
||||
//Calculate coefficientsw
|
||||
for (int i = 1; i <= o2; i++)
|
||||
{
|
||||
coEff[i] = window[i] * (float)System.Math.Sin(i * pe) * (float)System.Math.Cos(i * ps) / (i * PI);
|
||||
}
|
||||
|
||||
//Shift Impulse
|
||||
for (int i = o2 + 1; i <= this.myOrder; i++)
|
||||
{
|
||||
coEff[i] = coEff[i - o2];
|
||||
}
|
||||
for (int i = 0; i <= o2 - 1; i++)
|
||||
{
|
||||
coEff[i] = coEff[this.myOrder - i];
|
||||
}
|
||||
coEff[o2] = pe / PI;
|
||||
|
||||
return coEff;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
324
MES_Manage/App_code/Utilities/PassBandFilter/FIRFilters.cs
Normal file
324
MES_Manage/App_code/Utilities/PassBandFilter/FIRFilters.cs
Normal file
@@ -0,0 +1,324 @@
|
||||
//=================================================================
|
||||
// File: FIRFilters.cs
|
||||
//
|
||||
// Namespace: System.Web.UI.DataVisualization.Charting.Utilities
|
||||
//
|
||||
// Classes: FIRFilters, FFT
|
||||
//
|
||||
// Purpose: Used to perform digital filters on charts
|
||||
//
|
||||
//===================================================================
|
||||
// Chart Control for ASP.Net
|
||||
//===================================================================
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Web.UI.DataVisualization.Charting;
|
||||
|
||||
namespace System.Web.UI.DataVisualization.Charting.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class which implements the filtering functions. Currently Low Pass, High Pass and
|
||||
/// Band Pass are implemented.
|
||||
/// </summary>
|
||||
class FIRFilters
|
||||
{
|
||||
#region Members
|
||||
/// <summary>
|
||||
/// The number of samples is the same as the number of points
|
||||
/// </summary>
|
||||
private int mySamples;
|
||||
|
||||
/// <summary>
|
||||
/// Holds the coefficient from the window function
|
||||
/// </summary>
|
||||
private float[] myCoeff;
|
||||
|
||||
/// <summary>
|
||||
/// Holds the series which has the input data
|
||||
/// </summary>
|
||||
private Series myInputSeries;
|
||||
|
||||
/// <summary>
|
||||
/// Holds the series which we are outputting to
|
||||
/// </summary>
|
||||
private Series myFilterSeries;
|
||||
|
||||
/// <summary>
|
||||
/// FFT algorithm object
|
||||
/// </summary>
|
||||
private FFT myFFT;
|
||||
|
||||
/// <summary>
|
||||
/// Holds the current algorithm selected. Enumeration type is drawn from the FFT object.
|
||||
/// </summary>
|
||||
public FFT.Algorithm CurrentAlgorithm;
|
||||
|
||||
private float myFreqFrom;
|
||||
private float myFreqTo;
|
||||
private float myAttenuation;
|
||||
private float myBand;
|
||||
private float myAlpha;
|
||||
private int myTaps;
|
||||
private int myOrder;
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
/// <summary>
|
||||
/// The starting passband frequency, must be lower than ending frequency.
|
||||
/// </summary>
|
||||
public float FreqFrom
|
||||
{
|
||||
get { return myFreqFrom; }
|
||||
set { myFreqFrom = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ending passband frequency, must be higher than starting frequency.
|
||||
/// </summary>
|
||||
public float FreqTo
|
||||
{
|
||||
get { return myFreqTo; }
|
||||
set { myFreqTo = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stopband attenuation
|
||||
/// </summary>
|
||||
public float StopBandAttenuation
|
||||
{
|
||||
get { return myAttenuation; }
|
||||
set {
|
||||
myAttenuation = value;
|
||||
this.myFFT.StopBandAttenuation = myAttenuation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transition band
|
||||
/// </summary>
|
||||
public float TransitionBand
|
||||
{
|
||||
get { return myBand; }
|
||||
set {
|
||||
myBand = value;
|
||||
this.myFFT.TransitionBand = myBand;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Alpha value used for the Kaiser algorithm.
|
||||
/// </summary>
|
||||
public float Alpha
|
||||
{
|
||||
get { return myAlpha; }
|
||||
set {
|
||||
myAlpha = value;
|
||||
this.myFFT.Alpha = myAlpha;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of taps to be used. Taps is the number of samples processed at any one time.
|
||||
/// </summary>
|
||||
public int Taps
|
||||
{
|
||||
get { return myTaps; }
|
||||
set { myTaps = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter order. Must be an even number.
|
||||
/// </summary>
|
||||
public int Order
|
||||
{
|
||||
get { return myOrder; }
|
||||
set
|
||||
{
|
||||
//Assure value is even
|
||||
if ((value % 2) == 0)
|
||||
{
|
||||
myOrder = value;
|
||||
this.myFFT.Order = myOrder;
|
||||
}
|
||||
else
|
||||
throw new ArgumentOutOfRangeException("Order", "Filter order must be an even number.");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
/// <summary>
|
||||
/// Main constructor. Resets all settings within the FFT algorithm object.
|
||||
/// </summary>
|
||||
public FIRFilters()
|
||||
{
|
||||
//Create a new FFT object
|
||||
this.myFFT = new FFT();
|
||||
|
||||
//Default algorithm to Kaiser
|
||||
this.CurrentAlgorithm = FFT.Algorithm.Kaiser;
|
||||
|
||||
//Default taps to 35
|
||||
this.myTaps = 35;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
/// <summary>
|
||||
/// Performs a low pass filter. Output series will be cleared before being
|
||||
/// output to. If passband start and end frequencies are left at 0, defaults are used.
|
||||
/// </summary>
|
||||
/// <param name="iseries">Input series that contains input data. Input Y-Values must be contained in YValues[0] or unexpected output will occur</param>
|
||||
/// <param name="oseries">Output series to which filter will be written. Output Y-Values are written to YValues[0]</param>
|
||||
public void LowPassFilter(Series iseries, Series oseries)
|
||||
{
|
||||
//If no start and end frequencies are specified, default low pass frequency range to:
|
||||
//0 - 1000hz
|
||||
if (this.myFreqFrom == 0.0f && this.myFreqTo == 0.0f)
|
||||
{
|
||||
this.myFFT.FreqFrom = 0.0f;
|
||||
this.myFFT.FreqTo = 1000.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.myFFT.FreqFrom = this.myFreqFrom;
|
||||
this.myFFT.FreqTo = this.myFreqTo;
|
||||
}
|
||||
|
||||
//Generate the actual coefficients
|
||||
this.myCoeff = this.myFFT.GenerateCoefficients(FFT.FilterType.LowPass, CurrentAlgorithm);
|
||||
|
||||
//Filter the series based on the coefficients generated
|
||||
Filter(iseries, oseries);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a high pass filter. Output series will be cleared before being
|
||||
/// output to. If passband start and end frequencies are left at 0, defaults are used.
|
||||
/// </summary>
|
||||
/// <param name="iseries">Input series that contains input data. Input Y-Values must be contained in YValues[0] or unexpected output will occur</param>
|
||||
/// <param name="oseries">Output series to which filter will be written. Output Y-Values are written to YValues[0]</param>
|
||||
public void HighPassFilter(Series iseries, Series oseries)
|
||||
{
|
||||
//If no start and end frequencies are specified, default high pass frequency range to:
|
||||
//2000 - 4000hz
|
||||
if (this.myFreqFrom == 0.0f && this.myFreqTo == 0.0f)
|
||||
{
|
||||
this.myFFT.FreqFrom = 2000.0f;
|
||||
this.myFFT.FreqTo = 4000.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.myFFT.FreqFrom = this.myFreqFrom;
|
||||
this.myFFT.FreqTo = this.myFreqTo;
|
||||
}
|
||||
|
||||
//Generate the actual coefficients
|
||||
this.myCoeff = this.myFFT.GenerateCoefficients(FFT.FilterType.HighPass, CurrentAlgorithm);
|
||||
|
||||
//Filter the series based on the coefficients generated
|
||||
Filter(iseries, oseries);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a band pass filter. Output series will be cleared before being
|
||||
/// output to. If passband start and end frequencies are left at 0, defaults are used.
|
||||
/// </summary>
|
||||
/// <param name="iseries">Input series that contains input data. Input Y-Values must be contained in YValues[0] or unexpected output will occur</param>
|
||||
/// <param name="oseries">Output series to which filter will be written. Output Y-Values are written to YValues[0]</param>
|
||||
public void BandPassFilter(Series iseries, Series oseries)
|
||||
{
|
||||
//If no start and end frequencies are specified, default band pass frequency range to:
|
||||
//1000 - 1000hz
|
||||
if (this.myFreqFrom == 0.0f && this.myFreqTo == 0.0f)
|
||||
{
|
||||
this.myFFT.FreqFrom = 1000.0f;
|
||||
this.myFFT.FreqTo = 1000.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.myFFT.FreqFrom = this.myFreqFrom;
|
||||
this.myFFT.FreqTo = this.myFreqTo;
|
||||
}
|
||||
|
||||
//Generate the actual coefficients
|
||||
this.myCoeff = this.myFFT.GenerateCoefficients(FFT.FilterType.BandPass, CurrentAlgorithm);
|
||||
|
||||
//Filter the series based on the coefficients generated
|
||||
Filter(iseries, oseries);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Initialization
|
||||
/// <summary>
|
||||
/// Initializes the FIRFilters object by setting the input and output series members for use
|
||||
/// by the filter.
|
||||
/// </summary>
|
||||
/// <param name="iseries">Input series that contains input data</param>
|
||||
/// <param name="oseries">Output series to which filter will be written</param>
|
||||
private void SetIOSeries(Series iseries, Series oseries)
|
||||
{
|
||||
this.myInputSeries = iseries;
|
||||
this.myFilterSeries = oseries;
|
||||
|
||||
//Samples is the number of points contained in the input
|
||||
this.mySamples = myInputSeries.Points.Count;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Filter
|
||||
/// <summary>
|
||||
/// Performs the actual filter. Coefficients should have already be generated by the calling
|
||||
/// function, this function merely applies them and physically adds the points to the output series.
|
||||
/// </summary>
|
||||
/// <param name="iseries">Input series that contains input data</param>
|
||||
/// <param name="oseries">Output series to which filter will be written</param>
|
||||
private void Filter(Series iseries, Series oseries)
|
||||
{
|
||||
float[] x = new float[myTaps];
|
||||
float y;
|
||||
|
||||
//Set the series
|
||||
SetIOSeries(iseries, oseries);
|
||||
|
||||
//Clear series
|
||||
myFilterSeries.Points.Clear();
|
||||
|
||||
//Initialize x
|
||||
for (int i = 1; i < myTaps; i++)
|
||||
x[i] = 0.0f;
|
||||
|
||||
//Loop through every data point
|
||||
for (int i = 0; i < mySamples; i++)
|
||||
{
|
||||
//Initialize y
|
||||
y = 0.0f;
|
||||
|
||||
//Obtain the data value (Y value) at the specified X value (i)
|
||||
x[0] = Convert.ToSingle(myInputSeries.Points[i].YValues[0]);
|
||||
|
||||
//Loop through from 0 to number of taps and calculate the sum
|
||||
try
|
||||
{
|
||||
for (int j = 0; j < myTaps; j++)
|
||||
y = y + (x[j] * myCoeff[j]);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(e.Message + " Check filter order.");
|
||||
throw;
|
||||
}
|
||||
|
||||
//Shift all x values by 1 to the right
|
||||
for (int j = myTaps - 1; j > 0; j--)
|
||||
x[j] = x[j - 1];
|
||||
|
||||
//Add the y value to the output series at the current x value
|
||||
myFilterSeries.Points.Add(new System.Web.UI.DataVisualization.Charting.DataPoint(i, y));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
BIN
MES_Manage/App_code/Utilities/PassBandFilter/vssver2.scc
Normal file
BIN
MES_Manage/App_code/Utilities/PassBandFilter/vssver2.scc
Normal file
Binary file not shown.
@@ -0,0 +1,372 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Web.UI.DataVisualization.Charting;
|
||||
using System.Collections;
|
||||
|
||||
namespace System.Web.UI.DataVisualization.Charting.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class which improves the readability of the small segments in the Pie chart.
|
||||
/// Pie segments which are too small are shown in a supplemental pie chart series.
|
||||
/// </summary>
|
||||
public class PieCollectedDataHelper
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the percentage of the total series values. This value determines
|
||||
/// if the data point value is a "small" value and should be shown as collected.
|
||||
/// </summary>
|
||||
public double CollectedPercentage = 5.0;
|
||||
|
||||
/// <summary>
|
||||
/// Position in relative coordinates ( 0,0 - top left corner; 100,100 - bottom right corner)
|
||||
/// where original and supplemental pie charts should be placed.
|
||||
/// </summary>
|
||||
public RectangleF ChartAreaPosition = new RectangleF(5f, 5f, 90f, 90f);
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if small segments should be shown as one "collected" segment in the original series
|
||||
/// </summary>
|
||||
public bool ShowCollectedDataAsOneSlice = false;
|
||||
|
||||
/// <summary>
|
||||
/// Spacing between the original and supplemental chart areas in percentage
|
||||
/// </summary>
|
||||
public float ChartAreaSpacing = 5f;
|
||||
|
||||
/// <summary>
|
||||
/// Size ratio between the original and supplemental chart areas.
|
||||
/// Value of 1.0f indicates that same area size will be used.
|
||||
/// </summary>
|
||||
public float SupplementedAreaSizeRatio = 0.9f;
|
||||
|
||||
/// <summary>
|
||||
/// Color of the connection lines
|
||||
/// </summary>
|
||||
public Color ConnectionLinesColor = Color.FromArgb(64, 64, 64);
|
||||
|
||||
/// <summary>
|
||||
/// Collected pie segment label
|
||||
/// </summary>
|
||||
public string CollectedLabel = "Other";
|
||||
|
||||
|
||||
// Reference to the parameters
|
||||
private Chart chartControl = null;
|
||||
private Series series = null;
|
||||
|
||||
// Internal use fields
|
||||
private Series supplementalSeries = null;
|
||||
private ChartArea originalChartArea = null;
|
||||
private ChartArea supplementalChartArea = null;
|
||||
private float collectedPieSliceAngle = 0f;
|
||||
|
||||
#endregion // Fields
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Public constructor.
|
||||
/// </summary>
|
||||
/// <param name="chartControl">Reference to the chart control.</param>
|
||||
public PieCollectedDataHelper(Chart chartControl)
|
||||
{
|
||||
this.chartControl = chartControl;
|
||||
|
||||
// Handle chart PostPaint event to draw the "connection" between the
|
||||
// collected pie slice and supplemental chart.
|
||||
this.chartControl.PostPaint +=new EventHandler<ChartPaintEventArgs>(this.chart_PostPaint);
|
||||
}
|
||||
|
||||
#endregion // Constructor
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Shows small pie segments as supplemental pie chart series in the new chart area.
|
||||
/// </summary>
|
||||
/// <param name="seriesName">Series name </param>
|
||||
public void ShowSmallSegmentsAsSupplementalPie(string seriesName)
|
||||
{
|
||||
// Validate input
|
||||
if(this.chartControl == null)
|
||||
{
|
||||
throw(new ArgumentNullException("chartControl"));
|
||||
}
|
||||
if(this.CollectedPercentage > 100.0 || this.CollectedPercentage < 0.0)
|
||||
{
|
||||
throw(new ArgumentException("Value must be in range from 0 to 100 percent.", "CollectedPercentage"));
|
||||
}
|
||||
|
||||
// Initialize reference to the series
|
||||
this.series = this.chartControl.Series[seriesName];
|
||||
|
||||
// Check input series type
|
||||
if(this.series.ChartType != SeriesChartType.Pie &&
|
||||
this.series.ChartType != SeriesChartType.Doughnut)
|
||||
{
|
||||
throw(new InvalidOperationException("Only series with Pie or Doughnut chart type can be used."));
|
||||
}
|
||||
|
||||
// Check if specified series has data points
|
||||
if(series.Points.Count == 0)
|
||||
{
|
||||
throw(new InvalidOperationException("Cannot perform operatiuon on an empty series."));
|
||||
}
|
||||
|
||||
// Create "collected" pie slice in original series
|
||||
this.supplementalChartArea = null;
|
||||
if( CreateCollectedPie() )
|
||||
{
|
||||
// Calculate width of supplemental chart area
|
||||
float supplementalWidth = (this.ChartAreaPosition.Width - this.ChartAreaSpacing) / 2f * this.SupplementedAreaSizeRatio;
|
||||
|
||||
// Adjust position of the original chart area
|
||||
this.originalChartArea = this.chartControl.ChartAreas[this.series.ChartArea];
|
||||
originalChartArea.Position.X = this.ChartAreaPosition.X;
|
||||
originalChartArea.Position.Y = this.ChartAreaPosition.Y;
|
||||
originalChartArea.Position.Width = this.ChartAreaPosition.Width - supplementalWidth - this.ChartAreaSpacing;
|
||||
originalChartArea.Position.Height = this.ChartAreaPosition.Height;
|
||||
|
||||
// Original chart area must be in 2D mode
|
||||
originalChartArea.Area3DStyle.Enable3D = false;
|
||||
|
||||
// Create and adjust position of the supplemental chart area
|
||||
this.supplementalChartArea = new ChartArea();
|
||||
supplementalChartArea.Name = originalChartArea.Name + "_Supplemental";
|
||||
supplementalChartArea.Position.X = originalChartArea.Position.Right + this.ChartAreaSpacing;
|
||||
supplementalChartArea.Position.Y = this.ChartAreaPosition.Y;
|
||||
supplementalChartArea.Position.Width = supplementalWidth;
|
||||
supplementalChartArea.Position.Height = this.ChartAreaPosition.Height;
|
||||
this.chartControl.ChartAreas.Add(supplementalChartArea);
|
||||
|
||||
// Create supplemental pie chart series to show all the collected data
|
||||
this.supplementalSeries.Name = this.series.Name + "_Supplemental";
|
||||
this.supplementalSeries.ChartArea = supplementalChartArea.Name;
|
||||
this.chartControl.Series.Add(supplementalSeries);
|
||||
|
||||
// Copy some attributes from the original chart area
|
||||
supplementalChartArea.BackColor = originalChartArea.BackColor;
|
||||
supplementalChartArea.BorderColor = originalChartArea.BorderColor;
|
||||
supplementalChartArea.BorderWidth = originalChartArea.BorderWidth;
|
||||
supplementalChartArea.ShadowOffset = originalChartArea.ShadowOffset;
|
||||
|
||||
// Copy some attributes from the original series
|
||||
this.supplementalSeries.ChartType = this.series.ChartType;
|
||||
this.supplementalSeries.Palette = this.series.Palette;
|
||||
this.supplementalSeries.ShadowOffset = this.series.ShadowOffset;
|
||||
this.supplementalSeries.BorderColor = this.series.BorderColor;
|
||||
this.supplementalSeries.BorderWidth = this.series.BorderWidth;
|
||||
this.supplementalSeries.IsValueShownAsLabel = this.series.IsValueShownAsLabel;
|
||||
this.supplementalSeries.LabelBackColor = this.series.LabelBackColor;
|
||||
this.supplementalSeries.LabelBorderColor = this.series.LabelBorderColor;
|
||||
this.supplementalSeries.LabelBorderWidth = this.series.LabelBorderWidth;
|
||||
this.supplementalSeries.LabelFormat = this.series.LabelFormat;
|
||||
this.supplementalSeries.Font = this.series.Font;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the "collected" pie slice data point by re moving and accumulating all
|
||||
/// the values of the data points which values are less then specified percentage.
|
||||
/// </summary>
|
||||
/// <returns>True if collected pie slice was created.</returns>
|
||||
private bool CreateCollectedPie()
|
||||
{
|
||||
// Create supplemental series
|
||||
this.supplementalSeries = new Series();
|
||||
|
||||
// Calculate total vale of all point in series
|
||||
double total = 0.0;
|
||||
foreach(DataPoint dataPoint in this.series.Points)
|
||||
{
|
||||
total += Math.Abs(dataPoint.YValues[0]);
|
||||
}
|
||||
|
||||
// Count how many data points will be presented as collected
|
||||
double minValue = total / 100.0 * this.CollectedPercentage;
|
||||
int collectedPointsCount = 0;
|
||||
for(int index = 0; index < this.series.Points.Count; index++)
|
||||
{
|
||||
double pointValue = Math.Abs(this.series.Points[index].YValues[0]);
|
||||
if(pointValue <= minValue)
|
||||
{
|
||||
++collectedPointsCount;
|
||||
}
|
||||
}
|
||||
|
||||
// Do not collect data points if one or less points left in the original series
|
||||
if( (this.series.Points.Count - collectedPointsCount) <= 1 ||
|
||||
collectedPointsCount <= 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add Collected data point into series before applying palette colors
|
||||
DataPoint colectedDataPoint = null;
|
||||
if(this.ShowCollectedDataAsOneSlice)
|
||||
{
|
||||
colectedDataPoint = new DataPoint(this.series);
|
||||
this.series.Points.Add(colectedDataPoint);
|
||||
}
|
||||
|
||||
|
||||
// Apply pallete colors to series to save same data point colors
|
||||
// in supplemental series.
|
||||
this.chartControl.ApplyPaletteColors();
|
||||
foreach(DataPoint dataPoint in this.series.Points)
|
||||
{
|
||||
// Setting data point color to itself will clear the internal flag which
|
||||
// indicates that point color should be taken from the palette again when
|
||||
// control is rendered next time.
|
||||
dataPoint.Color = dataPoint.Color;
|
||||
}
|
||||
|
||||
// Remove points which value is less than specified percentage from total
|
||||
double collectedValue = 0.0;
|
||||
for(int index = 0; index < this.series.Points.Count; index++)
|
||||
{
|
||||
double pointValue = Math.Abs(this.series.Points[index].YValues[0]);
|
||||
if(pointValue <= minValue &&
|
||||
this.series.Points[index] != colectedDataPoint)
|
||||
{
|
||||
// Add point value to the collected value
|
||||
collectedValue += pointValue;
|
||||
|
||||
// Add point to supplemental series
|
||||
this.supplementalSeries.Points.Add(this.series.Points[index].Clone());
|
||||
|
||||
// Remove point from the series
|
||||
this.series.Points.RemoveAt(index);
|
||||
--index;
|
||||
}
|
||||
}
|
||||
|
||||
// Add all collected data points at the end of the series
|
||||
if(!ShowCollectedDataAsOneSlice)
|
||||
{
|
||||
foreach(DataPoint dataPoint in this.supplementalSeries.Points)
|
||||
{
|
||||
DataPoint dataPointCollected = dataPoint.Clone();
|
||||
dataPoint.IsVisibleInLegend = false;
|
||||
this.series.Points.Add(dataPointCollected);
|
||||
|
||||
// Disable labels in collected slices
|
||||
dataPointCollected.Label = String.Empty;
|
||||
dataPointCollected.LegendText = dataPointCollected.AxisLabel;
|
||||
dataPointCollected.AxisLabel = String.Empty;
|
||||
dataPointCollected.IsValueShownAsLabel = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we need to add the "collected" data point
|
||||
if(collectedValue > 0.0)
|
||||
{
|
||||
// Set collected data point value and other attributes
|
||||
if(this.ShowCollectedDataAsOneSlice)
|
||||
{
|
||||
colectedDataPoint.YValues[0] = collectedValue;
|
||||
colectedDataPoint.Label = this.CollectedLabel;
|
||||
colectedDataPoint.IsVisibleInLegend = false;
|
||||
|
||||
// Note: Collected data point may be exploded
|
||||
//colectedDataPoint["Exploded"] = "true";
|
||||
}
|
||||
|
||||
// Calculate collected pie slice angle
|
||||
this.collectedPieSliceAngle = (float) ( (360f / 100f) * (collectedValue / (total / 100) ) );
|
||||
|
||||
// Adjust the Pie chart start angle, so that the middle of the
|
||||
// collected slice looks directly at 3 o'clock.
|
||||
int startAngle = (int)Math.Round(this.collectedPieSliceAngle / 2.0);
|
||||
this.series["PieStartAngle"] = startAngle.ToString();
|
||||
|
||||
return true;
|
||||
}
|
||||
else if(colectedDataPoint != null)
|
||||
{
|
||||
// Remove collected data point
|
||||
this.series.Points.Remove(colectedDataPoint);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Chart post paint event handler.
|
||||
/// Used to draw the "connection" lines between the original and supplemental pies.
|
||||
/// </summary>
|
||||
/// <param name="sender">Event sender.</param>
|
||||
/// <param name="e">Event arguments.</param>
|
||||
private void chart_PostPaint(object sender, System.Web.UI.DataVisualization.Charting.ChartPaintEventArgs e)
|
||||
{
|
||||
if(sender is ChartArea)
|
||||
{
|
||||
ChartArea area = (ChartArea)sender;
|
||||
if(this.supplementalChartArea != null &&
|
||||
area.Name == this.supplementalChartArea.Name)
|
||||
{
|
||||
// Get position of the plotting areas in pixels
|
||||
RectangleF originalPosition = GetChartAreaPlottingPosition(this.originalChartArea, e.ChartGraphics);
|
||||
RectangleF supplementalPosition = GetChartAreaPlottingPosition(this.supplementalChartArea, e.ChartGraphics);
|
||||
|
||||
// Get coordinates of the "connection" lines
|
||||
PointF p1 = GetRotatedPlotAreaPoint(supplementalPosition, 325f);
|
||||
PointF p2 = GetRotatedPlotAreaPoint(supplementalPosition, 215f);
|
||||
PointF p3 = GetRotatedPlotAreaPoint(originalPosition, 90f - this.collectedPieSliceAngle / 2f);
|
||||
PointF p4 = GetRotatedPlotAreaPoint(originalPosition, 90f + this.collectedPieSliceAngle / 2f);
|
||||
|
||||
// Draw "connection lines"
|
||||
using( Pen pen = new Pen(this.ConnectionLinesColor, 1) )
|
||||
{
|
||||
e.ChartGraphics.Graphics.DrawLine(pen, p1, p3);
|
||||
e.ChartGraphics.Graphics.DrawLine(pen, p2, p4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method which calculates a point on the edje of the pie chart using
|
||||
/// specified angle.
|
||||
/// </summary>
|
||||
/// <param name="areaPosition">Chart are position in pixels.</param>
|
||||
/// <param name="angle">Point angle in degrees.</param>
|
||||
/// <returns>Point location in pixels.</returns>
|
||||
private PointF GetRotatedPlotAreaPoint(RectangleF areaPosition, float angle)
|
||||
{
|
||||
PointF[] points = new PointF[1];
|
||||
points[0] = new PointF(areaPosition.X + areaPosition.Width / 2f, areaPosition.Y);
|
||||
using( Matrix transformMatrix = new Matrix() )
|
||||
{
|
||||
transformMatrix.RotateAt(angle, new PointF(
|
||||
areaPosition.X + areaPosition.Width / 2f,
|
||||
areaPosition.Y + areaPosition.Height / 2f) );
|
||||
|
||||
transformMatrix.TransformPoints(points);
|
||||
}
|
||||
return points[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method which calculates chart area plotting position in pixels.
|
||||
/// </summary>
|
||||
/// <param name="area">Chart area to get the plotting area position.</param>
|
||||
/// <param name="chartGraphics">Chart graphics object.</param>
|
||||
/// <returns>Chart area ploting area position in pixels.</returns>
|
||||
private RectangleF GetChartAreaPlottingPosition(ChartArea area, ChartGraphics chartGraphics)
|
||||
{
|
||||
RectangleF plottingRect = area.Position.ToRectangleF();
|
||||
plottingRect.X += (area.Position.Width / 100F) * area.InnerPlotPosition.X;
|
||||
plottingRect.Y += (area.Position.Height / 100F) * area.InnerPlotPosition.Y;
|
||||
plottingRect.Width = (area.Position.Width / 100F) * area.InnerPlotPosition.Width;
|
||||
plottingRect.Height = (area.Position.Height / 100F) * area.InnerPlotPosition.Height;
|
||||
plottingRect = chartGraphics.GetAbsoluteRectangle(plottingRect);
|
||||
return plottingRect;
|
||||
}
|
||||
|
||||
#endregion // Methods
|
||||
}
|
||||
}
|
||||
BIN
MES_Manage/App_code/Utilities/PieCollectedDataHelper/vssver2.scc
Normal file
BIN
MES_Manage/App_code/Utilities/PieCollectedDataHelper/vssver2.scc
Normal file
Binary file not shown.
1053
MES_Manage/App_code/Utilities/SixSigma/SixSigma.cs
Normal file
1053
MES_Manage/App_code/Utilities/SixSigma/SixSigma.cs
Normal file
File diff suppressed because it is too large
Load Diff
0
MES_Manage/App_code/Utilities/SixSigma/_system~.ini
Normal file
0
MES_Manage/App_code/Utilities/SixSigma/_system~.ini
Normal file
BIN
MES_Manage/App_code/Utilities/SixSigma/vssver2.scc
Normal file
BIN
MES_Manage/App_code/Utilities/SixSigma/vssver2.scc
Normal file
Binary file not shown.
187
MES_Manage/App_code/Utilities/SpikeRemoval/SpikeRemoval.cs
Normal file
187
MES_Manage/App_code/Utilities/SpikeRemoval/SpikeRemoval.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
//=================================================================
|
||||
// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public class SpikeRemoval
|
||||
{
|
||||
#region Members
|
||||
private bool mySetCutoffLabels;
|
||||
private MarkerStyle myRemovedPointStyle;
|
||||
private float myMaximum;
|
||||
private float myMinimum;
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public bool SetCutoffLabels
|
||||
{
|
||||
get { return mySetCutoffLabels; }
|
||||
set { mySetCutoffLabels = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the style that is used for the marker of any deleted points.
|
||||
/// </summary>
|
||||
public MarkerStyle RemovedPointStyle
|
||||
{
|
||||
get { return myRemovedPointStyle; }
|
||||
set { myRemovedPointStyle = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains the maximum value of the data after it has had the spikes removed. This value also has
|
||||
/// the tolerance factored in.
|
||||
/// </summary>
|
||||
public float Maximum
|
||||
{
|
||||
get { return myMaximum; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains the minimum value of the data after it has had the spikes removed. This value also has
|
||||
/// the tolerance factored in.
|
||||
/// </summary>
|
||||
public float Minimum
|
||||
{
|
||||
get { return myMinimum; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
/// <summary>
|
||||
/// Default Constructor.
|
||||
/// </summary>
|
||||
public SpikeRemoval()
|
||||
{
|
||||
//Default removed point style to a diamond.
|
||||
myRemovedPointStyle = MarkerStyle.Diamond;
|
||||
|
||||
//Default labels to off.
|
||||
mySetCutoffLabels = false;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="dataseries">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.</param>
|
||||
/// <param name="range">The percentage range of data to be kept. Anything that lies outside of the range
|
||||
/// will be considered a spike.</param>
|
||||
/// <param name="tolerance">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.</param>
|
||||
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
|
||||
}
|
||||
}
|
||||
BIN
MES_Manage/App_code/Utilities/SpikeRemoval/vssver2.scc
Normal file
BIN
MES_Manage/App_code/Utilities/SpikeRemoval/vssver2.scc
Normal file
Binary file not shown.
0
MES_Manage/App_code/Utilities/_system~.ini
Normal file
0
MES_Manage/App_code/Utilities/_system~.ini
Normal file
Reference in New Issue
Block a user