@antv/g2
Version:
the Grammar of Graphics in Javascript
159 lines (135 loc) • 5.07 kB
text/typescript
import { getArcParams } from '@antv/g-canvas';
import { isNumberEqual, isEqual } from '@antv/util';
import { IShape, PathCommand } from '../../dependents';
import { GAnimateCfg } from '../../interface';
import { AnimateExtraCfg } from '../interface';
import { getArcPath, getSectorPath } from '../../util/graphics';
function getAngle(startPoint: number[], arcPath: PathCommand) {
let { startAngle, endAngle, arcFlag } = getArcParams(startPoint, arcPath);
if (!isNumberEqual(startAngle, -Math.PI * 0.5) && startAngle < -Math.PI * 0.5) {
startAngle += Math.PI * 2;
}
if (!isNumberEqual(endAngle, -Math.PI * 0.5) && endAngle < -Math.PI * 0.5) {
endAngle += Math.PI * 2;
}
if (arcPath[5] === 0) {
// 逆时针,需要将 startAngle 和 endAngle 转置,因为 G2 极坐标系为顺时针方向
[startAngle, endAngle] = [endAngle, startAngle];
}
if (isNumberEqual(startAngle, Math.PI * 1.5)) {
startAngle = Math.PI * -0.5;
}
if (isNumberEqual(endAngle, Math.PI * -0.5) && arcFlag) {
endAngle = Math.PI * 1.5;
}
return {
startAngle,
endAngle,
};
}
function getArcStartPoint(path: PathCommand) {
let startPoint;
if (path[0] === 'M' || path[0] === 'L') {
startPoint = [path[1], path[2]];
} else if (path[0] === 'a' || path[0] === 'A' || path[0] === 'C') {
startPoint = [path[path.length - 2], path[path.length - 1]];
}
return startPoint;
}
/**
* path 存在以下情况
* 1. 饼图不为整圆的 path,命令为 M, L, A, L, Z
* 2. 饼图为整圆的 path,命令为 M, M, A, A, M, Z
* 3. 环图不为整圆的 path,命令为 M, A, L, A, L, Z
* 4. 环图为整圆的 path,命令为 M, A, A, M, A, A, M, Z
* 5. radial-line, 不为整圆时的 path, 命令为 M, A, A, Z
* 6. radial-line, 为整圆时的 path,命令为 M, A, A, A, A, Z
* @param path theta 坐标系下圆弧的 path 命令
*/
export function getArcInfo(path: PathCommand[]) {
let startAngle;
let endAngle;
const arcPaths = path.filter((command) => {
return command[0] === 'A' || command[0] === 'a';
});
if (arcPaths.length === 0) {
return {
startAngle: 0,
endAngle: 0,
radius: 0,
innerRadius: 0,
};
}
const firstArcPathCommand = arcPaths[0];
const lastArcPathCommand = arcPaths.length > 1 ? arcPaths[1] : arcPaths[0];
const firstIndex = path.indexOf(firstArcPathCommand);
const lastIndex = path.indexOf(lastArcPathCommand);
const firstStartPoint = getArcStartPoint(path[firstIndex - 1]);
const lastStartPoint = getArcStartPoint(path[lastIndex - 1]);
const { startAngle: firstStartAngle, endAngle: firstEndAngle } = getAngle(firstStartPoint, firstArcPathCommand);
const { startAngle: lastStartAngle, endAngle: lastEndAngle } = getAngle(lastStartPoint, lastArcPathCommand);
if (isNumberEqual(firstStartAngle, lastStartAngle) && isNumberEqual(firstEndAngle, lastEndAngle)) {
startAngle = firstStartAngle;
endAngle = firstEndAngle;
} else {
startAngle = Math.min(firstStartAngle, lastStartAngle);
endAngle = Math.max(firstEndAngle, lastEndAngle);
}
let radius = firstArcPathCommand[1];
let innerRadius = arcPaths[arcPaths.length - 1][1];
if (radius < innerRadius) {
[radius, innerRadius] = [innerRadius, radius];
} else if (radius === innerRadius) {
innerRadius = 0;
}
return {
startAngle,
endAngle,
radius,
innerRadius,
};
}
/**
* @ignore
* 饼图更新动画
* @param shape 文本图形
* @param animateCfg
* @param cfg
*/
export function sectorPathUpdate(shape: IShape, animateCfg: GAnimateCfg, cfg: AnimateExtraCfg) {
const { toAttrs, coordinate } = cfg;
const path = (toAttrs as { path: PathCommand[] }).path || [];
const pathCommands = path.map((command) => command[0]);
if (path.length < 1) return;
const { startAngle: curStartAngle, endAngle: curEndAngle, radius, innerRadius } = getArcInfo(path);
const { startAngle: preStartAngle, endAngle: preEndAngle } = getArcInfo(shape.attr('path'));
const center = coordinate.getCenter();
const diffStartAngle = curStartAngle - preStartAngle;
const diffEndAngle = curEndAngle - preEndAngle;
// 没有 diff 时直接返回最终 attrs,不需要额外动画
if (diffStartAngle === 0 && diffEndAngle === 0) {
shape.attr('path', path);
return;
}
shape.animate(
(ratio) => {
const onFrameStartAngle = preStartAngle + ratio * diffStartAngle;
const onFrameEndAngle = preEndAngle + ratio * diffEndAngle;
return {
...toAttrs,
path:
// hack, 兼容 /examples/bar/basic/demo/radial-line.ts 动画
isEqual(pathCommands, ['M', 'A', 'A', 'Z'])
? getArcPath(center.x, center.y, radius, onFrameStartAngle, onFrameEndAngle)
: getSectorPath(center.x, center.y, radius, onFrameStartAngle, onFrameEndAngle, innerRadius),
};
},
{
...animateCfg,
callback: () => {
// 将 path 保持原始态,否则会影响 setState() 的动画
shape.attr('path', path);
},
}
);
}