前言
- 我一开始还没太注意树图和普通图的区别,直到我用自定义新增和减少才发现树图是完全不一样的逻辑。
树图
- 树图之间的节点关系是通过配置children来确定的,同时,必须有layout布局,来为其自动计算坐标。
- 树图的的新增和减少应该在每个node上进行操作,这样新增的节点会与需要的节点之间产生联系。要么就直接修改元数据的方式进行新增或者删除。
- 通常是找到group 也就是相同图像
- 我特意花了点时间,做完了树图的增删改查+样式修改:
import React, { useRef, useEffect, useState, useMemo } from "react";
import G6, { Graph, TreeGraph } from "@antv/g6";
import { IG6GraphEvent, TreeGraphData } from "@antv/g6/lib/types";
import { IEdge, INode } from "@antv/g6/lib/interface/item";
let addedCount = 0;
G6.registerBehavior("click-add-node", {
getEvents() {
return {
"canvas:click": "onClick",
};
},
onClick(ev: IG6GraphEvent) {
const self = this;
const graph = self.graph as TreeGraph;
const { x, y } = graph.getPointByClient(ev.clientX, ev.clientY);
graph.addItem("node", {
x: x,
y: y,
id: `node-${addedCount}`,
});
addedCount++;
},
});
G6.registerBehavior("click-add-child", {
getEvents() {
return {
"node:click": "onClick",
};
},
onClick(ev: IG6GraphEvent) {
const self = this;
const graph = self.graph as TreeGraph;
const item = ev.item;
if (item) {
const model = item.getModel() as TreeGraphData;
if (!model.children) {
model.children = [];
}
const { x, y } = graph.getPointByClient(ev.clientX, ev.clientY);
model.children.push({
x: x,
y: y,
id: `node-${addedCount}`,
});
graph.updateChild(model, model.id);
addedCount++;
}
},
});
G6.registerBehavior("del-node-and-edge", {
getEvents() {
return {
"node:click": "onClick",
"edge:click": "onEdgeClick",
};
},
onClick(ev: IG6GraphEvent) {
const self = this;
const node = ev.item;
const graph = self.graph as TreeGraph;
graph.removeChild(node!.getID());
},
onEdgeClick(ev: IG6GraphEvent) {
const self = this;
const currentEdge = ev.item;
const graph = self.graph as TreeGraph;
graph.removeItem(currentEdge as IEdge);
},
});
const data = {
id: "Modeling Methods",
children: [
{
id: "Classification",
children: [
{ id: "Logistic regression" },
{ id: "Linear discriminant analysis" },
{ id: "Rules" },
{ id: "Decision trees" },
{ id: "Naive Bayes" },
{ id: "K nearest neighbor" },
{ id: "Probabilistic neural network" },
{ id: "Support vector machine" },
],
},
{
id: "Consensus",
children: [
{
id: "Models diversity",
children: [
{ id: "Different initializations" },
{ id: "Different parameter choices" },
{ id: "Different architectures" },
{ id: "Different modeling methods" },
{ id: "Different training sets" },
{ id: "Different feature sets" },
],
},
{
id: "Methods",
children: [
{ id: "Classifier selection" },
{ id: "Classifier fusion" },
],
},
{
id: "Common",
children: [
{ id: "Bagging" },
{ id: "Boosting" },
{ id: "AdaBoost" },
],
},
],
},
{
id: "Regression",
children: [
{ id: "Multiple linear regression" },
{ id: "Partial least squares" },
{ id: "Multi-layer feedforward neural network" },
{ id: "General regression neural network" },
{ id: "Support vector regression" },
],
},
],
};
let graph: Graph;
function App() {
const ref = useRef<HTMLDivElement>(null);
const [state, setState] = useState("H");
const [show, setShow] = useState(false);
const [changeItem, setChangeItem] = useState<INode>();
const [label, setLabel] = useState("");
const [fill, setfill] = useState("");
const [fontSize, setFontSize] = useState(0);
useMemo(() => {
G6.registerBehavior("click-change-style", {
getEvents() {
return {
"node:click": "onClick",
};
},
onClick(ev: IG6GraphEvent) {
const item = ev.item;
const model = item!.getModel();
const value = model.label;
const f = model.style?.fill;
const labelcfg = model.labelCfg;
if (typeof value === "string") {
setLabel(value);
if (f) {
setfill(f);
} else {
setfill("");
}
if (labelcfg && labelcfg.style && labelcfg.style.fontSize) {
setFontSize(labelcfg.style.fontSize);
} else {
const nlabelcfg = {
style: {
fontSize: 14,
},
};
model.labelCfg = nlabelcfg;
setFontSize(14);
}
setChangeItem(item as INode);
}
},
});
}, []);
useEffect(() => {
graph = new G6.TreeGraph({
container: ref.current!,
width: 800,
height: 800,
modes: {
default: [
"drag-node",
"drag-canvas",
"zoom-canvas",
"collapse-expand",
],
addNode: ["click-add-node", "click-select"],
del: ["del-node-and-edge"],
addChild: ["click-add-child", "click-select"],
changeStyle: ["click-change-style"],
},
nodeStateStyles: {
selected: {
stroke: "#666",
lineWidth: 2,
fill: "steelblue",
},
},
layout: {
type: "mindmap",
direction: state,
getHeight: () => {
return 16;
},
getWidth: () => {
return 16;
},
getVGap: () => {
return 10;
},
getHGap: () => {
return 50;
},
},
defaultEdge: {
type: "cubic-horizontal",
style: {
stroke: "#A3B1BF",
},
},
});
graph.node(function (node) {
return {
label: node.id,
};
});
graph.data(data);
graph.render();
graph.fitView();
return () => graph.destroy();
}, [state]);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setLabel(e.target.value);
if (changeItem) {
const model = changeItem.getModel();
model.label = e.target.value;
changeItem.update(model);
}
};
const handleColor = (e: React.ChangeEvent<HTMLInputElement>) => {
setfill(e.target.value);
if (changeItem) {
const model = changeItem.getModel();
if (model.style) {
model.style.fill = e.target.value;
changeItem.update(model);
}
}
};
const handleFontSize = (e: React.ChangeEvent<HTMLInputElement>) => {
const num = parseFloat(e.target.value);
setFontSize(num);
if (changeItem) {
const model = changeItem.getModel();
if (model.labelCfg) {
if (model.labelCfg.style) {
model.labelCfg.style.fontSize = num;
} else {
model.labelCfg.style = {
fontSize: num,
};
}
changeItem.update(model);
}
}
};
return (
<div>
<button
onClick={() => {
graph.setMode("addNode");
setShow(false);
}}
>
添加节点
</button>
<button
onClick={() => {
graph.setMode("addChild");
setShow(false);
}}
>
添加孩子
</button>
<button
onClick={() => {
graph.setMode("default");
setShow(false);
}}
>
默认模式
</button>
<button
onClick={() => {
graph.setMode("del");
setShow(false);
}}
>
删除模式
</button>
<span>请选择布局模式:</span>
<select
value={state}
onChange={(e) => {
setState(e.target.value);
setShow(false);
}}
>
<option value="H">H</option>
<option value="V">V</option>
<option value="LR">LR</option>
<option value="RL">RL</option>
<option value="TB">TB</option>
<option value="BT">BT</option>
</select>
<button
onClick={() => {
setShow(true);
graph.setMode("changeStyle");
}}
>
修改样式
</button>
<div style={{ display: show ? "block" : "none" }}>
<div>
修改label
<input value={label} onChange={handleChange}></input>
</div>
<div>
修改颜色
<input value={fill} onChange={handleColor}></input>
</div>
<div>
修改字体
<input value={fontSize} onChange={handleFontSize}></input>
</div>
</div>
<div ref={ref}></div>
</div>
);
}
export default App;