/* eslint-disable standard/array-bracket-even-spacing */ import editorStyle from '../util/defaultStyle' const uniqBy = (arr, key) => { const result = [] arr.forEach(i => { if (!result.find(r => r[key] === i[key])) { result.push(i) } }) return result } export default function(G6) { G6.registerEdge('flow-polyline-round', { options: { style: { ...editorStyle.edgeStyle }, stateStyles: { selected: { lineWidth: editorStyle.edgeSelectedStyle.lineWidth, stroke: editorStyle.edgeSelectedStyle.stroke }, hover: { stroke: editorStyle.edgeActivedStyle.stroke } } }, setState(name, value, item) { const group = item.getContainer() const path = group.getChildByIndex(0) if (name === 'selected') { if (value) { path.attr('lineWidth', this.options.stateStyles.selected.lineWidth) path.attr('stroke', this.options.stateStyles.selected.stroke) // path.attr('stroke', this.options.style.stroke) } else { path.attr('lineWidth', this.options.style.lineWidth) path.attr('stroke', this.options.style.stroke) } } else if (name === 'hover') { if (value) { path.attr('stroke', this.options.stateStyles.hover.stroke) } else { path.attr('stroke', this.options.style.stroke) } } }, drawShape(cfg, group) { this.group = group const shapeStyle = this.getShapeStyle(cfg) const shape = group.addShape('path', { className: 'edge-shape', attrs: shapeStyle }) return shape }, drawLabel(cfg, group) { const labelCfg = cfg.labelCfg || {} const labelStyle = this.getLabelStyle(cfg, labelCfg, group) const label = group.addShape('text', { attrs: labelStyle }) const labelBBox = label.getBBox() group.addShape('rect', { className: 'edge-labelRect', attrs: { x: labelBBox.x - editorStyle.edgeLabelRectPadding / 2, y: labelBBox.y - editorStyle.edgeLabelRectPadding / 2, width: labelBBox.width + editorStyle.edgeLabelRectPadding, height: labelBBox.height + editorStyle.edgeLabelRectPadding, fill: '#fff', stroke: '#fff' } }) group.toBack() label.toFront() return label }, afterUpdate(cfg, item) { const label = item.getContainer().findByClassName('edge-label') const labelRect = item.getContainer().findByClassName('edge-labelRect') if (label) { const labelBBox = label.getBBox() labelRect.attr({ x: labelBBox.x - editorStyle.edgeLabelRectPadding / 2, y: labelBBox.y - editorStyle.edgeLabelRectPadding / 2, width: labelBBox.width + editorStyle.edgeLabelRectPadding, height: labelBBox.height + editorStyle.edgeLabelRectPadding }) } }, getShapeStyle(cfg) { cfg = this.getPathPoints(cfg) const startPoint = cfg.startPoint const endPoint = cfg.endPoint const controlPoints = this.getControlPoints(cfg) let points = [startPoint] if (controlPoints) { points = points.concat(controlPoints) } points.push(endPoint) const path = this.getPath(points) let style = this.options.style if (cfg.reverse) { style = { ...style, lineDash: [1, 3] } } else { style = { ...style, lineDash: null } } return { path, ...style, endArrow: { path: 'M 0,0 L -10,-4 S -8 0,-10 4 Z' } } }, getPath(points) { const path = [] for (let i = 0; i < points.length; i++) { const point = points[i] if (i === 0) { path.push(['M', point.x, point.y]) } else if (i === points.length - 1) { path.push(['L', point.x, point.y]) } else { const prevPoint = points[i - 1] const nextPoint = points[i + 1] let cornerLen = 5 if (Math.abs(point.y - prevPoint.y) > cornerLen || Math.abs(point.x - prevPoint.x) > cornerLen) { if (prevPoint.x === point.x) { path.push(['L', point.x, point.y > prevPoint.y ? point.y - cornerLen : point.y + cornerLen]) } else if (prevPoint.y === point.y) { path.push(['L', point.x > prevPoint.x ? point.x - cornerLen : point.x + cornerLen, point.y]) } } const yLen = Math.abs(point.y - nextPoint.y) const xLen = Math.abs(point.x - nextPoint.x) if (yLen > 0 && yLen < cornerLen) { cornerLen = yLen } else if (xLen > 0 && xLen < cornerLen) { cornerLen = xLen } if (prevPoint.x !== nextPoint.x && nextPoint.x === point.x) { path.push(['Q', point.x, point.y, point.x, point.y > nextPoint.y ? point.y - cornerLen : point.y + cornerLen]) } else if (prevPoint.y !== nextPoint.y && nextPoint.y === point.y) { path.push(['Q', point.x, point.y, point.x > nextPoint.x ? point.x - cornerLen : point.x + cornerLen, point.y]) } } } return path }, getControlPoints(cfg) { if (!cfg.sourceNode) { return cfg.controlPoints } return this.polylineFinding(cfg.sourceNode, cfg.targetNode, cfg.startPoint, cfg.endPoint, 15) }, getExpandedBBox(bbox, offset) { return bbox.width === 0 && bbox.height === 0 ? bbox : { centerX: bbox.centerX, centerY: bbox.centerY, minX: bbox.minX - offset, minY: bbox.minY - offset, maxX: bbox.maxX + offset, maxY: bbox.maxY + offset, height: bbox.height + 2 * offset, width: bbox.width + 2 * offset } }, getExpandedPort(bbox, point) { return Math.abs(point.x - bbox.centerX) / bbox.width > Math.abs(point.y - bbox.centerY) / bbox.height ? { x: point.x > bbox.centerX ? bbox.maxX : bbox.minX, y: point.y } : { x: point.x, y: point.y > bbox.centerY ? bbox.maxY : bbox.minY } }, combineBBoxes(sBBox, tBBox) { const minX = Math.min(sBBox.minX, tBBox.minX); const minY = Math.min(sBBox.minY, tBBox.minY) const maxX = Math.max(sBBox.maxX, tBBox.maxX); const maxY = Math.max(sBBox.maxY, tBBox.maxY) return { centerX: (minX + maxX) / 2, centerY: (minY + maxY) / 2, minX: minX, minY: minY, maxX: maxX, maxY: maxY, height: maxY - minY, width: maxX - minX } }, getBBoxFromVertexes(sPoint, tPoint) { const minX = Math.min(sPoint.x, tPoint.x); const maxX = Math.max(sPoint.x, tPoint.x) const minY = Math.min(sPoint.y, tPoint.y); const maxY = Math.max(sPoint.y, tPoint.y) return { centerX: (minX + maxX) / 2, centerY: (minY + maxY) / 2, maxX: maxX, maxY: maxY, minX: minX, minY: minY, height: maxY - minY, width: maxX - minX } }, vertexOfBBox(bbox) { return [{ x: bbox.minX, y: bbox.minY }, { x: bbox.maxX, y: bbox.minY }, { x: bbox.maxX, y: bbox.maxY }, { x: bbox.minX, y: bbox.maxY }] }, crossPointsByLineAndBBox(bbox, centerPoint) { let crossPoints = [] if (!(centerPoint.x < bbox.minX || centerPoint.x > bbox.maxX)) { crossPoints = crossPoints.concat([{ x: centerPoint.x, y: bbox.minY }, { x: centerPoint.x, y: bbox.maxY }]) } if (!(centerPoint.y < bbox.minY || centerPoint.y > bbox.maxY)) { crossPoints = crossPoints.concat([{ x: bbox.minX, y: centerPoint.y }, { x: bbox.maxX, y: centerPoint.y }]) } return crossPoints }, getConnectablePoints(sBBox, tBBox, sPoint, tPoint) { const lineBBox = this.getBBoxFromVertexes(sPoint, tPoint) const outerBBox = this.combineBBoxes(sBBox, tBBox) const sLineBBox = this.combineBBoxes(sBBox, lineBBox) const tLineBBox = this.combineBBoxes(tBBox, lineBBox) let points = [] points = points.concat(this.vertexOfBBox(sLineBBox), this.vertexOfBBox(tLineBBox), this.vertexOfBBox(outerBBox)) const centerPoint = { x: outerBBox.centerX, y: outerBBox.centerY }; [outerBBox, sLineBBox, tLineBBox, lineBBox].forEach(bbox => { points = points.concat(this.crossPointsByLineAndBBox(bbox, centerPoint)) }) points.push({ x: sPoint.x, y: tPoint.y }) points.push({ x: tPoint.x, y: sPoint.y }) return points }, filterConnectablePoints(points, bbox) { return points.filter(point => point.x <= bbox.minX || point.x >= bbox.maxX || point.y <= bbox.minY || point.y >= bbox.maxY) }, AStar(points, sPoint, tPoint, sBBox, tBBox) { const openList = [sPoint] const closeList = [] points = uniqBy(this.fillId(points), 'id') points.push(tPoint) let endPoint while (openList.length > 0) { let minCostPoint openList.forEach((p, i) => { if (!p.parent) { p.f = 0 } if (!minCostPoint) { minCostPoint = p } if (p.f < minCostPoint.f) { minCostPoint = p } }) if (minCostPoint.x === tPoint.x && minCostPoint.y === tPoint.y) { endPoint = minCostPoint break } openList.splice(openList.findIndex(o => o.x === minCostPoint.x && o.y === minCostPoint.y), 1) closeList.push(minCostPoint) const neighbor = points.filter(p => (p.x === minCostPoint.x || p.y === minCostPoint.y) && !(p.x === minCostPoint.x && p.y === minCostPoint.y) && !this.crossBBox([sBBox, tBBox], minCostPoint, p)) neighbor.forEach(p => { const inOpen = openList.find(o => o.x === p.x && o.y === p.y) const currentG = this.getCost(p, minCostPoint) // eslint-disable-next-line no-empty if (closeList.find(o => o.x === p.x && o.y === p.y)) { } else if (inOpen) { if (p.g > currentG) { p.parent = minCostPoint p.g = currentG p.f = p.g + p.h } } else { p.parent = minCostPoint p.g = currentG let h = this.getCost(p, tPoint) if (this.crossBBox([tBBox], p, tPoint)) { h += (tBBox.width / 2 + tBBox.height / 2) // 如果穿过bbox则增加该点的预估代价为bbox周长的一半 } p.h = h p.f = p.g + p.h openList.push(p) } }) } if (endPoint) { const result = [] result.push({ x: endPoint.x, y: endPoint.y }) while (endPoint.parent) { endPoint = endPoint.parent result.push({ x: endPoint.x, y: endPoint.y }) } return result.reverse() } return [] }, crossBBox(bboxes, p1, p2) { for (let i = 0; i < bboxes.length; i++) { const bbox = bboxes[i] if (p1.x === p2.x && bbox.minX < p1.x && bbox.maxX > p1.x) { if ((p1.y < bbox.maxY && p2.y >= bbox.maxY) || (p2.y < bbox.maxY && p1.y >= bbox.maxY)) { return true } } else if (p1.y === p2.y && bbox.minY < p1.y && bbox.maxY > p1.y) { if ((p1.x < bbox.maxX && p2.x >= bbox.maxX) || (p2.x < bbox.maxX && p1.x >= bbox.maxX)) { return true } } } return false }, getCost(p1, p2) { return Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y) }, getPointBBox(t) { return { centerX: t.x, centerY: t.y, minX: t.x, minY: t.y, maxX: t.x, maxY: t.y, height: 0, width: 0 } }, fillId(points) { points.forEach(p => { p.id = p.x + '-' + p.y }) return points }, polylineFinding(sNode, tNode, sPort, tPort, offset) { const sourceBBox = sNode && sNode.getBBox() ? sNode.getBBox() : this.getPointBBox(sPort) const targetBBox = tNode && tNode.getBBox() ? tNode.getBBox() : this.getPointBBox(tPort) const sBBox = this.getExpandedBBox(sourceBBox, offset) const tBBox = this.getExpandedBBox(targetBBox, offset) const sPoint = this.getExpandedPort(sBBox, sPort) const tPoint = this.getExpandedPort(tBBox, tPort) let points = this.getConnectablePoints(sBBox, tBBox, sPoint, tPoint) points = this.filterConnectablePoints(points, sBBox) points = this.filterConnectablePoints(points, tBBox) const polylinePoints = this.AStar(points, sPoint, tPoint, sBBox, tBBox) return polylinePoints } }, 'polyline') }