使用antv X6绘制拓扑关系图(层次布局)

您所在的位置:网站首页 html层级关系图 使用antv X6绘制拓扑关系图(层次布局)

使用antv X6绘制拓扑关系图(层次布局)

2024-07-05 05:38| 来源: 网络整理| 查看: 265

        需求:最近做了一个调用链路的需求,该需求需要展示出公司内部各系统之间的调用关系,调用关系虽然复杂,混乱,但总体有类似于树形结构的层级关系,类似于进化版的树形结构。

     先展示一下成果图:

        antv x6可以说是非常的强大,可以绘制很多类型图,甚至还可以绘制自定义拖拽的流程图,这里只是用到其中一小部分功能,具体可参照官网学习https://x6.antv.antgroup.com/tutorial/getting-startedicon-default.png?t=N7T8https://x6.antv.antgroup.com/tutorial/getting-started

下面记录一下本例的开发过程:

1.此处采用CDN引入(npm安装可参考官网):

/*jquery环境*/ /*自动布局*/

2.准备容器(样式附在最后)

3.创建节点样式,连接线样式。(根据自己项目需要更改,需要熟悉svg样式编写规则)

(本例,图中数据从后台数据中来)

X6 是基于 SVG 的渲染引擎,可以使用不同的 SVG 元素渲染节点和边,非常适合节点内容比较简单的场景。面对复杂的节点, SVG 中有一个特殊的 foreignObject 元素,在该元素中可以内嵌任何 XHTML 元素。参考:https://x6.antv.antgroup.com/tutorial/basic/node

//节点 Graph.registerNode( 'business-node', { width: 400, height: 200, attrs: { body: { stroke: '#5F95FF', strokeWidth: 0.5, fill: 'rgba(95,149,255,0.05)', refWidth: 1, refHeight: 1, pointerEvents: 'visiblePainted', }, title: { fill: '#409eff', stroke: '#fff', strokeWidth: 0.5, refWidth: 1, refHeight:0.2, }, title_text: { ref: 'title', refY: 0.3, refX: 0.5, textAnchor: 'middle', fontWeight: 'bold', fill: '#fff', fontSize: 18, cursor: 'pointer', event: 'node:title_text', }, item1: { fill: 'green', stroke: '#fff', strokeWidth: 0.5, refWidth: 0.5, refHeight: 0.4, x: 0, y: 40, }, item_text1: { ref: 'item1', refY: 0.25, refX: 0.5, fontWeight: 'bold', textAnchor: 'middle', fill: '#fff', fontSize: 28, cursor: 'pointer', event: 'node:item_text1', }, item_text1_1: { ref: 'item1', refY: 0.75, refX: 0.95, textAnchor: 'end', fill: '#d3d3d3', fontSize: 16, cursor: 'pointer', text:'tps', event: 'node:item_text1_1', }, item2: { fill: 'green', stroke: '#fff', strokeWidth: 0.5, refWidth: 0.5, refHeight: 0.4, x: 200, y: 40 }, item_text2: { ref: 'item2', refY: 0.25, refX: 0.5, fontWeight: 'bold', textAnchor: 'middle', fill: '#fff', fontSize: 28, cursor: 'pointer', }, item_text2_1: { ref: 'item2', refY: 0.75, refX: 0.95, textAnchor: 'end', fill: '#d3d3d3', fontSize: 16, cursor: 'pointer', text:'P95', }, item3: { fill: 'green', stroke: '#fff', strokeWidth: 0.5, refWidth: 0.5, refHeight: 0.4, x: 0, y: 120 }, item_text3_1: { ref: 'item3', refY: 0.75, refX: 0.95, textAnchor: 'end', fill: '#d3d3d3', fontSize: 16, cursor: 'pointer', text:'sli', }, item_text3: { ref: 'item3', refY: 0.25, refX: 0.5, fontWeight: 'bold', textAnchor: 'middle', fill: '#fff', fontSize: 28, cursor: 'pointer', }, item4: { fill: 'green', stroke: '#fff', strokeWidth: 0.5, refWidth: 0.5, refHeight: 0.4, x: 200, y: 120 }, item_text4: { ref: 'item4', refY: 0.25, refX: 0.5, fontWeight: 'bold', textAnchor: 'middle', fill: '#fff', fontSize: 25, cursor: 'pointer', }, item_text4_1: { ref: 'item4', refY: 0.75, refX: 0.95, textAnchor: 'end', fill: '#d3d3d3', fontSize: 16, cursor: 'pointer', text:'cpu/memory', }, }, markup: [ { tagName: 'rect', selector: 'body', }, { tagName: 'rect', selector: 'title', }, { tagName: 'text', selector: 'title_text', }, { tagName: 'rect', selector: 'item1', }, { tagName: 'text', selector: 'item_text1', }, { tagName: 'text', selector: 'item_text1_1', }, { tagName: 'rect', selector: 'item2', }, { tagName: 'text', selector: 'item_text2', }, { tagName: 'text', selector: 'item_text2_1', }, { tagName: 'rect', selector: 'item3', }, { tagName: 'text', selector: 'item_text3', }, { tagName: 'text', selector: 'item_text3_1', }, { tagName: 'rect', selector: 'item4', }, { tagName: 'text', selector: 'item_text4', }, { tagName: 'text', selector: 'item_text4_1', }, ], }, true, ) //连接线 Graph.registerEdge( 'business-edge', { inherit: 'edge', connector: {name: 'smooth'}, attrs: { line: { strokeWidth: 2, stroke: '#aabefd', targetMarker: { name:'classic' }, }, }, defaultLabel: { markup: [ { tagName: 'rect', selector: 'body', }, { tagName: 'text', selector: 'label', }, ], attrs: { label: { fill: '#525151', fontSize: 12, textAnchor: 'middle', textVerticalAnchor: 'middle', pointerEvents: 'none', }, body: { ref: 'label', fill: '#B1C2F8', stroke: '#7d94d7', strokeWidth: 2, rx: 3, ry: 3, refWidth: '140%', refHeight: '140%', refX: '-20%', refY: '-20%', }, }, position: { distance: 0.5, options: { absoluteDistance: true, reverseDistance: true, }, }, }, }, true, )

 4.绘制画布

const graph = new Graph({ container: document.getElementById('container'), width: $(window).width() , height: $(window).height() , background: { color: '#222B36', // 设置画布背景颜色 }, grid: { size: 10, // 网格大小 10px visible: true, // 渲染网格背景 }, autoResize: true, mousewheel: true, //滚轮放大,缩小 panning: { //鼠标左键点击,可移动画布 enabled: true, eventTypes: ['leftMouseDown'] }, resizing: { enabled: true } });

5.布局。在数据中可通过X,Y指定坐标,自定义布局。如果希望自适应,X6也提供了几套布局方式(网格布局,环形布局,层次布局),用法参考:https://x6.antv.antgroup.com/temp/layout

var gridLayout = new layout.DagreLayout({ //本例采用层次布局 type: 'grid', rankdir: 'LR', nodesep: 300, ranksep: 150, controlPoints: true, connecting: { allowLoop: true, } })

 6.根据数据,绘图。数据结构:{ nodes : [ ] , edges : [ ] }。注:数据中nodes.attrs里的属性名要和business-node节点里的属性名一一对应

const newModel = gridLayout.layout( data ) //根据原生数据,生成布局数据 graph.fromJSON(newModel) //根据布局数据,绘图 graph.scaleContentToFit() //图形所有内容铺满容器 graph.centerContent() //图形居中 var data = { // 节点 nodes: [ { id: 'node1', // String,可选,节点的唯一标识 "attrs": { "title_text": { "text": "北京联通1", 'id': 1, "pid": 2, ips:[], }, "item_text1": { "text": "在线人数1", 'id': 15, "value": 370, }, "item_text2": { "text": "访问量1", 'id': 20, "value": 370, }, "item_text3": { "text": "耗时1", 'id': 30, "value": 370, }, "item_text4": { "text": "报错率1", 'id': 40, "value": 370, }, }, shape: 'business-node', }, { id: 'node2', // String,节点的唯一标识 "attrs": { "title_text": { "text": "接入(北京移动)2" }, "item_text1": { "text": "在线人数2" }, "item_text2": { "text": "访问量2" }, "item_text3": { "text": "耗时2" }, "item_text4": { "text": "报错率2" }, }, shape: 'business-node', }, { id: 'node3', // String,节点的唯一标识 "attrs": { "title_text": { "text": "接入(北京电信)3" }, "item_text1": { "name": "在线人数3", // id:1 }, "item_text2": { "text": "访问量3" }, "item_text3": { "text": "耗时3" }, "item_text4": { "text": "报错率3" }, }, shape: 'business-node', }, { id: 'node9', // String,可选,节点的唯一标识 "attrs": { "title_text": { "text": "接入(北京联通)9", 'id': 1, "pid": 2, }, "item_text1": { "text": "在线人数9", 'id': 15, "value": 370, }, "item_text2": { "text": "访问量9", 'id': 20, "value": 370, }, "item_text3": { "text": "耗时9", 'id': 30, "value": 370, }, "item_text4": { "text": "报错率9", 'id': 40, "value": 370, }, }, shape: 'business-node', }, { id: 'node8', // String,可选,节点的唯一标识 "attrs": { "title_text": { "text": "接入(北京联通)8", 'id': 1, "pid": 2, }, "item_text1": { "text": "在线人数8", 'id': 15, "value": 370, }, "item_text2": { "text": "访问量8", 'id': 20, "value": 370, }, "item_text3": { "text": "耗时8", 'id': 30, "value": 370, }, "item_text4": { "text": "报错率8", 'id': 40, "value": 370, }, }, shape: 'business-node', }, ], // 边 edges: [ { id: 10, source: 'node1', // String,必须,起始节点 id target: 'node2', // String,必须,目标节点 id "shape": "business-edge", label: '1---2', }, { source: 'node3', // String,必须,起始节点 id target: 'node2', // String,必须,目标节点 id "shape": "business-edge", label: '3----2', arr: [1, 2], }, { source: 'node3', // String,必须,起始节点 id target: 'node1', // String,必须,目标节点 id "shape": "business-edge", label: '3-----1' }, { source: 'node9', // String,必须,起始节点 id target: 'node8', // String,必须,目标节点 id "shape": "business-edge", label: '9---8' }, { source: 'node2', // String,必须,起始节点 id target: 'node9', // String,必须,目标节点 id "shape": "business-edge", label: '2---9' }, ], };

至此,利用x6绘制层次拓扑图就完成了,有时,我们需要,鼠标悬停在节点上,连接线上,或者点击节点上的内容,出现详情,或者弹窗之类的功能,X6也为我们提供了api,只需要在注册节点时,添加点击事件(目前只看到支持点击事件) 

graph.on('node:item_text1', ({e, node, view, cell}) => { //可通过点击事件,进行一些改变节点或者边颜色的操作 node.setAttrs({ title:{fill:'#f59e40'} },{deep:true}) }

完整代码:(有问题欢迎交流,改正)

Document .toolTip1 { min-width: 100px; height: 100px; color: #e2e2e2; border-radius: 5px; background: rgba(58, 65, 77, .95); box-shadow: 0 5px 10px #717e8729; border: 1px solid #575f65; position: fixed; display: none; z-index: 15; } .toolTip1:hover { border: 1px solid #B1C2F8; } .toolTip { width: 150px; height: 150px; position: absolute; background: rgba(58, 65, 77, .95); box-shadow: 0 5px 10px #717e8729; border: 1px solid #575f65; color: #e2e2e2; padding: 10px; font-size: 12px; border-radius: 5px; z-index: 15; display: none; transition: all 0.3s; } .toolTip:hover { border: 1px solid #5f95ff; } .toolTip:hover .arrow { border-top: 6px solid #5f95ff; } .toolTip > .arrow { position: absolute; width: 0; height: 0; bottom: -6px; left: 20px; border-left: 6px solid transparent; border-right: 12px solid transparent; border-top: 6px solid #575f65; filter: drop-shadow(0 2px 12px rgba(0, 0, 0, .03)); z-index: 10; } let toolTipShow = false; let toolTip1Show = false; const {Graph, Shape, Cell} = X6 //注册节点 Graph.registerNode( 'business-node', { width: 400, height: 200, attrs: { body: { stroke: '#5F95FF', strokeWidth: 0.5, fill: 'rgba(95,149,255,0.05)', refWidth: 1, refHeight: 1, pointerEvents: 'visiblePainted', }, title: { fill: '#409eff', stroke: '#fff', strokeWidth: 0.5, refWidth: 1, refHeight:0.2, }, title_text: { ref: 'title', refY: 0.3, refX: 0.5, textAnchor: 'middle', fontWeight: 'bold', fill: '#fff', fontSize: 18, cursor: 'pointer', event: 'node:title_text', }, item1: { fill: 'green', stroke: '#fff', strokeWidth: 0.5, refWidth: 0.5, refHeight: 0.4, x: 0, y: 40, }, item_text1: { ref: 'item1', refY: 0.25, refX: 0.5, fontWeight: 'bold', textAnchor: 'middle', fill: '#fff', fontSize: 28, cursor: 'pointer', event: 'node:item_text1', }, item_text1_1: { ref: 'item1', refY: 0.75, refX: 0.95, textAnchor: 'end', fill: '#d3d3d3', fontSize: 16, cursor: 'pointer', text:'HUAWEI', event: 'node:item_text1_1', }, item2: { fill: 'green', stroke: '#fff', strokeWidth: 0.5, refWidth: 0.5, refHeight: 0.4, x: 200, y: 40 }, item_text2: { ref: 'item2', refY: 0.25, refX: 0.5, fontWeight: 'bold', textAnchor: 'middle', fill: '#fff', fontSize: 28, cursor: 'pointer', }, item_text2_1: { ref: 'item2', refY: 0.75, refX: 0.95, textAnchor: 'end', fill: '#d3d3d3', fontSize: 16, cursor: 'pointer', text:'Apple', }, item3: { fill: 'green', stroke: '#fff', strokeWidth: 0.5, refWidth: 0.5, refHeight: 0.4, x: 0, y: 120 }, item_text3_1: { ref: 'item3', refY: 0.75, refX: 0.95, textAnchor: 'end', fill: '#d3d3d3', fontSize: 16, cursor: 'pointer', text:'mi', }, item_text3: { ref: 'item3', refY: 0.25, refX: 0.5, fontWeight: 'bold', textAnchor: 'middle', fill: '#fff', fontSize: 28, cursor: 'pointer', }, item4: { fill: 'green', stroke: '#fff', strokeWidth: 0.5, refWidth: 0.5, refHeight: 0.4, x: 200, y: 120 }, item_text4: { ref: 'item4', refY: 0.25, refX: 0.5, fontWeight: 'bold', textAnchor: 'middle', fill: '#fff', fontSize: 25, cursor: 'pointer', }, item_text4_1: { ref: 'item4', refY: 0.75, refX: 0.95, textAnchor: 'end', fill: '#d3d3d3', fontSize: 16, cursor: 'pointer', text:'OPPO', }, }, markup: [ { tagName: 'rect', selector: 'body', }, { tagName: 'rect', selector: 'title', }, { tagName: 'text', selector: 'title_text', }, { tagName: 'rect', selector: 'item1', }, { tagName: 'text', selector: 'item_text1', }, { tagName: 'text', selector: 'item_text1_1', }, { tagName: 'rect', selector: 'item2', }, { tagName: 'text', selector: 'item_text2', }, { tagName: 'text', selector: 'item_text2_1', }, { tagName: 'rect', selector: 'item3', }, { tagName: 'text', selector: 'item_text3', }, { tagName: 'text', selector: 'item_text3_1', }, { tagName: 'rect', selector: 'item4', }, { tagName: 'text', selector: 'item_text4', }, { tagName: 'text', selector: 'item_text4_1', }, ], }, true, ) //注册边 Graph.registerEdge( 'business-edge', { inherit: 'edge', connector: {name: 'smooth'}, attrs: { line: { strokeWidth: 2, stroke: '#aabefd', targetMarker: { name:'classic' }, }, }, defaultLabel: { markup: [ { tagName: 'rect', selector: 'body', }, { tagName: 'text', selector: 'label', }, ], attrs: { label: { fill: '#525151', fontSize: 12, textAnchor: 'middle', textVerticalAnchor: 'middle', pointerEvents: 'none', }, body: { ref: 'label', fill: '#B1C2F8', stroke: '#7d94d7', strokeWidth: 2, rx: 3, ry: 3, refWidth: '140%', refHeight: '140%', refX: '-20%', refY: '-20%', }, }, position: { distance: 0.5, options: { absoluteDistance: true, reverseDistance: true, }, }, }, }, true, ) //数据 var data = { // 节点 nodes: [ { id: 'node1', // String,可选,节点的唯一标识 "attrs": { "title_text": { "text": "北京联通1", 'id': 1, "pid": 2, ips:[], }, "item_text1": { "text": "华为1", 'id': 15, "value": 370, }, "item_text2": { "text": "苹果1", 'id': 20, "value": 370, }, "item_text3": { "text": "小米1", 'id': 30, "value": 370, }, "item_text4": { "text": "OPPO1", 'id': 40, "value": 370, }, }, shape: 'business-node', }, { id: 'node2', // String,节点的唯一标识 "attrs": { "title_text": { "text": "北京移动2" }, "item_text1": { "text": "华为2" }, "item_text2": { "text": "苹果2" }, "item_text3": { "text": "小米2" }, "item_text4": { "text": "OPPO2" }, }, shape: 'business-node', }, { id: 'node3', // String,节点的唯一标识 "attrs": { "title_text": { "text": "北京电信3" }, "item_text1": { "name": "华为3", // id:1 }, "item_text2": { "text": "苹果3" }, "item_text3": { "text": "小米3" }, "item_text4": { "text": "OPPO3" }, }, shape: 'business-node', }, { id: 'node9', // String,可选,节点的唯一标识 "attrs": { "title_text": { "text": "北京联通9", 'id': 1, "pid": 2, }, "item_text1": { "text": "华为9", 'id': 15, "value": 370, }, "item_text2": { "text": "苹果9", 'id': 20, "value": 370, }, "item_text3": { "text": "小米9", 'id': 30, "value": 370, }, "item_text4": { "text": "OPPO9", 'id': 40, "value": 370, }, }, shape: 'business-node', }, { id: 'node8', // String,可选,节点的唯一标识 "attrs": { "title_text": { "text": "北京联通8", 'id': 1, "pid": 2, }, "item_text1": { "text": "华为8", 'id': 15, "value": 370, }, "item_text2": { "text": "苹果8", 'id': 20, "value": 370, }, "item_text3": { "text": "小米8", 'id': 30, "value": 370, }, "item_text4": { "text": "OPPO8", 'id': 40, "value": 370, }, }, shape: 'business-node', }, ], // 边 edges: [ { id: 10, source: 'node1', // String,必须,起始节点 id target: 'node2', // String,必须,目标节点 id "shape": "business-edge", label: '1---2', arr: [1, 2, 3, 4, 5, 6, 7, 8], obj: { "qqq": "lallala", "www": "hahah" } }, { source: 'node3', // String,必须,起始节点 id target: 'node2', // String,必须,目标节点 id "shape": "business-edge", label: '3----2', arr: [1, 2], }, { source: 'node3', // String,必须,起始节点 id target: 'node1', // String,必须,目标节点 id "shape": "business-edge", label: '3-----1' }, { source: 'node9', // String,必须,起始节点 id target: 'node8', // String,必须,目标节点 id "shape": "business-edge", label: '9---8' }, { source: 'node2', // String,必须,起始节点 id target: 'node9', // String,必须,目标节点 id "shape": "business-edge", label: '2---9' }, ], }; // 初始化 const graph = new Graph({ container: document.getElementById('container'), width: $(window).width() , height: $(window).height() , background: { color: '#222B36', // 设置画布背景颜色 }, grid: { size: 10, // 网格大小 10px visible: true, // 渲染网格背景 }, autoResize: true, //监听容器大小改变,并自动更新画布大小 mousewheel: true, //画布滚轮缩放 panning: { //画布平移 enabled: true, eventTypes: ['leftMouseDown'] }, resizing: { enabled: true } }); // 配置自动布局参数 var gridLayout = new layout.DagreLayout({ type: 'grid', rankdir: 'LR', nodesep: 300, ranksep: 150, controlPoints: true, connecting: { allowLoop: true, } }) const newModel = gridLayout.layout(data) graph.fromJSON(newModel) graph.scaleContentToFit() graph.centerContent() // 自定义事件 function CustomizeEvent() { graph.on('node:title_text', ({ e, node, view, cell }) => { console.log(e, node, view, cell); toolTipShow = true; const pos = node.position({ relative: true }) const { x, y } = graph.localToClient(pos.x, pos.y) $('.toolTip').css('top', `${y-180}px`) $('.toolTip').css('left', `${x}px`) $('.toolTip').css('display', 'block') }) graph.on("node:mouseleave", ({ e, edge, view }) => { toolTipShow = false setTimeout(() => { if (!toolTipShow) { $('.toolTip').css('display', 'none') } }, 600); // 延迟 1000ms 关闭弹框 }); graph.on("node:moving", ({ e, node, view }) => { let pos = node.position({ relative: true }) const { x, y } = graph.localToClient(pos.x, pos.y) $('.toolTip').css('top', `${y-180}px`) $('.toolTip').css('left', `${x}px`) }); graph.on('node:item_text1', ({ e, node, view, cell }) => { console.log(e, node, view, cell); }) graph.on('node:item_text1_1', ({ e, node, view, cell }) => { console.log(e, node, view, cell); }) graph.on("edge:click", ({ e, edge, view ,cell}) => { $('.toolTip1').css('top', `${e.clientY-3}px`) $('.toolTip1').css('left', `${e.clientX-3}px`) $('.toolTip1').css('display', 'block') }); graph.on("edge:mouseleave", ({ e, edge, view }) => { toolTip1Show = false setTimeout(() => { if (!toolTip1Show) { $('.toolTip1').css('display', 'none') } }, 800); // 延迟 1000ms 关闭弹框 }); } CustomizeEvent() //两个弹出框自动消失 $('.toolTip').hover(() => { toolTipShow = true; }, () => { toolTipShow = false; setTimeout(() => { if (!toolTipShow) { $('.toolTip').css('display', 'none') } }, 200); // 延迟 200ms 关闭弹框 } ) $('.toolTip1').hover(() => { toolTip1Show = true; }, () => { toolTip1Show = false; setTimeout(() => { if (!toolTip1Show) { $('.toolTip1').css('display', 'none') } }, 200); // 延迟 200ms 关闭弹框 } )



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3