{"id":50188643,"url":"https://github.com/synergycodes/ng-diagram-orgchart","last_synced_at":"2026-05-25T11:33:58.142Z","repository":{"id":352844705,"uuid":"1170824497","full_name":"synergycodes/ng-diagram-orgchart","owner":"synergycodes","description":"Interactive org chart template built with Angular and ng-diagram. Showcases how to achieve such functionality in   ng-diagram and can serve as a starting point to implement orgchart-like applications.","archived":false,"fork":false,"pushed_at":"2026-05-07T19:16:06.000Z","size":955,"stargazers_count":10,"open_issues_count":5,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-07T21:20:32.898Z","etag":null,"topics":["angular","angular-component","diagram","diagramming","drag-and-drop","elkjs","graph","ng-diagram","node-based-ui","node-editor","open-source","org-chart","organizational-chart","orgchart","tree-layout","typescript","visualization"],"latest_commit_sha":null,"homepage":"https://synergycodes.github.io/ng-diagram-orgchart/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/synergycodes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-02T15:06:21.000Z","updated_at":"2026-05-07T19:14:43.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/synergycodes/ng-diagram-orgchart","commit_stats":null,"previous_names":["synergycodes/ng-diagram-orgchart"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/synergycodes/ng-diagram-orgchart","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/synergycodes%2Fng-diagram-orgchart","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/synergycodes%2Fng-diagram-orgchart/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/synergycodes%2Fng-diagram-orgchart/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/synergycodes%2Fng-diagram-orgchart/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/synergycodes","download_url":"https://codeload.github.com/synergycodes/ng-diagram-orgchart/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/synergycodes%2Fng-diagram-orgchart/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33473704,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-25T06:32:55.349Z","status":"ssl_error","status_checked_at":"2026-05-25T06:32:35.322Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular","angular-component","diagram","diagramming","drag-and-drop","elkjs","graph","ng-diagram","node-based-ui","node-editor","open-source","org-chart","organizational-chart","orgchart","tree-layout","typescript","visualization"],"created_at":"2026-05-25T11:33:58.026Z","updated_at":"2026-05-25T11:33:58.132Z","avatar_url":"https://github.com/synergycodes.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ng-diagram Org Chart Template\n\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue)](https://opensource.org/licenses/MIT)\n\n**[Live Demo](https://synergycodes.github.io/ng-diagram-orgchart/)**\n\nInteractive organizational chart built with Angular 21 and [ng-diagram](https://www.npmjs.com/package/ng-diagram). Use this project as a starting point for building your own org-chart or tree-based diagram. Minimal dependencies: only Angular, ng-diagram, and ELK.js, with no opinionated third-party UI libraries.\n\nFeatures:\n\n- Automatic tree layout powered by [ELK.js](https://www.npmjs.com/package/elkjs)\n- Horizontal and vertical layout switching\n- Expand/collapse subtrees with child count badge\n- Drag-and-drop reordering (sibling and reparenting)\n- Node creation and removal\n- Minimap with zoom controls\n- Animated layout transitions\n- Three node display variants (full, compact, vacant)\n- Color-coded roles with initials avatars\n- Dark/light theme\n- Properties sidebar demonstrating diagram-to-UI integration\n\n## Getting Started\n\n**Prerequisites:** Node.js v20.19+ or v22.12+, npm 10+\n\n```bash\nnpm install\nnpm start\n```\n\nOpen [http://localhost:4200](http://localhost:4200).\n\n## Scripts\n\n| Command | Description |\n|---|---|\n| `npm start` | Start dev server with hot reload |\n| `npm run build` | Production build to `dist/` |\n| `npm run format` | Format code with Prettier |\n\n## ng-diagram APIs Demonstrated\n\nThis template wires up most of the ng-diagram public surface, useful as a reference for which APIs to reach for in your own integration.\n\n| Concern | API | Where in this repo |\n|---|---|---|\n| Bootstrap | `provideNgDiagram()` | `pages/org-chart-page.component.ts` |\n| Diagram component | `\u003cng-diagram\u003e` (`NgDiagramComponent`) | `diagram/diagram.component.html` |\n| Background | `\u003cng-diagram-background\u003e` (`NgDiagramBackgroundComponent`) | `diagram/diagram.component.html` |\n| Minimap | `\u003cng-diagram-minimap\u003e` (`NgDiagramMinimapComponent`), `MinimapNodeStyleFn` | `minimap-panel/minimap-panel.component.ts` |\n| Custom node template | `NgDiagramNodeTemplateMap`, `NgDiagramNodeTemplate\u003cTData\u003e` interface | `diagram/diagram.component.ts`, `diagram/node/node.component.ts` |\n| Custom edge template | `NgDiagramEdgeTemplateMap`, `NgDiagramEdgeTemplate\u003cTData\u003e`, `NgDiagramBaseEdgeComponent` | `diagram/edge.component.ts` |\n| Connection ports | `\u003cng-diagram-port\u003e` (`NgDiagramPortComponent`) | `diagram/node/node.component.html` |\n| Model init | `initializeModel()` | `diagram/diagram.component.ts` |\n| Model reads | `NgDiagramModelService` (`getNodeById`, `getEdgeById`, `getConnectedEdges`, `nodes()`, `edges()`, `getModel()`) | throughout `diagram/model/`, `drag-reorder/`, `properties-sidebar/` |\n| Model writes | `NgDiagramModelService` (`addNodes`, `addEdges`, `deleteNodes`, `deleteEdges`, `updateNodes`, `updateEdges`, `updateNodeData`) | `diagram/model/model-apply.service.ts`, `properties-sidebar/node-mutation.service.ts` |\n| Spatial query | `NgDiagramModelService.getNodesInRange(center, radius)` - find nodes within a pixel radius of a point | `drag-reorder/drag-reorder.service.ts`, `drag-reorder/drag.service.ts` |\n| Atomic transactions | `NgDiagramService.transaction(..., { waitForMeasurements: true })` | `diagram/model/model-apply.service.ts` |\n| Imperative event subscription | `NgDiagramService.addEventListener / removeEventListener` for events like `'nodeDragStarted'`, `'nodeDragEnded'`, `'selectionMoved'` | `drag-reorder/drag-reorder.service.ts` |\n| Template-output event payloads | `DiagramInitEvent`, `SelectionGestureEndedEvent`, `SelectionRemovedEvent`, `NodeDragStartedEvent` | `diagram/diagram.component.ts`, `drag-reorder/drag.service.ts` |\n| Viewport state | `NgDiagramViewportService` (`scale()`, `viewport()`) | `diagram/node/node.component.ts`, `diagram/animation/`, `diagram/node-visibility/` |\n| Viewport actions | `NgDiagramViewportService` (`zoomToFit`, `zoom`, `moveViewport`) | `diagram/diagram.component.ts`, `minimap-panel/minimap-panel.component.ts`, `diagram/node-visibility/viewport.ts` |\n| Selection | `NgDiagramSelectionService` (`selection()`, `select()`) | `properties-sidebar/properties-sidebar.service.ts`, `diagram/node/components/add-button/add-button.service.ts` |\n| Config typing | `NgDiagramConfig` (linking, zIndex elevation, etc.) | `diagram/diagram.component.ts` |\n| Core types | `Node\u003cTData\u003e`, `Edge\u003cTData\u003e`, `Point`, `Rect`, `Viewport` | throughout |\n\n## Customizing for Your Project\n\n### Configuration\n\nAll tunable values (animation speed, layout spacing, zoom behavior, drag thresholds) are centralized in a single config file:\n\n**`src/app/org-chart/org-chart.config.ts`**\n\nTo override defaults, add `provideOrgChartConfig` to your page providers:\n\n```typescript\nimport { provideOrgChartConfig } from './org-chart.config';\n\nproviders: [\n  provideOrgChartConfig({\n    animation: { durationMs: 500, layoutEnabled: false },\n    layout: { nodeSpacing: 200 },\n    viewport: { compactScaleThreshold: 0.5, zoomStep: 0.25 },\n    drag: { detectionRange: 150 },\n  }),\n]\n```\n\nUnspecified values keep their defaults. See `OrgChartConfig` interface for all options with documentation.\n\n### Data Model\n\nNode and edge data interfaces are defined in `src/app/org-chart/diagram/model/interfaces.ts`. The base node data includes properties for tree behavior:\n\n| Property | Purpose |\n|---|---|\n| `isCollapsed` | Whether the node's subtree is collapsed |\n| `isHidden` | Whether the node is hidden (inside a collapsed subtree) |\n| `hasChildren` | Whether the node has child nodes |\n| `collapsedChildrenCount` | Cached descendant count for collapsed nodes |\n| `sortOrder` | Sibling ordering within the tree |\n\nProperty names are exported as constants (e.g., `IS_COLLAPSED`, `HAS_CHILDREN`) from the same file. All reads go through getter functions in `data-getters.ts`, and all writes use bracket notation with these constants. To rename a property, change the constant value and the interface, no other files need updating.\n\n### Node Variants\n\nThe node component (`src/app/org-chart/diagram/node/`) renders three visual variants:\n\n- **full** - complete card with stats and capacity bar (zoom \u003e= threshold)\n- **compact** - header only (zoom \u003c threshold, configurable via `viewport.compactScaleThreshold`)\n- **vacant** - placeholder card for unfilled positions\n\nTo customize node appearance, edit the components in `src/app/org-chart/diagram/node/components/`.\n\n### Adding Your Own Data\n\nReplace the seed data in `src/app/org-chart/diagram/data.ts`. Each node needs:\n\n- A unique `id`\n- `type: 'orgChartNode'`\n- `position: { x: 0, y: 0 }` (layout engine computes actual positions)\n- A `data` object matching `OrgChartNodeData`\n\nEdges connect nodes via `source`/`target` IDs with port names `'port-out'` and `'port-in'`.\n\n### Theming\n\nTheme is driven by the `data-theme` attribute on `\u003chtml\u003e` (`\"light\"` or `\"dark\"`) and persisted in `localStorage`. The toggle UI lives in `src/app/org-chart/top-navbar/theme-toggle.component.ts`.\n\nColor tokens are defined in `src/tokens.css`:\n\n- **`--ngd-colors-*`** - base palette (grays + accent ramps `acc1`–`acc9` with shade and alpha variants).\n- **`--ngd-role-*`** - role-to-color mapping consumed by node templates (e.g. `--ngd-role-plant-director`). Each role variable points at one of the palette colors, so swapping a role's color is a one-line edit.\n\nRoles map to CSS variables via `getColorForRole()` and the `ORG_CHART_ROLE_COLORS` record in `src/app/org-chart/diagram/model/interfaces.ts`. To add a new role: extend the `OrgChartRole` enum, add a `--ngd-role-*` token, and add a row to `ORG_CHART_ROLE_COLORS`.\n\nGlobal stylesheet entry point: `src/styles.css` (imports `tokens.css`, typography, and `ng-diagram/styles.css`).\n\n### Layout Engine\n\nTree positions are computed by [ELK.js](https://www.npmjs.com/package/elkjs) using the `mrtree` algorithm. The integration is intentionally narrow:\n\n- **`diagram/layout/perform-layout.ts`** - single ELK call site. Converts ng-diagram nodes/edges into ELK input, runs `elk.layout(...)`, and returns the same nodes with updated `position`. Also enforces a uniform node size across the layout (the cached max width/height across all runs) so spacing stays stable when the compact/full variants produce different sizes.\n- **`diagram/layout/layout.service.ts`** - orchestration. Resolves the visible set (collapsed subtrees excluded), applies pending mutations from `ModelChanges` (deletions, edge-source overrides, new nodes, sort-order overrides), invokes `performLayout`, **pins the root** so the chart doesn't jump after re-layout, then writes position updates back into the same `ModelChanges` instance.\n- **`diagram/layout/visible-set.ts`** - pure helpers: `getVisibleSet` (current), `getFutureVisibleSet` (predicted after a collapse/expand), `findRootNode`.\n\nTo swap ELK for another engine (d3-hierarchy, dagre etc.), replace `perform-layout.ts` with a function of the same shape:\n\n```typescript\n(nodes: Node[], edges: Edge[], direction: 'DOWN' | 'RIGHT', nodeSpacing: number) =\u003e Promise\u003cNode[]\u003e\n```\n\n`LayoutService` is the only caller, and it handles visibility / sort-order / root-pinning around the call, so a replacement only needs to assign positions.\n\n`LayoutService` pre-sorts nodes and edges by `sortOrder` before invoking the engine, relying on the engine to honor that input order for siblings. ELK's `mrtree` does; other libraries may not. If the chosen engine doesn't preserve input order, the adapter can read each node's `sortOrder` (via `getSortOrder` from `model/data-getters.ts`) and pass it to the library in whatever ordering format it accepts.\n\n\n## Architecture\n\n### Service Hierarchy\n\nAll services are provided at the page component level (`OrgChartPageComponent`), no `providedIn: 'root'`. Drag-reorder services are scoped to the `DiagramComponent`.\n\n```\nOrgChartPageComponent (providers)\n  ├── Layout: LayoutGate, LayoutService, LayoutAnimationService\n  ├── Model: ModelApplyService, HierarchyService, SortOrderService,\n  │          ExpandCollapseService, AddNodeService\n  ├── UI: PropertiesSidebarService (→ NodeMutationService),\n  │       NodeVisibilityService,\n  │       NodeVisibilityConfigService\n  ├── Node actions: AddButtonService\n  └── DiagramComponent (providers)\n      └── DragService, DropService, DragReorderService\n```\n\n### Key Patterns\n\n- **Compute-then-apply mutations** - services build a `ModelChanges` accumulator (partial data patches allowed); `ModelApplyService` resolves patches against current state, runs layout and animation, and commits in a single `LayoutGate`-serialized transaction.\n- **Centralized property keys + getters** - every node/edge data field has a string-constant key (`IS_COLLAPSED`, `HAS_CHILDREN`, …) and a getter in `data-getters.ts`. Renames touch one constant + the interface; no callsites.\n- **Viewport overlays** - `appViewportBounds` / `appViewportOverlay` directives register UI elements that obscure the diagram so visibility calculations account for them.\n\n## Project Structure\n\n```\nsrc/app/org-chart/\n├── org-chart.config.ts                 # Central configuration\n├── pages/                              # Page container\n├── diagram/\n│   ├── diagram.component.ts            # Main diagram component\n│   ├── edge.component.ts               # Edge template\n│   ├── data.ts                         # Seed data\n│   ├── model/                          # Domain types \u0026 services\n│   │   ├── interfaces.ts               # Data types + property key constants\n│   │   ├── data-getters.ts             # Centralized property accessors\n│   │   ├── model-changes.ts            # Change accumulator\n│   │   ├── model-apply.service.ts      # Applies changes with layout\n│   │   ├── hierarchy.service.ts        # Parent-child relationships\n│   │   ├── expand-collapse.service.ts  # Subtree visibility\n│   │   ├── sort-order.service.ts       # Sibling ordering\n│   │   └── add-node.service.ts         # Node creation\n│   ├── node/                           # Node rendering (3 variants)\n│   ├── layout/                         # ELK.js layout engine\n│   ├── animation/                      # Layout + viewport animations\n│   └── node-visibility/                # Viewport-aware visibility\n├── drag-reorder/                       # Drag-and-drop subsystem\n│   ├── zone-detection/                 # Drop zone strategies\n│   └── drop-strategy/                  # Drop action strategies\n├── properties-sidebar/                 # Node editing panel\n├── shared/                             # Reusable UI (combobox, avatar)\n├── top-navbar/                         # Navigation bar + theme toggle\n├── toolbar-horizontal/                 # Layout direction toolbar\n└── minimap-panel/                      # Minimap with zoom controls\n```\n\n## Tech Stack\n\n- **Angular 21** - standalone components, signals, OnPush change detection\n- **ng-diagram** - diagram rendering, viewport management, selection\n- **ELK.js** - automatic tree layout\n- **Prettier** - code formatting\n\n## Known ng-diagram Issues\n\nThe template contains a few workarounds and compromises driven by current library gaps. Resolving these would let us simplify the template.\n\n### Issues with workarounds in this repo\n\n- **No API for hiding a node.** ng-diagram wraps each custom node in a `.node-content` div that intercepts pointer events, so hiding the custom node alone isn't enough. *Workaround:* `::ng-deep` CSS in `node.component.scss` reaches up to the wrapper to suppress both visibility and pointer events. A first-class hidden-node property would remove the `::ng-deep` entirely.\n\n### Issues without workarounds (felt by end users)\n\n- **Resize batch re-runs edge routing per node.** When many nodes change size at once (for example, 500 nodes switching between compact and full variants on a zoom threshold), edges visibly disconnect from their nodes for roughly one to two seconds before snapping back.\n- **Layout animation is naive in the template.** The animation implementation in this template is fairly naive. Proper native animation support in ng-diagram is needed so the template can drop its custom animation code. If you notice lag from animations, you can turn them off by passing `animation: { layoutEnabled: false }` to `provideOrgChartConfig` (see \"Configuration\" above).\n\nAll of the above are the highest-priority items for the team to fix in ng-diagram. That said, the template works today and is fully usable as-is.\n\n## ng-diagram Documentation\n\nFor comprehensive ng-diagram documentation, examples, and API reference, visit: **[ngdiagram.dev/docs](https://www.ngdiagram.dev/docs)**\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Support\n\n- **Issues**: [GitHub Issues](https://github.com/synergycodes/ng-diagram-orgchart/issues)\n- **Discussions**: [GitHub Discussions](https://github.com/synergycodes/ng-diagram-orgchart/discussions)\n- **ng-diagram Discussions**: [GitHub Discussions](https://github.com/synergycodes/ng-diagram/discussions), [Discord](https://discord.gg/FDMjRuarFb)\n- **ng-diagram Documentation**: [ngdiagram.dev/docs](https://www.ngdiagram.dev/docs)\n\n---\n\nBuilt with ❤️ by the [Synergy Codes](https://www.synergycodes.com/) team\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsynergycodes%2Fng-diagram-orgchart","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsynergycodes%2Fng-diagram-orgchart","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsynergycodes%2Fng-diagram-orgchart/lists"}