一、绘制正态分布曲线
-
定义绘图所需的控件和图形对象:
- 创建一个
PictureBox
控件用于显示正态分布图。 - 使用
Graphics
对象进行绘图操作。
- 创建一个
-
计算正态分布曲线的关键参数:
- 确定正态分布的均值(
stand
或median
,根据具体模式而定)、标准差(sd
)等参数。 - 根据给定的上下偏差(如
alarm1
、warm1
、alarm2
、warm2
等)计算出各个区间的范围。
- 确定正态分布的均值(
-
绘制正态分布曲线:
- 使用贝尔赛拉画法(
DrawBezier
方法)绘制正态曲线。在曲线上确定几个关键的点,如Point p1
到p7
,然后通过这些点绘制平滑的曲线。 - 绘制横纵坐标轴,使用不同颜色和线型绘制分界线以及各个区间的坐标值。
- 使用贝尔赛拉画法(
-
显示相关统计信息:
- 根据计算得到的平均值、标准差、CPK 等参数,在图上合适的位置显示这些统计信息。可以使用
g.DrawString
方法在特定位置绘制文本。
- 根据计算得到的平均值、标准差、CPK 等参数,在图上合适的位置显示这些统计信息。可以使用
二、填充直方图
-
准备工作:
- 创建一个
Bitmap
对象,将其作为直方图的绘制背景。 - 获取图形对象
g
,以便在Bitmap
上进行绘图操作。
- 创建一个
-
绘制直方图:
- 根据数据在不同区间的分布情况,计算每个区间的高度。
- 使用不同颜色的
SolidBrush
填充各个区间的直方图。
-
显示区间比例或数值:
- 根据设置的显示模式(按比例显示或按数值显示),在每个区间的上方绘制对应的比例或数值信息。
三、中位线和设定线的绘制
-
中位线算法:
- 如果是偶数个数据,取中间两个数的平均值作为中位数;如果是奇数个数据,直接取中间的数作为中位数。
-
绘制中位线:
- 根据中位数的位置,确定在正态分布图上的横坐标值。
- 使用特定颜色和线型的
Pen
对象绘制中位线。
-
绘制设定线(如果有设定值):
- 类似中位线的绘制方法,根据设定值确定横坐标值,然后绘制设定线。
-
绘制 3 倍标准偏差线:
- 计算出均值加减 3 倍标准差的位置,确定在正态分布图上的横坐标值。
- 使用特定颜色和线型的
Pen
对象绘制两条 3 倍标准偏差线。
四、数据处理和交互功能
-
数据添加方法:
- 外部可以调用
Add
方法向数据集合中添加新的数据点。这个方法会将数据添加到DATA
列表、Sort
列表以及其他相关的数据集合中,并自动进行统计计算和数据归档操作。同时,会将数据添加到DataGridView
中显示,并根据数据是否超出设定范围设置相应行的颜色。
- 外部可以调用
-
初始化和清空方法:
InitControl
方法用于初始化各个控件,包括布置底层PictureBox
、标题标签、正态分布模块、数据模块、打点模块等。同时设置各个控件的属性,如大小、位置、颜色、边框风格等,并添加必要的事件处理程序。Clear
方法用于清空该控件的所有属性和数据,包括重置平均值、标准差、CPK 等统计参数,清空数据表格,清除图形绘制等操作。
-
批次和参数变化方法:
Batch_ChangeOrInit
方法用于处理批次变化或初始化,更新批次显示标签的文本内容。ParaName_ChangeOrInit
方法用于处理参数变化或初始化,更新参数显示标签的文本内容。
-
数据归档方法:
Data_Archiving
方法用于在添加新数据时,根据数据值所在的区间进行数据归档,更新各个区间的计数。Data_Archiving_His
方法用于对历史数据进行自动归档,重新计算各个区间的数据分布情况。
-
处理滚动条:
HScrollBanding_Show
方法用于根据数据点的数量动态显示或隐藏滚动条。当数据点数量较多时,计算滚动条的最大值、最小值和步长等属性,并根据需要显示滚动条。
-
曲线坐标和背景绘制:
DrowLineAxes
方法用于绘制曲线坐标以及背景。创建一个Bitmap
对象作为曲线背景,使用Graphics
对象在其上绘制 X、Y 轴、区域填充、中心线、标识线值等内容。同时,添加放大和缩小功能的标签,并为其添加鼠标点击事件处理程序。
-
绘制曲线:
DrawLines
方法根据现有数据绘制曲线。首先获取每个数据点的 Y 轴坐标值,然后使用特定颜色和线型的Pen
对象绘制曲线,并根据数据点是否有料信号在曲线上绘制不同颜色的点。
-
获取 Y 轴坐标值:
GetYAxis
方法用于计算每个数据点在 Y 轴上的坐标值。根据数据值所在的区间,通过一定的比例计算出对应的 Y 轴位置,并将超出特定范围的数据点标记为超出区间。
-
事件处理程序:
- 处理鼠标在正态分布底子(
panel_back
)上的按下和抬起事件,用于显示提示信息和注销提示信息。 - 处理标签的鼠标双击事件,用于显示或隐藏数据表格。
- 处理导出标签的鼠标点击事件,用于将数据导出为 Excel 文件或 TXT 文件。
- 处理放大和缩小标签的鼠标点击事件,用于调整曲线的显示比例。
- 处理曲线底子(
panel_line
)的鼠标按下和抬起事件,用于显示数据点的详细信息。 - 处理滚动条的
ValueChanged
事件,用于在滚动条值改变时重新绘制曲线。
- 处理鼠标在正态分布底子(
五、正态分布函数库
-
平均值计算:
Avg_RealTime
方法用于计算数据的平均值。遍历数据集合,只对有料信号为真的数据进行求和,并计算平均值。
-
标准偏差计算:
SD_RealTime
方法用于计算数据的标准偏差。遍历数据集合,计算每个数据点与平均值的平方差之和,然后根据数据点数量计算标准偏差。
-
CPK 计算:
Cpk_RealTime
方法用于计算 CPK(过程能力指数)。根据给定的上下偏差和计算得到的平均值、标准偏差,按照特定公式计算 CPK 值。
-
偏度值计算:
Skewness_Calculete
方法用于计算数据的偏度值。根据公式计算每个数据点与平均值的三次方除以标准偏差的三次方之和,然后通过一定的比例计算偏度值。
-
峰度值计算:
Kurtosis_Calculete
方法用于计算数据的峰度值。根据公式计算每个数据点与平均值的四次方除以标准偏差的四次方之和,然后通过一定的比例计算峰度值。
-
最大值计算:
Max_Calculete
方法用于计算数据的最大值。遍历数据集合,只对有料信号为真的数据进行比较,找出最大值。
-
最小值计算:
Min_Calculete
方法用于计算数据的最小值。遍历数据集合,只对有料信号为真的数据进行比较,找出最小值。
-
合格率计算:
Hgl_Calculete
方法用于计算数据的合格率。根据落在特定区间的数据点数量与总数据点数量的比例计算合格率。
-
数学期望计算:
MathExpect_Calculete
方法用于计算数据的数学期望。通过统计数据集中每个数据值出现的次数,按照特定公式计算数学期望。
-
U 值计算:
U_Calculete
方法用于计算 U 值。当数据点数量大于 30 时,根据公式计算 U 值,即均值与中位数之差除以标准偏差除以数据点数量的平方根。
通过以上步骤,可以在 C# 中实现一个具有数据添加、统计计算、图形绘制和交互功能的正态分布图。可以根据具体需求对代码进行调整和扩展,以满足不同的应用场景。
代码:
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Data;
using System.Windows.Forms;
using System.Collections;
using System.IO;
using System.Threading;
namespace Distribution
{
public class Normal_Distribution
{
Graphics g;
Graphics realtimeline;
public PictureBox panel_all;//全底子
public PictureBox panel_back;//正态分布底子
private PictureBox panel_line;//曲线底子
private PictureBox panel_source;//数据底子
PictureBox pic_text;//提示框
PictureBox linebox;//提示框
Label label;
Label label_excel;
Label label_line;
Label magnify;//放大曲线
Label dwindle;//缩小曲线
DataGridView Grid;//数据显示框
delegate void settextcallback();//委托
delegate void setpanel_line();
private bool isRealTime_Line = false;//是否实时
private string batch;//所显示的批次
private string para_name;//所显示参数名称
private string seri_set = "";//参数设置显示SD、AVG、CPK
private int nb_h;//正态直方图的高
private int nb_w;//正态直方图的宽
private int nb_x;//正态直方图在实例中的位置X
private int nb_y;//正态直方图在实例中的位置Y
private Color nb_backcolor = Color.Snow;//背景颜色
//private BorderStyle nb_bordstyle;//边框风格
/// <summary>
/// 上上偏差
/// </summary>
private double alarm1;
/// <summary>
/// 上偏差
/// </summary>
private double warm1;
/// <summary>
/// 中间值
/// </summary>
private double stand;
/// <summary>
/// 下偏差
/// </summary>
private double alarm2;
/// <summary>
/// 下下偏差
/// </summary>
private double warm2;
/// <summary>
/// 中位数
/// </summary>
private double median;
private double set;//设定值
private double c1;//置信上区间
private double c2;//置信下区间
private int find_dot;//查点
private double modulus = 1;//放大或缩小系数
private int mode = 2;//正态分布方式 1为设定值为中间值 2为中位数为中间值
private int position = 2;//2 nb在左上 1 nb在右上
public Label Label_Batch = new Label();
public Label Label_Paraname = new Label();
public Label Label_Hgl = new Label();
public Label Label_Max = new Label();
public Label Label_Min = new Label();
//CPK SD AVG MAX MIN HGL等
static private double cpk;//CPK
static private double sd;//标准偏差值
static private double avg;//平均值
static private double value_sum;//数和
static private double value_p;//平方和
static private double max;//最大值
static private double min;//最小值
static private double hgl;//合格率
private double skewness;//偏度值
private double kurtosis;//峰度值
private double mathexpect;//数学期望
private double u;//正态分布U值
System.Threading.Timer timer; //线程
//队列 用于存放数据VALUE
public List<double> DATA = new List<double>();
//排序后数组 用于中位数算法
public List<double> Sort = new List<double>();
//用于存放时间
public ArrayList TIME = new ArrayList();
//用于存放AVG CPK SD SKEWNESS KURTOSIS
public List<double> AVGConter = new List<double>();
public List<double> CPKConter = new List<double>();
public List<double> SDConter = new List<double>();
public List<double> SKEWNESSConter = new List<double>();
public List<double> KURTOSISConter = new List<double>();
//动态数组
public ArrayList Points = new ArrayList();
// 画分的六个区间 以下是六个区间的数目 动态中位线区域 是变化的
private float acount;//大于ALARM1的数值
private float bcount;//介于ALARM1和WARM1之间的数值
private float ccount;//介于WARM1和STAND之间的数值
private float dcount;//介于STAND和WARM2之间的数值
private float ecount;//介于WARM2和ALARM2之间的数值
private float fcount;//小于ALARM2的数值
private int dots = 0;//临时记录实时曲线图的上次打点的数目
private int sign = 1;//标识数值和比例 1比例2数值
#region Line 曲线
Point[] points;//点集
List<Point> points_his = new List<Point>();
List<bool> exceed_points = new List<bool>();
/// <summary>
/// 有料信号的标记集合
/// </summary>
List<Nullable<bool>> haveToba = new List<bool?>();
Point pointstart;//坐标轴起始点
double point_r; //点的半径
HScrollBar hScrollBar; //滚动条
#endregion
#region 属性
public bool IsRealTime_Line
{
set
{
isRealTime_Line = value;
}
}
public double Median
{
get
{
return median;
}
}
public double Set
{
get
{
return set;
}
set
{
set = value;
}
}
public double C1
{
get
{
return c1;
}
set
{
c1 = value;
}
}
public double C2
{
get
{
return c2;
}
set
{
c2 = value;
}
}
public int Mode
{
get
{
return mode;
}
set
{
mode = value;
}
}
public int Position
{
get
{
return position;
}
set
{
position = value;
}
}
public double Avg
{
//只容许读取 不允许填写
get
{
return avg;
}
}
public double SD
{
//只容许读取 不允许填写
get
{
return sd;
}
}
public int Sign
{
get
{
return sign;
}
set
{
sign = value;
}
}
public string Seri_Set
{
get
{
return seri_set;
}
set
{
seri_set = value;
}
}
public string Batch
{
get
{
return batch;
}
set
{
batch = value;
}
}
public string Para_Name
{
get
{
return para_name;
}
set
{
para_name = value;
}
}
public double Alarm1
{
get
{
return alarm1;
}
//set
//{
// alarm1 = value;
//}
}
public double Warm1
{
get
{
return warm1;
}
//set
//{
// warm1 = value;
//}
}
public double Stand
{
get
{
return stand;
}
set
{
stand = value;
}
}
public double Alarm2
{
get
{
return alarm2;
}
//set
//{
// alarm2 = value;
//}
}
public double Warm2
{
get
{
return warm2;
}
//set
//{
// warm2 = value;
//}
}
public float Acount
{
get
{
return acount;
}
//set
//{
// acount = value;
//}
}
public float Bcount
{
get
{
return bcount;
}
//set
//{
// bcount = value;
//}
}
public float Ccount
{
get
{
return ccount;
}
//set
//{
// ccount = value;
//}
}
public float Dcount
{
get
{
return dcount;
}
//set
//{
// dcount = value;
//}
}
public float Ecount
{
get
{
return ecount;
}
//set
//{
// ecount = value;
//}
}
public float Fcount
{
get
{
return fcount;
}
//set
//{
// fcount = value;
//}
}
public int Dots
{
get
{
return dots;
}
}
public int Nb_Height
{
get
{
return nb_h;
}
set
{
nb_h = value;
}
}
public int Nb_Weight
{
get
{
return nb_w;
}
set
{
nb_w = value;
}
}
public int Nb_X
{
get
{
return nb_x;
}
set
{
nb_x = value;
}
}
public int Nb_Y
{
get
{
return nb_y;
}
set
{
nb_y = value;
}
}
public Color Nb_Backcolor
{
get
{
return nb_backcolor;
}
set
{
nb_backcolor = value;
}
}
public BorderStyle Nb_Bordstyle
{
get
{
return Nb_Bordstyle;
}
set
{
Nb_Bordstyle = value;
}
}
#endregion
/// <summary>
/// 数据添加点源 外部接口 用于数据录入和自动统计
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
public void Add(double data, string time, Nullable<bool> haveToba)
{
try
{
if (mode == 1)
stand = set;
if (mode == 2)
stand = median;
//warm1 = stand + c1;
//warm2 = stand - c2;
//alarm1 = warm1 + 1;
//alarm2 = warm2 - 1;
warm1 = Math.Round(stand + c1/3,2);
warm2 = Math.Round(stand - c2/3,2);
alarm1 = Math.Round(warm1 + c1 / 3,2);
alarm2 = Math.Round(warm2 - c2 / 3,2);
DATA.Add(data);
Sort.Add(data);
TIME.Add(time);
this.haveToba.Add(haveToba);
AVGConter.Add(avg);
CPKConter.Add(cpk);
SDConter.Add(sd);
SKEWNESSConter.Add(skewness);
KURTOSISConter.Add(kurtosis);
Calculate_Median();
if (mode == 1)
if (haveToba == true)
{
Data_Archiving(data);
}
if (mode == 2)
Data_Archiving_His();
Grid.Rows.Add(new object[] { time, Helper.ConvertBoolToString(haveToba), data.ToString(), avg.ToString(), sd.ToString(), cpk.ToString(), skewness.ToString(), kurtosis.ToString() });
if (data>set+c1 ||data <set-c2)
Grid.Rows[DATA.Count-1].DefaultCellStyle.ForeColor = Color.Red;
}
catch
{ }
}
/// <summary>
/// 底子初始化 第一执行点 各个控件初始化
/// </summary>
public void InitControl()
{
//布置底层
panel_all = new PictureBox();
panel_all.Size = new Size(nb_w, nb_h);
panel_all.BackColor = nb_backcolor;
panel_all.Location = new Point(nb_x, nb_y);
panel_all.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
//布置标题BATCH 、ParaName
Label_Batch.Location = new Point(0, (int)this.panel_all.Width / 100);
Label_Paraname.Location = new Point(2 * Lab