效果如图
官方案例
人工智能建模 DAG 图
vue2中自定义节点
代码
1.dag.json
[{"id": "1","shape": "dag-node","x": 290,"y": 110,"data": {"label": "读数据","status": "success"},"ports": [{"id": "1-1","group": "bottom"}]},{"id": "2","shape": "dag-node","x": 290,"y": 225,"data": {"label": "逻辑回归","status": "success"},"ports": [{"id": "2-1","group": "top"},{"id": "2-2","group": "bottom"},{"id": "2-3","group": "bottom"}]},{"id": "3","shape": "dag-node","x": 170,"y": 350,"data": {"label": "模型预测","status": "success"},"ports": [{"id": "3-1","group": "top"},{"id": "3-2","group": "bottom"}]},{"id": "4","shape": "dag-node","x": 450,"y": 350,"data": {"label": "读取参数","status": "success"},"ports": [{"id": "4-1","group": "top"},{"id": "4-2","group": "bottom"}]},{"id": "5","shape": "dag-edge","source": {"cell": "1","port": "1-1"},"target": {"cell": "2","port": "2-1"},"zIndex": 0},{"id": "6","shape": "dag-edge","source": {"cell": "2","port": "2-2"},"target": {"cell": "3","port": "3-1"},"zIndex": 0},{"id": "7","shape": "dag-edge","source": {"cell": "2","port": "2-3"},"target": {"cell": "4","port": "4-1"},"zIndex": 0}]
2.index.html
<html>
<head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>vue2-x6-DAG自定义vue组件节点</title><script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script><script type="text/javascript" src="https://unpkg.com/@antv/x6@1.34.14/dist/x6.js"></script><script type="text/javascript" src="https://unpkg.com/@antv/x6-vue-shape@1.1.4/dist/x6-vue-shape.js"></script><style>.node {display: flex;align-items: center;width: 100%;height: 100%;background-color: #fff;border: 1px solid #c2c8d5;border-left: 4px solid #5F95FF;border-radius: 4px;box-shadow: 0 2px 5px 1px rgba(0, 0, 0, 0.06);}.node img {width: 20px;height: 20px;flex-shrink: 0;margin-left: 8px;}.node .label {display: inline-block;flex-shrink: 0;width: 104px;margin-left: 8px;color: #666;font-size: 12px;}.node .status {flex-shrink: 0;}.node.success {border-left: 4px solid #52c41a;}.node.failed {border-left: 4px solid #ff4d4f;}.node.running .status img {animation: spin 1s linear infinite;}.x6-node-selected .node {border-color: #1890ff;border-radius: 2px;box-shadow: 0 0 0 4px #d4e8fe;}.x6-node-selected .node.success {border-color: #52c41a;border-radius: 2px;box-shadow: 0 0 0 4px #ccecc0;}.x6-node-selected .node.failed {border-color: #ff4d4f;border-radius: 2px;box-shadow: 0 0 0 4px #fedcdc;}.x6-edge:hover path:nth-child(2) {stroke: #1890ff;stroke-width: 1px;}.x6-edge-selected path:nth-child(2) {stroke: #1890ff;stroke-width: 1.5px !important;}@keyframes running-line {to {stroke-dashoffset: -1000;}}@keyframes spin {from {transform: rotate(0deg);}to {transform: rotate(360deg);}}</style>
</head><body><div id="workflow"><h5>注意事项:</h5><h6>1.必须启动前端服务访问,使用hbuilderx运行内置浏览器或者vscode安装Live Server插件,打开文件右键,选择“open with five server”即可,否则跨域!!!</h6><h6>2.cdn或本地引入方式x6-vue-shape版本必须和x6同一个大版本!!!</h6><button @click="add">外部点击Add第一个元素num</button><div id="container"></div></div>
</body></html>
<script>// 自定义vue组件节点const nodeBox = Vue.component('node-box', {template: `<div :class="['node', status]" @click="nodeDrag()"><img :src="image.logo" /><span class="label">{{label}}内部Add:{{ num }}</span><span class="status"><img v-if="status === 'success'" :src="image.success" /><img v-if="status === 'failed'" :src="image.failed" /><img v-if="status === 'running'" :src="image.running" /></span></div>`,inject: ["getGraph", "getNode"],data() {return {// 自定义测试数字num: 0,// 自定义节点名称label: '',// 自定义节点状态status: '',// 状态数据iconimage: {logo: 'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*evDjT5vjkX0AAAAAAAAAAAAAARQnAQ',success:'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*6l60T6h8TTQAAAAAAAAAAAAAARQnAQ',failed:'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*SEISQ6My-HoAAAAAAAAAAAAAARQnAQ',running:'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*t8fURKfgSOgAAAAAAAAAAAAAARQnAQ',}}},mounted() {const node = this.getNode();// 监听数据改变事件,通过外部控制节点数据node.on("change:data", ({ current }) => {this.num = current.num;this.label = current.label;this.status = current.status;});},methods: {// 内部控制节点数据,节点拖动后触发,拿到x,y数据回传后端nodeDrag() {const node = this.getNode();const { num } = node.getData();node.setData({num: num + 1,});console.log('节点坐标:', node.position())},},})// 父页面const workflow = new Vue({el: '#workflow',data() {return {graph: null,};},mounted() {// 使用 CDN 引入时暴露了 X6 全局变量const { Graph, Path } = X6// 自定义vue节点Graph.registerNode("dag-node", {inherit: "vue-shape",x: 200,y: 150,width: 180,height: 36,component: {template: `<node-box />`,components: {nodeBox,},},ports: {groups: {top: {position: 'top',attrs: {circle: {r: 4,magnet: true,stroke: '#C2C8D5',strokeWidth: 1,fill: '#fff',},},},bottom: {position: 'bottom',attrs: {circle: {r: 4,magnet: true,stroke: '#C2C8D5',strokeWidth: 1,fill: '#fff',},},},},},});// 注册连接线Graph.registerEdge('dag-edge',{inherit: 'edge',attrs: {line: {stroke: '#C2C8D5',strokeWidth: 1,targetMarker: null,},},},true,)// 注册连接线弧度Graph.registerConnector('algo-connector',(s, e) => {const offset = 4const deltaY = Math.abs(e.y - s.y)const control = Math.floor((deltaY / 3) * 2)const v1 = { x: s.x, y: s.y + offset + control }const v2 = { x: e.x, y: e.y - offset - control }return Path.normalize(`M ${s.x} ${s.y}L ${s.x} ${s.y + offset}C ${v1.x} ${v1.y} ${v2.x} ${v2.y} ${e.x} ${e.y - offset}L ${e.x} ${e.y}`,)},true,)// 定义画布const graph = new Graph({container: document.getElementById('container'),// 对齐线snapline: true,width: 800,height: 600,panning: {enabled: true,eventTypes: ['leftMouseDown', 'mouseWheel'],},mousewheel: {enabled: true,modifiers: 'ctrl',factor: 1.1,maxScale: 1.5,minScale: 0.5,},highlighting: {magnetAdsorbed: {name: 'stroke',args: {attrs: {fill: '#fff',stroke: '#31d0c6',strokeWidth: 4,},},},},connecting: {snap: true,allowBlank: false,allowLoop: false,highlight: true,connector: 'algo-connector',connectionPoint: 'anchor',anchor: 'center',validateMagnet({ magnet }) {return magnet.getAttribute('port-group') !== 'top'},createEdge() {return graph.createEdge({shape: 'dag-edge',attrs: {line: {strokeDasharray: '5 5',},},zIndex: -1,})},},selecting: {enabled: true,multiple: true,rubberEdge: true,rubberNode: true,modifiers: 'shift',rubberband: true,},});this.graph = graph// 连接线,连接完成事件graph.on('edge:connected', ({ edge }) => {edge.attr({line: {strokeDasharray: '',},})})// 监听子节点数据改变graph.on('node:change:data', ({ node }) => {const edges = graph.getIncomingEdges(node)const { status } = node.getData()edges?.forEach((edge) => {if (status === 'running') {edge.attr('line/strokeDasharray', 5)edge.attr('line/style/animation', 'running-line 30s infinite linear')} else {edge.attr('line/strokeDasharray', '')edge.attr('line/style/animation', '')}})})// 数据状态const nodeStatusList = [[{id: '1',status: 'running',},{id: '2',status: 'default',},{id: '3',status: 'default',},{id: '4',status: 'default',},],[{id: '1',status: 'success',},{id: '2',status: 'running',},{id: '3',status: 'default',},{id: '4',status: 'default',},],[{id: '1',status: 'success',},{id: '2',status: 'success',},{id: '3',status: 'running',},{id: '4',status: 'running',},],[{id: '1',status: 'success',},{id: '2',status: 'success',},{id: '3',status: 'success',},{id: '4',status: 'failed',},],]// 初始化节点/边const init = (data) => {const cells = []data.forEach((item) => {if (item.shape === 'dag-node') {cells.push(graph.createNode(item))} else {cells.push(graph.createEdge(item))}})graph.resetCells(cells)}// 显示节点状态const showNodeStatus = async (statusList) => {const status = statusList.shift()status?.forEach((item) => {const { id, status } = itemconst node = graph.getCellById(id)const data = node.getData()node.setData({...data,status: status,num: 0,})})setTimeout(() => {showNodeStatus(statusList)}, 3000)}// 模拟数据fetch('./dag.json').then((response) => response.json()).then((data) => {// 加载节点init(data)// 节点状态showNodeStatus(nodeStatusList)// 将画布内容中心与视口中心对齐graph.centerContent()})},methods: {// 控制子组件内部变量add() {const nodes = this.graph.getNodes();console.log(nodes)if (nodes.length) {nodes.forEach((node) => {// 1 为该组件id,正式环境根据传参id来控制具体某个元素内部if (node.id == 1) {const { num } = node.getData();node.setData({num: num + 1,});}});}},}});
</script>