基於 HTML5 WebGL 的 水泥工廠可視化系統
- 2020 年 4 月 13 日
- 筆記
前言
如今的製造行業,基於數據進行生產策略制定與管理已經成為一種趨勢,特別是 工業4.0 的浪潮下,數據戰略已經成為很多製造企業的優先戰略,而數據可視化以更直觀的方式,幫助指導決策,成為數據分析傳遞資訊的重要工具。通過數據可視化系統助力實現數據驅動的工業世界,為 工業4.0 提供更加靈活、敏捷、高效、個性化的數據支撐。今天就給大家帶來一個採用 Hightopo 的 HT for Web 產品實現了一個水泥工廠可視化系統。
系統預覽




本案例共有七個子系統:
- 數據概況 -- 展示全廠年月時間單位的各項數據概況
- 窯系統運行 -- 用窯製程流程動畫展示窯系統實時運行狀態
- 系統運行情況 -- 用動畫流程圖展示整個系統運行情況
- 生料品質控制 -- 用圖表和流程圖展示各種生料的配比情況
- 熟料品質控制 -- 用動畫流程圖展示各種熟料的配比情況
- 煤粉品質控制 -- 用圖表和流程圖對煤粉品質進行監控
- 智慧物流 -- 通過 3D 場景實時監控進出廠車輛,和各項原料運輸情
子系統頁面切換

切換不同子系統時,左側菜單和頂部標題是不需要切換的,所以我們把需要切換的內容部分別放在不同的 Block 中,Block 類型,本身不繪製任何內容,用於作為其它節點的父節點,可以與子節點同步大小,當它隱藏或顯示時,所有子節點都會跟著隱藏或顯示。所以當我們切換子系統時只需要控制對應的 Block 顯示隱藏,而不需要去載入切換多張圖紙。
流向地圖

在數據概況頁面中,流向地圖展示年度水泥向各地區的銷售情況,這裡我們用 Shape 類型繪製線段來連接源地和匯地,用流動效果表示銷售關係。流動效果只需引入 HT 的 ht-flow.js 插件,即可通過簡單的屬性設置實現,程式碼如下:
// 獲取線段的父節點
this.flowParent = dm.getDataByTag('saleFlowParent');
// 遍歷得到所有線段
this.flowParent.eachChild(child => {
// 開啟流動,設置流動樣式
child.s({
// 開啟流動
'flow': true,
// 設置流動組中最大元素的尺寸
'flow.element.max': 4,
// 設置流動組中的元素的漸變陰影中心顏色
'flow.element.shadow.begincolor': '#49e5fe',
// 設置流動組中的最大元素的漸變陰影尺寸
'flow.element.shadow.max': 16,
// 設置流動組中的元素的漸變陰影邊緣顏色
'flow.element.shadow.endcolor': 'rgba(73, 229, 254, 0)',
});
});
窯系統動畫

在窯系統運行頁面中,窯製程流程動畫很直觀的展示了窯系統實時運行狀態。畫面中火焰、水和熟料在傳送帶上運輸的動畫效果,為了在性能較差的設備上也能流暢運行,我通過切換不同矢量圖形的方式實現。這裡用到了 HT 矢量中狀態機制,先繪製多個不同的矢量組件,每個組件都可以定義狀態來決定自己在哪個狀態下顯示,只要通過 data.s('state') 修改節點狀態就可以實現如下效果:

使用一個定時器,不斷地改變節點的狀態值,相關程式碼如下:
this._stateTimer = setInterval(() => {
stateNodes.forEach(node => {
this.stateAnimation(node);
});
}, 180);
//切換狀態
stateAnimation(node) {
let stateIndex = (node.a('stateIndex') || 0) % stateEnum.length,
state = stateEnum[stateIndex].value;
node.s('state', state);
node.a('stateIndex', ++stateIndex);
}
流程圖動畫

流程圖中流動線同樣是使用 ht-flow.js 插件實現。由於圖紙上的線段比較多,我把不同的線段分組放在不同的 Block 下,遍歷其子節點設置樣式,程式碼如下:
//設置流動屬性
setNodeFlow (data, value) {
if (data instanceof ht.Block) {
data.eachChild(child => {
this.setNodeFlow(child, value);
});
}
else if (data.getDisplayName() === 'line'){
data.s({
'flow': value,
'flow.element.max': 4,
'flow.element.count': 1,
'flow.count': 5,
'flow.step': 10
});
}
}
//設置虛線流動屬性
setNodeDashFlow(data, value) {
if (data instanceof ht.Block) {
data.eachChild(child => {
this.setNodeDashFlow(child, value);
});
}
else if (data.getDisplayName() === 'border'){
if (value) {
data.s({
'shape.dash.flow': true,
'shape.dash': true
});
}
else {
data.s({
'shape.dash.flow': false,
'shape.dash': false
});
}
}
}
為了使動畫看起來更順暢,我給一些節點加上透明度動畫,設置節點透明度的程式碼如下:
//設置節點透明度
setNodeOpacity (data, value = 0.5) {
if (data instanceof ht.Block) {
data.eachChild(child => {
this.setNodeOpacity(child, value);
});
}
else {
data.s('opacity', value);
}
}
接下來只需要依次執行動畫:
//開始流程圖動畫
start() {
let {eo, eoInput, eoLine1, eoKind, eoCalu} = this;
//工況輸入透明度動畫
this.gv.enableFlow(30);
this.setNodeOpacity(eo);
this.setNodeFlow(eo, false);
(new Promise((resolve, reject) => {
this.animtion = startAnim({
frames: 16,
interval: 5,
finishFunc: () => {resolve()},
action: (v, t) => {
this.setNodeOpacity(eoInput, 0.5 + 0.5 * v);
}
});
})).then(() => {
//連線連線透明動畫,流動
return new Promise((resolve, reject) => {
this.animtion = startAnim({
frames: 12,
interval: 10,
finishFunc: () => {
this.setNodeFlow(eoLine1, true);
this.timer = setTimeout(() => {resolve()}, 1500);
},
action: (v, t) => {
this.setNodeOpacity(eoLine1, 0.5 + 0.5 * v);
}
});
})
}).then(() => {
//軟計算透明動畫
return new Promise(resolve => {
this.animtion = startAnim({
frames: 16,
interval: 5,
finishFunc: () => {resolve()},
action: (v, t) => {
this.setNodeOpacity(eoKind, 0.5 + 0.5 * v);
this.setNodeOpacity(eoCalu, 0.5 + 0.5 * v);
}
});
});
}).then(() => {
//軟計算透明虛線流動
return new Promise(resolve => {
this.setNodeDashFlow(eoKind, true);
this.setNodeDashFlow(eoCalu, true);
this.timer = setTimeout(() => {
this.setNodeDashFlow(eoKind, false);
this.setNodeDashFlow(eoCalu, false);
resolve();
}, 3000);
});
}).then(() => {
......
})
}
智慧物流
前面六個子系統均為 2D 介面,而智慧物流頁面則是嵌入了一個 3D 場景。實現方式是通過定義 HT 矢量 JSON 的 renderHTML 函數屬性,可實現在 GraphView 拓撲圖上,嵌入任意第三方 HTML DOM 元素。不過這裡也要注意一點,HT 的圖紙是 Canvas 實現的,renderHTML 的 DOM 元素一定在 Canvas 之上,使用 renderHTML 的 DOM 與常規 Canvas 上繪製的圖元不可能有層級控制可能性。下面展示一下 renderHTML 函數屬性里的程式碼:
renderHTML : function (data, gv, cache) {
// 避免重複創建g3d
if (!cache.g3d) {
// 創建 3D 視圖組件
var g3d = cache.g3d = new ht.graph3d.Graph3dView();
// 布局函數,根據圖元的位置資訊擺放HTML元素
g3d.layoutHTML = function () {
gv.layoutHTML(data, g3d, true);
};
// 阻止事件冒泡
g3d.getView().addEventListener('mousedown', function (event) {
event.stopPropagation();
});
g3d.getView().addEventListener('touchstart', function (event) {
event.stopPropagation();
});
}
// 獲取圖元自定義屬性sceneURL的值
var sceneURL = data.a('sceneURL');
// 獲取圖元自定義屬性onPostDeserialize的值
var onPostDeserialize = data.a('onPostDeserialize');
// 當圖元自定義屬性sceneURL改變時,清除舊dataModel,反序列化新的sceneURL
if (cache.g3d.sceneURL !== sceneURL) {
cache.g3d.dm().clear();
cache.g3d.sceneURL = sceneURL;
if (sceneURL) {
cache.g3d.deserialize(sceneURL, function (json, dm, g3d, datas) {
// 在反序列化後的回調函數中,執行onPostDeserialize函數
onPostDeserialize && onPostDeserialize(json, dm, g3d, datas);
});
}
}
return cache.g3d;
}

3D場景嵌入後,接下來實現水泥廠內的車輛動畫。根據後台傳來車輛進入工廠的數據,我們創建運載不同原料的車輛模型,讓它們沿著不同的路徑抵達對應的廠房。同樣是用 Shape 類型事先繪製好路徑,根據 Shape 的 Points 和 Segments 資訊,實現車輛沿著路徑行駛動畫。相關程式碼如下:
carAnimation(car, path, duration) {
// 車輛行駛動畫
ht.Default.startAnim({
duration: duration,
easing: Easing.easeNone,
action: function (v, t) {
// 設置偏移量
let offset = Math.floor(v * 100);
// 根據偏移量得到在路徑上的點坐標
let position = ht.Default.getPercentPositionOnPoints(path.getPoints(), path.getSegments(), offset);
// 根據偏移量得到在路徑上的點於路徑切線角度
let angle = ht.Default.getPercentAngle(path.getPoints(), path.getSegments(), offset);
// 設置車輛位置坐標及旋轉角度
car.setX(position.x);
car.setY(position.y);
car.setRotationY(Math.PI / 2 - angle);
},
});
}
總結
工業互聯網是工業發展的必經之路,我們國家是一個工業大國,正處在工業轉型升級的關鍵時刻,面臨著人工成本上升、原材料價格波動、貿易競爭日益加劇等問題,迫切需要提高效率、降低生產成本。只有堅定不移地推動工業互聯網落地,加快更多企業的數字化轉型和智慧化改造,才有能讓在全球化競爭中立於不敗之地。可視化作為智慧化數字化的最後一環,讓複雜抽象的數據變得真正可知可感,幫助決策者發現規律,洞悉未來,為企業提速增效。
還有更多的可視化案例可以參考://www.hightopo.com/demos/index.html

