import { StructureType, MindmapConnectorType, TopicType } from "@/enum"; import { TopicItem } from "@/types"; import { Edge, Graph, Path } from "@antv/x6"; import { getTheme } from "./theme"; import { caclculateX } from "@/utils/hierarchy/rightFishbone"; // 曲线 Graph.registerConnector( "curve-branch-connector", (sourcePoint, targetPoint, routerPoints, options) => { const midX = sourcePoint.x; const ctrX = (targetPoint.x - midX) / 5 + midX; const ctrY = targetPoint.y; const pathData = ` M ${sourcePoint.x} ${sourcePoint.y} Q ${ctrX} ${ctrY} ${targetPoint.x} ${targetPoint.y} `; return options.raw ? Path.parse(pathData) : pathData; }, true ); Graph.registerConnector( "curve-sub-connector", (sourcePoint, targetPoint, routerPoints, options) => { const midX = sourcePoint.x + (sourcePoint.x < targetPoint.x ? 10 : -10); const midY = sourcePoint.y; const ctrX = (targetPoint.x - midX) / 5 + midX; const ctrY = targetPoint.y; const pathData = ` M ${sourcePoint.x} ${sourcePoint.y} L ${midX} ${midY} Q ${ctrX} ${ctrY} ${targetPoint.x} ${targetPoint.y} `; return options.raw ? Path.parse(pathData) : pathData; }, true ); // 直线 Graph.registerConnector( "straight-branch-connector", function (sourcePoint, targetPoint, routerPoints, options) { const halfWidth = this.sourceBBox.width / 2; const midX = sourcePoint.x + (sourcePoint.x < targetPoint.x ? halfWidth + 10 : -halfWidth - 10); const midY = sourcePoint.y; const pathData = ` M ${sourcePoint.x} ${sourcePoint.y} L ${midX} ${midY} L ${targetPoint.x} ${targetPoint.y} `; return options.raw ? Path.parse(pathData) : pathData; }, true ); Graph.registerConnector( "straight-sub-connector", function (sourcePoint, targetPoint, routerPoints, options) { const midX = sourcePoint.x + (sourcePoint.x < targetPoint.x ? 10 : -10); const midY = sourcePoint.y; const pathData = ` M ${sourcePoint.x} ${sourcePoint.y} L ${midX} ${midY} L ${targetPoint.x} ${targetPoint.y} `; return options.raw ? Path.parse(pathData) : pathData; }, true ); // 圆角折线 Graph.registerConnector( "rounded-branch-connector", function (sourcePoint, targetPoint, routerPoints, options) { const halfWidth = this.sourceBBox.width / 2; const midX = sourcePoint.x + (sourcePoint.x < targetPoint.x ? halfWidth + 10 : -halfWidth - 10); const midY = sourcePoint.y; const pathData = ` M ${sourcePoint.x} ${sourcePoint.y} L ${midX} ${midY} L ${midX} ${targetPoint.y + (targetPoint.y < sourcePoint.y ? 5 : targetPoint.y === sourcePoint.y ? 0 : -5)} Q ${midX} ${targetPoint.y} ${midX + (targetPoint.x < sourcePoint.x ? -5 : 5)} ${targetPoint.y} L ${targetPoint.x} ${targetPoint.y} `; return options.raw ? Path.parse(pathData) : pathData; }, true ); Graph.registerConnector( "rounded-sub-connector", (sourcePoint, targetPoint, routerPoints, options) => { const midX = sourcePoint.x < targetPoint.x ? sourcePoint.x + 10 : sourcePoint.x - 10; const midY = sourcePoint.y; // 可能存在误差 const deviationY = Math.abs(targetPoint.y - sourcePoint.y); const pathData = deviationY < 10 ? `M ${sourcePoint.x} ${sourcePoint.y} L ${targetPoint.x} ${targetPoint.y} ` : ` M ${sourcePoint.x} ${sourcePoint.y} L ${midX} ${midY} L ${midX} ${targetPoint.y + (targetPoint.y < sourcePoint.y ? 5 : -5)} Q ${midX} ${targetPoint.y} ${midX + (targetPoint.x < sourcePoint.x ? -5 : 5)} ${targetPoint.y} L ${targetPoint.x} ${targetPoint.y} `; return options.raw ? Path.parse(pathData) : pathData; }, true ); // 折线 Graph.registerConnector( "poly-branch-connector", function (sourcePoint, targetPoint, routerPoints, options) { const halfWidth = this.sourceBBox.width / 2; const midX = sourcePoint.x + (sourcePoint.x < targetPoint.x ? halfWidth + 10 : -halfWidth - 10); const midY = sourcePoint.y; const pathData = ` M ${sourcePoint.x} ${sourcePoint.y} L ${midX} ${midY} L ${midX} ${targetPoint.y} L ${targetPoint.x} ${targetPoint.y} `; return options.raw ? Path.parse(pathData) : pathData; }, true ); Graph.registerConnector( "poly-sub-connector", function (sourcePoint, targetPoint, routerPoints, options) { const midX = sourcePoint.x + (sourcePoint.x < targetPoint.x ? 10 : -10); const midY = sourcePoint.y; const pathData = ` M ${sourcePoint.x} ${sourcePoint.y} L ${midX} ${midY} L ${midX} ${targetPoint.y} L ${targetPoint.x} ${targetPoint.y} `; return options.raw ? Path.parse(pathData) : pathData; }, true ); // 树形 Graph.registerConnector( "tree-branch-connector", function (sourcePoint, targetPoint, routerPoints, options) { const pathData = ` M ${sourcePoint.x} ${sourcePoint.y} L ${sourcePoint.x} ${sourcePoint.y + 10} L ${targetPoint.x} ${sourcePoint.y + 10} L ${targetPoint.x} ${targetPoint.y} `; return options.raw ? Path.parse(pathData) : pathData; }, true ); Graph.registerConnector( "tree-sub-connector", function (sourcePoint, targetPoint, routerPoints, options) { const pathData = ` M ${sourcePoint.x} ${sourcePoint.y} L ${sourcePoint.x} ${targetPoint.y} L ${targetPoint.x} ${targetPoint.y} `; return options.raw ? Path.parse(pathData) : pathData; }, true ); // 组织结构 Graph.registerConnector( "organization-branch-connector", function (sourcePoint, targetPoint, routerPoints, options) { const pathData = ` M ${sourcePoint.x} ${sourcePoint.y} L ${sourcePoint.x} ${sourcePoint.y + 10} L ${targetPoint.x} ${sourcePoint.y + 10} L ${targetPoint.x} ${targetPoint.y} `; return options.raw ? Path.parse(pathData) : pathData; }, true ); Graph.registerConnector( "organization-sub-connector", function (sourcePoint, targetPoint, routerPoints, options) { const pathData = ` M ${sourcePoint.x} ${sourcePoint.y} L ${sourcePoint.x} ${sourcePoint.y + 10} L ${targetPoint.x} ${sourcePoint.y + 10} L ${targetPoint.x} ${targetPoint.y} `; return options.raw ? Path.parse(pathData) : pathData; }, true ); // 括号 Graph.registerConnector( "bracket-branch-connector", function (sourcePoint, targetPoint, routerPoints, options) { const midX = sourcePoint.x + (sourcePoint.x < targetPoint.x ? 20 : -20); const midY = sourcePoint.y; const pathData = ` M ${sourcePoint.x} ${sourcePoint.y} Q ${midX} ${midY} ${midX} ${sourcePoint.y + (sourcePoint.y < targetPoint.y ? 20 : -20)} L ${midX} ${targetPoint.y + (targetPoint.y < sourcePoint.y ? 20 : targetPoint.y === sourcePoint.y ? 0 : -20)} Q ${midX} ${targetPoint.y} ${midX + (targetPoint.x < sourcePoint.x ? -20 : 20)} ${targetPoint.y} `; return options.raw ? Path.parse(pathData) : pathData; }, true ); Graph.registerConnector( "bracket-sub-connector", function (sourcePoint, targetPoint, routerPoints, options) { const midX = sourcePoint.x + (sourcePoint.x < targetPoint.x ? 10 : -10); const midY = sourcePoint.y; const pathData = ` M ${sourcePoint.x} ${sourcePoint.y} Q ${midX} ${midY} ${midX} ${sourcePoint.y + (sourcePoint.y < targetPoint.y ? 10 : -10)} L ${midX} ${targetPoint.y + (targetPoint.y < sourcePoint.y ? 10 : targetPoint.y === sourcePoint.y ? 0 : -10)} Q ${midX} ${targetPoint.y} ${midX + (targetPoint.x < sourcePoint.x ? -10 : 10)} ${targetPoint.y} `; return options.raw ? Path.parse(pathData) : pathData; }, true ); // 一个子项的括号 Graph.registerConnector( "bracket-one-branch-connector", function (sourcePoint, targetPoint, routerPoints, options, edgeView) { const targetCell = edgeView.targetView?.cell; let height = 0; if (targetCell?.isNode()) { height = targetCell.getBBox().height; } const targetTop = targetPoint.y - height / 2 - 3; const targetBottom = targetPoint.y + height / 2 + 3; const offset = 10; const midX = sourcePoint.x + (sourcePoint.x < targetPoint.x ? offset : -offset); const midY = sourcePoint.y; const pathData = ` M ${sourcePoint.x} ${sourcePoint.y} Q ${midX} ${midY} ${midX} ${sourcePoint.y - offset} L ${midX} ${targetTop + offset} Q ${midX} ${targetTop} ${midX + offset} ${targetTop} M ${sourcePoint.x} ${sourcePoint.y} Q ${midX} ${midY} ${midX} ${sourcePoint.y + offset} L ${midX} ${targetBottom - offset} Q ${midX} ${targetBottom} ${midX + offset} ${targetBottom} `; return options.raw ? Path.parse(pathData) : pathData; }, true ); Graph.registerConnector( "bracket-one-sub-connector", function (sourcePoint, targetPoint, routerPoints, options, edgeView) { const targetCell = edgeView.targetView?.cell; let height = 0; if (targetCell?.isNode()) { height = targetCell.getBBox().height; } const targetTop = targetPoint.y - height / 2 + 3; const targetBottom = targetPoint.y + height / 2 - 3; const offset = 5; const midX = sourcePoint.x + (sourcePoint.x < targetPoint.x ? offset : -offset); const midY = sourcePoint.y; const pathData = ` M ${sourcePoint.x} ${sourcePoint.y} Q ${midX} ${midY} ${midX} ${sourcePoint.y - offset} L ${midX} ${targetTop + offset} Q ${midX} ${targetTop} ${midX + offset} ${targetTop} M ${sourcePoint.x} ${sourcePoint.y} Q ${midX} ${midY} ${midX} ${sourcePoint.y + offset} L ${midX} ${targetBottom - offset} Q ${midX} ${targetBottom} ${midX + offset} ${targetBottom} `; return options.raw ? Path.parse(pathData) : pathData; }, true ); // 树形图 Graph.registerConnector( "tree-shape-connector", function (sourcePoint, targetPoint, routerPoints, options) { const pathData = ` M ${sourcePoint.x} ${sourcePoint.y} L ${sourcePoint.x} ${targetPoint.y} L ${targetPoint.x} ${targetPoint.y} `; return options.raw ? Path.parse(pathData) : pathData; }, true ); // 右侧鱼骨图 Graph.registerConnector( "fishbone-branch-right-connector", function (sourcePoint, targetPoint, routerPoints, options, edgeView) { const direction = sourcePoint.y > targetPoint.y ? "up" : "down"; const node = edgeView.targetView?.cell; let targetY = targetPoint.y; if (node?.isNode() && direction === "down") { targetY -= node.size().height; } const midX = caclculateX( { x: targetPoint.x, y: targetY }, sourcePoint.y, "right", direction ); let pathData = ""; if (direction === "up") { pathData = ` M ${targetPoint.x} ${targetPoint.y} L ${midX} ${sourcePoint.y} L ${sourcePoint.x} ${sourcePoint.y} `; } else { pathData = ` M ${targetPoint.x} ${targetY} L ${midX} ${sourcePoint.y} L ${sourcePoint.x} ${sourcePoint.y} `; } return options.raw ? Path.parse(pathData) : pathData; }, true ); Graph.registerConnector( "fishbone-sub-right-connector", function (sourcePoint, targetPoint, routerPoints, options, edgeView) { const sourceNode = edgeView.sourceView?.cell; const direction = sourcePoint.y < targetPoint.y ? "up" : "down"; let midX = caclculateX(sourcePoint, targetPoint.y, "right", direction); if (direction === "down" && sourceNode?.isNode()) { midX = caclculateX( { x: sourcePoint.x, y: sourcePoint.y - sourceNode.size().height }, targetPoint.y, "right", direction ); } let pathData = ""; if (sourceNode?.isNode() && sourceNode.data.type === TopicType.sub) { const size = sourceNode.size(); const sourceP = { x: sourcePoint.x + size.width / 2, y: sourcePoint.y - size.height / 2, }; pathData = ` M ${sourceP.x} ${sourceP.y} L ${sourceP.x + 10} ${sourceP.y} L ${sourceP.x + 10} ${targetPoint.y} L ${targetPoint.x} ${targetPoint.y} `; } else { pathData = ` M ${midX} ${targetPoint.y} L ${targetPoint.x} ${targetPoint.y} `; } return options.raw ? Path.parse(pathData) : pathData; }, true ); // 左侧鱼骨图 Graph.registerConnector( "fishbone-branch-left-connector", function (sourcePoint, targetPoint, routerPoints, options, edgeView) { const direction = sourcePoint.y > targetPoint.y ? "up" : "down"; const node = edgeView.targetView?.cell; let targetY = targetPoint.y; if (node?.isNode() && direction === "down") { targetY -= node.size().height; } const midX = caclculateX( { x: targetPoint.x, y: targetY }, sourcePoint.y, "left", direction ); let pathData = ""; if (direction === "up") { pathData = ` M ${targetPoint.x} ${targetPoint.y} L ${midX} ${sourcePoint.y} L ${sourcePoint.x} ${sourcePoint.y} `; } else { pathData = ` M ${targetPoint.x} ${targetY} L ${midX} ${sourcePoint.y} L ${sourcePoint.x} ${sourcePoint.y} `; } return options.raw ? Path.parse(pathData) : pathData; }, true ); Graph.registerConnector( "fishbone-sub-left-connector", function (sourcePoint, targetPoint, routerPoints, options, edgeView) { const sourceNode = edgeView.sourceView?.cell; let pathData = ""; // 子主题到子主题 if (sourceNode?.isNode() && sourceNode.data.type === TopicType.sub) { const size = sourceNode.size(); const sourceP = { x: sourcePoint.x - size.width, y: sourcePoint.y, }; pathData = ` M ${sourceP.x} ${sourceP.y} L ${sourceP.x - 10} ${sourceP.y} L ${sourceP.x - 10} ${targetPoint.y} L ${targetPoint.x} ${targetPoint.y} `; } else { // 分支子主题到子主题 const direction = sourcePoint.y < targetPoint.y ? "up" : "down"; const size = sourceNode?.isNode() ? sourceNode.size() : { width: 0, height: 0 }; let midX = caclculateX( { x: sourcePoint.x - size.width / 2, y: sourcePoint.y + size.height / 2, }, targetPoint.y, "left", direction ); if (direction === "down") { midX = caclculateX( { x: sourcePoint.x - size.width / 2, y: sourcePoint.y - size.height / 2, }, targetPoint.y, "left", direction ); } pathData = ` M ${midX} ${targetPoint.y} L ${targetPoint.x} ${targetPoint.y} `; } return options.raw ? Path.parse(pathData) : pathData; }, true ); const getConnector = ( structure: StructureType, theme: string, type: TopicType, options?: Record ) => { // 树状结构图 if (structure === StructureType.tree) { return `tree-${type}-connector`; } // 组织结构图 if (structure === StructureType.organization) { return `organization-${type}-connector`; } // 时间图 if ( [ StructureType.upwardTime, StructureType.downwardTime, StructureType.horizontalTime, ].includes(structure) ) { return `poly-${type}-connector`; } // 括号图 if ( [StructureType.leftBracket, StructureType.rightBracket].includes(structure) ) { if (options?.onlyOneChild) { return `bracket-one-${type}-connector`; } return `bracket-${type}-connector`; } // 树形图 if ( [ StructureType.leftTreeShape, StructureType.rightTreeShape, StructureType.treeShape, ].includes(structure) ) { return `tree-shape-connector`; } // 鱼骨 if (structure === StructureType.leftFishbone) { return `fishbone-${type}-left-connector`; } if (structure === StructureType.rightFishbone) { return `fishbone-${type}-right-connector`; } const themeObj = getTheme(theme); if (type === TopicType.branch) { return `${themeObj.branchConnector}-${type}-connector`; } else { return `${themeObj.subConnector}-${type}-connector`; } }; const getSourceAnchor = ( type: TopicType, structure: StructureType, options?: Record ) => { switch (structure) { case StructureType.left: { return type === TopicType.branch ? { name: "center", } : { name: "left", }; } case StructureType.right: { return type === TopicType.branch ? { name: "center", } : { name: "right", }; } case StructureType.leftRight: { return type === TopicType.branch ? "center" : options?.direction === "left" ? "left" : "right"; } case StructureType.tree: { return type === TopicType.branch ? { name: "bottom", args: { dy: -5, }, } : { name: "bottomLeft", args: { dx: 10, }, }; } case StructureType.organization: { return { name: "bottom", }; } case StructureType.leftBracket: { return { name: "left", args: { dx: -10, }, }; } case StructureType.rightBracket: { return { name: "right", args: { dx: 10, }, }; } case StructureType.upwardTime: case StructureType.downwardTime: case StructureType.horizontalTime: { return { name: "center", }; } case StructureType.leftFishbone: { return { name: type === TopicType.branch ? "left" : "right", }; } case StructureType.rightFishbone: return { name: type === TopicType.branch ? "right" : "bottom", }; case StructureType.leftTreeShape: case StructureType.rightTreeShape: case StructureType.treeShape: { return "bottom"; } } return { name: "center", }; }; const getTargetAnchor = ( type: TopicType, structure: StructureType, options?: Record ) => { switch (structure) { case StructureType.left: { return { name: "right", }; } case StructureType.right: { return { name: "left", }; } case StructureType.leftRight: { return options?.direction === "left" ? "right" : "left"; } case StructureType.tree: { return type === TopicType.branch ? { name: "top", } : { name: "left", }; } case StructureType.organization: { return { name: "top", }; } case StructureType.leftBracket: { return { name: "right", args: { dx: 10, }, }; } case StructureType.rightBracket: { return { name: "left", args: { dx: 10, }, }; } case StructureType.upwardTime: case StructureType.downwardTime: case StructureType.horizontalTime: { return { name: "left", }; } case StructureType.leftFishbone: { return { name: type === TopicType.branch ? "bottom" : "right", }; } case StructureType.rightFishbone: return { name: type === TopicType.branch ? "bottom" : "left", }; case StructureType.leftTreeShape: case StructureType.rightTreeShape: case StructureType.treeShape: { return "left"; } } return { name: "center", args: { dx: "25%", }, }; }; /** * 创建连线 * @param graph * @param sourceId * @param item * @param structure * @param theme * @returns */ export const createEdge = ( graph: Graph, sourceId: string, item: { id: string; data: TopicItem }, structure: StructureType, theme: string, options: Record = {} ): Edge => { return graph.createEdge({ id: item.id + "-edge", inherit: "edge", connector: { name: getConnector(structure, theme, item.data.type, options), }, zIndex: 0, source: { cell: sourceId, anchor: getSourceAnchor(item.data.type, structure, options), }, target: { cell: item.id, anchor: getTargetAnchor(item.data.type, structure, options), }, attrs: { line: { targetMarker: "", stroke: item.data.edge?.color || "#A2B1C3", strokeWidth: item.data?.edge?.width || 3, }, }, }); };