UNPKG

5.69 kBPlain TextView Raw
1import { deepMix, identity } from '@antv/util';
2import { mat3, vec3 } from 'gl-matrix';
3import { Options, Transformation, Transform, Transformer, Matrix3, Vector3, Vector2, Vector } from './type';
4import { compose, isMatrix, extend } from './utils';
5import {
6 cartesian,
7 translate,
8 custom,
9 matrix,
10 polar,
11 transpose,
12 scale,
13 shearX,
14 shearY,
15 reflect,
16 reflectX,
17 reflectY,
18 rotate,
19 helix,
20 parallel,
21 fisheye,
22 fisheyeX,
23 fisheyeY,
24 fisheyeCircular,
25} from './transforms';
26
27export class Coordinate {
28 // 所有变换合成后的函数
29 private output: Transform;
30
31 // 所有变换合成后的逆函数
32 private input: Transform;
33
34 // 当前的选项
35 private options: Options = {
36 x: 0,
37 y: 0,
38 width: 300,
39 height: 150,
40 transformations: [],
41 };
42
43 // 当前可以使用的变换
44 private transformers = {
45 cartesian,
46 translate,
47 custom,
48 matrix,
49 polar,
50 transpose,
51 scale,
52 'shear.x': shearX,
53 'shear.y': shearY,
54 reflect,
55 'reflect.x': reflectX,
56 'reflect.y': reflectY,
57 rotate,
58 helix,
59 parallel,
60 fisheye,
61 'fisheye.x': fisheyeX,
62 'fisheye.y': fisheyeY,
63 'fisheye.circular': fisheyeCircular,
64 };
65
66 /**
67 * Create a new Coordinate Object.
68 * @param options Custom options
69 */
70 constructor(options?: Partial<Options>) {
71 this.update(options);
72 }
73
74 /**
75 * Update options and inner state.
76 * @param options Options to be updated
77 */
78 public update(options: Partial<Options>) {
79 this.options = deepMix({}, this.options, options);
80 this.recoordinate();
81 }
82
83 /**
84 * Returns a new Coordinate with same options.
85 * @returns Coordinate
86 */
87 public clone() {
88 return new Coordinate(this.options);
89 }
90
91 /**
92 * Returns current options.
93 * @returns options
94 */
95 public getOptions() {
96 return this.options;
97 }
98
99 /**
100 * Clear transformations and update.
101 */
102 public clear() {
103 this.update({
104 transformations: [],
105 });
106 }
107
108 /**
109 * Returns the size of the bounding box of the coordinate.
110 * @returns [width, height]
111 */
112 public getSize(): [number, number] {
113 const { width, height } = this.options;
114 return [width, height];
115 }
116
117 /**
118 * Returns the center of the bounding box of the coordinate.
119 * @returns [centerX, centerY, centerZ]
120 */
121 public getCenter(): [number, number] {
122 const { x, y, width, height } = this.options;
123 return [(x * 2 + width) / 2, (y * 2 + height) / 2];
124 }
125
126 /**
127 * Add selected transformation.
128 * @param args transform type and params
129 * @returns Coordinate
130 */
131 public transform(...args: Transformation) {
132 const { transformations } = this.options;
133 this.update({
134 transformations: [...transformations, [...args]],
135 });
136 return this;
137 }
138
139 /**
140 * Apples transformations for the current vector.
141 * @param vector original vector2
142 * @returns transformed vector2
143 */
144 public map(vector: Vector2 | Vector) {
145 return this.output(vector);
146 }
147
148 /**
149 * Apples invert transformations for the current vector.
150 * @param vector transformed vector2
151 * @param vector original vector2
152 */
153 public invert(vector: Vector2 | Vector) {
154 return this.input(vector);
155 }
156
157 private recoordinate() {
158 this.output = this.compose();
159 this.input = this.compose(true);
160 }
161
162 // 将所有的变换合成一个函数
163 // 变换有两种类型:矩阵变换和函数变换
164 // 处理过程中需要把连续的矩阵变换合成一个变换函数,然后在和其他变换函数合成最终的变换函数
165 private compose(invert = false) {
166 const transformations = invert ? [...this.options.transformations].reverse() : this.options.transformations;
167 const getter = invert ? (d: Transformer) => d.untransform : (d: Transformer) => d.transform;
168 const matrixes = [];
169 const transforms = [];
170 const add = (transform: Transform, extended = true) => transforms.push(extended ? extend(transform) : transform);
171
172 for (const [name, ...args] of transformations) {
173 const createTransformer = this.transformers[name];
174 if (createTransformer) {
175 const { x, y, width, height } = this.options;
176 const transformer = createTransformer([...args], x, y, width, height);
177 if (isMatrix(transformer)) {
178 // 如果当前变换是矩阵变换,那么先保存下来
179 matrixes.push(transformer);
180 } else {
181 // 如果当前变换是函数变换,并且之前有没有合成的矩阵变换,那么现将之前的矩阵变换合成
182 if (matrixes.length) {
183 const transform = this.createMatrixTransform(matrixes, invert);
184 add(transform);
185 matrixes.splice(0, matrixes.length);
186 }
187 const transform = getter(transformer) || identity;
188 add(transform, name !== 'parallel'); // 对于非平行坐标系的变换需要扩展
189 }
190 }
191 }
192
193 // 合成剩下的矩阵变换
194 if (matrixes.length) {
195 const transform = this.createMatrixTransform(matrixes, invert);
196 add(transform);
197 }
198
199 return compose<Vector2 | Vector>(...transforms);
200 }
201
202 // 将连续的矩阵的运算合成一个变换函数
203 private createMatrixTransform(matrixes: Matrix3[], invert: boolean): Transform {
204 const matrix = mat3.create();
205 if (invert) matrixes.reverse();
206 matrixes.forEach((m) => mat3.mul(matrix, matrix, m));
207 if (invert) {
208 mat3.invert(matrix, mat3.clone(matrix));
209 }
210 return (vector: Vector2): Vector2 => {
211 const vector3: Vector3 = [vector[0], vector[1], 1];
212 vec3.transformMat3(vector3, vector3, matrix);
213 return [vector3[0], vector3[1]];
214 };
215 }
216}