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

keplergl / kepler.gl / 12509145710

26 Dec 2024 10:43PM UTC coverage: 67.491%. Remained the same
12509145710

push

github

web-flow
[chore] ts refactoring (#2861)

- move several base layer types to layer.d.ts
- other ts changes

Signed-off-by: Ihor Dykhta <dikhta.igor@gmail.com>

5841 of 10041 branches covered (58.17%)

Branch coverage included in aggregate %.

8 of 12 new or added lines in 9 files covered. (66.67%)

31 existing lines in 6 files now uncovered.

11978 of 16361 relevant lines covered (73.21%)

87.57 hits per line

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

74.8
/src/layers/src/point-layer/point-layer.ts
1
// SPDX-License-Identifier: MIT
2
// Copyright contributors to the kepler.gl project
3

4
import * as arrow from 'apache-arrow';
5

6
import {BrushingExtension} from '@deck.gl/extensions';
7
import {ScatterplotLayer} from '@deck.gl/layers';
8

9
import {GeoArrowScatterplotLayer} from '@kepler.gl/deckgl-arrow-layers';
10
import {FilterArrowExtension} from '@kepler.gl/deckgl-layers';
11

12
import Layer, {
13
  LayerBaseConfig,
14
  LayerBaseConfigPartial,
15
  LayerColorConfig,
16
  LayerSizeConfig,
17
  LayerStrokeColorConfig
18
} from '../base-layer';
19
import {
20
  hexToRgb,
21
  findDefaultColorField,
22
  DataContainerInterface,
23
  ArrowDataContainer
24
} from '@kepler.gl/utils';
25
import {default as KeplerTable} from '@kepler.gl/table';
26
import PointLayerIcon from './point-layer-icon';
27
import {
28
  LAYER_VIS_CONFIGS,
29
  DEFAULT_LAYER_COLOR,
30
  CHANNEL_SCALES,
31
  DEFAULT_COLOR_UI
32
} from '@kepler.gl/constants';
33
import {getTextOffsetByRadius, formatTextLabelData} from '../layer-text-label';
34
import {
35
  assignPointPairToLayerColumn,
36
  isLayerHoveredFromArrow,
37
  getBoundsFromArrowMetadata,
38
  createGeoArrowPointVector,
39
  getFilteredIndex,
40
  getNeighbors
41
} from '../layer-utils';
42
import {getGeojsonPointDataMaps, GeojsonPointDataMaps} from '../geojson-layer/geojson-utils';
43
import {
44
  ColorRange,
45
  Merge,
46
  RGBColor,
47
  VisConfigBoolean,
48
  VisConfigColorRange,
49
  VisConfigColorSelect,
50
  VisConfigNumber,
51
  VisConfigRange,
52
  LayerColumn,
53
  Field,
54
  AnimationConfig
55
} from '@kepler.gl/types';
56

57
export type PointLayerVisConfigSettings = {
58
  radius: VisConfigNumber;
59
  fixedRadius: VisConfigBoolean;
60
  opacity: VisConfigNumber;
61
  outline: VisConfigBoolean;
62
  thickness: VisConfigNumber;
63
  strokeColor: VisConfigColorSelect;
64
  colorRange: VisConfigColorRange;
65
  strokeColorRange: VisConfigColorRange;
66
  radiusRange: VisConfigRange;
67
  filled: VisConfigBoolean;
68
};
69

70
export type PointLayerColumnsConfig = {
71
  lat: LayerColumn;
72
  lng: LayerColumn;
73
  altitude?: LayerColumn;
74
  neighbors?: LayerColumn;
75
  geojson: LayerColumn;
76
  geoarrow: LayerColumn;
77
};
78

79
export type PointLayerVisConfig = {
80
  radius: number;
81
  fixedRadius: boolean;
82
  opacity: number;
83
  outline: boolean;
84
  thickness: number;
85
  strokeColor: RGBColor;
86
  colorRange: ColorRange;
87
  strokeColorRange: ColorRange;
88
  radiusRange: [number, number];
89
  filled: boolean;
90
  billboard: boolean;
91
  allowHover: boolean;
92
  showNeighborOnHover: boolean;
93
  showHighlightColor: boolean;
94
};
95
export type PointLayerVisualChannelConfig = LayerColorConfig &
96
  LayerSizeConfig &
97
  LayerStrokeColorConfig;
98
export type PointLayerConfig = Merge<
99
  LayerBaseConfig,
100
  {columns: PointLayerColumnsConfig; visConfig: PointLayerVisConfig}
101
> &
102
  PointLayerVisualChannelConfig;
103

104
export type PointLayerData = {
105
  position: number[];
106
  index: number;
107
  neighbors: any[];
108
};
109

110
export const pointPosAccessor =
111
  ({lat, lng, altitude}: PointLayerColumnsConfig) =>
11✔
112
  (dc: DataContainerInterface) =>
349✔
113
  (d: {index: number}) =>
349✔
114
    [
3,097✔
115
      dc.valueAt(d.index, lng.fieldIdx),
116
      dc.valueAt(d.index, lat.fieldIdx),
117
      altitude && altitude.fieldIdx > -1 ? dc.valueAt(d.index, altitude.fieldIdx) : 0
9,291✔
118
    ];
119

120
export const geojsonPosAccessor =
121
  ({geojson}: {geojson: LayerColumn}) =>
11✔
122
  d =>
10✔
123
    d[geojson.fieldIdx];
21✔
124

125
export const geoarrowPosAccessor =
126
  ({geoarrow}: PointLayerColumnsConfig) =>
11✔
127
  (dataContainer: DataContainerInterface) =>
×
128
  (d: {index: number}) => {
×
129
    const row = dataContainer.valueAt(d.index, geoarrow.fieldIdx);
×
130
    return [row.get(0), row.get(1), 0];
×
131
  };
132

133
export const COLUMN_MODE_POINTS = 'points';
11✔
134
export const COLUMN_MODE_GEOJSON = 'geojson';
11✔
135
export const COLUMN_MODE_GEOARROW = 'geoarrow';
11✔
136

137
export const pointRequiredColumns: ['lat', 'lng'] = ['lat', 'lng'];
11✔
138
export const pointOptionalColumns: ['altitude', 'neighbors'] = ['altitude', 'neighbors'];
11✔
139
export const geojsonRequiredColumns: ['geojson'] = ['geojson'];
11✔
140
export const geoarrowRequiredColumns: ['geoarrow'] = ['geoarrow'];
11✔
141

142
const SUPPORTED_COLUMN_MODES = [
11✔
143
  {
144
    key: COLUMN_MODE_POINTS,
145
    label: 'Point Columns',
146
    requiredColumns: pointRequiredColumns,
147
    optionalColumns: pointOptionalColumns
148
  },
149
  {
150
    key: COLUMN_MODE_GEOJSON,
151
    label: 'GeoJSON Feature',
152
    requiredColumns: geojsonRequiredColumns
153
  },
154
  {
155
    key: COLUMN_MODE_GEOARROW,
156
    label: 'Geoarrow Points',
157
    requiredColumns: geoarrowRequiredColumns
158
  }
159
];
160
const DEFAULT_COLUMN_MODE = COLUMN_MODE_POINTS;
11✔
161

162
const brushingExtension = new BrushingExtension();
11✔
163
const arrowCPUFilterExtension = new FilterArrowExtension();
11✔
164

165
function pushPointPosition(data: any[], pos: number[], index: number, neighbors?: number[]) {
166
  if (pos.every(Number.isFinite)) {
1,471✔
167
    data.push({
1,428✔
168
      position: pos,
169
      // index is important for filter
170
      index,
171
      ...(neighbors ? {neighbors} : {})
1,428✔
172
    });
173
  }
174
}
175

176
export const pointVisConfigs: {
177
  radius: 'radius';
178
  fixedRadius: 'fixedRadius';
179
  opacity: 'opacity';
180
  outline: 'outline';
181
  thickness: 'thickness';
182
  strokeColor: 'strokeColor';
183
  colorRange: 'colorRange';
184
  strokeColorRange: 'strokeColorRange';
185
  radiusRange: 'radiusRange';
186
  filled: VisConfigBoolean;
187
  billboard: 'billboard';
188
  allowHover: 'allowHover';
189
  showNeighborOnHover: 'showNeighborOnHover';
190
  showHighlightColor: 'showHighlightColor';
191
} = {
11✔
192
  radius: 'radius',
193
  fixedRadius: 'fixedRadius',
194
  opacity: 'opacity',
195
  outline: 'outline',
196
  thickness: 'thickness',
197
  strokeColor: 'strokeColor',
198
  colorRange: 'colorRange',
199
  strokeColorRange: 'strokeColorRange',
200
  radiusRange: 'radiusRange',
201
  filled: {
202
    ...LAYER_VIS_CONFIGS.filled,
203
    type: 'boolean',
204
    label: 'layer.fillColor',
205
    defaultValue: true,
206
    property: 'filled'
207
  },
208
  billboard: 'billboard',
209
  allowHover: 'allowHover',
210
  showNeighborOnHover: 'showNeighborOnHover',
211
  showHighlightColor: 'showHighlightColor'
212
};
213

214
export default class PointLayer extends Layer {
215
  declare config: PointLayerConfig;
216
  declare visConfigSettings: PointLayerVisConfigSettings;
217
  dataToFeature: GeojsonPointDataMaps = [];
188✔
218

219
  dataContainer: DataContainerInterface | null = null;
188✔
220
  geoArrowVector: arrow.Vector | undefined = undefined;
188✔
221

222
  /*
223
   * CPU filtering an arrow table by values and assembling a partial copy of the raw table is expensive
224
   * so we will use filteredIndex to create an attribute e.g. filteredIndex [0|1] for GPU filtering
225
   * in deck.gl layer, see: FilterArrowExtension in @kepler.gl/deckgl-layers.
226
   * Note that this approach can create visible lags in case of a lot of discarted geometry.
227
   */
228
  filteredIndex: Uint8ClampedArray | null = null;
188✔
229
  filteredIndexTrigger: number[] = [];
188✔
230

231
  constructor(props) {
232
    super(props);
188✔
233

234
    this.registerVisConfig(pointVisConfigs);
188✔
235
    this.getPositionAccessor = (dataContainer: DataContainerInterface) => {
188✔
236
      switch (this.config.columnMode) {
359!
237
        case COLUMN_MODE_GEOARROW:
238
          return geoarrowPosAccessor(this.config.columns)(dataContainer);
×
239
        case COLUMN_MODE_GEOJSON:
240
          return geojsonPosAccessor(this.config.columns);
10✔
241
        default:
242
          // COLUMN_MODE_POINTS
243
          return pointPosAccessor(this.config.columns)(dataContainer);
349✔
244
      }
245
    };
246
  }
247

248
  get type(): 'point' {
249
    return 'point';
967✔
250
  }
251

252
  get isAggregated(): false {
253
    return false;
21✔
254
  }
255

256
  get layerIcon() {
257
    return PointLayerIcon;
28✔
258
  }
259

260
  get optionalColumns() {
261
    return pointOptionalColumns;
244✔
262
  }
263

264
  get columnPairs() {
265
    return this.defaultPointColumnPairs;
13✔
266
  }
267

268
  get supportedColumnModes() {
269
    return SUPPORTED_COLUMN_MODES;
620✔
270
  }
271

272
  get noneLayerDataAffectingProps() {
273
    return [...super.noneLayerDataAffectingProps, 'radius'];
35✔
274
  }
275

276
  get visualChannels() {
277
    return {
1,855✔
278
      color: {
279
        ...super.visualChannels.color,
280
        accessor: 'getFillColor',
281
        condition: config => config.visConfig.filled,
24✔
282
        defaultValue: config => config.color
148✔
283
      },
284
      strokeColor: {
285
        property: 'strokeColor',
286
        key: 'strokeColor',
287
        field: 'strokeColorField',
288
        scale: 'strokeColorScale',
289
        domain: 'strokeColorDomain',
290
        range: 'strokeColorRange',
291
        channelScaleType: CHANNEL_SCALES.color,
292
        accessor: 'getLineColor',
293
        condition: config => config.visConfig.outline,
24✔
294
        defaultValue: config => config.visConfig.strokeColor || config.color
239✔
295
      },
296
      size: {
297
        ...super.visualChannels.size,
298
        property: 'radius',
299
        range: 'radiusRange',
300
        fixed: 'fixedRadius',
301
        channelScaleType: 'radius',
302
        accessor: 'getRadius',
303
        defaultValue: 1
304
      }
305
    };
306
  }
307

308
  setInitialLayerConfig(dataset) {
309
    if (!dataset.dataContainer.numRows()) {
59!
310
      return this;
×
311
    }
312
    const defaultColorField = findDefaultColorField(dataset);
59✔
313

314
    if (defaultColorField) {
59✔
315
      this.updateLayerConfig({
13✔
316
        // @ts-expect-error Remove this after updateLayerConfig converted into generic function
317
        colorField: defaultColorField
318
      });
319
      this.updateLayerVisualChannel(dataset, 'color');
13✔
320
    }
321

322
    return this;
59✔
323
  }
324

325
  static findDefaultLayerProps({fieldPairs = []}: KeplerTable) {
×
326
    const props: {
327
      label: string;
328
      color?: RGBColor;
329
      isVisible?: boolean;
330
      columns?: PointLayerColumnsConfig;
331
    }[] = [];
91✔
332

333
    // Make layer for each pair
334
    fieldPairs.forEach(pair => {
91✔
335
      const latField = pair.pair.lat;
75✔
336

337
      const prop: {
338
        label: string;
339
        color?: RGBColor;
340
        isVisible?: boolean;
341
        columns?: PointLayerColumnsConfig;
342
      } = {
75✔
343
        label: pair.defaultName || 'Point'
75!
344
      };
345

346
      // default layer color for begintrip and dropoff point
347
      if (latField.value in DEFAULT_LAYER_COLOR) {
75✔
348
        prop.color = hexToRgb(DEFAULT_LAYER_COLOR[latField.value]);
17✔
349
      }
350

351
      // set the first layer to be visible
352
      if (props.length === 0) {
75✔
353
        prop.isVisible = true;
47✔
354
      }
355
      // @ts-expect-error logically separate geojson column type?
356
      prop.columns = assignPointPairToLayerColumn(pair, true);
75✔
357

358
      props.push(prop);
75✔
359
    });
360

361
    return {props};
91✔
362
  }
363

364
  getDefaultLayerConfig(props: LayerBaseConfigPartial) {
365
    const defaultLayerConfig = super.getDefaultLayerConfig(props ?? {});
320!
366
    return {
320✔
367
      ...defaultLayerConfig,
368

369
      columnMode: props?.columnMode ?? DEFAULT_COLUMN_MODE,
607✔
370

371
      // add stroke color visual channel
372
      strokeColorField: null,
373
      strokeColorDomain: [0, 1],
374
      strokeColorScale: 'quantile',
375
      colorUI: {
376
        ...defaultLayerConfig.colorUI,
377
        strokeColorRange: DEFAULT_COLOR_UI
378
      }
379
    };
380
  }
381

382
  calculateDataAttribute({filteredIndex, dataContainer}: KeplerTable, getPosition) {
383
    const {columnMode} = this.config;
131✔
384

385
    // 1) COLUMN_MODE_GEOARROW - when we have a geoarrow point column
386
    // 2) COLUMN_MODE_POINTS + ArrowDataContainer > create geoarrow point column on the fly
387
    if (
131!
388
      dataContainer instanceof ArrowDataContainer &&
131!
389
      (columnMode === COLUMN_MODE_GEOARROW || columnMode === COLUMN_MODE_POINTS)
390
    ) {
391
      this.filteredIndex = getFilteredIndex(
×
392
        dataContainer.numRows(),
393
        filteredIndex,
394
        this.filteredIndex
395
      );
396
      this.filteredIndexTrigger = filteredIndex;
×
397

398
      if (this.config.columnMode === COLUMN_MODE_GEOARROW) {
×
399
        this.geoArrowVector = dataContainer.getColumn(this.config.columns.geoarrow.fieldIdx);
×
400
      } else {
401
        // generate a column compatible with geoarrow point
402
        this.geoArrowVector = createGeoArrowPointVector(dataContainer, getPosition);
×
403
      }
404

405
      return dataContainer.getTable();
×
406
    }
407

408
    // we don't need these in non-Arrow modes atm.
409
    this.geoArrowVector = undefined;
131✔
410
    this.filteredIndex = null;
131✔
411

412
    const data: PointLayerData[] = [];
131✔
413

414
    for (let i = 0; i < filteredIndex.length; i++) {
131✔
415
      const index = filteredIndex[i];
1,475✔
416
      let neighbors;
417

418
      if (this.config.columnMode === COLUMN_MODE_POINTS) {
1,475✔
419
        if (this.config.columns.neighbors?.value) {
1,454✔
420
          const {fieldIdx} = this.config.columns.neighbors;
60✔
421
          neighbors = Array.isArray(dataContainer.valueAt(index, fieldIdx))
60!
422
            ? dataContainer.valueAt(index, fieldIdx)
423
            : [];
424
        }
425
        const pos = getPosition({index});
1,454✔
426

427
        // if doesn't have point lat or lng, do not add the point
428
        // deck.gl can't handle position = null
429
        pushPointPosition(data, pos, index, neighbors);
1,454✔
430
      } else {
431
        // COLUMN_MODE_GEOJSON mode - point from geojson coordinates
432
        const coordinates = this.dataToFeature[i];
21✔
433
        // if multi points
434
        if (coordinates && Array.isArray(coordinates[0])) {
21✔
435
          coordinates.forEach(coord => {
2✔
436
            pushPointPosition(data, coord, index);
5✔
437
          });
438
        } else if (coordinates && Number.isFinite(coordinates[0])) {
19✔
439
          pushPointPosition(data, coordinates as number[], index);
12✔
440
        }
441
      }
442
    }
443

444
    return data;
131✔
445
  }
446

447
  formatLayerData(datasets, oldLayerData) {
448
    if (this.config.dataId === null) {
222!
449
      return {};
×
450
    }
451
    const {textLabel} = this.config;
222✔
452
    const {gpuFilter, dataContainer} = datasets[this.config.dataId];
222✔
453
    const {data, triggerChanged} = this.updateData(datasets, oldLayerData);
222✔
454
    const getPosition = d => d.position;
222✔
455

456
    // get all distinct characters in the text labels
457
    const textLabels = formatTextLabelData({
222✔
458
      textLabel,
459
      triggerChanged,
460
      oldLayerData,
461
      data,
462
      dataContainer,
463
      filteredIndex: this.filteredIndex
464
    });
465

466
    const accessors = this.getAttributeAccessors({dataContainer});
222✔
467

468
    const isFilteredAccessor = (data: {index: number}) => {
222✔
469
      return this.filteredIndex ? this.filteredIndex[data.index] : 1;
×
470
    };
471

472
    return {
222✔
473
      data,
474
      getPosition,
475
      getFilterValue: gpuFilter.filterValueAccessor(dataContainer)(),
476
      getFiltered: isFilteredAccessor,
477
      textLabels,
478
      ...accessors
479
    };
480
  }
481
  /* eslint-enable complexity */
482

483
  updateLayerMeta(dataset: KeplerTable) {
484
    const {dataContainer} = dataset;
130✔
485
    this.dataContainer = dataContainer;
130✔
486

487
    if (this.config.columnMode === COLUMN_MODE_GEOJSON) {
130✔
488
      const getFeature = this.getPositionAccessor();
5✔
489
      this.dataToFeature = getGeojsonPointDataMaps(dataContainer, getFeature);
5✔
490
    } else if (this.config.columnMode === COLUMN_MODE_GEOARROW) {
125!
491
      const boundsFromMetadata = getBoundsFromArrowMetadata(
×
492
        this.config.columns.geoarrow,
493
        dataContainer as ArrowDataContainer
494
      );
495
      if (boundsFromMetadata) {
×
496
        this.updateMeta({bounds: boundsFromMetadata});
×
497
      } else {
498
        const getPosition = this.getPositionAccessor(dataContainer);
×
499
        const bounds = this.getPointsBounds(dataContainer, getPosition);
×
500
        this.updateMeta({bounds});
×
501
      }
502
    } else {
503
      const getPosition = this.getPositionAccessor(dataContainer);
125✔
504
      const bounds = this.getPointsBounds(dataContainer, getPosition);
125✔
505
      this.updateMeta({bounds});
125✔
506
    }
507
  }
508

509
  // eslint-disable-next-line complexity
510
  renderLayer(opts) {
511
    const {data, gpuFilter, objectHovered, mapState, interactionConfig, dataset} = opts;
19✔
512

513
    // if no field size is defined we need to pass fixed radius = false
514
    const fixedRadius = this.config.visConfig.fixedRadius && Boolean(this.config.sizeField);
19!
515
    const radiusScale = this.getRadiusScaleByZoom(mapState, fixedRadius);
19✔
516

517
    const layerProps = {
19✔
518
      stroked: this.config.visConfig.outline,
519
      filled: this.config.visConfig.filled,
520
      lineWidthScale: this.config.visConfig.thickness,
521
      billboard: this.config.visConfig.billboard,
522
      radiusScale,
523
      ...(this.config.visConfig.fixedRadius ? {} : {radiusMaxPixels: 500})
19!
524
    };
525

526
    const updateTriggers = {
19✔
527
      getPosition: this.config.columns,
528
      getFilterValue: gpuFilter.filterValueUpdateTriggers,
529
      getFiltered: this.filteredIndexTrigger,
530
      ...this.getVisualChannelUpdateTriggers()
531
    };
532

533
    const useArrowLayer = Boolean(this.geoArrowVector);
19✔
534

535
    const defaultLayerProps = this.getDefaultDeckLayerProps(opts);
19✔
536
    const brushingProps = this.getBrushingExtensionProps(interactionConfig);
19✔
537
    const getPixelOffset = getTextOffsetByRadius(radiusScale, data.getRadius, mapState);
19✔
538
    const extensions = [
19✔
539
      ...defaultLayerProps.extensions,
540
      brushingExtension,
541
      ...(useArrowLayer ? [arrowCPUFilterExtension] : [])
19!
542
    ];
543

544
    const sharedProps = {
19✔
545
      getFilterValue: data.getFilterValue,
546
      extensions,
547
      filterRange: defaultLayerProps.filterRange,
548
      visible: defaultLayerProps.visible,
549
      ...brushingProps
550
    };
551
    const hoveredObject = this.hasHoveredObject(objectHovered);
19✔
552
    const {showNeighborOnHover, allowHover} = this.config.visConfig;
19✔
553
    let neighborsData: ReturnType<typeof getNeighbors> = [];
19✔
554
    if (allowHover && showNeighborOnHover && hoveredObject) {
19!
555
      // find neighbors
556
      neighborsData = getNeighbors(
×
557
        this.config.columns.neighbors,
558
        dataset.dataContainer,
559
        hoveredObject.index,
560
        this.getPositionAccessor(dataset.dataContainer)
561
      );
562
    }
563

564
    let ScatterplotLayerClass: typeof ScatterplotLayer | typeof GeoArrowScatterplotLayer =
565
      ScatterplotLayer;
19✔
566
    let deckLayerData = data.data;
19✔
567
    let getPosition = data.getPosition;
19✔
568
    if (useArrowLayer) {
19!
569
      ScatterplotLayerClass = GeoArrowScatterplotLayer;
×
570
      deckLayerData = dataset.dataContainer.getTable();
×
571
      getPosition = this.geoArrowVector;
×
572
    }
573

574
    return [
19✔
575
      // @ts-expect-error
576
      new ScatterplotLayerClass({
577
        ...defaultLayerProps,
578
        ...brushingProps,
579
        ...layerProps,
580
        ...data,
581
        data: deckLayerData,
582
        getPosition,
583
        parameters: {
584
          // circles will be flat on the map when the altitude column is not used
585
          depthTest: (this.config.columns.altitude?.fieldIdx as number) > -1
586
        },
587
        lineWidthUnits: 'pixels',
588
        updateTriggers,
589
        extensions,
590
        opacity: hoveredObject && showNeighborOnHover ? 0.2 : this.config.visConfig.opacity,
38!
591
        pickable: allowHover,
592
        autoHighlight: false
593
      }),
594
      // hover layer
595
      ...(hoveredObject
19!
596
        ? [
597
            new ScatterplotLayer({
598
              ...this.getDefaultHoverLayerProps(),
599
              ...layerProps,
600
              visible: defaultLayerProps.visible,
601
              data: [...neighborsData, hoveredObject],
602
              getLineColor: this.config.visConfig.showHighlightColor
×
603
                ? this.config.highlightColor
604
                : data.getLineColor,
605
              getFillColor: this.config.visConfig.showHighlightColor
×
606
                ? this.config.highlightColor
607
                : data.getFillColor,
608
              getRadius: data.getRadius,
609
              getPosition: data.getPosition
610
            })
611
          ]
612
        : []),
613
      // text label layer
614
      ...this.renderTextLabelLayer(
615
        {
616
          getPosition,
617
          sharedProps,
618
          getPixelOffset,
619
          updateTriggers,
620
          getFiltered: data.getFiltered
621
        },
622
        this.geoArrowVector
19!
623
          ? {
624
              ...opts,
625
              data: {...opts.data, getPosition}
626
            }
627
          : opts
628
      )
629
    ];
630
  }
631

632
  hasHoveredObject(objectInfo: {index: number}) {
633
    if (
19!
634
      isLayerHoveredFromArrow(objectInfo, this.id) &&
19!
635
      objectInfo.index >= 0 &&
636
      this.dataContainer
637
    ) {
638
      return {
×
639
        index: objectInfo.index,
640
        position: this.getPositionAccessor(this.dataContainer)(objectInfo)
641
      };
642
    }
643

644
    return super.hasHoveredObject(objectInfo);
19✔
645
  }
646

647
  getHoverData(
648
    object: {index: number} | arrow.StructRow | undefined,
649
    dataContainer: DataContainerInterface,
650
    fields: Field[],
651
    animationConfig: AnimationConfig,
652
    hoverInfo: {index: number}
653
  ) {
654
    // for arrow format, `object` is the Arrow row object Proxy,
655
    // and index is passed in `hoverInfo`.
656
    const index = this.geoArrowVector ? hoverInfo?.index : (object as {index: number}).index;
1!
657
    if (index >= 0) {
1!
658
      return dataContainer.row(index);
1✔
659
    }
UNCOV
660
    return null;
×
661
  }
662
}
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

© 2026 Coveralls, Inc