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

keplergl / kepler.gl / 22361650031

24 Feb 2026 05:09PM UTC coverage: 61.612% (-0.2%) from 61.806%
22361650031

Pull #3219

github

web-flow
Merge 1d9b34cb5 into cc33b0c8f
Pull Request #3219: Update kepler-jupyter to use kepler.gl v3.2.0

6382 of 12288 branches covered (51.94%)

Branch coverage included in aggregate %.

13078 of 19297 relevant lines covered (67.77%)

81.44 hits per line

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

74.9
/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
  DatasetType,
29
  LAYER_VIS_CONFIGS,
30
  DEFAULT_LAYER_COLOR,
31
  CHANNEL_SCALES,
32
  DEFAULT_COLOR_UI
33
} from '@kepler.gl/constants';
34
import {getTextOffsetByRadius, formatTextLabelData} from '../layer-text-label';
35
import {
36
  assignPointPairToLayerColumn,
37
  isLayerHoveredFromArrow,
38
  getBoundsFromArrowMetadata,
39
  getGeoArrowPointLayerProps,
40
  isGeoArrowPointField,
41
  createGeoArrowPointVector,
42
  getFilteredIndex,
43
  getNeighbors,
44
  FindDefaultLayerProps
45
} from '../layer-utils';
46
import {getGeojsonPointDataMaps, GeojsonPointDataMaps} from '../geojson-layer/geojson-utils';
47
import {
48
  ColorRange,
49
  Merge,
50
  RGBColor,
51
  VisConfigBoolean,
52
  VisConfigColorRange,
53
  VisConfigColorSelect,
54
  VisConfigNumber,
55
  VisConfigRange,
56
  LayerColumn,
57
  Field,
58
  AnimationConfig
59
} from '@kepler.gl/types';
60

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

74
export type PointLayerColumnsConfig = {
75
  lat: LayerColumn;
76
  lng: LayerColumn;
77
  altitude?: LayerColumn;
78
  neighbors?: LayerColumn;
79
  geojson: LayerColumn;
80
  geoarrow: LayerColumn;
81
};
82

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

108
export type PointLayerData = {
109
  position: number[];
110
  index: number;
111
  neighbors: any[];
112
};
113

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

124
export const geojsonPosAccessor =
125
  ({geojson}: {geojson: LayerColumn}) =>
13✔
126
  d =>
10✔
127
    d[geojson.fieldIdx];
21✔
128

129
export const geoarrowPosAccessor =
130
  ({geoarrow}: PointLayerColumnsConfig) =>
13✔
131
  (dataContainer: DataContainerInterface) =>
×
132
  (d: {index: number}) => {
×
133
    const row = dataContainer.valueAt(d.index, geoarrow.fieldIdx);
×
134
    return [row.get(0), row.get(1), 0];
×
135
  };
136

137
export const COLUMN_MODE_POINTS = 'points';
13✔
138
export const COLUMN_MODE_GEOJSON = 'geojson';
13✔
139
export const COLUMN_MODE_GEOARROW = 'geoarrow';
13✔
140

141
export const pointRequiredColumns: ['lat', 'lng'] = ['lat', 'lng'];
13✔
142
export const pointOptionalColumns: ['altitude', 'neighbors'] = ['altitude', 'neighbors'];
13✔
143
export const geojsonRequiredColumns: ['geojson'] = ['geojson'];
13✔
144
export const geoarrowRequiredColumns: ['geoarrow'] = ['geoarrow'];
13✔
145

146
const SUPPORTED_COLUMN_MODES = [
13✔
147
  {
148
    key: COLUMN_MODE_POINTS,
149
    label: 'Point Columns',
150
    requiredColumns: pointRequiredColumns,
151
    optionalColumns: pointOptionalColumns
152
  },
153
  {
154
    key: COLUMN_MODE_GEOJSON,
155
    label: 'GeoJSON Feature',
156
    requiredColumns: geojsonRequiredColumns,
157
    verifyField: f => !isGeoArrowPointField(f)
×
158
  },
159
  {
160
    key: COLUMN_MODE_GEOARROW,
161
    label: 'Geoarrow Points',
162
    requiredColumns: geoarrowRequiredColumns,
163
    verifyField: f => isGeoArrowPointField(f)
×
164
  }
165
];
166
const DEFAULT_COLUMN_MODE = COLUMN_MODE_POINTS;
13✔
167

168
const brushingExtension = new BrushingExtension();
13✔
169
const arrowCPUFilterExtension = new FilterArrowExtension();
13✔
170

171
function pushPointPosition(data: any[], pos: number[], index: number, neighbors?: number[]) {
172
  if (pos.every(Number.isFinite)) {
1,471✔
173
    data.push({
1,428✔
174
      position: pos,
175
      // index is important for filter
176
      index,
177
      ...(neighbors ? {neighbors} : {})
1,428✔
178
    });
179
  }
180
}
181

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

220
export default class PointLayer extends Layer {
221
  declare config: PointLayerConfig;
222
  declare visConfigSettings: PointLayerVisConfigSettings;
223
  dataToFeature: GeojsonPointDataMaps = [];
188✔
224

225
  dataContainer: DataContainerInterface | null = null;
188✔
226
  geoArrowVector: arrow.Vector | undefined = undefined;
188✔
227

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

237
  constructor(props) {
238
    super(props);
188✔
239

240
    this.registerVisConfig(pointVisConfigs);
188✔
241
    this.getPositionAccessor = (dataContainer: DataContainerInterface) => {
188✔
242
      switch (this.config.columnMode) {
359!
243
        case COLUMN_MODE_GEOARROW:
244
          return geoarrowPosAccessor(this.config.columns)(dataContainer);
×
245
        case COLUMN_MODE_GEOJSON:
246
          return geojsonPosAccessor(this.config.columns);
10✔
247
        default:
248
          // COLUMN_MODE_POINTS
249
          return pointPosAccessor(this.config.columns)(dataContainer);
349✔
250
      }
251
    };
252
  }
253

254
  get type(): 'point' {
255
    return 'point';
961✔
256
  }
257

258
  get isAggregated(): false {
259
    return false;
21✔
260
  }
261

262
  get layerIcon() {
263
    return PointLayerIcon;
28✔
264
  }
265

266
  get optionalColumns() {
267
    return pointOptionalColumns;
244✔
268
  }
269

270
  get columnPairs() {
271
    return this.defaultPointColumnPairs;
18✔
272
  }
273

274
  get supportedColumnModes() {
275
    return SUPPORTED_COLUMN_MODES;
618✔
276
  }
277

278
  get noneLayerDataAffectingProps() {
279
    return [...super.noneLayerDataAffectingProps, 'radius'];
35✔
280
  }
281

282
  get visualChannels() {
283
    return {
1,881✔
284
      color: {
285
        ...super.visualChannels.color,
286
        accessor: 'getFillColor',
287
        condition: config => config.visConfig.filled,
16✔
288
        defaultValue: config => config.color
147✔
289
      },
290
      strokeColor: {
291
        property: 'strokeColor',
292
        key: 'strokeColor',
293
        field: 'strokeColorField',
294
        scale: 'strokeColorScale',
295
        domain: 'strokeColorDomain',
296
        range: 'strokeColorRange',
297
        channelScaleType: CHANNEL_SCALES.color,
298
        accessor: 'getLineColor',
299
        condition: config => config.visConfig.outline,
16✔
300
        defaultValue: config => config.visConfig.strokeColor || config.color
238✔
301
      },
302
      size: {
303
        ...super.visualChannels.size,
304
        property: 'radius',
305
        range: 'radiusRange',
306
        fixed: 'fixedRadius',
307
        channelScaleType: 'radius',
308
        accessor: 'getRadius',
309
        defaultValue: 1
310
      }
311
    };
312
  }
313

314
  setInitialLayerConfig(dataset) {
315
    if (!dataset.dataContainer.numRows()) {
59!
316
      return this;
×
317
    }
318
    const defaultColorField = findDefaultColorField(dataset);
59✔
319

320
    if (defaultColorField) {
59✔
321
      this.updateLayerConfig({
13✔
322
        colorField: defaultColorField
323
      });
324
      this.updateLayerVisualChannel(dataset, 'color');
13✔
325
    }
326

327
    return this;
59✔
328
  }
329

330
  static findDefaultLayerProps(dataset: KeplerTable) {
331
    const {fieldPairs = [], type, label} = dataset;
91!
332

333
    const props: FindDefaultLayerProps[] = [];
91✔
334

335
    if (type === DatasetType.VECTOR_TILE) {
91!
336
      return {props};
×
337
    }
338

339
    // Make layer for each pair
340
    fieldPairs.forEach(pair => {
91✔
341
      const latField = pair.pair.lat;
75✔
342

343
      const prop: {
344
        label: string;
345
        color?: RGBColor;
346
        isVisible?: boolean;
347
        columns?: PointLayerColumnsConfig;
348
      } = {
75✔
349
        label:
350
          // Skip the generic 'point' fallback from findPointFieldPairs and use the dataset label instead
351
          pair.defaultName && pair.defaultName !== 'point'
225✔
352
            ? pair.defaultName
353
            : (typeof label === 'string' && label.replace(/\.[^/.]+$/, '')) || 'Point'
6✔
354
      };
355

356
      // default layer color for begintrip and dropoff point
357
      if (latField.value in DEFAULT_LAYER_COLOR) {
75✔
358
        prop.color = hexToRgb(DEFAULT_LAYER_COLOR[latField.value]);
17✔
359
      }
360

361
      // set the first layer to be visible
362
      if (props.length === 0) {
75✔
363
        prop.isVisible = true;
47✔
364
      }
365
      // @ts-expect-error logically separate geojson column type?
366
      prop.columns = assignPointPairToLayerColumn(pair, true);
75✔
367

368
      props.push(prop);
75✔
369
    });
370

371
    const altProps = getGeoArrowPointLayerProps(dataset);
91✔
372

373
    return {props, altProps};
91✔
374
  }
375

376
  getDefaultLayerConfig(props: LayerBaseConfigPartial) {
377
    const defaultLayerConfig = super.getDefaultLayerConfig(props ?? {});
320!
378
    return {
320✔
379
      ...defaultLayerConfig,
380

381
      columnMode: props?.columnMode ?? DEFAULT_COLUMN_MODE,
607✔
382

383
      // add stroke color visual channel
384
      strokeColorField: null,
385
      strokeColorDomain: [0, 1],
386
      strokeColorScale: 'quantile',
387
      colorUI: {
388
        ...defaultLayerConfig.colorUI,
389
        strokeColorRange: DEFAULT_COLOR_UI
390
      }
391
    };
392
  }
393

394
  calculateDataAttribute({filteredIndex, dataContainer}: KeplerTable, getPosition) {
395
    const {columnMode} = this.config;
131✔
396

397
    // 1) COLUMN_MODE_GEOARROW - when we have a geoarrow point column
398
    // 2) COLUMN_MODE_POINTS + ArrowDataContainer > create geoarrow point column on the fly
399
    if (
131!
400
      dataContainer instanceof ArrowDataContainer &&
131!
401
      (columnMode === COLUMN_MODE_GEOARROW || columnMode === COLUMN_MODE_POINTS)
402
    ) {
403
      this.filteredIndex = getFilteredIndex(
×
404
        dataContainer.numRows(),
405
        filteredIndex,
406
        this.filteredIndex
407
      );
408
      this.filteredIndexTrigger = filteredIndex;
×
409

410
      if (this.config.columnMode === COLUMN_MODE_GEOARROW) {
×
411
        this.geoArrowVector = dataContainer.getColumn(this.config.columns.geoarrow.fieldIdx);
×
412
      } else {
413
        // generate a column compatible with geoarrow point
414
        this.geoArrowVector = createGeoArrowPointVector(dataContainer, getPosition);
×
415
      }
416

417
      return dataContainer.getTable();
×
418
    }
419

420
    // we don't need these in non-Arrow modes atm.
421
    this.geoArrowVector = undefined;
131✔
422
    this.filteredIndex = null;
131✔
423

424
    const data: PointLayerData[] = [];
131✔
425

426
    for (let i = 0; i < filteredIndex.length; i++) {
131✔
427
      const index = filteredIndex[i];
1,475✔
428
      let neighbors;
429

430
      if (this.config.columnMode === COLUMN_MODE_POINTS) {
1,475✔
431
        if (this.config.columns.neighbors?.value) {
1,454✔
432
          const {fieldIdx} = this.config.columns.neighbors;
60✔
433
          neighbors = Array.isArray(dataContainer.valueAt(index, fieldIdx))
60!
434
            ? dataContainer.valueAt(index, fieldIdx)
435
            : [];
436
        }
437
        const pos = getPosition({index});
1,454✔
438

439
        // if doesn't have point lat or lng, do not add the point
440
        // deck.gl can't handle position = null
441
        pushPointPosition(data, pos, index, neighbors);
1,454✔
442
      } else {
443
        // COLUMN_MODE_GEOJSON mode - point from geojson coordinates
444
        const coordinates = this.dataToFeature[i];
21✔
445
        // if multi points
446
        if (coordinates && Array.isArray(coordinates[0])) {
21✔
447
          coordinates.forEach(coord => {
2✔
448
            pushPointPosition(data, coord, index);
5✔
449
          });
450
        } else if (coordinates && Number.isFinite(coordinates[0])) {
19✔
451
          pushPointPosition(data, coordinates as number[], index);
12✔
452
        }
453
      }
454
    }
455

456
    return data;
131✔
457
  }
458

459
  formatLayerData(datasets, oldLayerData) {
460
    if (this.config.dataId === null) {
222!
461
      return {};
×
462
    }
463
    const {textLabel} = this.config;
222✔
464
    const {gpuFilter, dataContainer} = datasets[this.config.dataId];
222✔
465
    const {data, triggerChanged} = this.updateData(datasets, oldLayerData);
222✔
466
    const getPosition = d => d.position;
222✔
467

468
    // get all distinct characters in the text labels
469
    const textLabels = formatTextLabelData({
222✔
470
      textLabel,
471
      triggerChanged,
472
      oldLayerData,
473
      data,
474
      dataContainer,
475
      filteredIndex: this.filteredIndex
476
    });
477

478
    const accessors = this.getAttributeAccessors({dataContainer});
222✔
479

480
    const isFilteredAccessor = (data: {index: number}) => {
222✔
481
      return this.filteredIndex ? this.filteredIndex[data.index] : 1;
×
482
    };
483

484
    return {
222✔
485
      data,
486
      getPosition,
487
      getFilterValue: gpuFilter.filterValueAccessor(dataContainer)(),
488
      getFiltered: isFilteredAccessor,
489
      textLabels,
490
      ...accessors
491
    };
492
  }
493
  /* eslint-enable complexity */
494

495
  updateLayerMeta(dataset: KeplerTable) {
496
    const {dataContainer} = dataset;
130✔
497
    this.dataContainer = dataContainer;
130✔
498

499
    if (this.config.columnMode === COLUMN_MODE_GEOJSON) {
130✔
500
      const getFeature = this.getPositionAccessor();
5✔
501
      this.dataToFeature = getGeojsonPointDataMaps(dataContainer, getFeature);
5✔
502
    } else if (this.config.columnMode === COLUMN_MODE_GEOARROW) {
125!
503
      const boundsFromMetadata = getBoundsFromArrowMetadata(
×
504
        this.config.columns.geoarrow,
505
        dataContainer as ArrowDataContainer
506
      );
507
      if (boundsFromMetadata) {
×
508
        this.updateMeta({bounds: boundsFromMetadata});
×
509
      } else {
510
        const getPosition = this.getPositionAccessor(dataContainer);
×
511
        const bounds = this.getPointsBounds(dataContainer, getPosition);
×
512
        this.updateMeta({bounds});
×
513
      }
514
    } else {
515
      const getPosition = this.getPositionAccessor(dataContainer);
125✔
516
      const bounds = this.getPointsBounds(dataContainer, getPosition);
125✔
517
      this.updateMeta({bounds});
125✔
518
    }
519
  }
520

521
  // eslint-disable-next-line complexity
522
  renderLayer(opts) {
523
    const {data, gpuFilter, objectHovered, mapState, interactionConfig, dataset} = opts;
18✔
524

525
    // if no field size is defined we need to pass fixed radius = false
526
    const fixedRadius = this.config.visConfig.fixedRadius && Boolean(this.config.sizeField);
18!
527
    const radiusScale = this.getRadiusScaleByZoom(mapState, fixedRadius);
18✔
528

529
    const layerProps = {
18✔
530
      stroked: this.config.visConfig.outline,
531
      filled: this.config.visConfig.filled,
532
      lineWidthScale: this.config.visConfig.thickness,
533
      billboard: this.config.visConfig.billboard,
534
      radiusScale,
535
      ...(this.config.visConfig.fixedRadius ? {} : {radiusMaxPixels: 500})
18!
536
    };
537

538
    const updateTriggers = {
18✔
539
      getPosition: this.config.columns,
540
      getFilterValue: gpuFilter.filterValueUpdateTriggers,
541
      getFiltered: this.filteredIndexTrigger,
542
      ...this.getVisualChannelUpdateTriggers()
543
    };
544

545
    const useArrowLayer = Boolean(this.geoArrowVector);
18✔
546

547
    const defaultLayerProps = this.getDefaultDeckLayerProps(opts);
18✔
548
    const brushingProps = this.getBrushingExtensionProps(interactionConfig);
18✔
549
    const getPixelOffset = getTextOffsetByRadius(radiusScale, data.getRadius, mapState);
18✔
550
    const extensions = [
18✔
551
      ...defaultLayerProps.extensions,
552
      brushingExtension,
553
      ...(useArrowLayer ? [arrowCPUFilterExtension] : [])
18!
554
    ];
555

556
    const sharedProps = {
18✔
557
      getFilterValue: data.getFilterValue,
558
      extensions,
559
      filterRange: defaultLayerProps.filterRange,
560
      visible: defaultLayerProps.visible,
561
      ...brushingProps
562
    };
563
    const hoveredObject = this.hasHoveredObject(objectHovered);
18✔
564
    const {showNeighborOnHover, allowHover} = this.config.visConfig;
18✔
565
    let neighborsData: ReturnType<typeof getNeighbors> = [];
18✔
566
    if (allowHover && showNeighborOnHover && hoveredObject) {
18!
567
      // find neighbors
568
      neighborsData = getNeighbors(
×
569
        this.config.columns.neighbors,
570
        dataset.dataContainer,
571
        hoveredObject.index,
572
        this.getPositionAccessor(dataset.dataContainer)
573
      );
574
    }
575

576
    let ScatterplotLayerClass: typeof ScatterplotLayer | typeof GeoArrowScatterplotLayer =
577
      ScatterplotLayer;
18✔
578
    let deckLayerData = data.data;
18✔
579
    let getPosition = data.getPosition;
18✔
580
    if (useArrowLayer) {
18!
581
      ScatterplotLayerClass = GeoArrowScatterplotLayer;
×
582
      deckLayerData = dataset.dataContainer.getTable();
×
583
      getPosition = this.geoArrowVector;
×
584
    }
585

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

644
  hasHoveredObject(objectInfo: {index: number}) {
645
    if (
18!
646
      isLayerHoveredFromArrow(objectInfo, this.id) &&
18!
647
      objectInfo.index >= 0 &&
648
      this.dataContainer
649
    ) {
650
      return {
×
651
        index: objectInfo.index,
652
        position: this.getPositionAccessor(this.dataContainer)(objectInfo)
653
      };
654
    }
655

656
    return super.hasHoveredObject(objectInfo);
18✔
657
  }
658

659
  getHoverData(
660
    object: {index: number} | arrow.StructRow | undefined,
661
    dataContainer: DataContainerInterface,
662
    fields: Field[],
663
    animationConfig: AnimationConfig,
664
    hoverInfo: {index: number}
665
  ) {
666
    // for arrow format, `object` is the Arrow row object Proxy,
667
    // and index is passed in `hoverInfo`.
668
    const index = this.geoArrowVector ? hoverInfo?.index : (object as {index: number}).index;
1!
669
    if (index >= 0) {
1!
670
      return dataContainer.row(index);
1✔
671
    }
672
    return null;
×
673
  }
674
}
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