美文网首页
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