import { BBox } from '@antv/g-svg'; import { each, isEmpty, isNumber, isNumberEqual, max, min } from '@antv/util'; import { Coordinate, IShape, Point } from '../dependents'; import { ShapeInfo } from '../interface'; // 获取图形的包围盒 function getPointsBox(points) { if (isEmpty(points)) { return null; } let minX = points[0].x; let maxX = points[0].x; let minY = points[0].y; let maxY = points[0].y; each(points, (point) => { minX = minX > point.x ? point.x : minX; maxX = maxX < point.x ? point.x : maxX; minY = minY > point.y ? point.y : minY; maxY = maxY < point.y ? point.y : maxY; }); return { minX, maxX, minY, maxY, centerX: (minX + maxX) / 2, centerY: (minY + maxY) / 2, }; } function uniqueValues(array: T[]) { return Array.from(new Set(array)).length === 1; } function mid(array: number[]) { return (min(array) + max(array)) / 2; } /** * @ignore * 根据弧度计算极坐标系下的坐标点 * @param centerX * @param centerY * @param radius * @param angleInRadian * @returns */ export function polarToCartesian(centerX: number, centerY: number, radius: number, angleInRadian: number) { return { x: centerX + radius * Math.cos(angleInRadian), y: centerY + radius * Math.sin(angleInRadian), }; } /** * @ignore * 根据起始角度计算绘制扇形的 path * @param centerX * @param centerY * @param radius * @param startAngleInRadian * @param endAngleInRadian * @returns */ export function getSectorPath( centerX: number, centerY: number, radius: number, startAngleInRadian: number, endAngleInRadian: number, innerRadius: number = 0 ) { const start = polarToCartesian(centerX, centerY, radius, startAngleInRadian); const end = polarToCartesian(centerX, centerY, radius, endAngleInRadian); const innerStart = polarToCartesian(centerX, centerY, innerRadius, startAngleInRadian); const innerEnd = polarToCartesian(centerX, centerY, innerRadius, endAngleInRadian); if (endAngleInRadian - startAngleInRadian === Math.PI * 2) { // 整个圆是分割成两个圆 const middlePoint = polarToCartesian(centerX, centerY, radius, startAngleInRadian + Math.PI); const innerMiddlePoint = polarToCartesian(centerX, centerY, innerRadius, startAngleInRadian + Math.PI); const circlePathCommands = [ ['M', start.x, start.y], ['A', radius, radius, 0, 1, 1, middlePoint.x, middlePoint.y], ['A', radius, radius, 0, 1, 1, end.x, end.y], ['M', innerStart.x, innerStart.y], ]; if (innerRadius) { circlePathCommands.push(['A', innerRadius, innerRadius, 0, 1, 0, innerMiddlePoint.x, innerMiddlePoint.y]); circlePathCommands.push(['A', innerRadius, innerRadius, 0, 1, 0, innerEnd.x, innerEnd.y]); } circlePathCommands.push(['M', start.x, start.y]); circlePathCommands.push(['Z']); return circlePathCommands; } const arcSweep = endAngleInRadian - startAngleInRadian <= Math.PI ? 0 : 1; const sectorPathCommands = [ ['M', start.x, start.y], ['A', radius, radius, 0, arcSweep, 1, end.x, end.y], ['L', innerEnd.x, innerEnd.y], ]; if (innerRadius) { sectorPathCommands.push(['A', innerRadius, innerRadius, 0, arcSweep, 0, innerStart.x, innerStart.y]); } sectorPathCommands.push(['L', start.x, start.y]); sectorPathCommands.push(['Z']); return sectorPathCommands; } /** * @ignore * Gets arc path * @param centerX * @param centerY * @param radius * @param startAngleInRadian * @param endAngleInRadian * @returns */ export function getArcPath( centerX: number, centerY: number, radius: number, startAngleInRadian: number, endAngleInRadian: number ) { const start = polarToCartesian(centerX, centerY, radius, startAngleInRadian); const end = polarToCartesian(centerX, centerY, radius, endAngleInRadian); if (isNumberEqual(endAngleInRadian - startAngleInRadian, Math.PI * 2)) { const middlePoint = polarToCartesian(centerX, centerY, radius, startAngleInRadian + Math.PI); return [ ['M', start.x, start.y], ['A', radius, radius, 0, 1, 1, middlePoint.x, middlePoint.y], ['A', radius, radius, 0, 1, 1, start.x, start.y], ['A', radius, radius, 0, 1, 0, middlePoint.x, middlePoint.y], ['A', radius, radius, 0, 1, 0, start.x, start.y], ['Z'], ]; } const arcSweep = endAngleInRadian - startAngleInRadian <= Math.PI ? 0 : 1; return [ ['M', start.x, start.y], ['A', radius, radius, 0, arcSweep, 1, end.x, end.y], ]; } /** * @ignore * 从数据模型中的 points 换算角度 * @param shapeModel * @param coordinate * @returns */ export function getAngle(shapeModel: ShapeInfo, coordinate: Coordinate) { const points = shapeModel.points; const box = getPointsBox(points); let endAngle; let startAngle; const { startAngle: coordStartAngle, endAngle: coordEndAngle } = coordinate; const diffAngle = coordEndAngle - coordStartAngle; if (coordinate.isTransposed) { endAngle = box.maxY * diffAngle; startAngle = box.minY * diffAngle; } else { endAngle = box.maxX * diffAngle; startAngle = box.minX * diffAngle; } endAngle += coordStartAngle; startAngle += coordStartAngle; return { startAngle, endAngle, }; } /** * @ignore * 计算多边形重心: https://en.wikipedia.org/wiki/Centroid#Of_a_polygon */ export function getPolygonCentroid(xs: number | number[], ys: number | number[]) { if (isNumber(xs) && isNumber(ys)) { // 普通色块图,xs 和 ys 是数值 return [xs, ys]; } xs = xs as number[]; ys = ys as number[]; // 当这个 polygon 的点在一条线上的时候 // 也就是说 xs 里面的值都相同,比如:[1, 1, 1, 1] // 或者说 ys 里面的值都相同,比如:[0, 0, 0, 0] // 下面计算得到的 k = 0 // 导致返回的值是 [NaN, NaN] // 所以这里做相应的处理 if (uniqueValues(xs) || uniqueValues(ys)) return [mid(xs), mid(ys)]; let i = -1; let x = 0; let y = 0; let former; let current = xs.length - 1; let diff; let k = 0; while (++i < xs.length) { former = current; current = i; k += diff = xs[former] * ys[current] - xs[current] * ys[former]; x += (xs[former] + xs[current]) * diff; y += (ys[former] + ys[current]) * diff; } k *= 3; return [x / k, y / k]; } /** * @ignore * 获取需要替换的属性,如果原先图形元素存在,而新图形不存在,则设置 undefined */ export function getReplaceAttrs(sourceShape: IShape, targetShape: IShape) { const originAttrs = sourceShape.attr(); const newAttrs = targetShape.attr(); each(originAttrs, (v, k) => { if (newAttrs[k] === undefined) { newAttrs[k] = undefined; } }); return newAttrs; }