ngx-interactive-org-chart

January 14, 2026 ยท View on GitHub

Modern Angular organizational chart component with interactive pan/zoom functionality

npm version license downloads

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

๐Ÿš€ Quick Start

View Interactive Demo โ†’

๐Ÿ“ฆ 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-chartAngular VersionNotes
1.1.4Angular 19Stable release
1.2.xAngular 20+Drag & drop, RTL support
1.3.xAngular 20+Mini map, dark mode, performance
1.4.xAngular 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

PropertyTypeDefaultDescription
dataOrgChartNoderequiredThe organizational data to display
collapsiblebooleantrueEnable/disable node collapsing
layout'vertical' | 'horizontal''vertical'Chart layout orientation
themeOptionsNgxInteractiveOrgChartTheme{}Theme configuration options for styling
nodeClassstringundefinedCustom CSS class applied to all nodes
initialZoomnumberundefinedInitial zoom level
minZoomnumber0.1Minimum zoom level
maxZoomnumber5Maximum zoom level
zoomSpeednumber1Zoom speed multiplier
zoomDoubleClickSpeednumber2Double-click zoom speed multiplier
initialCollapsedbooleanfalseInitial collapsed state for all nodes
isRtlbooleanfalseRight-to-left text direction support
displayChildrenCountbooleantrueShow children count on collapse buttons
highlightZoomNodeWidthRationumber0.3Node width ratio relative to viewport when highlighting (0.1-1.0)
highlightZoomNodeHeightRationumber0.4Node height ratio relative to viewport when highlighting (0.1-1.0)
highlightZoomMinimumnumber0.8Minimum zoom level when highlighting a node
draggablebooleanfalseEnable drag and drop functionality for nodes
canDragNode(node) => booleanundefinedPredicate function to determine if a node can be dragged
canDropNode(dragged, target) => booleanundefinedPredicate function to validate drop operations
dragEdgeThresholdnumber0.1Auto-pan threshold is calculated as 10% of container dimensions
dragAutoPanSpeednumber15Speed of auto-panning in pixels per frame during drag
showMiniMapbooleanfalseEnable/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
miniMapWidthnumber200Width of the mini map in pixels
miniMapHeightnumber150Height 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 canDragNode and canDropNode predicates
  • 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