另外一个版本是使用OpenTK开发,文字显示效果不太理想,因此换了SharpGL重新写了一个。
文字是缓存为图片,然后使用坐标映射的方式绘制到图表上。
资源忘了复制链接,欢迎来白嫖
public partial class QChart : UserControl
{
public QChart()
{
InitializeComponent();
BackColor = Color.White;
LineColor = Color.Gray;
DotColor = Color.Red;
TitleColor = Color.Red;
TxtColor = Color.Black;
XTitle = "X";
YTitle = "Y";
ZTitle = "Z";
XRange = 5;
YRange = 3;
ZRange = 3;
_rotationy = 1;
FontSize = 12;//文字大小
minx = miny = minz = 0;
maxx = maxy = maxz = 10;
XStep = YStep = ZStep = 1;
}
private int _x;
private float _rotationx, _rotationy;
private float zIndex = 12;//俯视角度,好看
private bool locked = false;//是否锁定
private List<Point3D> points;
public float FontSize { get; set; }
/// <summary>
/// 坐标线条颜色
/// </summary>
public Color LineColor { get; set; }
/// <summary>
/// 点颜色
/// </summary>
public Color DotColor { get; set; }
/// <summary>
/// 标题颜色
/// </summary>
public Color TitleColor { get; set; }
/// <summary>
/// 刻度颜色
/// </summary>
public Color TxtColor { get; set; }
/// <summary>
/// X轴标题
/// </summary>
public String XTitle { get; set; }
/// <summary>
/// Y轴标题
/// </summary>
public String YTitle { get; set; }
/// <summary>
/// Z轴标题
/// </summary>
public String ZTitle { get; set; }
public int XRange { get; set; }
public int YRange { get; set; }
public int ZRange { get; set; }
public double maxx { get; set; }
public double minx { get; set; }
public double maxy { get; set; }
public double miny { get; set; }
public double maxz { get; set; }
public double minz { get; set; }
/// <summary>
/// X方向的刻度间隔,默认1
/// </summary>
public int XStep { get; set; }
/// <summary>
/// y方向的刻度间隔,默认1
/// </summary>
public int YStep { get; set; }
/// <summary>
/// Z方向的刻度间隔,默认1
/// </summary>
public int ZStep { get; set; }
private Point mouse = new Point();//记录鼠标位置
private void openGLControl1_OpenGLDraw(object sender, RenderEventArgs e)
{
// Get the OpenGL object, just to clean up the code.
OpenGL gl = this.openGLControl1.OpenGL;
gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT);
gl.ClearColor((float)(BackColor.R / 255.0), (float)(BackColor.G / 255.0), (float)(BackColor.B / 255.0), 1f);//背景色
gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT | OpenGL.GL_STENCIL_BUFFER_BIT);
gl.MatrixMode(OpenGL.GL_MODELVIEW);
gl.LoadIdentity();
gl.Rotate(_rotationx, 1, 0, 0);//X轴旋转
gl.Rotate(_rotationy, 0, 1, 0);//Y轴旋转
//绘制坐标
gl.Color(LineColor.R, LineColor.G, LineColor.B);//线颜色
//gl.DrawText(0, 0, TitleColor, "宋体", FontSize, "ss");
//水平线,X面
for (int i = -XRange; i <= XRange; i++)
{
gl.Begin(OpenGL.GL_LINES);
gl.Vertex(i, -YRange, -ZRange);
gl.Vertex(i, -YRange, ZRange);
gl.End();
}
for (int i = -ZRange; i <= ZRange; i++)
{
gl.Begin(OpenGL.GL_LINES);
gl.Vertex(-XRange, -YRange, i);
gl.Vertex(XRange, -YRange, i);
gl.End();
}
//竖直线,Y面
for (int i = -YRange; i <= YRange; i++)
{
gl.Begin(OpenGL.GL_LINES);
gl.Vertex(-XRange, i, -ZRange);
gl.Vertex(-XRange, i, ZRange);
gl.End();
}
for (int i = -ZRange; i <= ZRange; i++)
{
gl.Begin(OpenGL.GL_LINES);
gl.Vertex(-XRange, -YRange, i);
gl.Vertex(-XRange, YRange, i);
gl.End();
}
//Z面
for (int i = -YRange; i <= YRange; i++)
{
gl.Begin(OpenGL.GL_LINES);
gl.Vertex(-XRange, i, -ZRange);
gl.Vertex(XRange, i, -ZRange);
gl.End();
}
for (int i = -XRange; i <= XRange; i++)
{
gl.Begin(OpenGL.GL_LINES);
gl.Vertex(i, -YRange, -ZRange);
gl.Vertex(i, YRange, -ZRange);
gl.End();
}
if (XStep < 1) XStep = 1;
//刻度x
for (int i = -XRange; i <= XRange; i+=XStep)
{
double v = (i + XRange) / (2.0 * XRange) * (maxx - minx);
DrawText(String.Format("{0:f2}", v), i - 0.2f, -YRange - 0.5f, -ZRange - 0.2f, TxtColor);
}
//DrawTextCN(XTitle, XRange + 0.5f, -YRange - 0.25f, -ZRange, TitleColor);
if (YStep < 1) YStep = 1;
//刻度Y
for (int i = -YRange; i < YRange; i+= YStep)
{
double v = (i + YRange) / (2.0 * YRange) * (maxy - miny);
DrawText(String.Format("{0:f2}", v), -XRange - 0.2f, i, -ZRange - 0.2f, TxtColor);
}
// DrawTextCN(YTitle, -XRange, YRange, -ZRange, TitleColor);
if (ZStep < 1) ZStep = 1;
//刻度Z
for (int i = -ZRange; i < ZRange; i+=ZStep)
{
double v = (i + ZRange) / (2.0 * YRange) * (maxz - minz);
DrawText(String.Format("{0:f2}", v), -XRange - 0.5f, -YRange - 0.5f, i, TxtColor);
}
// DrawTextCN(ZTitle, -XRange, -YRange - 0.5f, ZRange + 0.5f, TitleColor);
//输出三个坐标标题
gl.Color(TitleColor.R, TitleColor.G, TitleColor.B);
drawCNString(" ", 99, 99, 99, gl);
drawCNString(ZTitle, -XRange, -YRange,ZRange+0.5f, gl);
drawCNString(XTitle+0.5f , XRange, -YRange, -ZRange, gl);
drawCNString(YTitle , -XRange, YRange+0.5f, -ZRange, gl);
//如果有点,才绘制点
if (points != null && points.Count > 0)
{
gl.Color(DotColor.R, DotColor.G, DotColor.B);
// gl.PointSize(4);
foreach (Point3D p in points)
{
double x = XRange * (2 * (p.X - minx) / (maxx - minx) - 1);
double y = YRange * (2 * (p.Y - miny) / (maxy - miny) - 1);
double z = ZRange * (2 * (p.Z - minz) / (maxz - minz) - 1);
if (p.OnMouse) gl.PointSize(6);
else gl.PointSize(4);
gl.Begin(OpenGL.GL_POINTS);
gl.Vertex(x, y, z);
gl.End();
}
// GL.DrawPixels
}
gl.Flush();
}
private void DrawText(String s, double x, double y, double z, Color color)
{
OpenGL gl = this.openGLControl1.OpenGL;
var sv = SharpGL.SceneGraph.OpenGLSceneGraphExtensions.Project(gl, new SharpGL.SceneGraph.Vertex((float)x, (float)y, (float)z));
gl.DrawText((int)sv.X, (int)sv.Y, (float)(color.R / 255.0), (float)(color.G / 255.0), (float)(color.B / 255.0), "宋体", FontSize, s);
}
private void drawCNString(string str, float x, float y,float z, OpenGL gl)
{
int i;
// Create the font based on the face name.
var hFont = Win32.CreateFont((int)FontSize+4, 0, 0, 0, Win32.FW_DONTCARE, 0, 0, 0, Win32.DEFAULT_CHARSET,
Win32.OUT_OUTLINE_PRECIS, Win32.CLIP_DEFAULT_PRECIS, Win32.CLEARTYPE_QUALITY, Win32.CLEARTYPE_NATURAL_QUALITY, "宋体");
// Select the font handle.
var hOldObject = Win32.SelectObject(gl.RenderContextProvider.DeviceContextHandle, hFont);
// Create the list base.
var list = gl.GenLists(1);
gl.RasterPos(x, y,z);
// 逐个输出字符
for (i = 0; i < str.Length;i++)
{
bool result = Win32.wglUseFontBitmapsW(gl.RenderContextProvider.DeviceContextHandle, str[i], 1, list);
gl.CallList(list);
}
// 回收所有临时资源
//free(wstring);
gl.DeleteLists(list, 1);
// Reselect the old font.
Win32.SelectObject(gl.RenderContextProvider.DeviceContextHandle, hOldObject);
// Free the font.
Win32.DeleteObject(hFont);
//glDeleteLists(list, 1);
}
/// <summary>
/// 传入坐标集合
/// </summary>
/// <param name="points"></param>
public void ShowPoints(List<Point3D> points)
{
this.points = points;
if (points == null || points.Count == 0) return;
maxx = points[0].X;
minx = points[0].X;
maxy = points[0].Y;
miny = points[0].Y;
maxz = points[0].Z;
minz = points[0].Z;
foreach (Point3D p in points)
{
if (p.X > maxx) maxx = p.X;
if (p.X < minx) minx = p.X;
if (p.Y > maxy) maxy = p.Y;
if (p.Y < miny) miny = p.Y;
if (p.Z > maxz) maxz = p.Z;
if (p.Z < minz) minz = p.Z;
}
//防止直接到边界
maxx += 0.2;
minx -= 0.2;
maxy += 0.2;
miny -= 0.2;
maxz += 0.2;
minz -= 0.2;
}
private void QChart_Load(object sender, EventArgs e)
{
openGLControl1.OpenGLInitialized += OpenGLControl1_OpenGLInitialized;
this.Resize += QChart_Resize;
openGLControl1.MouseDown += on_MouseDown;
openGLControl1.MouseLeave += on_MouseLeave;
openGLControl1.MouseMove += on_MouseMove;
openGLControl1.MouseUp += on_MouseUp;
openGLControl1.MouseWheel += on_MouseWheel;
SetupViewport();
}
private void OpenGLControl1_OpenGLInitialized(object sender, EventArgs e)
{
SetupViewport();
}
private void QChart_Resize(object sender, EventArgs e)
{
SetupViewport();
}
//按键事件
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.KeyCode == Keys.Space)
{
this._x++;
SetupViewport();
this.Invalidate();
}
}
private void SetupViewport()
{
var w = this.Width;
var h = this.Height;
OpenGL GL = openGLControl1.OpenGL;
GL.MatrixMode(OpenGL.GL_PROJECTION);
GL.LoadIdentity();
GL.Viewport(0, 0, w, h); // Use all of the glControl painting area
if (h == 0) h = 1;
GL.Perspective(60.0f, w / h, 0.01, 100);
GL.LookAt(0, 0, zIndex, 0, 0, 0, 0, 1, 0);
}
private bool msDown = false;//是否鼠标按下
private int oldX = 0;//原始X坐标
private int oldY = 0;
private void on_MouseWheel(object sender, MouseEventArgs e)
{
// MessageBox.Show(e.Delta+"");
if (e.Delta > 0)
{
zIndex += 1;
if (zIndex > 20) zIndex = 20;
}
else if (e.Delta < 0)
{
zIndex -= 1;
if (zIndex < 8) zIndex = 8;
}
SetupViewport();
this.Invalidate();
}
private void on_MouseDown(object sender, MouseEventArgs e)
{
msDown = true;
oldX = e.X;//记录
oldY = e.Y;
Cursor = Cursors.Hand;
;
}
private void on_MouseMove(object sender, MouseEventArgs e)
{
if (points != null && points.Count > 0)
{
OpenGL gl = openGLControl1.OpenGL;
double[] viewmatrix=new double[16];
double[] projection = new double[16];
int[] viewport = new int[4];
gl.GetDouble(OpenGL.GL_MODELVIEW_MATRIX, viewmatrix);
gl.GetDouble(OpenGL.GL_PROJECTION,projection);
gl.GetInteger(OpenGL.GL_VIEWPORT, viewport);
for (int i = 0; i < points.Count; i++)
{
points[i].OnMouse = false;
}
int x = e.X;
int y = e.Y;
for (int i = 0; i < points.Count; i++)
{
var sv = SharpGL.SceneGraph.OpenGLSceneGraphExtensions.Project(gl, new SharpGL.SceneGraph.Vertex((float)points[i].X, (float)points[i].Y, (float)points[i].Z));
if (Math.Abs(sv.X - x) < 3 && Math.Abs(sv.Y - y) < 3)
{
points[i].OnMouse = true;
break;
}
}
}
if (!msDown) return;
int dx = e.X - oldX;
int dy = e.Y - oldY;
if (dy > 0) _rotationx += 0.5f;
else if (dy < 0) _rotationx -= 0.5f;
if (dx > 0) _rotationy += 0.5f;
else if (dx < 0) _rotationy -= 0.5f;
SetupViewport();
this.Invalidate();
}
private void on_MouseUp(object sender, MouseEventArgs e)
{
msDown = false;
Cursor = Cursors.Default;
}
private void on_MouseLeave(object sender, EventArgs e)
{
msDown = false;
}
}
核心代码如下