@libs-ui/services-diagram-draw 0.2.356-41 → 0.2.356-43
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 +568 -143
- package/esm2022/canvas.service.mjs +2 -2
- package/esm2022/diagram-draw.service.mjs +3 -5
- package/esm2022/interfaces/diagram.interface.mjs +1 -1
- package/fesm2022/libs-ui-services-diagram-draw.mjs +3 -5
- package/fesm2022/libs-ui-services-diagram-draw.mjs.map +1 -1
- package/interfaces/diagram.interface.d.ts +2 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,9 +1,46 @@
|
|
|
1
1
|
# @libs-ui/services-diagram-draw
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
> **Angular:** ^18.2.0
|
|
3
|
+
> Bộ ba service Angular quản lý toàn bộ vòng đời diagram flow: tính toán tọa độ, render node động, vẽ SVG connector, quản lý kéo-thả và điều hướng luồng giữa các element.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Giới thiệu
|
|
8
|
+
|
|
9
|
+
`@libs-ui/services-diagram-draw` cung cấp ba service độc lập phối hợp với nhau để xây dựng diagram flow tương tác:
|
|
10
|
+
|
|
11
|
+
- **`LibsUiDiagramDrawService`** — service trung tâm: tính tọa độ X/Y, render component node vào canvas, vẽ đường SVG, quản lý drop zone kéo-thả, hỗ trợ virtualization cho diagram lớn.
|
|
12
|
+
- **`LibsUiDiagramDrawCanvasService`** — service quản lý cấu trúc dữ liệu diagram: thêm/xóa/thay thế element, xây dựng flat list, kiểm tra rule kết nối.
|
|
13
|
+
- **`LibsUiDiagramDrawDirectionService`** — service quản lý điều hướng (connection line) giữa các element không liền kề: thêm/xóa/chỉnh sửa đường nối tự do.
|
|
14
|
+
|
|
15
|
+
Hỗ trợ hai chế độ layout: **vertical** (dọc từ trên xuống, mặc định) và **horizontal** (ngang từ trái sang phải).
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Tính năng
|
|
20
|
+
|
|
21
|
+
- Tính toán tọa độ tự động cho diagram dọc và ngang
|
|
22
|
+
- Render Angular component động vào canvas (main node và nodeOtherConfig)
|
|
23
|
+
- Vẽ SVG connector với cubic bezier tự động
|
|
24
|
+
- Hiển thị mũi tên chỉ hướng (vertical: ▼, horizontal: ►) giữa các node
|
|
25
|
+
- Render label trên nhánh (branch label) với màu sắc và class tùy chỉnh
|
|
26
|
+
- Drop zone kéo-thả với indicator "+" tùy biến hoàn toàn
|
|
27
|
+
- Virtualization — chỉ render node trong viewport, hỗ trợ diagram hàng trăm node
|
|
28
|
+
- Quản lý thêm/xóa/thay thế/di chuyển element trong cấu trúc linked-list
|
|
29
|
+
- Quản lý điều hướng tự do (next_other_id) và cleanup tự động khi xóa element
|
|
30
|
+
- Build dữ liệu navigation line để kết hợp với `@libs-ui/components-draw-line`
|
|
31
|
+
- Tự động tăng counter tên node khi thêm cùng loại (Email 1, Email 2, …)
|
|
32
|
+
- Kiểm tra rule kết nối (elementCodeConnectableBefore) trước khi cho phép drop
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Khi nào sử dụng
|
|
37
|
+
|
|
38
|
+
- Xây dựng giao diện thiết kế luồng tự động hóa (automation flow, journey builder)
|
|
39
|
+
- Cần render động nhiều loại node component khác nhau trên cùng canvas
|
|
40
|
+
- Diagram có cấu trúc phân nhánh (branching workflow) phức tạp
|
|
41
|
+
- Cần tính năng kéo-thả từ sidebar vào luồng với validation rule
|
|
42
|
+
- Diagram cần kết nối điều hướng tự do giữa các element không liền kề
|
|
43
|
+
- Diagram lớn (>50 node) cần virtualization để tránh lag render
|
|
7
44
|
|
|
8
45
|
---
|
|
9
46
|
|
|
@@ -18,209 +55,581 @@ npm install @libs-ui/services-diagram-draw
|
|
|
18
55
|
## Import
|
|
19
56
|
|
|
20
57
|
```typescript
|
|
21
|
-
import {
|
|
58
|
+
import {
|
|
59
|
+
LibsUiDiagramDrawService,
|
|
60
|
+
LibsUiDiagramDrawCanvasService,
|
|
61
|
+
LibsUiDiagramDrawDirectionService,
|
|
62
|
+
canvasConfigReadonly,
|
|
63
|
+
storeDataDefault,
|
|
64
|
+
} from '@libs-ui/services-diagram-draw';
|
|
65
|
+
|
|
66
|
+
import type {
|
|
67
|
+
IDiagramElement,
|
|
68
|
+
IDiagramElementBranch,
|
|
69
|
+
ICanvasConfig,
|
|
70
|
+
ICustomCanvasConfig,
|
|
71
|
+
IDiagramStoreData,
|
|
72
|
+
IDropZoneIndicatorStyle,
|
|
73
|
+
IRuleConnectable,
|
|
74
|
+
IHandlerFunction,
|
|
75
|
+
TDropZoneCanDropFn,
|
|
76
|
+
INavigationLineStyleConfig,
|
|
77
|
+
} from '@libs-ui/services-diagram-draw';
|
|
22
78
|
```
|
|
23
79
|
|
|
80
|
+
Ba service đều có `providedIn: 'root'` — inject trực tiếp bằng `inject()`, không cần khai báo trong `providers`.
|
|
81
|
+
|
|
24
82
|
---
|
|
25
83
|
|
|
26
|
-
##
|
|
84
|
+
## Ví dụ sử dụng
|
|
85
|
+
|
|
86
|
+
### Ví dụ 1 — Khởi tạo diagram cơ bản (vertical)
|
|
27
87
|
|
|
28
88
|
```typescript
|
|
29
|
-
|
|
89
|
+
import { Component, AfterViewInit, ElementRef, ViewContainerRef, inject, viewChild } from '@angular/core';
|
|
90
|
+
import {
|
|
91
|
+
LibsUiDiagramDrawService,
|
|
92
|
+
LibsUiDiagramDrawCanvasService,
|
|
93
|
+
storeDataDefault,
|
|
94
|
+
IDiagramElement,
|
|
95
|
+
IRuleConnectable,
|
|
96
|
+
} from '@libs-ui/services-diagram-draw';
|
|
97
|
+
import { MyNodeComponent } from './my-node.component';
|
|
98
|
+
|
|
99
|
+
@Component({
|
|
100
|
+
selector: 'app-diagram',
|
|
101
|
+
standalone: true,
|
|
102
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
103
|
+
template: `
|
|
104
|
+
<div style="position: relative; overflow: auto; width: 100%; height: 100vh;">
|
|
105
|
+
<svg #svgContainer style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none;">
|
|
106
|
+
<path style="fill: none; stroke: #9ca2ad; stroke-width: 1;"></path>
|
|
107
|
+
</svg>
|
|
108
|
+
<div #nodesContainer style="position: relative; width: 100%; height: 100%;"></div>
|
|
109
|
+
<ng-container #vcr></ng-container>
|
|
110
|
+
</div>
|
|
111
|
+
`,
|
|
112
|
+
})
|
|
30
113
|
export class DiagramComponent implements AfterViewInit {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
114
|
+
private readonly svgContainer = viewChild<ElementRef<HTMLDivElement>>('svgContainer');
|
|
115
|
+
private readonly nodesContainer = viewChild<ElementRef<HTMLDivElement>>('nodesContainer');
|
|
116
|
+
private readonly vcr = viewChild('vcr', { read: ViewContainerRef });
|
|
34
117
|
|
|
35
118
|
private readonly diagramService = inject(LibsUiDiagramDrawService);
|
|
119
|
+
private readonly canvasService = inject(LibsUiDiagramDrawCanvasService);
|
|
120
|
+
|
|
121
|
+
private readonly elements: IDiagramElement[] = [
|
|
122
|
+
{ id: '1', code: 'EMAIL', name: 'Gửi Email 1', specific_width: 165, specific_height: 48, next_id: '2', pre_id: '' },
|
|
123
|
+
{ id: '2', code: 'SMS', name: 'Gửi SMS 1', specific_width: 165, specific_height: 48, next_id: '3', pre_id: '1' },
|
|
124
|
+
{ id: '3', code: 'EXIT', element_type: 'EXIT', name: 'Kết thúc', specific_width: 165, specific_height: 48, pre_id: '2' },
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
private readonly rules: IRuleConnectable[] = [
|
|
128
|
+
{ elementCode: 'SMS', elementCodeConnectableBefore: ['EMAIL', 'SMS'] },
|
|
129
|
+
{ elementCode: 'EMAIL', elementCodeConnectableBefore: ['EMAIL', 'SMS'] },
|
|
130
|
+
];
|
|
36
131
|
|
|
37
132
|
ngAfterViewInit() {
|
|
38
|
-
|
|
39
|
-
this.diagramService.setConfig({
|
|
133
|
+
this.diagramService.setConfig = {
|
|
40
134
|
storeDataDefine: { ...storeDataDefault },
|
|
41
|
-
svgContainer: this.svgContainer,
|
|
42
|
-
nodesContainer: this.nodesContainer,
|
|
43
|
-
viewContainerRef: this.vcr,
|
|
135
|
+
svgContainer: this.svgContainer(),
|
|
136
|
+
nodesContainer: this.nodesContainer(),
|
|
137
|
+
viewContainerRef: this.vcr(),
|
|
44
138
|
nodeComponentType: MyNodeComponent,
|
|
45
|
-
|
|
46
|
-
});
|
|
139
|
+
};
|
|
47
140
|
|
|
48
|
-
|
|
141
|
+
this.canvasService.loadElements(this.elements, { x: 200, y: 40 }, this.rules);
|
|
49
142
|
this.diagramService.configXYElements(this.elements);
|
|
143
|
+
this.diagramService.renderNodes(this.elements);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
### Ví dụ 2 — Diagram ngang (horizontal) với drop zone
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import { Component, AfterViewInit, ElementRef, ViewContainerRef, inject, viewChild } from '@angular/core';
|
|
154
|
+
import {
|
|
155
|
+
LibsUiDiagramDrawService,
|
|
156
|
+
LibsUiDiagramDrawCanvasService,
|
|
157
|
+
storeDataDefault,
|
|
158
|
+
IDiagramElement,
|
|
159
|
+
IDiagramElementBranch,
|
|
160
|
+
IRuleConnectable,
|
|
161
|
+
TDropZoneCanDropFn,
|
|
162
|
+
} from '@libs-ui/services-diagram-draw';
|
|
163
|
+
import { MyNodeComponent } from './my-node.component';
|
|
164
|
+
|
|
165
|
+
@Component({
|
|
166
|
+
selector: 'app-diagram-horizontal',
|
|
167
|
+
standalone: true,
|
|
168
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
169
|
+
template: `
|
|
170
|
+
<div style="position: relative; overflow: auto; width: 100%; height: 100vh;">
|
|
171
|
+
<svg #svgContainer style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none;">
|
|
172
|
+
<path style="fill: none; stroke: #9ca2ad; stroke-width: 1;"></path>
|
|
173
|
+
</svg>
|
|
174
|
+
<div #nodesContainer style="position: relative; width: 100%; height: 100%;"></div>
|
|
175
|
+
<ng-container #vcr></ng-container>
|
|
176
|
+
</div>
|
|
177
|
+
`,
|
|
178
|
+
})
|
|
179
|
+
export class DiagramHorizontalComponent implements AfterViewInit {
|
|
180
|
+
private readonly svgContainer = viewChild<ElementRef<HTMLDivElement>>('svgContainer');
|
|
181
|
+
private readonly nodesContainer = viewChild<ElementRef<HTMLDivElement>>('nodesContainer');
|
|
182
|
+
private readonly vcr = viewChild('vcr', { read: ViewContainerRef });
|
|
183
|
+
|
|
184
|
+
private readonly diagramService = inject(LibsUiDiagramDrawService);
|
|
185
|
+
private readonly canvasService = inject(LibsUiDiagramDrawCanvasService);
|
|
50
186
|
|
|
51
|
-
|
|
187
|
+
private readonly elements: IDiagramElement[] = [
|
|
188
|
+
{ id: '1', code: 'TRIGGER', name: 'Trigger 1', specific_width: 165, specific_height: 48, next_id: '2', pre_id: '' },
|
|
189
|
+
{ id: '2', code: 'ACTION', name: 'Action 1', specific_width: 165, specific_height: 48, next_id: '3', pre_id: '1' },
|
|
190
|
+
{ id: '3', code: 'EXIT', element_type: 'EXIT', name: 'Kết thúc', specific_width: 165, specific_height: 48, pre_id: '2' },
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
private readonly rules: IRuleConnectable[] = [
|
|
194
|
+
{ elementCode: 'ACTION', elementCodeConnectableBefore: ['TRIGGER', 'ACTION'] },
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
ngAfterViewInit() {
|
|
198
|
+
// Chuyển sang chế độ ngang
|
|
199
|
+
this.diagramService.orientation.set('horizontal');
|
|
200
|
+
|
|
201
|
+
this.diagramService.setConfig = {
|
|
202
|
+
storeDataDefine: { ...storeDataDefault },
|
|
203
|
+
svgContainer: this.svgContainer(),
|
|
204
|
+
nodesContainer: this.nodesContainer(),
|
|
205
|
+
viewContainerRef: this.vcr(),
|
|
206
|
+
nodeComponentType: MyNodeComponent,
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
this.canvasService.loadElements(this.elements, { x: 40, y: 200 }, this.rules);
|
|
210
|
+
this.diagramService.configXYElements(this.elements);
|
|
52
211
|
this.diagramService.renderNodes(this.elements);
|
|
212
|
+
this.renderDropZones();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private renderDropZones() {
|
|
216
|
+
const canDropFn: TDropZoneCanDropFn = (_before, current, _after, _rules) => {
|
|
217
|
+
// Trả về true = ẨN drop zone, false = HIỂN THỊ
|
|
218
|
+
return current?.code === 'EXIT';
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
this.diagramService.renderDropZones(
|
|
222
|
+
this.canvasService.FlatElements,
|
|
223
|
+
(element: IDiagramElement, branch: IDiagramElementBranch | undefined, dragData: string) => {
|
|
224
|
+
const data: IDiagramElement = JSON.parse(dragData);
|
|
225
|
+
this.canvasService.addElement(element, branch, data);
|
|
226
|
+
this.diagramService.configXYElements(this.elements);
|
|
227
|
+
this.diagramService.renderNodes(this.elements);
|
|
228
|
+
this.renderDropZones();
|
|
229
|
+
},
|
|
230
|
+
canDropFn,
|
|
231
|
+
this.rules,
|
|
232
|
+
);
|
|
53
233
|
}
|
|
54
234
|
}
|
|
55
235
|
```
|
|
56
236
|
|
|
57
237
|
---
|
|
58
238
|
|
|
59
|
-
|
|
239
|
+
### Ví dụ 3 — Diagram phân nhánh (WORKFLOW element)
|
|
60
240
|
|
|
61
|
-
|
|
241
|
+
```typescript
|
|
242
|
+
const elementsWithBranch: IDiagramElement[] = [
|
|
243
|
+
{
|
|
244
|
+
id: '1',
|
|
245
|
+
code: 'START',
|
|
246
|
+
name: 'Bắt đầu',
|
|
247
|
+
specific_width: 165,
|
|
248
|
+
specific_height: 48,
|
|
249
|
+
next_id: '2',
|
|
250
|
+
pre_id: '',
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
id: '2',
|
|
254
|
+
code: 'CONDITION',
|
|
255
|
+
element_type: 'WORKFLOW',
|
|
256
|
+
name: 'Điều kiện 1',
|
|
257
|
+
specific_width: 165,
|
|
258
|
+
specific_height: 48,
|
|
259
|
+
next_id: '5',
|
|
260
|
+
pre_id: '1',
|
|
261
|
+
branches: [
|
|
262
|
+
{
|
|
263
|
+
code: 'YES',
|
|
264
|
+
label: 'Đúng',
|
|
265
|
+
labelBgColor: '#dcfce7',
|
|
266
|
+
labelColor: '#15803d',
|
|
267
|
+
elements: [
|
|
268
|
+
{ id: '3', code: 'EMAIL', name: 'Email 1', specific_width: 165, specific_height: 48, next_id: '', pre_id: '2' },
|
|
269
|
+
],
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
code: 'NO',
|
|
273
|
+
label: 'Sai',
|
|
274
|
+
labelBgColor: '#fee2e2',
|
|
275
|
+
labelColor: '#dc2626',
|
|
276
|
+
elements: [
|
|
277
|
+
{ id: '4', code: 'SMS', name: 'SMS 1', specific_width: 165, specific_height: 48, next_id: '', pre_id: '2' },
|
|
278
|
+
],
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
id: '5',
|
|
284
|
+
code: 'EXIT',
|
|
285
|
+
element_type: 'EXIT',
|
|
286
|
+
name: 'Kết thúc',
|
|
287
|
+
specific_width: 165,
|
|
288
|
+
specific_height: 48,
|
|
289
|
+
pre_id: '2',
|
|
290
|
+
},
|
|
291
|
+
];
|
|
292
|
+
```
|
|
62
293
|
|
|
63
|
-
|
|
64
|
-
| --------------------------------------------------------- | -------------------------------------- | ------- | --------------------------------------------------------------------------------------------------- |
|
|
65
|
-
| `set setConfig` | `IConfig` | `void` | Đăng ký config (ViewContainerRef, container refs, component types). Phải gọi trước mọi method khác. |
|
|
66
|
-
| `set setConfigCanvas` | `Partial<ICanvasConfig>` | `void` | Ghi đè các tham số canvas config (margin, kích thước, màu SVG, …). |
|
|
67
|
-
| `configXYElements(elements, updateSVG?, positionX?)` | `IDiagramElement[], boolean?, number?` | `void` | Tính toán tọa độ X/Y cho tất cả elements và cập nhật SVG path. |
|
|
68
|
-
| `renderNodes(elements)` | `IDiagramElement[]` | `void` | Clear nodes cũ, render component động, branch labels và arrow indicators. |
|
|
69
|
-
| `clearNodes()` | — | `void` | Destroy tất cả ComponentRef, xóa node-dynamic, branch-label, arrow-indicator khỏi DOM. |
|
|
70
|
-
| `renderDropZones(flatElements, onDrop, canDropFn, rules)` | Xem bên dưới | `void` | Render drop zone "+" tại các vị trí cho phép kéo-thả. |
|
|
71
|
-
| `clearDropZones()` | — | `void` | Xóa tất cả `.diagram-drop-zone` khỏi nodesContainer. |
|
|
294
|
+
---
|
|
72
295
|
|
|
73
|
-
|
|
296
|
+
### Ví dụ 4 — Virtualization cho diagram lớn
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
ngAfterViewInit() {
|
|
300
|
+
this.diagramService.setConfig = {
|
|
301
|
+
storeDataDefine: { ...storeDataDefault },
|
|
302
|
+
svgContainer: this.svgContainer(),
|
|
303
|
+
nodesContainer: this.nodesContainer(),
|
|
304
|
+
viewContainerRef: this.vcr(),
|
|
305
|
+
nodeComponentType: MyNodeComponent,
|
|
306
|
+
useVirtualizationTemplate: true, // Bật virtualization
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
this.diagramService.configXYElements(this.elements);
|
|
310
|
+
this.diagramService.renderNodes(this.elements);
|
|
311
|
+
|
|
312
|
+
// Cập nhật viewport khi scroll
|
|
313
|
+
const container = this.nodesContainer()?.nativeElement.parentElement;
|
|
314
|
+
container?.addEventListener('scroll', () => {
|
|
315
|
+
this.diagramService.updateViewport({
|
|
316
|
+
x: container.scrollLeft,
|
|
317
|
+
y: container.scrollTop,
|
|
318
|
+
width: container.clientWidth,
|
|
319
|
+
height: container.clientHeight,
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
### Ví dụ 5 — Navigation line (kết nối tự do)
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
import {
|
|
331
|
+
LibsUiDiagramDrawDirectionService,
|
|
332
|
+
LibsUiDiagramDrawCanvasService,
|
|
333
|
+
LibsUiDiagramDrawService,
|
|
334
|
+
INavigationLineStyleConfig,
|
|
335
|
+
} from '@libs-ui/services-diagram-draw';
|
|
336
|
+
|
|
337
|
+
// Trong component:
|
|
338
|
+
private readonly directionService = inject(LibsUiDiagramDrawDirectionService);
|
|
339
|
+
private readonly canvasService = inject(LibsUiDiagramDrawCanvasService);
|
|
340
|
+
private readonly diagramService = inject(LibsUiDiagramDrawService);
|
|
341
|
+
|
|
342
|
+
// Thêm điều hướng từ element tới target
|
|
343
|
+
addDirection(sourceElement: IDiagramElement, targetId: string) {
|
|
344
|
+
const flatElements = this.canvasService.FlatElements;
|
|
345
|
+
this.directionService.addElementDirection(targetId, sourceElement, flatElements);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Build dữ liệu navigation line để render qua draw-line directive
|
|
349
|
+
buildNavigationLines() {
|
|
350
|
+
const styleConfig: INavigationLineStyleConfig = {
|
|
351
|
+
lineStyle: { color: '#6366f1', width: 1.5 },
|
|
352
|
+
arrowStyle: { color: '#6366f1', size: 8 },
|
|
353
|
+
endpointGap: 8,
|
|
354
|
+
};
|
|
355
|
+
const navData = this.diagramService.buildNavigationData(
|
|
356
|
+
this.canvasService.FlatElements,
|
|
357
|
+
styleConfig,
|
|
358
|
+
);
|
|
359
|
+
// Truyền navData vào drawLineFunctionControl.setData(navData)
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## Methods — `LibsUiDiagramDrawService`
|
|
366
|
+
|
|
367
|
+
| Method | Signature | Mô tả |
|
|
368
|
+
|---|---|---|
|
|
369
|
+
| `set setConfig` | `(config: ICustomCanvasConfig): void` | Đăng ký config (container refs, component types). Phải gọi trước mọi method khác. |
|
|
370
|
+
| `set setConfigCanvas` | `(config: Partial<ICanvasConfig>): void` | Ghi đè tham số canvas config (margin, kích thước, màu SVG). |
|
|
371
|
+
| `configXYElements` | `(elements: IDiagramElement[], updateSVG?: boolean, positionX?: number): void` | Tính toán tọa độ X/Y cho tất cả elements và cập nhật SVG path. |
|
|
372
|
+
| `renderNodes` | `(elements: IDiagramElement[]): void` | Xóa node cũ, render component động, branch labels và arrow indicators. |
|
|
373
|
+
| `clearNodes` | `(): void` | Destroy tất cả ComponentRef, xóa `.diagram-node-dynamic`, `.branch-label`, `.node-arrow-indicator` khỏi DOM. |
|
|
374
|
+
| `renderDropZones` | `(flatElements, onDrop, canDropFn, rules): void` | Render drop zone "+" tại các vị trí cho phép kéo-thả. |
|
|
375
|
+
| `clearDropZones` | `(): void` | Xóa tất cả `.diagram-drop-zone` khỏi nodesContainer. |
|
|
376
|
+
| `updateViewport` | `(vp: { x, y, width, height }): void` | Cập nhật viewport để virtualization tính toán node hiển thị. Chỉ có hiệu lực khi `useVirtualizationTemplate: true`. |
|
|
377
|
+
| `buildNavigationData` | `(flatElements: IDiagramElement[], styleConfig?: INavigationLineStyleConfig): IDrawLineDataInput[]` | Build dữ liệu navigation line từ `next_other_id` để render qua draw-line directive. |
|
|
378
|
+
|
|
379
|
+
### Chi tiết `renderDropZones`
|
|
74
380
|
|
|
75
381
|
```typescript
|
|
76
382
|
renderDropZones(
|
|
77
383
|
flatElements: IDiagramElement[],
|
|
78
384
|
onDrop: (element: IDiagramElement, branch: IDiagramElementBranch | undefined, dragData: string) => void,
|
|
79
|
-
canDropFn: TDropZoneCanDropFn,
|
|
80
|
-
ruleConnectableElements: IRuleConnectable[]
|
|
385
|
+
canDropFn: TDropZoneCanDropFn,
|
|
386
|
+
ruleConnectableElements: IRuleConnectable[],
|
|
81
387
|
): void
|
|
82
388
|
```
|
|
83
389
|
|
|
84
|
-
|
|
390
|
+
**Lưu ý:** `canDropFn` trả về `true` → **ẨN** drop zone; `false` → **HIỂN THỊ** drop zone (ngược với convention thông thường).
|
|
391
|
+
|
|
392
|
+
---
|
|
85
393
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
|
89
|
-
|
|
394
|
+
## Methods — `LibsUiDiagramDrawCanvasService`
|
|
395
|
+
|
|
396
|
+
| Method | Signature | Mô tả |
|
|
397
|
+
|---|---|---|
|
|
398
|
+
| `loadElements` | `(elements: IDiagramElement[], positionFirstNode: { x, y }, rules: IRuleConnectable[]): void` | Khởi tạo danh sách elements, đặt vị trí node đầu tiên, lưu rule kết nối. |
|
|
399
|
+
| `addElement` | `(itemDrop, branchDrop, dataDrop, branchChoice?): void` | Thêm element vào sau `itemDrop`, tự động tạo EXIT node cho WORKFLOW. |
|
|
400
|
+
| `removeOrChangeElement` | `(elementRemove, elementChange, branchChoose?): Promise<void>` | Xóa hoặc thay thế element, tự động thêm EXIT khi cần. |
|
|
401
|
+
| `deleteChangeElementAndInsertBranchKeep` | `(elementRemove, elementChange, branchKeepElement, branchChoice?): Promise<void>` | Xóa element và giữ nguyên nhánh chỉ định. |
|
|
402
|
+
| `addElementExitForAllElementPreviousWhenElementDelete` | `(elementsDelete: IDiagramElement[]): void` | Khi xóa hàng loạt element, tự động thêm EXIT cho các element đang trỏ vào vùng bị xóa. |
|
|
403
|
+
| `checkRuleDragDrop` | `(before, current, after, rules): boolean` | Kiểm tra rule kết nối trước khi cho phép drop. |
|
|
404
|
+
| `checkExistNameElement` | `(name: string, flatElements: IDiagramElement[]): boolean` | Kiểm tra tên element đã tồn tại chưa (case-insensitive). |
|
|
405
|
+
| `get FlatElements` | `IDiagramElement[]` | Trả về flat list của tất cả element (bao gồm element trong branches). |
|
|
406
|
+
| `set Elements` | `(elements: IDiagramElement[]): void` | Gán lại root elements array. |
|
|
407
|
+
| `get Elements` | `IDiagramElement[]` | Lấy root elements array. |
|
|
408
|
+
| `set HandlerFunction` | `(fn: IHandlerFunction): void` | Đăng ký callback tạo default config branches cho WORKFLOW element. |
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## Methods — `LibsUiDiagramDrawDirectionService`
|
|
413
|
+
|
|
414
|
+
| Method | Signature | Mô tả |
|
|
415
|
+
|---|---|---|
|
|
416
|
+
| `buildElementConnectTo` | `(element, flatElements, branch?, checkRule?): IDiagramElement[]` | Build danh sách element có thể kết nối tới. |
|
|
417
|
+
| `addElementDirection` | `(targetId: string, element: IDiagramElement, flatElements: IDiagramElement[]): void` | Thêm điều hướng từ element tới target (cập nhật `next_other_id` và `pre_other_id`). |
|
|
418
|
+
| `addBranchDirection` | `(targetId, element, branch, flatElements): void` | Thêm điều hướng từ branch của WORKFLOW element tới target. |
|
|
419
|
+
| `editDirection` | `(flatElements, targetId, element, branch?): void` | Thay đổi điều hướng hiện tại sang target mới. |
|
|
420
|
+
| `deleteElementsBehindElementConnectTo` | `(element, flatElements): void` | Xóa tất cả element phía sau khi element chuyển sang điều hướng. |
|
|
421
|
+
| `deleteElementsBehindBranchConnectTo` | `(branch: IDiagramElementBranch): void` | Xóa tất cả element trong branch khi branch chuyển sang điều hướng. |
|
|
422
|
+
| `set ElementDirection` | `(element: IDiagramElement | undefined): void` | Lưu element đang chọn điều hướng. |
|
|
423
|
+
| `get ElementDirection` | `IDiagramElement | undefined` | Lấy element đang chọn điều hướng. |
|
|
424
|
+
| `set BranchDirection` | `(branch: { branch, index } | undefined): void` | Lưu branch đang chọn điều hướng. |
|
|
425
|
+
| `get OnViewDirection` | `Subject<{ id: string }>` | Observable phát khi cần highlight element đang được xem qua điều hướng. |
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
## Exported Constants
|
|
430
|
+
|
|
431
|
+
| Export | Type | Mô tả |
|
|
432
|
+
|---|---|---|
|
|
433
|
+
| `canvasConfigReadonly` | `Signal<ICanvasConfig>` (readonly) | Signal đọc canvas config global — dùng trong component mà không cần inject service. |
|
|
434
|
+
| `storeDataDefault` | `IDiagramStoreData` | Giá trị mặc định cho `storeDataDefine` trong `ICustomCanvasConfig`. |
|
|
90
435
|
|
|
91
436
|
---
|
|
92
437
|
|
|
93
438
|
## Canvas Config (`ICanvasConfig`)
|
|
94
439
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
| Property
|
|
98
|
-
|
|
99
|
-
| `ELEMENT_WIDTH_DEFAULT_BRANCH_WHEN_NOT_ELEMENT`
|
|
100
|
-
| `ELEMENT_MARGIN_DEFAULT`
|
|
101
|
-
| `ELEMENT_MARGIN_DEFAULT_BRANCH`
|
|
102
|
-
| `ELEMENT_MARGIN_BETWEEN_BRANCH_DEFAULT`
|
|
103
|
-
| `DEFAULT_BRANCH_WHEN_NO_ELEMENT`
|
|
104
|
-
| `ELEMENT_HEIGHT_CURVE`
|
|
105
|
-
| `ELEMENT_SVG_STROKE_COLOR`
|
|
106
|
-
| `ELEMENT_SVG_STROKE_WIDTH`
|
|
107
|
-
| `ELEMENT_MARGIN_DEFAULT_BRANCH_TO_ELEMENT_FIRST` | `78`
|
|
108
|
-
| `DISTANCE_TO_WAIT`
|
|
109
|
-
| `WAIT_TO_ELEMENT`
|
|
110
|
-
| `ELEMENT_WAIT_DEFAULT`
|
|
111
|
-
| `DISTANCE_WAIT_TO_NEXT_LINE`
|
|
112
|
-
| `TYPE_ELEMENT_EXIT`
|
|
113
|
-
| `TYPE_ELEMENT_WORKFLOW`
|
|
114
|
-
| `WIDTH_ELEMENT_DEFAULT`
|
|
115
|
-
| `HEIGHT_ELEMENT_DEFAULT`
|
|
116
|
-
| `ADD_ICON_DIAMETER`
|
|
440
|
+
Tùy chỉnh qua `set setConfigCanvas`:
|
|
441
|
+
|
|
442
|
+
| Property | Default | Mô tả |
|
|
443
|
+
|---|---|---|
|
|
444
|
+
| `ELEMENT_WIDTH_DEFAULT_BRANCH_WHEN_NOT_ELEMENT` | `60` | Chiều rộng branch rỗng (không có element) |
|
|
445
|
+
| `ELEMENT_MARGIN_DEFAULT` | `60` | Khoảng cách dọc giữa các element (vertical) hoặc ngang (horizontal) |
|
|
446
|
+
| `ELEMENT_MARGIN_DEFAULT_BRANCH` | `16` | Khoảng cách từ element có nhánh đến đường rẽ nhánh |
|
|
447
|
+
| `ELEMENT_MARGIN_BETWEEN_BRANCH_DEFAULT` | `60` | Khoảng cách ngang giữa các branch (tối thiểu 32 để tương thích navigation line) |
|
|
448
|
+
| `DEFAULT_BRANCH_WHEN_NO_ELEMENT` | `100` | Chiều cao/rộng branch khi không có element |
|
|
449
|
+
| `ELEMENT_HEIGHT_CURVE` | `10` | Độ cong của SVG connector |
|
|
450
|
+
| `ELEMENT_SVG_STROKE_COLOR` | `'9ca2ad'` | Màu đường kẻ SVG (không có ký tự `#` đứng trước) |
|
|
451
|
+
| `ELEMENT_SVG_STROKE_WIDTH` | `'1'` | Độ dày đường kẻ SVG |
|
|
452
|
+
| `ELEMENT_MARGIN_DEFAULT_BRANCH_TO_ELEMENT_FIRST` | `78` | Khoảng cách từ element cha đến element đầu tiên trong branch |
|
|
453
|
+
| `DISTANCE_TO_WAIT` | `30` | Khoảng cách từ element chính đến `nodeOtherConfig` |
|
|
454
|
+
| `WAIT_TO_ELEMENT` | `50` | Khoảng cách từ `nodeOtherConfig` đến element kế tiếp |
|
|
455
|
+
| `ELEMENT_WAIT_DEFAULT` | `28` | Chiều cao mặc định của `nodeOtherConfig` |
|
|
456
|
+
| `DISTANCE_WAIT_TO_NEXT_LINE` | `4` | Khoảng cách từ `nodeOtherConfig` đến đường nối tiếp theo |
|
|
457
|
+
| `TYPE_ELEMENT_EXIT` | `'EXIT'` | Giá trị `code`/`element_type` cho element kết thúc (không có drop zone bên dưới) |
|
|
458
|
+
| `TYPE_ELEMENT_WORKFLOW` | `'WORKFLOW'` | Giá trị `element_type` cho element có branches |
|
|
459
|
+
| `WIDTH_ELEMENT_DEFAULT` | `165` | Chiều rộng mặc định node (dùng khi `specific_width` không được set) |
|
|
460
|
+
| `HEIGHT_ELEMENT_DEFAULT` | `48` | Chiều cao mặc định node (dùng khi `specific_height` không được set) |
|
|
461
|
+
| `ADD_ICON_DIAMETER` | `24` | Đường kính của icon "+" trong drop zone |
|
|
462
|
+
|
|
463
|
+
Ví dụ tùy chỉnh:
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
this.diagramService.setConfigCanvas = {
|
|
467
|
+
ELEMENT_MARGIN_DEFAULT: 80,
|
|
468
|
+
ELEMENT_SVG_STROKE_COLOR: '6366f1',
|
|
469
|
+
WIDTH_ELEMENT_DEFAULT: 200,
|
|
470
|
+
HEIGHT_ELEMENT_DEFAULT: 56,
|
|
471
|
+
};
|
|
472
|
+
```
|
|
117
473
|
|
|
118
474
|
---
|
|
119
475
|
|
|
120
|
-
## Interfaces
|
|
476
|
+
## Types & Interfaces
|
|
477
|
+
|
|
478
|
+
```typescript
|
|
479
|
+
import type {
|
|
480
|
+
IDiagramElement,
|
|
481
|
+
IDiagramElementBranch,
|
|
482
|
+
ICanvasConfig,
|
|
483
|
+
ICustomCanvasConfig,
|
|
484
|
+
IDiagramStoreData,
|
|
485
|
+
IDropZoneIndicatorStyle,
|
|
486
|
+
IRuleConnectable,
|
|
487
|
+
IHandlerFunction,
|
|
488
|
+
TDropZoneCanDropFn,
|
|
489
|
+
INavigationLineStyleConfig,
|
|
490
|
+
} from '@libs-ui/services-diagram-draw';
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### `IDiagramElement`
|
|
121
494
|
|
|
122
495
|
```typescript
|
|
123
496
|
interface IDiagramElement {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
element_type?: string;
|
|
128
|
-
branches?: IDiagramElementBranch[];
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
497
|
+
id?: string; // Unique identifier
|
|
498
|
+
code?: string; // Loại element (VD: 'EMAIL', 'SMS', 'EXIT', 'CONDITION')
|
|
499
|
+
name?: string; // Tên hiển thị
|
|
500
|
+
element_type?: string; // 'WORKFLOW' cho element có nhánh, 'EXIT' cho kết thúc
|
|
501
|
+
branches?: IDiagramElementBranch[]; // Danh sách nhánh (chỉ có khi element_type = 'WORKFLOW')
|
|
502
|
+
next_id?: string; // ID element kế tiếp trong luồng chính
|
|
503
|
+
pre_id?: string; // ID element trước trong luồng chính
|
|
504
|
+
next_other_id?: string[]; // ID các element được điều hướng đến (kết nối tự do)
|
|
505
|
+
pre_other_id?: string[]; // ID các element điều hướng đến element này
|
|
506
|
+
nodeOtherConfig?: IDiagramElement; // Node phụ (VD: wait node, delay config)
|
|
507
|
+
specific_x?: number; // Vị trí X do người dùng/service set trước khi tính toán
|
|
508
|
+
specific_y?: number; // Vị trí Y do người dùng/service set trước khi tính toán
|
|
509
|
+
specific_width?: number; // Chiều rộng node (ghi đè WIDTH_ELEMENT_DEFAULT)
|
|
510
|
+
specific_height?: number; // Chiều cao node (ghi đè HEIGHT_ELEMENT_DEFAULT)
|
|
511
|
+
translateX?: number; // Tọa độ X cuối cùng sau khi tính toán (computed)
|
|
512
|
+
translateY?: number; // Tọa độ Y cuối cùng sau khi tính toán (computed)
|
|
513
|
+
attributeSvgD?: string; // SVG path string cho connector
|
|
514
|
+
subCodeOfElement?: string; // Sub-type code dùng khi element có điều hướng
|
|
515
|
+
position_end?: number; // Vị trí cuối của đường nối (computed)
|
|
516
|
+
disable?: boolean; // Ẩn/vô hiệu hóa element
|
|
517
|
+
specific_counter_current_code?: number; // Counter đếm số lượng element cùng code
|
|
140
518
|
}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### `IDiagramElementBranch`
|
|
141
522
|
|
|
523
|
+
```typescript
|
|
142
524
|
interface IDiagramElementBranch {
|
|
143
|
-
code?: string;
|
|
144
|
-
label?: string;
|
|
145
|
-
labelBgColor?: string;
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
525
|
+
code?: string; // Định danh nhánh (VD: 'YES', 'NO')
|
|
526
|
+
label?: string; // Nhãn hiển thị trên nhánh (VD: 'Đúng', 'Sai')
|
|
527
|
+
labelBgColor?: string; // Màu nền nhãn (VD: '#dcfce7')
|
|
528
|
+
labelColor?: string; // Màu chữ nhãn (VD: '#15803d')
|
|
529
|
+
labelClassName?: string; // CSS class cho nhãn (mặc định: 'libs-ui-font-h7r')
|
|
530
|
+
elements: IDiagramElement[]; // Danh sách element trong nhánh
|
|
531
|
+
onTheSide?: 'above' | 'under' | 'center'; // Vị trí nhãn tương đối với nhánh
|
|
532
|
+
specific_start_branch_x?: number; // Tọa độ X điểm đầu đường nhánh (computed)
|
|
533
|
+
specific_start_branch_y?: number; // Tọa độ Y điểm đầu đường nhánh (computed)
|
|
534
|
+
next_other_id?: string[]; // ID các element được điều hướng từ nhánh này
|
|
535
|
+
isDirection?: boolean; // Nhánh đang ở chế độ điều hướng
|
|
536
|
+
nodeOtherConfig?: IDiagramElement; // Node phụ tại điểm rẽ nhánh
|
|
152
537
|
}
|
|
538
|
+
```
|
|
153
539
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
540
|
+
### `ICustomCanvasConfig`
|
|
541
|
+
|
|
542
|
+
```typescript
|
|
543
|
+
interface ICustomCanvasConfig {
|
|
544
|
+
storeDataDefine: IDiagramStoreData; // Lưu trữ tọa độ max — dùng storeDataDefault
|
|
545
|
+
svgContainer?: ElementRef<HTMLDivElement>; // Ref đến SVG container để vẽ đường nối
|
|
546
|
+
nodesContainer?: ElementRef<HTMLDivElement>; // Ref đến div chứa node component
|
|
547
|
+
viewContainerRef?: ViewContainerRef; // ViewContainerRef để tạo component động
|
|
548
|
+
nodeComponentType?: Type<any>; // Component type cho main node
|
|
549
|
+
nodeOtherConfigComponentType?: Type<any>; // Component type cho nodeOtherConfig
|
|
550
|
+
useVirtualizationTemplate?: boolean; // Bật virtualization (mặc định: false)
|
|
551
|
+
dropZoneStyle?: IDropZoneIndicatorStyle; // Tùy biến giao diện indicator "+"
|
|
162
552
|
}
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### `IRuleConnectable`
|
|
163
556
|
|
|
557
|
+
```typescript
|
|
164
558
|
interface IRuleConnectable {
|
|
165
|
-
elementCode: string;
|
|
166
|
-
elementCodeConnectableBefore: string[];
|
|
559
|
+
elementCode: string; // Code của element cần kiểm tra
|
|
560
|
+
elementCodeConnectableBefore: string[]; // Danh sách code được phép đứng trước
|
|
167
561
|
}
|
|
168
|
-
|
|
169
|
-
type TDropZoneCanDropFn = (before: IDiagramElement | undefined, current: IDiagramElement | undefined, after: IDiagramElement | undefined, rules: IRuleConnectable[]) => boolean;
|
|
170
562
|
```
|
|
171
563
|
|
|
172
|
-
|
|
564
|
+
### `IHandlerFunction`
|
|
173
565
|
|
|
174
|
-
|
|
566
|
+
```typescript
|
|
567
|
+
interface IHandlerFunction {
|
|
568
|
+
getConfigDefaultBranches(code: string, dataDefault: IDiagramElement): IDiagramElement;
|
|
569
|
+
}
|
|
570
|
+
```
|
|
175
571
|
|
|
176
|
-
|
|
177
|
-
Tất cả các field đều optional — giá trị nào không truyền sẽ dùng mặc định nội bộ của service.
|
|
572
|
+
### `TDropZoneCanDropFn`
|
|
178
573
|
|
|
179
|
-
|
|
574
|
+
```typescript
|
|
575
|
+
type TDropZoneCanDropFn = (
|
|
576
|
+
beforeElementDrag: IDiagramElement | undefined, // Element trước vị trí drop
|
|
577
|
+
currentElementDrag: IDiagramElement | undefined, // Element tại vị trí drop
|
|
578
|
+
afterElementDrag: IDiagramElement | undefined, // Element sau vị trí drop
|
|
579
|
+
ruleConnectableElements: IRuleConnectable[],
|
|
580
|
+
) => boolean; // true = ẨN drop zone, false = HIỂN THỊ
|
|
581
|
+
```
|
|
180
582
|
|
|
181
|
-
|
|
182
|
-
| ------- | ----------------------------- | ------------------------------------------------------------------- |
|
|
183
|
-
| **1** | `renderIndicator` được truyền | Toàn bộ HTML indicator do hàm này sinh ra |
|
|
184
|
-
| **2** | Không có `renderIndicator` | SVG `"+"` tự sinh từ `backgroundColor`, `iconColor`, `borderRadius` |
|
|
583
|
+
### `INavigationLineStyleConfig`
|
|
185
584
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
585
|
+
```typescript
|
|
586
|
+
interface INavigationLineStyleConfig {
|
|
587
|
+
lineStyle?: ILineStyle; // Style đường kẻ (color, width, dash)
|
|
588
|
+
arrowStyle?: IArrowStyle; // Style mũi tên (color, size)
|
|
589
|
+
endpointGap?: number; // Khoảng cách từ cạnh node đến endpoint (mặc định: 8)
|
|
590
|
+
modeResolver?: (start: { x, y }, end: { x, y }) => TYPE_MODE; // Override chọn kiểu đường
|
|
591
|
+
}
|
|
592
|
+
```
|
|
190
593
|
|
|
191
|
-
|
|
594
|
+
---
|
|
192
595
|
|
|
193
|
-
|
|
596
|
+
## Tùy biến indicator "+" (`IDropZoneIndicatorStyle`)
|
|
194
597
|
|
|
195
|
-
|
|
196
|
-
| ----------------- | -------------------------- | ----------- | -------------------------------------------------------------------------------------------- |
|
|
197
|
-
| `backgroundColor` | `string` | `'#3b82f6'` | Màu nền của indicator |
|
|
198
|
-
| `iconColor` | `string` | `'#ffffff'` | Màu dấu `"+"` bên trong indicator |
|
|
199
|
-
| `borderRadius` | `string` | `'50%'` | Bo góc indicator — `'50%'` cho tròn, `'6px'` cho vuông bo góc |
|
|
200
|
-
| `opacity` | `number` | `0.95` | Độ mờ của indicator ở trạng thái bình thường |
|
|
201
|
-
| `renderIndicator` | `(size: number) => string` | — | **[Động]** Hàm trả về HTML tùy ý thay thế toàn bộ indicator. Ghi đè mọi field tĩnh bên trên. |
|
|
598
|
+
Truyền vào `dropZoneStyle` của `ICustomCanvasConfig` để kiểm soát giao diện nút "+" trong drop zone.
|
|
202
599
|
|
|
203
|
-
|
|
600
|
+
### Trạng thái bình thường
|
|
204
601
|
|
|
205
|
-
| Thuộc tính
|
|
206
|
-
|
|
207
|
-
| `
|
|
208
|
-
| `
|
|
209
|
-
| `
|
|
210
|
-
| `
|
|
211
|
-
| `
|
|
602
|
+
| Thuộc tính | Type | Mặc định | Mô tả |
|
|
603
|
+
|---|---|---|---|
|
|
604
|
+
| `backgroundColor` | `string` | `'#3b82f6'` | Màu nền của nút "+" |
|
|
605
|
+
| `iconColor` | `string` | `'#ffffff'` | Màu dấu "+" bên trong |
|
|
606
|
+
| `borderRadius` | `string` | `'50%'` | Bo góc — `'50%'` tròn, `'6px'` vuông |
|
|
607
|
+
| `opacity` | `number` | `0.95` | Độ mờ ở trạng thái bình thường |
|
|
608
|
+
| `renderIndicator` | `(size: number) => string` | — | Hàm trả về HTML tùy ý, ghi đè toàn bộ indicator |
|
|
212
609
|
|
|
213
|
-
###
|
|
610
|
+
### Trạng thái hover/drag-enter
|
|
214
611
|
|
|
215
|
-
|
|
612
|
+
| Thuộc tính | Type | Mặc định | Mô tả |
|
|
613
|
+
|---|---|---|---|
|
|
614
|
+
| `hoverBackgroundColor` | `string` | `'#eef4ff'` | Màu nền vùng highlight khi kéo vào |
|
|
615
|
+
| `hoverBorderColor` | `string` | `'rgba(59, 130, 246, 0.7)'` | Màu viền vùng highlight |
|
|
616
|
+
| `hoverBorderStyle` | `string` | `'dashed'` | Kiểu viền: `'dashed'`, `'solid'`, `'dotted'` |
|
|
617
|
+
| `hoverBorderRadius` | `number` | `10` | Border-radius (px) của vùng highlight |
|
|
618
|
+
| `renderHoverIndicator` | `() => string` | — | Hàm trả về HTML inject vào bên trong vùng highlight |
|
|
619
|
+
|
|
620
|
+
Ví dụ tùy biến màu sắc:
|
|
216
621
|
|
|
217
622
|
```typescript
|
|
218
623
|
this.diagramService.setConfig = {
|
|
219
|
-
|
|
624
|
+
storeDataDefine: { ...storeDataDefault },
|
|
625
|
+
svgContainer: this.svgContainer(),
|
|
626
|
+
nodesContainer: this.nodesContainer(),
|
|
627
|
+
viewContainerRef: this.vcr(),
|
|
628
|
+
nodeComponentType: MyNodeComponent,
|
|
220
629
|
dropZoneStyle: {
|
|
221
630
|
backgroundColor: '#10b981',
|
|
222
631
|
iconColor: '#ffffff',
|
|
223
|
-
borderRadius: '4px',
|
|
632
|
+
borderRadius: '4px',
|
|
224
633
|
opacity: 1,
|
|
225
634
|
hoverBackgroundColor: '#f0fdf4',
|
|
226
635
|
hoverBorderColor: 'rgba(16, 185, 129, 0.8)',
|
|
@@ -230,11 +639,15 @@ this.diagramService.setConfig = {
|
|
|
230
639
|
};
|
|
231
640
|
```
|
|
232
641
|
|
|
233
|
-
|
|
642
|
+
Ví dụ render indicator hoàn toàn tùy chỉnh:
|
|
234
643
|
|
|
235
644
|
```typescript
|
|
236
645
|
this.diagramService.setConfig = {
|
|
237
|
-
|
|
646
|
+
storeDataDefine: { ...storeDataDefault },
|
|
647
|
+
svgContainer: this.svgContainer(),
|
|
648
|
+
nodesContainer: this.nodesContainer(),
|
|
649
|
+
viewContainerRef: this.vcr(),
|
|
650
|
+
nodeComponentType: MyNodeComponent,
|
|
238
651
|
dropZoneStyle: {
|
|
239
652
|
renderIndicator: (size: number) => `
|
|
240
653
|
<div style="
|
|
@@ -242,8 +655,8 @@ this.diagramService.setConfig = {
|
|
|
242
655
|
background: #f59e0b; border-radius: 6px;
|
|
243
656
|
display: flex; align-items: center; justify-content: center;
|
|
244
657
|
">
|
|
245
|
-
<svg viewBox="0 0 24 24" fill="
|
|
246
|
-
<path d="M12 5v14M5 12h14" stroke="white" stroke-width="2"/>
|
|
658
|
+
<svg viewBox="0 0 24 24" fill="none" width="14" height="14">
|
|
659
|
+
<path d="M12 5v14M5 12h14" stroke="white" stroke-width="2.5" stroke-linecap="round"/>
|
|
247
660
|
</svg>
|
|
248
661
|
</div>
|
|
249
662
|
`,
|
|
@@ -252,9 +665,9 @@ this.diagramService.setConfig = {
|
|
|
252
665
|
+ Thêm vào đây
|
|
253
666
|
</span>
|
|
254
667
|
`,
|
|
255
|
-
hoverBackgroundColor: '#
|
|
256
|
-
hoverBorderColor: 'rgba(
|
|
257
|
-
hoverBorderStyle: '
|
|
668
|
+
hoverBackgroundColor: '#fffbeb',
|
|
669
|
+
hoverBorderColor: 'rgba(245, 158, 11, 0.6)',
|
|
670
|
+
hoverBorderStyle: 'dashed',
|
|
258
671
|
hoverBorderRadius: 12,
|
|
259
672
|
},
|
|
260
673
|
};
|
|
@@ -264,16 +677,28 @@ this.diagramService.setConfig = {
|
|
|
264
677
|
|
|
265
678
|
## Lưu ý quan trọng
|
|
266
679
|
|
|
267
|
-
|
|
680
|
+
⚠️ **Thứ tự khởi tạo bắt buộc**: Phải gọi `set setConfig` trước mọi method khác của `LibsUiDiagramDrawService`.
|
|
681
|
+
|
|
682
|
+
⚠️ **canvasConfig là signal global**: Thay đổi qua `set setConfigCanvas` sẽ ảnh hưởng đến mọi instance trong cùng app. Cần cẩn thận khi có nhiều diagram trên cùng trang.
|
|
268
683
|
|
|
269
|
-
|
|
684
|
+
⚠️ **`renderNodes()` tự gọi `clearNodes()` trước**: Không cần gọi thủ công trước khi render lại.
|
|
270
685
|
|
|
271
|
-
|
|
686
|
+
⚠️ **`canDropFn` logic ngược**: Trả về `true` → ẨN drop zone; `false` → HIỂN THỊ drop zone. Đây là thiết kế có chủ ý để hàm đóng vai trò "filter loại trừ".
|
|
272
687
|
|
|
273
|
-
|
|
688
|
+
⚠️ **`ELEMENT_SVG_STROKE_COLOR` không có ký tự `#`**: Giá trị phải là mã hex thuần (VD: `'9ca2ad'`, không phải `'#9ca2ad'`).
|
|
689
|
+
|
|
690
|
+
⚠️ **Virtualization yêu cầu scroll listener**: Khi bật `useVirtualizationTemplate: true`, cần tự lắng nghe sự kiện scroll của container và gọi `updateViewport()` để service cập nhật node hiển thị.
|
|
691
|
+
|
|
692
|
+
⚠️ **`ELEMENT_MARGIN_BETWEEN_BRANCH_DEFAULT` tối thiểu 32**: Giá trị dưới 32px chưa tương thích với navigation line rendering.
|
|
693
|
+
|
|
694
|
+
⚠️ **`loadElements` phải gọi trước `configXYElements`**: `LibsUiDiagramDrawCanvasService.loadElements()` khởi tạo vị trí node đầu tiên, cần được gọi trước khi service tính toán tọa độ.
|
|
274
695
|
|
|
275
696
|
---
|
|
276
697
|
|
|
277
698
|
## Demo
|
|
278
699
|
|
|
279
|
-
|
|
700
|
+
```bash
|
|
701
|
+
npx nx serve core-ui
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
Truy cập: `http://localhost:4500/services/diagram-draw`
|