使用 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":"[email protected]","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 绘制拓扑图 —— 异步加载与绘制的实现,布布扣,bubuko.com

时间: 2024-10-28 19:32:17

使用 jsPlumb 绘制拓扑图 —— 异步加载与绘制的实现的相关文章

cocos2d-x addImageAsync()异步加载资源成功之后的场景跳转问题

http://blog.csdn.net/w20175357/article/details/23546985 1.先说说addImageAsync()异步加载图片的问题 做游戏的时候现在资源的比较大,所有我们必然会有一个loading界面,而我在找写loading界面的方法的时候,发现了2种方法.       一种是自己创建一个线程,再在这个线程里面加载资源,不过由于openGL的限制,只能在主线程里面绘制UI,不过有的人也想出了其他的方法,就是先只缓存,再在主线程里面绘制.这里有这方面的教程

Cocos2d-x教程(36)-多线程与异步加载

欢迎加入Cocos2d-x 交流群:193411763 转载时请注明原文出处 :http://blog.csdn.net/u012945598/article/details/41312345 ---------------------------------------------------------------------------------------------------------------------------------------------------------

背水一战 Windows 10 (57) - 控件(集合类): ListViewBase - 增量加载, 分步绘制

原文:背水一战 Windows 10 (57) - 控件(集合类): ListViewBase - 增量加载, 分步绘制 [源码下载] 作者:webabcd 介绍背水一战 Windows 10 之 控件(集合类 - ListViewBase) 增量加载 分步绘制(大数据量流畅滚动) 示例1.ListViewBase 的增量加载Controls/CollectionControl/ListViewBaseDemo/MyIncrementalLoading.cs /* * 演示如何实现 ISuppo

Android异步加载访问网络图片-解析json

来自:http://www.imooc.com/video/7871 推荐大家去学习这个视频,讲解的很不错. 慕课网提供了一个json网址可以用来学习:http://www.imooc.com/api/teacher?type=4&num=30.我们的任务就是建立一个listview,将json提供的一些参数,主要是name,picSmall,description显示出来,效果图如下:  主要思路如下:listview中图片的加载,程序中使用了两种方式,一种是使用Thread类,一种是使用As

Android消息处理机制:源码剖析Handler、Looper,并实现图片异步加载

引言 我们在做 Android 开发时,常常需要实现异步加载图片/网页/其他.事实上,要实现异步加载,就需要实现线程间通信,而在 Android 中结合使用 Handler.Looper.Message 能够让不同的线程通信,完成异步任务.虽然 Android 官方为我们提供了 AsyncTask 类来完成异步任务,但这个类存在许多问题,并不好用,而且,AsyncTask 也是通过 Handler 和 Thread 来实现异步加载的,所以学习这方面的知识是有必要的 本文讲解思路大致如下:绘制 A

ListView的异步加载(笔记,多线程和AsyncTask)

异步加载最常用的两种方式: 多线程,线程池     AsyncTask 实例操作: 从一个网站上获取Json数据,然后将数据在ListView上显示. 1.创建item_layout布局 , 修改主界面布局 item_layout.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/re

高德地图点击查询信息定位到点+异步加载点+点击点显示信息窗口

一:配置地图 根据官方步骤申请key 可以异步或者同步调用地图,此处Wie同步 html <div id="container" tabindex="0"></div> js //设置中心位置,显示当前城市的中心点 var map = new AMap.Map('container', { resizeEnable: true, //center: [117.031479, 36.66314],//定位的济南//center缺省则根据IP自动

cocos2dx lua中异步加载网络图片,可用于显示微信头像

最近在做一个棋牌项目,脚本语言用的lua,登录需要使用微信登录,用户头像用微信账户的头像,微信接口返回的头像是一个url,那么遇到的一个问题就是如何在lua中异步加载这个头像,先在引擎源码里找了下可能会提供这个功能的地方,发现好像没有提供类似功能,那么只能自己动手写.所以我在ImageView这个类里面添加了一个成员方法,其实可以不写在ImageView里,而且我觉得非必需情况下还是不要修改引擎源码的好,因为如果源码改动比较多的话,将来引擎版本升级会比较麻烦.我写在ImageView里纯粹是想偷

Android-Universal-Image-Loader 图片异步加载类库的使用(超详细配置)

这个图片异步加载并缓存的类已经被很多开发者所使用,是最常用的几个开源库之一,主流的应用,随便反编译几个火的项目,都可以见到它的身影. 可是有的人并不知道如何去使用这库如何进行配置,网上查到的信息对于刚接触的人来说可能太少了,下面我就把我使用过程中所知道的写了下来,希望可以帮助自己和别人更深入了解这个库的使用和配置. GITHUB上的下载路径为:https://github.com/nostra13/Android-Universal-Image-Loader ,下载最新的库文件,并且导入到项目的