@antv/g6
Version:
A Graph Visualization Framework in JavaScript
302 lines • 12.7 kB
JavaScript
import { difference, isEqual } from '@antv/util';
import { getBBoxHeight, getBBoxWidth, getCombinedBBox, getNearestBoundaryPoint, getNearestBoundarySide, getNodeBBox, isPointBBoxCenter, isPointInBBox, isPointOnBBoxBoundary, isPointOutsideBBox, } from '../bbox';
import { isOrthogonal, moveTo, round } from '../point';
import { angle, distance, subtract, toVector2, toVector3 } from '../vector';
const defaultOptions = {
padding: 10,
};
/**
* <zh/> 获取两点之间的正交线段路径
*
* <en/> Get orthogonal line segments between two points
* @param sourcePoint - <zh/> 起始点 | <en/> start point
* @param targetPoint - <zh/> 终止点 | <en/> end point
* @param sourceNode - <zh/> 起始节点 | <en/> source node
* @param targetNode - <zh/> 终止节点 | <en/> target node
* @param controlPoints - <zh/> 控制点 | <en/> control points
* @param options - <zh/> 配置项 | <en/> options
* @returns <zh/> 路径点集 | <en/> vertices
*/
export function orth(sourcePoint, targetPoint, sourceNode, targetNode, controlPoints, options) {
const { padding } = Object.assign(defaultOptions, options);
const sourceBBox = getNodeBBox(sourceNode, padding);
const targetBBox = getNodeBBox(targetNode, padding);
const points = [sourcePoint, ...controlPoints, targetPoint];
// direction of previous route segment
let direction = null;
const result = [];
for (let fromIdx = 0, len = points.length; fromIdx < len - 1; fromIdx++) {
const toIdx = fromIdx + 1;
const from = points[fromIdx];
const to = points[toIdx];
const isOrth = isOrthogonal(from, to);
let route = null;
if (fromIdx === 0) {
if (toIdx === len - 1) {
// source -> target
if (sourceBBox.intersects(targetBBox)) {
route = insideNode(from, to, sourceBBox, targetBBox);
}
else if (!isPointBBoxCenter(from, sourceBBox) && !isPointBBoxCenter(to, targetBBox)) {
const fromWithPadding = getNearestBoundaryPoint(from, sourceBBox);
const toWithPadding = getNearestBoundaryPoint(to, targetBBox);
route = pointToPoint(fromWithPadding, toWithPadding, getDirection(fromWithPadding, toWithPadding));
route.points.unshift(fromWithPadding);
route.points.push(toWithPadding);
}
else if (!isOrth) {
route = nodeToNode(from, to, sourceBBox, targetBBox);
}
}
else {
// source -> point
if (isPointInBBox(to, sourceBBox)) {
route = insideNode(from, to, sourceBBox, getNodeBBox(to, padding), direction);
}
else if (!isOrth) {
route = nodeToPoint(from, to, sourceBBox);
}
}
}
else if (toIdx === len - 1) {
// point -> target
if (isPointInBBox(from, targetBBox)) {
route = insideNode(from, to, getNodeBBox(from, padding), targetBBox, direction);
}
else if (!isOrth) {
route = pointToNode(from, to, targetBBox, direction);
}
}
else if (!isOrth) {
// point -> point
route = pointToPoint(from, to, direction);
}
// set direction for next iteration
if (route) {
result.push(...route.points);
direction = route.direction;
}
else {
// orthogonal route and not looped
direction = getDirection(from, to);
}
if (toIdx < len - 1)
result.push(to);
}
return result.map(toVector2);
}
/**
* Direction to opposites direction map
*/
const opposites = {
N: 'S',
S: 'N',
W: 'E',
E: 'W',
};
/**
* Direction to radians map
*/
const radians = {
N: -Math.PI / 2,
S: Math.PI / 2,
E: 0,
W: Math.PI,
};
/**
* <zh/> 获取两点之间的方向,从 `from` 到 `to` 的方向
*
* <en/> Get the direction between two points, the direction from `from` to `to`
* @param from - <zh/> 起始点 | <en/> start point
* @param to - <zh/> 终止点 | <en/> end point
* @returns <zh/> 方向 | <en/> direction
*/
export function getDirection(from, to) {
const [fx, fy] = from;
const [tx, ty] = to;
if (fx === tx) {
return fy > ty ? 'N' : 'S';
}
if (fy === ty) {
return fx > tx ? 'W' : 'E';
}
return null;
}
/**
* <zh/> 获取包围盒的尺寸,根据方向返回宽度或者高度
*
* <en/> Get the size of the bounding box, return the width or height according to the direction
* @param bbox - <zh/> 包围盒 | <en/> bounding box
* @param direction - <zh/> 方向 | <en/> direction
* @returns <zh/> 尺寸 | <en/> size
*/
export function getBBoxSize(bbox, direction) {
return direction === 'N' || direction === 'S' ? getBBoxHeight(bbox) : getBBoxWidth(bbox);
}
/**
* <zh/> 从一个点到另一个点计算正交路由
*
* <en/> Calculate orthogonal route from one point to another
* @param from - <zh/> 起始点 | <en/> start point
* @param to - <zh/> 终止点 | <en/> end point
* @param direction - <zh/> 前一条线段的方向 | <en/> direction of the previous segment
* @returns <zh/> 正交路由 | <en/> orthogonal route
*/
export function pointToPoint(from, to, direction) {
const p1 = [from[0], to[1]];
const p2 = [to[0], from[1]];
const d1 = getDirection(from, p1);
const d2 = getDirection(from, p2);
const opposite = direction ? opposites[direction] : null;
const p = d1 === direction || (d1 !== opposite && d2 !== direction) ? p1 : p2;
return { points: [p], direction: getDirection(p, to) };
}
/**
* <zh/> 从节点到点计算正交路由
*
* <en/> Calculate orthogonal route from node to point
* @param from - <zh/> 起始点 | <en/> start point
* @param to - <zh/> 终止点 | <en/> end point
* @param fromBBox - <zh/> 起始节点的包围盒 | <en/> bounding box of the start node
* @returns <zh/> 正交路由 | <en/> orthogonal route
*/
export function nodeToPoint(from, to, fromBBox) {
if (isPointBBoxCenter(from, fromBBox)) {
const p = freeJoin(from, to, fromBBox);
return { points: [p], direction: getDirection(p, to) };
}
else {
const fromWithPadding = getNearestBoundaryPoint(from, fromBBox);
const isHorizontal = ['left', 'right'].includes(getNearestBoundarySide(from, fromBBox));
const p = isHorizontal ? [to[0], fromWithPadding[1]] : [fromWithPadding[0], to[1]];
return { points: [p], direction: getDirection(p, to) };
}
}
/**
* <zh/> 从点到节点计算正交路由
*
* <en/> Calculate orthogonal route from point to node
* @param from - <zh/> 起始点 | <en/> start point
* @param to - <zh/> 终止点 | <en/> end point
* @param toBBox - <zh/> 终止节点的包围盒 | <en/> bounding box of the end node
* @param direction - <zh/> 前一条线段的方向 | <en/> direction of the previous segment
* @returns <zh/> 正交路由 | <en/> orthogonal route
*/
export function pointToNode(from, to, toBBox, direction) {
const toWithPadding = isPointBBoxCenter(to, toBBox) ? to : getNearestBoundaryPoint(to, toBBox);
const points = [
[toWithPadding[0], from[1]],
[from[0], toWithPadding[1]],
];
const freePoints = points.filter((p) => isPointOutsideBBox(p, toBBox) && !isPointOnBBoxBoundary(p, toBBox, true));
const freeDirectionPoints = freePoints.filter((p) => getDirection(p, from) !== direction);
if (freeDirectionPoints.length > 0) {
// Pick a point which bears the same direction as the previous segment.
const p = freeDirectionPoints.find((p) => getDirection(from, p) === direction) || freeDirectionPoints[0];
return {
points: [p],
direction: getDirection(p, to),
};
}
else {
// Here we found only points which are either contained in the element or they would create
// a link segment going in opposites direction from the previous one.
// We take the point inside element and move it outside the element in the direction the
// route is going. Now we can join this point with the current end (using freeJoin).
const p = difference(points, freePoints)[0];
const p2 = moveTo(to, p, getBBoxSize(toBBox, direction) / 2);
const p1 = freeJoin(p2, from, toBBox);
return {
points: [p1, p2],
direction: getDirection(p2, to),
};
}
}
/**
* <zh/> 从节点到节点计算正交路由
*
* <en/> Calculate orthogonal route from node to node
* @param from - <zh/> 起始点 | <en/> start point
* @param to - <zh/> 终止点 | <en/> end point
* @param fromBBox - <zh/> 起始节点的包围盒 | <en/> bounding box of the start node
* @param toBBox - <zh/> 终止节点的包围盒 | <en/> bounding box of the end node
* @returns <zh/> 正交路由 | <en/> orthogonal route
*/
export function nodeToNode(from, to, fromBBox, toBBox) {
let route = nodeToPoint(from, to, fromBBox);
const p1 = toVector3(route.points[0]);
if (isPointInBBox(p1, toBBox)) {
route = nodeToPoint(to, from, toBBox);
const p2 = toVector3(route.points[0]);
if (isPointInBBox(p2, fromBBox)) {
const fromBorder = moveTo(from, p1, getBBoxSize(fromBBox, getDirection(from, p1)) / 2);
const toBorder = moveTo(to, p2, getBBoxSize(toBBox, getDirection(to, p2)) / 2);
const midPoint = [(fromBorder[0] + toBorder[0]) / 2, (fromBorder[1] + toBorder[1]) / 2];
const startRoute = nodeToPoint(from, midPoint, fromBBox);
const endRoute = pointToNode(midPoint, to, toBBox, startRoute.direction);
route.points = [startRoute.points[0], endRoute.points[0]];
route.direction = endRoute.direction;
}
}
return route;
}
/**
* <zh/> 在两个节点内部计算路由
*
* <en/> Calculate route inside two nodes
* @param from - <zh/> 起始点 | <en/> start point
* @param to - <zh/> 终止点 | <en/> end point
* @param fromBBox - <zh/> 起始节点的包围盒 | <en/> bounding box of the start node
* @param toBBox - <zh/> 终止节点的包围盒 | <en/> bounding box of the end node
* @param direction - <zh/> 方向 | <en/> direction
* @returns <zh/> 正交路由 | <en/> orthogonal route
*/
export function insideNode(from, to, fromBBox, toBBox, direction) {
const DEFAULT_OFFSET = 0.01;
const boundary = getCombinedBBox([fromBBox, toBBox]);
const reversed = distance(to, boundary.center) > distance(from, boundary.center);
const [start, end] = reversed ? [to, from] : [from, to];
const halfPerimeter = getBBoxHeight(boundary) + getBBoxWidth(boundary);
let p1;
if (direction) {
const ref = [
start[0] + halfPerimeter * Math.cos(radians[direction]),
start[1] + halfPerimeter * Math.sin(radians[direction]),
];
// `getNearestBoundaryPoint` returns a point on the boundary, so we need to move it a bit to ensure it's outside the element and then get the correct `p2` via `freeJoin`.
p1 = moveTo(getNearestBoundaryPoint(ref, boundary), ref, DEFAULT_OFFSET);
}
else {
p1 = moveTo(getNearestBoundaryPoint(start, boundary), start, -DEFAULT_OFFSET);
}
let p2 = freeJoin(p1, end, boundary);
let points = [round(p1, 2), round(p2, 2)];
if (isEqual(round(p1), round(p2))) {
const rad = angle(subtract(p1, start), [1, 0, 0]) + Math.PI / 2;
p2 = [end[0] + halfPerimeter * Math.cos(rad), end[1] + halfPerimeter * Math.sin(rad), 0];
p2 = round(moveTo(getNearestBoundaryPoint(p2, boundary), end, -DEFAULT_OFFSET), 2);
const p3 = freeJoin(p1, p2, boundary);
points = [p1, p3, p2];
}
return {
points: reversed ? points.reverse() : points,
direction: reversed ? getDirection(p1, to) : getDirection(p2, to),
};
}
/**
* <zh/> 返回一个点 `p`,使得线段 p,p1 和 p,p2 互相垂直,p 尽可能不在给定的包围盒内
*
* <en/> Returns a point `p` where lines p,p1 and p,p2 are perpendicular and p is not contained in the given box
* @param p1 - <zh/> 点 | <en/> point
* @param p2 - <zh/> 点 | <en/> point
* @param bbox - <zh/> 包围盒 | <en/> bounding box
* @returns <zh/> 点 | <en/> point
*/
export function freeJoin(p1, p2, bbox) {
let p = [p1[0], p2[1]];
if (isPointInBBox(p, bbox)) {
p = [p2[0], p1[1]];
}
return p;
}
//# sourceMappingURL=orth.js.map