# Lines - [WorkflowLinesManager](https://github.com/bytedance/flowgram.ai/blob/main/packages/canvas-engine/free-layout-core/src/workflow-lines-manager.ts) manages all lines - [WorkflowNodeLinesData](https://github.com/bytedance/flowgram.ai/blob/main/packages/canvas-engine/free-layout-core/src/entity-datas/workflow-node-lines-data.ts) manages lines connected to nodes - [WorkflowLineEntity](https://github.com/bytedance/flowgram.ai/blob/main/packages/canvas-engine/free-layout-core/src/entities/workflow-line-entity.ts) line entity ## Get All Line Entities ```ts pure const allLines = ctx.document.linesManager.getAllLines() // line entities containing from/to representing connected nodes ``` ## Create/Delete Lines ```ts pure // from and to are the node IDs to connect, fromPort, toPort are port IDs, if node has single port can be omitted const line = ctx.document.linesManager.createLine({ from, to, fromPort, toPort }) // delete line line.dispose() ``` ## Export Line Data :::note Basic line structure: - sourceNodeID: `string` source node id - targetNodeID: `string` target node id - sourcePortID?: `string | number` source port id, defaults to node's default port if omitted - targetPortID?: `string | number` target port id, defaults to node's default port if omitted ::: ```ts pure const json = ctx.document.linesManager.toJSON() ``` ## Get Input/Output Nodes or Lines for Current Node ```ts pure import { WorkflowNodeLinesData } from '@flowgram.ai/free-layout-editor' // get input nodes (calculated through connection lines) node.getData(WorkflowNodeLinesData).inputNodes // get all input nodes (recursively gets all upstream nodes) node.getData(WorkflowNodeLinesData).allInputNodes // get output nodes node.getData(WorkflowNodeLinesData).outputNodes // get all output nodes node.getData(WorkflowNodeLinesData).allOutputNodes // input lines node.getData(WorkflowNodeLinesData).inputLines // output lines node.getData(WorkflowNodeLinesData).outputLines ``` ## Line Configuration We provide rich line configuration parameters for `FreeLayoutEditorProvider`, see details in [FreeLayoutProps](https://github.com/bytedance/flowgram.ai/blob/main/packages/client/free-layout-editor/src/preset/free-layout-props.ts) ```tsx pure interface FreeLayoutProps { /** * Line color configuration */ lineColor?: LineColor; /** * Determine if line should be marked red * @param ctx * @param fromPort * @param toPort * @param lines */ isErrorLine?: (ctx: FreeLayoutPluginContext, fromPort: WorkflowPortEntity, toPort: WorkflowPortEntity | undefined, lines: WorkflowLinesManager) => boolean; /** * Determine if port should be marked red * @param ctx * @param port */ isErrorPort?: (ctx: FreeLayoutPluginContext, port: WorkflowPortEntity) => boolean; /** * Determine if port should be disabled * @param ctx * @param port */ isDisabledPort?: (ctx: FreeLayoutPluginContext, port: WorkflowPortEntity) => boolean; /** * Determine if line arrow should be reversed * @param ctx * @param line */ isReverseLine?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => boolean; /** * Determine if line arrow should be hidden * @param ctx * @param line */ isHideArrowLine?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => boolean; /** * Determine if line should show flowing effect * @param ctx * @param line */ isFlowingLine?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => boolean; /** * Determine if line should be disabled * @param ctx * @param line */ isDisabledLine?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => boolean; /** * Determine if line should be vertical * @param ctx * @param line */ isVerticalLine?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => boolean; /** * Line drag end * @param ctx * @param params */ onDragLineEnd?: (ctx: FreeLayoutPluginContext, params: onDragLineEndParams) => Promise; /** * Set line renderer type * @param ctx * @param line */ setLineRenderType?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => LineRenderType | undefined; /** * Set line style * @param ctx * @param line */ setLineClassName?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => string | undefined; /** * Whether to allow line creation * @param ctx * @param fromPort - start point * @param toPort - target point */ canAddLine?: (ctx: FreeLayoutPluginContext, fromPort: WorkflowPortEntity, toPort: WorkflowPortEntity, lines: WorkflowLinesManager, silent?: boolean) => boolean; /** * Whether to allow line deletion * @param ctx * @param line - target line * @param newLineInfo - new line info * @param silent - if false, can show toast */ canDeleteLine?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity, newLineInfo?: Required, silent?: boolean) => boolean; /** * Whether to allow line reset * @param fromPort - start point * @param oldToPort - old connection point * @param newToPort - new connection point * @param lines - line manager */ canResetLine?: (ctx: FreeLayoutPluginContext, fromPort: WorkflowPortEntity, oldToPort: WorkflowPortEntity, newToPort: WorkflowPortEntity, lines: WorkflowLinesManager) => boolean; } ``` ### 1. Custom Colors ```tsx pure function App() { const editorProps: FreeLayoutProps = { lineColor: { hidden: 'transparent', default: '#4d53e8', drawing: '#5DD6E3', hovered: '#37d0ff', selected: '#37d0ff', error: 'red', flowing: '#ff6b35', // Color for flowing lines (e.g., during workflow execution) }, // ...others } return ( ) } ``` ### 2. Limit Single Output Port to One Line ```tsx pure 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; } // control number of lines if (fromPort.availableLines.length >= 1) { return false } return true; }, // ...others } return ( ) } ``` ### 3. Add Node by Connecting to Empty Space See code in free layout best practices ```tsx pure 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; } // Can open add panel based on mousePos here await ctx.get(WorkflowNodePanelService).call({ fromPort, toPort: undefined, panelPosition: mousePos, enableBuildLine: true, panelProps: { enableNodePlaceholder: true, enableScrollClose: true, }, }); }, // ...others } return ( ) } ``` ## Add Label to Line See code in free layout best practices ```ts pure import { createFreeLinesPlugin } from '@flowgram.ai/free-lines-plugin'; const editorProps = { plugins: () => [ /** * Line render plugin */ createFreeLinesPlugin({ renderInsideLine: LineAddButton, }), ] } ``` ## Node Listening to Its Own Line Changes and Refresh ```tsx pure 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
xxxx
} ``` ## Listen to All Line Connection Changes This scenario is used when you want to monitor line connections in external components ```ts pure 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()) } ```