')); // false
+console.log(isElement({})); // false
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| value | 要检查的值 | unknown | - |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| result | 是否为 DOM 元素 | boolean | - |
+
+## requestAnimationFrame
+
+跨浏览器的 requestAnimationFrame 实现,用于执行动画。
+
+- 示例
+
+```ts
+import { requestAnimationFrame } from '@antv/util';
+
+// 基本使用
+requestAnimationFrame(() => {
+ console.log('Animation frame');
+});
+
+// 动画循环
+function animate() {
+ // 更新动画
+ requestAnimationFrame(animate);
+}
+animate();
+
+// 实际应用示例
+function smoothScroll(element: HTMLElement, target: number, duration: number) {
+ const start = element.scrollTop;
+ const distance = target - start;
+ const startTime = performance.now();
+
+ function update(currentTime: number) {
+ const elapsed = currentTime - startTime;
+ const progress = Math.min(elapsed / duration, 1);
+
+ element.scrollTop = start + distance * progress;
+
+ if (progress < 1) {
+ requestAnimationFrame(update);
+ }
+ }
+
+ requestAnimationFrame(update);
+}
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| fn | 动画回调函数 | FrameRequestCallback | - |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| handler | 动画句柄 | number | - |
+
+## cancelAnimationFrame
+
+取消之前请求的动画帧。
+
+- 示例
+
+```ts
+import { requestAnimationFrame, cancelAnimationFrame } from '@antv/util';
+
+// 基本使用
+const handler = requestAnimationFrame(() => {
+ console.log('Cancelled');
+});
+cancelAnimationFrame(handler);
+
+// 实际应用示例
+class Animation {
+ private frameId: number;
+
+ start() {
+ const animate = () => {
+ // 执行动画
+ this.frameId = requestAnimationFrame(animate);
+ };
+ this.frameId = requestAnimationFrame(animate);
+ }
+
+ stop() {
+ if (this.frameId) {
+ cancelAnimationFrame(this.frameId);
+ this.frameId = null;
+ }
+ }
+}
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| handler | 要取消的动画句柄 | number | - |
+
+- 返回值
+
+无返回值
+
+## augment
+
+扩展类的原型,支持混入多个对象或类的属性和方法。
+
+- 示例
+
+```ts
+import { augment } from '@antv/util';
+
+// 基本使用
+class Base {}
+const mixin = { sayHi() { console.log('Hi'); } };
+augment(Base, mixin);
+
+const obj = new Base();
+obj.sayHi(); // "Hi"
+
+// 混入类
+class Mixin {
+ greet() { console.log('Hello'); }
+}
+augment(Base, Mixin);
+obj.greet(); // "Hello"
+```
+
+## clone
+
+深度克隆对象或数组。
+
+- 示例
+
+```ts
+import { clone } from '@antv/util';
+
+// 基本使用
+const obj = { a: 1, b: { c: 2 } };
+const copy = clone(obj);
+console.log(copy); // { a b: { c: 2 } }
+console.log(copy !== obj); // true
+console.log(copy.b !== obj.b); // true
+
+// 克隆数组
+const arr = [1, [2, 3], { a: 4 }];
+const copyArr = clone(arr);
+console.log(copyArr); // [1, [2, 3], { a: 4 }]
+```
+
+## debounce
+
+创建一个防抖函数,延迟调用。
+
+- 示例
+
+```ts
+import { debounce } from '@antv/util';
+
+// 基本使用
+const handler = debounce(() => {
+ console.log('Resized');
+}, 200);
+
+window.addEventListener('resize', handler);
+
+// 立即执行
+const immediate = debounce(() => {
+ console.log('Clicked');
+}, 200, true);
+
+button.addEventListener('click', immediate);
+```
+
+## memoize
+
+缓存函数的计算结果。
+
+- 示例
+
+```ts
+import { memoize } from '@antv/util';
+
+// 基本使用
+const fibonacci = memoize((n: number): number => {
+ return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
+});
+
+console.log(fibonacci(10)); // 快速计算,使用缓存
+
+// 自定义缓存键
+const getUser = memoize(
+ (id: number, type: string) => fetchUser(id, type),
+ (id, type) => `${id}-${type}`
+);
+
+// 设置缓存大小
+const cached = memoize(heavyComputation, undefined, 1000);
+```
+
+- 参数说明
+
+| 方法 | 参数 | 类型 | 说明 |
+|---------|------|------|---------|
+| augment | args | any[] | 目标类和要混入的对象/类 |
+| clone | obj | any | 要克隆的对象或数组 |
+| debounce | func | Function | 要防抖的函数 |
+| debounce | wait | number | 延迟时间(ms) |
+| debounce | immediate | boolean | 是否立即执行 |
+| memoize | fn | Function | 要缓存的函数 |
+| memoize | resolver | Function | 缓存键生成函数 |
+| memoize | maxSize | number | 缓存大小限制 |
+
+- 返回值
+
+| 方法 | 返回类型 | 说明 |
+|---------|------|------|
+| augment | void | 无返回值 |
+| clone | any | 克隆后的对象/数组 |
+| debounce | Function | 防抖后的函数 |
+| memoize | Function | 带缓存的函数 |
+
+## deepMix
+
+深度混合多个对象。
+
+- 示例
+
+```ts
+import { deepMix } from '@antv/util';
+
+// 基本使用
+const obj1 = { a: 1, b: { c: 2 } };
+const obj2 = { b: { d: 3 }, e: 4 };
+console.log(deepMix({}, obj1, obj2));
+// { a: 1, b: { c: 2, d: 3 }, e: 4 }
+
+// 数组处理
+const result = deepMix({}, {
+ arr: [1, 2],
+ obj: { a: 1 }
+}, {
+ arr: [3],
+ obj: { b: 2 }
+});
+console.log(result);
+// { arr: [3], obj: { a: 1, b: 2 } }
+```
+
+## each
+
+遍历数组或对象的每个元素。
+
+- 示例
+
+```ts
+import { each } from '@antv/util';
+
+// 遍历数组
+const arr = [1, 2, 3];
+each(arr, (item, index) => {
+ console.log(item, index);
+});
+
+// 遍历对象
+const obj = { a: 1, b: 2 };
+each(obj, (value, key) => {
+ console.log(key, value);
+});
+
+// 提前终止
+each(arr, (item) => {
+ if (item === 2) return false;
+ console.log(item);
+});
+```
+
+## extend
+
+实现类继承。
+
+- 示例
+
+```ts
+import { extend } from '@antv/util';
+
+// 基本继承
+class Parent {
+ name: string;
+ constructor(name: string) {
+ this.name = name;
+ }
+}
+
+const Child = extend(function(name) {
+ Child.superclass.constructor.call(this, name);
+}, Parent);
+
+// 带方法继承
+extend(Child, Parent, {
+ sayHi() {
+ console.log(`Hi, ${this.name}`);
+ }
+}, {
+ staticMethod() {}
+});
+```
+
+## indexOf
+
+查找元素在数组中的索引。
+
+- 示例
+
+```ts
+import { indexOf } from '@antv/util';
+
+// 基本使用
+const arr = [1, 2, 3, 2];
+console.log(indexOf(arr, 2)); // 1
+console.log(indexOf(arr, 4)); // -1
+
+// 对象数组
+const items = [{ id: 1 }, { id: 2 }];
+const target = items[0];
+console.log(indexOf(items, target)); // 0
+```
+
+- 参数说明
+
+| 方法 | 参数 | 类型 | 说明 |
+|---------|------|------|---------|
+| deepMix | rst, ...args | any, ...any[] | 目标对象和源对象列表 |
+| each | elements, func | any[] \| object, Function | 要遍历的集合和回调函数 |
+| extend | subclass, superclass, overrides?, staticOverrides? | Function, Function, object?, object? | 子类、父类和扩展方法 |
+| indexOf | arr, obj | T[], T | 要搜索的数组和目标元素 |
+
+- 返回值
+
+| 方法 | 返回类型 | 说明 |
+|---------|------|------|
+| deepMix | any | 混合后的对象 |
+| each | void | 无返回值 |
+| extend | Function | 扩展后的子类 |
+| indexOf | number | 元素索引或-1 |
+
+## isEmpty
+
+判断值是否为空。
+
+- 示例
+
+```ts
+import { isEmpty } from '@antv/util';
+
+console.log(isEmpty(null)); // true
+console.log(isEmpty(undefined)); // true
+console.log(isEmpty([])); // true
+console.log(isEmpty({})); // true
+console.log(isEmpty(new Map())); // true
+console.log(isEmpty([1, 2])); // false
+console.log(isEmpty({ a: 1 })); // false
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| value | 要检查的值 | any | - |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| result | 是否为空 | boolean | - |
+
+## isEqual
+
+深度比较两个值是否相等。
+
+- 示例
+
+```ts
+import { isEqual } from '@antv/util';
+
+// 基本类型
+console.log(isEqual(1, 1)); // true
+console.log(isEqual('a', 'a')); // true
+
+// 数组
+console.log(isEqual([1, 2], [1, 2])); // true
+console.log(isEqual([1, 2], [2, 1])); // false
+
+// 对象
+console.log(isEqual(
+ { a: 1, b: 2 },
+ { a: 1, b: 2 }
+)); // true
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| value | 要比较的值 | any | - |
+| other | 另一个值 | any | - |
+
+## isEqualWith
+
+使用自定义函数比较两个值。
+
+- 示例
+
+```ts
+import { isEqualWith } from '@antv/util';
+
+const customizer = (v1, v2) => {
+ if (v1.type === v2.type) return true;
+ return false;
+};
+
+console.log(isEqualWith(
+ { type: 'a', value: 1 },
+ { type: 'a', value: 2 },
+ customizer
+)); // true
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| value | 要比较的值 | T | - |
+| other | 另一个值 | T | - |
+| fn | 比较函数 | (v1: T, v2: T) => boolean | - |
+
+## map
+
+映射数组的每个元素。
+
+- 示例
+
+```ts
+import { map } from '@antv/util';
+
+// 基本使用
+const numbers = [1, 2, 3];
+console.log(map(numbers, x => x * 2)); // [2, 4, 6]
+
+// 使用索引
+console.log(map(numbers, (x, i) => x + i)); // [1, 3, 5]
+
+// 对象数组
+const items = [{ value: 1 }, { value: 2 }];
+console.log(map(items, item => item.value)); // [1, 2]
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| arr | 要映射的数组 | T[] | - |
+| func | 映射函数 | (v: T, idx: number) => G | - |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| result | 映射后的数组 | G[] | - |
+
+## mapValues
+
+映射对象的所有值。
+
+- 示例
+
+```ts
+import { mapValues } from '@antv/util';
+
+// 基本使用
+const obj = { a: 1, b: 2 };
+console.log(mapValues(obj, x => x * 2));
+// { a: 2, b: 4 }
+
+// 使用键名
+console.log(mapValues(obj, (value, key) => `${key}:${value}`));
+// { a: 'a:1', b: 'b:2' }
+
+// 默认映射
+console.log(mapValues(obj));
+// { a: 1, b: 2 }
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| object | 源对象 | { [key: string]: T } | - |
+| func | 映射函数 | (value: T, key: string) => any | identity |
+
+## mix/assiage
+
+混合多个对象的属性。
+
+- 示例
+
+```ts
+import { mix } from '@antv/util';
+
+// 基本使用
+const obj1 = { a: 1 };
+const obj2 = { b: 2 };
+const obj3 = { c: 3 };
+
+console.log(mix({}, obj1, obj2, obj3));
+// { a: 1, b: 2, c: 3 }
+
+// 忽略 undefined
+console.log(mix({}, { a: 1, b: undefined }));
+// { a: 1 }
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| dist | 目标对象 | Base & A & B & C | - |
+| src1 | 源对象 1 | A | - |
+| src2 | 源对象 2 | B | - |
+| src3 | 源对象 3 | C | - |
+
+## get
+
+安全获取对象深层属性值。
+
+- 示例
+
+```ts
+import { get } from '@antv/util';
+
+const obj = {
+ a: {
+ b: {
+ c: 1
+ }
+ }
+};
+
+// 使用点号路径
+console.log(get(obj, 'a.b.c')); // 1
+console.log(get(obj, 'a.b.d', 0)); // 0
+
+// 使用数组路径
+console.log(get(obj, ['a', 'b', 'c'])); // 1
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| obj | 源对象 | any | - |
+| key | 属性路径 | string \| any[] | - |
+| defaultValue | 默认值 | any | - |
+
+## set
+
+设置对象深层属性值。
+
+- 示例
+
+```ts
+import { set } from '@antv/util';
+
+const obj = {};
+
+// 使用点号路径
+set(obj, 'a.b.c', 1);
+console.log(obj); // { a: { b: { c: 1 } } }
+
+// 使用数组路径
+set(obj, ['x', 0, 'y'], 2);
+console.log(obj); // { a: { b: { c: 1 } }, x: [{ y: 2 }] }
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| obj | 目标对象 | any | - |
+| path | 属性路径 | string \| any[] | - |
+| value | 要设置的值 | any | - |
+
+## pick
+
+创建一个从源对象中选取指定属性的对象。
+
+- 示例
+
+```ts
+import { pick } from '@antv/util';
+
+const object = { a: 1, b: '2', c: 3 };
+
+console.log(pick(object, ['a', 'c']));
+// { a: 1, c: 3 }
+
+// 处理不存在的键
+console.log(pick(object, ['a', 'd']));
+// { a: 1 }
+
+// 处理无效输入
+console.log(pick(null, ['a']));
+// {}
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| object | 源对象 | ObjectType
| - |
+| keys | 要选取的属性数组 | string[] | - |
+
+## omit
+
+创建一个忽略指定属性的对象。
+
+- 示例
+
+```ts
+import { omit } from '@antv/util';
+
+const object = { a: 1, b: '2', c: 3 };
+
+console.log(omit(object, ['b']));
+// { a: 1, c: 3 }
+
+// 忽略多个属性
+console.log(omit(object, ['a', 'c']));
+// { b: '2' }
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| obj | 源对象 | ObjectType | - |
+| keys | 要忽略的属性数组 | string[] | - |
+
+## throttle
+
+创建一个节流函数。
+
+- 示例
+
+```ts
+import { throttle } from '@antv/util';
+
+// 基本使用
+const handler = throttle(() => {
+ console.log('Throttled');
+}, 200);
+
+window.addEventListener('resize', handler);
+
+// 自定义选项
+const scroll = throttle(() => {
+ console.log('Scrolled');
+}, 300, {
+ leading: false, // 禁用第一次立即执行
+ trailing: true // 启用最后一次延迟执行
+});
+
+// 取消节流
+handler.cancel();
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| func | 要节流的函数 | Function | - |
+| wait | 等待时间(ms) | number | - |
+| options | 配置选项 | OptionsType | {} |
+
+## toArray
+
+将类数组对象转换为数组。
+
+- 示例
+
+```ts
+import { toArray } from '@antv/util';
+
+// 类数组对象
+function example() {
+ console.log(toArray(arguments));
+}
+example(1, 2, 3); // [1, 2, 3]
+
+// 字符串
+console.log(toArray('abc')); // ['a', 'b', 'c']
+
+// 无效输入
+console.log(toArray(null)); // []
+console.log(toArray(123)); // []
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| value | 要转换的值 | any | - |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| result | 转换后的数组 | any[] | - |
+
+## toString
+
+将值转换为字符串,处理 null 和 undefined。
+
+- 示例
+
+```ts
+import { toString } from '@antv/util';
+
+console.log(toString(123)); // "123"
+console.log(toString('abc')); // "abc"
+console.log(toString(null)); // ""
+console.log(toString(undefined)); // ""
+console.log(toString({})); // "[object Object]"
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| value | 要转换的值 | any | - |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| result | 转换后的字符串 | string | - |
+
+## uniqueId
+
+生成带前缀的唯一 ID。
+
+- 示例
+
+```ts
+import { uniqueId } from '@antv/util';
+
+console.log(uniqueId()); // "g1"
+console.log(uniqueId()); // "g2"
+console.log(uniqueId('user_')); // "user_1"
+console.log(uniqueId('user_')); // "user_2"
+
+// 不同前缀独立计数
+console.log(uniqueId('a_')); // "a_1"
+console.log(uniqueId('b_')); // "b_1"
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| prefix | ID 前缀 | string | 'g' |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| result | 唯一 ID | string | - |
+
+## size
+
+获取数组、类数组或对象的大小。
+
+- 示例
+
+```ts
+import { size } from '@antv/util';
+
+// 数组
+console.log(size([1, 2, 3])); // 3
+
+// 字符串
+console.log(size('abc')); // 3
+
+// 对象
+console.log(size({ a: 1, b: 2 })); // 2
+
+// 类数组对象
+console.log(size({ length: 3 })); // 3
+
+// 空值
+console.log(size(null)); // 0
+console.log(size(undefined)); // 0
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| o | 要计算大小的集合 | unknown | - |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| result | 集合的大小 | number | - |
diff --git a/docs/api/math.md b/docs/api/math.md
new file mode 100644
index 0000000..6f073ea
--- /dev/null
+++ b/docs/api/math.md
@@ -0,0 +1,146 @@
+# 数学 `math` 相关函数
+
+> 一些数学计算相关函数,目前主要是图形拾取和碰撞相关。
+
+- [isPointInPolygon](#ispointinpolygon) - 判断点是否在多边形内部(射线法)
+- [isPolygonsIntersect](#ispolygonsintersect) - 判断两个多边形是否相交
+
+## isPointInPolygon
+
+判断点是否在多边形内部(射线法)。
+
+- 使用射线法判断点是否在多边形内部
+- 支持判断点是否在多边形边上
+- 处理特殊情况(如点数少于 3 个的情况)
+- 使用容差处理浮点数精度问题
+
+```ts
+import { isPointInPolygon } from '@antv/util';
+
+// 定义一个矩形多边形
+const rectangle = [
+ [0, 0], // 左上
+ [100, 0], // 右上
+ [100, 100], // 右下
+ [0, 100] // 左下
+];
+
+// 判断点是否在多边形内
+console.log(isPointInPolygon(rectangle, 50, 50)); // true (在内部)
+console.log(isPointInPolygon(rectangle, 0, 0)); // true (在顶点上)
+console.log(isPointInPolygon(rectangle, 50, 0)); // true (在边上)
+console.log(isPointInPolygon(rectangle, 150, 150)); // false (在外部)
+
+// 定义一个三角形
+const triangle = [
+ [0, 0],
+ [100, 0],
+ [50, 100]
+];
+
+// 测试三角形的点
+console.log(isPointInPolygon(triangle, 50, 50)); // true
+console.log(isPointInPolygon(triangle, 0, 50)); // false
+
+// 特殊情况测试
+const invalidPolygon = [[0, 0], [100, 100]]; // 少于3个点
+console.log(isPointInPolygon(invalidPolygon, 50, 50)); // false
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| points | 多边形顶点数组 | number[][] | - | 多边形的顶点坐标数组 |
+| x | 检测点的 x 坐标 | number | - | 待检测点的 x 坐标 |
+| y | 检测点的 y 坐标 | number | - | 待检测点的 y 坐标 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 判断结果 | boolean | - | 点是否在多边形内部或边上 |
+
+- 注意事项
+
+1. 多边形顶点数量必须大于等于 3 个
+2. 支持凸多边形和凹多边形
+3. 边界点被认为是在多边形内部
+4. 使用容差处理浮点数精度问题
+5. 多边形顶点按顺序排列(顺时针或逆时针)
+
+
+## isPolygonsIntersect
+
+判断两个多边形是否相交。
+
+- 判断两个多边形是否存在交集
+- 使用包围盒快速判断是否可能相交
+- 检查点是否在另一个多边形内部
+- 检查线段是否相交
+- 支持凸多边形和凹多边形
+
+```ts
+import { isPolygonsIntersect } from '@antv/util';
+
+// 定义两个矩形
+const rectangle1 = [
+ [0, 0],
+ [100, 0],
+ [100, 100],
+ [0, 100]
+];
+
+const rectangle2 = [
+ [50, 50],
+ [150, 50],
+ [150, 150],
+ [50, 150]
+];
+
+// 相交的情况
+console.log(isPolygonsIntersect(rectangle1, rectangle2)); // true
+
+// 不相交的情况
+const rectangle3 = [
+ [200, 200],
+ [300, 200],
+ [300, 300],
+ [200, 300]
+];
+console.log(isPolygonsIntersect(rectangle1, rectangle3)); // false
+
+// 一个多边形包含另一个的情况
+const smallRect = [
+ [25, 25],
+ [75, 25],
+ [75, 75],
+ [25, 75]
+];
+console.log(isPolygonsIntersect(rectangle1, smallRect)); // true
+
+// 特殊情况测试
+const invalidPolygon = [[0, 0]]; // 单点
+console.log(isPolygonsIntersect(rectangle1, invalidPolygon)); // false
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| points1 | 第一个多边形的顶点数组 | number[][] | - |
+| points2 | 第二个多边形的顶点数组 | number[][] | - |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 |
+|---------|------|------|---------|
+| result | 两个多边形是否相交 | boolean | - |
+
+- 注意事项
+
+1. 多边形顶点数量必须大于等于 2 个
+2. 支持凸多边形和凹多边形
+3. 包围盒检测可以提高性能
+4. 一个多边形完全包含另一个多边形也被视为相交
+5. 边界接触也被视为相交
diff --git a/docs/api/matrix.md b/docs/api/matrix.md
new file mode 100644
index 0000000..605ed80
--- /dev/null
+++ b/docs/api/matrix.md
@@ -0,0 +1,315 @@
+# 矩阵 `matrix` 相关函数
+
+> 和可视化 matrix 相关的函数,向量夹角、距离、变换等等。
+
+- [angleTo](#angleto) - 计算两个二维向量之间的夹角
+- [direction](#direction) - 计算两个向量之间夹角的方向(顺时针或逆时针)
+- [transform](#transform) - 根据变换操作序列对矩阵进行变换
+- [vertical](#vertical) - 计算二维向量的垂直向量。
+
+## angleTo
+
+计算两个二维向量之间的夹角。
+
+- 计算两个二维向量之间的夹角
+- 支持顺时针和逆时针方向的角度计算
+- 使用 gl-matrix 库进行向量计算
+- 返回弧度值(0 到 2π)
+
+```ts
+import { angleTo } from '@antv/util';
+
+// 基本向量夹角
+const v1: [number, number] = [1, 0];
+const v2: [number, number] = [0, 1];
+console.log(angleTo(v1, v2)); // π/2 (90度)
+console.log(angleTo(v1, v2, true)); // 3π/2 (270度)
+
+// 相同方向的向量
+const v3: [number, number] = [1, 0];
+const v4: [number, number] = [2, 0];
+console.log(angleTo(v3, v4)); // 0
+
+// 相反方向的向量
+const v5: [number, number] = [1, 0];
+const v6: [number, number] = [-1, 0];
+console.log(angleTo(v5, v6)); // π (180度)
+
+// 实际应用示例
+function rotateVector(vector: [number, number], angle: number): [number, number] {
+ const cos = Math.cos(angle);
+ const sin = Math.sin(angle);
+ return [
+ vector[0] * cos - vector[1] * sin,
+ vector[0] * sin + vector[1] * cos
+ ];
+}
+
+// 计算旋转后的向量夹角
+const original: [number, number] = [1, 0];
+const rotated = rotateVector(original, Math.PI / 4); // 旋转45度
+console.log(angleTo(original, rotated)); // π/4 (45度)
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 详细说明 |
+|---------|------|------|---------|----------|
+| v1 | 第一个向量 | [number, number] | - | 起始向量 |
+| v2 | 第二个向量 | [number, number] | - | 目标向量 |
+| direct | 是否按逆时针方向计算 | boolean | false | 是否逆时针计算角度 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 详细说明 |
+|---------|------|------|---------|----------|
+| angle | 夹角弧度值 | number | - | 两个向量之间的夹角(0 到 2π) |
+
+- 注意事项
+
+1. 返回值是弧度制(0 到 2π)
+2. 默认顺时针方向计算角度
+3. direct 参数可以改变角度的计算方向
+4. 使用了 gl-matrix 库的 vec2.angle 计算基础角度
+5. 零向量可能导致计算结果不准确
+
+## direction
+
+计算两个向量之间夹角的方向(顺时针或逆时针)。
+
+- 判断从向量 v1 到向量 v2 的旋转方向
+- 使用向量叉积的方法计算方向
+- 返回值大于等于 0 表示顺时针方向
+- 返回值小于 0 表示逆时针方向
+
+```ts
+import { direction } from '@antv/util';
+
+// 基本方向判断
+const v1 = [1, 0];
+const v2 = [0, 1];
+console.log(direction(v1, v2)); // -1 (逆时针)
+
+const v3 = [0, -1];
+console.log(direction(v1, v3)); // 1 (顺时针)
+
+// 共线向量
+const v4 = [2, 0];
+console.log(direction(v1, v4)); // 0 (共线)
+
+// 实际应用示例
+function getRotationDirection(startAngle: number, endAngle: number): string {
+ const start = [Math.cos(startAngle), Math.sin(startAngle)];
+ const end = [Math.cos(endAngle), Math.sin(endAngle)];
+
+ return direction(start, end) >= 0 ? '顺时针' : '逆时针';
+}
+
+// 测试不同角度的旋转方向
+console.log(getRotationDirection(0, Math.PI / 2)); // '逆时针'
+console.log(getRotationDirection(0, -Math.PI / 2)); // '顺时针'
+
+// 在动画中使用
+function animateRotation(element: HTMLElement, targetAngle: number) {
+ const currentVector = [1, 0];
+ const targetVector = [
+ Math.cos(targetAngle),
+ Math.sin(targetAngle)
+ ];
+
+ const isClockwise = direction(currentVector, targetVector) >= 0;
+ // 根据方向选择最短路径旋转
+ element.style.transition = 'transform 1s';
+ element.style.transform = `rotate(${isClockwise ? '' : '-'}${targetAngle}rad)`;
+}
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| v1 | 第一个向量 | number[] | - | 起始向量 |
+| v2 | 第二个向量 | number[] | - | 目标向量 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 方向判断值 | number | - | >=0 表示顺时针,<0 表示逆时针 |
+
+- 注意事项
+
+1. 向量应该是二维的
+2. 返回值的正负表示旋转方向
+3. 返回值为 0 表示向量共线
+4. 计算基于向量叉积
+5. 坐标系采用数学标准坐标系(y 轴向上为正)
+
+## transform
+
+根据变换操作序列对矩阵进行变换。
+
+- 支持平移(translate)、缩放(scale)、旋转(rotate)和矩阵乘法(multiply)操作
+- 使用 gl-matrix 库进行矩阵运算
+- 按顺序执行变换操作
+- 支持初始矩阵输入
+- 所有变换都是左乘方式进行
+
+```ts
+import { transform } from '@antv/util';
+
+// 基本变换示例
+const matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; // 单位矩阵
+
+// 平移变换
+const translated = transform(matrix, [
+ ['t', 100, 200] // 向右平移100,向下平移200
+]);
+
+// 缩放变换
+const scaled = transform(matrix, [
+ ['s', 2, 3] // x方向放大2倍,y方向放大3倍
+]);
+
+// 旋转变换
+const rotated = transform(matrix, [
+ ['r', Math.PI / 4] // 旋转45度
+]);
+
+// 组合变换
+const combined = transform(matrix, [
+ ['t', 100, 100], // 先平移
+ ['r', Math.PI / 2], // 再旋转90度
+ ['s', 2, 2] // 最后放大2倍
+]);
+
+// 实际应用示例
+function createTransformMatrix(x: number, y: number, rotation: number, scale: number) {
+ return transform(null, [
+ ['t', x, y],
+ ['r', rotation],
+ ['s', scale, scale]
+ ]);
+}
+
+// 创建一个元素的变换矩阵
+const elementTransform = createTransformMatrix(100, 100, Math.PI / 4, 2);
+console.log(elementTransform);
+
+// 应用到DOM元素
+function applyMatrixToElement(element: HTMLElement, matrix: number[]) {
+ const cssMatrix = `matrix(${matrix[0]}, ${matrix[1]}, ${matrix[3]}, ${matrix[4]}, ${matrix[2]}, ${matrix[5]})`;
+ element.style.transform = cssMatrix;
+}
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| m | 初始矩阵 | number[] | [1,0,0,0,1,0,0,0,1] | 初始变换矩阵 |
+| actions | 变换操作数组 | any[][] | - | 变换操作序列 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| matrix | 变换后的矩阵 | number[] | - | 经过变换后的矩阵 |
+
+- 变换操作类型
+
+| 操作符 | 说明 | 参数格式 | 示例 |
+|---------|------|------|---------|
+| t | 平移 | ['t', x, y] | ['t', 100, 200] |
+| s | 缩放 | ['s', sx, sy] | ['s', 2, 3] |
+| r | 旋转 | ['r', angle] | ['r', Math.PI/4] |
+| m | 矩阵乘法 | ['m', matrix] | ['m', [1,0,0,0,1,0,0,0,1]] |
+
+- 注意事项
+
+1. 变换操作按数组顺序依次执行
+2. 所有变换都是左乘方式
+3. 旋转角度使用弧度制
+4. 矩阵使用 3x3 的形式存储
+5. 如果不提供初始矩阵,默认使用单位矩阵
+
+## vertical
+
+> 计算二维向量的垂直向量。
+
+- 计算给定二维向量的垂直向量
+- 支持顺时针和逆时针方向的垂直向量计算
+- 可以将结果存储在指定的数组中
+- 返回垂直向量的引用
+
+```ts
+import { vertical } from '@antv/util';
+
+// 基本使用
+const v = [1, 0]; // 原始向量
+const out = [0, 0]; // 用于存储结果的数组
+
+// 逆时针垂直向量
+vertical(out, v, true);
+console.log(out); // [0, -1]
+
+// 顺时针垂直向量
+vertical(out, v, false);
+console.log(out); // [0, 1]
+
+// 对任意向量求垂直向量
+const v2 = [3, 4];
+const perpendicular = [0, 0];
+vertical(perpendicular, v2, true);
+console.log(perpendicular); // [4, -3]
+
+// 实际应用示例
+function createPerpendicularLine(point: number[], direction: number[], length: number) {
+ const perpendicular = [0, 0];
+ vertical(perpendicular, direction, true);
+
+ // 标准化垂直向量
+ const magnitude = Math.sqrt(perpendicular[0]**2 + perpendicular[1]**2);
+ perpendicular[0] = (perpendicular[0] / magnitude) * length;
+ perpendicular[1] = (perpendicular[1] / magnitude) * length;
+
+ return {
+ start: [
+ point[0] - perpendicular[0]/2,
+ point[1] - perpendicular[1]/2
+ ],
+ end: [
+ point[0] + perpendicular[0]/2,
+ point[1] + perpendicular[1]/2
+ ]
+ };
+}
+
+// 使用示例
+const point = [100, 100];
+const direction = [1, 0];
+const perpendicularLine = createPerpendicularLine(point, direction, 50);
+console.log(perpendicularLine);
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| out | 结果向量 | number[] | - | 用于存储结果的数组 |
+| v | 原始向量 | number[] | - | 需要计算垂直向量的原始向量 |
+| flag | 方向标志 | boolean | - | true 表示逆时针,false 表示顺时针 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 垂直向量 | number[] | - | 计算得到的垂直向量(与 out 相同) |
+
+- 注意事项
+
+1. 输入向量必须是二维的
+2. out 数组会被修改
+3. 返回的是 out 数组的引用
+4. flag 为 true 时逆时针旋转 90 度
+5. flag 为 false 时顺时针旋转 90 度
diff --git a/docs/api/path.md b/docs/api/path.md
new file mode 100644
index 0000000..b045894
--- /dev/null
+++ b/docs/api/path.md
@@ -0,0 +1,5968 @@
+# 图形 `path` 相关方法
+
+> 和 SVG Path 相关的一些方法,覆盖面很广。
+
+- parser
+ - [isSpace](#isspace) - 判断字符是否为空白字符。
+ - [isPathCommand](#ispathcommand) - 判断字符是否为 SVG 路径命令。
+ - [isDigitStart](#isdigitstart) - 判断字符是否为数字的起始字符(包括数字、正负号和小数点)。
+ - [isDigit](#isdigit) - 判断字符是否为数字字符(0-9)。
+ - [isArcCommand](#isarccommand) - 判断字符是否为 SVG 路径中的弧线命令(A/a)。
+ - [finalizeSegment](#finalizesegment) - 完成 SVG 路径片段的解析,并将解析结果添加到路径段数组中。
+ - [parsePathString](#parsepathstring) - 将 SVG 路径字符串解析为路径片段数组。
+ - [scanFlag](#scanflag) - 扫描并验证 SVG 路径中弧线命令(A/a)的标志位参数。
+ - [scanParam](#scanparam) - 扫描并验证 SVG 路径中的数值参数。
+ - [scanSegment](#scansegment) - 扫描并解析 SVG 路径中的单个路径片段。
+ - [skipSpaces](#skipspaces) - 跳过路径字符串中的空白字符。
+
+- process
+ - [arcToCubic](#arctocubic) - 将 SVG 路径中的弧线命令(A)转换为三次贝塞尔曲线命令(C)。
+ - [clonePath](#clonepath) - 深度克隆路径数组,创建路径数据的完整副本。
+ - [fixArc](#fixarc) - 修复和处理弧线命令转换后的路径数组。
+ - [lineToCubic](#linetocubic) - 将直线转换为三次贝塞尔曲线。
+ - [normalizePath](#normalizepath) - 将 SVG 路径标准化为统一格式的路径数组。
+ - [normalizeSegment](#normalizesegment) - 标准化单个路径段,将特殊命令转换为基本命令(如 H/V -> L, T -> Q)。
+ - [quadToCubic](#quadtocubic) - 将二次贝塞尔曲线转换为三次贝塞尔曲线。
+ - [reverseCurve](#reversecurve) - 反转基于贝塞尔曲线的路径数组。
+ - [roundPath](#roundpath) - 对路径数组中的数值进行精度舍入。
+ - [segmentToCubic](#segmenttocubic) - 将各种路径段转换为三次贝塞尔曲线段。
+
+- util
+ - [distanceSquareRoot](#distancesquareroot) - 计算两点之间的欧几里得距离。
+ - [equalizeSegments](#equalizesegments) - 平衡两个路径的段数,使它们具有相同数量的贝塞尔曲线段。
+ - [getDrawDirection](#getdrawdirection) - 判断路径的绘制方向(顺时针或逆时针)。
+ - [getPathArea](#getpatharea) - 计算路径的面积,支持复杂的贝塞尔曲线路径。
+ - [getPathBBoxTotalLength](#getpathbboxtotallength) - 计算路径的边界框和总长度信息。
+ - [getPathBBox](#getpathbbox) - 获取路径的边界框(Bounding Box)信息。
+ - [getPointAtLength](#getpointatlength) - 获取路径上指定距离处的坐标点。
+ - [getPropertiesAtLength](#getpropertiesatlength) - 获取路径上指定距离处的段属性信息。
+ - [getPropertiesAtPoint](#getpropertiesatpoint) - 获取路径上距离指定点最近的位置及其属性信息。
+ - [getRotatedCurve](#getrotatedcurve) - 获取最佳旋转匹配的曲线路径。
+ - [getTotalLength](#gettotallength) - 计算路径的总长度。
+ - [isAbsoluteArray](#isabsolutearray) - 判断路径数组是否全部由绝对坐标命令组成。
+ - [isCurveArray](#iscurvearray) - 判断路径数组是否全部由三次贝塞尔曲线段(C)和移动命令(M)组成。
+ - [isNormalizedArray](#isnormalizedarray) - 判断路径数组是否为标准化的绝对坐标路径(不包含简写命令)。
+ - [isPathArray](#ispatharray) - 判断是否为有效的路径数组。
+ - [isPointInStroke](#ispointinstroke) - 判断点是否在路径的描边上。
+ - [midPoint](#midpoint) - 计算两点之间的中间点或按比例插值点。
+ - [pathLengthFactory](#pathlengthfactory) - 路径长度计算工厂函数,用于计算路径的长度、指定距离的点位置和边界框。
+ - [rotateVector](#rotatevector) - 旋转二维向量,根据给定角度计算向量的新坐标。
+ - [segmentArcFactory](#segmentarcfactory) - 计算弧线段的长度、指定距离的点位置和边界框。
+ - [segmentLineFactory](#segmentlinefactory) - 计算直线段的长度、指定距离的点位置和边界框。
+ - [segmentCubicFactory](#segmentcubicfactory) - 计算三次贝塞尔曲线段的长度、指定距离的点位置和边界框。
+ - [segmentQuadFactory](#segmentquadfactory) - 计算二次贝塞尔曲线段的长度、指定距离的点位置和边界框。
+
+- convert
+ - [path2Absolute](#path2absolute) - 将路径数组或路径字符串转换为绝对坐标路径数组。
+ - [path2Array](#path2array) - 将 SVG 路径字符串转换为标准的路径数组格式。
+ - [path2Curve](#path2curve) - 将 SVG 路径转换为三次贝塞尔曲线路径。
+ - [path2String](#path2string) - 将路径数组转换为 SVG 路径字符串,支持数值精度控制。
+
+
+## isSpace
+
+判断字符是否为空白字符。
+
+- 检测字符是否为空白字符
+- 支持所有标准空白字符
+- 支持特殊 Unicode 空白字符
+- 包含换行符和行终止符
+- 使用字符的 Unicode 编码进行判断
+
+```ts
+import { isSpace } from '@antv/util';
+
+// 基本空白字符测试
+console.log(isSpace(' '.charCodeAt(0))); // true (普通空格 0x20)
+console.log(isSpace('\t'.charCodeAt(0))); // true (制表符 0x09)
+console.log(isSpace('\n'.charCodeAt(0))); // true (换行符 0x0a)
+console.log(isSpace('\r'.charCodeAt(0))); // true (回车符 0x0d)
+
+// 特殊空白字符测试
+console.log(isSpace(0xa0)); // true (不间断空格 NBSP)
+console.log(isSpace(0x2000)); // true (en quad)
+console.log(isSpace(0x3000)); // true (表意文字空格)
+
+// 非空白字符测试
+console.log(isSpace('a'.charCodeAt(0))); // false
+console.log(isSpace('1'.charCodeAt(0))); // false
+
+// 实际应用示例
+function trimString(str: string): string {
+ let start = 0;
+ let end = str.length;
+
+ // 去除开头空白
+ while (start < end && isSpace(str.charCodeAt(start))) {
+ start++;
+ }
+
+ // 去除结尾空白
+ while (end > start && isSpace(str.charCodeAt(end - 1))) {
+ end--;
+ }
+
+ return str.slice(start, end);
+}
+
+// 使用示例
+console.log(trimString(' hello ')); // "hello"
+console.log(trimString('\t\nhello\r\n')); // "hello"
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| ch | 字符编码 | number | - | 要检查的字符的 Unicode 编码值 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 判断结果 | boolean | - | 是否为空白字符 |
+
+- 支持的空白字符
+
+| 类型 | Unicode | 说明 |
+|---------|------|------|
+| 普通空格 | 0x20 | 标准空格 |
+| 制表符 | 0x09 | 水平制表符 |
+| 换行符 | 0x0a | 换行 |
+| 回车符 | 0x0d | 回车 |
+| 行终止符 | 0x2028, 0x2029 | 行分隔符,段落分隔符 |
+| 特殊空格 | 0x1680-0xfeff | 各种 Unicode 空白字符 |
+
+
+## isPathCommand
+
+判断字符是否为 SVG 路径命令。
+
+```ts
+import { isPathCommand } from '@antv/util';
+
+// 基本命令测试
+console.log(isPathCommand('M'.charCodeAt(0))); // true (移动到)
+console.log(isPathCommand('m'.charCodeAt(0))); // true (相对移动到)
+console.log(isPathCommand('L'.charCodeAt(0))); // true (画线到)
+console.log(isPathCommand('z'.charCodeAt(0))); // true (闭合路径)
+
+// 曲线命令测试
+console.log(isPathCommand('C'.charCodeAt(0))); // true (三次贝塞尔曲线)
+console.log(isPathCommand('Q'.charCodeAt(0))); // true (二次贝塞尔曲线)
+console.log(isPathCommand('A'.charCodeAt(0))); // true (弧线)
+
+// 非命令字符测试
+console.log(isPathCommand('X'.charCodeAt(0))); // false
+console.log(isPathCommand('1'.charCodeAt(0))); // false
+
+// 实际应用示例
+function parsePath(pathString: string) {
+ const commands = [];
+ let current = '';
+
+ for (let i = 0; i < pathString.length; i++) {
+ const code = pathString.charCodeAt(i);
+ if (isPathCommand(code)) {
+ if (current) {
+ commands.push(current);
+ }
+ current = pathString[i];
+ } else {
+ current += pathString[i];
+ }
+ }
+ if (current) {
+ commands.push(current);
+ }
+ return commands;
+}
+
+// 使用示例
+console.log(parsePath('M10 10L20 20')); // ['M10 10', 'L20 20']
+console.log(parsePath('m5,5 l10,10')); // ['m5,5', 'l10,10']
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| code | 字符编码 | number | - | 要检查的字符的 Unicode 编码值 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 判断结果 | boolean | - | 是否为路径命令 |
+
+- 支持的路径命令
+
+| 命令 | 说明 | 大写 | 小写 |
+|---------|------|------|------|
+| M/m | 移动到 | 绝对坐标 | 相对坐标 |
+| Z/z | 闭合路径 | 绝对坐标 | 相对坐标 |
+| L/l | 画线到 | 绝对坐标 | 相对坐标 |
+| H/h | 水平线到 | 绝对坐标 | 相对坐标 |
+| V/v | 垂直线到 | 绝对坐标 | 相对坐标 |
+| C/c | 三次贝塞尔曲线 | 绝对坐标 | 相对坐标 |
+| S/s | 平滑三次贝塞尔曲线 | 绝对坐标 | 相对坐标 |
+| Q/q | 二次贝塞尔曲线 | 绝对坐标 | 相对坐标 |
+| T/t | 平滑二次贝塞尔曲线 | 绝对坐标 | 相对坐标 |
+| A/a | 弧线 | 绝对坐标 | 相对坐标 |
+
+
+## isDigitStart
+
+判断字符是否为数字的起始字符(包括数字、正负号和小数点)。
+
+```ts
+import { isDigitStart } from '@antv/util';
+
+// 数字测试
+console.log(isDigitStart('0'.charCodeAt(0))); // true
+console.log(isDigitStart('9'.charCodeAt(0))); // true
+console.log(isDigitStart('5'.charCodeAt(0))); // true
+
+// 符号测试
+console.log(isDigitStart('+'.charCodeAt(0))); // true
+console.log(isDigitStart('-'.charCodeAt(0))); // true
+console.log(isDigitStart('.'.charCodeAt(0))); // true
+
+// 非数字起始字符测试
+console.log(isDigitStart('a'.charCodeAt(0))); // false
+console.log(isDigitStart(' '.charCodeAt(0))); // false
+
+// 实际应用示例
+function parseNumber(str: string): { value: number, length: number } {
+ if (!isDigitStart(str.charCodeAt(0))) {
+ return { value: NaN, length: 0 };
+ }
+
+ let i = 1;
+ while (i < str.length && /[\d.]/.test(str[i])) {
+ i++;
+ }
+
+ return {
+ value: parseFloat(str.slice(0, i)),
+ length: i
+ };
+}
+
+// 使用示例
+console.log(parseNumber('123.45abc')); // { value: 123.45, length: 6 }
+console.log(parseNumber('-42px')); // { value: -42, length: 3 }
+console.log(parseNumber('+0.5em')); // { value: 0.5, length: 4 }
+console.log(parseNumber('.75%')); // { value: 0.75, length: 3 }
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| code | 字符编码 | number | - | 要检查的字符的 Unicode 编码值 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 判断结果 | boolean | - | 是否为数字起始字符 |
+
+- 支持的字符
+
+| 字符类型 | Unicode | 说明 |
+|---------|------|------|
+| 数字 | 48-57 | 0-9 |
+| 正号 | 0x2b | + |
+| 负号 | 0x2d | - |
+| 小数点 | 0x2e | . |
+
+
+## isDigit
+
+判断字符是否为数字字符(0-9)。
+
+```ts
+import { isDigit } from '@antv/util';
+
+// 数字测试
+console.log(isDigit('0'.charCodeAt(0))); // true
+console.log(isDigit('9'.charCodeAt(0))); // true
+console.log(isDigit('5'.charCodeAt(0))); // true
+
+// 非数字测试
+console.log(isDigit('.'.charCodeAt(0))); // false
+console.log(isDigit('-'.charCodeAt(0))); // false
+console.log(isDigit('a'.charCodeAt(0))); // false
+
+// 实际应用示例
+function extractNumbers(str: string): string {
+ return str
+ .split('')
+ .filter(char => isDigit(char.charCodeAt(0)))
+ .join('');
+}
+
+// 使用示例
+console.log(extractNumbers('abc123def456')); // "123456"
+console.log(extractNumbers('价格:99.99元')); // "9999"
+console.log(extractNumbers('电话:123-456-789')); // "123456789"
+
+// 数字验证示例
+function isNumericString(str: string): boolean {
+ return str.split('').every(char => isDigit(char.charCodeAt(0)));
+}
+
+console.log(isNumericString('12345')); // true
+console.log(isNumericString('12.34')); // false
+console.log(isNumericString('12a34')); // false
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| code | 字符编码 | number | - | 要检查的字符的 Unicode 编码值 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 判断结果 | boolean | - | 是否为数字字符 |
+
+- 支持的字符范围
+
+| 字符 | Unicode | 说明 |
+|---------|------|------|
+| 0-9 | 48-57 | 阿拉伯数字 |
+
+
+## isArcCommand
+
+判断字符是否为 SVG 路径中的弧线命令(A/a)。
+
+```ts
+import { isArcCommand } from '@antv/util';
+
+// 基本测试
+console.log(isArcCommand('A'.charCodeAt(0))); // true
+console.log(isArcCommand('a'.charCodeAt(0))); // true
+
+// 非弧线命令测试
+console.log(isArcCommand('L'.charCodeAt(0))); // false
+console.log(isArcCommand('M'.charCodeAt(0))); // false
+
+// 实际应用示例
+function parseArcSegment(pathData: string) {
+ const segments = [];
+ let current = '';
+
+ for (let i = 0; i < pathData.length; i++) {
+ const code = pathData.charCodeAt(i);
+ if (isArcCommand(code)) {
+ if (current) {
+ segments.push(current);
+ }
+ current = pathData[i];
+ } else {
+ current += pathData[i];
+ }
+ }
+ if (current) {
+ segments.push(current);
+ }
+ return segments.filter(seg => isArcCommand(seg.charCodeAt(0)));
+}
+
+// 使用示例
+const path = 'M10 10 A50 50 0 0 1 60 60 a30 30 0 0 1 30 30';
+console.log(parseArcSegment(path));
+// ['A50 50 0 0 1 60 60', 'a30 30 0 0 1 30 30']
+
+// 弧线参数解析示例
+function parseArcParams(arcCommand: string) {
+ const params = arcCommand.slice(1).trim().split(/[\s,]+/);
+ return {
+ command: arcCommand[0],
+ rx: parseFloat(params[0]),
+ ry: parseFloat(params[1]),
+ xAxisRotation: parseFloat(params[2]),
+ largeArcFlag: parseInt(params[3]),
+ sweepFlag: parseInt(params[4]),
+ x: parseFloat(params[5]),
+ y: parseFloat(params[6])
+ };
+}
+
+// 解析弧线命令
+const arc = 'A50 50 0 0 1 60 60';
+console.log(parseArcParams(arc));
+// {
+// command: 'A',
+// rx: 50,
+// ry: 50,
+// xAxisRotation: 0,
+// largeArcFlag: 0,
+// sweepFlag: 1,
+// x: 60,
+// y: 60
+// }
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| code | 字符编码 | number | - | 要检查的字符的 Unicode 编码值 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 判断结果 | boolean | - | 是否为弧线命令 |
+
+- 弧线命令格式
+
+| 命令 | 参数 | 说明 |
+|---------|------|------|
+| A/a | rx ry x-axis-rotation large-arc-flag sweep-flag x y | 绘制弧线 |
+
+
+## finalizeSegment
+
+完成 SVG 路径片段的解析,并将解析结果添加到路径段数组中。
+
+- 处理路径命令和对应的参数
+- 特殊处理 moveTo (M/m) 命令
+- 根据命令类型提取正确数量的参数
+- 支持命令的重复使用
+- 将解析结果添加到路径段数组中
+
+```ts
+import { finalizeSegment } from '@antv/util';
+
+// 路径解析器示例
+const pathParser: PathParser = {
+ pathValue: 'M10 10 L20 20',
+ segmentStart: 0,
+ data: [10, 10, 20, 20],
+ segments: []
+};
+
+// 基本使用
+finalizeSegment(pathParser);
+console.log(pathParser.segments);
+// [['M', 10, 10], ['L', 20, 20]]
+
+// MoveTo命令的特殊处理
+const moveParser: PathParser = {
+ pathValue: 'M10 10 20 20 30 30',
+ segmentStart: 0,
+ data: [10, 10, 20, 20, 30, 30],
+ segments: []
+};
+
+finalizeSegment(moveParser);
+console.log(moveParser.segments);
+// [['M', 10, 10], ['l', 20, 20], ['l', 30, 30]]
+
+// 实际应用示例
+function parsePath(pathString: string) {
+ const parser: PathParser = {
+ pathValue: pathString,
+ segmentStart: 0,
+ data: [],
+ segments: []
+ };
+
+ // 模拟数据收集
+ const numbers = pathString.match(/[-+]?[0-9]*\.?[0-9]+/g);
+ if (numbers) {
+ parser.data = numbers.map(Number);
+ }
+
+ finalizeSegment(parser);
+ return parser.segments;
+}
+
+// 使用示例
+console.log(parsePath('M10 10 L20 20 L30 30'));
+// [['M', 10, 10], ['L', 20, 20], ['L', 30, 30]]
+
+console.log(parsePath('M10 10 20 20 30 30'));
+// [['M', 10, 10], ['l', 20, 20], ['l', 30, 30]]
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| path | 路径解析器对象 | PathParser | - | 包含路径解析状态的对象 |
+
+- PathParser 接口
+
+| 属性 | 说明 | 类型 | 中文说明 |
+|---------|------|------|---------|
+| pathValue | 路径字符串 | string | 原始路径字符串 |
+| segmentStart | 片段起始位置 | number | 当前片段的起始索引 |
+| data | 数值数组 | number[] | 收集的数值参数 |
+| segments | 路径片段数组 | any[][] | 解析后的路径片段数组 |
+
+
+## parsePathString
+
+将 SVG 路径字符串解析为路径片段数组。
+
+- 功能说明
+
+- 解析 SVG 路径字符串为标准格式的数组
+- 支持所有标准 SVG 路径命令
+- 处理空格和分隔符
+- 错误处理和验证
+- 支持已经是数组格式的输入
+
+```ts
+import { parsePathString } from '@antv/util';
+
+// 基本路径解析
+console.log(parsePathString('M10 10L20 20'));
+// [['M', 10, 10], ['L', 20, 20]]
+
+// 复杂路径解析
+console.log(parsePathString('M10,10 L20,20 H30 V40 Z'));
+// [
+// ['M', 10, 10],
+// ['L', 20, 20],
+// ['H', 30],
+// ['V', 40],
+// ['Z']
+// ]
+
+// 曲线命令解析
+console.log(parsePathString('M0,0 C10,10 20,10 30,0'));
+// [
+// ['M', 0, 0],
+// ['C', 10, 10, 20, 10, 30, 0]
+// ]
+
+// 已经是数组格式的输入
+const pathArray = [['M', 10, 10], ['L', 20, 20]];
+console.log(parsePathString(pathArray));
+// [['M', 10, 10], ['L', 20, 20]]
+
+// 实际应用示例
+function createPathObject(pathString: string) {
+ const segments = parsePathString(pathString);
+ return {
+ moveTo: segments.filter(seg => seg[0] === 'M'),
+ lineTo: segments.filter(seg => seg[0] === 'L'),
+ closePath: segments.filter(seg => seg[0] === 'Z'),
+ curves: segments.filter(seg => ['C', 'S', 'Q', 'T'].includes(seg[0]))
+ };
+}
+
+// 使用示例
+const path = 'M10,10 L20,20 C30,30 40,40 50,50 Z';
+console.log(createPathObject(path));
+// {
+// moveTo: [['M', 10, 10]],
+// lineTo: [['L', 20, 20]],
+// closePath: [['Z']],
+// curves: [['C', 30, 30, 40, 40, 50, 50]]
+// }
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| pathInput | 路径输入 | PathArray \| string | - | SVG 路径字符串或路径数组 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 解析结果 | PathArray \| string | - | 路径片段数组或错误信息 |
+
+- 支持的路径命令
+
+| 命令 | 说明 | 参数格式 |
+|---------|------|------|
+| M/m | 移动到 | x, y |
+| L/l | 画线到 | x, y |
+| H/h | 水平线到 | x |
+| V/v | 垂直线到 | y |
+| C/c | 三次贝塞尔曲线 | x1, y1, x2, y2, x, y |
+| S/s | 平滑三次贝塞尔曲线 | x2, y2, x, y |
+| Q/q | 二次贝塞尔曲线 | x1, y1, x, y |
+| T/t | 平滑二次贝塞尔曲线 | x, y |
+| A/a | 弧线 | rx, ry, angle, large-arc, sweep, x, y |
+| Z/z | 闭合路径 | 无参数 |
+
+
+## scanFlag
+
+扫描并验证 SVG 路径中弧线命令(A/a)的标志位参数。
+
+- 功能说明
+
+- 验证弧线命令的标志位参数(large-arc-flag 和 sweep-flag)
+- 只接受 0 或 1 作为有效值
+- 更新解析器的参数值和索引
+- 提供详细的错误信息
+- 用于 SVG 路径解析过程
+
+```ts
+import { scanFlag } from '@antv/util';
+
+// 基本使用
+const validPath: PathParser = {
+ pathValue: 'A100 100 0 1 0 200 200',
+ index: 11, // 指向第一个标志位
+ param: 0,
+ err: ''
+};
+
+scanFlag(validPath);
+console.log(validPath.param); // 1
+console.log(validPath.index); // 12
+
+// 错误处理示例
+const invalidPath: PathParser = {
+ pathValue: 'A100 100 0 2 0 200 200',
+ index: 11,
+ param: 0,
+ err: ''
+};
+
+scanFlag(invalidPath);
+console.log(invalidPath.err);
+// "[path-util]: invalid Arc flag "2", expecting 0 or 1 at index 11"
+
+// 实际应用示例
+function parseArcFlags(arcCommand: string) {
+ const parser: PathParser = {
+ pathValue: arcCommand,
+ index: 0,
+ param: 0,
+ err: ''
+ };
+
+ // 跳过命令和前三个参数
+ const params = arcCommand.split(/[\s,]+/);
+ parser.index = arcCommand.indexOf(params[4]);
+
+ // 解析large-arc-flag
+ scanFlag(parser);
+ const largeArcFlag = parser.param;
+
+ // 跳到sweep-flag
+ parser.index = arcCommand.indexOf(params[5]);
+
+ // 解析sweep-flag
+ scanFlag(parser);
+ const sweepFlag = parser.param;
+
+ return {
+ largeArcFlag,
+ sweepFlag,
+ error: parser.err
+ };
+}
+
+// 使用示例
+console.log(parseArcFlags('A100 100 0 1 0 200 200'));
+// { largeArcFlag: 1, sweepFlag: 0, error: '' }
+
+console.log(parseArcFlags('A100 100 0 2 0 200 200'));
+// { largeArcFlag: 0, sweepFlag: 0, error: '...' }
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| path | 路径解析器对象 | PathParser | - | 包含路径解析状态的对象 |
+
+- PathParser 接口
+
+| 属性 | 说明 | 类型 | 中文说明 |
+|---------|------|------|---------|
+| pathValue | 路径字符串 | string | 原始路径字符串 |
+| index | 当前索引 | number | 当前解析位置 |
+| param | 参数值 | number | 解析得到的参数值 |
+| err | 错误信息 | string | 解析过程中的错误信息 |
+
+
+## scanParam
+
+扫描并验证 SVG 路径中的数值参数。
+
+- 功能说明
+
+- 验证路径字符串中的数值参数
+- 支持整数和浮点数
+- 支持科学计数法
+- 处理正负号
+- 提供详细的错误信息
+
+```ts
+import { scanParam } from '@antv/util';
+
+// 基本数值解析
+const path1: PathParser = {
+ pathValue: 'M100.5 200',
+ index: 1,
+ param: 0,
+ max: 10,
+ err: ''
+};
+
+scanParam(path1);
+console.log(path1.param); // 100.5
+console.log(path1.index); // 5
+
+// 科学计数法解析
+const path2: PathParser = {
+ pathValue: 'L1e2 50',
+ index: 1,
+ param: 0,
+ max: 6,
+ err: ''
+};
+
+scanParam(path2);
+console.log(path2.param); // 100
+
+// 错误处理示例
+const path3: PathParser = {
+ pathValue: 'M.e1 200',
+ index: 1,
+ param: 0,
+ max: 7,
+ err: ''
+};
+
+scanParam(path3);
+console.log(path3.err); // 包含错误信息
+
+// 实际应用示例
+function parseNumberSequence(str: string) {
+ const results = [];
+ const parser: PathParser = {
+ pathValue: str,
+ index: 0,
+ param: 0,
+ max: str.length,
+ err: ''
+ };
+
+ while (parser.index < parser.max && !parser.err) {
+ scanParam(parser);
+ if (!parser.err) {
+ results.push(parser.param);
+ }
+ // 跳过分隔符
+ parser.index++;
+ }
+
+ return {
+ values: results,
+ error: parser.err
+ };
+}
+
+// 使用示例
+console.log(parseNumberSequence('100.5 -200 1e2'));
+// { values: [100.5, -200, 100], error: '' }
+
+console.log(parseNumberSequence('100 200 abc'));
+// { values: [100, 200], error: '...' }
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| path | 路径解析器对象 | PathParser | - | 包含路径解析状态的对象 |
+
+- PathParser 接口
+
+| 属性 | 说明 | 类型 | 中文说明 |
+|---------|------|------|---------|
+| pathValue | 路径字符串 | string | 原始路径字符串 |
+| index | 当前索引 | number | 当前解析位置 |
+| param | 参数值 | number | 解析得到的参数值 |
+| max | 最大长度 | number | 字符串最大长度 |
+| err | 错误信息 | string | 解析过程中的错误信息 |
+
+- 支持的数值格式
+
+1. 整数:`123`
+2. 负数:`-123`
+3. 浮点数:`123.456`
+4. 科学计数法:`1e2`, `1.23e-4`
+
+
+## scanSegment
+
+扫描并解析 SVG 路径中的单个路径片段。
+
+- 功能说明
+
+- 解析路径命令和其参数
+- 处理特殊的弧线命令参数
+- 支持参数之间的逗号分隔
+- 跳过空白字符
+- 验证参数数量和格式
+
+```ts
+import { scanSegment } from '@antv/util';
+
+// 基本路径片段解析
+const path1: PathParser = {
+ pathValue: 'M100 200',
+ index: 0,
+ max: 8,
+ segmentStart: 0,
+ data: [],
+ segments: [],
+ param: 0,
+ err: ''
+};
+
+scanSegment(path1);
+console.log(path1.segments); // [['M', 100, 200]]
+
+// 弧线命令解析
+const path2: PathParser = {
+ pathValue: 'A100,100 0 1,0 200,200',
+ index: 0,
+ max: 20,
+ segmentStart: 0,
+ data: [],
+ segments: [],
+ param: 0,
+ err: ''
+};
+
+scanSegment(path2);
+console.log(path2.segments);
+// [['A', 100, 100, 0, 1, 0, 200, 200]]
+
+// 实际应用示例
+function parseSVGPath(pathString: string) {
+ const parser: PathParser = {
+ pathValue: pathString,
+ index: 0,
+ max: pathString.length,
+ segmentStart: 0,
+ data: [],
+ segments: [],
+ param: 0,
+ err: ''
+ };
+
+ while (parser.index < parser.max && !parser.err) {
+ scanSegment(parser);
+ }
+
+ return {
+ segments: parser.segments,
+ error: parser.err
+ };
+}
+
+// 使用示例
+console.log(parseSVGPath('M10,10 L20,20 A30,30 0 1,0 40,40 Z'));
+// {
+// segments: [
+// ['M', 10, 10],
+// ['L', 20, 20],
+// ['A', 30, 30, 0, 1, 0, 40, 40],
+// ['Z']
+// ],
+// error: ''
+// }
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| path | 路径解析器对象 | PathParser | - | 包含路径解析状态的对象 |
+
+- PathParser 接口
+
+| 属性 | 说明 | 类型 | 中文说明 |
+|---------|------|------|---------|
+| pathValue | 路径字符串 | string | 原始路径字符串 |
+| index | 当前索引 | number | 当前解析位置 |
+| max | 最大长度 | number | 字符串最大长度 |
+| segmentStart | 片段起始位置 | number | 当前片段的起始索引 |
+| data | 数值数组 | number[] | 收集的数值参数 |
+| segments | 路径片段数组 | any[][] | 解析后的路径片段数组 |
+| param | 参数值 | number | 当前解析的参数值 |
+| err | 错误信息 | string | 解析过程中的错误信息 |
+
+
+## skipSpaces
+
+跳过路径字符串中的空白字符。
+
+- 功能说明
+
+- 跳过所有类型的空白字符
+- 更新解析器的当前位置
+- 支持多种空白字符格式
+- 用于路径解析过程中的空白处理
+- 确保解析器指向有效字符
+
+```ts
+import { skipSpaces } from '@antv/util';
+
+// 基本使用
+const path1: PathParser = {
+ pathValue: 'M 100 200',
+ index: 1,
+ max: 12
+};
+
+skipSpaces(path1);
+console.log(path1.index); // 4 (跳过空格后指向'1')
+
+// 混合空白字符
+const path2: PathParser = {
+ pathValue: 'L\t\n\r100,200',
+ index: 1,
+ max: 11
+};
+
+skipSpaces(path2);
+console.log(path2.index); // 4 (跳过所有空白字符)
+
+// 实际应用示例
+function parsePathValues(pathString: string) {
+ const parser: PathParser = {
+ pathValue: pathString,
+ index: 0,
+ max: pathString.length
+ };
+
+ const values = [];
+ let currentValue = '';
+
+ while (parser.index < parser.max) {
+ // 跳过开头的空白
+ skipSpaces(parser);
+
+ // 收集数字或字符
+ while (parser.index < parser.max &&
+ !isSpace(parser.pathValue.charCodeAt(parser.index))) {
+ currentValue += parser.pathValue[parser.index];
+ parser.index++;
+ }
+
+ if (currentValue) {
+ values.push(currentValue);
+ currentValue = '';
+ }
+ }
+
+ return values;
+}
+
+// 使用示例
+console.log(parsePathValues('M 100 200 L 300 400'));
+// ['M', '100', '200', 'L', '300', '400']
+
+// 格式化路径字符串
+function formatPathString(pathString: string) {
+ const parser: PathParser = {
+ pathValue: pathString,
+ index: 0,
+ max: pathString.length
+ };
+
+ let formatted = '';
+ let lastWasCommand = false;
+
+ while (parser.index < parser.max) {
+ skipSpaces(parser);
+
+ if (parser.index < parser.max) {
+ const char = parser.pathValue[parser.index];
+ if (/[A-Za-z]/.test(char)) {
+ if (formatted && !lastWasCommand) {
+ formatted += ' ';
+ }
+ lastWasCommand = true;
+ } else {
+ if (formatted) {
+ formatted += ' ';
+ }
+ lastWasCommand = false;
+ }
+ formatted += char;
+ parser.index++;
+ }
+ }
+
+ return formatted;
+}
+
+console.log(formatPathString('M 100 200 L 300 400'));
+// "M 100 200 L 300 400"
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| path | 路径解析器对象 | PathParser | - | 包含路径解析状态的对象 |
+
+- PathParser 接口
+
+| 属性 | 说明 | 类型 | 中文说明 |
+|---------|------|------|---------|
+| pathValue | 路径字符串 | string | 原始路径字符串 |
+| index | 当前索引 | number | 当前解析位置 |
+| max | 最大长度 | number | 字符串最大长度 |
+
+
+## arcToCubic
+
+将 SVG 路径中的弧线命令(A)转换为三次贝塞尔曲线命令(C)。
+
+- 功能说明
+
+- 将椭圆弧转换为一系列三次贝塞尔曲线
+- 支持旋转角度
+- 处理大弧和小弧标志
+- 处理顺时针和逆时针方向
+- 基于 SVG 规范的实现
+
+```ts
+import { arcToCubic } from '@antv/util';
+
+// 基本弧线转换
+const result1 = arcToCubic(
+ 0, 0, // 起点 (x1, y1)
+ 100, 50, // 半径 (rx, ry)
+ 0, // 旋转角度
+ 0, // 大弧标志
+ 1, // 顺时针标志
+ 100, 0 // 终点 (x2, y2)
+);
+console.log(result1); // 返回贝塞尔曲线控制点坐标
+
+// 实际应用示例
+function createArcPath(
+ start: [number, number],
+ radius: [number, number],
+ end: [number, number],
+ options = { angle: 0, largeArc: 0, sweep: 1 }
+) {
+ const points = arcToCubic(
+ start[0], start[1],
+ radius[0], radius[1],
+ options.angle,
+ options.largeArc,
+ options.sweep,
+ end[0], end[1],
+ null
+ );
+
+ // 转换为SVG路径
+ const path = [
+ `M ${start[0]} ${start[1]}`,
+ `C ${points.slice(0, 6).join(' ')}`,
+ ];
+
+ if (points.length > 6) {
+ for (let i = 6; i < points.length; i += 6) {
+ path.push(`C ${points.slice(i, i + 6).join(' ')}`);
+ }
+ }
+
+ return path.join(' ');
+}
+
+// 使用示例
+const arcPath = createArcPath(
+ [0, 0], // 起点
+ [100, 50], // 半径
+ [100, 0], // 终点
+ { angle: 45, largeArc: 0, sweep: 1 }
+);
+console.log(arcPath);
+
+// 动画插值示例
+function interpolateArc(
+ start: [number, number],
+ end: [number, number],
+ progress: number
+) {
+ const points = arcToCubic(
+ start[0], start[1],
+ 50, 50, // 固定半径
+ 0, 0, 1,
+ end[0], end[1],
+ null
+ );
+
+ // 计算当前点位置
+ const t = progress;
+ const p0 = points.slice(0, 2);
+ const p1 = points.slice(2, 4);
+ const p2 = points.slice(4, 6);
+ const p3 = points.slice(6, 8);
+
+ return bezierPoint(p0, p1, p2, p3, t);
+}
+
+// 贝塞尔曲线点计算
+function bezierPoint(p0: number[], p1: number[], p2: number[], p3: number[], t: number) {
+ const mt = 1 - t;
+ return [
+ mt * mt * mt * p0[0] + 3 * mt * mt * t * p1[0] + 3 * mt * t * t * p2[0] + t * t * t * p3[0],
+ mt * mt * mt * p0[1] + 3 * mt * mt * t * p1[1] + 3 * mt * t * t * p2[1] + t * t * t * p3[1]
+ ];
+}
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| X1, Y1 | 起点坐标 | number | - | 弧线起点的 x,y 坐标 |
+| RX, RY | 半径 | number | - | 椭圆的 x,y 半径 |
+| angle | 旋转角度 | number | - | 椭圆的旋转角度(度) |
+| LAF | 大弧标志 | number | - | 是否选择大弧(0 或 1) |
+| SF | 顺时针标志 | number | - | 是否顺时针绘制(0 或 1) |
+| X2, Y2 | 终点坐标 | number | - | 弧线终点的 x,y 坐标 |
+| recursive | 递归参数 | number[] | - | 用于递归调用的参数数组 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| points | 控制点坐标 | number[] | - | 贝塞尔曲线的控制点坐标数组 |
+
+
+## clonePath
+
+深度克隆路径数组,创建路径数据的完整副本。
+
+- 功能说明
+
+- 创建路径数组的深度副本
+- 处理嵌套数组结构
+- 保持原始数据不变
+- 支持单个路径段和完整路径数组
+- 返回新的路径数组实例
+
+```ts
+import { clonePath } from '@antv/util';
+
+// 基本路径克隆
+const path1: PathArray = [
+ ['M', 10, 10],
+ ['L', 20, 20]
+];
+const clone1 = clonePath(path1);
+console.log(clone1); // [['M', 10, 10], ['L', 20, 20]]
+console.log(clone1 !== path1); // true
+console.log(clone1[0] !== path1[0]); // true
+
+// 单个路径段克隆
+const segment: PathSegment = ['C', 10, 20, 30, 40, 50, 60];
+const clonedSegment = clonePath(segment);
+console.log(clonedSegment); // [['C', 10, 20, 30, 40, 50, 60]]
+
+// 实际应用示例
+function transformPath(path: PathArray, dx: number, dy: number) {
+ const cloned = clonePath(path);
+ return cloned.map(segment => {
+ const [command, ...params] = segment;
+ if (command === 'H') {
+ return [command, params[0] + dx];
+ }
+ if (command === 'V') {
+ return [command, params[0] + dy];
+ }
+ return [
+ command,
+ ...params.map((p, i) => p + (i % 2 ? dy : dx))
+ ];
+ });
+}
+
+// 使用示例
+const originalPath: PathArray = [
+ ['M', 0, 0],
+ ['L', 50, 50],
+ ['H', 100],
+ ['V', 100]
+];
+
+const transformedPath = transformPath(originalPath, 10, 20);
+console.log(transformedPath);
+// [
+// ['M', 10, 20],
+// ['L', 60, 70],
+// ['H', 110],
+// ['V', 120]
+// ]
+console.log(originalPath); // 原始路径保持不变
+
+// 路径动画示例
+function createPathAnimator(path: PathArray) {
+ const pathCopy = clonePath(path);
+ let currentPath = pathCopy;
+
+ return {
+ getPath: () => currentPath,
+ transform: (dx: number, dy: number) => {
+ currentPath = transformPath(currentPath, dx, dy);
+ },
+ reset: () => {
+ currentPath = clonePath(pathCopy);
+ }
+ };
+}
+
+// 使用动画器
+const animator = createPathAnimator([
+ ['M', 0, 0],
+ ['L', 100, 100]
+]);
+
+animator.transform(10, 10);
+console.log(animator.getPath());
+// [['M', 10, 10], ['L', 110, 110]]
+
+animator.reset();
+console.log(animator.getPath());
+// [['M', 0, 0], ['L', 100, 100]]
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| path | 路径数据 | PathArray \| PathSegment | - | 要克隆的路径数组或路径段 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 克隆结果 | PathArray | - | 克隆后的新路径数组 |
+
+
+## fixArc
+
+修复和处理弧线命令转换后的路径数组。
+
+- 功能说明
+
+- 处理转换后的弧线命令
+- 将长路径段分割成多个三次贝塞尔曲线
+- 维护命令类型记录
+- 确保路径数组格式正确
+- 优化路径数据结构
+
+```ts
+import { fixArc } from '@antv/util';
+
+// 基本使用
+const pathArray: PathArray = [
+ ['M', 0, 0],
+ ['A', 10, 20, 30, 40, 50, 60, 70, 80, 90, 100] // 过长的弧线段
+];
+const commands = ['M', 'A'];
+
+fixArc(pathArray, commands, 1);
+console.log(pathArray);
+// [
+// ['M', 0, 0],
+// ['C', 10, 20, 30, 40, 50, 60],
+// ['C', 70, 80, 90, 100]
+// ]
+console.log(commands); // ['M', 'A', 'A']
+
+// 实际应用示例
+function processPath(path: PathArray) {
+ const commands: string[] = path.map(segment => segment[0]);
+
+ for (let i = 0; i < path.length; i++) {
+ if (commands[i] === 'A') {
+ fixArc(path, commands, i);
+ // 由于fixArc可能改变数组长度,需要调整索引
+ i--;
+ }
+ }
+
+ return {
+ path,
+ commands
+ };
+}
+
+// 使用示例
+const originalPath: PathArray = [
+ ['M', 0, 0],
+ ['A', 50, 50, 0, 1, 1, 100, 100, 150, 150, 200, 200]
+];
+
+const result = processPath(originalPath);
+console.log(result);
+// {
+// path: [
+// ['M', 0, 0],
+// ['C', 50, 50, 0, 100, 100],
+// ['C', 150, 150, 200, 200]
+// ],
+// commands: ['M', 'A', 'A']
+// }
+
+// 路径优化示例
+function optimizePath(path: PathArray) {
+ const commands: string[] = [];
+ const optimizedPath = path.slice();
+
+ for (let i = 0; i < optimizedPath.length; i++) {
+ commands[i] = optimizedPath[i][0];
+ if (commands[i] === 'A') {
+ fixArc(optimizedPath, commands, i);
+ }
+ }
+
+ return optimizedPath;
+}
+
+// 使用优化函数
+const complexPath: PathArray = [
+ ['M', 0, 0],
+ ['A', 100, 100, 0, 1, 1, 200, 200, 300, 300]
+];
+
+console.log(optimizePath(complexPath));
+// [
+// ['M', 0, 0],
+// ['C', 100, 100, 200, 200],
+// ['C', 300, 300]
+// ]
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| pathArray | 路径数组 | PathArray | - | 要处理的路径数组 |
+| allPathCommands | 命令数组 | string[] | - | 记录所有路径命令的数组 |
+| i | 当前索引 | number | - | 当前处理的路径段索引 |
+
+
+## lineToCubic
+
+将直线转换为三次贝塞尔曲线。
+
+- 功能说明
+
+- 将直线段转换为等效的三次贝塞尔曲线
+- 使用中点作为控制点
+- 保持视觉效果不变
+- 统一路径段表示方式
+- 简化路径处理
+
+```ts
+import { lineToCubic } from '@antv/util';
+
+// 基本使用
+const result = lineToCubic(0, 0, 100, 100);
+console.log(result); // [50, 50, 100, 100, 100, 100]
+
+// 水平线转换
+const horizontal = lineToCubic(0, 0, 100, 0);
+console.log(horizontal); // [50, 0, 100, 0, 100, 0]
+
+// 垂直线转换
+const vertical = lineToCubic(0, 0, 0, 100);
+console.log(vertical); // [0, 50, 0, 100, 0, 100]
+
+// 实际应用示例
+function convertPathToCubic(path: [number, number][]) {
+ const result = [];
+
+ for (let i = 0; i < path.length - 1; i++) {
+ const [x1, y1] = path[i];
+ const [x2, y2] = path[i + 1];
+
+ if (i === 0) {
+ result.push(['M', x1, y1]);
+ }
+
+ const cubic = lineToCubic(x1, y1, x2, y2);
+ result.push(['C', ...cubic]);
+ }
+
+ return result;
+}
+
+// 使用示例
+const points = [
+ [0, 0],
+ [100, 100],
+ [200, 0]
+];
+
+console.log(convertPathToCubic(points));
+// [
+// ['M', 0, 0],
+// ['C', 50, 50, 100, 100, 100, 100],
+// ['C', 150, 50, 200, 0, 200, 0]
+// ]
+
+// 动画插值示例
+function createLineAnimator(x1: number, y1: number, x2: number, y2: number) {
+ const cubic = lineToCubic(x1, y1, x2, y2);
+
+ return (t: number) => {
+ // 三次贝塞尔曲线插值
+ const mt = 1 - t;
+ const mt2 = mt * mt;
+ const mt3 = mt2 * mt;
+ const t2 = t * t;
+ const t3 = t2 * t;
+
+ return {
+ x: mt3 * x1 + 3 * mt2 * t * cubic[0] + 3 * mt * t2 * cubic[2] + t3 * cubic[4],
+ y: mt3 * y1 + 3 * mt2 * t * cubic[1] + 3 * mt * t2 * cubic[3] + t3 * cubic[5]
+ };
+ };
+}
+
+// 使用动画器
+const animator = createLineAnimator(0, 0, 100, 100);
+console.log(animator(0)); // { x: 0, y: 0 }
+console.log(animator(0.5)); // { x: 50, y: 50 }
+console.log(animator(1)); // { x: 100, y: 100 }
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| x1 | 起点 x 坐标 | number | - | 直线起点的 x 坐标 |
+| y1 | 起点 y 坐标 | number | - | 直线起点的 y 坐标 |
+| x2 | 终点 x 坐标 | number | - | 直线终点的 x 坐标 |
+| y2 | 终点 y 坐标 | number | - | 直线终点的 y 坐标 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| points | 控制点坐标 | number[] | - | 贝塞尔曲线的控制点坐标数组 [cx1,cy1,cx2,cy2,x,y] |
+
+- 注意事项
+
+1. 返回 6 个数值(3 个控制点)
+2. 使用中点作为控制点
+3. 保持直线的视觉效果
+4. 便于统一处理
+5. 支持任意方向的直线
+
+
+## normalizePath
+
+将 SVG 路径标准化为统一格式的路径数组。
+
+- 功能说明
+
+- 将路径转换为绝对坐标
+- 标准化所有路径命令
+- 转换特殊命令(如 H、V)为通用命令(L)
+- 保持路径参数的连续性
+- 维护控制点信息
+
+```ts
+import { normalizePath } from '@antv/util';
+
+// 基本路径标准化
+console.log(normalizePath('M0 0 H50'));
+// [['M', 0, 0], ['L', 50, 0]]
+
+// 相对命令标准化
+console.log(normalizePath('M10 10 l20 20'));
+// [['M', 10, 10], ['L', 30, 30]]
+
+// 复杂路径标准化
+const complexPath = 'M0,0 h50 v50 H0 V0 z';
+console.log(normalizePath(complexPath));
+// [
+// ['M', 0, 0],
+// ['L', 50, 0],
+// ['L', 50, 50],
+// ['L', 0, 50],
+// ['L', 0, 0],
+// ['Z']
+// ]
+
+// 实际应用示例
+function createPathAnalyzer(pathString: string) {
+ const normalized = normalizePath(pathString);
+
+ return {
+ // 获取所有点
+ getPoints() {
+ return normalized.map(segment => {
+ const [cmd, ...params] = segment;
+ return cmd === 'Z' ? null : [params[params.length - 2], params[params.length - 1]];
+ }).filter(Boolean);
+ },
+
+ // 获取路径长度(简单估算)
+ getLength() {
+ let length = 0;
+ let prevX, prevY;
+
+ normalized.forEach(segment => {
+ const [cmd, ...params] = segment;
+ if (cmd === 'M') {
+ [prevX, prevY] = params;
+ } else if (cmd === 'L') {
+ const [x, y] = params;
+ length += Math.sqrt(
+ Math.pow(x - prevX, 2) + Math.pow(y - prevY, 2)
+ );
+ [prevX, prevY] = [x, y];
+ }
+ });
+
+ return length;
+ },
+
+ // 获取边界框
+ getBBox() {
+ const points = this.getPoints();
+ const xs = points.map(p => p[0]);
+ const ys = points.map(p => p[1]);
+
+ return {
+ x: Math.min(...xs),
+ y: Math.min(...ys),
+ width: Math.max(...xs) - Math.min(...xs),
+ height: Math.max(...ys) - Math.min(...ys)
+ };
+ }
+ };
+}
+
+// 使用示例
+const analyzer = createPathAnalyzer('M0,0 h100 v100 h-100 z');
+console.log(analyzer.getPoints());
+// [[0,0], [100,0], [100,100], [0,100]]
+
+console.log(analyzer.getLength());
+// 400
+
+console.log(analyzer.getBBox());
+// { x: 0, y: 0, width: 100, height: 100 }
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| pathInput | 路径输入 | string \| PathArray | - | SVG 路径字符串或路径数组 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 标准化路径 | NormalArray | - | 标准化后的路径数组 |
+
+
+## normalizeSegment
+
+标准化单个路径段,将特殊命令转换为基本命令(如 H/V -> L, T -> Q)。
+
+- 功能说明
+
+- 将水平线段(H)转换为直线段(L)
+- 将垂直线段(V)转换为直线段(L)
+- 将平滑曲线(S)转换为贝塞尔曲线(C)
+- 将平滑二次曲线(T)转换为二次贝塞尔曲线(Q)
+- 维护控制点信息
+
+```ts
+import { normalizeSegment } from '@antv/util';
+
+// 基本参数对象
+const params = {
+ x1: 0, y1: 0, // 当前点
+ x2: 0, y2: 0, // 前一个控制点
+ qx: null, qy: null // 二次贝塞尔曲线控制点
+};
+
+// 水平线段转换
+console.log(normalizeSegment(['H', 100], params));
+// ['L', 100, 0]
+
+// 垂直线段转换
+console.log(normalizeSegment(['V', 100], params));
+// ['L', 0, 100]
+
+// 平滑曲线转换
+params.x1 = 50;
+params.y1 = 50;
+params.x2 = 25;
+params.y2 = 25;
+console.log(normalizeSegment(['S', 75, 75, 100, 100], params));
+// ['C', 75, 75, 75, 75, 100, 100]
+
+// 实际应用示例
+function normalizePath(segments: PathSegment[]) {
+ const params = {
+ x1: 0, y1: 0,
+ x2: 0, y2: 0,
+ qx: null, qy: null
+ };
+
+ return segments.map(segment => {
+ const normalized = normalizeSegment(segment, params);
+
+ // 更新当前点
+ const [cmd, ...values] = normalized;
+ if (values.length >= 2) {
+ params.x1 = values[values.length - 2];
+ params.y1 = values[values.length - 1];
+ }
+
+ return normalized;
+ });
+}
+
+// 使用示例
+const path = [
+ ['M', 0, 0],
+ ['H', 100],
+ ['V', 100],
+ ['S', 150, 150, 200, 200]
+];
+
+console.log(normalizePath(path));
+// [
+// ['M', 0, 0],
+// ['L', 100, 0],
+// ['L', 100, 100],
+// ['C', 100, 100, 150, 150, 200, 200]
+// ]
+
+// 处理二次贝塞尔曲线
+function normalizeQuadratic(segments: PathSegment[]) {
+ const params = {
+ x1: 0, y1: 0,
+ x2: 0, y2: 0,
+ qx: null, qy: null
+ };
+
+ return segments.map(segment => {
+ if (segment[0] === 'Q') {
+ params.qx = segment[1];
+ params.qy = segment[2];
+ }
+ return normalizeSegment(segment, params);
+ });
+}
+
+// 使用示例
+const quadraticPath = [
+ ['M', 0, 0],
+ ['Q', 50, 50, 100, 0],
+ ['T', 200, 0]
+];
+
+console.log(normalizeQuadratic(quadraticPath));
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| segment | 路径段 | PathSegment | - | 要标准化的路径段 |
+| params | 参数对象 | object | - | 包含控制点信息的参数对象 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 标准化路径段 | NormalSegment | - | 标准化后的路径段 |
+
+- 参数对象属性
+
+| 属性 | 说明 | 类型 | 中文说明 |
+|---------|------|------|---------|
+| x1, y1 | 当前点坐标 | number | 当前绘制点的坐标 |
+| x2, y2 | 前一控制点 | number | 前一个控制点的坐标 |
+| qx, qy | 二次曲线控制点 | number | 二次贝塞尔曲线的控制点 |
+
+
+## quadToCubic
+
+将二次贝塞尔曲线转换为三次贝塞尔曲线。
+
+- 功能说明
+
+- 将二次贝塞尔曲线(Q)转换为三次贝塞尔曲线(C)
+- 保持曲线的视觉效果不变
+- 使用 1/3 和 2/3 比例计算控制点
+- 统一曲线表示方式
+- 返回完整的控制点坐标
+
+```ts
+import { quadToCubic } from '@antv/util';
+
+// 基本使用
+const result = quadToCubic(0, 0, 50, 50, 100, 0);
+console.log(result);
+// [33.33, 33.33, 66.67, 33.33, 100, 0]
+
+// 实际应用示例
+function convertQuadraticPath(path: [number, number][]) {
+ const result = [];
+
+ for (let i = 0; i < path.length - 2; i += 2) {
+ const [x1, y1] = path[i]; // 起点
+ const [qx, qy] = path[i + 1]; // 控制点
+ const [x2, y2] = path[i + 2]; // 终点
+
+ if (i === 0) {
+ result.push(['M', x1, y1]);
+ }
+
+ const cubic = quadToCubic(x1, y1, qx, qy, x2, y2);
+ result.push(['C', ...cubic]);
+ }
+
+ return result;
+}
+
+// 使用示例
+const quadraticPath = [
+ [0, 0], // 起点
+ [50, 50], // 控制点1
+ [100, 0], // 终点1/起点2
+ [150, 50], // 控制点2
+ [200, 0] // 终点2
+];
+
+console.log(convertQuadraticPath(quadraticPath));
+// [
+// ['M', 0, 0],
+// ['C', 33.33, 33.33, 66.67, 33.33, 100, 0],
+// ['C', 133.33, 33.33, 166.67, 33.33, 200, 0]
+// ]
+
+// 动画插值示例
+function createCurveAnimator(x1: number, y1: number, qx: number, qy: number, x2: number, y2: number) {
+ const cubic = quadToCubic(x1, y1, qx, qy, x2, y2);
+
+ return (t: number) => {
+ // 三次贝塞尔曲线插值
+ const mt = 1 - t;
+ const mt2 = mt * mt;
+ const mt3 = mt2 * mt;
+ const t2 = t * t;
+ const t3 = t2 * t;
+
+ return {
+ x: mt3 * x1 +
+ 3 * mt2 * t * cubic[0] +
+ 3 * mt * t2 * cubic[2] +
+ t3 * x2,
+ y: mt3 * y1 +
+ 3 * mt2 * t * cubic[1] +
+ 3 * mt * t2 * cubic[3] +
+ t3 * y2
+ };
+ };
+}
+
+// 使用动画器
+const animator = createCurveAnimator(0, 0, 50, 50, 100, 0);
+console.log(animator(0)); // { x: 0, y: 0 }
+console.log(animator(0.5)); // { x: 50, y: 25 }
+console.log(animator(1)); // { x: 100, y: 0 }
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| x1 | 起点 x 坐标 | number | - | 曲线起点的 x 坐标 |
+| y1 | 起点 y 坐标 | number | - | 曲线起点的 y 坐标 |
+| qx | 控制点 x 坐标 | number | - | 二次贝塞尔曲线控制点的 x 坐标 |
+| qy | 控制点 y 坐标 | number | - | 二次贝塞尔曲线控制点的 y 坐标 |
+| x2 | 终点 x 坐标 | number | - | 曲线终点的 x 坐标 |
+| y2 | 终点 y 坐标 | number | - | 曲线终点的 y 坐标 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| points | 控制点坐标 | number[] | - | 三次贝塞尔曲线的控制点坐标数组 [cpx1,cpy1,cpx2,cpy2,x,y] |
+
+- 注意事项
+
+1. 保持曲线形状不变
+2. 使用固定比例计算
+3. 返回 6 个坐标值
+4. 适用于路径转换
+5. 便于统一处理
+
+
+## reverseCurve
+
+反转基于贝塞尔曲线的路径数组。
+
+- 功能说明
+
+- 反转曲线路径的方向
+- 保持曲线的形状不变
+- 重新计算控制点位置
+- 仅处理贝塞尔曲线路径
+- 维护路径的连续性
+
+```ts
+import { reverseCurve } from '@antv/util';
+
+// 基本使用
+const curve: CurveArray = [
+ ['M', 0, 0],
+ ['C', 20, 20, 40, 20, 60, 0]
+];
+
+console.log(reverseCurve(curve));
+// [
+// ['M', 60, 0],
+// ['C', 40, 20, 20, 20, 0, 0]
+// ]
+
+// 多段曲线反转
+const multiCurve: CurveArray = [
+ ['M', 0, 0],
+ ['C', 20, 20, 40, 20, 60, 0],
+ ['C', 80, -20, 100, -20, 120, 0]
+];
+
+console.log(reverseCurve(multiCurve));
+// [
+// ['M', 120, 0],
+// ['C', 100, -20, 80, -20, 60, 0],
+// ['C', 40, 20, 20, 20, 0, 0]
+// ]
+
+// 实际应用示例
+function createReversiblePath(points: [number, number][]) {
+ // 创建曲线路径
+ const createCurve = (points: [number, number][]): CurveArray => {
+ const result: CurveArray = [['M', points[0][0], points[0][1]]];
+
+ for (let i = 1; i < points.length; i++) {
+ const prev = points[i - 1];
+ const curr = points[i];
+ const cp1 = [(prev[0] + curr[0]) / 2, prev[1]];
+ const cp2 = [(prev[0] + curr[0]) / 2, curr[1]];
+ result.push(['C', ...cp1, ...cp2, ...curr]);
+ }
+
+ return result;
+ };
+
+ const forward = createCurve(points);
+ const backward = reverseCurve(forward);
+
+ return {
+ forward,
+ backward,
+ getPath: (reverse = false) => reverse ? backward : forward
+ };
+}
+
+// 使用示例
+const points: [number, number][] = [
+ [0, 0],
+ [50, 50],
+ [100, 0]
+];
+
+const path = createReversiblePath(points);
+console.log('Forward:', path.getPath());
+console.log('Backward:', path.getPath(true));
+
+// 动画路径示例
+function createPathAnimator(curve: CurveArray) {
+ return {
+ forward: (t: number) => interpolatePath(curve, t),
+ backward: (t: number) => interpolatePath(reverseCurve(curve), t)
+ };
+}
+
+function interpolatePath(curve: CurveArray, t: number) {
+ // 简单的线性插值示例
+ if (t <= 0) return curve[0].slice(-2);
+ if (t >= 1) return curve[curve.length - 1].slice(-2);
+
+ const segmentIndex = Math.floor(t * (curve.length - 1));
+ const segment = curve[segmentIndex + 1];
+ const localT = (t * (curve.length - 1)) % 1;
+
+ // 贝塞尔曲线插值计算
+ return bezierPoint(segment.slice(1), localT);
+}
+
+// 使用动画器
+const animator = createPathAnimator([
+ ['M', 0, 0],
+ ['C', 20, 20, 40, 20, 60, 0]
+]);
+
+console.log(animator.forward(0.5)); // 正向动画中点
+console.log(animator.backward(0.5)); // 反向动画中点
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| pathArray | 曲线路径数组 | CurveArray | - | 要反转的贝塞尔曲线路径数组 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 反转后的路径 | CurveArray | - | 反转后的贝塞尔曲线路径数组 |
+
+- 注意事项
+
+1. 仅支持贝塞尔曲线路径
+2. 保持曲线形状不变
+3. 重新计算控制点
+4. 维护路径连续性
+5. 起点变为终点
+
+
+## roundPath
+
+对路径数组中的数值进行精度舍入。
+
+- 功能说明
+
+- 控制路径坐标的精度
+- 支持自定义小数位数
+- 可以关闭舍入功能
+- 保持命令类型不变
+- 创建新的路径数组
+
+```ts
+import { roundPath } from '@antv/util';
+
+// 基本使用
+const path: PathArray = [
+ ['M', 10.123, 20.456],
+ ['L', 30.789, 40.987]
+];
+
+console.log(roundPath(path, 2));
+// [
+// ['M', 10.12, 20.46],
+// ['L', 30.79, 40.99]
+// ]
+
+// 关闭舍入
+console.log(roundPath(path, 'off'));
+// 返回原始值的副本
+
+// 整数舍入
+console.log(roundPath(path, 0));
+// [
+// ['M', 10, 20],
+// ['L', 31, 41]
+// ]
+
+// 实际应用示例
+function createPathOptimizer(precision: number | 'off' = 2) {
+ return {
+ optimize(path: PathArray) {
+ return roundPath(path, precision);
+ },
+
+ // 计算路径长度(简单估算)
+ getLength(path: PathArray) {
+ const optimized = this.optimize(path);
+ let length = 0;
+ let prevX, prevY;
+
+ optimized.forEach(segment => {
+ const [cmd, ...params] = segment;
+ if (cmd === 'M') {
+ [prevX, prevY] = params;
+ } else if (cmd === 'L') {
+ const [x, y] = params;
+ length += Math.sqrt(
+ Math.pow(x - prevX, 2) + Math.pow(y - prevY, 2)
+ );
+ [prevX, prevY] = [x, y];
+ }
+ });
+
+ return length;
+ },
+
+ // 创建SVG路径字符串
+ toPathString(path: PathArray) {
+ return this.optimize(path)
+ .map(segment => segment.join(' '))
+ .join(' ');
+ }
+ };
+}
+
+// 使用示例
+const optimizer = createPathOptimizer(2);
+
+const complexPath: PathArray = [
+ ['M', 0.123, 0.456],
+ ['L', 50.789, 50.987],
+ ['L', 100.234, 0.567]
+];
+
+console.log(optimizer.optimize(complexPath));
+// [
+// ['M', 0.12, 0.46],
+// ['L', 50.79, 50.99],
+// ['L', 100.23, 0.57]
+// ]
+
+console.log(optimizer.getLength(complexPath));
+// 计算优化后的路径长度
+
+console.log(optimizer.toPathString(complexPath));
+// "M 0.12 0.46 L 50.79 50.99 L 100.23 0.57"
+
+// 动态精度调整
+function adjustPathPrecision(path: PathArray, viewportWidth: number) {
+ // 根据视口宽度动态调整精度
+ const precision = viewportWidth < 500 ? 1 :
+ viewportWidth < 1000 ? 2 : 3;
+
+ return roundPath(path, precision);
+}
+
+// 使用示例
+const responsivePath = adjustPathPrecision(complexPath, 800);
+console.log(responsivePath);
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| path | 路径数组 | PathArray | - | 要处理的路径数组 |
+| round | 精度控制 | number \| 'off' | - | 小数位数或关闭舍入 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 处理后的路径 | PathArray | - | 精度调整后的路径数组 |
+
+- 注意事项
+
+1. 仅处理数值部分
+2. 保持命令字符不变
+3. 创建新的数组
+4. 精度值必须为整数
+5. 'off'表示不进行舍入
+
+
+## segmentToCubic
+
+将各种路径段转换为三次贝塞尔曲线段。
+
+- 功能说明
+
+- 将不同类型的路径段统一转换为三次贝塞尔曲线
+- 支持弧线(A)、二次贝塞尔曲线(Q)、直线(L)和闭合路径(Z)的转换
+- 保持移动命令(M)不变
+- 维护控制点信息
+- 处理特殊情况
+
+```ts
+import { segmentToCubic } from '@antv/util';
+
+// 基本参数对象
+const params = {
+ x1: 0, y1: 0, // 当前点
+ x: 0, y: 0, // 起始点
+ qx: null, qy: null // 二次贝塞尔曲线控制点
+};
+
+// 直线转换
+console.log(segmentToCubic(['L', 100, 100], params));
+// ['C', 50, 50, 100, 100, 100, 100]
+
+// 二次贝塞尔曲线转换
+console.log(segmentToCubic(['Q', 50, 50, 100, 0], params));
+// ['C', 33.33, 33.33, 66.67, 33.33, 100, 0]
+
+// 实际应用示例
+function convertPathToCubic(pathSegments: PathSegment[]) {
+ const params = {
+ x1: 0, y1: 0,
+ x: 0, y: 0,
+ qx: null, qy: null
+ };
+
+ return pathSegments.map(segment => {
+ const converted = segmentToCubic(segment, params);
+
+ // 更新当前点
+ const [cmd, ...values] = converted;
+ if (values.length >= 2) {
+ params.x1 = values[values.length - 2];
+ params.y1 = values[values.length - 1];
+ }
+ if (cmd === 'M') {
+ params.x = values[0];
+ params.y = values[1];
+ }
+
+ return converted;
+ });
+}
+
+// 使用示例
+const path = [
+ ['M', 0, 0],
+ ['L', 100, 0],
+ ['Q', 150, 50, 200, 0],
+ ['A', 50, 50, 0, 0, 1, 250, 50],
+ ['Z']
+];
+
+console.log(convertPathToCubic(path));
+
+// 动画插值示例
+function createPathAnimator(segments: PathSegment[]) {
+ const cubicPath = convertPathToCubic(segments);
+
+ return (t: number) => {
+ if (t <= 0) return cubicPath[0].slice(-2);
+ if (t >= 1) return cubicPath[cubicPath.length - 1].slice(-2);
+
+ const segmentIndex = Math.floor(t * (cubicPath.length - 1));
+ const segment = cubicPath[segmentIndex];
+ const localT = (t * (cubicPath.length - 1)) % 1;
+
+ if (segment[0] === 'C') {
+ return bezierInterpolate(
+ [segment[1], segment[2]],
+ [segment[3], segment[4]],
+ [segment[5], segment[6]],
+ localT
+ );
+ }
+
+ return segment.slice(-2);
+ };
+}
+
+// 贝塞尔曲线插值
+function bezierInterpolate(p1: number[], p2: number[], p3: number[], t: number) {
+ const mt = 1 - t;
+ return [
+ mt * mt * p1[0] + 2 * mt * t * p2[0] + t * t * p3[0],
+ mt * mt * p1[1] + 2 * mt * t * p2[1] + t * t * p3[1]
+ ];
+}
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| segment | 路径段 | PathSegment | - | 要转换的路径段 |
+| params | 参数对象 | ParserParams | - | 包含控制点信息的参数对象 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 转换结果 | CSegment \| MSegment | - | 转换后的三次贝塞尔曲线段或移动命令 |
+
+
+## distanceSquareRoot
+
+计算两点之间的欧几里得距离。
+
+- 功能说明
+
+- 计算二维平面上两点之间的直线距离
+- 使用平方根公式计算
+- 接受坐标点数组作为参数
+- 返回非负数值
+- 基于欧几里得距离公式
+
+```ts
+import { distanceSquareRoot } from '@antv/util';
+
+// 基本使用
+const point1: [number, number] = [0, 0];
+const point2: [number, number] = [3, 4];
+console.log(distanceSquareRoot(point1, point2)); // 5
+
+// 水平距离
+console.log(distanceSquareRoot([0, 0], [5, 0])); // 5
+
+// 垂直距离
+console.log(distanceSquareRoot([0, 0], [0, 5])); // 5
+
+// 实际应用示例
+function createDistanceCalculator() {
+ return {
+ // 计算点到点集合中最近的距离
+ getNearestDistance(point: [number, number], points: [number, number][]) {
+ return Math.min(...points.map(p => distanceSquareRoot(point, p)));
+ },
+
+ // 计算路径长度
+ getPathLength(points: [number, number][]) {
+ let length = 0;
+ for (let i = 1; i < points.length; i++) {
+ length += distanceSquareRoot(points[i-1], points[i]);
+ }
+ return length;
+ },
+
+ // 判断点是否在圆内
+ isPointInCircle(
+ point: [number, number],
+ center: [number, number],
+ radius: number
+ ) {
+ return distanceSquareRoot(point, center) <= radius;
+ }
+ };
+}
+
+// 使用示例
+const calculator = createDistanceCalculator();
+
+// 计算最近距离
+const points = [
+ [0, 0],
+ [3, 4],
+ [6, 8]
+] as [number, number][];
+
+const targetPoint: [number, number] = [2, 2];
+console.log(calculator.getNearestDistance(targetPoint, points));
+
+// 计算路径长度
+console.log(calculator.getPathLength(points));
+
+// 点在圆内判断
+console.log(calculator.isPointInCircle([1, 1], [0, 0], 2)); // true
+
+// 碰撞检测示例
+function createCollisionDetector(radius: number) {
+ const points: [number, number][] = [];
+
+ return {
+ addPoint(point: [number, number]) {
+ if (!this.hasCollision(point)) {
+ points.push(point);
+ return true;
+ }
+ return false;
+ },
+
+ hasCollision(point: [number, number]) {
+ return points.some(p =>
+ distanceSquareRoot(point, p) < radius * 2
+ );
+ },
+
+ getPoints() {
+ return [...points];
+ }
+ };
+}
+
+// 使用碰撞检测器
+const detector = createCollisionDetector(5);
+console.log(detector.addPoint([0, 0])); // true
+console.log(detector.addPoint([4, 4])); // true
+console.log(detector.addPoint([1, 1])); // false (太近)
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| a | 第一个点 | [number, number] | - | 第一个点的坐标 |
+| b | 第二个点 | [number, number] | - | 第二个点的坐标 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| distance | 距离 | number | - | 两点之间的距离 |
+
+- 注意事项
+
+1. 返回非负数值
+2. 使用欧几里得距离公式
+3. 参数必须是二维坐标
+4. 计算结果是精确值
+5. 适用于平面坐标系
+
+
+## equalizeSegments
+
+平衡两个路径的段数,使它们具有相同数量的贝塞尔曲线段。
+
+- 功能说明
+
+- 调整两个路径使其具有相同数量的段
+- 通过分割曲线段来实现平衡
+- 保持路径的视觉效果
+- 支持递归处理
+- 考虑曲线长度进行优化分割
+
+```ts
+import { equalizeSegments } from '@antv/util';
+
+// 基本使用
+const path1: PathArray = [
+ ['M', 0, 0],
+ ['C', 50, 0, 100, 50, 100, 100]
+];
+
+const path2: PathArray = [
+ ['M', 0, 0],
+ ['C', 25, 25, 75, 75, 100, 100],
+ ['C', 125, 125, 150, 150, 200, 200]
+];
+
+const [equalPath1, equalPath2] = equalizeSegments(path1, path2);
+console.log(equalPath1.length === equalPath2.length); // true
+
+// 实际应用示例
+function createPathMorpher(startPath: PathArray, endPath: PathArray) {
+ const [equalizedStart, equalizedEnd] = equalizeSegments(startPath, endPath);
+
+ return {
+ // 获取某个时间点的插值路径
+ getPath(t: number): PathArray {
+ return equalizedStart.map((segment, i) => {
+ if (segment[0] === 'M') {
+ return [
+ 'M',
+ interpolate(segment[1], equalizedEnd[i][1], t),
+ interpolate(segment[2], equalizedEnd[i][2], t)
+ ];
+ } else if (segment[0] === 'C') {
+ return [
+ 'C',
+ interpolate(segment[1], equalizedEnd[i][1], t),
+ interpolate(segment[2], equalizedEnd[i][2], t),
+ interpolate(segment[3], equalizedEnd[i][3], t),
+ interpolate(segment[4], equalizedEnd[i][4], t),
+ interpolate(segment[5], equalizedEnd[i][5], t),
+ interpolate(segment[6], equalizedEnd[i][6], t)
+ ];
+ }
+ return segment;
+ }) as PathArray;
+ },
+
+ // 获取动画帧
+ getFrames(frames: number): PathArray[] {
+ return Array.from({ length: frames }, (_, i) =>
+ this.getPath(i / (frames - 1))
+ );
+ }
+ };
+}
+
+// 线性插值函数
+function interpolate(start: number, end: number, t: number): number {
+ return start + (end - start) * t;
+}
+
+// 使用示例
+const morpher = createPathMorpher(
+ [['M', 0, 0], ['C', 50, 0, 100, 50, 100, 100]],
+ [['M', 100, 0], ['C', 150, 0, 200, 50, 200, 100]]
+);
+
+// 获取中间状态
+console.log(morpher.getPath(0.5));
+
+// 获取动画帧
+const frames = morpher.getFrames(5);
+console.log(frames);
+
+// 路径动画示例
+function animatePath(element: SVGPathElement, startPath: PathArray, endPath: PathArray, duration: number) {
+ const morpher = createPathMorpher(startPath, endPath);
+ const startTime = performance.now();
+
+ function update(currentTime: number) {
+ const elapsed = currentTime - startTime;
+ const progress = Math.min(elapsed / duration, 1);
+
+ const currentPath = morpher.getPath(progress);
+ element.setAttribute('d', pathToString(currentPath));
+
+ if (progress < 1) {
+ requestAnimationFrame(update);
+ }
+ }
+
+ requestAnimationFrame(update);
+}
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| path1 | 第一个路径 | PathArray | - | 要平衡的第一个路径 |
+| path2 | 第二个路径 | PathArray | - | 要平衡的第二个路径 |
+| TL | 目标段数 | number | - | 可选的目标段数 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 平衡后的路径 | CurveArray[] | - | 包含两个平衡后路径的数组 |
+
+- 注意事项
+
+1. 保持路径视觉效果
+2. 考虑曲线长度
+3. 递归处理直到平衡
+4. 仅处理贝塞尔曲线
+5. 维护路径连续性
+
+
+## getDrawDirection
+
+判断路径的绘制方向(顺时针或逆时针)。
+
+- 功能说明
+
+- 通过计算路径面积判断绘制方向
+- 正面积表示顺时针方向
+- 负面积表示逆时针方向
+- 适用于闭合路径
+- 基于路径面积计算
+
+```ts
+import { getDrawDirection } from '@antv/util';
+
+// 基本使用
+const clockwise: PathArray = [
+ ['M', 0, 0],
+ ['L', 100, 0],
+ ['L', 100, 100],
+ ['L', 0, 100],
+ ['Z']
+];
+console.log(getDrawDirection(clockwise)); // true (顺时针)
+
+const counterClockwise: PathArray = [
+ ['M', 0, 0],
+ ['L', 0, 100],
+ ['L', 100, 100],
+ ['L', 100, 0],
+ ['Z']
+];
+console.log(getDrawDirection(counterClockwise)); // false (逆时针)
+
+// 实际应用示例
+function createPathAnalyzer() {
+ return {
+ // 判断绘制方向
+ isClockwise(path: PathArray) {
+ return getDrawDirection(path);
+ },
+
+ // 确保路径为顺时针方向
+ ensureClockwise(path: PathArray): PathArray {
+ return this.isClockwise(path) ? path : reversePath(path);
+ },
+
+ // 确保路径为逆时针方向
+ ensureCounterClockwise(path: PathArray): PathArray {
+ return this.isClockwise(path) ? reversePath(path) : path;
+ },
+
+ // 反转路径方向
+ reversePath(path: PathArray): PathArray {
+ const result = [path[0]]; // 保持起点
+ for (let i = path.length - 1; i > 0; i--) {
+ result.push(path[i]);
+ }
+ return result;
+ }
+ };
+}
+
+// 使用示例
+const analyzer = createPathAnalyzer();
+
+const path: PathArray = [
+ ['M', 0, 0],
+ ['L', 100, 0],
+ ['L', 100, 100],
+ ['L', 0, 100],
+ ['Z']
+];
+
+console.log(analyzer.isClockwise(path)); // true
+console.log(analyzer.ensureCounterClockwise(path)); // 返回反转后的路径
+
+// 复合路径处理示例
+function processCompoundPath(paths: PathArray[]) {
+ const analyzer = createPathAnalyzer();
+
+ return paths.map((path, index) =>
+ // 外层路径顺时针,内层路径逆时针
+ index === 0 ?
+ analyzer.ensureClockwise(path) :
+ analyzer.ensureCounterClockwise(path)
+ );
+}
+
+// 使用示例
+const compound = [
+ // 外层矩形
+ [
+ ['M', 0, 0],
+ ['L', 100, 0],
+ ['L', 100, 100],
+ ['L', 0, 100],
+ ['Z']
+ ],
+ // 内层矩形
+ [
+ ['M', 25, 25],
+ ['L', 75, 25],
+ ['L', 75, 75],
+ ['L', 25, 75],
+ ['Z']
+ ]
+];
+
+console.log(processCompoundPath(compound));
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| pathArray | 路径数组 | PathArray | - | 要判断方向的路径数组 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 方向判断结果 | boolean | - | true 表示顺时针,false 表示逆时针 |
+
+- 注意事项
+
+1. 适用于闭合路径
+2. 基于路径面积计算
+3. 路径应该是完整的
+4. 考虑路径的起始点
+5. 支持复杂路径
+
+
+## getPathArea
+
+计算路径的面积,支持复杂的贝塞尔曲线路径。
+
+- 功能说明
+
+- 计算路径围成的面积
+- 支持三次贝塞尔曲线
+- 自动转换路径为曲线形式
+- 考虑路径方向(正负面积)
+- 基于数学公式精确计算
+
+```ts
+import { getPathArea } from '@antv/util';
+
+// 基本矩形路径
+const rectangle: PathArray = [
+ ['M', 0, 0],
+ ['L', 100, 0],
+ ['L', 100, 100],
+ ['L', 0, 100],
+ ['Z']
+];
+console.log(getPathArea(rectangle)); // 10000 (100 * 100)
+
+// 曲线路径
+const curve: PathArray = [
+ ['M', 0, 0],
+ ['C', 50, 0, 100, 50, 100, 100],
+ ['C', 100, 150, 50, 200, 0, 200],
+ ['Z']
+];
+console.log(getPathArea(curve)); // 返回曲线围成的面积
+
+// 实际应用示例
+function createShapeAnalyzer() {
+ return {
+ // 计算面积
+ getArea(path: PathArray) {
+ return Math.abs(getPathArea(path));
+ },
+
+ // 判断点是否在路径内
+ isPointInPath(path: PathArray, point: [number, number]) {
+ // 通过射线法判断点是否在路径内
+ const testPath: PathArray = [
+ ['M', point[0], point[1]],
+ ['L', point[0] + 10000, point[1]] // 水平射线
+ ];
+
+ // 计算交点数量
+ const intersections = getPathIntersections(path, testPath);
+ return intersections.length % 2 === 1;
+ },
+
+ // 计算路径的重心
+ getCentroid(path: PathArray) {
+ const area = getPathArea(path);
+ let cx = 0;
+ let cy = 0;
+
+ // 遍历路径段计算重心
+ let x = 0, y = 0;
+ path2Curve(path).forEach(seg => {
+ if (seg[0] === 'M') {
+ [, x, y] = seg;
+ } else {
+ const [c1x, c1y, c2x, c2y, x2, y2] = seg.slice(1);
+ // 计算每段的贡献
+ const segArea = getCubicSegArea(x, y, c1x, c1y, c2x, c2y, x2, y2);
+ cx += ((x + c1x + c2x + x2) / 4) * segArea;
+ cy += ((y + c1y + c2y + y2) / 4) * segArea;
+ [x, y] = [x2, y2];
+ }
+ });
+
+ return {
+ x: cx / area,
+ y: cy / area
+ };
+ }
+ };
+}
+
+// 使用示例
+const analyzer = createShapeAnalyzer();
+
+const shape: PathArray = [
+ ['M', 0, 0],
+ ['L', 100, 0],
+ ['L', 100, 100],
+ ['L', 0, 100],
+ ['Z']
+];
+
+console.log(analyzer.getArea(shape)); // 10000
+console.log(analyzer.isPointInPath(shape, [50, 50])); // true
+console.log(analyzer.getCentroid(shape)); // { x: 50, y: 50 }
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| path | 路径数组 | PathArray | - | 要计算面积的路径数组 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| area | 面积值 | number | - | 路径围成的面积(可能为负) |
+
+- 注意事项
+
+1. 路径应该是闭合的
+2. 返回值可能为负(取决于路径方向)
+3. 自动转换为贝塞尔曲线
+4. 基于精确的数学公式
+5. 支持复杂路径计算
+
+
+## getPathBBoxTotalLength
+
+计算路径的边界框和总长度信息。
+
+- 功能说明
+
+- 计算路径的边界框(BBox)
+- 计算路径的总长度
+- 计算中心点坐标
+- 提供估算的深度值
+- 处理空路径的情况
+
+```ts
+import { getPathBBoxTotalLength } from '@antv/util';
+
+// 基本使用
+const path: PathArray = [
+ ['M', 0, 0],
+ ['L', 100, 0],
+ ['L', 100, 100],
+ ['L', 0, 100],
+ ['Z']
+];
+
+console.log(getPathBBoxTotalLength(path));
+// {
+// length: 400, // 路径总长度
+// width: 100, // 宽度
+// height: 100, // 高度
+// x: 0, // 左上角x坐标
+// y: 0, // 左上角y坐标
+// x2: 100, // 右下角x坐标
+// y2: 100, // 右下角y坐标
+// cx: 50, // 中心x坐标
+// cy: 50, // 中心y坐标
+// cz: 150 // 估算深度
+// }
+
+// 实际应用示例
+function createPathAnalyzer() {
+ return {
+ // 获取路径信息
+ getPathInfo(path: PathArray) {
+ const info = getPathBBoxTotalLength(path);
+ return {
+ ...info,
+ // 计算额外信息
+ aspectRatio: info.width / info.height,
+ area: info.width * info.height,
+ perimeter: info.length,
+ diagonal: Math.sqrt(info.width ** 2 + info.height ** 2)
+ };
+ },
+
+ // 创建居中变换
+ createCenterTransform(path: PathArray, targetWidth: number, targetHeight: number) {
+ const { width, height, cx, cy } = getPathBBoxTotalLength(path);
+
+ const scale = Math.min(
+ targetWidth / width,
+ targetHeight / height
+ );
+
+ const tx = targetWidth / 2 - cx * scale;
+ const ty = targetHeight / 2 - cy * scale;
+
+ return {
+ scale,
+ translate: [tx, ty],
+ transform: `translate(${tx},${ty}) scale(${scale})`
+ };
+ },
+
+ // 检查碰撞
+ checkCollision(path1: PathArray, path2: PathArray) {
+ const box1 = getPathBBoxTotalLength(path1);
+ const box2 = getPathBBoxTotalLength(path2);
+
+ return !(
+ box2.x > box1.x2 ||
+ box2.x2 < box1.x ||
+ box2.y > box1.y2 ||
+ box2.y2 < box1.y
+ );
+ }
+ };
+}
+
+// 使用示例
+const analyzer = createPathAnalyzer();
+
+// 获取路径信息
+const pathInfo = analyzer.getPathInfo(path);
+console.log(pathInfo);
+
+// 创建居中变换
+const transform = analyzer.createCenterTransform(path, 500, 500);
+console.log(transform);
+
+// 碰撞检测
+const path2: PathArray = [
+ ['M', 50, 50],
+ ['L', 150, 50],
+ ['L', 150, 150],
+ ['L', 50, 150],
+ ['Z']
+];
+
+console.log(analyzer.checkCollision(path, path2)); // true
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| path | 路径数组 | PathArray | - | 要分析的路径数组 |
+| options | 配置选项 | Partial | - | 可选的配置参数 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 路径信息 | PathBBoxTotalLength | - | 包含边界框和长度信息的对象 |
+
+- 返回对象属性
+
+| 属性 | 说明 | 类型 | 中文说明 |
+|---------|------|------|---------|
+| length | 路径长度 | number | 路径的总长度 |
+| width | 宽度 | number | 边界框的宽度 |
+| height | 高度 | number | 边界框的高度 |
+| x | 左上角 x | number | 边界框左上角 x 坐标 |
+| y | 左上角 y | number | 边界框左上角 y 坐标 |
+| x2 | 右下角 x | number | 边界框右下角 x 坐标 |
+| y2 | 右下角 y | number | 边界框右下角 y 坐标 |
+| cx | 中心 x | number | 边界框中心 x 坐标 |
+| cy | 中心 y | number | 边界框中心 y 坐标 |
+| cz | 深度估计 | number | 估算的深度值 |
+
+
+## getPathBBox
+
+获取路径的边界框(Bounding Box)信息。
+
+- 功能说明
+
+- 计算路径的边界框
+- 支持字符串或数组格式的路径
+- 计算中心点坐标
+- 提供估算的深度值
+- 处理空路径的情况
+
+```ts
+import { getPathBBox } from '@antv/util';
+
+// 基本使用
+const path: PathArray = [
+ ['M', 0, 0],
+ ['L', 100, 0],
+ ['L', 100, 100],
+ ['L', 0, 100],
+ ['Z']
+];
+
+console.log(getPathBBox(path));
+// {
+// width: 100, // 宽度
+// height: 100, // 高度
+// x: 0, // 左上角x坐标
+// y: 0, // 左上角y坐标
+// x2: 100, // 右下角x坐标
+// y2: 100, // 右下角y坐标
+// cx: 50, // 中心x坐标
+// cy: 50, // 中心y坐标
+// cz: 150 // 估算深度
+// }
+
+// 字符串路径
+console.log(getPathBBox('M0,0 L100,0 L100,100 L0,100 Z'));
+
+// 实际应用示例
+function createShapeUtil() {
+ return {
+ // 计算缩放比例以适应目标大小
+ calculateScale(path: PathArray, targetWidth: number, targetHeight: number) {
+ const bbox = getPathBBox(path);
+ return {
+ scaleX: targetWidth / bbox.width,
+ scaleY: targetHeight / bbox.height,
+ uniform: Math.min(targetWidth / bbox.width, targetHeight / bbox.height)
+ };
+ },
+
+ // 创建居中变换矩阵
+ createCenterTransform(path: PathArray, containerWidth: number, containerHeight: number) {
+ const bbox = getPathBBox(path);
+
+ return {
+ translateX: (containerWidth - bbox.width) / 2 - bbox.x,
+ translateY: (containerHeight - bbox.height) / 2 - bbox.y,
+ toString() {
+ return `translate(${this.translateX},${this.translateY})`;
+ }
+ };
+ },
+
+ // 检查是否包含点
+ containsPoint(path: PathArray, x: number, y: number) {
+ const bbox = getPathBBox(path);
+ return (
+ x >= bbox.x &&
+ x <= bbox.x2 &&
+ y >= bbox.y &&
+ y <= bbox.y2
+ );
+ }
+ };
+}
+
+// 使用示例
+const shapeUtil = createShapeUtil();
+
+// 计算缩放
+const scale = shapeUtil.calculateScale(path, 500, 300);
+console.log(scale);
+
+// 居中变换
+const transform = shapeUtil.createCenterTransform(path, 800, 600);
+console.log(transform.toString());
+
+// 点击检测
+console.log(shapeUtil.containsPoint(path, 50, 50)); // true
+
+// SVG元素定位示例
+function positionElement(element: SVGElement, path: PathArray, padding = 10) {
+ const bbox = getPathBBox(path);
+
+ element.style.position = 'absolute';
+ element.style.left = `${bbox.x - padding}px`;
+ element.style.top = `${bbox.y - padding}px`;
+ element.style.width = `${bbox.width + padding * 2}px`;
+ element.style.height = `${bbox.height + padding * 2}px`;
+}
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| path | 路径 | string \| PathArray | - | 要分析的路径 |
+| options | 配置选项 | Partial | - | 可选的配置参数 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 边界框信息 | PathBBox | - | 包含边界框信息的对象 |
+
+- 返回对象属性
+
+| 属性 | 说明 | 类型 | 中文说明 |
+|---------|------|------|---------|
+| width | 宽度 | number | 边界框的宽度 |
+| height | 高度 | number | 边界框的高度 |
+| x | 左上角 x | number | 边界框左上角 x 坐标 |
+| y | 左上角 y | number | 边界框左上角 y 坐标 |
+| x2 | 右下角 x | number | 边界框右下角 x 坐标 |
+| y2 | 右下角 y | number | 边界框右下角 y 坐标 |
+| cx | 中心 x | number | 边界框中心 x 坐标 |
+| cy | 中心 y | number | 边界框中心 y 坐标 |
+| cz | 深度估计 | number | 估算的深度值 |
+
+
+## getPointAtLength
+
+获取路径上指定距离处的坐标点。
+
+- 功能说明
+
+- 计算路径上特定距离的点坐标
+- 支持字符串或数组格式的路径
+- 基于路径总长度的比例计算
+- 支持自定义配置选项
+- 精确定位路径上的点
+
+```ts
+import { getPointAtLength } from '@antv/util';
+
+// 基本使用
+const path: PathArray = [
+ ['M', 0, 0],
+ ['L', 100, 0],
+ ['L', 100, 100]
+];
+
+console.log(getPointAtLength(path, 50)); // [50, 0]
+console.log(getPointAtLength(path, 150)); // [100, 50]
+
+// 字符串路径
+console.log(getPointAtLength('M0,0 L100,0 L100,100', 100));
+
+// 实际应用示例
+function createPathAnimator() {
+ return {
+ // 创建路径动画
+ animate(path: PathArray, duration: number = 1000) {
+ const startTime = performance.now();
+ const totalLength = pathLengthFactory(path, undefined, { length: true }).length;
+
+ return {
+ // 获取当前动画帧的点
+ getCurrentPoint(currentTime: number) {
+ const elapsed = currentTime - startTime;
+ const progress = Math.min(elapsed / duration, 1);
+ return getPointAtLength(path, totalLength * progress);
+ },
+
+ // 创建动画函数
+ start(callback: (point: [number, number]) => void) {
+ const animate = (time: number) => {
+ const point = this.getCurrentPoint(time);
+ callback(point);
+
+ if (time - startTime < duration) {
+ requestAnimationFrame(animate);
+ }
+ };
+
+ requestAnimationFrame(animate);
+ }
+ };
+ },
+
+ // 创建路径采样器
+ createSampler(path: PathArray, samples: number = 100) {
+ const totalLength = pathLengthFactory(path, undefined, { length: true }).length;
+ const step = totalLength / (samples - 1);
+
+ return Array.from({ length: samples }, (_, i) =>
+ getPointAtLength(path, i * step)
+ );
+ },
+
+ // 创建路径跟随器
+ createFollower(path: PathArray) {
+ const totalLength = pathLengthFactory(path, undefined, { length: true }).length;
+
+ return {
+ getPointAtDistance(distance: number) {
+ return getPointAtLength(path, distance);
+ },
+
+ getPointAtPercent(percent: number) {
+ return getPointAtLength(path, totalLength * Math.min(Math.max(percent, 0), 1));
+ }
+ };
+ }
+ };
+}
+
+// 使用示例
+const animator = createPathAnimator();
+
+// 路径动画
+const path = [
+ ['M', 0, 0],
+ ['C', 50, 0, 100, 50, 100, 100]
+];
+
+animator.animate(path).start(point => {
+ console.log('Current position:', point);
+});
+
+// 路径采样
+const samples = animator.createSampler(path, 10);
+console.log('Path samples:', samples);
+
+// 路径跟随
+const follower = animator.createFollower(path);
+console.log(follower.getPointAtPercent(0.5)); // 路径中点
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| pathInput | 路径 | string \| PathArray | - | 要分析的路径 |
+| distance | 距离 | number | - | 要获取点的距离 |
+| options | 配置选项 | Partial | - | 可选的配置参数 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| point | 坐标点 | [number, number] | - | 指定距离处的坐标点 |
+
+
+## getPropertiesAtLength
+
+获取路径上指定距离处的段属性信息。
+
+- 功能说明
+
+- 获取指定距离处的路径段信息
+- 计算到该段的累计长度
+- 返回段的索引和属性
+- 支持字符串或数组格式的路径
+- 处理边界情况
+
+```ts
+import { getPropertiesAtLength } from '@antv/util';
+
+// 基本使用
+const path: PathArray = [
+ ['M', 0, 0],
+ ['L', 100, 0],
+ ['L', 100, 100],
+ ['L', 0, 100]
+];
+
+console.log(getPropertiesAtLength(path, 150));
+// {
+// segment: ['L', 100, 100], // 当前段
+// index: 2, // 段索引
+// length: 100, // 段长度
+// lengthAtSegment: 100, // 到该段起点的累计长度
+// point: { x: 100, y: 100 } // 段终点坐标
+// }
+
+// 实际应用示例
+function createPathAnalyzer() {
+ return {
+ // 获取路径段信息
+ getSegmentInfo(path: PathArray, distance: number) {
+ const props = getPropertiesAtLength(path, distance);
+ return {
+ ...props,
+ // 计算在当前段的进度
+ segmentProgress: props.length ?
+ (distance - props.lengthAtSegment) / props.length : 0,
+ // 计算总体进度
+ totalProgress: distance / getTotalLength(path)
+ };
+ },
+
+ // 创建路径动画控制器
+ createAnimationController(path: PathArray) {
+ const totalLength = getTotalLength(path);
+
+ return {
+ // 获取指定进度的位置信息
+ getPositionAt(progress: number) {
+ const distance = totalLength * Math.min(Math.max(progress, 0), 1);
+ return this.getSegmentInfo(path, distance);
+ },
+
+ // 创建动画生成器
+ *createAnimator(duration: number, fps = 60) {
+ const frames = duration / 1000 * fps;
+ const step = 1 / frames;
+
+ for (let progress = 0; progress <= 1; progress += step) {
+ yield this.getPositionAt(progress);
+ }
+ }
+ };
+ },
+
+ // 路径分段器
+ splitPath(path: PathArray, segments: number) {
+ const totalLength = getTotalLength(path);
+ const segmentLength = totalLength / segments;
+
+ return Array.from({ length: segments + 1 }, (_, i) =>
+ getPropertiesAtLength(path, i * segmentLength)
+ );
+ }
+ };
+}
+
+// 使用示例
+const analyzer = createPathAnalyzer();
+
+// 获取特定位置信息
+const segmentInfo = analyzer.getSegmentInfo(path, 150);
+console.log(segmentInfo);
+
+// 创建动画控制器
+const controller = analyzer.createAnimationController(path);
+const animator = controller.createAnimator(1000);
+
+// 运行动画
+function runAnimation() {
+ const frame = animator.next();
+ if (!frame.done) {
+ console.log('Current position:', frame.value);
+ requestAnimationFrame(runAnimation);
+ }
+}
+runAnimation();
+
+// 路径分段
+const segments = analyzer.splitPath(path, 4);
+console.log('Path segments:', segments);
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| pathInput | 路径 | string \| PathArray | - | 要分析的路径 |
+| distance | 距离 | number | - | 指定的距离 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 段属性 | SegmentProperties | - | 包含段信息的对象 |
+
+- 返回对象属性
+
+| 属性 | 说明 | 类型 | 中文说明 |
+|---------|------|------|---------|
+| segment | 路径段 | PathSegment | 当前路径段 |
+| index | 索引 | number | 段在路径中的索引 |
+| length | 长度 | number | 当前段的长度 |
+| lengthAtSegment | 累计长度 | number | 到该段的累计长度 |
+| point | 坐标点 | {x: number, y: number} | 段终点坐标 |
+
+
+## getPropertiesAtPoint
+
+获取路径上距离指定点最近的位置及其属性信息。
+
+- 功能说明
+
+- 查找路径上最接近给定点的位置
+- 计算点到路径的最短距离
+- 返回最近点的段属性信息
+- 使用二分查找优化精度
+- 支持复杂路径结构
+
+```ts
+import { getPropertiesAtPoint } from '@antv/util';
+
+// 基本使用
+const path: PathArray = [
+ ['M', 0, 0],
+ ['L', 100, 0],
+ ['L', 100, 100]
+];
+
+const point = { x: 50, y: 50 };
+console.log(getPropertiesAtPoint(path, point));
+// {
+// closest: { x: 100, y: 50 }, // 最近点
+// distance: 50, // 最短距离
+// segment: { // 段属性
+// segment: ['L', 100, 100],
+// index: 2,
+// length: 100,
+// lengthAtSegment: 100
+// }
+// }
+
+// 实际应用示例
+function createPathInteractor() {
+ return {
+ // 创建路径跟随器
+ createPathFollower(path: PathArray) {
+ return {
+ // 获取最近点信息
+ getNearestPoint(point: Point) {
+ return getPropertiesAtPoint(path, point);
+ },
+
+ // 检查点是否在路径附近
+ isNearPath(point: Point, threshold = 5) {
+ const { distance } = getPropertiesAtPoint(path, point);
+ return distance <= threshold;
+ },
+
+ // 获取路径上的投影点
+ getProjection(point: Point) {
+ const { closest } = getPropertiesAtPoint(path, point);
+ return closest;
+ }
+ };
+ },
+
+ // 创建磁吸效果
+ createSnapEffect(path: PathArray, threshold = 10) {
+ return (point: Point) => {
+ const { closest, distance } = getPropertiesAtPoint(path, point);
+ if (distance <= threshold) {
+ return closest;
+ }
+ return point;
+ };
+ },
+
+ // 创建路径距离监听器
+ createDistanceMonitor(path: PathArray) {
+ return {
+ // 监听点到路径的距离变化
+ watch(point: Point, callback: (info: PointProperties) => void) {
+ let lastDistance = Infinity;
+
+ return (newPoint: Point) => {
+ const props = getPropertiesAtPoint(path, newPoint);
+ if (props.distance !== lastDistance) {
+ lastDistance = props.distance;
+ callback(props);
+ }
+ };
+ }
+ };
+ }
+ };
+}
+
+// 使用示例
+const interactor = createPathInteractor();
+
+// 创建路径跟随器
+const follower = interactor.createPathFollower(path);
+console.log(follower.isNearPath({ x: 50, y: 10 })); // true
+
+// 创建磁吸效果
+const snap = interactor.createSnapEffect(path);
+console.log(snap({ x: 98, y: 48 })); // { x: 100, y: 50 }
+
+// 创建距离监听器
+const monitor = interactor.createDistanceMonitor(path);
+const unwatch = monitor.watch({ x: 0, y: 0 }, (info) => {
+ console.log('Distance changed:', info.distance);
+});
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| pathInput | 路径 | string \| PathArray | - | 要分析的路径 |
+| point | 目标点 | Point | - | 要检测的点坐标 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 点属性 | PointProperties | - | 包含最近点信息的对象 |
+
+- 返回对象属性
+
+| 属性 | 说明 | 类型 | 中文说明 |
+|---------|------|------|---------|
+| closest | 最近点 | Point | 路径上最近的点 |
+| distance | 距离 | number | 到路径的最短距离 |
+| segment | 段属性 | SegmentProperties | 最近点所在段的属性 |
+
+
+## getRotatedCurve
+
+获取最佳旋转匹配的曲线路径。
+
+- 功能说明
+
+- 计算两个曲线之间的最佳旋转匹配
+- 通过比较点之间的距离来评估匹配度
+- 支持循环旋转比较
+- 返回最佳匹配的旋转路径
+- 优化路径变形效果
+
+```ts
+import { getRotatedCurve } from '@antv/util';
+
+// 基本使用
+const curve1: CurveArray = [
+ ['M', 0, 0],
+ ['C', 20, 20, 40, 20, 60, 0],
+ ['C', 80, -20, 100, -20, 120, 0]
+];
+
+const curve2: CurveArray = [
+ ['M', 120, 0],
+ ['C', 100, -20, 80, -20, 60, 0],
+ ['C', 40, 20, 20, 20, 0, 0]
+];
+
+console.log(getRotatedCurve(curve1, curve2));
+// 返回最佳匹配的旋转路径
+
+// 实际应用示例
+function createShapeMorpher() {
+ return {
+ // 创建形状变形器
+ createMorph(from: CurveArray, to: CurveArray) {
+ const rotated = getRotatedCurve(from, to);
+
+ return {
+ // 获取中间状态
+ getIntermediate(progress: number) {
+ return rotated.map((segment, i) => {
+ if (segment[0] === 'M') {
+ return [
+ 'M',
+ interpolate(segment[1], to[i][1], progress),
+ interpolate(segment[2], to[i][2], progress)
+ ];
+ }
+ return [
+ 'C',
+ ...segment.slice(1).map((value, j) =>
+ interpolate(value, to[i][j + 1], progress)
+ )
+ ];
+ });
+ },
+
+ // 创建动画帧
+ createFrames(frames: number) {
+ return Array.from({ length: frames }, (_, i) =>
+ this.getIntermediate(i / (frames - 1))
+ );
+ }
+ };
+ },
+
+ // 创建形状匹配器
+ createMatcher(shapes: CurveArray[]) {
+ return {
+ // 找到最佳匹配
+ findBestMatch(target: CurveArray) {
+ return shapes.map(shape => ({
+ shape,
+ rotated: getRotatedCurve(shape, target),
+ distance: calculateDistance(shape, target)
+ })).sort((a, b) => a.distance - b.distance)[0];
+ }
+ };
+ }
+ };
+}
+
+// 辅助函数:插值计算
+function interpolate(start: number, end: number, progress: number) {
+ return start + (end - start) * progress;
+}
+
+// 辅助函数:计算两个形状间的距离
+function calculateDistance(shape1: CurveArray, shape2: CurveArray) {
+ return shape1.reduce((sum, segment, i) => {
+ if (segment[0] === 'M') return sum;
+ return sum + distanceSquareRoot(
+ segment.slice(-2) as [number, number],
+ shape2[i].slice(-2) as [number, number]
+ );
+ }, 0);
+}
+
+// 使用示例
+const morpher = createShapeMorpher();
+
+// 创建形状变形
+const morphing = morpher.createMorph(curve1, curve2);
+console.log(morphing.getIntermediate(0.5)); // 中间状态
+console.log(morphing.createFrames(10)); // 动画帧
+
+// 形状匹配
+const matcher = morpher.createMatcher([curve1, curve2]);
+const bestMatch = matcher.findBestMatch(curve1);
+console.log(bestMatch);
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| a | 源曲线 | CurveArray | - | 源曲线路径 |
+| b | 目标曲线 | CurveArray | - | 目标曲线路径 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 旋转后的曲线 | CurveArray | - | 最佳匹配的旋转曲线 |
+
+
+## getTotalLength
+
+计算路径的总长度。
+
+- 功能说明
+
+- 计算路径的总长度
+- 支持字符串或数组格式的路径
+- 提供配置选项
+- 高效准确的长度计算
+- 等同于 SVG 的 getTotalLength() 方法
+
+```ts
+import { getTotalLength } from '@antv/util';
+
+// 基本使用
+const path: PathArray = [
+ ['M', 0, 0],
+ ['L', 100, 0],
+ ['L', 100, 100]
+];
+
+console.log(getTotalLength(path)); // 200
+
+// 字符串路径
+console.log(getTotalLength('M0,0 L100,0 L100,100')); // 200
+
+// 实际应用示例
+function createPathUtil() {
+ return {
+ // 计算路径进度
+ calculateProgress(path: PathArray, distance: number) {
+ const totalLength = getTotalLength(path);
+ return Math.min(Math.max(distance / totalLength, 0), 1);
+ },
+
+ // 创建路径采样器
+ createSampler(path: PathArray, samples: number = 100) {
+ const totalLength = getTotalLength(path);
+ const step = totalLength / (samples - 1);
+
+ return {
+ // 获取采样点
+ getPoints() {
+ return Array.from({ length: samples }, (_, i) =>
+ getPointAtLength(path, i * step)
+ );
+ },
+
+ // 获取均匀分布的长度
+ getLengths() {
+ return Array.from({ length: samples }, (_, i) => i * step);
+ }
+ };
+ },
+
+ // 创建路径动画控制器
+ createAnimationController(path: PathArray) {
+ const totalLength = getTotalLength(path);
+
+ return {
+ // 获取指定时间的位置
+ getPositionAtTime(time: number, duration: number) {
+ const progress = Math.min(time / duration, 1);
+ return getPointAtLength(path, totalLength * progress);
+ },
+
+ // 创建动画生成器
+ *createFrames(duration: number, fps = 60) {
+ const frameCount = duration / 1000 * fps;
+ const step = totalLength / frameCount;
+
+ for (let distance = 0; distance <= totalLength; distance += step) {
+ yield getPointAtLength(path, distance);
+ }
+ }
+ };
+ }
+ };
+}
+
+// 使用示例
+const pathUtil = createPathUtil();
+
+// 计算进度
+console.log(pathUtil.calculateProgress(path, 100)); // 0.5
+
+// 路径采样
+const sampler = pathUtil.createSampler(path, 5);
+console.log(sampler.getPoints());
+console.log(sampler.getLengths());
+
+// 动画控制
+const animator = pathUtil.createAnimationController(path);
+
+// 创建动画
+function animate(duration: number) {
+ const startTime = performance.now();
+
+ function update(currentTime: number) {
+ const elapsed = currentTime - startTime;
+ const position = animator.getPositionAtTime(elapsed, duration);
+
+ // 更新位置...
+ console.log(position);
+
+ if (elapsed < duration) {
+ requestAnimationFrame(update);
+ }
+ }
+
+ requestAnimationFrame(update);
+}
+
+animate(1000); // 1秒动画
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| pathInput | 路径 | string \| PathArray | - | 要计算长度的路径 |
+| options | 配置选项 | Partial | - | 可选的配置参数 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| length | 总长度 | number | - | 路径的总长度 |
+
+
+## isAbsoluteArray
+
+判断路径数组是否全部由绝对坐标命令组成。
+
+- 功能说明
+
+- 检查路径数组是否为绝对坐标
+- 验证所有命令是否为大写
+- 类型保护功能
+- 支持路径数组格式
+- 用于路径验证
+
+```ts
+import { isAbsoluteArray } from '@antv/util';
+
+// 基本使用
+const absolutePath: PathArray = [
+ ['M', 0, 0],
+ ['L', 100, 0],
+ ['L', 100, 100]
+];
+console.log(isAbsoluteArray(absolutePath)); // true
+
+const relativePath: PathArray = [
+ ['M', 0, 0],
+ ['l', 100, 0],
+ ['l', 0, 100]
+];
+console.log(isAbsoluteArray(relativePath)); // false
+
+// 实际应用示例
+function createPathValidator() {
+ return {
+ // 验证路径格式
+ validatePath(path: PathArray) {
+ return {
+ isValid: isPathArray(path),
+ isAbsolute: isAbsoluteArray(path),
+ hasRelative: path.some(([cmd]) => cmd !== cmd.toUpperCase()),
+ commands: path.map(([cmd]) => cmd)
+ };
+ },
+
+ // 确保路径为绝对坐标
+ ensureAbsolute(path: PathArray): AbsoluteArray {
+ if (isAbsoluteArray(path)) {
+ return path;
+ }
+ return convertToAbsolute(path);
+ },
+
+ // 创建路径检查器
+ createPathChecker() {
+ return {
+ // 检查路径是否符合要求
+ check(path: PathArray) {
+ if (!isAbsoluteArray(path)) {
+ throw new Error('Path must use absolute coordinates');
+ }
+ return true;
+ },
+
+ // 验证并修复路径
+ validate(path: PathArray) {
+ return this.check(path) ? path : this.ensureAbsolute(path);
+ }
+ };
+ }
+ };
+}
+
+// 使用示例
+const validator = createPathValidator();
+
+// 验证路径
+const pathInfo = validator.validatePath(absolutePath);
+console.log(pathInfo);
+// {
+// isValid: true,
+// isAbsolute: true,
+// hasRelative: false,
+// commands: ['M', 'L', 'L']
+// }
+
+// 路径检查器
+const checker = validator.createPathChecker();
+
+try {
+ checker.check(relativePath);
+} catch (error) {
+ console.log(error.message); // "Path must use absolute coordinates"
+}
+
+// 辅助函数:转换为绝对坐标
+function convertToAbsolute(path: PathArray): AbsoluteArray {
+ let currentX = 0;
+ let currentY = 0;
+
+ return path.map(segment => {
+ const [cmd, ...params] = segment;
+ if (cmd === cmd.toLowerCase()) {
+ // 转换相对坐标为绝对坐标
+ const absolute = [cmd.toUpperCase()];
+ for (let i = 0; i < params.length; i += 2) {
+ currentX += params[i] || 0;
+ currentY += params[i + 1] || 0;
+ absolute.push(currentX, currentY);
+ }
+ return absolute;
+ }
+ // 更新当前位置
+ if (params.length >= 2) {
+ [currentX, currentY] = params.slice(-2);
+ }
+ return segment;
+ }) as AbsoluteArray;
+}
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| path | 路径 | string \| PathArray | - | 要检查的路径 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 判断结果 | boolean | - | 是否为绝对坐标路径 |
+
+
+## isCurveArray
+
+判断路径数组是否全部由三次贝塞尔曲线段(C)和移动命令(M)组成。
+
+- 功能说明
+
+- 检查路径是否为标准化的曲线数组
+- 验证所有段是否为 M 或 C 命令
+- 提供类型保护功能
+- 用于曲线路径验证
+- 确保路径格式一致性
+
+```ts
+import { isCurveArray } from '@antv/util';
+
+// 基本使用
+const curvePath: PathArray = [
+ ['M', 0, 0],
+ ['C', 20, 20, 40, 20, 60, 0]
+];
+console.log(isCurveArray(curvePath)); // true
+
+const mixedPath: PathArray = [
+ ['M', 0, 0],
+ ['L', 100, 0],
+ ['Q', 150, 50, 200, 0]
+];
+console.log(isCurveArray(mixedPath)); // false
+
+// 实际应用示例
+function createCurveUtil() {
+ return {
+ // 验证曲线路径
+ validateCurve(path: PathArray) {
+ return {
+ isCurve: isCurveArray(path),
+ segments: path.map(([cmd]) => cmd),
+ isValid: path.every(([cmd]) => 'MC'.includes(cmd))
+ };
+ },
+
+ // 确保路径为曲线格式
+ ensureCurve(path: PathArray) {
+ if (isCurveArray(path)) {
+ return path;
+ }
+ return convertToCurve(path);
+ },
+
+ // 创建曲线处理器
+ createCurveProcessor() {
+ return {
+ // 处理路径
+ process(path: PathArray) {
+ const curves = this.ensureCurve(path);
+ return {
+ // 获取控制点
+ getControlPoints() {
+ return curves.reduce((points, segment) => {
+ if (segment[0] === 'C') {
+ points.push(
+ [segment[1], segment[2]],
+ [segment[3], segment[4]]
+ );
+ }
+ return points;
+ }, [] as [number, number][]);
+ },
+
+ // 简化曲线
+ simplify(tolerance = 1) {
+ return simplifyBezier(curves, tolerance);
+ }
+ };
+ }
+ };
+ }
+ };
+}
+
+// 使用示例
+const curveUtil = createCurveUtil();
+
+// 验证曲线
+const curveInfo = curveUtil.validateCurve(curvePath);
+console.log(curveInfo);
+// {
+// isCurve: true,
+// segments: ['M', 'C'],
+// isValid: true
+// }
+
+// 曲线处理
+const processor = curveUtil.createCurveProcessor();
+const processed = processor.process(curvePath);
+console.log(processed.getControlPoints());
+
+// 辅助函数:转换为曲线
+function convertToCurve(path: PathArray): PathArray {
+ return path.map((segment, i) => {
+ const [cmd, ...params] = segment;
+ if (cmd === 'M') return segment;
+ if (cmd === 'L') {
+ const prev = path[i - 1];
+ const [x1, y1] = prev.slice(-2);
+ const [x2, y2] = params;
+ // 将直线转换为曲线
+ return [
+ 'C',
+ x1 + (x2 - x1) / 3,
+ y1 + (y2 - y1) / 3,
+ x1 + 2 * (x2 - x1) / 3,
+ y1 + 2 * (y2 - y1) / 3,
+ x2,
+ y2
+ ];
+ }
+ // 其他命令的转换...
+ return segment;
+ });
+}
+
+// 简化贝塞尔曲线
+function simplifyBezier(curves: PathArray, tolerance: number): PathArray {
+ // 实现曲线简化算法...
+ return curves;
+}
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| path | 路径 | string \| PathArray | - | 要检查的路径 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 判断结果 | boolean | - | 是否为曲线数组 |
+
+
+## isNormalizedArray
+
+判断路径数组是否为标准化的绝对坐标路径(不包含简写命令)。
+
+- 功能说明
+
+- 检查路径是否使用绝对坐标
+- 验证是否只包含标准命令(A、C、L、M、Q、Z)
+- 不允许使用简写命令(H、V、S、T)
+- 提供类型保护功能
+- 确保路径格式标准化
+
+```ts
+import { isNormalizedArray } from '@antv/util';
+
+// 基本使用
+const normalizedPath: PathArray = [
+ ['M', 0, 0],
+ ['L', 100, 0],
+ ['C', 150, 0, 200, 50, 200, 100]
+];
+console.log(isNormalizedArray(normalizedPath)); // true
+
+const shorthandPath: PathArray = [
+ ['M', 0, 0],
+ ['H', 100], // 简写命令
+ ['V', 100] // 简写命令
+];
+console.log(isNormalizedArray(shorthandPath)); // false
+
+// 实际应用示例
+function createPathNormalizer() {
+ return {
+ // 验证路径格式
+ validatePath(path: PathArray) {
+ return {
+ isNormalized: isNormalizedArray(path),
+ hasShorthand: path.some(([cmd]) => 'HSTVT'.includes(cmd)),
+ commands: path.map(([cmd]) => cmd),
+ isAbsolute: path.every(([cmd]) => cmd === cmd.toUpperCase())
+ };
+ },
+
+ // 标准化路径
+ normalizePath(path: PathArray) {
+ if (isNormalizedArray(path)) {
+ return path;
+ }
+ return convertToNormalized(path);
+ },
+
+ // 创建路径处理器
+ createPathProcessor() {
+ return {
+ // 处理并验证路径
+ process(path: PathArray) {
+ const normalized = this.normalizePath(path);
+
+ return {
+ path: normalized,
+ // 获取所有点
+ getPoints() {
+ return normalized.map(segment =>
+ segment.slice(-2) as [number, number]
+ );
+ },
+ // 获取命令统计
+ getCommandStats() {
+ return normalized.reduce((stats, [cmd]) => {
+ stats[cmd] = (stats[cmd] || 0) + 1;
+ return stats;
+ }, {} as Record);
+ }
+ };
+ }
+ };
+ }
+ };
+}
+
+// 使用示例
+const normalizer = createPathNormalizer();
+
+// 验证路径
+const pathInfo = normalizer.validatePath(normalizedPath);
+console.log(pathInfo);
+// {
+// isNormalized: true,
+// hasShorthand: false,
+// commands: ['M', 'L', 'C'],
+// isAbsolute: true
+// }
+
+// 路径处理
+const processor = normalizer.createPathProcessor();
+const processed = processor.process(shorthandPath);
+console.log(processed.getCommandStats());
+
+// 辅助函数:转换为标准化格式
+function convertToNormalized(path: PathArray): PathArray {
+ let currentX = 0;
+ let currentY = 0;
+
+ return path.map(segment => {
+ const [cmd, ...params] = segment;
+ switch (cmd) {
+ case 'H':
+ currentX = params[0];
+ return ['L', currentX, currentY];
+ case 'V':
+ currentY = params[0];
+ return ['L', currentX, currentY];
+ case 'S':
+ // 转换平滑曲线命令为完整的贝塞尔曲线
+ // 实现转换逻辑...
+ break;
+ case 'T':
+ // 转换平滑二次贝塞尔曲线为标准格式
+ // 实现转换逻辑...
+ break;
+ default:
+ if (params.length >= 2) {
+ [currentX, currentY] = params.slice(-2);
+ }
+ return segment;
+ }
+ });
+}
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| path | 路径 | string \| PathArray | - | 要检查的路径 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 判断结果 | boolean | - | 是否为标准化路径 |
+
+- 标准命令列表
+
+| 命令 | 说明 | 参数格式 |
+|---------|------|------|
+| A | 弧线 | rx,ry,angle,large-arc,sweep,x,y |
+| C | 三次贝塞尔曲线 | x1,y1,x2,y2,x,y |
+| L | 直线 | x,y |
+| M | 移动 | x,y |
+| Q | 二次贝塞尔曲线 | x1,y1,x,y |
+| Z | 闭合路径 | 无参数 |
+
+
+## isPathArray
+
+判断是否为有效的路径数组。
+
+- 功能说明
+
+- 验证数组是否为有效的路径数组
+- 检查每个段的命令和参数数量
+- 支持所有标准 SVG 路径命令
+- 提供类型保护功能
+- 验证路径格式的完整性
+
+```ts
+import { isPathArray } from '@antv/util';
+
+// 基本使用
+const validPath: PathArray = [
+ ['M', 0, 0],
+ ['L', 100, 0],
+ ['C', 150, 0, 200, 50, 200, 100]
+];
+console.log(isPathArray(validPath)); // true
+
+const invalidPath = [
+ ['M', 0], // 参数不足
+ ['L', 100, 0],
+ ['X', 200, 0] // 无效命令
+];
+console.log(isPathArray(invalidPath)); // false
+
+// 实际应用示例
+function createPathValidator() {
+ return {
+ // 验证路径
+ validatePath(path: any) {
+ if (!isPathArray(path)) {
+ return {
+ isValid: false,
+ errors: this.getValidationErrors(path)
+ };
+ }
+
+ return {
+ isValid: true,
+ commands: path.map(([cmd]) => cmd)
+ };
+ },
+
+ // 获取验证错误
+ getValidationErrors(path: any[]) {
+ const errors = [];
+
+ if (!Array.isArray(path)) {
+ errors.push('Input must be an array');
+ return errors;
+ }
+
+ path.forEach((segment, index) => {
+ if (!Array.isArray(segment)) {
+ errors.push(`Segment ${index} must be an array`);
+ return;
+ }
+
+ const [cmd] = segment;
+ const lowerCmd = cmd.toLowerCase();
+
+ if (!'achlmqstvz'.includes(lowerCmd)) {
+ errors.push(`Invalid command "${cmd}" at segment ${index}`);
+ }
+
+ const expectedParams = paramsCount[lowerCmd];
+ const actualParams = segment.length - 1;
+
+ if (expectedParams !== actualParams) {
+ errors.push(
+ `Wrong number of parameters for "${cmd}" command at segment ${index}. ` +
+ `Expected ${expectedParams}, got ${actualParams}`
+ );
+ }
+ });
+
+ return errors;
+ },
+
+ // 创建路径构建器
+ createPathBuilder() {
+ const segments: PathArray = [];
+
+ return {
+ moveTo(x: number, y: number) {
+ segments.push(['M', x, y]);
+ return this;
+ },
+
+ lineTo(x: number, y: number) {
+ segments.push(['L', x, y]);
+ return this;
+ },
+
+ curveTo(x1: number, y1: number, x2: number, y2: number, x: number, y: number) {
+ segments.push(['C', x1, y1, x2, y2, x, y]);
+ return this;
+ },
+
+ close() {
+ segments.push(['Z']);
+ return this;
+ },
+
+ build() {
+ if (!isPathArray(segments)) {
+ throw new Error('Invalid path construction');
+ }
+ return segments;
+ }
+ };
+ }
+ };
+}
+
+// 使用示例
+const validator = createPathValidator();
+
+// 验证路径
+console.log(validator.validatePath(validPath));
+console.log(validator.validatePath(invalidPath));
+
+// 使用路径构建器
+const builder = validator.createPathBuilder();
+const newPath = builder
+ .moveTo(0, 0)
+ .lineTo(100, 0)
+ .curveTo(150, 0, 200, 50, 200, 100)
+ .close()
+ .build();
+
+console.log(isPathArray(newPath)); // true
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| path | 路径 | string \| PathArray | - | 要验证的路径 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 判断结果 | boolean | - | 是否为有效路径数组 |
+
+- 支持的命令
+
+| 命令 | 说明 | 参数数量 | 中文说明 |
+|---------|------|------|---------|
+| A/a | 弧线 | 7 | 绘制弧线 |
+| C/c | 三次贝塞尔曲线 | 6 | 绘制三次贝塞尔曲线 |
+| H/h | 水平线 | 1 | 绘制水平线 |
+| L/l | 直线 | 2 | 绘制直线 |
+| M/m | 移动 | 2 | 移动到点 |
+| Q/q | 二次贝塞尔曲线 | 4 | 绘制二次贝塞尔曲线 |
+| S/s | 平滑三次贝塞尔曲线 | 4 | 绘制平滑三次贝塞尔曲线 |
+| T/t | 平滑二次贝塞尔曲线 | 2 | 绘制平滑二次贝塞尔曲线 |
+| V/v | 垂直线 | 1 | 绘制垂直线 |
+| Z/z | 闭合路径 | 0 | 闭合路径 |
+
+
+## isPointInStroke
+
+判断点是否在路径的描边上。
+
+- 功能说明
+
+- 检测点是否位于路径的描边上
+- 支持字符串或数组格式的路径
+- 使用距离阈值进行判断
+- 高精度检测(默认阈值 0.001)
+- 基于最近点距离计算
+
+```ts
+import { isPointInStroke } from '@antv/util';
+
+// 基本使用
+const path: PathArray = [
+ ['M', 0, 0],
+ ['L', 100, 0],
+ ['L', 100, 100]
+];
+
+console.log(isPointInStroke(path, { x: 50, y: 0 })); // true
+console.log(isPointInStroke(path, { x: 50, y: 1 })); // false
+
+// 实际应用示例
+function createPathInteractor() {
+ return {
+ // 创建路径检测器
+ createDetector(path: PathArray, tolerance = 0.001) {
+ return {
+ // 检测点是否在路径上
+ isOnPath(point: Point) {
+ return isPointInStroke(path, point);
+ },
+
+ // 获取路径上的点
+ getPointsOnPath(points: Point[]) {
+ return points.filter(point =>
+ isPointInStroke(path, point)
+ );
+ },
+
+ // 创建点击区域检测
+ createClickDetector(threshold = 5) {
+ return (event: MouseEvent) => {
+ const point = {
+ x: event.clientX,
+ y: event.clientY
+ };
+ return this.isOnPath(point);
+ };
+ }
+ };
+ },
+
+ // 创建路径编辑器
+ createPathEditor(path: PathArray) {
+ const detector = this.createDetector(path);
+ let selectedPoint: Point | null = null;
+
+ return {
+ // 处理鼠标移动
+ handleMouseMove(point: Point) {
+ if (detector.isOnPath(point)) {
+ // 高亮路径...
+ return true;
+ }
+ return false;
+ },
+
+ // 处理点击
+ handleClick(point: Point) {
+ if (detector.isOnPath(point)) {
+ selectedPoint = point;
+ return true;
+ }
+ selectedPoint = null;
+ return false;
+ },
+
+ // 获取选中点
+ getSelectedPoint() {
+ return selectedPoint;
+ }
+ };
+ },
+
+ // 创建路径分析器
+ createPathAnalyzer(path: PathArray, sampleCount = 100) {
+ return {
+ // 获取路径上的采样点
+ getSamplePoints() {
+ const points: Point[] = [];
+ const length = getTotalLength(path);
+ const step = length / (sampleCount - 1);
+
+ for (let i = 0; i <= length; i += step) {
+ const point = getPointAtLength(path, i);
+ points.push(point);
+ }
+
+ return points;
+ },
+
+ // 检查点序列是否在路径上
+ checkPointSequence(points: Point[]) {
+ return points.every(point =>
+ isPointInStroke(path, point)
+ );
+ }
+ };
+ }
+ };
+}
+
+// 使用示例
+const interactor = createPathInteractor();
+
+// 创建检测器
+const detector = interactor.createDetector(path);
+console.log(detector.isOnPath({ x: 50, y: 0 }));
+
+// 创建编辑器
+const editor = interactor.createPathEditor(path);
+editor.handleMouseMove({ x: 50, y: 0 });
+
+// 创建分析器
+const analyzer = interactor.createPathAnalyzer(path);
+const samplePoints = analyzer.getSamplePoints();
+console.log(analyzer.checkPointSequence(samplePoints));
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| pathInput | 路径 | string \| PathArray | - | 要检测的路径 |
+| point | 检测点 | Point | - | 要检测的点坐标 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 判断结果 | boolean | - | 点是否在路径描边上 |
+
+
+## midPoint
+
+计算两点之间的中间点或按比例插值点。
+
+- 功能说明
+
+- 计算两点之间的插值点
+- 支持任意比例的插值
+- 线性插值计算
+- 返回插值点坐标
+- 用于路径和动画计算
+
+```ts
+import { midPoint } from '@antv/util';
+
+// 基本使用
+const point1 = [0, 0];
+const point2 = [100, 100];
+
+console.log(midPoint(point1, point2, 0.5)); // [50, 50] 中点
+console.log(midPoint(point1, point2, 0.25)); // [25, 25] 四分之一点
+console.log(midPoint(point1, point2, 0.75)); // [75, 75] 四分之三点
+
+// 实际应用示例
+function createInterpolator() {
+ return {
+ // 创建点插值器
+ createPointInterpolator(start: number[], end: number[]) {
+ return {
+ // 获取插值点
+ getPoint(t: number) {
+ return midPoint(start, end, Math.max(0, Math.min(1, t)));
+ },
+
+ // 获取多个插值点
+ getPoints(count: number) {
+ return Array.from({ length: count }, (_, i) =>
+ this.getPoint(i / (count - 1))
+ );
+ }
+ };
+ },
+
+ // 创建路径插值器
+ createPathInterpolator(points: number[][]) {
+ return {
+ // 获取路径上的点
+ getPointAtPercent(percent: number) {
+ const t = Math.max(0, Math.min(1, percent));
+ const totalSegments = points.length - 1;
+ const segment = Math.floor(t * totalSegments);
+ const segmentT = (t * totalSegments) % 1;
+
+ return midPoint(
+ points[segment],
+ points[Math.min(segment + 1, points.length - 1)],
+ segmentT
+ );
+ },
+
+ // 采样路径点
+ samplePoints(count: number) {
+ return Array.from({ length: count }, (_, i) =>
+ this.getPointAtPercent(i / (count - 1))
+ );
+ }
+ };
+ },
+
+ // 创建动画生成器
+ createAnimator(start: number[], end: number[], duration: number) {
+ const startTime = performance.now();
+
+ return {
+ // 获取当前动画帧
+ getCurrentPoint() {
+ const elapsed = performance.now() - startTime;
+ const progress = Math.min(elapsed / duration, 1);
+ return midPoint(start, end, progress);
+ },
+
+ // 启动动画
+ animate(callback: (point: number[]) => void) {
+ const update = () => {
+ const point = this.getCurrentPoint();
+ callback(point);
+
+ if (performance.now() - startTime < duration) {
+ requestAnimationFrame(update);
+ }
+ };
+
+ requestAnimationFrame(update);
+ }
+ };
+ }
+ };
+}
+
+// 使用示例
+const interpolator = createInterpolator();
+
+// 点插值
+const pointInterp = interpolator.createPointInterpolator([0, 0], [100, 100]);
+console.log(pointInterp.getPoints(5));
+
+// 路径插值
+const pathPoints = [[0, 0], [50, 50], [100, 0], [150, 50]];
+const pathInterp = interpolator.createPathInterpolator(pathPoints);
+console.log(pathInterp.samplePoints(10));
+
+// 动画示例
+const animator = interpolator.createAnimator([0, 0], [100, 100], 1000);
+animator.animate(point => {
+ console.log('Current position:', point);
+});
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| a | 起始点 | number[] | - | 起始点坐标 |
+| b | 终点 | number[] | - | 终点坐标 |
+| t | 插值比例 | number | - | 0-1 之间的插值比例 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| point | 插值点 | number[] | - | 计算得到的插值点坐标 |
+
+
+## pathLengthFactory
+
+路径长度计算工厂函数,用于计算路径的长度、指定距离的点位置和边界框。
+
+- 功能说明
+
+- 计算路径总长度
+- 获取指定距离处的点坐标
+- 计算路径的边界框
+- 支持所有标准路径命令
+- 处理复杂路径计算
+
+```ts
+import { pathLengthFactory } from '@antv/util';
+
+// 基本使用
+const path: PathArray = [
+ ['M', 0, 0],
+ ['L', 100, 0],
+ ['L', 100, 100]
+];
+
+console.log(pathLengthFactory(path));
+// {
+// length: 200, // 总长度
+// point: { x: 0, y: 0 }, // 起点
+// min: { x: 0, y: 0 }, // 最小坐标
+// max: { x: 100, y: 100 } // 最大坐标
+// }
+
+// 获取特定距离的点
+console.log(pathLengthFactory(path, 150));
+// 返回距离起点150单位的点坐标
+
+// 实际应用示例
+function createPathAnalyzer() {
+ return {
+ // 创建路径分析器
+ analyzePath(path: PathArray) {
+ const info = pathLengthFactory(path);
+
+ return {
+ // 获取路径信息
+ getInfo() {
+ return info;
+ },
+
+ // 获取路径上的点
+ getPointAt(distance: number) {
+ return pathLengthFactory(path, distance).point;
+ },
+
+ // 获取路径采样点
+ getSamplePoints(count: number) {
+ const { length } = info;
+ const step = length / (count - 1);
+
+ return Array.from({ length: count }, (_, i) =>
+ pathLengthFactory(path, i * step).point
+ );
+ },
+
+ // 创建动画控制器
+ createAnimationController(duration: number) {
+ const { length } = info;
+
+ return {
+ // 获取当前位置
+ getPosition(time: number) {
+ const progress = Math.min(time / duration, 1);
+ return pathLengthFactory(path, length * progress).point;
+ },
+
+ // 创建动画
+ animate(callback: (point: { x: number, y: number }) => void) {
+ const startTime = performance.now();
+
+ const update = (currentTime: number) => {
+ const elapsed = currentTime - startTime;
+ const point = this.getPosition(elapsed);
+ callback(point);
+
+ if (elapsed < duration) {
+ requestAnimationFrame(update);
+ }
+ };
+
+ requestAnimationFrame(update);
+ }
+ };
+ }
+ };
+ },
+
+ // 创建路径变换器
+ createPathTransformer(path: PathArray) {
+ const { min, max } = pathLengthFactory(path);
+
+ return {
+ // 缩放到指定大小
+ scaleToSize(width: number, height: number) {
+ const scaleX = width / (max.x - min.x);
+ const scaleY = height / (max.y - min.y);
+
+ return this.transformPath(path, {
+ scale: Math.min(scaleX, scaleY),
+ translate: [-min.x, -min.y]
+ });
+ }
+ };
+ }
+ };
+}
+
+// 使用示例
+const analyzer = createPathAnalyzer();
+const pathAnalysis = analyzer.analyzePath(path);
+
+// 获取路径信息
+console.log(pathAnalysis.getInfo());
+
+// 获取采样点
+console.log(pathAnalysis.getSamplePoints(10));
+
+// 创建动画
+const animation = pathAnalysis.createAnimationController(1000);
+animation.animate(point => {
+ console.log('Current position:', point);
+});
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| pathInput | 路径 | string \| PathArray | - | 要分析的路径 |
+| distance | 距离 | number | - | 可选的指定距离 |
+| options | 配置选项 | Partial | - | 可选的配置参数 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 计算结果 | LengthFactory | - | 包含长度和边界信息的对象 |
+
+- 返回对象属性
+
+| 属性 | 说明 | 类型 | 中文说明 |
+|---------|------|------|---------|
+| length | 总长度 | number | 路径的总长度 |
+| point | 指定点 | {x: number, y: number} | 指定距离处的点 |
+| min | 最小坐标 | {x: number, y: number} | 边界框最小坐标 |
+| max | 最大坐标 | {x: number, y: number} | 边界框最大坐标 |
+
+
+## rotateVector
+
+旋转二维向量,根据给定角度计算向量的新坐标。
+
+- 功能说明
+
+- 将二维向量绕原点旋转
+- 使用弧度表示旋转角度
+- 基于旋转矩阵计算
+- 返回新的坐标点
+- 保持向量长度不变
+
+```ts
+import { rotateVector } from '@antv/util';
+
+// 基本使用
+console.log(rotateVector(1, 0, Math.PI / 2)); // { x: 0, y: 1 } // 90度
+console.log(rotateVector(1, 0, Math.PI)); // { x: -1, y: 0 } // 180度
+console.log(rotateVector(0, 1, -Math.PI / 2)); // { x: 1, y: 0 } // -90度
+
+// 实际应用示例
+function createVectorUtil() {
+ return {
+ // 创建向量旋转器
+ createRotator(x: number, y: number) {
+ return {
+ // 旋转指定角度
+ rotate(angle: number) {
+ return rotateVector(x, y, angle);
+ },
+
+ // 获取多个角度的点
+ getPoints(angles: number[]) {
+ return angles.map(angle => this.rotate(angle));
+ },
+
+ // 创建均匀分布的点
+ createEvenPoints(count: number) {
+ const step = (Math.PI * 2) / count;
+ return Array.from({ length: count }, (_, i) =>
+ this.rotate(i * step)
+ );
+ }
+ };
+ },
+
+ // 创建动画旋转器
+ createAnimatedRotator(x: number, y: number) {
+ return {
+ // 创建旋转动画
+ animate(duration: number, rounds = 1) {
+ const startTime = performance.now();
+ const totalAngle = Math.PI * 2 * rounds;
+
+ return {
+ // 获取当前位置
+ getCurrentPosition(currentTime: number) {
+ const elapsed = currentTime - startTime;
+ const progress = Math.min(elapsed / duration, 1);
+ return rotateVector(x, y, totalAngle * progress);
+ },
+
+ // 启动动画
+ start(callback: (point: { x: number, y: number }) => void) {
+ const update = (time: number) => {
+ const position = this.getCurrentPosition(time);
+ callback(position);
+
+ if (time - startTime < duration) {
+ requestAnimationFrame(update);
+ }
+ };
+
+ requestAnimationFrame(update);
+ }
+ };
+ }
+ };
+ },
+
+ // 创建形状生成器
+ createShapeGenerator() {
+ return {
+ // 生成正多边形的点
+ createRegularPolygon(radius: number, sides: number) {
+ const rotator = this.createRotator(radius, 0);
+ return rotator.createEvenPoints(sides);
+ },
+
+ // 生成星形的点
+ createStar(outerRadius: number, innerRadius: number, points: number) {
+ const angleStep = Math.PI / points;
+ const allPoints = [];
+
+ for (let i = 0; i < points * 2; i++) {
+ const radius = i % 2 === 0 ? outerRadius : innerRadius;
+ const angle = i * angleStep;
+ allPoints.push(rotateVector(radius, 0, angle));
+ }
+
+ return allPoints;
+ }
+ };
+ }
+ };
+}
+
+// 使用示例
+const vectorUtil = createVectorUtil();
+
+// 基本旋转
+const rotator = vectorUtil.createRotator(100, 0);
+console.log(rotator.rotate(Math.PI / 4)); // 45度旋转
+
+// 创建均匀分布的点
+console.log(rotator.createEvenPoints(6)); // 六边形的顶点
+
+// 动画旋转
+const animator = vectorUtil.createAnimatedRotator(100, 0);
+const animation = animator.animate(1000, 2); // 1秒转2圈
+animation.start(point => {
+ console.log('Current position:', point);
+});
+
+// 生成形状
+const shapeGenerator = vectorUtil.createShapeGenerator();
+console.log(shapeGenerator.createRegularPolygon(100, 5)); // 正五边形
+console.log(shapeGenerator.createStar(100, 50, 5)); // 五角星
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| x | x 坐标 | number | - | 向量的 x 坐标 |
+| y | y 坐标 | number | - | 向量的 y 坐标 |
+| rad | 旋转角度 | number | - | 旋转的弧度值 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 旋转结果 | {x: number, y: number} | - | 旋转后的坐标点 |
+
+
+## segmentArcFactory
+
+计算弧线段的长度、指定距离的点位置和边界框。
+
+- 功能说明
+
+- 计算弧线段的总长度
+- 获取弧线上指定距离的点
+- 计算弧线的边界框
+- 支持椭圆弧参数
+- 提供采样点精度控制
+
+```ts
+import { segmentArcFactory } from '@antv/util';
+
+// 基本使用
+const arcInfo = segmentArcFactory(
+ 0, 0, // 起点
+ 100, 50, // 半径
+ 45, // 旋转角度
+ 0, // 大弧标志
+ 1, // 顺时针标志
+ 100, 100, // 终点
+ 0, // 距离
+ { sampleSize: 30 } // 配置选项
+);
+
+console.log(arcInfo);
+// {
+// length: 数值, // 弧线长度
+// point: {x, y}, // 指定距离的点
+// min: {x, y}, // 最小坐标
+// max: {x, y} // 最大坐标
+// }
+
+// 实际应用示例
+function createArcUtil() {
+ return {
+ // 创建弧线分析器
+ createArcAnalyzer(
+ start: [number, number],
+ radii: [number, number],
+ angle: number,
+ flags: [number, number],
+ end: [number, number]
+ ) {
+ return {
+ // 获取弧线信息
+ getInfo(options = {}) {
+ return segmentArcFactory(
+ ...start,
+ ...radii,
+ angle,
+ ...flags,
+ ...end,
+ 0,
+ options
+ );
+ },
+
+ // 获取采样点
+ getSamplePoints(count: number) {
+ const { length } = this.getInfo();
+ const step = length / (count - 1);
+
+ return Array.from({ length: count }, (_, i) =>
+ segmentArcFactory(
+ ...start,
+ ...radii,
+ angle,
+ ...flags,
+ ...end,
+ i * step,
+ { sampleSize: count }
+ ).point
+ );
+ },
+
+ // 创建动画控制器
+ createAnimationController(duration: number) {
+ const { length } = this.getInfo();
+
+ return {
+ // 获取当前位置
+ getPosition(time: number) {
+ const progress = Math.min(time / duration, 1);
+ return segmentArcFactory(
+ ...start,
+ ...radii,
+ angle,
+ ...flags,
+ ...end,
+ length * progress,
+ { sampleSize: 60 }
+ ).point;
+ },
+
+ // 启动动画
+ animate(callback: (point: { x: number, y: number }) => void) {
+ const startTime = performance.now();
+
+ const update = (currentTime: number) => {
+ const elapsed = currentTime - startTime;
+ const point = this.getPosition(elapsed);
+ callback(point);
+
+ if (elapsed < duration) {
+ requestAnimationFrame(update);
+ }
+ };
+
+ requestAnimationFrame(update);
+ }
+ };
+ }
+ };
+ }
+ };
+}
+
+// 使用示例
+const arcUtil = createArcUtil();
+
+// 创建弧线分析器
+const analyzer = arcUtil.createArcAnalyzer(
+ [0, 0], // 起点
+ [100, 50], // 半径
+ 45, // 角度
+ [0, 1], // 标志
+ [100, 100] // 终点
+);
+
+// 获取弧线信息
+console.log(analyzer.getInfo());
+
+// 获取采样点
+console.log(analyzer.getSamplePoints(10));
+
+// 创建动画
+const animation = analyzer.createAnimationController(1000);
+animation.animate(point => {
+ console.log('Current position:', point);
+});
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| X1, Y1 | 起点坐标 | number | - | 弧线起点 |
+| RX, RY | 半径 | number | - | 椭圆半径 |
+| angle | 旋转角度 | number | - | 椭圆旋转角度 |
+| LAF | 大弧标志 | number | - | 是否选择大弧 |
+| SF | 方向标志 | number | - | 是否顺时针 |
+| X2, Y2 | 终点坐标 | number | - | 弧线终点 |
+| distance | 距离 | number | - | 指定距离 |
+| options | 配置选项 | Partial | - | 配置参数 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 计算结果 | LengthFactory | - | 包含长度和边界信息的对象 |
+
+- 配置选项
+
+| 选项 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| bbox | 是否计算边界框 | boolean | true | 是否计算边界框 |
+| length | 是否计算长度 | boolean | true | 是否计算长度 |
+| sampleSize | 采样点数量 | number | 30 | 采样精度 |
+
+
+## segmentCubicFactory
+
+计算三次贝塞尔曲线段的长度、指定距离的点位置和边界框。
+
+- 功能说明
+
+- 计算贝塞尔曲线段的总长度
+- 获取曲线上指定距离的点
+- 计算曲线的边界框
+- 支持采样点精度控制
+- 基于贝塞尔曲线公式计算
+
+```ts
+import { segmentCubicFactory } from '@antv/util';
+
+// 基本使用
+const curveInfo = segmentCubicFactory(
+ 0, 0, // 起点
+ 50, 0, // 控制点1
+ 50, 100, // 控制点2
+ 100, 100, // 终点
+ 0, // 距离
+ { sampleSize: 10 } // 配置选项
+);
+
+console.log(curveInfo);
+// {
+// length: 数值, // 曲线长度
+// point: {x, y}, // 指定距离的点
+// min: {x, y}, // 最小坐标
+// max: {x, y} // 最大坐标
+// }
+
+// 实际应用示例
+function createBezierUtil() {
+ return {
+ // 创建贝塞尔曲线分析器
+ createCurveAnalyzer(
+ start: [number, number],
+ cp1: [number, number],
+ cp2: [number, number],
+ end: [number, number]
+ ) {
+ return {
+ // 获取曲线信息
+ getInfo(options = {}) {
+ return segmentCubicFactory(
+ ...start,
+ ...cp1,
+ ...cp2,
+ ...end,
+ 0,
+ options
+ );
+ },
+
+ // 获取采样点
+ getSamplePoints(count: number) {
+ const { length } = this.getInfo();
+ const step = length / (count - 1);
+
+ return Array.from({ length: count }, (_, i) =>
+ segmentCubicFactory(
+ ...start,
+ ...cp1,
+ ...cp2,
+ ...end,
+ i * step,
+ { sampleSize: count }
+ ).point
+ );
+ },
+
+ // 创建动画控制器
+ createAnimationController(duration: number) {
+ const { length } = this.getInfo();
+
+ return {
+ // 获取当前位置
+ getPosition(time: number) {
+ const progress = Math.min(time / duration, 1);
+ return segmentCubicFactory(
+ ...start,
+ ...cp1,
+ ...cp2,
+ ...end,
+ length * progress,
+ { sampleSize: 30 }
+ ).point;
+ },
+
+ // 创建动画
+ animate(callback: (point: { x: number, y: number }) => void) {
+ const startTime = performance.now();
+
+ const update = (currentTime: number) => {
+ const elapsed = currentTime - startTime;
+ const point = this.getPosition(elapsed);
+ callback(point);
+
+ if (elapsed < duration) {
+ requestAnimationFrame(update);
+ }
+ };
+
+ requestAnimationFrame(update);
+ }
+ };
+ }
+ };
+ },
+
+ // 创建路径生成器
+ createPathGenerator() {
+ return {
+ // 生成平滑曲线路径
+ createSmoothPath(points: [number, number][], tension = 0.5) {
+ const result = [];
+
+ for (let i = 0; i < points.length - 1; i++) {
+ const p0 = points[Math.max(0, i - 1)];
+ const p1 = points[i];
+ const p2 = points[i + 1];
+ const p3 = points[Math.min(points.length - 1, i + 2)];
+
+ const cp1 = [
+ p1[0] + (p2[0] - p0[0]) * tension,
+ p1[1] + (p2[1] - p0[1]) * tension
+ ];
+
+ const cp2 = [
+ p2[0] - (p3[0] - p1[0]) * tension,
+ p2[1] - (p3[1] - p1[1]) * tension
+ ];
+
+ result.push([p1, cp1, cp2, p2]);
+ }
+
+ return result;
+ }
+ };
+ }
+ };
+}
+
+// 使用示例
+const bezierUtil = createBezierUtil();
+
+// 创建曲线分析器
+const analyzer = bezierUtil.createCurveAnalyzer(
+ [0, 0], // 起点
+ [50, 0], // 控制点1
+ [50, 100], // 控制点2
+ [100, 100] // 终点
+);
+
+// 获取曲线信息
+console.log(analyzer.getInfo());
+
+// 获取采样点
+console.log(analyzer.getSamplePoints(10));
+
+// 创建动画
+const animation = analyzer.createAnimationController(1000);
+animation.animate(point => {
+ console.log('Current position:', point);
+});
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| x1, y1 | 起点坐标 | number | - | 曲线起点 |
+| c1x, c1y | 控制点 1 | number | - | 第一控制点 |
+| c2x, c2y | 控制点 2 | number | - | 第二控制点 |
+| x2, y2 | 终点坐标 | number | - | 曲线终点 |
+| distance | 距离 | number | - | 指定距离 |
+| options | 配置选项 | Partial | - | 配置参数 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 计算结果 | LengthFactory | - | 包含长度和边界信息的对象 |
+
+- 配置选项
+
+| 选项 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| bbox | 是否计算边界框 | boolean | true | 是否计算边界框 |
+| length | 是否计算长度 | boolean | true | 是否计算长度 |
+| sampleSize | 采样点数量 | number | 10 | 采样精度 |
+
+
+## segmentLineFactory
+
+计算直线段的长度、指定距离的点位置和边界框。
+
+- 功能说明
+
+- 计算直线段的总长度
+- 获取线段上指定距离的点
+- 计算线段的边界框
+- 支持垂直线和水平线
+- 处理闭合路径(Z 命令)
+
+```ts
+import { segmentLineFactory } from '@antv/util';
+
+// 基本使用
+const lineInfo = segmentLineFactory(
+ 0, 0, // 起点
+ 100, 100, // 终点
+ 50 // 距离
+);
+
+console.log(lineInfo);
+// {
+// length: 141.42, // 线段长度
+// point: { x: 50, y: 50 }, // 指定距离的点
+// min: { x: 0, y: 0 }, // 最小坐标
+// max: { x: 100, y: 100 } // 最大坐标
+// }
+
+// 实际应用示例
+function createLineUtil() {
+ return {
+ // 创建线段分析器
+ createLineAnalyzer(start: [number, number], end: [number, number]) {
+ return {
+ // 获取线段信息
+ getInfo(distance?: number) {
+ return segmentLineFactory(
+ ...start,
+ ...end,
+ distance || 0
+ );
+ },
+
+ // 获取等分点
+ getDivisionPoints(divisions: number) {
+ const { length } = this.getInfo();
+ const step = length / divisions;
+
+ return Array.from({ length: divisions + 1 }, (_, i) =>
+ segmentLineFactory(...start, ...end, i * step).point
+ );
+ },
+
+ // 检查点是否在线段上
+ isPointOnLine(point: [number, number], tolerance = 0.1) {
+ const { length } = this.getInfo();
+ const d1 = distanceSquareRoot(start, point);
+ const d2 = distanceSquareRoot(point, end);
+
+ return Math.abs(d1 + d2 - length) <= tolerance;
+ }
+ };
+ },
+
+ // 创建动画控制器
+ createAnimationController(
+ start: [number, number],
+ end: [number, number],
+ duration: number
+ ) {
+ const { length } = segmentLineFactory(...start, ...end, 0);
+
+ return {
+ // 获取当前位置
+ getPosition(time: number) {
+ const progress = Math.min(time / duration, 1);
+ return segmentLineFactory(
+ ...start,
+ ...end,
+ length * progress
+ ).point;
+ },
+
+ // 创建动画
+ animate(callback: (point: { x: number, y: number }) => void) {
+ const startTime = performance.now();
+
+ const update = (currentTime: number) => {
+ const elapsed = currentTime - startTime;
+ const point = this.getPosition(elapsed);
+ callback(point);
+
+ if (elapsed < duration) {
+ requestAnimationFrame(update);
+ }
+ };
+
+ requestAnimationFrame(update);
+ }
+ };
+ },
+
+ // 创建网格生成器
+ createGridGenerator() {
+ return {
+ // 生成网格线
+ generateGrid(width: number, height: number, cols: number, rows: number) {
+ const lines = [];
+ const dx = width / cols;
+ const dy = height / rows;
+
+ // 垂直线
+ for (let i = 0; i <= cols; i++) {
+ const x = i * dx;
+ lines.push(segmentLineFactory(x, 0, x, height, 0));
+ }
+
+ // 水平线
+ for (let i = 0; i <= rows; i++) {
+ const y = i * dy;
+ lines.push(segmentLineFactory(0, y, width, y, 0));
+ }
+
+ return lines;
+ }
+ };
+ }
+ };
+}
+
+// 使用示例
+const lineUtil = createLineUtil();
+
+// 创建线段分析器
+const analyzer = lineUtil.createLineAnalyzer([0, 0], [100, 100]);
+console.log(analyzer.getInfo());
+console.log(analyzer.getDivisionPoints(4));
+console.log(analyzer.isPointOnLine([50, 50]));
+
+// 创建动画
+const animation = lineUtil.createAnimationController(
+ [0, 0],
+ [100, 100],
+ 1000
+);
+animation.animate(point => {
+ console.log('Current position:', point);
+});
+
+// 生成网格
+const gridGenerator = lineUtil.createGridGenerator();
+const grid = gridGenerator.generateGrid(200, 200, 4, 4);
+console.log(grid);
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| x1, y1 | 起点坐标 | number | - | 线段起点 |
+| x2, y2 | 终点坐标 | number | - | 线段终点 |
+| distance | 距离 | number | - | 指定距离 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 计算结果 | LengthFactory | - | 包含长度和边界信息的对象 |
+
+
+## segmentQuadFactory
+
+计算二次贝塞尔曲线段的长度、指定距离的点位置和边界框。
+
+- 功能说明
+
+- 计算二次贝塞尔曲线的总长度
+- 获取曲线上指定距离的点
+- 计算曲线的边界框
+- 支持采样点精度控制
+- 基于二次贝塞尔曲线公式计算
+
+```ts
+import { segmentQuadFactory } from '@antv/util';
+
+// 基本使用
+const curveInfo = segmentQuadFactory(
+ 0, 0, // 起点
+ 50, 50, // 控制点
+ 100, 0, // 终点
+ 0, // 距离
+ { sampleSize: 10 } // 配置选项
+);
+
+console.log(curveInfo);
+// {
+// length: 数值, // 曲线长度
+// point: {x, y}, // 指定距离的点
+// min: {x, y}, // 最小坐标
+// max: {x, y} // 最大坐标
+// }
+
+// 实际应用示例
+function createQuadBezierUtil() {
+ return {
+ // 创建曲线分析器
+ createAnalyzer(
+ start: [number, number],
+ control: [number, number],
+ end: [number, number]
+ ) {
+ return {
+ // 获取曲线信息
+ getInfo(options = {}) {
+ return segmentQuadFactory(
+ ...start,
+ ...control,
+ ...end,
+ 0,
+ options
+ );
+ },
+
+ // 获取采样点
+ getSamplePoints(count: number) {
+ const { length } = this.getInfo();
+ const step = length / (count - 1);
+
+ return Array.from({ length: count }, (_, i) =>
+ segmentQuadFactory(
+ ...start,
+ ...control,
+ ...end,
+ i * step,
+ { sampleSize: count }
+ ).point
+ );
+ },
+
+ // 创建动画控制器
+ createAnimationController(duration: number) {
+ const { length } = this.getInfo();
+
+ return {
+ // 获取当前位置
+ getPosition(time: number) {
+ const progress = Math.min(time / duration, 1);
+ return segmentQuadFactory(
+ ...start,
+ ...control,
+ ...end,
+ length * progress,
+ { sampleSize: 30 }
+ ).point;
+ },
+
+ // 创建动画
+ animate(callback: (point: { x: number, y: number }) => void) {
+ const startTime = performance.now();
+
+ const update = (currentTime: number) => {
+ const elapsed = currentTime - startTime;
+ const point = this.getPosition(elapsed);
+ callback(point);
+
+ if (elapsed < duration) {
+ requestAnimationFrame(update);
+ }
+ };
+
+ requestAnimationFrame(update);
+ }
+ };
+ }
+ };
+ },
+
+ // 创建路径生成器
+ createPathGenerator() {
+ return {
+ // 生成平滑曲线
+ createSmoothCurve(points: [number, number][], tension = 0.5) {
+ const result = [];
+
+ for (let i = 0; i < points.length - 1; i++) {
+ const p0 = points[i];
+ const p1 = points[i + 1];
+ const cp = [
+ p0[0] + (p1[0] - p0[0]) * tension,
+ (p0[1] + p1[1]) / 2
+ ];
+
+ result.push([p0, cp, p1]);
+ }
+
+ return result;
+ }
+ };
+ }
+ };
+}
+
+// 使用示例
+const bezierUtil = createQuadBezierUtil();
+
+// 创建分析器
+const analyzer = bezierUtil.createAnalyzer(
+ [0, 0], // 起点
+ [50, 50], // 控制点
+ [100, 0] // 终点
+);
+
+// 获取曲线信息
+console.log(analyzer.getInfo());
+
+// 获取采样点
+console.log(analyzer.getSamplePoints(10));
+
+// 创建动画
+const animation = analyzer.createAnimationController(1000);
+animation.animate(point => {
+ console.log('Current position:', point);
+});
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| x1, y1 | 起点坐标 | number | - | 曲线起点 |
+| qx, qy | 控制点 | number | - | 控制点坐标 |
+| x2, y2 | 终点坐标 | number | - | 曲线终点 |
+| distance | 距离 | number | - | 指定距离 |
+| options | 配置选项 | Partial | - | 配置参数 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 计算结果 | LengthFactory | - | 包含长度和边界信息的对象 |
+
+- 配置选项
+
+| 选项 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| bbox | 是否计算边界框 | boolean | true | 是否计算边界框 |
+| length | 是否计算长度 | boolean | true | 是否计算长度 |
+| sampleSize | 采样点数量 | number | 10 | 采样精度 |
+
+
+## path2Absolute
+
+将路径数组或路径字符串转换为绝对坐标路径数组。
+
+- 功能说明
+
+- 将相对路径命令转换为绝对路径命令
+- 支持所有 SVG 路径命令(M, L, H, V, C, S, Q, T, A, Z)
+- 保持路径的形状不变
+- 跟踪当前绘制点的位置
+- 处理闭合路径命令(Z)
+
+```ts
+import { path2Absolute } from '@antv/util';
+
+// 基本路径转换
+const relativePath = 'm10,20 l30,40';
+console.log(path2Absolute(relativePath));
+// [['M', 10, 20], ['L', 40, 60]]
+
+// 复杂路径转换
+const complexPath = 'M10 10 h 80 v 80 h -80 Z';
+console.log(path2Absolute(complexPath));
+// [
+// ['M', 10, 10],
+// ['H', 90],
+// ['V', 90],
+// ['H', 10],
+// ['Z']
+// ]
+
+// 曲线路径转换
+const curvePath = 'm10,10 c10,20 30,40 50,60';
+console.log(path2Absolute(curvePath));
+// [
+// ['M', 10, 10],
+// ['C', 20, 30, 40, 50, 60, 70]
+// ]
+
+// 实际应用示例
+function normalizePath(pathString: string) {
+ const absolutePath = path2Absolute(pathString);
+ return absolutePath.map(segment => {
+ const [command, ...params] = segment;
+ return `${command}${params.join(',')}`;
+ }).join(' ');
+}
+
+// 使用示例
+const path = 'm10,20 l30,40 h50 z';
+console.log(normalizePath(path));
+// "M10,20 L40,60 H90 Z"
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| pathInput | 路径输入 | string \| PathArray | - | SVG 路径字符串或路径数组 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 绝对路径数组 | AbsoluteArray | - | 转换后的绝对坐标路径数组 |
+
+- 支持的路径命令
+
+| 命令 | 说明 | 相对命令 | 绝对命令 |
+|---------|------|------|---------|
+| M/m | 移动到 | m | M |
+| L/l | 画线到 | l | L |
+| H/h | 水平线到 | h | H |
+| V/v | 垂直线到 | v | V |
+| C/c | 三次贝塞尔曲线 | c | C |
+| S/s | 平滑三次贝塞尔曲线 | s | S |
+| Q/q | 二次贝塞尔曲线 | q | Q |
+| T/t | 平滑二次贝塞尔曲线 | t | T |
+| A/a | 弧线 | a | A |
+| Z/z | 闭合路径 | z | Z |
+
+- 注意事项
+
+1. 输入可以是字符串或路径数组
+2. 所有数值会被转换为数字类型
+3. 保持原始路径的形状不变
+4. 跟踪当前点位置以计算绝对坐标
+5. 处理路径闭合时返回到起始点
+
+
+## path2Array
+
+将 SVG 路径字符串转换为标准的路径数组格式。
+
+- 功能说明
+
+- 将 SVG 路径字符串解析为数组格式
+- 使用 parsePathString 进行解析
+- 返回标准化的路径数组
+- 便于路径数据的处理和操作
+- 类型安全的转换
+
+```ts
+import { path2Array } from '@antv/util';
+
+// 基本路径转换
+console.log(path2Array('M10 10L20 20'));
+// [['M', 10, 10], ['L', 20, 20]]
+
+// 复杂路径转换
+console.log(path2Array('M10,10 L20,20 H30 V40 Z'));
+// [
+// ['M', 10, 10],
+// ['L', 20, 20],
+// ['H', 30],
+// ['V', 40],
+// ['Z']
+// ]
+
+// 曲线命令转换
+console.log(path2Array('M0,0 C10,10 20,10 30,0'));
+// [
+// ['M', 0, 0],
+// ['C', 10, 10, 20, 10, 30, 0]
+// ]
+
+// 实际应用示例
+function analyzePath(pathString: string) {
+ const segments = path2Array(pathString);
+
+ return {
+ startPoint: segments.find(seg => seg[0] === 'M')?.slice(1),
+ lines: segments.filter(seg => seg[0] === 'L'),
+ curves: segments.filter(seg => ['C', 'S', 'Q', 'T'].includes(seg[0])),
+ closed: segments.some(seg => seg[0] === 'Z')
+ };
+}
+
+// 使用示例
+const pathAnalysis = analyzePath('M10,10 L20,20 C30,30 40,40 50,50 Z');
+console.log(pathAnalysis);
+// {
+// startPoint: [10, 10],
+// lines: [['L', 20, 20]],
+// curves: [['C', 30, 30, 40, 40, 50, 50]],
+// closed: true
+// }
+
+// 路径变换示例
+function translatePath(pathString: string, dx: number, dy: number) {
+ return path2Array(pathString).map(segment => {
+ const [command, ...params] = segment;
+ if (command === 'H') {
+ return [command, params[0] + dx];
+ }
+ if (command === 'V') {
+ return [command, params[0] + dy];
+ }
+ return [
+ command,
+ ...params.map((p, i) => p + (i % 2 ? dy : dx))
+ ];
+ });
+}
+
+// 平移路径
+console.log(translatePath('M10,10 L20,20', 5, 10));
+// [['M', 15, 20], ['L', 25, 30]]
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| pathInput | SVG 路径字符串 | string | - | 需要转换的 SVG 路径字符串 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 路径数组 | PathArray | - | 转换后的标准路径数组 |
+
+- 路径数组格式
+
+```typescript
+type PathArray = [string, ...number[]][];
+```
+
+每个数组元素的格式:
+- 第一个元素是命令字符串
+- 后续元素是该命令的参数值
+
+- 注意事项
+
+1. 输入必须是有效的 SVG 路径字符串
+2. 返回类型安全的路径数组
+3. 保持原始路径的完整性
+4. 支持所有标准 SVG 路径命令
+5. 便于后续处理和转换
+
+
+## path2Curve
+
+将 SVG 路径转换为三次贝塞尔曲线路径。
+
+- 功能说明
+
+- 将各种路径命令转换为三次贝塞尔曲线(C 命令)
+- 保持路径的形状不变
+- 支持记录闭合路径(Z 命令)的位置
+- 处理弧线命令的特殊情况
+- 维护曲线的连续性
+
+```ts
+import { path2Curve } from '@antv/util';
+
+// 基本路径转换
+console.log(path2Curve('M10 10L20 20'));
+// [
+// ['M', 10, 10],
+// ['C', 10, 10, 20, 20, 20, 20]
+// ]
+
+// 复杂路径转换
+const arcPath = 'M0,0 A50,50 0 0,1 50,50';
+console.log(path2Curve(arcPath));
+// [
+// ['M', 0, 0],
+// ['C', 27.614, 0, 50, 22.386, 50, 50]
+// ]
+
+// 带闭合路径的转换
+const result = path2Curve('M0,0 L50,0 L50,50 Z', true);
+if (Array.isArray(result)) {
+ const [curves, zIndexes] = result;
+ console.log(curves);
+ // [
+ // ['M', 0, 0],
+ // ['C', 0, 0, 50, 0, 50, 0],
+ // ['C', 50, 0, 50, 50, 50, 50],
+ // ['Z']
+ // ]
+ console.log(zIndexes); // [3]
+}
+
+// 实际应用示例
+function createAnimatedPath(pathString: string) {
+ const curves = path2Curve(pathString) as CurveArray;
+
+ return {
+ curves,
+ getPointAt(t: number) {
+ // 简单的点插值示例
+ if (t <= 0) return curves[0].slice(-2);
+ if (t >= 1) return curves[curves.length - 1].slice(-2);
+
+ const segmentIndex = Math.floor(t * (curves.length - 1));
+ const segment = curves[segmentIndex];
+ const localT = (t * (curves.length - 1)) % 1;
+
+ // 贝塞尔曲线插值计算
+ return getBezierPoint(segment.slice(1), localT);
+ }
+ };
+}
+
+// 使用示例
+const animator = createAnimatedPath('M0,0 L100,0 L100,100');
+console.log(animator.getPointAt(0)); // [0, 0]
+console.log(animator.getPointAt(0.5)); // [100, 0]
+console.log(animator.getPointAt(1)); // [100, 100]
+
+// 辅助函数:计算贝塞尔曲线上的点
+function getBezierPoint(points: number[], t: number) {
+ const [x1, y1, x2, y2, x3, y3, x4, y4] = points;
+ const t1 = 1 - t;
+
+ return [
+ t1 * t1 * t1 * x1 + 3 * t1 * t1 * t * x2 + 3 * t1 * t * t * x3 + t * t * t * x4,
+ t1 * t1 * t1 * y1 + 3 * t1 * t1 * t * y2 + 3 * t1 * t * t * y3 + t * t * t * y4
+ ];
+}
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| pathInput | 路径输入 | string \| PathArray | - | SVG 路径字符串或路径数组 |
+| needZCommandIndexes | 是否需要 Z 命令索引 | boolean | false | 是否返回闭合路径命令的位置 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 转换结果 | CurveArray \| [CurveArray, number[]] | - | 曲线数组或带 Z 命令索引的元组 |
+
+- 注意事项
+
+1. 所有路径段都转换为 C 命令
+2. 保持路径的视觉效果不变
+3. 维护曲线的连续性
+4. 可选择保留 Z 命令位置
+5. 支持弧线命令的转换
+
+
+## path2String
+
+将路径数组转换为 SVG 路径字符串,支持数值精度控制。
+
+- 功能说明
+
+- 将路径数组转换为 SVG 路径字符串
+- 支持数值精度的四舍五入
+- 生成标准的 SVG path 'd' 属性值
+- 连接所有路径段
+- 优化输出格式
+
+```ts
+import { path2String } from '@antv/util';
+
+// 基本路径转换
+const path1 = [
+ ['M', 10, 10],
+ ['L', 20, 20],
+ ['Z']
+];
+console.log(path2String(path1)); // "M10 10L20 20Z"
+
+// 带精度控制的转换
+const path2 = [
+ ['M', 10.123, 10.456],
+ ['L', 20.789, 20.987]
+];
+console.log(path2String(path2, 2)); // "M10.12 10.46L20.79 20.99"
+
+// 曲线路径转换
+const path3 = [
+ ['M', 0, 0],
+ ['C', 10, 10, 20, 20, 30, 30]
+];
+console.log(path2String(path3)); // "M0 0C10 10 20 20 30 30"
+
+// 实际应用示例
+function createSVGPath(points: number[][], closed = false, precision = 2) {
+ const pathArray = [
+ ['M', points[0][0], points[0][1]],
+ ...points.slice(1).map(point => ['L', point[0], point[1]])
+ ];
+
+ if (closed) {
+ pathArray.push(['Z']);
+ }
+
+ return path2String(pathArray, precision);
+}
+
+// 使用示例
+const points = [
+ [10.123, 20.456],
+ [30.789, 40.987],
+ [50.432, 60.765]
+];
+
+console.log(createSVGPath(points));
+// "M10.12 20.46L30.79 40.99L50.43 60.77"
+
+console.log(createSVGPath(points, true));
+// "M10.12 20.46L30.79 40.99L50.43 60.77Z"
+
+// SVG元素创建示例
+function createSVGElement(pathData: PathArray, precision = 2) {
+ const svgNS = "http://www.w3.org/2000/svg";
+ const path = document.createElementNS(svgNS, "path");
+ path.setAttribute("d", path2String(pathData, precision));
+ return path;
+}
+
+// 动态路径更新示例
+function animatePath(pathElement: SVGPathElement, newPath: PathArray) {
+ const currentPath = pathElement.getAttribute("d");
+ pathElement.setAttribute("d", path2String(newPath));
+}
+```
+
+- 参数说明
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| path | 路径数组 | PathArray | - | 要转换的路径数组 |
+| round | 精度控制 | number \| 'off' | 'off' | 数值精度(小数位数)或关闭四舍五入 |
+
+- 返回值
+
+| 参数 | 说明 | 类型 | 默认值 | 中文说明 |
+|---------|------|------|---------|----------|
+| result | 路径字符串 | string | - | SVG 路径字符串(d 属性值) |
+
+- 注意事项
+
+1. 输入必须是有效的路径数组
+2. 精度控制影响所有数值
+3. 'off' 表示不进行四舍五入
+4. 生成的字符串符合 SVG 标准
+5. 命令和参数之间使用空格分隔
+
+
+## 常量
+
+```ts
+// SVG 路径命令的参数数量常量对象
+const paramsCount = {
+ a: 7,
+ c: 6,
+ h: 1,
+ l: 2,
+ m: 2,
+ r: 4,
+ q: 4,
+ s: 4,
+ t: 2,
+ v: 1,
+ z: 0,
+};
+
+// 路径参数解析器的初始状态对象
+const paramsParser = {
+ x1: 0,
+ y1: 0,
+ x2: 0,
+ y2: 0,
+ x: 0,
+ y: 0,
+ qx: null,
+ qy: null,
+}
+```
\ No newline at end of file
diff --git a/package.json b/package.json
index 3c600ad..eddeec6 100644
--- a/package.json
+++ b/package.json
@@ -17,8 +17,8 @@
"clean": "rimraf lib esm dist",
"lint-staged": "lint-staged",
"size": "limit-size",
- "lint": "eslint ./src ./__tests__ && prettier ./src ./__tests__ --check ",
- "fix": "eslint ./src ./__tests__ --fix && prettier ./src ./__tests__ --write ",
+ "lint": "eslint ./src ./__tests__ && prettier ./src ./__tests__ --check && lint-md docs/**/* README.md",
+ "fix": "eslint ./src ./__tests__ --fix && prettier ./src ./__tests__ --write && lint-md docs/**/* README.md --fix",
"test": "jest",
"build:umd": "rimraf ./dist && rollup -c && npm run size",
"build:cjs": "rimraf ./lib && tsc --module commonjs --outDir lib",
@@ -36,6 +36,7 @@
"@antv/path-util": "^2.0.15",
"@commitlint/cli": "^18.6.1",
"@commitlint/config-conventional": "^18.6.3",
+ "@lint-md/cli": "^2.0.0",
"@rollup/plugin-commonjs": "^26.0.1",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-terser": "^0.4.4",
diff --git a/src/color/tocssgradient.ts b/src/color/tocssgradient.ts
index 3cd5f6f..80e2e2c 100644
--- a/src/color/tocssgradient.ts
+++ b/src/color/tocssgradient.ts
@@ -6,6 +6,9 @@ function isGradientColor(val) {
return /^[r,R,L,l]{1}[\s]*\(/.test(val);
}
+/**
+ * 将 g 渐变转换为 css 渐变
+ */
export function toCSSGradient(gradientColor) {
if (isGradientColor(gradientColor)) {
let cssColor;
diff --git a/src/path/convert/path-2-absolute.ts b/src/path/convert/path-2-absolute.ts
index 1981990..9699bd1 100644
--- a/src/path/convert/path-2-absolute.ts
+++ b/src/path/convert/path-2-absolute.ts
@@ -2,6 +2,9 @@ import { isAbsoluteArray } from '../util/is-absolute-array';
import { parsePathString } from '../parser/parse-path-string';
import type { PathArray, AbsoluteArray, AbsoluteSegment } from '../types';
+/**
+ * Converts a `PathArray` to an `AbsoluteArray`.
+ */
export function path2Absolute(pathInput: string | PathArray): AbsoluteArray {
if (isAbsoluteArray(pathInput)) {
return [].concat(pathInput) as AbsoluteArray;
diff --git a/tsconfig.json b/tsconfig.json
index 513580c..dc7bbd7 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -17,5 +17,5 @@
"baseUrl": "src"
},
"include": ["src/**/*"],
- "exclude": ["node_modules"]
+ "exclude": ["node_modules", "docs"]
}