import { Addon, FunctionExt, Graph, Shape } from '@antv/x6' import { getImg } from '../index.js' import graphData from './data/data' import { customPorts } from './ports' import './shape' const insertCss = require('insert-css') insertCss(` @keyframes ant-line { to { stroke-dashoffset: -1000 } } `) export default class FlowGraph { static graph = null static stencil = null /** * 初始化方法 * @param {*} dom 画板容器 * @param {*} width 容器宽度 * @param {*} height 容器高度 * @param {*} flag 默认为true,传入false只实例化画板 * @returns */ static init(dom, width = 1200, height = 900, flag = true) { // 初始化 流程图画板 this.graph = new Graph({ background: { color: '#f4f4f4' // 设置画布背景颜色 }, container: dom, width: width, height: height, autoResize: true, grid: { size: 10, visible: true, type: 'doubleMesh', args: [ { color: '#cccccc', thickness: 1 }, { color: '#5F95FF', thickness: 1, factor: 4 } ] }, scroller: { enabled: false, pageVisible: false, pageBreak: false, pannable: false }, // 开启画布缩放 mousewheel: { enabled: true, // modifiers: ['ctrl', 'meta'], minScale: 0.1, maxScale: 10, // 当鼠标滚轮滚动时触发缩放 zoomOnMouseWheel: true }, interacting: { nodeMovable: false, // 节点是否可以被移动。 edgeMovable: false, // 边是否可以被移动。 edgeLabelMovable: false, // 边的标签是否可以被移动。 arrowheadMovable: false, // 边的起始/终止箭头是否可以被移动 vertexMovable: false, // 边的路径点是否可以被移动。 vertexAddable: false, // 是否可以添加边的路径点。 vertexDeletable: false // 边的路径点是否可以被删除。 }, connecting: { anchor: 'center', connectionPoint: 'anchor', allowBlank: true, highlight: true, snap: true, // 是否自动吸附 allowMulti: true, // 是否允许在相同的起始节点和终止之间创建多条边 allowNode: false, // 是否允许边链接到节点(非节点上的链接桩) allowBlank: false, // 是否允许连接到空白点 allowLoop: false, // 是否允许创建循环连线,即边的起始节点和终止节点为同一节点, allowEdge: false, // 是否允许边链接到另一个边 highlight: false, // 拖动边时,是否高亮显示所有可用的连接桩或节点 connectionPoint: 'anchor', // 指定连接点 anchor: 'center', // 指定被连接的节点的锚点 createEdge() { // X6 的 Shape 命名空间中内置 Edge、DoubleEdge、ShadowEdge 三种边 return new Shape.DoubleEdge({ attrs: { // line: { // // stroke: '#5F95FF', // // strokeWidth: 4, // // targetMarker: { // // name: 'classic', // // size: 8, // // }, // stroke: '#1890ff', // strokeDasharray: 5, // targetMarker: null,//block classic diamond cross async path circle circlePlus ellipse // style: { // animation: 'ant-line 30s infinite linear', // }, // }, line: { strokeWidth: 4, stroke: '#cfe7f2', strokeDasharray: 0, style: { animation: 'ant-line 0s infinite linear' }, targetMarker: null // 去掉箭头 }, outline: { stroke: '#456d89', strokeWidth: 7 } }, router: { name: 'metro', args: { offset: 32, direction: 'H' } } }) }, validateConnection({ sourceView, targetView, sourceMagnet, targetMagnet }) { if (sourceView === targetView) { return false } if (!sourceMagnet) { return false } if (!targetMagnet) { return false } return true } }, highlighting: { magnetAvailable: { name: 'stroke', args: { padding: 4, attrs: { strokeWidth: 4, stroke: 'rgba(223,234,255)' } } } }, // 开启拖拽平移(防止冲突,按下修饰键并点击鼠标才能触发画布拖拽) panning: { enabled: true, // modifiers: 'shift' }, resizing: true, rotating: true, selecting: { enabled: true, multiple: true, rubberband: true, movable: true, showNodeSelectionBox: true }, snapline: true, history: true, clipboard: { enabled: true }, keyboard: { enabled: true }, embedding: { enabled: true, findParent({ node }) { const bbox = node.getBBox() return this.getNodes().filter((node) => { // 只有 data.parent 为 true 的节点才是父节点 const data = node.getData() if (data && data.parent) { const targetBBox = node.getBBox() return bbox.isIntersectWithRect(targetBBox) } return false }) } } }) if (!flag) { // this.graph.centerContent() this.graph.hideGrid() // 返显渲染的时候 隐藏网格 return this.graph } // this.initStencil() // this.initShape() // this.initGraphShape() // this.initEvent() // return this.graph } // 初始化根节点 static initStencil() { this.stencil = new Addon.Stencil({ target: this.graph, stencilGraphWidth: 280, search: { rect: true }, collapsable: true, groups: [ { name: 'basic', title: '基础节点', graphHeight: 180 }, { name: 'custom-image', title: '系统设计图', graphHeight: 600 } // { // name: 'combination', // title: '组合节点', // layoutOptions: { // columns: 1, // marginX: 60, // }, // graphHeight: 260, // }, // { // name: 'group', // title: '节点组', // graphHeight: 100, // layoutOptions: { // columns: 1, // marginX: 60, // }, // }, ] }) const stencilContainer = document.querySelector('#stencil') stencilContainer?.appendChild(this.stencil.container) } // 初始化具体每个根节点下不同类型节点 static initShape() { const { graph } = this // 基础节点 const r1 = graph.createNode({ shape: 'flow-chart-rect', attrs: { body: { rx: 24, ry: 24 }, text: { text: '起始节点' } } }) const r2 = graph.createNode({ shape: 'flow-chart-rect', attrs: { text: { text: '流程节点' } } }) const r3 = graph.createNode({ shape: 'flow-chart-rect', width: 52, height: 52, angle: 45, attrs: { 'edit-text': { style: { transform: 'rotate(-45deg)' } }, text: { text: '判断节点', transform: 'rotate(-45deg)' } }, ports: { groups: { top: { position: { name: 'top', args: { dx: -26 } } }, right: { position: { name: 'right', args: { dy: -26 } } }, bottom: { position: { name: 'bottom', args: { dx: 26 } } }, left: { position: { name: 'left', args: { dy: 26 } } } } } }) const r4 = graph.createNode({ shape: 'flow-chart-rect', width: 70, height: 70, attrs: { body: { rx: 35, ry: 35 }, text: { text: '链接节点' } } }) // 组合节点 const c1 = graph.createNode({ shape: 'flow-chart-image-rect' }) const c2 = graph.createNode({ shape: 'flow-chart-title-rect' }) const c3 = graph.createNode({ shape: 'flow-chart-animate-text' }) // 节点组 const g1 = graph.createNode({ shape: 'groupNode', attrs: { text: { text: 'Group Name' } }, data: { parent: true } }) // 系统设计图 const imgs = [ { image: getImg('ldb.png') }, { image: getImg('wft1.png') }, { image: getImg('wft2.png') }, { image: getImg('wft3.png') }, { image: getImg('wft4.png') }, { image: getImg('wft5.png') }, { image: getImg('lqt.png') }, { image: getImg('lqb-o.png') }, { image: getImg('ldb-o.png') }, { image: getImg('bh.png') }, { image: getImg('f1.png') }, { image: getImg('f2.png') }, { image: getImg('f3.png') } ] const imgNodes = imgs.map(item => { return graph.createNode({ // shape: 'flow-chart-image-rect-custom', // attrs: { // image: { // 'xlink:href': item.image, // } // } shape: 'image', // 可选值:Rect Circle Ellipse Polygon Polyline Path Image HTML TextBlock BorderedImage EmbeddedImage InscribedImage Cylinder imageUrl: item.image, attrs: { image: { // fill: 'yellow', } }, width: 52, height: 52, ports: { ...customPorts } }) }) this.stencil.load([r1, r2, r3, r4], 'basic') this.stencil.load(imgNodes, 'custom-image') // this.stencil.load([c1, c2, c3], 'combination') // this.stencil.load([g1], 'group') } // 根据json渲染节点和边 static initGraphShape(gd = graphData) { console.log(gd) var data = null data = gd // setInterval(() => { console.log(data.cells[0].position.x) data.cells[0].position.x += 1 this.graph.fromJSON(data) const nodes = this.graph.getNodes() console.log(nodes) // }, 100); } // 连接桩显示时机 static showPorts(ports, show) { for (let i = 0, len = ports.length; i < len; i = i + 1) { ports[i].style.visibility = show ? 'visible' : 'hidden' } } // 事件相关 static initEvent() { const { graph } = this const container = document.getElementById('container') // 右键编辑文本 graph.on('node:contextmenu', ({ cell, view }) => { console.log(view.container) const oldText = cell.attr('text/text') cell.attr('text/style/display', 'none') const elem = view.container.querySelector('.x6-edit-text') if (elem) { elem.innerText = oldText elem.focus() } const onBlur = () => { cell.attr('text/text', elem.innerText) cell.attr('text/style/display', 'inline-block') } if (elem) { elem.addEventListener('blur', () => { onBlur() elem.removeEventListener('blur', onBlur) }) } }) // 鼠标移入 显示连接桩 graph.on('node:mouseenter', FunctionExt.debounce(() => { const ports = container.querySelectorAll('.x6-port-body') this.showPorts(ports, true) }), 500) // 鼠标移出 隐藏连接桩 graph.on('node:mouseleave', () => { const ports = container.querySelectorAll('.x6-port-body') this.showPorts(ports, false) }) graph.on('node:collapse', ({ node, e }) => { e.stopPropagation() node.toggleCollapse() const collapsed = node.isCollapsed() const cells = node.getDescendants() cells.forEach(n => { if (collapsed) { n.hide() } else { n.show() } }) }) // backspace graph.bindKey('delete', () => { const cells = graph.getSelectedCells() if (cells.length) { graph.removeCells(cells) } }) // 鼠标动态添加/删除小工具。 graph.on('edge:mouseenter', ({ cell }) => { /** * EdgeTool * vertices 路径点工具,在路径点位置渲染一个小圆点,拖动小圆点修改路径点位置,双击小圆点删除路径点,在边上单击添加路径点。 * segments 线段工具。在边的每条线段的中心渲染一个工具条,可以拖动工具条调整线段两端的路径点的位置。 * boundary 根据边的包围盒渲染一个包围边的矩形。注意,该工具仅仅渲染一个矩形,不带任何交互。 * button 在指定位置处渲染一个按钮,支持自定义按钮的点击交互。 * button-remove 在指定的位置处,渲染一个删除按钮,点击时删除对应的边。 * source-arrowhead-和-target-arrowhead 在边的起点或终点渲染一个图形(默认是箭头),拖动该图形来修改边的起点或终点。 * edge-editor 提供边上文本编辑功能。 */ cell.addTools([ { name: 'vertices', args: { attrs: { fill: '#007acc' }, // 移动路径点过程中的吸附半径。当路径点与邻近的路径点的某个坐标 (x, y) 距离在半径范围内时,将当前路径点的对应坐标 (x, y) 吸附到邻居路径的路径点。 snapRadius: 20, // 在边上按下鼠标时,是否可以添加新的路径点。 addable: false, // 是否可以通过双击移除路径点。 removable: false, // 是否自动移除冗余的路径点。 removeRedundancies: false, // 是否阻止工具上的鼠标事件冒泡到边视图上。阻止后鼠标与工具交互时将不会触发边的 mousedown、mousemove 和 mouseup 事件。 stopPropagation: false } } ]) }) graph.on('edge:mouseleave', ({ cell }) => { cell.removeTools() }) } }