天天看點

vue + antv g6實作拓撲圖(用來做資料血緣展示業務)官方文檔具體使用

antv g6使用

  • 官方文檔
  • 具體使用
    • 業務需求
    • 主要問題
    • 完整代碼

官方文檔

官方文檔連結(看完之後的感覺是我懂了,但沒完全懂。)

具體使用

業務需求

vue + antv g6實作拓撲圖(用來做資料血緣展示業務)官方文檔具體使用
  • 做資料血緣,大概類似做ER圖吧,一開始想使用echarts拓撲圖來做,但是echarts有個定位問題一直沒弄明白,找不到辦法解決,echarts的節點需要自定義位置,但是我們是動态資料,且力引導算法不滿足業務需求,是以最後選擇了antv g6。

主要問題

1、需要自定義節點,g6使用自定義dom節點滿足改需求,但是根據文檔提供,dom節點無法使用g6提供的點選事件。

vue + antv g6實作拓撲圖(用來做資料血緣展示業務)官方文檔具體使用
  • 通過配置節點type,使節點指向某自定義節點。
// 目前節點高亮 通過type比對自定義節點
if (this.data.nodes[i].highLight == 1) {
	this.data.nodes[i].type = 'center'
} else {
	this.data.nodes[i].type = 'dom-node'
}
           
  • 自定義dom節點
G6.registerNode(
	'dom-node', {
		draw: (cfg, group) => {
			// console.log(cfg, group)
			const shape = group.addShape('dom', {
				attrs: {
					width: cfg.size[0],
					height: cfg.size[1],
					// 傳入 DOM 的 html
					html: `
					<div onclick="select(${cfg.name})" id="${cfg.select}" class="dom-node-style" style="cursor:pointer; border-radius: 5px; width: ${
					  cfg.size[0] - 5
					}px; height: ${cfg.size[1] - 5}px; display: flex;">
					  
					  <span style="margin:auto; padding:auto; color: #000">${cfg.id}</span>
					</div> `,
				},

				draggable: true,
			});
			return shape;
		},
	},
	'single-node',
);
// 目前表節點
G6.registerNode(
	'center', {
		draw: (cfg, group) => {
			// console.log(cfg)
			const shape = group.addShape('dom', {
				attrs: {
					width: cfg.size[0],
					height: cfg.size[1],
					// 傳入 DOM 的 html   ${cfg.isActive ? "class='selected-style'" : "class='node-style'"}"
					html: `    
						<div onclick="select(${cfg.name})" id="${cfg.select}" class="node-style" style="width: ${
						  cfg.size[0] - 5
						}px; height: ${cfg.size[1] - 5}px; display: flex;border-radius: 5px;cursor:pointer;">
						  <span style="margin:auto; padding:auto; color: #000">${cfg.id}</span>
						</div> `,
				},

				draggable: true,
			});
			return shape;
		},

	},
	'single-node',
);
           

2、調整狀态樣式,點選節點或線時改變樣式。

  • 預設線可直接配置。具體可檢視文檔
defaultEdge: {
	style: {
		endArrow: true,
		lineWidth: 2,
		stroke: '#CED4D9',
		fill: "#CED4D9",
	},
},
edgeStateStyles: {
	click: {
		lineWidth: 2,
		stroke: '#5394ef',
		fill: "#5394ef",
	},
},
// 邊點選事件
graph.on('edge:click', (e) => {
	// 先将所有目前是 click 狀态的邊置為非 click 狀态
	const clickEdges = graph.findAllByState('edge', 'click');
	clickEdges.forEach((ce) => {
		graph.setItemState(ce, 'click', false);
	});
	const item = e.item; // 擷取滑鼠進入的邊元素對象
	const jobId = item._cfg.model.jobId
	graph.setItemState(item, 'click', true); // 設定目前邊的 click 狀态為 true
	that.getLineInfo(jobId)
});
           
  • 自定義dom節點需要動态改變class屬性來改變樣式,暫未發現更好的解決辦法,歡迎交流!
// 節點點選事件
let that = this;
window.select = function(id) {
	const clickEdges = graph.findAllByState('edge', 'click');
	clickEdges.forEach((ce) => {
		graph.setItemState(ce, 'click', false);
	});
	that.getNodeInfo(id)
	var divId = 'temp' + id
	// 擷取目前選中div 點選後該div替換選中樣式 其他所有div恢複未選中樣式
	var selectId = document.getElementById(divId)
	for (let i in that.data.nodes) {
		if (that.data.nodes[i].name == id) {
			// if (that.data.nodes[i].isActive == false) {
				selectId.setAttribute('class', 'selected-style')
				that.data.nodes[i].isActive = true
			// }
		} else {
			let tempId = document.getElementById('temp' + that.data.nodes[i].name)
			if (that.data.nodes[i].type == 'dom-node') {
				tempId.setAttribute('class', 'dom-node-style')
			} else {
				tempId.setAttribute('class', 'node-style')
			}
			that.data.nodes[i].isActive = false
		}
	}
}
           

3、如何銷毀畫布?

  • 在每次請求資料之前判斷,如果已經有資料則銷毀畫布。
// 避免多次渲染資料 銷毀畫布
if (this.chart !== '') {
	this.chart.destroy()
}
           
  • 在渲染g6前指派

    this.chart = graph

4、互動模式使用,default 模式中包含點選選中節點行為和拖拽畫布行為,該行為觸發時會重新渲染畫布導緻自定義dom節點設定的狀态樣式取消。(自定義dom節點寫在draw中,改變dom節點的狀态樣式如選中後變色等,是通過動态改變class屬性實作的),

該問題目前沒有解決

5、布局問題,g6也使用自定義節點位置,但是提供了dagre層次布局,該算法可滿足需求。

layout: {
	type: 'dagre', //層次布局
	rankdir: 'LR', // 可選,預設為圖的中心
	align: 'DL', // 可選
	nodesep: 25, // 可選
	ranksep: 25, // 可選
	controlPoints: true, // 可選
},
           

完整代碼

vue + antv g6實作拓撲圖(用來做資料血緣展示業務)官方文檔具體使用
<script>
    //引入g6
	import G6 from '@antv/g6';
	import {
		getGraphData,
		getLineInfo,
		getNodeInfo
	} from '@/api/home/assetCatalogueDetail'
	export default {
		data() {
			return {
				id: 0,
				type:0,//0-全部血緣,1-直系父子,2-所有父表,3-所有子表
				activeName:'first',
				chart: '',
				visible: true,
				nodeList: [],
				lineList: [],
				data: {
					// 點集
					nodes: [],
					// 邊集
					edges: [],
				}
			}
		},
		methods: {
			init(id) {
				this.id = id
				this.getData()
			},
			getData() {
				// 避免多次渲染資料 銷毀畫布
				if (this.chart !== '') {
					this.chart.destroy()
				}
				this.getNodeInfo(this.id)
				getGraphData(Object.assign({
					basicDataId:this.id,
					relationType: this.type
				})).then(response => {
					this.data.nodes = response.data.data.node
					this.data.edges = response.data.data.line
					// console.log(this.data)
					for (let i in this.data.nodes) {
						// g6 id代表節點名稱
						let name = this.data.nodes[i].name
						let id = this.data.nodes[i].id
						this.data.nodes[i].id = name
						this.data.nodes[i].name = id
						// 設定節點的連接配接點 anchorPoint 指的是邊連入節點的相對位置,即節點與其相關邊的交點位置
						this.data.nodes[i].anchorPoints = [
							[0.5, 0],
							[1, 0.5],
							[0, 0.5],
							[0.5, 1],
						]
						this.data.nodes[i].select = 'temp' + id
						this.data.nodes[i].isActive = false
						this.data.nodes[i].size = [120, 40]
						// 目前節點高亮 通過type比對自定義節點
						if (this.data.nodes[i].highLight == 1) {
							this.data.nodes[i].type = 'center'
						} else {
							this.data.nodes[i].type = 'dom-node'
						}
					}
				
					this.renderView()
				})
			},
			renderView() {
				G6.registerNode(
					'dom-node', {
						draw: (cfg, group) => {
							// console.log(cfg, group)
							const shape = group.addShape('dom', {
								attrs: {
									width: cfg.size[0],
									height: cfg.size[1],
									// 傳入 DOM 的 html
									html: `
									<div onclick="select(${cfg.name})" id="${cfg.select}" class="dom-node-style" style="cursor:pointer; border-radius: 5px; width: ${
									  cfg.size[0] - 5
									}px; height: ${cfg.size[1] - 5}px; display: flex;">
									  
									  <span style="margin:auto; padding:auto; color: #000">${cfg.id}</span>
									</div> `,
								},

								draggable: true,
							});
							return shape;
						},
					},
					'single-node',
				);
				// 目前表節點
				G6.registerNode(
					'center', {
						draw: (cfg, group) => {
							// console.log(cfg)
							const shape = group.addShape('dom', {
								attrs: {
									width: cfg.size[0],
									height: cfg.size[1],
									// 傳入 DOM 的 html   ${cfg.isActive ? "class='selected-style'" : "class='node-style'"}"
									html: `    
										<div onclick="select(${cfg.name})" id="${cfg.select}" class="node-style" style="width: ${
										  cfg.size[0] - 5
										}px; height: ${cfg.size[1] - 5}px; display: flex;border-radius: 5px;cursor:pointer;">
										  <span style="margin:auto; padding:auto; color: #000">${cfg.id}</span>
										</div> `,
								},

								draggable: true,
							});
							return shape;
						},

					},
					'single-node',
				);

				const graph = new G6.Graph({
					renderer: 'svg', //使用 Dom node 的時候需要使用 svg 的渲染形勢
					container: 'mountNode',
					width: 800,
					height: 500,
					layout: {
						type: 'dagre', //層次布局
						rankdir: 'LR', // 可選,預設為圖的中心
						align: 'DL', // 可選
						nodesep: 25, // 可選
						ranksep: 25, // 可選
						controlPoints: true, // 可選
					},
					defaultEdge: {
						style: {
							endArrow: true,
							lineWidth: 2,
							stroke: '#CED4D9',
							fill: "#CED4D9",
							// cursor:'pointer'
						},
					},
					edgeStateStyles: {
						click: {
							lineWidth: 2,
							stroke: '#5394ef',
							fill: "#5394ef",
						},
					},
					modes: {
						default: [
							// 'drag-canvas', //拖拽畫布
							// 'zoom-canvas', //縮放畫布
						]
					},
					fitCenter: true, //平移圖到中心将對齊到畫布中心,但不縮放

				});
				// 節點點選事件
				let that = this;
				window.select = function(id) {
					const clickEdges = graph.findAllByState('edge', 'click');
					clickEdges.forEach((ce) => {
						graph.setItemState(ce, 'click', false);
					});
					that.getNodeInfo(id)
					var divId = 'temp' + id
					// 擷取目前選中div 點選後該div替換選中樣式 其他所有div恢複未選中樣式
					var selectId = document.getElementById(divId)
					for (let i in that.data.nodes) {
						if (that.data.nodes[i].name == id) {
							// if (that.data.nodes[i].isActive == false) {
								selectId.setAttribute('class', 'selected-style')
								that.data.nodes[i].isActive = true
							// }
						} else {
							let tempId = document.getElementById('temp' + that.data.nodes[i].name)
							if (that.data.nodes[i].type == 'dom-node') {
								tempId.setAttribute('class', 'dom-node-style')
							} else {
								tempId.setAttribute('class', 'node-style')
							}
							that.data.nodes[i].isActive = false
						}
					}
				}
				// 邊點選事件
				graph.on('edge:click', (e) => {
					// 先将所有目前是 click 狀态的邊置為非 click 狀态
					const clickEdges = graph.findAllByState('edge', 'click');
					clickEdges.forEach((ce) => {
						graph.setItemState(ce, 'click', false);
					});
					const item = e.item; // 擷取滑鼠進入的邊元素對象
					const jobId = item._cfg.model.jobId
					graph.setItemState(item, 'click', true); // 設定目前邊的 click 狀态為 true
					that.getLineInfo(jobId)
				});
				this.chart = graph
				graph.data(this.data); // 讀取 Step 2 中的資料源到圖上
				graph.render(); // 渲染圖
				// graph.fitView();
			},
			getNodeInfo(value) {
				this.visible = true
				getNodeInfo(Object.assign({
					id: value,
					curId: this.id
				})).then(response => {
					this.nodeList = response.data.data
					// console.log(this.nodeList)
				})
			},
			getLineInfo(value) {
				this.visible = false
				getLineInfo(value).then(response => {
					this.lineList = response.data.data
					// console.log(this.lineList)
				})
			},
			
		}
	}
</script>

<style>
	.node-style {
		background-color: #fff;
		border: 1px solid #5B8FF9;
	}

	.selected-style {
		background-color: orange;
	}

	.dom-node-style {
		background-color: #fff;
		border: 1px solid #000;
	}
</style>