基於 HTML5 的 Web SCADA 報表

背景

最近在一個 SCADA 項目中遇到了在 Web 頁面中展現設備報表的需求。一個完整的報表,通常包含了篩選操做區、表格、Chart、展板等多種元素,而其中的數據表格是最經常使用的控件。在以往的工業項目中,全部的表格看起來千篇一概,就是經過數字和簡單的背景顏色變化來展現相關信息。可是如今經過各類移動 App 和 Web 應用的薰陶,人們的審美和要求都在不斷提升,尤爲是在 Web 項目中,還採用老式的數字表格確實也有點落伍了。 html

如何選擇一個合適的 HTML 前端表格控件?此處能夠省略一萬字。哈哈。jQuery、Angular、React 等陣營中的控件庫中都有很多成熟案例,可是這些基於 DOM 的控件也有不足,一個是效率問題:若是在數據量很大表格的中採用自定義的單元格控件,對瀏覽器的負擔實在過重,尤爲是移動端。另外一個問題是開發效率,上述的控件庫中各自的封裝程度、接口形式都有所不一樣,但總體上仍是要求開發者對 CSS、JS 都有較深的瞭解。還有控件的複用、嵌入、發佈、移植,也都是問題。 前端

基於上面的考慮,最後採用了基於 Canvas 的 HT。經過 HT 表格控件的自定義渲染接口,以及 Web Worker 的多線程數據模擬,實現的表格控件效果以下: 數據庫

http://www.hightopo.com/demo/... 後端

SouthEast

開始

首先咱們要作的就是結合業務邏輯,對錶格中不一樣列的數據,進行不一樣的渲染。例如設備歷史信息中的運行時間、停機時間等,比較適合用餅圖來彙總展現,用戶就能夠很直觀的從列表上對比出設備的歷史情況。 咱們來看看這一步是怎樣實現的。 數組

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...)。多線程

先建立一個對象,該 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}

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

SouthEast

「統計」列的餅圖,實際上更簡單。仍是利用 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 繪製。

SouthEast

爲了運行效率考慮,在表格的單元格中繪製 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 中的數據總量改爲一萬條,單頁數量也是一萬條,進行測試:

SouthEast

出乎意料的是,HT 面對上萬數據量的複雜表格,輕鬆經受住了考驗。頁面的滾動、點擊等交互毫無影響,動態刷新沒有延遲,表格加載、排序等操做時,會有小的卡頓,在可接受的程度以內。固然也跟客戶端的機器配置有關。能夠想象,幾萬個 Chart的展現以及動態刷新,對於基於dom的控件幾乎是件沒法完成的任務。關於 HT 的其餘矢量和控件,一樣有高性能特性:http://www.hightopo.com/demo/...

後記

如前文所述,咱們基於 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 進行全局配置,在單獨文件中進行樣式的總體管理,實現外觀樣式與功能的分離,有助於工程管理。

相關文章
相關標籤/搜索