mirror of
https://gitee.com/antv/g6.git
synced 2024-12-02 11:48:29 +08:00
feat: path util file and test
This commit is contained in:
parent
73223e7946
commit
d5d4fd9520
@ -26,7 +26,7 @@
|
|||||||
"clean": "rimraf esm lib dist",
|
"clean": "rimraf esm lib dist",
|
||||||
"lint": "lint-staged",
|
"lint": "lint-staged",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test-live": "DEBUG_MODE=1 jest --watch",
|
"test-live": "DEBUG_MODE=1 jest --watch ./tests/unit/util",
|
||||||
"coverage": "jest --coverage",
|
"coverage": "jest --coverage",
|
||||||
"ci": "run-s build coverage",
|
"ci": "run-s build coverage",
|
||||||
"doc": "rimraf apis && typedoc",
|
"doc": "rimraf apis && typedoc",
|
||||||
|
13
src/interface/shape.ts
Normal file
13
src/interface/shape.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export interface IPoint {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRect extends IPoint {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICircle extends IPoint {
|
||||||
|
r: number;
|
||||||
|
}
|
52
src/util/math.ts
Normal file
52
src/util/math.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { IPoint } from '../interface/math'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否在区间内
|
||||||
|
* @param {number} value 值
|
||||||
|
* @param {number} min 最小值
|
||||||
|
* @param {number} max 最大值
|
||||||
|
* @return {boolean} bool 布尔
|
||||||
|
*/
|
||||||
|
const isBetween = (value: number, min: number, max: number) => value >= min && value <= max
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取两条线段的交点
|
||||||
|
* @param {object} p0 第一条线段起点
|
||||||
|
* @param {object} p1 第一条线段终点
|
||||||
|
* @param {object} p2 第二条线段起点
|
||||||
|
* @param {object} p3 第二条线段终点
|
||||||
|
* @return {object} 交点
|
||||||
|
*/
|
||||||
|
export const getLineIntersect = (p0: IPoint, p1: IPoint, p2: IPoint, p3: IPoint): IPoint => {
|
||||||
|
const tolerance = 0.001;
|
||||||
|
|
||||||
|
const E: IPoint = {
|
||||||
|
x: p2.x - p0.x,
|
||||||
|
y: p2.y - p0.y
|
||||||
|
};
|
||||||
|
const D0: IPoint = {
|
||||||
|
x: p1.x - p0.x,
|
||||||
|
y: p1.y - p0.y
|
||||||
|
};
|
||||||
|
const D1: IPoint = {
|
||||||
|
x: p3.x - p2.x,
|
||||||
|
y: p3.y - p2.y
|
||||||
|
};
|
||||||
|
const kross: number = D0.x * D1.y - D0.y * D1.x;
|
||||||
|
const sqrKross: number = kross * kross;
|
||||||
|
const sqrLen0: number = D0.x * D0.x + D0.y * D0.y;
|
||||||
|
const sqrLen1: number = D1.x * D1.x + D1.y * D1.y;
|
||||||
|
let point: IPoint;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
100
src/util/path.ts
Normal file
100
src/util/path.ts
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import { vec2 } from '@antv/matrix-util'
|
||||||
|
import { catmullRom2Bezier } from '@antv/path-util'
|
||||||
|
import { IPoint } from '../interface/math'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 替换字符串中的字段
|
||||||
|
* @param {String} str 模版字符串
|
||||||
|
* @param {Object} o json data
|
||||||
|
*/
|
||||||
|
const substitute = (str: string, o): string => {
|
||||||
|
if(!str || !o) {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
return str.replace(/\\?\{([^{}]+)\}/g, (match: string, name: string) => {
|
||||||
|
if(match.charAt(0) === '\\') {
|
||||||
|
return match.slice(1)
|
||||||
|
}
|
||||||
|
return o[name] || ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 给定坐标获取三次贝塞尔曲线的 M 及 C 值
|
||||||
|
* @param points coordinate set
|
||||||
|
*/
|
||||||
|
export const getSpline = (points: IPoint[]) => {
|
||||||
|
const data: number[] = []
|
||||||
|
|
||||||
|
if(points.length < 2) {
|
||||||
|
console.warn(`point length must largn than 2, now it's ${points.length}`)
|
||||||
|
}
|
||||||
|
for(const point of points) {
|
||||||
|
const { x, y } = point
|
||||||
|
data.push(x)
|
||||||
|
data.push(y)
|
||||||
|
}
|
||||||
|
|
||||||
|
const spliePath = catmullRom2Bezier(data)
|
||||||
|
spliePath.unshift([ 'M', points[0].x, points[0].y ])
|
||||||
|
return spliePath
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据起始点、相对位置、偏移量计算控制点
|
||||||
|
* @param {IPoint} startPoint 起始点,包含 x,y
|
||||||
|
* @param {IPoint} endPoint 结束点, 包含 x,y
|
||||||
|
* @param {Number} percent 相对位置,范围 0-1
|
||||||
|
* @param {Number} offset 偏移量
|
||||||
|
* @return {IPoint} 控制点,包含 x,y
|
||||||
|
*/
|
||||||
|
export const getControlPoint = (
|
||||||
|
startPoint: IPoint, endPoint: IPoint, percent: number = 0, offset: number = 0): IPoint => {
|
||||||
|
|
||||||
|
const point: IPoint = {
|
||||||
|
x: (1 - percent) * startPoint.x + percent * endPoint.x,
|
||||||
|
y: (1 - percent) * startPoint.y + percent * endPoint.y
|
||||||
|
};
|
||||||
|
|
||||||
|
let tangent = []
|
||||||
|
vec2.normalize(tangent, [ endPoint.x - startPoint.x, endPoint.x - startPoint.y ])
|
||||||
|
|
||||||
|
if(tangent.length === 0) {
|
||||||
|
tangent = [ 0, 0 ]
|
||||||
|
}
|
||||||
|
const perpendicular = [ -tangent[1] * offset, tangent[0] * offset ]; // 垂直向量
|
||||||
|
point.x += perpendicular[0];
|
||||||
|
point.y += perpendicular[1];
|
||||||
|
return point;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点集转化为Path多边形
|
||||||
|
* @param {Array} points 点集
|
||||||
|
* @param {Boolen} z 是否封闭
|
||||||
|
* @return {Array} Path
|
||||||
|
*/
|
||||||
|
export const pointsToPolygon = (points: IPoint[], z: boolean): string => {
|
||||||
|
if(!points.length) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = ''
|
||||||
|
let str = ''
|
||||||
|
|
||||||
|
for (let i = 0, length = points.length; i < length; i++) {
|
||||||
|
const item = points[i];
|
||||||
|
if (i === 0) {
|
||||||
|
str = 'M{x} {y}';
|
||||||
|
} else {
|
||||||
|
str = 'L{x} {y}';
|
||||||
|
}
|
||||||
|
path += substitute(str, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (z) {
|
||||||
|
path += 'Z';
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
116
tests/unit/util/path-spec.ts
Normal file
116
tests/unit/util/path-spec.ts
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import { getControlPoint, getSpline, pointsToPolygon } from '../../../src/util/path'
|
||||||
|
|
||||||
|
describe('Path Util Test', () => {
|
||||||
|
it('getSpline', () => {
|
||||||
|
const points = [
|
||||||
|
{
|
||||||
|
x: 10,
|
||||||
|
y: 12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 5,
|
||||||
|
y: 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2,
|
||||||
|
y: 7
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const splinePath = getSpline(points)
|
||||||
|
|
||||||
|
expect(splinePath.length).toEqual(3)
|
||||||
|
const first = splinePath[0]
|
||||||
|
expect(first.length).toEqual(3)
|
||||||
|
expect(first[0]).toEqual('M')
|
||||||
|
expect(first[1]).toEqual(10)
|
||||||
|
expect(first[2]).toEqual(12)
|
||||||
|
|
||||||
|
const second = splinePath[1]
|
||||||
|
expect(second.length).toEqual(7)
|
||||||
|
expect(second[0]).toEqual('C')
|
||||||
|
expect(second[1]).toEqual(9.166666666666666)
|
||||||
|
expect(second[2]).toEqual(10.833333333333334)
|
||||||
|
expect(second[5]).toEqual(5)
|
||||||
|
|
||||||
|
const three = splinePath[2]
|
||||||
|
expect(three.length).toEqual(7)
|
||||||
|
expect(three[2]).toEqual(4.166666666666667)
|
||||||
|
expect(three[6]).toEqual(7)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('getControlPoint horizontal', () => {
|
||||||
|
const start = { x: 0, y: 0 };
|
||||||
|
const end = { x: 100, y: 0 };
|
||||||
|
|
||||||
|
const controlPoint = getControlPoint(start, end, 0.5, 10)
|
||||||
|
|
||||||
|
expect(controlPoint.x).toEqual(42.928932188134524)
|
||||||
|
expect(controlPoint.y).toEqual(7.0710678118654755)
|
||||||
|
|
||||||
|
const points = getControlPoint(start, end, 0.2, -2)
|
||||||
|
expect(points).toEqual({ x: 21.414213562373096, y: -1.4142135623730951 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('getControlPoint vertical', () => {
|
||||||
|
const start = { x: 0, y: 0 };
|
||||||
|
const end = { x: 0, y: 100 };
|
||||||
|
|
||||||
|
const point = getControlPoint(start, end, 0.5)
|
||||||
|
expect(point).toEqual({ x: 0, y: 50 })
|
||||||
|
|
||||||
|
const point2 = getControlPoint(start, end, 0.2, -2)
|
||||||
|
console.log(point2)
|
||||||
|
expect(point2).toEqual({ x: 0, y: 20 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('getControlPoint 45', () => {
|
||||||
|
const start = { x: 0, y: 0 };
|
||||||
|
const end = { x: 100, y: 100 };
|
||||||
|
const point = getControlPoint(start, end, 0.5, 10);
|
||||||
|
const sqrt2 = Math.sqrt(2);
|
||||||
|
expect(point.x).toEqual(50 - sqrt2 * 10 / 2);
|
||||||
|
expect(point.y).toEqual(50 + sqrt2 * 10 / 2);
|
||||||
|
})
|
||||||
|
|
||||||
|
it('getControlPoint 135', () => {
|
||||||
|
const start = { x: 100, y: 100 };
|
||||||
|
const end = { x: 0, y: 0 };
|
||||||
|
const point = getControlPoint(start, end, 0.5, 10);
|
||||||
|
const sqrt2 = Math.sqrt(2);
|
||||||
|
expect(point.x).toEqual(50 + sqrt2 * 10 / 2);
|
||||||
|
expect(point.y).toEqual(50 - sqrt2 * 10 / 2);
|
||||||
|
})
|
||||||
|
|
||||||
|
it('pointsToPolygon z = false', () => {
|
||||||
|
const points = [
|
||||||
|
{
|
||||||
|
x: 1,
|
||||||
|
y: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 5,
|
||||||
|
y: 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const polygonPoint = pointsToPolygon(points, false)
|
||||||
|
expect(polygonPoint).toEqual('M1 2L5 5')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('pointsToPolygon z = true', () => {
|
||||||
|
const points = [
|
||||||
|
{
|
||||||
|
x: 1,
|
||||||
|
y: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 5,
|
||||||
|
y: 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const polygonPoint = pointsToPolygon(points, true)
|
||||||
|
expect(polygonPoint).toEqual('M1 2L5 5Z')
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user