lost-sia 2.0.1-alpha3 → 2.0.1-alpha6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/package.json +36 -24
  2. package/src/AnnoExampleViewer.jsx +2 -3
  3. package/src/Annotation/logic/Annotation.ts +39 -0
  4. package/src/Annotation/logic/AnnotationUtils.ts +30 -0
  5. package/src/Annotation/ui/AnnotationComponent.tsx +265 -0
  6. package/src/Annotation/ui/atoms/AnnoBar.tsx +132 -0
  7. package/src/Annotation/ui/atoms/DaviIcon.tsx +57 -0
  8. package/src/Annotation/ui/atoms/Edge.tsx +57 -0
  9. package/src/Annotation/ui/atoms/Node.tsx +126 -0
  10. package/src/Annotation/ui/atoms/PolygonArea.tsx +75 -0
  11. package/src/Annotation/ui/tools/BBox.tsx +330 -0
  12. package/src/Annotation/ui/tools/Line.tsx +220 -0
  13. package/src/Annotation/ui/tools/Point.tsx +50 -0
  14. package/src/Annotation/ui/tools/Polygon.tsx +233 -0
  15. package/src/Canvas/Canvas.tsx +869 -0
  16. package/src/Canvas/LabelInput.tsx +74 -0
  17. package/src/InfoBoxes/AnnoDetails.jsx +1 -2
  18. package/src/InfoBoxes/AnnoStats.jsx +0 -1
  19. package/src/InfoBoxes/InfoBox.jsx +2 -2
  20. package/src/InfoBoxes/InfoBoxArea.jsx +1 -4
  21. package/src/InfoBoxes/LabelInfo.jsx +4 -12
  22. package/src/SIAFilterButton.jsx +12 -13
  23. package/src/SIASettingButton.jsx +9 -10
  24. package/src/Sia.jsx +2 -0
  25. package/src/Sia2.tsx +392 -0
  26. package/src/Toolbar/NavigationButtons.tsx +21 -0
  27. package/src/Toolbar/Toolbar.tsx +76 -0
  28. package/src/Toolbar/ToolbarItem.jsx +30 -0
  29. package/src/Toolbar/ToolbarItems/AccessibilityTools.tsx +69 -0
  30. package/src/Toolbar/ToolbarItems/AnnoToolSelector.tsx +88 -0
  31. package/src/Toolbar/ToolbarItems/ImageToolItems/ImageLabel.tsx +62 -0
  32. package/src/Toolbar/ToolbarItems/ImageToolItems/TagLabel.tsx +56 -0
  33. package/src/Toolbar/ToolbarItems/ImageTools.tsx +63 -0
  34. package/src/Toolbar/ToolbarItems/Instructions.tsx +124 -0
  35. package/src/Toolbar/ToolbarItems/InstructionsModal.tsx +20 -0
  36. package/src/filterTools.js +1 -1
  37. package/src/index.ts +14 -0
  38. package/src/models/AllowedTools.tsx +9 -0
  39. package/src/models/AnnotationMode.tsx +12 -0
  40. package/src/models/AnnotationSettings.tsx +9 -0
  41. package/src/models/AnnotationStatus.tsx +9 -0
  42. package/src/models/AnnotationTool.tsx +8 -0
  43. package/src/models/CanvasAction.tsx +29 -0
  44. package/src/models/Direction.tsx +8 -0
  45. package/src/models/EditorModes.tsx +12 -0
  46. package/src/models/ExternalAnnotation.ts +15 -0
  47. package/src/models/KeyAction.tsx +21 -0
  48. package/src/models/Label.tsx +8 -0
  49. package/src/models/UiConfig.tsx +6 -0
  50. package/src/models/index.js +8 -0
  51. package/src/stories/Canvas/Canvas.stories.tsx +156 -0
  52. package/src/stories/Canvas/CanvasOffset.tsx +58 -0
  53. package/src/stories/Canvas/CanvasWithOffset.stories.tsx +156 -0
  54. package/src/stories/FilterDropdown.stories.ts +23 -0
  55. package/src/stories/{Sia.stories.jsx → SIA/SIA.stories.tsx} +25 -14
  56. package/src/stories/SIA2/DemoWrapper.stories.tsx +167 -0
  57. package/src/stories/SIA2/DemoWrapper.tsx +54 -0
  58. package/src/stories/SIA2/Sia2.stories.tsx +62 -0
  59. package/src/stories/Toolbar/ImageTools/ImageLabel.stories.tsx +32 -0
  60. package/src/stories/Toolbar/ImageTools/TagLabel.stories.tsx +37 -0
  61. package/src/stories/Toolbar/Instructions.stories.tsx +22 -0
  62. package/src/stories/Toolbar/Toolbar.stories.tsx +101 -0
  63. package/src/stories/main.scss +6 -0
  64. package/src/stories/siaDummyData2.ts +265 -0
  65. package/src/styles/style.scss +30 -0
  66. package/src/types/notificationType.js +5 -5
  67. package/src/types.tsx +11 -0
  68. package/src/utils/KeyMapper.ts +93 -0
  69. package/src/utils/annoConversion2.ts +145 -0
  70. package/src/utils/color.ts +61 -0
  71. package/src/utils/mouse2.ts +35 -0
  72. package/src/utils/transform2.ts +343 -0
  73. package/src/utils/windowViewport2.ts +50 -0
  74. package/src/index.js +0 -13
  75. package/src/stories/Configure.mdx +0 -369
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lost-sia",
3
- "version": "2.0.1-alpha3",
3
+ "version": "2.0.1-alpha6",
4
4
  "description": "Single Image Annotation Tool",
5
5
  "license": "MIT",
6
6
  "repository": "l3p-cv/lost-sia",
@@ -8,6 +8,7 @@
8
8
  "module": "dist/index.es.js",
9
9
  "jsnext:main": "dist/index.es.js",
10
10
  "type": "module",
11
+ "types": "dist/index.d.ts",
11
12
  "keywords": [
12
13
  "lost",
13
14
  "annotation",
@@ -20,32 +21,48 @@
20
21
  },
21
22
  "exports": {
22
23
  ".": {
23
- "import": "./src/index.js",
24
- "require": "./src/index.js"
24
+ "development": {
25
+ "types": "./src/types.ts",
26
+ "import": "./src/index.ts"
27
+ },
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js",
30
+ "require": "./dist/index.js"
25
31
  },
26
32
  "./utils": {
27
- "import": "./src/utils/index.js",
28
- "require": "./src/utils/index.js"
33
+ "development": "./src/utils/index.js",
34
+ "types": "./dist/utils/index.d.ts",
35
+ "import": "./dist/utils/index.js",
36
+ "require": "./dist/utils/index.js"
37
+ },
38
+ "./models": {
39
+ "development": "./src/models/index.js",
40
+ "types": "./dist/models/index.d.ts",
41
+ "import": "./dist/models/index.js",
42
+ "require": "./dist/models/index.js"
29
43
  },
30
44
  "./styles/variables": "./src/styles/_variables.scss",
31
45
  "./styles/coreui": "./src/styles/coreui.scss"
32
46
  },
33
47
  "files": [
48
+ "dist",
34
49
  "src",
35
50
  "styles"
36
51
  ],
52
+ "style": "dist/main.css",
37
53
  "scripts": {
38
54
  "build": "vite build",
39
55
  "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
40
56
  "preview": "vite preview",
41
57
  "publishpkg": "npm publish",
42
58
  "test": "vitest run",
59
+ "start": "storybook dev -p 6006",
43
60
  "storybook": "storybook dev -p 6006",
44
61
  "build-storybook": "storybook build",
45
62
  "format": "prettier --write src/"
46
63
  },
47
64
  "dependencies": {
48
- "@coreui/react": "^5.0.0",
65
+ "@coreui/react": "^5.7.0",
49
66
  "@fortawesome/free-regular-svg-icons": "^6.7.2",
50
67
  "@fortawesome/free-solid-svg-icons": "^6.7.2",
51
68
  "@fortawesome/react-fontawesome": "^0.2.0",
@@ -55,7 +72,8 @@
55
72
  "react-draggable": "^4.4.6",
56
73
  "sass": "^1.89.2",
57
74
  "semantic-ui-css": "2.5.0",
58
- "semantic-ui-react": "^2.0.3"
75
+ "semantic-ui-react": "^2.0.3",
76
+ "vite-plugin-dts": "^4.5.4"
59
77
  },
60
78
  "peerDependencies": {
61
79
  "lodash": "^4.17.15",
@@ -64,16 +82,11 @@
64
82
  "react-dom": "^18.0.0"
65
83
  },
66
84
  "devDependencies": {
67
- "@chromatic-com/storybook": "^1.5.0",
68
- "@storybook/addon-docs": "^8.6.14",
69
- "@storybook/addon-essentials": "^8.1.5",
70
- "@storybook/addon-interactions": "^8.1.5",
71
- "@storybook/addon-links": "^8.1.5",
72
- "@storybook/addon-onboarding": "^8.1.5",
73
- "@storybook/blocks": "^8.1.5",
74
- "@storybook/react": "^8.1.5",
75
- "@storybook/react-vite": "^8.1.5",
76
- "@storybook/test": "^8.1.5",
85
+ "@microsoft/api-extractor": "^7.52.13",
86
+ "@storybook/addon-links": "^9.0.12",
87
+ "@storybook/react": "^9.0.12",
88
+ "@storybook/react-vite": "^9.0.12",
89
+ "@storybook/test": "9.0.0-alpha.2",
77
90
  "@vitejs/plugin-react": "^4.3.0",
78
91
  "cross-env": "^7.0.3",
79
92
  "eslint": "^8.0.1",
@@ -81,18 +94,17 @@
81
94
  "eslint-config-standard-react": "^13.0.0",
82
95
  "eslint-plugin-import": "^2.29.1",
83
96
  "eslint-plugin-node": "^11.1.0",
84
- "eslint-plugin-promise": "^6.2.0",
97
+ "eslint-plugin-promise": "^6.6.0",
85
98
  "eslint-plugin-react": "^7.34.2",
86
99
  "eslint-plugin-standard": "^5.0.0",
87
- "eslint-plugin-storybook": "^0.8.0",
88
- "glob": "^10.4.1",
100
+ "glob": "^10.4.5",
89
101
  "prettier": "^3.3.3",
90
- "prop-types": "^15.8.1",
91
102
  "react-redux": "^9.1.2",
92
103
  "redux": "^5.0.1",
93
- "storybook": "^8.1.5",
94
- "vite": "^5.2.12",
95
- "vitest": "^1.6.0"
104
+ "storybook": "^9.0.12",
105
+ "unplugin-dts": "^1.0.0-beta.0",
106
+ "vite": "^5.4.19",
107
+ "vitest": "^1.6.1"
96
108
  },
97
109
  "eslintConfig": {
98
110
  "extends": [
@@ -1,6 +1,6 @@
1
1
  import Prompt from "./Prompt";
2
2
  import React from "react";
3
- import { Card, Divider, Header, Image } from "semantic-ui-react";
3
+ import { Card, Image } from "semantic-ui-react";
4
4
 
5
5
  const LabelExampleViewer = (props) => {
6
6
  const requestExample = (e) => {
@@ -14,9 +14,8 @@ const LabelExampleViewer = (props) => {
14
14
  if (!props.exampleImg) return null;
15
15
  const description = (
16
16
  <div>
17
- {/* <Divider horizontal> Description </Divider> */}
18
17
  {props.lbl.description}
19
- <Divider horizontal> comment </Divider>
18
+ <h4>Comment:</h4>
20
19
  {props.exampleImg.anno ? props.exampleImg.anno.comment : "no comment"}
21
20
  </div>
22
21
  );
@@ -0,0 +1,39 @@
1
+ import AnnotationMode from "../../models/AnnotationMode";
2
+ import AnnotationStatus from "../../models/AnnotationStatus";
3
+ import AnnotationTool from "../../models/AnnotationTool";
4
+ import { Point } from "../../types";
5
+
6
+ class Annotation {
7
+ internalId: number;
8
+ externalId?: string;
9
+ annoTime?: number;
10
+ coordinates: Point[];
11
+ labelIds?: number[];
12
+ mode: AnnotationMode; // do we even need this globally? - only really used inside AnnotationComponent
13
+ selectedNode: number;
14
+ status?: AnnotationStatus;
15
+ type: AnnotationTool;
16
+ timestamp?: DOMHighResTimeStamp;
17
+
18
+ constructor(
19
+ internalId: number,
20
+ type: AnnotationTool,
21
+ coordinates: Point[],
22
+ mode: AnnotationMode = AnnotationMode.CREATE,
23
+ status: AnnotationStatus = AnnotationStatus.CREATING,
24
+ externalId: string = "",
25
+ ) {
26
+ this.internalId = internalId;
27
+ this.externalId = externalId;
28
+ this.labelIds = [];
29
+ this.type = type;
30
+ this.mode = mode;
31
+ this.status = status;
32
+ this.coordinates = coordinates;
33
+ this.selectedNode = 1;
34
+ this.timestamp = performance.now();
35
+ this.annoTime = 0.0;
36
+ }
37
+ }
38
+
39
+ export default Annotation;
@@ -0,0 +1,30 @@
1
+ import { Point } from "../../types";
2
+ import Annotation from "./Annotation";
3
+
4
+ const addNode = (annotation: Annotation, point: Point) => {
5
+ const _annotation = { ...annotation };
6
+ _annotation.coordinates.push(point);
7
+ return _annotation;
8
+ };
9
+
10
+ const startAnnotimeMeasure = (annotation: Annotation) => {
11
+ const _annotation = { ...annotation };
12
+ _annotation.timestamp = performance.now();
13
+ return _annotation;
14
+ };
15
+
16
+ const stopAnnotimeMeasure = (annotation: Annotation): [Annotation, number] => {
17
+ const _annotation = { ...annotation };
18
+ const now = performance.now();
19
+ const duration = (now - _annotation.timestamp) / 1000;
20
+ _annotation.annoTime += duration;
21
+ _annotation.timestamp = now;
22
+
23
+ return [_annotation, duration];
24
+ };
25
+
26
+ export default {
27
+ addNode,
28
+ startAnnotimeMeasure,
29
+ stopAnnotimeMeasure,
30
+ };
@@ -0,0 +1,265 @@
1
+ import AnnotationTool from "../../models/AnnotationTool";
2
+ import Label from "../../models/Label";
3
+ import Annotation from "../logic/Annotation";
4
+ import * as colorUtils from "../../utils/color";
5
+ import PointTool from "./tools/Point";
6
+ import Line from "./tools/Line";
7
+ import AnnoBar from "./atoms/AnnoBar";
8
+ import CanvasAction from "../../models/CanvasAction";
9
+ import BBox from "./tools/BBox";
10
+ import Polygon from "./tools/Polygon";
11
+ import { useEffect, useRef, useState } from "react";
12
+ import { Point } from "../../types";
13
+ import AnnotationMode from "../../models/AnnotationMode";
14
+ import AnnotationSettings from "../../models/AnnotationSettings";
15
+
16
+ type AnnotationComponentProps = {
17
+ scaledAnnotation: Annotation;
18
+ annotationSettings: AnnotationSettings;
19
+ possibleLabels: Label[];
20
+ svgScale: number;
21
+ pageToStageOffset: Point;
22
+ strokeWidth: number;
23
+ nodeRadius: number;
24
+ isSelected: boolean;
25
+ isDisabled?: boolean;
26
+ onFinishAnnoCreate: (fullyCreatedAnnotation: Annotation) => void;
27
+ onLabelIconClicked: (markerPosition: Point) => void;
28
+ onAction?: (annotation: Annotation, canvasAction: CanvasAction) => void;
29
+ onAnnoChanged?: (annotation: Annotation) => void;
30
+ onAnnotationModeChange?: (annotationMode: AnnotationMode) => void;
31
+ };
32
+
33
+ const AnnotationComponent = ({
34
+ scaledAnnotation,
35
+ annotationSettings,
36
+ possibleLabels,
37
+ svgScale,
38
+ pageToStageOffset,
39
+ strokeWidth,
40
+ nodeRadius,
41
+ isSelected,
42
+ isDisabled = false,
43
+ onFinishAnnoCreate,
44
+ onLabelIconClicked,
45
+ onAction = (_, __) => {},
46
+ onAnnoChanged = (_) => {},
47
+ onAnnotationModeChange = (_) => {},
48
+ }: AnnotationComponentProps) => {
49
+ const [coordinates, setCoordinates] = useState<Point[]>(
50
+ scaledAnnotation.coordinates,
51
+ );
52
+
53
+ const [annotationMode, setAnnotationMode] = useState<AnnotationMode>(
54
+ scaledAnnotation.mode,
55
+ );
56
+ const [isDragging, setIsDragging] = useState<boolean>(false);
57
+
58
+ /**
59
+ * during user editing of the annotation, multiple events are fired by the children
60
+ * onMoving for updating the data
61
+ * onMoved for telling its parents (us) that the user has finished moving
62
+ * onMoved has no access to up-to-date coordinates, because the state is always one render step behind the setState
63
+ * since both events are fired during the same render, onMoved would give away old coorinates
64
+ * use this reference as a workaround to get the up-to-date coordinates even in this edge-case
65
+ */
66
+ const coordinatesRef = useRef<Point[]>(coordinates);
67
+
68
+ useEffect(() => {
69
+ coordinatesRef.current = coordinates;
70
+ }, [coordinates]);
71
+
72
+ const finishAnnoCreate = () => {
73
+ setAnnotationMode(AnnotationMode.VIEW);
74
+
75
+ const newAnnotation = {
76
+ ...scaledAnnotation,
77
+ coordinates: coordinatesRef.current,
78
+ };
79
+
80
+ onFinishAnnoCreate(newAnnotation);
81
+ };
82
+
83
+ const getLabel = (labelId: number): Label | undefined => {
84
+ return possibleLabels.find((label: Label) => {
85
+ return label.id === labelId;
86
+ });
87
+ };
88
+
89
+ const getColor = () => {
90
+ if (!scaledAnnotation.labelIds || scaledAnnotation.labelIds.length == 0)
91
+ return colorUtils.getDefaultColor();
92
+
93
+ const label = getLabel(scaledAnnotation.labelIds[0]);
94
+
95
+ if (label === undefined || label.color === undefined)
96
+ return colorUtils.getDefaultColor();
97
+
98
+ return label.color;
99
+ };
100
+
101
+ const color = getColor();
102
+ const annotationStyle = {
103
+ stroke: color,
104
+ fill: color,
105
+ strokeWidth: strokeWidth / svgScale,
106
+ r: nodeRadius / svgScale,
107
+ };
108
+
109
+ const changeAnnoCoords = (newCoordinates: Point[]) => {
110
+ setCoordinates(newCoordinates);
111
+
112
+ let newCoordinatesWithoutMouse = newCoordinates;
113
+
114
+ // while adding/moving the annotation, the mouse cursor is the last point of the coordinates
115
+ // remove it before saving the annotation
116
+ if ([AnnotationMode.ADD, AnnotationMode.MOVE].includes(annotationMode)) {
117
+ // last point is mouse - remove it before export
118
+ newCoordinatesWithoutMouse = newCoordinates.slice(0, -1);
119
+ }
120
+
121
+ onAnnoChanged({
122
+ ...scaledAnnotation,
123
+ coordinates: newCoordinatesWithoutMouse,
124
+ });
125
+ };
126
+
127
+ const onMoving = (newCoords: Point[]) => {
128
+ if (annotationMode !== AnnotationMode.CREATE)
129
+ setAnnotationMode(AnnotationMode.MOVE);
130
+
131
+ setCoordinates(newCoords);
132
+ };
133
+
134
+ const onMoved = () => {
135
+ setAnnotationMode(AnnotationMode.VIEW);
136
+
137
+ // moving finished - send event to canvas
138
+ onAnnoChanged({
139
+ ...scaledAnnotation,
140
+ coordinates: coordinatesRef.current,
141
+ });
142
+ };
143
+
144
+ useEffect(() => {
145
+ onAnnotationModeChange(annotationMode);
146
+ }, [annotationMode]);
147
+
148
+ // apply coordinate changes from sia (e.g. out of image fixes)
149
+ // ignore outside changes while creating annotation
150
+ useEffect(() => {
151
+ if (annotationMode === AnnotationMode.CREATE) return;
152
+ setCoordinates(scaledAnnotation.coordinates);
153
+ }, [scaledAnnotation]);
154
+
155
+ const renderAnno = () => {
156
+ switch (scaledAnnotation.type) {
157
+ case AnnotationTool.Point:
158
+ return (
159
+ <PointTool
160
+ isSelected={isSelected}
161
+ annotationSettings={annotationSettings}
162
+ coordinates={coordinates[0]}
163
+ pageToStageOffset={pageToStageOffset}
164
+ svgScale={svgScale}
165
+ style={annotationStyle}
166
+ onMoving={(newPoint: Point) => {
167
+ setAnnotationMode(AnnotationMode.MOVE);
168
+ setCoordinates([newPoint]);
169
+ }}
170
+ onMoved={onMoved}
171
+ onIsDraggingStateChanged={setIsDragging}
172
+ />
173
+ );
174
+ case AnnotationTool.Line:
175
+ return (
176
+ <Line
177
+ annotationSettings={annotationSettings}
178
+ coordinates={coordinates}
179
+ isSelected={isSelected}
180
+ pageToStageOffset={pageToStageOffset}
181
+ annotationMode={annotationMode}
182
+ setAnnotationMode={setAnnotationMode}
183
+ svgScale={svgScale}
184
+ style={annotationStyle}
185
+ onAddNode={changeAnnoCoords}
186
+ onDeleteNode={changeAnnoCoords}
187
+ onMoving={onMoving}
188
+ onMoved={onMoved}
189
+ onIsDraggingStateChanged={setIsDragging}
190
+ onFinishAnnoCreate={finishAnnoCreate}
191
+ />
192
+ );
193
+ case AnnotationTool.BBox:
194
+ return (
195
+ <BBox
196
+ annotationMode={annotationMode}
197
+ annotationSettings={annotationSettings}
198
+ startCoords={coordinates[0]}
199
+ endCoords={coordinates[1]}
200
+ isSelected={isSelected}
201
+ pageToStageOffset={pageToStageOffset}
202
+ style={annotationStyle}
203
+ svgScale={svgScale}
204
+ onDeleteNode={() => {
205
+ console.log("TODO");
206
+ }}
207
+ onIsDraggingStateChanged={setIsDragging}
208
+ onFinishAnnoCreate={finishAnnoCreate}
209
+ onMoving={onMoving}
210
+ onMoved={onMoved}
211
+ />
212
+ );
213
+ case AnnotationTool.Polygon:
214
+ return (
215
+ <Polygon
216
+ annotationSettings={annotationSettings}
217
+ coordinates={coordinates}
218
+ isSelected={isSelected}
219
+ isDisabled={isDisabled}
220
+ pageToStageOffset={pageToStageOffset}
221
+ annotationMode={annotationMode}
222
+ setAnnotationMode={setAnnotationMode}
223
+ svgScale={svgScale}
224
+ style={annotationStyle}
225
+ onAddNode={changeAnnoCoords}
226
+ onDeleteNode={changeAnnoCoords}
227
+ onMoving={onMoving}
228
+ onMoved={onMoved}
229
+ onIsDraggingStateChanged={setIsDragging}
230
+ onFinishAnnoCreate={finishAnnoCreate}
231
+ />
232
+ );
233
+ }
234
+ };
235
+
236
+ return (
237
+ <g
238
+ // visibility={this.state.visibility}
239
+ // onClick={(e) => this.onClick(e)}
240
+ // onMouseDown={(e) => this.onMouseDown(e)}
241
+ // onContextMenu={(e) => this.onContextMenu(e)}
242
+ onClick={(e) => {
243
+ e.stopPropagation();
244
+ onAction(scaledAnnotation, CanvasAction.ANNO_SELECTED);
245
+ }}
246
+ >
247
+ {!isDragging && annotationMode !== AnnotationMode.CREATE && (
248
+ <AnnoBar
249
+ annotationCoordinates={coordinates}
250
+ canLabel={annotationSettings.canLabel}
251
+ labels={possibleLabels}
252
+ color={color}
253
+ isSelected={isSelected}
254
+ selectedLabelIds={scaledAnnotation.labelIds}
255
+ style={annotationStyle}
256
+ svgScale={svgScale}
257
+ onLabelIconClicked={onLabelIconClicked}
258
+ />
259
+ )}
260
+ {renderAnno()}
261
+ </g>
262
+ );
263
+ };
264
+
265
+ export default AnnotationComponent;
@@ -0,0 +1,132 @@
1
+ import { CSSProperties, useEffect, useRef, useState } from "react";
2
+ import Label from "../../../models/Label";
3
+ import { Point } from "../../../types";
4
+ import transform from "../../../utils/transform2";
5
+ import DaviIcon from "./DaviIcon";
6
+
7
+ const DEFAULT_FONT_SIZE = 10;
8
+ const DEFAULT_RECT_HEIGHT = 15;
9
+ const TEXT_PADDING = 3;
10
+
11
+ type AnnoBarProps = {
12
+ annotationCoordinates: Point[];
13
+ canLabel: boolean;
14
+ color: string;
15
+ labels: Label[];
16
+ selectedLabelIds: number[];
17
+ isSelected: boolean;
18
+ svgScale: number;
19
+ style: CSSProperties;
20
+ onLabelIconClicked: (markerPosition: Point) => void;
21
+ };
22
+ const AnnoBar = ({
23
+ annotationCoordinates,
24
+ canLabel,
25
+ color,
26
+ labels,
27
+ selectedLabelIds = [],
28
+ isSelected,
29
+ svgScale,
30
+ style,
31
+ onLabelIconClicked,
32
+ }: AnnoBarProps) => {
33
+ const [barPosition, setBarPosition] = useState<Point>({ x: 0, y: 0 });
34
+ const [barWidth, setBarWidth] = useState<number>(0);
35
+ const [barHeight, setBarHeight] = useState<number>(0);
36
+ const [fontSize, setFontSize] = useState<number>(0);
37
+ const [labelText, setLabelText] = useState<string>("");
38
+
39
+ const textRef = useRef(null);
40
+
41
+ useEffect(() => {
42
+ setLabelText(getLabelText());
43
+ }, [selectedLabelIds]);
44
+
45
+ useEffect(() => {
46
+ // get the most top left point from the annotation
47
+ const topPoints: Point[] = transform.getTopPoint(annotationCoordinates);
48
+ const topLeftPoint: Point = transform.getMostLeftPoints(topPoints)[0];
49
+
50
+ const newBarPosition: Point = {
51
+ x: topLeftPoint.x + 7 / svgScale,
52
+ y: topLeftPoint.y - 10 / svgScale,
53
+ };
54
+
55
+ setBarPosition(newBarPosition);
56
+
57
+ // calculate scaled font size
58
+ const newFontSize = Math.ceil(DEFAULT_FONT_SIZE / svgScale);
59
+ setFontSize(newFontSize);
60
+
61
+ setBarHeight(DEFAULT_RECT_HEIGHT / svgScale);
62
+ }, [svgScale]);
63
+
64
+ useEffect(() => {
65
+ if (textRef === undefined) return;
66
+
67
+ // calculate size of box around label text
68
+ const textElement: DOMRect = textRef.current.getBoundingClientRect();
69
+ const textWidth: number = textElement.width;
70
+ const rectWidth = (textWidth + TEXT_PADDING) / svgScale;
71
+
72
+ setBarWidth(rectWidth);
73
+ }, [textRef, labelText, fontSize]);
74
+
75
+ const getLabelText = () => {
76
+ const selectedLabels: Label[] = labels.filter((label: Label) =>
77
+ selectedLabelIds.includes(label.id),
78
+ );
79
+
80
+ const labelText = selectedLabels.map((l) => l.name).join(", ");
81
+
82
+ return labelText.length ? labelText : "no label";
83
+ };
84
+
85
+ return (
86
+ <g>
87
+ {isSelected && canLabel && (
88
+ <DaviIcon
89
+ x={barPosition.x - 33 / svgScale}
90
+ y={barPosition.y - 30 / svgScale}
91
+ color={color}
92
+ size={60 / svgScale}
93
+ onClick={() => onLabelIconClicked(barPosition)}
94
+ />
95
+ )}
96
+
97
+ <rect
98
+ x={barPosition.x}
99
+ y={barPosition.y - 6 / (svgScale * 1.2)}
100
+ width={barWidth}
101
+ height={barHeight}
102
+ rx={5 / svgScale}
103
+ opacity="0.5"
104
+ style={style}
105
+ />
106
+ <text
107
+ x={barPosition.x + 1 / svgScale}
108
+ y={barPosition.y + 6 / svgScale}
109
+ fill="white"
110
+ textAnchor="start"
111
+ alignmentBaseline="central"
112
+ ref={textRef}
113
+ fontSize={`${fontSize}pt`}
114
+ >
115
+ {labelText}
116
+ </text>
117
+ {/* This second rect is to prevent text from getting marked */}
118
+ <rect
119
+ x={barPosition.x}
120
+ y={barPosition.y - 6 / (svgScale * 1.2)}
121
+ width={barWidth}
122
+ height={DEFAULT_RECT_HEIGHT}
123
+ rx={5 / svgScale}
124
+ opacity="0.01"
125
+ style={style}
126
+ onContextMenu={(e) => e.preventDefault()}
127
+ />
128
+ </g>
129
+ );
130
+ };
131
+
132
+ export default AnnoBar;
@@ -0,0 +1,57 @@
1
+ type DaviIconProps = {
2
+ x: number;
3
+ y: number;
4
+ color: string;
5
+ size: number;
6
+ onClick?: () => void;
7
+ };
8
+
9
+ const DaviIcon = ({
10
+ x,
11
+ y,
12
+ color,
13
+ size = 60,
14
+ onClick = () => {},
15
+ }: DaviIconProps) => {
16
+ const scale: number = size / 1400;
17
+
18
+ return (
19
+ <g
20
+ transform={`translate(${x} ${y}) scale(${scale})`}
21
+ fill={color}
22
+ onClick={onClick}
23
+ >
24
+ <path
25
+ id="Maps"
26
+ d="M620.561,817.217c-1.568-3.62-3.771-7.101-4.611-10.885
27
+ c-24.452-109.811-74.341-207.569-139.215-298.675c-27.507-38.628-55.814-77.404-77.438-119.371
28
+ C324.363,242.85,402.696,58.71,574.209,26.508c145.509-27.32,282.953,75.871,296.543,222.773
29
+ c4.659,50.356-7.471,97.96-32.152,141.022c-27.812,48.526-58.75,95.364-90.073,141.758
30
+ c-50.917,75.411-91.062,155.558-113.421,244.091c-2.438,9.652-3.936,19.543-6.271,29.227c-0.992,4.104-3.023,7.961-4.584,11.93
31
+ C623.021,817.277,621.789,817.247,620.561,817.217z"
32
+ />
33
+ <path
34
+ id="Text"
35
+ fillRule="evenodd"
36
+ clipRule="evenodd"
37
+ fill="#FFFFFF"
38
+ d="M724.709,250.898
39
+ c-0.055-3.974,0.047-7.949,0.033-11.923c-0.007-1.228-1.54-2.767-2.76-2.777c-2.428-0.021-4.862-0.015-7.29-0.015
40
+ c-22.466,0.001-44.934,0.005-67.404,0.009c-2.475,0.001-3.658,1.154-3.658,3.588c-0.001,47.365-0.001,94.73-0.001,142.096
41
+ c0,13.526,0,27.05,0.001,40.577c0,2.305,1.274,3.584,3.562,3.59c6.623,0.004,9.256-0.07,19.873,0.004
42
+ c10.618,0.072,14.146,8.543,14.146,14.189c-0.002,5.645-4.233,13.055-14.299,13.367c-13.135,0.061-26.277-0.02-39.414-0.014
43
+ c-14.575,0.004-29.146,0.004-43.722,0.02c-4.191-0.084-13.717-2.789-13.717-13.492s6.233-13.643,13.132-14.117
44
+ c4.979,0.037,13.802,0.057,20.701,0.049c1.695,0,3.11-1.537,3.11-3.361c0.003-13.912-0.004-27.823-0.004-41.736
45
+ c0-30.252,0-60.503,0.002-90.754c0.002-17.03,0.005-33.649,0.002-50.678c0-1.991-1.38-3.336-3.396-3.336
46
+ c-24.79,0-49.577,0-74.362,0c-2.133,0-3.38,1.22-3.383,3.308c-0.006,3.754-0.011,7.509,0.005,11.262
47
+ c-0.023,6.339-3.067,13.143-12.456,13.143c-9.389,0-14.337-4.647-14.915-13.23c-0.128-4.305,0.004-8.612,0.004-12.918
48
+ c0-8.392,0-16.781,0-25.173c0-0.321,0.228-5.156,4.091-9.277c3.864-4.122,8.014-3.822,8.445-3.822
49
+ c25.835,0.003,51.672,0.002,77.507,0.002c46.813,0.001,140.443,0.001,140.443,0.001s6.153-0.088,10.926-0.052
50
+ c0.3-0.008,5.713,0.363,9.178,3.994c3.46,3.631,3.046,8.407,3.046,8.635c-0.007,12.862,0.079,25.725-0.003,38.587
51
+ c-0.074,8.051-6.938,12.819-13.703,12.847C731.482,263.519,724.956,259.059,724.709,250.898z"
52
+ />
53
+ </g>
54
+ );
55
+ };
56
+
57
+ export default DaviIcon;