mxgraph進階(三)Web繪圖——mxGraph專案實戰(精華篇)
Web繪圖——mxGraph專案實戰(精華篇)
宣告
本文部分內容所屬論文現已發表,請慎重對待。
需求
由於小論文實驗需求,需要實現根據使用者日誌提取出行為序列,然後根據行為序列生成有向圖的形式,並且連線相鄰動作的弧上標有執行此次相鄰動作的頻次,每個動作另附有一個數據集,這樣有向圖加資料集就構成了使用者互動圖。為此,自己想到了mxGraph,遂決定學習之。
起步
此次專案實戰是受閱讀參考文獻[1]啟發,並在其圖形佈局例項基礎上進行。其原始介面如圖1所示,自己要實現的介面佈局與之頗有幾分神似。只是該佈局介面不支援節點與邊的定製,為此需要結合經典的“Hello world”例項,其原始介面佈局如圖2所示。
圖1 graphlayout例項
圖2 Hello World!例項
由於自己是零基礎開始學習這一Web繪圖框架,首先是閱讀其原始碼。有關mxgraph的啟動載入原理及其元素瞭解,請閱讀《》、《》兩篇博文。
例項1原始碼閱讀
- <!Doctype html>
- <html xmlns=http://www.w3.org/1999/xhtml>
- <head>
- <meta http-equiv=Content-Type content="text/html;charset=utf-8">
- <title>圖形佈局</title>
- <!-- 如果本檔案的包與src不是在同一個目錄,就要將basepath設定到src目錄下 -->
- <script type="text/javascript">
- mxBasePath = '../src';
- </script>
- <!-- 引入支援庫檔案 -->
- <script type="text/javascript" src="../src/js/mxClient.js"></script>
- <!-- 示例程式碼 -->
- <script type="text/javascript">
- // 程式在此啟動
- function main(container)
- {
- // 檢測瀏覽器相容性
- if (!mxClient.isBrowserSupported())
- {
- mxUtils.error('Browser is not supported!', 200, false);
- }
- else
- {
- // 在容器中建立圖形
- var graph = new mxGraph(container);
- // 禁用選擇和單元格處理
- graph.setEnabled(false);
- // 更改點風格的樣式
- var style = graph.getStylesheet().getDefaultVertexStyle();
- style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_ELLIPSE;
- style[mxConstants.STYLE_PERIMETER] = mxPerimeter.EllipsePerimeter;
- style[mxConstants.STYLE_GRADIENTCOLOR] = 'white';
- style[mxConstants.STYLE_FONTSIZE] = '10';
- // 設定容器隨內容自適應
- //graph.setResizeContainer(true);
- // 設定圖大小
- graph.gridSize = 40;
- // 建立預設窗體
- var parent = graph.getDefaultParent();
- // 建立新的佈局演算法
- var layout = new mxFastOrganicLayout(graph);
- // 移動距離
- layout.forceConstant = 80;
- // 動畫效果選項
- var animate = document.getElementById('animate');
- // 新增按鈕來更新佈局
- document.body.insertBefore(mxUtils.button('圓形佈局Circle Layout',
- function(evt)
- {
- graph.getModel().beginUpdate();
- try
- {
- // 建立圓形佈局演算法
- var circleLayout = new mxCircleLayout(graph);
- circleLayout.execute(parent);
- }
- catch (e)
- {
- throw e;
- }
- finally
- {
- if (animate.checked)
- {
- var morph = new mxMorphing(graph);
- morph.addListener(mxEvent.DONE, function()
- {
- graph.getModel().endUpdate();
- });
- morph.startAnimation();
- }
- else
- {
- graph.getModel().endUpdate();
- }
- }
- }
- ), document.body.firstChild);
- // 新增按鈕來更新佈局
- document.body.insertBefore(mxUtils.button('隨機佈局Organic Layout',
- function(evt)
- {
- graph.getModel().beginUpdate();
- try
- {
- layout.execute(parent);
- }
- catch (e)
- {
- throw e;
- }
- finally
- {
- if (animate.checked)
- {
- //預設值是 6, 1.5, 20
- var morph = new mxMorphing(graph, 10, 1.7, 20);
- morph.addListener(mxEvent.DONE, function()
- {
- graph.getModel().endUpdate();
- });
- morph.startAnimation();
- }
- else
- {
- graph.getModel().endUpdate();
- }
- }
- }
- ), document.body.firstChild);
- // 開啟更新事務
- graph.getModel().beginUpdate();
- var w = 30;
- var h = 30;
- try
- {
- var v1 = graph.insertVertex(parent, null, 'A', 0, 0, w, h);
- var v2 = graph.insertVertex(parent, null, 'B', 0, 0, w, h);
- var v3 = graph.insertVertex(parent, null, 'C', 0, 0, w, h);
- var v4 = graph.insertVertex(parent, null, 'D', 0, 0, w, h);
- var v5 = graph.insertVertex(parent, null, 'E', 0, 0, w, h);
- var v6 = graph.insertVertex(parent, null, 'F', 0, 0, w, h);
- var v7 = graph.insertVertex(parent, null, 'G', 0, 0, w, h);
- var v8 = graph.insertVertex(parent, null, 'H', 0, 0, w, h);
- var e1 = graph.insertEdge(parent, null, 'ab', v1, v2);
- var e2 = graph.insertEdge(parent, null, 'ac', v1, v3);
- var e3 = graph.insertEdge(parent, null, 'cd', v3, v4);
- var e4 = graph.insertEdge(parent, null, 'be', v2, v5);
- var e5 = graph.insertEdge(parent, null, 'cf', v3, v6);
- var e6 = graph.insertEdge(parent, null, 'ag', v1, v7);
- var e7 = graph.insertEdge(parent, null, 'gh', v7, v8);
- var e8 = graph.insertEdge(parent, null, 'gc', v7, v3);
- var e9 = graph.insertEdge(parent, null, 'gd', v7, v4);
- var e10 = graph.insertEdge(parent, null, 'eh', v5, v8);
- // 執行更改
- layout.execute(parent);
- }
- finally
- {
- // 結束更新事務
- graph.getModel().endUpdate();
- }
- }
- };
- </script>
- </head>
- <!-- 頁面載入時啟動程式 -->
- <body onload="main(document.getElementById('graphContainer'))">
- <!-- 建立帶網格桌布和曲線的一個容器,請一定要定義的position和overflow的屬性!根據線上API的54 頁內容增加的大小偵聽器 -->
- <div id="graphContainer"
- style="position:relative;overflow:visible;width:821px;height:641px;background:url('editors/images/grid.gif');">
- </div>
- <br>
- <input type="checkbox" id="animate" checked="checked"/> Transitions
- </body>
- </html>
從原始碼中可以看出,mxgraph首先是建立一個容器及其基本元素,然後在此容器基礎上完成圖形的繪製。
例項2原始碼閱讀
- <!Doctype html>
- <html xmlns=http://www.w3.org/1999/xhtml>
- <head>
- <meta http-equiv=Content-Type content="text/html;charset=utf-8">
- <title>Hello, World! example for mxGraph</title>
- <!-- 如果不是在同一個目錄的庫,就設定根目錄'mxBasePath' -->
- <script type="text/javascript">
- mxBasePath = '../src';
- </script>
- <!-- 載入和初始化庫'mxClient.js' -->
- <script type="text/javascript" src="../src/js/mxClient.js"></script>
- <!-- 示例程式碼 -->
- <script type="text/javascript">
- //程式從這裡開始。建立了一個示例圖中的DOM節點與指定的ID。呼叫此函式時從onLoad事件處理程式的檔案(見下文)
- function main(container)
- {
- //檢查瀏覽器是否支援
- if (!mxClient.isBrowserSupported()) {
- //如果瀏覽器不支援,顯示錯誤資訊。
- mxUtils.error('Browser is not supported!', 200, false);
- } else {
- //能夠實現在SVG中清晰呈現矩形,即:去鋸齒效果
- mxRectangleShape.prototype.crisp = true;
- //在給定的容器中建立的圖形
- var graph = new mxGraph(container);
- //設定容器自動調整大小
- //graph.setResizeContainer(true);
- //允許彈性選項
- new mxRubberband(graph);
- // 在物件中建立預設元件
- var parent = graph.getDefaultParent();
- //在圖形中插入元件
- //開啟模型的事務
- graph.getModel().beginUpdate();
- try {
- //插入點
- var v1 = graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30);
- var v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30);
- //插入線
- var e1 = graph.insertEdge(parent, null, '', v1, v2);
- }
- finally {
- //事務結束
- graph.getModel().endUpdate();//"G:/mxgraph-1_10_4_2/mxgraph/javascript/examples/control.html"
- }
- }
- };
- </script>
- </head>
- <!-- 頁面載入時啟動程式 -->
- <body onload="main(document.getElementById('graphContainer'))">
- <!-- 建立帶網格桌布和曲線的一個容器 -->
- <div id="graphContainer"
- style="position:relative;overflow:hidden;width:321px;height:241px;background:url('../examples/editors/images/grid.gif');cursor:default;">
- </div>
- </body>
- </html>
將示例2的程式碼與示例1的程式碼進行對比,並沒有發現示例二中元素可以編輯的原因。可能的原因在於以下樣式設定:
- // 更改點風格的樣式
- var style = graph.getStylesheet().getDefaultVertexStyle();
- style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_ELLIPSE;
- style[mxConstants.STYLE_PERIMETER] = mxPerimeter.EllipsePerimeter;
- style[mxConstants.STYLE_GRADIENTCOLOR] = 'white';
- style[mxConstants.STYLE_FONTSIZE] = '10';
經過思考,發覺其實圖中元素沒必要可編輯,只要顯示即可。
有關原始碼中類及其方法的具體內容,請參看“附”小結中的API文件。
轉變思想
在由日誌生成有向圖的過程中,自己可以生成頂點,邊及邊上的權值的獲取還存在一定的難度,尤其是權值的獲取,自己需要在原有權值的基礎上實現增加操作,步驟過於繁瑣。為此,考慮轉變一下解決問題的解決思路。
可以考慮使用關聯矩陣的方法。對於獲取到的使用者序列,得到其關聯矩陣。
舉例
表1 使用者行為序列表
編號 | 行為序列 |
1 | <a,b,c,d,e> |
2 | <a,c,e,f,g> |
3 | <b,e,f,a,g> |
4 | <b,c,a,d,f,g> |
5 | <a,b,e,f,e,f,g> |
某使用者行為序列如表1所示,其關聯矩陣如表2所示。其中以左側列資料為有向邊的起始點,以頂層行資料為有向邊的終止點。兩點交叉處的數值若非0,則說明兩點之間存在一條有向邊,數值表示邊上的權值,即使用者行為序列中此路徑出現的次數。
表2 使用者行為序列關聯矩陣
a | b | c | d | e | f | g | |
a | A[0][0] | 1+1A[0][1] | 1A[0][2] | 1A[0][3] | A[0][4] | A[0][5] | 1A[0][6] |
b | A[1][0] | A[1][1] | 1+1A[1][2] | A[1][3] | 1+1A[1][4] | A[1][5] | A[1][6] |
c | 1A[2][0] | A[2][1] | A[2][2] | 1A[2][3] | 1A[2][4] | A[2][5] | A[2][6] |
d | A[3][0] | A[3][1] | A[3][2] | A[3][3] | 1A[3][4] | 1A[3][5] | A[3][6] |
e | A[4][0] | A[4][1] | A[4][2] | A[4][3] | A[4][4] | 1+1+1+1A[4][5] | A[4][6] |
f | 1A[5][0] | A[5][1] | A[5][2] | A[5][3] | 1A[5][4] | A[5][5] | 1+1+1A[5][6] |
g | A[6][0] | A[6][1] | A[6][2] | A[6][3] | A[6][4] | A[6][5] | A[6][6] |
由表2可知:
1)矩陣主對角線上的元素全部為空,說明有向圖中不存在自環。
2)該關聯矩陣中非空元素較少,空元素居多,說明有向圖中頂點之間的關係複雜度不會太大。
3)根據有向邊上權值的大小,可觀察出使用者習慣性的行為序列。
4)注意到該關聯矩陣由使用者日誌中僅僅5條行為序列所得,將此種情形擴充套件到使用者行為序列數量達到一定值時,應考慮所對應關聯矩陣特點。由於在一個系統中,使用者可執 行動作種類數量是一定的,而且在一段時間內同一使用者的行為往往表現出一定的規律性,故可得出一段時間內單使用者的行為序列所對應的關聯矩陣為稀疏矩陣的結論。
為了方便繪製互動圖,我們將使用者行為序列編號改變一下對映形式,將a對應於0,b對應於1,相應的g對應於6。則表1所對應的使用者行為序列表等價於表3。
表3 使用者行為序列表
編號 | 行為序列 |
1 | <0,1,2,3,4> |
2 | <0,2,4,5,6> |
3 | <1,4,5,9,6> |
4 | <1,2,0,3,5,6> |
5 | <0,1,4,5,4,5,6> |
按照使用者行為序列相關的關聯矩陣儲存(二維陣列儲存頂點)——>遍歷行為序列方式插入有向邊及計算有向邊權值的思路,可得到圖3所示的互動圖。
圖3 使用者行為序列互動圖
對照圖1與表3,可驗證該互動圖的正確性。該互動圖可以完整的表述表3中所列使用者行為。但是,我們應該注意到,圖1還可以表達表3中所不包含的互動行為。例如圖1中所包含的行為序列<0,2,3,4,5,6>在表3中並不存在。即表3中的行為序列集合包含於圖1所標識的行為序列中。
互動圖優化
仔細觀察可以發現對於圖3中的雙向邊,例如(v4,v5)和(v5,v4)、(v2,v0)和(v0,v2),對於雙向邊的權值相同的情況,圖3顯示正常,但是當權值不同的時候,就會出現覆蓋的現象。例如W(v4,v5)=4,W(v5,v4)=1,從圖1可以看到有向邊顯示的權值為4,權值1被覆蓋掉。為此,需要進行圖1的優化操作。
從技術角度考慮,需要結合mxGraph的特點。官網例項中存在圖4所示的效果圖。可以考慮將有向邊進行拆分,分拆為兩條單向邊的形式。
圖4 mxGraph例項效果圖
再次閱讀程式碼,將以下語句中的引數改為true之後,發現圖形元素就可拖拽、編輯了。
- // 允許選擇和單元格處理
- graph.setEnabled(true);
官方API解釋如下:
優化後的使用者互動圖如圖3所示。對於雙向邊的處理方法為分拆為兩條有向邊。
演算法缺陷:
1)時間、空間複雜度較大。存在多處迴圈巢狀導致程式執行時時間、空間複雜度較大。
2)此方法目前只是作為一個原型,輸入引數均為常量,而非由使用者日誌中提取得到。有關日誌的處理工作前期已經完成。需要做的工作就是將不同的功能模組組裝起來。
對於矩陣的儲存,詳情請參見《稀疏矩陣》、《》博文。
有關論文,後期貼出。
附
注
現在很多網站都會在網站的頂部顯示一行公告,從正常的html原始碼上來說,這一行公告內容必定是在頁面所有元素的最前面的,也就是body元素後面的第一個元素。
首先解釋下為什麼不直接將程式碼寫在body塊內且設定為第一個元素,因為從seo的角度來講,網站頂部顯示的一行公告基本與網站的內容沒有多大關係。而作為網站html原始碼中比較靠前的內容,是搜尋引擎比較看重的內容。因此一段無關網站內容的內容最好不要放在html原始碼的前面。
下面說正題,下面這段程式碼就是通過js動態建立一個div並且將該div放在頁面的最前面。
- var divObj=document.createElement("div");
- //divObj.setAttribute('id','topAlert');
- divObj.innerHTML='警告:轉載www.daimajiayuan.com網站文章不帶原文連結者,本站有權追究其法律責任!';
- var first=document.body.firstChild;//得到頁面的第一個元素
- document.body.insertBefore(divObj,first);//在得到的第一個元素之前插入
致謝
mxGraph專案是在chwshuang寫的一篇部落格中受到的啟發,參考文獻[1]便是此博主的博文,再次表示感謝。
參考文獻
美文美圖