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

keplergl / kepler.gl / 25884645943

14 May 2026 08:43PM UTC coverage: 57.684% (-1.0%) from 58.684%
25884645943

push

github

web-flow
feat: basic annotations (#3434)

* feat: basic annotations

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

* fixes and improvements

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* fix annotations lag

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* tests, lint, fixes

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* formatting/prettier

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* update icon from target to letters

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* fix tests

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* fixes

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

* fix dragging

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* fixes

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* fixes

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* fixes

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* follow up

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* fixes; follow ups

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

---------

Signed-off-by: Ihor Dykhta <dikhta.igor@gmail.com>
Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>
Co-authored-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

7158 of 14867 branches covered (48.15%)

Branch coverage included in aggregate %.

217 of 737 new or added lines in 25 files covered. (29.44%)

70 existing lines in 2 files now uncovered.

14556 of 22776 relevant lines covered (63.91%)

77.67 hits per line

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

8.7
/src/components/src/annotations/annotation-node.tsx
1
// SPDX-License-Identifier: MIT
2
// Copyright contributors to the kepler.gl project
3

4
import React, {FC} from 'react';
5
import {useDraggable} from '@dnd-kit/core';
6
import {Annotation, AnnotationWithArm} from '@kepler.gl/types';
7
import {AnnotationKind} from '@kepler.gl/constants';
8
import {makeMarker, MapViewport, CircleAnnotationMarker} from './annotation-utils';
9

10
const HANDLE_RADIUS = 6;
7✔
11

12
const MoveHandle: FC<{id: string; x: number; y: number}> = ({id, x, y}) => {
7✔
NEW
13
  const {attributes, listeners, setNodeRef} = useDraggable({id});
×
NEW
14
  return (
×
15
    <circle
16
      ref={setNodeRef as any}
17
      {...listeners}
18
      {...attributes}
19
      cx={x}
20
      cy={y}
21
      r={HANDLE_RADIUS}
22
      fill="transparent"
23
      stroke="rgba(255,255,255,0.6)"
24
      strokeWidth={1}
25
      cursor="move"
26
      style={{pointerEvents: 'all'}}
27
    />
28
  );
29
};
30

31
const ResizeHandle: FC<{id: string; x: number; y: number}> = ({id, x, y}) => {
7✔
NEW
32
  const {attributes, listeners, setNodeRef} = useDraggable({id});
×
NEW
33
  return (
×
34
    <circle
35
      ref={setNodeRef as any}
36
      {...listeners}
37
      {...attributes}
38
      cx={x}
39
      cy={y}
40
      r={HANDLE_RADIUS}
41
      fill="transparent"
42
      stroke="rgba(255,255,255,0.6)"
43
      strokeWidth={1}
44
      cursor="ew-resize"
45
      style={{pointerEvents: 'all'}}
46
    />
47
  );
48
};
49

50
export type AnnotationNodeProps = {
51
  annotation: Annotation;
52
  viewport: MapViewport;
53
  isEditing: boolean;
54
  isSelected: boolean;
55
};
56

57
const AnnotationNode: FC<AnnotationNodeProps> = ({annotation, viewport, isEditing, isSelected}) => {
7✔
NEW
58
  const {lineColor, lineWidth} = annotation;
×
NEW
59
  const marker = makeMarker(annotation, viewport);
×
NEW
60
  const {kind, x, y, tx, ty} = marker;
×
NEW
61
  const arrW = 3 + lineWidth * 2;
×
NEW
62
  const isArm = 'armLength' in annotation;
×
63

NEW
64
  const moveHandle = isEditing ? (
×
65
    <MoveHandle id={`${annotation.id}:MOVE_POINT`} x={0} y={0} />
66
  ) : null;
67

68
  const selectionHighlight =
NEW
69
    isEditing && isSelected ? (
×
70
      <circle r={HANDLE_RADIUS + 2} fill="none" stroke="rgba(64,169,255,0.6)" strokeWidth={2} />
71
    ) : null;
72

NEW
73
  let body: React.ReactNode = null;
×
74

NEW
75
  switch (kind) {
×
76
    case AnnotationKind.CIRCLE: {
NEW
77
      const circleMarker = marker as CircleAnnotationMarker;
×
NEW
78
      body = (
×
79
        <>
80
          <circle
81
            r={circleMarker.r}
82
            fill="none"
83
            stroke={lineColor}
84
            strokeWidth={lineWidth}
85
            {...(isSelected && isEditing ? {strokeDasharray: '6 3'} : {})}
×
86
          />
87
          <path
88
            d={`M${circleMarker.ax},${circleMarker.ay} L${tx},${ty}`}
89
            stroke={lineColor}
90
            strokeWidth={lineWidth}
91
            fill="none"
92
            strokeLinecap="round"
93
          />
94
          {isEditing ? (
×
95
            <>
96
              {moveHandle}
97
              {selectionHighlight}
98
              <ResizeHandle id={`${annotation.id}:RESIZE`} x={circleMarker.r} y={0} />
99
            </>
100
          ) : null}
101
        </>
102
      );
NEW
103
      break;
×
104
    }
105
    case AnnotationKind.POINT:
106
    case AnnotationKind.ARROW:
NEW
107
      body = (
×
108
        <>
109
          <path
110
            d={`M0,0 L${tx},${ty}`}
111
            stroke={lineColor}
112
            fill="none"
113
            strokeWidth={lineWidth}
114
            strokeLinecap="round"
115
          />
116
          {kind === AnnotationKind.ARROW ? (
×
117
            <path
118
              transform={
119
                isArm ? `rotate(${90 + (annotation as AnnotationWithArm).angle})` : undefined
×
120
              }
121
              d={`M0,${lineWidth} L${-arrW},${-arrW} L${arrW},${-arrW} Z`}
122
              stroke="none"
123
              fill={lineColor}
124
            />
125
          ) : null}
126
          {kind === AnnotationKind.POINT ? (
×
127
            <circle r={lineWidth * 1.5} fill={lineColor} stroke="none" />
128
          ) : null}
129
          {moveHandle}
130
          {selectionHighlight}
131
        </>
132
      );
NEW
133
      break;
×
134
    default:
NEW
135
      body = (
×
136
        <>
137
          {moveHandle}
138
          {selectionHighlight}
139
        </>
140
      );
141
  }
142

NEW
143
  return <g transform={`translate(${x},${y})`}>{body}</g>;
×
144
};
145

146
export default AnnotationNode;
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