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

keplergl / kepler.gl / 25830234009

13 May 2026 10:31PM UTC coverage: 57.668% (-1.0%) from 58.644%
25830234009

Pull #3434

github

web-flow
Merge b10a56eba into b4790f0f5
Pull Request #3434: feat: basic annotations

7143 of 14848 branches covered (48.11%)

Branch coverage included in aggregate %.

216 of 732 new or added lines in 25 files covered. (29.51%)

74 existing lines in 3 files now uncovered.

14551 of 22771 relevant lines covered (63.9%)

77.31 hits per line

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

10.81
/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}) => {
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

NEW
68
  let body: React.ReactNode = null;
×
69

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

NEW
125
  return <g transform={`translate(${x},${y})`}>{body}</g>;
×
126
};
127

128
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