antv-G6拓扑图的自定义关系图小结
一、需求背景
结合项目需求有如下需求:
- 分为上下两行,类似关系图;
- 自定义node样式;
- 击一个node,再点击另一类别node,生成连线。点击一个node后,再次点击当前node或者点击其他位置,取消选中。直线连接。点击连线,可以删除连线。连线样式有弧度。划上node节点出现弹框
二、主要代码实现
import G6 from '@antv/g6'; //引入G6
const setG6Topu = ()=>{ //先清除原有 if(graphs.current){ graphs.current.destroy() } graphs.current = new G6.Graph({ container: "topologicalCon", width: tuopuWidth, height: 154, linkCenter: true, modes: { // 根据不同需求设置拓扑图展示/可操作状态 default: ['click-add-edge','rectNode'], view: [], }, defaultNode: { type: "rectNode", size: [32,48], color: "#008dff", style: { fill: "#9EC9FF", // lineWidth: 3 }, labelCfg: { } }, nodeStateStyles: { selected: { stroke: '#f00', lineWidth: 5 } }, plugins:[tooltip], defaultEdge: { type: 'polyline', style: { radius: 10, offset: 12, stroke: '#40A9FF', lineWidth: 1, } } }); //添加数据 graphs.current.data(tpData); //切换状态 if(type=="view" || disableState){ graphs.current.setMode('view') }else{ graphs.current.setMode('default') } //加载 graphs.current.render(); } //self为自定义暂存点数据,getEdgesObj()/getNodeObj()/getEdgesObj等为自定义获取数据方法以及自定义判断 //G6自带核心方法黄色框中标出。 //点击node逻辑:不可连线则清除,可连线则起点保存,终点生成线 //鼠标滑过逻辑:有起点下生成跟随鼠标的直需线 //点击线逻辑:有起点下点击则清空,无起点则为单纯点击线(本次需求为删除) G6.registerBehavior('click-add-edge', { // Set the events and the corresponding responsing function for this behavior getEvents() { return { // 'click': 'onClick' 'node:click': 'onNodeClick', // The event is canvas:click, the responsing function is onClick 'mousemove': 'onMousemove', // The event is mousemove, the responsing function is onMousemove 'edge:click': 'onEdgeClick', // The event is edge:click, the responsing function is onEdgeClick }; }, onNodeClick(ev) { //点击node操作 const node = ev.item; const point = { x: ev.x, y: ev.y }; const model = node.getModel(); let obg = {...tpData} let {nodes=[],edges = []} = obg //判断是否已被连线,有则该次清空连线事件 const hasLined = getEdgesObj(edges,model.id) if(hasLined){ if (self.current.addingEdge && self.current.edge) { graphs.current.removeItem(self.current.edge); self.current.addingEdge = false self.current.edge = null self.current.id = null return } return } //判断是否已有暂存点,有则为生成线,无则为生成新连线,做起点暂存 if (self.current.addingEdge && self.current.edge) { const sourceObj = getNodeObj(nodes,self.current.id) const targetObj = getNodeObj(nodes,model.id) //判断起点终点为同一个 if(sourceObj.addtype == targetObj.addtype){ graphs.current.removeItem(self.current.edge); self.current.addingEdge = false self.current.edge = null self.current.id = null return } const lineWired = getEdgesObj(edges,self.current.id,model.id) if(lineWired){ graphs.current.removeItem(self.current.edge); self.current.addingEdge = false self.current.edge = null self.current.id = null return } edges.push({ source: self.current.id, target: model.id, }) obg.edges = edges seTtpData(obg) self.current.addingEdge = false self.current.edge = null self.current.id = null } else { self.current.addingEdge = true //添加线 self.current.edge = graphs.current.addItem('edge', { source: model.id, target: model.id, }) self.current.id = model.id } }, onMousemove(ev) { //鼠标在拓扑区域滑动操作 const point = { x: ev.x, y: ev.y }; if (self.current.addingEdge && self.current.edge) { //有暂存点则修未连接线颜色/样式 graphs.current.updateItem(self.current.edge, { type: 'line', target: point, style: { lineDash: [2, 2] , // 虚线边 stroke: '#5FB333', } }); } }, onEdgeClick(ev) { //点击线触发事件 const currentEdge = ev.item; if (self.current.addingEdge && self.current.edge === currentEdge) { //有暂存点则进行删除暂存操作 graphs.current.removeItem(self.current.edge); self.current.addingEdge = false self.current.edge = null self.current.id = null }else{ //没暂存点则删除线 const node = ev.item const model = node.getModel(); let obg = {...tpData} let {edges = []} = obg const sourceObj = getNodeObj(obg.nodes,model.source) const targetObj = getNodeObj(obg.nodes,model.target) let list = [] edges.forEach(el=>{ if(el.source == model.source && el.target == model.target){ return }else{ list.push(el) } }) obg.edges = [...list] //自定义node节点,网上流传两种方式,此种支持G6定义事件,符合实际需求 G6.registerNode('rectNode', { draw: (cfg, group) => { //根据条件判断可点击/不可点击样式,可不关注 let styObg = { opacity:1, fill:'#FFFFFF', stroke:'#40A9FF', color:'#000000', textX:0, textY:cfg.addtype != 'top'?-8:15, imgX:-8, imgY:cfg.addtype != 'top'?2:-20, } if(disableState || cfg.disabledFlag){ styObg={ opacity:0.3, fill:'#FFFFFF', stroke:'rgba(0,0,0,.25)', color:'rgba(0,0,0,.25)', textX:0, textY:cfg.addtype != 'top'?-8:15, imgX:-8, imgY:cfg.addtype != 'top'?2:-20, } } //最外面的那层 const shape = group.addShape('rect', { draggable: true, attrs: { x: -16, y: -24, width: 32, height: 48, fill: styObg.fill, //填充色 stroke: styObg.stroke, //边框 radius: 8, }, }); //里面的那层 group.addShape('image', { attrs: { x: styObg.imgX, y:styObg.imgY, width: 16, height: 16, img: cfg.img, opacity: styObg.opacity, }, name: 'ip-cp-icon', }); //文字 group.addShape('text', { attrs: { textAlign: 'center', x: styObg.textX, y: styObg.textY, width: 32, height: 12, lineHeight: 10, fontSize:12, text: getLable(cfg.label), fill: styObg.color, }, }); return shape; }, }); //G6悬浮框 const tooltip = new G6.Tooltip({ getContent(e) { const outDiv = document.createElement('div'); outDiv.style.minWidth = '180px'; outDiv.style.maxWidth = '680px'; let lineObj = getLineObj(e.item.getModel().id) outDiv.innerHTML = ` <h4>${e.item.getModel().CiName || e.item.getModel().id}</h4> <div>${相关连线'}:${lineObj.source?lineObj.source+' ——'+ lineObj.target :'-'} </div>` return outDiv }, itemTypes: ['node'] })
三、其他补充
node位置:在node的数据中的x,y属性定义,间隔与高度可动态计算;
容易忽略的一点:代码整理删除和修改地方可能有点乱,实现逻辑与思路看下就行;
四、成品
五、
其实,到第四个就完了,占占篇幅而已。