使用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

转载于:https://www.cnblogs.com/smallrock/p/4481320.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JsPlumb 是一个流程图和拓扑图绘制库,可以用它来创建各种形状和连接线。下面是一个简单的使用 JsPlumb 创建拓扑图的例子: 首先,在 HTML 中添加必要的引用: ```html <!DOCTYPE html> <html> <head> <title>JsPlumb Topology Example</title> <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jsPlumb/2.15.5/js/jsplumb.min.js"></script> </head> <body> <div id="canvas"></div> </body> </html> ``` 然后,在 JavaScript 中创建拓扑图: ```javascript // 创建一个 JsPlumb 实例 var jsPlumbInstance = jsPlumb.getInstance(); // 设置连接线的默认样式 var connectorPaintStyle = { strokeWidth: 2, stroke: "#61B7CF", joinstyle: "round", outlineStroke: "white", outlineWidth: 2 }; // 设置连接线的箭头样式 var connectorHoverStyle = { strokeWidth: 3, stroke: "#216477", outlineWidth: 5, outlineStroke: "white" }; // 设置端点的默认样式 var endpointStyle = { endpoint: "Dot", paintStyle: { stroke: "#7AB02C", fill: "transparent", radius: 7, strokeWidth: 1 }, hoverPaintStyle: { fill: "#216477", stroke: "#216477" }, connectorStyle: connectorPaintStyle, connectorHoverStyle: connectorHoverStyle, connectorOverlays: [ [ "Arrow", { location: 1, id: "arrow", length: 14, foldback: 0.8 } ], [ "Label", { label: "", id: "label", cssClass: "aLabel" } ] ], dragOptions: {}, overlays: [ [ "PlainArrow", { location: 0.5, width: 12, length: 12 } ], [ "Label", { label: "foo", id: "label", cssClass: "aLabel" } ] ] }; // 在画布上添加两个端点和一个连接线 var sourceEndpoint = { endpoint: "Dot", paintStyle: { stroke: "#7AB02C", fill: "transparent", radius: 7, strokeWidth: 1 }, isSource: true, connector: [ "Bezier", { curviness: 63 } ], connectorStyle: connectorPaintStyle, hoverPaintStyle: { fill: "#216477", stroke: "#216477" }, connectorHoverStyle: connectorHoverStyle, dragOptions: {}, overlays: [ [ "Label", { location: [0.5, -0.5], label: "Output", cssClass: "endpointLabel" } ] ] }; var targetEndpoint = { endpoint: "Dot", paintStyle: { fill: "#7AB02C", radius: 7 }, hoverPaintStyle: { fill: "#216477" }, maxConnections: -1, dropOptions: { hoverClass: "hover", activeClass: "active" }, isTarget: true, overlays: [ [ "Label", { location: [0.5, 1.5], label: "Input", cssClass: "endpointLabel" } ] ] }; jsPlumbInstance.addEndpoint($("#canvas"), { uuid: "output" }, sourceEndpoint); jsPlumbInstance.addEndpoint($("#canvas"), { uuid: "input" }, targetEndpoint); jsPlumbInstance.connect({ uuids: ["output", "input"] }); ``` 上面的代码创建了一个画布,并在画布上添加了两个端点和一个连接线。该例子中使用了默认的样式,你可以根据需要自定义样式。 最后,你可以通过 jsPlumbInstance.reset() 方法来清除画布上的所有元素。 希望这个例子对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值