@antv/g6
Version:
graph visualization frame work
300 lines (293 loc) • 8.05 kB
JavaScript
/**
* @fileOverview math util
* @author huangtonger@aliyun.com
*/
const BaseUtil = require('./base');
const tolerance = 0.001;
const MathUtil = {
/**
* 是否在区间内
* @param {number} value 值
* @param {number} min 最小值
* @param {number} max 最大值
* @return {boolean} bool 布尔
*/
isBetween(value, min, max) {
return value >= min && value <= max;
},
/**
* 两线段交点
* @param {object} p0 第一条线段起点
* @param {object} p1 第一条线段终点
* @param {object} p2 第二条线段起点
* @param {object} p3 第二条线段终点
* @return {object} 交点
*/
getLineIntersect(p0, p1, p2, p3) {
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 (MathUtil.isBetween(s, 0, 1) && MathUtil.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 {object} rect rect
* @param {object} point point
* @return {object} rst;
*/
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 = MathUtil.getLineIntersect(points[i - 1], points[i], center, point);
if (rst) {
break;
}
}
return rst;
},
/**
* get point and circle inIntersect
* @param {Object} circle 圆点,x,y,r
* @param {Object} point 点 x,y
* @return {object} applied point
*/
getCircleIntersectByPoint(circle, point) {
const cx = circle.x;
const cy = circle.y;
const r = circle.r;
const { x, y } = point;
const d = Math.sqrt(Math.pow((x - cx), 2) + Math.pow((y - cy), 2));
if (d < r) {
return null;
}
const dx = (x - cx);
const dy = (y - cy);
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
*/
getEllispeIntersectByPoint(ellipse, point) {
// 计算线段 (point.x, point.y) 到 (ellipse.x, ellipse.y) 与椭圆的交点
const a = ellipse.rx;
const b = ellipse.ry;
const cx = ellipse.x;
const cy = ellipse.y;
// const c = Math.sqrt(a * a - b * b); // 焦距
const dx = (point.x - cx);
const dy = (point.y - cy);
let angle = Math.atan2(dy / b, dx / a); // 直接通过 x,y 求夹角,求出来的范围是 -PI, PI
if (angle < 0) {
angle += 2 * Math.PI; // 转换到 0,2PI
}
// 通过参数方程求交点
// const r = (b * b) / (a - c * Math.sin(angle));
return {
x: cx + a * Math.cos(angle),
y: cy + b * Math.sin(angle)
};
},
/**
* coordinate matrix transformation
* @param {number} point coordinate
* @param {number} matrix matrix
* @param {number} tag could be 0 or 1
* @return {object} transformed point
*/
applyMatrix(point, matrix, tag = 1) {
const vector = [ point.x, point.y, tag ];
BaseUtil.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
*/
invertMatrix(point, matrix, tag = 1) {
const inversedMatrix = BaseUtil.mat3.invert([], matrix);
const vector = [ point.x, point.y, tag ];
BaseUtil.vec3.transformMat3(vector, vector, inversedMatrix);
return {
x: vector[0],
y: vector[1]
};
},
/**
* Floyd Warshall algorithm for shortest path distances matrix
* @param {array} adjMatrix adjacency matrix
* @return {array} distances shortest path distances matrix
*/
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;
},
getAdjMatrix(data, directed) {
const nodes = data.nodes;
const edges = data.edges;
const matrix = [];
// map node with index in data.nodes
const nodeMap = new Map();
nodes.forEach((node, i) => {
nodeMap.set(node.id, i);
const row = [];
matrix.push(row);
});
// const n = nodes.length;
edges.forEach(e => {
const source = e.source;
const target = e.target;
const sIndex = nodeMap.get(source);
const tIndex = nodeMap.get(target);
matrix[sIndex][tIndex] = 1;
if (!directed) matrix[tIndex][sIndex] = 1;
});
return matrix;
},
/**
* if the graph about the shortest path matrix is connected
* @param {array} matrix shortest path matrix
* @return {boolean} connected
*/
isConnected(matrix) {
if (matrix.length > 0) {
for (let j = 0; j < matrix[0].length; j++) {
if (matrix[0][j] === Infinity) return false;
}
}
return true;
},
getEDistance(p1, p2) {
return Math.sqrt((p1[0] - p2[0]) * (p1[0] - p2[0])
+ (p1[1] - p2[1]) * (p1[1] - p2[1]));
},
scaleMatrix(matrix, scale) {
const result = [];
matrix.forEach(row => {
const newRow = [];
row.forEach(v => {
newRow.push(v * scale);
});
result.push(newRow);
});
return result;
},
randomInitPos(size, xRange = [ 0, 1 ], yRange = [ 0, 1 ]) {
const positions = [];
for (let i = 0; i < size; i++) {
const x = Math.random() * (xRange[1] - xRange[0]) + xRange[0];
const y = Math.random() * (yRange[1] - yRange[0]) + yRange[0];
positions.push([ x, y ]);
}
return positions;
},
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
};
},
distance(p1, p2) {
const vx = p1.x - p2.x;
const vy = p1.y - p2.y;
return Math.sqrt(vx * vx + vy * vy);
}
};
module.exports = BaseUtil.mix({}, BaseUtil, MathUtil);