react-diagrams自定义端口布局:从左到右、从上到下的排列策略
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布局实现了端口的水平均匀分布。关键点包括:
- 使用
position: absolute将端口容器定位在节点顶部 display: flex启用弹性布局justifyContent: 'space-around'使端口均匀分布- 通过
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中构建复杂流程图的关键能力。通过本文介绍的方法,你可以实现各种排列策略:
- 基础对齐:使用
PortModelAlignment定义端口的基本方向 - 水平排列:使用
display: flex和justifyContent实现从左到右排列 - 垂直排列:使用
display: flex和flexDirection: column实现从上到下排列 - 灵活扩展:通过端口索引和自定义计算实现更复杂的布局算法
建议在实际开发中,将端口布局逻辑封装在独立的组件中,提高代码的可维护性和复用性。同时,考虑为端口添加交互效果(如悬停高亮),提升用户体验。
如果你想查看更多端口布局示例,可以参考项目中的演示代码:diagrams-demo-gallery/demos/demo-custom-node1,其中包含了钻石节点的完整实现。
更多推荐
所有评论(0)