"use strict"; /* eslint-disable no-underscore-dangle */ Object.defineProperty(exports, "__esModule", { value: true }); exports.jumpover = void 0; const x6_geometry_1 = require("@antv/x6-geometry"); // takes care of math. error for case when jump is too close to end of line const CLOSE_PROXIMITY_PADDING = 1; const F13 = 1 / 3; const F23 = 2 / 3; function setupUpdating(view) { let updateList = view.graph._jumpOverUpdateList; // first time setup for this paper if (updateList == null) { updateList = view.graph._jumpOverUpdateList = []; view.graph.on('cell:mouseup', () => { const list = view.graph._jumpOverUpdateList; // add timeout to wait for the target node to be connected // fix https://github.com/antvis/X6/issues/3387 setTimeout(() => { for (let i = 0; i < list.length; i += 1) { list[i].update(); } }); }); view.graph.on('model:reseted', () => { updateList = view.graph._jumpOverUpdateList = []; }); } // add this link to a list so it can be updated when some other link is updated if (updateList.indexOf(view) < 0) { updateList.push(view); // watch for change of connector type or removal of link itself // to remove the link from a list of jump over connectors const clean = () => updateList.splice(updateList.indexOf(view), 1); view.cell.once('change:connector', clean); view.cell.once('removed', clean); } } function createLines(sourcePoint, targetPoint, route = []) { const points = [sourcePoint, ...route, targetPoint]; const lines = []; points.forEach((point, idx) => { const next = points[idx + 1]; if (next != null) { lines.push(new x6_geometry_1.Line(point, next)); } }); return lines; } function findLineIntersections(line, crossCheckLines) { const intersections = []; crossCheckLines.forEach((crossCheckLine) => { const intersection = line.intersectsWithLine(crossCheckLine); if (intersection) { intersections.push(intersection); } }); return intersections; } function getDistence(p1, p2) { return new x6_geometry_1.Line(p1, p2).squaredLength(); } /** * Split input line into multiple based on intersection points. */ function createJumps(line, intersections, jumpSize) { return intersections.reduce((memo, point, idx) => { // skipping points that were merged with the previous line // to make bigger arc over multiple lines that are close to each other if (skippedPoints.includes(point)) { return memo; } // always grab the last line from buffer and modify it const lastLine = memo.pop() || line; // calculate start and end of jump by moving by a given size of jump const jumpStart = x6_geometry_1.Point.create(point).move(lastLine.start, -jumpSize); let jumpEnd = x6_geometry_1.Point.create(point).move(lastLine.start, +jumpSize); // now try to look at the next intersection point const nextPoint = intersections[idx + 1]; if (nextPoint != null) { const distance = jumpEnd.distance(nextPoint); if (distance <= jumpSize) { // next point is close enough, move the jump end by this // difference and mark the next point to be skipped jumpEnd = nextPoint.move(lastLine.start, distance); skippedPoints.push(nextPoint); } } else { // this block is inside of `else` as an optimization so the distance is // not calculated when we know there are no other intersection points const endDistance = jumpStart.distance(lastLine.end); // if the end is too close to possible jump, draw remaining line instead of a jump if (endDistance < jumpSize * 2 + CLOSE_PROXIMITY_PADDING) { memo.push(lastLine); return memo; } } const startDistance = jumpEnd.distance(lastLine.start); if (startDistance < jumpSize * 2 + CLOSE_PROXIMITY_PADDING) { // if the start of line is too close to jump, draw that line instead of a jump memo.push(lastLine); return memo; } // finally create a jump line const jumpLine = new x6_geometry_1.Line(jumpStart, jumpEnd); // it's just simple line but with a `isJump` property jumppedLines.push(jumpLine); memo.push(new x6_geometry_1.Line(lastLine.start, jumpStart), jumpLine, new x6_geometry_1.Line(jumpEnd, lastLine.end)); return memo; }, []); } function buildPath(lines, jumpSize, jumpType, radius) { const path = new x6_geometry_1.Path(); let segment; // first move to the start of a first line segment = x6_geometry_1.Path.createSegment('M', lines[0].start); path.appendSegment(segment); lines.forEach((line, index) => { if (jumppedLines.includes(line)) { let angle; let diff; let control1; let control2; if (jumpType === 'arc') { // approximates semicircle with 2 curves angle = -90; // determine rotation of arc based on difference between points diff = line.start.diff(line.end); // make sure the arc always points up (or right) const xAxisRotate = diff.x < 0 || (diff.x === 0 && diff.y < 0); if (xAxisRotate) { angle += 180; } const center = line.getCenter(); const centerLine = new x6_geometry_1.Line(center, line.end).rotate(angle, center); let halfLine; // first half halfLine = new x6_geometry_1.Line(line.start, center); control1 = halfLine.pointAt(2 / 3).rotate(angle, line.start); control2 = centerLine.pointAt(1 / 3).rotate(-angle, centerLine.end); segment = x6_geometry_1.Path.createSegment('C', control1, control2, centerLine.end); path.appendSegment(segment); // second half halfLine = new x6_geometry_1.Line(center, line.end); control1 = centerLine.pointAt(1 / 3).rotate(angle, centerLine.end); control2 = halfLine.pointAt(1 / 3).rotate(-angle, line.end); segment = x6_geometry_1.Path.createSegment('C', control1, control2, line.end); path.appendSegment(segment); } else if (jumpType === 'gap') { segment = x6_geometry_1.Path.createSegment('M', line.end); path.appendSegment(segment); } else if (jumpType === 'cubic') { // approximates semicircle with 1 curve angle = line.start.theta(line.end); const xOffset = jumpSize * 0.6; let yOffset = jumpSize * 1.35; // determine rotation of arc based on difference between points diff = line.start.diff(line.end); // make sure the arc always points up (or right) const xAxisRotate = diff.x < 0 || (diff.x === 0 && diff.y < 0); if (xAxisRotate) { yOffset *= -1; } control1 = new x6_geometry_1.Point(line.start.x + xOffset, line.start.y + yOffset).rotate(angle, line.start); control2 = new x6_geometry_1.Point(line.end.x - xOffset, line.end.y + yOffset).rotate(angle, line.end); segment = x6_geometry_1.Path.createSegment('C', control1, control2, line.end); path.appendSegment(segment); } } else { const nextLine = lines[index + 1]; if (radius === 0 || !nextLine || jumppedLines.includes(nextLine)) { segment = x6_geometry_1.Path.createSegment('L', line.end); path.appendSegment(segment); } else { buildRoundedSegment(radius, path, line.end, line.start, nextLine.end); } } }); return path; } function buildRoundedSegment(offset, path, curr, prev, next) { const prevDistance = curr.distance(prev) / 2; const nextDistance = curr.distance(next) / 2; const startMove = -Math.min(offset, prevDistance); const endMove = -Math.min(offset, nextDistance); const roundedStart = curr.clone().move(prev, startMove).round(); const roundedEnd = curr.clone().move(next, endMove).round(); const control1 = new x6_geometry_1.Point(F13 * roundedStart.x + F23 * curr.x, F23 * curr.y + F13 * roundedStart.y); const control2 = new x6_geometry_1.Point(F13 * roundedEnd.x + F23 * curr.x, F23 * curr.y + F13 * roundedEnd.y); let segment; segment = x6_geometry_1.Path.createSegment('L', roundedStart); path.appendSegment(segment); segment = x6_geometry_1.Path.createSegment('C', control1, control2, roundedEnd); path.appendSegment(segment); } let jumppedLines; let skippedPoints; const jumpover = function (sourcePoint, targetPoint, routePoints, options = {}) { jumppedLines = []; skippedPoints = []; setupUpdating(this); const jumpSize = options.size || 5; const jumpType = options.type || 'arc'; const radius = options.radius || 0; // list of connector types not to jump over. const ignoreConnectors = options.ignoreConnectors || ['smooth']; const graph = this.graph; const model = graph.model; const allLinks = model.getEdges(); // there is just one link, draw it directly if (allLinks.length === 1) { return buildPath(createLines(sourcePoint, targetPoint, routePoints), jumpSize, jumpType, radius); } const edge = this.cell; const thisIndex = allLinks.indexOf(edge); const defaultConnector = graph.options.connecting.connector || {}; // not all links are meant to be jumped over. const edges = allLinks.filter((link, idx) => { const connector = link.getConnector() || defaultConnector; // avoid jumping over links with connector type listed in `ignored connectors`. if (ignoreConnectors.includes(connector.name)) { return false; } // filter out links that are above this one and have the same connector type // otherwise there would double hoops for each intersection if (idx > thisIndex) { return connector.name !== 'jumpover'; } return true; }); // find views for all links const linkViews = edges.map((edge) => { return graph.findViewByCell(edge); }); // create lines for this link const thisLines = createLines(sourcePoint, targetPoint, routePoints); // create lines for all other links const linkLines = linkViews.map((linkView) => { if (linkView == null) { return []; } if (linkView === this) { return thisLines; } return createLines(linkView.sourcePoint, linkView.targetPoint, linkView.routePoints); }); // transform lines for this link by splitting with jump lines at // points of intersection with other links const jumpingLines = []; thisLines.forEach((line) => { // iterate all links and grab the intersections with this line // these are then sorted by distance so the line can be split more easily const intersections = edges .reduce((memo, link, i) => { // don't intersection with itself if (link !== edge) { const lineIntersections = findLineIntersections(line, linkLines[i]); memo.push(...lineIntersections); } return memo; }, []) .sort((a, b) => getDistence(line.start, a) - getDistence(line.start, b)); if (intersections.length > 0) { // split the line based on found intersection points jumpingLines.push(...createJumps(line, intersections, jumpSize)); } else { // without any intersection the line goes uninterrupted jumpingLines.push(line); } }); const path = buildPath(jumpingLines, jumpSize, jumpType, radius); jumppedLines = []; skippedPoints = []; return options.raw ? path : path.serialize(); }; exports.jumpover = jumpover; //# sourceMappingURL=jumpover.js.map