代码有注释,在通用性方面进行了处理
可以指定极值,可以自定义分段,对样本数据分段比较灵活,
不填临界值时平均分段,不指定极值时样本数据中的最大值为极大值。极小值默认为0,但可以设置。
////// 提供获取统计图的相关方法 /// 适合有极值和无极值,无极值时采用样本内的极值 /// 除极小值包含在近右片外,其他临界值包含在近左片 /// public class StatisticsGraph { ////// 样本数据 /// private ListSamples = null; private int NumberOfSegments = 10;//段数 即得到的图线有10个柱 private double MaxSample = 0;//最大样本数据 private double MinSample = 0;//最小样本数据 private int MaxExtremum = 100;//样本极值 ,如统计学生成绩时 极值一般为100,即卷满分 private float[] Demarcation = new float[] { 10, 20, 30, 40, 50, 60, 70, 80, 90 };//限有极值时设置 /// /// 根据样本数据初始化 /// /// public StatisticsGraph(Listsamples) { this.Init(samples); } /// /// 通过指定分片段数和样本数据进行初始化 /// /// /// public StatisticsGraph(Listsamples, int numberOfSegments) { this.HasSpecifiedExtremum = false; this.Init(samples); this.NumberOfSegments = numberOfSegments; this.Demarcation = GetDemarcation(); } /// /// 通过指定分片段数、极大值和样本数据进行初始化 /// /// /// /// public StatisticsGraph(Listsamples, int numberOfSegments, int maxExtremum) { this.HasSpecifiedExtremum = true; this.MaxExtremum = maxExtremum; this.Init(samples); this.NumberOfSegments = numberOfSegments; this.Demarcation = GetDemarcation(); } /// /// 通过指定极大值、样本数据和自定义分段临界值进行初始化 /// /// /// /// public StatisticsGraph(Listsamples, float[] demarcation, int maxExtremum) { this.HasSpecifiedExtremum = true; this.Init(samples); this.MaxExtremum = maxExtremum; this.Demarcation = demarcation; this.NumberOfSegments = demarcation.Count(); } public int MinExtremum = 0;//最小极值默认为0 private void Init(List samples) { this.Samples = samples; this.MaxSample = Samples.Max(); this.MinSample = Samples.Min(); } /// /// 是否指定了极大值 /// private bool _hasSpecifiedExtremum = true; ////// 是否已经指定了极大值 /// public bool HasSpecifiedExtremum { get { return _hasSpecifiedExtremum; } private set { _hasSpecifiedExtremum = value; } } ////// 获取每个分片的临界点 /// ///private float[] GetDemarcation() { if (NumberOfSegments <= 1) { return null; } float[] result = new float[NumberOfSegments - 1]; if (HasSpecifiedExtremum == false)//未指定极大值时 样本中最大值为统计范围内极大值 { MaxExtremum = Convert.ToInt32(Math.Floor(MaxSample)) + 1; MinExtremum = Convert.ToInt32(Math.Floor(MinSample)) - 1; } int span = MaxExtremum - MinExtremum;//极值跨度 float segSpan = span * 1f / NumberOfSegments;//每个片段的跨度 for (int i = 0; i < NumberOfSegments - 1; i++) { result[i] = MinExtremum + segSpan * (i + 1); } return result; } /// /// 获取各个分片的样本数量 /// 结果《分片的极右值,该片的样本数》 /// 分片总数为临界点数+1个 /// ///private Dictionary GetSampleNumbersOfPerSegment() { Dictionary result = new Dictionary (); List segRightValue = new List ();//分片极右值 foreach (var item in Demarcation) { segRightValue.Add(item); } segRightValue.Add(MaxExtremum); for (int i = 0; i < segRightValue.Count; i++) { int value = 0; foreach (float m in Samples)//计算每个片段的样本数 { if (i == 0) { if (m <= segRightValue[i]) { value += 1; } } else { if (m <= segRightValue[i] && m > segRightValue[i - 1]) { value += 1; } } } result.Add(segRightValue[i], value); } return result; } /// /// 获取每个分片的左上顶点坐标 /// /// 最大利用宽度 /// 最小利用宽度 ///private List GetTopLeftPointFOfSegment(PointF bottomLeft, float UsableWidth, float UsableHeight, out float widthPerSeg, out float unitHeight, out Dictionary SampleNumbersOfPerSegment) { List result = new List (); SampleNumbersOfPerSegment = GetSampleNumbersOfPerSegment();//获取每个片段占有的样本数 int maxSampleNumbersOfSegment = SampleNumbersOfPerSegment.Max(x => x.Value);//所有分片中 最大分片样本数 widthPerSeg = UsableWidth * 1f / (SampleNumbersOfPerSegment.Count * 2 + 1);//每个分片的宽度 分片之间还有空白分片 unitHeight = UsableHeight * 1f / maxSampleNumbersOfSegment;//充分利用高度的情况下 单位样本数所占高度 for (int i = 0; i < SampleNumbersOfPerSegment.Count; i++) { PointF pf = new PointF(); pf.X = bottomLeft.X + (i * 2 + 1) * widthPerSeg;//每个片段的左边X坐标 pf.Y = bottomLeft.Y - SampleNumbersOfPerSegment.ElementAt(i).Value * unitHeight;//每个片段上边Y坐标 result.Add(pf); } return result; } /// /// 获取每个分片的 条形数据(包括:左上角坐标,高度和宽度) /// /// /// ///private RectangleF[] GetRectangleFs(PointF bottomLeft, float UsableWidth, float UsableHeight, out Dictionary SampleNumbersOfPerSegment) { float widthPerSeg = 0;//每个片段的宽度 float unitHeight = 0;//单位样本数据在Y轴上表示需要的高度 //每个片段的左上角坐标 List pfs = GetTopLeftPointFOfSegment(bottomLeft, UsableWidth, UsableHeight, out widthPerSeg, out unitHeight, out SampleNumbersOfPerSegment); RectangleF[] RFs = new RectangleF[pfs.Count]; for (int i = 0; i < pfs.Count; i++) { //通过计算宽度和高度 结合左上角坐标 以准确描述每个矩形的大小和位置 RFs[i] = new RectangleF(pfs[i], new SizeF(widthPerSeg, SampleNumbersOfPerSegment.ElementAt(i).Value * unitHeight)); } return RFs; } /// /// 获得10段柱状图 /// 横轴 分数段;纵轴 该分数段的 人数 /// /// /// /// /// ///public Bitmap GetBargraph(int width, int heigh, string XunitName, string YunitName, string familyName = "宋体") { Font font = new Font(familyName, 10); Bitmap bitmap = new Bitmap(width, heigh); Graphics gdi = Graphics.FromImage(bitmap); //用白色填充整个图片,因为默认是黑色 gdi.Clear(Color.White); //抗锯齿 gdi.SmoothingMode = SmoothingMode.HighQuality; //高质量的文字 gdi.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; //像素均偏移0.5个单位,以消除锯齿 gdi.PixelOffsetMode = PixelOffsetMode.Half; int margin = 30;//坐标轴与边框的距离 int padding = 20;//实际表示内容区域 与坐标轴右和上边的距离 PointF bottomLeft = new PointF(margin, heigh - margin);//坐标原点 PointF topLeft = new PointF(bottomLeft.X, margin);//Y轴最上顶端坐标 PointF topLeft_bl = new PointF(topLeft.X - 6, topLeft.Y + 10);//Y轴箭头左下端坐标 PointF topLeft_br = new PointF(topLeft.X + 6, topLeft_bl.Y);//Y轴箭头右下端坐标 gdi.DrawLines(Pens.Black, new PointF[] { topLeft_bl, topLeft, topLeft_br });//画坐标轴Y轴箭头 gdi.DrawString(string.Format("({0})", YunitName), font, Brushes.Black, topLeft_bl.X - 20, topLeft.Y - 14);//在Y轴箭头左下角写上 Y轴表示的单位 PointF bottomRight = new PointF(width - margin, bottomLeft.Y);//X轴最右端坐标 PointF bottomRight_lt = new PointF(bottomRight.X - 10, bottomRight.Y - 6);//X轴箭头左上端坐标 PointF bottomRight_lb = new PointF(bottomRight_lt.X, bottomRight.Y + 6);//X轴箭头右下端坐标 gdi.DrawLines(Pens.Black, new PointF[] { bottomRight_lt, bottomRight, bottomRight_lb });//画坐标轴X轴箭头 gdi.DrawString(string.Format("({0})", XunitName), font, Brushes.Black, bottomRight_lt.X - 3, bottomRight_lt.Y + 13);//在X轴箭头的下方 写上X轴表示的单位 gdi.DrawLines(Pens.Black, new PointF[] { topLeft, bottomLeft, bottomRight });//画坐标轴 float usableHeight = bottomLeft.Y - margin - padding;//内容区高度 float usableWidth = width - margin * 2 - padding;//内容区宽度 Dictionary SampleNumbersOfPerSegment = null;//各个片段的描述数据 RectangleF[] RFs = GetRectangleFs(bottomLeft, usableWidth, usableHeight, out SampleNumbersOfPerSegment);//获取条形图位置数据 gdi.FillRectangles(new SolidBrush(Color.FromArgb(70, 161, 185)), RFs);//填充柱形 //标上坐标轴上的数据 //X轴上写的内容 string Xcontent = string.Empty; //条形顶上方写的内容 string Ycontent = string.Empty; for (int i = 0; i < SampleNumbersOfPerSegment.Count; i++) { if (i == 0) Xcontent = "X<=" + SampleNumbersOfPerSegment.ElementAt(i).Key.ToString("F0"); else { Xcontent = string.Format("{0} <={1}", SampleNumbersOfPerSegment.ElementAt(i - 1).Key.ToString("F0"), SampleNumbersOfPerSegment.ElementAt(i).Key.ToString("F0")); } gdi.DrawString(Xcontent, font, Brushes.Black, RFs[i].X - 8, bottomLeft.Y + 5); //写上X轴上的数据 Ycontent = SampleNumbersOfPerSegment.ElementAt(i).Value.ToString(); gdi.DrawString(Ycontent, font, Brushes.Black, RFs[i].X + 3, RFs[i].Y - 13); //写上Y轴上的数据 } return bitmap; } }
StatisticsGraph sg = new StatisticsGraph(scores.ConvertAll(x => (float)x),10,100); System.Diagnostics.Stopwatch w = new System.Diagnostics.Stopwatch(); w.Start(); Bitmap bitmap = sg.GetBargraph(800, 480,"分","人数"); bitmap.Save("tt.jpg", System.Drawing.Imaging.ImageFormat.Jpeg); Debug.WriteLine(w.Elapsed);
一张图片大约耗时20毫秒。
测试数据得到的柱形图: