ngx-interactive-org-chart
January 14, 2026 ยท View on GitHub
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.
๐ Quick Start
๐ฆ Installation
npm install ngx-interactive-org-chart
For detailed documentation, installation guide, and API reference, see the Library Documentation.
๐ฏ Repository Structure
โโโ projects/
โ โโโ ngx-interactive-org-chart/ # ๐ Main library
โ โโโ demo/ # ๐ช Demo application
โโโ dist/ # ๐ฆ Build outputs
โโโ docs/ # ๐ GitHub Pages demo
โจ 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 for pan, zoom, and drag & drop
- โก High Performance - Optimized rendering with canvas-based mini map
- ๐ Searchable Nodes - Easily find nodes in large charts
- ๐งญ Smart Highlight & Focus - Dynamically zoom to nodes with optimal sizing
- ๐ Custom Node Templates - Use Angular templates for nodes
- ๐ฑ๏ธ Drag & Drop - Reorganize nodes with drag and drop (works on touch screens!)
- ๐ฏ 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 |
| 1.4.x | Angular 21+ | Latest features |
๐ 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"
[highlightZoomNodeWidthRatio]="0.3"
[highlightZoomNodeHeightRatio]="0.4"
/>
`,
})
export class DemoComponent {
orgData: OrgChartNode<{ title: string; department: string }> = {
id: 'ceo',
name: 'John Smith',
data: {
title: 'Chief Executive Officer',
department: 'Executive',
},
children: [
{
id: 'cto',
name: 'Jane Doe',
data: {
title: 'Chief Technology Officer',
department: 'Engineering',
},
children: [
{
id: 'dev1',
name: 'Mike Johnson',
data: {
title: 'Senior Developer',
department: 'Engineering',
},
},
],
},
{
id: 'cfo',
name: 'Sarah Wilson',
data: {
title: 'Chief Financial Officer',
department: 'Finance',
},
},
],
};
themeOptions: NgxInteractiveOrgChartTheme = {
node: {
background: '#ffffff',
shadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
borderRadius: '8px',
},
connector: {
color: '#d1d5db',
activeColor: '#3b82f6',
},
};
}
๐ฏ 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 horizontally and vertically
resetPanning() {
this.orgChart.resetPan(); // Centers the chart
}
// Reset both pan and zoom to fit the chart within the containing box
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 |
showMiniMap | boolean | false | Enable/disable the mini map navigation |
miniMapPosition | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'bottom-right' | Position of the mini map within the chart container |
miniMapWidth | number | 200 | Width of the mini map in pixels |
miniMapHeight | number | 150 | Height of the mini map in pixels |
๐ฑ๏ธ Drag & Drop
Enable drag and drop to allow users to reorganize the chart:
import { moveNode } from 'ngx-interactive-org-chart';
@Component({
template: `
<ngx-interactive-org-chart
[data]="orgData()"
[draggable]="true"
[canDragNode]="canDragNode"
[canDropNode]="canDropNode"
(nodeDrop)="onNodeDrop($event)"
>
<!-- Optional: Custom drag handle -->
<ng-template #dragHandleTemplate let-node="node">
<button class="drag-handle">โฎโฎ</button>
</ng-template>
</ngx-interactive-org-chart>
`,
})
export class MyComponent {
orgData = signal<OrgChartNode>(/* your data */);
// Optional: Control which nodes can be dragged
canDragNode = (node: OrgChartNode) => node.id !== 'root';
// Optional: Control where nodes can be dropped
canDropNode = (dragged: OrgChartNode, target: OrgChartNode) => {
return target.data?.type === 'Department';
};
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);
}
}
}
Features:
- Responsive auto-panning - Threshold automatically calculated as 10% of container (perfect for all screen sizes)
- Drag constraints with
canDragNodeandcanDropNodepredicates - ESC key to cancel drag operation
- Visual feedback for valid/invalid drop targets
- Custom drag handle templates
- Helper functions for tree manipulation (
moveNode,findNode,removeNode, etc.) - Full control over data updates
- Touch screen support - Works seamlessly on mobile devices and tablets
For complete documentation, see the Library Documentation.
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)) || [],
};
}
}
๐ Requirements
- Angular 21+
- TypeScript 5.9+
๐๏ธ Development
# Clone the repository
git clone https://github.com/zeyadelshaf3y/ngx-interactive-org-chart.git
# Install dependencies
npm install
# Build the library
npm run build:lib
# Start development server
npm start
# Run tests
npm test
๐ License
MIT ยฉ Zeyad Alshafey