@tic-nova/ngx-interactive-org-chart 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
ADDED
|
@@ -0,0 +1,1171 @@
|
|
|
1
|
+
# ngx-interactive-org-chart
|
|
2
|
+
|
|
3
|
+
> Modern Angular organizational chart component with interactive pan/zoom functionality
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/ngx-interactive-org-chart)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://www.npmjs.com/package/ngx-interactive-org-chart)
|
|
8
|
+
|
|
9
|
+
A beautiful, interactive organizational chart component for Angular applications. Built with modern Angular features and designed for ease of use and customization.
|
|
10
|
+
|
|
11
|
+
## ✨ Features
|
|
12
|
+
|
|
13
|
+
- 🎯 **Interactive Pan & Zoom** - Smooth navigation with mouse/touch
|
|
14
|
+
- 🗺️ **Mini Map Navigation** - Bird's-eye view with drag navigation and real-time viewport tracking
|
|
15
|
+
- 🌳 **Hierarchical Layout** - Perfect for organizational structures
|
|
16
|
+
- 🎨 **Fully Themable** - Complete theme system including mini map customization with CSS variable support
|
|
17
|
+
- 📱 **Mobile Friendly** - Touch gestures support
|
|
18
|
+
- ⚡ **High Performance** - Optimized rendering with canvas-based mini map
|
|
19
|
+
- 🔍 **Searchable Nodes** - Easily find nodes in large charts
|
|
20
|
+
- 🧭 **Focus & Highlight** - Quickly navigate to specific nodes
|
|
21
|
+
- 📊 **Custom Node Templates** - Use Angular templates for nodes
|
|
22
|
+
- 🖱️ **Drag & Drop** - Reorganize nodes with drag and drop support
|
|
23
|
+
- 🎯 **Custom Drag Handles** - Use custom templates for drag handles
|
|
24
|
+
- 📈 **Dynamic Data Binding** - Reactive updates with Angular signals
|
|
25
|
+
- 📦 **Tree Shakable** - Import only what you need
|
|
26
|
+
- 🔄 **Collapsible Nodes** - Expand/collapse functionality
|
|
27
|
+
- 🌐 **RTL Support** - Right-to-left text direction
|
|
28
|
+
- 🌓 **Dark Mode Ready** - Automatic theme detection and CSS variable resolution
|
|
29
|
+
- 🧩 **Modular Design** - Standalone component for easy integration
|
|
30
|
+
- 🔧 **TypeScript Support** - Full type definitions included
|
|
31
|
+
- 🛠️ **Easy Setup** - Minimal configuration required
|
|
32
|
+
- 🎪 **Angular 20+** - Built with latest Angular features
|
|
33
|
+
- 🆓 **100% Free** - Open source MIT license
|
|
34
|
+
|
|
35
|
+
## 📋 Version Compatibility
|
|
36
|
+
|
|
37
|
+
| ngx-interactive-org-chart | Angular Version | Notes |
|
|
38
|
+
| ------------------------- | --------------- | -------------------------------- |
|
|
39
|
+
| 1.1.4 | Angular 19 | Stable release |
|
|
40
|
+
| 1.2.x | Angular 20+ | Drag & drop, RTL support |
|
|
41
|
+
| 1.3.x | Angular 20+ | Mini map, dark mode, performance |
|
|
42
|
+
|
|
43
|
+
## 🚀 Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm install ngx-interactive-org-chart
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Setup Angular Animations
|
|
50
|
+
|
|
51
|
+
The component uses Angular animations for smooth transitions. Add the animations module to your `main.ts`:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { bootstrapApplication } from '@angular/platform-browser';
|
|
55
|
+
import { provideAnimations } from '@angular/platform-browser/animations';
|
|
56
|
+
import { AppComponent } from './app/app.component';
|
|
57
|
+
|
|
58
|
+
bootstrapApplication(AppComponent, {
|
|
59
|
+
providers: [
|
|
60
|
+
provideAnimations(), // Required for ngx-interactive-org-chart
|
|
61
|
+
// ... your other providers
|
|
62
|
+
],
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Or if you're using NgModules:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
|
70
|
+
|
|
71
|
+
@NgModule({
|
|
72
|
+
imports: [
|
|
73
|
+
BrowserModule,
|
|
74
|
+
BrowserAnimationsModule, // Required for ngx-interactive-org-chart
|
|
75
|
+
// ... your other modules
|
|
76
|
+
],
|
|
77
|
+
// ...
|
|
78
|
+
})
|
|
79
|
+
export class AppModule {}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 📖 Usage
|
|
83
|
+
|
|
84
|
+
### Basic Example
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import { Component } from '@angular/core';
|
|
88
|
+
import {
|
|
89
|
+
NgxInteractiveOrgChart,
|
|
90
|
+
OrgChartNode,
|
|
91
|
+
} from 'ngx-interactive-org-chart';
|
|
92
|
+
|
|
93
|
+
@Component({
|
|
94
|
+
selector: 'app-demo',
|
|
95
|
+
standalone: true,
|
|
96
|
+
imports: [NgxInteractiveOrgChart],
|
|
97
|
+
template: `
|
|
98
|
+
<ngx-interactive-org-chart [data]="orgData" [themeOptions]="themeOptions" />
|
|
99
|
+
`,
|
|
100
|
+
})
|
|
101
|
+
export class DemoComponent {
|
|
102
|
+
orgData: OrgChartNode = {
|
|
103
|
+
id: 'ceo', // auto generated if not provided
|
|
104
|
+
name: 'John Smith',
|
|
105
|
+
data: {
|
|
106
|
+
// add any additional data properties here to customize the node and use it for displaying different types of nodes
|
|
107
|
+
},
|
|
108
|
+
children: [
|
|
109
|
+
{
|
|
110
|
+
id: 'cto',
|
|
111
|
+
name: 'Jane Doe',
|
|
112
|
+
children: [
|
|
113
|
+
{
|
|
114
|
+
id: 'dev1',
|
|
115
|
+
name: 'Mike Johnson',
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: 'cfo',
|
|
121
|
+
name: 'Sarah Wilson',
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Data Structure
|
|
129
|
+
|
|
130
|
+
The component expects hierarchical data in the following format:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
interface OrgChartNode<T = any> {
|
|
134
|
+
id?: string;
|
|
135
|
+
name?: string;
|
|
136
|
+
data?: T;
|
|
137
|
+
children?: OrgChartNode<T>[];
|
|
138
|
+
collapsed?: boolean;
|
|
139
|
+
hidden?: boolean;
|
|
140
|
+
nodeClass?: string;
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Component Options
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
interface NgxInteractiveOrgChartTheme {
|
|
148
|
+
node?: {
|
|
149
|
+
background?: string;
|
|
150
|
+
color?: string;
|
|
151
|
+
shadow?: string;
|
|
152
|
+
outlineColor?: string;
|
|
153
|
+
outlineWidth?: string;
|
|
154
|
+
activeOutlineColor?: string;
|
|
155
|
+
highlightShadowColor?: string;
|
|
156
|
+
padding?: string;
|
|
157
|
+
borderRadius?: string;
|
|
158
|
+
activeColor?: string;
|
|
159
|
+
containerSpacing?: string;
|
|
160
|
+
maxWidth?: string;
|
|
161
|
+
minWidth?: string;
|
|
162
|
+
maxHeight?: string;
|
|
163
|
+
minHeight?: string;
|
|
164
|
+
dragOverOutlineColor?: string;
|
|
165
|
+
};
|
|
166
|
+
connector?: {
|
|
167
|
+
color?: string;
|
|
168
|
+
activeColor?: string;
|
|
169
|
+
borderRadius?: string;
|
|
170
|
+
width?: string;
|
|
171
|
+
};
|
|
172
|
+
collapseButton?: {
|
|
173
|
+
size?: string;
|
|
174
|
+
borderColor?: string;
|
|
175
|
+
borderRadius?: string;
|
|
176
|
+
color?: string;
|
|
177
|
+
background?: string;
|
|
178
|
+
hoverColor?: string;
|
|
179
|
+
hoverBackground?: string;
|
|
180
|
+
hoverShadow?: string;
|
|
181
|
+
hoverTransformScale?: string;
|
|
182
|
+
focusOutline?: string;
|
|
183
|
+
countFontSize?: string;
|
|
184
|
+
};
|
|
185
|
+
container?: {
|
|
186
|
+
background?: string;
|
|
187
|
+
border?: string;
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### 🎯 Smart Zoom & Highlighting
|
|
193
|
+
|
|
194
|
+
The component features intelligent zoom calculation that automatically adjusts to provide optimal viewing of highlighted nodes:
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
// Configure dynamic zoom behavior
|
|
198
|
+
<ngx-interactive-org-chart
|
|
199
|
+
[data]="orgData"
|
|
200
|
+
[highlightZoomNodeWidthRatio]="0.4" // Node takes 40% of container width
|
|
201
|
+
[highlightZoomNodeHeightRatio]="0.5" // Node takes 50% of container height
|
|
202
|
+
[highlightZoomMinimum]="1.0" // Never zoom below 100%
|
|
203
|
+
/>
|
|
204
|
+
|
|
205
|
+
// Programmatically highlight nodes
|
|
206
|
+
@ViewChild(NgxInteractiveOrgChart) orgChart!: NgxInteractiveOrgChart;
|
|
207
|
+
|
|
208
|
+
highlightManager() {
|
|
209
|
+
this.orgChart.highlightNode('cto'); // Automatically zooms to optimal level
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## 📐 Layout Options
|
|
214
|
+
|
|
215
|
+
The component supports both vertical and horizontal layout orientations:
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
// Vertical layout (default)
|
|
219
|
+
<ngx-interactive-org-chart
|
|
220
|
+
[data]="orgData"
|
|
221
|
+
layout="vertical"
|
|
222
|
+
/>
|
|
223
|
+
|
|
224
|
+
// Horizontal layout
|
|
225
|
+
<ngx-interactive-org-chart
|
|
226
|
+
[data]="orgData"
|
|
227
|
+
layout="horizontal"
|
|
228
|
+
/>
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## 🖱️ Pan Functionality
|
|
232
|
+
|
|
233
|
+
The component includes built-in pan functionality that allows users to navigate large organizational charts:
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
// Pan functionality is enabled by default
|
|
237
|
+
// Users can click and drag to pan around the chart
|
|
238
|
+
// Touch gestures are supported on mobile devices
|
|
239
|
+
|
|
240
|
+
@ViewChild(NgxInteractiveOrgChart) orgChart!: NgxInteractiveOrgChart;
|
|
241
|
+
|
|
242
|
+
// Programmatically control panning
|
|
243
|
+
panToSpecificLocation() {
|
|
244
|
+
// Pan to specific coordinates (x, y, smooth)
|
|
245
|
+
this.orgChart.pan(100, 200, true); // Pans to x: 100, y: 200 with smooth animation
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Reset pan to center
|
|
249
|
+
resetPanning() {
|
|
250
|
+
this.orgChart.resetPan(); // Centers the chart
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Reset both pan and zoom
|
|
254
|
+
resetView() {
|
|
255
|
+
this.orgChart.resetPanAndZoom(); // Centers and fits the chart
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**Pan Features:**
|
|
260
|
+
|
|
261
|
+
- **Mouse Support:** Click and drag to pan around the chart
|
|
262
|
+
- **Touch Support:** Touch and drag gestures on mobile devices
|
|
263
|
+
- **Smooth Animation:** Animated transitions when panning programmatically
|
|
264
|
+
- **Momentum:** Natural momentum-based panning for smooth user experience
|
|
265
|
+
|
|
266
|
+
## 📋 Component Properties
|
|
267
|
+
|
|
268
|
+
| Property | Type | Default | Description |
|
|
269
|
+
| ------------------------------ | ------------------------------ | ------------ | ------------------------------------------------------------------ |
|
|
270
|
+
| `data` | `OrgChartNode` | required | The organizational data to display |
|
|
271
|
+
| `collapsible` | `boolean` | `true` | Enable/disable node collapsing |
|
|
272
|
+
| `layout` | `'vertical' \| 'horizontal'` | `'vertical'` | Chart layout orientation |
|
|
273
|
+
| `themeOptions` | `NgxInteractiveOrgChartTheme` | `{}` | Theme configuration options for styling |
|
|
274
|
+
| `nodeClass` | `string` | `undefined` | Custom CSS class applied to all nodes |
|
|
275
|
+
| `initialZoom` | `number` | `undefined` | Initial zoom level |
|
|
276
|
+
| `minZoom` | `number` | `0.1` | Minimum zoom level |
|
|
277
|
+
| `maxZoom` | `number` | `5` | Maximum zoom level |
|
|
278
|
+
| `zoomSpeed` | `number` | `1` | Zoom speed multiplier |
|
|
279
|
+
| `zoomDoubleClickSpeed` | `number` | `2` | Double-click zoom speed multiplier |
|
|
280
|
+
| `initialCollapsed` | `boolean` | `false` | Initial collapsed state for all nodes |
|
|
281
|
+
| `isRtl` | `boolean` | `false` | Right-to-left text direction support |
|
|
282
|
+
| `displayChildrenCount` | `boolean` | `true` | Show children count on collapse buttons |
|
|
283
|
+
| `highlightZoomNodeWidthRatio` | `number` | `0.3` | Node width ratio relative to viewport when highlighting (0.1-1.0) |
|
|
284
|
+
| `highlightZoomNodeHeightRatio` | `number` | `0.4` | Node height ratio relative to viewport when highlighting (0.1-1.0) |
|
|
285
|
+
| `highlightZoomMinimum` | `number` | `0.8` | Minimum zoom level when highlighting a node |
|
|
286
|
+
| `draggable` | `boolean` | `false` | Enable drag and drop functionality for nodes |
|
|
287
|
+
| `canDragNode` | `(node) => boolean` | `undefined` | Predicate function to determine if a node can be dragged |
|
|
288
|
+
| `canDropNode` | `(dragged, target) => boolean` | `undefined` | Predicate function to validate drop operations |
|
|
289
|
+
| `dragEdgeThreshold` | `number` | `0.1` | Auto-pan threshold is calculated as 10% of container dimensions |
|
|
290
|
+
| `dragAutoPanSpeed` | `number` | `15` | Speed of auto-panning in pixels per frame during drag |
|
|
291
|
+
|
|
292
|
+
### Component Events
|
|
293
|
+
|
|
294
|
+
| Event | Type | Description |
|
|
295
|
+
| --------------- | --------------------------------------------------------------- | ------------------------------------------- |
|
|
296
|
+
| `nodeDrop` | `{ draggedNode: OrgChartNode<T>, targetNode: OrgChartNode<T> }` | Emitted when a node is dropped onto another |
|
|
297
|
+
| `nodeDragStart` | `OrgChartNode<T>` | Emitted when a node drag operation starts |
|
|
298
|
+
| `nodeDragEnd` | `OrgChartNode<T>` | Emitted when a node drag operation ends |
|
|
299
|
+
|
|
300
|
+
### Component Methods
|
|
301
|
+
|
|
302
|
+
The component exposes several useful methods that can be called using a template reference:
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
@Component({
|
|
306
|
+
template: `
|
|
307
|
+
<ngx-interactive-org-chart #orgChart [data]="orgData" />
|
|
308
|
+
<button (click)="orgChart.zoomIn({ by: 10, relative: true })">Zoom In</button>
|
|
309
|
+
<button (click)="orgChart.zoomOut({ by: 10, relative: true })">Zoom Out</button>
|
|
310
|
+
<!-- Reset zoom and pan takes padding param for outer container -->
|
|
311
|
+
<button (click)="orgChart.resetPanAndZoom(50)">Reset</button>
|
|
312
|
+
<button (click)="orgChart.resetPan()">Reset Pan</button>
|
|
313
|
+
<button (click)="orgChart.resetZoom()">Reset Zoom</button>
|
|
314
|
+
<!-- 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 -->
|
|
315
|
+
<button (click)="orgChart.highlightNode('cto')">Highlight CTO</button>
|
|
316
|
+
`
|
|
317
|
+
})
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
| Method | Description |
|
|
321
|
+
| ------------------------------ | ---------------------------------------- |
|
|
322
|
+
| `zoomIn(options?)` | Zooms in the chart |
|
|
323
|
+
| `zoomOut(options?)` | Zooms out the chart |
|
|
324
|
+
| `resetZoom(padding?)` | Resets zoom to fit content |
|
|
325
|
+
| `resetPan()` | Resets pan position to center |
|
|
326
|
+
| `resetPanAndZoom(padding?)` | Resets both pan and zoom |
|
|
327
|
+
| `highlightNode(nodeId)` | Highlights and focuses a specific node |
|
|
328
|
+
| `toggleCollapseAll(collapse?)` | Collapses or expands all nodes |
|
|
329
|
+
| `getScale()` | Returns current zoom scale as percentage |
|
|
330
|
+
|
|
331
|
+
### Dynamic Zoom Configuration
|
|
332
|
+
|
|
333
|
+
The component supports dynamic zoom calculation when highlighting nodes. This ensures optimal zoom levels based on the node size and viewport dimensions:
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
@Component({
|
|
337
|
+
template: `
|
|
338
|
+
<ngx-interactive-org-chart
|
|
339
|
+
[data]="orgData"
|
|
340
|
+
[highlightZoomNodeWidthRatio]="0.4"
|
|
341
|
+
[highlightZoomNodeHeightRatio]="0.5"
|
|
342
|
+
[highlightZoomMinimum]="1.0"
|
|
343
|
+
/>
|
|
344
|
+
`
|
|
345
|
+
})
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
**Configuration Options:**
|
|
349
|
+
|
|
350
|
+
- `highlightZoomNodeWidthRatio` (0.1-1.0): How much of the viewport width the highlighted node should occupy
|
|
351
|
+
- `highlightZoomNodeHeightRatio` (0.1-1.0): How much of the viewport height the highlighted node should occupy
|
|
352
|
+
- `highlightZoomMinimum`: Minimum zoom level when highlighting (prevents over-zooming out)
|
|
353
|
+
|
|
354
|
+
**Examples:**
|
|
355
|
+
|
|
356
|
+
- Small nodes: Use higher ratios (0.4-0.6) for better visibility
|
|
357
|
+
- Large nodes: Use lower ratios (0.2-0.3) to avoid excessive zoom
|
|
358
|
+
- Mobile devices: Consider using higher minimum zoom for readability
|
|
359
|
+
|
|
360
|
+
### Custom Node Templates
|
|
361
|
+
|
|
362
|
+
You can customize how nodes are displayed by providing your own template. Use the `#nodeTemplate` template reference to override the default node appearance:
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
enum TypeEnum {
|
|
366
|
+
Employee = 'employee',
|
|
367
|
+
Contractor = 'contractor',
|
|
368
|
+
Department = 'department',
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
interface ApiResponse {
|
|
372
|
+
readonly id: number;
|
|
373
|
+
readonly name: string;
|
|
374
|
+
readonly title?: string;
|
|
375
|
+
readonly thumbnail?: string;
|
|
376
|
+
readonly type: TypeEnum;
|
|
377
|
+
readonly children?: ApiResponse[];
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
@Component({
|
|
381
|
+
selector: 'app-custom-org-chart',
|
|
382
|
+
standalone: true,
|
|
383
|
+
imports: [NgxInteractiveOrgChart],
|
|
384
|
+
template: `
|
|
385
|
+
<ngx-interactive-org-chart
|
|
386
|
+
[data]="orgChartData() ?? {}"
|
|
387
|
+
[themeOptions]="themeOptions"
|
|
388
|
+
[displayChildrenCount]="false"
|
|
389
|
+
>
|
|
390
|
+
<ng-template #nodeTemplate let-node="node">
|
|
391
|
+
@let nodeData = node?.data;
|
|
392
|
+
|
|
393
|
+
@switch (true) {
|
|
394
|
+
@case (
|
|
395
|
+
nodeData.type === dataTypeEnum.Employee ||
|
|
396
|
+
nodeData.type === dataTypeEnum.Contractor
|
|
397
|
+
) {
|
|
398
|
+
@let isContractor = nodeData.type === dataTypeEnum.Contractor;
|
|
399
|
+
|
|
400
|
+
<section class="demo__employee">
|
|
401
|
+
<section class="demo__employee-thumbnail">
|
|
402
|
+
<img [src]="nodeData?.thumbnail" />
|
|
403
|
+
</section>
|
|
404
|
+
<section class="demo__employee-details">
|
|
405
|
+
<span class="demo__employee-details-name">{{
|
|
406
|
+
nodeData?.name
|
|
407
|
+
}}</span>
|
|
408
|
+
<span class="demo__employee-details-position">{{
|
|
409
|
+
nodeData?.title
|
|
410
|
+
}}</span>
|
|
411
|
+
@if (isContractor) {
|
|
412
|
+
<small class="demo__employee-details-type">Contractor</small>
|
|
413
|
+
}
|
|
414
|
+
</section>
|
|
415
|
+
</section>
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
@case (nodeData.type === dataTypeEnum.Department) {
|
|
419
|
+
<section class="demo__department">
|
|
420
|
+
<section class="demo__department-details">
|
|
421
|
+
<span class="demo__department-details-name">{{
|
|
422
|
+
nodeData?.name
|
|
423
|
+
}}</span>
|
|
424
|
+
<span class="demo__department-details-description">
|
|
425
|
+
{{ node?.descendantsCount }} Members
|
|
426
|
+
</span>
|
|
427
|
+
</section>
|
|
428
|
+
</section>
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
</ng-template>
|
|
432
|
+
</ngx-interactive-org-chart>
|
|
433
|
+
`,
|
|
434
|
+
styles: [
|
|
435
|
+
`
|
|
436
|
+
.demo {
|
|
437
|
+
&__employee {
|
|
438
|
+
display: flex;
|
|
439
|
+
gap: 1rem;
|
|
440
|
+
align-items: center;
|
|
441
|
+
|
|
442
|
+
&-thumbnail {
|
|
443
|
+
img {
|
|
444
|
+
border-radius: 50%;
|
|
445
|
+
width: 3rem;
|
|
446
|
+
height: 3rem;
|
|
447
|
+
object-fit: cover;
|
|
448
|
+
box-shadow: 0 0 0.25rem rgba(0, 0, 0, 0.3);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
&-details {
|
|
453
|
+
display: flex;
|
|
454
|
+
flex-direction: column;
|
|
455
|
+
gap: 0.25rem;
|
|
456
|
+
align-items: flex-start;
|
|
457
|
+
|
|
458
|
+
&-name {
|
|
459
|
+
color: var(--text-primary);
|
|
460
|
+
font-weight: 600;
|
|
461
|
+
font-size: 0.875rem;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
&-position {
|
|
465
|
+
font-size: 0.75rem;
|
|
466
|
+
color: #6c757d;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
&-type {
|
|
470
|
+
font-size: 0.5rem;
|
|
471
|
+
background-color: rgb(203, 225, 232);
|
|
472
|
+
padding: 0.125rem 0.25rem;
|
|
473
|
+
border-radius: 0.25rem;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
&__department {
|
|
479
|
+
display: flex;
|
|
480
|
+
gap: 1rem;
|
|
481
|
+
align-items: center;
|
|
482
|
+
|
|
483
|
+
&-details {
|
|
484
|
+
display: flex;
|
|
485
|
+
flex-direction: column;
|
|
486
|
+
gap: 0.25rem;
|
|
487
|
+
align-items: flex-start;
|
|
488
|
+
|
|
489
|
+
&-name {
|
|
490
|
+
font-weight: 600;
|
|
491
|
+
font-size: 0.875rem;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
&-description {
|
|
495
|
+
font-size: 0.75rem;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
&-name {
|
|
500
|
+
font-weight: 600;
|
|
501
|
+
font-size: 0.875rem;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
`,
|
|
506
|
+
],
|
|
507
|
+
})
|
|
508
|
+
export class CustomOrgChartComponent {
|
|
509
|
+
data: ApiResponse = {
|
|
510
|
+
id: 1,
|
|
511
|
+
name: 'Company',
|
|
512
|
+
type: TypeEnum.Department,
|
|
513
|
+
children: [
|
|
514
|
+
{
|
|
515
|
+
id: 2,
|
|
516
|
+
name: 'Engineering',
|
|
517
|
+
type: TypeEnum.Department,
|
|
518
|
+
children: [
|
|
519
|
+
{
|
|
520
|
+
id: 3,
|
|
521
|
+
name: 'Alice Johnson',
|
|
522
|
+
title: 'Software Engineer',
|
|
523
|
+
thumbnail: 'https://randomuser.me/api/portraits/women/21.jpg',
|
|
524
|
+
type: TypeEnum.Employee,
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
id: 4,
|
|
528
|
+
name: 'Bob Smith',
|
|
529
|
+
title: 'Senior Developer',
|
|
530
|
+
thumbnail: 'https://randomuser.me/api/portraits/men/21.jpg',
|
|
531
|
+
type: TypeEnum.Contractor,
|
|
532
|
+
},
|
|
533
|
+
],
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
id: 5,
|
|
537
|
+
name: 'Marketing',
|
|
538
|
+
type: TypeEnum.Department,
|
|
539
|
+
children: [
|
|
540
|
+
{
|
|
541
|
+
id: 6,
|
|
542
|
+
name: 'Carol White',
|
|
543
|
+
title: 'Marketing Manager',
|
|
544
|
+
thumbnail: 'https://randomuser.me/api/portraits/women/21.jpg',
|
|
545
|
+
type: TypeEnum.Employee,
|
|
546
|
+
},
|
|
547
|
+
],
|
|
548
|
+
},
|
|
549
|
+
],
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
protected readonly orgChartData = signal<OrgChartNode<ApiResponse> | null>(
|
|
553
|
+
null
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
readonly #setOrgChartData = effect(() => {
|
|
557
|
+
this.orgChartData.set(this.mapDataToOrgChartNode(this.data));
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
protected readonly dataTypeEnum = TypeEnum;
|
|
561
|
+
|
|
562
|
+
protected readonly themeOptions: NgxInteractiveOrgChartTheme = {
|
|
563
|
+
node: {
|
|
564
|
+
background: 'white',
|
|
565
|
+
color: 'black',
|
|
566
|
+
shadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
|
567
|
+
borderRadius: '8px',
|
|
568
|
+
outlineColor: '#e0e0e0',
|
|
569
|
+
activeOutlineColor: '#1976d2',
|
|
570
|
+
},
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
private mapDataToOrgChartNode({
|
|
574
|
+
children,
|
|
575
|
+
...data
|
|
576
|
+
}: ApiResponse): OrgChartNode<ApiResponse> {
|
|
577
|
+
return {
|
|
578
|
+
id: data.id.toString(),
|
|
579
|
+
name: data.name, // for search purposes
|
|
580
|
+
collapsed: data.type === TypeEnum.Department, // collapse departments by default
|
|
581
|
+
style: {
|
|
582
|
+
// Apply any conditional styles here: For example, different background colors based on type
|
|
583
|
+
background: data.type === TypeEnum.Department ? '#e3f2fd' : '#f1f1f1',
|
|
584
|
+
color: data.type === TypeEnum.Department ? '#1976d2' : '#333',
|
|
585
|
+
// or you can just use predefined css variables (preferable)
|
|
586
|
+
'--node-background':
|
|
587
|
+
data.type === TypeEnum.Department ? '#e3f2fd' : '#f1f1f1',
|
|
588
|
+
'--node-color': data.type === TypeEnum.Department ? '#1976d2' : '#333',
|
|
589
|
+
},
|
|
590
|
+
// you can also set a custom class for each node, but make sure you apply this class in ng-deep
|
|
591
|
+
nodeClass:
|
|
592
|
+
data.type === TypeEnum.Department ? 'department-node' : 'employee-node',
|
|
593
|
+
data: {
|
|
594
|
+
...data,
|
|
595
|
+
},
|
|
596
|
+
children: children?.map(child => this.mapDataToOrgChartNode(child)) || [],
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
The custom template receives the node data through the `let-node="node"` directive. You can access:
|
|
603
|
+
|
|
604
|
+
- `node.name` - The node name
|
|
605
|
+
- `node.data` - Custom data object with any properties you define
|
|
606
|
+
- `node.id` - Unique node identifier
|
|
607
|
+
- `node.children` - Array of child nodes
|
|
608
|
+
- `node.collapsed` - Current collapsed state
|
|
609
|
+
- `node.descendantsCount` - Total number of descendants (useful for displaying counts)
|
|
610
|
+
|
|
611
|
+
## 🖱️ Drag & Drop
|
|
612
|
+
|
|
613
|
+
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.
|
|
614
|
+
|
|
615
|
+
### Basic Drag & Drop Setup
|
|
616
|
+
|
|
617
|
+
```typescript
|
|
618
|
+
import { Component, signal } from '@angular/core';
|
|
619
|
+
import {
|
|
620
|
+
NgxInteractiveOrgChart,
|
|
621
|
+
OrgChartNode,
|
|
622
|
+
moveNode,
|
|
623
|
+
} from 'ngx-interactive-org-chart';
|
|
624
|
+
|
|
625
|
+
@Component({
|
|
626
|
+
selector: 'app-drag-drop-demo',
|
|
627
|
+
standalone: true,
|
|
628
|
+
imports: [NgxInteractiveOrgChart],
|
|
629
|
+
template: `
|
|
630
|
+
<ngx-interactive-org-chart
|
|
631
|
+
[data]="orgData()"
|
|
632
|
+
[draggable]="true"
|
|
633
|
+
(nodeDrop)="onNodeDrop($event)"
|
|
634
|
+
(nodeDragStart)="onDragStart($event)"
|
|
635
|
+
(nodeDragEnd)="onDragEnd($event)"
|
|
636
|
+
/>
|
|
637
|
+
`,
|
|
638
|
+
})
|
|
639
|
+
export class DragDropDemoComponent {
|
|
640
|
+
orgData = signal<OrgChartNode>({
|
|
641
|
+
id: '1',
|
|
642
|
+
name: 'CEO',
|
|
643
|
+
children: [
|
|
644
|
+
{ id: '2', name: 'CTO', children: [] },
|
|
645
|
+
{ id: '3', name: 'CFO', children: [] },
|
|
646
|
+
],
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Handle node drop event.
|
|
651
|
+
* IMPORTANT: The library does NOT modify your data automatically.
|
|
652
|
+
* You must handle the data restructuring yourself.
|
|
653
|
+
*/
|
|
654
|
+
onNodeDrop(event: { draggedNode: OrgChartNode; targetNode: OrgChartNode }) {
|
|
655
|
+
const currentData = this.orgData();
|
|
656
|
+
|
|
657
|
+
// Option 1: Use the built-in helper function (recommended)
|
|
658
|
+
const updatedData = moveNode(
|
|
659
|
+
currentData,
|
|
660
|
+
event.draggedNode.id,
|
|
661
|
+
event.targetNode.id
|
|
662
|
+
);
|
|
663
|
+
|
|
664
|
+
if (updatedData) {
|
|
665
|
+
this.orgData.set(updatedData);
|
|
666
|
+
// Optionally save to backend
|
|
667
|
+
// this.api.updateOrgStructure(updatedData);
|
|
668
|
+
} else {
|
|
669
|
+
alert('Cannot move node: Invalid operation');
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Option 2: Implement your own custom logic
|
|
673
|
+
// const updatedData = this.customMoveLogic(currentData, event);
|
|
674
|
+
// this.orgData.set(updatedData);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
onDragStart(node: OrgChartNode) {
|
|
678
|
+
console.log('Drag started:', node.name);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
onDragEnd(node: OrgChartNode) {
|
|
682
|
+
console.log('Drag ended:', node.name);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
### Custom Drag Handle
|
|
688
|
+
|
|
689
|
+
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:
|
|
690
|
+
|
|
691
|
+
```typescript
|
|
692
|
+
@Component({
|
|
693
|
+
template: `
|
|
694
|
+
<ngx-interactive-org-chart
|
|
695
|
+
[data]="orgData()"
|
|
696
|
+
[draggable]="true"
|
|
697
|
+
(nodeDrop)="onNodeDrop($event)"
|
|
698
|
+
>
|
|
699
|
+
<!-- Custom node template -->
|
|
700
|
+
<ng-template #nodeTemplate let-node="node">
|
|
701
|
+
<div class="custom-node">
|
|
702
|
+
<h3>{{ node.name }}</h3>
|
|
703
|
+
<p>{{ node.data?.title }}</p>
|
|
704
|
+
</div>
|
|
705
|
+
</ng-template>
|
|
706
|
+
|
|
707
|
+
<!-- Custom drag handle template -->
|
|
708
|
+
<ng-template #dragHandleTemplate let-node="node">
|
|
709
|
+
<button class="drag-handle" title="Drag to move">
|
|
710
|
+
⋮⋮
|
|
711
|
+
</button>
|
|
712
|
+
</ng-template>
|
|
713
|
+
</ngx-interactive-org-chart>
|
|
714
|
+
`,
|
|
715
|
+
styles: [`
|
|
716
|
+
.drag-handle {
|
|
717
|
+
cursor: move;
|
|
718
|
+
padding: 4px 8px;
|
|
719
|
+
background: #f0f0f0;
|
|
720
|
+
border: 1px solid #ccc;
|
|
721
|
+
border-radius: 4px;
|
|
722
|
+
user-select: none;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
.drag-handle:hover {
|
|
726
|
+
background: #e0e0e0;
|
|
727
|
+
}
|
|
728
|
+
`]
|
|
729
|
+
})
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
### Helper Functions
|
|
733
|
+
|
|
734
|
+
The library provides several helper functions for common tree operations:
|
|
735
|
+
|
|
736
|
+
```typescript
|
|
737
|
+
import {
|
|
738
|
+
moveNode,
|
|
739
|
+
findNode,
|
|
740
|
+
removeNode,
|
|
741
|
+
addNodeToParent,
|
|
742
|
+
isNodeDescendant,
|
|
743
|
+
} from 'ngx-interactive-org-chart';
|
|
744
|
+
|
|
745
|
+
// Move a node to a new parent
|
|
746
|
+
const updatedTree = moveNode(tree, draggedNodeId, targetParentId);
|
|
747
|
+
|
|
748
|
+
// Find a specific node by ID
|
|
749
|
+
const node = findNode(tree, nodeId);
|
|
750
|
+
|
|
751
|
+
// Remove a node from the tree
|
|
752
|
+
const treeWithoutNode = removeNode(tree, nodeId);
|
|
753
|
+
|
|
754
|
+
// Add a node to a specific parent
|
|
755
|
+
const treeWithNewNode = addNodeToParent(tree, parentId, newNode);
|
|
756
|
+
|
|
757
|
+
// Check if a node is a descendant of another
|
|
758
|
+
const isDescendant = isNodeDescendant(ancestorNode, descendantId);
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
### Drag & Drop Features
|
|
762
|
+
|
|
763
|
+
- **Auto-panning**: Automatically pans the view when dragging near viewport edges (configurable threshold and speed)
|
|
764
|
+
- **Visual Feedback**: Shows drag-over state on target nodes with color hints
|
|
765
|
+
- **Drag Constraints**: Use `canDragNode` and `canDropNode` predicates to control what can be dragged and where
|
|
766
|
+
- **ESC to Cancel**: Press ESC key during drag to cancel the operation
|
|
767
|
+
- **Validation**: Prevents dropping nodes on themselves or their descendants
|
|
768
|
+
- **Custom Handles**: Optional custom drag handle templates
|
|
769
|
+
- **Events**: Full control with dragStart, dragEnd, and drop events
|
|
770
|
+
- **Helper Functions**: Built-in utilities for tree manipulation
|
|
771
|
+
- **Pure Functions**: All helpers are immutable and return new tree structures
|
|
772
|
+
- **Touch Screen Support**: Full drag and drop support on mobile devices and tablets
|
|
773
|
+
|
|
774
|
+
### Touch Screen Support
|
|
775
|
+
|
|
776
|
+
The drag and drop functionality works seamlessly on touch-enabled devices (smartphones and tablets):
|
|
777
|
+
|
|
778
|
+
**Features:**
|
|
779
|
+
|
|
780
|
+
- **Touch Gestures**: Long press or drag to initiate drag operation
|
|
781
|
+
- **Visual Ghost Element**: A semi-transparent copy of the node follows your finger during drag
|
|
782
|
+
- **Auto-panning**: Works with touch gestures when dragging near screen edges
|
|
783
|
+
- **Drop Zones**: Visual feedback shows valid/invalid drop targets
|
|
784
|
+
- **Smooth Performance**: Optimized for 60fps touch interactions
|
|
785
|
+
- **Hybrid Support**: Works on devices with both touch and mouse input
|
|
786
|
+
|
|
787
|
+
**How it works:**
|
|
788
|
+
|
|
789
|
+
1. Touch and hold a draggable node
|
|
790
|
+
2. Start moving your finger - a ghost element appears after a small movement threshold (10px)
|
|
791
|
+
3. The org chart auto-pans when you drag near the edges
|
|
792
|
+
4. Valid drop targets show visual feedback (dashed outline)
|
|
793
|
+
5. Invalid targets show a "not-allowed" indicator
|
|
794
|
+
6. Release to drop, or drag outside to cancel
|
|
795
|
+
|
|
796
|
+
**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.
|
|
797
|
+
|
|
798
|
+
**Example:**
|
|
799
|
+
|
|
800
|
+
```typescript
|
|
801
|
+
@Component({
|
|
802
|
+
template: `
|
|
803
|
+
<ngx-interactive-org-chart
|
|
804
|
+
[data]="orgData()"
|
|
805
|
+
[draggable]="true"
|
|
806
|
+
(nodeDrop)="onNodeDrop($event)"
|
|
807
|
+
/>
|
|
808
|
+
`,
|
|
809
|
+
})
|
|
810
|
+
export class MyComponent {
|
|
811
|
+
// Works with both mouse and touch
|
|
812
|
+
onNodeDrop(event: { draggedNode: OrgChartNode; targetNode: OrgChartNode }) {
|
|
813
|
+
const updated = moveNode(
|
|
814
|
+
this.orgData(),
|
|
815
|
+
event.draggedNode.id,
|
|
816
|
+
event.targetNode.id
|
|
817
|
+
);
|
|
818
|
+
if (updated) {
|
|
819
|
+
this.orgData.set(updated);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
### Drag Constraints & Validation
|
|
826
|
+
|
|
827
|
+
Control which nodes can be dragged and where they can be dropped using predicate functions:
|
|
828
|
+
|
|
829
|
+
```typescript
|
|
830
|
+
@Component({
|
|
831
|
+
template: `
|
|
832
|
+
<ngx-interactive-org-chart
|
|
833
|
+
[data]="orgData()"
|
|
834
|
+
[draggable]="true"
|
|
835
|
+
[canDragNode]="canDragNode"
|
|
836
|
+
[canDropNode]="canDropNode"
|
|
837
|
+
[dragAutoPanSpeed]="20"
|
|
838
|
+
(nodeDrop)="onNodeDrop($event)"
|
|
839
|
+
/>
|
|
840
|
+
`,
|
|
841
|
+
})
|
|
842
|
+
export class MyComponent {
|
|
843
|
+
// Prevent specific nodes from being dragged
|
|
844
|
+
canDragNode = (node: OrgChartNode) => {
|
|
845
|
+
// Example: CEO can't be moved
|
|
846
|
+
return node.data?.role !== 'CEO';
|
|
847
|
+
};
|
|
848
|
+
|
|
849
|
+
// Control where nodes can be dropped
|
|
850
|
+
canDropNode = (draggedNode: OrgChartNode, targetNode: OrgChartNode) => {
|
|
851
|
+
// Example: Only departments can have children
|
|
852
|
+
if (targetNode.data?.type !== 'Department') {
|
|
853
|
+
return false;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Example: Managers can't report to employees
|
|
857
|
+
if (
|
|
858
|
+
draggedNode.data?.role === 'Manager' &&
|
|
859
|
+
targetNode.data?.role === 'Employee'
|
|
860
|
+
) {
|
|
861
|
+
return false;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
return true;
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
onNodeDrop(event: { draggedNode: OrgChartNode; targetNode: OrgChartNode }) {
|
|
868
|
+
// Use built-in helper to move nodes
|
|
869
|
+
const updated = moveNode(
|
|
870
|
+
this.orgData(),
|
|
871
|
+
event.draggedNode.id,
|
|
872
|
+
event.targetNode.id
|
|
873
|
+
);
|
|
874
|
+
|
|
875
|
+
if (updated) {
|
|
876
|
+
this.orgData.set(updated);
|
|
877
|
+
// Optionally sync with backend
|
|
878
|
+
this.api.updateOrgChart(updated);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
**Visual Feedback:**
|
|
885
|
+
|
|
886
|
+
- Valid drop targets show a **dashed outline with subtle background tint**
|
|
887
|
+
- Invalid drop targets show **reduced opacity and not-allowed cursor**
|
|
888
|
+
- Press **ESC** to cancel drag operation at any time
|
|
889
|
+
|
|
890
|
+
**Auto-panning Configuration:**
|
|
891
|
+
|
|
892
|
+
The auto-pan threshold is **automatically calculated as 10% of the container dimensions**, making it responsive across all screen sizes:
|
|
893
|
+
|
|
894
|
+
- **Small screens (mobile)**: Smaller activation zone prevents accidental auto-panning
|
|
895
|
+
- **Large screens (desktop)**: Larger comfortable edge zones
|
|
896
|
+
- **No configuration needed**: Works perfectly out of the box
|
|
897
|
+
|
|
898
|
+
You can still customize the panning speed:
|
|
899
|
+
|
|
900
|
+
- `dragAutoPanSpeed` (default: 15): Panning speed in pixels per frame
|
|
901
|
+
|
|
902
|
+
> **Note**: The `dragEdgeThreshold` property is deprecated. The threshold is now dynamically calculated for optimal UX.
|
|
903
|
+
|
|
904
|
+
### Data Handling Pattern
|
|
905
|
+
|
|
906
|
+
**Important**: The library follows a controlled component pattern and does NOT modify your data automatically. This design gives you:
|
|
907
|
+
|
|
908
|
+
- ✅ **Full control** over validation logic
|
|
909
|
+
- ✅ **Backend synchronization** capabilities
|
|
910
|
+
- ✅ **Custom business rules** implementation
|
|
911
|
+
- ✅ **Undo/redo** functionality support
|
|
912
|
+
- ✅ **Optimistic updates** with rollback
|
|
913
|
+
|
|
914
|
+
**Pattern:**
|
|
915
|
+
|
|
916
|
+
1. User drags and drops a node
|
|
917
|
+
2. Library emits `nodeDrop` event with source and target nodes
|
|
918
|
+
3. You handle the event and restructure your data
|
|
919
|
+
4. Update your data signal/input
|
|
920
|
+
5. Library automatically re-renders the updated structure
|
|
921
|
+
|
|
922
|
+
```typescript
|
|
923
|
+
onNodeDrop(event: { draggedNode: OrgChartNode; targetNode: OrgChartNode }) {
|
|
924
|
+
// 1. Get current data
|
|
925
|
+
const currentData = this.orgData();
|
|
926
|
+
|
|
927
|
+
// 2. Validate the operation (optional)
|
|
928
|
+
if (!this.canMove(event.draggedNode, event.targetNode)) {
|
|
929
|
+
this.showError('Cannot move this node');
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// 3. Update the data structure
|
|
934
|
+
const updatedData = moveNode(
|
|
935
|
+
currentData,
|
|
936
|
+
event.draggedNode.id,
|
|
937
|
+
event.targetNode.id
|
|
938
|
+
);
|
|
939
|
+
|
|
940
|
+
if (!updatedData) return;
|
|
941
|
+
|
|
942
|
+
// 4. Update state (with rollback capability)
|
|
943
|
+
const previousData = currentData;
|
|
944
|
+
this.orgData.set(updatedData);
|
|
945
|
+
|
|
946
|
+
// 5. Sync with backend
|
|
947
|
+
this.api.updateOrgChart(updatedData).subscribe({
|
|
948
|
+
error: () => {
|
|
949
|
+
// Rollback on error
|
|
950
|
+
this.orgData.set(previousData);
|
|
951
|
+
this.showError('Failed to update');
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
## 🗺️ Mini Map Navigation
|
|
958
|
+
|
|
959
|
+
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.
|
|
960
|
+
|
|
961
|
+
### Basic Mini Map Setup
|
|
962
|
+
|
|
963
|
+
```typescript
|
|
964
|
+
import { Component } from '@angular/core';
|
|
965
|
+
import { NgxInteractiveOrgChart } from 'ngx-interactive-org-chart';
|
|
966
|
+
|
|
967
|
+
@Component({
|
|
968
|
+
selector: 'app-minimap-demo',
|
|
969
|
+
standalone: true,
|
|
970
|
+
imports: [NgxInteractiveOrgChart],
|
|
971
|
+
template: `
|
|
972
|
+
<ngx-interactive-org-chart
|
|
973
|
+
[data]="orgData"
|
|
974
|
+
[showMiniMap]="true"
|
|
975
|
+
[miniMapPosition]="'bottom-right'"
|
|
976
|
+
[miniMapWidth]="200"
|
|
977
|
+
[miniMapHeight]="150"
|
|
978
|
+
/>
|
|
979
|
+
`,
|
|
980
|
+
})
|
|
981
|
+
export class MiniMapDemoComponent {
|
|
982
|
+
orgData = {
|
|
983
|
+
id: '1',
|
|
984
|
+
name: 'CEO',
|
|
985
|
+
children: [
|
|
986
|
+
/* ... your org chart data ... */
|
|
987
|
+
],
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
### Mini Map Configuration
|
|
993
|
+
|
|
994
|
+
| Input | Type | Default | Description |
|
|
995
|
+
| ----------------- | -------------------------------------------------------------- | ---------------- | ---------------------------------- |
|
|
996
|
+
| `showMiniMap` | `boolean` | `false` | Enable/disable the mini map |
|
|
997
|
+
| `miniMapPosition` | `'top-left' \| 'top-right' \| 'bottom-left' \| 'bottom-right'` | `'bottom-right'` | Position of the mini map on screen |
|
|
998
|
+
| `miniMapWidth` | `number` | `200` | Width of the mini map in pixels |
|
|
999
|
+
| `miniMapHeight` | `number` | `150` | Height of the mini map in pixels |
|
|
1000
|
+
|
|
1001
|
+
### Mini Map Features
|
|
1002
|
+
|
|
1003
|
+
- **📊 Visual Overview**: Shows a simplified view of the entire organizational chart
|
|
1004
|
+
- **🎯 Viewport Indicator**: Blue rectangle shows your current visible area
|
|
1005
|
+
- **� Drag Navigation**: Drag the viewport indicator to smoothly pan the main chart
|
|
1006
|
+
- **🔄 Auto-Update**: Automatically updates when the chart structure changes
|
|
1007
|
+
- **🎨 Fully Themable**: Customizable through theme options with 8 configurable properties
|
|
1008
|
+
- **⚡ High Performance**: Optimized rendering with debounced updates
|
|
1009
|
+
|
|
1010
|
+
### Example with All Options
|
|
1011
|
+
|
|
1012
|
+
```typescript
|
|
1013
|
+
@Component({
|
|
1014
|
+
selector: 'app-advanced-minimap',
|
|
1015
|
+
standalone: true,
|
|
1016
|
+
imports: [NgxInteractiveOrgChart],
|
|
1017
|
+
template: `
|
|
1018
|
+
<ngx-interactive-org-chart
|
|
1019
|
+
[data]="largeOrgData"
|
|
1020
|
+
[showMiniMap]="miniMapVisible()"
|
|
1021
|
+
[miniMapPosition]="miniMapPosition()"
|
|
1022
|
+
[miniMapWidth]="250"
|
|
1023
|
+
[miniMapHeight]="180"
|
|
1024
|
+
[themeOptions]="themeOptions"
|
|
1025
|
+
/>
|
|
1026
|
+
|
|
1027
|
+
<!-- Toggle button -->
|
|
1028
|
+
<button (click)="toggleMiniMap()">
|
|
1029
|
+
{{ miniMapVisible() ? 'Hide' : 'Show' }} Mini Map
|
|
1030
|
+
</button>
|
|
1031
|
+
`,
|
|
1032
|
+
})
|
|
1033
|
+
export class AdvancedMiniMapComponent {
|
|
1034
|
+
miniMapVisible = signal(true);
|
|
1035
|
+
miniMapPosition = signal<
|
|
1036
|
+
'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
|
|
1037
|
+
>('bottom-right');
|
|
1038
|
+
|
|
1039
|
+
largeOrgData = {
|
|
1040
|
+
// ... large organizational chart data
|
|
1041
|
+
};
|
|
1042
|
+
|
|
1043
|
+
themeOptions = {
|
|
1044
|
+
// ... your theme options
|
|
1045
|
+
};
|
|
1046
|
+
|
|
1047
|
+
toggleMiniMap() {
|
|
1048
|
+
this.miniMapVisible.update(v => !v);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
### Mini Map Styling
|
|
1054
|
+
|
|
1055
|
+
The mini map is fully themable through the `themeOptions` input. It automatically inherits and adapts to your chart's theme configuration:
|
|
1056
|
+
|
|
1057
|
+
```typescript
|
|
1058
|
+
import { NgxInteractiveOrgChartTheme } from 'ngx-interactive-org-chart';
|
|
1059
|
+
|
|
1060
|
+
const customTheme: NgxInteractiveOrgChartTheme = {
|
|
1061
|
+
// ... other theme options
|
|
1062
|
+
miniMap: {
|
|
1063
|
+
background: 'rgba(255, 255, 255, 0.95)',
|
|
1064
|
+
borderColor: 'rgba(0, 0, 0, 0.15)',
|
|
1065
|
+
borderRadius: '8px',
|
|
1066
|
+
shadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
|
|
1067
|
+
nodeColor: 'rgba(0, 0, 0, 0.6)',
|
|
1068
|
+
viewportBackground: 'rgba(59, 130, 246, 0.2)',
|
|
1069
|
+
viewportBorderColor: 'rgb(59, 130, 246)',
|
|
1070
|
+
viewportBorderWidth: '2px',
|
|
1071
|
+
},
|
|
1072
|
+
};
|
|
1073
|
+
```
|
|
1074
|
+
|
|
1075
|
+
#### Mini Map Theme Options
|
|
1076
|
+
|
|
1077
|
+
| Property | Type | Default | Description |
|
|
1078
|
+
| --------------------- | -------- | --------------------------- | --------------------------------- |
|
|
1079
|
+
| `background` | `string` | `rgba(255, 255, 255, 0.95)` | Background color of the mini map |
|
|
1080
|
+
| `borderColor` | `string` | `rgba(0, 0, 0, 0.15)` | Border color of the mini map |
|
|
1081
|
+
| `borderRadius` | `string` | `8px` | Border radius of the mini map |
|
|
1082
|
+
| `shadow` | `string` | `0 4px 6px -1px rgba(...)` | Box shadow of the mini map |
|
|
1083
|
+
| `nodeColor` | `string` | `rgba(0, 0, 0, 0.6)` | Color of nodes in the mini map |
|
|
1084
|
+
| `viewportBackground` | `string` | `rgba(59, 130, 246, 0.2)` | Background color of viewport area |
|
|
1085
|
+
| `viewportBorderColor` | `string` | `rgb(59, 130, 246)` | Border color of viewport area |
|
|
1086
|
+
| `viewportBorderWidth` | `string` | `2px` | Border width of viewport area |
|
|
1087
|
+
|
|
1088
|
+
You can also use CSS custom properties for additional customization:
|
|
1089
|
+
|
|
1090
|
+
```scss
|
|
1091
|
+
::ng-deep ngx-org-chart-mini-map {
|
|
1092
|
+
.mini-map-container {
|
|
1093
|
+
// Override theme values with custom CSS
|
|
1094
|
+
border-radius: 12px !important;
|
|
1095
|
+
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2) !important;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
.viewport-indicator {
|
|
1099
|
+
// Custom styling for the viewport indicator
|
|
1100
|
+
border-width: 3px !important;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
```
|
|
1104
|
+
|
|
1105
|
+
### Performance Considerations
|
|
1106
|
+
|
|
1107
|
+
The mini map is optimized for performance:
|
|
1108
|
+
|
|
1109
|
+
- Uses HTML Canvas for efficient rendering
|
|
1110
|
+
- Debounced updates (100ms) to prevent excessive redraws
|
|
1111
|
+
- MutationObserver for smart DOM change detection
|
|
1112
|
+
- RequestAnimationFrame for smooth viewport tracking
|
|
1113
|
+
- Only redraws when necessary (chart changes, pan, zoom)
|
|
1114
|
+
|
|
1115
|
+
### Use Cases
|
|
1116
|
+
|
|
1117
|
+
The mini map is particularly useful for:
|
|
1118
|
+
|
|
1119
|
+
- **Large Organizations**: Navigate charts with 50+ nodes
|
|
1120
|
+
- **Deep Hierarchies**: Quickly jump between different levels
|
|
1121
|
+
- **Complex Structures**: Overview of multi-department organizations
|
|
1122
|
+
- **Presentations**: Show context while focusing on specific areas
|
|
1123
|
+
- **User Onboarding**: Help users understand chart structure
|
|
1124
|
+
|
|
1125
|
+
## 🎨 Styling
|
|
1126
|
+
|
|
1127
|
+
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.
|
|
1128
|
+
|
|
1129
|
+
## 📊 Live Demo
|
|
1130
|
+
|
|
1131
|
+
Check out the interactive demo to see the component in action:
|
|
1132
|
+
|
|
1133
|
+
**[View Live Demo →](https://zeyadelshaf3y.github.io/ngx-interactive-org-chart)**
|
|
1134
|
+
|
|
1135
|
+
## 🤝 Contributing
|
|
1136
|
+
|
|
1137
|
+
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.
|
|
1138
|
+
|
|
1139
|
+
1. Fork the project
|
|
1140
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
1141
|
+
3. Commit your changes (`git commit -m 'Add some amazing-feature'`)
|
|
1142
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
1143
|
+
5. Open a Pull Request
|
|
1144
|
+
|
|
1145
|
+
## 🤖 Issues & Support
|
|
1146
|
+
|
|
1147
|
+
If you encounter any issues or have questions:
|
|
1148
|
+
|
|
1149
|
+
1. Check the [GitHub Issues](https://github.com/zeyadelshaf3y/ngx-interactive-org-chart/issues)
|
|
1150
|
+
2. Create a new issue with a detailed description
|
|
1151
|
+
3. Include your Angular version and reproduction steps
|
|
1152
|
+
|
|
1153
|
+
## 💝 Support the Project
|
|
1154
|
+
|
|
1155
|
+
If this library helps you, consider supporting its development:
|
|
1156
|
+
|
|
1157
|
+
- ⭐ Star the repository on GitHub
|
|
1158
|
+
- 🐛 Report bugs and suggest features
|
|
1159
|
+
- 💝 [Buy me a coffee](https://buymeacoffee.com/zeyadalshafey)
|
|
1160
|
+
- 💖 [GitHub Sponsors](https://github.com/sponsors/zeyadelshaf3y)
|
|
1161
|
+
|
|
1162
|
+
## 📄 License
|
|
1163
|
+
|
|
1164
|
+
MIT © [Zeyad Alshafey](https://github.com/zeyadelshaf3y)
|
|
1165
|
+
|
|
1166
|
+
## 🔗 Links
|
|
1167
|
+
|
|
1168
|
+
- [GitHub Repository](https://github.com/zeyadelshaf3y/ngx-interactive-org-chart)
|
|
1169
|
+
- [NPM Package](https://www.npmjs.com/package/ngx-interactive-org-chart)
|
|
1170
|
+
- [Live Demo](https://zeyadelshaf3y.github.io/ngx-interactive-org-chart)
|
|
1171
|
+
- [Issues](https://github.com/zeyadelshaf3y/ngx-interactive-org-chart/issues)
|