Skip to content

Commit 640b52d

Browse files
committed
update(image): use domportal to render label and attributes
1 parent 2631ecb commit 640b52d

18 files changed

+278
-162
lines changed

packages/image/src/annotations/Annotation.ts

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import type { Line } from '../shapes';
12
import type { BasicImageAnnotation, ToolName } from '../interface';
23
import { Group } from '../shapes/Group';
34
import { eventEmitter, monitor } from '../singletons';
45
import { DEFAULT_LABEL_COLOR } from '../constant';
5-
import type { DomPortal } from '../core/DomPortal';
6+
import { DomPortal } from '../core/DomPortal';
67
import { EInternalEvent } from '../enums/internalEvent.enum';
78

89
// TODO: 去除本类的any
@@ -24,6 +25,18 @@ export interface AnnotationParams<Data extends BasicImageAnnotation, Style> {
2425
onPick?: (e: MouseEvent, annotation: any) => void;
2526
}
2627

28+
export interface TextPositionParams {
29+
shape: Line;
30+
container: HTMLElement;
31+
isAboveLine: boolean;
32+
}
33+
34+
export interface TextPosition {
35+
x: number;
36+
y: number;
37+
rotate: number;
38+
}
39+
2740
export class Annotation<Data extends BasicImageAnnotation, Style> {
2841
public id: string;
2942

@@ -51,6 +64,47 @@ export class Annotation<Data extends BasicImageAnnotation, Style> {
5164

5265
static strokeWidth = 2;
5366

67+
static minOffsetDistance = 14;
68+
69+
static rotationThreshold = 90;
70+
71+
static calculateTextPosition({ shape, container, isAboveLine }: TextPositionParams): TextPosition {
72+
const { angle, rotate, centerX, centerY } = shape.getLineInfo();
73+
74+
// 根据角度决定偏移方向
75+
let perpendicularAngle: number;
76+
if (Math.abs(rotate) <= Annotation.rotationThreshold) {
77+
// 角度在-90到90度之间
78+
perpendicularAngle = isAboveLine ? angle + Math.PI / 2 : angle - Math.PI / 2;
79+
} else {
80+
// 角度超过90度
81+
perpendicularAngle = isAboveLine ? angle - Math.PI / 2 : angle + Math.PI / 2;
82+
}
83+
84+
const offsetDistance = Annotation.minOffsetDistance;
85+
const x = centerX + Math.cos(perpendicularAngle) * offsetDistance - container.clientWidth / 2;
86+
const y = centerY + Math.sin(perpendicularAngle) * offsetDistance - container.clientHeight / 2;
87+
88+
// 根据角度调整旋转,确保文字始终可读
89+
let finalRotate = rotate;
90+
if (Math.abs(rotate) > Annotation.rotationThreshold) {
91+
finalRotate = rotate + 180;
92+
}
93+
94+
return { x, y, rotate: finalRotate };
95+
}
96+
97+
static createTextDomPortal(content: string, isAboveLine: boolean, order: number, bindShape: Line): DomPortal {
98+
return new DomPortal({
99+
content,
100+
getPosition: (shape, container) =>
101+
Annotation.calculateTextPosition({ shape: shape as Line, container, isAboveLine }),
102+
order,
103+
preventPointerEvents: true,
104+
bindShape,
105+
});
106+
}
107+
54108
public get isHovered() {
55109
return false;
56110
}
@@ -109,9 +163,9 @@ export class Annotation<Data extends BasicImageAnnotation, Style> {
109163

110164
protected generateLabelDom(text: string, style?: string, extra?: string) {
111165
return `
112-
<div style="color: #fff; font-size: 12px; font-weight: bold; background-color: ${
113-
this.labelColor
114-
}; padding: 2px 4px; ${style ?? ''};">
166+
<div style="color: #fff; font-size: 12px; background-color: ${this.labelColor}; padding: 1px 2px; ${
167+
style ?? ''
168+
};">
115169
${this.showOrder ? this.data.order + ' ' : ''}${text}
116170
${extra ?? ''}
117171
</div>
@@ -120,9 +174,9 @@ export class Annotation<Data extends BasicImageAnnotation, Style> {
120174

121175
protected generateAttributeDom(text: string, style?: string, extra?: string) {
122176
return `
123-
<div style="color: #fff; font-size: 12px; font-weight: bold; background-color: ${
124-
this.labelColor
125-
}; padding: 2px 4px; ${style ?? ''};">
177+
<div style="color: #fff; font-size: 12px; background-color: ${this.labelColor}; padding: 1px 2px; ${
178+
style ?? ''
179+
};">
126180
${text
127181
.split('\n')
128182
.map((line) => `<div>${line}</div>`)

packages/image/src/annotations/Cuboid.annotation.ts

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import Color from 'color';
22
import type { ILabel } from '@labelu/interface';
33

44
import uid from '@/utils/uid';
5+
import { DomPortal } from '@/core/DomPortal';
56

67
import type { BasicImageAnnotation } from '../interface';
78
import type { AnnotationParams } from './Annotation';
@@ -225,23 +226,56 @@ export class AnnotationCuboid extends Annotation<CuboidData, CuboidStyle> {
225226
}),
226227
);
227228

228-
const attributesText = AnnotationCuboid.labelStatic.getLabelTextWithAttributes(data.label, data.attributes);
229-
230-
// label
231-
group.add(
232-
new ShapeText({
233-
id: uid(),
234-
coordinate: {
235-
x: front.bl.x,
236-
y: front.bl.y,
237-
},
238-
text: `${this.showOrder ? data.order + ' ' : ''}${attributesText}`,
239-
style: {
240-
opacity: visible ? 1 : 0,
241-
fill: labelColor,
242-
},
229+
const labelText = AnnotationCuboid.labelStatic.getLabelText(data.label);
230+
const attributesText = AnnotationCuboid.labelStatic.getAttributeTexts(data.label, data.attributes);
231+
232+
const backShape = group.shapes[2];
233+
const frontShape = group.shapes[1];
234+
this.doms.push(
235+
new DomPortal({
236+
content: this.generateLabelDom(labelText),
237+
getPosition: (shape, container) => ({
238+
x: shape.dynamicCoordinate[0].x,
239+
y: shape.dynamicCoordinate[0].y - container.clientHeight,
240+
}),
241+
order: data.order,
242+
preventPointerEvents: true,
243+
bindShape: backShape,
243244
}),
244245
);
246+
247+
if (attributesText) {
248+
this.doms.push(
249+
new DomPortal({
250+
content: this.generateAttributeDom(attributesText),
251+
getPosition: (shape, container) => ({
252+
x: shape.dynamicCoordinate[0].x,
253+
y: shape.dynamicCoordinate[0].y + container.clientHeight + 5,
254+
}),
255+
order: data.order,
256+
preventPointerEvents: true,
257+
bindShape: frontShape,
258+
}),
259+
);
260+
}
261+
262+
// const attributesText = AnnotationCuboid.labelStatic.getLabelTextWithAttributes(data.label, data.attributes);
263+
264+
// // label
265+
// group.add(
266+
// new ShapeText({
267+
// id: uid(),
268+
// coordinate: {
269+
// x: front.bl.x,
270+
// y: front.bl.y,
271+
// },
272+
// text: `${this.showOrder ? data.order + ' ' : ''}${attributesText}`,
273+
// style: {
274+
// opacity: visible ? 1 : 0,
275+
// fill: labelColor,
276+
// },
277+
// }),
278+
// );
245279
}
246280

247281
public destroy(): void {

packages/image/src/annotations/Line.annotation.ts

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import type { ILabel } from '@labelu/interface';
22
import Color from 'color';
33

4-
import uid from '@/utils/uid';
4+
import { DomPortal } from '@/core/DomPortal';
55

66
import type { BasicImageAnnotation } from '../interface';
77
import type { AnnotationParams } from './Annotation';
88
import { Annotation } from './Annotation';
99
import type { LineStyle } from '../shapes/Line.shape';
1010
import { Line } from '../shapes/Line.shape';
1111
import type { AxisPoint } from '../shapes/Point.shape';
12-
import type { Group, TextStyle } from '../shapes';
12+
import type { Group } from '../shapes';
1313
import { Spline, ShapeText } from '../shapes';
1414
import { LabelBase } from './Label.base';
1515
import { EInternalEvent } from '../enums';
@@ -19,7 +19,7 @@ export interface PointItem extends AxisPoint {
1919
id: string;
2020
}
2121

22-
export type LineGroup = Group<Line | ShapeText, LineStyle>;
22+
export type LineGroup = Group;
2323

2424
export interface LineData extends BasicImageAnnotation {
2525
points: PointItem[];
@@ -31,7 +31,7 @@ export interface LineData extends BasicImageAnnotation {
3131
controlPoints?: AxisPoint[];
3232
}
3333

34-
export class AnnotationLine extends Annotation<LineData, Line | Spline | ShapeText, LineStyle | TextStyle> {
34+
export class AnnotationLine extends Annotation<LineData, LineStyle> {
3535
public labelColor: string = LabelBase.DEFAULT_COLOR;
3636

3737
public strokeColor: string = LabelBase.DEFAULT_COLOR;
@@ -86,7 +86,7 @@ export class AnnotationLine extends Annotation<LineData, Line | Spline | ShapeTe
8686
}
8787

8888
private _setupShapes() {
89-
const { data, group, style, labelColor, strokeColor } = this;
89+
const { data, group, style, strokeColor } = this;
9090

9191
const { type, visible = true } = data;
9292

@@ -130,19 +130,36 @@ export class AnnotationLine extends Annotation<LineData, Line | Spline | ShapeTe
130130
throw new Error('Invalid line type!');
131131
}
132132

133-
const attributesText = AnnotationLine.labelStatic.getLabelTextWithAttributes(data.label, data.attributes);
134-
135-
group.add(
136-
new ShapeText({
137-
id: uid(),
138-
coordinate: data.points[0],
139-
text: `${this.showOrder ? data.order + ' ' : ''}${attributesText}`,
140-
style: {
141-
opacity: visible ? 1 : 0,
142-
fill: labelColor,
143-
},
133+
const labelText = AnnotationLine.labelStatic.getLabelText(data.label);
134+
const attributesText = AnnotationLine.labelStatic.getAttributeTexts(data.label, data.attributes);
135+
136+
this.doms.push(
137+
new DomPortal({
138+
content: this.generateLabelDom(labelText),
139+
getPosition: (shape, container) => ({
140+
x: shape.dynamicCoordinate[0].x,
141+
y: shape.dynamicCoordinate[0].y - container.clientHeight,
142+
}),
143+
order: data.order,
144+
preventPointerEvents: true,
145+
bindShape: group.shapes[0] as Line,
144146
}),
145147
);
148+
149+
if (attributesText) {
150+
this.doms.push(
151+
new DomPortal({
152+
content: this.generateAttributeDom(attributesText),
153+
getPosition: (shape) => ({
154+
x: shape.dynamicCoordinate[0].x,
155+
y: shape.dynamicCoordinate[0].y,
156+
}),
157+
order: data.order,
158+
preventPointerEvents: true,
159+
bindShape: group.shapes[0],
160+
}),
161+
);
162+
}
146163
}
147164

148165
private _handleMouseOver = () => {

packages/image/src/annotations/Point.annotation.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { ILabel } from '@labelu/interface';
22
import Color from 'color';
33

44
import uid from '@/utils/uid';
5+
import { DomPortal } from '@/core/DomPortal';
56

67
import type { BasicImageAnnotation } from '../interface';
78
import type { AnnotationParams } from './Annotation';
@@ -62,6 +63,37 @@ export class AnnotationPoint extends Annotation<PointData, PointStyle> {
6263
}),
6364
);
6465

66+
const labelText = AnnotationPoint.labelStatic.getLabelText(data.label);
67+
const attributesText = AnnotationPoint.labelStatic.getAttributeTexts(data.label, data.attributes);
68+
69+
this.doms.push(
70+
new DomPortal({
71+
content: this.generateLabelDom(labelText),
72+
getPosition: (shape, container) => ({
73+
x: shape.dynamicCoordinate[0].x,
74+
y: shape.dynamicCoordinate[0].y - container.clientHeight - Annotation.strokeWidth - 4,
75+
}),
76+
order: data.order,
77+
preventPointerEvents: true,
78+
bindShape: group.shapes[0],
79+
}),
80+
);
81+
82+
if (attributesText) {
83+
this.doms.push(
84+
new DomPortal({
85+
content: this.generateAttributeDom(attributesText),
86+
getPosition: (shape, container) => ({
87+
x: shape.dynamicCoordinate[0].x - container.clientWidth / 2,
88+
y: shape.dynamicCoordinate[0].y + 4,
89+
}),
90+
order: data.order,
91+
preventPointerEvents: true,
92+
bindShape: group.shapes[0],
93+
}),
94+
);
95+
}
96+
6597
// const attributesText = AnnotationPoint.labelStatic.getLabelTextWithAttributes(data.label, data.attributes);
6698

6799
// group.add(

0 commit comments

Comments
 (0)