美文网首页
X6和React的结合

X6和React的结合

作者: skoll | 来源:发表于2022-01-17 16:34 被阅读0次

    简单的来渲染数据,是非常简单的

    1 .关键是拿react渲染出的画面,导出在导入,能不能再次在X6中还原
    2 .react结合的操作

    1 .可以不用记那么多的api了,任何复杂的,具有交互的操作以及想要的表现都可以用react来写,方便的一批,可以无缝切换到之前的逻辑里面
    2 .也就不用仅仅是改变一个属性都要查半天文档了...
    

    3 .所有东西都在一个svg标签里面

    改变样式的几种方式

    1 .直接在css中改

    .x6-node-selected .node {
      border-color: #1890ff;
      border-radius: 2px;
      box-shadow: 0 0 0 4px #d4e8fe;
    }
    

    2 .可以不做这样的操作

      graph.current.on('edge:mouseenter',({edge})=>{
                edge.attr('line/stroke','#31d0c6')
                edge.addTools([
                    //'source-arrowhead',
                    //'target-arrowhead',
                    //不加这俩箭头啊,加了奇丑无比
                    {
                        name:'button-remove',
                        args:{
                            distance:-30
                        }
                    }
                ])
            })
    
            graph.current.on('edge:mouseleave',({edge})=>{
                edge.attr('line/stroke','#A2B1C3')
                edge.removeTools()
            })
    

    test.css

    .node{
        display: flex;
        align-items: center;
        width: 200px;
        height: 40px;
        background-color: #fff;
        border: 1px solid #c2c8d5;
        border-left: 4px solid #5F95FF;
        border-radius: 4px;
        box-shadow: 0 2px 5px 1px rgba(0, 0, 0, 0.06);
    }
    
    .node img {
        width: 20px;
        height: 20px;
        flex-shrink: 0;
        margin-left: 8px;
      }
    .node .label {
        display: inline-block;
        flex-shrink: 0;
        width: 104px;
        margin-left: 8px;
        color: #666;
        font-size: 12px;
    }
    .node .status {
        flex-shrink: 0;
    }
    .node.success {
    border-left: 4px solid #52c41a;
    }
    .node.failed {
    border-left: 4px solid #ff4d4f;
    }
    .node.running .status img {
    animation: spin 1s linear infinite;
    }
    
    
      /* 有关选择之后的样式 */
    .x6-node-selected .node {
    border-color: #1890ff;
    border-radius: 2px;
    box-shadow: 0 0 0 4px #d4e8fe;
    }
    .x6-node-selected .node.success {
    border-color: #52c41a;
    border-radius: 2px;
    box-shadow: 0 0 0 4px #ccecc0;
    }
    .x6-node-selected .node.failed {
    border-color: #ff4d4f;
    border-radius: 2px;
    box-shadow: 0 0 0 4px #fedcdc;
    }
    
    /*线被触摸的时候的样式改变 */
    
    /* 按理说这些样式应该是固定的,因为几乎想要改变样式的时候都要这么做,他把一些原来需要了解api才能实现的方法做成了仅仅需要操作css就能实现效果的操作了 */
    .x6-edge:hover path:nth-child(2){
    stroke: #1890ff;
    stroke-width: 1px;
    }
      
    .x6-edge-selected path:nth-child(2){
    stroke: #1890ff;
    stroke-width: 1.5px !important;
    }
      
    @keyframes running-line {
    to {
        stroke-dashoffset: -1000;
    }
    }
    @keyframes spin {
    from {
        transform: rotate(0deg);
    }
    to {
        transform: rotate(360deg);
    }
    }
    

    react.js

    // 这是用来测试react渲染节点,并且导入再导入的效果
    import React,{useEffect,useRef,useState} from 'react'
    import { Graph,Node,Path,Cell } from '@antv/x6'
    import '@antv/x6-react-shape'
    import './test.css'
    
    const imageLists = {
        logo: 'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*evDjT5vjkX0AAAAAAAAAAAAAARQnAQ',
        success:
          'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*6l60T6h8TTQAAAAAAAAAAAAAARQnAQ',
        failed:
          'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*SEISQ6My-HoAAAAAAAAAAAAAARQnAQ',
        running:
          'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*t8fURKfgSOgAAAAAAAAAAAAAARQnAQ',
    }
    
    function AlgoNode (props){
        // 这里的数据交互都是要先拿id选到node,然后getData方法
        const {node}=props
        const data=node.getData()
        const [state,setState]=useState(data)
        const {label,status='default'}=state
        // 这种对象结构还可以设置默认值的,有点流批
    
        function handleClick(){
          // 模拟操作导致的ui变化
          setState({
            label:"哈哈",
            status:'running'
          })
        }
        return (
            <div className={`node ${status}`}>
                {/* 这里直接就给他上状态了 */}
                <img 
                    src={imageLists.logo}
                    alt="节点图标icon"
                    onClick={handleClick}
                    ></img>
                <span className='label'>{label}</span>
                <span className='status'>
                    {status==='success'&&<img src={imageLists.success} alt="success"></img>}
                    {status==='failed'&&<img src={imageLists.failed} alt="failed"></img>}
                    {status==='running'&&<img src={imageLists.running} alt="running"></img>}
                </span>
            </div>
        )
    }
    //自定义了一个Node节点
    
    Graph.registerNode(
        'dag-node',
        {
            inherit:'react-shape',
            //继承自官方给的框架
            width:180,
            height:36,
            // 这里一定要给大小,不然桩的位置有问题.
            component:<AlgoNode/>,
            ports: {
                groups: {
                  top: {
                    position: 'top',
                    attrs: {
                      circle: {
                        r: 4,
                        magnet: true,
                        stroke: '#C2C8D5',
                        strokeWidth: 1,
                        fill: '#fff',
                      },
                    },
                  },
                  bottom: {
                    position: 'bottom',
                    attrs: {
                      circle: {
                        r: 4,
                        magnet: true,
                        stroke: '#C2C8D5',
                        strokeWidth: 1,
                        fill: '#fff',
                      },
                    },
                  },
                },
              },
        },
        true
    )
    // 注册到X6框架上:这里面最重要的是给框架注册了这个节点,功能上来说就是加了连接桩,本来自己写的是没有的
    
    Graph.registerEdge(
        'dag-edge',
        {
          inherit: 'edge',
          attrs: {
            line: {
              stroke: '#C2C8D5',
              strokeWidth: 1,
              targetMarker: null,
            },
          },
        },
        true,
      )
    //   
    
    const nodeStatusList=[
        [
            {
              id: '1',
              status: 'running',
            },
            {
              id: '2',
              status: 'default',
            },
            {
              id: '3',
              status: 'default',
            },
            {
              id: '4',
              status: 'default',
            },
          ],
          [
            {
              id: '1',
              status: 'success',
            },
            {
              id: '2',
              status: 'running',
            },
            {
              id: '3',
              status: 'default',
            },
            {
              id: '4',
              status: 'default',
            },
          ],
          [
            {
              id: '1',
              status: 'success',
            },
            {
              id: '2',
              status: 'success',
            },
            {
              id: '3',
              status: 'running',
            },
            {
              id: '4',
              status: 'running',
            },
          ],
          [
            {
              id: '1',
              status: 'success',
            },
            {
              id: '2',
              status: 'success',
            },
            {
              id: '3',
              status: 'success',
            },
            {
              id: '4',
              status: 'failed',
            },
          ],
    ]
    //所有数据的状态,可以看成是每次我们拖拽数据产生的操作,这些是绑定好的数据
    
    const nodeData=[
        {
          "id": "1",
          "shape": "dag-node",
          "x": 290,
          "y": 110,
          "data": {
            "label": "读数据",
            "status": "success"
            // 节点相关的数据他都是放在这里
          },
          "ports": [
            {
              "id": "1-1",
              "group": "bottom"
            }
          ]
        },
        {
          "id": "2",
          "shape": "dag-node",
          "x": 290,
          "y": 225,
          "data": {
            "label": "逻辑回归",
            "status": "success"
          },
          "ports": [
            {
              "id": "2-1",
              "group": "top"
            },
            {
              "id": "2-2",
              "group": "bottom"
            },
            {
              "id": "2-3",
              "group": "bottom"
            }
          ]
        },
        {
          "id": "3",
          "shape": "dag-node",
          "x": 170,
          "y": 350,
          "data": {
            "label": "模型预测",
            "status": "success"
          },
          "ports": [
            {
              "id": "3-1",
              "group": "top"
            },
            {
              "id": "3-2",
              "group": "bottom"
            }
          ]
        },
        {
          "id": "4",
          "shape": "dag-node",
          "x": 450,
          "y": 350,
          "data": {
            "label": "读取参数",
            "status": "success"
          },
          "ports": [
            {
              "id": "4-1",
              "group": "top"
            },
            {
              "id": "4-2",
              "group": "bottom"
            }
          ]
        },
        {
          "id": "5",
          "shape": "dag-edge",
          "source": {
            "cell": "1",
            "port": "1-1"
          },
          "target": {
            "cell": "2",
            "port": "2-1"
          },
          "zIndex": 0
        },
        {
          "id": "6",
          "shape": "dag-edge",
          "source": {
            "cell": "2",
            "port": "2-2"
          },
          "target": {
            "cell": "3",
            "port": "3-1"
          },
          "zIndex": 0
        },
        {
          "id": "7",
          "shape": "dag-edge",
          "source": {
            "cell": "2",
            "port": "2-3"
          },
          "target": {
            "cell": "4",
            "port": "4-1"
          },
          "zIndex": 0
        }
    ]
    //这里才是数据,每一个节点的里面的data保存着我们节点携带的数据,shape这里直接是最新的数据了
    
    export default function Test(props){
        const graph=useRef()
        useEffect(()=>{
            graph.current=new Graph({
                container:document.getElementById('container'),
                width:1000,
                height:1000,
                panning: {
                    enabled: true,
                    eventTypes: ['leftMouseDown', 'mouseWheel'],
                },
                selecting: {
                  enabled: true,
                  multiple: true,
                  rubberEdge: true,
                  rubberNode: true,
                  modifiers: 'shift',
                  rubberband: true,
                },
                
              })
            // 把nodeData的数据应用到x6里面:初始化操作.插入是要这个操作
            let cells=[]
            nodeData.forEach((item)=>{
                if(item.shape==='dag-node'){
                    cells.push(graph.current.createNode(item))                
                }else{
                    cells.push(graph.current.createEdge(item))
                }
            })
            graph.current.resetCells(cells)
            // console.log(cells)
            // showNodeStatus(nodeStatusList)
        },[])
    
        function handleDownLoad(){
            const data=graph.current.toJSON()
            console.log(data)
        }
    
        function handleInsert(data){
          // graph.current.fromJSON(data.graph)
          // 导出是可以的.
          showNodeStatus(nodeStatusList)
        }
    
        function showNodeStatus(statusList){
           const status=statusList.shift()
    
           status.forEach((item)=>{
             const {id,status}=item
             const node=graph.current.getCellById(id)
             //获取当前的node
             const data=node.getData()
             node.setData({
               ...data,
               status:status
             })
             console.log(node.getData())  
           })
        }
    
        return (
            <>
               <div className="content" id="container">
               
               </div>
               <div>
                 <button onClick={handleDownLoad}>导出数据</button>
                 <button onClick={handleInsert}>导入数据</button>
               </div>
            </>
            
        )
    }
    

    注意的问题

    1 .性能问题
    2 .实现原理:SVG中的foreignObject元素,在这个元素中可以内嵌任何XHTML元素,所以我们需要借助这个元素来渲染html元素和React组件到需要的位置
    3 .

    渲染html的方式

    1 .最关键问题:当html属性值为HTML或者函数的时候.将不能通过graph.toJSON()方法导出画布数据,
    2 .解决方法1:注册HTML元素

    Graph.registerHTMLComponent('my-html1', elem)
    

    3 .解决方法2:注册HTML元素的函数

    Graph.registerHTMLComponent('my-html2', (node) => {
      const data = node.getData()
      if (data.flag) {
        return document.createElement('div')
      }
      return document.createElement('p')
    })
    

    4 .将节点的html属性指定为注册的组件名

    graph.addNode({
      x: 40,
      y: 40,
      width: 100,
      height: 40,
      shape: 'html',
      html: 'my-html1',
    })
    

    渲染React的方式

    1 .上面讲的是全部导入数据的操作.下面这个是拖拽生成的操作

    graph.addNode({
      x: 40,
      y: 40,
      width: 100,
      height: 40,
      shape: 'react-shape',
      component: <MyComponent text="Hello" />,
    })
    

    2 .节点渲染时讲自动为React组件注入一个名为node的属性,属性值为当前节点的实例.
    3 .注意,节点每次重新渲染都将触发React组件重新渲染,这里要做处理.
    4 .提升挂载性能.usePortal.

    挂载性能的usePortal

    // 这是用来测试react渲染节点,并且导入再导入的效果
    import React,{useEffect,useRef,useState} from 'react'
    import ReactDOM from 'react-dom'
    import { Graph,Markup,Path,Cell } from '@antv/x6'
    import {usePortal,ReactShape} from '@antv/x6-react-shape'
    
    import './test.css'
    
    const imageLists = {
        logo: 'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*evDjT5vjkX0AAAAAAAAAAAAAARQnAQ',
        success:
          'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*6l60T6h8TTQAAAAAAAAAAAAAARQnAQ',
        failed:
          'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*SEISQ6My-HoAAAAAAAAAAAAAARQnAQ',
        running:
          'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*t8fURKfgSOgAAAAAAAAAAAAAARQnAQ',
    }
    
    const UNIQ_GRAPH_ID="path_id_1"
    //任意字符串,是画布的唯一标识,一张画布的全部用这个id创建
    // 看这个意思是全部节点可以分批同时创建
    
    function AlgoNode (props){
        // 这里的数据交互都是要先拿id选到node,然后getData方法
        const [state,setState]=useState(props.data)
        const {label='success',status='default'}=state
        // 这种对象结构还可以设置默认值的,有点流批
    
        function handleClick(){
          // 模拟操作导致的ui变化
          setState({
            label:"哈哈",
            status:'running'
          })
        }
        return (
            <div className={`node ${status}`}>
                {/* 这里直接就给他上状态了 */}
                <img 
                    src={imageLists.logo}
                    alt="节点图标icon"
                    onClick={handleClick}
                    ></img>
                <span className='label'>{label}</span>
                <span className='status'>
                    {status==='success'&&<img src={imageLists.success} alt="success"></img>}
                    {status==='failed'&&<img src={imageLists.failed} alt="failed"></img>}
                    {status==='running'&&<img src={imageLists.running} alt="running"></img>}
                </span>
            </div>
        )
    }
    //自定义了一个Node节点
    
    Graph.registerNode(
        'dag-node',
        {
            inherit:'react-shape',
            //继承自官方给的框架
            width:180,
            height:36,
            portMarkup: [Markup.getForeignObjectMarkup()],
            // 这里一定要给大小,不然桩的位置有问题.
            ports: {
                groups: {
                  top: {
                    position: 'top',
                    attrs: {
                      circle: {
                        r: 4,
                        magnet: true,
                        stroke: '#C2C8D5',
                        strokeWidth: 1,
                        fill: '#fff',
                      },
                    },
                  },
                  bottom: {
                    position: 'bottom',
                    attrs: {
                      circle: {
                        r: 4,
                        magnet: true,
                        stroke: '#C2C8D5',
                        strokeWidth: 1,
                        fill: '#fff',
                      },
                    },
                  },
                },
            },
            component:<AlgoNode/>,
            // 为啥这个样式没有用到.索性桩也用react实现吧.就是不知道这种拖拽生成线的时候行不行
        },
        true
    )
    // 注册到X6框架上:这里面最重要的是给框架注册了这个节点,功能上来说就是加了连接桩,本来自己写的是没有的
    
    Graph.registerEdge(
        'dag-edge',
        {
          inherit: 'edge',
          attrs: {
            line: {
              stroke: '#C2C8D5',
              strokeWidth: 1,
              targetMarker: null,
            },
          },
        },
        true,
      )
    
      Graph.registerConnector(
        'algo-connector',
        (s, e) => {
          const offset = 4
          const deltaY = Math.abs(e.y - s.y)
          const control = Math.floor((deltaY / 3) * 2)
      
          const v1 = { x: s.x, y: s.y + offset + control }
          const v2 = { x: e.x, y: e.y - offset - control }
      
          return Path.normalize(
            `M ${s.x} ${s.y}
             L ${s.x} ${s.y + offset}
             C ${v1.x} ${v1.y} ${v2.x} ${v2.y} ${e.x} ${e.y - offset}
             L ${e.x} ${e.y}
            `,
          )
        },
        true,
      )
    
    const nodeStatusList=[
        [
            {
              id: '1',
              status: 'running',
            },
            {
              id: '2',
              status: 'default',
            },
            {
              id: '3',
              status: 'default',
            },
            {
              id: '4',
              status: 'default',
            },
          ],
          [
            {
              id: '1',
              status: 'success',
            },
            {
              id: '2',
              status: 'running',
            },
            {
              id: '3',
              status: 'default',
            },
            {
              id: '4',
              status: 'default',
            },
          ],
          [
            {
              id: '1',
              status: 'success',
            },
            {
              id: '2',
              status: 'success',
            },
            {
              id: '3',
              status: 'running',
            },
            {
              id: '4',
              status: 'running',
            },
          ],
          [
            {
              id: '1',
              status: 'success',
            },
            {
              id: '2',
              status: 'success',
            },
            {
              id: '3',
              status: 'success',
            },
            {
              id: '4',
              status: 'failed',
            },
          ],
    ]
    //所有数据的状态,可以看成是每次我们拖拽数据产生的操作,这些是绑定好的数据
    
    const nodeData=[
        {
          "id": "1",
          "shape": "dag-node",
          "x": 290,
          "y": 110,
          "data": {
            "label": "读数据",
            "status": "success"
            // 节点相关的数据他都是放在这里
          },
          "ports": [
            {
              "id": "1-1",
              "group": "bottom"
            }
          ]
        },
        {
          "id": "2",
          "shape": "dag-node",
          "x": 290,
          "y": 225,
          "data": {
            "label": "逻辑回归",
            "status": "success"
          },
          "ports": [
            {
              "id": "2-1",
              "group": "top"
            },
            {
              "id": "2-2",
              "group": "bottom"
            },
            {
              "id": "2-3",
              "group": "bottom"
            }
          ]
        },
        {
          "id": "3",
          "shape": "dag-node",
          "x": 170,
          "y": 350,
          "data": {
            "label": "模型预测",
            "status": "success"
          },
          "ports": [
            {
              "id": "3-1",
              "group": "top"
            },
            {
              "id": "3-2",
              "group": "bottom"
            }
          ]
        },
        {
          "id": "4",
          "shape": "dag-node",
          "x": 450,
          "y": 350,
          "data": {
            "label": "读取参数",
            "status": "success"
          },
          "ports": [
            {
              "id": "4-1",
              "group": "top"
            },
            {
              "id": "4-2",
              "group": "bottom"
            }
          ]
        },
        {
          "id": "5",
          "shape": "dag-edge",
          "source": {
            "cell": "1",
            "port": "1-1"
          },
          "target": {
            "cell": "2",
            "port": "2-1"
          },
          "zIndex": 0
        },
        {
          "id": "6",
          "shape": "dag-edge",
          "source": {
            "cell": "2",
            "port": "2-2"
          },
          "target": {
            "cell": "3",
            "port": "3-1"
          },
          "zIndex": 0
        },
        {
          "id": "7",
          "shape": "dag-edge",
          "source": {
            "cell": "2",
            "port": "2-3"
          },
          "target": {
            "cell": "4",
            "port": "4-1"
          },
          "zIndex": 0
        }
    ]
    //这里才是数据,每一个节点的里面的data保存着我们节点携带的数据,shape这里直接是最新的数据了
    
    export default function Test(props){
        const graph=useRef()
        const [Protal,setGraph]=usePortal(UNIQ_GRAPH_ID)
        useEffect(()=>{
            const graph=new Graph({
                container:document.getElementById('container'),
                width:1000,
                height:1000,
                panning: {
                    enabled: true,
                    eventTypes: ['leftMouseDown', 'mouseWheel'],
                },
                selecting: {
                  enabled: true,
                  multiple: true,
                  rubberEdge: true,
                  rubberNode: true,
                  modifiers: 'shift',
                  rubberband: true,
                },
                onPortRendered(args){
                  // 这里是所有桩子的回调,自定义样式都要走这里
                  //还是这种方式没有读到args属性的原因..还是要找出这个问题
                  const selectors = args.contentSelectors
                  const container = selectors && selectors.foContent
                  if(container){
                    ReactDOM.render((<div>-</div>),container)
                  }
                },
                connecting: {
                  snap: true,
                  allowBlank: false,
                  allowLoop: false,
                  highlight: true,
                  connector: 'algo-connector',
                  connectionPoint: 'anchor',
                  anchor: 'center',
                  validateMagnet({ magnet }) {
                    return magnet.getAttribute('port-group') !== 'top'
                  },
                  createEdge() {
                    return graph.createEdge({
                      shape: 'dag-edge',
                      attrs: {
                        line: {
                          strokeDasharray: '5 5',
                        },
                      },
                      zIndex: -1,
                    })
                  },
                },
                
              })
            // 把nodeData的数据应用到x6里面:初始化操作.插入是要这个操作
            setGraph(graph)
    
            const nodes=nodeData.map(dataItem=>{
              if(dataItem.shape==='dag-node'){
                // return new ReactShape({
                //   view:UNIQ_GRAPH_ID,
                //   ...dataItem,
                //   component:<AlgoNode data={dataItem.data}/>,
                // })
                return graph.createNode({
                  ...dataItem,
                  component:<AlgoNode data={dataItem.data}/>,
                  view:UNIQ_GRAPH_ID
                })
                //关键竟然还是这个操作,要return,还有component这个参数必须有
              }else{
                return graph.createEdge(dataItem)
              }
            })
            graph.addCell(nodes)
        },[setGraph])
    
        function handleDownLoad(){
            const data=graph.current.toJSON()
            console.log(data)
        }
    
        function handleInsert(data){
          // graph.current.fromJSON(data.graph)
          // 导出是可以的.
          showNodeStatus(nodeStatusList)
        }
    
        function showNodeStatus(statusList){
           const status=statusList.shift()
    
           status.forEach((item)=>{
             const {id,status}=item
             const node=graph.current.getCellById(id)
             //获取当前的node
             const data=node.getData()
             node.setData({
               ...data,
               status:status
             })
             console.log(node.getData())  
           })
        }
    
        return (
            <>
               <div className="content" id="container">
                <Protal></Protal>
               </div>
               <div>
                 <button onClick={handleDownLoad}>导出数据</button>
                 <button onClick={handleInsert}>导入数据</button>
               </div>
            </>
            
        )
    }
    

    又发现了一个问题,自定义框和自定义桩一起操作的时候有问题

    1 .自定义桩无法触发拖拽拉线.感觉是鼠标覆盖上去,直接响应下方的整体的node了,桩子的没有被触发
    2 .桩子的mouseDown,没有触发...
    3 .看结构明明是分开两个渲染的啊.
    4 .看下github的issue吧

    相关文章

      网友评论

          本文标题:X6和React的结合

          本文链接:https://www.haomeiwen.com/subject/gcdacrtx.html