npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@libs-ui/services-diagram-draw

v0.2.357-2

Published

> 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.

Readme

@libs-ui/services-diagram-draw

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.


Giới thiệu

@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:

  • 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.
  • 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.
  • 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.

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).


Tính năng

  • Tính toán tọa độ tự động cho diagram dọc và ngang
  • Render Angular component động vào canvas (main node và nodeOtherConfig)
  • Vẽ SVG connector với cubic bezier tự động
  • Hiển thị mũi tên chỉ hướng (vertical: ▼, horizontal: ►) giữa các node
  • Render label trên nhánh (branch label) với màu sắc và class tùy chỉnh
  • Drop zone kéo-thả với indicator "+" tùy biến hoàn toàn
  • Virtualization — chỉ render node trong viewport, hỗ trợ diagram hàng trăm node
  • Quản lý thêm/xóa/thay thế/di chuyển element trong cấu trúc linked-list
  • Quản lý điều hướng tự do (next_other_id) và cleanup tự động khi xóa element
  • Build dữ liệu navigation line để kết hợp với @libs-ui/components-draw-line
  • Tự động tăng counter tên node khi thêm cùng loại (Email 1, Email 2, …)
  • Kiểm tra rule kết nối (elementCodeConnectableBefore) trước khi cho phép drop

Khi nào sử dụng

  • Xây dựng giao diện thiết kế luồng tự động hóa (automation flow, journey builder)
  • Cần render động nhiều loại node component khác nhau trên cùng canvas
  • Diagram có cấu trúc phân nhánh (branching workflow) phức tạp
  • Cần tính năng kéo-thả từ sidebar vào luồng với validation rule
  • Diagram cần kết nối điều hướng tự do giữa các element không liền kề
  • Diagram lớn (>50 node) cần virtualization để tránh lag render

Cài đặt

npm install @libs-ui/services-diagram-draw

Import

import {
  LibsUiDiagramDrawService,
  LibsUiDiagramDrawCanvasService,
  LibsUiDiagramDrawDirectionService,
  canvasConfigReadonly,
  storeDataDefault,
} from '@libs-ui/services-diagram-draw';

import type {
  IDiagramElement,
  IDiagramElementBranch,
  ICanvasConfig,
  ICustomCanvasConfig,
  IDiagramStoreData,
  IDropZoneIndicatorStyle,
  IRuleConnectable,
  IHandlerFunction,
  TDropZoneCanDropFn,
  INavigationLineStyleConfig,
} from '@libs-ui/services-diagram-draw';

Ba service đều có providedIn: 'root' — inject trực tiếp bằng inject(), không cần khai báo trong providers.


Ví dụ sử dụng

Ví dụ 1 — Khởi tạo diagram cơ bản (vertical)

import { Component, AfterViewInit, ElementRef, ViewContainerRef, inject, viewChild } from '@angular/core';
import {
  LibsUiDiagramDrawService,
  LibsUiDiagramDrawCanvasService,
  storeDataDefault,
  IDiagramElement,
  IRuleConnectable,
} from '@libs-ui/services-diagram-draw';
import { MyNodeComponent } from './my-node.component';

@Component({
  selector: 'app-diagram',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div style="position: relative; overflow: auto; width: 100%; height: 100vh;">
      <svg #svgContainer style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none;">
        <path style="fill: none; stroke: #9ca2ad; stroke-width: 1;"></path>
      </svg>
      <div #nodesContainer style="position: relative; width: 100%; height: 100%;"></div>
      <ng-container #vcr></ng-container>
    </div>
  `,
})
export class DiagramComponent implements AfterViewInit {
  private readonly svgContainer = viewChild<ElementRef<HTMLDivElement>>('svgContainer');
  private readonly nodesContainer = viewChild<ElementRef<HTMLDivElement>>('nodesContainer');
  private readonly vcr = viewChild('vcr', { read: ViewContainerRef });

  private readonly diagramService = inject(LibsUiDiagramDrawService);
  private readonly canvasService = inject(LibsUiDiagramDrawCanvasService);

  private readonly elements: IDiagramElement[] = [
    { id: '1', code: 'EMAIL', name: 'Gửi Email 1', specific_width: 165, specific_height: 48, next_id: '2', pre_id: '' },
    { id: '2', code: 'SMS',   name: 'Gửi SMS 1',   specific_width: 165, specific_height: 48, next_id: '3', pre_id: '1' },
    { id: '3', code: 'EXIT',  element_type: 'EXIT', name: 'Kết thúc', specific_width: 165, specific_height: 48, pre_id: '2' },
  ];

  private readonly rules: IRuleConnectable[] = [
    { elementCode: 'SMS',   elementCodeConnectableBefore: ['EMAIL', 'SMS'] },
    { elementCode: 'EMAIL', elementCodeConnectableBefore: ['EMAIL', 'SMS'] },
  ];

  ngAfterViewInit() {
    this.diagramService.setConfig = {
      storeDataDefine: { ...storeDataDefault },
      svgContainer: this.svgContainer(),
      nodesContainer: this.nodesContainer(),
      viewContainerRef: this.vcr(),
      nodeComponentType: MyNodeComponent,
    };

    this.canvasService.loadElements(this.elements, { x: 200, y: 40 }, this.rules);
    this.diagramService.configXYElements(this.elements);
    this.diagramService.renderNodes(this.elements);
  }
}

Ví dụ 2 — Diagram ngang (horizontal) với drop zone

import { Component, AfterViewInit, ElementRef, ViewContainerRef, inject, viewChild } from '@angular/core';
import {
  LibsUiDiagramDrawService,
  LibsUiDiagramDrawCanvasService,
  storeDataDefault,
  IDiagramElement,
  IDiagramElementBranch,
  IRuleConnectable,
  TDropZoneCanDropFn,
} from '@libs-ui/services-diagram-draw';
import { MyNodeComponent } from './my-node.component';

@Component({
  selector: 'app-diagram-horizontal',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div style="position: relative; overflow: auto; width: 100%; height: 100vh;">
      <svg #svgContainer style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none;">
        <path style="fill: none; stroke: #9ca2ad; stroke-width: 1;"></path>
      </svg>
      <div #nodesContainer style="position: relative; width: 100%; height: 100%;"></div>
      <ng-container #vcr></ng-container>
    </div>
  `,
})
export class DiagramHorizontalComponent implements AfterViewInit {
  private readonly svgContainer = viewChild<ElementRef<HTMLDivElement>>('svgContainer');
  private readonly nodesContainer = viewChild<ElementRef<HTMLDivElement>>('nodesContainer');
  private readonly vcr = viewChild('vcr', { read: ViewContainerRef });

  private readonly diagramService = inject(LibsUiDiagramDrawService);
  private readonly canvasService = inject(LibsUiDiagramDrawCanvasService);

  private readonly elements: IDiagramElement[] = [
    { id: '1', code: 'TRIGGER', name: 'Trigger 1', specific_width: 165, specific_height: 48, next_id: '2', pre_id: '' },
    { id: '2', code: 'ACTION',  name: 'Action 1',  specific_width: 165, specific_height: 48, next_id: '3', pre_id: '1' },
    { id: '3', code: 'EXIT',    element_type: 'EXIT', name: 'Kết thúc', specific_width: 165, specific_height: 48, pre_id: '2' },
  ];

  private readonly rules: IRuleConnectable[] = [
    { elementCode: 'ACTION', elementCodeConnectableBefore: ['TRIGGER', 'ACTION'] },
  ];

  ngAfterViewInit() {
    // Chuyển sang chế độ ngang
    this.diagramService.orientation.set('horizontal');

    this.diagramService.setConfig = {
      storeDataDefine: { ...storeDataDefault },
      svgContainer: this.svgContainer(),
      nodesContainer: this.nodesContainer(),
      viewContainerRef: this.vcr(),
      nodeComponentType: MyNodeComponent,
    };

    this.canvasService.loadElements(this.elements, { x: 40, y: 200 }, this.rules);
    this.diagramService.configXYElements(this.elements);
    this.diagramService.renderNodes(this.elements);
    this.renderDropZones();
  }

  private renderDropZones() {
    const canDropFn: TDropZoneCanDropFn = (_before, current, _after, _rules) => {
      // Trả về true = ẨN drop zone, false = HIỂN THỊ
      return current?.code === 'EXIT';
    };

    this.diagramService.renderDropZones(
      this.canvasService.FlatElements,
      (element: IDiagramElement, branch: IDiagramElementBranch | undefined, dragData: string) => {
        const data: IDiagramElement = JSON.parse(dragData);
        this.canvasService.addElement(element, branch, data);
        this.diagramService.configXYElements(this.elements);
        this.diagramService.renderNodes(this.elements);
        this.renderDropZones();
      },
      canDropFn,
      this.rules,
    );
  }
}

Ví dụ 3 — Diagram phân nhánh (WORKFLOW element)

const elementsWithBranch: IDiagramElement[] = [
  {
    id: '1',
    code: 'START',
    name: 'Bắt đầu',
    specific_width: 165,
    specific_height: 48,
    next_id: '2',
    pre_id: '',
  },
  {
    id: '2',
    code: 'CONDITION',
    element_type: 'WORKFLOW',
    name: 'Điều kiện 1',
    specific_width: 165,
    specific_height: 48,
    next_id: '5',
    pre_id: '1',
    branches: [
      {
        code: 'YES',
        label: 'Đúng',
        labelBgColor: '#dcfce7',
        labelColor: '#15803d',
        elements: [
          { id: '3', code: 'EMAIL', name: 'Email 1', specific_width: 165, specific_height: 48, next_id: '', pre_id: '2' },
        ],
      },
      {
        code: 'NO',
        label: 'Sai',
        labelBgColor: '#fee2e2',
        labelColor: '#dc2626',
        elements: [
          { id: '4', code: 'SMS', name: 'SMS 1', specific_width: 165, specific_height: 48, next_id: '', pre_id: '2' },
        ],
      },
    ],
  },
  {
    id: '5',
    code: 'EXIT',
    element_type: 'EXIT',
    name: 'Kết thúc',
    specific_width: 165,
    specific_height: 48,
    pre_id: '2',
  },
];

Ví dụ 4 — Virtualization cho diagram lớn

ngAfterViewInit() {
  this.diagramService.setConfig = {
    storeDataDefine: { ...storeDataDefault },
    svgContainer: this.svgContainer(),
    nodesContainer: this.nodesContainer(),
    viewContainerRef: this.vcr(),
    nodeComponentType: MyNodeComponent,
    useVirtualizationTemplate: true,  // Bật virtualization
  };

  this.diagramService.configXYElements(this.elements);
  this.diagramService.renderNodes(this.elements);

  // Cập nhật viewport khi scroll
  const container = this.nodesContainer()?.nativeElement.parentElement;
  container?.addEventListener('scroll', () => {
    this.diagramService.updateViewport({
      x: container.scrollLeft,
      y: container.scrollTop,
      width: container.clientWidth,
      height: container.clientHeight,
    });
  });
}

Ví dụ 5 — Navigation line (kết nối tự do)

import {
  LibsUiDiagramDrawDirectionService,
  LibsUiDiagramDrawCanvasService,
  LibsUiDiagramDrawService,
  INavigationLineStyleConfig,
} from '@libs-ui/services-diagram-draw';

// Trong component:
private readonly directionService = inject(LibsUiDiagramDrawDirectionService);
private readonly canvasService = inject(LibsUiDiagramDrawCanvasService);
private readonly diagramService = inject(LibsUiDiagramDrawService);

// Thêm điều hướng từ element tới target
addDirection(sourceElement: IDiagramElement, targetId: string) {
  const flatElements = this.canvasService.FlatElements;
  this.directionService.addElementDirection(targetId, sourceElement, flatElements);
}

// Build dữ liệu navigation line để render qua draw-line directive
buildNavigationLines() {
  const styleConfig: INavigationLineStyleConfig = {
    lineStyle: { color: '#6366f1', width: 1.5 },
    arrowStyle: { color: '#6366f1', size: 8 },
    endpointGap: 8,
  };
  const navData = this.diagramService.buildNavigationData(
    this.canvasService.FlatElements,
    styleConfig,
  );
  // Truyền navData vào drawLineFunctionControl.setData(navData)
}

Methods — LibsUiDiagramDrawService

| Method | Signature | Mô tả | |---|---|---| | set setConfig | (config: ICustomCanvasConfig): void | Đăng ký config (container refs, component types). Phải gọi trước mọi method khác. | | set setConfigCanvas | (config: Partial<ICanvasConfig>): void | Ghi đè tham số canvas config (margin, kích thước, màu SVG). | | 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. | | renderNodes | (elements: IDiagramElement[]): void | Xóa node cũ, render component động, branch labels và arrow indicators. | | clearNodes | (): void | Destroy tất cả ComponentRef, xóa .diagram-node-dynamic, .branch-label, .node-arrow-indicator khỏi DOM. | | renderDropZones | (flatElements, onDrop, canDropFn, rules): void | Render drop zone "+" tại các vị trí cho phép kéo-thả. | | clearDropZones | (): void | Xóa tất cả .diagram-drop-zone khỏi nodesContainer. | | 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. | | buildNavigationData | (flatElements: IDiagramElement[], styleConfig?: INavigationLineStyleConfig): IDrawLineDataInput[] | Build dữ liệu navigation line từ next_other_id để render qua draw-line directive. |

Chi tiết renderDropZones

renderDropZones(
  flatElements: IDiagramElement[],
  onDrop: (element: IDiagramElement, branch: IDiagramElementBranch | undefined, dragData: string) => void,
  canDropFn: TDropZoneCanDropFn,
  ruleConnectableElements: IRuleConnectable[],
): void

Lưu ý: canDropFn trả về trueẨN drop zone; falseHIỂN THỊ drop zone (ngược với convention thông thường).


Methods — LibsUiDiagramDrawCanvasService

| Method | Signature | Mô tả | |---|---|---| | 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. | | addElement | (itemDrop, branchDrop, dataDrop, branchChoice?): void | Thêm element vào sau itemDrop, tự động tạo EXIT node cho WORKFLOW. | | removeOrChangeElement | (elementRemove, elementChange, branchChoose?): Promise<void> | Xóa hoặc thay thế element, tự động thêm EXIT khi cần. | | deleteChangeElementAndInsertBranchKeep | (elementRemove, elementChange, branchKeepElement, branchChoice?): Promise<void> | Xóa element và giữ nguyên nhánh chỉ định. | | 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. | | checkRuleDragDrop | (before, current, after, rules): boolean | Kiểm tra rule kết nối trước khi cho phép drop. | | checkExistNameElement | (name: string, flatElements: IDiagramElement[]): boolean | Kiểm tra tên element đã tồn tại chưa (case-insensitive). | | get FlatElements | IDiagramElement[] | Trả về flat list của tất cả element (bao gồm element trong branches). | | set Elements | (elements: IDiagramElement[]): void | Gán lại root elements array. | | get Elements | IDiagramElement[] | Lấy root elements array. | | set HandlerFunction | (fn: IHandlerFunction): void | Đăng ký callback tạo default config branches cho WORKFLOW element. |


Methods — LibsUiDiagramDrawDirectionService

| Method | Signature | Mô tả | |---|---|---| | buildElementConnectTo | (element, flatElements, branch?, checkRule?): IDiagramElement[] | Build danh sách element có thể kết nối tới. | | 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_idpre_other_id). | | addBranchDirection | (targetId, element, branch, flatElements): void | Thêm điều hướng từ branch của WORKFLOW element tới target. | | editDirection | (flatElements, targetId, element, branch?): void | Thay đổi điều hướng hiện tại sang target mới. | | deleteElementsBehindElementConnectTo | (element, flatElements): void | Xóa tất cả element phía sau khi element chuyển sang điều hướng. | | deleteElementsBehindBranchConnectTo | (branch: IDiagramElementBranch): void | Xóa tất cả element trong branch khi branch chuyển sang điều hướng. | | set ElementDirection | (element: IDiagramElement | undefined): void | Lưu element đang chọn điều hướng. | | get ElementDirection | IDiagramElement | undefined | Lấy element đang chọn điều hướng. | | set BranchDirection | (branch: { branch, index } | undefined): void | Lưu branch đang chọn điều hướng. | | get OnViewDirection | Subject<{ id: string }> | Observable phát khi cần highlight element đang được xem qua điều hướng. |


Exported Constants

| Export | Type | Mô tả | |---|---|---| | canvasConfigReadonly | Signal<ICanvasConfig> (readonly) | Signal đọc canvas config global — dùng trong component mà không cần inject service. | | storeDataDefault | IDiagramStoreData | Giá trị mặc định cho storeDataDefine trong ICustomCanvasConfig. |


Canvas Config (ICanvasConfig)

Tùy chỉnh qua set setConfigCanvas:

| Property | Default | Mô tả | |---|---|---| | ELEMENT_WIDTH_DEFAULT_BRANCH_WHEN_NOT_ELEMENT | 60 | Chiều rộng branch rỗng (không có element) | | ELEMENT_MARGIN_DEFAULT | 60 | Khoảng cách dọc giữa các element (vertical) hoặc ngang (horizontal) | | ELEMENT_MARGIN_DEFAULT_BRANCH | 16 | Khoảng cách từ element có nhánh đến đường rẽ nhánh | | 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) | | DEFAULT_BRANCH_WHEN_NO_ELEMENT | 100 | Chiều cao/rộng branch khi không có element | | ELEMENT_HEIGHT_CURVE | 10 | Độ cong của SVG connector | | ELEMENT_SVG_STROKE_COLOR | '9ca2ad' | Màu đường kẻ SVG (không có ký tự # đứng trước) | | ELEMENT_SVG_STROKE_WIDTH | '1' | Độ dày đường kẻ SVG | | ELEMENT_MARGIN_DEFAULT_BRANCH_TO_ELEMENT_FIRST | 78 | Khoảng cách từ element cha đến element đầu tiên trong branch | | DISTANCE_TO_WAIT | 30 | Khoảng cách từ element chính đến nodeOtherConfig | | WAIT_TO_ELEMENT | 50 | Khoảng cách từ nodeOtherConfig đến element kế tiếp | | ELEMENT_WAIT_DEFAULT | 28 | Chiều cao mặc định của nodeOtherConfig | | DISTANCE_WAIT_TO_NEXT_LINE | 4 | Khoảng cách từ nodeOtherConfig đến đường nối tiếp theo | | TYPE_ELEMENT_EXIT | 'EXIT' | Giá trị code/element_type cho element kết thúc (không có drop zone bên dưới) | | TYPE_ELEMENT_WORKFLOW | 'WORKFLOW' | Giá trị element_type cho element có branches | | WIDTH_ELEMENT_DEFAULT | 165 | Chiều rộng mặc định node (dùng khi specific_width không được set) | | HEIGHT_ELEMENT_DEFAULT | 48 | Chiều cao mặc định node (dùng khi specific_height không được set) | | ADD_ICON_DIAMETER | 24 | Đường kính của icon "+" trong drop zone |

Ví dụ tùy chỉnh:

this.diagramService.setConfigCanvas = {
  ELEMENT_MARGIN_DEFAULT: 80,
  ELEMENT_SVG_STROKE_COLOR: '6366f1',
  WIDTH_ELEMENT_DEFAULT: 200,
  HEIGHT_ELEMENT_DEFAULT: 56,
};

Types & Interfaces

import type {
  IDiagramElement,
  IDiagramElementBranch,
  ICanvasConfig,
  ICustomCanvasConfig,
  IDiagramStoreData,
  IDropZoneIndicatorStyle,
  IRuleConnectable,
  IHandlerFunction,
  TDropZoneCanDropFn,
  INavigationLineStyleConfig,
} from '@libs-ui/services-diagram-draw';

IDiagramElement

interface IDiagramElement {
  id?: string;                  // Unique identifier
  code?: string;                // Loại element (VD: 'EMAIL', 'SMS', 'EXIT', 'CONDITION')
  name?: string;                // Tên hiển thị
  element_type?: string;        // 'WORKFLOW' cho element có nhánh, 'EXIT' cho kết thúc
  branches?: IDiagramElementBranch[];   // Danh sách nhánh (chỉ có khi element_type = 'WORKFLOW')
  next_id?: string;             // ID element kế tiếp trong luồng chính
  pre_id?: string;              // ID element trước trong luồng chính
  next_other_id?: string[];     // ID các element được điều hướng đến (kết nối tự do)
  pre_other_id?: string[];      // ID các element điều hướng đến element này
  nodeOtherConfig?: IDiagramElement;    // Node phụ (VD: wait node, delay config)
  specific_x?: number;          // Vị trí X do người dùng/service set trước khi tính toán
  specific_y?: number;          // Vị trí Y do người dùng/service set trước khi tính toán
  specific_width?: number;      // Chiều rộng node (ghi đè WIDTH_ELEMENT_DEFAULT)
  specific_height?: number;     // Chiều cao node (ghi đè HEIGHT_ELEMENT_DEFAULT)
  translateX?: number;          // Tọa độ X cuối cùng sau khi tính toán (computed)
  translateY?: number;          // Tọa độ Y cuối cùng sau khi tính toán (computed)
  attributeSvgD?: string;       // SVG path string cho connector
  subCodeOfElement?: string;    // Sub-type code dùng khi element có điều hướng
  position_end?: number;        // Vị trí cuối của đường nối (computed)
  disable?: boolean;            // Ẩn/vô hiệu hóa element
  specific_counter_current_code?: number; // Counter đếm số lượng element cùng code
}

IDiagramElementBranch

interface IDiagramElementBranch {
  code?: string;                        // Định danh nhánh (VD: 'YES', 'NO')
  label?: string;                       // Nhãn hiển thị trên nhánh (VD: 'Đúng', 'Sai')
  labelBgColor?: string;                // Màu nền nhãn (VD: '#dcfce7')
  labelColor?: string;                  // Màu chữ nhãn (VD: '#15803d')
  labelClassName?: string;              // CSS class cho nhãn (mặc định: 'libs-ui-font-h7r')
  elements: IDiagramElement[];          // Danh sách element trong nhánh
  onTheSide?: 'above' | 'under' | 'center'; // Vị trí nhãn tương đối với nhánh
  specific_start_branch_x?: number;    // Tọa độ X điểm đầu đường nhánh (computed)
  specific_start_branch_y?: number;    // Tọa độ Y điểm đầu đường nhánh (computed)
  next_other_id?: string[];            // ID các element được điều hướng từ nhánh này
  isDirection?: boolean;               // Nhánh đang ở chế độ điều hướng
  nodeOtherConfig?: IDiagramElement;   // Node phụ tại điểm rẽ nhánh
}

ICustomCanvasConfig

interface ICustomCanvasConfig {
  storeDataDefine: IDiagramStoreData;               // Lưu trữ tọa độ max — dùng storeDataDefault
  svgContainer?: ElementRef<HTMLDivElement>;         // Ref đến SVG container để vẽ đường nối
  nodesContainer?: ElementRef<HTMLDivElement>;       // Ref đến div chứa node component
  viewContainerRef?: ViewContainerRef;              // ViewContainerRef để tạo component động
  nodeComponentType?: Type<any>;                    // Component type cho main node
  nodeOtherConfigComponentType?: Type<any>;         // Component type cho nodeOtherConfig
  useVirtualizationTemplate?: boolean;              // Bật virtualization (mặc định: false)
  dropZoneStyle?: IDropZoneIndicatorStyle;          // Tùy biến giao diện indicator "+"
}

IRuleConnectable

interface IRuleConnectable {
  elementCode: string;                          // Code của element cần kiểm tra
  elementCodeConnectableBefore: string[];       // Danh sách code được phép đứng trước
}

IHandlerFunction

interface IHandlerFunction {
  getConfigDefaultBranches(code: string, dataDefault: IDiagramElement): IDiagramElement;
}

TDropZoneCanDropFn

type TDropZoneCanDropFn = (
  beforeElementDrag: IDiagramElement | undefined,    // Element trước vị trí drop
  currentElementDrag: IDiagramElement | undefined,   // Element tại vị trí drop
  afterElementDrag: IDiagramElement | undefined,     // Element sau vị trí drop
  ruleConnectableElements: IRuleConnectable[],
) => boolean;  // true = ẨN drop zone, false = HIỂN THỊ

INavigationLineStyleConfig

interface INavigationLineStyleConfig {
  lineStyle?: ILineStyle;           // Style đường kẻ (color, width, dash)
  arrowStyle?: IArrowStyle;         // Style mũi tên (color, size)
  endpointGap?: number;             // Khoảng cách từ cạnh node đến endpoint (mặc định: 8)
  modeResolver?: (start: { x, y }, end: { x, y }) => TYPE_MODE; // Override chọn kiểu đường
}

Tùy biến indicator "+" (IDropZoneIndicatorStyle)

Truyền vào dropZoneStyle của ICustomCanvasConfig để kiểm soát giao diện nút "+" trong drop zone.

Trạng thái bình thường

| Thuộc tính | Type | Mặc định | Mô tả | |---|---|---|---| | backgroundColor | string | '#3b82f6' | Màu nền của nút "+" | | iconColor | string | '#ffffff' | Màu dấu "+" bên trong | | borderRadius | string | '50%' | Bo góc — '50%' tròn, '6px' vuông | | opacity | number | 0.95 | Độ mờ ở trạng thái bình thường | | renderIndicator | (size: number) => string | — | Hàm trả về HTML tùy ý, ghi đè toàn bộ indicator |

Trạng thái hover/drag-enter

| Thuộc tính | Type | Mặc định | Mô tả | |---|---|---|---| | hoverBackgroundColor | string | '#eef4ff' | Màu nền vùng highlight khi kéo vào | | hoverBorderColor | string | 'rgba(59, 130, 246, 0.7)' | Màu viền vùng highlight | | hoverBorderStyle | string | 'dashed' | Kiểu viền: 'dashed', 'solid', 'dotted' | | hoverBorderRadius | number | 10 | Border-radius (px) của vùng highlight | | renderHoverIndicator | () => string | — | Hàm trả về HTML inject vào bên trong vùng highlight |

Ví dụ tùy biến màu sắc:

this.diagramService.setConfig = {
  storeDataDefine: { ...storeDataDefault },
  svgContainer: this.svgContainer(),
  nodesContainer: this.nodesContainer(),
  viewContainerRef: this.vcr(),
  nodeComponentType: MyNodeComponent,
  dropZoneStyle: {
    backgroundColor: '#10b981',
    iconColor: '#ffffff',
    borderRadius: '4px',
    opacity: 1,
    hoverBackgroundColor: '#f0fdf4',
    hoverBorderColor: 'rgba(16, 185, 129, 0.8)',
    hoverBorderStyle: 'dashed',
    hoverBorderRadius: 8,
  },
};

Ví dụ render indicator hoàn toàn tùy chỉnh:

this.diagramService.setConfig = {
  storeDataDefine: { ...storeDataDefault },
  svgContainer: this.svgContainer(),
  nodesContainer: this.nodesContainer(),
  viewContainerRef: this.vcr(),
  nodeComponentType: MyNodeComponent,
  dropZoneStyle: {
    renderIndicator: (size: number) => `
      <div style="
        width: ${size}px; height: ${size}px;
        background: #f59e0b; border-radius: 6px;
        display: flex; align-items: center; justify-content: center;
      ">
        <svg viewBox="0 0 24 24" fill="none" width="14" height="14">
          <path d="M12 5v14M5 12h14" stroke="white" stroke-width="2.5" stroke-linecap="round"/>
        </svg>
      </div>
    `,
    renderHoverIndicator: () => `
      <span style="color: #f59e0b; font-size: 11px; font-weight: 600;">
        + Thêm vào đây
      </span>
    `,
    hoverBackgroundColor: '#fffbeb',
    hoverBorderColor: 'rgba(245, 158, 11, 0.6)',
    hoverBorderStyle: 'dashed',
    hoverBorderRadius: 12,
  },
};

Lưu ý quan trọng

⚠️ 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.

⚠️ 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.

⚠️ renderNodes() tự gọi clearNodes() trước: Không cần gọi thủ công trước khi render lại.

⚠️ 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ừ".

⚠️ 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').

⚠️ 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ị.

⚠️ 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.

⚠️ 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 độ.


Demo

npx nx serve core-ui

Truy cập: http://localhost:4500/services/diagram-draw