通过官方文档,可知高亮相邻节点分为两种方法,文档描述并不是很清楚,对刚接触这个库的小白并不是很友好,慢慢总结慢慢来吧
内置的高亮节点
是通过内置的Behavior activate-relations
来实现,Behavior 是 G6 提供的定义图上交互事件的机制。与交互模式 Mode
配合使用
activate-relations:当鼠标移到某节点时,突出显示该节点以及与其直接关联的节点和连线;
- 参数:
trigger: 'mouseenter'
。表示出发机制,可以是mouseenter
、click
;activeState: 'active'
。活跃节点状态,默认为active
,可以与 graph 实例的xxxStateStyles
结合实现丰富的视觉效果。inactiveState: 'inactive'
。非活跃节点状态,默认值为inactive
。同样可以与 graph 实例的xxxStateStyles
结合实现丰富的视觉效果。- 文档上还提到了另外两个参数,但是本案例中并未使用,暂不做说明 resetSelected、shouldUpdate(e)
- 具体用法
let drawGraph = document.getElementById("drawGraph"); this.graphWidth = drawGraph.scrollWidth; this.graphHeight = drawGraph.scrollHeight || 1200; graphG = new this.G6.Graph({ container: "drawGraph", width: this.graphWidth, height: this.graphHeight, modes: { default: [ { type: "activate-relations", activeState: 'active', inactiveState: 'inactive' }, ], // default: ['activate-relations'] // 由于活跃节点及非活跃节点状态均采用默认值,因此可以简写为这种形式 }, nodeStateStyles:{}, // 配置节点状态样式 edgeStateStyles:{}, // 配置边状态样式 comboStateStyles:{}, // 配置分组状态样式 } graphG.data(data); graphG.render();
如果仅采用内置的高亮节点,会采用默认的样式,最终的渲染效果为:
自定义高亮
这种方式是通过自定义状态
,在通过实例提供的setItemState
、 clearItemStates
设置和清除目标的状态信息,同样需要与graph 实例的 xxxStateStyles
结合实现。
graphG = new this.G6.Graph({ container: "drawGraph", width: this.graphWidth, height: this.graphHeight, nodeStateStyles:{ // 配置节点状态样式,此处就先写一个,后续会有完整的案例分享 highlight: { fill: "#db4437", shadowColor: '#fff', stroke: "#db4437", cursor: "pointer", 'text-shape': { lineWidth: 1, fill: "#db4437", stroke: "#db4437", }, }, }, edgeStateStyles:{}, // 配置边状态样式 comboStateStyles:{}, // 配置分组状态样式 } graphG.data(data); graphG.render(); graphG.on("combo:mouseenter", (e) => { let edgeItem = e.item graphG.setItemState(edgeItem, 'highlight', true) edgeItem.getEdges().forEach(edge => { graphG.setItemState(edge.getTarget(), 'highlight', true) graphG.setItemState(edge.getSource(), 'highlight', true) graphG.setItemState(edge, 'highlight', true) }) graphG.paint() graphG.setAutoPaint(true) }); graphG.on('combo:mouseleave', (e) => { graphG.setAutoPaint(false) graphG.getNodes().forEach(node => { graphG.clearItemStates(node) }) graphG.getEdges().forEach(edge => { graphG.clearItemStates(edge) }) graphG.getCombos().forEach(combo => { graphG.clearItemStates(combo) }) graphG.paint() graphG.setAutoPaint(true) })
如果仅采用自定义高亮节点,最终的渲染效果为:
自定义高亮时保持原始颜色
通过上面的案例,可以看出,combo:mouseenter
时相关联的边和点全部高亮,并且统一了连线的颜色,此时可能会与我们的需求相违背,可能连线还是想要保持原来的颜色,因为不同的颜色描述两点之间的不同类型的指向关系。那么此时在处理鼠标事件时,需要获取要节点和连线 原始样式。
graphG.on("combo:mouseenter", (e) => { let comboItem = e.item; const originStyle = comboItem._cfg.originStyle["circle-combo"].fill; comboItem._cfg.styles.highlight.fill = originStyle; graphG.setItemState(comboItem, "highlight", true); comboItem.getEdges().forEach((edge) => { const originStyle = edge._cfg.originStyle["edge-shape"].stroke; // 获取边edge 原始颜色 edge._cfg.styles.highlight.stroke = originStyle; let edgeSource = edge.getSource(); let edgeTarget = edge.getTarget(); if ( edgeSource._cfg.type === "combo" && edgeSource._cfg.model.id =="100-600" ) { const originStyle = edgeSource._cfg.originStyle["circle-combo"].fill; // 获取分组combo 原始颜色 edgeSource._cfg.styles.highlight.fill = originStyle; } if ( edgeTarget._cfg.type === "combo" && edgeTarget._cfg.model.id =="100-600" ) { const originStyle = edgeTarget._cfg.originStyle["circle-combo"].fill; edgeTarget._cfg.styles.highlight.fill = originStyle; } graphG.setItemState(edgeSource, "highlight", true); graphG.setItemState(edgeTarget, "highlight", true); graphG.setItemState(edge, "highlight", true); }); });
那么此时最终的效果为:
总结
其实两种方法与异曲同工之妙,都是进行状态的处理,只不过一个是帮我们处理了一部分状态与样式,可以直接拿来用,但往往内置的样式与我们实际使用时不相符,因此可以使用两者结合的方式,最终效果及完整demo,采用随机数来模拟实体与关系。
案例完整代码
<template> <div> <div id="drawGraph"></div> </div> </template> <script> let graphG = null export default { mounted() { this.initData(); }, methods: { initData() { let combos = [ { id: '100-600', label: '100-600' }, { id: '100-200', label: '100-200' }, { id: '200-300', label: '200-300' }, { id: '300-400', label: '300-400' }, { id: '400-500', label: '400-500' }, { id: '500-600', label: '500-600' }, ] let edges = [ { source: '100-600', target: '100-200' }, { source: '100-600', target: '200-300' }, { source: '100-600', target: '300-400' }, { source: '100-600', target: '400-500' }, { source: '100-600', target: '500-600' }, ] // 生成(20-30)随机数 模拟节点node let randomCount = Math.floor(Math.random() * 10) + 20; let row_clo = Math.floor(Math.sqrt(randomCount)); let origin = [-150, 50], row = 110, clo = 150; let nodes = [] for (let i = 0; i < randomCount; i++) { let randomNum = String(Math.floor(Math.random() * 500) + 100); // 生成100-600之间的随机数,并与combo进行连线 let rowindex = Math.floor(i / row_clo); let cloindex = i % row_clo; let x = origin[0] + clo * cloindex let y = origin[1] + row * rowindex let node = { label: randomNum, id: randomNum, x, y, style: { fillOpacity: 0.5, cursor: "pointer", fill: randomNum % 5 == 0 ? "#81C7D4" : "#986DB2" } } let index = Math.floor(randomNum / 100) let edge = { source: combos[index].id, target: randomNum, lineWidth: 1, style: { lineDash: [3, 3], lineWidth: 0.5, stroke: "#00AA90" } } nodes.push(node) edges.push(edge) } let data = { combos, edges, nodes } console.log(data); this.makeRelationData(data); }, // 分组 点 连线处理 makeRelationData(data) { if (graphG) { graphG.destroy(); } let drawGraph = document.getElementById("drawGraph"); this.graphWidth = drawGraph.scrollWidth; this.graphHeight = drawGraph.scrollHeight || 1200; let origin = [this.graphWidth / 2, 100]; let row = 150, clo = 180; let combos = data.combos let row_clo = Math.floor(Math.sqrt(combos.length)); for (let i = 0; i < combos.length; i++) { let rowindex = Math.floor(i / row_clo) + 1; let cloindex = (i % row_clo) + 1; // 分组默认样式设置 if (i === 0) { combos[i].x = this.graphWidth / 3 combos[i].y = this.graphHeight / 3 combos[i].style = { fill: "#a5e4f0", opacity: 0.5, cursor: "pointer", }; } else { // 分组定位 combos[i].x = origin[0] + clo * cloindex; combos[i].y = origin[1] + row * rowindex; if (i % 2 === 1) { combos[i].y += 40; } combos[i].style = { fill: "#f6cd6b", fillOpacity: 0.2, } } } this.drawQfast(data) }, drawQfast(data) { graphG = new this.G6.Graph({ container: "drawGraph", width: this.graphWidth, height: this.graphHeight, modes: { default: [ { type: "zoom-canvas", enableOptimize: true, optimizeZoom: 0.2 }, { type: "drag-canvas", enableOptimize: true }, { type: "drag-node", enableOptimize: true, onlyChangeComboSize: true }, { type: "drag-combo", enableOptimize: true, onlyChangeComboSize: true }, { type: "activate-relations", activeState: 'active', inactiveState: 'inactive' }, ], }, defaultEdge: { type: 'cubic-horizontal', lineWidth: 1, style: { endArrow: true, stroke: "#FAD069", }, }, defaultNode: { type: "circle", size: 15, labelCfg: { position: "bottom", style: { fontSize: 15, }, }, }, defaultCombo: { cursor: "pointer", opacity: 0, type: "circle", lineWidth: 1, collapsed: true, labelCfg: { position: "top", refY: 5, style: { fontSize: 16, }, }, }, nodeStateStyles: { highlight: { fill: "#db4437", shadowColor: '#fff', stroke: "#db4437", cursor: "pointer", 'text-shape': { lineWidth: 1, fill: "#db4437", stroke: "#db4437", }, }, inactive: { stroke: '#eee', lineWidth: 1, 'text-shape': { fill: "#eee", stroke: "#eee", }, }, }, edgeStateStyles: { hover: { lineWidth: 3, }, highlight: { stroke: '#00AA90', lineWidth: 3, }, }, comboStateStyles: { highlight: { fill: "#f6cd6b", opacity: 0.7, cursor: "pointer", 'text-shape': { fill: "#A5E4F0", stroke: "#A5E4F0", lineWidth: 1, }, }, inactive: { stroke: '#eee', lineWidth: 1, 'text-shape': { fill: "#eee", stroke: "#eee", }, }, }, }); graphG.data(data); graphG.render(); // 渲染图 graphG.on("edge:mouseenter", (e) => { graphG.setItemState(e.item, "hover", true); }); graphG.on("edge:mouseleave", (e) => { graphG.setItemState(e.item, "hover", false); }); graphG.on("combo:mouseenter", (e) => { let comboItem = e.item; const originStyle = comboItem._cfg.originStyle["circle-combo"].fill; comboItem._cfg.styles.highlight.fill = originStyle; graphG.setItemState(comboItem, "highlight", true); comboItem.getEdges().forEach((edge) => { const originStyle = edge._cfg.originStyle["edge-shape"].stroke; // 获取边edge 原始颜色 edge._cfg.styles.highlight.stroke = originStyle; let edgeSource = edge.getSource(); let edgeTarget = edge.getTarget(); if ( edgeSource._cfg.type === "combo" && edgeSource._cfg.model.id =="100-600" ) { const originStyle = edgeSource._cfg.originStyle["circle-combo"].fill; // 获取分组combo 原始颜色 edgeSource._cfg.styles.highlight.fill = originStyle; } if ( edgeTarget._cfg.type === "combo" && edgeTarget._cfg.model.id =="100-600" ) { const originStyle = edgeTarget._cfg.originStyle["circle-combo"].fill; edgeTarget._cfg.styles.highlight.fill = originStyle; } graphG.setItemState(edgeSource, "highlight", true); graphG.setItemState(edgeTarget, "highlight", true); graphG.setItemState(edge, "highlight", true); }); }); graphG.on('combo:mouseleave', () => { graphG.setAutoPaint(false) graphG.getNodes().forEach(node => { graphG.clearItemStates(node) }) graphG.getEdges().forEach(edge => { graphG.clearItemStates(edge) }) graphG.getCombos().forEach(combo => { graphG.clearItemStates(combo) }) graphG.paint() graphG.setAutoPaint(true) }) }, } }; </script>