react-diagrams自定义端口布局:从左到右、从上到下的排列策略

【免费下载链接】react-diagrams a super simple, no-nonsense diagramming library written in react that just works 【免费下载链接】react-diagrams 项目地址: https://gitcode.com/gh_mirrors/re/react-diagrams

在使用react-diagrams构建流程图时,节点的端口(Port)布局直接影响用户体验和流程图的可读性。默认的端口排列方式可能无法满足特定业务需求,本文将详细介绍如何通过自定义实现端口的从左到右、从上到下排列策略,帮助你构建更专业的流程图应用。

端口布局基础:理解PortModel与对齐方式

react-diagrams中的端口布局核心在于PortModel类的alignment属性。通过设置不同的对齐方式,我们可以控制端口在节点上的基本位置。官方文档中提到,端口支持四种基本对齐方式:上(TOP)、右(RIGHT)、下(BOTTOM)和左(LEFT)docs/customizing/ports.md

以下是钻石节点(Diamond Node)中端口布局的经典实现,通过构造函数传入对齐方式参数:

// 钻石端口模型定义
export class DiamondPortModel extends PortModel {
	constructor(alignment: PortModelAlignment) {
		super({
			type: 'diamond',
			name: alignment,
			alignment: alignment // 设置端口对齐方式
		});
	}
	
	createLinkModel(): LinkModel {
		return new DefaultLinkModel();
	}
}

在节点模型中,我们可以为每个方向添加一个端口:

// 钻石节点模型
export class DiamondNodeModel extends NodeModel {
	constructor() {
		super({ type: 'diamond' });
		this.addPort(new DiamondPortModel(PortModelAlignment.TOP));
		this.addPort(new DiamondPortModel(PortModelAlignment.LEFT));
		this.addPort(new DiamondPortModel(PortModelAlignment.BOTTOM));
		this.addPort(new DiamondPortModel(PortModelAlignment.RIGHT));
	}
}

从左到右排列:水平布局实现

当需要在节点的某一侧(如顶部或底部)排列多个端口时,我们需要自定义端口的位置计算逻辑。以下是实现顶部端口从左到右排列的关键代码:

// 自定义节点部件中的端口渲染逻辑
<div style={{ 
  position: 'absolute', 
  top: 0, 
  left: 0, 
  width: '100%', 
  display: 'flex', 
  justifyContent: 'space-around' 
}}>
  {node.getPorts().filter(port => port.getOptions().alignment === PortModelAlignment.TOP)
    .map((port, index) => (
      <PortWidget
        key={index}
        port={port}
        engine={engine}
        style={{ 
          width: 16, 
          height: 16,
          margin: '0 8px'
        }}
      >
        <div style={{ 
          width: '100%', 
          height: '100%', 
          background: 'orange', 
          borderRadius: '50%' 
        }} />
      </PortWidget>
    ))}
</div>

上述代码通过CSS的flex布局实现了端口的水平均匀分布。关键点包括:

  1. 使用position: absolute将端口容器定位在节点顶部
  2. display: flex启用弹性布局
  3. justifyContent: 'space-around'使端口均匀分布
  4. 通过filter筛选出顶部对齐的端口

从上到下排列:垂直布局实现

对于左侧或右侧的多端口垂直排列,我们可以采用类似的弹性布局策略,但需要设置flexDirection: 'column'

// 垂直排列的端口容器
<div style={{ 
  position: 'absolute', 
  top: 0, 
  left: 0, 
  height: '100%', 
  display: 'flex', 
  flexDirection: 'column',
  justifyContent: 'space-around' 
}}>
  {node.getPorts().filter(port => port.getOptions().alignment === PortModelAlignment.LEFT)
    .map((port, index) => (
      <PortWidget
        key={index}
        port={port}
        engine={engine}
        style={{ width: 16, height: 16, margin: '8px 0' }}
      >
        <div style={{ width: '100%', height: '100%', background: 'blue', borderRadius: '50%' }} />
      </PortWidget>
    ))}
</div>

完整实现:自定义多端口节点

结合水平和垂直布局策略,我们可以创建一个支持多端口排列的自定义节点。以下是完整的实现步骤:

1. 创建自定义端口模型

// src/custom-ports/CustomPortModel.ts
import { PortModel, PortModelAlignment, LinkModel, DefaultLinkModel } from '@projectstorm/react-diagrams';

export class CustomPortModel extends PortModel {
	constructor(
		alignment: PortModelAlignment, 
		public index: number // 用于计算位置的索引
	) {
		super({
			type: 'custom',
			name: `${alignment}-${index}`,
			alignment: alignment
		});
	}
	
	createLinkModel(): LinkModel {
		return new DefaultLinkModel();
	}
}

2. 创建支持多端口的节点模型

// src/custom-ports/MultiPortNodeModel.ts
import { NodeModel } from '@projectstorm/react-diagrams';
import { CustomPortModel } from './CustomPortModel';
import { PortModelAlignment } from '@projectstorm/react-diagrams';

export class MultiPortNodeModel extends NodeModel {
	constructor() {
		super({ type: 'multi-port-node' });
		
		// 添加左侧端口(从上到下排列)
		for (let i = 0; i < 3; i++) {
			this.addPort(new CustomPortModel(PortModelAlignment.LEFT, i));
		}
		
		// 添加右侧端口(从上到下排列)
		for (let i = 0; i < 3; i++) {
			this.addPort(new CustomPortModel(PortModelAlignment.RIGHT, i));
		}
		
		// 添加顶部端口(从左到右排列)
		for (let i = 0; i < 2; i++) {
			this.addPort(new CustomPortModel(PortModelAlignment.TOP, i));
		}
		
		// 添加底部端口(从左到右排列)
		for (let i = 0; i < 2; i++) {
			this.addPort(new CustomPortModel(PortModelAlignment.BOTTOM, i));
		}
	}
}

3. 创建节点部件实现端口布局

// src/custom-ports/MultiPortNodeWidget.tsx
import * as React from 'react';
import { NodeWidget } from '@projectstorm/react-diagrams';
import { MultiPortNodeModel } from './MultiPortNodeModel';
import { PortWidget, DiagramEngine, PortModelAlignment } from '@projectstorm/react-diagrams';

interface MultiPortNodeWidgetProps {
	node: MultiPortNodeModel;
	engine: DiagramEngine;
	size?: number;
}

export class MultiPortNodeWidget extends React.Component<MultiPortNodeWidgetProps> {
	render() {
		const size = this.props.size || 150;
		const portSize = 16;
		const leftPorts = this.props.node.getPorts().filter(p => p.getOptions().alignment === PortModelAlignment.LEFT);
		const rightPorts = this.props.node.getPorts().filter(p => p.getOptions().alignment === PortModelAlignment.RIGHT);
		const topPorts = this.props.node.getPorts().filter(p => p.getOptions().alignment === PortModelAlignment.TOP);
		const bottomPorts = this.props.node.getPorts().filter(p => p.getOptions().alignment === PortModelAlignment.BOTTOM);
		
		return (
			<div style={{
				width: size,
				height: size,
				background: '#f0f0f0',
				border: '2px solid #333',
				borderRadius: 4,
				position: 'relative'
			}}>
				{/* 节点标题 */}
				<div style={{ 
					textAlign: 'center', 
					background: '#333', 
					color: 'white', 
					padding: '4px', 
					fontSize: '12px' 
				}}>
					多端口节点
				</div>
				
				{/* 左侧端口(从上到下排列) */}
				<div style={{
					position: 'absolute',
					left: -portSize/2,
					top: 20,
					bottom: 20,
					display: 'flex',
					flexDirection: 'column',
					justifyContent: 'space-around',
					width: portSize
				}}>
					{leftPorts.map((port, index) => (
						<PortWidget
							key={index}
							port={port}
							engine={this.props.engine}
						>
							<div style={{
								width: portSize,
								height: portSize,
								background: 'blue',
								borderRadius: '50%'
							}} />
						</PortWidget>
					))}
				</div>
				
				{/* 右侧端口(从上到下排列) */}
				<div style={{
					position: 'absolute',
					right: -portSize/2,
					top: 20,
					bottom: 20,
					display: 'flex',
					flexDirection: 'column',
					justifyContent: 'space-around',
					width: portSize
				}}>
					{rightPorts.map((port, index) => (
						<PortWidget
							key={index}
							port={port}
							engine={this.props.engine}
						>
							<div style={{
								width: portSize,
								height: portSize,
								background: 'red',
								borderRadius: '50%'
							}} />
						</PortWidget>
					))}
				</div>
				
				{/* 顶部端口(从左到右排列) */}
				<div style={{
					position: 'absolute',
					top: -portSize/2,
					left: 20,
					right: 20,
					display: 'flex',
					justifyContent: 'space-around',
					height: portSize
				}}>
					{topPorts.map((port, index) => (
						<PortWidget
							key={index}
							port={port}
							engine={this.props.engine}
						>
							<div style={{
								width: portSize,
								height: portSize,
								background: 'green',
								borderRadius: '50%'
							}} />
						</PortWidget>
					))}
				</div>
				
				{/* 底部端口(从左到右排列) */}
				<div style={{
					position: 'absolute',
					bottom: -portSize/2,
					left: 20,
					right: 20,
					display: 'flex',
					justifyContent: 'space-around',
					height: portSize
				}}>
					{bottomPorts.map((port, index) => (
						<PortWidget
							key={index}
							port={port}
							engine={this.props.engine}
						>
							<div style={{
								width: portSize,
								height: portSize,
								background: 'yellow',
								borderRadius: '50%'
							}} />
						</PortWidget>
					))}
				</div>
			</div>
		);
	}
}

4. 注册节点工厂

// src/custom-ports/index.ts
import { AbstractReactFactory } from '@projectstorm/react-diagrams';
import { MultiPortNodeModel } from './MultiPortNodeModel';
import { MultiPortNodeWidget } from './MultiPortNodeWidget';

export class MultiPortNodeFactory extends AbstractReactFactory<MultiPortNodeModel> {
	constructor() {
		super('multi-port-node');
	}
	
	generateModel(initialConfig: any): MultiPortNodeModel {
		return new MultiPortNodeModel();
	}
	
	generateReactWidget(event: any): JSX.Element {
		return <MultiPortNodeWidget node={event.model} engine={this.engine} />;
	}
}

端口布局效果展示

通过上述实现,我们可以创建一个四侧都有多个端口的节点,其中:

  • 左侧和右侧端口采用从上到下的垂直排列
  • 顶部和底部端口采用从左到右的水平排列

这种布局方式特别适合复杂的业务流程图,如数据处理流程、状态机等场景。你可以根据实际需求调整端口数量、大小和间距,实现更个性化的布局效果。

总结与最佳实践

自定义端口布局是react-diagrams中构建复杂流程图的关键能力。通过本文介绍的方法,你可以实现各种排列策略:

  1. 基础对齐:使用PortModelAlignment定义端口的基本方向
  2. 水平排列:使用display: flexjustifyContent实现从左到右排列
  3. 垂直排列:使用display: flexflexDirection: column实现从上到下排列
  4. 灵活扩展:通过端口索引和自定义计算实现更复杂的布局算法

建议在实际开发中,将端口布局逻辑封装在独立的组件中,提高代码的可维护性和复用性。同时,考虑为端口添加交互效果(如悬停高亮),提升用户体验。

如果你想查看更多端口布局示例,可以参考项目中的演示代码:diagrams-demo-gallery/demos/demo-custom-node1,其中包含了钻石节点的完整实现。

【免费下载链接】react-diagrams a super simple, no-nonsense diagramming library written in react that just works 【免费下载链接】react-diagrams 项目地址: https://gitcode.com/gh_mirrors/re/react-diagrams

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐