作为嵌入式工程师在设计数字电源时需要将串口传上来的电压电流等数据通过图表方式实时显示在电脑上,刚开始使用第三方图表控件msChart,这个控件功能还是蛮强大的,该要的效果一般都能实现,但在数字电源开发这种应用场合该控件就不太合适了,因为以50Hz的交流电波形举例,1秒有100个周期变化再乖以每周期至少100个点,那1秒内就将有10000个数据需要在图表上显示出来,用第三方图表控件没有哪个有这么快的刷新速度。
怎么解决该问题呢?最好的方式是通过GDI绘制曲线图,因为只有自已绘制才能最大效率利用CPU来作图,而不是像第三方控件为一些无关紧要的任务浪费时间。
范例工程下载地址(旧版本):http://download.csdn.net/detail/iejinshan/9133417
范例工程下载地址(新版本):https://download.csdn.net/download/iejinshan/10467169
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;
public class GDIChart
{
Bitmap bmp;
Graphics graphics;
PictureBox pictureBox;
DataGridView GridView1, GridView2;
HScrollBar hScrollBar;
public double Xoffset, Yoffset,Ymax,Ymin;
int YMoveUpValue; //向上偏移量
int XMoveRightValue;//向右偏移量
//double Xp;
int NowID, RowSum, NowRow;
//int Xwidth = 0;
//int DotSumCopy;
Point pCopy;
string[] FieldName;
//double[] mLabel;
//int[] yLabel;
int[,] XY;
Point p;//得到鼠标xy位置
int Help;
public GDIChart(ref PictureBox bx, ref DataGridView dataGridView1, ref DataGridView dataGridView2,ref HScrollBar hscrollBar)
{
YMoveUpValue = 16;
pictureBox = bx;
GridView1 = dataGridView1;
GridView2 = dataGridView2;
hScrollBar = hscrollBar;
FieldName = new string[GridView1.Rows.Count];
//mLabel = new double[GridView1.Rows.Count];
//yLabel = new int[GridView1.Rows.Count];
XY = new int[GridView1.Rows.Count,2];
for (int i = 0; i < FieldName.Length; i++)
{
FieldName[i] = GridView1.Rows[i].HeaderCell.Value.ToString();
}
Ymax = 3600;
Ymin = 0;
Help = 0;
pictureBox.Resize += new System.EventHandler(EventHandlerBmpInit);//pictureBox大小变化时产生事件重置Bitmap对象大小
pictureBox.MouseDown += new MouseEventHandler(pictureBox_MouseDown);
pictureBox.MouseWheel += new MouseEventHandler(PictureBox_MouseWheel);
pictureBox.KeyDown += new KeyEventHandler(pictureBox_KeyDown);
//pictureBox.MouseEnter += new EventHandler(pictureBox_MouseEnter);
//pictureBox.MouseLeave += new EventHandler(pictureBox_MouseLeave);
}
private bool 打印字符_带背景色(ref Graphics g, ref Bitmap bmp,Font font, int x, int y, string str)
{
//Font font = new Font("宋体", 9f);
PointF pointF = new PointF(x, y);
SizeF sizeF = g.MeasureString(str, font);
g.FillRectangle(Brushes.PowderBlue, new RectangleF(pointF, sizeF));
g.DrawString(str, font, Brushes.Black, pointF);
return true;
}
//去除小数点后多余数值
private bool RemoveDecimal(double In, ref double Out)
{
try
{
if (In == 0) { return true; }
if (In >= 10 || In <= -10)
{//In有整数
Out = (double)((int)In);
Out += 0;
}
else
{//In无整数
int cnt = 1;
if (In > 0)
{//正数
while (In < 10)
{
cnt *= 10;
In *= 10;
}
}
else
{//负数
while (In < -10)
{
cnt *= 10;
In *= 10;
}
}
Out = (double)(int)(In+0) / cnt;
}
return true;
}
catch (Exception ex) { sys.ToolWindows.LogAdd("RemoveDecimal " + ex.Message.ToString()); }
return false;
}
//初始化参数
private bool Init(ref DataTable dt,int DotSum,double y1,double y2)
{
try
{
Xoffset = (double)(bmp.Width - XMoveRightValue) / DotSum; //计算出X轴每个像素值
Yoffset = (y2 - y1) / (double)(bmp.Height - YMoveUpValue); //计算出Y轴每个像素值
RemoveDecimal(Xoffset, ref Xoffset);//去除小数点后多余数值
RemoveDecimal(Yoffset, ref Yoffset);//去除小数点后多余数值
return true;
}
catch (Exception ex) { sys.ToolWindows.LogAdd("ChartInit " + ex.Message.ToString()); }
return false;
}
//绘制曲线
private bool DrawingCurve(ref DataTable dt,DataGridView gridView,Color[] color,double Ystart)
{
try
{
Pen pen;
pen = new Pen(System.Drawing.Color.Red, 1);
int x1, y1, x2=0, y2,flag;
p = pictureBox.PointToClient(Control.MousePosition);//得到鼠标xy位置
p.X -= XMoveRightValue;
p.Y += YMoveUpValue;
graphics.TranslateTransform(XMoveRightValue, -YMoveUpValue);
for (int a = 0; a < gridView.Rows.Count;a++ )
{
if (Convert.ToBoolean(gridView.Rows[a].Cells["Enable"].Value) == true)
{
flag = 0;
pen.Color = color[a];
for (int b = 0; b < dt.Rows.Count-1;b++)
{
double value0 = Convert.ToDouble(dt.Rows[b + 0][a + 2]);
double value1 = Convert.ToDouble(dt.Rows[b + 1][a + 2]);
value0 -= Ystart;
value1 -= Ystart;
value0 += Convert.ToDouble(gridView.Rows[a].Cells["Move"].Value);
value1 += Convert.ToDouble(gridView.Rows[a].Cells["Move"].Value);
value0 *= Convert.ToDouble(gridView.Rows[a].Cells["Zoom"].Value);
value1 *= Convert.ToDouble(gridView.Rows[a].Cells["Zoom"].Value);
x1 = (int)((b + 0) * Xoffset);
x2 = (int)((b + 1) * Xoffset);
y1 = bmp.Height - (int)(value0 / Yoffset);
y2 = bmp.Height - (int)(value1 / Yoffset);
graphics.DrawLine(pen, x1, y1, x2, y2);
if (flag == 0 && x1 >= p.X)
{
flag = 1;
XY[a, 0] = x1;
XY[a, 1] = y1;
}
}
}
}
graphics.TranslateTransform(-XMoveRightValue, YMoveUpValue);
return true;
}
catch (Exception ex) { sys.ToolWindows.LogAdd("DrawingCurve " + ex.Message.ToString()); }
return false;
}
//绘制标签
private bool DrawingLabel(ref DataTable dt,Color[] color)
{
try
{
if (p.X >= 0 && p.Y >= YMoveUpValue && p.X <= pictureBox.Width - XMoveRightValue && p.Y <= pictureBox.Height)
{
int x1, y1, x2, y2;
Pen pen = new Pen(Color.Red, 1);
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
graphics.TranslateTransform(XMoveRightValue, -YMoveUpValue);
x1 = p.X;
x2 = p.X;
y1 = 3;
y2 = pictureBox.Height;
graphics.DrawLine(pen, x1, y1, x2, y2);
x1 = 0;
x2 = pictureBox.Width;
y1 = p.Y;
y2 = p.Y;
graphics.DrawLine(pen, x1, y1, x2, y2);
pen.Width = 2;
int row = (int)((double)(p.X) / Xoffset) + 1;
if (row >= 0 && row < dt.Rows.Count)
{
NowRow = row;
RowSum = dt.Rows.Count;
NowID = Convert.ToInt32(dt.Rows[row][0]);
DataTable SetTable = (GridView1.DataSource as DataTable);
if (pCopy != p)
{
for (int i = 0; i < dt.Columns.Count - 2; i++)
{
SetTable.Rows[i]["Value"] = Convert.ToDouble(dt.Rows[row][i + 2]);
SetTable.Rows[i]["ID"] = Convert.ToDouble(dt.Rows[row][0]);
SetTable.Rows[i]["Time"] = Convert.ToDateTime(dt.Rows[row][1]);
}
GridView1.Refresh();
}
string str;
Font font = new Font("宋体", 10);
int Height = font.Height / 2;
for (int i = 0; i < dt.Columns.Count - 2; i++)
{
if (Convert.ToBoolean(GridView1.Rows[i].Cells["Enable"].Value) == true)
{
pen.Color = color[i];
str = dt.Rows[row][i + 2].ToString();
x1 = XY[i, 0];
y1 = XY[i, 1];
x2 = XY[i, 0] + 30;
y2 = XY[i, 1];
if (y2 < 15 + YMoveUpValue + Height) { y2 += 15; } else { y2 -= 15; }
graphics.DrawLine(pen, x1, y1, x2, y2);
打印字符_带背景色(ref graphics, ref bmp, font, x2, y2-Height, SetTable.Rows[i][0].ToString() + "->" + str);
}
}
}
else
{
NowID = 0;
RowSum = 0;
NowRow = 0;
}
graphics.TranslateTransform(-XMoveRightValue, YMoveUpValue);
}
else
{
NowID = 0;
RowSum = 0;
NowRow = 0;
}
pCopy = p;
return true;
}
catch (Exception ex) { sys.ToolWindows.LogAdd("DrawingLabel " + ex.Message.ToString()); }
return false;
}
//绘制help
private bool DrawingHelp(double Ystart)
{
try
{
if (Help == 1)
{
p.Y = bmp.Height - p.Y;
p.X = p.X - XMoveRightValue;
string str = "";
//str += "Yoffset=" + Yoffset.ToString() + "\n";
//str += "Xoffset=" + Xoffset.ToString() + "\n";
str += "Dot=" + sys.DotSum.ToString() + "\n";
//str += "Width=" + (pictureBox.Width - XMoveRightValue).ToString() + "\n";
//str += "Row=" + sys.ShowDataTable.Rows.Count.ToString() + "\n";
str += "Y=" + ((double)p.Y * Yoffset + Ystart).ToString() + "\n";
//str += "X=" + p.X / Xoffset + "\n";
打印字符_带背景色(ref graphics, ref bmp,new Font("宋体", 10), XMoveRightValue + 16, 0, str);
}
return true;
}
catch (Exception ex) { sys.ToolWindows.LogAdd("DrawingHelp " + ex.Message.ToString()); }
return false;
}
//绘制刻度表
private bool DrawingScale(double Ystart, ref DataTable dt)
{
try
{
int x1, y1, x2, y2;
SizeF sf = new SizeF();
//计算最大字符长度
XMoveRightValue = 0;
for (int y = YMoveUpValue; y < bmp.Height - 5; )
{
string str = (Yoffset * (y - YMoveUpValue) + Ystart).ToString();
sf = graphics.MeasureString(str, new Font("宋体", 10));
if (XMoveRightValue < sf.Width)
{
XMoveRightValue = (int)sf.Width;
}
y += 50;
}
//画Y轴刻度
x1 = XMoveRightValue;
y1 = bmp.Height - YMoveUpValue;
x2 = XMoveRightValue;
y2 = 2;
graphics.DrawLine(new Pen(Color.Black, 1), x1, y1, x2, y2);//画长线
for (int y = YMoveUpValue; y < bmp.Height - 5; )
{
if ((y - YMoveUpValue) % 50 == 0)
{
x1 = XMoveRightValue;
y1 = bmp.Height - y;
x2 = XMoveRightValue + 10;
y2 = bmp.Height - y;
graphics.DrawLine(new Pen(Color.Black, 1), x1, y1, x2, y2);//画刻度线max
string str = (Yoffset * (y - YMoveUpValue) + Ystart).ToString();
sf = graphics.MeasureString(str, new Font("宋体", 10));
graphics.DrawString(str, new Font("宋体", 10), Brushes.Black, new PointF(XMoveRightValue - sf.Width, y1 - 8));
}
else
{
x1 = XMoveRightValue;
y1 = bmp.Height - y;
x2 = XMoveRightValue + 5;
y2 = bmp.Height - y;
graphics.DrawLine(new Pen(Color.Black, 1), x1, y1, x2, y2);//画刻度线min
}
y += 5;
}
/**/
//画X轴刻度
x1 = XMoveRightValue;
y1 = bmp.Height - YMoveUpValue;
x2 = bmp.Width - 5;
y2 = bmp.Height - YMoveUpValue;
graphics.DrawLine(new Pen(Color.Black, 1), x1, y1, x2, y2);//画长线
for (int i = XMoveRightValue; i < bmp.Width - 10; )
{
if ((i - XMoveRightValue) % 50 == 0)
{
x1 = i;
y1 = bmp.Height - YMoveUpValue - 10;
x2 = i;
y2 = bmp.Height - YMoveUpValue;
graphics.DrawLine(new Pen(Color.Black, 1), x1, y1, x2, y2);//画刻度线max
int row = (int)((double)(i - XMoveRightValue) / Xoffset);
if (dt.Rows.Count > row && row > 0 && sf.Width < 50)
{
string str = dt.Rows[row][0].ToString();
sf = graphics.MeasureString(str, new Font("宋体", 10));
graphics.DrawString(str, new Font("宋体", 10), Brushes.Black, new PointF(i - sf.Width / 2, bmp.Height - 12));
}
else
{
sf.Width = 0;
}
}
else
{
x1 = i;
y1 = bmp.Height - YMoveUpValue - 5;
x2 = i;
y2 = bmp.Height - YMoveUpValue;
graphics.DrawLine(new Pen(Color.Black, 1), x1, y1, x2, y2);//画刻度线min
}
i += 5;
}
return true;
}
catch (Exception ex) { sys.ToolWindows.LogAdd("DrawingScale " + ex.Message.ToString()); }
return false;
}
//int sum = 0,start = 0,stop = 0;
public bool Updata(ref DataTable data, Color[] color, int DotSum)
{
try
{
Init(ref data, DotSum, Ymin, Ymax);
DrawingCurve(ref data, GridView1, color, Ymin);
DrawingLabel(ref data, color);
DrawingHelp(Ymin);
DrawingScale(Ymin, ref data);
pictureBox.CreateGraphics().DrawImage(bmp, 0, 0);
graphics.Clear(Color.White);//清缓存
return true;
}
catch (Exception ex) { sys.ToolWindows.LogAdd("ChartUpdata " + ex.Message.ToString()); }
return false;
}
//pictureBox大小变化时需要同时调整Bitmap对象大小
private void EventHandlerBmpInit(object sender, EventArgs e)
{
bmp = new Bitmap(pictureBox.Width, pictureBox.Height);
graphics = Graphics.FromImage(bmp);
}
//让PictureBox支持MouseWheel事件
private void pictureBox_MouseDown(object sender, MouseEventArgs e)
{
pictureBox.Focus();
}
private void PictureBox_MouseWheel(object sender, MouseEventArgs e)
{
if ((Control.ModifierKeys & Keys.Control) == Keys.Control)
{
double offset = (Ymax - Ymin) / pictureBox.Height;
double Y = pictureBox.Height - e.Y;
double UpWidth = (double)(pictureBox.Height - Y) * Yoffset * 0.2;
double DownWidth = (double)(Y - 0) * Yoffset * 0.2;
if (e.Delta > 0)
{//Mouse Wheeled Up(缩小)
Ymax -= UpWidth;
Ymin += DownWidth;
}
else
{//Mouse Wheeled Down(放大)
Ymax += UpWidth;
Ymin -= DownWidth;
}
if (Ymax < Ymin) { Ymax = Ymin + 10; }
RemoveDecimal(Ymax, ref Ymax);
RemoveDecimal(Ymin, ref Ymin);
//Console.WriteLine("delte="+e.Delta.ToString() + ", X=" + e.X.ToString() + ", Y=" + e.Y.ToString());
}
else
{
if (e.Delta > 0)
{
sys.DotSum = (int)((double)sys.DotSum * 0.8);
}
else
{
sys.DotSum = (int)((double)sys.DotSum * 1.2);
}
if (sys.DotSum < 10) { sys.DotSum = 10; }
else if (sys.DotSum > 50000) { sys.DotSum = 50000; }
int length = (int)((double)NowRow / RowSum * sys.DotSum);
int value = NowID - length;
if (value <= hScrollBar.Maximum && value > hScrollBar.Minimum)
{
hScrollBar.Value = value;
}
}
}
private void pictureBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.F1)
{
if (Help == 0) { Help = 1; } else { Help = 0; }
}
else if ((Control.ModifierKeys & Keys.Control) == Keys.Control && e.KeyCode == Keys.F2)
{
if (sys.ToolWindows.Visible == false)
{
sys.ToolWindows.Show();
}
else
{
sys.ToolWindows.Close();
}
}
}
}