npm package: @moxa/graph
Description: - [Install](#install) - [Basic Usage](#basic-usage) - [Components](#components) - [Behaviors](#behaviors) - [Layouts](#layouts) - [Plugins](#plugins) - [Graph Control](#graph-control) - [Event Handling](#event-handling) - [Developer Guidelines](#developer
Latest version: 3.0.0-beta.16

# Moxa Graph Library

### [👉 Documentation](https://moxa.gitlab.io/sw/f2e/one/one-moxa-vizion/?path=/docs/introduction--docs)

- [Install](#install)
- [Basic Usage](#basic-usage)
- [Components](#components)
- [Behaviors](#behaviors)
- [Layouts](#layouts)
- [Plugins](#plugins)
- [Graph Control](#graph-control)
- [Event Handling](#event-handling)
- [Developer Guidelines](#developer-guidelines)

## Install

### Use `npm`

```bash
npm i @moxa/graph
```

### Use `pnpm`

```bash
pnpm add @moxa/graph
```

## Basic Usage

> Graph will be generated on the dom element with the specified `id`

### Create a DOM

```html
<div id="container"></div>
```

### Initialize Graph

> You need to provide a json config object to initialize graph instance <br />
> The config include all graph settings, please refer to `MxGraphConfig`

```typescript
import { Graph } from '@moxa/graph';

const graph = new Graph({
  container: 'container',
  width: 500,
  height: 500,
  renderer: 'canvas', // 'canvas', 'svg', or 'webgl'
  data: {
    nodes: [
      {
        id: 'node1',
        data: {
          type: 'node-device',
          x: 100,
          y: 50,
        },
      },
      {
        id: 'node2',
        data: {
          type: 'node-device',
          x: 100,
          y: 250,
        },
      },
      {
        id: 'node3',
        data: {
          type: 'node-device',
          x: 300,
          y: 50,
        },
      },
    ],
    edges: [
      {
        id: 'edge1',
        source: 'node1',
        target: 'node2',
        data: {
          type: 'edge-line',
        },
      },
      {
        id: 'edge2',
        source: 'node2',
        target: 'node3',
        data: {
          type: 'edge-quadratic',
        },
      },
    ],
  },
});

// Render the graph
graph.render();
```

## Components

### Node Components

- **node-device**: Device node with icon and label support
- **node-icon**: Icon-only node component
- **node-label**: Label-only node component

### Edge Components

- **edge-line**: Straight line edges
- **edge-polyline**: Multi-segment polyline edges
- **edge-quadratic**: Curved quadratic edges
- **edge-arrow**: Arrow markers for edges
- **edge-label**: Labels for edges

### Group Components

- **group-device**: Device group with expand/collapse functionality

## Behaviors

Interactive behaviors for user interaction:

- **brush-select**: Rectangle selection
- **click-select**: Click to select elements
- **collapse-expand**: Group collapse/expand
- **create-edge**: Interactive edge creation
- **drag-canvas**: Canvas dragging
- **drag-element**: Element dragging
- **fix-element-size**: Fixed element sizing
- **focus-element**: Element focusing
- **hover-activate**: Hover activation
- **scroll-canvas**: Canvas scrolling
- **select-all**: Select all elements
- **zoom-canvas**: Canvas zooming

## Layouts

Automatic layout algorithms:

- **align**: Alignment layout
- **force**: Force-directed layout
- **grid**: Grid layout
- **ring**: Circular ring layout
- **tree**: Hierarchical tree layout

## Plugins

Extensible plugin system:

- **context-menu**: Right-click context menu
- **element-toolbar**: Element-specific toolbar
- **fixed-toolbar**: Fixed position toolbar
- **graph-background**: Customizable graph background
- **history**: Undo/redo functionality
- **minimap**: Mini navigation map
- **snapline**: Element alignment guides
- **tooltip**: Element tooltips

## Graph Control

> You can control the presentation and behavior of the graph by calling the `Graph` methods

```typescript
// Data Management
graph.addNode({ id: 'node1', data: { type: 'node-device' } });
graph.updateNode('node1', { data: { x: 100, y: 100 } });
graph.removeNode('node1');

graph.addEdge({ id: 'edge1', source: 'node1', target: 'node2' });
graph.updateEdge('edge1', { data: { type: 'edge-line' } });
graph.removeEdge('edge1');

graph.addGroup({ id: 'group1', data: { type: 'group-device' } });
graph.updateGroup('group1', { data: { collapsed: true } });
graph.removeGroup('group1');

// View Control
graph.zoom(1.5);
graph.zoomTo(2.0, { x: 100, y: 100 });
graph.fitView();
graph.fitCenter();
graph.focusItem('node1');

// Layout
graph.setLayout({ type: 'force', options: {} });
graph.layout();

// Behaviors
graph.setBehavior(['drag-canvas', 'zoom-canvas']);

// Plugins
graph.setPlugin('minimap', { size: [200, 150] });

// Themes
graph.setTheme('dark');
graph.setThemeTokens({ primaryColor: '#1890ff' });

// State Management
graph.setElementState('node1', 'selected', true);
graph.clearElementState('node1', 'selected');
```

## Event Handling

```typescript
// Listen Events
graph.on('node:click', (event) => {
  console.log('Node clicked:', event.itemId);
});

graph.on('edge:click', (event) => {
  console.log('Edge clicked:', event.itemId);
});

graph.on('canvas:click', (event) => {
  console.log('Canvas clicked:', event.canvas);
});

// Remove event listeners
graph.off('node:click', listener);

// One-time event listeners
graph.once('afterrender', () => {
  console.log('Graph rendered');
});
```

## Developer Guidelines

This section provides guidelines for developers who want to contribute to or extend the Moxa Graph library.

### Project Structure & Folder Responsibilities

```
libs/graph/
├── src/                     # Source code
│   ├── core/                # 🔧 Core functionality (Graph API, models, utilities)
│   ├── components/          # 🎨 Visual components (nodes, edges, groups)
│   ├── behaviors/           # 🖱️ Interactive behaviors (12 behaviors)
│   ├── layouts/             # 📐 Layout algorithms (5 layouts)
│   ├── plugins/             # 🔌 Plugin system (8 plugins)
│   ├── shared/              # 🛠️ Shared utilities and types
│   ├── assets/              # 📦 Static assets (icons, images)
│   ├── styles/              # 🎨 Global styles
│   └── stories/             # 📚 API documentation
├── tests/                   # 🧪 Test utilities
├── e2e/                     # 🎭 End-to-end testing
├── .storybook/              # 📖 Storybook configuration
└── package.json             # 📦 Package configuration
```

#### Detailed Folder Responsibilities

##### Core (`src/core/`)

**Purpose**: Essential graph functionality and APIs

- **`graph/`**: Main Graph class implementing the public API, lifecycle management, and core operations
- **`model/`**: TypeScript type definitions, interfaces, and data structures used throughout the library
- **`utils/`**: Core utility functions for graph manipulation, theme management, and element operations

##### Components (`src/components/`)

**Purpose**: Visual rendering components for graph elements

- **Node Components**: Different node types (device, icon, label) with specialized rendering logic
- **Edge Components**: Various edge types (line, polyline, quadratic) with arrow and label support
- **Group Components**: Container components for grouping and organizing elements
- **Shared**: Common utilities and transformations used across components

##### Behaviors (`src/behaviors/`)

**Purpose**: User interaction handling and graph manipulation

- Each behavior handles specific user interactions (clicking, dragging, selecting)
- Behaviors are modular and can be enabled/disabled independently
- Includes both mouse and keyboard interaction handling

##### Layouts (`src/layouts/`)

**Purpose**: Automatic positioning algorithms for graph elements

- **Force**: Physics-based layout for natural node arrangements
- **Tree**: Hierarchical layouts for tree-structured data
- **Grid**: Regular grid arrangements for systematic positioning
- **Ring**: Circular arrangements for cyclical data
- **Align**: Tools for manual element alignment

##### Plugins (`src/plugins/`)

**Purpose**: Extensible features that enhance graph functionality

- Each plugin is self-contained and optional
- Plugins can add UI elements (toolbars, tooltips, minimap)
- Includes data management features (history, context menus)

##### Shared (`src/shared/`)

**Purpose**: Common utilities and infrastructure

- **Types**: Shared TypeScript definitions used across modules
- **Utils**: Generic utility functions and shared components
- **Transforms**: Data transformation pipeline for processing graph configurations
- **Constants**: Application-wide constants and configuration values

##### Testing Infrastructure

- **`tests/`**: Reusable test utilities and helper functions
- **`e2e/`**: End-to-end visual regression testing with Playwright
- \*\*Each component includes its own test suite with visual snapshots

##### Documentation & Development

- **`.storybook/`**: Interactive documentation and component playground
- **`stories/`**: Written documentation and API guides
- \*\*Each component includes comprehensive Storybook stories for demonstration and testing

### Development Workflow

1. **Setup Development Environment**

   ```bash
   # Clone the repository
   git clone <repository-url>

   # Install dependencies
   pnpm install

   # Start development server
   pnpm dev
   ```

2. **Building the Library**

   ```bash
   # Build the library
   pnpm build:graph

   # Run unit tests
   pnpm test:graph

   # Run e2e tests
   pnpm e2e:graph

   # Start Storybook for development
   pnpm storybook:graph
   ```

### Extending the Library

#### Creating Custom Components

Custom components are created using the G6 5.x API and registered with specific types:

```typescript
import { ExtensionController } from '@antv/g6';

// Custom Node Component
const customNode = (context) => {
  const { model, theme } = context;
  const { data } = model;

  return {
    circle: {
      r: 20,
      fill: data.customProperty === 'special' ? 'red' : 'blue',
      stroke: theme.nodeStroke,
      lineWidth: 2,
    },
  };
};

// Register the custom component
ExtensionController.register('node', 'custom-node', customNode);

// Use in graph data
const nodeData = {
  id: 'node1',
  data: {
    type: 'custom-node',
    customProperty: 'special',
  },
};
```

#### Creating Custom Layouts

```typescript
import { ExtensionController } from '@antv/g6';

// Custom Layout Implementation
const customLayout = (graph, options) => {
  return {
    id: 'custom-layout',
    async execute() {
      const { nodes } = graph.getData();
      const { radius = 200 } = options;

      nodes.forEach((node, index) => {
        const angle = (index / nodes.length) * Math.PI * 2;
        node.data.x = Math.cos(angle) * radius;
        node.data.y = Math.sin(angle) * radius;
      });

      return { nodes, edges: graph.getData().edges };
    },
  };
};

// Register the layout
ExtensionController.register('layout', 'custom-layout', customLayout);

// Apply custom layout
graph.setLayout({
  type: 'custom-layout',
  options: { radius: 300 },
});
```

#### Creating Plugins

```typescript
import { BasePlugin } from '@antv/g6';

class CustomPlugin extends BasePlugin {
  constructor(options) {
    super(options);
  }

  init() {
    // Setup plugin
    this.graph.on('node:click', this.handleNodeClick);
  }

  handleNodeClick = (event) => {
    // Implement custom behavior
    console.log('Node clicked:', event.itemId);
  };

  destroy() {
    // Clean up
    this.graph.off('node:click', this.handleNodeClick);
    super.destroy();
  }
}

// Add plugin to graph
graph.setPlugin(
  'custom-plugin',
  new CustomPlugin({
    // plugin options
  }),
);
```

## Development Workflow

This section describes the complete development workflow for contributing to the Moxa Graph library, including component implementation, Storybook documentation, and visual testing.

### Directory Structure & Responsibilities

Each component follows a standardized directory structure:

```
{component-name}/
├── index.ts                    # Component export
├── models/                     # TypeScript type definitions
│   └── index.ts
├── utils/                      # Utility functions
│   └── index.ts
├── stories/                    # Storybook documentation
│   ├── {story-name}.stories.ts     # Storybook configuration
│   ├── {story-name}.component.ts   # Angular wrapper component
│   └── data/                       # Test data definitions
│       └── {story-name}-data.ts
├── tests/                      # Visual regression tests
│   ├── {test-name}.spec.ts
│   └── __snapshots__/          # Visual test snapshots
│       └── {test-name}.png
└── transforms/                 # Data transformation helpers (optional)
    └── index.ts
```

#### Directory Responsibilities

- **`index.ts`**: Main component export, registers the component with G6
- **`models/`**: TypeScript interfaces and type definitions for component configuration
- **`utils/`**: Helper functions for styling, positioning, state management
- **`stories/`**: Complete Storybook documentation with interactive examples
  - **`data/`**: Reusable test data configurations for different scenarios
- **`tests/`**: Playwright visual regression tests ensuring UI consistency
- **`transforms/`**: Data transformation utilities (for complex components)

### Development Process

#### 1. Component Implementation

Start by implementing the core component logic:

```typescript
// components/my-component/index.ts
import { ExtensionController } from '@antv/g6';

const myComponent = (context) => {
  const { model, theme } = context;
  const { data } = model;

  return {
    // Component rendering logic
    rect: {
      width: data.width || 100,
      height: data.height || 50,
      fill: theme.primaryColor,
    },
  };
};

// Register component
ExtensionController.register('node', 'my-component', myComponent);

export { myComponent };
```

#### 2. Type Definitions

Define TypeScript interfaces in `models/`:

```typescript
// components/my-component/models/index.ts
export interface MyComponentConfig {
  width?: number;
  height?: number;
  customProperty?: string;
}

export interface MyComponentData extends NodeData {
  type: 'my-component';
  config: MyComponentConfig;
}
```

#### 3. Storybook Documentation

Create comprehensive Storybook stories:

```typescript
// components/my-component/stories/my-component.stories.ts
import { Meta, StoryObj } from '@storybook/angular';
import { MY_COMPONENT_DATA } from './data/my-component-data';
import { MyComponentComponent } from './my-component.component';

export default {
  title: 'Components/My Component',
  component: MyComponentComponent,
  parameters: {
    docs: {
      description: {
        component: `
# My Component

This component demonstrates custom node rendering capabilities.

## Features
- Configurable dimensions
- Theme integration
- State management

## Usage
\`\`\`typescript
const graph = new Graph({
  container: 'graph',
  data: MY_COMPONENT_DATA,
});
\`\`\`
        `,
      },
    },
  },
  args: {
    graphData: MY_COMPONENT_DATA,
  },
} as Meta;

type Story = StoryObj<MyComponentComponent>;

export const Basic: Story = {};
export const Advanced: Story = {
  args: {
    graphData: ADVANCED_MY_COMPONENT_DATA,
  },
};
```

#### 4. Angular Wrapper Component

Create an Angular component for Storybook:

```typescript
// components/my-component/stories/my-component.component.ts
import { Component, input, computed } from '@angular/core';
import { Graph, GraphConfig, GraphData } from '@moxa/graph';
import { StoryWrapperComponent, JsonViewerComponent } from '@shared/utils/components';

@Component({
  selector: 'moxa-vizion-my-component',
  imports: [JsonViewerComponent, StoryWrapperComponent],
  template: `
    <story-wrapper>
      <json-viewer [data]="data()">
        <div container [id]="id"></div>
      </json-viewer>
    </story-wrapper>
  `,
})
export class MyComponentComponent {
  graph!: Graph;
  id: string = Math.random().toString(36).substring(2);

  graphData = input<GraphData>();

  data = computed<GraphConfig>(() => ({
    container: this.id,
    data: this.graphData()!,
  }));

  ngAfterViewInit(): void {
    this.graph = new Graph(this.data());
    this.graph.render();
  }

  ngOnDestroy(): void {
    this.graph.destroy();
  }
}
```

#### 5. Test Data

Define comprehensive test data:

```typescript
// components/my-component/stories/data/my-component-data.ts
import { GraphData } from '@moxa/graph';

export const MY_COMPONENT_DATA: GraphData = {
  nodes: [
    {
      id: 'node1',
      data: {
        type: 'my-component',
        x: 100,
        y: 100,
        width: 120,
        height: 60,
      },
    },
    // More test scenarios...
  ],
};
```

#### 6. Visual Testing

Implement Playwright visual regression tests:

```typescript
// components/my-component/stories/tests/my-component-basic.spec.ts
import { test } from '@playwright/test';
import { getGraphContainer, getStorybookUrl, verifySnapshot } from '@tests/helpers';

const STORY_ID = 'components-my-component--basic';
const SNAPSHOT_NAME = 'my-component-basic.png';

test.describe('My Component Visual Tests', () => {
  test.beforeEach(async ({ page }) => {
    await page.setViewportSize({ width: 600, height: 500 });
  });

  test('should display basic component correctly', async ({ page, baseURL }) => {
    await page.goto(getStorybookUrl(baseURL, STORY_ID));
    await page.waitForSelector('div.container', { timeout: 10000 });

    const graphContainer = getGraphContainer(page);
    await page.waitForTimeout(1000); // Wait for graph rendering

    await verifySnapshot(graphContainer, SNAPSHOT_NAME);
  });
});
```

### Testing Strategy

#### Visual Regression Testing

The library uses Playwright for comprehensive visual regression testing:

1. **Snapshot Generation**: First test run generates baseline snapshots
2. **Automatic Comparison**: Subsequent runs compare against baselines
3. **Pixel-Perfect Accuracy**: Detects even minor visual changes
4. **Cross-Browser Support**: Ensures consistency across different browsers

#### Test Configuration

Playwright is configured for optimal visual testing:

```typescript
// playwright.config.ts highlights
export default defineConfig({
  testMatch: ['**/e2e/**/*.spec.ts', '**/src/**/tests/*.spec.ts'],
  snapshotPathTemplate: `{testFileDir}/__snapshots__/{arg}{ext}`,
  use: {
    viewport: { width: 1280, height: 720 },
    deviceScaleFactor: 1, // Fixed DPR for consistency
    // Disable animations for consistent screenshots
    launchOptions: {
      args: ['--disable-gpu', '--disable-web-security'],
    },
  },
  expect: {
    toMatchSnapshot: {
      maxDiffPixelRatio: 0.01,
      threshold: 0.1,
    },
  },
});
```

### Development Commands

```bash
# Start Storybook for development
pnpm storybook:graph

# Run all tests
pnpm test:graph

# Run visual regression tests
pnpm e2e:graph

# Update visual snapshots (when intentional changes are made)
pnpm e2e:graph --update-snapshots

# Build the library
pnpm build:graph

# Generate documentation
pnpm docs:graph
```

### Quality Assurance

#### Pre-commit Checklist

Before submitting code, ensure:

1. ✅ **Component Registration**: Component is properly registered with G6
2. ✅ **Type Safety**: All TypeScript interfaces are defined
3. ✅ **Storybook Stories**: Complete documentation with examples
4. ✅ **Test Data**: Comprehensive test scenarios
5. ✅ **Visual Tests**: All visual regression tests pass
6. ✅ **Documentation**: Component behavior is clearly documented
7. ✅ **Performance**: Component renders efficiently for large datasets

#### Code Review Process

1. **Functionality Review**: Verify component logic and API design
2. **Documentation Review**: Ensure Storybook stories are comprehensive
3. **Visual Review**: Check all visual test snapshots for accuracy
4. **Performance Review**: Validate rendering performance
5. **Integration Review**: Confirm component works with existing ecosystem

### Best Practices

1. **Performance Considerations**
   - Use throttling and debouncing for event handlers on large graphs
   - Implement pagination or virtualization for very large datasets
   - Consider using WebGL renderer for graphs with thousands of elements

2. **Accessibility**
   - Ensure proper contrast ratios in your themes
   - Add ARIA attributes to important interactive elements
   - Support keyboard navigation for critical operations

3. **Testing**
   - Write comprehensive visual regression tests for all component states
   - Create multiple test scenarios covering edge cases
   - Maintain up-to-date snapshots when making intentional visual changes
   - Test across different browsers and device sizes

4. **Documentation**
   - Provide clear usage examples in Storybook
   - Document all configuration options
   - Include interactive demos showing component capabilities
   - Explain integration patterns with other components

### Troubleshooting

Common issues and their solutions:

1. **Graph not rendering properly**
   - Check if container has proper dimensions
   - Verify data format follows required schema
   - Check browser console for errors

2. **Performance issues with large graphs**
   - Try different renderers (SVG vs Canvas vs WebGL)
   - Simplify node/edge renderers
   - Use appropriate layout algorithms

3. **Events not firing**
   - Verify event listeners are properly attached
   - Check if event propagation is being stopped
   - Ensure targets exist when attaching listeners