-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
view.ts
2037 lines (1823 loc) · 54.9 KB
/
view.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
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import {
clone,
deepMix,
each,
filter,
find,
flatten,
get,
isBoolean,
isFunction,
isNil,
isObject,
isString,
isUndefined,
mix,
remove,
set,
size,
uniqueId,
isEqual,
isPlainObject,
} from '@antv/util';
import { Attribute, Coordinate, Event as GEvent, GroupComponent, ICanvas, IGroup, IShape, Scale } from '../dependents';
import {
AxisOption,
ComponentOption,
CoordinateCfg,
CoordinateOption,
Data,
Datum,
FacetCfgMap,
FilterCondition,
GeometryOption,
LegendOption,
LooseObject,
Options,
Point,
Region,
ScaleOption,
TooltipOption,
ViewCfg,
ViewPadding,
ViewAppendPadding,
} from '../interface';
import { GROUP_Z_INDEX, LAYER, PLOT_EVENTS, VIEW_LIFE_CIRCLE } from '../constant';
import Base from '../base';
import { Facet, getFacet } from '../facet';
import Geometry from '../geometry/base';
import { createInteraction, Interaction } from '../interaction';
import { getTheme } from '../theme';
import { BBox } from '../util/bbox';
import { getCoordinateClipCfg, isPointInCoordinate } from '../util/coordinate';
import { uniq } from '../util/helper';
import { findDataByPoint } from '../util/tooltip';
import { parsePadding } from '../util/padding';
import { getDefaultCategoryScaleRange } from '../util/scale';
import { createTheme } from '../theme/util';
import Chart from './chart';
import { getComponentController, getComponentControllerNames } from './controller';
import Annotation from './controller/annotation';
import { Controller } from './controller/base';
import CoordinateController from './controller/coordinate';
import Tooltip from './controller/tooltip';
import Slider from './controller/slider';
import Scrollbar from './controller/scrollbar';
import Axis from './controller/axis';
import Gesture from './controller/gesture';
import Legend from './controller/legend';
import Event from './event';
import defaultLayout, { Layout } from './layout';
import { ScalePool } from './util/scale-pool';
import { PaddingCal } from './layout/padding-cal';
import { calculatePadding } from './layout/auto';
import { defaultSyncViewPadding } from './util/sync-view-padding';
/**
* G2 视图 View 类
*/
export class View extends Base {
/** view id,全局唯一。 */
public id: string;
/** 父级 view,如果没有父级,则为空。 */
public parent: View;
/** 所有的子 view。 */
public views: View[] = [];
/** 所有的 geometry 实例。 */
public geometries: Geometry[] = [];
/** 所有的组件 controllers。 */
public controllers: Controller[] = [];
/** 所有的 Interaction 实例。 */
public interactions: Record<string, Interaction> = {};
/** view 区域空间。 */
public viewBBox: BBox;
/** 坐标系的位置大小,ViewBBox - padding = coordinateBBox。 */
public coordinateBBox: BBox;
/** view 的 padding 大小,传入的配置(不是解析之后的值)。 */
public padding: ViewPadding;
/** padding的基础上增加的调整值 */
public appendPadding: ViewAppendPadding;
/** G.Canvas 实例。 */
public canvas: ICanvas;
/** 存储最终计算的 padding 结果 */
public autoPadding: PaddingCal;
/** 三层 Group 图形中的背景层。 */
public backgroundGroup: IGroup;
/** 三层 Group 图形中的中间层。 */
public middleGroup: IGroup;
/** 三层 Group 图形中的前景层。 */
public foregroundGroup: IGroup;
/** 是否对超出坐标系范围的 Geometry 进行剪切 */
public limitInPlot: boolean = false;
/**
* 标记 view 的大小位置范围,均是 0 ~ 1 范围,便于开发者使用,起始点为左上角。
*/
protected region: Region;
/** 主题配置,存储当前主题配置。 */
protected themeObject: LooseObject;
// 配置信息存储
protected options: Options = {
data: [],
animate: true, // 默认开启动画
}; // 初始化为空
/** 过滤之后的数据 */
protected filteredData: Data;
/** 配置开启的组件插件,默认为全局配置的组件。 */
private usedControllers: string[] = getComponentControllerNames();
/** 所有的 scales */
private scalePool: ScalePool = new ScalePool();
/** 布局函数 */
protected layoutFunc: Layout = defaultLayout;
/** 生成的坐标系实例,{@link https://github.com/antvis/coord/blob/master/src/coord/base.ts|Coordinate} */
protected coordinateInstance: Coordinate;
/** Coordinate 相关的控制器类,负责坐标系实例的创建、更新、变换等 */
protected coordinateController: CoordinateController;
/** 分面类实例 */
protected facetInstance: Facet;
/** 当前鼠标是否在 plot 内(CoordinateBBox) */
private isPreMouseInPlot: boolean = false;
/** 默认标识位,用于判定数据是否更新 */
private isDataChanged: boolean = false;
/** 用于判断坐标系范围是否发生变化的标志位 */
private isCoordinateChanged: boolean = false;
/** 从当前这个 view 创建的 scale key */
private createdScaleKeys = new Map<string, boolean>();
/** 背景色样式的 shape */
private backgroundStyleRectShape;
/** 是否同步子 view 的 padding */
private syncViewPadding;
constructor(props: ViewCfg) {
super({ visible: props.visible });
const {
id = uniqueId('view'),
parent,
canvas,
backgroundGroup,
middleGroup,
foregroundGroup,
region = { start: { x: 0, y: 0 }, end: { x: 1, y: 1 } },
padding,
appendPadding,
theme,
options,
limitInPlot,
syncViewPadding,
} = props;
this.parent = parent;
this.canvas = canvas;
this.backgroundGroup = backgroundGroup;
this.middleGroup = middleGroup;
this.foregroundGroup = foregroundGroup;
this.region = region;
this.padding = padding;
this.appendPadding = appendPadding;
// 接受父 view 传入的参数
this.options = { ...this.options, ...options };
this.limitInPlot = limitInPlot;
this.id = id;
this.syncViewPadding = syncViewPadding;
// 初始化 theme
this.themeObject = isObject(theme) ? deepMix({}, getTheme('default'), createTheme(theme)) : getTheme(theme);
this.init();
}
/**
* 设置 layout 布局函数
* @param layout 布局函数
* @returns void
*/
public setLayout(layout: Layout) {
this.layoutFunc = layout;
}
/**
* 生命周期:初始化
* @returns voids
*/
public init() {
// 计算画布的 viewBBox
this.calculateViewBBox();
// 事件委托机制
this.initEvents();
// 初始化组件 controller
this.initComponentController();
this.initOptions();
}
/**
* 生命周期:渲染流程,渲染过程需要处理数据更新的情况。
* render 函数仅仅会处理 view 和子 view。
* @param isUpdate 是否触发更新流程。
*/
public render(isUpdate: boolean = false) {
this.emit(VIEW_LIFE_CIRCLE.BEFORE_RENDER);
// 递归渲染
this.paint(isUpdate);
this.emit(VIEW_LIFE_CIRCLE.AFTER_RENDER);
if (this.visible === false) {
// 用户在初始化的时候声明 visible: false
this.changeVisible(false);
}
}
/**
* 生命周期:清空图表上所有的绘制内容,但是不销毁图表,chart 仍可使用。
* @returns void
*/
public clear() {
this.emit(VIEW_LIFE_CIRCLE.BEFORE_CLEAR);
// 1. 清空缓存和计算数据
this.filteredData = [];
this.coordinateInstance = undefined;
this.isDataChanged = false; // 复位
this.isCoordinateChanged = false; // 复位
// 2. 清空 geometries
const geometries = this.geometries;
for (let i = 0; i < geometries.length; i++) {
geometries[i].clear();
// view 中使用 geometry 的时候,还需要清空它的容器,不然下一次 chart.geometry() 的时候,又创建了一个,导致泄露, #2799。
geometries[i].container.remove(true);
}
this.geometries = [];
// 3. 清空 controllers
const controllers = this.controllers;
for (let i = 0; i < controllers.length; i++) {
if (controllers[i].name === 'annotation') {
// 需要清空配置项
(controllers[i] as Annotation).clear(true);
} else {
controllers[i].clear();
}
}
// 4. 删除 scale 缓存
this.createdScaleKeys.forEach((v: boolean, k: string) => {
this.getRootView().scalePool.deleteScale(k);
});
this.createdScaleKeys.clear();
// 递归处理子 view
const views = this.views;
for (let i = 0; i < views.length; i++) {
views[i].clear();
}
this.emit(VIEW_LIFE_CIRCLE.AFTER_CLEAR);
}
/**
* 生命周期:销毁,完全无法使用。
* @returns void
*/
public destroy() {
// 销毁前事件,销毁之后已经没有意义了,所以不抛出事件
this.emit(VIEW_LIFE_CIRCLE.BEFORE_DESTROY);
const interactions = this.interactions;
// 销毁 interactions
each(interactions, (interaction) => {
if (interaction) {
// 有可能已经销毁,设置了 undefined
interaction.destroy();
}
});
this.clear();
// 销毁 controller 中的组件
const controllers = this.controllers;
for (let i = 0, len = controllers.length; i < len; i++) {
const controller = controllers[i];
controller.destroy();
}
this.backgroundGroup.remove(true);
this.middleGroup.remove(true);
this.foregroundGroup.remove(true);
super.destroy();
}
/* end 生命周期函数 */
/**
* 显示或者隐藏整个 view。
* @param visible 是否可见
* @returns View
*/
public changeVisible(visible: boolean): View {
super.changeVisible(visible);
const geometries = this.geometries;
for (let i = 0, len = geometries.length; i < len; i++) {
const geometry = geometries[i];
geometry.changeVisible(visible);
}
const controllers = this.controllers;
for (let i = 0, len = controllers.length; i < len; i++) {
const controller = controllers[i];
controller.changeVisible(visible);
}
this.foregroundGroup.set('visible', visible);
this.middleGroup.set('visible', visible);
this.backgroundGroup.set('visible', visible);
// group.set('visible', visible) 不会触发自动刷新
this.getCanvas().draw();
return this;
}
/**
* 装载数据源。
*
* ```ts
* view.data([{ city: '杭州', sale: 100 }, { city: '上海', sale: 110 } ]);
* ```
*
* @param data 数据源,json 数组。
* @returns View
*/
public data(data: Data): View {
set(this.options, 'data', data);
this.isDataChanged = true;
return this;
}
/**
* @deprecated
* This method will be removed at G2 V4.1. Replaced by {@link #data(data)}
*/
public source(data: Data): View {
console.warn('This method will be removed at G2 V4.1. Please use chart.data() instead.');
return this.data(data);
}
/**
* 设置数据筛选规则。
*
* ```ts
* view.filter('city', (value: any, datum: Datum) => value !== '杭州');
*
* // 删除 'city' 字段对应的筛选规则。
* view.filter('city', null);
* ```
*
* @param field 数据字段
* @param condition 筛选规则
* @returns View
*/
public filter(field: string, condition: FilterCondition | null): View {
if (isFunction(condition)) {
set(this.options, ['filters', field], condition);
return this;
}
// condition 为空,则表示删除过滤条件
if (!condition && get(this.options, ['filters', field])) {
delete this.options.filters[field];
}
return this;
}
/**
* 开启或者关闭坐标轴。
*
* ```ts
* view.axis(false); // 不展示坐标轴
* ```
* @param field 坐标轴开关
*/
public axis(field: boolean): View;
/**
* 对特定的某条坐标轴进行配置。
*
* @example
* ```ts
* view.axis('city', false); // 不展示 'city' 字段对应的坐标轴
*
* // 将 'city' 字段对应的坐标轴的标题隐藏
* view.axis('city', {
* title: null,
* });
* ```
*
* @param field 要配置的坐标轴对应的字段名称
* @param axisOption 坐标轴具体配置,更详细的配置项可以参考:https://github.com/antvis/component#axis
*/
public axis(field: string, axisOption: AxisOption): View;
public axis(field: string | boolean, axisOption?: AxisOption): View {
if (isBoolean(field)) {
set(this.options, ['axes'], field);
} else {
set(this.options, ['axes', field], axisOption);
}
return this;
}
/**
* 对图例进行整体配置。
*
* ```ts
* view.legend(false); // 关闭图例
*
* view.legend({
* position: 'right',
* }); // 图例进行整体配置
* ```
* @param field
* @returns View
*/
public legend(field: LegendOption): View;
/**
* 对特定的图例进行配置。
*
* @example
* ```ts
* view.legend('city', false); // 关闭某个图例,通过数据字段名进行关联
*
* // 对特定的图例进行配置
* view.legend('city', {
* position: 'right',
* });
* ```
*
* @param field 图例对应的数据字段名称
* @param legendOption 图例配置,更详细的配置项可以参考:https://github.com/antvis/component#axis
* @returns View
*/
public legend(field: string, legendOption: LegendOption): View;
public legend(field: string | LegendOption, legendOption?: LegendOption): View {
if (isBoolean(field)) {
set(this.options, ['legends'], field);
} else if (isString(field)) {
set(this.options, ['legends', field], legendOption);
if (isPlainObject(legendOption) && legendOption?.selected) {
set(this.options, ['filters', field], (name: string) => {
return legendOption?.selected[name] ?? true;
});
}
} else {
// 设置全局的 legend 配置
set(this.options, ['legends'], field);
}
return this;
}
/**
* 批量设置 scale 配置。
*
* ```ts
* view.scale({
* sale: {
* min: 0,
* max: 100,
* }
* });
* ```
* Scale 的详细配置项可以参考:https://github.com/antvis/scale#api
* @returns View
*/
public scale(field: Record<string, ScaleOption>): View;
/**
* 为特性的数据字段进行 scale 配置。
*
* ```ts
* view.scale('sale', {
* min: 0,
* max: 100,
* });
* ```
*
* @returns View
*/
public scale(field: string, scaleOption: ScaleOption): View;
public scale(field: string | Record<string, ScaleOption>, scaleOption?: ScaleOption): View {
if (isString(field)) {
set(this.options, ['scales', field], scaleOption);
} else if (isObject(field)) {
each(field, (v: ScaleOption, k: string) => {
set(this.options, ['scales', k], v);
});
}
return this;
}
/**
* tooltip 提示信息配置。
*
* ```ts
* view.tooltip(false); // 关闭 tooltip
*
* view.tooltip({
* shared: true
* });
* ```
*
* @param cfg Tooltip 配置,更详细的配置项参考:https://github.com/antvis/component#tooltip
* @returns View
*/
public tooltip(cfg: boolean | TooltipOption): View {
set(this.options, 'tooltip', cfg);
return this;
}
/**
* 辅助标记配置。
*
* ```ts
* view.annotation().line({
* start: ['min', 85],
* end: ['max', 85],
* style: {
* stroke: '#595959',
* lineWidth: 1,
* lineDash: [3, 3],
* },
* });
* ```
* 更详细的配置项:https://github.com/antvis/component#annotation
* @returns [[Annotation]]
*/
public annotation(): Annotation {
return this.getController('annotation');
}
/**
* @deprecated
* This method will be removed at G2 V4.1. Replaced by {@link #guide()}
*/
public guide(): Annotation {
console.warn('This method will be removed at G2 V4.1. Please use chart.annotation() instead.');
return this.annotation();
}
/**
* 坐标系配置。
*
* @example
* ```ts
* view.coordinate({
* type: 'polar',
* cfg: {
* radius: 0.85,
* },
* actions: [
* [ 'transpose' ],
* ],
* });
* ```
*
* @param option
* @returns
*/
public coordinate(option?: CoordinateOption): CoordinateController;
/**
* 声明坐标系类型,并进行配置。
*
* ```ts
* // 直角坐标系,并进行转置变换
* view.coordinate('rect').transpose();
*
* // 默认创建直角坐标系
* view.coordinate();
* ```
*
* @param type 坐标系类型
* @param [coordinateCfg] 坐标系配置
* @returns
*/
public coordinate(type: string, coordinateCfg?: CoordinateCfg): CoordinateController;
public coordinate(type: string | CoordinateOption, coordinateCfg?: CoordinateCfg): CoordinateController {
// 提供语法糖,使用更简单
if (isString(type)) {
set(this.options, 'coordinate', { type, cfg: coordinateCfg } as CoordinateOption);
} else {
set(this.options, 'coordinate', type);
}
// 更新 coordinate 配置
this.coordinateController.update(this.options.coordinate);
return this.coordinateController;
}
/**
* @deprecated
* This method will be removed at G2 V4.1. Replaced by {@link #coordinate()}
*/
public coord(type: string | CoordinateOption, coordinateCfg?: CoordinateCfg): CoordinateController {
console.warn('This method will be removed at G2 V4.1. Please use chart.coordinate() instead.');
// @ts-ignore
return this.coordinate(type, coordinateCfg);
}
/**
* view 分面绘制。
*
* ```ts
* view.facet('rect', {
* rowField: 'province',
* columnField: 'category',
* eachView: (innerView: View, facet?: FacetData) => {
* innerView.line().position('city*sale');
* },
* });
* ```
*
* @param type 分面类型
* @param cfg 分面配置, [[FacetCfgMap]]
* @returns View
*/
public facet<T extends keyof FacetCfgMap>(type: T, cfg: FacetCfgMap[T]): View {
// 先销毁掉之前的分面
if (this.facetInstance) {
this.facetInstance.destroy();
}
// 创建新的分面
const Ctor = getFacet(type);
if (!Ctor) {
throw new Error(`facet '${type}' is not exist!`);
}
this.facetInstance = new Ctor(this, { ...cfg, type });
return this;
}
/*
* 开启或者关闭动画。
*
* ```ts
* view.animate(false);
* ```
*
* @param status 动画状态,true 表示开始,false 表示关闭
* @returns View
*/
public animate(status: boolean): View {
set(this.options, 'animate', status);
return this;
}
/**
* 更新配置项,用于配置项式声明。
* @param options 配置项
*/
public updateOptions(options: Options) {
this.clear(); // 清空
mix(this.options, options);
// 需要把已存在的 view 销毁,否则会重复创建
// 目前针对配置项还没有特别好的 view 更新机制,为了不影响主流流程,所以在这里直接销毁
this.views.forEach((view) => view.destroy());
this.views = [];
this.initOptions();
// 初始化坐标系大小,保证 padding 计算正确
this.coordinateBBox = this.viewBBox;
return this;
}
/**
* 往 `view.options` 属性中存储配置项。
* @param name 属性名称
* @param opt 属性值
* @returns view
*/
public option(name: string, opt: any): View {
// 对于内置的 option,避免覆盖。
// name 在原型上,说明可能是内置 API,存在 option 被覆盖的风险,不处理
if (View.prototype[name]) {
throw new Error(`Can't use built in variable name "${name}", please change another one.`);
}
// 存入到 option 中
set(this.options, name, opt);
return this;
}
/**
* 设置主题。
*
* ```ts
* view.theme('dark'); // 'dark' 需要事先通过 `registerTheme()` 接口注册完成
*
* view.theme({ defaultColor: 'red' });
* ```
*
* @param theme 主题名或者主题配置
* @returns View
*/
public theme(theme: string | LooseObject): View {
this.themeObject = isObject(theme) ? deepMix({}, this.themeObject, createTheme(theme)) : getTheme(theme);
return this;
}
/* end 一系列传入配置的 API */
/**
* Call the interaction based on the interaction name
*
* ```ts
* view.interaction('my-interaction', { extra: 'hello world' });
* ```
* 详细文档可以参考:https://g2.antv.vision/zh/docs/api/general/interaction
* @param name interaction name
* @param cfg interaction config
* @returns
*/
public interaction(name: string, cfg?: LooseObject): View {
const existInteraction = this.interactions[name];
// 存在则先销毁已有的
if (existInteraction) {
existInteraction.destroy();
}
// 新建交互实例
const interaction = createInteraction(name, this, cfg);
if (interaction) {
interaction.init();
this.interactions[name] = interaction;
}
return this;
}
/**
* 移除当前 View 的 interaction
* ```ts
* view.removeInteraction('my-interaction');
* ```
* @param name interaction name
*/
public removeInteraction(name: string) {
const existInteraction = this.interactions[name];
// 存在则先销毁已有的
if (existInteraction) {
existInteraction.destroy();
this.interactions[name] = undefined;
}
}
/**
* 修改数据,数据更新逻辑,数据更新仅仅影响当前这一层的 view
*
* ```ts
* view.changeData([{ city: '北京', sale: '200' }]);
* ```
*
* @param data
* @returns void
*/
public changeData(data: Data) {
this.isDataChanged = true;
this.emit(VIEW_LIFE_CIRCLE.BEFORE_CHANGE_DATA, Event.fromData(this, VIEW_LIFE_CIRCLE.BEFORE_CHANGE_DATA, null));
// 1. 保存数据
this.data(data);
// 2. 渲染
this.paint(true);
// 3. 遍历子 view 进行 change data
const views = this.views;
for (let i = 0, len = views.length; i < len; i++) {
const view = views[i];
// FIXME 子 view 有自己的数据的情况,该如何处理?
view.changeData(data);
}
this.emit(VIEW_LIFE_CIRCLE.AFTER_CHANGE_DATA, Event.fromData(this, VIEW_LIFE_CIRCLE.AFTER_CHANGE_DATA, null));
}
/* View 管理相关的 API */
/**
* 创建子 view
*
* ```ts
* const innerView = view.createView({
* start: { x: 0, y: 0 },
* end: { x: 0.5, y: 0.5 },
* padding: 8,
* });
* ```
*
* @param cfg
* @returns View
*/
public createView(cfg?: Partial<ViewCfg>): View {
// 将会在 4.1 版本中移除递归嵌套 view,仅仅只允许 chart - view 两层。
// 这个 API 理论上用户量不多,所以暂时不发大版本,所以先暂时打一个 warning。
if (this.parent && this.parent.parent) {
// 存在 3 层 结构了
console.warn('The view nesting recursive feature will be removed at G2 V4.1. Please avoid to use it.');
}
// 子 view 共享 options 配置数据
const sharedOptions = {
data: this.options.data,
scales: clone(this.options.scales),
axes: clone(this.options.axes),
coordinate: clone(this.coordinateController.getOption()),
tooltip: clone(this.options.tooltip),
legends: clone(this.options.legends),
animate: this.options.animate,
visible: this.visible,
};
const v = new View({
parent: this,
canvas: this.canvas,
// 子 view 共用三层 group
backgroundGroup: this.backgroundGroup.addGroup({ zIndex: GROUP_Z_INDEX.BG }),
middleGroup: this.middleGroup.addGroup({ zIndex: GROUP_Z_INDEX.MID }),
foregroundGroup: this.foregroundGroup.addGroup({ zIndex: GROUP_Z_INDEX.FORE }),
theme: this.themeObject,
padding: this.padding,
...cfg,
options: {
...sharedOptions,
...get(cfg, 'options', {}),
},
});
this.views.push(v);
return v;
}
/**
* @deprecated
* This method will be removed at G2 V4.1. Replaced by {@link #createView()}
*/
public view(cfg?: Partial<ViewCfg>) {
console.warn('This method will be removed at G2 V4.1. Please use chart.createView() instead.');
return this.createView(cfg);
}
/**
* 删除一个子 view
* @param view
* @return removedView
*/
public removeView(view: View): View {
const removedView = remove(this.views, (v: View) => v === view)[0];
if (removedView) {
removedView.destroy();
}
return removedView;
}
/* end View 管理相关的 API */
// 一些 get 方法
/**
* 获取当前坐标系实例。
* @returns [[Coordinate]]
*/
public getCoordinate() {
return this.coordinateInstance;
}
/**
* 获取当前 view 的主题配置。
* @returns themeObject
*/
public getTheme(): LooseObject {
return this.themeObject;
}
/**
* 获得 x 轴字段的 scale 实例。
* @returns view 中 Geometry 对于的 x scale
*/
public getXScale(): Scale {
// 拿第一个 Geometry 的 X scale
// 隐藏逻辑:一个 view 中的 Geometry 必须 x 字段一致
const g = this.geometries[0];
return g ? g.getXScale() : null;
}
/**
* 获取 y 轴字段的 scales 实例。
* @returns view 中 Geometry 对于的 y scale 数组
*/
public getYScales(): Scale[] {
// 拿到所有的 Geometry 的 Y scale,然后去重
const tmpMap = {};
return this.geometries.map((g: Geometry) => {
const yScale = g.getYScale();
const field = yScale.field;
if (!tmpMap[field]) {
tmpMap[field] = true;
return yScale;
}
});
}
/**
* 获取 x 轴或者 y 轴对应的所有 scale 实例。
* @param dimType x | y
* @returns x 轴或者 y 轴对应的所有 scale 实例。
*/
public getScalesByDim(dimType: 'x' | 'y'): Record<string, Scale> {
const geometries = this.geometries;
const scales = {};
for (let i = 0, len = geometries.length; i < len; i++) {
const geometry = geometries[i];
const scale = dimType === 'x' ? geometry.getXScale() : geometry.getYScale();
if (scale && !scales[scale.field]) {
scales[scale.field] = scale;
}
}
return scales;
}
/**
* 根据字段名去获取 scale 实例。
* @param field 数据字段名称
* @param key id
*/
public getScaleByField(field: string, key?: string): Scale {
const defaultKey = key ? key : this.getScaleKey(field);
// 调用根节点 view 的方法获取
return this.getRootView().scalePool.getScale(defaultKey);
}
/**
* 返回所有配置信息。
* @returns 所有的 view API 配置。
*/
public getOptions(): Options {
return this.options;
}
/**
* 获取 view 的数据(过滤后的数据)。
* @returns 处理过滤器之后的数据。
*/
public getData() {
return this.filteredData;
}
/**
* 获得绘制的层级 group。
* @param layer 层级名称。
* @returns 对应层级的 Group。
*/
public getLayer(layer: LAYER): IGroup {
return layer === LAYER.BG