在众多的工业控制系统领域常常会实时采集现场的温度、压力、扭矩等数据,这些数据对于监控人员进行现场态势感知、进行未来趋势预测具有重大指导价值。工程控制人员如果只是阅读海量的数据报表,对于现场整个态势的掌控会十分困难,因此往往希望借助一些图表进行展示,其中趋势图是常用的实时数据展示方式之一。目前实现趋势图、曲线图的工具很多也很成熟,一些是通过CS模式开发的,需要在工程控制人员操作的计算机上安装相应软件,这种方式有其特有的优势,但是有时也有不方便的地方。于是一些基于BS模式的展现方式就自然而然的被广泛应用起来。采用BS模式的展现方式工程控制人员可以通过任何一台可以连接Web服务器的PC,通过常用的浏览器就可以实时的查看当前环境现场的各种指标参数,其便利性是显而易见的。下面我就介绍一下这种BS模式的实时数据展现曲线图的方法。由于本文的目的不是去实现一个现场环境可用的应用产品,因此只是针对涉及的技术进行讲解,起一个指导作用,因此参考者请依据自己项目的实际需要对本文章涉及的代码进行优化使用。
在通过BS系统实现趋势图、曲线图的可选方案很多,本文主要通过Highcharts.com旗下的Highcharts
API包来实现。由于此包是通过JS脚本实现的,因此个人认为相对来说适应面可能更好,当然通过诸如jFreeChart这类工具也是可以实现的。
言归正传,我们来讲解如何通过Highcharts
API实现趋势图在页面上的呈现。
首先我们需要简单规划一下我们系统的架构。由于实现实时数据的趋势图呈现,因此系统大致我们可以设计成三层:显示层、逻辑层、数据源。如下图所示:
显示层就是将数据呈现给工程控制人员的的展示页面,这层主要由JSP、JS、CSS等文件构成,工程控制人员通过浏览器(诸如IE、火狐等)就能直接看到希望的曲线图。显示层只负责显示,而显示需要的数据是经过一定清洗、规格化的,显示层拿到符合规格化要求的数据后,就可以直接进行显示,并响应和人的交互。数据的清洗、规格化工作都是在逻辑层中实现,逻辑层通过获取的数据源信息,进行必要的数据逻辑转换、数据清洗、数据规格化处理。数据源是一个复杂的重要的,它可以是直接来自下位机的数据通讯,也可以是下位机将数据存储在中间数据库中,也可以是一系列的数据文件。
在本文中只是模拟数据源,并不是实现数据源的读取。并且对应逻辑层的处理,也进行了忽略,这部分内容因为涉及具体的数据获取、清洗、转换、规格化,和具体工程项目的需求有较大关系,加之也不是本文规划的中心,因此此部分代码设计实现本文也不涉及。
显示层的实现涉及到JSON、JQuery、Highcharts,我们首先建立一个标准的Web应用(其实如果作为例子,使用一个html文件也行,此处本人计划后期会扩展本案例,实现后续一些诸如逻辑层的功能,因此建立了一个Web应用工程,有点画蛇添足,还请见谅)。本人采用Netbeans
IDE 7.2版本开发(如何用Netbeans IDE开发Web应用请参考本人其它文章),因此建立完成后的工程结构如下:
要使用Highcharts,我们需要导入highcharts.js文件。highcharts.js文件可以从Highcharts官网获得(官网地址:http://www.highcharts.com/),从官网下载的压缩文件中包含有我们开发需要的highcharts.js文件外,还包含一些其它的文件,诸如例子文件等。
Highcharts是使用js来实现的,同时应用到了JQuery技术,因此还需要去获得最新版的JQuery包(官网地址:http://jquery.com/)。
以上两块准备好后,我们将其加入新建的工程中,在工程文件中我们规划了一个放置所有js文件的地方,有一个js目录,将highcharts.js、jquery-1.8.3.min.js文件都放置到此目录下。大家看到我的工程中在js目录下有一个modules子目录,此子目录下放置的exporting.js文件是可有可无的,如果大家要使用将图表导出等功能就需要使用此文件,因此需要导入工程中,否则完全可以不需要。
工程环境准备停当,我们就可以打开本工程的默认,也是唯一的一个jsp页面,对此页面做一个编写修改。主要修改有如下几个地方:
第一,在jsp页面头部引入highcharts.js、jquery-1.8.3.min.js文件,应用代码参考如下:
<scripttype="text/javascript"src="http://zhaowenbinmail.blog.163.com/blog/js/jquery-1.8.3.min.js"></script>
<scriptsrc="http://zhaowenbinmail.blog.163.com/blog/js/highcharts.js"></script>
第二,在jsp页面的Body体中加入一个div元素,highcharts将在这个div元素中绘制曲线图。
<divid="container"style="min-width:100px;
height:400px; margin:0auto"></div>
注意:我们为这个div指定了一个id值,这个id值将来对我们很有用,它是使highcharts知道在何处绘制图表的根源。
第三,我们需要在jsp页面中加入我们自己的js文件。这个文件用来实现特定的业务呈现逻辑。
<scripttype="text/javascript"src="http://zhaowenbinmail.blog.163.com/blog/js/chart.js"></script>
现在我来讲解一下我需要实现的业务的大体需求。我需要在页面上显示大桥表面一天24小时的温度变化情况(当然这些温度值的变化本案例中都是通过随机数来产生的)。在我们的很轴方向上需要显示从0点开始到晚上23点的时间刻度,并且要求固定就显示0到23这24个刻度,在纵轴方向显示桥面传感器检测到的本小时内温度最大值,然后模拟时间推移显示每小时的温度变化曲线图。
依据以上需求分析,在页面呈现时,我们就要去读取本天从0点开始到当前时刻的数据,并将数据绘制显示成曲线图。因此我们接下来就要编辑我们自己的js文件——chart.js。在此文件中创建Highcharts对象,通过设置相关的属性来影响曲线图的呈现,使其满足我们的需求要求。
我们首先定义一个全局的图表对象,我们命名为chart,同时示例化Highcharts对象,具体见下面代码:
var chart;
$(function(){
$(document).ready(function(){
chart =newHighcharts.Chart({
chart:{
renderTo:‘container‘,
type:‘line‘,
marginRight:130,
marginBottom:80,
events:{
load:loadTime
}
},
title:{
text:‘大桥采集数据‘,
x:-20
},
subtitle:{
text:‘传感器编号: 传感器1‘,
x:-20
},
xAxis:{
title:{
enabled:true,
text:‘时间(小时)‘
},
max:23,
min:0,
tickPixelInterval:50
},
yAxis:{
title:{
text:‘压力 (℃)‘
},
plotLines:[{
value:0,
width:1,
color:‘#808080‘
}]
},
tooltip:{
formatter:function(){
return‘<b>‘+this.series.name
+‘</b><br/>‘+
this.x
+‘: ‘+this.y +‘℃‘;
}
},
legend:{
x:-50,
y:10,
enabled:true
},
exporting:{
enabled:false
},
plotOptions:{
line:{
gapSize:100
}
},
series:[{
name:‘最大值‘,
data: getFirstData()
}]
});
});
});
在上面代码中我们实例化了一个Highcharts对象,并指定了此对象一些属性。在定义中我们可以看到如下代码:
chart:{
renderTo:‘container‘,
type:‘line‘,
marginRight:130,
marginBottom:80,
events:{
load:loadTime
}
}
在这段代码中就知道了图表需要绘制到的div元素,注意看上面红色字体部分。这个地方的container就是jsp页面上div的id值(大家可以回过去查看一下我前面的代码)。
其后指定了这个图表的类型(见上面代码中蓝色字体部分)。这里指定的Line类型,这就会绘制成曲线。
代码中还指定了图表的事件,目前只指定了一个load事件,在图表装载时将会调用执行load时间对应的函数。代码后面的loadTime是我编写的一个js函数,这个函数后面介绍。
通过title、subtitle指定图表的标题、子标题,见下面代码:
title:{
text:‘大桥采集数据‘,
x:-20
},
subtitle:{
text:‘传感器编号: 传感器1‘,
x:-20
},
通过改变这里的设置可以影响图表上显示的效果,显示效果如下图红框选中部分内容。
通过xAxis、yAxis设定图表中横坐标、纵坐标的属性。
xAxis:{
title:{
enabled:true,
text:‘时间(小时)‘
},
max:23,
min:0,
tickPixelInterval:50
},
yAxis:{
title:{
text:‘压力 (℃)‘
},
plotLines:[{
value:0,
width:1,
color:‘#808080‘
}]
},
通过在xAxis、yAxis中设置title属性控制横坐标、纵坐标上的文字描述显示。
在横坐标中有时我们是需要指定坐标上每个坐标点需要显示的文字内容的,这时我们需要使用到一个categories属性,通过将一个数组值传给categories属性,这样在横坐标上的每个坐标点就会按照指定的属性内容显示坐标点信息。为了实现如上目的,我们需要再设置一个变量,并将这个变量赋予categories属性。详见下:
var x_arr=[‘0:00‘,‘1:00‘,‘2:00‘,‘3:00‘,‘4:00‘,‘5:00‘,
‘6:00‘,‘7:00‘,‘8:00‘,‘9:00‘,‘10:00‘,‘11:00‘,
‘12:00‘,‘13:00‘,‘14:00‘,‘15:00‘,‘16:00‘,‘17:00‘,
‘18:00‘,‘19:00‘,‘20:00‘,‘21:00‘,‘22:00‘,‘23:00‘];
通过上面代码段我们定义了一个变量,然后看看如何指定到chart对象中,请注意如下代码端中红色部门,就是较前面代码端增加的地方:
xAxis:{
title:{
enabled:true,
text:‘时间(小时)‘
},
categories:x_arr,
max:23,
min:0,
tickPixelInterval:50
},
现在页面上显示的图表效果如下图所示:
横坐标上就是按照我们在x_arr变量中设定的内容显示的。如果设定的内容少于横坐标的坐标点,则前面的坐标点将按x_arr变量中设定内容显示,后面的坐标点将按默认的坐标点信息显示。
工程控制人员通过此曲线图可以知道每个时间点的最高温度情况,但是具体某个点温度是多少,工程人员希望通过将鼠标指向图表中采集点就可以展现出来。要实现这个功能,我们需要在chart对象中指定tooltip属性,见下面代码:
tooltip:{
formatter:function(){
return‘<b>‘+this.series.name
+‘</b><br/>‘+
this.x
+‘: ‘+this.y +‘℃‘;
}
},
配置上了这个属性就能得到下图显示的效果:
当鼠标移动到10点中的采集点时,屏幕上会立即显示当时的温度值。
针对有些应用场合,我们在一个图表中将展示多个线条代表不同的含义,这时就需要通过图例来说明什么颜色线条代表什么含义,这种情况下就需要启用图例说明,要启用图例说明就必须通过如下属性进行配置:
egend:{
x:-50,
y:10,
enabled:true
},
只要使egend对象中的enabled属性设置为true就可以。设置为false,将不显示图例。
上面介绍的是图例基本的属性设置,现在我们需要模拟一些大桥传感器获得的数据,依据这些数据来显示曲线图。在这里我们首先要定义两个js函数,这三个函数就是模拟获取数据的,请看下面:
function loadTime(){
window.setTimeout(getData,TIMEOUT);
}
上面这个函数就是我们在chart对象中设置events中的load事件指定的函数。在本函数中主要启动了一个定时器,在超过指定时间计秒后,将调用函数getData。
function getFirstData(){
var data
=[];
var y_mx=Math.round(Math.random()*10);
var i;
for(i =0; i <=0; i++){
data.push({
x: current_time,
y: y_mx
});
if(current_time<=23){
current_time++;
}
}
return
data;
}
这个函数是用来模拟初始化chart对象时,获得的大桥桥面温度值。其返回的是一个数组对象,数组中的每个对象包含x、y属性,这两个属性用来告诉chart对象x轴的某个坐标上面的y值是多少,并在此处显示一个坐标点,同时将连接相邻两个坐标点形成曲线。代码中的current_time是一个全局变量,用来对当前采集显示次数进行计数。
function getData(){
var current_x=x_arr[current_time];
//获取最大值
var series_mx = chart.series[0];
var y_mx=Math.round(Math.random()*10);
series_mx.addPoint([current_time, y_mx],true,false);
current_time++;
if(current_time<=23){
window.setTimeout(getData,TIMEOUT);
}
}
上面这个函数其它部分都很简单,关键的一个重点代码是
series_mx.addPoint([current_time, y_mx],true,false);
这一段,这段代码是在chart图表中加入一个新的坐标点。
截至到目前,全部的代码就编写完成,现在运行它,就可以模拟出大桥桥面稳定24小时检测情况的实时趋势图。
其实使用highcharts来做实时趋势图简单的处理还是比较简单的,更多的应用还可以在这些基础应用上进行拓展
<script type="text/javascript">
var x_arr = [‘0:00‘, ‘1:00‘,
‘2:00‘, ‘3:00‘, ‘4:00‘, ‘5:00‘,‘6:00‘, ‘7:00‘, ‘8:00‘, ‘9:00‘, ‘10:00‘,
‘11:00‘,‘12:00‘, ‘13:00‘, ‘14:00‘, ‘15:00‘, ‘16:00‘, ‘17:00‘,‘18:00‘, ‘19:00‘,
‘20:00‘, ‘21:00‘, ‘22:00‘, ‘23:00‘];
var chart;
var
current_time = 0;
var TIMEOUT = 1000;
$(function () {
$(document).ready(function () {
chart = new
Highcharts.Chart({
chart: {
renderTo: ‘container‘,
type: ‘line‘,
marginRight: 130,
marginBottom: 80,
events: {
load: loadTime
}
},
title:
{
text: ‘大桥采集数据‘,
x: -20
},
subtitle: {
text: ‘传感器编号:
传感器‘,
x: -20
},
xAxis: {
title: {
enabled: true,
text: ‘时间(小时)‘
},
categories: x_arr,
max: 23,
min: 0,
tickPixelInterval: 50
},
yAxis: {
title: {
text: ‘压力 (℃)‘
},
plotLines: [{
value: 0,
width: 1,
color:
‘#808080‘
}]
},
tooltip: {
formatter: function () {
return ‘<b>‘ + this.series.name +
‘</b><br/>‘ +
this.x + ‘: ‘ + this.y
+ ‘℃‘;
}
},
legend: {
x: -50,
y:
10,
enabled: true
},
exporting: {
enabled: false
},
plotOptions: {
line: {
gapSize: 100
}
},
series: [{
name: ‘最大值‘,
data: getFirstData()
}]
});
});
});
function loadTime() {
window.setTimeout(getData,
TIMEOUT);
}
function getFirstData() {
var data = [];
var y_mx = Math.round(Math.random() * 10);
var i;
for (i = 0; i <= 3; i++) {
data.push({
x: current_time,
y: y_mx
});
if (current_time <= 23) {
current_time++;
}
}
return
data;
}
function getData() {
var current_x =
x_arr[current_time];
//获取最大值
var series_mx = chart.series[0];
var
y_mx = Math.round(Math.random() * 10);
series_mx.addPoint([current_time, y_mx], true, false);
current_time++;
if (current_time <= 23) {
window.setTimeout(getData, TIMEOUT);
}
}
</script>