最近在一個 SCADA 項目中遇到了在 Web 頁面中展現設備報表的需求。一個完整的報表,通常包含了篩選操做區、表格、Chart、展板等多種元素,而其中的數據表格是最經常使用的控件。在以往的工業項目中,全部的表格看起來千篇一概,就是經過數字和簡單的背景顏色變化來展現相關信息。可是如今經過各類移動 App 和 Web 應用的薰陶,人們的審美和要求都在不斷提升,尤爲是在 Web 項目中,還採用老式的數字表格確實也有點落伍了。 html
如何選擇一個合適的 HTML 前端表格控件?此處能夠省略一萬字。哈哈。jQuery、Angular、React 等陣營中的控件庫中都有很多成熟案例,可是這些基於 DOM 的控件也有不足,一個是效率問題:若是在數據量很大表格的中採用自定義的單元格控件,對瀏覽器的負擔實在過重,尤爲是移動端。另外一個問題是開發效率,上述的控件庫中各自的封裝程度、接口形式都有所不一樣,但總體上仍是要求開發者對 CSS、JS 都有較深的瞭解。還有控件的複用、嵌入、發佈、移植,也都是問題。 前端
基於上面的考慮,最後採用了基於 Canvas 的 HT。經過 HT 表格控件的自定義渲染接口,以及 Web Worker 的多線程數據模擬,實現的表格控件效果以下: 數據庫
http://www.hightopo.com/demo/... 後端
首先咱們要作的就是結合業務邏輯,對錶格中不一樣列的數據,進行不一樣的渲染。例如設備歷史信息中的運行時間、停機時間等,比較適合用餅圖來彙總展現,用戶就能夠很直觀的從列表上對比出設備的歷史情況。 咱們來看看這一步是怎樣實現的。 數組
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}
至此,咱們就完成了啓停時間這幾列的自定義繪製:
「統計」列的餅圖,實際上更簡單。仍是利用 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 圖例小矩形,實際上是應該畫在表頭的。我爲了偷懶,就畫在了單元格,致使畫面顯得有點亂。
衆所周知,瀏覽器的 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/...
如前文所述,咱們基於 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 進行全局配置,在單獨文件中進行樣式的總體管理,實現外觀樣式與功能的分離,有助於工程管理。