使用 jsPlumb 绘制拓扑图 —— 异步加载与绘制的实现
本文实现的方法可以边异步加载数据边绘制拓扑图。 有若干点需要说明一下:
1. 一次性获取所有数据并绘制拓扑图, 请参见文章: <使用 JsPlumb 绘制拓扑图的通用方法> ; 本文实现的最终显示效果与之类似, 所使用的基本方法与之类似。
2. 在此次实现中, 可以一边异步加载数据一边绘制拓扑图, 是动态可扩展的;
3. 所有影响节点位置、布局的配置均放置在最前面, 便于修改, 避免在代码中穿梭, 浪费时间;
4. 布局算法比之前的实现更加完善;
5. 此实现由于与业务逻辑绑得比较紧, 可复用的部分不多, 但是可以作为一个模板, 用在读者自己的场景中, 自行修改相应的节点类型、URL等。
6. 添加了附着点的点击事件处理, 可以刷新显示关联实体;
7. 主流程很简单: 发送 AJAX 请求获取数据 ---> 创建节点(实际上就是DIV) ---> 计算节点位置、布局 ---> 添加节点附着点 ---> 缓存节点连接 ---> 连接所有现有的缓存节点连接。 多个 AJAX 请求的处理是异步的, 顺序没有控制。
8. 代码:
/**
* 使用 jsPlumb 根据指定的拓扑数据结构绘制拓扑图
* 使用 drawTopo_asyn(vmName, regionNo, parentDivId) 方法
*/
/**
* 初始化拓扑图实例及外观设置
*/
(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],
label:"",
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.length; 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.type;
var nodeType = toId.split('_')[0];
var content = toId.split('_')[1];
if (nodeType == VM_TYPE) {
switch (anchorType) {
case 'Right':
cacheKey = 'VM-DEVICE-'+ vmNodeData.key;
cacheConnectionData[cacheKey] = null;
linkDevices(vmNodeData, vmNodeData.key);
break;
case 'Top':
cacheKey = 'VM-ACCOUNT-'+ vmNodeData.key;
cacheConnectionData[cacheKey] = null;
vmName = vmNodeData.key;
regionNo = vmNodeData.data.region_no;
linkAccount(vmNodeData, vmName, regionNo);
break;
case 'Bottom':
cacheKey = 'VM-NC-'+ vmNodeData.key;
cacheConnectionData[cacheKey] = null;
ncId = vmNodeData.data.nc_id;
regionNo = vmNodeData.data.region_no;
linkNc(vmNodeData, ncId, regionNo);
break;
case 'Left':
cacheKey = 'VM-VIP-'+ vmNodeData.key;
cacheConnectionData[cacheKey] = null;
vmInnerIp = vmNodeData.data.vm_inner_ip;
linkVips(vmNodeData, vmInnerIp);
break;
default:
break;
}
}
else if (nodeType == DEVICE_TYPE) {
if (anchorType == 'Bottom') {
cacheKey = 'DEVICE-SNAPSHOT-'+ content;
cacheConnectionData[cacheKey] = null;
deviceNodeData = deviceNodeDataMapping[content];
linkSnapshot(deviceNodeData.data.aliUid, content, deviceNodeData);
}
}
});
}
for (var j = 0; j < targetAnchors.length; j++) {
var targetUUID = toId + targetAnchors[j];
jsPlumb.addEndpoint(toId, this.targetEndpoint, { anchor:targetAnchors[j], uuid:targetUUID });
}
}
};
})();
//////////////////////////////////////////////////////////////////////////////
// 这里将所有用到的数据结构汇聚在这里, 避免修改时需要在代码中穿行, 浪费时间
/**
* 重新刷新VM关联实体时需要使用到VM的信息,这里进行全局缓存,避免重复查询
* 重新刷新VM关联实体时VM必定存在, vmNodeData 也必定是最近一次查询的结果
*/
var vmNodeData = {};
/**
* 重新刷新磁盘关联快照实体时需要使用到磁盘的信息,这里进行全局缓存,避免重复查询
* 重新刷新磁盘关联快照实体时磁盘必定存在,且必定是最近一次查询的结果
* eg. {'instanceId': { "ecsInstanceId": "vmName", "houyiDiskId": "102-80012003",
"aliUid": aliUidNum, "instanceId": "d-28ilj8rsf", ... }}
*/
var deviceNodeDataMapping = {};
/**
* 拓扑图中的节点类型
*/
var nodeTypeArray = ['VM', 'DEVICE', 'NC', 'VIP', 'SNAPSHOT', 'CLUSTER', 'AVZ', 'ACCOUNT'];
var VM_TYPE = nodeTypeArray[0];
var DEVICE_TYPE = nodeTypeArray[1];
var NC_TYPE = nodeTypeArray[2];
var VIP_TYPE = nodeTypeArray[3];
var SNAPSHOT_TYPE= nodeTypeArray[4];
var CLUSTER_TYPE= nodeTypeArray[5];
var AVZ_TYPE= nodeTypeArray[6];
var ACCOUNT_TYPE= nodeTypeArray[7];
/**
* cacheConnectionData 节点之间的已有连接数目缓存, 在计算节点位置及布局方法 computeLayout 中用到
* eg. {
* 'VM-DEVICE-vmkey': 2, 'VM-NC-vmkey':1, 'VM-VIP-vmkey':2 , 'VM-ACCOUNT-vmkey': 1,
* }
* 表示已经有2磁盘/1NC/2VIP/1ACCOUNT 与VM(key 为 vmkey)连接, 这些信息用于计算与VM相连接的同类型的下一个实体的相对位置
*/
var cacheConnectionData = {};
/**
* 连接关系的存储, 在 cacheConnections , reconnectAll 方法中用到
* 由于重复节点会移动到新的位置,原有连接会出现断连现象, 因此采用"每次刷新拉取新实体时重连所有连线" 的策略, 可以保证实时性, 只要连接数不多重复连接的开销是可以接受的.
* connections = [[startPoint1, endPoint1], [startPoint2, endPoint2], ..., [startPointN, endPointN]];
*/
var connections = [];
/**
* 实体与实体上附着点方向的设置
* DEVICE_TYPE: [['Right', 'Bottom'], ['Left']] 的含义是:
* 对于DEVICE实体: 作为起始节点时, 附着点可以在右方中点(连接CLUSTER), 下方中点(连接快照); 作为终止节点时, 附着点仅在左方中点(连接VM)
*/
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 的下方附着点的连接
*/
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']
}
/**
* 节点之间的水平与垂直相对位置
*/
var largeVerticalDistance = 270;
var verticalDistance = 220;
var horizontalDistance = 300;
var shortVerticalDistance = 50;
var shortHorizontalDistance = 220;
/**
* 节点之间的水平或垂直相对位置和距离的设置
* "VM-DEVICE": [largeVerticalDistance, horizontalDistance]
*/
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]
}
/**
* 根节点位置
*/
rootPosition = [220, 360];
rootTop = rootPosition[0];
rootLeft = rootPosition[1];
var parentDiv = null;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function drawtopo_asyn(vmName, regionNo, parentDivId) { parentDiv = $('#'+parentDivId); var vmInfoReq = {
'url': httpPrefix + '/controllers/vm/obtainVmData',
'params' : {
'vm_name' : vmName,
'region_no': regionNo
}
};
var vmjq = doAjaxRequest(vmInfoReq);
var vmInfoLoadedAfter = function(resultJson) { vmNodeData = resultJson.result.data;
if (vmNodeData == null) {
alert('没有找到VM的相关信息!');
return ;
}
vmNode = createDiv(vmNodeData); vmDivId = obtainNodeDivId(vmNodeData);
$('#'+vmDivId).css('top', rootTop + 'px');
$('#'+vmDivId).css('left', rootLeft + 'px'); linkAccount(vmNodeData, vmName, regionNo); ncId = vmNodeData.data.nc_id;
linkNc(vmNodeData, ncId, regionNo); // vmName = 'ATX-28n2dhdq8';
linkDevices(vmNodeData, vmName); vmInnerIp = vmNodeData.data.vm_inner_ip;
linkVips(vmNodeData, vmInnerIp); };
vmjq.done(vmInfoLoadedAfter);
}
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@alibaba-inc.com","kp":null},"success":true}}; if (resultJson.result.success) {
accountData = resultJson.result.data;
accountNodeData = createAccountData(accountData);
accountNode = createDiv(accountNodeData);
cacheConnections(vmNodeData, accountNodeData, obtainConnectionDirections(vmNodeData, accountNodeData));
reconnectAll(connections);
}
else {
$('#error').append('获取关联云账号信息失败!');
}
}).fail(function() {
$('#error').append('获取关联云账号信息失败! ');
}); }
function linkNc(vmNodeData, ncId, regionNo) {
var ncInfoReq = {
'url': 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.data;
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);
}
else {
$('#error').append('获取关联NC实体失败!');
}
}).fail(function() {
$('#error').append('获取关联NC实体失败!');
});
}
function linkDevices(vmNodeData, vmName) {
var deviceInfoReq = {
'url' : httpPrefix + '/controllers/disk/search',
'params': {
'vmName': vmName
}
}
var regionPeNickName = vmNodeData.data.region_pe_nickname;
var devicejq = doAjaxRequest(deviceInfoReq);
devicejq.done(function(resultJson) { total = resultJson.data.total;
if (total > 0) {
devices = resultJson.data.list; for (var i=0; i<total; i++) { deviceData = devices[i];
deviceData['regionPeNickName'] = regionPeNickName;
deviceNodeData = createDeviceData(deviceData);
deviceNodeDataMapping[deviceData.instanceId] = deviceNodeData;
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);
}
else {
$('#error').append('该VM没有关联的磁盘实体!');
}
}).fail(function() {
$('#error').append('获取关联磁盘实体失败!');
});
}
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.total;
vips = resultJson.data;
if (total > 0) {
for (j=0; j<total; j++) {
var vipInfo = vips[j];
vipNodeData = createVipData(vipInfo);
vipNode = createDiv(vipNodeData);
cacheConnections(vmNodeData, vipNodeData, obtainConnectionDirections(vmNodeData,vipNodeData));
}
reconnectAll(connections);
}
else {
$('#error').append('该VM没有关联的VIP实体!');
}
}).fail(function() {
$('#error').append('获取关联VIP实体失败!');
}); }
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.total;
if (total > 0) {
snapshotDataList = resultJson.list;
for (k=0; k<total; k++) {
snapshotData = snapshotDataList[k];
snapshotNodeData = createSnapshotData(snapshotData);
snapshotNode = createDiv(snapshotNodeData);
cacheConnections(deviceNodeData, snapshotNodeData, obtainConnectionDirections(deviceNodeData, snapshotNodeData));
}
reconnectAll(connections);
}
else {
$('#error').append('磁盘 ' + diskId + ' 没有关联的快照实体!');
}
}).fail(function() {
$('#error').append('磁盘' + diskId + ' 获取关联快照实体失败!');
});
}
/**
* createXXXData
* 创建拓扑图所使用的节点数据
*/
function createVmData(vmData) {
return {
'type': 'VM',
'key': vmData.vm_name,
'data': vmData
}
}
function createNcData(ncData) {
return {
'type': 'NC',
'key': ncData.ip,
'data': ncData
}
}
function createNcClusterData(ncData) {
return {
'type': 'CLUSTER',
'key': ncData.regionPeNickName,
'data': {
'regionNo': ncData.regionNo,
'regionNickName': ncData.regionNickName,
'regionPeNickName': ncData.regionPeNickName
}
}
}
function createDeviceData(deviceData) {
return {
'type': 'DEVICE',
'key': deviceData.instanceId,
'data': deviceData
}
}
function createDeviceClusterData(deviceData) {
return {
'type': 'CLUSTER',
'key': deviceData.regionNo,
'data': {
'regionNo': deviceData.regionNo
}
}
}
function createSnapshotData(snapshotData) {
return {
'type': 'SNAPSHOT',
'key': snapshotData.snapshotId,
'data': snapshotData
}
}
function createSnapshotClusterData(snapshotData) {
return {
'type': 'CLUSTER',
'key': snapshotData.regionNo,
'data': {
'regionNo': snapshotData.regionNo
}
}
}
function createVipData(vipData) {
return {
'type': 'VIP',
'key': vipData.vipAddress,
'data': vipData
}
}
function createAccountData(accountData) {
return {
'type': 'ACCOUNT',
'key': accountData.aliyunID,
'data': accountData
}
}
/**
* 缓存起始节点 beginNode 和终止节点 endNode 的连接关系
*/
function cacheConnections(beginNode, endNode, directions) { computeLayout(beginNode, endNode); var startPoint = obtainNodeDivId(beginNode) + directions[0];
var endPoint = obtainNodeDivId(endNode) + directions[1];
connections.push([startPoint, endPoint]);
}
/**
* 计算节点位置及布局
*/
function computeLayout(beginNode, endNode) { var beginDivId = obtainNodeDivId(beginNode);
var endDivId = obtainNodeDivId(endNode);
var beginNodeType = beginNode.type;
var endNodeType = endNode.type; beginNodeTop = $('#'+beginDivId).offset().top;
beginNodeLeft = $('#'+beginDivId).offset().left; var key = beginNodeType + '-' + endNodeType + '-' + beginNode.key;
if (cacheConnectionData[key] == null) {
cacheConnectionData[key] = -1;
}
else {
cacheConnectionData[key] = cacheConnectionData[key]+1;
}
connNum = cacheConnectionData[key]; var typeKey = beginNodeType + '-' + endNodeType;
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];
}
break;
case DEVICE_TYPE:
if (endNodeType == CLUSTER_TYPE) {
endNodePosition = [beginNodeTop+relVertiDistance, beginNodeLeft+relHoriDistance];
}
else if (endNodeType == SNAPSHOT_TYPE) {
endNodePosition = [beginNodeTop+relVertiDistance, beginNodeLeft+(connNum+1)*relHoriDistance];
}
break;
case VIP_TYPE:
break;
case NC_TYPE:
if (endNodeType == CLUSTER_TYPE) {
endNodePosition = [beginNodeTop+relVertiDistance, beginNodeLeft+relHoriDistance];
}
break;
case SNAPSHOT_TYPE:
default:
break;
} $('#'+endDivId).css('top', endNodePosition[0] + 'px');
$('#'+endDivId).css('left', endNodePosition[1] + 'px'); addEndPoints(beginDivId, beginNodeType);
addEndPoints(endDivId, endNodeType);
}
/**
* 为节点添加用于连线的附着点
* @param nodeDivId 节点的 DIV ID
* @param type 节点类型
*/
function addEndPoints(nodeDivId, type) {
var startAttachedPoints = entityEndPointsMapping[type][0];
var endAttachedPoints = entityEndPointsMapping[type][1];
topoDrawUtil.addEndpoints(nodeDivId, startAttachedPoints, endAttachedPoints);
}
/**
* 连接所有连线
*/
function reconnectAll(connections) { var i=0;
for (i=0; i<connections.length; i++) {
jsPlumb.connect({uuids:connections[i], editable: false});
}
// 使所有拓扑节点均为可拉拽的
jsPlumb.draggable(jsPlumb.getSelector(".node"), { grid: [5, 5] });
}
/**
* div id cache , avoid duplicated div.
* {'divId': 'divStr'}
*/
divIdCache = {};
/**
* 为节点数据创建节点\附着点并返回节点的DIV
*/
function createDiv(metaNode) {
var clickUrl = '';
var display = '';
var type = metaNode.type;
var regionPeNickname = '';
if (metaNode.data != null) {
regionPeNickname = metaNode.data.regionPeNickName;
} 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.key;
break;
case DEVICE_TYPE:
displayDevice1 = metaNode.data.instanceId;
clickDeviceUrl2 = httpPrefix + '/framehtml/device_monitor.html?device_id=' + metaNode.data.houyiDiskId + '®ion_pe_nickname='+regionPeNickname;
displayDevice2 = metaNode.data.houyiDiskId;
break;
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.key;
break;
case VIP_TYPE:
display = metaNode.key + ':' + metaNode.data.port;
clickUrl = httpPrefix + '/framehtml/vip_monitor.html?vip=' + display + '®ion_pe_nickname='+regionPeNickname + '&slbdb_configId='+metaNode.data.slbdb_configId;
break;
case SNAPSHOT_TYPE:
display = metaNode.key + '<br/>' + metaNode.data.houyiSnapshotId + '<br/><span style="color:green">'+ metaNode.data.regionNo + '</span>';
break;
case CLUSTER_TYPE:
case AVZ_TYPE:
case ACCOUNT_TYPE:
display = metaNode.key;
break;
default:
break;
} if (type == VM_TYPE || type == NC_TYPE || type == VIP_TYPE || type == ACCOUNT_TYPE ) {
divStr = '<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){
divStr = '<div class="node biggerNode" id="' + nodeDivId + '"><strong>'
+ metaNode.type + '<br/>' + displayDevice1 + '<br/><a href="' + clickDeviceUrl2 + '" target="_blank">' + displayDevice2 + '</a><br/></strong></div>';
}
else {
divStr = '<div class="node biggerNode" id="' + nodeDivId + '"><strong>'
+ metaNode.type + '<br/>' + display + '<br/></strong></div>';
}
parentDiv.append(divStr); divIdCache[nodeDivId] = divStr;
return divStr;
}
function obtainConnectionDirections(srcNodeData, destNodeData) {
var key = srcNodeData.type + '-' + destNodeData.type;
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'
*/
function obtainNodeDivId(metaNode) {
if (metaNode.type == VIP_TYPE) {
return metaNode.type.toUpperCase() + '_' + transferKey(metaNode.key) + '_' + metaNode.data.port;
}
return metaNode.type.toUpperCase() + '_' + transferKey(metaNode.key);
}
function transferKey(key) {
return key.replace(/\./g, 'ZZZ').replace(/\@/g,'YYY');
}
function revTransferKey(value) {
return value.replace(/ZZZ/g, '.').replace('/YYY/g','@');
}
使用 jsPlumb 绘制拓扑图 —— 异步加载与绘制的实现的更多相关文章
- Android异步加载访问网络图片-解析json
来自:http://www.imooc.com/video/7871 推荐大家去学习这个视频,讲解的很不错. 慕课网提供了一个json网址可以用来学习:http://www.imooc.com/api ...
- (转)Unity3D研究院之异步加载游戏场景与异步加载游戏资源进度条(三十一)
异步任务相信大家应该不会陌生,那么本章内容MOMO将带领大家学习Unity中的一些异步任务.在同步加载游戏场景的时候通常会使用方法 Application.LoadLevel(“yourScene ...
- android ListView异步加载图片(双缓存)
首先声明,参考博客地址:http://www.iteye.com/topic/685986 对于ListView,相信很多人都很熟悉,因为确实太常见了,所以,做的用户体验更好,就成了我们的追求... ...
- Unity3D研究院之异步加载游戏场景与异步加载游戏资源进度条
Unity3D研究院之异步加载游戏场景与异步加载游戏资源进度条 异步任务相信大家应该不会陌生,那么本章内容MOMO将带领大家学习Unity中的一些异步任务.在同步加载游戏场景的时候通常会使用方法 Ap ...
- 背水一战 Windows 10 (57) - 控件(集合类): ListViewBase - 增量加载, 分步绘制
[源码下载] 背水一战 Windows 10 (57) - 控件(集合类): ListViewBase - 增量加载, 分步绘制 作者:webabcd 介绍背水一战 Windows 10 之 控件(集 ...
- AsyncTask使用实例,异步加载图片
在上一篇,详细介绍了AsynTask的基础知识.没有读过的朋友可以点击下面的链接: http://www.cnblogs.com/fuly550871915/p/4892310.html 那么在这篇文 ...
- cocos2d-x addImageAsync()异步加载资源成功之后的场景跳转问题
http://blog.csdn.net/w20175357/article/details/23546985 1.先说说addImageAsync()异步加载图片的问题 做游戏的时候现在资源的比较大 ...
- (转)Unity3D研究院之异步加载游戏场景与异步加载游戏资源进度条(三十一)
http://www.xuanyusong.com/archives/1427 异步任务相信大家应该不会陌生,那么本章内容MOMO将带领大家学习Unity中的一些异步任务.在同步加载游戏场景的时候通 ...
- Jetpack Compose学习(4)——Image(图片)使用及Coil图片异步加载库使用
原文地址 Jetpack Compose学习(4)--Image(图片)使用及Coil图片异步加载库使用 | Stars-One的杂货小窝 本篇讲解下关于Image的使用及使用Coil开源库异步加载网 ...
随机推荐
- Spark cache 和 persist
1)RDD的cache()方法其实调用的就是persist方法,缓存策略均为MEMORY_ONLY:2)可以通过persist方法手工设定StorageLevel来满足工程需要的存储级别:3)cach ...
- [转载]QQ通讯原理及QQ是怎么穿透内网的
QQ是一个基于TCP/UDP协议的通讯软件 发送消息的时候是UDP打洞,登陆的时候使用HTTP~因为登陆服务器其实就是一个HTTP服务器,只不过不是常用的那些,那个服务器是腾讯自行开发的!!! 一.登 ...
- JAVA6开发WebService (三)——几个概念
转载自http://wuhongyu.iteye.com/blog/808922 要了解WebService,光能写代码不行啊,这说说WebService最基本的概念. 首先WebService要知道 ...
- 配置DNS实验一例
1安装bind软件 2查看当前DNS服务 3修改配置文件 4测试
- Android Studio 环境部署 (转载)
Android Studio的安装和使用过程经常需要下载以来文件和Gradle版本,而Google网站在天朝的访问可谓步履维艰,没有稳定的FQ工具是非常痛苦的.何况,作为一个优秀的程序员,不能访问国外 ...
- time模块目录下自己建立一个名为log的文件夹
使用python调用ping命令,然后在日志中记录ping的结果,用来监测网络连通情况. 代码: [python]from time import *from subprocess import *w ...
- 玩坏JVM很简单--toString的递归调用
在JVM的内存管理机制下很少发生内存溢出的情况.至少我碰见的少,好像在SSH我多次发布项目时候出现过一次.今天看见一个特简单的方法让内存溢出(好吧,我似乎作死了--!): public class I ...
- CMS .NET 程序框架 从2.0/3.5升级到4.0 版本后 需要调整的地方
问题一: document.forms1.action 不可使用 需要修改程 document.forms[0] .NET 程序框架 从2.0/3.5升级到4.0 版本后,document.forms ...
- 10.5.2 Boot Block 启动块 - 操作系统教程
简单一篇文章明白地讲解了计算机操作系统的启动过程 OPERATING SYSTEM CONCEPTS ABRAHAM SILBERSCHATZ PETER BAER GALVIN GREG GAGNE ...
- jfinal路由简单解析
在jfinal中,通过JFinalFilter对所有的类进行过滤. 以下是路由的调用关系(我在调用关系旁边做了标记,会贴出具体的代码和解释): -1- Config: Routes -2- Inter ...