UNPKG

ngx-interactive-org-chart

Version:

Modern Angular organizational chart component with interactive pan/zoom functionality and more..

1,172 lines (978 loc) 39.7 kB
# 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)