@ngx-km/graph 0.0.1 → 0.0.2

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.
Files changed (2) hide show
  1. package/README.md +333 -17
  2. package/package.json +5 -5
package/README.md CHANGED
@@ -1,16 +1,16 @@
1
1
  # @ngx-km/graph
2
2
 
3
- Main graph visualization library combining all sub-libraries into a unified component.
3
+ Angular 19+ library for creating interactive graph visualizations with custom node components.
4
4
 
5
5
  ## Features
6
6
 
7
- - Unified graph component integrating grid, layout, path-finding, and path-drawing
8
- - Custom Angular components as nodes
9
- - Type-safe node and relationship definitions
10
- - Automatic layout with ELK.js
11
- - Intelligent path routing between nodes
12
- - Custom path pill components
13
- - Reactive input handling with position preservation
7
+ - Use any Angular component as a node
8
+ - Automatic graph layout (hierarchical, force-directed, stress)
9
+ - Obstacle-aware path routing between nodes
10
+ - Infinite canvas with pan and zoom
11
+ - Draggable nodes with position tracking
12
+ - Custom labels on paths (pill components)
13
+ - Fully type-safe API
14
14
 
15
15
  ## Installation
16
16
 
@@ -18,26 +18,342 @@ Main graph visualization library combining all sub-libraries into a unified comp
18
18
  npm install @ngx-km/graph
19
19
  ```
20
20
 
21
- ## Usage
21
+ ## Quick Start
22
22
 
23
23
  ```typescript
24
- import { GraphComponent } from '@ngx-km/graph';
24
+ import { Component } from '@angular/core';
25
+ import { GraphComponent, GraphNode, GraphRelationship } from '@ngx-km/graph';
26
+ import { MyNodeComponent } from './my-node.component';
25
27
 
26
28
  @Component({
29
+ selector: 'app-root',
30
+ standalone: true,
27
31
  imports: [GraphComponent],
28
32
  template: `
29
33
  <ngx-graph
30
34
  [nodes]="nodes"
31
35
  [relationships]="relationships"
32
- [nodeComponent]="MyNodeComponent">
33
- </ngx-graph>
34
- `
36
+ />
37
+ `,
38
+ styles: [`
39
+ :host { display: block; width: 100%; height: 100vh; }
40
+ `]
35
41
  })
36
- export class MyComponent {}
42
+ export class AppComponent {
43
+ nodes: GraphNode<MyNodeData>[] = [
44
+ { id: '1', component: MyNodeComponent, data: { label: 'Node 1' } },
45
+ { id: '2', component: MyNodeComponent, data: { label: 'Node 2' } },
46
+ { id: '3', component: MyNodeComponent, data: { label: 'Node 3' } },
47
+ ];
48
+
49
+ relationships: GraphRelationship[] = [
50
+ { id: 'e1', sourceId: '1', targetId: '2' },
51
+ { id: 'e2', sourceId: '2', targetId: '3' },
52
+ ];
53
+ }
37
54
  ```
38
55
 
39
- ## Running unit tests
56
+ ## Creating Node Components
40
57
 
41
- ```bash
42
- nx test ngx-graph
58
+ Node components receive data via the `GRAPH_NODE_DATA` injection token:
59
+
60
+ ```typescript
61
+ import { Component, inject } from '@angular/core';
62
+ import { GRAPH_NODE_DATA } from '@ngx-km/graph';
63
+
64
+ interface MyNodeData {
65
+ label: string;
66
+ status?: 'active' | 'inactive';
67
+ }
68
+
69
+ @Component({
70
+ selector: 'app-my-node',
71
+ standalone: true,
72
+ template: `
73
+ <div class="node" [class.active]="data?.status === 'active'">
74
+ {{ data?.label }}
75
+ </div>
76
+ `,
77
+ styles: [`
78
+ .node {
79
+ padding: 16px 24px;
80
+ background: white;
81
+ border: 2px solid #e5e7eb;
82
+ border-radius: 8px;
83
+ font-weight: 500;
84
+ }
85
+ .node.active {
86
+ border-color: #10b981;
87
+ background: #ecfdf5;
88
+ }
89
+ `]
90
+ })
91
+ export class MyNodeComponent {
92
+ data = inject<MyNodeData>(GRAPH_NODE_DATA, { optional: true });
93
+ }
43
94
  ```
95
+
96
+ ## API Reference
97
+
98
+ ### Inputs
99
+
100
+ | Input | Type | Description |
101
+ |-------|------|-------------|
102
+ | `nodes` | `GraphNode<T>[]` | Array of node definitions |
103
+ | `relationships` | `GraphRelationship<T>[]` | Array of relationship definitions |
104
+ | `config` | `GraphConfig` | Configuration object |
105
+
106
+ ### Outputs
107
+
108
+ | Output | Type | Description |
109
+ |--------|------|-------------|
110
+ | `nodePositionChange` | `NodePositionChangeEvent` | Emitted when a node is dragged |
111
+ | `viewportChange` | `ViewportState` | Emitted on pan/zoom |
112
+
113
+ ### Public Methods
114
+
115
+ ```typescript
116
+ // Get a reference to the graph component
117
+ @ViewChild(GraphComponent) graph!: GraphComponent;
118
+
119
+ // Fit all content to viewport
120
+ this.graph.fitToView(fitZoom?: boolean, padding?: number);
121
+
122
+ // Center viewport on a specific node
123
+ this.graph.centerOnNode(nodeId: string);
124
+
125
+ // Scroll node into view with minimal pan
126
+ this.graph.scrollToNode(nodeId: string, padding?: number);
127
+
128
+ // Check if node is visible in viewport
129
+ this.graph.isNodeVisible(nodeId: string, padding?: number): boolean;
130
+
131
+ // Force recalculate layout
132
+ this.graph.recalculateLayout();
133
+ ```
134
+
135
+ ## Interfaces
136
+
137
+ ### GraphNode
138
+
139
+ ```typescript
140
+ interface GraphNode<T = unknown> {
141
+ id: string; // Unique identifier
142
+ component: Type<unknown>; // Angular component to render
143
+ data?: T; // Data passed to component
144
+ x?: number; // Initial X position (optional)
145
+ y?: number; // Initial Y position (optional)
146
+ }
147
+ ```
148
+
149
+ ### GraphRelationship
150
+
151
+ ```typescript
152
+ interface GraphRelationship<T = unknown> {
153
+ id: string; // Unique identifier
154
+ sourceId: string; // Source node ID
155
+ targetId: string; // Target node ID
156
+ type?: 'one-way' | 'two-way'; // Arrow direction
157
+ data?: T; // Data passed to pill component
158
+ sourceAnchorSide?: 'top' | 'right' | 'bottom' | 'left';
159
+ targetAnchorSide?: 'top' | 'right' | 'bottom' | 'left';
160
+ }
161
+ ```
162
+
163
+ ## Configuration
164
+
165
+ ### Full Configuration Example
166
+
167
+ ```typescript
168
+ import { GraphConfig } from '@ngx-km/graph';
169
+
170
+ const config: GraphConfig = {
171
+ // Grid settings
172
+ grid: {
173
+ backgroundMode: 'dots', // 'dots' | 'lines' | 'none'
174
+ cellSize: 20,
175
+ panEnabled: true,
176
+ zoomEnabled: true,
177
+ dragEnabled: true,
178
+ minZoom: 0.1,
179
+ maxZoom: 3,
180
+ },
181
+
182
+ // Layout settings
183
+ layout: {
184
+ algorithm: 'layered', // 'layered' | 'force' | 'stress'
185
+ direction: 'DOWN', // 'DOWN' | 'UP' | 'LEFT' | 'RIGHT'
186
+ nodeSpacing: 50,
187
+ layerSpacing: 100,
188
+ autoLayout: true,
189
+ preservePositions: true,
190
+ fitPadding: 40,
191
+ },
192
+
193
+ // Path settings
194
+ paths: {
195
+ pathType: 'orthogonal', // 'orthogonal' | 'bezier' | 'straight'
196
+ strokeColor: '#6366f1',
197
+ strokeWidth: 2,
198
+ strokePattern: 'solid', // 'solid' | 'dashed' | 'dotted'
199
+ cornerRadius: 8,
200
+ arrowSize: 10,
201
+ obstaclePadding: 20,
202
+ },
203
+
204
+ // Optional: Component for path labels
205
+ pillComponent: MyPillComponent,
206
+ };
207
+ ```
208
+
209
+ ### Layout Algorithms
210
+
211
+ | Algorithm | Best For |
212
+ |-----------|----------|
213
+ | `layered` | Hierarchical structures, flowcharts, org charts |
214
+ | `force` | Networks, social graphs, unstructured data |
215
+ | `stress` | General purpose, good edge length consistency |
216
+
217
+ ### Path Types
218
+
219
+ | Type | Description |
220
+ |------|-------------|
221
+ | `orthogonal` | Right-angle paths that avoid obstacles |
222
+ | `bezier` | Smooth curved paths |
223
+ | `straight` | Direct lines between nodes |
224
+
225
+ ## Pill Components
226
+
227
+ Add labels or controls to paths using pill components:
228
+
229
+ ```typescript
230
+ import { Component, inject } from '@angular/core';
231
+ import { GRAPH_RELATIONSHIP_DATA } from '@ngx-km/graph';
232
+
233
+ interface EdgeData {
234
+ label: string;
235
+ }
236
+
237
+ @Component({
238
+ selector: 'app-edge-pill',
239
+ standalone: true,
240
+ template: `
241
+ <div class="pill">{{ data?.label }}</div>
242
+ `,
243
+ styles: [`
244
+ .pill {
245
+ padding: 4px 12px;
246
+ background: #6366f1;
247
+ color: white;
248
+ border-radius: 12px;
249
+ font-size: 12px;
250
+ }
251
+ `]
252
+ })
253
+ export class EdgePillComponent {
254
+ data = inject<EdgeData>(GRAPH_RELATIONSHIP_DATA, { optional: true });
255
+ }
256
+ ```
257
+
258
+ Use it in config:
259
+
260
+ ```typescript
261
+ const config: GraphConfig = {
262
+ pillComponent: EdgePillComponent,
263
+ };
264
+ ```
265
+
266
+ ## Reactive Data Updates
267
+
268
+ Both node and relationship data support Angular Signals for reactive updates:
269
+
270
+ ```typescript
271
+ import { signal } from '@angular/core';
272
+
273
+ // Data as signals - component updates automatically
274
+ nodes: GraphNode[] = [
275
+ {
276
+ id: '1',
277
+ component: MyNodeComponent,
278
+ data: signal({ label: 'Node 1', count: 0 }),
279
+ },
280
+ ];
281
+
282
+ // Update data reactively
283
+ updateNode() {
284
+ const nodeData = this.nodes[0].data as WritableSignal<MyNodeData>;
285
+ nodeData.update(d => ({ ...d, count: d.count + 1 }));
286
+ }
287
+ ```
288
+
289
+ ## Complete Example
290
+
291
+ ```typescript
292
+ import { Component, signal } from '@angular/core';
293
+ import {
294
+ GraphComponent,
295
+ GraphNode,
296
+ GraphRelationship,
297
+ GraphConfig,
298
+ } from '@ngx-km/graph';
299
+ import { MyNodeComponent } from './my-node.component';
300
+ import { EdgePillComponent } from './edge-pill.component';
301
+
302
+ @Component({
303
+ selector: 'app-workflow',
304
+ standalone: true,
305
+ imports: [GraphComponent],
306
+ template: `
307
+ <ngx-graph
308
+ #graph
309
+ [nodes]="nodes"
310
+ [relationships]="relationships"
311
+ [config]="config"
312
+ (nodePositionChange)="onNodeMoved($event)"
313
+ />
314
+ <button (click)="graph.fitToView(true)">Fit to View</button>
315
+ `,
316
+ })
317
+ export class WorkflowComponent {
318
+ nodes: GraphNode[] = [
319
+ { id: 'start', component: MyNodeComponent, data: { label: 'Start', type: 'start' } },
320
+ { id: 'process', component: MyNodeComponent, data: { label: 'Process', type: 'task' } },
321
+ { id: 'decision', component: MyNodeComponent, data: { label: 'Approve?', type: 'decision' } },
322
+ { id: 'end', component: MyNodeComponent, data: { label: 'End', type: 'end' } },
323
+ ];
324
+
325
+ relationships: GraphRelationship[] = [
326
+ { id: 'e1', sourceId: 'start', targetId: 'process' },
327
+ { id: 'e2', sourceId: 'process', targetId: 'decision' },
328
+ { id: 'e3', sourceId: 'decision', targetId: 'end', data: { label: 'Yes' } },
329
+ ];
330
+
331
+ config: GraphConfig = {
332
+ layout: {
333
+ algorithm: 'layered',
334
+ direction: 'DOWN',
335
+ nodeSpacing: 60,
336
+ layerSpacing: 120,
337
+ },
338
+ paths: {
339
+ pathType: 'orthogonal',
340
+ strokeColor: '#6366f1',
341
+ cornerRadius: 8,
342
+ },
343
+ pillComponent: EdgePillComponent,
344
+ };
345
+
346
+ onNodeMoved(event: NodePositionChangeEvent) {
347
+ console.log(`Node ${event.nodeId} moved to (${event.x}, ${event.y})`);
348
+ }
349
+ }
350
+ ```
351
+
352
+ ## Requirements
353
+
354
+ - Angular 19+
355
+ - TypeScript 5.4+
356
+
357
+ ## License
358
+
359
+ MIT
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@ngx-km/graph",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "dependencies": {
5
- "@ngx-km/grid": "^0.0.1",
6
- "@ngx-km/layout": "^0.0.1",
7
- "@ngx-km/path-finding": "^0.0.1",
8
- "@ngx-km/path-drawing": "^0.0.1",
5
+ "@ngx-km/grid": "^0.0.2",
6
+ "@ngx-km/layout": "^0.0.2",
7
+ "@ngx-km/path-finding": "^0.0.2",
8
+ "@ngx-km/path-drawing": "^0.0.2",
9
9
  "tslib": "^2.3.0"
10
10
  },
11
11
  "peerDependencies": {