/
util.ts
387 lines (362 loc) · 10 KB
/
util.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
import { each, isArray, map } from '@antv/util';
import { View } from '../../chart';
import { BBox, PathCommand, Point } from '../../dependents';
import Geometry from '../../geometry/base';
import Element from '../../geometry/element/';
import { catmullRom2bezier, getLinePath } from '../../geometry/shape/util/path';
import { toPoints } from '../../util/bbox';
import isPolygonsIntersect from '@antv/path-util/lib/is-polygons-intersect';
import { ComponentOption, IInteractionContext, LooseObject } from '../../interface';
function getMaskBBox(context: IInteractionContext, tolerance: number) {
const event = context.event;
const maskShape = event.target;
const maskBBox = maskShape.getCanvasBBox();
// 如果 bbox 过小则不返回
if (!(maskBBox.width >= tolerance || maskBBox.height >= tolerance)) {
return null;
}
return maskBBox;
}
function getMaskPath(context: IInteractionContext, tolerance: number) {
const event = context.event;
const maskShape = event.target;
const maskBBox = maskShape.getCanvasBBox();
// 如果 bbox 过小则不返回
if (!(maskBBox.width >= tolerance || maskBBox.height >= tolerance)) {
return null;
}
return maskShape.attr('path');
}
/**
* 获取当前事件相关的图表元素
* @param context 交互的上下文
* @ignore
*/
export function getCurrentElement(context: IInteractionContext): Element {
const event = context.event;
let element;
const target = event.target;
if (target) {
element = target.get('element');
}
return element;
}
/**
* 获取委托对象
* @param context 上下文
* @ignore
*/
export function getDelegationObject(context: IInteractionContext): LooseObject {
const event = context.event;
const target = event.target;
let delegateObject;
if (target) {
delegateObject = target.get('delegateObject');
}
return delegateObject;
}
export function isElementChange(context: IInteractionContext) {
const event = context.event.gEvent;
// 在同一个 element 内部移动,label 和 shape 之间
if (event && event.fromShape && event.toShape && event.fromShape.get('element') === event.toShape.get('element')) {
return false;
}
return true;
}
/**
* 是否是列表组件
* @param delegateObject 委托对象
* @ignore
*/
export function isList(delegateObject: LooseObject): boolean {
return delegateObject && delegateObject.component && delegateObject.component.isList();
}
/**
* 是否是滑块组件
* @param delegateObject 委托对象
* @ignore
*/
export function isSlider(delegateObject: LooseObject): boolean {
return delegateObject && delegateObject.component && delegateObject.component.isSlider();
}
/**
* 是否由 mask 触发
* @param context 上下文
* @ignore
*/
export function isMask(context: IInteractionContext): boolean {
const event = context.event;
const target = event.target;
return target && target.get('name') === 'mask';
}
/**
* 获取被遮挡的 elements
* @param context 上下文
* @ignore
*/
export function getMaskedElements(context: IInteractionContext, tolerance: number): Element[]{
const target = context.event.target;
if (target.get('type') === 'path') {
const maskPath = getMaskPath(context, tolerance);
if (!maskPath) {
return;
}
return getElementsByPath(context.view, maskPath);
}
const maskBBox = getMaskBBox(context, tolerance);
// 如果 bbox 过小则不返回
if (!maskBBox) {
return null;
}
return getIntersectElements(context.view, maskBBox);
}
/**
* @ignore
*/
export function getSiblingMaskElements(context: IInteractionContext, sibling: View, tolerance: number) {
const maskBBox = getMaskBBox(context, tolerance);
// 如果 bbox 过小则不返回
if (!maskBBox) {
return null;
}
const view = context.view;
const start = getSiblingPoint(view, sibling, {x: maskBBox.x, y: maskBBox.y});
const end = getSiblingPoint(view, sibling, {x: maskBBox.maxX, y: maskBBox.maxY});
const box = {
minX: start.x,
minY: start.y,
maxX: end.x,
maxY: end.y
};
return getIntersectElements(sibling, box);
}
/**
* 获取所有的图表元素
* @param view View/Chart
* @ignore
*/
export function getElements(view: View): Element[] {
const geometries = view.geometries;
let rst: Element[] = [];
each(geometries, (geom: Geometry) => {
const elements = geom.elements;
rst = rst.concat(elements);
});
if (view.views && view.views.length) {
each(view.views, subView => {
rst = rst.concat(getElements(subView));
});
}
return rst;
}
/**
* 获取所有的图表元素
* @param view View/Chart
* @param field 字段名
* @param value 字段值
* @ignore
*/
export function getElementsByField(view: View, field: string, value: any) {
const elements = getElements(view);
return elements.filter(el => {
return getElementValue(el, field) === value;
});
}
/**
* 根据状态名获取图表元素
* @param view View/Chart
* @param stateName 状态名
* @ignore
*/
export function getElementsByState(view: View, stateName: string): Element[] {
const geometries = view.geometries;
let rst: Element[] = [];
each(geometries, (geom: Geometry) => {
const elements = geom.getElementsBy((el) => el.hasState(stateName));
rst = rst.concat(elements);
});
return rst;
}
/**
* 获取图表元素对应字段的值
* @param element 图表元素
* @param field 字段名
* @ignore
*/
export function getElementValue(element: Element, field) {
const model = element.getModel();
const record = model.data;
let value;
if (isArray(record)) {
value = record[0][field];
} else {
value = record[field];
}
return value;
}
/**
* 两个包围盒是否相交
* @param box1 包围盒1
* @param box2 包围盒2
* @ignore
*/
export function intersectRect(box1, box2) {
return !(box2.minX > box1.maxX || box2.maxX < box1.minX || box2.minY > box1.maxY || box2.maxY < box1.minY);
}
/**
* 获取包围盒内的图表元素
* @param view View/Chart
* @param box 包围盒
* @ignore
*/
export function getIntersectElements(view: View, box) {
const elements = getElements(view);
const rst = [];
each(elements, (el) => {
const shape = el.shape;
const shapeBBox = shape.getCanvasBBox();
if (intersectRect(box, shapeBBox)) {
rst.push(el);
}
});
return rst;
}
function pathToPoints(path: any[]) {
const points = [];
each(path, seg => {
const command = seg[0];
if (command !== 'A') {
for(let i = 1; i < seg.length; i = i+2) {
points.push([seg[i], seg[i + 1]]);
}
} else {
const length = seg.length;
points.push([seg[length - 2], seg[length - 1]]);
}
});
return points;
}
/**
* 获取包围盒内的图表元素
* @param view View/Chart
* @param path 路径
* @ignore
*/
export function getElementsByPath(view: View, path: any[]) {
const elements = getElements(view);
const points = pathToPoints(path);
const rst = elements.filter((el: Element) => {
const shape = el.shape;
let shapePoints;
if (shape.get('type') === 'path') {
shapePoints = pathToPoints(shape.attr('path'))
} else {
const shapeBBox = shape.getCanvasBBox();
shapePoints = toPoints(shapeBBox);
}
return isPolygonsIntersect(points, shapePoints);
});
return rst;
}
/**
* 获取当前 View 的所有组件
* @param view View/Chart
* @ignore
*/
export function getComponents(view) {
return map(view.getComponents(), (co: ComponentOption) => co.component);
}
/** @ignore */
export function distance(p1: Point, p2: Point) {
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
return Math.sqrt(dx * dx + dy * dy);
}
/** @ignore */
export function getSpline(points: Point[], z: boolean): PathCommand[] {
if (points.length <= 2) {
return getLinePath(points, false);
}
const first = points[0];
const arr = [];
each(points, (point) => {
arr.push(point.x);
arr.push(point.y);
});
const path = catmullRom2bezier(arr, z, null);
path.unshift(['M', first.x, first.y]);
return path;
}
/**
* 检测点是否在包围盒内
* @param box 包围盒
* @param point 点
* @ignore
*/
export function isInBox(box: BBox, point: Point) {
return box.x <= point.x && box.maxX >= point.x && box.y <= point.y && box.maxY > point.y;
}
/**
* 获取同 view 同一级的 views
* @param view 当前 view
* @returns 同一级的 views
* @ignore
*/
export function getSilbings(view: View): View[] {
const parent = view.parent;
let siblings = null;
if (parent) {
siblings = parent.views.filter(sub => sub !== view);
}
return siblings;
}
function point2Normalize(view: View, point: Point): Point {
const coord = view.getCoordinate();
return coord.invert(point);
}
/**
* 将 view 上的一点转换成另一个 view 的点
* @param view 当前的 view
* @param sibling 同一层级的 view
* @param point 指定点
* @ignore
*/
export function getSiblingPoint(view: View, sibling: View, point: Point): Point {
const normalPoint = point2Normalize(view, point);
return sibling.getCoordinate().convert(normalPoint);
}
/**
* 是否在记录中,临时因为所有的 view 中的数据不是引用,而使用的方法
* 不同 view 上对数据的引用不相等,导致无法直接用 includes
* 假设 x, y 值相等时是同一条数据,这个假设不完全正确,而改成 isEqual 则成本太高
* 后面改成同一个引用时可以修改回来
* @param records
* @param record
* @param xFiled
* @param yField
* @returns
* @ignore
*/
export function isInRecords(records: object[], record: object, xFiled: string, yField: string) {
let isIn = false;
each(records, r => {
if (r[xFiled] === record[xFiled] && r[yField] === record[yField]) {
isIn = true;
return false;
}
});
return isIn;
}
// 级联获取 field 对应的 scale,如果 view 上没有,遍历子 view
export function getScaleByField(view: View, field: string) {
let scale = view.getScaleByField(field);
if (!scale && view.views) {
each(view.views, subView => {
scale = getScaleByField(subView, field);
if (scale) {
return false; // 终止循环
}
});
}
return scale;
}