ngx-interactive-org-chart
v1.3.0
Published
Modern Angular organizational chart component with interactive pan/zoom functionality and more..
Maintainers
Readme
ngx-interactive-org-chart
Modern Angular organizational chart component with interactive pan/zoom functionality
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
npm install ngx-interactive-org-chartSetup Angular Animations
The component uses Angular animations for smooth transitions. Add the animations module to your main.ts:
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:
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
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:
interface OrgChartNode<T = any> {
id?: string;
name?: string;
data?: T;
children?: OrgChartNode<T>[];
collapsed?: boolean;
hidden?: boolean;
nodeClass?: string;
}Component Options
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:
// 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:
// 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:
// 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:
@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:
@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 occupyhighlightZoomNodeHeightRatio(0.1-1.0): How much of the viewport height the highlighted node should occupyhighlightZoomMinimum: 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:
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 namenode.data- Custom data object with any properties you definenode.id- Unique node identifiernode.children- Array of child nodesnode.collapsed- Current collapsed statenode.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
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:
@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:
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
canDragNodeandcanDropNodepredicates 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:
- Touch and hold a draggable node
- Start moving your finger - a ghost element appears after a small movement threshold (10px)
- The org chart auto-pans when you drag near the edges
- Valid drop targets show visual feedback (dashed outline)
- Invalid targets show a "not-allowed" indicator
- 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:
@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:
@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
dragEdgeThresholdproperty 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:
- User drags and drops a node
- Library emits
nodeDropevent with source and target nodes - You handle the event and restructure your data
- Update your data signal/input
- Library automatically re-renders the updated structure
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
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
@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:
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:
::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:
🤝 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.
- Fork the project
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing-feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
🤖 Issues & Support
If you encounter any issues or have questions:
- Check the GitHub Issues
- Create a new issue with a detailed description
- 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
- 💖 GitHub Sponsors
📄 License
MIT © Zeyad Alshafey
