通过G6实现
官网:antv-g6.gitee.io/zh/examples…
下载"@antv/g6": "4.8.24",
父组件
import React, { useEffect } from "react";
import TreeGraphComponent from "./flowGraph";
const data = {
id: "root",
label: "root",
children: [
{
id: "c1",
label: "c1",
children: [
{
id: "c1-1",
label: "c1-1",
},
{
id: "c1-2",
label: "c1-2",
children: [
{
id: "c1-2-1",
label: "c1-2-1",
},
{
id: "c1-2-2",
label: "c1-2-2",
},
],
},
],
},
{
id: "c2",
label: "c2",
},
{
id: "c3",
label: "c3",
children: [
{
id: "c3-1",
label: "c3-1",
},
{
id: "c3-2",
label: "c3-2",
children: [
{
id: "c3-2-1",
label: "c3-2-1",
},
{
id: "c3-2-2",
label: "c3-2-2",
},
{
id: "c3-2-3",
label: "c3-2-3",
},
],
},
{
id: "c3-3",
label: "c3-3",
},
],
},
],
};
function App() {
return (
<div style={{ width: "100%", height: "100%" }}>
<TreeGraphComponent data={data} />
</div>
);
}
export default App;
子组件
import React, { useRef, useState, useEffect } from "react";
import G6, { Grid, TreeGraphData } from "@antv/g6";
const TreeGraphComponent = (props: any) => {
const { data } = props;
const containerRef = useRef(null); //获取画布元素
const graphRef = useRef(null); //储存graph实例
const COLLAPSE_ICON = function COLLAPSE_ICON(x: any, y: any, r: any) {
return [
["M", x - r, y - r],
["a", r, r, 0, 1, 0, r * 2, 0],
["a", r, r, 0, 1, 0, -r * 2, 0],
["M", x + 2 - r, y - r],
["L", x + r - 2, y - r],
];
};
const EXPAND_ICON = function EXPAND_ICON(x: any, y: any, r: any) {
return [
["M", x - r, y - r],
["a", r, r, 0, 1, 0, r * 2, 0],
["a", r, r, 0, 1, 0, -r * 2, 0],
["M", x + 2 - r, y - r],
["L", x + r - 2, y - r],
["M", x, y - 2 * r + 2],
["L", x, y - 2],
];
};
const getShapeStyle = (cfg: any) => {
let cfgTmp = {
labelCfg: null,
leftIcon: null,
style: null,
width: null,
height: null,
};
cfgTmp.labelCfg = cfg.labelCfg;
cfgTmp.leftIcon = cfg.leftIcon;
cfgTmp.style = cfg.style;
cfgTmp.width = cfg.x;
cfgTmp.height = cfg.y;
return cfgTmp;
};
useEffect(() => {
console.log("useEffect11111");
if (!graphRef.current && containerRef.current) {
const offsetWidth = containerRef.current.offsetWidth;
const offsetHeight = containerRef.current.offsetHeight;
console.log("offsetWidth====", offsetWidth);
console.log("containerRef.current===", containerRef.current);
// // 创建 TreeGraph 实例
// graphRef.current = new G6.TreeGraph({
// container: containerRef.current,
// width: 800,
// height: 600,
// // plugins: [grid],
// modes: {
// default: ["drag-canvas", "zoom-canvas"],
// },
// defaultEdge: {
// type: "cubic-horizontal",
// style: {
// stroke: "#A3B1BF",
// },
// },
// layout: {
// type: "compactBox",
// direction: "TB",
// getId: function getId(d: any) {
// return d.id;
// },
// getHeight: function getHeight() {
// return 16;
// },
// getWidth: function getWidth() {
// return 16;
// },
// getVGap: function getVGap() {
// return 40;
// },
// getHGap: function getHGap() {
// return 70;
// },
// },
// });
const defaultStateStyles = {
hover: {
stroke: "#1890ff",
lineWidth: 2,
},
};
const defaultNodeStyle = {
fill: "#91d5ff",
stroke: "#40a9ff",
radius: 5,
};
const defaultEdgeStyle = {
stroke: "#91d5ff",
endArrow: {
path: "M 0,0 L 12, 6 L 9,0 L 12, -6 Z",
fill: "#91d5ff",
d: -20,
},
};
const defaultLayout = {
type: "compactBox",
direction: "TB",
getId: function getId(d: any) {
return d.id;
},
getHeight: function getHeight() {
return 16;
},
getWidth: function getWidth() {
return 16;
},
getVGap: function getVGap() {
return 40;
},
getHGap: function getHGap() {
return 70;
},
};
const defaultLabelCfg = {
style: {
fill: "#000",
fontSize: 12,
},
};
G6.Util.traverseTree(data, (d: any) => {
d.leftIcon = {
style: {
fill: "#e6fffb",
stroke: "#e6fffb",
},
img: "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*Q_FQT6nwEC8AAAAAAAAAAABkARQnAQ",
};
return true;
});
// 注册自定义节点
G6.registerNode(
"icon-node",
{
options: {
size: [60, 20],
stroke: "#91d5ff",
fill: "#91d5ff",
},
draw(cfg, group) {
const w = 242;
const h = 104;
const styles = getShapeStyle(cfg);
// const { labelCfg = {}, leftIcon:any = null } = cfg;
const leftIcon: any = cfg.leftIcon;
const keyShape = group.addShape("rect", {
attrs: {
x: -w / 2,
y: -h / 2,
width: 120,
height: 40,
stroke: "#44ACFC", //边框颜色
fill: "#94D4FC", //背景颜色
radius: 2,
// labelCfg: { style: { fill: "#000", fontSize: 12 } },
// leftIcon: { style: { fill: "#e6fffb", stroke: "#e6fffb" } },
// fill: "#91d5ff",
// radius: 5,
// stroke: "#40a9ff",
hover: {
lineWidth: 2,
// stroke: "red",
},
},
});
/**
* leftIcon 格式如下:
* {
* style: ShapeStyle;
* img: ''
* }
*/
if (leftIcon) {
const { style, img } = leftIcon;
group.addShape("rect", {
attrs: {
x: 1 - w / 2,
y: 1 - h / 2,
width: 38,
height: 40 - 2,
fill: "#8c8c8c",
radius: [2, 2],
...style,
},
});
group.addShape("image", {
attrs: {
x: 8 - w / 2,
y: 8 - h / 2,
width: 24,
height: 24,
img:
img ||
"https://g.alicdn.com/cm-design/arms-trace/1.0.155/styles/armsTrace/images/TAIR.png",
},
name: "image-shape",
});
}
// 如果不需要动态增加或删除元素,则不需要 add 这两个 marker
group.addShape("marker", {
attrs: {
x: 40 - w / 2,
y: 52 - h / 2,
r: 6,
stroke: "#73d13d",
cursor: "pointer",
symbol: EXPAND_ICON,
},
name: "add-item",
});
//删除元素
group.addShape("marker", {
attrs: {
x: 80 - w / 2,
y: 52 - h / 2,
r: 6,
stroke: "#ff4d4f",
cursor: "pointer",
symbol: COLLAPSE_ICON,
},
name: "remove-item",
});
if (cfg.label) {
group.addShape("text", {
attrs: {
fill: "red", //label中字体颜色
fontSize: 12, //label中字体大小
text: cfg.label,
x: 50 - w / 2,
y: 25 - h / 2,
},
});
}
return keyShape;
},
update: undefined,
},
"rect"
);
G6.registerEdge("flow-line", {
draw(cfg, group) {
const startPoint = cfg.startPoint;
const endPoint = cfg.endPoint;
const { style } = cfg;
const shape = group.addShape("path", {
attrs: {
stroke: style.stroke,
endArrow: style.endArrow,
path: [
["M", startPoint.x, startPoint.y],
["L", startPoint.x, (startPoint.y + endPoint.y) / 2],
["L", endPoint.x, (startPoint.y + endPoint.y) / 2],
["L", endPoint.x, endPoint.y],
],
},
});
return shape;
},
});
const minimap = new G6.Minimap({
size: [150, 100],
});
const grid = new G6.Grid({
// img: "http://localhost:3000/static/media/logo.fc2116e0bf414cf5e6b7.png",
});
console.log("grid0000", grid);
const graph = new G6.TreeGraph({
container: containerRef.current,
width: offsetWidth,
height: offsetHeight,
linkCenter: true,
// plugins: [minimap, grid],
plugins: [grid],
modes: {
default: ["drag-canvas", "zoom-canvas"],
},
defaultNode: {
type: "icon-node",
size: [40, 40],
style: defaultNodeStyle,
labelCfg: defaultLabelCfg,
},
defaultEdge: {
type: "flow-line",
style: defaultEdgeStyle,
},
nodeStateStyles: defaultStateStyles,
edgeStateStyles: defaultStateStyles,
layout: defaultLayout,
});
// 加载数据
// graph.data(data);
// // 渲染树图
// graph.render();
graph.read(data);
// if (containerRef.current.getElementsByTagName("canvas")) {
// const canvasRefList =
// containerRef.current.getElementsByTagName("canvas");
// console.log(
// "canvas000000",
// canvasRefList[0].width,
// canvasRefList[0].style.width
// );
// console.log(
// "qqqqqqqq",
// canvasRefList[0].style.width,
// String(offsetWidth) + "px",
// canvasRefList[0].style.width == String(offsetWidth) + "px"
// );
// if (canvasRefList[0].style.width != String(offsetWidth) + "px") {
// console.log(
// canvasRefList[0].style.width == String(offsetWidth) + "px"
// );
// canvasRefList[0].style.width = String(offsetWidth) + "px";
// }
// // canvasRefList[0].width = offsetWidth;
// }
// 返回值zoom表示当前视口的缩放比例
const zoom = graph.getZoom();
console.log("zoom000000", zoom);
//让画布内容适应视口。
// graph.fitView(20);
// 在渲染和动画完成后调用(平移图到中心将对齐到画布中心,但不缩放。优先级低于 fitView。)
graph.fitCenter();
graphRef.current = graph;
console.log("qqq");
//鼠标hover事件移入模块
graph.on("node:mouseenter", (evt) => {
const { item } = evt;
if (item) {
graph.setItemState(item, "hover", true);
}
});
//鼠标hover事件移出模块
graph.on("node:mouseleave", (evt) => {
const { item } = evt;
if (item) {
graph.setItemState(item, "hover", false);
}
});
graph.on("node:click", (evt) => {
const { item, target } = evt;
const targetType = target.get("type");
const name = target.get("name");
// 更新节点名称
if (targetType != "marker" && item) {
const nodeName = prompt("请输入新的节点名称:");
if (nodeName) {
graph.update(
item,
{
label: nodeName,
},
false
);
}
}
// 增加元素
if (targetType === "marker" && item) {
let model: TreeGraphData = item.getModel() as TreeGraphData;
if (name === "add-item") {
if (!model.children) {
model.children = [];
}
const id = `n-${Math.random()}`;
model.children.push({
id,
// label: id.substr(0, 8),
label: id.substring(0, 8),
leftIcon: {
style: {
fill: "#e6fffb",
stroke: "#e6fffb",
},
img: "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*Q_FQT6nwEC8AAAAAAAAAAABkARQnAQ",
},
});
graph.updateChild(model, model.id);
} else if (name === "remove-item") {
//删减元素
graph.removeChild(model.id);
}
}
});
}
// 清理函数
return () => {
console.log("returnreturnreturnreturnreturnreturn");
// if (graphRef.current) {
// graphRef.current.destroy();
// graphRef.current = null;
// }
};
}, []);
const apply = () => {
console.log("000000");
console.log("graphRef.current0000", graphRef.current);
};
return (
<div
ref={containerRef}
style={{ width: "100%", height: "600px", border: "1px solid red" }}
>
<button
onClick={() => {
apply();
}}
style={{ position: "absolute" }}
>
完成
</button>
</div>
);
};
export default TreeGraphComponent;