C#中Chart控件的一些由浅至深的理解

    最近在做这样一个桌面应用程序,从公司后台服务器查询后,获取到设备上报的数据(每条都是字符串+JSON的形式),其中字符串里包含了设备名称、上报时间这两条信息,JSON中则包含了数据上报类型、重点关注的设备CPU温度及其他次要信息。需求呢,则是需要将这些数据中的时间、温度筛选出来制成折线图,并达到最终的一个显示效果(算是简单的数据可视化吧)。

    其中参考几篇帮助较大的博文:

1.C# Chart控件,chart、Series、ChartArea曲线图绘制的重要属性

http://blog.sina.com.cn/s/blog_621e24e20101cp64.html

2.C# Chart各个属性详细解析、应用

https://blog.csdn.net/Andrewniu/article/details/78770227

3.c#改变坐标轴的标注

https://blog.csdn.net/u014683488/article/details/52106424

4.当然还有微软的官方文档

https://docs.microsoft.com/zh-cn/dotnet/api/system.web.ui.datavisualization.charting.customlabel?view=netframework-4.7.2

    实际上工具使用者的需求是这样的:

①如图中所示,x轴能准确看出时间,且只显示整点的网格线,y轴则显示某个区间内的CPU温度,并且在重点关注范围能够明显区分;

②曲线上前后两点之间,若上报时间大于30分钟,则曲线不连续;

③显示温度最大值的时间及数值,并有y轴网格线穿过该点(未实现)

    下面进入正文,在满足各个需求在实现过程中遇到的问题。

    在此之前,需要大概了解的chart属性有ChartAreas、Series,想要画出目标图形,需要对两者的众多属性进行设置,上面参考博文中有详细解释,在我的理解中,按照结构来讲大概是如下图这样,Chart控件内可以添加多个ChartAreas,可以理解为作图区域,默认有一个为ChartAreas[0];而在各个ChartAreas中都可以有多个Series,Series是一些列点(Points)的集合,而这些点组成的集合与X轴(X axis)以及Y轴(Y axis)则构成了我们眼中的图表,即存在这样的结构:

----Chart                                                    ......一级

--------ChartAreas                                      ......二级

------------Series                                         ......三级

----------------Points                                    ......四级

------------X axis                                         ......三级

------------Y axis                                          ......三级

    在心中有个大致的认识后,还是需要动手才能创造出目标物,毕竟写代码这种事情读万卷书不如行万里路。那么就根据各个需求各个击破。

一、需求一

    该需求中设计操作有,①改变x轴标签(label)②设置x轴y轴最大最小值及间隔③区分不同区间的y值。

    此处主要是对ChartAreas中的Axes(轴)进行操作,先看一下Axes集合中包含的内容,进行操作“选择chart——点击‘ChartAreas’后的‘集合’——点击‘Axes’后的‘集合’”,如图:

直接贴代码

①给x轴添加标签

CustomLabel label = new CustomLabel();
label.FromPosition = i - 0.5;
label.ToPosition = i + 0.5;
//标签序号,在同一个标签中
label.RowIndex = 0;
chart1.ChartAreas[0].AxisX.CustomLabels.Add(label);

②设置x、y轴最大最小值及间隔

chart1.ChartAreas[0].AxisX.Maximum = EndHour;
chart1.ChartAreas[0].AxisX.Minimum = BeginHour;
chart1.ChartAreas[0].AxisX.Interval = 1;
chart1.ChartAreas[0].AxisY.Maximum = 120;
chart1.ChartAreas[0].AxisX.Minimum = 60;
chart1.ChartAreas[0].AxisY.Interval = 5;

③之前想的是创建两个ChartAres进行叠加,新的ares作为“背景”置于折线图后,尝试过之后发现并不容易(水平有限-.-),就退而求其次自己在excel表格中对等高的单元格填充颜色后获得所需比例的图像,按照温度的关注区间,制作了两张颜色所占高度不同比例的图,最后根据y轴数据最小值来设置不同背景;

if (Min_Y < 80)
{
    chart1.ChartAreas[0].AxisY.Minimum = 60;
    chart1.ChartAreas[0].BackImage = Application.StartupPath + "\\2.png";
}
else
{
    chart1.ChartAreas[0].AxisY.Minimum = 80;
    chart1.ChartAreas[0].BackImage = Application.StartupPath + "\\1.png";
}

    至此需求一的关键实现已满足。

二、需求二

    两点之间不连续的话,之前并没听说过折线图的点之间还可以不连线,则考虑到两个series之间并不存在绝对联系,则可通过添加一个新的series来实现。

if (time - lastTime > (decimal)0.5)       //大于30分钟,不跟上一条点集连续,再加一条
{
	Series NewSeries = new Series();
	//NewSeries.ChartType = SeriesChartType.Line;
	onSeries = onSeries + 1;
	NewSeries.Name = "CPU温度" + onSeries;
	chart1.Series.Add(NewSeries.Name);
	chart1.Series[NewSeries.Name].MarkerBorderColor = Color.Honeydew;
	chart1.Series[NewSeries.Name].MarkerBorderWidth = 2;
	chart1.Series[NewSeries.Name].MarkerSize = 8;
	chart1.Series[NewSeries.Name].MarkerColor = Color.Red;
	chart1.Series[NewSeries.Name].MarkerStyle = MarkerStyle.Circle;
	chart1.Series[NewSeries.Name].ChartType = SeriesChartType.Line;
}

    那么需求二关键实现也满足了。

三、需求三

    可能对于整个需求来说,需求三才是一个难点,之前的想法并没有把标签Label和工具提示ToolTip的作用区分开来,在尝试后认为:

①Label作为Series上点Points的一个属性,在设置后会显示在图表上表示该点的附近的一个合适位置,设置后则显示在所在ChartAres上(未找到可以隐藏该属性的方法,如Label.Visible=false这样的);

②作为ToolTip的话,设置完成后可以通过获得该点的该属性值获取设置到的文本,故在此设置此属性来显示最大值上文字。

故对每个点的ToolTip都进行设置,在轮询找到Y值最大值时,进行设置:

chart1.Series[onSeries].Points[now].ToolTip = "时间:" + X_Value[i] + "\r\n" + "值:" + Y_Value[i];

    在上述代码段中,X_Value为从数据中筛选到的数据上报时间的集合,将其值设置为该点的工具提示文本,后面要做的就是只将最大值的Label设置为与ToolTip文本相关的值(要注意,ToolTip后面也是有用的,就是在鼠标移动到该点时,将ToolTip的内容显示出来),下面先设置最大值的显示:

chart1.Series[Max_Index].Points[Max_Y_Index].Label = "时间:" + chart1.Series[Max_Index].Points[Max_Y_Index].ToolTip + "\r\n" + "值:" + Max_Y;

    在上述代码段中,Max_Index为最大值所在Series的序号,Max_Y_Index为最大值的点在其所在Series的序号,即最大值所在点的标签文本=最大值所在点的工具提示文本+其他描述。

    接下来看似已经实现了所有需求,但在需求中漏掉了较为重要的一点,作为数据可视化来讲,怎么能只是显示一个数据呢(虽然用户需求是这样,但作为初级程序员来说,不能对自己降低要求),要做的就是上面说的,移动鼠标到一个Point,显示该点的ToolTip,直接贴代码:

private void chart1_GetToolTipText(object sender, ToolTipEventArgs e)
{
	try
	{
		HitTestResult myTestResult = chart1.HitTest(e.X, e.Y, ChartElementType.DataPoint);//获取命中测试的结果
		if (myTestResult.ChartElementType == ChartElementType.DataPoint)
		{
			//第几个点
			int i = myTestResult.PointIndex;
			DataPoint dp = myTestResult.Series.Points[i];
			double XValue = chart1.Series[0].Points[i].XValue;//获取数据点的X值
			//string YValue = dp.YValues[0].ToString();//获取数据点的Y值
			JSONObject j;
			e.Text = "序列:" + myTestResult.Series.Name + "\r\n时间:" + dp.ToolTip + "\r\n值:" + dp.YValues[0] + "\r\n";//ChartResult.Series[i].ToolTip;
		}
	}
	catch (Exception exc)
	{

	}
}

显示效果的话如下:

四、总结

    引用一句话来总结吧,实践是检验真理的唯一标准。古人也说,纸上谈来终觉浅,绝知此事要躬行。看的再多不如动手一试,当然也不是说在无任何基础的情况下去试,之前看到一位大牛博客中说的,当学习理论知识感到饱和或者厌倦的时候,就去动手做一些东西;当动手到毫无头绪如何进行下一步的时候,尝试去查阅理论知识来丰富自己的思维,这句话在我看来是非常赞同并在贯彻落实的。

    当然,学习的过程缺少不了总结,在这个桌面应用中,主要是用于将本地磁盘中的视频与上报数据中的温度进行对比,以分析各个时间段视频的各项参数是否符合标准,其中当然不仅上述的些许操作,整个流程如下:

①操作access数据库存储配置文件

②向后台发送http请求获取上报数据

③上述的显示原始数据初步处理的结果

④获取本地视频的各项参数(帧率、大小、时长等)

⑤以时间为x轴数值,以各项参数为y轴参数,画出多条曲线加上文字分析,对比配置文件内的标准,检测在某个时间点的温度下视频的某个参数是否符合。

    贴上一张最终结果的设计界面,有想法的同学私信交流。

猜你喜欢

转载自blog.csdn.net/qq_23958061/article/details/84667242