UNPKG

@antv/x6

Version:

JavaScript diagramming library that uses SVG and HTML for rendering

156 lines (138 loc) 4.67 kB
import { toRad, Line, Point, type PointLike } from '../../geometry' import type { RouterDefinition } from './index' export interface LoopRouterOptions { width?: number height?: number angle?: 'auto' | number merge?: boolean | number } function rollup(points: PointLike[], merge?: boolean | number) { if (merge != null && merge !== false) { const amount = typeof merge === 'boolean' ? 0 : merge if (amount > 0) { const center1 = Point.create(points[1]).move(points[2], amount) const center2 = Point.create(points[1]).move(points[0], amount) return [center1.toJSON(), ...points, center2.toJSON()] } { const center = points[1] return [{ ...center }, ...points, { ...center }] } } return points } export const loop: RouterDefinition<LoopRouterOptions> = ( vertices, options, edgeView, ) => { const width = options.width || 50 const height = options.height || 80 const halfHeight = height / 2 const angle = options.angle || 'auto' const sourceAnchor = edgeView.sourceAnchor const targetAnchor = edgeView.targetAnchor const sourceBBox = edgeView.sourceBBox const targetBBox = edgeView.targetBBox if (sourceAnchor.equals(targetAnchor)) { const getVertices = (angle: number) => { const rad = toRad(angle) const sin = Math.sin(rad) const cos = Math.cos(rad) const center = new Point( sourceAnchor.x + cos * width, sourceAnchor.y + sin * width, ) const ref = new Point( center.x - cos * halfHeight, center.y - sin * halfHeight, ) const p1 = ref.clone().rotate(-90, center) const p2 = ref.clone().rotate(90, center) return [p1.toJSON(), center.toJSON(), p2.toJSON()] } const validate = (end: PointLike) => { const start = sourceAnchor.clone().move(end, -1) const line = new Line(start, end) return ( !sourceBBox.containsPoint(end) && !sourceBBox.intersectsWithLine(line) ) } const angles = [0, 90, 180, 270, 45, 135, 225, 315] if (typeof angle === 'number') { return rollup(getVertices(angle), options.merge) } const center = sourceBBox.getCenter() if (center.equals(sourceAnchor)) { return rollup(getVertices(0), options.merge) } const deg = center.angleBetween( sourceAnchor, center.clone().translate(1, 0), ) let ret = getVertices(deg) if (validate(ret[1])) { return rollup(ret, options.merge) } // return the best vertices for (let i = 1, l = angles.length; i < l; i += 1) { ret = getVertices(deg + angles[i]) if (validate(ret[1])) { return rollup(ret, options.merge) } } return rollup(ret, options.merge) } { const line = new Line(sourceAnchor, targetAnchor) let parallel = line.parallel(-width) let center = parallel.getCenter() let p1 = parallel.start.clone().move(parallel.end, halfHeight) let p2 = parallel.end.clone().move(parallel.start, halfHeight) const ref = line.parallel(-1) const line1 = new Line(ref.start, center) const line2 = new Line(ref.end, center) if ( sourceBBox.containsPoint(center) || targetBBox.containsPoint(center) || sourceBBox.intersectsWithLine(line1) || sourceBBox.intersectsWithLine(line2) || targetBBox.intersectsWithLine(line1) || targetBBox.intersectsWithLine(line2) ) { parallel = line.parallel(width) center = parallel.getCenter() p1 = parallel.start.clone().move(parallel.end, halfHeight) p2 = parallel.end.clone().move(parallel.start, halfHeight) } if (options.merge) { const line = new Line(sourceAnchor, targetAnchor) const normal = new Line(center, line.center).setLength( Number.MAX_SAFE_INTEGER, ) const intersects1 = sourceBBox.intersectsWithLine(normal) const intersects2 = targetBBox.intersectsWithLine(normal) const intersects = intersects1 ? Array.isArray(intersects1) ? intersects1 : [intersects1] : [] if (intersects2) { if (Array.isArray(intersects2)) { intersects.push(...intersects2) } else { intersects.push(intersects2) } } const anchor = line.center.closest(intersects) if (anchor) { edgeView.sourceAnchor = anchor.clone() edgeView.targetAnchor = anchor.clone() } else { edgeView.sourceAnchor = line.center.clone() edgeView.targetAnchor = line.center.clone() } } return rollup([p1.toJSON(), center.toJSON(), p2.toJSON()], options.merge) } }