const allLines = ctx.document.linesManager.getAllLines() // 线条实体 包含 from/to 分别代表线条连接的节点
// from和 to 为对应要连线的节点id, fromPort, toPort 为 端口 id, 如果节点为单个端口可以不指定
const line = ctx.document.linesManager.createLine({ from, to, fromPort, toPort })
// 删除线条
line.dispose()
string
开始节点 idstring
目标节点 idstring | number
开始端口 id, 缺省则采用开始节点的默认端口string | number
目标端口 id, 缺省则采用目标节点的默认端口const json = ctx.document.linesManager.toJSON()
// 获取当前节点的输入节点(通过连接线计算)
node.lines.inputNodes
// 获取所有输入节点 (会往上递归获取所有)
node.lines.allInputNodes
// 获取输出节点
node.lines.outputNodes
// 获取所有输出节点
node.lines.allOutputNodes
// 输入线条 (包含 isDrawing 或 isHidden 的线条)
node.lines.inputLines
// 输出线条 (包含 isDrawing 或 isHidden 的线条)
node.lines.outputLines
// 所有线条 (不包含 isDrawing 或 isHidden 的线条)
node.lines.availableLines
我们提供丰富的线条配置参数, 给 FreeLayoutEditorProvider
, 详细见 FreeLayoutProps
interface FreeLayoutProps {
/**
* 线条颜色配置
*/
lineColor?: LineColor;
/**
* 判断线条是否标红
* @param ctx
* @param fromPort
* @param toPort
* @param lines
*/
isErrorLine?: (ctx: FreeLayoutPluginContext, fromPort: WorkflowPortEntity, toPort: WorkflowPortEntity | undefined, lines: WorkflowLinesManager) => boolean;
/**
* 判断端口是否标红
* @param ctx
* @param port
*/
isErrorPort?: (ctx: FreeLayoutPluginContext, port: WorkflowPortEntity) => boolean;
/**
* 判断端口是否禁用
* @param ctx
* @param port
*/
isDisabledPort?: (ctx: FreeLayoutPluginContext, port: WorkflowPortEntity) => boolean;
/**
* 判断线条箭头是否反转
* @param ctx
* @param line
*/
isReverseLine?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => boolean;
/**
* 判断线条是否隐藏箭头
* @param ctx
* @param line
*/
isHideArrowLine?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => boolean;
/**
* 判断线条是否展示流动效果
* @param ctx
* @param line
*/
isFlowingLine?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => boolean;
/**
* 判断线条是否禁用
* @param ctx
* @param line
*/
isDisabledLine?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => boolean;
/**
* 拖拽线条结束
* @param ctx
* @param params
*/
onDragLineEnd?: (ctx: FreeLayoutPluginContext, params: onDragLineEndParams) => Promise<void>;
/**
* 设置线条渲染器类型
* @param ctx
* @param line
*/
setLineRenderType?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => LineRenderType | undefined;
/**
* 设置线条样式
* @param ctx
* @param line
*/
setLineClassName?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => string | undefined;
/**
* 是否允许创建线条
* @param ctx
* @param fromPort - 开始点
* @param toPort - 目标点
*/
canAddLine?: (ctx: FreeLayoutPluginContext, fromPort: WorkflowPortEntity, toPort: WorkflowPortEntity, lines: WorkflowLinesManager, silent?: boolean) => boolean;
/**
*
* 是否允许删除线条
* @param ctx
* @param line - 目标线条
* @param newLineInfo - 新的线条信息
* @param silent - 如果为false,可以加 toast 弹窗
*/
canDeleteLine?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity, newLineInfo?: Required<WorkflowLinePortInfo>, silent?: boolean) => boolean;
/**
* 是否允许重置线条
* @param ctx
* @param oldLine - 原来的线条
* @param newLineInfo - 新的线条信息
* @param lines - 线条管理器
*/
canResetLine?: (ctx: FreeLayoutPluginContext, oldLine: WorkflowLineEntity, newLineInfo: WorkflowLinePortInfo, lines: WorkflowLinesManager ) => boolean;
}
/**
* more: https://github.com/bytedance/flowgram.ai/blob/main/packages/canvas-engine/free-layout-core/src/entities/workflow-line-entity.ts#L41
*/
line.updateUIState({
lockedColor: 'blue',
strokeWidth: 2,
strokeWidthSelected: 3,
className: 'xxx',
style: {}
})
ctx.document.linesManager.getAllLines().forEach(line => {
if (line.from.flowNodeType === 'start') {
line.lockedColor = 'blue'
} else if (line.to.flowNodeType === 'end') {
line.lockedColor = 'yellow'
}
})
function App() {
const editorProps: FreeLayoutProps = {
lineColor: {
hidden: 'transparent',
default: '#4d53e8',
drawing: '#5DD6E3',
hovered: '#37d0ff',
selected: '#37d0ff',
error: 'red',
},
// ...others
}
return (
<FreeLayoutEditorProvider {...editorProps}>
<EditorRenderer className="demo-editor" />
</FreeLayoutEditorProvider>
)
}
function App() {
const editorProps: FreeLayoutProps = {
/*
* Check whether the line can be added
* 判断是否连线
*/
canAddLine(ctx, fromPort, toPort) {
// not the same node
if (fromPort.node === toPort.node) {
return false;
}
// 控制线条添加数目
if (fromPort.availableLines.length >= 1) {
return false
}
return true;
},
// ...others
}
return (
<FreeLayoutEditorProvider {...editorProps}>
<EditorRenderer className="demo-editor" />
</FreeLayoutEditorProvider>
)
}
代码见自由布局最佳实践
function App() {
const editorProps: FreeLayoutProps = {
/**
* Drag the end of the line to create an add panel (feature optional)
* 拖拽线条结束需要创建一个添加面板 (功能可选)
*/
async onDragLineEnd(ctx, params) {
const { fromPort, toPort, mousePos, line, originLine } = params;
if (originLine || !line) {
return;
}
if (toPort) {
return;
}
// 这里可以根据 mousePos 打开添加面板
await ctx.get(WorkflowNodePanelService).call({
fromPort,
toPort: undefined,
panelPosition: mousePos,
enableBuildLine: true,
panelProps: {
enableNodePlaceholder: true,
enableScrollClose: true,
},
});
},
// ...others
}
return (
<FreeLayoutEditorProvider {...editorProps}>
<EditorRenderer className="demo-editor" />
</FreeLayoutEditorProvider>
)
}
你可以通过注册自定义的箭头渲染器来完全自定义线条的箭头样式。
import {
FlowRendererKey,
type ArrowRendererProps,
useEditorProps
} from '@flowgram.ai/free-layout-editor';
// 1. 创建自定义箭头组件
function CustomArrowRenderer({ id, pos, reverseArrow, strokeWidth, vertical, hide }: ArrowRendererProps) {
if (hide) return null;
const size = 8;
const rotation = reverseArrow
? (vertical ? 270 : 180)
: (vertical ? 90 : 0);
return (
<g
id={id}
transform={`translate(${pos.x}, ${pos.y}) rotate(${rotation})`}
>
<path
d={`M0,0 L${-size},-${size/2} L${-size},${size/2} Z`}
fill="currentColor"
strokeWidth={strokeWidth}
stroke="currentColor"
/>
</g>
);
}
// 2. 在编辑器中注册自定义箭头
function App() {
const materials = {
components: {
[FlowRendererKey.ARROW_RENDERER]: CustomArrowRenderer,
},
};
const editorProps = useEditorProps({
materials,
// ...其他配置
});
return (
<FreeLayoutEditorProvider {...editorProps}>
<EditorRenderer className="demo-editor" />
</FreeLayoutEditorProvider>
);
}
高级用法:根据线条状态动态渲染不同的箭头样式:
function AdvancedArrowRenderer({ id, pos, reverseArrow, strokeWidth, vertical, hide, line }: ArrowRendererProps) {
if (hide) return null;
const size = 8;
const rotation = reverseArrow
? (vertical ? 270 : 180)
: (vertical ? 90 : 0);
// 根据线条状态选择不同的箭头样式
let arrowPath: string;
let fillColor: string;
if (line?.hasError) {
// 错误状态:红色感叹号箭头
arrowPath = `M0,0 L${-size},-${size/2} L${-size},${size/2} Z`;
fillColor = '#ff4d4f';
} else if (line?.processing) {
// 处理中状态:蓝色圆形箭头
arrowPath = `M0,0 m-${size/2},0 a${size/2},${size/2} 0 1,0 ${size},0 a${size/2},${size/2} 0 1,0 -${size},0`;
fillColor = '#1890ff';
} else {
// 默认状态:标准三角形箭头
arrowPath = `M0,0 L${-size},-${size/2} L${-size},${size/2} Z`;
fillColor = 'currentColor';
}
return (
<g
id={id}
transform={`translate(${pos.x}, ${pos.y}) rotate(${rotation})`}
>
<path
d={arrowPath}
fill={fillColor}
strokeWidth={strokeWidth}
stroke={fillColor}
/>
</g>
);
}
ArrowRendererProps 接口:
interface ArrowRendererProps {
id: string; // 箭头唯一标识符
pos: { x: number; y: number }; // 箭头位置
reverseArrow: boolean; // 是否反转箭头方向
strokeWidth: number; // 线条粗细
vertical: boolean; // 是否为垂直线条
hide: boolean; // 是否隐藏箭头
line?: WorkflowLineEntity; // 线条实体(可 用于获取状态)
}
代码见自由布局最佳实践
import { createFreeLinesPlugin } from '@flowgram.ai/free-lines-plugin';
const editorProps = {
plugins: () => [
/**
* Line render plugin
* 连线渲染插件
*/
createFreeLinesPlugin({
renderInsideLine: LineAddButton,
}),
]
}
import {
useRefresh,
WorkflowNodeLinesData,
} from '@flowgram.ai/free-layout-editor';
function NodeRender({ node }) {
const refresh = useRefresh()
const linesData = node.get(WorkflowNodeLinesData)
useEffect(() => {
const dispose = linesData.onDataChange(() => refresh())
return () => dispose.dispose()
}, [])
return <div>xxxx</div>
}
这个场景用于当希望在外部组件监听线条连接情况
import { useEffect } from 'react'
import { WorkflowLinesManager, useRefresh } from '@flowgram.ai/free-layout-editor'
function SomeReact() {
const refresh = useRefresh()
const linesManager = useService(WorkflowLinesManager)
useEffect(() => {
const dispose = linesManager.onAvailableLinesChange(() => refresh())
return () => dispose.dispose()
}, [])
console.log(ctx.document.linesManager.getAllLines())
}