用Grails绘制实时曲线图

开发应用时,常会碰到需要通过图表显示实时数据。本文参考BjÖrn Wilmsmann的博文,以实时显示某日的最高气温和最低气温为例向大家介绍Grails下的实现方法。

所谓实时,就是当有新数据时,需要在图表中及时的显示出来。这里采用“推模式”,即新数据产生时,将数据推向前台用于展现。我们使用CometD插件CometD是一个使用Ajax实现服务器端和客户端在不可靠的网络上进行多通道异步通信的框架,具体详情可参考其文档)。为了模拟新增数据,采用quartz插件定时生成随机数据。图表显示采用dygraphs,一个开源的javascript库。

首先是准备工作:

  • 分别采用如下命令,为Grails应用安装插件:
  • grails install-plugin cometd
    grails install-plugin quartz
    
  • 在GSP中需要用到CometD的javascript,目前提供了Dojo和jQuery两种工具库,本文使用jQuery。请先下载CometD,将cometd-javascript\jquery\target\cometd-javascript-jquery-2.2.0.war中的org和jquery目录拷贝至Grails-app\web-app\js目录下。
  • Github上下载dygraphs,将其中的rgbcolor、strftime目录以及相关js拷贝至Grails-app\web-app\js\dygraph目录下,见下图:

下面要开始写代码了。首先,需要创建一个服务:ChartService,用来“推”数据:

import org.springframework.beans.factory.InitializingBean
import grails.converters.JSON
class ChartService implements InitializingBean {

    static transactional = true
    def bayeux  
    def bayeuxSession
    
    void afterPropertiesSet() {
        bayeuxSession = bayeux.newLocalSession()
        bayeuxSession.handshake()	
    }
    def pushData() {       
        //将数据“推”入通道/results/getdata
        bayeuxSession.getChannel('/results/getdata')
            .publish(['payload':['datastring':datastring]] as JSON)        
    }
}

这里需要注意,ChartService实现了Spring提供的接口InitializingBean,其中有一个afterPropertiesSet的方法,用于初始化,在注入ChartService时会调用。在该方法中我们使用bayeux与Bayeux服务器握手。findDatas为自定义方法,用于“推”数据。

接着,使用grails create-job命令创建ChartJob,用于定时产生随机数,并调用ChartService中的findDatas方法:

class ChartJob {
    def timeout = 3000l // 每3秒钟执行一次Job
    def chartService
    static String timestring="20110403"
    def execute() {
        def data=new Random();
        timestring=Integer.parseInt(timestring, 10) +1	
        chartService.pushData(timestring+","
            +data.nextInt(20-10+1)+1+","+data.nextInt(20-10+1)+1+"\n");
    }
}

接下来,就是前台获取数据了。创建cometd-subscriptions.js(详细代码参见参见BjÖrn Wilmsmann的博文)和init.js,这里详细介绍一下init.js:

//定义通道,可定义多个
var channels = ['/test', '/results', '/results/getdata'];
var datastring;
var testCallback = function() { };
var resultsCallback = function() { };
var searchResultsCallback = function(message) {
    //获取通道中的信息
    var data=JSON.parse(message.data).payload.datastring;
	datastring+=data;
	//创建Dygraph图表
    new Dygraph(document.getElementById('bordered'), datastring, {
        labelsDivStyles: { border: '1px solid black' }, 
        title: 'Demo',      
        xlabel: '日期',      
        ylabel: '温度(F)'    
    });
};
//为每个通道定义回调函数
var callbackFunctions =  { '/test' : testCallback, '/results' : resultsCallback, '/results/getdata' : searchResultsCallback };

//初始化CometD,形式为/AppContext/cometd
$.cometd.init('/chart/cometd');

//侦听连接通道
$.cometd.addListener('/meta/connect', function(message) {
    if ($.cometd.isDisconnected()){
        return;
    }
    if (message.successful) {
        //如果链接通畅,发送一条消息
        $.cometd.publish('/test', { 'data': { 'message':'Connection with CometD server has been established.' } });		
    }
});

//订阅通道
refreshCometSubscriptions(channels, callbackFunctions);

在需要显示图表的GSP页面中添加如下代码:

<g:javascript library="org/cometd" />
<g:javascript library="org/cometd/AckExtension" />
<g:javascript library="org/cometd/ReloadExtension" />
<g:javascript library="jquery/jquery-1.5.1" />
<g:javascript library="jquery/json2" />
<g:javascript library="jquery/jquery.cookie" />
<g:javascript library="jquery/jquery.cometd" />
<g:javascript library="jquery/jquery.cometd-reload" />
<g:javascript library="cometd-subscriptions" />
<g:javascript library="init" />
<g:javascript library="dygraph/dygraph-dev" />

完毕!效果图如下:

而“拉模式”,就是由前台发起请求触发后台传送消息,其实现,请参见BjÖrn Wilmsmann的原文

By huwh - Posted on 15 六月 2011