在眾多的工業控制系統領域常常會即時採集現場的溫度、壓力、扭矩等資料,這些資料對於監控人員進行現場Threat Detection Service、進行未來趨勢預測具有重大指導價值。工程式控制制人員如果只是閱讀海量的資料報表,對於現場整個態勢的掌控會十分困難,因此往往希望藉助一些圖表進行展示,其中趨勢圖是常用的即時資料展示方式之一。目前實現趨勢圖、曲線圖的工具很多也很成熟,一些是通過CS模式開發的,需要在工程式控制制人員操作的電腦上安裝相應軟體,這種方式有其特有的優勢,但是有時也有不方便的地方。於是一些基於BS模式的展現方式就自然而然的被廣泛應用起來。採用BS模式的展現方式工程式控制制人員可以通過任何一台可以串連Web伺服器的PC,通過常用的瀏覽器就可以即時的查看當前環境現場的各種指標參數,其便利性是顯而易見的。下面我就介紹一下這種BS模式的即時資料展現曲線圖的方法。由於本文的目的不是去實現一個現場環境可用的應用產品,因此只是針對涉及的技術進行講解,起一個指導作用,因此參考者請依據自己項目的實際需要對本文章涉及的代碼進行最佳化使用。
在通過BS系統實現趨勢圖、曲線圖的可選方案很多,本文主要通過Highcharts.com旗下的Highcharts API包來實現。由於此包是通過JS指令碼實現的,因此個人認為相對來說適應面可能更好,當然通過諸如jFreeChart這類工具也是可以實現的。
言歸正傳,我們來講解如何通過Highcharts API實現趨勢圖在頁面上的呈現。
首先我們需要簡單規劃一下我們系統的架構。由於實現即時資料的趨勢圖呈現,因此系統大致我們可以設計成三層:顯示層、邏輯層、資料來源。如下圖所示:
顯示層就是將資料呈現給工程式控制制人員的的展示頁面,這層主要由JSP、JS、CSS等檔案構成,工程式控制制人員通過瀏覽器(諸如IE、Firefox等)就能直接看到希望的曲線圖。顯示層只負責顯示,而顯示需要的資料是經過一定清洗、規格化的,顯示層拿到符合規格化要求的資料後,就可以直接進行顯示,並響應和人的互動。資料的清洗、規格化工作都是在邏輯層中實現,邏輯層通過擷取的資料來源資訊,進行必要的資料邏輯轉換、資料清洗、資料規格化處理。資料來源是一個複雜的重要的,它可以是直接來自下位機的資料通訊,也可以是下位機將資料存放區在中間資料庫中,也可以是一系列的資料檔案。
在本文中只是類比資料源,並不是實現資料來源的讀取。並且對應邏輯層的處理,也進行了忽略,這部分內容因為涉及具體的資料擷取、清洗、轉換、規格化,和具體工程項目的需求有較大關係,加之也不是本文規劃的中心,因此此部分代碼設計實現本文也不涉及。
顯示層的實現涉及到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檔案,應用代碼參考如下:
<script type="text/javascript" src="http://zhaowenbinmail.blog.163.com/blog/js/jquery-1.8.3.min.js"></script>
<script src="http://zhaowenbinmail.blog.163.com/blog/js/highcharts.js"></script> 第二,在jsp頁面的Body體中加入一個div元素,highcharts將在這個div元素中繪製曲線圖。
<div id="container" style="min-width: 100px; height: 400px; margin: 0 auto"></div> 注意:我們為這個div指定了一個id值,這個id值將來對我們很有用,它是使highcharts知道在何處繪製圖表的根源。
第三,我們需要在jsp頁面中加入我們自己的js檔案。這個檔案用來實現特定的業務呈現邏輯。
<script type="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 = new Highcharts.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來做即時趨勢圖簡單的處理還是比較簡單的,更多的應用還可以在這些基礎應用上進行拓展。