diff --git a/__tests__/unit/transforms/polar.spec.ts b/__tests__/unit/transforms/polar.spec.ts index 697cc8e..6e6abf8 100644 --- a/__tests__/unit/transforms/polar.spec.ts +++ b/__tests__/unit/transforms/polar.spec.ts @@ -78,4 +78,17 @@ describe('Polar', () => { expect(v1).toBe(100); expect(v2).toBeCloseTo(20); }); + + test.only('polar() maps normalized value to normalized polar coordinate with auto adjust', () => { + const coord = new Coordinate({ + width: 200, + height: 300, + transformations: [['polar', (-11 / 10) * Math.PI, (1 / 10) * Math.PI, 0, 1, true]], + }); + + expect(coord.map([0.7, 0.58])).toEqual([0.6324264444247805, 0.5211192142721996]); + const [v1, v2] = coord.invert([0.6324264444247805, 0.5211192142721996]); + expect(v1).toBeCloseTo(0.7); + expect(v2).toBeCloseTo(0.58); + }); }); diff --git a/rollup.config.js b/rollup.config.js index ea9d86c..341dfd8 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -12,6 +12,6 @@ module.exports = [ format: 'umd', sourcemap: false, }, - plugins: [resolve(), typescript(), commonjs(), uglify()], + plugins: [resolve(), commonjs(), typescript(), uglify()], }, ]; diff --git a/src/coordinate.ts b/src/coordinate.ts index 3eab096..08f7f89 100644 --- a/src/coordinate.ts +++ b/src/coordinate.ts @@ -118,9 +118,28 @@ export class Coordinate { * Returns the center of the bounding box of the coordinate. * @returns [centerX, centerY] */ - public getCenter(): [number, number] { + public getBoxCenter(): [number, number] { const { x, y, width, height } = this.options; - return [(x * 2 + width) / 2, (y * 2 + height) / 2]; + return [x + width / 2, y + height / 2]; + } + + /** + * Returns the radial center when polar exists, or else returns center of bounding box of the coordinate. + * @returns [centerX, centerY] + */ + public getCenter(): [number, number] { + const { x, y, width, height, transformations } = this.options; + + // If has polar transoformer. + const polar = transformations.find((transformation) => transformation[0] === 'polar'); + if (polar) { + const [name, ...params] = polar; + const createTransformer = this.transformers[name]; + // @ts-ignore + const { offsetX, offsetY } = createTransformer([...params], x, y, width, height); + return [x + (width / 2) * (1 + offsetX), y + (height / 2) * (1 - offsetY)]; + } + return this.getBoxCenter(); } /** diff --git a/src/transforms/polar.ts b/src/transforms/polar.ts index e2f909e..64dfd49 100644 --- a/src/transforms/polar.ts +++ b/src/transforms/polar.ts @@ -1,12 +1,12 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { Linear } from '@antv/scale'; import { Vector2, CreateTransformer } from '../type'; -import { adjustAngle } from '../utils'; +import { adjustAngle, autoPolar } from '../utils'; /** * Maps normalized value to normalized polar coordinate at the center of the bounding box. * It is used for Nightingale Rose Diagram. - * @param params [x0, x1, y0, y1] + * @param params [x0, x1, y0, y1, auto] * @param x x of the the bounding box of coordinate * @param y y of the the bounding box of coordinate * @param width width of the the bounding box of coordinate @@ -14,7 +14,7 @@ import { adjustAngle } from '../utils'; * @returns transformer */ export const polar: CreateTransformer = (params, x, y, width, height) => { - const [startAngle, endAngle, innerRadius, outerRadius] = params as number[]; + const [startAngle, endAngle, innerRadius, outerRadius, auto] = params as [number, number, number, number, boolean]; const radius = new Linear({ range: [innerRadius, outerRadius], }); @@ -24,19 +24,22 @@ export const polar: CreateTransformer = (params, x, y, width, height) => { const aspect = height / width; const sx = aspect > 1 ? 1 : aspect; const sy = aspect > 1 ? 1 / aspect : 1; + const [offsetX, offsetY, radiusOffset] = auto ? autoPolar(startAngle, endAngle, width, height) : [0, 0, 0]; + return { + offsetX, + offsetY, transform(vector: Vector2) { const [v1, v2] = vector; const theta = angle.map(v1); - const r = radius.map(v2); - + const r = radius.map(v2) * (1 + radiusOffset); // 根据长宽比调整,使得极坐标系内切外接矩形 const x = r * Math.cos(theta) * sx; const y = r * Math.sin(theta) * sy; // 将坐标的原点移动到外接矩形的中心,并且将长度设置为一半 - const dx = x * 0.5 + 0.5; - const dy = y * 0.5 + 0.5; + const dx = x * 0.5 + 0.5 * (1 + offsetX); + const dy = y * 0.5 + 0.5 * (1 - offsetY); return [dx, dy]; }, untransform(vector: Vector2) { diff --git a/src/type.ts b/src/type.ts index f8ee62e..63568bc 100644 --- a/src/type.ts +++ b/src/type.ts @@ -6,7 +6,7 @@ type Translate = ['translate', number, number]; type Cartesian = ['cartesian']; type Custom = ['custom', TransformCallback]; type Matrix = ['matrix', Matrix3]; -type Polar = ['polar', number, number, number, number]; +type Polar = ['polar', number, number, number, number, boolean?]; type Transpose = ['transpose']; type Scale = ['scale', number, number]; type ShearX = ['shear.x', number]; diff --git a/src/utils/auto.ts b/src/utils/auto.ts new file mode 100644 index 0000000..0ba7411 --- /dev/null +++ b/src/utils/auto.ts @@ -0,0 +1,53 @@ +/** + * Sample to calucation the polar bbox. + */ +function getPolarBbox(startTheta: number, endTheta: number) { + if (Math.abs(endTheta - startTheta) >= Math.PI * 2) { + return { + minX: -0.5, + maxX: 0.5, + minY: -0.5, + maxY: 0.5, + }; + } + + const xs = [0, Math.cos(startTheta), Math.cos(endTheta)]; + const ys = [0, Math.sin(startTheta), Math.sin(endTheta)]; + + for (let i = Math.min(startTheta, endTheta); i < Math.max(startTheta, endTheta); i += Math.PI / 18) { + xs.push(Math.cos(i)); + ys.push(Math.sin(i)); + } + + return { + minX: Math.min(...xs) / 2, + maxX: Math.max(...xs) / 2, + minY: Math.min(...ys) / 2, + maxY: Math.max(...ys) / 2, + }; +} +/** + * Adjust x,y and radius in polar automatically. + */ +export function autoPolar(startTheta: number, endTheta: number, width: number, height: number) { + const { minX, maxX, minY, maxY } = getPolarBbox(startTheta, endTheta); + + const w = maxX - minX; + const h = maxY - minY; + + const max = Math.max(width, height); + + // Calucate base on width. + const offsetRadius = Math.min(width / w, height / h) / max - 1; + const offsetX = Math.abs(maxX + minX) * (1 + offsetRadius); + const offsetY = Math.abs(maxY + minY) * (1 + offsetRadius); + + return [ + // x + offsetX * (Math.abs(maxX) - Math.abs(minX) ? -1 : 1), + // y + offsetY * (Math.abs(maxY) - Math.abs(minY) ? -1 : 1), + // r + offsetRadius, + ]; +} diff --git a/src/utils/index.ts b/src/utils/index.ts index ce9f5b8..957ce7a 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -2,3 +2,4 @@ export { compose } from './compose'; export { isMatrix } from './isMatrix'; export { extend } from './extend'; export { adjustAngle } from './adjustAngle'; +export { autoPolar } from './auto';