mirror of
https://gitee.com/antv/g6.git
synced 2024-12-04 20:59:15 +08:00
708 lines
19 KiB
TypeScript
708 lines
19 KiB
TypeScript
import React, { useEffect } from 'react';
|
|
import G6 from '../../../src';
|
|
import { IGraph } from '../../../src/interface/graph';
|
|
import { GraphData } from '../../../src/types';
|
|
|
|
|
|
interface IModelNodeShapeCfg {
|
|
config: {
|
|
width: number;
|
|
headerHeight: number;
|
|
fieldHeight: number;
|
|
styleConfig: {
|
|
default: {
|
|
node: any;
|
|
edge: any;
|
|
};
|
|
active: {
|
|
node: any;
|
|
edge: any;
|
|
};
|
|
selected: {
|
|
node: any;
|
|
edge: any;
|
|
};
|
|
};
|
|
}
|
|
data: {
|
|
label: string;
|
|
key: string;
|
|
fields: IField[];
|
|
name: string;
|
|
}
|
|
style: any
|
|
isKeySharp?: boolean
|
|
active?: boolean
|
|
selected?: boolean
|
|
into?: boolean
|
|
out?: boolean
|
|
hide?: boolean
|
|
inactive?: boolean
|
|
}
|
|
interface IField {
|
|
key: string
|
|
label: string
|
|
originalKey: string
|
|
type: string
|
|
isForeign?: boolean
|
|
relationModel?: string
|
|
}
|
|
|
|
const setNodeStateAttr = (state, s, cfg) => {
|
|
Object.entries(cfg.config.styleConfig[state].node).forEach(([k, v]) => {
|
|
s.attr(k, v)
|
|
})
|
|
}
|
|
|
|
const isEng = (str) => {
|
|
for (let i = 0; i < str.length; i++) {
|
|
const charCode = str.charCodeAt(i)
|
|
if (charCode < 0 || charCode > 128) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
|
|
}
|
|
|
|
const getSplitStrings = (str: string) => {
|
|
if (isEng(str)) return getEngGroup(str)
|
|
const reg = /.{5}/g
|
|
const rs = str.match(reg) || [str]
|
|
rs.push(str.substring(rs.join('').length))
|
|
return rs
|
|
}
|
|
|
|
const getEngGroup = (str: string) => {
|
|
const strs = str.replace(/(?<!^)([A-Z])/g, `-$1`)
|
|
return strs.split('-')
|
|
}
|
|
|
|
const getTopAnch = (num) => {
|
|
let res = []
|
|
for (let i = 0; i < num; i++) {
|
|
res.push([(i + 1) / num, 0])
|
|
|
|
}
|
|
return res
|
|
}
|
|
|
|
const getBottomAnch = (num) => {
|
|
let res = []
|
|
for (let i = 0; i <= num; i++) {
|
|
res.push([(i) / num, 1])
|
|
|
|
}
|
|
return res
|
|
}
|
|
|
|
const getLeftAnch = (num) => {
|
|
let res = []
|
|
for (let i = 0; i < num; i++) {
|
|
res.push([0, (i + 1) / num])
|
|
|
|
}
|
|
return res
|
|
}
|
|
|
|
const getRightAnch = (num) => {
|
|
let res = []
|
|
for (let i = 0; i <= num; i++) {
|
|
res.push([1, (i) / num])
|
|
|
|
}
|
|
return res
|
|
}
|
|
|
|
G6.registerEdge('console-line', {
|
|
labelAutoRotate: true,
|
|
update: null,
|
|
}, 'cubic')
|
|
const Relation = {
|
|
ToOne: '1:1',
|
|
ToMany: '1:n',
|
|
}
|
|
G6.registerNode('console-model-Node', {
|
|
getAnchorPoints(cfg) {
|
|
const {
|
|
config,
|
|
data,
|
|
} = cfg as any;
|
|
const {
|
|
fields,
|
|
} = data as any;
|
|
const h = config.headerHeight + getLength(fields.length) * config.fieldHeight
|
|
return [[0, config.headerHeight / 2 / h], // 左上方
|
|
[1, config.headerHeight / 2 / h], // 右上方
|
|
...fields.map((field, index) => {
|
|
const x = 0
|
|
const l = config.headerHeight + config.fieldHeight * (index + 1) - config.fieldHeight / 2
|
|
const y = l / h
|
|
return [x, y]
|
|
}), ...fields.map((field, index) => {
|
|
const x = 1
|
|
const l = config.headerHeight + config.fieldHeight * (index + 1) - config.fieldHeight / 2
|
|
const y = l / h
|
|
return [x, y]
|
|
}),
|
|
...getTopAnch(50),
|
|
...getBottomAnch(50),
|
|
...getLeftAnch(100),
|
|
...getRightAnch(100),
|
|
]
|
|
},
|
|
afterDraw(cfg, group) {
|
|
if (cfg.hide) {
|
|
group.hide()
|
|
}
|
|
},
|
|
|
|
render: (cfg: IModelNodeShapeCfg, group) => {
|
|
const {
|
|
config,
|
|
data,
|
|
} = cfg
|
|
|
|
group.addShape('rect', {
|
|
visible: !cfg.isKeySharp,
|
|
name: data.key,
|
|
draggable: true,
|
|
attrs: {
|
|
y: -(getLength(data.fields.length) * config.fieldHeight / 2) - config.headerHeight / 2 + 4,
|
|
x: -(config.width / 2) + 3,
|
|
width: 50, //config.width - 6 ,
|
|
height: 30, //config.fieldHeight,
|
|
// text: data.label,
|
|
id: 'header',
|
|
// fontSize: config.fieldHeight - 12,
|
|
// opacity: !cfg.isKeySharp ? 1 : 0,
|
|
className: 'header',
|
|
shadowColor: 'rgba(0,0,0,0.06)',
|
|
cursor: 'move',
|
|
shadowBlur: 1,
|
|
shadowOffsetX: 1,
|
|
shadowOffsetY: 2,
|
|
// radius: [2, 4],
|
|
fill: '#FAFAFA',
|
|
},
|
|
})
|
|
|
|
group.addShape('text', {
|
|
visible: !cfg.isKeySharp,
|
|
name: data.key,
|
|
draggable: true,
|
|
attrs: {
|
|
x: -(config.width / 2) + 20,
|
|
y: -(getLength(data.fields.length) * config.fieldHeight / 2),
|
|
text: data.label,
|
|
id: 'headerlabel1',
|
|
cursor: 'move',
|
|
fontSize: 12, //config.fieldHeight - 16,
|
|
// opacity: !cfg.isKeySharp ? 1 : 0,
|
|
className: 'headerlabel',
|
|
textBaseline: 'middle',
|
|
textAlign: 'left',
|
|
// radius: [2, 4],
|
|
fill: 'rgba(0,0,0,0.80)',
|
|
},
|
|
})
|
|
// const nameList = (data.name || '').split('_').flatMap((nameStr) => nameStr.split('-')).flatMap((nameStr) => nameStr.split('/')).flatMap((a) => getSplitStrings(a)).filter((a) => a)
|
|
const nameList = (data.name || '').split('_');
|
|
|
|
const height = config.headerHeight + (data.fields.length >= 12 ? data.fields.length : 12) * config.fieldHeight
|
|
const nameLength = nameList.length
|
|
nameList.forEach((nameText, index) => {
|
|
group.addShape('text', {
|
|
visible: cfg.isKeySharp,
|
|
name: nameText,
|
|
draggable: true,
|
|
attrs: {
|
|
x: 0,
|
|
y: - height / 2 + height / (nameLength + 1) * (index + 1),
|
|
fontSize: 12,//config.width / 5,
|
|
text: nameText,
|
|
// opacity: index === nameLength - 1 ? 1 : 0.3,
|
|
id: 'headerlabel2',
|
|
className: 'headerlabel',
|
|
textBaseline: 'middle',
|
|
textAlign: 'center',
|
|
// radius: [2, 4],
|
|
fill: 'black',
|
|
},
|
|
})
|
|
}) // group.addShape('text', {
|
|
// visible: cfg.isKeySharp,
|
|
// attrs: {
|
|
// x: 0,
|
|
// y: (config.headerHeight + data.fields.length * config.fieldHeight) / 6,
|
|
// fontSize: 20,
|
|
// text: data.key,
|
|
// id: 'headerlabel3',
|
|
// opacity: cfg.isKeySharp ? 1 : 0,
|
|
// className: 'headerlabel',
|
|
// textBaseline: 'middle',
|
|
// textAlign: 'center',
|
|
// // radius: [2, 4],
|
|
// fill: 'black',
|
|
// },
|
|
// })
|
|
// group.addShape('text', {
|
|
// attrs: {
|
|
// x: (config.width),
|
|
// y: (config.headerHeight + data.fields.length * config.fieldHeight) / 6,
|
|
// fontSize: 20,
|
|
// text: data.key,
|
|
// id: 'headerlabel4',
|
|
// opacity: cfg.isKeySharp ? 1 : 0,
|
|
// className: 'headerlabel',
|
|
// textBaseline: 'middle',
|
|
// textAlign: 'center',
|
|
// // radius: [2, 4],
|
|
// fill: 'black',
|
|
// },
|
|
// })
|
|
|
|
data.fields.forEach((field, index) => {
|
|
group.addShape('rect', {
|
|
visible: !cfg.isKeySharp,
|
|
name: field.key,
|
|
draggable: true,
|
|
attrs: {
|
|
x: -(config.width / 2) + 3,
|
|
fieldName: field.key,
|
|
name: field.key,
|
|
draggable: true,
|
|
fieldBg: true,
|
|
arg: field.originalKey,
|
|
fieldHover: true,
|
|
y: -((config.headerHeight + getLength(data.fields.length) * config.fieldHeight) / 2) + config.headerHeight + config.fieldHeight * index,
|
|
// stroke: 'black',
|
|
width: 50,//config.width - 6,
|
|
id: 'field',
|
|
height: 30, //config.fieldHeight,
|
|
// click: 'fieldEdit',
|
|
// radius: [2, 4],
|
|
// fill: field.isForeign ? '#dee1e6' : 'white',
|
|
// ...cfg.style || {},
|
|
// fill: field.isForeign ? '#dee1e6' : 'white',
|
|
fill: 'white',
|
|
cursor: 'move',
|
|
// ...cfg.style || {},
|
|
},
|
|
})
|
|
const {
|
|
relationModel,
|
|
type,
|
|
isForeign,
|
|
} = field
|
|
group.addShape('text', {
|
|
visible: !cfg.isKeySharp,
|
|
name: field.key,
|
|
draggable: true,
|
|
attrs: {
|
|
x: -config.width / 2 + 20,
|
|
fieldHover: true,
|
|
name: field.key,
|
|
draggable: true,
|
|
// click: 'fieldEdit',
|
|
y: -((config.headerHeight + getLength(data.fields.length) * config.fieldHeight) / 2) + config.headerHeight + config.fieldHeight * index + config.fieldHeight / 2,
|
|
text: field.label,
|
|
fieldName: field.key,
|
|
arg: field.originalKey,
|
|
fontSize: 12,//config.fieldHeight - 16,
|
|
textBaseline: 'middle',
|
|
cursor: 'move',
|
|
id: 'field',
|
|
textAlign: 'start',
|
|
opacity: !cfg.isKeySharp ? 1 : 0,
|
|
// radius: [2, 4],
|
|
fill: isForeign ? '#06409E' : 'rgba(0,0,0,0.60)', // fill: 'rgb(153,153,153)',
|
|
// fill: field.isForeign ? 'black' : 'black',
|
|
|
|
},
|
|
})
|
|
group.addShape('text', {
|
|
visible: !cfg.isKeySharp,
|
|
name: field.key,
|
|
draggable: true,
|
|
attrs: {
|
|
x: config.width / 2 - 40,
|
|
fieldHover: true,
|
|
|
|
// click: 'fieldEdit',
|
|
y: -((config.headerHeight + getLength(data.fields.length) * config.fieldHeight) / 2) + config.headerHeight + config.fieldHeight * index + config.fieldHeight / 2,
|
|
text: isForeign ? `${relationModel}(${Relation[field.type]})` : `[${field.type}]`,
|
|
id: 'field',
|
|
textBaseline: 'middle',
|
|
fieldName: field.key,
|
|
arg: field.originalKey,
|
|
fontSize: 12,//config.fieldHeight - 16,
|
|
textAlign: 'right',
|
|
// opacity: !cfg.isKeySharp ? 1 : 0,
|
|
// radius: [2, 4],
|
|
// fill: field.isForeign ? 'black' : 'black',
|
|
fill: isForeign ? '#06409E' : 'rgba(0,0,0,0.30)',
|
|
},
|
|
})
|
|
group.addShape('text', {
|
|
visible: !cfg.isKeySharp,
|
|
name: field.key,
|
|
draggable: true,
|
|
attrs: {
|
|
|
|
x: config.width / 2 - 30,
|
|
y: -((config.headerHeight + getLength(data.fields.length) * config.fieldHeight) / 2) + config.headerHeight + config.fieldHeight * index + config.fieldHeight / 2,
|
|
text: '✎',
|
|
click: 'fieldEdit',
|
|
arg: field.originalKey,
|
|
fieldHoverShow: true,
|
|
fieldHover: true,
|
|
fieldName: field.key,
|
|
fontSize: 12,//config.fieldHeight - 16,
|
|
// fontSize: config.headerHeight - 6 ,
|
|
id: 'field',
|
|
// cursor: 'pointer',
|
|
opacity: 0,
|
|
// className: 'headerlabel',
|
|
textBaseline: 'middle',
|
|
textAlign: 'left',
|
|
cursor: 'pointer',
|
|
// radius: [2, 4],
|
|
fill: 'black',
|
|
},
|
|
})
|
|
}) // const h = config.headerHeight + data.fields.length * config.fieldHeight
|
|
|
|
const diffLength = getLength(data.fields.length) - data.fields.length
|
|
if (diffLength) {
|
|
for (let i = 0; i < diffLength; i++) {
|
|
// ---
|
|
group.addShape('rect', {
|
|
name: i,
|
|
draggable: true,
|
|
visible: !cfg.isKeySharp,
|
|
attrs: {
|
|
x: -(config.width / 2) + 3,
|
|
// fieldBg: true,
|
|
// fieldHover: true,
|
|
y: -((config.headerHeight + getLength(data.fields.length) * config.fieldHeight) / 2) + config.headerHeight + config.fieldHeight * (data.fields.length + i),
|
|
// stroke: 'black',
|
|
width: 50,//config.width - 6,
|
|
id: 'field',
|
|
height: 30,//config.fieldHeight,
|
|
// click: 'fieldEdit',
|
|
// radius: [2, 4],
|
|
// fill: field.isForeign ? '#dee1e6' : 'white',
|
|
// ...cfg.style || {},
|
|
// fill: field.isForeign ? '#dee1e6' : 'white',
|
|
fill: 'white',
|
|
cursor: 'move',
|
|
// ...cfg.style || {},
|
|
},
|
|
|
|
// ---
|
|
})
|
|
}
|
|
}
|
|
|
|
// const anchors = [
|
|
// [0, 0], // 左上方
|
|
// ...data.fields.map((_, i) => {
|
|
// if (_.isForeign) {
|
|
// const x = 0
|
|
// const l = config.headerHeight + data.fields.length * (i + 1)
|
|
// const y = l / h
|
|
// return [x, y, i]
|
|
// } else return null
|
|
// }),
|
|
// ].filter((a) => a)
|
|
// group.addShape('circle', {
|
|
// attrs: {
|
|
// x: -config.width / 2,
|
|
// y: (config.headerHeight / 2) - h / 2,
|
|
// r: 2,
|
|
// fill: 'green',
|
|
// },
|
|
// })
|
|
// anchors.forEach(([x, y, i ]) => {
|
|
// group.addShape('circle', {
|
|
// attrs: {
|
|
// x: -config.width / 2,
|
|
// y: (config.headerHeight + config.fieldHeight * (i) + (config.fieldHeight / 2)) - h / 2,
|
|
// r: 2,
|
|
// fill: 'red',
|
|
// },
|
|
// })
|
|
// })
|
|
},
|
|
|
|
draw: function drawShape(cfg: any, group) {
|
|
const {
|
|
config,
|
|
data,
|
|
} = cfg
|
|
group.addShape('rect', {
|
|
visible: false,
|
|
name: 'a',
|
|
draggable: true,
|
|
attrs: {
|
|
y: 100,
|
|
x: 100,
|
|
width: 100,
|
|
height: 80,
|
|
id: 'header',
|
|
className: 'header',
|
|
shadowColor: 'rgba(0,0,0,0.1)',
|
|
cursor: 'move',
|
|
shadowBlur: 1,
|
|
shadowOffsetX: 1,
|
|
shadowOffsetY: 2,
|
|
fill: '#000'
|
|
},
|
|
})
|
|
let keyShape = group.addShape('rect', {
|
|
name: data.key,
|
|
draggable: true,
|
|
attrs: {
|
|
id: 'keySharp',
|
|
x: 0,//-(config.width / 2),
|
|
y: 0,// -(config.headerHeight + getLength(data.fields.length) * config.fieldHeight) / 2,
|
|
width: 50,//config.width,
|
|
cursor: 'move',
|
|
|
|
// opacity: 0.85,
|
|
height: 30,//config.headerHeight + getLength(data.fields.length) * config.fieldHeight + 4,
|
|
...cfg.config.styleConfig.default.node,
|
|
},
|
|
})
|
|
this.render(cfg, group)
|
|
return keyShape
|
|
},
|
|
}, 'single-node')
|
|
|
|
const getLength = (length) => {
|
|
return length >= 8 ? length : 8
|
|
}
|
|
|
|
let graph: IGraph = null;
|
|
|
|
const Tutorial = () => {
|
|
const container = React.useRef();
|
|
useEffect(() => {
|
|
if (!graph) {
|
|
// 实例化 Minimap 插件
|
|
const minimap = new G6.Minimap({
|
|
size: [100, 100],
|
|
className: 'minimap',
|
|
type: 'delegate',
|
|
});
|
|
|
|
// 实例化 Grid 插件
|
|
const grid = new G6.Grid();
|
|
|
|
const graph = new G6.Graph({
|
|
container: container.current as string | HTMLElement,
|
|
width: 800,
|
|
height: 600,
|
|
fitView: true,
|
|
defaultNode: {
|
|
size: 20,
|
|
labelCfg: {
|
|
style: {
|
|
fill: '#fff',
|
|
},
|
|
},
|
|
},
|
|
defaultEdge: {
|
|
labelCfg: {
|
|
autoRotate: true,
|
|
},
|
|
},
|
|
nodeStateStyles: {
|
|
hover: {
|
|
fill: 'lightsteelblue',
|
|
cursor: 'pointer',
|
|
},
|
|
click: {
|
|
stroke: '#000',
|
|
lineWidth: 3,
|
|
},
|
|
},
|
|
edgeStateStyles: {
|
|
click: {
|
|
stroke: 'steelblue',
|
|
},
|
|
},
|
|
layout: {
|
|
type: 'dagre',
|
|
controlPoints: false,
|
|
linkDistance: 100,
|
|
preventOverlap: true,
|
|
nodeStrength: -30,
|
|
edgeStrength: 0.1,
|
|
},
|
|
modes: {
|
|
default: [
|
|
'drag-node',
|
|
'drag-canvas',
|
|
'zoom-canvas',
|
|
// 点提示框交互工具的配置
|
|
{
|
|
type: 'tooltip',
|
|
formatText(model) {
|
|
const text = 'label: ' + model.label + '<br/> class: ' + model.class;
|
|
return text;
|
|
},
|
|
shouldUpdate: e => {
|
|
return true;
|
|
},
|
|
},
|
|
// 边提示框交互工具的配置
|
|
{
|
|
type: 'edge-tooltip',
|
|
formatText(model) {
|
|
const text =
|
|
'source: ' +
|
|
model.source +
|
|
'<br/> target: ' +
|
|
model.target +
|
|
'<br/> weight: ' +
|
|
model.weight;
|
|
return text;
|
|
},
|
|
shouldUpdate: e => {
|
|
return true;
|
|
},
|
|
},
|
|
],
|
|
},
|
|
plugins: [minimap, grid], // 将 Minimap 和 Grid 插件的实例配置到图上
|
|
});
|
|
|
|
// graph.get('canvas').set('localRefresh', false);
|
|
G6.registerNode('custom-rect', {
|
|
draw(cfg, group) {
|
|
return group.addShape('rect', {
|
|
attrs: {
|
|
width: cfg.size[0],
|
|
height: cfg.size[1],
|
|
x: 0,
|
|
y: 0,
|
|
fill: '#ff0'
|
|
}
|
|
});
|
|
}
|
|
});
|
|
// graph.get('canvas').set('localRefresh', false);
|
|
// $.getJSON('https://gw.alipayobjects.com/os/basement_prod/6cae02ab-4c29-44b2-b1fd-4005688febcb.json', data => {
|
|
const main = async () => {
|
|
const response = await fetch(
|
|
'https://gw.alipayobjects.com/os/basement_prod/6cae02ab-4c29-44b2-b1fd-4005688febcb.json',
|
|
);
|
|
const data = response as GraphData;
|
|
const nodes = data.nodes;
|
|
const edges = data.edges;
|
|
nodes.forEach(node => {
|
|
node.type = 'console-model-Node'
|
|
if (!node.style) {
|
|
node.style = {};
|
|
}
|
|
node.style.lineWidth = 1;
|
|
node.style.stroke = '#666';
|
|
node.style.fill = 'steelblue';
|
|
switch (node.class) {
|
|
case 'c0':
|
|
node.type = 'circle';
|
|
break;
|
|
case 'c1':
|
|
node.type = 'custom-rect';
|
|
node.size = [80, 60];
|
|
break;
|
|
case 'c2':
|
|
node.type = 'ellipse';
|
|
node.size = [100, 50];
|
|
break;
|
|
}
|
|
});
|
|
edges.push({
|
|
source: '19',
|
|
target: '19',
|
|
type: 'loop',
|
|
loopCfg: {
|
|
clockwise: true,
|
|
dist: 100
|
|
},
|
|
label: 'test',
|
|
labelCfg: {
|
|
//autoRotate: true,
|
|
style: {
|
|
stroke: "#AEC1DE",
|
|
lineWidth: 2,
|
|
fill: "#AEC1DE"
|
|
}
|
|
},
|
|
style: {
|
|
lineWidth: 3,
|
|
opacity: 1,
|
|
size: 2,
|
|
radius: 5,
|
|
endArrow: {
|
|
path: "M 6,0 L -6,-6 L -3,0 L -6,6 Z",
|
|
d: 6
|
|
},
|
|
startArrow: {
|
|
path: "M 12,10 L 12,-10 L 2, -6, L 2,6 Z",
|
|
d: 3
|
|
},
|
|
stroke: "#AEC1DE",
|
|
},
|
|
});
|
|
edges.forEach(edge => {
|
|
edge.type = 'console-line'
|
|
if (!edge.style) {
|
|
edge.style = {};
|
|
}
|
|
edge.style.opacity = 0.6;
|
|
edge.style.stroke = 'grey';
|
|
});
|
|
|
|
graph.data(data);
|
|
graph.render();
|
|
|
|
graph.on('node:mouseenter', e => {
|
|
const nodeItem = e.item;
|
|
graph.setItemState(nodeItem, 'hover', true);
|
|
});
|
|
graph.on('node:mouseleave', e => {
|
|
const nodeItem = e.item;
|
|
graph.setItemState(nodeItem, 'hover', false);
|
|
});
|
|
graph.on('node:click', e => {
|
|
const clickNodes = graph.findAllByState('node', 'click');
|
|
clickNodes.forEach(cn => {
|
|
graph.setItemState(cn, 'click', false);
|
|
});
|
|
const nodeItem = e.item;
|
|
graph.setItemState(nodeItem, 'click', true);
|
|
});
|
|
graph.on('edge:click', e => {
|
|
const clickEdges = graph.findAllByState('edge', 'click');
|
|
clickEdges.forEach(ce => {
|
|
graph.setItemState(ce, 'click', false);
|
|
});
|
|
const edgeItem = e.item;
|
|
graph.setItemState(edgeItem, 'click', true);
|
|
});
|
|
|
|
|
|
|
|
};
|
|
main();
|
|
}
|
|
});
|
|
|
|
return <div ref={container}></div>;
|
|
};
|
|
|
|
export default Tutorial;
|