C#使用Chart图表控件实时显示运动坐标
我们假定运动轨迹是一个圆。
获取实时轴位置:这里将轴的轨迹以一个圆为例,比如x*x+y*y=100
已知圆方程x*x+y*y=100,将其分为32等分,求这32个点的坐标,第一个点的坐标为X+,即坐标(10,0)
思路:旋转角度,坐标方程(radius* cosθ, radius* sinθ)
分隔次数divideCount,一个圆是360°,弧度数为2π,以X+为第一个坐标,每次旋转(360/divideCount)°
圆弧上的点的坐标为(radius* cosθ, radius* sinθ),θ为∠XOP,即以X轴正方向作为旋转轴,旋转指定的角度
新建窗体应用程序ShowRealTimeCoordinateDemo,将默认的Form1重命名为FormShowGlueTrack,添加对System.Windows.Forms.DataVisualization图表框架的引用。
新建辅助类CncGlobal
源程序如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace ShowRealTimeCoordinateDemo
{public static class CncGlobal{/// <summary>/// 是否显示涂胶轨迹/// </summary>public static bool IsShowGlueTrack { get; set; } = false;/// <summary>/// 清除所有涂胶轨迹序列Series事件/// </summary>public static event Action ClearGlueTrackEvent;public static void ClearAllGlueTracks(){ClearGlueTrackEvent?.Invoke();}/// <summary>/// 创建一个涂胶轨迹序列Series事件/// </summary>public static event Action<string> CreateOneTrackSeriesEvent;/// <summary>/// 创建一个轨迹序列/// </summary>/// <param name="seriesName"></param>public static void CreateOneTrackSeries(string seriesName){CreateOneTrackSeriesEvent?.Invoke(seriesName);}static int divideCount = 60;//分隔次数static double radius = 10;//圆心(0,0),半径为10static int i = 0;//分隔次数/// <summary>/// 获取实时轴位置:这里将轴的轨迹以一个圆为例,比如x*x+y*y=100/// 已知圆方程x*x+y*y=100,将其分为32等分,求这32个点的坐标,第一个点的坐标为X+,即坐标(10,0)/// 思路:旋转角度,坐标方程(radius* cosθ, radius* sinθ)/// 分隔次数divideCount,一个圆是360°,弧度数为2π,以X+为第一个坐标,每次旋转(360/divideCount)°/// 圆弧上的点的坐标为(radius* cosθ, radius* sinθ),θ为∠XOP,即以X轴正方向作为旋转轴,旋转指定的角度/// </summary>/// <param name="X_POS"></param>/// <param name="Y_POS"></param>/// <param name="Z_POS"></param>/// <returns></returns>public static bool GetAxisPosition(ref double X_POS, ref double Y_POS, ref double Z_POS){Z_POS = 0;X_POS = radius * Math.Cos(2.0 * i / divideCount * Math.PI);Y_POS = radius * Math.Sin(2.0 * i / divideCount * Math.PI);i++;if (i >= divideCount) {i = 0;}return true;}}
}
窗体FormShowGlueTrack的设计器如下:

设计器程序如下:
文件FormShowGlueTrack.designer.cs
namespace ShowRealTimeCoordinateDemo
{partial class FormShowGlueTrack{/// <summary>/// Required designer variable./// </summary>private System.ComponentModel.IContainer components = null;/// <summary>/// Clean up any resources being used./// </summary>/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>protected override void Dispose(bool disposing){if (disposing && (components != null)){components.Dispose();}base.Dispose(disposing);}#region Windows Form Designer generated code/// <summary>/// Required method for Designer support - do not modify/// the contents of this method with the code editor./// </summary>private void InitializeComponent(){System.Windows.Forms.DataVisualization.Charting.ChartArea chartArea2 = new System.Windows.Forms.DataVisualization.Charting.ChartArea();System.Windows.Forms.DataVisualization.Charting.Legend legend2 = new System.Windows.Forms.DataVisualization.Charting.Legend();System.Windows.Forms.DataVisualization.Charting.Series series2 = new System.Windows.Forms.DataVisualization.Charting.Series();this.chart1 = new System.Windows.Forms.DataVisualization.Charting.Chart();this.button1 = new System.Windows.Forms.Button();this.button2 = new System.Windows.Forms.Button();this.button3 = new System.Windows.Forms.Button();this.button4 = new System.Windows.Forms.Button();((System.ComponentModel.ISupportInitialize)(this.chart1)).BeginInit();this.SuspendLayout();// // chart1// chartArea2.Name = "ChartArea1";this.chart1.ChartAreas.Add(chartArea2);legend2.Name = "Legend1";this.chart1.Legends.Add(legend2);this.chart1.Location = new System.Drawing.Point(6, 6);this.chart1.Name = "chart1";series2.ChartArea = "ChartArea1";series2.Legend = "Legend1";series2.Name = "Series1";this.chart1.Series.Add(series2);this.chart1.Size = new System.Drawing.Size(750, 600);this.chart1.TabIndex = 0;this.chart1.Text = "chart1";// // button1// this.button1.Location = new System.Drawing.Point(819, 55);this.button1.Name = "button1";this.button1.Size = new System.Drawing.Size(119, 23);this.button1.TabIndex = 1;this.button1.Text = "开始显示涂胶轨迹";this.button1.UseVisualStyleBackColor = true;this.button1.Click += new System.EventHandler(this.button1_Click);// // button2// this.button2.Location = new System.Drawing.Point(819, 115);this.button2.Name = "button2";this.button2.Size = new System.Drawing.Size(119, 23);this.button2.TabIndex = 2;this.button2.Text = "停止显示涂胶轨迹";this.button2.UseVisualStyleBackColor = true;this.button2.Click += new System.EventHandler(this.button2_Click);// // button3// this.button3.Location = new System.Drawing.Point(819, 195);this.button3.Name = "button3";this.button3.Size = new System.Drawing.Size(119, 23);this.button3.TabIndex = 3;this.button3.Text = "清除所有序列";this.button3.UseVisualStyleBackColor = true;this.button3.Click += new System.EventHandler(this.button3_Click);// // button4// this.button4.Location = new System.Drawing.Point(819, 326);this.button4.Name = "button4";this.button4.Size = new System.Drawing.Size(119, 23);this.button4.TabIndex = 4;this.button4.Text = "测试圆轨迹方程";this.button4.UseVisualStyleBackColor = true;this.button4.Click += new System.EventHandler(this.button4_Click);// // FormShowGlueTrack// this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;this.ClientSize = new System.Drawing.Size(951, 621);this.Controls.Add(this.button4);this.Controls.Add(this.button3);this.Controls.Add(this.button2);this.Controls.Add(this.button1);this.Controls.Add(this.chart1);this.Name = "FormShowGlueTrack";this.Text = "实时显示运动控制轨迹,只考虑X轴、Y轴坐标-斯内科";this.Load += new System.EventHandler(this.FormShowGlueTrack_Load);((System.ComponentModel.ISupportInitialize)(this.chart1)).EndInit();this.ResumeLayout(false);}#endregionprivate System.Windows.Forms.DataVisualization.Charting.Chart chart1;private System.Windows.Forms.Button button1;private System.Windows.Forms.Button button2;private System.Windows.Forms.Button button3;private System.Windows.Forms.Button button4;}
}
窗体FormShowGlueTrack程序如下:
文件FormShowGlueTrack.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
/** 在使用C#进行图形绘制时,特别是在使用如Windows Forms的Chart控件时,确保图形(特别是圆形)正确地显示为圆形而非椭圆是非常重要的。* 椭圆通常是由于绘图时坐标比例不正确或者绘图区域的比例设置不当导致的。
1. 确保绘图区域比例正确
首先,确保你的Chart控件的绘图区域(ChartArea)的宽高比是1:1。这可以通过设置ChartArea的AxisX和AxisY的IsStartedFromZero属性为true,并适当设置Minimum和Maximum值来实现。
2. 使用相同的刻度间隔
为了确保X轴和Y轴的刻度间隔相同,可以设置:
chart1.ChartAreas[0].AxisX.Interval = 1;
chart1.ChartAreas[0].AxisY.Interval = 1;
3. 检查绘图区域大小
确保你的Chart控件本身的大小也是正方形的,或者至少宽度和高度相等。设置控件大小来实现:
chart1.Width = 400; // 设置宽度
chart1.Height = 400; // 设置高度,确保宽度和高度相同.实际测试需要XY比例为5:4
*/namespace ShowRealTimeCoordinateDemo
{public partial class FormShowGlueTrack : Form{public FormShowGlueTrack(){InitializeComponent();this.SetStyle(ControlStyles.DoubleBuffer, true);}private void FormShowGlueTrack_Load(object sender, EventArgs e){CncGlobal.CreateOneTrackSeriesEvent += CncGlobal_CreateOneTrackSeriesEvent;CncGlobal.ClearGlueTrackEvent += CncGlobal_ClearGlueTrackEvent;chart1.Width = 750; // 设置宽度chart1.Height = 600; // 设置高度,确保宽度和高度相同.形成一个正方形.实际测试需要XY比例为5:4chart1.ChartAreas[0].AxisX.IsStartedFromZero = true;chart1.ChartAreas[0].AxisY.IsStartedFromZero = true;chart1.ChartAreas[0].AxisX.Minimum = -10;chart1.ChartAreas[0].AxisX.Maximum = 10; // 或任何其他合适的值chart1.ChartAreas[0].AxisY.Minimum = -10;chart1.ChartAreas[0].AxisY.Maximum = 10; // 或任何其他合适的值// 设置X轴的刻度chart1.ChartAreas[0].AxisX.Interval = 1; // 设置X轴的间隔为1chart1.ChartAreas[0].AxisX.IntervalType = DateTimeIntervalType.Number; // 设置为数字类型// 设置Y轴的刻度chart1.ChartAreas[0].AxisY.Interval = 1; // 设置Y轴的间隔为1chart1.ChartAreas[0].AxisY.IntervalType = DateTimeIntervalType.Number; // 设置为数字类型chart1.ChartAreas[0].AxisX.ArrowStyle = AxisArrowStyle.SharpTriangle;//轴显示箭头chart1.ChartAreas[0].AxisY.ArrowStyle = AxisArrowStyle.SharpTriangle;//轴显示箭头chart1.ChartAreas[0].AxisX.MajorGrid.Enabled = false;//隐藏X网格线chart1.ChartAreas[0].AxisY.MajorGrid.Enabled = false;//隐藏Y网格线chart1.ChartAreas[0].AxisX.Title = "X轴";chart1.ChartAreas[0].AxisX.TitleFont = new Font("宋体", 10);chart1.ChartAreas[0].AxisY.Title = "Y轴";chart1.ChartAreas[0].AxisY.TitleFont = new Font("宋体", 10);chart1.Series.Clear();//清除默认序列Task.Factory.StartNew(() => {while (true){if (CncGlobal.IsShowGlueTrack){double X_POS = 0;double Y_POS = 0;double Z_POS = 0;bool actionResult = CncGlobal.GetAxisPosition(ref X_POS, ref Y_POS, ref Z_POS);if (actionResult){RefreshChart(X_POS, Y_POS);}else{#region 测试功能使用,正式环境需删除/*x += direction * 10;if (x >= 1000){direction = -1;}else if (x <= 100){direction = 1;}y = x <= 500 ? 200 : 300;RefreshChart(x, y);*/#endregion}}System.Threading.Thread.Sleep(100);}});}private void CncGlobal_CreateOneTrackSeriesEvent(string seriesName){if (!IsHandleCreated){return;}this.BeginInvoke(new Action(() =>{Series series = new Series(seriesName);series.Color = Color.Blue;//chart1.Series[0].Name = "涂胶轨迹一";//chart1.Series[0].IsValueShownAsLabel = true; //显示Y轴的具体值series.MarkerStyle = MarkerStyle.Circle;series.ChartType = SeriesChartType.Line;//StepLine;series.Points.Clear();//序列名称必须为唯一名称;否则,将引发异常。Series seriesFind = chart1.Series.FindByName(seriesName);if (seriesFind == null){chart1.Series.Add(series);//确保当前序列只有一个}else{seriesFind.Points.Clear();}}));}private void CncGlobal_ClearGlueTrackEvent(){if (!IsHandleCreated){return;}this.BeginInvoke(new Action(() =>{chart1.Series.Clear();//清除图表中的所有序列}));}private void RefreshChart(double xValue, double yValue) {if (!IsHandleCreated) {return;}this.BeginInvoke(new Action(() =>{if (chart1.Series.Count > 0){//只考虑最后一次的序列Series series = chart1.Series[chart1.Series.Count - 1];series.Points.AddXY(xValue, yValue);//限制数据量:至多显示200个数据点if (series.Points.Count > 300){series.Points.RemoveAt(0);}chart1.Refresh();}}));}private void button1_Click(object sender, EventArgs e){CncGlobal_CreateOneTrackSeriesEvent("涂胶轨迹一");button1.Enabled = false;CncGlobal.IsShowGlueTrack = true;}private void button2_Click(object sender, EventArgs e){if (chart1.Series.Count > 0){//只考虑最后一次的序列Series series = chart1.Series[chart1.Series.Count - 1];//停止时最后一个终点颜色是红色if (series.Points.Count > 0){series.Points[series.Points.Count - 1].Color = Color.Red; ;}//chart1.Refresh();}CncGlobal.IsShowGlueTrack = false;button1.Enabled = true;}private void button3_Click(object sender, EventArgs e){CncGlobal_ClearGlueTrackEvent();}private void button4_Click(object sender, EventArgs e){/** 已知圆方程x*x+y*y=100,将其分为32等分,求这32个点的坐标,第一个点的坐标为X+,即坐标(10,0)* 思路:旋转角度,坐标方程(radius*cosθ,radius*sinθ)* 分隔次数divideCount,一个圆是360°,弧度数为2π,以X+为第一个坐标,每次旋转(360/divideCount)°* 圆弧上的点的坐标为(radius*cosθ,radius*sinθ),θ为∠XOP,即以X轴正方向作为旋转轴,旋转指定的角度*/int divideCount = 8;//分隔次数double radius = 10;//圆心(0,0),半径为10Tuple<double, double>[] circles = new Tuple<double, double>[divideCount];//circles[0] = Tuple.Create(radius, 0.0);for (int i = 0; i < divideCount; i++){circles[i] = Tuple.Create(radius * Math.Cos(2.0 * i / divideCount * Math.PI), radius * Math.Sin(2.0 * i / divideCount * Math.PI));}MessageBox.Show(string.Join(",\n", circles.Select(x => $"({x.Item1},{x.Item2})")));}}
}
运行如图:

