@memberjunction/ng-entity-relationship-diagram 0.0.1 → 2.130.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.
Files changed (38) hide show
  1. package/README.md +754 -27
  2. package/dist/lib/components/entity-details/entity-details.component.d.ts +70 -0
  3. package/dist/lib/components/entity-details/entity-details.component.d.ts.map +1 -0
  4. package/dist/lib/components/entity-details/entity-details.component.js +694 -0
  5. package/dist/lib/components/entity-details/entity-details.component.js.map +1 -0
  6. package/dist/lib/components/entity-filter-panel/entity-filter-panel.component.d.ts +48 -0
  7. package/dist/lib/components/entity-filter-panel/entity-filter-panel.component.d.ts.map +1 -0
  8. package/dist/lib/components/entity-filter-panel/entity-filter-panel.component.js +179 -0
  9. package/dist/lib/components/entity-filter-panel/entity-filter-panel.component.js.map +1 -0
  10. package/dist/lib/components/erd-composite/erd-composite.component.d.ts +170 -0
  11. package/dist/lib/components/erd-composite/erd-composite.component.d.ts.map +1 -0
  12. package/dist/lib/components/erd-composite/erd-composite.component.js +391 -0
  13. package/dist/lib/components/erd-composite/erd-composite.component.js.map +1 -0
  14. package/dist/lib/components/erd-diagram.component.d.ts +486 -0
  15. package/dist/lib/components/erd-diagram.component.d.ts.map +1 -0
  16. package/dist/lib/components/erd-diagram.component.js +1936 -0
  17. package/dist/lib/components/erd-diagram.component.js.map +1 -0
  18. package/dist/lib/components/mj-entity-erd.component.d.ts +261 -0
  19. package/dist/lib/components/mj-entity-erd.component.d.ts.map +1 -0
  20. package/dist/lib/components/mj-entity-erd.component.js +416 -0
  21. package/dist/lib/components/mj-entity-erd.component.js.map +1 -0
  22. package/dist/lib/entity-relationship-diagram.module.d.ts +83 -0
  23. package/dist/lib/entity-relationship-diagram.module.d.ts.map +1 -0
  24. package/dist/lib/entity-relationship-diagram.module.js +121 -0
  25. package/dist/lib/entity-relationship-diagram.module.js.map +1 -0
  26. package/dist/lib/interfaces/erd-types.d.ts +491 -0
  27. package/dist/lib/interfaces/erd-types.d.ts.map +1 -0
  28. package/dist/lib/interfaces/erd-types.js +6 -0
  29. package/dist/lib/interfaces/erd-types.js.map +1 -0
  30. package/dist/lib/utils/entity-to-erd-adapter.d.ts +64 -0
  31. package/dist/lib/utils/entity-to-erd-adapter.d.ts.map +1 -0
  32. package/dist/lib/utils/entity-to-erd-adapter.js +231 -0
  33. package/dist/lib/utils/entity-to-erd-adapter.js.map +1 -0
  34. package/dist/public-api.d.ts +9 -0
  35. package/dist/public-api.d.ts.map +1 -0
  36. package/dist/public-api.js +16 -0
  37. package/dist/public-api.js.map +1 -0
  38. package/package.json +45 -6
package/README.md CHANGED
@@ -1,45 +1,772 @@
1
1
  # @memberjunction/ng-entity-relationship-diagram
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
3
+ A package providing **two Angular components** for rendering interactive Entity Relationship Diagrams (ERD) using D3.js force-directed graphs:
4
4
 
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
5
+ 1. **`<mj-entity-erd>`** - Higher-level MemberJunction wrapper that accepts `EntityInfo[]` directly
6
+ 2. **`<mj-erd-diagram>`** - Generic, reusable component that works with any data
6
7
 
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
8
+ ## Which Component Should I Use?
8
9
 
9
- ## Purpose
10
+ | Use Case | Component | Why |
11
+ |----------|-----------|-----|
12
+ | **MemberJunction project** | `<mj-entity-erd>` | Auto-discovers relationships, handles transformation |
13
+ | **Custom data sources** | `<mj-erd-diagram>` | Full control over node/link data |
14
+ | **Single entity + related** | `<mj-entity-erd>` | Built-in depth/relationship discovery |
15
+ | **Non-MJ Angular project** | `<mj-erd-diagram>` | No MJ dependencies in your code |
10
16
 
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@memberjunction/ng-entity-relationship-diagram`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
17
+ ---
18
+
19
+ ## `<mj-entity-erd>` - MemberJunction Entity Wrapper
20
+
21
+ The higher-level wrapper component designed specifically for MemberJunction projects. It accepts `EntityInfo[]` directly and handles all the transformation logic internally.
22
+
23
+ ### Features
24
+
25
+ - **Direct EntityInfo Input** - No manual transformation needed
26
+ - **Automatic Relationship Discovery** - Finds related entities automatically
27
+ - **Configurable Depth** - Control how many relationship hops to include
28
+ - **Parent-Controlled State** - Emits state changes for persistence by parent
29
+ - **Bidirectional Relationships** - Optionally include incoming and outgoing relationships
30
+
31
+ ### Basic Usage
32
+
33
+ ```typescript
34
+ import { EntityRelationshipDiagramModule } from '@memberjunction/ng-entity-relationship-diagram';
35
+ import { EntityInfo, Metadata } from '@memberjunction/core';
36
+
37
+ @Component({
38
+ selector: 'my-entity-viewer',
39
+ template: `
40
+ <mj-entity-erd
41
+ [entities]="selectedEntities"
42
+ [allEntities]="allEntities"
43
+ [selectedEntityId]="currentEntityId"
44
+ [depth]="1"
45
+ [includeIncoming]="true"
46
+ [includeOutgoing]="true"
47
+ (entitySelected)="onEntitySelected($event)"
48
+ (openRecord)="onOpenEntity($event)">
49
+ </mj-entity-erd>
50
+ `
51
+ })
52
+ export class MyEntityViewerComponent {
53
+ allEntities: EntityInfo[] = [];
54
+ selectedEntities: EntityInfo[] = [];
55
+ currentEntityId: string | null = null;
56
+
57
+ constructor() {
58
+ const md = new Metadata();
59
+ this.allEntities = md.Entities;
60
+ }
61
+
62
+ onEntitySelected(entity: EntityInfo) {
63
+ this.currentEntityId = entity.ID;
64
+ }
65
+
66
+ onOpenEntity(entity: EntityInfo) {
67
+ // Navigate to entity record
68
+ this.router.navigate(['/entities', entity.ID]);
69
+ }
70
+ }
71
+ ```
72
+
73
+ ### API Reference
74
+
75
+ #### Inputs
76
+
77
+ | Input | Type | Default | Description |
78
+ |-------|------|---------|-------------|
79
+ | `entities` | `EntityInfo[]` | `[]` | Primary entities to display |
80
+ | `allEntities` | `EntityInfo[]` | `[]` | All entities for relationship discovery |
81
+ | `selectedEntityId` | `string \| null` | `null` | ID of currently selected entity |
82
+ | `depth` | `number` | `1` | Relationship hops to include (0 = only primary entities) |
83
+ | `includeIncoming` | `boolean` | `true` | Include entities that reference primary entities |
84
+ | `includeOutgoing` | `boolean` | `true` | Include entities referenced by primary entities via FK |
85
+ | `showHeader` | `boolean` | `true` | Show header with zoom controls |
86
+ | `headerTitle` | `string` | `'Entity Relationship Diagram'` | Header title |
87
+ | `isRefreshing` | `boolean` | `false` | Show loading overlay |
88
+ | `readOnly` | `boolean` | `false` | Disable interactions |
89
+ | `config` | `ERDConfig` | `{}` | Configuration options |
90
+
91
+ #### Outputs
92
+
93
+ | Output | Type | Description |
94
+ |--------|------|-------------|
95
+ | `entitySelected` | `EntityInfo` | Entity was selected |
96
+ | `entityDeselected` | `void` | Selection was cleared |
97
+ | `openRecord` | `EntityInfo` | Entity double-clicked (typically for navigation) |
98
+ | `stateChange` | `ERDState` | Diagram state changed (zoom, pan, selection) |
99
+
100
+ ### Single Entity Mode (Entity Forms)
101
+
102
+ Perfect for showing one entity with its relationships:
15
103
 
16
- ## What is OIDC Trusted Publishing?
104
+ ```html
105
+ <mj-entity-erd
106
+ [entities]="[currentEntity]"
107
+ [allEntities]="allEntities"
108
+ [selectedEntityId]="currentEntity.ID"
109
+ [depth]="1"
110
+ [showHeader]="false"
111
+ (openRecord)="navigateToEntity($event)">
112
+ </mj-entity-erd>
113
+ ```
17
114
 
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
115
+ ### Multi-Entity Mode (Dashboards)
19
116
 
20
- ## Setup Instructions
117
+ Display multiple entities with full navigation:
21
118
 
22
- To properly configure OIDC trusted publishing for this package:
119
+ ```html
120
+ <mj-entity-erd
121
+ [entities]="filteredEntities"
122
+ [allEntities]="allEntities"
123
+ [selectedEntityId]="selectedId"
124
+ [depth]="1"
125
+ [includeIncoming]="true"
126
+ [includeOutgoing]="true"
127
+ (entitySelected)="onSelect($event)"
128
+ (entityDeselected)="onDeselect()"
129
+ (openRecord)="openEntityForm($event)"
130
+ (stateChange)="saveUserPreferences($event)">
131
+ </mj-entity-erd>
132
+ ```
23
133
 
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
134
+ ### State Persistence Example
28
135
 
29
- ## DO NOT USE THIS PACKAGE
136
+ The wrapper emits state changes but doesn't persist them. Handle persistence in your container:
30
137
 
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
138
+ ```typescript
139
+ @Component({...})
140
+ export class EntityDashboardComponent {
141
+ savedState: ERDState | null = null;
36
142
 
37
- ## More Information
143
+ ngOnInit() {
144
+ // Load saved preferences
145
+ const saved = localStorage.getItem('entity-erd-state');
146
+ if (saved) {
147
+ this.savedState = JSON.parse(saved);
148
+ }
149
+ }
38
150
 
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
151
+ onStateChange(state: ERDState) {
152
+ // Save to user preferences
153
+ localStorage.setItem('entity-erd-state', JSON.stringify(state));
154
+ }
155
+ }
156
+ ```
157
+
158
+ ### Utility Functions
159
+
160
+ The package also exports utility functions for advanced use cases:
161
+
162
+ ```typescript
163
+ import {
164
+ buildERDDataFromEntities,
165
+ entityInfoToERDNode,
166
+ entityFieldToERDField,
167
+ entitiesToERDNodes,
168
+ getOriginalEntityFromERDNode,
169
+ findEntityByNodeId
170
+ } from '@memberjunction/ng-entity-relationship-diagram';
171
+
172
+ // Build complete ERD data with automatic relationship discovery
173
+ const { nodes, links } = buildERDDataFromEntities(primaryEntities, {
174
+ allEntities: md.Entities,
175
+ includeIncoming: true,
176
+ includeOutgoing: true,
177
+ depth: 2
178
+ });
179
+
180
+ // Convert single entity
181
+ const erdNode = entityInfoToERDNode(entityInfo);
182
+
183
+ // Convert entity field
184
+ const erdField = entityFieldToERDField(entityFieldInfo);
185
+
186
+ // Batch convert entities
187
+ const erdNodes = entitiesToERDNodes(entityInfoArray);
188
+
189
+ // Extract original EntityInfo from ERDNode (if available)
190
+ const originalEntity = getOriginalEntityFromERDNode(erdNode);
191
+
192
+ // Find entity by node ID
193
+ const entity = findEntityByNodeId(nodeId, allEntities);
194
+ ```
42
195
 
43
196
  ---
44
197
 
45
- **Maintained for OIDC setup purposes only**
198
+ ## `<mj-erd-diagram>` - Generic ERD Component
199
+
200
+ The lower-level generic component for full control over the data.
201
+
202
+ ## Features
203
+
204
+ - **Interactive Force-Directed Layout** - Nodes automatically arrange themselves using physics-based simulation
205
+ - **Zoom and Pan** - Mouse wheel zoom and drag-to-pan with configurable limits
206
+ - **Node Selection** - Click to select nodes with visual highlighting
207
+ - **Focus Mode** - Show a single entity with its N-hop related entities (entity-centric view)
208
+ - **Relationship Visualization** - Arrows show foreign key relationships with field-level connections
209
+ - **Customizable Styling** - Full control over colors, sizes, and appearance via configuration
210
+ - **Rich Event System** - Comprehensive events for clicks, hovers, context menus, and drags
211
+ - **State Persistence** - Save and restore diagram state (zoom, pan, selection, node positions)
212
+ - **Read-Only Mode** - Disable interactions while keeping visualization intact
213
+ - **SVG Export** - Export the diagram as SVG for embedding or printing
214
+
215
+ ## Installation
216
+
217
+ ```bash
218
+ npm install @memberjunction/ng-entity-relationship-diagram
219
+ ```
220
+
221
+ ## Quick Start
222
+
223
+ ### 1. Import the Module
224
+
225
+ ```typescript
226
+ import { EntityRelationshipDiagramModule } from '@memberjunction/ng-entity-relationship-diagram';
227
+
228
+ @NgModule({
229
+ imports: [
230
+ EntityRelationshipDiagramModule
231
+ ]
232
+ })
233
+ export class YourModule { }
234
+ ```
235
+
236
+ ### 2. Basic Usage
237
+
238
+ ```html
239
+ <mj-erd-diagram
240
+ [nodes]="entityNodes"
241
+ [selectedNodeId]="selectedId"
242
+ (nodeSelected)="onNodeSelected($event)"
243
+ (nodeDoubleClick)="onNodeDoubleClick($event)">
244
+ </mj-erd-diagram>
245
+ ```
246
+
247
+ ```typescript
248
+ import { ERDNode, ERDNodeClickEvent } from '@memberjunction/ng-entity-relationship-diagram';
249
+
250
+ export class YourComponent {
251
+ entityNodes: ERDNode[] = [
252
+ {
253
+ id: 'users',
254
+ name: 'Users',
255
+ fields: [
256
+ { id: 'id', name: 'ID', isPrimaryKey: true, type: 'uuid' },
257
+ { id: 'email', name: 'Email', isPrimaryKey: false, type: 'string' },
258
+ { id: 'roleId', name: 'RoleID', isPrimaryKey: false, relatedNodeId: 'roles', relatedFieldName: 'ID' }
259
+ ]
260
+ },
261
+ {
262
+ id: 'roles',
263
+ name: 'Roles',
264
+ fields: [
265
+ { id: 'id', name: 'ID', isPrimaryKey: true, type: 'uuid' },
266
+ { id: 'name', name: 'Name', isPrimaryKey: false, type: 'string' }
267
+ ]
268
+ }
269
+ ];
270
+
271
+ selectedId: string | null = null;
272
+
273
+ onNodeSelected(node: ERDNode) {
274
+ this.selectedId = node.id;
275
+ console.log('Selected:', node.name);
276
+ }
277
+
278
+ onNodeDoubleClick(event: ERDNodeClickEvent) {
279
+ // Navigate to entity detail
280
+ this.router.navigate(['/entities', event.node.id]);
281
+ }
282
+ }
283
+ ```
284
+
285
+ ## Real-World Example: Entity Admin Dashboard
286
+
287
+ This component is used in MemberJunction's Entity Admin Dashboard to visualize database schema. Here's how it's integrated:
288
+
289
+ **Location:** `packages/Angular/Explorer/dashboards/src/EntityAdmin/components/erd-composite.component.ts`
290
+
291
+ ```typescript
292
+ import { ERDDiagramComponent, ERDNode, ERDNodeClickEvent } from '@memberjunction/ng-entity-relationship-diagram';
293
+ import { entitiesToERDNodes, findEntityByNodeId } from '../utils/entity-to-erd-adapter';
294
+
295
+ @Component({
296
+ selector: 'mj-erd-composite',
297
+ templateUrl: './erd-composite.component.html'
298
+ })
299
+ export class ERDCompositeComponent {
300
+ @ViewChild(ERDDiagramComponent) erdDiagram!: ERDDiagramComponent;
301
+
302
+ entities: EntityInfo[] = [];
303
+ erdNodes: ERDNode[] = [];
304
+ selectedEntity: EntityInfo | null = null;
305
+
306
+ async ngOnInit() {
307
+ const md = new Metadata();
308
+ this.entities = md.Entities;
309
+
310
+ // Convert MJ EntityInfo to generic ERDNode
311
+ this.erdNodes = entitiesToERDNodes(this.entities);
312
+ }
313
+
314
+ onERDNodeSelected(node: ERDNode) {
315
+ // Convert back to EntityInfo for detail panel
316
+ const entity = findEntityByNodeId(node.id, this.entities);
317
+ if (entity) {
318
+ this.selectedEntity = entity;
319
+ }
320
+ }
321
+ }
322
+ ```
323
+
324
+ **Template:**
325
+ ```html
326
+ <mj-erd-diagram
327
+ [nodes]="filteredERDNodes"
328
+ [isRefreshing]="isRefreshingERD"
329
+ [selectedNodeId]="selectedEntity?.ID || null"
330
+ [showHeader]="true"
331
+ headerTitle="Entity Relationship Diagram"
332
+ (nodeSelected)="onERDNodeSelected($event)"
333
+ (nodeClick)="onERDNodeClick($event)"
334
+ (nodeDeselected)="onEntityDeselected()"
335
+ (refreshRequested)="refreshERD()">
336
+ </mj-erd-diagram>
337
+ ```
338
+
339
+ ## Focus Mode - Entity-Centric Views
340
+
341
+ Focus mode is perfect for showing a single entity as the center with its related entities. This is ideal for:
342
+ - Entity detail pages
343
+ - Relationship exploration
344
+ - Schema documentation
345
+
346
+ ```html
347
+ <!-- Show Users entity with directly related entities -->
348
+ <mj-erd-diagram
349
+ [nodes]="allNodes"
350
+ [focusNodeId]="'users'"
351
+ [focusDepth]="1"
352
+ (nodeSelected)="onNodeSelected($event)">
353
+ </mj-erd-diagram>
354
+ ```
355
+
356
+ ### Focus Depth Options
357
+
358
+ | Depth | Description |
359
+ |-------|-------------|
360
+ | `0` | Show only the focus node |
361
+ | `1` | Focus node + directly related nodes |
362
+ | `2` | Focus node + nodes within 2 relationship hops |
363
+ | `3+` | Continue expanding the relationship graph |
364
+
365
+ ## API Reference
366
+
367
+ ### Inputs
368
+
369
+ #### Data Inputs
370
+
371
+ | Input | Type | Default | Description |
372
+ |-------|------|---------|-------------|
373
+ | `nodes` | `ERDNode[]` | `[]` | The nodes (entities) to display |
374
+ | `selectedNodeId` | `string \| null` | `null` | ID of the currently selected node |
375
+ | `highlightedNodeIds` | `string[]` | `[]` | IDs of nodes to highlight |
376
+ | `focusNodeId` | `string \| null` | `null` | ID of node to focus on (enables focus mode) |
377
+ | `focusDepth` | `number` | `1` | Relationship hops to include in focus mode |
378
+
379
+ #### State Inputs
380
+
381
+ | Input | Type | Default | Description |
382
+ |-------|------|---------|-------------|
383
+ | `isRefreshing` | `boolean` | `false` | Shows loading overlay when true |
384
+ | `readOnly` | `boolean` | `false` | Disables dragging and selection |
385
+
386
+ #### Configuration Inputs
387
+
388
+ | Input | Type | Default | Description |
389
+ |-------|------|---------|-------------|
390
+ | `config` | `ERDConfig` | `{}` | Configuration options (see below) |
391
+ | `showHeader` | `boolean` | `true` | Show header with zoom controls |
392
+ | `headerTitle` | `string` | `'Entity Relationship Diagram'` | Title in header |
393
+
394
+ ### Outputs (Events)
395
+
396
+ #### Selection Events
397
+
398
+ | Output | Type | Description |
399
+ |--------|------|-------------|
400
+ | `nodeClick` | `ERDNodeClickEvent` | Node clicked (set `cancel=true` to prevent selection) |
401
+ | `nodeDoubleClick` | `ERDNodeDoubleClickEvent` | Node double-clicked |
402
+ | `nodeSelected` | `ERDNode` | Node selected after click |
403
+ | `nodeDeselected` | `void` | Current node deselected |
404
+ | `linkClick` | `ERDLinkClickEvent` | Relationship link clicked |
405
+
406
+ #### Hover Events
407
+
408
+ | Output | Type | Description |
409
+ |--------|------|-------------|
410
+ | `nodeHover` | `ERDNodeHoverEvent` | Mouse entered a node |
411
+ | `nodeHoverEnd` | `ERDNode` | Mouse left a node |
412
+ | `linkHover` | `ERDLinkHoverEvent` | Mouse entered a link |
413
+ | `linkHoverEnd` | `ERDLink` | Mouse left a link |
414
+
415
+ #### Context Menu Events
416
+
417
+ | Output | Type | Description |
418
+ |--------|------|-------------|
419
+ | `nodeContextMenu` | `ERDNodeContextMenuEvent` | Right-click on node |
420
+ | `linkContextMenu` | `ERDLinkContextMenuEvent` | Right-click on link |
421
+ | `diagramContextMenu` | `ERDDiagramContextMenuEvent` | Right-click on background |
422
+
423
+ #### Drag Events
424
+
425
+ | Output | Type | Description |
426
+ |--------|------|-------------|
427
+ | `nodeDragStart` | `ERDNodeDragEvent` | Drag started (set `cancel=true` to prevent) |
428
+ | `nodeDragEnd` | `ERDNodeDragEvent` | Drag ended |
429
+
430
+ #### Diagram Events
431
+
432
+ | Output | Type | Description |
433
+ |--------|------|-------------|
434
+ | `zoomChange` | `ERDZoomEvent` | Zoom level or pan position changed |
435
+ | `refreshRequested` | `void` | Refresh button clicked |
436
+ | `layoutComplete` | `void` | Force simulation completed layout |
437
+ | `stateChange` | `ERDState` | Any state change (selection, zoom, etc.) |
438
+
439
+ ### Public Methods
440
+
441
+ Access via `@ViewChild(ERDDiagramComponent)`:
442
+
443
+ #### Zoom Control
444
+
445
+ ```typescript
446
+ zoomIn(): void // Zoom in 1.5x
447
+ zoomOut(): void // Zoom out 0.67x
448
+ resetZoom(): void // Reset to default zoom
449
+ zoomToNode(nodeId: string, scale?: number): void // Center on specific node
450
+ zoomToFit(padding?: number): void // Fit all nodes in view
451
+ centerDiagram(): void // Center without changing zoom
452
+ ```
453
+
454
+ #### Selection
455
+
456
+ ```typescript
457
+ selectNode(nodeId: string): boolean // Programmatically select
458
+ deselectAll(): void // Clear all selections
459
+ ```
460
+
461
+ #### Highlighting
462
+
463
+ ```typescript
464
+ highlightNode(nodeId: string): void // Highlight a specific node
465
+ clearHighlights(): void // Clear all highlights
466
+ highlightRelated(nodeId: string, depth?: number): void // Highlight node + related
467
+ getRelatedNodes(nodeId: string, depth?: number): ERDRelationshipInfo[]
468
+ ```
469
+
470
+ #### State Management
471
+
472
+ ```typescript
473
+ getState(): ERDState // Get current state for persistence
474
+ setState(state: Partial<ERDState>, restorePositions?: boolean): void
475
+ ```
476
+
477
+ #### Layout Control
478
+
479
+ ```typescript
480
+ freezeLayout(): void // Stop simulation, fix positions
481
+ unfreezeLayout(): void // Resume natural movement
482
+ ```
483
+
484
+ #### Utilities
485
+
486
+ ```typescript
487
+ refresh(): void // Rebuild visualization
488
+ triggerResize(): void // Recalculate after container resize
489
+ getZoomState(): ERDZoomEvent // Get current zoom/pan state
490
+ exportAsSVG(): string // Export as SVG string
491
+ ```
492
+
493
+ ## Configuration Options
494
+
495
+ ```typescript
496
+ const config: ERDConfig = {
497
+ // Node Sizing
498
+ nodeWidth: 180, // Width of each node box
499
+ nodeBaseHeight: 60, // Base height before adding fields
500
+ fieldHeight: 20, // Height per field row
501
+ maxNodeHeight: 300, // Maximum node height
502
+
503
+ // Force Simulation
504
+ chargeStrength: -800, // Repulsion between nodes (negative)
505
+ linkDistance: 80, // Base distance between linked nodes
506
+ collisionPadding: 20, // Extra padding for collision detection
507
+
508
+ // Display Options
509
+ showFieldDetails: true, // Show PK/FK fields in nodes
510
+ showRelationshipLabels: true, // Show field names on links
511
+ showHeader: true, // Show header with controls
512
+ showNodeCount: true, // Show count in header
513
+ showMinimap: false, // Show navigation minimap
514
+ showLegend: false, // Show color legend
515
+
516
+ // Interaction
517
+ enableDragging: true, // Allow node dragging
518
+ enableZoom: true, // Enable mouse wheel zoom
519
+ enablePan: true, // Enable drag-to-pan
520
+ minZoom: 0.1, // Minimum zoom level
521
+ maxZoom: 4, // Maximum zoom level
522
+ initialZoom: 1, // Starting zoom level
523
+ enableMultiSelect: false, // Ctrl+click multi-select
524
+
525
+ // Animation
526
+ animationDuration: 750, // Transition duration in ms
527
+ fitOnLoad: true, // Auto-fit diagram on initial load
528
+
529
+ // Empty State
530
+ emptyStateMessage: 'No entities to display',
531
+ emptyStateIcon: 'fa-solid fa-diagram-project',
532
+
533
+ // Layout
534
+ layoutAlgorithm: 'force', // 'force' | 'horizontal' | 'vertical' | 'radial'
535
+
536
+ // Colors
537
+ colors: {
538
+ nodeBackground: '#f8f9fa',
539
+ nodeBorder: '#333',
540
+ nodeHeader: '#007bff',
541
+ nodeHeaderText: 'white',
542
+ primaryKeyBackground: '#fff3cd',
543
+ primaryKeyText: '#856404',
544
+ foreignKeyBackground: '#cce5ff',
545
+ foreignKeyText: '#004085',
546
+ linkColor: '#666',
547
+ selectedBorder: '#4CAF50',
548
+ highlightBorder: '#ff9800',
549
+ relatedBorder: '#ff6b35'
550
+ }
551
+ };
552
+ ```
553
+
554
+ ## Data Interfaces
555
+
556
+ ### ERDNode
557
+
558
+ ```typescript
559
+ interface ERDNode {
560
+ id: string; // Unique identifier
561
+ name: string; // Display name
562
+ schemaName?: string; // Optional schema/namespace
563
+ description?: string; // Optional description
564
+ status?: string; // e.g., 'Active', 'Deprecated'
565
+ baseTable?: string; // Underlying table name
566
+ fields: ERDField[]; // All fields in this node
567
+ customData?: Record<string, unknown>; // Additional data
568
+ }
569
+ ```
570
+
571
+ ### ERDField
572
+
573
+ ```typescript
574
+ interface ERDField {
575
+ id: string; // Unique identifier
576
+ name: string; // Display name
577
+ type?: string; // Data type (e.g., 'string', 'int')
578
+ isPrimaryKey: boolean; // Is this a PK?
579
+ relatedNodeId?: string; // FK target node ID
580
+ relatedNodeName?: string; // FK target node name
581
+ relatedFieldName?: string; // FK target field name
582
+ description?: string;
583
+ allowsNull?: boolean;
584
+ defaultValue?: string;
585
+ length?: number; // For string fields
586
+ precision?: number; // For numeric fields
587
+ scale?: number;
588
+ isVirtual?: boolean; // Computed field
589
+ autoIncrement?: boolean;
590
+ possibleValues?: ERDFieldValue[];
591
+ customData?: Record<string, unknown>;
592
+ }
593
+ ```
594
+
595
+ ### ERDLink
596
+
597
+ ```typescript
598
+ interface ERDLink {
599
+ sourceNodeId: string;
600
+ targetNodeId: string;
601
+ sourceField: ERDField;
602
+ targetField?: ERDField;
603
+ isSelfReference: boolean;
604
+ relationshipType?: 'one-to-one' | 'one-to-many' | 'many-to-one' | 'many-to-many';
605
+ label?: string;
606
+ }
607
+ ```
608
+
609
+ ## State Persistence
610
+
611
+ Save and restore diagram state for user preferences:
612
+
613
+ ```typescript
614
+ // Save to localStorage
615
+ const state = this.erdDiagram.getState();
616
+ localStorage.setItem('erd-state', JSON.stringify(state));
617
+
618
+ // Restore on component init
619
+ ngAfterViewInit() {
620
+ const savedState = localStorage.getItem('erd-state');
621
+ if (savedState) {
622
+ this.erdDiagram.setState(JSON.parse(savedState));
623
+ }
624
+ }
625
+ ```
626
+
627
+ ### ERDState Interface
628
+
629
+ ```typescript
630
+ interface ERDState {
631
+ selectedNodeId: string | null;
632
+ highlightedNodeIds: string[];
633
+ zoomLevel: number;
634
+ translateX: number;
635
+ translateY: number;
636
+ focusNodeId: string | null;
637
+ focusDepth: number;
638
+ nodePositions: Record<string, {
639
+ x: number;
640
+ y: number;
641
+ fx?: number | null;
642
+ fy?: number | null;
643
+ }>;
644
+ }
645
+ ```
646
+
647
+ ## Event Handling Examples
648
+
649
+ ### Custom Context Menu
650
+
651
+ ```typescript
652
+ onNodeContextMenu(event: ERDNodeContextMenuEvent) {
653
+ event.cancel = true; // Prevent browser context menu
654
+
655
+ this.contextMenuItems = [
656
+ { label: 'View Details', action: () => this.viewDetails(event.node) },
657
+ { label: 'Edit Entity', action: () => this.editEntity(event.node) },
658
+ { label: 'Show Related', action: () => this.erdDiagram.highlightRelated(event.node.id) }
659
+ ];
660
+
661
+ this.contextMenuPosition = event.position;
662
+ this.showContextMenu = true;
663
+ }
664
+ ```
665
+
666
+ ### Tooltips on Hover
667
+
668
+ ```typescript
669
+ onNodeHover(event: ERDNodeHoverEvent) {
670
+ this.tooltip = {
671
+ visible: true,
672
+ content: `${event.node.name}\n${event.relatedNodes.length} related entities`,
673
+ x: event.position.x + 10,
674
+ y: event.position.y + 10
675
+ };
676
+ }
677
+
678
+ onNodeHoverEnd() {
679
+ this.tooltip.visible = false;
680
+ }
681
+ ```
682
+
683
+ ### Prevent Selection for Locked Nodes
684
+
685
+ ```typescript
686
+ onNodeClick(event: ERDNodeClickEvent) {
687
+ if (event.node.customData?.['locked']) {
688
+ event.cancel = true;
689
+ this.showMessage('This entity is locked');
690
+ }
691
+ }
692
+ ```
693
+
694
+ ## Styling
695
+
696
+ The component uses Angular's view encapsulation but exposes CSS variables for customization:
697
+
698
+ ```css
699
+ /* In your global styles or component styles */
700
+ mj-erd-diagram {
701
+ --erd-background: #f5f5f5;
702
+ --erd-header-background: #e0e0e0;
703
+ --erd-header-text: #333;
704
+ }
705
+ ```
706
+
707
+ For deeper customization, the component uses `::ng-deep` compatible class names:
708
+ - `.erd-section` - Main container
709
+ - `.section-header` - Header bar
710
+ - `.erd-container` - SVG container
711
+ - `.node` - Node group
712
+ - `.entity-rect` - Node rectangle
713
+ - `.entity-header` - Node header bar
714
+ - `.link-group` - Link group
715
+ - `.link` - Link path
716
+
717
+ ## Converting Custom Data to ERDNode
718
+
719
+ Create an adapter function to convert your domain types:
720
+
721
+ ```typescript
722
+ // Example: Convert database schema to ERDNode
723
+ function tableToERDNode(table: DatabaseTable): ERDNode {
724
+ return {
725
+ id: table.id,
726
+ name: table.tableName,
727
+ schemaName: table.schema,
728
+ fields: table.columns.map(col => ({
729
+ id: col.name,
730
+ name: col.name,
731
+ type: col.dataType,
732
+ isPrimaryKey: col.isPrimaryKey,
733
+ relatedNodeId: col.foreignKeyTable,
734
+ relatedFieldName: col.foreignKeyColumn,
735
+ allowsNull: col.nullable
736
+ }))
737
+ };
738
+ }
739
+ ```
740
+
741
+ ## Performance Considerations
742
+
743
+ - For diagrams with 100+ nodes, consider using focus mode to show subsets
744
+ - Use `freezeLayout()` after the initial layout settles for static diagrams
745
+ - Adjust `chargeStrength` and `linkDistance` for different node densities
746
+ - Set `fitOnLoad: false` if you're restoring state to avoid double-animation
747
+
748
+ ## Browser Support
749
+
750
+ - Chrome 80+
751
+ - Firefox 75+
752
+ - Safari 13+
753
+ - Edge 80+
754
+
755
+ Requires support for:
756
+ - SVG
757
+ - ResizeObserver
758
+ - CSS Grid/Flexbox
759
+
760
+ ## Dependencies
761
+
762
+ - Angular 18+
763
+ - D3.js 7+
764
+ - RxJS 7+
765
+
766
+ ## License
767
+
768
+ MIT
769
+
770
+ ## Contributing
771
+
772
+ Issues and PRs welcome at https://github.com/MemberJunction/MJ