基於 HTML5 的電力接線圖 SCADA 應用


在電力、油田燃氣、供水管網等工業自動化領域 Web SCADA 的概念已經提出了多年,早些年的 Web SCADA 前端技術大部分還是基於 Flex、Silverlight 甚至 Applet 這樣的重客戶端方案,在 HTML5 流行前 VML 和 SVG 算是真正純種 Web 方案也是有不少應用,近些年隨着 HTML5 的流行,加上移動終端以及瀏覽器對 HTML5 支持的普及,越來越多新項目開始采用真正純 HTML5 的方案,更具體的應該說是大數據量應用性能高於 SVG 的 Canvas 方案,已經逐漸成為當今 Web SCADA 前端技術的首選標配方案。

例子代碼下載:http://www.hightopo.com/demo/electric-bling/index.html

示例圖片(圖中“發光”的部分是會閃爍的):

這個例子我依舊是用 HT for Web 進行開發的,其中重復的部分我都封裝為一個“圖標”了,這邊說的“圖標”指的就是矢量圖標。矢量在 HT for Web 中是矢量圖形的簡稱,常見的 png 和 jpg 這類的柵格位圖, 通過存儲每個像素的顏色信息來描述圖形,這種方式的圖片在拉伸放大或縮小時會出現圖形模糊,線條變粗出現鋸齒等問題。 而矢量圖片通過點、線和多邊形來描述圖形,因此在無限放大和縮小圖片的情況下依然能保持一致的精確度。

在 HT for Web 中所有能用柵格位圖的地方都可用矢量圖形替代,例如 GraphView 組件上的圖元圖片,TreeView 和 TableView 上的圖標等, 甚至整個 HT 框架做出來的系統界面可以實現全矢量化,這樣 GraphView 組件上的圖元縮放都不會失真,並且不再需要為 Retina 顯示屏提供不同尺寸的圖片, 在 devicePixelRatio 多樣化的移動時代, 要實現完美的跨平台,矢量可能是的最低成本的解決方案。

在 HT 中,矢量采用 JSON 格式描述,使用方式和普通的柵格位圖一致,通過 ht.Default.setImage('hightopo', jsonObject) 進行注冊, 使用是將相應圖片注冊名設置到數據模型即可,如 node.setImage('hightopo') 和 node.setIcon('hightopo') 等。

矢量 json 描述必需包含 width、height 和 comps 參數信息:

  • width 矢量圖形的寬度
  • height 矢量圖形的高度
  • comps 矢量圖形的組件 Array 數組,每個數組對象為一個獨立的組件類型,數組的順序為組件繪制先后順序

同時可設置以下可選參數信息:

  • visible 是否可見,默認為 true
  • opacity 透明度,默認為 1,可取值范圍 0~1
  • color 染色顏色,設置上該顏色后矢量內部繪制內容將會融合該染色值
  • clip 用於裁剪繪制區域,可設置兩種類型:boolean
    • boolean 類型,控制繪制時超出 width 和 height 區域的內容是否被裁剪,默認為 false 不裁剪
    • function 類型,可利用 canvas 畫筆繪制,實現自定義裁剪任意形狀的效果

那么我們來看看這個圖標是怎么用 HT 繪制的:

從圖片上可以看出來,這個圖標由一條直線、一個矩形以及一個箭頭組成,我們把這個圖標取名為 arrow:

ht.Default.setImage('arrow', {//注冊圖片 arrow
    "width": 60,//矢量圖形的寬度
    "height": 30,//矢量圖形的高度
    "comps": [//矢量圖形的組件 Array 數組,每個數組對象為一個獨立的組件類型,數組的順序為組件繪制先后順序
        {//繪制直線部分
            "type": "shape",//多邊形
            "borderWidth": 1,//邊框寬度
            "borderColor": "rgb(255,0,0)",//邊框顏色
            "points": [//點信息 points [x1, y1, x2, y2, x3, y3, ...] 的方式存儲點坐標
                1.4262,
                14.93626,
                51.46768,
                14.93626
            ]
        },
        {//繪制箭頭尖的部分
            "type": "shape",
            "borderWidth": 1,
            "borderColor": "rgb(255,0,0)",
            "rotation": 4.71239,//旋轉
            "points": [
                45.50336,
                9.63425,
                52.88591,
                13.92955,
                60.26846,
                9.63425,
                52.88591,
                20.23827,
                45.50336,
                9.63425
            ]
        },
        {//繪制矩形部分
            "type": "rect",//矩形
            "background": {//背景顏色
                "func": "attr@lightBg",
                "value": "rgb(255,0,0)"
            },
            "borderColor": "#979797",
            "shadow": {//陰影
                "func": "attr@shadow",
                "value": false
            },
            "shadowColor": {//陰影顏色
                "func": "attr@shadowColor",
                "value": "rgba(255,0,0,0.7)"
            },
            "shadowBlur": 32,//陰影模糊
            "shadowOffsetX": 0,//陰影橫偏移
            "shadowOffsetY": 0,//陰影縱偏移
            "rect": [//指定矩形的區域 [x, y, width, height] 起始位置的坐標和矩形的大小值 
                11.44694,
                10.43626,
                30,
                9
            ]
        }
    ]
});

每一個數組對象中的基本類型與 style 的 shape 參數是完全一一對應, 只是將 style 中的名稱改成駱駝式命名法去掉了.分隔符,查找對應的 style 屬性請參考 HT for Web 風格手冊,有些后期添加的屬性可能在風格手冊中還沒有添加,大家只要知道這么一個屬性就行了,一般看屬性名就知道這個屬性的功能了。

上面代碼中有一段可能讓大家疑惑的點我沒有在代碼中解釋,接下來我們着重來講一下這個部分的內容:數據綁定。從文章一開始的圖片我們知道,這個圖標中的矩形部分是會變顏色的。

數據綁定意味將 Data 圖元的數據模型信息,與界面圖形的顏色、大小和角度等可視化參數進行自動同步, HT 的預定義圖形組件默認就已與 DataModel 中的 Data 數據綁定,例如用戶修改 Node 的 position 位置值, 則 GraphView 和 Graph3dView 上的相應圖元位置會自動同步變化。

傳統的數據綁定有單向綁定和雙向綁定的概念,但 HT 系統的設計模式使得綁定更加簡化易於理解,HT 只有一個 DataModel 數據模型, 綁定 DataModel 的圖形組件並沒有組件內部的其他數據模型,所以組件都是如實根據 DataModel 來呈現界面效果,因此當用戶拖拽圖元移動時, 本質也是修改了數據模型中 Node 的 position 位置值,而該屬性變化觸發的事件通過模型再次派發到圖形組件,引發圖形組件根據新的模型信息刷新界面。

綁定的格式很簡單,只需將以前的參數值用一個帶 func 屬性的對象替換即可,func 的內容有以下幾種類型:

  • function 類型,直接調用該函數,並傳入相關 Data 和 view 對象,由函數返回值決定參數值,即 func(data, view) 調用。
  • string 類型:
    • func@*** 開頭,則返回 data.getStyle(***) 值,其中 *** 代表 style 的屬性名
    • attr@*** 開頭,則返回 data.getAttr(***) 值,其中 *** 代表 attr 的屬性名
    • field@*** 開頭,則返回 data.*** 值,其中 *** 代表 data 的屬性名
    • 如果不匹配以上情況,則直接將 string 類型作為 data 對象的函數名調用 data.***(view),返回值作為參數值

除了 func 屬性外,還可設置 value 屬性作為默認值,如果對應的 func 取得的值為 undefined 或 null 時,則會采用 value 屬性定義的默認值。 例如以下代碼,如果對應的 Data 對象的 attr 屬性 lightBg 為 undefined 或 null 時,則會采用 rgb(255, 0, 0) 顏色:

"background": {//背景顏色
    "func": "attr@lightBg",// 返回的是 getAttr('lightBg')的值
    "value": "rgb(255,0,0)"//設置默認值
}

同理,上面代碼中的 shadow 和 shadowColor 也都是以這種方式來進行數據綁定的,綁定的數據只與這個數組對象部分有關,所以就算這個圖標是一張圖片,我們還是能單獨控制局部改變顏色等等。想了解所有的 func 的使用可以參考這個例子http://www.hightopo.com/guide/guide/core/databinding/examples/example_piebinding.html,所有的類型都用上了,非常實用。

我在代碼中就是通過控制這幾個綁定的屬性來改變這個數組對象的顏色的,燈要閃爍,肯定會有“發光”的感覺才更真實,那么這里還需要解釋一個內容,shadow 這個屬性,解釋為陰影,什么是陰影?比較好的一種解釋就是,在一個矩形框中,由矩形中心點觸發,由內至外顏色逐漸變淺,有一點像虛化,下面這張圖片就是陰影的展示:

接着是搭建場景,大家可以直接使用 lightBling/displays/電力 下的 大廈.json 文件,在這個文件中,我設置了部分的“箭頭”圖標的 tag 標簽。在 HT 中,一般建議 id 屬性由 HT 自動分配,用戶業務意義的唯一標示可存在 tag 屬性上,通過 Data#setTag(tag) 函數允許任意動態改變 tag 值, 通過 DataModel#getDataByTag(tag) 可查找到對應的 Data 對象,並支持通過 DataModel#removeDataByTag(tag) 刪除 Data 對象。

不過我是直接在 json 中添加 “tag” 屬性,具體的 json 拓撲結構說明如下:

我們用到的 大廈.json,我拿一部分出來解析一下:

{
    "c": "ht.Node",//類名,用來反序列化
    "i": 274997,//id 值
    "p": {//get/set 類型屬性 這里面的所有屬性都可通過 get/set獲取和設置
        "displayName": "燈-紅",//顯示名稱
        "tag": "alarm",//標簽 可通過 getTag 和 setTag 來獲取和設置
        "image": "symbols/隧道用圖標/交通燈/燈/燈-紅.json",//圖片 引用的路徑為相對路徑 這邊調用的“紅燈”圖標的 json 文件
        "position": {//坐標
          "x": 70.9971,
          "y": 47.78651
        }
    },
    "s": {//對應 setStyle 屬性
        "2d.movable": false,//2d 下不可以動 若要開啟,直接設置 setStyle('2d.movable', true) 即可,下面同理
        "2d.editable": false//2d 下不可編輯
    }
}

其實整個不需要動畫的部分都是 json 文件中的內容,大家可以根據上面的 json 拓撲結構來解析圖紙的 json。那么問題來了,如何在 GraphView 中載入圖紙的 json 文件?HT 封裝了 ht.Defautl.xhrLoad 函數用來將對應的圖紙 json 載入到 GraphView 上,參數為 text 文本,需要通過 ht.Default.parse 轉義成 json:

ht.Default.xhrLoad('displays/電力/大廈.json', function(text){
    var json = ht.Default.parse(text);
    window.gv.dm().deserialize(json);//反序列化,並將反序列化的對象加入 DataModel
});

此時,DataModel 中的內容就是這個 json 文件反序列化出來的所有圖元了,所以我們可以通過 DataModel 任意獲取和改變 json 中的圖元的樣式和屬性。其中,setAttr/getAttr 中的屬性我們必須先定義一下,不然 HT 又不知道這個節點是否有這個用戶自定義的屬性:

for(var i = 0; i < gv.dm().size(); i++){
    var data = gv.dm().getDatas().get(i);//獲取 datamodel 中的對應 i 的節點
    if(data.getTag()){//如果這個節點有設置 tag 值
        data.a('shadow', false);//設置自定義屬性,並且給一個值
        data.a('shadowColor', 'rgba(255,0,0,0.9)');
        data.a('lightBg', 'rgb(255, 0, 0)');
        data.a('alarmColor', 'red');
    }
}

這下我們就可以任意操作上面已經聲明過的屬性了,當然,HT 默認的屬性我們也能任意操作!我在 json 文件中設置了幾個 tag 標簽,light1~light15 以及 alarm 標簽,我們可以通過 data.getTag() 來獲取這個節點對應的標簽名,或者通過 dataModel.getDataByTag(tagName) 已知標簽名來獲取對應節點。

動畫的部分 HT 有三種動畫的方式,針對的點不同,這里我用到的是 schedule 主要用於在指定的時間間隔進行函數回調處理。HT 中調度進行的流程是,先通過 DataModel 添加調度任務,DataModel 會在調度任務指定的時間間隔到達時, 遍歷 DataModel 所有圖元回調調度任務的 action 函數,可在該函數中對傳入的 Data 圖元做相應的屬性修改以達到動畫效果。

以下是我例子中的動畫相關代碼:

var blingTask = {
    interval: 1000,//間隔毫秒數
    action: function(data){//間隔動作函數
        if(data.getTag() === 'light1' || data.getTag() === 'light13' || data.getTag() === 'light5' || data.getTag() === 'light6' || data.getTag() === 'light10' || data.getTag() === 'light11' || data.getTag() === 'light12' || data.getTag() === 'light14' || data.getTag() === 'light15'){
            if(data.a('lightBg') === 'rgb(255, 0, 0)'){//如果屬性lightBg值為這個,就做以下一系列的動作 
                data.a('lightBg', 'rgb(0, 255, 0)');
                data.a('shadow', true);
                data.a('shadowColor', 'rgba(0, 255, 0, 0.9)');
            }else if(data.a('lightBg') === 'rgb(0, 255, 0)'){
                data.a('lightBg', 'rgb(255, 255, 0)');
                data.a('shadow', true);
                data.a('shadowColor', 'rgba(255, 255, 0, 0.9)');
            }else{
                data.a('lightBg', 'rgb(255, 0, 0)');
                data.a('shadow', true);
                data.a('shadowColor', 'rgba(255, 0, 0, 0.9)');
            }
        }else if(data.getTag() === 'alarm'){
            if(data.a('alarmColor') === 'red'){
                data.a('alarmColor', 'rgb(0, 255, 0)');
            }else{
                data.a('alarmColor', 'red');
            }
        }
    }
};
window.gv.dm().addScheduleTask(blingTask);//添加動畫進 DataModel 中

其他部分我相信大家都能看得懂,實在不就去官網(或者這個鏈接也行,里面還有常見問題)查查對應的文檔,寫得很清楚哦~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM