├── .dumi ├── favicon.png └── global.css ├── .dumirc.ts ├── .editorconfig ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .prettierignore ├── .prettierrc.js ├── LICENSE ├── README.md ├── docs ├── guide │ ├── case.md │ ├── core.md │ ├── custom.md │ ├── demo │ │ ├── components │ │ │ ├── Edges.tsx │ │ │ ├── Nodes.tsx │ │ │ ├── data.ts │ │ │ └── index.less │ │ ├── custom.tsx │ │ ├── dooring.tsx │ │ ├── index.less │ │ ├── layout.tsx │ │ └── multihandle.tsx │ ├── edge.md │ ├── edgePro.md │ ├── flow.md │ ├── index.md │ ├── intro.md │ ├── layout.md │ ├── node.md │ ├── plugin.md │ ├── subflow.md │ ├── terms.md │ ├── theme.md │ └── viewport.md └── index.md ├── package.json ├── pnpm-lock.yaml ├── public ├── bg.svg ├── d3.png ├── edge.webp ├── elk.png ├── favicon.png ├── force.png ├── library.png └── overview.webp └── tsconfig.json /.dumi/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrXujiang/react-flow/0c6e9995b838a3deb08a074a149b06b3464f4ee4/.dumi/favicon.png -------------------------------------------------------------------------------- /.dumi/global.css: -------------------------------------------------------------------------------- 1 | .dumi-default-navbar { 2 | padding-left: 60px !important; 3 | } 4 | 5 | .react-flow__panel.react-flow__attribution { 6 | display: none !important; 7 | } 8 | -------------------------------------------------------------------------------- /.dumirc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'dumi'; 2 | 3 | export default defineConfig({ 4 | title: 'React-Flow 中文文档', 5 | themeConfig: { 6 | name: 'React-Flow', 7 | favicons: [ 8 | '/favicon.png' 9 | ], 10 | logo: '/favicon.png', 11 | nav: [ 12 | { title: '指南', link: '/guide' }, 13 | { title: 'Flow工作流案例', link: '/guide/case' }, 14 | { title: '页面制作', link: 'https://dooring.vip' }, 15 | { title: '笔记反馈', link: 'http://doc.dooring.vip/design/doc?id=d1722395646623&uid=wep_251711700015023' }, 16 | ], 17 | socialLinks: { 18 | github: 'https://github.com/MrXujiang/react-flow', 19 | zhihu: 'https://www.zhihu.com/people/build800' 20 | }, 21 | footer: '中文版由 徐小夕 提供技术支持' 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /dist 3 | .dumi/tmp 4 | .dumi/tmp-production 5 | .DS_Store 6 | deploy.sh 7 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx commitlint --edit "${1}" 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .dumi/tmp 2 | .dumi/tmp-production 3 | *.yaml 4 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | proseWrap: 'never', 4 | singleQuote: true, 5 | trailingComma: 'all', 6 | overrides: [ 7 | { 8 | files: '*.md', 9 | options: { 10 | proseWrap: 'preserve', 11 | }, 12 | }, 13 | ], 14 | }; 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 徐小夕 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-flow 2 | 3 | ![](./public/overview.webp) 4 | 5 | 中文文档: http://react-flow.com 6 | 7 | 文档搭建引擎: http://flowmix.turntip.cn/docx 8 | 9 | 开源版工作流编辑器: https://github.com/MrXujiang/flowmix-flow 10 | 11 | 工作流编辑器demo: http://flowmix.turntip.cn/flow-v0 12 | 13 | 如果大家觉得有帮助, 欢迎点个小小的 `star`, 后续还会持续更新! 14 | 15 | ## Development 16 | 17 | ```bash 18 | # install dependencies 19 | $ pnpm install 20 | 21 | # start dev server 22 | $ pnpm start 23 | 24 | # build docs 25 | $ pnpm run build 26 | ``` 27 | 28 | ## 更多优质项目 29 | 30 | | 名称 | 描述 | 31 | | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | 32 | | [H5-Dooring](https://github.com/MrXujiang/h5-Dooring) | 让 H5 制作像搭积木一样简单, 轻松搭建 H5 页面, H5 网站, PC 端网站, LowCode 平台. | 33 | | [V6.Dooring](https://github.com/MrXujiang/v6.dooring.public) | 可视化大屏解决方案, 提供一套可视化编辑引擎, 助力个人或企业轻松定制自己的可视化大屏应用. | 34 | | [dooring-electron-lowcode](https://github.com/MrXujiang/dooring-electron-lowcode) | 基于 electron 的 H5-Dooring 编辑器桌面端. | 35 | | [DooringX](https://github.com/H5-Dooring/dooringx) | 快速高效搭建可视化拖拽平台. | 36 | | [Mitu](https://github.com/H5-Dooring/mitu-editor) | 一款轻量级且可扩展的图片/图形编辑器解决方案. | 37 | | [xijs](https://github.com/MrXujiang/xijs) | 一个开箱即用的面向业务的javascript工具库 | 38 | 39 | ## 贡献 40 | 41 | 欢迎对next感兴趣的朋友一起共建: 42 | 43 | - 提交 [issues](https://github.com/MrXujiang/next-admin/issues) 来报告问题和优化建议. 44 | - 主动 [pull requests](https://github.com/MrXujiang/next-admin/pulls) 来优化代码. 45 | 46 | ## 联系 47 | 48 | 微信: `cxzk_168` 49 | 50 | ## LICENSE 51 | 52 | MIT 53 | -------------------------------------------------------------------------------- /docs/guide/case.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 指南 3 | title: 脑图应用 4 | order: 1 5 | group: 6 | title: 应用案例 7 | order: 4 8 | --- 9 | 10 | ## 案例一: Dooring可视化搭建平台系统架构 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/guide/core.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 指南 3 | title: 核心概念 4 | order: 2 5 | group: 6 | title: 概念&原理 7 | order: 1 8 | --- 9 | 10 | ## 核心概念 11 | 12 | 在下面的部分中,我将介绍 `React Flow` 的核心概念,并解释如何创建交互式流程。 13 | 14 | 流由节点和边(或仅节点)组成。我们可以将节点和边数组作为 `props` 传递给 `ReactFlow` 组件。因此,所有节点和边缘 ID 都必须是唯一的。节点需要一个位置和一个标签(如果您使用自定义节点,这可能会有所不同),而边缘需要源(节点 id)和目标(节点 id)。我们可以在节点选项和边缘选项部分中阅读有关选项的更多信息。 15 | 16 | ### 受控与非受控 17 | 18 | 使用 `React Flow`,我们有两种设置流程的方法。我们可以创建受控或非受控的。建议使用受控流,但对于更简单的用例,我们也可以设置不受控流。在接下来的部分中,我们将设置受控流。让我们首先向 `ReactFlow` 组件添加一些节点和边: 19 | 20 | ```tsx 21 | import { useState } from 'react'; 22 | import { ReactFlow } from '@xyflow/react'; 23 | 24 | const initialNodes = [ 25 | { 26 | id: '1', 27 | type: 'input', 28 | data: { label: 'Input Node' }, 29 | position: { x: 250, y: 25 }, 30 | }, 31 | 32 | { 33 | id: '2', 34 | // you can also pass a React component as a label 35 | data: { label:
Dooring Node
}, 36 | position: { x: 100, y: 125 }, 37 | }, 38 | { 39 | id: '3', 40 | type: 'output', 41 | data: { label: 'Output Node' }, 42 | position: { x: 250, y: 250 }, 43 | }, 44 | ]; 45 | 46 | const initialEdges = [ 47 | { id: 'e1-2', source: '1', target: '2' }, 48 | { id: 'e2-3', source: '2', target: '3', animated: true }, 49 | ]; 50 | 51 | function Flow() { 52 | const [nodes, setNodes] = useState(initialNodes); 53 | const [edges, setEdges] = useState(initialEdges); 54 | 55 | return
; 56 | } 57 | 58 | export default Flow; 59 | ``` 60 | 61 | ### 基础能力 62 | 63 | 默认情况下,除了在设置受控流时处理视口之外,`React Flow` 不会执行任何内部状态更新。与 64 | 组件一样,我们需要传递处理程序以将 `React Flow` 触发的更改应用到节点和边。为了选择、拖动和删除节点和边,我们需要实现 `onNodesChange` 和 `onEdgesChange` 处理程序: 65 | 66 | ```tsx 67 | import { useCallback, useState } from 'react'; 68 | import { ReactFlow, applyEdgeChanges, applyNodeChanges } from '@xyflow/react'; 69 | 70 | const initialNodes = [ 71 | { 72 | id: '1', 73 | type: 'input', 74 | data: { label: 'Input Node' }, 75 | position: { x: 250, y: 25 }, 76 | }, 77 | 78 | { 79 | id: '2', 80 | // you can also pass a React component as a label 81 | data: { label:
Dooring Node
}, 82 | position: { x: 100, y: 125 }, 83 | }, 84 | { 85 | id: '3', 86 | type: 'output', 87 | data: { label: 'Output Node' }, 88 | position: { x: 250, y: 250 }, 89 | }, 90 | ]; 91 | 92 | const initialEdges = [ 93 | { id: 'e1-2', source: '1', target: '2' }, 94 | { id: 'e2-3', source: '2', target: '3', animated: true }, 95 | ]; 96 | 97 | function Flow() { 98 | const [nodes, setNodes] = useState(initialNodes); 99 | const [edges, setEdges] = useState(initialEdges); 100 | 101 | const onNodesChange = useCallback( 102 | (changes) => setNodes((nds) => applyNodeChanges(changes, nds)), 103 | [setNodes], 104 | ); 105 | const onEdgesChange = useCallback( 106 | (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)), 107 | [setEdges], 108 | ); 109 | 110 | return ( 111 |
112 | 119 |
120 | 121 | ); 122 | } 123 | 124 | export default Flow; 125 | ``` 126 | 127 | 这里发生了什么?每当 `React Flow` 触发更改(节点初始化、节点拖动、边缘选择等)时,就会调用 `onNodesChange` 处理程序。我们导出 `applyNodeChanges` 处理程序,以便您不需要自己处理更改。 128 | 129 | `applyNodeChanges` 处理程序返回更新后的节点数组,即新节点数组。您现在拥有具有以下交互类型的交互流: 130 | 131 | - 可选择的节点和边 132 | - 可拖动节点 133 | - 可移除节点和边 134 | -(按 `Backspace` 删除选定的节点或边,可以使用 `deleteKeyCode` 属性进行调整) 135 | 按 `Shift` 键可以选择多选区域(这是默认的选择键代码) 通过按命令进行多项选择(这是默认的 `multiSelectionKeyCode`) 136 | 137 | 为了方便起见,我们导出辅助钩子 `useNodesState` 和 `useEdgesState`,可以使用它们来创建节点和边状态: 138 | 139 | ```tsx | pure 140 | const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); 141 | const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); 142 | ``` 143 | 144 | ### 连接节点 145 | 146 | 获得完整交互性所缺少的最后一个部分是 `onConnect` 处理程序。我们需要实现它,以便处理新连接。 147 | 148 | ```tsx 149 | import { useCallback, useState } from 'react'; 150 | import { ReactFlow, addEdge, applyEdgeChanges, applyNodeChanges } from '@xyflow/react'; 151 | 152 | const initialNodes = [ 153 | { 154 | id: '1', 155 | type: 'input', 156 | data: { label: 'Input Node' }, 157 | position: { x: 250, y: 25 }, 158 | }, 159 | 160 | { 161 | id: '2', 162 | // you can also pass a React component as a label 163 | data: { label:
Dooring Node
}, 164 | position: { x: 100, y: 125 }, 165 | }, 166 | { 167 | id: '3', 168 | type: 'output', 169 | data: { label: 'Output Node' }, 170 | position: { x: 250, y: 250 }, 171 | }, 172 | ]; 173 | 174 | const initialEdges = [ 175 | { id: 'e1-2', source: '1', target: '2' }, 176 | { id: 'e2-3', source: '2', target: '3', animated: true }, 177 | ]; 178 | 179 | function Flow() { 180 | const [nodes, setNodes] = useState(initialNodes); 181 | const [edges, setEdges] = useState(initialEdges); 182 | 183 | const onNodesChange = useCallback( 184 | (changes) => setNodes((nds) => applyNodeChanges(changes, nds)), 185 | [setNodes], 186 | ); 187 | const onEdgesChange = useCallback( 188 | (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)), 189 | [setEdges], 190 | ); 191 | const onConnect = useCallback( 192 | (connection) => setEdges((eds) => addEdge(connection, eds)), 193 | [setEdges], 194 | ); 195 | 196 | return ( 197 |
198 | 206 |
207 | 208 | ); 209 | } 210 | 211 | export default Flow; 212 | ``` 213 | 214 | 在此示例中,我们使用 `addEdge` 处理程序返回一组带有新创建的边的边。如果想在创建边缘时设置某个边缘选项,我们可以像这样传递选项: 215 | 216 | ```tsx | pure 217 | const onConnect = useCallback( 218 | (connection) => 219 | setEdges((eds) => addEdge({ ...connection, animated: true }, eds)), 220 | [setEdges], 221 | ); 222 | ``` 223 | 224 | 或使用 `defaultEdgeOptions` 属性: 225 | 226 | ```tsx | pure 227 | const defaultEdgeOptions = { animated: true }; 228 | ... 229 | ; 237 | ``` 238 | 239 | 240 | 241 | 242 | 243 | 244 | -------------------------------------------------------------------------------- /docs/guide/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 指南 3 | title: 自定义节点 4 | order: 1 5 | group: 6 | title: 自定义React-Flow 7 | order: 2 8 | --- 9 | 10 | ## 自定义节点 11 | 12 | `React Flow` 的一个强大功能是添加自定义节点的能力。在我们的自定义节点中,可以渲染我们想要的一切。例如,可以定义多个源和目标句柄并呈现表单输入或图表。在本节中,我们将实现一个带有输入字段的节点,该输入字段更新应用程序另一部分中的一些文本。 13 | 14 | ### 实现自定义节点 15 | 16 | 自定义节点是一个 `React` 组件,它被包装以提供选择或拖动等基本功能。除了其他属性之外,我们还从包装器组件传递`位置`或`数据`等属性。让我们开始实现 `TextUpdaterNode`。我们使用 `Handle` 组件能够将自定义节点与其他节点连接并向该节点添加输入字段: 17 | 18 | ```tsx | pure 19 | import { useCallback } from 'react'; 20 | import { Handle, Position } from '@xyflow/react'; 21 | 22 | const handleStyle = { left: 10 }; 23 | 24 | function TextUpdaterNode({ data }) { 25 | const onChange = useCallback((evt) => { 26 | console.log(evt.target.value); 27 | }, []); 28 | 29 | return ( 30 | <> 31 | 32 |
33 | 34 | 35 |
36 | 37 | 43 | 44 | ); 45 | } 46 | ``` 47 | 48 | 如您所见,我们已将类名 `nodrag` 添加到输入框中。可以防止在输入字段内拖动并让我们选择文本。 49 | 50 | ### 添加节点类型 51 | 52 | 我们可以通过将新的节点类型添加到 `nodeTypes` 属性来将其添加到 `React Flow`。节点类型在组件外部被记忆或定义是很重要的。否则,`React` 在每次渲染时都会创建一个新对象,这会导致性能问题和错误。 53 | 54 | ```tsx | pure 55 | const nodeTypes = useMemo(() => ({ textUpdater: TextUpdaterNode }), []); 56 | 57 | return ; 58 | ``` 59 | 60 | 定义新的节点类型后,我们可以使用 `type` 节点选项来使用它: 61 | 62 | ```tsx | pure 63 | const nodes = [ 64 | { 65 | id: 'node-1', 66 | type: 'textUpdater', 67 | position: { x: 0, y: 0 }, 68 | data: { value: 123 }, 69 | }, 70 | ]; 71 | ``` 72 | 73 | 将所有内容放在一起并添加一些基本样式后,我们得到一个将文本打印到控制台的自定义节点: 74 | 75 | 76 | 77 | ### 使用多句柄 78 | 79 | 正如所看到的,我们向节点添加了两个源句柄,以便它有两个输出。如果你想用这些特定的句柄连接其他节点,节点 `id` 是不够的,你还需要传递特定的句柄`id`。在本例中,一个句柄的 `ID` 为`“a”`,另一个句柄的 `ID` 为`“b”`。句柄特定边使用引用节点内句柄的 `sourceHandle` 或 `targetHandle` 选项: 80 | 81 | ```tsx | pure 82 | const initialEdges = [ 83 | { id: 'edge-1', source: 'node-1', sourceHandle: 'a', target: 'node-2' }, 84 | { id: 'edge-2', source: 'node-1', sourceHandle: 'b', target: 'node-3' }, 85 | ]; 86 | ``` 87 | 88 | 在这种情况下,两个句柄的源节点都是 `node-1`,但句柄 `ID` 不同。一个来自句柄 `ID“a”`,另一个来自 `“b”`。两条边也有不同的目标节点: 89 | 90 | 91 | 92 | 请注意,如果我们以编程方式更改自定义节点中句柄的位置或数量,则需要使用 `useUpdateNodeInternals` 挂钩来正确通知 `ReactFlow` 更改。从这章我们应该能够构建自定义节点。在大多数情况下,我们建议仅使用自定义节点。内置的只是基本示例。可以在自定义节点 `API` 部分找到传递的 `props` 列表和更多信息。 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /docs/guide/demo/components/Edges.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | BaseEdge, 3 | EdgeLabelRenderer, 4 | getStraightPath, 5 | getBezierPath, 6 | useReactFlow, 7 | } from '@xyflow/react'; 8 | 9 | import { Tag } from 'antd'; 10 | 11 | function CustomEdge({ id, sourceX, sourceY, targetX, targetY }) { 12 | const { setEdges } = useReactFlow(); 13 | const [edgePath, labelX, labelY] = getBezierPath({ 14 | sourceX, 15 | sourceY, 16 | targetX, 17 | targetY, 18 | }); 19 | 20 | return ( 21 | <> 22 | 23 | 24 |
{ 32 | // setEdges((es) => es.filter((e) => e.id !== id)); 33 | }} 34 | > 35 | 持续升级 36 |
37 |
38 | 39 | ); 40 | } 41 | 42 | export { 43 | CustomEdge, 44 | } -------------------------------------------------------------------------------- /docs/guide/demo/components/Nodes.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from 'react'; 2 | import { 3 | Handle, Position 4 | } from '@xyflow/react'; 5 | import { Button, Card } from 'antd'; 6 | import { GroupOutlined, GlobalOutlined, HddOutlined } from '@ant-design/icons'; 7 | import './index.less'; 8 | 9 | const handleStyle = { left: 10 }; 10 | 11 | function TextUpdaterNode({ data, isConnectable }) { 12 | const [val, setVal] = useState(data.value); 13 | const onChange = useCallback((evt) => { 14 | setVal(evt.target.value); 15 | }, []); 16 | 17 | return ( 18 |
19 | 24 |
25 | 26 | 27 | 28 |
29 | 36 | 42 |
43 | ); 44 | } 45 | 46 | function LogoNode({ data, isConnectable }) { 47 | const { src, text } = data; 48 | return ( 49 |
50 | 57 | 63 |
64 | 65 |
{ text }
66 |
67 |
68 | ); 69 | } 70 | 71 | const iconMap = { 72 | l1: , 73 | l2: , 74 | l3: 75 | } 76 | 77 | function BaseNode({ data, isConnectable }) { 78 | const { iconType, text, id, type, noHandle, link } = data; 79 | return ( 80 |
81 | { 82 | noHandle ? null : 83 | <> 84 | 91 | 98 | 104 | 105 | } 106 | 107 | 113 |
114 | ); 115 | } 116 | 117 | function ImgNode({ data, isConnectable }) { 118 | const { src, id } = data; 119 | return ( 120 |
121 | 128 | 135 | 文档} style={{ width: 500 }}> 136 | 137 | 138 |
139 | ); 140 | } 141 | 142 | 143 | export { 144 | TextUpdaterNode, 145 | LogoNode, 146 | BaseNode, 147 | ImgNode 148 | } -------------------------------------------------------------------------------- /docs/guide/demo/components/data.ts: -------------------------------------------------------------------------------- 1 | const initialNodes = [ 2 | { 3 | id: 'node-1', 4 | type: 'logo', 5 | position: { x: 0, y: 0 }, 6 | data: { 7 | src: 'https://fast.dooring.vip/static/user/7e200e46-4976-44e4-8af9-7082871b6934/dd072c2a6fa1563c631e2a0955eced61.png' , 8 | text: 'Dooring可视化搭建平台' 9 | }, 10 | }, 11 | { 12 | id: 'node-2', 13 | type: 'base', 14 | targetPosition: 'top', 15 | position: { x: 0, y: 200 }, 16 | data: { iconType: 'l1', text: '应用场景', id: 'node-2' }, 17 | }, 18 | { 19 | id: 'node-2-1', 20 | type: 'base', 21 | targetPosition: 'bottom', 22 | position: { x: 100, y: 300 }, 23 | data: { iconType: 'l3', text: '营销搭建中台', id: 'node-2-1' }, 24 | }, 25 | { 26 | id: 'node-2-2', 27 | type: 'base', 28 | targetPosition: 'top', 29 | position: { x: -100, y: 260 }, 30 | data: { iconType: 'l3', text: '业务搭建平台', id: 'node-2-2' }, 31 | }, 32 | { 33 | id: 'node-2-3', 34 | type: 'base', 35 | targetPosition: 'top', 36 | position: { x: 30, y: 360 }, 37 | data: { iconType: 'l3', text: '品宣设计平台', id: 'node-2-3' }, 38 | }, 39 | { 40 | id: 'node-2-4', 41 | type: 'base', 42 | targetPosition: 'top', 43 | position: { x: -160, y: 360 }, 44 | data: { iconType: 'l3', text: '电商&活动搭建平台', id: 'node-2-4' }, 45 | }, 46 | { 47 | id: 'node-3', 48 | type: 'base', 49 | targetPosition: 'top', 50 | position: { x: 200, y: 200 }, 51 | data: { iconType: 'l2', text: '核心模块', id: 'node-3' }, 52 | }, 53 | { 54 | id: 'core', 55 | type: 'output', 56 | targetPosition: 'top', 57 | data: { label: null }, 58 | position: { x: 130, y: 420 }, 59 | style: { 60 | width: 380, 61 | height: 150, 62 | }, 63 | }, 64 | { 65 | id: 'jump', 66 | type: 'base', 67 | targetPosition: 'top', 68 | data: { text: '立即体验', id: 'jump', link: 'https://dooring.vip', type: 'primary' }, 69 | position: { x: -120, y: 520 }, 70 | }, 71 | { 72 | id: 'core-1', 73 | type: 'base', 74 | position: { x: 20, y: 20 }, 75 | parentId: 'core', 76 | extent: 'parent', 77 | data: { type: 'primary', text: '搭建引擎', id: 'core-1', noHandle: true }, 78 | }, 79 | { 80 | id: 'core-2', 81 | type: 'base', 82 | position: { x: 130, y: 20 }, 83 | parentId: 'core', 84 | extent: 'parent', 85 | data: { type: 'primary', text: '规则引擎', id: 'core-1', noHandle: true }, 86 | }, 87 | { 88 | id: 'core-3', 89 | type: 'base', 90 | position: { x: 240, y: 20 }, 91 | parentId: 'core', 92 | extent: 'parent', 93 | data: { type: 'primary', text: '流程引擎', id: 'core-1', noHandle: true }, 94 | }, 95 | { 96 | id: 'core-4', 97 | type: 'base', 98 | position: { x: 20, y: 80 }, 99 | parentId: 'core', 100 | extent: 'parent', 101 | data: { type: 'primary', text: '设计辅助', id: 'core-1', noHandle: true }, 102 | }, 103 | { 104 | id: 'core-5', 105 | type: 'base', 106 | position: { x: 130, y: 80 }, 107 | parentId: 'core', 108 | extent: 'parent', 109 | data: { type: 'primary', text: '组件商店', id: 'core-1', noHandle: true }, 110 | }, 111 | { 112 | id: 'core-6', 113 | type: 'base', 114 | position: { x: 240, y: 80 }, 115 | parentId: 'core', 116 | extent: 'parent', 117 | data: { type: 'primary', text: '数据源机制', id: 'core-1', noHandle: true }, 118 | }, 119 | { 120 | id: 'node-4', 121 | type: 'base', 122 | targetPosition: 'top', 123 | position: { x: 400, y: 200 }, 124 | data: { iconType: 'l3', text: '技术架构', id: 'node-4' }, 125 | }, 126 | { 127 | id: 'node-5', 128 | type: 'img', 129 | targetPosition: 'top', 130 | position: { x: 520, y: 300 }, 131 | data: { src: 'https://basic.dooring.cn/saas/user/7e200e46-4976-44e4-8af9-7082871b6934/FvK6aLnN_5S4GE2uGfqQjeoVz5Kt.png?imageView2/0/format/webp/q/75', id: 'node-5' }, 132 | }, 133 | ]; 134 | 135 | const initialEdges = [ 136 | { id: 'edge-1', source: 'node-1', target: 'node-2', sourceHandle: 'a', animated: true }, 137 | { id: 'edge-2', source: 'node-1', target: 'node-3', type: 'custom-edge', sourceHandle: 'b', animated: true }, 138 | { id: 'edge-3-1', source: 'node-3', target: 'core', type: 'custom-edge', animated: true }, 139 | { id: 'jump', source: 'node-2-4', target: 'jump', type: 'custom-edge' }, 140 | { id: 'edge-2-1', source: 'node-2', target: 'node-2-1', sourceHandle: 'node-2-btm', animated: true }, 141 | { id: 'edge-2-2', source: 'node-2', target: 'node-2-2', sourceHandle: 'node-2-btm', animated: true }, 142 | { id: 'edge-2-3', source: 'node-2', target: 'node-2-3', sourceHandle: 'node-2-btm', animated: true }, 143 | { id: 'edge-2-4', source: 'node-2', target: 'node-2-4', sourceHandle: 'node-2-btm', animated: true }, 144 | { id: 'edge-3', source: 'node-1', target: 'node-4', sourceHandle: 'b', animated: true }, 145 | { id: 'edge-4', source: 'node-4', target: 'node-5', sourceHandle: 'node-4-btm' }, 146 | ]; 147 | 148 | export { 149 | initialNodes, 150 | initialEdges 151 | } -------------------------------------------------------------------------------- /docs/guide/demo/components/index.less: -------------------------------------------------------------------------------- 1 | .text-updater-node { 2 | height: 50px; 3 | border: 1px solid #eee; 4 | padding: 5px; 5 | border-radius: 5px; 6 | background: white; 7 | } 8 | 9 | .text-updater-node label { 10 | display: block; 11 | color: #777; 12 | font-size: 12px; 13 | } 14 | 15 | .flow-logo { 16 | 17 | > div { 18 | display: flex; 19 | align-items: center; 20 | img { 21 | width: 42px; 22 | } 23 | .flow-logo-text { 24 | margin-left: 10px; 25 | font-size: 24px; 26 | } 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /docs/guide/demo/custom.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from 'react'; 2 | import { 3 | ReactFlow, 4 | addEdge, 5 | applyEdgeChanges, 6 | applyNodeChanges, 7 | Handle, Position 8 | } from '@xyflow/react'; 9 | import './index.less'; 10 | 11 | const handleStyle = { left: 10 }; 12 | 13 | function TextUpdaterNode({ data, isConnectable }) { 14 | const [val, setVal] = useState(data.value); 15 | const onChange = useCallback((evt) => { 16 | console.log(evt.target.value); 17 | setVal(evt.target.value); 18 | }, []); 19 | 20 | return ( 21 |
22 | 27 |
28 | 29 | 30 |
31 | 38 | 44 |
45 | ); 46 | } 47 | 48 | const rfStyle = { 49 | backgroundColor: '#B8CEFF', 50 | }; 51 | 52 | const initialNodes = [ 53 | { 54 | id: 'node-1', 55 | type: 'textUpdater', 56 | position: { x: 0, y: 0 }, 57 | data: { value: 'h5-dooring' }, 58 | }, 59 | ]; 60 | // we define the nodeTypes outside of the component to prevent re-renderings 61 | // you could also use useMemo inside the component 62 | const nodeTypes = { textUpdater: TextUpdaterNode }; 63 | 64 | function Flow() { 65 | const [nodes, setNodes] = useState(initialNodes); 66 | const [edges, setEdges] = useState([]); 67 | 68 | const onNodesChange = useCallback( 69 | (changes) => setNodes((nds) => applyNodeChanges(changes, nds)), 70 | [setNodes], 71 | ); 72 | const onEdgesChange = useCallback( 73 | (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)), 74 | [setEdges], 75 | ); 76 | const onConnect = useCallback( 77 | (connection) => setEdges((eds) => addEdge(connection, eds)), 78 | [setEdges], 79 | ); 80 | 81 | return ( 82 |
83 | 93 |
94 | ); 95 | } 96 | 97 | export default Flow; -------------------------------------------------------------------------------- /docs/guide/demo/dooring.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from 'react'; 2 | import { 3 | ReactFlow, 4 | addEdge, 5 | applyEdgeChanges, 6 | applyNodeChanges, 7 | SelectionMode, 8 | MiniMap, 9 | Controls, 10 | Background, 11 | Panel 12 | } from '@xyflow/react'; 13 | import { Button } from 'antd'; 14 | import { TextUpdaterNode, LogoNode, BaseNode, ImgNode } from './components/Nodes'; 15 | import { CustomEdge } from './components/Edges'; 16 | import { initialEdges, initialNodes } from './components/data'; 17 | 18 | const handleStyle = { left: 10 }; 19 | const panOnDrag = [1, 2]; 20 | 21 | const rfStyle = { 22 | backgroundColor: '#f7f9fb', 23 | }; 24 | 25 | const nodeTypes = { 26 | textUpdater: TextUpdaterNode, 27 | logo: LogoNode, 28 | base: BaseNode, 29 | img: ImgNode 30 | }; 31 | 32 | const edgeTypes = { 33 | 'custom-edge': CustomEdge, 34 | }; 35 | 36 | const nodeColor = (node) => { 37 | switch (node.type) { 38 | case 'base': 39 | return '#6ede87'; 40 | case 'img': 41 | return '#1890ff'; 42 | default: 43 | return '#ff0072'; 44 | } 45 | }; 46 | 47 | function Flow() { 48 | const [nodes, setNodes] = useState(initialNodes); 49 | const [edges, setEdges] = useState(initialEdges); 50 | 51 | const onNodesChange = useCallback( 52 | (changes) => setNodes((nds) => applyNodeChanges(changes, nds)), 53 | [setNodes], 54 | ); 55 | const onEdgesChange = useCallback( 56 | (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)), 57 | [setEdges], 58 | ); 59 | const onConnect = useCallback( 60 | (connection) => setEdges((eds) => addEdge(connection, eds)), 61 | [setEdges], 62 | ); 63 | 64 | return ( 65 |
66 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 |
89 | ); 90 | } 91 | 92 | export default Flow; -------------------------------------------------------------------------------- /docs/guide/demo/index.less: -------------------------------------------------------------------------------- 1 | .react-flow__node-output { 2 | border: 1px solid #eee; 3 | box-shadow: none !important; 4 | background-color: #fff; 5 | border-radius: 6px; 6 | } -------------------------------------------------------------------------------- /docs/guide/demo/layout.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react'; 2 | import { 3 | ReactFlow, 4 | ReactFlowProvider, 5 | useNodesState, 6 | useEdgesState, 7 | useReactFlow, 8 | } from '@xyflow/react'; 9 | 10 | const initialNodes = [ 11 | { 12 | id: '1', 13 | type: 'input', 14 | data: { label: 'input' }, 15 | position: { x: 0, y: 0 }, 16 | }, 17 | { 18 | id: '2', 19 | data: { label: 'node 2' }, 20 | position: { x: 0, y: 100 }, 21 | }, 22 | { 23 | id: '2a', 24 | data: { label: 'node 2a' }, 25 | position: { x: 0, y: 200 }, 26 | }, 27 | { 28 | id: '2b', 29 | data: { label: 'node 2b' }, 30 | position: { x: 0, y: 300 }, 31 | }, 32 | { 33 | id: '2c', 34 | data: { label: 'node 2c' }, 35 | position: { x: 0, y: 400 }, 36 | }, 37 | { 38 | id: '2d', 39 | data: { label: 'node 2d' }, 40 | position: { x: 0, y: 500 }, 41 | }, 42 | { 43 | id: '3', 44 | data: { label: 'node 3' }, 45 | position: { x: 200, y: 100 }, 46 | }, 47 | ]; 48 | 49 | const initialEdges = [ 50 | { id: 'e12', source: '1', target: '2', animated: true }, 51 | { id: 'e13', source: '1', target: '3', animated: true }, 52 | { id: 'e22a', source: '2', target: '2a', animated: true }, 53 | { id: 'e22b', source: '2', target: '2b', animated: true }, 54 | { id: 'e22c', source: '2', target: '2c', animated: true }, 55 | { id: 'e2c2d', source: '2c', target: '2d', animated: true }, 56 | ]; 57 | 58 | 59 | const getLayoutedElements = (nodes, edges) => { 60 | return { nodes, edges }; 61 | }; 62 | 63 | const LayoutFlow = () => { 64 | const { fitView } = useReactFlow(); 65 | const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); 66 | const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); 67 | 68 | const onLayout = useCallback(() => { 69 | const layouted = getLayoutedElements(nodes, edges); 70 | 71 | setNodes([...layouted.nodes]); 72 | setEdges([...layouted.edges]); 73 | 74 | window.requestAnimationFrame(() => { 75 | fitView(); 76 | }); 77 | }, [nodes, edges]); 78 | 79 | return ( 80 |
81 | 88 |
89 | 90 | ); 91 | }; 92 | 93 | export default function () { 94 | return ( 95 | 96 | 97 | 98 | ); 99 | } -------------------------------------------------------------------------------- /docs/guide/demo/multihandle.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from 'react'; 2 | import { 3 | ReactFlow, 4 | addEdge, 5 | applyEdgeChanges, 6 | applyNodeChanges, 7 | Handle, Position 8 | } from '@xyflow/react'; 9 | import './index.less'; 10 | 11 | const handleStyle = { left: 10 }; 12 | 13 | function TextUpdaterNode({ data, isConnectable }) { 14 | const [val, setVal] = useState(data.value); 15 | const onChange = useCallback((evt) => { 16 | console.log(evt.target.value); 17 | setVal(evt.target.value); 18 | }, []); 19 | 20 | return ( 21 |
22 | 27 |
28 | 29 | 30 |
31 | 38 | 44 |
45 | ); 46 | } 47 | 48 | const rfStyle = { 49 | backgroundColor: '#B8CEFF', 50 | }; 51 | 52 | const initialNodes = [ 53 | { 54 | id: 'node-1', 55 | type: 'textUpdater', 56 | position: { x: 0, y: 0 }, 57 | data: { value: 123 }, 58 | }, 59 | { 60 | id: 'node-2', 61 | type: 'output', 62 | targetPosition: 'top', 63 | position: { x: 0, y: 200 }, 64 | data: { label: 'node 2' }, 65 | }, 66 | { 67 | id: 'node-3', 68 | type: 'output', 69 | targetPosition: 'top', 70 | position: { x: 200, y: 200 }, 71 | data: { label: 'node 3' }, 72 | }, 73 | ]; 74 | 75 | const initialEdges = [ 76 | { id: 'edge-1', source: 'node-1', target: 'node-2', sourceHandle: 'a' }, 77 | { id: 'edge-2', source: 'node-1', target: 'node-3', sourceHandle: 'b' }, 78 | ]; 79 | 80 | const nodeTypes = { textUpdater: TextUpdaterNode }; 81 | 82 | function Flow() { 83 | const [nodes, setNodes] = useState(initialNodes); 84 | const [edges, setEdges] = useState(initialEdges); 85 | 86 | const onNodesChange = useCallback( 87 | (changes) => setNodes((nds) => applyNodeChanges(changes, nds)), 88 | [setNodes], 89 | ); 90 | const onEdgesChange = useCallback( 91 | (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)), 92 | [setEdges], 93 | ); 94 | const onConnect = useCallback( 95 | (connection) => setEdges((eds) => addEdge(connection, eds)), 96 | [setEdges], 97 | ); 98 | 99 | return ( 100 |
101 | 111 |
112 | ); 113 | } 114 | 115 | export default Flow; -------------------------------------------------------------------------------- /docs/guide/edge.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 指南 3 | title: 自定义边 4 | order: 3 5 | group: 6 | title: 自定义React-Flow 7 | order: 2 8 | --- 9 | 10 | ## 自定义边 11 | 12 | 与自定义节点一样,`React Flow` 中的自定义边的一部分只是 `React` 组件:这意味着可以沿着边渲染任何想要的东西!本指南将展示如何使用一些附加控件来实现自定义边缘。 13 | 14 | ### 一个简单的自定义边案例 15 | 16 | 如果边不能渲染两个连接节点之间的路径,那么它对我们来说就没多大用处。这些路径始终基于 `SVG`,并且通常使用 17 | 组件进行渲染。为了计算要渲染的实际 `SVG` 路径,`React Flow` 附带了一些方便的实用函数: 18 | 19 | - getBezierPath 20 | - getSimpleBezierPath 21 | - getSmoothStepPath 22 | - getStraightPath 23 | 24 | 为了启动我们的自定义边缘,我们只需在源和目标之间渲染一条直线路径。 25 | 26 | ```tsx | pure 27 | import { BaseEdge, getStraightPath } from '@xyflow/react'; 28 | 29 | export default function CustomEdge({ id, sourceX, sourceY, targetX, targetY }) { 30 | const [edgePath] = getStraightPath({ 31 | sourceX, 32 | sourceY, 33 | targetX, 34 | targetY, 35 | }); 36 | 37 | return ( 38 | <> 39 | 40 | 41 | ); 42 | } 43 | ``` 44 | 45 | :::info{title=提示} 46 | 传递给自定义边缘组件的所有 props 都可以在 EdgeProps 类型下的 API 参考中找到。 47 | ::: 48 | 49 | 这为我们提供了一条直边,其行为与默认的 “`straight`(直)” 边类型相同。要使用它,我们还需要更新 50 | 组件上的 `edgeTypes` 属性。 51 | 52 | 在组件外部定义 `EdgeTypes` 对象或使用 `React` 的 `useMemo` 钩子来防止不必要的重新渲染非常重要。如果忘记执行此操作,`React Flow` 将在控制台中显示警告。 53 | 54 | ```tsx | pure 55 | import ReactFlow from '@xyflow/react' 56 | import CustomEdge from './CustomEdge' 57 | 58 | 59 | const edgeTypes = { 60 | 'custom-edge': CustomEdge 61 | } 62 | 63 | export function Flow() { 64 | return 65 | } 66 | ``` 67 | 68 | 定义 `edgeTypes` 对象后,我们可以通过将边的类型字段设置为 “`custom-edge`” 来使用新的自定义边。 69 | 70 | ```tsx 71 | import { useCallback } from 'react'; 72 | import { 73 | ReactFlow, 74 | addEdge, 75 | useNodesState, 76 | useEdgesState, 77 | BaseEdge, getStraightPath 78 | } from '@xyflow/react'; 79 | 80 | import '@xyflow/react/dist/style.css'; 81 | 82 | function CustomEdge({ id, sourceX, sourceY, targetX, targetY }) { 83 | const [edgePath] = getStraightPath({ 84 | sourceX, 85 | sourceY, 86 | targetX, 87 | targetY, 88 | }); 89 | 90 | return ( 91 | <> 92 | 93 | 94 | ); 95 | } 96 | 97 | const initialNodes = [ 98 | { id: 'a', position: { x: 0, y: 0 }, data: { label: 'Node A' } }, 99 | { id: 'b', position: { x: 0, y: 100 }, data: { label: 'Node B' } }, 100 | ]; 101 | 102 | const initialEdges = [ 103 | { id: 'a->b', type: 'custom-edge', source: 'a', target: 'b' }, 104 | ]; 105 | 106 | const edgeTypes = { 107 | 'custom-edge': CustomEdge, 108 | }; 109 | 110 | function Flow() { 111 | const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); 112 | const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); 113 | const onConnect = useCallback( 114 | (connection) => { 115 | const edge = { ...connection, type: 'custom-edge' }; 116 | setEdges((eds) => addEdge(edge, eds)); 117 | }, 118 | [setEdges], 119 | ); 120 | 121 | return ( 122 |
123 | 132 |
133 | ); 134 | } 135 | 136 | export default Flow; 137 | ``` 138 | 139 | ### 添加边标签 140 | 141 | 自定义边最常见的用途之一是沿着边缘的路径呈现一些控件或信息。在 `React Flow` 中,我们称之为边标签,与边路径不同,边标签可以是任何 `React` 组件! 142 | 143 | 要渲染自定义边标签,我们必须将其包装在 `` 组件中。出于性能原因,这是必要的:边标签渲染器是所有边标签都渲染到其中的单个容器的部分。 144 | 145 | 让我们向自定义边添加一个按钮,可用于删除其附加的边: 146 | 147 | ```tsx | pure 148 | import { 149 | BaseEdge, 150 | EdgeLabelRenderer, 151 | getStraightPath, 152 | useReactFlow, 153 | } from '@xyflow/react'; 154 | 155 | export default function CustomEdge({ id, sourceX, sourceY, targetX, targetY }) { 156 | const { setEdges } = useReactFlow(); 157 | const [edgePath] = getStraightPath({ 158 | sourceX, 159 | sourceY, 160 | targetX, 161 | targetY, 162 | }); 163 | 164 | return ( 165 | <> 166 | 167 | 168 | 173 | 174 | 175 | ); 176 | } 177 | ``` 178 | 179 | 如果我们现在尝试使用此边,我们将看到该按钮呈现在流程的中心(它可能隐藏在“节点 A”后面)。由于边标签的存在,我们需要做一些额外的工作来自己定位按钮。 180 | 181 | ![](/edge.webp) 182 | 183 | 幸运的是,我们的路径`API`可以帮助我们做到这一点!除了要渲染的 `SVG` 路径之外,这些函数还返回路径中点的 `x` 和 `y` 坐标。然后,我们可以使用这些坐标将自定义边缘标签转换到正确的位置! 184 | 185 | ```tsx | pure 186 | export default function CustomEdge({ id, sourceX, sourceY, targetX, targetY }) { 187 | const { setEdges } = useReactFlow(); 188 | const [edgePath, labelX, labelY] = getStraightPath({ ... }); 189 | 190 | return ( 191 | ... 192 | 253 | 254 | 255 | ); 256 | } 257 | 258 | 259 | const initialNodes = [ 260 | { id: 'a', position: { x: 0, y: 0 }, data: { label: 'Node A' } }, 261 | { id: 'b', position: { x: 0, y: 100 }, data: { label: 'Node B' } }, 262 | { id: 'c', position: { x: 0, y: 200 }, data: { label: 'Node C' } }, 263 | ]; 264 | 265 | const initialEdges = [ 266 | { id: 'a->b', type: 'custom-edge', source: 'a', target: 'b' }, 267 | { id: 'b->c', type: 'custom-edge', source: 'b', target: 'c' }, 268 | ]; 269 | 270 | const edgeTypes = { 271 | 'custom-edge': CustomEdge, 272 | }; 273 | 274 | function Flow() { 275 | const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); 276 | const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); 277 | const onConnect = useCallback( 278 | (connection) => { 279 | const edge = { ...connection, type: 'custom-edge' }; 280 | setEdges((eds) => addEdge(edge, eds)); 281 | }, 282 | [setEdges], 283 | ); 284 | 285 | return ( 286 |
287 | 296 |
297 | ); 298 | } 299 | 300 | export default Flow; 301 | ``` 302 | 303 | 304 | -------------------------------------------------------------------------------- /docs/guide/edgePro.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 指南 3 | title: 自定义边属性 4 | order: 4 5 | group: 6 | title: 自定义React-Flow 7 | order: 2 8 | --- 9 | 10 | ## 边的属性 11 | 12 | 当我们实现自定义边时,它被包装在启用一些基本功能的组件中。自定义边组件接收以下属性: 13 | 14 | ```tsx | pure 15 | export type EdgeProps = { 16 | id: string; 17 | animated: boolean; 18 | data: EdgeType['data']; 19 | style: React.CSSProperties; 20 | selected: boolean; 21 | source: string; 22 | target: string; 23 | sourceHandleId?: string | null; 24 | targetHandleId?: string | null; 25 | interactionWidth: number; 26 | sourceX: number; 27 | sourceY: number; 28 | targetX: number; 29 | targetY: number; 30 | sourcePosition: Position; 31 | targetPosition: Position; 32 | label?: string | React.ReactNode; 33 | labelStyle?: React.CSSProperties; 34 | labelShowBg?: boolean; 35 | labelBgStyle?: CSSProperties; 36 | labelBgPadding?: [number, number]; 37 | labelBgBorderRadius?: number; 38 | markerStart?: string; 39 | markerEnd?: string; 40 | pathOptions?: any; 41 | }; 42 | ``` 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /docs/guide/flow.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 指南 3 | title: 流程编排 4 | order: 2 5 | group: 6 | title: 应用案例 7 | order: 4 8 | --- 9 | 10 | ## 正在筹备中...... 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/guide/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 指南 3 | title: 快速开始 4 | order: -1 5 | group: 6 | title: 介绍 7 | order: -1 8 | --- 9 | 10 | # 快速开始 11 | 12 |
13 | 14 | ## 基于 Vite 模版快速创建 15 | 16 | ```bash 17 | npx degit xyflow/vite-react-flow-template app-name 18 | ``` 19 | 20 | 如果你已经有完成的React环境, 可以直接安装依赖: 21 | 22 | ```bash 23 | pnpm add @xyflow/react 24 | ``` 25 | 26 | ## 第一个 Flow 案例 27 | 28 | `reactflow` 包将 `` 组件作为默认导出。 接下来我们看一个简单的案例: 29 | 30 | ```tsx 31 | import React from 'react'; 32 | import { ReactFlow } from '@xyflow/react'; 33 | 34 | import '@xyflow/react/dist/style.css'; 35 | 36 | const initialNodes = [ 37 | { id: '1', position: { x: 0, y: 0 }, data: { label: 'Dooring' } }, 38 | { id: '2', position: { x: 0, y: 100 }, data: { label: 'Flow' } }, 39 | ]; 40 | const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }]; 41 | 42 | export default function App() { 43 | return ( 44 |
45 | 46 |
47 | ); 48 | } 49 | ``` 50 | 51 | 有几个值得注意的点: 52 | 53 | - 我们必须导入react-flow的样式文件 54 | - `` 组件必须被包裹在一个指定宽高的容器里 55 | 56 | ## 添加节点交互 57 | 58 | 使用 `React Flow` 创建的图形是完全可交互的。我们可以移动节点,将它们连接在一起,删除它们,……要获得我们需要的基本功能,需要做三件事: 59 | 60 | - 在节点发生变化时的回调 61 | - 在边发生变化时的回调 62 | - 在节点被连接时的回调 63 | 64 | 幸运的是,我们提供了一些钩子来使这变得容易! 65 | 66 | ```tsx 67 | import React, { useCallback } from 'react'; 68 | import { 69 | ReactFlow, 70 | useNodesState, 71 | useEdgesState, 72 | addEdge, 73 | } from '@xyflow/react'; 74 | 75 | const initialNodes = [ 76 | { id: '1', position: { x: 0, y: 0 }, data: { label: 'Dooring' } }, 77 | { id: '2', position: { x: 0, y: 100 }, data: { label: 'Flow' } }, 78 | ]; 79 | const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }]; 80 | 81 | export default function App() { 82 | const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); 83 | const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); 84 | 85 | const onConnect = useCallback( 86 | (params) => setEdges((eds) => addEdge(params, eds)), 87 | [setEdges], 88 | ); 89 | 90 | return ( 91 |
92 | 100 |
101 | ); 102 | } 103 | ``` 104 | 105 | ## 额外的配置 106 | 107 | 最后,`React Flow` 自带了一些插件,用于实现诸如`迷你地图`或`视口控件`等功能. 108 | 109 | ```tsx 110 | import React, { useCallback } from 'react'; 111 | import { 112 | ReactFlow, 113 | MiniMap, 114 | Controls, 115 | Background, 116 | useNodesState, 117 | useEdgesState, 118 | addEdge, 119 | } from '@xyflow/react'; 120 | 121 | const initialNodes = [ 122 | { id: '1', position: { x: 0, y: 0 }, data: { label: 'Dooring' } }, 123 | { id: '2', position: { x: 0, y: 100 }, data: { label: 'Flow' } }, 124 | { id: '3', position: { x: 200, y: 100 }, data: { label: 'Nocode/WEP' } }, 125 | ]; 126 | const initialEdges = [ 127 | { id: 'e1-2', source: '1', target: '2' }, 128 | { id: 'e1-3', source: '1', target: '3' } 129 | ]; 130 | 131 | export default function App() { 132 | const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); 133 | const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); 134 | 135 | const onConnect = useCallback( 136 | (params) => setEdges((eds) => addEdge(params, eds)), 137 | [setEdges], 138 | ); 139 | 140 | return ( 141 |
142 | 150 | 151 | 152 | 153 | 154 |
155 | ); 156 | } 157 | ``` 158 | 159 | 到此, 我们就创建好了我们第一个可交互的流程图, 接下来我们继续介绍 `react-flow` 的原理和更多高级的技巧. 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /docs/guide/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 指南 3 | title: 基本介绍 4 | order: -1 5 | group: 6 | title: 概念&原理 7 | order: 1 8 | --- 9 | 10 | ## 基本介绍 11 | 12 | `React Flow` 是一个用于构建基于节点的应用程序的库。这些应用程序可以是从简单的静态图表到数据可视化再到复杂的可视化编辑器的任何场景。你可以实现自定义节点类型和边,并且它自带了一些组件,如迷你地图和视口控件。 13 | 如果你想快速入门,可以查看[快速入门](/guide)指南,否则,让我们来看看 `React Flow` 的关键特性。 14 | 15 | ### 亮点 16 | 17 | - 👌 易于使用:`React Flow` 已经自带了许多你想要的开箱即用的功能。拖拽节点、缩放和平移、选择多个节点和边,以及添加/删除边等都已内置。 18 | 19 | - 🎨 可定制:`React Flow` 支持自定义节点类型和边类型。因为自定义节点只是 `React` 组件,所以你可以实现任何你想要的需求:你不会被限制在内置节点类型上。自定义边允许你在节点边上添加标签、控件和定制逻辑。 20 | 21 | - ⚡️ 快速渲染:`React Flow` 只渲染已改变的节点,并确保只有在视口中的节点才会被完全显示出来。 22 | 23 | - 🧩 内置插件:我们在 React Flow 中自带了一些插件: 24 | 25 | - `` 插件实现了一些基本的可定制背景图案。 26 | - `` 插件在屏幕的角落显示图形的一个缩略图。 27 | - `` 插件添加了缩放、居中以及锁定视口的控制功能。 28 | - `` 插件让你可以轻松地在视口顶部放置内容。 29 | - `` 插件允许你渲染附加在节点上的工具栏。 30 | - ` ` 插件让你可以轻松地为你的节点添加缩放功能。 31 | 32 | 33 | - 💪 可靠:`React Flow` 完全是用 `TypeScript` 编写的,以便及早发现错误并使修复容易。对于其他方面,我们有一个强大的压测支持,这样你就可以更自信地使用 `React Flow` 了。 34 | 35 | ## 继续探索 36 | 37 | 现在我们对 `React Flow` 有了更好的了解,下一页将介绍一些在文档中会反复出现的常见术语:`nodes`, `edges`, 和 `handles`。 38 | -------------------------------------------------------------------------------- /docs/guide/layout.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 指南 3 | title: 布局框架 4 | order: 1 5 | group: 6 | title: 布局 7 | order: 3 8 | --- 9 | 10 | ## 布局 11 | 12 | 我们经常被问到如何处理 `React Flow` 中的布局。虽然我们可以在 `React Flow` 中构建一些基本的布局,但我们相信您最了解您的应用程序的需求,并且有如此多的选项,我们认为您最好选择最适合您工作的工具(更不用说它是一个整体)为我们做了大量的工作)。 13 | 14 | 如果不知道这些选项是什么,不用担心,本指南可以为您提供帮助!我们将把资源分成用于布局节点的资源和用于路由边缘的资源。 15 | 16 | 首先,让我们整理一个简单的示例流程,我们可以将其用作测试不同布局选项的基础。 17 | 18 | 19 | 20 | 下面的每个示例都将构建在这个空流程上。我们尽可能将示例限制在一个 `index.js` 文件中,以便可以轻松比较它们的设置方式。 21 | 22 | ### 布局节点 23 | 24 | 对于布局节点,有一些我们认为值得检查的第三方库: 25 | 26 | ![](/library.png) 27 | 28 | `Dagre` 目前有一个未解决的问题,如果子流中的任何节点连接到子流外部的节点,则该问题将阻止其正确布局子流。 29 | 30 | 我们对这些选项从最简单到最复杂进行了松散的排序,其中 `dagre` 很大程度上是一个嵌入式解决方案,而 `elkjs` 是一个成熟的高度可配置的布局引擎。 31 | 32 | 下面,我们将看一个简短的示例,了解如何将每个库与 `React Flow` 一起使用。特别是对于 `dagre` 和 `elkjs`,我们有一些单独的示例,您可以参考此处和此处。 33 | 34 | #### Dagre 35 | 36 | `Dagre` 是一个用于布局有向图的简单库。它具有最少的配置选项,并且注重速度而不是选择最佳布局。如果您需要将流程组织成树,我们强烈推荐 `dagre`。 37 | 38 | ```tsx | pure 39 | import Dagre from '@dagrejs/dagre'; 40 | import React, { useCallback } from 'react'; 41 | import { 42 | ReactFlow, 43 | ReactFlowProvider, 44 | Panel, 45 | useNodesState, 46 | useEdgesState, 47 | useReactFlow, 48 | } from '@xyflow/react'; 49 | 50 | import { initialNodes, initialEdges } from './nodes-edges.js'; 51 | import '@xyflow/react/dist/style.css'; 52 | 53 | const getLayoutedElements = (nodes, edges, options) => { 54 | const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({})); 55 | g.setGraph({ rankdir: options.direction }); 56 | 57 | edges.forEach((edge) => g.setEdge(edge.source, edge.target)); 58 | nodes.forEach((node) => 59 | g.setNode(node.id, { 60 | ...node, 61 | width: node.measured?.width ?? 0, 62 | height: node.measured?.height ?? 0, 63 | }), 64 | ); 65 | 66 | Dagre.layout(g); 67 | 68 | return { 69 | nodes: nodes.map((node) => { 70 | const position = g.node(node.id); 71 | // We are shifting the dagre node position (anchor=center center) to the top left 72 | // so it matches the React Flow node anchor point (top left). 73 | const x = position.x - (node.measured?.width ?? 0) / 2; 74 | const y = position.y - (node.measured?.height ?? 0) / 2; 75 | 76 | return { ...node, position: { x, y } }; 77 | }), 78 | edges, 79 | }; 80 | }; 81 | 82 | const LayoutFlow = () => { 83 | const { fitView } = useReactFlow(); 84 | const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); 85 | const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); 86 | 87 | const onLayout = useCallback( 88 | (direction) => { 89 | console.log(nodes); 90 | const layouted = getLayoutedElements(nodes, edges, { direction }); 91 | 92 | setNodes([...layouted.nodes]); 93 | setEdges([...layouted.edges]); 94 | 95 | window.requestAnimationFrame(() => { 96 | fitView(); 97 | }); 98 | }, 99 | [nodes, edges], 100 | ); 101 | 102 | return ( 103 | 110 | 111 | 112 | 113 | 114 | 115 | ); 116 | }; 117 | 118 | export default function () { 119 | return ( 120 | 121 | 122 | 123 | ); 124 | } 125 | ``` 126 | 127 | 不费吹灰之力,我们就得到了组织良好的树形布局!每当调用 `getLayoutedElements` 时,我们都会重置 `dagre` 图并根据 `Direction` 属性设置图的方向(从左到右或从上到下)。 `Dagre` 需要知道每个节点的尺寸才能对它们进行布局,因此我们迭代节点列表并将它们添加到 `Dagre` 的内部图中。 128 | 129 | 布局图形后,我们将返回一个带有布局节点和边的对象。我们通过映射原始节点列表并根据 `dagre` 图中存储的节点更新每个节点的位置来做到这一点。 130 | 131 | #### D3-Hierarchy 132 | 133 | 当您知道您的图形是具有单个根节点的树时,`d3-hierarchy` 可以提供一些有趣的布局选项。虽然该库可以很好地布局简单的树,但它还具有用于树图、分区布局和外壳图的布局算法。 134 | 135 | 136 | 137 | ```tsx | pure 138 | import { stratify, tree } from 'd3-hierarchy'; 139 | import React, { useCallback, useMemo } from 'react'; 140 | import { 141 | ReactFlow, 142 | ReactFlowProvider, 143 | Panel, 144 | useNodesState, 145 | useEdgesState, 146 | useReactFlow, 147 | } from '@xyflow/react'; 148 | 149 | import { initialNodes, initialEdges } from './nodes-edges.js'; 150 | import '@xyflow/react/dist/style.css'; 151 | 152 | const g = tree(); 153 | 154 | const getLayoutedElements = (nodes, edges, options) => { 155 | if (nodes.length === 0) return { nodes, edges }; 156 | 157 | const { width, height } = document 158 | .querySelector(`[data-id="${nodes[0].id}"]`) 159 | .getBoundingClientRect(); 160 | const hierarchy = stratify() 161 | .id((node) => node.id) 162 | .parentId((node) => edges.find((edge) => edge.target === node.id)?.source); 163 | const root = hierarchy(nodes); 164 | const layout = g.nodeSize([width * 2, height * 2])(root); 165 | 166 | return { 167 | nodes: layout 168 | .descendants() 169 | .map((node) => ({ ...node.data, position: { x: node.x, y: node.y } })), 170 | edges, 171 | }; 172 | }; 173 | 174 | const LayoutFlow = () => { 175 | const { fitView } = useReactFlow(); 176 | const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); 177 | const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); 178 | 179 | const onLayout = useCallback( 180 | (direction) => { 181 | const { nodes: layoutedNodes, edges: layoutedEdges } = 182 | getLayoutedElements(nodes, edges, { 183 | direction, 184 | }); 185 | 186 | setNodes([...layoutedNodes]); 187 | setEdges([...layoutedEdges]); 188 | 189 | window.requestAnimationFrame(() => { 190 | fitView(); 191 | }); 192 | }, 193 | [nodes, edges], 194 | ); 195 | 196 | return ( 197 | 204 | 205 | 206 | 207 | 208 | ); 209 | }; 210 | 211 | export default function () { 212 | return ( 213 | 214 | 215 | 216 | ); 217 | } 218 | ``` 219 | 220 | :::warning 221 | D3 层次结构期望您的图形具有单个根节点,因此它并非在所有情况下都有效。同样重要的是要注意,`d3-hierarchy` 在计算布局时为所有节点分配相同的宽度和高度,因此如果您要显示许多不同的节点类型,它不是最佳选择。 222 | ::: 223 | 224 | #### D3-Force 225 | 226 | 强制一些比树更有趣的东西,强制导向的布局可能是正确的选择。 `D3-Force` 是一个基于物理的布局库,可以通过向节点施加不同的力来使用位置节点。 227 | 228 | 因此,与 `dagre` 和 `d3-hierarchy` 相比,它的配置和使用稍微复杂一些。重要的是,`d3-force` 的布局算法是迭代的,因此我们需要一种方法来跨多个渲染持续计算布局。 229 | 230 | 231 | 232 | ```tsx | pure 233 | import { 234 | forceSimulation, 235 | forceLink, 236 | forceManyBody, 237 | forceX, 238 | forceY, 239 | } from 'd3-force'; 240 | import React, { useMemo } from 'react'; 241 | import { 242 | ReactFlow, 243 | ReactFlowProvider, 244 | Panel, 245 | useNodesState, 246 | useEdgesState, 247 | useReactFlow, 248 | useNodesInitialized, 249 | } from '@xyflow/react'; 250 | 251 | import { initialNodes, initialEdges } from './nodes-edges.js'; 252 | import { collide } from './collide.js'; 253 | 254 | import '@xyflow/react/dist/style.css'; 255 | 256 | const simulation = forceSimulation() 257 | .force('charge', forceManyBody().strength(-1000)) 258 | .force('x', forceX().x(0).strength(0.05)) 259 | .force('y', forceY().y(0).strength(0.05)) 260 | .force('collide', collide()) 261 | .alphaTarget(0.05) 262 | .stop(); 263 | 264 | const useLayoutedElements = () => { 265 | const { getNodes, setNodes, getEdges, fitView } = useReactFlow(); 266 | const initialized = useNodesInitialized(); 267 | 268 | return useMemo(() => { 269 | let nodes = getNodes().map((node) => ({ 270 | ...node, 271 | x: node.position.x, 272 | y: node.position.y, 273 | })); 274 | let edges = getEdges().map((edge) => edge); 275 | let running = false; 276 | 277 | // If React Flow hasn't initialized our nodes with a width and height yet, or 278 | // if there are no nodes in the flow, then we can't run the simulation! 279 | if (!initialized || nodes.length === 0) return [false, {}]; 280 | 281 | simulation.nodes(nodes).force( 282 | 'link', 283 | forceLink(edges) 284 | .id((d) => d.id) 285 | .strength(0.05) 286 | .distance(100), 287 | ); 288 | 289 | // The tick function is called every animation frame while the simulation is 290 | // running and progresses the simulation one step forward each time. 291 | const tick = () => { 292 | getNodes().forEach((node, i) => { 293 | const dragging = Boolean( 294 | document.querySelector(`[data-id="${node.id}"].dragging`), 295 | ); 296 | 297 | // Setting the fx/fy properties of a node tells the simulation to "fix" 298 | // the node at that position and ignore any forces that would normally 299 | // cause it to move. 300 | nodes[i].fx = dragging ? node.position.x : null; 301 | nodes[i].fy = dragging ? node.position.y : null; 302 | }); 303 | 304 | simulation.tick(); 305 | setNodes( 306 | nodes.map((node) => ({ ...node, position: { x: node.x, y: node.y } })), 307 | ); 308 | 309 | window.requestAnimationFrame(() => { 310 | // Give React and React Flow a chance to update and render the new node 311 | // positions before we fit the viewport to the new layout. 312 | fitView(); 313 | 314 | // If the simulation hasn't be stopped, schedule another tick. 315 | if (running) tick(); 316 | }); 317 | }; 318 | 319 | const toggle = () => { 320 | running = !running; 321 | running && window.requestAnimationFrame(tick); 322 | }; 323 | 324 | const isRunning = () => running; 325 | 326 | return [true, { toggle, isRunning }]; 327 | }, [initialized]); 328 | }; 329 | 330 | const LayoutFlow = () => { 331 | const [nodes, , onNodesChange] = useNodesState(initialNodes); 332 | const [edges, , onEdgesChange] = useEdgesState(initialEdges); 333 | const [initialized, { toggle, isRunning }] = useLayoutedElements(); 334 | 335 | return ( 336 | 342 | 343 | {initialized && ( 344 | 347 | )} 348 | 349 | 350 | ); 351 | }; 352 | 353 | export default function () { 354 | return ( 355 | 356 | 357 | 358 | ); 359 | } 360 | ``` 361 | 362 | 我们已将 `getLayoutedElements` 更改为名为 `useLayoutedElements` 的挂钩。此外,我们将使用 `useReactFlow` 挂钩中的 `get getNodes` 和 `getEdges` 函数,而不是显式传递节点和边。当与初始化的存储选择器结合使用时,这一点很重要,因为它将阻止我们在节点更新时重新配置模拟。 363 | 364 | #### Elkjs 365 | 366 | `Elkjs` 无疑是最可配置的选择,但它也是最复杂的。 `Elkjs` 是一个已移植到 `JavaScript` 的 `Java` 库,它提供了大量用于配置图形布局的选项。 367 | 368 | 369 | 370 | ```tsx | pure 371 | import ELK from 'elkjs/lib/elk.bundled.js'; 372 | import React, { useCallback } from 'react'; 373 | import { 374 | ReactFlow, 375 | ReactFlowProvider, 376 | Panel, 377 | useNodesState, 378 | useEdgesState, 379 | useReactFlow, 380 | } from '@xyflow/react'; 381 | 382 | import { initialNodes, initialEdges } from './nodes-edges.js'; 383 | import '@xyflow/react/dist/style.css'; 384 | 385 | const elk = new ELK(); 386 | 387 | const useLayoutedElements = () => { 388 | const { getNodes, setNodes, getEdges, fitView } = useReactFlow(); 389 | const defaultOptions = { 390 | 'elk.algorithm': 'layered', 391 | 'elk.layered.spacing.nodeNodeBetweenLayers': 100, 392 | 'elk.spacing.nodeNode': 80, 393 | }; 394 | 395 | const getLayoutedElements = useCallback((options) => { 396 | const layoutOptions = { ...defaultOptions, ...options }; 397 | const graph = { 398 | id: 'root', 399 | layoutOptions: layoutOptions, 400 | children: getNodes(), 401 | edges: getEdges(), 402 | }; 403 | 404 | elk.layout(graph).then(({ children }) => { 405 | // By mutating the children in-place we saves ourselves from creating a 406 | // needless copy of the nodes array. 407 | children.forEach((node) => { 408 | node.position = { x: node.x, y: node.y }; 409 | }); 410 | 411 | setNodes(children); 412 | window.requestAnimationFrame(() => { 413 | fitView(); 414 | }); 415 | }); 416 | }, []); 417 | 418 | return { getLayoutedElements }; 419 | }; 420 | 421 | const LayoutFlow = () => { 422 | const [nodes, , onNodesChange] = useNodesState(initialNodes); 423 | const [edges, , onEdgesChange] = useEdgesState(initialEdges); 424 | const { getLayoutedElements } = useLayoutedElements(); 425 | 426 | return ( 427 | 434 | 435 | 445 | 455 | 464 | 473 | 474 | 475 | ); 476 | }; 477 | 478 | export default function () { 479 | return ( 480 | 481 | 482 | 483 | ); 484 | } 485 | ``` 486 | 487 | 488 | 489 | 490 | 491 | -------------------------------------------------------------------------------- /docs/guide/node.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 指南 3 | title: 自定义节点属性 4 | order: 2 5 | group: 6 | title: 自定义React-Flow 7 | order: 2 8 | --- 9 | 10 | ## 节点属性 11 | 12 | 13 | 当实现自定义节点时,它被包装在一个组件中,该组件支持选择和拖动等基本功能。自定义节点接收以下属性: 14 | 15 | ```tsx | pure 16 | export type NodeProps = { 17 | id: string; 18 | data: Node['data']; 19 | dragHandle?: boolean; 20 | type?: string; 21 | selected?: boolean; 22 | isConnectable?: boolean; 23 | zIndex?: number; 24 | positionAbsoluteX: number; 25 | positionAbsoluteY: number; 26 | dragging: boolean; 27 | targetPosition?: Position; 28 | sourcePosition?: Position; 29 | }; 30 | ``` 31 | 32 | ### 使用 33 | 34 | ```tsx | pure 35 | import { useState } from 'react'; 36 | import { NodeProps, Node } from '@xyflow/react'; 37 | 38 | export type CounterNode = Node< 39 | { 40 | initialCount?: number; 41 | }, 42 | 'counter' 43 | >; 44 | 45 | export default function CounterNode(props: NodeProps) { 46 | const [count, setCount] = useState(props.data?.initialCount ?? 0); 47 | 48 | return ( 49 |
50 |

计数: {count}

51 | 54 |
55 | ); 56 | } 57 | ``` 58 | 59 | 请记住通过将自定义节点添加到组件的 `nodeTypes` 属性来注册我们的自定义节点。 60 | 61 | ### 注意事项 62 | 63 | 如果自定义节点内有不应拖动节点的控件(如滑块)或其他元素,则可以将 `nodrag` 类添加到这些元素。这可以防止单击此类元素时的默认拖动行为以及默认节点选择行为。 64 | 65 | ```tsx | pure 66 | export default function CustomNode(props: NodeProps) { 67 | return ( 68 |
69 | 70 |
71 | ); 72 | } 73 | ``` 74 | 75 | 如果自定义节点内有滚动容器,则可以添加 `nowheel` 类以在自定义节点内滚动时禁用默认画布平移行为。 76 | 77 | ```tsx | pure 78 | export default function CustomNode(props: NodeProps) { 79 | return ( 80 |
81 |

可滚动的内容......

82 |
83 | ); 84 | } 85 | ``` 86 | 87 | 创建自己的自定义节点时,还需要记住为它们设置样式!与内置节点不同,自定义节点没有默认样式,因此可以使用我们熟悉的任何样式方法,例如 `tailwind`. 88 | 89 | -------------------------------------------------------------------------------- /docs/guide/plugin.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 指南 3 | title: 插件介绍 4 | order: 4 5 | group: 6 | title: 概念&原理 7 | order: 1 8 | --- 9 | 10 | ## 插件系统 11 | 12 | `React Flow` 附带了几个额外的插件组件。在本指南中,我们向您展示如何使用它们。我们在这里使用之前的示例代码。 13 | 14 | ### MiniMap(缩略图) 15 | 16 | 如果我们的流程图很大,可能希望快速获得概览。为此,我们构建了 `MiniMap` 组件。可以通过将其添加为子项来轻松将其添加到您的流程中: 17 | 18 | ```tsx 19 | import { ReactFlow, MiniMap } from '@xyflow/react'; 20 | 21 | const defaultNodes = [ 22 | { 23 | id: '1', 24 | type: 'input', 25 | data: { label: 'Dooring用户' }, 26 | position: { x: 250, y: 25 }, 27 | style: { backgroundColor: '#6ede87', color: 'white' }, 28 | }, 29 | 30 | { 31 | id: '2', 32 | // you can also pass a React component as a label 33 | data: { label:
Dooring零代码平台
}, 34 | position: { x: 100, y: 125 }, 35 | style: { backgroundColor: '#ff0072', color: 'white' }, 36 | }, 37 | { 38 | id: '3', 39 | type: 'output', 40 | data: { label: '发布页面' }, 41 | position: { x: 250, y: 250 }, 42 | style: { backgroundColor: '#6865A5', color: 'white' }, 43 | }, 44 | ]; 45 | const defaultEdges = [ 46 | { id: 'e1-2', source: '1', target: '2' }, 47 | { id: 'e2-3', source: '2', target: '3', animated: true }, 48 | ]; 49 | 50 | const nodeColor = (node) => { 51 | switch (node.type) { 52 | case 'input': 53 | return '#6ede87'; 54 | case 'output': 55 | return '#6865A5'; 56 | default: 57 | return '#ff0072'; 58 | } 59 | }; 60 | 61 | function Flow() { 62 | return ( 63 |
64 | 65 | 66 | 67 |
68 | 69 | ); 70 | } 71 | 72 | export default Flow; 73 | ``` 74 | 75 | ### 画布控件 76 | 77 | `React Flow` 带有一个可自定义的控件栏,我们可以通过导入 `Controls` 组件来使用它: 78 | 79 | ```tsx 80 | import { ReactFlow, Controls } from '@xyflow/react'; 81 | 82 | const defaultNodes = [ 83 | { 84 | id: '1', 85 | type: 'input', 86 | data: { label: 'Dooring用户' }, 87 | position: { x: 250, y: 25 }, 88 | style: { backgroundColor: '#6ede87', color: 'white' }, 89 | }, 90 | 91 | { 92 | id: '2', 93 | // you can also pass a React component as a label 94 | data: { label:
Dooring零代码平台
}, 95 | position: { x: 100, y: 125 }, 96 | style: { backgroundColor: '#ff0072', color: 'white' }, 97 | }, 98 | { 99 | id: '3', 100 | type: 'output', 101 | data: { label: '发布页面' }, 102 | position: { x: 250, y: 250 }, 103 | style: { backgroundColor: '#6865A5', color: 'white' }, 104 | }, 105 | ]; 106 | const defaultEdges = [ 107 | { id: 'e1-2', source: '1', target: '2' }, 108 | { id: 'e2-3', source: '2', target: '3', animated: true }, 109 | ]; 110 | 111 | function Flow() { 112 | return ( 113 |
114 | 115 | 116 | 117 |
118 | 119 | ); 120 | } 121 | 122 | export default Flow; 123 | ``` 124 | 125 | ### 背景 126 | 127 | 如果想显示图案背景,可以使用 `Background` 组件: 128 | 129 | ```tsx 130 | import { useState } from 'react'; 131 | import { ReactFlow, Background, Panel } from '@xyflow/react'; 132 | import '@xyflow/react/dist/style.css'; 133 | 134 | const defaultNodes = [ 135 | { 136 | id: '1', 137 | type: 'input', 138 | data: { label: 'Dooring用户' }, 139 | position: { x: 250, y: 25 }, 140 | style: { backgroundColor: '#6ede87', color: 'white' }, 141 | }, 142 | 143 | { 144 | id: '2', 145 | // you can also pass a React component as a label 146 | data: { label:
Dooring零代码平台
}, 147 | position: { x: 100, y: 125 }, 148 | style: { backgroundColor: '#ff0072', color: 'white' }, 149 | }, 150 | { 151 | id: '3', 152 | type: 'output', 153 | data: { label: '发布页面' }, 154 | position: { x: 250, y: 250 }, 155 | style: { backgroundColor: '#6865A5', color: 'white' }, 156 | }, 157 | ]; 158 | 159 | const defaultEdges = [ 160 | { id: 'e1-2', source: '1', target: '2' }, 161 | { id: 'e2-3', source: '2', target: '3', animated: true }, 162 | ]; 163 | 164 | function Flow() { 165 | const [variant, setVariant] = useState('cross'); 166 | 167 | return ( 168 |
169 | 170 | 171 | 172 |
修改背景:
173 | 174 | 175 | 176 |
177 |
178 |
179 | 180 | ); 181 | } 182 | 183 | export default Flow; 184 | ``` 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | -------------------------------------------------------------------------------- /docs/guide/subflow.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 指南 3 | title: 子流程 4 | order: 1 5 | group: 6 | title: 布局 7 | order: 3 8 | --- 9 | 10 | ## 子流程 11 | 12 | :::info{title=注意} 13 | 弃用parentNode 属性!在版本 11.11.0 中,我们已将parentNode 选项重命名为parentId。旧属性仍然受支持,但将在版本 12 中删除。 14 | ::: 15 | 16 | 子流是节点内的流。它可以是单独的流,也可以是与其父级之外的其他节点连接的流。此功能还可用于对节点进行分组。在文档的这一部分中,我们将构建一个包含子流的流,并向您展示子节点的特定选项。 17 | 18 | :::warning 19 | 节点顺序: 在 nodes/defaultNodes 数组中,父节点出现在其子节点之前是很重要的,这样才能正确处理。 20 | ::: 21 | 22 | ### 添加子节点 23 | 24 | 如果要将一个节点添加为另一个节点的子节点,则需要使用 `parentId`(在以前的版本中称为`parentNode`)选项(您可以在节点选项部分找到所有选项的列表)。一旦我们这样做了,子节点就会相对于其父节点定位。 `{ x: 0, y: 0 }` 的位置是父级的左上角。 25 | 26 | 在此示例中,我们通过传递 `style` 选项来设置父节点的固定宽度和高度。此外,我们将子范围设置为 “parent”,这样我们就无法将子节点移出父节点。 27 | 28 | ```tsx 29 | import { useCallback, useState } from 'react'; 30 | import { 31 | ReactFlow, 32 | addEdge, 33 | applyEdgeChanges, 34 | applyNodeChanges, 35 | Background, 36 | } from '@xyflow/react'; 37 | 38 | const initialNodes = [ 39 | { 40 | id: 'A', 41 | type: 'group', 42 | data: { label: null }, 43 | position: { x: 0, y: 0 }, 44 | style: { 45 | width: 170, 46 | height: 140, 47 | }, 48 | }, 49 | { 50 | id: 'B', 51 | type: 'input', 52 | data: { label: 'Dooring Node' }, 53 | position: { x: 10, y: 10 }, 54 | parentId: 'A', 55 | extent: 'parent', 56 | }, 57 | { 58 | id: 'C', 59 | data: { label: 'React Flow' }, 60 | position: { x: 10, y: 90 }, 61 | parentId: 'A', 62 | extent: 'parent', 63 | }, 64 | ]; 65 | 66 | const initialEdges = [ 67 | { id: 'b-c', source: 'B', target: 'C' } 68 | ]; 69 | 70 | const rfStyle = { 71 | backgroundColor: '#D0C0F7', 72 | }; 73 | 74 | function Flow() { 75 | const [nodes, setNodes] = useState(initialNodes); 76 | const [edges, setEdges] = useState(initialEdges); 77 | 78 | const onNodesChange = useCallback( 79 | (changes) => setNodes((nds) => applyNodeChanges(changes, nds)), 80 | [setNodes], 81 | ); 82 | const onEdgesChange = useCallback( 83 | (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)), 84 | [setEdges], 85 | ); 86 | const onConnect = useCallback( 87 | (connection) => setEdges((eds) => addEdge(connection, eds)), 88 | [setEdges], 89 | ); 90 | 91 | return ( 92 |
93 | 103 | 104 | 105 |
106 | ); 107 | } 108 | 109 | export default Flow; 110 | ``` 111 | 112 | ### 使用子节点特定选项 113 | 114 | 当移动父节点时,我们可以看到子节点也移动。使用 `parentId` 选项将一个节点添加到另一个节点只做一件事:相对于其父节点定位它。子节点并不是真正的子标记。可以将子级拖动或定位到其父级之外(当未设置范围:'parent'选项时),但是当您移动父级时,子级也会随之移动。 115 | 116 | 在上面的示例中,我们使用组类型作为父节点,但您也可以使用任何其他类型。组类型只是一种没有附加句柄的便利节点类型。 117 | 118 | 现在我们要添加更多的节点和边。正如您所看到的,我们可以连接组内的节点并创建从子流到外部节点的连接: 119 | 120 | ```tsx 121 | import { useCallback, useState } from 'react'; 122 | import { 123 | ReactFlow, 124 | addEdge, 125 | applyEdgeChanges, 126 | applyNodeChanges, 127 | Background, 128 | } from '@xyflow/react'; 129 | 130 | const initialNodes = [ 131 | { 132 | id: 'A', 133 | type: 'group', 134 | position: { x: 0, y: 0 }, 135 | style: { 136 | width: 170, 137 | height: 140, 138 | }, 139 | }, 140 | { 141 | id: 'A-1', 142 | type: 'input', 143 | data: { label: 'Child Node 1' }, 144 | position: { x: 10, y: 10 }, 145 | parentId: 'A', 146 | extent: 'parent', 147 | }, 148 | { 149 | id: 'A-2', 150 | data: { label: 'Child Node 2' }, 151 | position: { x: 10, y: 90 }, 152 | parentId: 'A', 153 | extent: 'parent', 154 | }, 155 | { 156 | id: 'B', 157 | type: 'output', 158 | position: { x: -100, y: 200 }, 159 | data: { label: 'Node B' }, 160 | }, 161 | { 162 | id: 'C', 163 | type: 'output', 164 | position: { x: 100, y: 200 }, 165 | data: { label: 'Node C' }, 166 | }, 167 | ]; 168 | const initialEdges = [ 169 | { id: 'a1-a2', source: 'A-1', target: 'A-2' }, 170 | { id: 'a2-b', source: 'A-2', target: 'B' }, 171 | { id: 'a2-c', source: 'A-2', target: 'C' }, 172 | ]; 173 | 174 | const rfStyle = { 175 | backgroundColor: '#D0C0F7', 176 | }; 177 | 178 | function Flow() { 179 | const [nodes, setNodes] = useState(initialNodes); 180 | const [edges, setEdges] = useState(initialEdges); 181 | 182 | const onNodesChange = useCallback( 183 | (changes) => setNodes((nds) => applyNodeChanges(changes, nds)), 184 | [setNodes], 185 | ); 186 | const onEdgesChange = useCallback( 187 | (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)), 188 | [setEdges], 189 | ); 190 | const onConnect = useCallback( 191 | (connection) => setEdges((eds) => addEdge(connection, eds)), 192 | [setEdges], 193 | ); 194 | 195 | return ( 196 |
197 | 207 | 208 | 209 |
210 | ); 211 | } 212 | 213 | export default Flow; 214 | ``` 215 | 216 | ### 使用默认节点作为父元素 217 | 218 | 让我们删除节点 B 的标签并添加一些子节点。在此示例中,可以看到我们也可以使用默认节点类型之一作为父节点。我们还将子节点设置为`draggable: false`,以便它们不再可拖动。 219 | 220 | ```tsx 221 | import { useCallback, useState } from 'react'; 222 | import { 223 | ReactFlow, 224 | addEdge, 225 | applyEdgeChanges, 226 | applyNodeChanges, 227 | Background, 228 | } from '@xyflow/react'; 229 | 230 | const initialNodes = [ 231 | { 232 | id: 'A', 233 | type: 'group', 234 | position: { x: 0, y: 0 }, 235 | style: { 236 | width: 170, 237 | height: 140, 238 | }, 239 | }, 240 | { 241 | id: 'A-1', 242 | type: 'input', 243 | data: { label: 'Child Node 1' }, 244 | position: { x: 10, y: 10 }, 245 | parentId: 'A', 246 | extent: 'parent', 247 | }, 248 | { 249 | id: 'A-2', 250 | data: { label: 'Child Node 2' }, 251 | position: { x: 10, y: 90 }, 252 | parentId: 'A', 253 | extent: 'parent', 254 | }, 255 | { 256 | id: 'B', 257 | type: 'output', 258 | position: { x: -100, y: 200 }, 259 | data: null, 260 | style: { 261 | width: 170, 262 | height: 140, 263 | backgroundColor: 'rgba(240,240,240,0.25)', 264 | }, 265 | }, 266 | { 267 | id: 'B-1', 268 | data: { label: 'Child 1' }, 269 | position: { x: 50, y: 10 }, 270 | parentId: 'B', 271 | extent: 'parent', 272 | draggable: false, 273 | style: { 274 | width: 60, 275 | }, 276 | }, 277 | { 278 | id: 'B-2', 279 | data: { label: 'Child 2' }, 280 | position: { x: 10, y: 90 }, 281 | parentId: 'B', 282 | extent: 'parent', 283 | // draggable: false, 284 | style: { 285 | width: 60, 286 | }, 287 | }, 288 | { 289 | id: 'B-3', 290 | data: { label: 'Child 3' }, 291 | position: { x: 100, y: 90 }, 292 | parentId: 'B', 293 | extent: 'parent', 294 | // draggable: false, 295 | style: { 296 | width: 60, 297 | }, 298 | }, 299 | { 300 | id: 'C', 301 | type: 'output', 302 | position: { x: 100, y: 200 }, 303 | data: { label: 'Node C' }, 304 | }, 305 | ]; 306 | const initialEdges = [ 307 | { id: 'a1-a2', source: 'A-1', target: 'A-2' }, 308 | { id: 'a2-b', source: 'A-2', target: 'B' }, 309 | { id: 'a2-c', source: 'A-2', target: 'C' }, 310 | { id: 'b1-b2', source: 'B-1', target: 'B-2' }, 311 | { id: 'b1-b3', source: 'B-1', target: 'B-3' }, 312 | ]; 313 | 314 | const rfStyle = { 315 | backgroundColor: '#D0C0F7', 316 | }; 317 | 318 | function Flow() { 319 | const [nodes, setNodes] = useState(initialNodes); 320 | const [edges, setEdges] = useState(initialEdges); 321 | 322 | const onNodesChange = useCallback( 323 | (changes) => setNodes((nds) => applyNodeChanges(changes, nds)), 324 | [setNodes], 325 | ); 326 | const onEdgesChange = useCallback( 327 | (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)), 328 | [setEdges], 329 | ); 330 | const onConnect = useCallback( 331 | (connection) => setEdges((eds) => addEdge(connection, eds)), 332 | [setEdges], 333 | ); 334 | 335 | return ( 336 |
337 | 347 | 348 | 349 |
350 | ); 351 | } 352 | 353 | export default Flow; 354 | ``` 355 | 356 | -------------------------------------------------------------------------------- /docs/guide/terms.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 指南 3 | title: 术语&定义 4 | order: 1 5 | group: 6 | title: 概念&原理 7 | order: 1 8 | --- 9 | 10 | ## 基本概念 11 | 12 | 在文档的这一部分,我们会介绍一些基本的 `React Flow` 术语和定义。在 `React Flow` 中我们会经常使用的三个概念是`nodes`, `edges`, 和 `handles`。 13 | 14 | ### Nodes(节点) 15 | 16 | ```tsx 17 | import React from 'react'; 18 | import { ReactFlow } from '@xyflow/react'; 19 | 20 | const initialNodes = [ 21 | { id: '1', position: { x: 100, y: 50 }, data: { label: 'Dooring节点' } } 22 | ]; 23 | 24 | export default function App() { 25 | return ( 26 |
27 | 28 |
29 | ); 30 | } 31 | ``` 32 | 33 | `React Flow` 中的节点是一个 `React` 组件。这意味着它可以渲染我们喜欢的任何内容。每个节点都有一个 `x` 和 `y` 坐标,这指明了节点在视口中的位置。默认情况下,节点如上例所示。我们可以在节点选项文档中找到用于自定义节点的所有选项。 34 | 35 | #### 自定义节点 36 | 37 | 这就是 `React Flow` 的神奇之处。我们可以自定义节点的外观和行为。我们可以创建的许多功能并非内置于 `React Flow` 中。 38 | 39 | 使用自定义节点执行以下操作: 40 | 41 | - 渲染表单元素 42 | - 可视化数据 43 | - 支持多个手柄 44 | 45 | 如下案例: 46 | 47 | ```tsx | pure 48 | import { useCallback } from 'react'; 49 | import { Handle, Position } from '@xyflow/react'; 50 | 51 | const handleStyle = { left: 10 }; 52 | 53 | function TextUpdaterNode({ data }) { 54 | const onChange = useCallback((evt) => { 55 | console.log(evt.target.value); 56 | }, []); 57 | 58 | return ( 59 | <> 60 | 61 |
62 | 63 | 64 |
65 | 66 | 72 | 73 | ); 74 | } 75 | ``` 76 | 77 | ### Handles(句柄) 78 | 79 | ```tsx 80 | import React from 'react'; 81 | import { ReactFlow, Handle, Position } from '@xyflow/react'; 82 | 83 | const style = { 84 | border: '1px solid #eee', 85 | padding: '5px 10px', 86 | borderRadius: '5px', 87 | background: 'white', 88 | background: '#06c', 89 | color: 'white', 90 | } 91 | 92 | const CustomNode = ({ data }) => { 93 | return ( 94 | <> 95 |
96 | {data.label} 97 |
98 | 99 | 100 | 101 | 102 | ); 103 | }; 104 | 105 | const nodeTypes = { textUpdater: CustomNode }; 106 | 107 | const initialNodes = [ 108 | { 109 | id: '1', 110 | position: { x: 100, y: 50 }, 111 | data: { label: 'Dooring节点' }, 112 | type: 'textUpdater', 113 | } 114 | ]; 115 | 116 | export default function App() { 117 | return ( 118 |
119 | 120 |
121 | ); 122 | } 123 | ``` 124 | 125 | 句柄(在其他库中也称为“端口”)是边连接到节点的地方。手柄可放置在任何地方,并可根据我们的喜好设计风格。它只是一个 `div` 元素。默认情况下,它在节点的顶部、底部、左侧或右侧显示为灰色圆圈。创建自定义节点时,我们可以在节点中拥有所需数量的句柄。 126 | 127 | 案例代码如下: 128 | 129 | ```tsx | pure 130 | import React from 'react'; 131 | import { ReactFlow, Handle, Position } from '@xyflow/react'; 132 | 133 | const style = { 134 | border: '1px solid #eee', 135 | padding: '5px 10px', 136 | borderRadius: '5px', 137 | background: 'white', 138 | background: '#06c', 139 | color: 'white', 140 | } 141 | 142 | const CustomNode = ({ data }) => { 143 | return ( 144 | <> 145 |
146 | {data.label} 147 |
148 | 149 | 150 | 151 | 152 | ); 153 | }; 154 | 155 | const nodeTypes = { textUpdater: CustomNode }; 156 | 157 | const initialNodes = [ 158 | { 159 | id: '1', 160 | position: { x: 100, y: 50 }, 161 | data: { label: 'Dooring节点' }, 162 | type: 'textUpdater', 163 | } 164 | ]; 165 | 166 | export default function App() { 167 | return ( 168 |
169 | 170 |
171 | ); 172 | } 173 | ``` 174 | 175 | 176 | ### Edges(边) 177 | 178 | ```tsx 179 | import { useCallback } from 'react'; 180 | import { 181 | ReactFlow, 182 | addEdge, 183 | useNodesState, 184 | useEdgesState, 185 | BaseEdge, 186 | EdgeLabelRenderer, 187 | getStraightPath, 188 | Handle, 189 | Position 190 | } from '@xyflow/react'; 191 | 192 | const CustomEdge = ({ id, sourceX, sourceY, targetX, targetY }) => { 193 | const [edgePath, labelX, labelY] = getStraightPath({ 194 | sourceX, 195 | sourceY, 196 | targetX, 197 | targetY, 198 | }); 199 | 200 | return ( 201 | <> 202 | 203 | 204 | 205 | 自定义 206 | 207 | 208 | 209 | ); 210 | } 211 | 212 | const style = { 213 | border: '1px solid #eee', 214 | padding: '5px 10px', 215 | borderRadius: '5px', 216 | background: 'white', 217 | background: '#06c', 218 | color: 'white', 219 | } 220 | 221 | const CustomNode = ({ data }) => { 222 | return ( 223 | <> 224 |
225 | {data.label} 226 |
227 | 228 | 229 | 230 | 231 | ); 232 | }; 233 | 234 | 235 | const initialNodes = [ 236 | { id: 'a', position: { x: 0, y: 0 }, data: { label: 'Dooring系列产品' }, type: 'textUpdater' }, 237 | { id: 'b', position: { x: 300, y: 0 }, data: { label: 'H5-Dooring' }, type: 'textUpdater' }, 238 | ]; 239 | 240 | const initialEdges = [ 241 | { id: 'a->b', type: 'custom-edge', source: 'a', target: 'b' }, 242 | ]; 243 | 244 | const edgeTypes = { 245 | 'custom-edge': CustomEdge, 246 | }; 247 | 248 | const nodeTypes = { textUpdater: CustomNode }; 249 | 250 | function Flow() { 251 | const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); 252 | const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); 253 | const onConnect = useCallback( 254 | (connection) => { 255 | const edge = { ...connection, type: 'custom-edge' }; 256 | setEdges((eds) => addEdge(edge, eds)); 257 | }, 258 | [setEdges], 259 | ); 260 | 261 | return ( 262 |
263 | 273 |
274 | ); 275 | } 276 | 277 | export default Flow; 278 | ``` 279 | 280 | 一条边连接两个节点。每条边都需要一个目标节点和一个源节点。 `React Flow` 具有四种内置边缘类型: 281 | 282 | - 默认(贝塞尔曲线) 283 | - smoothstep 284 | - step 285 | - Straight 286 | 287 | 边缘是 `SVG` 路径,可以使用 `CSS` 进行样式设置,并且是完全可定制的。如果我们使用多个句柄,则可以单独引用它们来为一个节点创建多个连接。 288 | 289 | 290 | ### Connection Line(连接线) 291 | 292 | `React Flow` 的内置功能,可以通过单击并从一个手柄拖动到另一个手柄来创建新边缘。拖动时,占位符边缘称为连接线。连接线还内置四种类型,并且可以定制。我们可以在 `props` 部分找到用于配置连接线的 `props`。 293 | 294 | ### 视口 295 | 296 | ```tsx 297 | import React, { useCallback, useState } from 'react'; 298 | import { ReactFlow } from '@xyflow/react'; 299 | 300 | export default function App() { 301 | const [initialNodes, setState] = useState({ id: '1', width: 160, 302 | position: { x: 100, y: 50 }, data: { label: 'Dooring节点, 拖拽试试?' } }); 303 | 304 | const handleNodesChange = useCallback((nodes) => { 305 | if(nodes[0].type === 'position') { 306 | if(!nodes[0].position.x) return 307 | 308 | const x = Math.floor(nodes[0].position.x); 309 | const y = Math.floor(nodes[0].position.y); 310 | setState({...initialNodes, data: { label: `x: ${x}, y: ${y}`}, position: nodes[0].position}); 311 | } 312 | 313 | }, []); 314 | 315 | return ( 316 |
317 | 318 |
319 | ); 320 | } 321 | ``` 322 | 323 | 所有 `React Flow` 都存在于视口内部。视口具有 `x、y` 和缩放值。当我们拖动窗格时,会更改 `x` 和 `y` 坐标,当我们放大或缩小时,会更改缩放级别。 324 | 325 | -------------------------------------------------------------------------------- /docs/guide/theme.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 指南 3 | title: 自定义主题 4 | order: 5 5 | group: 6 | title: 自定义React-Flow 7 | order: 2 8 | --- 9 | 10 | ## 主题 11 | 12 | `React Flow` 在构建时就考虑到了深度定制。我们的许多用户完全改变了 `React Flow` 的外观,以匹配他们自己的品牌或设计系统。本指南将向大家介绍自定义 `React Flow` 外观的不同方法。 13 | 14 | ### 默认样式 15 | 16 | `React Flow` 的默认样式足以使用内置节点。它们为填充、边框半径和动画边缘等样式提供了一些合理的默认值。可以在下面看到它们的样子: 17 | 18 | ```tsx 19 | import React, { useCallback } from 'react'; 20 | import { 21 | ReactFlow, 22 | MiniMap, 23 | Controls, 24 | Background, 25 | useNodesState, 26 | useEdgesState, 27 | addEdge, 28 | } from '@xyflow/react'; 29 | 30 | const initialNodes = [ 31 | { id: '1', position: { x: 0, y: 0 }, data: { label: 'Dooring' } }, 32 | { id: '2', position: { x: 0, y: 100 }, data: { label: 'Flow' } }, 33 | { id: '3', position: { x: 200, y: 100 }, data: { label: 'Nocode/WEP' } }, 34 | ]; 35 | const initialEdges = [ 36 | { id: 'e1-2', source: '1', target: '2' }, 37 | { id: 'e1-3', source: '1', target: '3' } 38 | ]; 39 | 40 | export default function App() { 41 | const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); 42 | const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); 43 | 44 | const onConnect = useCallback( 45 | (params) => setEdges((eds) => addEdge(params, eds)), 46 | [setEdges], 47 | ); 48 | 49 | return ( 50 |
51 | 59 | 60 | 61 | 62 | 63 |
64 | ); 65 | } 66 | ``` 67 | 68 | 通常会通过将这些默认样式导入 `App.jsx` 文件或其他入口点来加载它们. 69 | 70 | ```tsx | pure 71 | import '@xyflow/react/dist/style.css'; 72 | ``` 73 | 74 | 无需深入研究自定义节点和边,我们可以通过三种方式来设计 `React Flow` 的基本外观: 75 | 76 | - 通过 `style props` 传递内联样式 77 | - 使用自定义 `CSS` 覆盖内置类 78 | - 覆盖 `React Flow` 使用的 `CSS` 变量 79 | 80 | ### 内置深色和浅色模式 81 | 82 | 我们可以使用 `colorMode` 属性('dark'、'light' 或 'system')选择一种内置颜色模式,如下示例中所示。 83 | 84 | ```tsx | pure 85 | import ReactFlow from '@xyflow/react'; 86 | 87 | export default function Flow() { 88 | return 89 | } 90 | ``` 91 | 92 | 当使用 `colorMode` 属性时,`React Flow` 会向根元素 (`.react-flow`) 添加一个类,可以使用该类根据颜色模式设置流的样式: 93 | 94 | ```css | pure 95 | .dark .react-flow__node { 96 | background: #777; 97 | color: white; 98 | } 99 | 100 | .light .react-flow__node { 101 | background: white; 102 | color: #111; 103 | } 104 | ``` 105 | 106 | ### 自定义样式 107 | 108 | 开始自定义流程外观的最简单方法是使用 `React Flow` 组件上的 `style` 属性来内联您自己的 `CSS`。 109 | 110 | ```tsx | pure 111 | import ReactFlow from '@xyflow/react'; 112 | const styles = { 113 | background: 'red', 114 | width: '100%', 115 | height: 300, 116 | }; 117 | 118 | export default function Flow() { 119 | return 120 | } 121 | ``` 122 | 123 | ### CSS变量 124 | 125 | 如果我们不想完全替换默认样式,而只是想调整整体外观和感觉,则可以覆盖我们在整个库中使用的一些 `CSS` 变量。 126 | 127 | 这些变量大多是不言自明的。下表列出了可能想要调整的所有变量及其默认值以供参考: 128 | 129 | | 变量 | 默认值 | 130 | | ---- | ---- | 131 | | --edge-stroke-default | #b1b1b7 | 132 | | --edge-stroke-width-default | 1 | 133 | | --edge-stroke-selected-default | #555 | 134 | | --connectionline-stroke-default | #b1b1b7 | 135 | | --connectionline-stroke-width-default | 1 | 136 | | --attribution-background-color-default | rgba(255, 255, 255, 0.5) | 137 | | --minimap-background-color-default | #fff | 138 | | --background-pattern-dot-color-default | #91919a | 139 | | --background-pattern-line-color-default | #eee | 140 | | --background-pattern-cross-color-default | #e2e2e2 | 141 | | --node-color-default | inherit | 142 | | --node-border-default | 1px solid #1a192b | 143 | | --node-background-color-default | #fff | 144 | | --node-group-background-color-default | rgba(240, 240, 240, 0.25) | 145 | | --node-boxshadow-hover-default | 0 1px 4px 1px rgba(0, 0, 0, 0.08) | 146 | | --node-boxshadow-selected-default | 0 0 0 0.5px #1a192b | 147 | | --handle-background-color-default | #1a192b | 148 | | --handle-border-color-default | #fff | 149 | | --selection-background-color-default | rgba(0, 89, 220, 0.08) | 150 | | --selection-border-default | 1px dotted rgba(0, 89, 220, 0.8) | 151 | | --controls-button-background-color-default | #fefefe | 152 | | --controls-button-background-color-hover-default | #f4f4f4 | 153 | | --controls-button-color-default | inherit | 154 | | --controls-button-color-hover-default | inherit | 155 | | --controls-button-border-color-default | #eee | 156 | | --controls-box-shadow-default | 0 0 2px 1px rgba(0, 0, 0, 0.08) | 157 | 158 | 这些变量用于定义 `React Flow` 的各个元素的默认值。这意味着它们仍然可以在每个元素的基础上被内联样式或自定义类覆盖。 159 | 160 | ### 重写内置类 161 | 162 | 有些人认为大量使用内联样式是一种反模式。在这种情况下,可以使用自己的 `CSS` 覆盖 `React Flow` 使用的内置类。 `React Flow` 中的各种元素都附加了许多类,但下面列出了您可能想要覆盖的类: 163 | 164 | | 类名 | 描述 | 165 | | ---- | ---- | 166 | |.react-flow | The outermost container | 167 | |.react-flow__renderer | The inner container | 168 | |.react-flow__zoompane | Zoom & pan pane | 169 | |.react-flow__selectionpane | Selection pane | 170 | |.react-flow__selection | User selection | 171 | |.react-flow__edges | The element containing all edges in the flow | 172 | |.react-flow__edge | Applied to each Edge in the flow | 173 | |.react-flow__edge.selected | Added to an Edge when selected | 174 | |.react-flow__edge.animated | Added to an Edge when its animated prop is true | 175 | |.react-flow__edge.updating | Added to an Edge while it gets updated via onReconnect | 176 | |.react-flow__edge-path | The SVG `` element of an Edge | 177 | |.react-flow__edge-text | The SVG `` element of an Edge label | 178 | |.react-flow__edge-textbg | The SVG `` element behind an Edge label | 179 | |.react-flow__connection | Applied to the current connection line | 180 | |.react-flow__connection-path | The SVG `` of a connection line | 181 | |.react-flow__nodes | The element containing all nodes in the flow | 182 | |.react-flow__node | Applied to each Node in the flow | 183 | |.react-flow__node.selected | Added to a Node when selected. | 184 | |.react-flow__node-default | Added when Node type is "default" | 185 | |.react-flow__node-input | Added when Node type is "input" | 186 | |.react-flow__node-output | Added when Node type is "output" | 187 | |.react-flow__nodesselection | Nodes selection | 188 | |.react-flow__nodesselection-rect | Nodes selection rect | 189 | |.react-flow__handle | Applied to each `` component | 190 | |.react-flow__handle-top | Applied when a handle's Position is set to "top" | 191 | |.react-flow__handle-right | Applied when a handle's Position is set to "right" | 192 | |.react-flow__handle-bottom | Applied when a handle's Position is set to "bottom" | 193 | |.react-flow__handle-left | Applied when a handle's Position is set to "left" | 194 | |.react-flow__handle-connecting | Applied when a connection line is above a handle. | 195 | |.react-flow__handle-valid | Applied when a connection line is above a handle and the connection is valid | 196 | |.react-flow__background | Applied to the `` component | 197 | |.react-flow__minimap | Applied to the `` component | 198 | |.react-flow__controls | Applied to the `` component | 199 | 200 | :::warning 201 | 需要注意的是有些类名是 `react-flow` 框架功能必备的, 如果覆盖可能导致功能上的bug. 202 | ::: 203 | 204 | ### 第三方解决方案 205 | 206 | 您可以选择完全退出 `React Flow` 的默认样式并使用第三方样式解决方案。如果您想执行此操作,则必须确保仍导入基本样式。 207 | 208 | ```tsx| pure 209 | import '@xyflow/react/dist/base.css'; 210 | ``` 211 | 212 | #### Styled Components 模式 213 | 许多直接渲染的组件(例如``)都接受 `className` 和 `style` 属性。这意味着可以使用我们喜欢的任何样式解决方案,例如`Styled Components`: 214 | 215 | ```tsx | pure 216 | import { MiniMap } from '@xyflow/react'; 217 | 218 | const StyledMiniMap = styled(MiniMap)` 219 | background-color: ${(props) => props.theme.bg}; 220 | 221 | .react-flow__minimap-mask { 222 | fill: ${(props) => props.theme.minimapMaskBg}; 223 | } 224 | 225 | .react-flow__minimap-node { 226 | fill: ${(props) => props.theme.nodeBg}; 227 | stroke: none; 228 | } 229 | `; 230 | ``` 231 | 232 | #### TailwindCSS 模式 233 | 234 | 自定义节点和边是 `React` 组件,可以使用任何我们想要的样式解决方案来设置它们的样式。例如,可能想要使用 `Tailwind` 来设置节点的样式: 235 | 236 | ```tsx | pure 237 | function CustomNode({ data }) { 238 | return ( 239 |
240 |
241 |
242 | {data.emoji} 243 |
244 |
245 |
{data.name}
246 |
{data.job}
247 |
248 |
249 | 250 | 255 | 260 |
261 | ); 262 | } 263 | ``` 264 | 265 | :::warning 266 | 如果我们想覆盖默认样式,请确保在 `React Flows` 基本样式之后导入 `Tailwinds` 入口点。 267 | ::: 268 | 269 | ```tsx | pure 270 | import '@xyflow/react/dist/style.css'; 271 | import 'tailwind.css'; 272 | ``` 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | -------------------------------------------------------------------------------- /docs/guide/viewport.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 指南 3 | title: 视口 4 | order: 3 5 | group: 6 | title: 概念&原理 7 | order: 1 8 | --- 9 | 10 | ## 平移和缩放 11 | 12 | `React Flow` 的默认平移和缩放行为受到[滑动地图](https://wiki.openstreetmap.org/wiki/Slippy_map)的启发。我们可以通过拖动进行平移,通过滚动进行缩放。可以使用提供的属性轻松自定义此行为: 13 | 14 | - panOnDrag: 默认: true 15 | - selectionOnDrag: 默认: false (available since 11.4.0) 16 | - panOnScroll: 默认: false (Overwrites zoomOnScroll) 17 | - panOnScrollSpeed: 默认: 0.5 18 | - panOnScrollMode: 默认: 'free'. 'free' (所有方向), 'vertical' (仅垂直) or - 'horizontal' (仅水平) 19 | - zoomOnScroll: 默认: true 20 | - zoomOnPinch: 默认: true 21 | - zoomOnDoubleClick: 默认: true 22 | - preventScrolling: 默认: true (阻止浏览器饿的滚动行为) 23 | - zoomActivationKeyCode: 默认 'Meta' 24 | - panActivationKeyCode: 默认 'Space' 25 | 26 | ## 默认视口控制 27 | 28 | 如上所述,默认控件是: 29 | 30 | - 平移:拖动鼠标 31 | - 缩放:滚动 32 | - 创建选择:Shift + 拖动 33 | 34 | 35 | ```tsx 36 | import { useCallback } from 'react'; 37 | import { 38 | ReactFlow, 39 | addEdge, 40 | useEdgesState, 41 | useNodesState, 42 | } from '@xyflow/react'; 43 | 44 | const initialNodes = [ 45 | { 46 | id: '1', 47 | data: { label: 'Dooring' }, 48 | position: { x: 150, y: 0 }, 49 | }, 50 | { 51 | id: '2', 52 | data: { label: 'Nest-Admin' }, 53 | position: { x: 0, y: 150 }, 54 | }, 55 | { 56 | id: '3', 57 | data: { label: 'Next-Admin' }, 58 | position: { x: 300, y: 150 }, 59 | }, 60 | ]; 61 | 62 | const initialEdges = [ 63 | { id: 'e1-2', source: '1', target: '2' }, 64 | { id: 'e1-3', source: '1', target: '3' }, 65 | ]; 66 | 67 | function Flow() { 68 | const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); 69 | const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); 70 | 71 | const onConnect = useCallback( 72 | (connection) => setEdges((eds) => addEdge(connection, eds)), 73 | [setEdges], 74 | ); 75 | 76 | return ( 77 |
78 | 86 |
87 | 88 | ); 89 | } 90 | 91 | export default Flow; 92 | ``` 93 | 94 | ## 类似 Figma 的视口控件 95 | 96 | 如果你更喜欢 `Figma/sketch/design` 工具控件,可以设置`panOnScroll={true}`和`selectionOnDrag={true}`: 97 | 98 | - 平移:空格+拖动鼠标、滚动、鼠标中键或右键 99 | - 缩放:俯仰或 cmd + 滚动 100 | - 创建选区:拖动鼠标 101 | 102 | ```tsx 103 | import { useCallback } from 'react'; 104 | import { 105 | ReactFlow, 106 | addEdge, 107 | useEdgesState, 108 | useNodesState, 109 | SelectionMode, 110 | } from '@xyflow/react'; 111 | 112 | const initialNodes = [ 113 | { 114 | id: '1', 115 | data: { label: 'Dooring' }, 116 | position: { x: 150, y: 0 }, 117 | }, 118 | { 119 | id: '2', 120 | data: { label: 'Nest-Admin' }, 121 | position: { x: 0, y: 150 }, 122 | }, 123 | { 124 | id: '3', 125 | data: { label: 'Next-Admin' }, 126 | position: { x: 300, y: 150 }, 127 | }, 128 | ]; 129 | 130 | const initialEdges = [ 131 | { id: 'e1-2', source: '1', target: '2' }, 132 | { id: 'e1-3', source: '1', target: '3' }, 133 | ]; 134 | 135 | function Flow() { 136 | const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); 137 | const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); 138 | 139 | const onConnect = useCallback( 140 | (connection) => setEdges((eds) => addEdge(connection, eds)), 141 | [setEdges], 142 | ); 143 | 144 | const panOnDrag = [1, 2]; 145 | 146 | return ( 147 |
148 | 160 |
161 | 162 | ); 163 | } 164 | 165 | export default Flow; 166 | ``` 167 | 168 | 在此示例中,我们还设置 `SelectionMode={SelectionMode.Partial}` 以便能够将节点添加到仅部分选择的选择中。 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: A static site based on dumi 3 | hero: 4 | title: React-Flow 5 | description: 一款用于构建基于节点的编辑器和交互式流程的可定制 React 组件 6 | actions: 7 | - text: 快速开始 8 | link: /guide 9 | - text: 可视化搭建平台 10 | link: https://dooring.vip 11 | --- 12 | 13 | ## React-Flow: 一款开箱即用的工作流开源引擎 14 | 15 | 16 | 17 | 友情链接: 18 | 19 | - [H5-Dooring零代码搭建平台](https://dooring.vip) 20 | - [AI + 文档知识引擎](http://doc.dooring.vip) 21 | 22 | 技术博客 & 技术交流: 23 | 24 | 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-flow", 3 | "version": "0.0.1", 4 | "description": "A static site based on dumi", 5 | "scripts": { 6 | "start": "npm run dev", 7 | "dev": "dumi dev", 8 | "build": "dumi build", 9 | "prepare": "husky install && dumi setup" 10 | }, 11 | "authors": [], 12 | "license": "MIT", 13 | "commitlint": { 14 | "extends": [ 15 | "@commitlint/config-conventional" 16 | ] 17 | }, 18 | "lint-staged": { 19 | "*.{md,json}": [ 20 | "prettier --write --no-error-on-unmatched-pattern" 21 | ] 22 | }, 23 | "devDependencies": { 24 | "@commitlint/cli": "^17.1.2", 25 | "@commitlint/config-conventional": "^17.1.0", 26 | "dumi": "^2.2.13", 27 | "husky": "^8.0.1", 28 | "lint-staged": "^13.0.3", 29 | "prettier": "^2.7.1" 30 | }, 31 | "dependencies": { 32 | "@ant-design/icons": "^5.4.0", 33 | "@xyflow/react": "^12.0.3" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /public/d3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrXujiang/react-flow/0c6e9995b838a3deb08a074a149b06b3464f4ee4/public/d3.png -------------------------------------------------------------------------------- /public/edge.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrXujiang/react-flow/0c6e9995b838a3deb08a074a149b06b3464f4ee4/public/edge.webp -------------------------------------------------------------------------------- /public/elk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrXujiang/react-flow/0c6e9995b838a3deb08a074a149b06b3464f4ee4/public/elk.png -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrXujiang/react-flow/0c6e9995b838a3deb08a074a149b06b3464f4ee4/public/favicon.png -------------------------------------------------------------------------------- /public/force.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrXujiang/react-flow/0c6e9995b838a3deb08a074a149b06b3464f4ee4/public/force.png -------------------------------------------------------------------------------- /public/library.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrXujiang/react-flow/0c6e9995b838a3deb08a074a149b06b3464f4ee4/public/library.png -------------------------------------------------------------------------------- /public/overview.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrXujiang/react-flow/0c6e9995b838a3deb08a074a149b06b3464f4ee4/public/overview.webp -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "skipLibCheck": true, 5 | "esModuleInterop": true, 6 | "baseUrl": "./", 7 | "paths": { 8 | "@@/*": [".dumi/tmp/*"] 9 | } 10 | }, 11 | "include": [".dumirc.ts"] 12 | } 13 | --------------------------------------------------------------------------------