在電力、油田燃氣、供水管網等工業自動化領域 Web SCADA 的概念已經提出了多年,早些年的 Web SCADA 前端技術大部分仍是基於 Flex、Silverlight 甚至 Applet 這樣的重客戶端方案,在 HTML5 流行前 VML 和 SVG 算是真正純種 Web 方案,也有着很多應用。近些年隨着 HTML5 的流行,加上移動終端以及瀏覽器對 HTML5 支持的普及,愈來愈多新項目開始採用純 HTML5 的方案,更具體的應該說是大數據量應用性能高於 SVG 的 Canvas 方案,已經逐漸成爲當今 Web SCADA 前端技術的首選標配方案。javascript
示例圖片(圖中「發光」的部分是會閃爍的):html
這個例子我依舊是用 HT for Web 進行開發的,其中重複的部分我都封裝爲一個「圖標」,這邊說的「圖標」指的就是矢量圖標。矢量在 HT for Web 中是矢量圖形的簡稱,常見的 png 和 jpg 這類的柵格位圖, 經過存儲每一個像素的顏色信息來描述圖形,這種方式的圖片在拉伸放大或縮小時會出現圖形模糊,線條變粗出現鋸齒等問題。 而矢量圖片經過點、線和多邊形來描述圖形,所以在無限放大和縮小圖片的狀況下依然能保持一致的精確度。前端
在 HT for Web 中全部能用柵格位圖的地方均可用矢量圖形替代,例如 GraphView 組件上的圖元圖片,TreeView 和 TableView 上的圖標等, 甚至整個 HT 框架作出來的系統界面能夠實現全矢量化,這樣 GraphView 組件上的圖元縮放都不會失真,而且再也不須要爲 Retina 顯示屏提供不一樣尺寸的圖片, 在 devicePixelRatio 多樣化的移動時代, 要實現完美的跨平臺,矢量多是的最低成本的解決方案。java
在 HT 中,矢量採用 JSON 格式描述,使用方式和普通的柵格位圖一致,經過 ht.Default.setImage('hightopo', jsonObject)
進行註冊, 使用是將相應圖片註冊名設置到數據模型便可,如 node.setImage('hightopo')
和 node.setIcon('hightopo')
等。node
矢量 json 描述必需包含 width、height 和 comps 參數信息:json
同時可設置如下可選參數信息:設計模式
那麼咱們來看看這個圖標是怎麼用 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 的內容有如下幾種類型:
除了 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 的使用能夠參考這個例子www.hightopo.com/guide/guide…,全部的類型都用上了,很是實用。
我在代碼中就是經過控制這幾個綁定的屬性來改變這個數組對象的顏色的,燈要閃爍,確定會有「發光」的感受才更真實,那麼這裏還須要解釋一個內容,shadow 這個屬性,解釋爲陰影,什麼是陰影?比較好的一種解釋就是,在一個矩形框中,由矩形中心點觸發,由內至外顏色逐漸變淺,有一點像虛化,下面這張圖片就是陰影的展現:
接着是搭建場景,你們能夠直接使用「大廈.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 中
複製代碼