@moxa/graph 3.0.0-beta.1 → 3.0.0-beta.10

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 (118) hide show
  1. package/README.md +615 -114
  2. package/assets/icon-sprite.svg +1 -1
  3. package/behaviors/drill-down/drill-down-manager.d.ts +55 -0
  4. package/behaviors/drill-down/drill-down-manager.d.ts.map +1 -0
  5. package/behaviors/drill-down/index.d.ts +71 -0
  6. package/behaviors/drill-down/index.d.ts.map +1 -0
  7. package/behaviors/drill-down/models/index.d.ts +7 -0
  8. package/behaviors/drill-down/models/index.d.ts.map +1 -0
  9. package/components/edge-arrow/transforms/index.d.ts.map +1 -1
  10. package/components/edge-label/index.d.ts +4 -4
  11. package/components/edge-label/index.d.ts.map +1 -1
  12. package/components/edge-line/index.d.ts.map +1 -1
  13. package/components/edge-polyline/index.d.ts.map +1 -1
  14. package/components/edge-quadratic/index.d.ts.map +1 -1
  15. package/components/group-device/index.d.ts +34 -0
  16. package/components/group-device/index.d.ts.map +1 -1
  17. package/components/group-device/models/index.d.ts +10 -1
  18. package/components/group-device/models/index.d.ts.map +1 -1
  19. package/components/group-device/transforms/index.d.ts.map +1 -1
  20. package/components/group-device/utils/style.d.ts.map +1 -1
  21. package/components/node-device/models/index.d.ts +2 -0
  22. package/components/node-device/models/index.d.ts.map +1 -1
  23. package/components/node-device/utils/style.d.ts.map +1 -1
  24. package/components/node-label/index.d.ts +1 -1
  25. package/components/node-label/index.d.ts.map +1 -1
  26. package/components/shared/transforms/edge-transform.d.ts.map +1 -1
  27. package/components/shared/transforms/group-transform.d.ts.map +1 -1
  28. package/components/shared/transforms/node-transform.d.ts.map +1 -1
  29. package/components/shared/utils/edge-utils/index.d.ts +1 -0
  30. package/components/shared/utils/edge-utils/index.d.ts.map +1 -1
  31. package/components/shared/utils/edge-utils/state-style.d.ts +24 -0
  32. package/components/shared/utils/edge-utils/state-style.d.ts.map +1 -0
  33. package/components/shared/utils/edge-utils/style.d.ts +19 -1
  34. package/components/shared/utils/edge-utils/style.d.ts.map +1 -1
  35. package/components/shared/utils/group-utils/drill-icon.d.ts +10 -0
  36. package/components/shared/utils/group-utils/drill-icon.d.ts.map +1 -0
  37. package/components/shared/utils/group-utils/index.d.ts +3 -0
  38. package/components/shared/utils/group-utils/index.d.ts.map +1 -0
  39. package/components/shared/utils/group-utils/label-background.d.ts +10 -0
  40. package/components/shared/utils/group-utils/label-background.d.ts.map +1 -0
  41. package/components/shared/utils/index.d.ts +1 -0
  42. package/components/shared/utils/index.d.ts.map +1 -1
  43. package/components/shared/utils/node-utils/config.d.ts +27 -0
  44. package/components/shared/utils/node-utils/config.d.ts.map +1 -0
  45. package/components/shared/utils/node-utils/icon-style.d.ts.map +1 -1
  46. package/components/shared/utils/node-utils/index.d.ts +6 -5
  47. package/components/shared/utils/node-utils/index.d.ts.map +1 -1
  48. package/components/shared/utils/node-utils/label-background.d.ts.map +1 -1
  49. package/components/shared/utils/node-utils/offset.d.ts +2 -6
  50. package/components/shared/utils/node-utils/offset.d.ts.map +1 -1
  51. package/components/shared/utils/node-utils/state-styles.d.ts +63 -0
  52. package/components/shared/utils/node-utils/state-styles.d.ts.map +1 -0
  53. package/components/shared/utils/node-utils/status-style.d.ts.map +1 -1
  54. package/components/shared/utils/node-utils/text-style.d.ts.map +1 -1
  55. package/components/shared/utils/node-utils/theme.d.ts +1 -1
  56. package/components/shared/utils/node-utils/theme.d.ts.map +1 -1
  57. package/components/shared/utils/node-utils/title-style.d.ts.map +1 -1
  58. package/core/graph/graph.d.ts +218 -187
  59. package/core/graph/graph.d.ts.map +1 -1
  60. package/core/model/behavior.model.d.ts +6 -2
  61. package/core/model/behavior.model.d.ts.map +1 -1
  62. package/core/model/edge.model.d.ts +221 -0
  63. package/core/model/edge.model.d.ts.map +1 -1
  64. package/core/model/event.model.d.ts +7 -1
  65. package/core/model/event.model.d.ts.map +1 -1
  66. package/core/model/group.model.d.ts +1 -0
  67. package/core/model/group.model.d.ts.map +1 -1
  68. package/core/model/index.d.ts +1 -0
  69. package/core/model/index.d.ts.map +1 -1
  70. package/core/model/label.model.d.ts +2 -1
  71. package/core/model/label.model.d.ts.map +1 -1
  72. package/core/model/node.model.d.ts +98 -0
  73. package/core/model/node.model.d.ts.map +1 -1
  74. package/core/model/plugin.model.d.ts +6 -2
  75. package/core/model/plugin.model.d.ts.map +1 -1
  76. package/core/model/text.model.d.ts +5 -0
  77. package/core/model/text.model.d.ts.map +1 -0
  78. package/index.cjs +77 -77
  79. package/index.d.ts +1 -0
  80. package/index.d.ts.map +1 -1
  81. package/index.js +20306 -19052
  82. package/layouts/grid/index.d.ts.map +1 -1
  83. package/package.json +1 -1
  84. package/plugins/context-menu/index.d.ts +11 -1
  85. package/plugins/context-menu/index.d.ts.map +1 -1
  86. package/plugins/history/config/command-filter.d.ts +5 -0
  87. package/plugins/history/config/command-filter.d.ts.map +1 -0
  88. package/plugins/history/config/drill-integration.d.ts +7 -0
  89. package/plugins/history/config/drill-integration.d.ts.map +1 -0
  90. package/plugins/history/config/history-options.d.ts +4 -0
  91. package/plugins/history/config/history-options.d.ts.map +1 -0
  92. package/plugins/history/history.model.d.ts +5 -0
  93. package/plugins/history/history.model.d.ts.map +1 -0
  94. package/plugins/history/index.d.ts +8 -6
  95. package/plugins/history/index.d.ts.map +1 -1
  96. package/plugins/index.d.ts +10 -0
  97. package/plugins/index.d.ts.map +1 -0
  98. package/plugins/minimap/index.d.ts +3 -2
  99. package/plugins/minimap/index.d.ts.map +1 -1
  100. package/plugins/rich-tooltip/index.d.ts +11 -0
  101. package/plugins/rich-tooltip/index.d.ts.map +1 -0
  102. package/plugins/snapline/index.d.ts +130 -2
  103. package/plugins/snapline/index.d.ts.map +1 -1
  104. package/plugins/snapline/models/index.d.ts +1 -1
  105. package/plugins/snapline/models/index.d.ts.map +1 -1
  106. package/plugins/snapline/models/snapline-models.d.ts +61 -0
  107. package/plugins/snapline/models/snapline-models.d.ts.map +1 -0
  108. package/plugins/tooltip/index.d.ts +1 -2
  109. package/plugins/tooltip/index.d.ts.map +1 -1
  110. package/shared/transforms/behavior-transform.d.ts +1 -0
  111. package/shared/transforms/behavior-transform.d.ts.map +1 -1
  112. package/shared/transforms/graph-transform.d.ts.map +1 -1
  113. package/shared/transforms/plugin-transform.d.ts +5 -4
  114. package/shared/transforms/plugin-transform.d.ts.map +1 -1
  115. package/shared/utils/config-setter.d.ts +9 -0
  116. package/shared/utils/config-setter.d.ts.map +1 -0
  117. package/shared/utils/index.d.ts +3 -2
  118. package/shared/utils/index.d.ts.map +1 -1
package/README.md CHANGED
@@ -4,6 +4,10 @@
4
4
 
5
5
  - [Install](#install)
6
6
  - [Basic Usage](#basic-usage)
7
+ - [Components](#components)
8
+ - [Behaviors](#behaviors)
9
+ - [Layouts](#layouts)
10
+ - [Plugins](#plugins)
7
11
  - [Graph Control](#graph-control)
8
12
  - [Event Handling](#event-handling)
9
13
  - [Developer Guidelines](#developer-guidelines)
@@ -41,45 +45,34 @@ pnpm add @moxa/graph
41
45
  import { Graph } from '@moxa/graph';
42
46
 
43
47
  const graph = new Graph({
44
- renderer: 'svg',
45
48
  container: 'container',
46
49
  width: 500,
47
50
  height: 500,
51
+ renderer: 'canvas', // 'canvas', 'svg', or 'webgl'
48
52
  data: {
49
53
  nodes: [
50
54
  {
51
55
  id: 'node1',
52
- config: {
53
- type: 'circle-node',
54
- point: { x: 100, y: 50 },
56
+ data: {
57
+ type: 'node-device',
58
+ x: 100,
59
+ y: 50,
55
60
  },
56
61
  },
57
62
  {
58
63
  id: 'node2',
59
- config: {
60
- type: 'circle-node',
61
- point: { x: 100, y: 250 },
64
+ data: {
65
+ type: 'node-device',
66
+ x: 100,
67
+ y: 250,
62
68
  },
63
69
  },
64
70
  {
65
71
  id: 'node3',
66
- config: {
67
- type: 'circle-node',
68
- point: { x: 300, y: 50 },
69
- },
70
- },
71
- {
72
- id: 'node4',
73
- config: {
74
- type: 'circle-node',
75
- point: { x: 300, y: 250 },
76
- },
77
- },
78
- {
79
- id: 'node5',
80
- config: {
81
- type: 'circle-node',
82
- point: { x: 200, y: 150 },
72
+ data: {
73
+ type: 'node-device',
74
+ x: 300,
75
+ y: 50,
83
76
  },
84
77
  },
85
78
  ],
@@ -88,91 +81,245 @@ const graph = new Graph({
88
81
  id: 'edge1',
89
82
  source: 'node1',
90
83
  target: 'node2',
84
+ data: {
85
+ type: 'edge-line',
86
+ },
91
87
  },
92
88
  {
93
89
  id: 'edge2',
94
- source: 'node1',
95
- target: 'node5',
96
- },
97
- {
98
- id: 'edge3',
99
- source: 'node5',
90
+ source: 'node2',
100
91
  target: 'node3',
101
- },
102
- {
103
- id: 'edge4',
104
- source: 'node3',
105
- target: 'node4',
92
+ data: {
93
+ type: 'edge-quadratic',
94
+ },
106
95
  },
107
96
  ],
108
97
  },
109
98
  });
99
+
100
+ // Render the graph
101
+ graph.render();
110
102
  ```
111
103
 
104
+ ## Components
105
+
106
+ ### Node Components
107
+
108
+ - **node-device**: Device node with icon and label support
109
+ - **node-icon**: Icon-only node component
110
+ - **node-label**: Label-only node component
111
+
112
+ ### Edge Components
113
+
114
+ - **edge-line**: Straight line edges
115
+ - **edge-polyline**: Multi-segment polyline edges
116
+ - **edge-quadratic**: Curved quadratic edges
117
+ - **edge-arrow**: Arrow markers for edges
118
+ - **edge-label**: Labels for edges
119
+
120
+ ### Group Components
121
+
122
+ - **group-device**: Device group with expand/collapse functionality
123
+
124
+ ## Behaviors
125
+
126
+ Interactive behaviors for user interaction:
127
+
128
+ - **brush-select**: Rectangle selection
129
+ - **click-select**: Click to select elements
130
+ - **collapse-expand**: Group collapse/expand
131
+ - **create-edge**: Interactive edge creation
132
+ - **drag-canvas**: Canvas dragging
133
+ - **drag-element**: Element dragging
134
+ - **fix-element-size**: Fixed element sizing
135
+ - **focus-element**: Element focusing
136
+ - **hover-activate**: Hover activation
137
+ - **scroll-canvas**: Canvas scrolling
138
+ - **select-all**: Select all elements
139
+ - **zoom-canvas**: Canvas zooming
140
+
141
+ ## Layouts
142
+
143
+ Automatic layout algorithms:
144
+
145
+ - **align**: Alignment layout
146
+ - **force**: Force-directed layout
147
+ - **grid**: Grid layout
148
+ - **ring**: Circular ring layout
149
+ - **tree**: Hierarchical tree layout
150
+
151
+ ## Plugins
152
+
153
+ Extensible plugin system:
154
+
155
+ - **context-menu**: Right-click context menu
156
+ - **element-toolbar**: Element-specific toolbar
157
+ - **fixed-toolbar**: Fixed position toolbar
158
+ - **graph-background**: Customizable graph background
159
+ - **history**: Undo/redo functionality
160
+ - **minimap**: Mini navigation map
161
+ - **snapline**: Element alignment guides
162
+ - **tooltip**: Element tooltips
163
+
112
164
  ## Graph Control
113
165
 
114
166
  > You can control the presentation and behavior of the graph by calling the `Graph` methods
115
167
 
116
168
  ```typescript
117
- // Update Graph Data
118
- graph.updateNode({ ... });
119
- graph.updateEdge({ ... });
120
- graph.updateGroup({ ... });
169
+ // Data Management
170
+ graph.addNode({ id: 'node1', data: { type: 'node-device' } });
171
+ graph.updateNode('node1', { data: { x: 100, y: 100 } });
172
+ graph.removeNode('node1');
173
+
174
+ graph.addEdge({ id: 'edge1', source: 'node1', target: 'node2' });
175
+ graph.updateEdge('edge1', { data: { type: 'edge-line' } });
176
+ graph.removeEdge('edge1');
177
+
178
+ graph.addGroup({ id: 'group1', data: { type: 'group-device' } });
179
+ graph.updateGroup('group1', { data: { collapsed: true } });
180
+ graph.removeGroup('group1');
121
181
 
122
182
  // View Control
123
- graph.zoom();
183
+ graph.zoom(1.5);
184
+ graph.zoomTo(2.0, { x: 100, y: 100 });
124
185
  graph.fitView();
125
- graph.focusItem();
186
+ graph.fitCenter();
187
+ graph.focusItem('node1');
188
+
189
+ // Layout
190
+ graph.setLayout({ type: 'force', options: {} });
191
+ graph.layout();
126
192
 
127
- // Change Behaviors
128
- graph.changeBehavior([ ... ], true);
193
+ // Behaviors
194
+ graph.setBehavior(['drag-canvas', 'zoom-canvas']);
129
195
 
130
- // Change layouts
131
- graph.changeLayout();
196
+ // Plugins
197
+ graph.setPlugin('minimap', { size: [200, 150] });
132
198
 
133
- // Change Theme
134
- graph.changeTheme();
199
+ // Themes
200
+ graph.setTheme('dark');
201
+ graph.setThemeTokens({ primaryColor: '#1890ff' });
135
202
 
136
- // Plugin Management
137
- graph.addPlugin({ ... });
138
- graph.updatePlugin({ ... });
139
- graph.removePlugin({ ... });
203
+ // State Management
204
+ graph.setElementState('node1', 'selected', true);
205
+ graph.clearElementState('node1', 'selected');
140
206
  ```
141
207
 
142
208
  ## Event Handling
143
209
 
144
210
  ```typescript
145
211
  // Listen Events
146
- graph.events.nodeClick.once((e) => { ... });
147
- graph.events.beginCreateEdge.on((e) => { ... });
148
- graph.events.groupExpanded.off((e) => { ... });
212
+ graph.on('node:click', (event) => {
213
+ console.log('Node clicked:', event.itemId);
214
+ });
215
+
216
+ graph.on('edge:click', (event) => {
217
+ console.log('Edge clicked:', event.itemId);
218
+ });
219
+
220
+ graph.on('canvas:click', (event) => {
221
+ console.log('Canvas clicked:', event.canvas);
222
+ });
223
+
224
+ // Remove event listeners
225
+ graph.off('node:click', listener);
226
+
227
+ // One-time event listeners
228
+ graph.once('afterrender', () => {
229
+ console.log('Graph rendered');
230
+ });
149
231
  ```
150
232
 
151
233
  ## Developer Guidelines
152
234
 
153
235
  This section provides guidelines for developers who want to contribute to or extend the Moxa Graph library.
154
236
 
155
- ### Project Structure
237
+ ### Project Structure & Folder Responsibilities
156
238
 
157
239
  ```
158
240
  libs/graph/
159
- ├── src/
160
- │ ├── lib/ # Core functionality
161
- ├── behavior/ # Graph behaviors and interactions
162
- ├── edge/ # Edge definitions and renderers
163
- ├── graph/ # Main graph implementation
164
- ├── group/ # Node grouping functionality
165
- ├── layout/ # Layout algorithms
166
- ├── model/ # Data models and types
167
- ├── node/ # Node definitions and renderers
168
- │ ├── plugin/ # Plugin system
169
- │ │ ├── themes/ # Theming and styling
170
- │ │ └── utils/ # Utility functions
171
- ├── assets/ # Static assets
172
- │ ├── styles/ # Global styles
173
- │ └── stories/ # Storybook examples
241
+ ├── src/ # Source code
242
+ │ ├── core/ # 🔧 Core functionality (Graph API, models, utilities)
243
+ │ ├── components/ # 🎨 Visual components (nodes, edges, groups)
244
+ │ ├── behaviors/ # 🖱️ Interactive behaviors (12 behaviors)
245
+ │ ├── layouts/ # 📐 Layout algorithms (5 layouts)
246
+ │ ├── plugins/ # 🔌 Plugin system (8 plugins)
247
+ │ ├── shared/ # 🛠️ Shared utilities and types
248
+ │ ├── assets/ # 📦 Static assets (icons, images)
249
+ │ ├── styles/ # 🎨 Global styles
250
+ └── stories/ # 📚 API documentation
251
+ ├── tests/ # 🧪 Test utilities
252
+ ├── e2e/ # 🎭 End-to-end testing
253
+ ├── .storybook/ # 📖 Storybook configuration
254
+ └── package.json # 📦 Package configuration
174
255
  ```
175
256
 
257
+ #### Detailed Folder Responsibilities
258
+
259
+ ##### Core (`src/core/`)
260
+
261
+ **Purpose**: Essential graph functionality and APIs
262
+
263
+ - **`graph/`**: Main Graph class implementing the public API, lifecycle management, and core operations
264
+ - **`model/`**: TypeScript type definitions, interfaces, and data structures used throughout the library
265
+ - **`utils/`**: Core utility functions for graph manipulation, theme management, and element operations
266
+
267
+ ##### Components (`src/components/`)
268
+
269
+ **Purpose**: Visual rendering components for graph elements
270
+
271
+ - **Node Components**: Different node types (device, icon, label) with specialized rendering logic
272
+ - **Edge Components**: Various edge types (line, polyline, quadratic) with arrow and label support
273
+ - **Group Components**: Container components for grouping and organizing elements
274
+ - **Shared**: Common utilities and transformations used across components
275
+
276
+ ##### Behaviors (`src/behaviors/`)
277
+
278
+ **Purpose**: User interaction handling and graph manipulation
279
+
280
+ - Each behavior handles specific user interactions (clicking, dragging, selecting)
281
+ - Behaviors are modular and can be enabled/disabled independently
282
+ - Includes both mouse and keyboard interaction handling
283
+
284
+ ##### Layouts (`src/layouts/`)
285
+
286
+ **Purpose**: Automatic positioning algorithms for graph elements
287
+
288
+ - **Force**: Physics-based layout for natural node arrangements
289
+ - **Tree**: Hierarchical layouts for tree-structured data
290
+ - **Grid**: Regular grid arrangements for systematic positioning
291
+ - **Ring**: Circular arrangements for cyclical data
292
+ - **Align**: Tools for manual element alignment
293
+
294
+ ##### Plugins (`src/plugins/`)
295
+
296
+ **Purpose**: Extensible features that enhance graph functionality
297
+
298
+ - Each plugin is self-contained and optional
299
+ - Plugins can add UI elements (toolbars, tooltips, minimap)
300
+ - Includes data management features (history, context menus)
301
+
302
+ ##### Shared (`src/shared/`)
303
+
304
+ **Purpose**: Common utilities and infrastructure
305
+
306
+ - **Types**: Shared TypeScript definitions used across modules
307
+ - **Utils**: Generic utility functions and shared components
308
+ - **Transforms**: Data transformation pipeline for processing graph configurations
309
+ - **Constants**: Application-wide constants and configuration values
310
+
311
+ ##### Testing Infrastructure
312
+
313
+ - **`tests/`**: Reusable test utilities and helper functions
314
+ - **`e2e/`**: End-to-end visual regression testing with Playwright
315
+ - \*\*Each component includes its own test suite with visual snapshots
316
+
317
+ ##### Documentation & Development
318
+
319
+ - **`.storybook/`**: Interactive documentation and component playground
320
+ - **`stories/`**: Written documentation and API guides
321
+ - \*\*Each component includes comprehensive Storybook stories for demonstration and testing
322
+
176
323
  ### Development Workflow
177
324
 
178
325
  1. **Setup Development Environment**
@@ -194,92 +341,439 @@ libs/graph/
194
341
  # Build the library
195
342
  pnpm build:graph
196
343
 
197
- # Run tests
344
+ # Run unit tests
198
345
  pnpm test:graph
346
+
347
+ # Run e2e tests
348
+ pnpm e2e:graph
349
+
350
+ # Start Storybook for development
351
+ pnpm storybook:graph
199
352
  ```
200
353
 
201
354
  ### Extending the Library
202
355
 
203
- #### Creating Custom Nodes
356
+ #### Creating Custom Components
204
357
 
205
- Custom nodes can be created by extending the base node classes:
358
+ Custom components are created using the G6 5.x API and registered with specific types:
206
359
 
207
360
  ```typescript
208
- import { BaseNode, NodeConfig } from '@moxa/graph';
361
+ import { ExtensionController } from '@antv/g6';
209
362
 
210
- interface CustomNodeConfig extends NodeConfig {
211
- customProperty: string;
212
- }
363
+ // Custom Node Component
364
+ const customNode = (context) => {
365
+ const { model, theme } = context;
366
+ const { data } = model;
213
367
 
214
- class CustomNode extends BaseNode<CustomNodeConfig> {
215
- render() {
216
- // Implement custom rendering logic
217
- const shape = this.createShape('circle', {
368
+ return {
369
+ circle: {
218
370
  r: 20,
219
- fill: this.config.customProperty === 'special' ? 'red' : 'blue',
220
- });
371
+ fill: data.customProperty === 'special' ? 'red' : 'blue',
372
+ stroke: theme.nodeStroke,
373
+ lineWidth: 2,
374
+ },
375
+ };
376
+ };
221
377
 
222
- return shape;
223
- }
224
- }
378
+ // Register the custom component
379
+ ExtensionController.register('node', 'custom-node', customNode);
225
380
 
226
- // Register custom node
227
- graph.registerNode('custom-node', CustomNode);
381
+ // Use in graph data
382
+ const nodeData = {
383
+ id: 'node1',
384
+ data: {
385
+ type: 'custom-node',
386
+ customProperty: 'special',
387
+ },
388
+ };
228
389
  ```
229
390
 
230
391
  #### Creating Custom Layouts
231
392
 
232
393
  ```typescript
233
- import { BaseLayout, LayoutConfig } from '@moxa/graph';
234
-
235
- class CustomLayout extends BaseLayout {
236
- layout() {
237
- // Implement custom layout algorithm
238
- this.nodes.forEach((node, index) => {
239
- // Position nodes in a circle
240
- const angle = (index / this.nodes.length) * Math.PI * 2;
241
- const radius = 200;
242
-
243
- node.updatePosition({
244
- x: Math.cos(angle) * radius + this.width / 2,
245
- y: Math.sin(angle) * radius + this.height / 2,
394
+ import { ExtensionController } from '@antv/g6';
395
+
396
+ // Custom Layout Implementation
397
+ const customLayout = (graph, options) => {
398
+ return {
399
+ id: 'custom-layout',
400
+ async execute() {
401
+ const { nodes } = graph.getData();
402
+ const { radius = 200 } = options;
403
+
404
+ nodes.forEach((node, index) => {
405
+ const angle = (index / nodes.length) * Math.PI * 2;
406
+ node.data.x = Math.cos(angle) * radius;
407
+ node.data.y = Math.sin(angle) * radius;
246
408
  });
247
- });
248
- }
249
- }
409
+
410
+ return { nodes, edges: graph.getData().edges };
411
+ },
412
+ };
413
+ };
414
+
415
+ // Register the layout
416
+ ExtensionController.register('layout', 'custom-layout', customLayout);
250
417
 
251
418
  // Apply custom layout
252
- graph.changeLayout('custom', {
253
- /* layout options */
419
+ graph.setLayout({
420
+ type: 'custom-layout',
421
+ options: { radius: 300 },
254
422
  });
255
423
  ```
256
424
 
257
425
  #### Creating Plugins
258
426
 
259
427
  ```typescript
260
- import { BasePlugin, PluginConfig } from '@moxa/graph';
428
+ import { BasePlugin } from '@antv/g6';
261
429
 
262
430
  class CustomPlugin extends BasePlugin {
431
+ constructor(options) {
432
+ super(options);
433
+ }
434
+
263
435
  init() {
264
436
  // Setup plugin
265
- this.graph.events.nodeClick.on(this.handleNodeClick);
437
+ this.graph.on('node:click', this.handleNodeClick);
266
438
  }
267
439
 
268
- handleNodeClick = (e) => {
440
+ handleNodeClick = (event) => {
269
441
  // Implement custom behavior
270
- console.log('Node clicked:', e.node.id);
442
+ console.log('Node clicked:', event.itemId);
271
443
  };
272
444
 
273
445
  destroy() {
274
446
  // Clean up
275
- this.graph.events.nodeClick.off(this.handleNodeClick);
447
+ this.graph.off('node:click', this.handleNodeClick);
448
+ super.destroy();
276
449
  }
277
450
  }
278
451
 
279
452
  // Add plugin to graph
280
- graph.addPlugin('custom', new CustomPlugin());
453
+ graph.setPlugin(
454
+ 'custom-plugin',
455
+ new CustomPlugin({
456
+ // plugin options
457
+ }),
458
+ );
459
+ ```
460
+
461
+ ## Development Workflow
462
+
463
+ This section describes the complete development workflow for contributing to the Moxa Graph library, including component implementation, Storybook documentation, and visual testing.
464
+
465
+ ### Directory Structure & Responsibilities
466
+
467
+ Each component follows a standardized directory structure:
468
+
469
+ ```
470
+ {component-name}/
471
+ ├── index.ts # Component export
472
+ ├── models/ # TypeScript type definitions
473
+ │ └── index.ts
474
+ ├── utils/ # Utility functions
475
+ │ └── index.ts
476
+ ├── stories/ # Storybook documentation
477
+ │ ├── {story-name}.stories.ts # Storybook configuration
478
+ │ ├── {story-name}.component.ts # Angular wrapper component
479
+ │ └── data/ # Test data definitions
480
+ │ └── {story-name}-data.ts
481
+ ├── tests/ # Visual regression tests
482
+ │ ├── {test-name}.spec.ts
483
+ │ └── __snapshots__/ # Visual test snapshots
484
+ │ └── {test-name}.png
485
+ └── transforms/ # Data transformation helpers (optional)
486
+ └── index.ts
281
487
  ```
282
488
 
489
+ #### Directory Responsibilities
490
+
491
+ - **`index.ts`**: Main component export, registers the component with G6
492
+ - **`models/`**: TypeScript interfaces and type definitions for component configuration
493
+ - **`utils/`**: Helper functions for styling, positioning, state management
494
+ - **`stories/`**: Complete Storybook documentation with interactive examples
495
+ - **`data/`**: Reusable test data configurations for different scenarios
496
+ - **`tests/`**: Playwright visual regression tests ensuring UI consistency
497
+ - **`transforms/`**: Data transformation utilities (for complex components)
498
+
499
+ ### Development Process
500
+
501
+ #### 1. Component Implementation
502
+
503
+ Start by implementing the core component logic:
504
+
505
+ ```typescript
506
+ // components/my-component/index.ts
507
+ import { ExtensionController } from '@antv/g6';
508
+
509
+ const myComponent = (context) => {
510
+ const { model, theme } = context;
511
+ const { data } = model;
512
+
513
+ return {
514
+ // Component rendering logic
515
+ rect: {
516
+ width: data.width || 100,
517
+ height: data.height || 50,
518
+ fill: theme.primaryColor,
519
+ },
520
+ };
521
+ };
522
+
523
+ // Register component
524
+ ExtensionController.register('node', 'my-component', myComponent);
525
+
526
+ export { myComponent };
527
+ ```
528
+
529
+ #### 2. Type Definitions
530
+
531
+ Define TypeScript interfaces in `models/`:
532
+
533
+ ```typescript
534
+ // components/my-component/models/index.ts
535
+ export interface MyComponentConfig {
536
+ width?: number;
537
+ height?: number;
538
+ customProperty?: string;
539
+ }
540
+
541
+ export interface MyComponentData extends NodeData {
542
+ type: 'my-component';
543
+ config: MyComponentConfig;
544
+ }
545
+ ```
546
+
547
+ #### 3. Storybook Documentation
548
+
549
+ Create comprehensive Storybook stories:
550
+
551
+ ```typescript
552
+ // components/my-component/stories/my-component.stories.ts
553
+ import { Meta, StoryObj } from '@storybook/angular';
554
+ import { MY_COMPONENT_DATA } from './data/my-component-data';
555
+ import { MyComponentComponent } from './my-component.component';
556
+
557
+ export default {
558
+ title: 'Components/My Component',
559
+ component: MyComponentComponent,
560
+ parameters: {
561
+ docs: {
562
+ description: {
563
+ component: `
564
+ # My Component
565
+
566
+ This component demonstrates custom node rendering capabilities.
567
+
568
+ ## Features
569
+ - Configurable dimensions
570
+ - Theme integration
571
+ - State management
572
+
573
+ ## Usage
574
+ \`\`\`typescript
575
+ const graph = new Graph({
576
+ container: 'graph',
577
+ data: MY_COMPONENT_DATA,
578
+ });
579
+ \`\`\`
580
+ `,
581
+ },
582
+ },
583
+ },
584
+ args: {
585
+ graphData: MY_COMPONENT_DATA,
586
+ },
587
+ } as Meta;
588
+
589
+ type Story = StoryObj<MyComponentComponent>;
590
+
591
+ export const Basic: Story = {};
592
+ export const Advanced: Story = {
593
+ args: {
594
+ graphData: ADVANCED_MY_COMPONENT_DATA,
595
+ },
596
+ };
597
+ ```
598
+
599
+ #### 4. Angular Wrapper Component
600
+
601
+ Create an Angular component for Storybook:
602
+
603
+ ```typescript
604
+ // components/my-component/stories/my-component.component.ts
605
+ import { Component, input, computed } from '@angular/core';
606
+ import { Graph, GraphConfig, GraphData } from '@moxa/graph';
607
+ import { StoryWrapperComponent, JsonViewerComponent } from '@shared/utils/components';
608
+
609
+ @Component({
610
+ selector: 'moxa-vizion-my-component',
611
+ imports: [JsonViewerComponent, StoryWrapperComponent],
612
+ template: `
613
+ <story-wrapper>
614
+ <json-viewer [data]="data()">
615
+ <div container [id]="id"></div>
616
+ </json-viewer>
617
+ </story-wrapper>
618
+ `,
619
+ })
620
+ export class MyComponentComponent {
621
+ graph!: Graph;
622
+ id: string = Math.random().toString(36).substring(2);
623
+
624
+ graphData = input<GraphData>();
625
+
626
+ data = computed<GraphConfig>(() => ({
627
+ container: this.id,
628
+ data: this.graphData()!,
629
+ }));
630
+
631
+ ngAfterViewInit(): void {
632
+ this.graph = new Graph(this.data());
633
+ this.graph.render();
634
+ }
635
+
636
+ ngOnDestroy(): void {
637
+ this.graph.destroy();
638
+ }
639
+ }
640
+ ```
641
+
642
+ #### 5. Test Data
643
+
644
+ Define comprehensive test data:
645
+
646
+ ```typescript
647
+ // components/my-component/stories/data/my-component-data.ts
648
+ import { GraphData } from '@moxa/graph';
649
+
650
+ export const MY_COMPONENT_DATA: GraphData = {
651
+ nodes: [
652
+ {
653
+ id: 'node1',
654
+ data: {
655
+ type: 'my-component',
656
+ x: 100,
657
+ y: 100,
658
+ width: 120,
659
+ height: 60,
660
+ },
661
+ },
662
+ // More test scenarios...
663
+ ],
664
+ };
665
+ ```
666
+
667
+ #### 6. Visual Testing
668
+
669
+ Implement Playwright visual regression tests:
670
+
671
+ ```typescript
672
+ // components/my-component/stories/tests/my-component-basic.spec.ts
673
+ import { test } from '@playwright/test';
674
+ import { getGraphContainer, getStorybookUrl, verifySnapshot } from '@tests/helpers';
675
+
676
+ const STORY_ID = 'components-my-component--basic';
677
+ const SNAPSHOT_NAME = 'my-component-basic.png';
678
+
679
+ test.describe('My Component Visual Tests', () => {
680
+ test.beforeEach(async ({ page }) => {
681
+ await page.setViewportSize({ width: 600, height: 500 });
682
+ });
683
+
684
+ test('should display basic component correctly', async ({ page, baseURL }) => {
685
+ await page.goto(getStorybookUrl(baseURL, STORY_ID));
686
+ await page.waitForSelector('div.container', { timeout: 10000 });
687
+
688
+ const graphContainer = getGraphContainer(page);
689
+ await page.waitForTimeout(1000); // Wait for graph rendering
690
+
691
+ await verifySnapshot(graphContainer, SNAPSHOT_NAME);
692
+ });
693
+ });
694
+ ```
695
+
696
+ ### Testing Strategy
697
+
698
+ #### Visual Regression Testing
699
+
700
+ The library uses Playwright for comprehensive visual regression testing:
701
+
702
+ 1. **Snapshot Generation**: First test run generates baseline snapshots
703
+ 2. **Automatic Comparison**: Subsequent runs compare against baselines
704
+ 3. **Pixel-Perfect Accuracy**: Detects even minor visual changes
705
+ 4. **Cross-Browser Support**: Ensures consistency across different browsers
706
+
707
+ #### Test Configuration
708
+
709
+ Playwright is configured for optimal visual testing:
710
+
711
+ ```typescript
712
+ // playwright.config.ts highlights
713
+ export default defineConfig({
714
+ testMatch: ['**/e2e/**/*.spec.ts', '**/src/**/tests/*.spec.ts'],
715
+ snapshotPathTemplate: `{testFileDir}/__snapshots__/{arg}{ext}`,
716
+ use: {
717
+ viewport: { width: 1280, height: 720 },
718
+ deviceScaleFactor: 1, // Fixed DPR for consistency
719
+ // Disable animations for consistent screenshots
720
+ launchOptions: {
721
+ args: ['--disable-gpu', '--disable-web-security'],
722
+ },
723
+ },
724
+ expect: {
725
+ toMatchSnapshot: {
726
+ maxDiffPixelRatio: 0.01,
727
+ threshold: 0.1,
728
+ },
729
+ },
730
+ });
731
+ ```
732
+
733
+ ### Development Commands
734
+
735
+ ```bash
736
+ # Start Storybook for development
737
+ pnpm storybook:graph
738
+
739
+ # Run all tests
740
+ pnpm test:graph
741
+
742
+ # Run visual regression tests
743
+ pnpm e2e:graph
744
+
745
+ # Update visual snapshots (when intentional changes are made)
746
+ pnpm e2e:graph --update-snapshots
747
+
748
+ # Build the library
749
+ pnpm build:graph
750
+
751
+ # Generate documentation
752
+ pnpm docs:graph
753
+ ```
754
+
755
+ ### Quality Assurance
756
+
757
+ #### Pre-commit Checklist
758
+
759
+ Before submitting code, ensure:
760
+
761
+ 1. ✅ **Component Registration**: Component is properly registered with G6
762
+ 2. ✅ **Type Safety**: All TypeScript interfaces are defined
763
+ 3. ✅ **Storybook Stories**: Complete documentation with examples
764
+ 4. ✅ **Test Data**: Comprehensive test scenarios
765
+ 5. ✅ **Visual Tests**: All visual regression tests pass
766
+ 6. ✅ **Documentation**: Component behavior is clearly documented
767
+ 7. ✅ **Performance**: Component renders efficiently for large datasets
768
+
769
+ #### Code Review Process
770
+
771
+ 1. **Functionality Review**: Verify component logic and API design
772
+ 2. **Documentation Review**: Ensure Storybook stories are comprehensive
773
+ 3. **Visual Review**: Check all visual test snapshots for accuracy
774
+ 4. **Performance Review**: Validate rendering performance
775
+ 5. **Integration Review**: Confirm component works with existing ecosystem
776
+
283
777
  ### Best Practices
284
778
 
285
779
  1. **Performance Considerations**
@@ -293,10 +787,17 @@ graph.addPlugin('custom', new CustomPlugin());
293
787
  - Support keyboard navigation for critical operations
294
788
 
295
789
  3. **Testing**
296
- - Write unit tests for custom components
297
- - Create visual regression tests for custom node renders
790
+ - Write comprehensive visual regression tests for all component states
791
+ - Create multiple test scenarios covering edge cases
792
+ - Maintain up-to-date snapshots when making intentional visual changes
298
793
  - Test across different browsers and device sizes
299
794
 
795
+ 4. **Documentation**
796
+ - Provide clear usage examples in Storybook
797
+ - Document all configuration options
798
+ - Include interactive demos showing component capabilities
799
+ - Explain integration patterns with other components
800
+
300
801
  ### Troubleshooting
301
802
 
302
803
  Common issues and their solutions: