UNPKG

@logicflow/extension

Version:
234 lines (214 loc) 6.46 kB
/** * 路径插件,此插件支持获取绘制的图中所有的路径。 * 需要指定开始节点类型。 */ import LogicFlow from '@logicflow/core' import { getBpmnId } from '../../bpmn/getBpmnId' type Path = { routeId: string name: string elements: string[] type: number } type RawPath = Path & { similarElement: RawPath weight: number } type PathNodeData = { id: string data: any nextNodes: string[] } export class FlowPath { lf: LogicFlow paths: RawPath[] startNodeType?: string static pluginName = 'flowPath' constructor({ lf }) { this.lf = lf this.paths = [] // 给lf添加方法 lf.getPathes = () => { if (!this.startNodeType) { throw new Error('需要预先指定开始节点类型') } return this.getPathes() } lf.setRawPaths = (paths: Path[]) => { this.setPathes(paths) } lf.getRawPathes = () => this.paths lf.setStartNodeType = (type: string) => { this.startNodeType = type } } setPathes(paths) { this.paths = paths.map(({ routeId, name, elements, type }) => ({ routeId, name, elements, type, similarElement: null, weight: 0, })) } getPathes() { const graphData = this.lf.getGraphRawData() const nodesMap: Map<string, PathNodeData> = new Map() const startNodeIds: string[] = [] graphData.nodes.forEach((node) => { nodesMap.set(node.id, { id: node.id, data: node, nextNodes: [], }) if (node.type === this.startNodeType) { startNodeIds.push(node.id) } }) graphData.edges.forEach((edge) => { const node = nodesMap.get(edge.sourceNodeId) node?.nextNodes.push(edge.targetNodeId) }) let pathElements: string[][] = [] startNodeIds.forEach((id) => { const node = nodesMap.get(id) if (node) { pathElements = pathElements.concat( this.findPathElements(node, nodesMap, []), ) } }) return this.getNewPathes(pathElements) } private findPathElements( node: PathNodeData, nodesMap: Map<string, PathNodeData>, elements: string[] = [], ) { const newPaths: string[] = [...elements] newPaths.push(node.id) if (node.nextNodes.length === 0) { return [newPaths] } let subPath = [] for (let i = 0; i < node.nextNodes.length; i++) { const n = nodesMap.get(node.nextNodes[i]) let p // 循环路径 if (n) { const idx = newPaths.indexOf(n.id) if (idx !== -1) { p = [[...newPaths.slice(idx), n.id]] } else { p = this.findPathElements(n, nodesMap, [...newPaths]) } subPath = subPath.concat(p) } } return subPath } /** * 设置路径id * 如果存在原始路径Id, 则需要比较新路径是否在原始路径中存在相似路径 * 如果有,则尽量使用原始路径。 * 相似路径 * 1. 如果所有的节点都相同,则必定为同一路径。(包括顺序不同) * 2. 如果新路径比原来路径少了或者多了部分节点,则记录为相似路径。基于不同的差异,标记不同的权重。 * 3. 基于新路径在旧路径占用权限,设置新路径Id. * 4. 如果某一条旧路径被多条新路径标记为相同的权重,则这两条新路径都使用新Id. */ private getNewPathes(pathElements) { const paths: any = [] // 由于循环路径不包括开始,所以存在重复的情况,此处去重。 const LoopSet = new Set() pathElements.forEach((elements) => { const routeId = this.getNewId('path') const name = this.getNewId('路径') const isLoop = this.isLoopPath(elements) const elementStr = elements.join(',') if (!LoopSet.has(elementStr)) { LoopSet.add(elementStr) paths.push({ routeId, name, elements, type: isLoop, weight: 0, similarElement: '', }) } }) const oldPaths = JSON.parse(JSON.stringify(this.paths)) // 1) 找到所有路径最相似的路径, 给旧路径标记其最接近的路径 paths.forEach((newPath) => { for (let i = 0; i < oldPaths.length; i++) { const oldPath = oldPaths[i] const weight = this.similar2Path( [...newPath.elements], [...oldPath.elements], ) if (weight > newPath.weight && oldPath.weight <= weight) { newPath.weight = weight newPath.similarElement = oldPath if (weight === oldPath.weight && oldPath.similarElement) { // 特殊处理,如果两个路径都与同一条旧路径有相似的权重,则这两个路径的相似路径都置空 oldPath.similarElement.similarElement = null oldPath.similarElement.weight = 0 oldPath.similarElement = null oldPath.weight = 0 } else { oldPath.similarElement = newPath oldPath.weight = weight } } } }) // 2) 再次遍历所有路径,如果该路径的相似路径对应的相似路径是自己,那么就设置该路径id和name为其相似路径。 paths.forEach((newPath) => { if ( newPath.similarElement && newPath.similarElement.similarElement === newPath ) { newPath.routeId = newPath.similarElement.routeId newPath.name = newPath.similarElement.name } delete newPath.similarElement delete newPath.weight }) this.setPathes(paths) return paths } private similar2Path(x, y) { let z = 0 const s = x.length + y.length x.sort() y.sort() let a = x.shift() let b = y.shift() while (a !== undefined && b !== undefined) { if (a === b) { z++ a = x.shift() b = y.shift() } else if (a < b) { a = x.shift() } else if (a > b) { b = y.shift() } } return (z / s) * 200 } private getNewId(prefix) { return `${prefix}_${getBpmnId()}` } /** * 判断是否为循环路径 * 由于前面进行了特殊处理,将循环部分单独提出来作为路径 * 所有循环路径必定开始节点等于结束节点。 */ private isLoopPath(elements) { const { length } = elements return elements.indexOf(elements[length - 1]) !== length - 1 ? 1 : 0 } }