butterfly-dag
Version:
一个基于数据驱动的节点式编排组件库,让你有方便快捷定制可视化流程图表
391 lines (355 loc) • 8.66 kB
JavaScript
// to: https://github.com/antvis/G6/tree/3.5.1/src/util/math.ts
import { mat3, transform, vec3 } from '@antv/matrix-util';
const isBetween = (value, min, max) => value >= min && value <= max;
/**
* 获取两条线段的交点
* @param {Point} p0 第一条线段起点
* @param {Point} p1 第一条线段终点
* @param {Point} p2 第二条线段起点
* @param {Point} p3 第二条线段终点
* @return {Point} 交点
*/
const getLineIntersect = (p0, p1, p2, p3) => {
const tolerance = 0.001;
const E = {
x: p2.x - p0.x,
y: p2.y - p0.y,
};
const D0 = {
x: p1.x - p0.x,
y: p1.y - p0.y,
};
const D1 = {
x: p3.x - p2.x,
y: p3.y - p2.y,
};
const kross = D0.x * D1.y - D0.y * D1.x;
const sqrKross = kross * kross;
const sqrLen0 = D0.x * D0.x + D0.y * D0.y;
const sqrLen1 = D1.x * D1.x + D1.y * D1.y;
let point = null;
if (sqrKross > tolerance * sqrLen0 * sqrLen1) {
const s = (E.x * D1.y - E.y * D1.x) / kross;
const t = (E.x * D0.y - E.y * D0.x) / kross;
if (isBetween(s, 0, 1) && isBetween(t, 0, 1)) {
point = {
x: p0.x + s * D0.x,
y: p0.y + s * D0.y,
};
}
}
return point;
};
/**
* point and rectangular intersection point
* @param {IRect} rect rect
* @param {Point} point point
* @return {PointPoint} rst;
*/
export const getRectIntersectByPoint = (rect, point) => {
const { x, y, width, height } = rect;
const cx = x + width / 2;
const cy = y + height / 2;
const points = [];
const center = {
x: cx,
y: cy,
};
points.push({
x,
y,
});
points.push({
x: x + width,
y,
});
points.push({
x: x + width,
y: y + height,
});
points.push({
x,
y: y + height,
});
points.push({
x,
y,
});
let rst = null;
for (let i = 1; i < points.length; i++) {
rst = getLineIntersect(points[i - 1], points[i], center, point);
if (rst) {
break;
}
}
return rst;
};
/**
* get point and circle inIntersect
* @param {ICircle} circle 圆点,x,y,r
* @param {Point} point 点 x,y
* @return {Point} applied point
*/
export const getCircleIntersectByPoint = (circle, point) => {
const { x: cx, y: cy, r } = circle;
const { x, y } = point;
const dx = x - cx;
const dy = y - cy;
const d = Math.sqrt(dx * dx + dy * dy);
if (d < r) {
return null;
}
const signX = Math.sign(dx);
const signY = Math.sign(dy);
const angle = Math.atan(dy / dx);
return {
x: cx + Math.abs(r * Math.cos(angle)) * signX,
y: cy + Math.abs(r * Math.sin(angle)) * signY,
};
};
/**
* get point and ellipse inIntersect
* @param {Object} ellipse 椭圆 x,y,rx,ry
* @param {Object} point 点 x,y
* @return {object} applied point
*/
export const getEllipseIntersectByPoint = (ellipse, point) => {
const a = ellipse.rx;
const b = ellipse.ry;
const cx = ellipse.x;
const cy = ellipse.y;
const dx = point.x - cx;
const dy = point.y - cy;
// 直接通过 x,y 求夹角,求出来的范围是 -PI, PI
let angle = Math.atan2(dy / b, dx / a);
if (angle < 0) {
angle += 2 * Math.PI; // 转换到 0,2PI
}
return {
x: cx + a * Math.cos(angle),
y: cy + b * Math.sin(angle),
};
};
/**
* coordinate matrix transformation
* @param {number} point coordinate
* @param {Matrix} matrix matrix
* @param {number} tag could be 0 or 1
* @return {Point} transformed point
*/
export const applyMatrix = (point, matrix, tag) => {
const vector = [point.x, point.y, tag];
if (!matrix || matrix[0] === NaN) {
matrix = mat3.create();
}
vec3.transformMat3(vector, vector, matrix);
return {
x: vector[0],
y: vector[1],
};
};
/**
* coordinate matrix invert transformation
* @param {number} point coordinate
* @param {number} matrix matrix
* @param {number} tag could be 0 or 1
* @return {object} transformed point
*/
export const invertMatrix = (point, matrix, tag) => {
if (!matrix || matrix[0] === NaN) {
matrix = mat3.create();
}
let inversedMatrix = mat3.invert([], matrix);
if (!inversedMatrix) {
inversedMatrix = mat3.create();
}
const vector = [point.x, point.y, tag];
vec3.transformMat3(vector, vector, inversedMatrix);
return {
x: vector[0],
y: vector[1],
};
};
/**
*
* @param p1 First coordinate
* @param p2 second coordinate
* @param p3 three coordinate
*/
export const getCircleCenterByPoints = (p1, p2, p3) => {
const a = p1.x - p2.x;
const b = p1.y - p2.y;
const c = p1.x - p3.x;
const d = p1.y - p3.y;
const e = (p1.x * p1.x - p2.x * p2.x - p2.y * p2.y + p1.y * p1.y) / 2;
const f = (p1.x * p1.x - p3.x * p3.x - p3.y * p3.y + p1.y * p1.y) / 2;
const denominator = b * c - a * d;
return {
x: -(d * e - b * f) / denominator,
y: -(a * f - c * e) / denominator,
};
};
/**
* get distance by two points
* @param p1 first point
* @param p2 second point
*/
export const distance = (p1, p2) => {
const vx = p1.x - p2.x;
const vy = p1.y - p2.y;
return Math.sqrt(vx * vx + vy * vy);
};
/**
* scale matrix
* @param matrix [ [], [], [] ]
* @param ratio
*/
export const scaleMatrix = (matrix, ratio) => {
const result = [];
matrix.forEach(row => {
const newRow = [];
row.forEach(v => {
newRow.push(v * ratio);
});
result.push(newRow);
});
return result;
};
/**
* Floyd Warshall algorithm for shortest path distances matrix
* @param {array} adjMatrix adjacency matrix
* @return {array} distances shortest path distances matrix
*/
export const floydWarshall = (adjMatrix) => {
// initialize
const dist = [];
const size = adjMatrix.length;
for (let i = 0; i < size; i += 1) {
dist[i] = [];
for (let j = 0; j < size; j += 1) {
if (i === j) {
dist[i][j] = 0;
} else if (adjMatrix[i][j] === 0 || !adjMatrix[i][j]) {
dist[i][j] = Infinity;
} else {
dist[i][j] = adjMatrix[i][j];
}
}
}
// floyd
for (let k = 0; k < size; k += 1) {
for (let i = 0; i < size; i += 1) {
for (let j = 0; j < size; j += 1) {
if (dist[i][j] > dist[i][k] + dist[k][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
}
}
}
}
return dist;
};
/**
* get adjacency matrix
* @param data graph data
* @param directed whether it's a directed graph
*/
export const getAdjMatrix = (data, directed) => {
const { nodes, edges } = data;
const matrix = [];
// map node with index in data.nodes
const nodeMap = {};
if (!nodes) {
throw new Error('invalid nodes data!');
}
if (nodes) {
nodes.forEach((node, i) => {
nodeMap[node.id] = i;
const row = [];
matrix.push(row);
});
}
if (edges) {
edges.forEach(e => {
const { source, target } = e;
const sIndex = nodeMap[source];
const tIndex = nodeMap[target];
matrix[sIndex][tIndex] = 1;
if (!directed) {
matrix[tIndex][sIndex] = 1;
}
});
}
return matrix;
};
/**
* 平移group
* @param group Group 实例
* @param vec 移动向量
*/
export const translate = (group, vec) => {
group.translate(vec.x, vec.y);
};
/**
* 移动到指定坐标点
* @param group Group 实例
* @param point 移动到的坐标点
*/
export const move = (group, point) => {
let matrix = group.getMatrix();
if (!matrix) {
matrix = mat3.create();
}
const bbox = group.getCanvasBBox();
const vx = point.x - bbox.minX;
const vy = point.y - bbox.minY;
const movedMatrix = transform(matrix, [['t', vx, vy]]);
group.setMatrix(movedMatrix);
};
/**
* 缩放 group
* @param group Group 实例
* @param point 在x 和 y 方向上的缩放比例
*/
export const scale = (group, ratio) => {
let matrix = group.getMatrix();
if (!matrix) {
matrix = mat3.create();
}
let scaleXY = ratio;
if (!isArray(ratio)) {
scaleXY = [ratio, ratio];
}
if (isArray(ratio) && ratio.length === 1) {
scaleXY = [ratio[0], ratio[0]];
}
matrix = transform(matrix, [['s', (scaleXY)[0], (scaleXY)[1]]]);
group.setMatrix(matrix);
};
/**
*
* @param group Group 实例
* @param ratio 选择角度
*/
export const rotate = (group, angle) => {
let matrix = group.getMatrix();
if (!matrix) {
matrix = mat3.create();
}
matrix = transform(matrix, [['r', angle]]);
group.setMatrix(matrix);
};
export const getDegree = (n, nodeIdxMap, edges) => {
const degrees = [];
for (let i = 0; i < n; i++) {
degrees[i] = 0;
}
edges.forEach(e => {
if (e.source) {
degrees[nodeIdxMap[e.source]] += 1;
}
if (e.target) {
degrees[nodeIdxMap[e.target]] += 1;
}
});
return degrees;
};