React Flow

news2024/9/23 11:24:57
// 创建项目
npm create vite@latest my-react-flow-app -- --template react
// 安装插件
npm install reactflow
// 运行项目
npm run dev

 

 

 

1、App.jsx

import { useCallback, useState } from 'react';
import ReactFlow,
{
  addEdge,
  ReactFlowProvider,
  MiniMap,
  Controls,
  useNodesState,
  useEdgesState,
  useReactFlow,
  MarkerType,
  Panel,
  ConnectionMode
} from 'reactflow';
import 'reactflow/dist/style.css';
import './index.css';

import UpdateNode from './components/nodeContent';
import UpdateEdge from './components/edgeContent';
import ResizableNodeSelected from './components/ResizableNodeSelected';
import {nodes as initialNodes1,edges as initialEdges1} from './components/data';

const nodeTypes = {
  ResizableNodeSelected,
};

const rfStyle = {
  backgroundColor: '#B8CEFF',
};

const initialNodes = [
  {
    id: '1',
    type: 'ResizableNodeSelected',
    position: { x: 100, y: 100 },
    data: { label: '1' },
    style: {
      background: "#F3A011",
      color: "white",
      border: '1px solid orange',
      borderRadius: '100%',
      width: 80,
      height: 80,
    },
  },
  {
    id: '2',
    type: 'ResizableNodeSelected',
    position: { x: 200, y: 300 },
    data: { label: '2' },
    style: {
      background: "#F3A011",
      color: "white",
      border: '1px solid orange',
      borderRadius: '100%',
      width: 80,
      height: 80,
    },
  },
  {
    id: '3',
    type: 'ResizableNodeSelected',
    position: { x: 100, y: 500 },
    data: { label: '3' },
    style: {
      background: "#F3A011",
      color: "white",
      border: '1px solid orange',
      borderRadius: '100%',
      width: 80,
      height: 80,
    },
  },
];
const initialEdges = [
  {
    id: 'e1-2',
    source: '1',
    target: '2',
    style: { stroke: "#116F97" },
    label: "连接1-2",
    sourceHandle: 'c',
    targetHandle: 'a',
  },
  {
    id: "e2-3",
    source: "2",
    target: "3",
    // labelStyle: { fill: "#116F97", fontWeight: 100 }, // 连接线名称样式
    style: { stroke: "#116F97" }, // 连接线颜色
    label: "连接2-3",
    sourceHandle: 'c',
    targetHandle: 'a',
  },
];

const flowKey = 'flow_test';
const localNodes = JSON.parse(localStorage.getItem(flowKey)).nodes;
const localEdges = JSON.parse(localStorage.getItem(flowKey)).edges;
let nodeId = 1;

function App1() {
  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes1);
  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges1);
  const [nodeInfo, setNodeInfo] = useState({});
  const [edgeInfo, setEdgeInfo] = useState({});
  const [nodeShow, setNodeShow] = useState(true);
  const onConnect = useCallback(
    (connection) => setEdges((eds) => addEdge(connection, eds)),
    [setEdges]
  );

  // 保存
  const [rfInstance, setRfInstance] = useState({});
  const onSave = useCallback(() => {
    if (rfInstance) {
      const flow = rfInstance.toObject();
      localStorage.setItem(flowKey, JSON.stringify(flow));
      console.log(JSON.stringify(flow));
    }
  }, [rfInstance]
  );

  // 恢复
  const { setViewport } = useReactFlow();
  const onRestore = useCallback(() => {
    const restoreFlow = async () => {
      const flow = JSON.parse(localStorage.getItem(flowKey));
      if (flow) {
        const { x = 0, y = 0, zoom = 0 } = flow.viewport;
        setNodes(flow.nodes || []);
        setEdges(flow.edges || []);
        setViewport({ x, y, zoom });
      }
    };
    restoreFlow();
  }, [setNodes, setViewport]
  );

  // 清空
  const onDelete = useCallback(() => {
    const restoreFlow = async () => {
      setNodes([] || []);
      setEdges([] || []);
    };
    restoreFlow();
  }, [setNodes]
  );

  // 点击节点
  const onNodeClick = (e, node) => {
    setNodeInfo({
      ...node.data,
      id: node.id,
      nodeBg: node.style && node.style.background ? node.style.background : '#ffffff',
    });
    setNodeShow(true);
  };

  // 点击节点连接线
  const onEdgeClick = (e, edge) => {
    setEdgeInfo(edges.find((item) => edge.id === item.id));
    setNodeShow(false);
  };


  // 新增节点
  const reactFlowInstance = useReactFlow();
  const onAdd = useCallback(() => {
    const id = `${++nodeId}`;
    const newNode = {
      id,
      type: 'ResizableNodeSelected',
      position: {
        x: 100,
        y: 300,
        // x: Math.random() * 200,
        // y: Math.random() * 200,
      },
      data: {
        label: `Node ${id}`,
      },
      style: {
        background: "#F3A011",
        color: "white",
        border: '1px solid orange',
        borderRadius: '100%',
        width: 80,
        height: 80,

      },
    };
    reactFlowInstance.addNodes(newNode);
  }, []);

  // 改变节点内容
  const changeNode = (val) => {
    setNodes((nds) =>
      nds.map((item) => {
        if (item.id === val.id) {
          item.data = val;
          item.hidden = val.isHidden;
          item.style = { background: val.nodeBg, width: 80, height: 80, borderRadius: '100%', color: "white", fontSize: 2 };
        }
        return item;
      }),
    );
  };

  // 改变连接线内容
  const changeEdge = (val) => {
    setEdges((nds) =>
      nds.map((item) => {
        if (item.id === val.id) {
          item.label = val.label;
          item.type = val.type;
          item.hidden = val.isHidden;
          item.style = { stroke: val.color };
        }
        return item;
      }),
    );
  };

  // 默认edge样式
  const defaultEdgeOptions = {
    style: {
      strokeWidth: 1,
      stroke: '#116F97'
    },
    type: 'default',
    markerEnd: {
      type: MarkerType.ArrowClosed,
      color: '#116F97'
    } // 连接线尾部的箭头
  }

  return (
    <div style={{ width: '100vw', height: '100vh' }}>

      <ReactFlow
        nodes={nodes} // 节点
        edges={edges} // 连接线
        onNodesChange={onNodesChange} // 节点拖拽等改变
        onEdgesChange={onEdgesChange} // 连接线拖拽等改变
        onNodeClick={onNodeClick} // 点击节点
        onEdgeClick={onEdgeClick} // 点击连接线
        onConnect={onConnect} // 节点直接连接
        nodeTypes={nodeTypes} // 节点类型
        // edgeTypes={edgeTypes}
        fitView // 渲染节点数据
        style={rfStyle} // 背景色
        defaultEdgeOptions={defaultEdgeOptions} // 默认连接线样式
        onInit={setRfInstance} // 初始化保存的数据
        connectionMode={ConnectionMode.Loose}
      />
      {nodeShow ? (
        <UpdateNode info={nodeInfo} onChange={changeNode} />
      ) : (
          <UpdateEdge info={edgeInfo} onChange={changeEdge} />
        )}
      <Panel position='top-left'>
        <button onClick={onAdd}>add node</button>
        <button onClick={onSave}>save</button>
        <button onClick={onRestore}>restore</button>
        <button onClick={onDelete}>delete</button>
      </Panel>
      <MiniMap />
      <Controls />
    </div>
  );
}

export default function () {
  return (
    <ReactFlowProvider>
      <App1 />

    </ReactFlowProvider>
  );
}

 2、index.css

:root {
  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
  /* line-height: 2; */
  font-weight: 400;

  /* color-scheme: light dark; */
  color: rgba(255, 255, 255, 0.87);
  background-color: #242424;

  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* -webkit-text-size-adjust: 100%; */
}

a {
  font-weight: 500;
  color: #646cff;
  text-decoration: inherit;
}
a:hover {
  color: #535bf2;
}

body {
  margin: 0;
  display: flex;
  place-items: center;
  min-width: 320px;
  min-height: 100vh;
}

h1 {
  font-size: 3.2em;
  line-height: 1.1;
}

button {
  border-radius: 8px;
  border: 1px solid transparent;
  padding: 0.6em 1.2em;
  font-size: 1em;
  font-weight: 500;
  font-family: inherit;
  background-color: #1a1a1a;
  cursor: pointer;
  transition: border-color 0.25s;
}
button:hover {
  border-color: #646cff;
}
button:focus,
button:focus-visible {
  outline: 4px auto -webkit-focus-ring-color;
}

@media (prefers-color-scheme: light) {
  :root {
    color: #213547;
    background-color: #ffffff;
  }
  a:hover {
    color: #116F97;
  }
  button {
    background-color: #f9f9f9;
  }
}

/* edge颜色 */
.react-flow__handle{
  color: #116F97;
  background-color: #116F97;
  border:0;
  border-radius: 100%;
  min-width: 1px;
  min-height: 1px;
}
.react-flow__edge-textbg{
  fill:#3a94BB;
}
.react-flow__handle.connectionindicator{
  width: 1;
  height: 1;
}
.react-flow__node{
  width: 50;
  height: 50;
}


/* 4个连接点样式 */
.simple-floatingedges {
  flex-direction: column;
  display: flex;
  flex-grow: 1;
  height: 100%;
}
.simple-floatingedges .react-flow__handle {
  width: 8px;
  height: 8px;
  background-color: #bbb;
}
.simple-floatingedges .react-flow__handle-top {
  top: -5px;
}
.simple-floatingedges .react-flow__handle-bottom {
  bottom: -5px;
}
.simple-floatingedges .react-flow__handle-left {
  left: -5px;
}
.simple-floatingedges .react-flow__handle-right {
  right: -5px;
}
.simple-floatingedges .react-flow__node-custom {
  background: #fff;
  border: 1px solid #1a192b;
  border-radius: 3px;
  color: #222;
  font-size: 12px;
  padding: 10px;
  text-align: center;
  width: 150px;
}

/* node与wdge编辑样式 */
.dndflow {
  display: flex;
  flex-direction: column;
  flex-grow: 1;
  height: 70vh;
}
.react-flow__attribution {
  display: none;
}
.dndflow aside {
  padding: 15px 10px;
  font-size: 12px;
  background: #fcfcfc;
  border-right: 1px solid #eee;
}
.dndflow aside .description {
  margin-bottom: 10px;
}
.dndflow .dndnode {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 20px;
  margin-bottom: 10px;
  padding: 4px;
  border: 1px solid #1a192b;
  border-radius: 2px;
  cursor: grab;
}
.dndflow .dndnode.input {
  border-color: #0041d0;
}
.dndflow .dndnode.output {
  border-color: #ff0072;
}
.dndflow .reactflow-wrapper {
  flex-grow: 1;
  height: 100%;
}
.dndflow .selectall {
  margin-top: 10px;
}
@media screen and (min-width: 768px) {
  .dndflow {
    flex-direction: row;
  }

  .dndflow aside {
    width: 20%;
    max-width: 250px;
  }
}
.my_handle {
  z-index: 99;
}
.nodeContent {
  position: relative;
  color: #222;
  font-size: 12px;
  line-height: 10px;
  text-align: center;
  background-color: #fff;
  border: 1px solid #1a192b;
  border-radius: 3px;
}
.nodeStyle {
  width: 110px;
  height: 30px;
  line-height: 10px;
}
.updatenode__controls {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 4;
  padding: 16px;
  font-size: 12px;
  background-color: #fff;
}
.updatenode__controls label {
  display: block;
}
.updatenode__bglabel {
  margin-top: 10px;
}
.updatenode__checkboxwrapper {
  display: flex;
  align-items: center;
  margin-top: 10px;
}


3、nodeContent.tsx

import React, { useState, useEffect } from 'react';
import { Input, Switch } from 'antd';

export type nodeProps = {
  info: any;
  onChange: (val: any) => void;
};

export default ({ info, onChange }: nodeProps) => {
  const [nodeInfo, setNodeInfo] = useState<any>({});

  useEffect(() => {
    if (info.id) {
      if (!info.isHidden) {
        info.isHidden = false;
      }
      setNodeInfo(info);
    }
  }, [info.id]);

  // 改变名称
  const setNodeName = (value: string) => {
    setNodeInfo({
      ...nodeInfo,
      label: value,
    });
    onChange({
      ...nodeInfo,
      label: value,
    });
  };

  // 改变背景色
  const setNodeBg = (value: string) => {
    setNodeInfo({
      ...nodeInfo,
      nodeBg: value,
    });
    onChange({
      ...nodeInfo,
      nodeBg: value,
    });
  };

  // 是否隐藏
  const setNodeHidden = (value: boolean) => {
    setNodeInfo({
      ...nodeInfo,
      isHidden: value,
    });
    onChange({
      ...nodeInfo,
      isHidden: value,
    });
  };

  return nodeInfo.id ? (
    <div className="updatenode__controls">
      <label>名称:</label>
      <Input
        placeholder=""
        value={nodeInfo.label}
        onChange={(evt) => setNodeName(evt.target.value)}
      />
      <label className="updatenode__bglabel">背景色:</label>
      <Input type="color" value={nodeInfo.nodeBg} onChange={(evt) => setNodeBg(evt.target.value)} />
      <div className="updatenode__checkboxwrapper">
        <label>是否隐藏:</label>
        {/* <Switch checked={nodeInfo.isHidden} onChange={setNodeHidden} /> */}
        <input type='checkbox' checked={nodeInfo.isHidden} onChange={(evt) => setNodeHidden(evt.target.checked)} />
      </div>
    </div>
  ) : (
      <></>
    );
};

4、edgeContent.tsx

import React, { useState, useEffect } from 'react';
import { Input, Select, Switch } from 'antd';

const { Option } = Select;

export type edgeProps = {
  info: any;
  onChange: (val: any) => void;
};

export default ({ info, onChange }: edgeProps) => {
  const [edgeInfo, setEdgeInfo] = useState<any>({});
  const edgeTypes = [
    { label: '曲线', value: 'default' },
    { label: '直线', value: 'straight' },
    { label: '直角线', value: 'step' },
    { label: '圆滑直角线', value: 'smoothstep' },
  ];

  useEffect(() => {
    if (info.id) {
      if (info.style) {
        info.color = info.style.stroke;
      }
      if (!info.isHidden) {
        info.isHidden = false;
      }
      setEdgeInfo(info);
    }
  }, [info.id]);

  // 改变名称
  const setNodeName = (value: string) => {
    setEdgeInfo({
      ...edgeInfo,
      label: value,
    });
    onChange({
      ...edgeInfo,
      label: value,
    });
  };

  // 改变颜色
  const setNodeBg = (value: string) => {
    setEdgeInfo({
      ...edgeInfo,
      color: value,
    });
    onChange({
      ...edgeInfo,
      color: value,
    });
  };

  // 改变类型
  const changeEdgeType = (value: string) => {
    setEdgeInfo({
      ...edgeInfo,
      type: value,
    });
    onChange({
      ...edgeInfo,
      type: value,
    });
  };

  // 是否隐藏
  const setEdgeHidden = (value: boolean) => {
    setEdgeInfo({
      ...edgeInfo,
      isHidden: value,
    });
    onChange({
      ...edgeInfo,
      isHidden: value,
    });
  };

  return edgeInfo.id ? (
    <div className="updatenode__controls">
      <label>连接线名称:</label>
      <Input
        placeholder=""
        value={edgeInfo.label}
        onChange={(evt) => setNodeName(evt.target.value)}
      />
      <label className="updatenode__bglabel">连接线颜色:</label>
      <Input type="color" value={edgeInfo.color} onChange={(evt) => setNodeBg(evt.target.value)} />
      <div className="updatenode__checkboxwrapper">
        <label>连接线类型:</label>
        <Select defaultValue="曲线 " value={edgeInfo.type} onChange={changeEdgeType}>
          {edgeTypes.map((item) => (
            <Option value={item.value} key={item.value}>
              {item.label}
            </Option>
          ))}
        </Select>
      </div>
      <div className="updatenode__checkboxwrapper">
        <label>是否隐藏:</label>
        <Switch checked={edgeInfo.isHidden} onChange={setEdgeHidden} />
      </div>
    </div>
  ) : (
      <></>
    );
};

5、ResizableNodeSelected.tsx

import { memo } from 'react';
import { Handle, Position, NodeResizer } from 'reactflow';

const ResizableNodeSelected = ({ data, selected }) => {
  return (
    <>
      <NodeResizer color="#F3A011" isVisible={selected} minWidth={80} minHeight={80} />
      <div
        style={{
          // width: 60,
          // height: 60,
          padding: 10,
          // display: "flex",
          // justifyContent: "center",
          // alignItems: "center",
          // fontSize: 2
        }}
      >
        {data.label}
      </div>
      <Handle style={{ opacity: 0 }} type="source" position={Position.Top} id='a' />
      <Handle style={{ opacity: 0 }} type="source" position={Position.Right} id='b' />
      <Handle style={{ opacity: 0 }} type="source" position={Position.Bottom} id='c' />
      <Handle style={{ opacity: 0 }} type="source" position={Position.Left} id='d' />
    </>
  );
};

export default memo(ResizableNodeSelected);

6、data.js

export const nodes = [
    { "width": 80, "height": 80, "id": "13", "type": "ResizableNodeSelected", "position": { "x": 181.99158953145331, "y": 472.7199877834713 }, "data": { "label": "服务实例JVM堆大小", "id": "13", "nodeBg": "#F3A011", "isHidden": false }, "style": { "background": "#F3A011", "width": 80, "height": 80, "borderRadius": "100%", "color": "white", "fontSize": 2 }, "selected": false, "positionAbsolute": { "x": 181.99158953145331, "y": 472.7199877834713 }, "dragging": false, "hidden": false },
    { "width": 80, "height": 80, "id": "12", "type": "ResizableNodeSelected", "position": { "x": 458.51664737488375, "y": 497.7400344424826 }, "data": { "label": "服务实例JVM线程数", "id": "12", "nodeBg": "#F3A011", "isHidden": false }, "style": { "background": "#F3A011", "width": 80, "height": 80, "borderRadius": "100%", "color": "white", "fontSize": 2 }, "selected": false, "positionAbsolute": { "x": 458.51664737488375, "y": 497.7400344424826 }, "dragging": false, "hidden": false },
    { "width": 80, "height": 80, "id": "11", "type": "ResizableNodeSelected", "position": { "x": 456.86503312460417, "y": 278.4940093032253 }, "data": { "label": "应用服务平均响应时长", "id": "11", "nodeBg": "#F3A011", "isHidden": false }, "style": { "background": "#F3A011", "width": 80, "height": 80, "borderRadius": "100%", "color": "white", "fontSize": 2 }, "selected": false, "positionAbsolute": { "x": 456.86503312460417, "y": 278.4940093032253 }, "dragging": false, "hidden": false },
    { "width": 80, "height": 80, "id": "10", "type": "ResizableNodeSelected", "position": { "x": 188.70901987307826, "y": 316.5335073980088 }, "data": { "label": "存储I/O负载", "id": "10", "nodeBg": "#F3A011", "isHidden": false }, "style": { "background": "#F3A011", "width": 80, "height": 80, "borderRadius": "100%", "color": "white", "fontSize": 2 }, "selected": false, "positionAbsolute": { "x": 188.70901987307826, "y": 316.5335073980088 }, "dragging": false, "hidden": false },
    { "width": 80, "height": 80, "id": "9", "type": "ResizableNodeSelected", "position": { "x": 86.9908194969212, "y": 56.769326529302944 }, "data": { "label": "服务端点平均响应时长", "id": "9", "nodeBg": "#F3A011", "isHidden": false }, "style": { "background": "#F3A011", "width": 80, "height": 80, "borderRadius": "100%", "color": "white", "fontSize": 2 }, "selected": false, "positionAbsolute": { "x": 86.9908194969212, "y": 56.769326529302944 }, "dragging": false, "hidden": false },
    { "width": 80, "height": 80, "id": "8", "type": "ResizableNodeSelected", "position": { "x": 461.40008223448365, "y": 92.49854876752454 }, "data": { "label": "服务实例", "id": "8", "nodeBg": "#F3A011", "isHidden": false }, "style": { "background": "#F3A011", "width": 80, "height": 80, "borderRadius": "100%", "color": "white", "fontSize": 2 }, "selected": false, "positionAbsolute": { "x": 461.40008223448365, "y": 92.49854876752454 }, "dragging": false, "hidden": false },
    { "width": 80, "height": 80, "id": "7", "type": "ResizableNodeSelected", "position": { "x": -87.30759005365732, "y": 133.76253323256137 }, "data": { "label": "端点链路(动态模型)平均响应时长", "id": "7", "nodeBg": "#F3A011", "isHidden": false }, "style": { "background": "#F3A011", "width": 80, "height": 80, "borderRadius": "100%", "color": "white", "fontSize": 4 }, "selected": false, "dragging": false, "hidden": false, "positionAbsolute": { "x": -87.30759005365732, "y": 133.76253323256137 } },
    { "width": 80, "height": 80, "id": "6", "type": "ResizableNodeSelected", "position": { "x": -11.910201399135396, "y": 485.8445794117532 }, "data": { "label": "主机I/O负载", "id": "6", "nodeBg": "#F3A011", "isHidden": false }, "style": { "background": "#F3A011", "width": 80, "height": 80, "borderRadius": "100%", "color": "white", "fontSize": 4 }, "selected": false, "dragging": false, "hidden": false, "positionAbsolute": { "x": -11.910201399135396, "y": 485.8445794117532 }, "resizing": false },
    { "width": 80, "height": 80, "id": "5", "type": "ResizableNodeSelected", "position": { "x": -10, "y": 280.5 }, "data": { "label": "Mysql实例慢查询", "id": "5", "nodeBg": "#F3A011", "isHidden": false }, "style": { "background": "#F3A011", "width": 80, "height": 80, "borderRadius": "100%", "color": "white", "fontSize": 4 }, "selected": false, "dragging": false, "hidden": false, "positionAbsolute": { "x": -10, "y": 280.5 } },
    { "width": 80, "height": 80, "id": "4", "type": "ResizableNodeSelected", "position": { "x": -282.5, "y": 453 }, "data": { "label": "主机内存使用率", "id": "4", "nodeBg": "#F3A011", "isHidden": false }, "style": { "background": "#F3A011", "width": 80, "height": 80, "borderRadius": "100%", "color": "white", "fontSize": 4 }, "selected": false, "dragging": false, "hidden": false, "positionAbsolute": { "x": -282.5, "y": 453 } },
    { "width": 80, "height": 80, "id": "3", "type": "ResizableNodeSelected", "position": { "x": -275, "y": 283 }, "data": { "label": "主机CPU使用率", "id": "3", "nodeBg": "#F3A011", "isHidden": false }, "style": { "background": "#F3A011", "width": 80, "height": 80, "borderRadius": "100%", "color": "white", "fontSize": 4 }, "selected": false, "positionAbsolute": { "x": -275, "y": 283 }, "dragging": false, "hidden": false },
    { "width": 80, "height": 80, "id": "2", "type": "ResizableNodeSelected", "position": { "x": -271, "y": 133.5 }, "data": { "label": "消息中间件堆积数", "id": "2", "nodeBg": "#F3A011", "isHidden": false }, "style": { "background": "#F3A011", "width": 80, "height": 80, "borderRadius": "100%", "color": "white", "fontSize": 4 }, "selected": false, "dragging": false, "hidden": false, "positionAbsolute": { "x": -271, "y": 133.5 } }
];

export const edges = [
    { "style": { "stroke": "#116F97" }, "type": "smoothstep", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "4", "sourceHandle": "a", "target": "5", "targetHandle": "d", "id": "reactflow__edge-4a-5d", "selected": false, "hidden": true },
    { "style": { "stroke": "#116F97" }, "type": "straight", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "4", "sourceHandle": "b", "target": "5", "targetHandle": "c", "id": "reactflow__edge-4b-5c", "selected": false, "hidden": true },
    { "style": { "stroke": "#116F97" }, "type": "default", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "5", "sourceHandle": "d", "target": "4", "targetHandle": "a", "id": "reactflow__edge-5d-4a", "selected": false, "hidden": true },
    { "style": { "stroke": "#116F97" }, "type": "default", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "2", "sourceHandle": "b", "target": "7", "targetHandle": "d", "id": "reactflow__edge-2b-7d", "selected": false, "label": "模型间接关系", "hidden": false },
    { "style": { "stroke": "#116F97" }, "type": "default", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "5", "sourceHandle": "d", "target": "3", "targetHandle": "b", "id": "reactflow__edge-5d-3b", "selected": false, "label": "模型直接关系", "hidden": false },
    { "style": { "stroke": "#116F97" }, "type": "default", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "6", "sourceHandle": "a", "target": "5", "targetHandle": "c", "id": "reactflow__edge-6a-5c", "selected": false, "label": "模型直接关系", "hidden": false },
    { "style": { "stroke": "#116F97" }, "type": "smoothstep", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "5", "sourceHandle": "a", "target": "7", "targetHandle": "c", "id": "reactflow__edge-5a-7c", "selected": false, "label": "模型直接关系", "hidden": false },
    { "style": { "stroke": "#116F97" }, "type": "smoothstep", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "4", "sourceHandle": "a", "target": "5", "targetHandle": "c", "id": "reactflow__edge-4a-5c", "selected": false, "label": "模型直接关系", "hidden": false },
    { "style": { "stroke": "#116F97" }, "type": "step", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "5", "sourceHandle": "b", "target": "7", "targetHandle": "b", "id": "reactflow__edge-5b-7b", "selected": false, "label": "模型直接关系", "hidden": false },
    { "style": { "stroke": "#116F97" }, "type": "smoothstep", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "7", "sourceHandle": "b", "target": "9", "targetHandle": "d", "id": "reactflow__edge-7b-9d", "selected": false, "hidden": false },
    { "style": { "stroke": "#116F97" }, "type": "smoothstep", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "10", "sourceHandle": "d", "target": "5", "targetHandle": "b", "id": "reactflow__edge-10d-5b", "selected": false, "label": "模型直接关系", "hidden": false },
    { "style": { "stroke": "#116F97" }, "type": "smoothstep", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "8", "sourceHandle": "c", "target": "11", "targetHandle": "a", "id": "reactflow__edge-8c-11a", "selected": false, "label": "模型直接关系", "hidden": false },
    { "style": { "stroke": "#116F97" }, "type": "straight", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "12", "sourceHandle": "a", "target": "11", "targetHandle": "c", "id": "reactflow__edge-12a-11c", "selected": false, "label": "模型直接关系", "hidden": false },
    { "style": { "stroke": "#116F97" }, "type": "smoothstep", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "13", "sourceHandle": "b", "target": "11", "targetHandle": "d", "id": "reactflow__edge-13b-11d", "selected": false, "label": "模型直接关系", "hidden": false },
    { "style": { "stroke": "#116F97" }, "type": "smoothstep", "markerEnd": { "type": "arrowclosed", "color": "#116F97" }, "source": "9", "sourceHandle": "b", "target": "11", "targetHandle": "d", "id": "reactflow__edge-9b-11d", "selected": false, "label": "模型直接关系", "hidden": false }
]

// "viewport": { "x": 654.5507940552135, "y": -54.945769269730704, "zoom": 1.6908994642667994 } }

7、package.json

{
  "name": "my-react-flow-app",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview"
  },
  "dependencies": {
    "antd": "^5.7.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "reactflow": "^11.7.4"
  },
  "devDependencies": {
    "@types/react": "^18.2.14",
    "@types/react-dom": "^18.2.6",
    "@vitejs/plugin-react": "^4.0.1",
    "eslint": "^8.44.0",
    "eslint-plugin-react": "^7.32.2",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-react-refresh": "^0.4.1",
    "vite": "^4.4.0"
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/801560.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

适用于虚拟环境的免费企业备份软件

多年来&#xff0c;许多行业严重依赖物理服务器提供计算资源——你可以想象到巨大的服务器机房和笨重的服务器的场景。 然而&#xff0c;随着业务快速增长&#xff0c;许多组织发现物理服务器已经无法有效利用计算资源。因此&#xff0c;为了节省成本&#xff0c;引入了虚拟服…

STL中 vector常见函数用法和迭代器失效的解决方案【C++】

文章目录 size && capacityreserveresizeempty迭代器begin和end push_back &&pop_backinsert && erasefindswap[ ]范围for遍历vector迭代器失效问题 size && capacity #include <iostream> #include <vector> using namespace st…

一文搞懂如何在群晖NAS中使用cpolar实现【内网穿透】

文章目录 1.1前言2.如何在群晖nas中使用cpolar内网穿透2.1第一步——进入套件中心2.2第二步——管理cpolar套件2.3第三步——建立专属的数据隧道2.4 第四步——查看本地数据隧道状态是否激活 3.结语 1.1前言 今天&#xff0c;我们来为大家介绍&#xff0c;如何在群晖系统中&am…

设计模式-命令模式在Java中的使用示例-桌面程序自定义功能键

场景 欲开发一个桌面版应用程序&#xff0c;该应用程序为用户提供了一系列自定义功能键&#xff0c;用户可以通过这些功能键来实现一些快捷操作。 用户可以将功能键和相应功能绑定在一起&#xff0c;还可以根据需要来修改功能键的设置&#xff0c;而且系统在未来可能还会增加…

做外贸工厂跳过我联系了客户

在我十多年外贸创业历程里&#xff0c;遇过不少想跳过我直接和客户联系的供应商。 譬如给客户发邮件&#xff0c;悄咪咪将我从抄送里删掉。又或者偷偷将名片塞在货柜里&#xff0c;期望客户一开箱就能看到。 然而这么多年过去&#xff0c;一次让他们得逞的机会都没有。每一次…

C++多线程编程(第三章 案例1,使用互斥锁+ list模拟线程通信)

主线程和子线程进行list通信&#xff0c;要用到互斥锁&#xff0c;避免同时操作 1、封装线程基类XThread控制线程启动和停止&#xff1b; 2、模拟消息服务器线程&#xff0c;接收字符串消息&#xff0c;并模拟处理&#xff1b; 3、通过Unique_lock和mutex互斥方位list 消息队列…

【力扣每日一题】2023.7.28 并行课程3

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 题目给我们课程表以及每门课需要学习的时间&#xff0c;我们可以同时学任意数量的课程&#xff0c;但是学习的条件是先修课程都已经学完了…

【雕爷学编程】MicroPython动手做(02)——尝试搭建K210开发板的IDE环境6

#尝试搭建K210的Micropython开发环境&#xff08;Win10&#xff09; #实验程序之六&#xff1a;测试Microphone阵列算法 #尝试搭建K210的Micropython开发环境&#xff08;Win10&#xff09; #实验程序之六&#xff1a;测试Microphone阵列算法from Maix import MIC_ARRAY as mi…

MySQL运维:从全备sql文件中提取指定表的数据并恢复

目录 一、运行环境 二、需求说明 三、思路分析 五、具体方案 六、恢复表数据 一、运行环境 系统&#xff1a;CentOS7.3 数据库&#xff1a;MySQL 8.0.21 二、需求说明 线上有个表的数据被误操作了很多&#xff0c;无法通过bin-log进行具体的恢复。所以当前我们需要从全…

Spring Boot 应用程序生命周期扩展点妙用

文章目录 前言1. 应用程序生命周期扩展点2. 使用场景示例2.1 SpringApplicationRunListener2.2 ApplicationEnvironmentPreparedEvent2.3 ApplicationPreparedEvent2.4 ApplicationStartedEvent2.5 ApplicationReadyEvent2.6 ApplicationFailedEvent2.7 ApplicationRunner 3. 参…

产品能力|AIRIOT可视化组态引擎如何应用于物联业务场景中

在物联网的业务应用场景中&#xff0c;可视化组态是一个必不可少的功能需求。不同的行业场景&#xff0c;都需要将物联设备采集的数据和业务场景状态进行直观的可视化展示&#xff0c;供使用者进行分析或决策。如工艺流程用能监测、3D场景构建、能耗趋势场景报警联动、重点设备…

RAD-NeRF模型

问题1&#xff1a; 添加在以下的参数里添加bin_size0 问题2&#xff1a; 更行GLIBC_2.29_glibc_2_29.so_xihuanyuye的博客-CSDN博客

如何使用Crank给我们的类库做基准测试

背景 当我们写了一个类库提供给别人使用时&#xff0c;我们可能会对它做一些基准测试来测试一下它的性能指标&#xff0c;好比内存分配等。 在 .NET 的世界中&#xff0c;用 BenchmarkDotNet 来做这件事是非常不错的选择&#xff0c;我们只要写少量的代码就可以在本地运行基准…

【小白必看】Python爬取NBA球员数据示例

文章目录 前言导入需要的库和模块设置请求头和请求地址发送HTTP请求并获取响应处理响应结果解析数据将结果保存到文件完整代码详细解析 运行效果结束语 前言 使用 Python 爬取 NBA 球员数据的示例代码。通过发送 HTTP 请求&#xff0c;解析 HTML 页面&#xff0c;然后提取出需要…

imgcat命令行查看图片

背景 昨天在哔哩哔哩上看到了mac控制台工具imgcat 可以实现在控制台查看图片&#xff0c;我觉得太酷炫了&#xff01;于是手动的安利一下。 下载工具 curl "https://iterm2.com/utilities/imgcat" > imgcat执行权限 chmod x imgcat第一次使用 ./imgcat ~/img…

RocketMQ发送消息还有这种坑?遇到SYSTEM_BUSY不重试?

这里是weihubeats,觉得文章不错可以关注公众号小奏技术&#xff0c;文章首发。拒绝营销号&#xff0c;拒绝标题党 RocketMQ版本 5.1.0 背景 最近线上的RocketMQ集群遇到了如下问题&#xff0c;业务方的小伙伴反馈问题&#xff0c;说出现了 MQBrokerException&#xff1a;CO…

C++数据结构笔记(10)递归实现二叉树的三序遍历

对于三种遍历方式来说&#xff0c;均为先左后右&#xff01;区别在于根结点的位置顺序 先序遍历&#xff1a;根——左——右 中序遍历&#xff1a;左——根——右 后序遍历&#xff1a;左——右——根 &#xff08;所谓先中后的顺序&#xff0c;是指根结点D先于子树还是后于…

力扣算法练习(四)

1.盛水最多的容器(11) 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#xff1a;…

Vue 常用指令 v-on 自定义参数和事件修饰符

自定义参数就是可以在触发事件的时候传入自定义的值。 文本框&#xff0c;绑定了一个按钮事件&#xff0c;对应的逻辑是sayhi&#xff0c;现在无论按下什么按钮都会触发这个sayhi。但是实际上不是所有的按钮都会触发&#xff0c;只会限定某一些按钮&#xff0c;最常见的按钮就…

linux下nginx的安装和使用

文章目录 &#x1f4d2;安装nginx1️⃣上传到对应目录2️⃣解压nginx3️⃣检查是否启动成功 &#x1f4d2;使用nginx1️⃣简单的反向代理2️⃣介绍location配置中root和alias的区别 &#x1f4d2;安装nginx 官网下载: https://nginx.org/ 我这里自己下载的是稳定版本 nginx-1.…