基於 HTML5 Canvas 2D 拓撲圖和 3D 機櫃模型的增刪操作

圖撲軟體發表於2020-04-06

今天好好地消化了一下我們的資料容器 DataModel,這裡給初學者做一個典型的資料模型事件處理的例子作為參考。這個例子看起來很簡單,實際上結合了資料模型中非常重要的三個事件處理的部分:屬性變化事件監聽、選中變化事件監聽以及資料模型變化事件監聽。

為了讓這個例子具現化,我將這個簡單的例子做了一點改動,下面我會一一解釋。

例子地址:http://hightopo.com/guide/guide/core/datamodel/examples/example_datamodel.html
圖片描述

這是我改造之後的模樣,將 dataModel 資料容器共享,通過對資料容器的增刪事件的監聽得到的現在的結果,並且在顯示上做了一點“手腳”。下面我們從頭解析這例子,你們會知道為什麼我特地將這個簡單的例子提出來。

首先,我們得建立場景將作為基礎,整個場景我算是分為三個部分,頂部工具欄,2D 部分以及 3D 部分。頂部工具欄部分使用的純 HTML 寫的:

<button onclick="addData()">Add</button>
<button onclick="removeData()">Remove</button>
<button onclick="clearDataModel()">Clear</button>
<span id="property" class="output"></span>
<span id="model" class="output"></span>
<span id="selection" class="output"></span>

因為有點選事件,所以我們直接在 button 按鈕上進行,後面的 span 標籤顯示純文字內容。

我們知道,HT 的所有元件都是基於一個根部 div 的,要將這個 div 部署到 html 頁面上很簡單,但是 HT 內部對這個 div 設定了絕對定位,所以我們在新增這個 div 進 HTML 頁面中時,也要設定絕對定位中的位置,我在頁面中新增了一個 div,將 HT 的部分都新增進這個 div 中:

<div id="myDiv" style="border: 1px solid red; width: 800px; height: 600px;position: absolute; "></div>
dataModel = new ht.DataModel();
g3d = new ht.graph3d.Graph3dView(dataModel);
g3d.setGridVisible(true); // 設定網格可見
g3d.setEye(185, 50, 470); // 設定 3d 的眼睛位置
g3d.setCenter(200, 47, 10); // 設定 3d 的中心位置, 這兩個屬性都是為了讓使用者看 3d 上的場景更舒服,更直接
g2d = new ht.graph.GraphView(dataModel);
g2d.setEditable(true); // 設定 2d 圖元可編輯
g2d.fitContent(true); // 將所有的圖元顯示到頁面上
splitView = new ht.widget.SplitView(g2d, g3d, 'v', 0.3); // 分割元件,裝了2d 和 3d 兩個場景
splitView.addToDOM(); // 將分割元件新增進 body 中,並設定絕對定位的位置
myDiv = document.getElementById('myDiv');
myDiv.appendChild(splitView.getView()); // 將分割元件新增進 myDiv 中

接著新增節點進入 dataModel 資料模型之中,我們這裡做的是機房的機櫃,本來想做的是伺服器,手頭上暫時只有這個資源,也不賴。我封裝了一個增加函式,一個刪除函式,還有一個清楚函式,分別對應的是工具欄上的“Add”、“Remove”以及“Clear”三個功能:

function addData() {
    var data = new ht.Node();
    data.setPosition(index*60, 50);
    data.setName('node'+index);
    data.setSize(40, 40);
    data.setImage('cabinet');
    data.s({
        'image.stretch': 'centerUniform',
    'shape3d': 'cabinet'
    });
    index++;
    dataModel.add(data);
    return data;
}
function removeData() {
    if(!dataModel.sm().ld()) return;
    dataModel.remove(dataModel.sm().ld());
}
function clearDataModel(){
    dataModel.clear();
    index = 0;
}

其中,程式碼中出現的“data.setImage(‘cabinet’)”,是我通過 ht.Default.setImage(‘cabinet’, ‘imageURL’) 方式定義的,呼叫的時候直接 data.setImage(‘imageName’) 即可,具體參考 HT for Web 入門手冊 image 章節

2D 的圖片顯示肯定和 3D 的模型顯示是不一樣的,2D 中我們直接用貼圖就能解決,而 HT 3D 中支援 obj 格式的模型顯示,就是這個部分:

圖片描述

HT 封裝瞭解析 obj 格式的函式 ht.Default.loadObj 函式用來匯入模型,該函式有三個引數,第一第二分別為 obj 檔案的路徑和 mtl 檔案的路徑,第三個引數為 json 格式控制引數,具體引數請參考 HT for Web OBJ 手冊 loadObj 函式章節(ps:用 obj 模型會導致跨域問題,要放到伺服器上執行):

ht.Default.loadObj('obj/機櫃元件1.obj', 'obj/機櫃元件1.mtl', {
     cube: true, // 是否將模型縮放到單位1的尺寸範圍內,預設為 false
     center: true, // 模型是否居中
     prefix: 'obj/', // 路徑字首,如果前面引數寫了路徑字首,這個不寫也可以
     shape3d: 'cabinet', // 指定 shape3d 名稱
     finishFunc: function(modelMap, array, rawS3){ // 呼叫 ht.Default.parseObj 解析後的返回值,若載入或解析失敗則返回值為空
         window.rawS3 = rawS3; // 讓當前模型的尺寸為原始尺寸
         if(modelMap){
             cabinet1 = addData(); // 新增兩個節點到 dataModel 中
             cabinet2 = addData();
         }
    } 
});

現在,節點和模型都已經匯入到場景中了,終於來到了我們今天的重點,事件互動部分。ht.DataModel 資料容器管理著 Data 資料的增刪以及變化事件的派發,這裡我們就這兩種事件進行對 Data 資料的管理。

  1. addDataModelChangeListener(function(e) {}, scope)
    增加資料模型增刪變化事件監聽器,可用簡寫 mm(func, scope), func 為監聽器函式,scope
    為監聽器函式域(可選),在監聽器函式中的 event 有兩個屬性: kind 和 data,其中 kind 為事件的型別:

    e.kind === 'add’代表新增Data物件,e.data 為被新增的物件
    e.kind ==='remove’代表刪除Data物件,e.data 為被刪除的物件
    e.kind === 'clear’代表容器被清除

這裡我們將對模型的增刪事件的監聽結果傳給 HTML 中的 id 為 model 的 span 作為內容:

var model = document.getElementById('model');
dataModel.addDataModelChangeListener(function(e) {
    if(e.kind === 'add') // 如果事件型別為 add 增加節點
        model.innerHTML = e.data + ' added'; // 就將model 的內容替換為新增的節點 added
    if(e.kind === 'remove')
        model.innerHTML = e.data + ' removed';
    if(e.kind === 'clear')
        model.innerHTML = 'dataModel cleared'
});
  1. addDataPropertyChangeListener(function(e) {}, scope) 增加模型中 Data
    資料屬性變化事件監聽器,可用簡寫 md(func, scope),其中 event 事件有四種屬性:

    e.data 代表屬性變化的物件
    e.property 代表變化屬性的名字
    e.newValue 代表屬性的新值
    e.oldValue 代表屬性的老值
    Data 物件在設定屬性值函式內呼叫 firePropertyChange(property, oldValue, newValue) 觸發屬性變化事件:
    get/set 型別屬性,如 setAge(98) 觸發事件的 e.property為age
    style 型別屬性名前加 s: 字首以區分,如 setStyle(‘age’, 98) 觸發事件的 e.property為s:age
    attr 型別屬性名前加 a: 字首以區分,如 setAttr(‘age’, 98) 觸發事件的 e.property為a:age

這裡我們將對模型中 Data 的屬性變化事件的監聽結果傳給 HTML 中的 id 為 property 的 span 作為內容:

var model = document.getElementById('model');
dataModel.addDataPropertyChangeListener(function(e) {
    property.innerHTML = e.data + '\'s ' + e.property + ' has changed, the old value is ' + e.oldValue + ' and the new value is ' + e.newValue; 
});
  1. 最後,我們對選中的節點進行增加監聽器,監聽選中變化事件。ht.SelectionModel管理 DataModel 模型中 Data
    物件的選擇狀態, 每個 DataModel 物件都內建一個 SelectionModel 選擇模型,控制這個
    SelectionModel 即可控制所有繫結該 DataModel 的元件的物件選擇狀態, 這意味著共享同一 DataModel
    的元件預設就具有選中聯動功能。

如果希望某些元件不與其他元件選中聯動,可通過呼叫 view.setSelectionModelShared(false), 這樣該 view 將建立一個專屬的 SelectionModel 例項。

綜上所述有兩種途徑可得到 SelectionModel:

dataModel.getSelectionModel() 獲取資料容器中元件共享的選中模型。
view.getSelectionModel() 獲取當前元件使用的選中模型,selectionModelShared 為false時,返回 view 專用的選擇模型。
addSelectionChangeListener(function(e) {}, scope) 增加監聽器,監聽選中變化事件,簡寫為 ms(func, scope):

e.datas 包含所有選中狀態變化的物件,之前選中現在取消選中,或之前沒選中現在被選中的物件
e.kind === ‘set’ 代表此事件由 setSelection(datas) 引發
e.kind === ‘remove’ 代表此事件由 removeSelection(datas) 引發
e.kind === ‘append’ 代表此事件由 appendSelection(datas) 引發
e.kind === ‘clear’ 代表此事件由 clearSelection(datas) 引發
這裡我們將對模型中 Data 的選中變化事件的監聽結果傳給 HTML 中的 id 為 selection 的 span 作為內容:

var selection = document.getElementById('selection');
dataModel.sm().addSelectionChangeListener(function(e){
    if(dataModel.sm().size() === 0) selection.innerHTML = 'Nothing selected'; // 如果選中模型的“長度”為 0,即沒有選中內容
    else if(dataModel.sm().size() === 1) selection.innerHTML = e.datas + ' selected';
    else selection.innerHTML = dataModel.sm().size() + ' datas selected';
});

以上,所有的程式碼全部分析完畢!大家可以天馬行空,建立出屬於你自己的 3 維模型!

相關文章