// import { Addon, FunctionExt, Graph, Shape } from "@antv/x6"; import { 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: { 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(); }); } }