jsplumb绘制js简单实现流程图绘制的线是怎么实现的

国内最容易使用和开发的流程设计器
界面简洁,容易操作,用户体验好
支持子流程
步骤权限控制
结合表单设计器
步骤独立样式
8b,dPPYba,
88a8PPPP8b,
a8P_____88
"PPPPPP"88
8PP"""""""
Firefly95、xinG拿到一个Demo该怎么下手? - 简书
拿到一个Demo该怎么下手?
大家好,我是帅气小伙,3个月的实习期刚好过去,是时候总结一下这段时间的工作经历了。给自己的总结是,基本入门基于Spirng框架的后台开发。自我感觉是,比一般人学得多,因为我的导师是一名高级Java工程师。关于和这位大哥哥的故事,我们后续说吧。今天,我要为大家讲的是如何从一个Demo下手,从而完成工作中的需求。项目背景我要给大家简单地说几句关于我在实习期做的项目。不然大家是听不懂的。我这3个月做的项目是一个基于mule的数据转换中心。原理类似于 。项目搭建在Spring boot上,将封装的mule以component的形式启动,从而配置多种类型的端口,不同的端口类型有不同的获取数据的途径(webserivce,database,jms,http等调用方式),将flow视为待处理数据流,以Groovy这门JVM语言编排成指定格式的Json或者Xml格式的数据。看不懂也没关系。接着往下看吧。
需求"在Web的管理页面,用线图绘制出参数之间的映射关系,并以连线的方式自动化生成Groovy脚本",总监如是说。对于我来说这简直太难了。因为我3个月的情况是从未开发过web项目的,更别提那些复杂的js了。总监也知道了我的实际情况。于是在网上给我找了Demo。我上我也行,上网一搜“jsplumb 流程图”,第5篇文章。
我做出来之后
实现过程拿到一个Demo首先你要让它运行起来,很简单双加点开Html即可,映入眼帘的是
玩一下,看看有什么功能,很简单,拖拽连线,节点可动。然后就看查看网页源码其实看HTML页面的3步曲很简单&head&&/head&
主要看看引用的第三方的css,js库&body&&/body&
页面元素&script&&/script&
这个页面的js代码根据这个套路,我就找到了这个页面的js怎么才能找到程序入口呢?很简单,逐个方法(function)删除,删除到哪个导致程序无法运行,那个就是入口了。找到入口怎么办?一步一步看完,直到结尾,看你能看得懂的源码我就不提了,因为demo都有,接下来的实现过程,请看着demo源代码对比一下,基本没变多少。动态绘图的基本套路1.数据初始化2.图形绘制3.数据保存如何才能实现动态绘图?动态获取获取数据 ajax关键不是动态,而是数据,如何定义数据结构才是关键。为了让大家好看,我就截图了数据库参数模型
参数=图端点
一张关系图=节点+线条
节点里有端点
一个端点代表一个数据属性含有脚本
两点确定一条线
线条保存模型
[{"source":"SER__OUT_95","target":"PRO__IN_103"}]
这些数据模型,就是我摸索了2个星期才想出来的,实在不容易。定义好这些数据结构,该如何处理呢?
总的代码流程是这样走的
1.查数据库的,目的是找出第一个节点
2.查数据库,找到图中的含红色端点的节点
3.查数据库,找到中间的节点(存在多个)
4.动态计算节点的位置和长宽,定宽不定长,位置是紧贴的5.同层级内按端点名称的首字母排序,为了方便连线和美观6.动态增加节点的宽度(因为如果层级越深,节点左边的lable将会显示不全)7.查数据库,获取之前保存的线条(json数据)调用绘制方法经过后台代码的处理后,我们得到了json数据,接下来就是解析json数据并调用jsplumb的API1.初始化jsplumb实例
接着下面吧,不会用markdown
接下来我们将draw方法
图=节点+线条
节点=div+svg
2.节点初始化
跟demo的代码是一样的,只是改了一下而已
3.端点初始化
下面就是调用jsplumb的绘点方法(下面还有)part1
什么是端点的左右填充物(还没完)part2
填充物的Html是这样的
这些就填充物
设置端点的参数(接着上面)part3
4.线条初始化
调用jsplumb实例方法
连线监听,连完线就把脚本加上
调用jsplumb实例,获取所有的连线,最后提交到后台处理
总结大家都看了我的代码了吧,很烂说真的,我确实是一个新手,但是我的思路是清晰的,我知道应该做什么,程序员就是这样,随着年月的增长,我的代码也不会这么幼稚了。作为一个刚入行的程序员,当总监叫我搭建这个项目的时候,我虽然懵逼,但是我的回答是“请相信我,我可以的”。想想,你在生活中,因为一句“我不会”,你曾经错过了多少次机会。如果不是我当初的自信,我觉得我现在就只会写写接口了,也绝对没有与高级java合作的机会。
默默地为全栈理想而奋斗jsPlumb介绍;ShenBY沈本义(qq:);以下jsPlumb介绍基于jsPlubm1.3.;Jsplumb是Jquery的一个插件,它能够让;官方示例:http://morrisonpitt;Jsplumb介绍地址:JsPlumb允许您使用;浏览器的兼容性;jsPlumb1.3.3已经在以下浏览器测试:;IE6onWindowsX
jsPlumb介绍
ShenBY 沈本义(qq:)
以下jsPlumb介绍基于jsPlubm1.3.3版本,并且基础库是用jQuery1.3.x或以上,与其他版本基础库或jsPlumb的比较或后期版本升级,本文档不做介绍。 本人英文水平有限,有疑惑请自行到官网对照翻译。 摘要
Jsplumb是Jquery的一个插件,它能够让你用动态的或静态的链接来连接html界面上行的元素,并且从1.1.0版本开始,提供用鼠标拖动来链接。目前该插件支持三个javascript库,有Jquery、MooToos、Yui3,jsplumb代码是开源的,并且是麻省理工学院许可,由google进行代码托管。
官方示例:/jsPlumb/html/demo.html 代码地址:/p/jsplumb/
Jsplumb介绍地址:JsPlumb允许您使用SVG、Canvas 或者 VML链接屏幕上的元素,这些取决于您使用的浏览器的能力。
浏览器的兼容性
jsPlumb 1.3.3 已经在以下浏览器测试:
IE 6 on Windows XP
IE 7 on Windows XP
IE 8 on Windows XP
Firefox 3.5.8 on Windows XP
IE 9 on Windows 7
Chrome 12 on Windows 7
Firefox 3.5.8 on Windows 7
Firefox 3.6.3 on Ubuntu 10.04
Chrome on Ubuntu 10.04
Safari 4 on Mac Tiger
Safari 4 on Windows Vista
Safari 5.0.5 on Windows 7
Opera 10.54 on Windows XP
1. 需要导入
使用jsPlumb需要到如的类库文件,是根据您使用的javascript类库的不同而定,目前提供以下版本。
jquery的1.3.x或更高版本 jquery ui的1.7.x或1.8.x(如果您需要支持拖放)
MooTools核心库版本1.2.4或更高版本(jsPlumb已在1.2.4和1.3.3版本上测试) MooTools的Drag.Move的1.2.4.4或更高版本
YUI3.3.X ,目前jsPlumb只在YUI3.3.0上测试,YUI3其他版本可能也可以正常工作
jsPlumb可以使用SVG,HTML5的画布元素或VML去呈现需要显示的对象,现代大部分浏览器都支持Canvas 和 SVG,
以下都不支持这些,默认情况下,
jsPlumb都使用canvas去渲染,但如果在使用IE9以下的情况下,jsPlumb将使用vml去渲染,您可以通过下列方式调整渲染模式:
1. 基本概念
jsPlumb是将所有链接的东西放在一起,所以在jsPlumb中,核心是链接对象,jsPlumb
本身可以分成四个组成部分
锚(Anchor):一个位置,放置端点的地方,相对于一个元素的来源,您不需要自己硬编码来创建它,jsPlumb提供给您各种功能,您只需要按照您的需要创建它就可以了。它没有可视化的显示,只是一个逻辑位置,可以使用锚的id来引用它,jsPlumb支持这样做,并且您可以使用坐标来表示[x,y,x方向,y方向]
端点(Endpoint ):链接的一端的可视化表示,您可以创建并可以链接他们;您可以
连接器(Connector):链接两个元素的线,页面的可视化表示,jsPlumb有三种默认让他们支持拖拽,或者您可以直接使用jsPlumb.connect()在创建链接时直接创建它们。
类型:Bezier曲线,直线,和流程图链接器,您不用去处理连接器,当您需要使用它们时,您只需要定义它们即可。
覆盖物(Overlay):一个UI组件,是用来是用来装饰连接器,例如标签、箭头等。
2. 锚(Anchors)
锚的概念是指:定义一个链接线能够链接的点,jsPlumb有9个默认的锚点位置,您可以使用它们去链接元素,具体有:
这些位置在jsplumb底层代码中都是以阵列语法表示的,[X,Y,DX,DY],其中X,Y是在区间[0,1]指定锚的位置,DX和DY是在区间[-1,1]指定曲线的事件锚的方向坐标,例如[0,0.5,-1,0]定义了一个“LeftCenter”连接器的曲线,从锚点单向向左的曲线.同样,[0.5,0,0,-1],定义一个“CenterTop”锚连接器所产生的向上的曲线。
3. 动态锚(Dynamic Anchors) 这些都是可以在若干地点之一定位的锚点,当你每次移动一个元素时,会自动选择一个最合适的位置,没有特殊的语法来创建一个DynamicAnchor,你只需要提供一个独立的锚位置,例如数组:
默认的动态锚:
jsPlumb提供一个动态的锚,定名为“AutoDefault”默认位置有:TopCenter,RightMiddle,BottomCenter和LeftMiddle
动态锚和可拖动的连接是可以进行互操作的,
当你开始拖动连接或释放它时,
会锁定一个动态的锚的位置,您可以在界面上看到链接锚点的切换变化。
4. 连接器、端点,覆盖物(Connector, Endpoint & Overlay Definitions)
在我们讨论连接器、终点和覆盖物之前,需要提醒的是:你需要定义一个连接器,端点或覆盖,您必须使用一个“定义”,而不是直接构建一个,这个定义可以是一个字符串,它指定你需要创建的对象。
或者您需要构件的对象的属性名组成的数组,您可以通过其构造函数来创建,例如
也有三个参数的方法,可以让你指定两套参数,jsPlumb会自动为你合并,例如
5. 连接器(Connectors)
链接的线,实际上是界面上的各个元素的链接线,jsPlumb有三条连接器实现,一条直线、Bezier曲线和”流程图”连线,默认的连接线是贝塞尔曲线,您可以有选择的设置一个链接器,可以通过设置“connector”来定义一个连接线,或者在添加端点时设置连接线,如果您没有为connector设置一个值,那么他会用他的默认值”Default” 三种链接的定义语法类似
贝塞尔曲线:
贝塞尔曲线提供了两个端点之间的立方体路径,它支持一个构造函数参数,
curviness -可选,默认为150,这定义的锚点位于贝塞尔曲线的控制点,以像素为单位的距离,但这并不意味着你的连接器会穿过从你的曲线到这个距离的点,这仅是一个标识而已。
直线:直连绘制直线的两个端点之间。 没有构造函数的参数支持,使用参数endpointStyle定义连接样式或添加端点时定义连接线样式
流程图:这种类型的连接器,绘制一系列垂直或水平段组成的连接 - 经典的流程图,支持一个单一的构造函数参数
存根 -这是最小长度,以像素为单位,从端点发出的初始存根。 此参数是可选的,默认为30像素
6. 端点类型(Endpoint Types)
一个端点的UI组件,标志着一个锚的位置,是连接器连接的点,jsPlumb有三个端点实现,点、矩形和图形,你可以指定端点的属性,在jsPlumb.connect时指定属性,或者jsPlumb.Addendpiont时指定,或者设置jsPlumbde target时设定属性,语法可参照connector。 三个可用的端点类型,他们的构造函数参数如下:
点端点(Dot Endpoint):
此端点在屏幕上画一个点。 它支持一个构造函数参数:
半径 -可选,默认为10个像素。 定义点的半径
矩形端点(Rectangle Endpoint):
绘制一个矩形。 支持的构造函数的参数是:
宽度 -可选;默认为20像素。 定义矩形的宽度。
高度 -可选,默认为20个像素。 定义矩形的高度。 图片端点(Image Endpoint):
从一个给定的的URL绘制图像。 此端点支持一个构造函数的参数:
SRC -必需的。 指定要使用的图像的URL。
7. 覆盖物类型(Overlay Types)
覆盖界面上的链接元素,如标签或箭头等,jsPlumb有四个默认值:
Arrow:可配置的箭头,放在两个点的连接线上,你可以控制箭头的长度和宽度,
转折点:是一个折回点,方向点(允许值是1和-1,意味着是点的链接方向)
Label:在点的连接器上的可配置的标签
PlainArrow:一个三角形箭头,没有折回点
三亿文库包含各类专业文献、中学教育、文学作品欣赏、专业论文、幼儿教育、小学教育、外语学习资料、各类资格考试、高等教育、18jsplumb介绍等内容。 & & & & 本文实现的方法可以边异步加载数据边绘制拓扑图。 有若干点需要说明一下:
& & & & 1. &一次性获取所有数据并绘制拓扑图, 请参见文章:&&; 本文实现的最终显示效果与之类似, 所使用的基本方法与之类似。
& & & & 2. &在此次实现中, 可以一边异步加载数据一边绘制拓扑图, 是动态可扩展的;&
& & & & 3. &所有影响节点位置、布局的配置均放置在最前面, 便于修改, 避免在代码中穿梭, 浪费时间;
& & & & 4. &布局算法比之前的实现更加完善;
& & & & 5. &此实现由于与业务逻辑绑得比较紧, 可复用的部分不多, 但是可以作为一个模板, 用在读者自己的场景中, 自行修改相应的节点类型、URL等。
& & & & 6. &添加了附着点的点击事件处理, 可以刷新显示关联实体;
& & & & 7. &主流程很简单: &发送 AJAX 请求获取数据 ---& 创建节点(实际上就是DIV) ---& 计算节点位置、布局 ---& 添加节点附着点 ---& 缓存节点连接 ---& 连接所有现有的缓存节点连接。 &多个 AJAX 请求的处理是异步的, 顺序没有控制。
& & & & 8. &代码:& &
* 使用 jsPlumb 根据指定的拓扑数据结构绘制拓扑图
* 使用 drawTopo_asyn(vmName, regionNo, parentDivId) 方法
* 初始化拓扑图实例及外观设置
8 (function() {
jsPlumb.importDefaults({
DragOptions : { cursor: 'pointer', zIndex:2000 },
EndpointStyles : [{ fillStyle:'#225588' }, { fillStyle:'#558822' }],
Endpoints : [ [ "Dot", { radius:2 } ], [ "Dot", { radius: 2 } ]],
ConnectionOverlays : [
[ "Label", { location:1 } ],
[ "Label", {
location:0.1,
id:"label",
cssClass:"aLabel"
var connectorPaintStyle = {
lineWidth: 1,
strokeStyle: "#096EBB",
joinstyle:"round",
outlineColor: "#096EBB",
outlineWidth: 1
var connectorHoverStyle = {
lineWidth: 2,
strokeStyle: "#5C96BC",
outlineWidth: 2,
outlineColor:"white"
var endpointHoverStyle = {
fillStyle:"#5C96BC"
window.topoDrawUtil = {
sourceEndpoint: {
endpoint:"Dot",
paintStyle:{
strokeStyle:"#1e8151",
fillStyle:"transparent",
radius: 4,
lineWidth:2
isSource:true,
maxConnections:-1,
connector:[ "Flowchart", { stub:[40, 60], gap:1, cornerRadius:5, alwaysRespectStubs:true } ],
connectorStyle: connectorPaintStyle,
hoverPaintStyle: endpointHoverStyle,
connectorHoverStyle: connectorHoverStyle,
dragOptions:{},
overlays:[
[ "Label", {
location:[0.5, 1.5],
cssClass:"endpointSourceLabel"
targetEndpoint: {
endpoint: "Dot",
paintStyle: { fillStyle:"#1e8151",radius: 2 },
hoverPaintStyle: endpointHoverStyle,
maxConnections:-1,
dropOptions:{ hoverClass:"hover", activeClass:"active" },
isTarget:true,
overlays:[
[ "Label", { location:[0.5, -0.5], label:"", cssClass:"endpointTargetLabel" } ]
initConnection: function(connection) {
connection.getOverlay("label").setLabel(connection.sourceId + "-" + connection.targetId);
connection.bind("editCompleted", function(o) {
if (typeof console != "undefined")
console.log("connection edited. path is now ", o.path);
removeAllEndPoints: function(nodeDivId) {
jsPlumb.removeAllEndpoints($('#'+nodeDivId));
addEndpoints: function(toId, sourceAnchors, targetAnchors) {
for (var i = 0; i & sourceAnchors. i++) {
var sourceUUID = toId + sourceAnchors[i];
var endPoint = jsPlumb.addEndpoint(toId, this.sourceEndpoint, { anchor:sourceAnchors[i], uuid:sourceUUID });
endPoint.bind("click", function(endpoint) {
var anchorType = endpoint.anchor.
var nodeType = toId.split('_')[0];
var content = toId.split('_')[1];
if (nodeType == VM_TYPE) {
switch (anchorType) {
case 'Right':
cacheKey = 'VM-DEVICE-'+ vmNodeData.
cacheConnectionData[cacheKey] = null;
linkDevices(vmNodeData, vmNodeData.key);
case 'Top':
cacheKey = 'VM-ACCOUNT-'+ vmNodeData.
cacheConnectionData[cacheKey] = null;
vmName = vmNodeData.
regionNo = vmNodeData.data.region_
linkAccount(vmNodeData, vmName, regionNo);
case 'Bottom':
cacheKey = 'VM-NC-'+ vmNodeData.
cacheConnectionData[cacheKey] = null;
ncId = vmNodeData.data.nc_
regionNo = vmNodeData.data.region_
linkNc(vmNodeData, ncId, regionNo);
case 'Left':
cacheKey = 'VM-VIP-'+ vmNodeData.
cacheConnectionData[cacheKey] = null;
vmInnerIp = vmNodeData.data.vm_inner_
linkVips(vmNodeData, vmInnerIp);
else if (nodeType == DEVICE_TYPE) {
if (anchorType == 'Bottom') {
cacheKey = 'DEVICE-SNAPSHOT-'+
cacheConnectionData[cacheKey] = null;
deviceNodeData = deviceNodeDataMapping[content];
linkSnapshot(deviceNodeData.data.aliUid, content, deviceNodeData);
for (var j = 0; j & targetAnchors. j++) {
var targetUUID = toId + targetAnchors[j];
jsPlumb.addEndpoint(toId, this.targetEndpoint, { anchor:targetAnchors[j], uuid:targetUUID });
152 //////////////////////////////////////////////////////////////////////////////
153 // 这里将所有用到的数据结构汇聚在这里, 避免修改时需要在代码中穿行, 浪费时间
* 重新刷新VM关联实体时需要使用到VM的信息,这里进行全局缓存,避免重复查询
* 重新刷新VM关联实体时VM必定存在, vmNodeData 也必定是最近一次查询的结果
158 var vmNodeData = {};
* 重新刷新磁盘关联快照实体时需要使用到磁盘的信息,这里进行全局缓存,避免重复查询
* 重新刷新磁盘关联快照实体时磁盘必定存在,且必定是最近一次查询的结果
* eg. {'instanceId': { "ecsInstanceId": "vmName", "houyiDiskId": "102-",
"aliUid": aliUidNum,
"instanceId": "d-28ilj8rsf", ... }}
165 var deviceNodeDataMapping = {};
* 拓扑图中的节点类型
169 var nodeTypeArray = ['VM', 'DEVICE', 'NC', 'VIP', 'SNAPSHOT', 'CLUSTER', 'AVZ', 'ACCOUNT'];
170 var VM_TYPE = nodeTypeArray[0];
171 var DEVICE_TYPE = nodeTypeArray[1];
172 var NC_TYPE = nodeTypeArray[2];
173 var VIP_TYPE = nodeTypeArray[3];
174 var SNAPSHOT_TYPE= nodeTypeArray[4];
175 var CLUSTER_TYPE= nodeTypeArray[5];
176 var AVZ_TYPE= nodeTypeArray[6];
177 var ACCOUNT_TYPE= nodeTypeArray[7];
* cacheConnectionData 节点之间的已有连接数目缓存, 在计算节点位置及布局方法 computeLayout 中用到
'VM-DEVICE-vmkey': 2,
'VM-NC-vmkey':1,
'VM-VIP-vmkey':2 , 'VM-ACCOUNT-vmkey': 1,
* 表示已经有2磁盘/1NC/2VIP/1ACCOUNT 与VM(key 为 vmkey)连接, 这些信息用于计算与VM相连接的同类型的下一个实体的相对位置
185 var cacheConnectionData = {};
* 连接关系的存储, 在 cacheConnections , reconnectAll 方法中用到
* 由于重复节点会移动到新的位置,原有连接会出现断连现象, 因此采用"每次刷新拉取新实体时重连所有连线" 的策略, 可以保证实时性, 只要连接数不多重复连接的开销是可以接受的.
* connections = [[startPoint1, endPoint1], [startPoint2, endPoint2], ..., [startPointN, endPointN]];
191 var connections = [];
* 实体与实体上附着点方向的设置
* DEVICE_TYPE: [['Right', 'Bottom'], ['Left']] 的含义是:
* 对于DEVICE实体: 作为起始节点时, 附着点可以在右方中点(连接CLUSTER), 下方中点(连接快照); 作为终止节点时, 附着点仅在左方中点(连接VM)
197 var entityEndPointsMapping = {
"VM": [['Top', 'Bottom', 'Right', 'Left'], []],
"DEVICE": [['Right', 'Bottom'], ['Left']],
"NC": [['Bottom'], ['Top']],
"VIP": [[], ['Right']],
"SNAPSHOT": [[], ['Top']],
"CLUSTER": [[], ['Left', 'Top']],
"AVZ": [['Bottom'], ['Top']],
"ACCOUNT": [[], ['Bottom']]
* 连接线附着点方向设置
* "VM-ACCOUNT": ['Top', 'Bottom'] 的含义是:
* VM 的上方附着点 与 ACCOUNT 的下方附着点的连接
212 var connectionDirectionMapping = {
"VM-ACCOUNT": ['Top', 'Bottom'],
"VM-NC": ['Bottom', 'Top'],
"NC-CLUSTER": ['Bottom', 'Top'],
"VM-DEVICE": ['Right', 'Left'],
"DEVICE-CLUSTER": ['Right', 'Left'],
"VM-VIP": ['Left', 'Right'],
"DEVICE-SNAPSHOT": ['Bottom', 'Top']
* 节点之间的水平与垂直相对位置
224 var largeVerticalDistance = 270;
225 var verticalDistance = 220;
226 var horizontalDistance = 300;
227 var shortVerticalDistance = 50;
228 var shortHorizontalDistance = 220;
* 节点之间的水平或垂直相对位置和距离的设置
* "VM-DEVICE": [largeVerticalDistance, horizontalDistance]
233 var connectionDistanceMapping = {
"VM-ACCOUNT": [-verticalDistance, 0],
"VM-NC": [shortVerticalDistance, 0],
"NC-CLUSTER": [shortVerticalDistance, 0],
"VM-DEVICE": [largeVerticalDistance, horizontalDistance],
"DEVICE-CLUSTER": [-108, horizontalDistance],
"VM-VIP": [verticalDistance, -horizontalDistance],
"DEVICE-SNAPSHOT": [shortVerticalDistance, shortHorizontalDistance]
* 根节点位置
245 rootPosition = [220, 360];
246 rootTop = rootPosition[0];
247 rootLeft = rootPosition[1];
248 var parentDiv = null;
249 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
250 function drawtopo_asyn(vmName, regionNo, parentDivId) {
parentDiv = $('#'+parentDivId);
var vmInfoReq = {
httpPrefix + '/controllers/vm/obtainVmData',
'params' : {
'vm_name' : vmName,
'region_no': regionNo
var vmjq = doAjaxRequest(vmInfoReq);
var vmInfoLoadedAfter = function(resultJson) {
vmNodeData = resultJson.result.
if (vmNodeData == null) {
alert('没有找到VM的相关信息!');
vmNode = createDiv(vmNodeData);
vmDivId = obtainNodeDivId(vmNodeData);
$('#'+vmDivId).css('top', rootTop + 'px');
$('#'+vmDivId).css('left', rootLeft + 'px');
linkAccount(vmNodeData, vmName, regionNo);
ncId = vmNodeData.data.nc_
linkNc(vmNodeData, ncId, regionNo);
// vmName = 'ATX-28n2dhdq8';
linkDevices(vmNodeData, vmName);
vmInnerIp = vmNodeData.data.vm_inner_
linkVips(vmNodeData, vmInnerIp);
vmjq.done(vmInfoLoadedAfter);
289 function linkAccount(vmNodeData, vmName, regionNo) {
var accountInfoReq = {
'url': httpPrefix + '/controllers/vm/obtainAliyunAccountInfo',
'params': {
'vm_name' : vmName,
'region_no': regionNo
var accountjq = doAjaxRequest(accountInfoReq);
accountjq.done(function(resultJson) {
// for test
// resultJson = {"result":{"msg":"successful","code":200,"data":{"errorCode":null,"errorMsg":null,"aliyunID":"it-cloudpc@","kp":null},"success":true}};
if (resultJson.result.success) {
accountData = resultJson.result.
accountNodeData = createAccountData(accountData);
accountNode = createDiv(accountNodeData);
cacheConnections(vmNodeData, accountNodeData, obtainConnectionDirections(vmNodeData, accountNodeData));
reconnectAll(connections);
$('#error').append('获取关联云账号信息失败!');
}).fail(function() {
$('#error').append('获取关联云账号信息失败! ');
318 function linkNc(vmNodeData, ncId, regionNo) {
var ncInfoReq = {
httpPrefix + '/controllers/nc/listNc',
'params': {
'region_no': regionNo,
'nc_id': ncId,
'start': 0,
'page': 1,
'limit': 1
var ncjq = doAjaxRequest(ncInfoReq);
ncjq.done(function(resultJson) {
ncDataList = resultJson.
if (ncDataList.length & 0) {
ncData = ncDataList[0];
ncNodeData = createNcData(ncData);
ncNode = createDiv(ncNodeData);
cacheConnections(vmNodeData, ncNodeData, obtainConnectionDirections(vmNodeData, ncNodeData));
ncClusterNodeData = createNcClusterData(ncData);
ncClusterNode = createDiv(ncClusterNodeData);
cacheConnections(ncNodeData, ncClusterNodeData, obtainConnectionDirections(ncNodeData, ncClusterNodeData));
reconnectAll(connections);
$('#error').append('获取关联NC实体失败!');
}).fail(function() {
$('#error').append('获取关联NC实体失败!');
350 function linkDevices(vmNodeData, vmName) {
var deviceInfoReq = {
httpPrefix + '/controllers/disk/search',
'params': {
'vmName': vmName
var regionPeNickName = vmNodeData.data.region_pe_
var devicejq = doAjaxRequest(deviceInfoReq);
devicejq.done(function(resultJson) {
total = resultJson.data.
if (total & 0) {
devices = resultJson.data.
for (var i=0; i& i++) {
deviceData = devices[i];
deviceData['regionPeNickName'] = regionPeNickN
deviceNodeData = createDeviceData(deviceData);
deviceNodeDataMapping[deviceData.instanceId] = deviceNodeD
deviceNode = createDiv(deviceNodeData);
cacheConnections(vmNodeData, deviceNodeData, obtainConnectionDirections(vmNodeData, deviceNodeData));
deviceClusterNodeData = createDeviceClusterData(deviceData);
deviceClusterNode = createDiv(deviceClusterNodeData);
cacheConnections(deviceNodeData, deviceClusterNodeData, obtainConnectionDirections(deviceNodeData,deviceClusterNodeData));
linkSnapshot(devices[i].aliUid, devices[i].instanceId, deviceNodeData);
reconnectAll(connections);
$('#error').append('该VM没有关联的磁盘实体!');
}).fail(function() {
$('#error').append('获取关联磁盘实体失败!');
388 function linkVips(vmNodeData, vmInnerIp) {
var vipInfoReq = {
'url': httpPrefix + '/controllers/slbvip/listVip',
'params': {
'realserver_param': vmInnerIp,
'start': 0,
'page': 1,
'limit': 100
var vipjq = doAjaxRequest(vipInfoReq);
vipjq.done(function(resultJson) {
total = resultJson.
vips = resultJson.
if (total & 0) {
for (j=0; j& j++) {
var vipInfo = vips[j];
vipNodeData = createVipData(vipInfo);
vipNode = createDiv(vipNodeData);
cacheConnections(vmNodeData, vipNodeData, obtainConnectionDirections(vmNodeData,vipNodeData));
reconnectAll(connections);
$('#error').append('该VM没有关联的VIP实体!');
}).fail(function() {
$('#error').append('获取关联VIP实体失败!');
421 function linkSnapshot(aliUid, diskId, deviceNodeData) {
var snapshotInfoReq = {
'url': httpPrefix + '/controllers/snapshot/search',
'params': {
'aliUid': aliUid,
'diskId': diskId
var snapshotjq = doAjaxRequest(snapshotInfoReq);
snapshotjq.done(function(resultJson) {
total = resultJson.
if (total & 0) {
snapshotDataList = resultJson.
for (k=0; k& k++) {
snapshotData = snapshotDataList[k];
snapshotNodeData = createSnapshotData(snapshotData);
snapshotNode = createDiv(snapshotNodeData);
cacheConnections(deviceNodeData, snapshotNodeData, obtainConnectionDirections(deviceNodeData, snapshotNodeData));
reconnectAll(connections);
$('#error').append('磁盘 ' + diskId + ' 没有关联的快照实体!');
}).fail(function() {
$('#error').append('磁盘' + diskId + ' 获取关联快照实体失败!');
* createXXXData
* 创建拓扑图所使用的节点数据
455 function createVmData(vmData) {
'type': 'VM',
'key': vmData.vm_name,
'data': vmData
462 function createNcData(ncData) {
'type': 'NC',
'key': ncData.ip,
'data': ncData
469 function createNcClusterData(ncData) {
'type': 'CLUSTER',
'key': ncData.regionPeNickName,
'regionNo': ncData.regionNo,
'regionNickName': ncData.regionNickName,
'regionPeNickName': ncData.regionPeNickName
480 function createDeviceData(deviceData) {
'type': 'DEVICE',
'key': deviceData.instanceId,
'data': deviceData
487 function createDeviceClusterData(deviceData) {
'type': 'CLUSTER',
'key': deviceData.regionNo,
'regionNo': deviceData.regionNo
496 function createSnapshotData(snapshotData) {
'type': 'SNAPSHOT',
'key': snapshotData.snapshotId,
'data': snapshotData
503 function createSnapshotClusterData(snapshotData) {
'type': 'CLUSTER',
'key': snapshotData.regionNo,
'regionNo': snapshotData.regionNo
512 function createVipData(vipData) {
'type': 'VIP',
'key': vipData.vipAddress,
'data': vipData
519 function createAccountData(accountData) {
'type': 'ACCOUNT',
'key': accountData.aliyunID,
'data': accountData
* 缓存起始节点 beginNode 和终止节点 endNode 的连接关系
529 function cacheConnections(beginNode, endNode, directions) {
computeLayout(beginNode, endNode);
var startPoint = obtainNodeDivId(beginNode) + directions[0];
var endPoint = obtainNodeDivId(endNode) + directions[1];
connections.push([startPoint, endPoint]);
* 计算节点位置及布局
540 function computeLayout(beginNode, endNode) {
var beginDivId = obtainNodeDivId(beginNode);
var endDivId = obtainNodeDivId(endNode);
var beginNodeType = beginNode.
var endNodeType = endNode.
beginNodeTop = $('#'+beginDivId).offset().
beginNodeLeft = $('#'+beginDivId).offset().
var key = beginNodeType + '-' + endNodeType + '-' + beginNode.
if (cacheConnectionData[key] == null) {
cacheConnectionData[key] = -1;
cacheConnectionData[key] = cacheConnectionData[key]+1;
connNum = cacheConnectionData[key];
var typeKey = beginNodeType + '-' + endNodeT
var relDistance = connectionDistanceMapping[typeKey];
var relVertiDistance = relDistance[0];
var relHoriDistance = relDistance[1];
switch (beginNodeType) {
case VM_TYPE:
if (endNodeType == VIP_TYPE) {
endNodePosition = [beginNodeTop+connNum*relVertiDistance, beginNodeLeft+relHoriDistance];
else if (endNodeType == DEVICE_TYPE) {
endNodePosition = [beginNodeTop+connNum*relVertiDistance, beginNodeLeft+relHoriDistance];
else if (endNodeType == NC_TYPE) {
endNodePosition = [beginNodeTop+relVertiDistance, beginNodeLeft+relHoriDistance];
else if (endNodeType == ACCOUNT_TYPE) {
endNodePosition = [beginNodeTop+relVertiDistance, beginNodeLeft+relHoriDistance];
case DEVICE_TYPE:
if (endNodeType == CLUSTER_TYPE) {
endNodePosition = [beginNodeTop+relVertiDistance, beginNodeLeft+relHoriDistance];
else if (endNodeType == SNAPSHOT_TYPE) {
endNodePosition = [beginNodeTop+relVertiDistance, beginNodeLeft+(connNum+1)*relHoriDistance];
case VIP_TYPE:
case NC_TYPE:
if (endNodeType == CLUSTER_TYPE) {
endNodePosition = [beginNodeTop+relVertiDistance, beginNodeLeft+relHoriDistance];
case SNAPSHOT_TYPE:
$('#'+endDivId).css('top', endNodePosition[0] + 'px');
$('#'+endDivId).css('left', endNodePosition[1] + 'px');
addEndPoints(beginDivId, beginNodeType);
addEndPoints(endDivId, endNodeType);
* 为节点添加用于连线的附着点
* @param nodeDivId
节点的 DIV ID
* @param type
610 function addEndPoints(nodeDivId, type) {
var startAttachedPoints = entityEndPointsMapping[type][0];
var endAttachedPoints = entityEndPointsMapping[type][1];
topoDrawUtil.addEndpoints(nodeDivId, startAttachedPoints, endAttachedPoints);
* 连接所有连线
618 function reconnectAll(connections) {
for (i=0; i&connections. i++) {
jsPlumb.connect({uuids:connections[i], editable: false});
// 使所有拓扑节点均为可拉拽的
jsPlumb.draggable(jsPlumb.getSelector(".node"), { grid: [5, 5] });
* div id cache , avoid duplicated div.
* {'divId': 'divStr'}
631 divIdCache = {};
* 为节点数据创建节点\附着点并返回节点的DIV
635 function createDiv(metaNode) {
var clickUrl = '';
var display = '';
var type = metaNode.
var regionPeNickname = '';
if (metaNode.data != null) {
regionPeNickname = metaNode.data.regionPeNickN
nodeDivId = obtainNodeDivId(metaNode);
if (divIdCache[nodeDivId] != null) {
// 该节点要移动到新的位置, 因此原来的附着点要去掉
topoDrawUtil.removeAllEndPoints(nodeDivId);
return divIdCache[nodeDivId];
switch(type.toUpperCase()) {
case VM_TYPE:
clickUrl = httpPrefix + '/framehtml/vm_monitor.html?vm_name=' + metaNode.key + '&data='+JSON.stringify(metaNode.data).replace(/\"/g,"'");
display = metaNode.
case DEVICE_TYPE:
displayDevice1 = metaNode.data.instanceId;
clickDeviceUrl2 = httpPrefix + '/framehtml/device_monitor.html?device_id=' + metaNode.data.houyiDiskId + '&ion_pe_nickname='+regionPeN
displayDevice2 = metaNode.data.houyiDiskId;
case NC_TYPE:
var regionNo = metaNode.data.regionNo;
clickUrl = httpPrefix + '/framehtml/nc_monitor.html?nc_ip=' + metaNode.key + '&ion_pe_nickname='+regionPeNickname + '&ion_no='+regionNo;
display = metaNode.
case VIP_TYPE:
display = metaNode.key + ':' + metaNode.data.
clickUrl = httpPrefix + '/framehtml/vip_monitor.html?vip=' + display + '&ion_pe_nickname='+regionPeNickname + '&slbdb_configId='+metaNode.data.slbdb_configId;
case SNAPSHOT_TYPE:
display = metaNode.key + '&br/&' + metaNode.data.houyiSnapshotId + '&br/&&span style="color:green"&'+ metaNode.data.regionNo + '&/span&';
case CLUSTER_TYPE:
case AVZ_TYPE:
case ACCOUNT_TYPE:
display = metaNode.
if (type == VM_TYPE || type == NC_TYPE || type == VIP_TYPE || type == ACCOUNT_TYPE ) {
'&div class="node biggerNode" id="' + nodeDivId + '"&&strong&'
+ metaNode.type + '&br/&&a href="' + clickUrl + '" target="_blank"&' + display + '&/a&&br/&&/strong&&/div&';
else if (type == DEVICE_TYPE){
'&div class="node biggerNode" id="' + nodeDivId + '"&&strong&'
+ metaNode.type + '&br/&' + displayDevice1 + '&br/&&a href="' + clickDeviceUrl2 + '" target="_blank"&' + displayDevice2 + '&/a&&br/&&/strong&&/div&';
divStr = '&div class="node biggerNode" id="' + nodeDivId + '"&&strong&'
+ metaNode.type + '&br/&' + display + '&br/&&/strong&&/div&';
parentDiv.append(divStr);
divIdCache[nodeDivId] = divS
return divS
700 function obtainConnectionDirections(srcNodeData, destNodeData) {
var key = srcNodeData.type + '-' + destNodeData.
var startDirection = connectionDirectionMapping[key][0];
var endDirection = connectionDirectionMapping[key][1];
return [startDirection, endDirection];
* 生成节点的 DIV id
* divId = nodeType.toUpperCase + "_" + key
* key 可能为 IP , 其中的 . 将被替换成 ZZZ , 因为 jquery id 选择器中 . 属于转义字符.
* eg. {type: 'VM', key: '1.1.1.1' }, divId = 'VM_1ZZZ1ZZZ1ZZZ1'
712 function obtainNodeDivId(metaNode) {
if (metaNode.type == VIP_TYPE) {
return metaNode.type.toUpperCase() + '_' + transferKey(metaNode.key) + '_' + metaNode.data.
return metaNode.type.toUpperCase() + '_' + transferKey(metaNode.key);
718 function transferKey(key) {
return key.replace(/\./g, 'ZZZ').replace(/\@/g,'YYY');
721 function revTransferKey(value) {
return value.replace(/ZZZ/g, '.').replace('/YYY/g','@');
阅读(...) 评论()

我要回帖

更多关于 jsplumb 流程图 的文章

 

随机推荐