以前在網上看到別人寫的有關元素週期表的文章,深深的勾起了一波回憶,記憶裏初中時期背的「氫氦鋰鈹硼,碳氮氧氟氖,鈉鎂鋁硅磷,硫氯氬鉀鈣」、「養(氧)龜(硅)鋁鐵蓋(鈣),哪(鈉)家(鉀)沒(鎂)青(氫)菜(鈦)」,高中時期記的質量守恆、元素守恆、原子守恆、電子守恆,時間過的飛快,轉眼咱們都已經這麼大了。。。html
如今我用 HT 來實現它,HT 有 2D 拓撲和 3D 模型場景,兩種形式我都實現了,話很少說,先看效果圖。node
整個頁面由 HT UI 組件構成,使用 ht.ui.TabLayout 標籤頁佈局組件,進行 23D 界面的分別展現。程序員
2D界面:總體是一個 ht.ui.SplitLayout 分割組件(左右分割),左邊使用 ht.ui.HTView 包裝了 GraphView 拓撲圖組件,右邊是一個 ht.ui.Form 表單組件。json
3D界面:總體是一個 ht.ui.SplitLayout 分割組件(上下分割),上邊添加了 ht.ui.HBoxLayout 構成的按鈕組,下邊是使用 ht.ui.HTView 包裝了 Graph3dView 場景。緩存
demo 地址:http://www.hightopo.com/demo/elementTable/index.html框架
拓撲圖組件dom
先來講左邊的拓撲圖組件,ht.graph.GraphView 是 HT 框架中 2D 功能最豐富的組件,具備基本圖形的呈現和編輯功能,拓撲節點連線及自動佈局功能,電力和電信等行業預約義對象,具備動畫渲染等特效, 所以其應用面很普遍,可做爲監控領域的繪圖工具和人機界面,可做爲通常性的圖形化編輯工具,可擴展成工做流和組織圖等企業應用。ide
拓撲圖中展現的 118 個元素,每個都是 ht.Node 拓撲節點,默認的節點展現是一個小電腦樣式,在這裏咱們經過 setImage 設置節點顯示的圖片信息,以下圖:函數
矢量圖經過點、線和多邊形來描述圖形,所以在無限放大和縮小圖片的狀況下依然能保持一致的精確度。上圖就是一張矢量圖,由 1 個矩形和 6 個文字組成,任意縮放不失真,你們能夠訪問 demo 地址,經過滾輪來縮放拓撲圖進行體驗,具體矢量圖的繪製請參考矢量手冊。工具
確定有人會有疑問,118 種元素,是否要繪製 118 張矢量圖,感受稍微還能接受,若是是成千上萬呢,那麼人會累趴下的。不用怕,HT 幫咱們解決了這個問題,對繪製的矢量圖進行數據綁定,將繪製內容的屬性綁定到節點的屬性上,應用中經過更新節點對應屬性,圖形界面就會自動刷新,達到實時顯示數據的效果,好比個人這張矢量圖,我將 6 個元素屬性文本內容和字體顏色以及矩形背景色都進行了數據綁定,綁定好以後我只須要經過 node.a('background', '#FEB64D') 就能夠修改矩形的背景色(backgrouond 是矩形背景色綁定的屬性),具體數據綁定請參考數據綁定手冊。
既然說到了數據綁定,咱們就先看下顯示元素分類的功能,以下圖對比,節點樣式的變化不是經過從新 setImage 設置另外一張矢量圖,而是修改原矢量中綁定的樣式屬性。根據元素所屬類別,修改矢量圖的矩形背景色、元素中文名文本顏色。切換狀態的按鈕是 ht.ui.ToggleButton 開關按鈕,擁有「0/1」兩種狀態的切換,經過監聽按鈕是否選中,來切換元素週期表樣式。
1 toggle.on('p:selected', e => {
2 if (e.newValue) { 3 this.htView.legend.s('2d.visible', true); // 顯示類別圖例 4 this.htView.addClassification(); // 展現分類 5 } 6 else { 7 this.htView.legend.s('2d.visible', false); // 隱藏類別圖例 8 this.htView.initElements(); // 原始樣式 9 } 10 });
元素類別圖例也是一個 ht.Node 節點,一樣是繪製的矢量,它從一開始就在圖紙中, node.s('2d.visible', false) 設置爲不可見,要展現分類時,再設置爲 true 顯示。
表單面板
右邊的表單面板有 5 行,第 2 行就是上邊提到的顯示分類功能,第 3 行是一個文本輸入框,用來獲取元素序數,限制了只能輸入數字,還增長了輸入數的驗證,只能輸入 1~118 。
代碼以下:
1 let textField = new ht.ui.TextField();
2 textField.setFormDataName('textField'); // 設置在表單中的名稱 3 textField.setPlaceholder('請輸入查詢的元素序數!'); 4 textField.setMaskRe(/\d/); // 限制只能輸入數字 5 textField.setInstant(true); // 開啓即時模式,值改變就派發屬性改變事件 6 textField.on('p:value', (e) => { // 監聽值改變事件 7 let value = e.newValue; 8 if (value > 118) { 9 textField.setErrorMessage('只有 1 ~ 118 號元素喲!', { 10 placements: ['top'] 11 }); 12 } 13 else { textField.setErrorMessage(null); } 14 });
第 4 行是一個文本區域 ht.ui.TextArea,用來展現查詢的元素信息。
第 5 行是一組按鈕,用來提交查詢數據和重置表單信息。
按鈕組
上邊是一個 ht.ui.HBoxLayout 橫向佈局器,hbox 中添加了 4 個按鈕,來進行 3D 形態轉換。
按鈕支持圖標和文字,提供 normal、hover、active、disabled 四種狀態,按鈕生成代碼:
1 createButton(text) {
2 let button = new ht.ui.Button(); 3 button.setBorder(null); 4 button.setHoverBorder(null); 5 button.setActiveBorder(null); 6 button.setBackground(new ht.ui.drawable.ColorDrawable('rgba(37,115,194,0.6)', 4)); // normal 背景 7 button.setHoverBackground(new ht.ui.drawable.ColorDrawable('rgba(10,92,173,0.50)', 4)); // hover 背景 8 button.setActiveBackground(new ht.ui.drawable.ColorDrawable('rgba(15,132,250,0.6)', 4)); // active 背景 9 button.setText(text); 10 button.setTextColor('rgb(0, 211, 255)'); 11 button.setHoverTextColor('rgb(0, 211, 255)'); 12 button.setActiveTextColor('rgb(0, 211, 255)'); 13 14 return button; 15 }
經過 button.on('click', e => { // 切換函數 }) 來監聽點擊事件。
3D 場景
下邊是 ht.graph3d.Graph3dView,經過對 WebGL 底層技術的封裝,與 HT 其餘組件同樣, 基於 HT 統一的 DataModel 數據模型來驅動圖形顯示,極大下降了 3D 圖形技術開發的門檻,在熟悉 HT 數據模型基礎上, 通常程序員只須要 1 個小時的學習便可上手 3D 圖形開發。
元素在 3D 場景顯示爲一個面片,對面片進行 2D 時作好的矢量貼圖,一樣經過修改節點屬性,來控制顯示樣式。
1 node.s({
2 'shape3d': 'billboard', // 設置節點類型爲‘billboard’公告板 3 'shape3d.image': 'symbols/元素2.json', // 設置面片貼圖 4 'shape3d.reverse.flip': true, // 設置反面是否顯示正面內容 5 'shape3d.image.cache': true, // 進行貼圖緩存 6 'shape3d.fixSizeOnScreen': false, // 設置是否固定保持屏幕大小,不隨縮放而變化 7 'select.brightness': 1 // 設置選中亮度爲 1 8 });
接下來講幾種旋轉變化,dm 是 DataModel 即綁定的數據容器,datasMap 用來存放元素變化先後的位置信息,用於動畫驅動時使用。
1. 隨機打亂:設置一組空間範圍值,生成範圍內的(x,y,z)隨機值,用以設置節點位置。
1 let dm = this.dm,
2 datasMap = {}; 3 4 dm.each(data => { 5 let x = Math.random() * 2000 - 1000; // 獲取隨機 x 6 let y = Math.random() * 2000 - 1000; // 獲取隨機 y 7 let z = Math.random() * 500 - 250; // 獲取隨機 z 8 9 let position = data.getPosition3d(), 10 px = position[0], 11 py = position[1], 12 pz = position[2]; 13 14 datasMap[data] = { 15 x: x, 16 y: y, 17 z: z, 18 px: px, 19 py: py, 20 pz: pz 21 }; 22 });
2. 球形環繞:繞球面螺旋線生成點座標。
1 let dm = this.dm,
2 datas = dm.getDatas(), 3 datasMap = {}; 4 5 let r = 400, 6 theta, phi; 7 8 for (let i = 0; i < 118; i++) { 9 let data = datas.get(i); 10 theta = (i + 1) / 118 * 180; // 獲取球系座標 11 phi = (i + 1) / 118 * 360 * 10; // 獲取球系座標 12 // 球系座標轉換爲 HT 三維座標 13 let z = r * Math.sin(theta * Math.PI / 180) * Math.cos(phi * Math.PI / 180), 14 x = r * Math.sin(theta * Math.PI / 180) * Math.sin(phi * Math.PI / 180), 15 y = r * Math.cos(theta * Math.PI / 180); 16 17 let position = data.getPosition3d(), 18 px = position[0], 19 py = position[1], 20 pz = position[2]; 21 22 datasMap[data] = { 23 x: x, 24 y: y, 25 z: z, 26 px: px, 27 py: py, 28 pz: pz 29 }; 30 }
3. 環形圍繞:設置一個環繞半徑、起始高度,以固定角度旋轉,每次下降節點的設置高度。
1 let dm = this.dm,
2 datasMap = {}, 3 datas = dm.getDatas(), 4 radius = 400, 5 angle = 18, 6 num = 360 / angle; 7 8 let y = 300, 9 count = 0; 10 for (let i = 0; i < 6; i++) { 11 for (let j = 0; j < num; j++) { 12 let data = datas.get(count), 13 radian = Math.PI / 180 * j * angle; 14 15 if (!data) break; 16 count++; 17 18 let x = radius * Math.cos(radian), 19 z = radius * Math.sin(radian); 20 21 let position = data.p3(), 22 px = position[0], 23 py = position[1], 24 pz = position[2]; 25 26 datasMap[data] = { 27 x: x, 28 y: y, 29 z: z, 30 px: px, 31 py: py, 32 pz: pz 33 }; 34 y -= 6; 35 } 36 }
4. 復原:根據記錄的元素的行數和列數,計算元素節點的 xy 值,z 值固定。
1 let dm = this.dm,
2 datasMap = {}; 3 4 dm.each(data => { 5 let index = data.a('index'), 6 row = data.a('row'), 7 col = data.a('col'); 8 9 let position = data.getPosition3d(), 10 px = position[0], 11 py = position[1], 12 pz = position[2]; 13 14 datasMap[data] = { 15 index: index, 16 row: row, 17 col: col, 18 px: px, 19 py: py, 20 pz: pz 21 }; 22 });
5. 元素切換狀態時的動畫:詳情瞭解入門手冊動畫
1 ht.Default.startAnim({
2 duration: 1500, 3 easing: function(t) { 4 return t * t; 5 }, 6 action: function(v, t) { 7 dm.each(data => { 8 let info = datasMap[data], 9 x = info.x, 10 y = info.y, 11 z = info.z, 12 px = info.px, 13 py = info.py, 14 pz = info.pz; 15 16 data.p3(px + v * (x - px), py + v * (y - py), pz + v * (z - pz)); // 移動元素位置 17 data.lookAt([0, y, 0], 'back'); // 調整元素朝向 18 }); 19 } 20 });
再次看過元素週期表,你是否想起化學課上滿黑板的化學方程式,是否想起了化學實驗課酒精燈的燃燒,是否還記得實驗操做流程、儀器的正確擺放。