使用JsPlumb绘制拓扑图的通用方法

转自:http://www.it165.net/pro/html/201311/7616.html

使用JsPlumb绘制拓扑图的通用方法

一、 实现目标

绘制拓扑图, 实际上是个数据结构和算法的问题。 需要设计一个合适的数据结构来表达拓扑结构,设计一个算法来计算拓扑节点的位置及连接。

二、 实现思想

1. 数据结构

首先, 从节点开始。 显然, 需要一个字段 type 表示节点类型, 一个字段 data 表示节点数据(详情), 对于连接, 则采用一个 rel 字段, 表示有哪些节点与之关联, 相当于C 里面的指针。 为了唯一标识该节点, 还需要一个字段 key 。 通过 type-key 组合来唯一标识该节点。 这样, 初步定下数据结构如下:

a. 节点数据结构: node = { type: ‘typeName‘, key: ‘key‘, rel: [], data: {‘More Info‘}}
b. rel, data 可选 , type-key 唯一标识该节点, rel 为空标识该节点为叶子节点
c. 关联关系: rel: [node1, node2, ..., nodeN]
d. 更多详情: 关于节点的更多信息可放置于此属性中

2. 算法

在算法上, 要预先规划好各个节点类型如何布局以及如何连接。 连接方向很容易定: 根据起始节点及终止节点的类型组合, 可以规定不同的连接方向。 位置确定稍有点麻烦。 这里采用的方法是: 采用深度遍历方法, 下一个的节点位置通过上一个节点位置确定, 不同类型的节点位置计算不一样, 但是相同类型的节点位置是重合的, 需要在后面进行调整。实际上, 这个节点位置的算法是不够高明的, 如果有更好的算法, 请告知。

3. JsPlumb

jsPlumb 有几个基本概念。 首先, 拓扑节点实际上是 DIV 区域,每个DIV 都必须有一个ID,用于唯一标识该节点。 连接拓扑节点的一个重要概念是EndPoint . EndPoint 是附着于节点上的连接线的端点, 简称“附着点”。 将附着点 attach 到指定拓扑节点上的方法如下:

jsPlumb.addEndpoint(toId, this.sourceEndpoint, { anchor: sourceAnchor, uuid:sourceUUID });

toId 是 拓扑节点的 DIV 区域的 ID 值, sourceEndpoint 是附着点的样式设置, 可以复用 , sourceAnchor 是附着点位置, 共有八种:

Top (also aliased as TopCenter) - TopRight - Right (also aliased as RightMiddle) - BottomRight - Bottom (also aliased asBottomCenter) -BottomLeft - Left (also aliased as LeftMiddle) - TopLeft

sourceUUID 是拓扑节点与附着位置的结合, 也就是说, 要将一个 附着点附着到拓扑节点为 toId 的 sourceAnchor 指定的位置上。 每个拓扑节点都可以定义多个源附着点和目标附着点。 源附着点是连接线的起始端, 目标附着点是连接线的终止端。

两个 uuid 即可定义一条连接线:

jsPlumb.connect({uuids:[startPoint, endPoint], editable: false});

startPoint 和 endPoint 分别是连接线的起始端 Endpoint uuid 和 终止段 Endpoint uuid. 它定义了从起始拓扑节点的指定附着点连接到终止拓扑节点的指定附着点。

三、 实现代码

drawTopo.js 提供绘制拓扑图的基本方法, 只要按照数据结构扔进去, 就可以自动绘制出拓扑图来。

  1 /**
  2  * 使用 jsPlumb 根据指定的拓扑数据结构绘制拓扑图
  3  * 使用 drawTopo(topoData, nodeTypeArray) 方法
  4  *
  5  */
  6
  7 /**
  8  * 初始化拓扑图实例及外观设置
  9  */
 10 (function() {
 11
 12     jsPlumb.importDefaults({
 13
 14         DragOptions : { cursor: ‘pointer‘, zIndex:2000 },
 15
 16         EndpointStyles : [{ fillStyle:‘#225588‘ }, { fillStyle:‘#558822‘ }],
 17
 18         Endpoints : [ [ "Dot", { radius:2 } ], [ "Dot", { radius: 2 } ]],
 19
 20         ConnectionOverlays : [
 21             [ "Arrow", { location:1 } ],
 22             [ "Label", {
 23                 location:0.1,
 24                 id:"label",
 25                 cssClass:"aLabel"
 26             }]
 27         ]
 28     });
 29
 30     var connectorPaintStyle = {
 31         lineWidth: 1,
 32         strokeStyle: "#096EBB",
 33         joinstyle:"round",
 34         outlineColor: "#096EBB",
 35         outlineWidth: 1
 36     };
 37
 38     var connectorHoverStyle = {
 39         lineWidth: 2,
 40         strokeStyle: "#5C96BC",
 41         outlineWidth: 2,
 42         outlineColor:"white"
 43     };
 44
 45     var endpointHoverStyle = {
 46         fillStyle:"#5C96BC"
 47     };
 48
 49     window.topoDrawUtil = {
 50
 51         sourceEndpoint: {
 52             endpoint:"Dot",
 53             paintStyle:{
 54                 strokeStyle:"#1e8151",
 55                 fillStyle:"transparent",
 56                 radius: 2,
 57                 lineWidth:2
 58             },
 59             isSource:true,
 60             maxConnections:-1,
 61             connector:[ "Flowchart", { stub:[40, 60], gap:10, cornerRadius:5, alwaysRespectStubs:true } ],
 62             connectorStyle: connectorPaintStyle,
 63             hoverPaintStyle: endpointHoverStyle,
 64             connectorHoverStyle: connectorHoverStyle,
 65             dragOptions:{},
 66             overlays:[
 67                 [ "Label", {
 68                     location:[0.5, 1.5],
 69                     label:"",
 70                     cssClass:"endpointSourceLabel"
 71                 } ]
 72             ]
 73         },
 74
 75         targetEndpoint: {
 76             endpoint: "Dot",
 77             paintStyle: { fillStyle:"#1e8151",radius: 2 },
 78             hoverPaintStyle: endpointHoverStyle,
 79             maxConnections:-1,
 80             dropOptions:{ hoverClass:"hover", activeClass:"active" },
 81             isTarget:true,
 82             overlays:[
 83                 [ "Label", { location:[0.5, -0.5], label:"", cssClass:"endpointTargetLabel" } ]
 84             ]
 85         },
 86
 87         initConnection: function(connection) {
 88             connection.getOverlay("label").setLabel(connection.sourceId + "-" + connection.targetId);
 89             connection.bind("editCompleted", function(o) {
 90                 if (typeof console != "undefined")
 91                     console.log("connection edited. path is now ", o.path);
 92             });
 93         },
 94
 95         addEndpoints: function(toId, sourceAnchors, targetAnchors) {
 96             for (var i = 0; i < sourceAnchors.length; i++) {
 97                 var sourceUUID = toId + sourceAnchors[i];
 98                 jsPlumb.addEndpoint(toId, this.sourceEndpoint, { anchor:sourceAnchors[i], uuid:sourceUUID });
 99             }
100             for (var j = 0; j < targetAnchors.length; j++) {
101                 var targetUUID = toId + targetAnchors[j];
102                 jsPlumb.addEndpoint(toId, this.targetEndpoint, { anchor:targetAnchors[j], uuid:targetUUID });
103             }
104         }
105     };
106
107
108 })();
109
110 /**
111  * drawTopo 根据给定拓扑数据绘制拓扑图
112  * @param topoData 拓扑数据
113  * @param rootPosition 拓扑图根节点的位置
114  * @param nodeTypeArray 节点类型数组
115  *
116  * 拓扑图的所有节点是自动生成的, DIV class = "node" , id= nodeType.toUpperCase + "-" + key
117  * 拓扑图的所有节点连接也是自动生成的, 可以进行算法改善与优化, 但使用者不需要关心此问题
118  * 需要定义节点类型数组 nodeTypeArray
119  *
120  * 拓扑数据结构:
121  * 1. 节点数据结构: node = { type: ‘typeName‘, key: ‘key‘, rel: [], data: {‘More Info‘}}
122  *    rel, data 可选 , type-key 唯一标识该节点
123  * 2. 关联关系: rel: [node1, node2, ..., nodeN]
124  * 3. 更多详情: 关于节点的更多信息可放置于此属性中
125  * 4. 示例:
126  *   var topoData = {
127  *            type: ‘VM‘, key: ‘110.75.188.35‘,
128  *            rel: [
129  *                 {   type: ‘DEVICE‘, key: ‘3-120343‘ },
130  *                 {      type: ‘DEVICE‘, key: ‘3-120344‘ },
131  *                 {      type: ‘VIP‘,       key: ‘223.6.250.2‘,
132  *                     rel: [
133  *                         { type: ‘VM‘, key: ‘110.75.189.12‘ },
134  *                         { type: ‘VM‘, key: ‘110.75.189.12‘ }
135  *                     ]
136  *                 },
137  *                 {      type: ‘NC‘,  key: ‘10.242.192.2‘,
138  *                     rel: [
139  *                       { type: ‘VM‘, key: ‘110.75.188.132‘ },
140  *                       { type: ‘VM‘, key: ‘110.75.188.135‘ },
141  *                       { type: ‘VM‘, key: ‘110.75.188.140‘ }
142  *                     ]
143  *
144  *                 }
145  *            ]
146  *        };
147  *
148  */
149 function drawTopo(topoData, rootPosition, nodeTypeArray) {
150
151     // 创建所有拓扑节点及连接并确定其位置
152     createNodes(topoData, rootPosition, nodeTypeArray);
153
154     // 调整重合节点的位置, 添加节点的附着点, 即连接线的端点
155     adjust(topoData, nodeTypeArray);
156
157     // 使所有拓扑节点均为可拉拽的
158     jsPlumb.draggable(jsPlumb.getSelector(".node"), { grid: [5, 5] });
159
160     // 创建所有节点连接
161     createConnections(topoData, nodeTypeArray);
162
163 }
164
165 /**
166  * 根据给定拓扑数据绘制拓扑节点并确定其位置, 使用深度优先遍历
167  * @param topoData 拓扑数据
168  * @param rootPosition 根节点的位置设定
169  * @param nodeTypeArray 拓扑节点类型
170  */
171 function createNodes(rootData, rootPosition, nodeTypeArray) {
172
173     if (rootData == null) {
174         return ;
175     }
176
177     var topoRegion = $(‘#topoRegion‘);
178     var relData = rootData.rel;
179     var i=0, relLen = relLength(relData);;
180     var VM_TYPE = nodeTypeArray[0];
181     var DEVICE_TYPE = nodeTypeArray[1];
182     var NC_TYPE = nodeTypeArray[2];
183     var VIP_TYPE = nodeTypeArray[3];
184
185     // 根节点的位置, 单位: px
186     var rootTop = rootPosition[0];
187     var rootLeft = rootPosition[1];
188
189     var nextRootData = {};
190     var nextRootPosition = [];
191
192     // 自动生成并插入根节点的 DIV
193     var divStr = createDiv(rootData);
194     var nodeDivId = obtainNodeDivId(rootData);
195     topoRegion.append(divStr);
196     //console.log(divStr);
197
198     // 设置节点位置
199     $(‘#‘+nodeDivId).css(‘top‘, rootTop + ‘px‘);
200     $(‘#‘+nodeDivId).css(‘left‘, rootLeft + ‘px‘);
201
202     for (i=0; i < relLen; i++) {
203         nextRootData = relData[i];
204         nextRootPosition = obtainNextRootPosition(rootData, nextRootData, rootPosition, nodeTypeArray);
205         createNodes(nextRootData, nextRootPosition, nodeTypeArray);
206     }
207
208 }
209
210 /**
211  * 调整重合节点的位置, 并添加节点的附着点, 即连接线的端点
212  */
213 function adjust(topoData, nodeTypeArray) {
214
215     var vm_deviceOffset = 0;   // 起始节点为 vm , 终止节点为 device, device div 的偏移量
216     var vm_vipOffset = 0;      // 起始节点为 vm , 终止节点为 vip, vip div 的偏移量
217     var vm_ncOffset = 0;       // 起始节点为 vm , 终止节点为 nc, nc div 的偏移量
218     var vip_vmOffset = 0;      // 起始节点为 vip , 终止节点为 vm, vm div 的偏移量
219     var nc_vmOffset = 0;      // 起始节点为nc , 终止节点为 vm, vm div 的偏移量
220     var verticalDistance = 120;
221     var horizontalDistance = 150;
222
223     var VM_TYPE = nodeTypeArray[0];
224     var DEVICE_TYPE = nodeTypeArray[1];
225     var NC_TYPE = nodeTypeArray[2];
226     var VIP_TYPE = nodeTypeArray[3];
227
228     $(‘.node‘).each(function(index, element) {
229         var nodeDivId = $(element).attr(‘id‘);
230         var nodeType = nodeDivId.split(‘-‘)[0];
231         var offset = $(element).offset();
232         var originalTop = offset.top;
233         var originalLeft = offset.left;
234         var parentNode = $(element).parent();
235         var parentNodeType = parentNode.attr(‘id‘).split(‘-‘)[0];
236         switch (nodeType) {
237             case VM_TYPE:
238                 // VM 位置水平偏移
239                 $(element).css(‘left‘, (originalLeft + vip_vmOffset*horizontalDistance) + ‘px‘);
240                 vip_vmOffset++;
241                 topoDrawUtil.addEndpoints(nodeDivId, [‘Top‘, ‘Bottom‘, ‘Right‘], []);
242                 break;
243             case DEVICE_TYPE:
244                 // DEVICE 位置垂直偏移
245                 $(element).css(‘top‘, (originalTop + (vm_deviceOffset-1)*verticalDistance) + ‘px‘);
246                 vm_deviceOffset++;
247                 topoDrawUtil.addEndpoints(nodeDivId, [], [‘Left‘]);
248                 break;
249             case VIP_TYPE:
250                 // VIP 位置水平偏移
251                 $(element).css(‘left‘, (originalLeft + vm_vipOffset*horizontalDistance) + ‘px‘);
252                 vm_vipOffset++;
253                 topoDrawUtil.addEndpoints(nodeDivId, [‘Top‘], [‘Bottom‘]);
254                 break;
255             case NC_TYPE:
256                 // NC 位置水平偏移
257                 $(element).css(‘left‘, (originalLeft + vm_ncOffset*verticalDistance) + ‘px‘);
258                 vm_ncOffset++;
259                 topoDrawUtil.addEndpoints(nodeDivId, [‘Bottom‘], [‘Top‘]);
260                 break;
261             default:
262                 break;
263         }
264     });
265 }
266
267 /**
268  * 获取下一个根节点的位置, 若节点类型相同, 则位置会重合, 需要后续调整一次
269  * @root            当前根节点
270  * @nextRoot        下一个根节点
271  * @rootPosition    当前根节点的位置
272  * @nodeTypeArray   节点类型数组
273  */
274 function obtainNextRootPosition(root, nextRoot, rootPosition, nodeTypeArray) {
275
276     var VM_TYPE = nodeTypeArray[0];
277     var DEVICE_TYPE = nodeTypeArray[1];
278     var NC_TYPE = nodeTypeArray[2];
279     var VIP_TYPE = nodeTypeArray[3];
280
281     var startNodeType = root.type;
282     var endNodeType = nextRoot.type;
283     var nextRootPosition = [];
284     var rootTop = rootPosition[0];
285     var rootLeft = rootPosition[1];
286
287     var verticalDistance = 120;
288     var horizontalDistance = 250;
289     var shortVerticalDistance = 80;
290
291     switch (startNodeType) {
292         case VM_TYPE:
293             if (endNodeType == VIP_TYPE) {
294                 nextRootPosition = [rootTop-verticalDistance, rootLeft];
295             }
296             else if (endNodeType == DEVICE_TYPE) {
297                 nextRootPosition = [rootTop, rootLeft+horizontalDistance];
298             }
299             else if (endNodeType == NC_TYPE) {
300                 nextRootPosition = [rootTop+verticalDistance, rootLeft];
301             }
302             break;
303         case VIP_TYPE:
304             if (endNodeType == VM_TYPE) {
305                 nextRootPosition = [rootTop-shortVerticalDistance, rootLeft];
306             }
307             break;
308         case NC_TYPE:
309             if (endNodeType == VM_TYPE) {
310                 nextRootPosition = [rootTop+shortVerticalDistance, rootLeft];
311             }
312             break;
313         default:
314             break;
315     }
316     return nextRootPosition;
317 }
318
319 /**
320  * 根据给定拓扑数据, 绘制节点之间的连接关系, 使用深度优先遍历
321  * @param topoData 拓扑数据
322  * @param nodeTypeArray 节点类型数组
323  */
324 function createConnections(topoData, nodeTypeArray) {
325
326     if (topoData == null) {
327         return ;
328     }
329     var rootData = topoData;
330     var relData = topoData.rel;
331     var i=0, len = relLength(relData);;
332     for (i=0; i < len; i++) {
333         connectionNodes(rootData, relData[i], nodeTypeArray);
334         createConnections(relData[i], nodeTypeArray);
335     }
336 }
337
338 /**
339  * 连接起始节点和终止节点
340  * @beginNode 起始节点
341  * @endNode 终止节点
342  * NOTE: 根据是起始节点与终止节点的类型
343  */
344 function connectionNodes(beginNode, endNode, nodeTypeArray)
345 {
346     var startNodeType = beginNode.type;
347     var endNodeType = endNode.type;
348     var startDirection = ‘‘;
349     var endDirection = ‘‘;
350
351     var VM_TYPE = nodeTypeArray[0];
352     var DEVICE_TYPE = nodeTypeArray[1];
353     var NC_TYPE = nodeTypeArray[2];
354     var VIP_TYPE = nodeTypeArray[3];
355
356     switch (startNodeType) {
357         case VM_TYPE:
358             if (endNodeType == VIP_TYPE) {
359                 // VIP 绘制于 VM 上方
360                 startDirection = ‘Top‘;
361                 endDirection = ‘Bottom‘;
362             }
363             else if (endNodeType == DEVICE_TYPE) {
364                 // DEVICE 绘制于 VM 右方
365                 startDirection = ‘Right‘;
366                 endDirection = ‘Left‘;
367             }
368             else if (endNodeType == NC_TYPE) {
369                 // NC 绘制于 VM 下方
370                 startDirection = ‘Bottom‘;
371                 endDirection = ‘Top‘;
372             }
373             break;
374         case VIP_TYPE:
375             if (endNodeType == VM_TYPE) {
376                 // VM 绘制于 VIP 上方
377                 startDirection = ‘Top‘;
378                 endDirection = ‘Top‘;
379             }
380             break;
381         case NC_TYPE:
382             if (endNodeType == VM_TYPE) {
383                 // VM 绘制于 NC 下方
384                 startDirection = ‘Bottom‘;
385                 endDirection = ‘Bottom‘;
386             }
387             break;
388         default:
389             break;
390     }
391     var startPoint = obtainNodeDivId(beginNode) + startDirection;
392     var endPoint = obtainNodeDivId(endNode) + endDirection;
393     jsPlumb.connect({uuids:[startPoint, endPoint], editable: false});
394 }
395
396 function createDiv(metaNode) {
397     return ‘<div class="node" id="‘ + obtainNodeDivId(metaNode) + ‘"><strong>‘
398             + metaNode.type + ‘<br/><a href="http://aliyun.com">‘ + metaNode.key + ‘</a><br/></strong></div>‘
399 }
400
401 /**
402  * 生成节点的 DIV id
403  * divId = nodeType.toUpperCase + "-" + key
404  * key 可能为 IP , 其中的 . 将被替换成 ZZZ , 因为 jquery id 选择器中 . 属于转义字符.
405  * eg. {type: ‘VM‘, key: ‘1.1.1.1‘ }, divId = ‘VM-1ZZZ1ZZZ1ZZZ1‘
406  */
407 function obtainNodeDivId(metaNode) {
408     return metaNode.type.toUpperCase() + ‘-‘ + transferKey(metaNode.key);
409 }
410
411 function transferKey(key) {
412     return key.replace(/\./g, ‘ZZZ‘);
413 }
414
415 function revTransferKey(value) {
416     return value.replace(/ZZZ/g, ‘.‘);
417 }
418
419
420 /**
421  * 合并新的拓扑结构到原来的拓扑结构中, 新的拓扑结构中有节点与原拓扑结构中的某个节点相匹配: type-key 相等
422  * @param srcTopoData 原来的拓扑结构
423  * @param newTopoData 要添加的的拓扑结构
424  */
425 function mergeNewTopo(srcTopoData, newTopoData) {
426
427     var srcTopoData = shallowCopyTopo(srcTopoData);
428
429     if (srcTopoData == null || newTopoData == null) {
430         return srcTopoData || newTopoData;
431     }
432
433     var srcRoot = srcTopoData;
434     var newRoot = newTopoData;
435
436     var newRelData = newTopoData.rel;
437     var i=0, newRelLen = relLength(newRelData);
438
439     var matched = findMatched(srcRoot, newRoot);
440     if (matched == null) {
441         // 没有找到匹配的节点, 直接返回原有的拓扑结构
442         return srcTopoData;
443     }
444     matched.rel = matched.rel.concat(newRelData);
445     return srcTopoData;
446 }
447
448 /**
449  * 在原拓扑结构中查找与新拓扑结构根节点 newRootData 匹配的节点
450  * @param srcRootData 原拓扑结构
451  * @param newRootData 新拓扑结构的根节点
452  * @returns 原拓扑结构中与新拓扑结构根节点匹配的节点 or null if not found
453  */
454 function findMatched(srcRootData, newRootData) {
455     var srcRelData = srcRootData.rel;
456     var i=0, srcRelLen = relLength(srcRelData);
457     var matched = null;
458     if ((srcRootData.type == newRootData.type) && (srcRootData.key == newRootData.key)) {
459         return srcRootData;
460     }
461     for (i=0; i<srcRelLen; i++) {
462         matched = findMatched(srcRelData[i], newRootData);
463         if (matched != null) {
464             return matched;
465         }
466     }
467     return matched;
468 }
469
470 function relLength(relData) {
471     if (isArray(relData)) {
472         return relData.length;
473     }
474     return 0;
475 }
476
477 function isArray(value) {
478     return value && (typeof value === ‘object‘) && (typeof value.length === ‘number‘);
479 }
480
481 /**
482  * 浅复制拓扑结构
483  */
484 function shallowCopyTopo(srcTopoData) {
485     return srcTopoData;
486 }
487
488 /**
489  * 深复制拓扑结构
490  */
491 function deepCopyTopo(srcTopoData) {
492     //TODO identical to deep copy of js json
493 }

topodemo.html 绘制拓扑图的客户端接口。 只要引进相应的依赖 JS,预置一个 <div id="topoRegion"></div>

<!doctype html>
<html>
    <head>
        <title>jsPlumb 1.5.3 - flowchart connectors demonstration - jQuery</title>
        <link rel="stylesheet" href="topo-all.css">
        <link rel="stylesheet" href="topo.css">

        <!-- DEP -->
        <script src="../jsPlumb/jquery-1.9.0-min.js"></script>
        <script src="../jsPlumb/jquery-ui-1.9.2-min.js"></script>

        <!-- /DEP -->

        <!-- JS -->
        <!-- support lib for bezier stuff -->
        <script src="../jsPlumb/jsBezier-0.6-min.js"></script>
        <!-- <a href="http://www.it165.net/pro/webjsp/" target="_blank" class="keylink">jsp</a>lumb geom functions -->
        <script src="../jsPlumb/<a href="http://www.it165.net/pro/webjsp/" target="_blank" class="keylink">jsp</a>lumb-geom-0.1.js"></script>
        <!-- jsplumb util -->
        <script src="../jsPlumb/util.js"></script>
        <!-- base DOM adapter -->
        <script src="../jsPlumb/dom-adapter.js"></script>
        <!-- main jsplumb engine -->
        <script src="../jsPlumb/jsPlumb.js"></script>
        <!-- endpoint -->
        <script src="../jsPlumb/endpoint.js"></script>
        <!-- connection -->
        <script src="../jsPlumb/connection.js"></script>
        <!-- anchors -->
        <script src="../jsPlumb/anchors.js"></script>
        <!-- connectors, endpoint and overlays  -->
        <script src="../jsPlumb/defaults.js"></script>
        <!-- connector editors -->
        <script src="../jsPlumb/connector-editors.js"></script>
        <!-- bezier connectors -->
        <script src="../jsPlumb/connectors-bezier.js"></script>
        <!-- state machine connectors -->
        <script src="../jsPlumb/connectors-statemachine.js"></script>
        <!-- flowchart connectors -->
        <script src="../jsPlumb/connectors-flowchart.js"></script>
        <!-- SVG renderer -->
        <script src="../jsPlumb/renderers-svg.js"></script>
        <!-- canvas renderer -->
        <script src="../jsPlumb/renderers-canvas.js"></script>
        <!-- vml renderer -->
        <script src="../jsPlumb/renderers-vml.js"></script>

        <!-- jquery jsPlumb adapter -->
        <script src="../jsPlumb/jquery.jsPlumb.js"></script>
        <!-- /JS -->

        <!--  demo code -->
        <script src="drawtopo.js"></script>

        <script type="text/javascript">
            jsPlumb.bind("ready", function() {

                // 拓扑数据结构根节点位置设置
                var rootPosition = [270, 300];
                var nodeTypeArray = [‘VM‘, ‘DEVICE‘, ‘NC‘, ‘VIP‘];
                var topoData = {
                    type: ‘VM‘, key: ‘110.75.188.35‘,
                    rel: [
                         {
                             type: ‘DEVICE‘,
                             key: ‘3-120343‘
                         },

                         {
                             type: ‘DEVICE‘,
                             key: ‘3-120344‘
                         },

                         {
                             type: ‘VIP‘,
                             key: ‘223.6.250.2‘,
                             rel: [
                                 { type: ‘VM‘, key: ‘110.75.189.12‘ },
                                 { type: ‘VM‘, key: ‘110.75.189.13‘ }
                             ]
                         },

                         {
                             type: ‘NC‘,
                             key: ‘10.242.192.2‘,
                             rel: [
                                 { type: ‘VM‘, key: ‘110.75.188.132‘ },
                                 { type: ‘VM‘, key: ‘110.75.188.135‘ }
                             ]

                         }
                    ]
                };

                drawTopo(topoData, rootPosition, nodeTypeArray);

                var newTopoData = {
                    type: ‘NC‘,
                    key: ‘10.242.192.2‘,
                    rel: [
                           { type: ‘VM‘, key: ‘110.75.188.140‘ }
                    ]
                };

                var mergedTopoData = mergeNewTopo(topoData, newTopoData);
                $(‘#topoRegion‘).empty();
                drawTopo(mergedTopoData, rootPosition, nodeTypeArray);

            });

        </script>

    </head>
    <body>        

        <div id="topoRegion">
        </div>

    </body>
</html>

样式文件及依赖JS 见工程示例。 里面已经包含绘制拓扑图的最小依赖。

四、 最终效果图

源码下载地址:http://download.csdn.net/detail/shuqin1984/6488513

时间: 2024-10-08 23:48:24

使用JsPlumb绘制拓扑图的通用方法的相关文章

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

本文实现的方法可以边异步加载数据边绘制拓扑图. 有若干点需要说明一下: 1.  一次性获取所有数据并绘制拓扑图, 请参见文章: <使用 JsPlumb 绘制拓扑图的通用方法> ; 本文实现的最终显示效果与之类似, 所使用的基本方法与之类似. 2.  在此次实现中, 可以一边异步加载数据一边绘制拓扑图, 是动态可扩展的: 3.  所有影响节点位置.布局的配置均放置在最前面, 便于修改, 避免在代码中穿梭, 浪费时间: 4.  布局算法比之前的实现更加完善: 5.  此实现由于与业务逻辑绑得比较紧

控件绘制的四种方法

OWNER?DRAW实现自绘按钮 一准备工作 在您决定开发 Windows 提供的常规免费自定义控件范围之外的控件之后,您必需确定自己的控件将有多少独到之处 - 在功能和外观两方面.例如,我们假定您正在创建一个类似于计速表的控件.由于公共控件库 (ComCtrl32.dll) 中没有类似的控件,您完全需要自己进行以下操作:编写所有控件功能需要的代码,进行绘制,默认终端用户的交互,以及控件与其父窗口之间需要的任意消息处理. (#add 两方面,公共控件库中没有类似的 完全重写;? 只想调整公共控件

.NET基础架构方法—DataTableToExcel通用方法

p { display: block; margin: 3px 0 0 0; } --> .NET架构基础方法—DataTableToExcel通用方法(NPOI) 今天封装DataTaleToExcel通用方法,也是大家开发中特别常用的.首先去下载NPOI,链接http://npoi.codeplex.com/ ,使用包中的net4.0版本的dll,全部引用.官网中已经给了足够的示例,我只拿来异步分,给类命名为ExcelTools.cs .下面上代码 1 using System; 2 usi

.NET基础架构方法—DataTableToList通用方法

p { display: block; margin: 3px 0 0 0; } --> .NET架构基础方法—DataTableToList通用方法 我们经常需要将从数据库中所读取的数据以DataTable类型返回,也经常需要遍历DataTable转换为List>T<.我们也经常需要为每一个DataTable转换为List单独编写适合他们数据库架构地方法.下面上代码: public static class DataTableTools<T> where T : class

hibernate学习笔记4---HQL、通用方法的抽取实现

一.通用方法的抽取实现 由于hibernate中对增删改查的一切操作都是面向对象的,所以将增删改查抽取成通用方法,以满足不同的表的增删改查操作,简化jdbc代码. 具体例子如下: [java] view plaincopyprint? package cn.itcast.hibernate; import java.io.Serializable; import org.hibernate.Session; import org.hibernate.SessionFactory; import 

js通用方法检测浏览器是否已安装指定插件(IE与非IE通用)

/* * 检测是否已安装指定插件 * * pluginName 插件名称 */ function checkPlugins(pluginName) { var np = navigator.plugins; if (window.ActiveXObject) { // IE // ActiveXObject的对象名 var activexObjectName = pluginName + "." + pluginName; try { var axobj = eval("ne

Collection接口 Collection的通用方法 foreach Iterator 迭代器

Collection接口 单列集合的接口list 有索引 有序set 无索引 无序AbstractCollection是实现了Collection接口的抽象父类Collection<> c = new ArrayList<>();多态 只可以用父类的方法,不可以用子类特有的方法 Collection的通用方法增 add()删 remove()清空集合 clear()判断元素是否为空 isEmpty判断元素是否存在 contains()集合的长度 size() 增强for forea

List对象排序的通用方法

转自 @author chenchuang import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.Collections;import java.util.Comparator;import java.util.List; /**  * List对象排序的通用方法  *   * @author chenchuang  *   * @param <E>

List对象排序通用方法

在数据库中查出来的列表list中,往往需要对不同的字段重新排序,一般的做法都是使用排序的字段,重新到数据库中查询.如果不到数据库查询,直接在第一次查出来的list中排序,无疑会提高系统的性能. 只要把第一次查出来的结果存放在session中,就可以对list重新排序了.一般对list排序可以使用Collections.sort(list),但如果list中包含是一个对象的话,这种方法还是行不通的.那要怎么排序呢?如果有一个UserInfo对象,包含如下字段: private java.lang.