基於 HTML5 的 Web SCADA 報表

圖撲軟體發表於2016-12-27

背景

最近在一個 SCADA 專案中遇到了在 Web 頁面中展示裝置報表的需求。一個完整的報表,一般包含了篩選操作區、表格、Chart、展板等多種元素,而其中的資料表格是最常用的控制元件。在以往的工業專案中,所有的表格看起來千篇一律,就是通過數字和簡單的背景顏色變化來展示相關資訊。但是現在通過各種移動 App 和 Web 應用的薰陶,人們的審美和要求都在不斷提高,尤其是在 Web 專案中,還採用老式的數字表格確實也有點落伍了。 

如何選擇一個合適的 HTML 前端表格控制元件?此處可以省略一萬字。哈哈。jQuery、Angular、React 等陣營中的控制元件庫中都有不少成熟案例,但是這些基於 DOM 的控制元件也有不足,一個是效率問題:如果在資料量很大表格的中採用自定義的單元格控制元件,對瀏覽器的負擔實在太重,尤其是移動端。另一個問題是開發效率,上述的控制元件庫中各自的封裝程度、介面形式都有所不同,但整體上還是要求開發者對 CSS、JS 都有較深的瞭解。還有控制元件的複用、嵌入、釋出、移植,也都是問題。 

基於上面的考慮,最後採用了基於 Canvas 的 HT。通過 HT 表格控制元件的自定義渲染介面,以及 Web Worker 的多執行緒資料模擬,實現的表格控制元件效果如下: 

http://www.hightopo.com/demo/pagetable/index.html 

開始

首先我們要做的就是結合業務邏輯,對錶格中不同列的資料,進行不同的渲染。例如裝置歷史資訊中的執行時間、停機時間等,比較適合用餅圖來彙總展示,使用者就可以很直觀的從列表上對比出裝置的歷史狀況。 我們來看看這一步是怎樣實現的。 

HT 有自己的 DataModel 資料模型,省略了我們對資料狀態管理、時間派發、ui重新整理的開發工作。DataModel 容器中的子元素 Data,即是 HT 中最基礎的資料結構,可以對映到不同的ui控制元件上。在畫布上,Data 可以展示成向量、圖片或者文字等,在樹形控制元件上,Data 展示為樹的一個節點。在表格當中每個 Data 對應著表格中的一行 Row。 也就是表格控制元件自身包含一個 DataModel,在繪製時,將這個 Model 中的每個 Data 都繪製成一行。 不同的列,展示的是該 Data 中的不同屬性。例如我們可以把裝置的停機時間,儲存到 Data 的 stopping 屬性。 在配置表格的列 Column 資訊時,我們可以指定該列的表頭描述“停機時間”,其資料單元格對應 Data 的 Stopping 屬性,以及自定義繪製格式:

{
    name: 'stopping',       //對應的data屬性
    accessType: 'attr',
    align: 'center',        
    color: '#E2E2E2',       //文字顏色
    displayName: '停機',    //表頭描述
    drawCell: pageTable.getDrawLegend('stopping','#E2E2E2')
},

自定義渲染

在單元格的基本顯示格式中,已經預設提供了文字、陣列、顏色等型別,可以自動的對資料格式化,並展示為文字或背景顏色等,但是還未滿足我們的個性需求,因此就要將 Column 中的 drawCell 過載為自定義的渲染函式。 drawCell 的引數:function (g, data, selected, column, x, y, w, h, view),其中 g 是 Canvas 的環境資訊,data 是該行的資料體,我們根據這些資訊,再利用 HTML5 原生的 Canvas API 就可以畫出想要的效果。 

懶得親自直接用 HTML5 的原生介面? HT 提供了對 Canvas API 的封裝介面,包括各種向量型別以及一些簡單的 Chart。利用該功能,可以輕鬆組合出複雜的效果,具體介紹可以參考我們的向量手冊(http://www.hightopo.com/guide/guide/core/vector/ht-vector-guide.html)。

先建立一個物件,該 image 向量物件負責包含對組合向量的描述資訊,然後將該 image 物件以及 drawCell 的上下文資訊,作為引數傳入 ht.Default.drawStretchImage 函式,即可實現自定義繪製。

//drawCell
function (g, data, selected, column, x, y, w, h, tableView) {
    var value = data.a(attr);
    var image = {
        width: 60,
        height: 30,
        comps: [
            {
                type: 'rect',
                rect: [11,11,8,8],
                borderWidth: 1,
                borderColor: '#34495E',
                background: legendColor,
                depth: 3
            },
            {
                type: 'text',
                text: value,
                rect:[30, 0, 30, 30],
                align: 'left',
                color: '#eee',
                font: 'bold 12px Arial'
            }
        ]};
    ht.Default.drawStretchImage(g, image, 'centerUniform', x, y, w, h);
}

因為有多個 Legend 圖例顯示的列,所以我們可以簡單包裝一下,用了一個 getDrawLegend 函式,引數是該列圖例的顏色及 Data 屬性名稱,返回值是 drawCell 函式。

getDrawLegend: function(attr,legendColor){return drawCell}

至此,我們就完成了啟停時間這幾列的自定義繪製: 

“統計”列的餅圖,實際上更簡單。還是利用 HT 的向量介面,把上述幾項時間資料傳入餅圖向量結構即可。

var values = [
    data.a('running'), 
    data.a('stopping'), 
    data.a('overhauling')
];
var image = {
    width: 200,
    height: 200,
    comps: [
        {
            type: 'pieChart',
            rect: [20,20, 150, 150],
            hollow: false,
            label: false,
            labelColor: 'white',
            shadow: true,
            shadowColor: 'rgba(0, 0, 0, 0.8)',
            values: values,
            startAngle: Math.PI,
            colors: pieColors
        }
    ]
};

其他列的渲染過程大同小異。在“風速”列中,我們可以根據風速大小計算一個顏色透明值,來實現同一色系的對映變換,比原來那種非紅即綠的報警表,看起來更舒服一些。在“可用率”列,用 Rect 的不同長度變化,來模擬進度條的效果。在功率曲線中稍微有點不同,因為想實現曲線覆蓋區域的顏色漸變,在 HT 的 lineChart 中沒有找到相關介面,所以直接採用了 Canvas 繪製。 

為了執行效率考慮,在表格的單元格中繪製 Chart,應該追求簡潔大方,一目瞭然。這幾個 Legend 圖例小矩形,其實是應該畫在表頭的。我為了偷懶,就畫在了單元格,導致畫面顯得有點亂。

Web Worker

眾所周知,瀏覽器的 JS 環境是基於單程式的,在頁面元素較多,而且有很大運算需求的情況下,會導致無法兼顧渲染任務和計算任務,造成頁面卡頓或失去響應。在這種情況,可以考慮使用 Web Worker 的多執行緒,來分擔一些計算任務。 

Web Worker 是 HTML5 的多執行緒 API,和我們原來傳統概念中的多執行緒開發有所不同。Web Worker 的執行緒之間,沒有記憶體共享的概念,所有資訊互動都採用 Message 的非同步傳遞。這樣多執行緒之間無法訪問對方的上下文,也無法訪問對方的成員變數及函式,也不存在互斥鎖等概念。在訊息中傳遞的資料,也是通過值傳遞,而不是地址傳遞。 

在 Demo 中,我們利用 Web Worker 作為模擬後端,產生虛擬資料。並採用前端分頁的方式,從 worker 獲取當前頁顯示條目的相關資料。 在主執行緒中,建立 Web Worker註冊訊息監聽函式。

worker = new Worker("worker.js");    
worker.addEventListener('message', function(e) {  
    //收到worker的訊息後,重新整理表格
    pageTable.update(e.data);
});

pageTable.request = function(){
    //向worker傳送分頁資料請求
    worker.postMessage({
        pageIndex: pageTable.getPageIndex(),
        pageRowSize: pageTable.getPageRowSize()        
    });                 
}; 
pageTable.request();

本處的new Worker建立,對於主執行緒來說是非同步的,等載入完 worker.js,並完成初始化後,該 worker 才是真正可用狀態。我們不需要考慮 worker 的可用狀態,可以在建立語句後直接傳送訊息。在完成初始化之前向其傳送的請求,都會自動儲存在主執行緒的臨時訊息佇列中,等 worker 建立完成,這些資訊會轉移到 worker 的正式訊息佇列。 

在 worker 中,創造虛擬隨機資料,監聽主執行緒訊息,並返回其指定的資料。

self.addEventListener('message', function(e) {
    var pageInfo = getPageInfo(e.data.pageIndex, e.data.pageRowSize);   
    self.postMessage(pageInfo);
}, false);

由於前面提到的無法記憶體共享,Web Worker 無法操作 Dom,也不適用於與主執行緒進行大資料量頻繁的互動。那麼在生產環境中,Web Worker 能發揮什麼作用?在我們這種應用場景,Web Worker 適合在後臺進行資料清洗,可以對從後端取到的裝置歷史資料進行插值計算、格式轉換等操作,再配合上 HT 的前端分頁,就能實現大量資料的無壓力展示。

分頁

傳統上有後端分頁和前端分頁,我們可以根據實際專案的資料量、網速、資料庫等因素綜合考慮。 

採用後端分頁的話,可以簡化前端架構。缺點是換頁時會有延遲,使用者體驗不好。而且在高併發的情況下,頻繁的歷史資料查詢會對後端資料庫造成很大壓力。 

採用前端分頁,需要擔心的是資料量。整表的資料量太大,會造成第一次獲取時的載入太慢,前端資源佔用過多。 

在本專案中,得益於給力的 GOLDEN 實時資料庫,我們可以放心的採用前端分頁。歷史資料插值、統計等操作可以在資料庫層完成,傳遞到前端的是初步精簡後的資料。在數千臺裝置的歷史查詢中,得到的資料量完全可以一次傳送,再由前端分頁展示。 

在某些應用場景,我們會在表格中顯示一些實時資料,這些資料是必須是動態獲取的。類似在 Demo 中的趨勢重新整理效果,我們可以在建立表格時批量獲取所有歷史資料,然後再動態向資料庫獲取當前頁所需的實時資料。如果網速實在不理想,也可以先只獲取第一頁的歷史資料,隨後在後臺執行緒慢慢接收完整資料。 

這樣的架構實現了海量資料的快速載入,換頁操作毫無延遲,當前頁面元素實時動態重新整理的最終效果。 

還有一些傳統客戶,喜歡在一張完整的大表上進行資料篩選、排序等操作。 

我們可以把 Demo 中的資料總量改成一萬條,單頁數量也是一萬條,進行測試: 

出乎意料的是,HT 面對上萬資料量的複雜表格,輕鬆經受住了考驗。頁面的滾動、點選等互動毫無影響,動態重新整理沒有延遲,表格載入、排序等操作時,會有小的卡頓,在可接受的程度之內。當然也跟客戶端的機器配置有關。可以想象,幾萬個 Chart的展示以及動態重新整理,對於基於dom的控制元件幾乎是件無法完成的任務。關於 HT 的其他向量和控制元件,同樣有高效能特性:http://www.hightopo.com/demo/fan/index.html

後記

如前文所述,我們基於 HT 的表格實現了海量資料的可定製展現,並取得了令人滿意的效果。以下是一些還可以改進的地方。 

在 Demo 中,通過對 HT 表格控制元件的 drawCell 進行過載,實現了自定義渲染,然後把這些 drawCell 放到了 PageTable 的原型函式中,以供 Column 呼叫。實際上,更好的辦法應該是把這些常見的 Chart、圖例封裝到 Column 的基本型別中,那樣在配置表格 Column 列時,可以指定 type 為 pieChart 或 lineChart 即可,不需再自行繪製相關向量。 

對於這些表格中的 Chart,也可以增加一些互動介面,例如可以增加單元格 Tooltip 的自定義渲染功能,在滑鼠停留時浮出一個資訊量更大的 Chart,可以對指定裝置進行更深入的瞭解。 
介面美觀優化。對 HT 的控制元件進行顏色定製,可以通過相關介面進行配置:

var tableHeader = pageTable.getTablePane().getTableHeader();    
tableHeader.getView().style.backgroundColor = 'rgba(51,51,51,1)';     
tableHeader.setColumnLineColor('#777');
var tableView = pageTable.getTablePane().getTableView();                 
tableView.setSelectBackground('#3D5D73');
tableView.setRowLineColor('#222941');
tableView.setColumnLineVisible(false);                
tableView.setRowHeight(30);

今後也可以對htconfig進行全域性配置,在單獨檔案中進行樣式的整體管理,實現外觀樣式與功能的分離,有助於工程管理。

 

相關文章