npm package: @libs-ui/components-draw-line
Description: > 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.
Latest version: 0.2.356-23

# @libs-ui/components-draw-line

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

## Giới thiệu

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

### Tính năng

- ✅ 5 chế độ vẽ đường: quart-in, horizontal, vertical, horizontal-single-curve, vertical-single-curve
- ✅ Tự động né vật cản (obstacle avoidance) với obstacleRect
- ✅ Kéo thả mũi tên endpoint bằng chuột (drag-and-drop)
- ✅ 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
- ✅ Tùy chỉnh style đường (stroke, dash, width), mũi tên, và circle
- ✅ Tự động tính toán viewBox cho SVG
- ✅ Hỗ trợ separatedPoints cho đường nối phức tạp
- ✅ Debug mode hiển thị rect/circle trực quan
- ✅ Chạy ngoài NgZone để tối ưu hiệu năng

## Khi nào sử dụng

- Khi cần vẽ đường nối giữa các node trong flowchart, diagram, workflow editor
- Khi cần hiển thị mối quan hệ giữa các phần tử trên giao diện
- 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ử
- Khi cần vẽ đường cong phức tạp né tránh các vật cản trên canvas
- Phù hợp cho automation builder, mind map, ERD diagram, pipeline editor

## Cài đặt

```bash
# npm
npm install @libs-ui/components-draw-line

# yarn
yarn add @libs-ui/components-draw-line
```

## Import

```typescript
import {
  LibsUiComponentsDrawLineDirective,
  IMoDrawLineFunctionControl,
  IDrawLineDataInput,
  IReachablePointRange,
  IViewBoxConfig,
  TYPE_MODE,
} from '@libs-ui/components-draw-line';

@Component({
  standalone: true,
  imports: [LibsUiComponentsDrawLineDirective],
  // ...
})
export class YourComponent {}
```

## Ví dụ

### Basic — Vẽ đường nối đơn giản

```html
<div
  LibsUiComponentsDrawLineDirective
  (outDrawLineFunctionControl)="onFunctionControl($event)">
</div>
```

```typescript
import { LibsUiComponentsDrawLineDirective, IMoDrawLineFunctionControl, IDrawLineDataInput } from '@libs-ui/components-draw-line';

@Component({
  standalone: true,
  imports: [LibsUiComponentsDrawLineDirective],
  // ...
})
export class BasicDrawLineComponent {
  private fnControl!: IMoDrawLineFunctionControl;

  onFunctionControl(ctrl: IMoDrawLineFunctionControl): void {
    this.fnControl = ctrl;

    const lines: IDrawLineDataInput[] = [
      {
        id: 'line-1',
        mode: 'quart-in',
        points: {
          start: { x: 100, y: 100 },
          end: { x: 300, y: 200 },
        },
      },
    ];

    this.fnControl.setData(lines);
  }
}
```

### With Controls — Sử dụng FunctionsControl

```typescript
export class ControlDrawLineComponent {
  private fnControl!: IMoDrawLineFunctionControl;

  onFunctionControl(ctrl: IMoDrawLineFunctionControl): void {
    this.fnControl = ctrl;
  }

  addLine(): void {
    this.fnControl.setData([
      {
        id: 'line-horizontal',
        mode: 'horizontal',
        points: {
          start: { x: 50, y: 150 },
          end: { x: 400, y: 300 },
        },
        lineStyle: {
          stroke: '#3B82F6',
          strokeWidth: '2px',
          curve: 15,
        },
        arrowStyle: {
          fill: '#3B82F6',
          stroke: '#3B82F6',
          width: 5,
          height: 8,
        },
      },
    ]);
  }

  removeLine(): void {
    this.fnControl.removeLine('line-horizontal');
  }

  updateViewBox(): void {
    this.fnControl.updateViewBox({
      minX: 0,
      minY: 0,
      width: 800,
      height: 600,
    });
  }
}
```

### Obstacle Avoidance — Né vật cản

```typescript
export class ObstacleDrawLineComponent {
  private fnControl!: IMoDrawLineFunctionControl;

  onFunctionControl(ctrl: IMoDrawLineFunctionControl): void {
    this.fnControl = ctrl;

    this.fnControl.setData([
      {
        id: 'line-obstacle',
        mode: 'horizontal',
        points: {
          start: { x: 50, y: 100 },
          end: { x: 500, y: 100 },
          obstacleRect: [
            { id: 'box-1', x: 200, y: 50, width: 100, height: 100 },
          ],
        },
        lineStyle: { stroke: '#EF4444', strokeWidth: '2px' },
      },
    ]);
  }
}
```

### Reachable Point Range — Vùng có thể kết nối

```html
<div
  LibsUiComponentsDrawLineDirective
  (outDrawLineFunctionControl)="onFunctionControl($event)"
  (outConnected)="onConnected($event)">
</div>
```

```typescript
export class ReachableDrawLineComponent {
  private fnControl!: IMoDrawLineFunctionControl;

  onFunctionControl(ctrl: IMoDrawLineFunctionControl): void {
    this.fnControl = ctrl;

    this.fnControl.setReachablePointRange([
      {
        id: 'target-1',
        x: 400,
        y: 200,
        style: { r: 6, fill: '#10B981', stroke: '#059669', strokeWidth: '2px' },
      },
    ]);

    this.fnControl.setData([
      {
        id: 'drag-line',
        mode: 'quart-in',
        endCircle: true,
        endCircleStyle: { r: 4, fill: '#3B82F6' },
        points: {
          start: { x: 100, y: 200 },
          end: { x: 250, y: 200 },
        },
      },
    ]);
  }

  onConnected(event: { dataLine: any; dataReachablePointRange: any }): void {
    console.log('Kết nối thành công:', event);
  }
}
```

## API

### [LibsUiComponentsDrawLineDirective]

#### Inputs

| Property          | Type              | Default      | Description                                      |
| ----------------- | ----------------- | ------------ | ------------------------------------------------ |
| `[drawRectDebug]` | `boolean`         | `false`      | Hiển thị debug rect/circle cho obstacle trên SVG |
| `[svgElement]`    | `SVGSVGElement`   | auto-created | SVG element để vẽ, tự tạo nếu không truyền vào  |
| `[viewBoxConfig]` | `IViewBoxConfig`  | `undefined`  | Cấu hình viewBox cho SVG element                 |

#### Outputs

| Property                       | Type                                                        | Description                                                    |
| ------------------------------ | ----------------------------------------------------------- | -------------------------------------------------------------- |
| `(outConnected)`               | `{ dataLine: IDrawLineDataInput, dataReachablePointRange: IReachablePointRange }` | Emit khi kéo mũi tên đến reachable point thành công |
| `(outDrawLineFunctionControl)` | `IMoDrawLineFunctionControl`                                | Emit object chứa các methods điều khiển draw-line              |

#### FunctionsControl Methods

| Method                     | Signature                                                              | Description                                           |
| -------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------- |
| `removeLine`               | `(id: string, points?: Array<IPoints>) => void`                       | Xóa đường nối theo id, có thể chỉ xóa một số points  |
| `removeReachablePointRange`| `(ids: Array<string>) => void`                                        | Xóa các reachable point range theo danh sách id       |
| `setData`                  | `(data: Array<IDrawLineDataInput>) => void`                           | Thêm dữ liệu đường nối để vẽ                         |
| `setReachablePointRange`   | `(data: Array<IReachablePointRange>) => void`                         | Thêm các vùng có thể kết nối (target points)          |
| `updateViewBox`            | `(viewBoxConfig?: IViewBoxConfig) => void`                            | Cập nhật viewBox cho SVG element                      |

## Types & Interfaces

```typescript
/**
 * Chế độ vẽ đường
 */
export type TYPE_MODE =
  | 'quart-in'
  | 'vertical'
  | 'vertical-single-curve'
  | 'horizontal'
  | 'horizontal-single-curve';

/**
 * Hướng mũi tên
 */
export type TYPE_DIRECTION = 'right' | 'left' | 'top' | 'bottom';

/**
 * Dữ liệu đầu vào cho mỗi đường nối
 */
export interface IDrawLineDataInput {
  id: string;
  points: IPoints;
  mode: TYPE_MODE;
  idConnected?: string;
  lineStyle?: ILineStyle;
  arrowStyle?: IArrowStyle;
  arrowDirection?: TYPE_DIRECTION;
  ignoreDrawArrow?: boolean;
  startCircle?: boolean;
  startCircleStyle?: ICircleStyle;
  endCircle?: boolean;
  endCircleStyle?: ICircleStyle;
  isRemove?: boolean;
  ref?: IDrawLineDataInput;
  startEndMode?: 'right-left' | 'bottom-top' | 'bottom-left';
}

/**
 * Tọa độ điểm đầu và điểm cuối
 */
export interface IPoints {
  start: IPoint;
  end: IPoint;
  initialized?: boolean;
  obstacleRect?: Array<IRect>;
  separatedPoints?: Array<IPoints>;
  pathElement?: SVGPathElement;
  arrowElement?: SVGPolylineElement;
  circleStartElement?: SVGCircleElement;
  circleEndElement?: SVGCircleElement;
  onDestroyEvent?: Subject<void>;
  mode?: TYPE_MODE;
  id?: string;
  name?: string;
}

/**
 * Tọa độ một điểm
 */
export interface IPoint {
  x: number;
  y: number;
}

/**
 * Vùng có thể kết nối
 */
export interface IReachablePointRange {
  id: string;
  x: number;
  y: number;
  style: ICircleStyle;
  idConnected?: string;
  ref?: IReachablePointRange;
  element?: SVGCircleElement;
  onDestroyEvent?: Subject<void>;
}

/**
 * Cấu hình viewBox cho SVG
 */
export interface IViewBoxConfig {
  minX?: number;
  minY?: number;
  width?: number;
  height?: number;
  ignoreViewBox?: boolean;
  marginBetweenElement?: number;
}

/**
 * Style cho đường nối
 */
export interface ILineStyle {
  stroke?: string;
  strokeDasharray?: string;
  strokeWidth?: string;
  fill?: string;
  curve?: number;
  distancePoint?: number;
}

/**
 * Style cho mũi tên
 */
export interface IArrowStyle {
  stroke?: string;
  strokeWidth?: string;
  width?: number;
  height?: number;
  fill?: string;
}

/**
 * Style cho circle (điểm tròn)
 */
export interface ICircleStyle {
  stroke?: string;
  strokeWidth?: string;
  fill?: string;
  r?: number;
}

/**
 * Vùng chữ nhật (obstacle / debug rect)
 */
export interface IRect {
  id?: string;
  x: number;
  y: number;
  width: number;
  height: number;
  gapXObstacleRect?: number;
  gapYObstacleRect?: number;
}

/**
 * Object điều khiển draw-line
 */
export interface IMoDrawLineFunctionControl {
  setData: (data: Array<IDrawLineDataInput>) => void;
  removeLine: (id: string, points?: Array<IPoints>) => void;
  setReachablePointRange: (data: Array<IReachablePointRange>) => void;
  removeReachablePointRange: (ids: Array<string>) => void;
  updateViewBox: (viewBoxConfig?: IViewBoxConfig) => void;
}
```

## Công nghệ

| Technology | Version | Purpose               |
| ---------- | ------- | --------------------- |
| Angular    | ≥18.0.0 | Framework             |
| @libs-ui/utils | 0.2.355-14 | Utilities (cloneDeep, getDragEventByElement, checkMouseOverInContainer) |
| RxJS       | ~7.8.0  | Reactive programming  |
| SVG        | -       | Rendering đường nối   |

## Demo

```bash
npx nx serve core-ui
```

Truy cập: `http://localhost:4500/draw-line`

## Kiểm thử

```bash
# Chạy tests
nx test draw-line

# Lint
nx lint draw-line

# Coverage
nx test draw-line --coverage

# Watch mode
nx test draw-line --watch
```

## License

MIT