@libs-ui/components-draw-line 0.2.355-8 → 0.2.356-0
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.
package/README.md
CHANGED
|
@@ -1,3 +1,436 @@
|
|
|
1
|
-
# draw-line
|
|
1
|
+
# @libs-ui/components-draw-line
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Directive vẽ đường SVG nối giữa các điểm với nhiều chế độ đường cong, hỗ trợ né obstacle và kéo thả mũi tên.
|
|
4
|
+
|
|
5
|
+
## Giới thiệu
|
|
6
|
+
|
|
7
|
+
`LibsUiComponentsDrawLineDirective` là một standalone Angular directive dùng để vẽ các đường nối SVG giữa hai điểm với nhiều chế độ vẽ khác nhau (quart-in, horizontal, vertical, horizontal-single-curve, vertical-single-curve). Directive hỗ trợ tự động né vật cản (obstacle avoidance), kéo thả điểm đầu mũi tên và xác định vùng có thể kết nối (reachable point range).
|
|
8
|
+
|
|
9
|
+
### Tính năng
|
|
10
|
+
|
|
11
|
+
- ✅ 5 chế độ vẽ đường: quart-in, horizontal, vertical, horizontal-single-curve, vertical-single-curve
|
|
12
|
+
- ✅ Tự động né vật cản (obstacle avoidance) với obstacleRect
|
|
13
|
+
- ✅ Kéo thả mũi tên endpoint bằng chuột (drag-and-drop)
|
|
14
|
+
- ✅ Vùng có thể kết nối (reachable point range) với sự kiện khi kết nối thành công
|
|
15
|
+
- ✅ Tùy chỉnh style đường (stroke, dash, width), mũi tên, và circle
|
|
16
|
+
- ✅ Tự động tính toán viewBox cho SVG
|
|
17
|
+
- ✅ Hỗ trợ separatedPoints cho đường nối phức tạp
|
|
18
|
+
- ✅ Debug mode hiển thị rect/circle trực quan
|
|
19
|
+
- ✅ Chạy ngoài NgZone để tối ưu hiệu năng
|
|
20
|
+
|
|
21
|
+
## Khi nào sử dụng
|
|
22
|
+
|
|
23
|
+
- Khi cần vẽ đường nối giữa các node trong flowchart, diagram, workflow editor
|
|
24
|
+
- Khi cần hiển thị mối quan hệ giữa các phần tử trên giao diện
|
|
25
|
+
- Khi cần cho phép người dùng kéo thả để tạo kết nối giữa các phần tử
|
|
26
|
+
- Khi cần vẽ đường cong phức tạp né tránh các vật cản trên canvas
|
|
27
|
+
- Phù hợp cho automation builder, mind map, ERD diagram, pipeline editor
|
|
28
|
+
|
|
29
|
+
## Cài đặt
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# npm
|
|
33
|
+
npm install @libs-ui/components-draw-line
|
|
34
|
+
|
|
35
|
+
# yarn
|
|
36
|
+
yarn add @libs-ui/components-draw-line
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Import
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import {
|
|
43
|
+
LibsUiComponentsDrawLineDirective,
|
|
44
|
+
IMoDrawLineFunctionControl,
|
|
45
|
+
IDrawLineDataInput,
|
|
46
|
+
IReachablePointRange,
|
|
47
|
+
IViewBoxConfig,
|
|
48
|
+
TYPE_MODE,
|
|
49
|
+
} from '@libs-ui/components-draw-line';
|
|
50
|
+
|
|
51
|
+
@Component({
|
|
52
|
+
standalone: true,
|
|
53
|
+
imports: [LibsUiComponentsDrawLineDirective],
|
|
54
|
+
// ...
|
|
55
|
+
})
|
|
56
|
+
export class YourComponent {}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Ví dụ
|
|
60
|
+
|
|
61
|
+
### Basic — Vẽ đường nối đơn giản
|
|
62
|
+
|
|
63
|
+
```html
|
|
64
|
+
<div
|
|
65
|
+
LibsUiComponentsDrawLineDirective
|
|
66
|
+
(outDrawLineFunctionControl)="onFunctionControl($event)">
|
|
67
|
+
</div>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { LibsUiComponentsDrawLineDirective, IMoDrawLineFunctionControl, IDrawLineDataInput } from '@libs-ui/components-draw-line';
|
|
72
|
+
|
|
73
|
+
@Component({
|
|
74
|
+
standalone: true,
|
|
75
|
+
imports: [LibsUiComponentsDrawLineDirective],
|
|
76
|
+
// ...
|
|
77
|
+
})
|
|
78
|
+
export class BasicDrawLineComponent {
|
|
79
|
+
private fnControl!: IMoDrawLineFunctionControl;
|
|
80
|
+
|
|
81
|
+
onFunctionControl(ctrl: IMoDrawLineFunctionControl): void {
|
|
82
|
+
this.fnControl = ctrl;
|
|
83
|
+
|
|
84
|
+
const lines: IDrawLineDataInput[] = [
|
|
85
|
+
{
|
|
86
|
+
id: 'line-1',
|
|
87
|
+
mode: 'quart-in',
|
|
88
|
+
points: {
|
|
89
|
+
start: { x: 100, y: 100 },
|
|
90
|
+
end: { x: 300, y: 200 },
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
this.fnControl.setData(lines);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### With Controls — Sử dụng FunctionsControl
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
export class ControlDrawLineComponent {
|
|
104
|
+
private fnControl!: IMoDrawLineFunctionControl;
|
|
105
|
+
|
|
106
|
+
onFunctionControl(ctrl: IMoDrawLineFunctionControl): void {
|
|
107
|
+
this.fnControl = ctrl;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
addLine(): void {
|
|
111
|
+
this.fnControl.setData([
|
|
112
|
+
{
|
|
113
|
+
id: 'line-horizontal',
|
|
114
|
+
mode: 'horizontal',
|
|
115
|
+
points: {
|
|
116
|
+
start: { x: 50, y: 150 },
|
|
117
|
+
end: { x: 400, y: 300 },
|
|
118
|
+
},
|
|
119
|
+
lineStyle: {
|
|
120
|
+
stroke: '#3B82F6',
|
|
121
|
+
strokeWidth: '2px',
|
|
122
|
+
curve: 15,
|
|
123
|
+
},
|
|
124
|
+
arrowStyle: {
|
|
125
|
+
fill: '#3B82F6',
|
|
126
|
+
stroke: '#3B82F6',
|
|
127
|
+
width: 5,
|
|
128
|
+
height: 8,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
]);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
removeLine(): void {
|
|
135
|
+
this.fnControl.removeLine('line-horizontal');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
updateViewBox(): void {
|
|
139
|
+
this.fnControl.updateViewBox({
|
|
140
|
+
minX: 0,
|
|
141
|
+
minY: 0,
|
|
142
|
+
width: 800,
|
|
143
|
+
height: 600,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Obstacle Avoidance — Né vật cản
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
export class ObstacleDrawLineComponent {
|
|
153
|
+
private fnControl!: IMoDrawLineFunctionControl;
|
|
154
|
+
|
|
155
|
+
onFunctionControl(ctrl: IMoDrawLineFunctionControl): void {
|
|
156
|
+
this.fnControl = ctrl;
|
|
157
|
+
|
|
158
|
+
this.fnControl.setData([
|
|
159
|
+
{
|
|
160
|
+
id: 'line-obstacle',
|
|
161
|
+
mode: 'horizontal',
|
|
162
|
+
points: {
|
|
163
|
+
start: { x: 50, y: 100 },
|
|
164
|
+
end: { x: 500, y: 100 },
|
|
165
|
+
obstacleRect: [
|
|
166
|
+
{ id: 'box-1', x: 200, y: 50, width: 100, height: 100 },
|
|
167
|
+
],
|
|
168
|
+
},
|
|
169
|
+
lineStyle: { stroke: '#EF4444', strokeWidth: '2px' },
|
|
170
|
+
},
|
|
171
|
+
]);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Reachable Point Range — Vùng có thể kết nối
|
|
177
|
+
|
|
178
|
+
```html
|
|
179
|
+
<div
|
|
180
|
+
LibsUiComponentsDrawLineDirective
|
|
181
|
+
(outDrawLineFunctionControl)="onFunctionControl($event)"
|
|
182
|
+
(outConnected)="onConnected($event)">
|
|
183
|
+
</div>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
export class ReachableDrawLineComponent {
|
|
188
|
+
private fnControl!: IMoDrawLineFunctionControl;
|
|
189
|
+
|
|
190
|
+
onFunctionControl(ctrl: IMoDrawLineFunctionControl): void {
|
|
191
|
+
this.fnControl = ctrl;
|
|
192
|
+
|
|
193
|
+
this.fnControl.setReachablePointRange([
|
|
194
|
+
{
|
|
195
|
+
id: 'target-1',
|
|
196
|
+
x: 400,
|
|
197
|
+
y: 200,
|
|
198
|
+
style: { r: 6, fill: '#10B981', stroke: '#059669', strokeWidth: '2px' },
|
|
199
|
+
},
|
|
200
|
+
]);
|
|
201
|
+
|
|
202
|
+
this.fnControl.setData([
|
|
203
|
+
{
|
|
204
|
+
id: 'drag-line',
|
|
205
|
+
mode: 'quart-in',
|
|
206
|
+
endCircle: true,
|
|
207
|
+
endCircleStyle: { r: 4, fill: '#3B82F6' },
|
|
208
|
+
points: {
|
|
209
|
+
start: { x: 100, y: 200 },
|
|
210
|
+
end: { x: 250, y: 200 },
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
]);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
onConnected(event: { dataLine: any; dataReachablePointRange: any }): void {
|
|
217
|
+
console.log('Kết nối thành công:', event);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## API
|
|
223
|
+
|
|
224
|
+
### [LibsUiComponentsDrawLineDirective]
|
|
225
|
+
|
|
226
|
+
#### Inputs
|
|
227
|
+
|
|
228
|
+
| Property | Type | Default | Description |
|
|
229
|
+
| ----------------- | ----------------- | ------------ | ------------------------------------------------ |
|
|
230
|
+
| `[drawRectDebug]` | `boolean` | `false` | Hiển thị debug rect/circle cho obstacle trên SVG |
|
|
231
|
+
| `[svgElement]` | `SVGSVGElement` | auto-created | SVG element để vẽ, tự tạo nếu không truyền vào |
|
|
232
|
+
| `[viewBoxConfig]` | `IViewBoxConfig` | `undefined` | Cấu hình viewBox cho SVG element |
|
|
233
|
+
|
|
234
|
+
#### Outputs
|
|
235
|
+
|
|
236
|
+
| Property | Type | Description |
|
|
237
|
+
| ------------------------------ | ----------------------------------------------------------- | -------------------------------------------------------------- |
|
|
238
|
+
| `(outConnected)` | `{ dataLine: IDrawLineDataInput, dataReachablePointRange: IReachablePointRange }` | Emit khi kéo mũi tên đến reachable point thành công |
|
|
239
|
+
| `(outDrawLineFunctionControl)` | `IMoDrawLineFunctionControl` | Emit object chứa các methods điều khiển draw-line |
|
|
240
|
+
|
|
241
|
+
#### FunctionsControl Methods
|
|
242
|
+
|
|
243
|
+
| Method | Signature | Description |
|
|
244
|
+
| -------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------- |
|
|
245
|
+
| `removeLine` | `(id: string, points?: Array<IPoints>) => void` | Xóa đường nối theo id, có thể chỉ xóa một số points |
|
|
246
|
+
| `removeReachablePointRange`| `(ids: Array<string>) => void` | Xóa các reachable point range theo danh sách id |
|
|
247
|
+
| `setData` | `(data: Array<IDrawLineDataInput>) => void` | Thêm dữ liệu đường nối để vẽ |
|
|
248
|
+
| `setReachablePointRange` | `(data: Array<IReachablePointRange>) => void` | Thêm các vùng có thể kết nối (target points) |
|
|
249
|
+
| `updateViewBox` | `(viewBoxConfig?: IViewBoxConfig) => void` | Cập nhật viewBox cho SVG element |
|
|
250
|
+
|
|
251
|
+
## Types & Interfaces
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
/**
|
|
255
|
+
* Chế độ vẽ đường
|
|
256
|
+
*/
|
|
257
|
+
export type TYPE_MODE =
|
|
258
|
+
| 'quart-in'
|
|
259
|
+
| 'vertical'
|
|
260
|
+
| 'vertical-single-curve'
|
|
261
|
+
| 'horizontal'
|
|
262
|
+
| 'horizontal-single-curve';
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Hướng mũi tên
|
|
266
|
+
*/
|
|
267
|
+
export type TYPE_DIRECTION = 'right' | 'left' | 'top' | 'bottom';
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Dữ liệu đầu vào cho mỗi đường nối
|
|
271
|
+
*/
|
|
272
|
+
export interface IDrawLineDataInput {
|
|
273
|
+
id: string;
|
|
274
|
+
points: IPoints;
|
|
275
|
+
mode: TYPE_MODE;
|
|
276
|
+
idConnected?: string;
|
|
277
|
+
lineStyle?: ILineStyle;
|
|
278
|
+
arrowStyle?: IArrowStyle;
|
|
279
|
+
arrowDirection?: TYPE_DIRECTION;
|
|
280
|
+
ignoreDrawArrow?: boolean;
|
|
281
|
+
startCircle?: boolean;
|
|
282
|
+
startCircleStyle?: ICircleStyle;
|
|
283
|
+
endCircle?: boolean;
|
|
284
|
+
endCircleStyle?: ICircleStyle;
|
|
285
|
+
isRemove?: boolean;
|
|
286
|
+
ref?: IDrawLineDataInput;
|
|
287
|
+
startEndMode?: 'right-left' | 'bottom-top' | 'bottom-left';
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Tọa độ điểm đầu và điểm cuối
|
|
292
|
+
*/
|
|
293
|
+
export interface IPoints {
|
|
294
|
+
start: IPoint;
|
|
295
|
+
end: IPoint;
|
|
296
|
+
initialized?: boolean;
|
|
297
|
+
obstacleRect?: Array<IRect>;
|
|
298
|
+
separatedPoints?: Array<IPoints>;
|
|
299
|
+
pathElement?: SVGPathElement;
|
|
300
|
+
arrowElement?: SVGPolylineElement;
|
|
301
|
+
circleStartElement?: SVGCircleElement;
|
|
302
|
+
circleEndElement?: SVGCircleElement;
|
|
303
|
+
onDestroyEvent?: Subject<void>;
|
|
304
|
+
mode?: TYPE_MODE;
|
|
305
|
+
id?: string;
|
|
306
|
+
name?: string;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Tọa độ một điểm
|
|
311
|
+
*/
|
|
312
|
+
export interface IPoint {
|
|
313
|
+
x: number;
|
|
314
|
+
y: number;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Vùng có thể kết nối
|
|
319
|
+
*/
|
|
320
|
+
export interface IReachablePointRange {
|
|
321
|
+
id: string;
|
|
322
|
+
x: number;
|
|
323
|
+
y: number;
|
|
324
|
+
style: ICircleStyle;
|
|
325
|
+
idConnected?: string;
|
|
326
|
+
ref?: IReachablePointRange;
|
|
327
|
+
element?: SVGCircleElement;
|
|
328
|
+
onDestroyEvent?: Subject<void>;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Cấu hình viewBox cho SVG
|
|
333
|
+
*/
|
|
334
|
+
export interface IViewBoxConfig {
|
|
335
|
+
minX?: number;
|
|
336
|
+
minY?: number;
|
|
337
|
+
width?: number;
|
|
338
|
+
height?: number;
|
|
339
|
+
ignoreViewBox?: boolean;
|
|
340
|
+
marginBetweenElement?: number;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Style cho đường nối
|
|
345
|
+
*/
|
|
346
|
+
export interface ILineStyle {
|
|
347
|
+
stroke?: string;
|
|
348
|
+
strokeDasharray?: string;
|
|
349
|
+
strokeWidth?: string;
|
|
350
|
+
fill?: string;
|
|
351
|
+
curve?: number;
|
|
352
|
+
distancePoint?: number;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Style cho mũi tên
|
|
357
|
+
*/
|
|
358
|
+
export interface IArrowStyle {
|
|
359
|
+
stroke?: string;
|
|
360
|
+
strokeWidth?: string;
|
|
361
|
+
width?: number;
|
|
362
|
+
height?: number;
|
|
363
|
+
fill?: string;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Style cho circle (điểm tròn)
|
|
368
|
+
*/
|
|
369
|
+
export interface ICircleStyle {
|
|
370
|
+
stroke?: string;
|
|
371
|
+
strokeWidth?: string;
|
|
372
|
+
fill?: string;
|
|
373
|
+
r?: number;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Vùng chữ nhật (obstacle / debug rect)
|
|
378
|
+
*/
|
|
379
|
+
export interface IRect {
|
|
380
|
+
id?: string;
|
|
381
|
+
x: number;
|
|
382
|
+
y: number;
|
|
383
|
+
width: number;
|
|
384
|
+
height: number;
|
|
385
|
+
gapXObstacleRect?: number;
|
|
386
|
+
gapYObstacleRect?: number;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Object điều khiển draw-line
|
|
391
|
+
*/
|
|
392
|
+
export interface IMoDrawLineFunctionControl {
|
|
393
|
+
setData: (data: Array<IDrawLineDataInput>) => void;
|
|
394
|
+
removeLine: (id: string, points?: Array<IPoints>) => void;
|
|
395
|
+
setReachablePointRange: (data: Array<IReachablePointRange>) => void;
|
|
396
|
+
removeReachablePointRange: (ids: Array<string>) => void;
|
|
397
|
+
updateViewBox: (viewBoxConfig?: IViewBoxConfig) => void;
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## Công nghệ
|
|
402
|
+
|
|
403
|
+
| Technology | Version | Purpose |
|
|
404
|
+
| ---------- | ------- | --------------------- |
|
|
405
|
+
| Angular | ≥18.0.0 | Framework |
|
|
406
|
+
| @libs-ui/utils | 0.2.355-14 | Utilities (cloneDeep, getDragEventByElement, checkMouseOverInContainer) |
|
|
407
|
+
| RxJS | ~7.8.0 | Reactive programming |
|
|
408
|
+
| SVG | - | Rendering đường nối |
|
|
409
|
+
|
|
410
|
+
## Demo
|
|
411
|
+
|
|
412
|
+
```bash
|
|
413
|
+
npx nx serve core-ui
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Truy cập: `http://localhost:4500/draw-line`
|
|
417
|
+
|
|
418
|
+
## Kiểm thử
|
|
419
|
+
|
|
420
|
+
```bash
|
|
421
|
+
# Chạy tests
|
|
422
|
+
nx test draw-line
|
|
423
|
+
|
|
424
|
+
# Lint
|
|
425
|
+
nx lint draw-line
|
|
426
|
+
|
|
427
|
+
# Coverage
|
|
428
|
+
nx test draw-line --coverage
|
|
429
|
+
|
|
430
|
+
# Watch mode
|
|
431
|
+
nx test draw-line --watch
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
## License
|
|
435
|
+
|
|
436
|
+
MIT
|