简单的来渲染数据,是非常简单的
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吧
网友评论