使用 jsPlumb 绘制拓扑图 —— 异步载入与绘制的实现
本文实现的方法能够边异步载入数据边绘制拓扑图。
有若干点须要说明一下:
1. 一次性获取全部数据并绘制拓扑图。 请參见文章: <使用 JsPlumb 绘制拓扑图的通用方法> ; 本文实现的终于显示效果与之类似, 所使用的基本方法与之类似。
2. 在此次实现中, 能够一边异步载入数据一边绘制拓扑图, 是动态可扩展的;
3. 全部影响节点位置、布局的配置均放置在最前面, 便于改动。 避免在代码中穿梭, 浪费时间;
4. 布局算法比之前的实现更加完好。
5. 此实现因为与业务逻辑绑得比較紧, 可复用的部分不多, 可是能够作为一个模板。 用在读者自己的场景中。 自行改动对应的节点类型、URL等。
6. 加入了附着点的点击事件处理。 能够刷新显示关联实体;
7. 主流程非常easy: 发送 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 绘制拓扑图 —— 异步载入与绘制的实现的更多相关文章
- 使用 jsPlumb 绘制拓扑图 —— 异步加载与绘制的实现
本文实现的方法可以边异步加载数据边绘制拓扑图. 有若干点需要说明一下: 1. 一次性获取所有数据并绘制拓扑图, 请参见文章: <使用 JsPlumb 绘制拓扑图的通用方法> ; 本文实现 ...
- 使用JsPlumb绘制拓扑图的通用方法
转自:http://www.it165.net/pro/html/201311/7616.html 使用JsPlumb绘制拓扑图的通用方法 一. 实现目标 绘制拓扑图, 实际上是个数据结构和算法的问题 ...
- 使用 JsPlumb 绘制拓扑图的通用方法
摘要: 实现 JsPlumb 绘制拓扑图的通用方法. 只要服务端返回一个符合指定格式的数据结构,就可以绘制相应的拓扑图. 难度: 中级 示例工程见: http://download.csdn.net ...
- ArcGIS Runtime for Android 使用异步GP服务绘制等值线
关于基于Android上ArcGIS Server GP服务的调用,已经有前辈给出了很好的例子: http://blog.csdn.net/esrichinacd/article/details/92 ...
- (转)ArcGIS Runtime for Android 使用异步GP服务绘制等值线
关于基于Android上ArcGIS Server GP服务的调用,已经有前辈给出了很好的例子: http://blog.csdn.net/esrichinacd/article/details/92 ...
- ListView的异步载入(笔记,多线程和AsyncTask)
异步载入最经常使用的两种方式: 多线程,线程池 AsyncTask 实例操作: 从一个站点上获取Json数据.然后将数据在ListView上显示. 1.创建item_layout布局 , 改动 ...
- lazyload.js实现图片异步载入
所谓图片异步加载,意思是不用一次把图片全部加载完,你可以叫它延迟加载,缓冲加载都行. 看看你有没有这种需求:某篇文章图片很多,如果在载入文章时就载入所有图片,无疑会延缓载入速度,让用户等更久,所以,我 ...
- Android异步载入全解析之使用多线程
异步载入之使用多线程 初次尝试 异步.异步,事实上说白了就是多任务处理.也就是多线程执行.多线程那就会有各种问题,我们一步步来看.首先.我们创建一个class--ImageLoaderWithoutC ...
- cocos2dx3.2 异步载入和动态载入
半个月没有更新博客,从这个项目開始学习了非常多细节的东西,都不太成系统.可是却是开发上线中必须经历的东西.比方超级玛丽系列(一)中的正确的异步载入,正确的分层.正确的合成和载入plist.及时的移除未 ...
随机推荐
- MQ-传输方式Topic和Queue的对比
Jms规范里的两种message传输方式Topic和Queue,两者的对比如下表(): Topic Queue 概要 Publish Subscribe messaging 发布订阅消息 Poi ...
- maven打包源代码sources.jar和javadoc.jar帮助文档
maven中如何打包源代码 *-sources.jar 方式一 : 命令行方式 进入cmd命令行,进入项目工程pom.xml所在路径目录,运行mvn source:jar 和 mvn javado ...
- JSOI2007文本生成器
1030: [JSOI2007]文本生成器 Time Limit: 1 Sec Memory Limit: 162 MBSubmit: 1613 Solved: 656[Submit][Statu ...
- SSH2中memcached作为hibernate二级缓存
一.安装memcached服务端 1. 下载memcached的windows稳定版,解压放某个盘下面,比如在c:\memcached2. 在CMD下输入 "c:\memcached\mem ...
- 【CSS】
12个很少被人知道的CSS事实 通过CSS禁止Chrome自动为输入框添加橘黄色边框http://www.solagirl.net/override-chromes-automatic-border- ...
- Ext入门学习系列(五)表格控件(1)
上节学习了Ext面板控件,为后面的各个控件学习奠定基础,在此基础上本章将学习网络开发最期待的功能——表格控件. 我们都知道网络编程语言中,除了.net其他的基本没有提供网格控件,而最近的asp.net ...
- 一个可能是pip的一个BUG
今天重新安装了Python,把Python的安装位置改为 D:\Program Files\Python\Python34\ 用pip 安装 Django 的时候出现一下错误 >pip inst ...
- android广告平台介绍
广告模式: 广告条:最普遍的广告模式,嵌入在应用界面内,用户点击行为会带来收入. 积分墙:应用通过限制功能.去广告等引导用户进入积分墙页面下载广告应用得到积分来换取使用的模式,用户安装完推荐广 ...
- RhinoMocks简单范例
using System; namespace MockTest { public interface IBBB { int Number { get; } int Compute(int j); i ...
- ASP.NET入门(class0612)
内容:掌握基于ASP.Net的Web开发,B/S结构原理.ASP.Net内部原理.状态管理(Cookie.Session.ViewState等).数据验证.普通ASP.Net控件.母版.ListVie ...