# ngx-interactive-org-chart

> Modern Angular organizational chart component with interactive pan/zoom functionality

[![npm version](https://img.shields.io/npm/v/ngx-interactive-org-chart)](https://www.npmjs.com/package/ngx-interactive-org-chart)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Downloads](https://img.shields.io/npm/dm/ngx-interactive-org-chart.svg)](https://www.npmjs.com/package/ngx-interactive-org-chart)

A beautiful, interactive organizational chart component for Angular applications. Built with modern Angular features and designed for ease of use and customization.

## ✨ Features

- 🎯 **Interactive Pan & Zoom** - Smooth navigation with mouse/touch
- 🗺️ **Mini Map Navigation** - Bird's-eye view with drag navigation and real-time viewport tracking
- 🌳 **Hierarchical Layout** - Perfect for organizational structures
- 🎨 **Fully Themable** - Complete theme system including mini map customization with CSS variable support
- 📱 **Mobile Friendly** - Touch gestures support
- ⚡ **High Performance** - Optimized rendering with canvas-based mini map
- 🔍 **Searchable Nodes** - Easily find nodes in large charts
- 🧭 **Focus & Highlight** - Quickly navigate to specific nodes
- 📊 **Custom Node Templates** - Use Angular templates for nodes
- 🖱️ **Drag & Drop** - Reorganize nodes with drag and drop support
- 🎯 **Custom Drag Handles** - Use custom templates for drag handles
- 📈 **Dynamic Data Binding** - Reactive updates with Angular signals
- 📦 **Tree Shakable** - Import only what you need
- 🔄 **Collapsible Nodes** - Expand/collapse functionality
- 🌐 **RTL Support** - Right-to-left text direction
- 🌓 **Dark Mode Ready** - Automatic theme detection and CSS variable resolution
- 🧩 **Modular Design** - Standalone component for easy integration
- 🔧 **TypeScript Support** - Full type definitions included
- 🛠️ **Easy Setup** - Minimal configuration required
- 🎪 **Angular 20+** - Built with latest Angular features
- 🆓 **100% Free** - Open source MIT license

## 📋 Version Compatibility

| ngx-interactive-org-chart | Angular Version | Notes                            |
| ------------------------- | --------------- | -------------------------------- |
| 1.1.4                     | Angular 19      | Stable release                   |
| 1.2.x                     | Angular 20+     | Drag & drop, RTL support         |
| 1.3.x                     | Angular 20+     | Mini map, dark mode, performance |

## 🚀 Installation

```bash
npm install ngx-interactive-org-chart
```

### Setup Angular Animations

The component uses Angular animations for smooth transitions. Add the animations module to your `main.ts`:

```typescript
import { bootstrapApplication } from '@angular/platform-browser';
import { provideAnimations } from '@angular/platform-browser/animations';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, {
  providers: [
    provideAnimations(), // Required for ngx-interactive-org-chart
    // ... your other providers
  ],
});
```

Or if you're using NgModules:

```typescript
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

@NgModule({
  imports: [
    BrowserModule,
    BrowserAnimationsModule, // Required for ngx-interactive-org-chart
    // ... your other modules
  ],
  // ...
})
export class AppModule {}
```

## 📖 Usage

### Basic Example

```typescript
import { Component } from '@angular/core';
import {
  NgxInteractiveOrgChart,
  OrgChartNode,
} from 'ngx-interactive-org-chart';

@Component({
  selector: 'app-demo',
  standalone: true,
  imports: [NgxInteractiveOrgChart],
  template: `
    <ngx-interactive-org-chart [data]="orgData" [themeOptions]="themeOptions" />
  `,
})
export class DemoComponent {
  orgData: OrgChartNode = {
    id: 'ceo', // auto generated if not provided
    name: 'John Smith',
    data: {
      // add any additional data properties here to customize the node and use it for displaying different types of nodes
    },
    children: [
      {
        id: 'cto',
        name: 'Jane Doe',
        children: [
          {
            id: 'dev1',
            name: 'Mike Johnson',
          },
        ],
      },
      {
        id: 'cfo',
        name: 'Sarah Wilson',
      },
    ],
  };
}
```

### Data Structure

The component expects hierarchical data in the following format:

```typescript
interface OrgChartNode<T = any> {
  id?: string;
  name?: string;
  data?: T;
  children?: OrgChartNode<T>[];
  collapsed?: boolean;
  hidden?: boolean;
  nodeClass?: string;
}
```

### Component Options

```typescript
interface NgxInteractiveOrgChartTheme {
  node?: {
    background?: string;
    color?: string;
    shadow?: string;
    outlineColor?: string;
    outlineWidth?: string;
    activeOutlineColor?: string;
    highlightShadowColor?: string;
    padding?: string;
    borderRadius?: string;
    activeColor?: string;
    containerSpacing?: string;
    maxWidth?: string;
    minWidth?: string;
    maxHeight?: string;
    minHeight?: string;
    dragOverOutlineColor?: string;
  };
  connector?: {
    color?: string;
    activeColor?: string;
    borderRadius?: string;
    width?: string;
  };
  collapseButton?: {
    size?: string;
    borderColor?: string;
    borderRadius?: string;
    color?: string;
    background?: string;
    hoverColor?: string;
    hoverBackground?: string;
    hoverShadow?: string;
    hoverTransformScale?: string;
    focusOutline?: string;
    countFontSize?: string;
  };
  container?: {
    background?: string;
    border?: string;
  };
}
```

### 🎯 Smart Zoom & Highlighting

The component features intelligent zoom calculation that automatically adjusts to provide optimal viewing of highlighted nodes:

```typescript
// Configure dynamic zoom behavior
<ngx-interactive-org-chart
  [data]="orgData"
  [highlightZoomNodeWidthRatio]="0.4"     // Node takes 40% of container width
  [highlightZoomNodeHeightRatio]="0.5"    // Node takes 50% of container height
  [highlightZoomMinimum]="1.0"            // Never zoom below 100%
/>

// Programmatically highlight nodes
@ViewChild(NgxInteractiveOrgChart) orgChart!: NgxInteractiveOrgChart;

highlightManager() {
  this.orgChart.highlightNode('cto'); // Automatically zooms to optimal level
}
```

## 📐 Layout Options

The component supports both vertical and horizontal layout orientations:

```typescript
// Vertical layout (default)
<ngx-interactive-org-chart
  [data]="orgData"
  layout="vertical"
/>

// Horizontal layout
<ngx-interactive-org-chart
  [data]="orgData"
  layout="horizontal"
/>
```

## 🖱️ Pan Functionality

The component includes built-in pan functionality that allows users to navigate large organizational charts:

```typescript
// Pan functionality is enabled by default
// Users can click and drag to pan around the chart
// Touch gestures are supported on mobile devices

@ViewChild(NgxInteractiveOrgChart) orgChart!: NgxInteractiveOrgChart;

// Programmatically control panning
panToSpecificLocation() {
  // Pan to specific coordinates (x, y, smooth)
  this.orgChart.pan(100, 200, true); // Pans to x: 100, y: 200 with smooth animation
}

// Reset pan to center
resetPanning() {
  this.orgChart.resetPan(); // Centers the chart
}

// Reset both pan and zoom
resetView() {
  this.orgChart.resetPanAndZoom(); // Centers and fits the chart
}
```

**Pan Features:**

- **Mouse Support:** Click and drag to pan around the chart
- **Touch Support:** Touch and drag gestures on mobile devices
- **Smooth Animation:** Animated transitions when panning programmatically
- **Momentum:** Natural momentum-based panning for smooth user experience

## 📋 Component Properties

| Property                       | Type                           | Default      | Description                                                        |
| ------------------------------ | ------------------------------ | ------------ | ------------------------------------------------------------------ |
| `data`                         | `OrgChartNode`                 | required     | The organizational data to display                                 |
| `collapsible`                  | `boolean`                      | `true`       | Enable/disable node collapsing                                     |
| `layout`                       | `'vertical' \| 'horizontal'`   | `'vertical'` | Chart layout orientation                                           |
| `themeOptions`                 | `NgxInteractiveOrgChartTheme`  | `{}`         | Theme configuration options for styling                            |
| `nodeClass`                    | `string`                       | `undefined`  | Custom CSS class applied to all nodes                              |
| `initialZoom`                  | `number`                       | `undefined`  | Initial zoom level                                                 |
| `minZoom`                      | `number`                       | `0.1`        | Minimum zoom level                                                 |
| `maxZoom`                      | `number`                       | `5`          | Maximum zoom level                                                 |
| `zoomSpeed`                    | `number`                       | `1`          | Zoom speed multiplier                                              |
| `zoomDoubleClickSpeed`         | `number`                       | `2`          | Double-click zoom speed multiplier                                 |
| `initialCollapsed`             | `boolean`                      | `false`      | Initial collapsed state for all nodes                              |
| `isRtl`                        | `boolean`                      | `false`      | Right-to-left text direction support                               |
| `displayChildrenCount`         | `boolean`                      | `true`       | Show children count on collapse buttons                            |
| `highlightZoomNodeWidthRatio`  | `number`                       | `0.3`        | Node width ratio relative to viewport when highlighting (0.1-1.0)  |
| `highlightZoomNodeHeightRatio` | `number`                       | `0.4`        | Node height ratio relative to viewport when highlighting (0.1-1.0) |
| `highlightZoomMinimum`         | `number`                       | `0.8`        | Minimum zoom level when highlighting a node                        |
| `draggable`                    | `boolean`                      | `false`      | Enable drag and drop functionality for nodes                       |
| `canDragNode`                  | `(node) => boolean`            | `undefined`  | Predicate function to determine if a node can be dragged           |
| `canDropNode`                  | `(dragged, target) => boolean` | `undefined`  | Predicate function to validate drop operations                     |
| `dragEdgeThreshold`            | `number`                       | `0.1`        | Auto-pan threshold is calculated as 10% of container dimensions    |
| `dragAutoPanSpeed`             | `number`                       | `15`         | Speed of auto-panning in pixels per frame during drag              |

### Component Events

| Event           | Type                                                            | Description                                 |
| --------------- | --------------------------------------------------------------- | ------------------------------------------- |
| `nodeDrop`      | `{ draggedNode: OrgChartNode<T>, targetNode: OrgChartNode<T> }` | Emitted when a node is dropped onto another |
| `nodeDragStart` | `OrgChartNode<T>`                                               | Emitted when a node drag operation starts   |
| `nodeDragEnd`   | `OrgChartNode<T>`                                               | Emitted when a node drag operation ends     |

### Component Methods

The component exposes several useful methods that can be called using a template reference:

```typescript
@Component({
  template: `
    <ngx-interactive-org-chart #orgChart [data]="orgData" />
    <button (click)="orgChart.zoomIn({ by: 10, relative: true })">Zoom In</button>
    <button (click)="orgChart.zoomOut({ by: 10, relative: true })">Zoom Out</button>
    <!-- Reset zoom and pan takes padding param for outer container -->
    <button (click)="orgChart.resetPanAndZoom(50)">Reset</button>
    <button (click)="orgChart.resetPan()">Reset Pan</button>
    <button (click)="orgChart.resetZoom()">Reset Zoom</button>
    <!-- Highlight a specific node by node.id  - if you want to get node id by searching for a node use orgChart.flattenedNodes() it returns a signal of all nodes flattened -->
    <button (click)="orgChart.highlightNode('cto')">Highlight CTO</button>
  `
})
```

| Method                         | Description                              |
| ------------------------------ | ---------------------------------------- |
| `zoomIn(options?)`             | Zooms in the chart                       |
| `zoomOut(options?)`            | Zooms out the chart                      |
| `resetZoom(padding?)`          | Resets zoom to fit content               |
| `resetPan()`                   | Resets pan position to center            |
| `resetPanAndZoom(padding?)`    | Resets both pan and zoom                 |
| `highlightNode(nodeId)`        | Highlights and focuses a specific node   |
| `toggleCollapseAll(collapse?)` | Collapses or expands all nodes           |
| `getScale()`                   | Returns current zoom scale as percentage |

### Dynamic Zoom Configuration

The component supports dynamic zoom calculation when highlighting nodes. This ensures optimal zoom levels based on the node size and viewport dimensions:

```typescript
@Component({
  template: `
    <ngx-interactive-org-chart
      [data]="orgData"
      [highlightZoomNodeWidthRatio]="0.4"
      [highlightZoomNodeHeightRatio]="0.5"
      [highlightZoomMinimum]="1.0"
    />
  `
})
```

**Configuration Options:**

- `highlightZoomNodeWidthRatio` (0.1-1.0): How much of the viewport width the highlighted node should occupy
- `highlightZoomNodeHeightRatio` (0.1-1.0): How much of the viewport height the highlighted node should occupy
- `highlightZoomMinimum`: Minimum zoom level when highlighting (prevents over-zooming out)

**Examples:**

- Small nodes: Use higher ratios (0.4-0.6) for better visibility
- Large nodes: Use lower ratios (0.2-0.3) to avoid excessive zoom
- Mobile devices: Consider using higher minimum zoom for readability

### Custom Node Templates

You can customize how nodes are displayed by providing your own template. Use the `#nodeTemplate` template reference to override the default node appearance:

```typescript
enum TypeEnum {
  Employee = 'employee',
  Contractor = 'contractor',
  Department = 'department',
}

interface ApiResponse {
  readonly id: number;
  readonly name: string;
  readonly title?: string;
  readonly thumbnail?: string;
  readonly type: TypeEnum;
  readonly children?: ApiResponse[];
}

@Component({
  selector: 'app-custom-org-chart',
  standalone: true,
  imports: [NgxInteractiveOrgChart],
  template: `
    <ngx-interactive-org-chart
      [data]="orgChartData() ?? {}"
      [themeOptions]="themeOptions"
      [displayChildrenCount]="false"
    >
      <ng-template #nodeTemplate let-node="node">
        @let nodeData = node?.data;

        @switch (true) {
          @case (
            nodeData.type === dataTypeEnum.Employee ||
            nodeData.type === dataTypeEnum.Contractor
          ) {
            @let isContractor = nodeData.type === dataTypeEnum.Contractor;

            <section class="demo__employee">
              <section class="demo__employee-thumbnail">
                <img [src]="nodeData?.thumbnail" />
              </section>
              <section class="demo__employee-details">
                <span class="demo__employee-details-name">{{
                  nodeData?.name
                }}</span>
                <span class="demo__employee-details-position">{{
                  nodeData?.title
                }}</span>
                @if (isContractor) {
                  <small class="demo__employee-details-type">Contractor</small>
                }
              </section>
            </section>
          }

          @case (nodeData.type === dataTypeEnum.Department) {
            <section class="demo__department">
              <section class="demo__department-details">
                <span class="demo__department-details-name">{{
                  nodeData?.name
                }}</span>
                <span class="demo__department-details-description">
                  {{ node?.descendantsCount }} Members
                </span>
              </section>
            </section>
          }
        }
      </ng-template>
    </ngx-interactive-org-chart>
  `,
  styles: [
    `
      .demo {
        &__employee {
          display: flex;
          gap: 1rem;
          align-items: center;

          &-thumbnail {
            img {
              border-radius: 50%;
              width: 3rem;
              height: 3rem;
              object-fit: cover;
              box-shadow: 0 0 0.25rem rgba(0, 0, 0, 0.3);
            }
          }

          &-details {
            display: flex;
            flex-direction: column;
            gap: 0.25rem;
            align-items: flex-start;

            &-name {
              color: var(--text-primary);
              font-weight: 600;
              font-size: 0.875rem;
            }

            &-position {
              font-size: 0.75rem;
              color: #6c757d;
            }

            &-type {
              font-size: 0.5rem;
              background-color: rgb(203, 225, 232);
              padding: 0.125rem 0.25rem;
              border-radius: 0.25rem;
            }
          }
        }

        &__department {
          display: flex;
          gap: 1rem;
          align-items: center;

          &-details {
            display: flex;
            flex-direction: column;
            gap: 0.25rem;
            align-items: flex-start;

            &-name {
              font-weight: 600;
              font-size: 0.875rem;
            }

            &-description {
              font-size: 0.75rem;
            }
          }

          &-name {
            font-weight: 600;
            font-size: 0.875rem;
          }
        }
      }
    `,
  ],
})
export class CustomOrgChartComponent {
  data: ApiResponse = {
    id: 1,
    name: 'Company',
    type: TypeEnum.Department,
    children: [
      {
        id: 2,
        name: 'Engineering',
        type: TypeEnum.Department,
        children: [
          {
            id: 3,
            name: 'Alice Johnson',
            title: 'Software Engineer',
            thumbnail: 'https://randomuser.me/api/portraits/women/21.jpg',
            type: TypeEnum.Employee,
          },
          {
            id: 4,
            name: 'Bob Smith',
            title: 'Senior Developer',
            thumbnail: 'https://randomuser.me/api/portraits/men/21.jpg',
            type: TypeEnum.Contractor,
          },
        ],
      },
      {
        id: 5,
        name: 'Marketing',
        type: TypeEnum.Department,
        children: [
          {
            id: 6,
            name: 'Carol White',
            title: 'Marketing Manager',
            thumbnail: 'https://randomuser.me/api/portraits/women/21.jpg',
            type: TypeEnum.Employee,
          },
        ],
      },
    ],
  };

  protected readonly orgChartData = signal<OrgChartNode<ApiResponse> | null>(
    null
  );

  readonly #setOrgChartData = effect(() => {
    this.orgChartData.set(this.mapDataToOrgChartNode(this.data));
  });

  protected readonly dataTypeEnum = TypeEnum;

  protected readonly themeOptions: NgxInteractiveOrgChartTheme = {
    node: {
      background: 'white',
      color: 'black',
      shadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
      borderRadius: '8px',
      outlineColor: '#e0e0e0',
      activeOutlineColor: '#1976d2',
    },
  };

  private mapDataToOrgChartNode({
    children,
    ...data
  }: ApiResponse): OrgChartNode<ApiResponse> {
    return {
      id: data.id.toString(),
      name: data.name, // for search purposes
      collapsed: data.type === TypeEnum.Department, // collapse departments by default
      style: {
        // Apply any conditional styles here: For example, different background colors based on type
        background: data.type === TypeEnum.Department ? '#e3f2fd' : '#f1f1f1',
        color: data.type === TypeEnum.Department ? '#1976d2' : '#333',
        // or you can just use predefined css variables (preferable)
        '--node-background':
          data.type === TypeEnum.Department ? '#e3f2fd' : '#f1f1f1',
        '--node-color': data.type === TypeEnum.Department ? '#1976d2' : '#333',
      },
      // you can also set a custom class for each node, but make sure you apply this class in ng-deep
      nodeClass:
        data.type === TypeEnum.Department ? 'department-node' : 'employee-node',
      data: {
        ...data,
      },
      children: children?.map(child => this.mapDataToOrgChartNode(child)) || [],
    };
  }
}
```

The custom template receives the node data through the `let-node="node"` directive. You can access:

- `node.name` - The node name
- `node.data` - Custom data object with any properties you define
- `node.id` - Unique node identifier
- `node.children` - Array of child nodes
- `node.collapsed` - Current collapsed state
- `node.descendantsCount` - Total number of descendants (useful for displaying counts)

## 🖱️ Drag & Drop

The component supports drag and drop functionality, allowing users to reorganize the organizational chart dynamically. The library provides events and helper functions to handle the data restructuring.

### Basic Drag & Drop Setup

```typescript
import { Component, signal } from '@angular/core';
import {
  NgxInteractiveOrgChart,
  OrgChartNode,
  moveNode,
} from 'ngx-interactive-org-chart';

@Component({
  selector: 'app-drag-drop-demo',
  standalone: true,
  imports: [NgxInteractiveOrgChart],
  template: `
    <ngx-interactive-org-chart
      [data]="orgData()"
      [draggable]="true"
      (nodeDrop)="onNodeDrop($event)"
      (nodeDragStart)="onDragStart($event)"
      (nodeDragEnd)="onDragEnd($event)"
    />
  `,
})
export class DragDropDemoComponent {
  orgData = signal<OrgChartNode>({
    id: '1',
    name: 'CEO',
    children: [
      { id: '2', name: 'CTO', children: [] },
      { id: '3', name: 'CFO', children: [] },
    ],
  });

  /**
   * Handle node drop event.
   * IMPORTANT: The library does NOT modify your data automatically.
   * You must handle the data restructuring yourself.
   */
  onNodeDrop(event: { draggedNode: OrgChartNode; targetNode: OrgChartNode }) {
    const currentData = this.orgData();

    // Option 1: Use the built-in helper function (recommended)
    const updatedData = moveNode(
      currentData,
      event.draggedNode.id,
      event.targetNode.id
    );

    if (updatedData) {
      this.orgData.set(updatedData);
      // Optionally save to backend
      // this.api.updateOrgStructure(updatedData);
    } else {
      alert('Cannot move node: Invalid operation');
    }

    // Option 2: Implement your own custom logic
    // const updatedData = this.customMoveLogic(currentData, event);
    // this.orgData.set(updatedData);
  }

  onDragStart(node: OrgChartNode) {
    console.log('Drag started:', node.name);
  }

  onDragEnd(node: OrgChartNode) {
    console.log('Drag ended:', node.name);
  }
}
```

### Custom Drag Handle

By default, the entire node is draggable. You can provide a custom drag handle template to specify which part of the node should be used for dragging:

```typescript
@Component({
  template: `
    <ngx-interactive-org-chart
      [data]="orgData()"
      [draggable]="true"
      (nodeDrop)="onNodeDrop($event)"
    >
      <!-- Custom node template -->
      <ng-template #nodeTemplate let-node="node">
        <div class="custom-node">
          <h3>{{ node.name }}</h3>
          <p>{{ node.data?.title }}</p>
        </div>
      </ng-template>

      <!-- Custom drag handle template -->
      <ng-template #dragHandleTemplate let-node="node">
        <button class="drag-handle" title="Drag to move">
          ⋮⋮
        </button>
      </ng-template>
    </ngx-interactive-org-chart>
  `,
  styles: [`
    .drag-handle {
      cursor: move;
      padding: 4px 8px;
      background: #f0f0f0;
      border: 1px solid #ccc;
      border-radius: 4px;
      user-select: none;
    }

    .drag-handle:hover {
      background: #e0e0e0;
    }
  `]
})
```

### Helper Functions

The library provides several helper functions for common tree operations:

```typescript
import {
  moveNode,
  findNode,
  removeNode,
  addNodeToParent,
  isNodeDescendant,
} from 'ngx-interactive-org-chart';

// Move a node to a new parent
const updatedTree = moveNode(tree, draggedNodeId, targetParentId);

// Find a specific node by ID
const node = findNode(tree, nodeId);

// Remove a node from the tree
const treeWithoutNode = removeNode(tree, nodeId);

// Add a node to a specific parent
const treeWithNewNode = addNodeToParent(tree, parentId, newNode);

// Check if a node is a descendant of another
const isDescendant = isNodeDescendant(ancestorNode, descendantId);
```

### Drag & Drop Features

- **Auto-panning**: Automatically pans the view when dragging near viewport edges (configurable threshold and speed)
- **Visual Feedback**: Shows drag-over state on target nodes with color hints
- **Drag Constraints**: Use `canDragNode` and `canDropNode` predicates to control what can be dragged and where
- **ESC to Cancel**: Press ESC key during drag to cancel the operation
- **Validation**: Prevents dropping nodes on themselves or their descendants
- **Custom Handles**: Optional custom drag handle templates
- **Events**: Full control with dragStart, dragEnd, and drop events
- **Helper Functions**: Built-in utilities for tree manipulation
- **Pure Functions**: All helpers are immutable and return new tree structures
- **Touch Screen Support**: Full drag and drop support on mobile devices and tablets

### Touch Screen Support

The drag and drop functionality works seamlessly on touch-enabled devices (smartphones and tablets):

**Features:**

- **Touch Gestures**: Long press or drag to initiate drag operation
- **Visual Ghost Element**: A semi-transparent copy of the node follows your finger during drag
- **Auto-panning**: Works with touch gestures when dragging near screen edges
- **Drop Zones**: Visual feedback shows valid/invalid drop targets
- **Smooth Performance**: Optimized for 60fps touch interactions
- **Hybrid Support**: Works on devices with both touch and mouse input

**How it works:**

1. Touch and hold a draggable node
2. Start moving your finger - a ghost element appears after a small movement threshold (10px)
3. The org chart auto-pans when you drag near the edges
4. Valid drop targets show visual feedback (dashed outline)
5. Invalid targets show a "not-allowed" indicator
6. Release to drop, or drag outside to cancel

**No configuration needed** - touch support is automatically enabled when `draggable` is set to `true`. All drag constraints (`canDragNode`, `canDropNode`) work identically for both mouse and touch input.

**Example:**

```typescript
@Component({
  template: `
    <ngx-interactive-org-chart
      [data]="orgData()"
      [draggable]="true"
      (nodeDrop)="onNodeDrop($event)"
    />
  `,
})
export class MyComponent {
  // Works with both mouse and touch
  onNodeDrop(event: { draggedNode: OrgChartNode; targetNode: OrgChartNode }) {
    const updated = moveNode(
      this.orgData(),
      event.draggedNode.id,
      event.targetNode.id
    );
    if (updated) {
      this.orgData.set(updated);
    }
  }
}
```

### Drag Constraints & Validation

Control which nodes can be dragged and where they can be dropped using predicate functions:

```typescript
@Component({
  template: `
    <ngx-interactive-org-chart
      [data]="orgData()"
      [draggable]="true"
      [canDragNode]="canDragNode"
      [canDropNode]="canDropNode"
      [dragAutoPanSpeed]="20"
      (nodeDrop)="onNodeDrop($event)"
    />
  `,
})
export class MyComponent {
  // Prevent specific nodes from being dragged
  canDragNode = (node: OrgChartNode) => {
    // Example: CEO can't be moved
    return node.data?.role !== 'CEO';
  };

  // Control where nodes can be dropped
  canDropNode = (draggedNode: OrgChartNode, targetNode: OrgChartNode) => {
    // Example: Only departments can have children
    if (targetNode.data?.type !== 'Department') {
      return false;
    }

    // Example: Managers can't report to employees
    if (
      draggedNode.data?.role === 'Manager' &&
      targetNode.data?.role === 'Employee'
    ) {
      return false;
    }

    return true;
  };

  onNodeDrop(event: { draggedNode: OrgChartNode; targetNode: OrgChartNode }) {
    // Use built-in helper to move nodes
    const updated = moveNode(
      this.orgData(),
      event.draggedNode.id,
      event.targetNode.id
    );

    if (updated) {
      this.orgData.set(updated);
      // Optionally sync with backend
      this.api.updateOrgChart(updated);
    }
  }
}
```

**Visual Feedback:**

- Valid drop targets show a **dashed outline with subtle background tint**
- Invalid drop targets show **reduced opacity and not-allowed cursor**
- Press **ESC** to cancel drag operation at any time

**Auto-panning Configuration:**

The auto-pan threshold is **automatically calculated as 10% of the container dimensions**, making it responsive across all screen sizes:

- **Small screens (mobile)**: Smaller activation zone prevents accidental auto-panning
- **Large screens (desktop)**: Larger comfortable edge zones
- **No configuration needed**: Works perfectly out of the box

You can still customize the panning speed:

- `dragAutoPanSpeed` (default: 15): Panning speed in pixels per frame

> **Note**: The `dragEdgeThreshold` property is deprecated. The threshold is now dynamically calculated for optimal UX.

### Data Handling Pattern

**Important**: The library follows a controlled component pattern and does NOT modify your data automatically. This design gives you:

- ✅ **Full control** over validation logic
- ✅ **Backend synchronization** capabilities
- ✅ **Custom business rules** implementation
- ✅ **Undo/redo** functionality support
- ✅ **Optimistic updates** with rollback

**Pattern:**

1. User drags and drops a node
2. Library emits `nodeDrop` event with source and target nodes
3. You handle the event and restructure your data
4. Update your data signal/input
5. Library automatically re-renders the updated structure

```typescript
onNodeDrop(event: { draggedNode: OrgChartNode; targetNode: OrgChartNode }) {
  // 1. Get current data
  const currentData = this.orgData();

  // 2. Validate the operation (optional)
  if (!this.canMove(event.draggedNode, event.targetNode)) {
    this.showError('Cannot move this node');
    return;
  }

  // 3. Update the data structure
  const updatedData = moveNode(
    currentData,
    event.draggedNode.id,
    event.targetNode.id
  );

  if (!updatedData) return;

  // 4. Update state (with rollback capability)
  const previousData = currentData;
  this.orgData.set(updatedData);

  // 5. Sync with backend
  this.api.updateOrgChart(updatedData).subscribe({
    error: () => {
      // Rollback on error
      this.orgData.set(previousData);
      this.showError('Failed to update');
    }
  });
}
```

## 🗺️ Mini Map Navigation

The component includes a built-in mini map feature for easy navigation of large organizational charts. The mini map provides a bird's-eye view of the entire chart with a viewport indicator showing your current position.

### Basic Mini Map Setup

```typescript
import { Component } from '@angular/core';
import { NgxInteractiveOrgChart } from 'ngx-interactive-org-chart';

@Component({
  selector: 'app-minimap-demo',
  standalone: true,
  imports: [NgxInteractiveOrgChart],
  template: `
    <ngx-interactive-org-chart
      [data]="orgData"
      [showMiniMap]="true"
      [miniMapPosition]="'bottom-right'"
      [miniMapWidth]="200"
      [miniMapHeight]="150"
    />
  `,
})
export class MiniMapDemoComponent {
  orgData = {
    id: '1',
    name: 'CEO',
    children: [
      /* ... your org chart data ... */
    ],
  };
}
```

### Mini Map Configuration

| Input             | Type                                                           | Default          | Description                        |
| ----------------- | -------------------------------------------------------------- | ---------------- | ---------------------------------- |
| `showMiniMap`     | `boolean`                                                      | `false`          | Enable/disable the mini map        |
| `miniMapPosition` | `'top-left' \| 'top-right' \| 'bottom-left' \| 'bottom-right'` | `'bottom-right'` | Position of the mini map on screen |
| `miniMapWidth`    | `number`                                                       | `200`            | Width of the mini map in pixels    |
| `miniMapHeight`   | `number`                                                       | `150`            | Height of the mini map in pixels   |

### Mini Map Features

- **📊 Visual Overview**: Shows a simplified view of the entire organizational chart
- **🎯 Viewport Indicator**: Blue rectangle shows your current visible area
- **� Drag Navigation**: Drag the viewport indicator to smoothly pan the main chart
- **🔄 Auto-Update**: Automatically updates when the chart structure changes
- **🎨 Fully Themable**: Customizable through theme options with 8 configurable properties
- **⚡ High Performance**: Optimized rendering with debounced updates

### Example with All Options

```typescript
@Component({
  selector: 'app-advanced-minimap',
  standalone: true,
  imports: [NgxInteractiveOrgChart],
  template: `
    <ngx-interactive-org-chart
      [data]="largeOrgData"
      [showMiniMap]="miniMapVisible()"
      [miniMapPosition]="miniMapPosition()"
      [miniMapWidth]="250"
      [miniMapHeight]="180"
      [themeOptions]="themeOptions"
    />

    <!-- Toggle button -->
    <button (click)="toggleMiniMap()">
      {{ miniMapVisible() ? 'Hide' : 'Show' }} Mini Map
    </button>
  `,
})
export class AdvancedMiniMapComponent {
  miniMapVisible = signal(true);
  miniMapPosition = signal<
    'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
  >('bottom-right');

  largeOrgData = {
    // ... large organizational chart data
  };

  themeOptions = {
    // ... your theme options
  };

  toggleMiniMap() {
    this.miniMapVisible.update(v => !v);
  }
}
```

### Mini Map Styling

The mini map is fully themable through the `themeOptions` input. It automatically inherits and adapts to your chart's theme configuration:

```typescript
import { NgxInteractiveOrgChartTheme } from 'ngx-interactive-org-chart';

const customTheme: NgxInteractiveOrgChartTheme = {
  // ... other theme options
  miniMap: {
    background: 'rgba(255, 255, 255, 0.95)',
    borderColor: 'rgba(0, 0, 0, 0.15)',
    borderRadius: '8px',
    shadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
    nodeColor: 'rgba(0, 0, 0, 0.6)',
    viewportBackground: 'rgba(59, 130, 246, 0.2)',
    viewportBorderColor: 'rgb(59, 130, 246)',
    viewportBorderWidth: '2px',
  },
};
```

#### Mini Map Theme Options

| Property              | Type     | Default                     | Description                       |
| --------------------- | -------- | --------------------------- | --------------------------------- |
| `background`          | `string` | `rgba(255, 255, 255, 0.95)` | Background color of the mini map  |
| `borderColor`         | `string` | `rgba(0, 0, 0, 0.15)`       | Border color of the mini map      |
| `borderRadius`        | `string` | `8px`                       | Border radius of the mini map     |
| `shadow`              | `string` | `0 4px 6px -1px rgba(...)`  | Box shadow of the mini map        |
| `nodeColor`           | `string` | `rgba(0, 0, 0, 0.6)`        | Color of nodes in the mini map    |
| `viewportBackground`  | `string` | `rgba(59, 130, 246, 0.2)`   | Background color of viewport area |
| `viewportBorderColor` | `string` | `rgb(59, 130, 246)`         | Border color of viewport area     |
| `viewportBorderWidth` | `string` | `2px`                       | Border width of viewport area     |

You can also use CSS custom properties for additional customization:

```scss
::ng-deep ngx-org-chart-mini-map {
  .mini-map-container {
    // Override theme values with custom CSS
    border-radius: 12px !important;
    box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2) !important;
  }

  .viewport-indicator {
    // Custom styling for the viewport indicator
    border-width: 3px !important;
  }
}
```

### Performance Considerations

The mini map is optimized for performance:

- Uses HTML Canvas for efficient rendering
- Debounced updates (100ms) to prevent excessive redraws
- MutationObserver for smart DOM change detection
- RequestAnimationFrame for smooth viewport tracking
- Only redraws when necessary (chart changes, pan, zoom)

### Use Cases

The mini map is particularly useful for:

- **Large Organizations**: Navigate charts with 50+ nodes
- **Deep Hierarchies**: Quickly jump between different levels
- **Complex Structures**: Overview of multi-department organizations
- **Presentations**: Show context while focusing on specific areas
- **User Onboarding**: Help users understand chart structure

## 🎨 Styling

You can add a custom class to each node that will be applied separately or use the `nodeClass` input that will be applied to all nodes or you can use the `themeOptions` input to define global styles for nodes, connectors, and the chart container.

## 📊 Live Demo

Check out the interactive demo to see the component in action:

**[View Live Demo →](https://zeyadelshaf3y.github.io/ngx-interactive-org-chart)**

## 🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

1. Fork the project
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing-feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## 🤖 Issues & Support

If you encounter any issues or have questions:

1. Check the [GitHub Issues](https://github.com/zeyadelshaf3y/ngx-interactive-org-chart/issues)
2. Create a new issue with a detailed description
3. Include your Angular version and reproduction steps

## 💝 Support the Project

If this library helps you, consider supporting its development:

- ⭐ Star the repository on GitHub
- 🐛 Report bugs and suggest features
- 💝 [Buy me a coffee](https://buymeacoffee.com/zeyadalshafey)
- 💖 [GitHub Sponsors](https://github.com/sponsors/zeyadelshaf3y)

## 📄 License

MIT © [Zeyad Alshafey](https://github.com/zeyadelshaf3y)

## 🔗 Links

- [GitHub Repository](https://github.com/zeyadelshaf3y/ngx-interactive-org-chart)
- [NPM Package](https://www.npmjs.com/package/ngx-interactive-org-chart)
- [Live Demo](https://zeyadelshaf3y.github.io/ngx-interactive-org-chart)
- [Issues](https://github.com/zeyadelshaf3y/ngx-interactive-org-chart/issues)
