mxgraph進階 三 Web繪圖——mxGraph項目實戰 精華篇
Web繪圖——mxGraph項目實戰(精華篇)
聲明
本文部分內容所屬論文現已發表,請慎重對待。
需求
由於小論文實驗需求,需要實現根據用戶日誌提取出行為序列,然後根據行為序列生成有向圖的形式,並且連接相鄰動作的弧上標有執行此次相鄰動作的頻次,每個動作另附有一個數據集,這樣有向圖加數據集就構成了用戶交互圖。為此,自己想到了mxGraph,遂決定學習之。
起步
此次項目實戰是受閱讀參考文獻[1]啟發,並在其圖形布局實例基礎上進行。其原始界面如圖1所示,自己要實現的界面布局與之頗有幾分神似。只是該布局界面不支持節點與邊的定制,為此需要結合經典的“Hello world”實例,其原始界面布局如圖2所示。
圖1 graphlayout實例
圖2 Hello World!實例
由於自己是零基礎開始學習這一Web繪圖框架,首先是閱讀其源碼。有關mxgraph的啟動加載原理及其元素了解,請閱讀《mxgraph的初步介紹與開發入門》、《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)此方法目前只是作為一個原型,輸入參數均為常量,而非由用戶日誌中提取得到。有關日誌的處理工作前期已經完成。需要做的工作就是將不同的功能模塊組裝起來。
對於矩陣的存儲,詳情請參見《稀疏矩陣》、《矩陣(稀疏矩陣)的壓縮存儲》博文。
有關論文,後期貼出。
附
官網:http://www.jgraph.com/
Demo:http://jgraph.github.io/mxgraph/javascript/index.html
API:http://jgraph.github.io/mxgraph/docs/js-api/files/index-txt.html
中文版使用手冊:http://www.mxgraph.cn/doc/mxgraph/
註
現在很多網站都會在網站的頂部顯示一行公告,從正常的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]便是此博主的博文,再次表示感謝。
參考文獻
1. http://chwshuang.iteye.com/blog/1797168
2. http://www.w3school.com.cn/js/js_obj_array.asp
美文美圖
再分享一下我老師大神的人工智能教程吧。零基礎!通俗易懂!風趣幽默!希望你也加入到我們人工智能的隊伍中來!http://www.captainbed.net
mxgraph進階 三 Web繪圖——mxGraph項目實戰 精華篇