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.
npm install ngx-interactive-org-chart
For detailed documentation, installation guide, and API reference, see the Library Documentation.
βββ projects/
β βββ ngx-interactive-org-chart/ # π Main library
β βββ demo/ # πͺ Demo application
βββ dist/ # π¦ Build outputs
βββ docs/ # π GitHub Pages demo
- π― Interactive Pan & Zoom - Smooth navigation with mouse/touch
- π³ Hierarchical Layout - Perfect for organizational structures
- π¨ Customizable Styling - Fully themeable with CSS/SCSS
- π± Mobile Friendly - Touch gestures support
- β‘ High Performance - Optimized rendering
- π 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
- π 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
- π§© Modular Design - Standalone component for easy integration
- π§ TypeScript Support - Full type definitions included
- π οΈ Easy Setup - Minimal configuration required
- πͺ Angular 19+ - Built with latest Angular features
- π 100% Free - Open source MIT license
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',
},
};
}
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
}
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"
/>
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
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 |
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)) || [],
};
}
}
- Angular 19+
- TypeScript 5.4+
# Clone the repository
git clone https://github.com/zeyadelshaf3y/ngx-interactive-org-chart.git
# Install dependencies
npm install
# Start development server
npm start
# Build the library
npm run build:lib
# Run tests
npm test
MIT Β© Zeyad Alshafey