• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

antvis / L7Plot / 3730327136

pending completion
3730327136

push

github

yunji
chore: publish

731 of 1962 branches covered (37.26%)

Branch coverage included in aggregate %.

3048 of 4600 relevant lines covered (66.26%)

175.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

36.12
/packages/composite-layers/src/composite-layers/bubble-layer/index.ts
1
import { clone, isEqual, isUndefined, omit } from '@antv/util';
2
import { CompositeLayer } from '../../core/composite-layer';
21✔
3
import { PointLayer } from '../../core-layers/point-layer';
21✔
4
import { TextLayer } from '../../core-layers/text-layer';
21✔
5
import { ICoreLayer, ISource, SourceOptions, MouseEvent } from '../../types';
21✔
6
import { getDefaultState } from './adaptor';
21✔
7
import { EMPTY_JSON_SOURCE } from '../common/constants';
21✔
8
import { DEFAULT_OPTIONS, DEFAULT_STATE } from './constants';
21✔
9
import { BubbleLayerOptions } from './types';
21✔
10
import { getLabelLayerOptions } from '../common/label-layer';
21✔
11
import { isGestureMultiSelect } from '../common/multi-select';
21✔
12

21✔
13
export class BubbleLayer extends CompositeLayer<BubbleLayerOptions> {
21✔
14
  /**
21✔
15
   * 默认配置项
21✔
16
   */
17
  static DefaultOptions = DEFAULT_OPTIONS;
20!
18
  /**
19
   * 复合图层类型
20
   */
21
  public type = CompositeLayer.LayerType.BubbleLayer;
20✔
22
  /**
23
   * 主图层
24
   */
25
  protected get layer() {
20✔
26
    return this.fillLayer;
27
  }
28
  /**
29
   * 填充图层
20✔
30
   */
31
  public get fillLayer() {
32
    return this.subLayers.getLayer('fillLayer') as ICoreLayer;
33
  }
20✔
34
  /**
×
35
   * 高亮描边图层
×
36
   */
37
  public get highlightStrokeLayer() {
38
    return this.subLayers.getLayer('highlightStrokeLayer') as ICoreLayer;
39
  }
40
  /**
20✔
41
   * 高亮数据
×
42
   */
43
  private highlightData: any;
44
  /**
45
   * 选中填充面图层
46
   */
20✔
47
  public get selectFillLayer() {
×
48
    return this.subLayers.getLayer('selectFillLayer') as ICoreLayer;
×
49
  }
50
  /**
20✔
51
   * 选中描边图层
20✔
52
   */
53
  public get selectStrokeLayer() {
21✔
54
    return this.subLayers.getLayer('selectStrokeLayer') as ICoreLayer;
55
  }
56
  /**
57
   * 选中数据
58
   */
×
59
  private selectData: { feature: any; featureId: number }[] = [];
60
  /**
61
   * 标注文本图层
62
   */
63
  public get labelLayer() {
21✔
64
    return this.subLayers.getLayer('labelLayer') as TextLayer;
65
  }
66
  /**
67
   * 图层交互状态配置
68
   */
260✔
69
  private layerState = DEFAULT_STATE;
70

71
  constructor(options: BubbleLayerOptions) {
72
    super(options);
73
    this.initSubLayersEvent();
21✔
74
  }
75

76
  /**
77
   * 获取默认配置
78
   */
60✔
79
  public getDefaultOptions(): Partial<BubbleLayerOptions> {
80
    return BubbleLayer.DefaultOptions;
81
  }
82

83
  /**
21✔
84
   * 创建子图层
85
   */
86
  protected createSubLayers() {
87
    const source = this.source;
88
    this.layerState = getDefaultState(this.options.state);
20✔
89

90
    // 映射填充图层
91
    const fillLayer = new PointLayer({
92
      ...this.getFillLayerOptions(),
93
      id: 'fillLayer',
21✔
94
      shape: 'circle',
95
      source,
96
      interaction: true,
97
    });
98

60✔
99
    // 高亮描边图层
100
    const highlightStrokeLayer = new PointLayer({
101
      ...this.getHighlightStrokeLayerOptions(),
102
      id: 'highlightStrokeLayer',
103
      shape: 'circle',
21✔
104
    });
105

106
    // 选中填充图层
107
    const selectFillLayer = new PointLayer({
108
      ...this.getSelectFillLayerOptions(),
100✔
109
      id: 'selectFillLayer',
110
      shape: 'circle',
111
    });
112

113
    // 选中描边图层
114
    const selectStrokeLayer = new PointLayer({
115
      ...this.getSelectStrokeLayerOptions(),
116
      id: 'selectStrokeLayer',
21✔
117
      shape: 'circle',
20✔
118
    });
119

120
    // 标注图层
121
    const labelLayer = new TextLayer({
122
      ...getLabelLayerOptions<BubbleLayerOptions>(this.options),
21✔
123
      id: 'labelLayer',
20✔
124
      source,
20✔
125
    });
126

20✔
127
    const subLayers = [fillLayer, highlightStrokeLayer, selectFillLayer, selectStrokeLayer, labelLayer];
128

20✔
129
    return subLayers;
130
  }
20✔
131

132
  /**
20✔
133
   * 获取填充图层配置项
134
   */
20✔
135
  private getFillLayerOptions() {
20✔
136
    const {
20✔
137
      visible,
138
      minZoom,
139
      maxZoom,
140
      zIndex = 0,
141
      fillColor,
21✔
142
      radius,
20!
143
      opacity,
144
      strokeColor,
20✔
145
      lineOpacity,
20✔
146
      lineWidth,
20!
147
      ...baseConfig
148
    } = omit<any>(this.options, ['source']) as Omit<BubbleLayerOptions, 'source'>;
149
    // omit source 目前是图层共享 source,避免更新时透传 source 数据
20✔
150
    const defaultState = this.layerState;
151

152
    const fillState = {
20!
153
      active: defaultState.active.fillColor === false ? false : { color: defaultState.active.fillColor },
154
      select: false,
155
    };
20✔
156
    const fillStyle = {
20✔
157
      opacity: opacity,
158
      stroke: strokeColor,
21✔
159
      strokeOpacity: isUndefined(lineOpacity) ? opacity : lineOpacity,
160
      strokeWidth: lineWidth,
20!
161
    };
20✔
162

20✔
163
    const options = {
164
      ...baseConfig,
20!
165
      visible,
166
      minZoom,
60!
167
      maxZoom,
168
      zIndex,
20✔
169
      color: fillColor,
40✔
170
      size: radius,
171
      state: fillState,
172
      style: fillStyle,
173
    };
174

175
    return options;
176
  }
177

20✔
178
  private getHighlightStrokeLayerOptions() {
179
    const { visible, minZoom, maxZoom, zIndex = 0, radius } = this.options;
21✔
180
    const defaultState = this.layerState;
20!
181
    const strokeStyle = {
20✔
182
      opacity: 0,
20!
183
      stroke: defaultState.active.strokeColor || undefined,
20✔
184
      strokeOpacity: defaultState.active.lineOpacity,
20✔
185
      strokeWidth: defaultState.active?.lineWidth,
40✔
186
    };
187

188
    const options = {
189
      visible: visible && Boolean(defaultState.active.strokeColor),
190
      zIndex: zIndex + 0.1,
191
      minZoom,
192
      maxZoom,
193
      source: EMPTY_JSON_SOURCE,
194
      size: radius,
195
      style: strokeStyle,
20✔
196
    };
197

21✔
198
    return options;
20!
199
  }
20✔
200

20✔
201
  private getSelectFillLayerOptions() {
202
    const { visible, minZoom, maxZoom, zIndex = 0, radius, opacity } = this.options;
20!
203
    const defaultState = this.layerState;
204
    const color = defaultState.select.fillColor || undefined;
205
    const fillStyle = { opacity: opacity };
206

20✔
207
    const option = {
40✔
208
      visible: visible && Boolean(color),
209
      zIndex: zIndex + 0.1,
210
      minZoom,
211
      maxZoom,
212
      source: EMPTY_JSON_SOURCE,
213
      color,
214
      size: radius,
215
      style: fillStyle,
20✔
216
      state: { select: false, active: false },
217
    };
218

219
    return option;
220
  }
21✔
221

×
222
  private getSelectStrokeLayerOptions() {
×
223
    const { visible, minZoom, maxZoom, zIndex = 0, radius } = this.options;
×
224
    const defaultState = this.layerState;
×
225
    const strokeStyle = {
226
      opacity: 0,
227
      stroke: defaultState.select.strokeColor || undefined,
×
228
      strokeOpacity: defaultState.select.lineOpacity,
×
229
      strokeWidth: defaultState.select.lineWidth,
230
    };
×
231

×
232
    const option = {
×
233
      visible: visible && Boolean(strokeStyle.stroke),
234
      zIndex: zIndex + 0.1,
235
      minZoom,
236
      maxZoom,
237
      source: EMPTY_JSON_SOURCE,
21✔
238
      size: radius,
×
239
      style: strokeStyle,
×
240
    };
×
241

242
    return option;
×
243
  }
×
244

×
245
  /**
×
246
   * 设置子图层数据
247
   */
×
248
  protected setSubLayersSource(source: SourceOptions | ISource) {
×
249
    if (this.isSourceInstance(source)) {
×
250
      this.source = source;
×
251
      this.fillLayer.setSource(source);
252
      this.labelLayer.setSource(source);
253
    } else {
×
254
      const { data, ...option } = source;
255
      this.source.setData(data, option);
×
256
    }
257

258
    this.highlightStrokeLayer.changeData(EMPTY_JSON_SOURCE);
259
    this.selectFillLayer.changeData(EMPTY_JSON_SOURCE);
260
    this.selectStrokeLayer.changeData(EMPTY_JSON_SOURCE);
21✔
261
  }
×
262

×
263
  /**
264
   * 设置高亮描边子图层数据
×
265
   */
×
266
  protected setHighlightLayerSource(feature?: any, featureId = -999) {
267
    if (this.highlightData === featureId) {
×
268
      return;
×
269
    }
270
    const features = feature ? [feature] : [];
×
271
    const parser = this.source.parser;
272
    const data = parser.type === 'geojson' ? { type: 'FeatureCollection', features } : features;
×
273

×
274
    if (features.length) {
×
275
      // 获取映射后的 size 大小,避免 fillLayer 的 size 不是常量的情况
276
      const encodedData = this.fillLayer.layer.getEncodedData();
×
277
      const encodedFeature = encodedData.find((item) => item.id === featureId);
×
278
      const featureSize = encodedFeature?.size;
×
279
      this.highlightStrokeLayer.update({ size: featureSize, source: { data, parser } });
×
280
    } else {
281
      this.highlightStrokeLayer.changeData({ data, parser });
282
    }
283

284
    this.highlightData = featureId;
×
285
  }
×
286

287
  /**
288
   * 设置选中描边与填充子图层数据
289
   */
290
  protected setSelectLayerSource(selectData: any[] = []) {
×
291
    if (
292
      this.selectData.length === selectData.length &&
293
      isEqual(
×
294
        this.selectData.map(({ featureId }) => featureId),
×
295
        selectData.map(({ featureId }) => featureId)
296
      )
297
    ) {
×
298
      return;
299
    }
×
300

301
    const parser = this.source.parser;
×
302

303
    if (selectData.length) {
304
      const featureSizeKey = '_Feature_Size_';
305
      // 获取映射后的 size 大小,避免 fillLayer 的 size 不是常量的情况
×
306
      const encodedData = this.fillLayer.layer.getEncodedData();
307
      let data: Record<string, any>[] | Record<string, any>;
308

309
      if (parser.type === 'geojson') {
310
        data = {
311
          type: 'FeatureCollection',
×
312
          features: selectData.map(({ feature, featureId }) => ({
×
313
            ...feature,
×
314
            properties: {
315
              ...feature.properties,
×
316
              [featureSizeKey]: encodedData.find((item) => item.id === featureId)?.size,
317
            },
318
          })),
319
        };
320
      } else {
21✔
321
        data = selectData.map(({ feature, featureId }) => ({
322
          ...feature,
20✔
323
          [featureSizeKey]: encodedData.find((item) => item.id === featureId)?.size,
20✔
324
        }));
20✔
325
      }
20✔
326

20✔
327
      const sizeAttr = {
20!
328
        field: featureSizeKey,
×
329
        value: (obj: Record<string, any>) => obj[featureSizeKey],
330
      };
20!
331
      this.selectFillLayer.update({
20✔
332
        size: sizeAttr,
20✔
333
        source: { data, parser },
334
      });
335
      this.selectStrokeLayer.update({
20!
336
        size: sizeAttr,
20✔
337
        source: { data, parser },
338
      });
339
    } else {
21✔
340
      const data = parser.type === 'geojson' ? { type: 'FeatureCollection', features: [] } : [];
×
341
      this.selectFillLayer.changeData({ data, parser });
×
342
      this.selectStrokeLayer.changeData({ data, parser });
×
343
    }
×
344

×
345
    this.selectData = selectData;
×
346
  }
×
347

348
  /**
349
   * 初始化子图层事件
×
350
   */
351
  protected initSubLayersEvent() {
×
352
    // 初始化主图层交互事件
353
    this.fillLayer.off('mousemove', this.onHighlighHandle);
354
    this.fillLayer.off('mouseout', this.onHighlighHandle);
×
355
    this.fillLayer.off('click', this.onSelectHandle);
×
356
    this.selectData = [];
×
357
    this.highlightData = null;
358
    if (!this.options.state) return;
359
    // active
×
360
    if (this.options.state.active) {
361
      this.fillLayer.on('mousemove', this.onHighlighHandle);
×
362
      this.fillLayer.on('mouseout', this.onUnhighlighHandle);
363
    }
×
364
    // select
365
    if (this.options.state.select) {
366
      this.fillLayer.on('click', this.onSelectHandle);
367
    }
368
  }
21✔
369

×
370
  /**
×
371
   * 图层高亮回调
372
   */
373
  private onHighlighHandle = (event: MouseEvent) => {
374
    const { feature, featureId } = event;
375
    this.setHighlightLayerSource(feature, featureId);
21✔
376
  };
×
377

×
378
  /**
379
   * 图层取消高亮回调
380
   */
381
  private onUnhighlighHandle = () => {
382
    this.setHighlightLayerSource();
21✔
383
  };
384

×
385
  /**
386
   * 图层选中回调
×
387
   */
388
  private onSelectHandle = (event: MouseEvent) => {
×
389
    const { feature, featureId } = event;
390
    this.handleSelectData(featureId, feature);
×
391
  };
392

×
393
  private handleSelectData(featureId: number, feature: any, isSelfMultiSelect?: boolean) {
394
    const { enabledMultiSelect, triggerMultiSelectKey } = this.options;
×
395
    const isMultiSelect = isGestureMultiSelect(enabledMultiSelect, triggerMultiSelectKey) || isSelfMultiSelect;
×
396
    let selectData = clone(this.selectData);
×
397
    const index = selectData.findIndex((item) => item.featureId === featureId);
398

×
399
    if (index === -1) {
×
400
      if (isMultiSelect) {
401
        selectData.push({ feature, featureId });
×
402
      } else {
×
403
        selectData = [{ feature, featureId }];
404
      }
405
      this.emit('select', feature, clone(selectData));
406
    } else {
407
      const unselectFeature = selectData[index];
408
      if (isMultiSelect) {
409
        selectData.splice(index, 1);
21✔
410
      } else {
×
411
        selectData = [];
×
412
      }
×
413
      this.emit('unselect', unselectFeature, clone(selectData));
×
414
    }
415

×
416
    this.setSelectLayerSource(selectData);
×
417
  }
418

×
419
  /**
×
420
   * 更新
421
   */
422
  public update(options: Partial<BubbleLayerOptions>) {
423
    super.update(options);
424

425
    this.initSubLayersEvent();
21✔
426
  }
×
427

×
428
  /**
×
429
   * 更新: 更新配置
×
430
   */
×
431
  public updateOption(options: Partial<BubbleLayerOptions>) {
432
    super.updateOption(options);
433
    this.layerState = getDefaultState(this.options.state);
434
  }
435

21✔
436
  /**
×
437
   * 更新子图层
×
438
   */
×
439
  protected updateSubLayers(options: Partial<BubbleLayerOptions>) {
×
440
    // 映射填充面图层
×
441
    this.fillLayer.update(this.getFillLayerOptions(), false);
442

×
443
    // 高亮图层
×
444
    this.highlightStrokeLayer.update(this.getHighlightStrokeLayerOptions(), false);
445

×
446
    // 选中填充图层
×
447
    this.selectFillLayer.update(this.getSelectFillLayerOptions(), false);
×
448

449
    // 选中描边图层
450
    this.selectStrokeLayer.update(this.getSelectStrokeLayerOptions(), false);
451

452
    // 标注图层
453
    this.labelLayer.update(getLabelLayerOptions<BubbleLayerOptions>(this.options), false);
21✔
454

×
455
    // 重置高亮/选中状态
×
456
    if (this.options.visible) {
×
457
      if (!isUndefined(options.state) && !isEqual(this.lastOptions.state, this.options.state)) {
×
458
        this.updateHighlightSubLayers();
×
459
      }
460

×
461
      if (this.layerState.active.strokeColor) {
×
462
        this.setHighlightLayerSource();
463
      }
×
464
      if (this.layerState.select.fillColor || this.layerState.select.strokeColor) {
×
465
        this.setSelectLayerSource();
466
      }
467
    }
468
  }
469

21✔
470
  /**
×
471
   * 更新高亮及选中子图层
472
   */
473
  private updateHighlightSubLayers() {
474
    const defaultState = this.layerState;
475
    const lasetDefaultState = getDefaultState(this.lastOptions.state);
21✔
476

21✔
477
    if (lasetDefaultState.active.strokeColor !== defaultState.active.strokeColor) {
478
      defaultState.active.strokeColor ? this.highlightStrokeLayer.show() : this.highlightStrokeLayer.hide();
21✔
479
    }
480

481
    if (lasetDefaultState.select.fillColor !== defaultState.select.fillColor) {
482
      defaultState.select.fillColor ? this.selectFillLayer.show() : this.selectFillLayer.hide();
483
    }
484

485
    if (lasetDefaultState.select.strokeColor !== defaultState.select.strokeColor) {
486
      defaultState.select.strokeColor ? this.selectStrokeLayer.show() : this.selectStrokeLayer.hide();
487
    }
488
  }
489

490
  /**
491
   * 设置图层 zIndex
492
   */
493
  public setIndex(zIndex: number) {
494
    this.fillLayer.setIndex(zIndex);
495
    this.highlightStrokeLayer.setIndex(zIndex + 0.1);
496
    this.selectFillLayer.setIndex(zIndex + 0.1);
497
    this.selectStrokeLayer.setIndex(zIndex + 0.1);
498
    this.labelLayer.setIndex(zIndex + 0.1);
499
  }
500

501
  /**
502
   * 设置图层高亮状态
503
   */
504
  public setActive(field: string, value: number | string) {
505
    const source = this.fillLayer.source;
506
    const featureId = source.getFeatureId(field, value);
507
    if (isUndefined(featureId)) {
508
      console.warn(`Feature non-existent by field: ${field},value: ${value}`);
509
      return;
510
    }
511

512
    if (this.layerState.active.fillColor) {
513
      this.fillLayer.layer.setActive(featureId);
514
    }
515

516
    if (this.layerState.active.strokeColor) {
517
      const feature = source.getFeatureById(featureId);
518
      this.setHighlightLayerSource(feature, featureId);
519
    }
520
  }
521

522
  /**
523
   * 设置图层选中状态
524
   */
525
  public setSelect(field: string, value: number | string) {
526
    const source = this.fillLayer.source;
527
    const featureId = source.getFeatureId(field, value);
528
    if (isUndefined(featureId)) {
529
      console.warn(`Feature non-existent by field: ${field},value: ${value}`);
530
      return;
531
    }
532

533
    if (this.layerState.select.strokeColor === false && this.layerState.select.fillColor === false) {
534
      return;
535
    }
536

537
    const feature = source.getFeatureById(featureId);
538
    this.handleSelectData(featureId, feature, true);
539
  }
540

541
  /**
542
   * 图层框选数据
543
   */
544
  public boxSelect(bounds: [number, number, number, number], callback: (...args: any[]) => void) {
545
    this.fillLayer.boxSelect(bounds, callback);
546
  }
547
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc