【G6】G6学习笔记(四)

前言

  • 我一开始还没太注意树图和普通图的区别,直到我用自定义新增和减少才发现树图是完全不一样的逻辑。

树图

  • 树图之间的节点关系是通过配置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;
// Register a custom behavior: add a node when user click the blank part of canvas
G6.registerBehavior("click-add-node", {
	// Set the events and the corresponding responsing function for this behavior
	getEvents() {
		// The event is canvas:click, the responsing function is onClick
		return {
			"canvas:click": "onClick",
		};
	},
	// Click event
	onClick(ev: IG6GraphEvent) {
		const self = this;
		const graph = self.graph as TreeGraph;
		// Add a new node
		const { x, y } = graph.getPointByClient(ev.clientX, ev.clientY);
		graph.addItem("node", {
			x: x,
			y: y,
			id: `node-${addedCount}`, // Generate the unique id
		});

		addedCount++;
	},
});

G6.registerBehavior("click-add-child", {
	// Set the events and the corresponding responsing function for this behavior
	getEvents() {
		// The event is canvas:click, the responsing function is onClick
		return {
			"node:click": "onClick",
		};
	},
	// Click event
	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 = [];
			}
			// Add a new node
			const { x, y } = graph.getPointByClient(ev.clientX, ev.clientY);
			model.children.push({
				x: x,
				y: y,
				id: `node-${addedCount}`, // Generate the unique id
			});
			graph.updateChild(model, model.id);
			addedCount++;
		}
	},
});

G6.registerBehavior("del-node-and-edge", {
	// Set the events and the corresponding responsing function for this behavior
	getEvents() {
		return {
			"node:click": "onClick", // The event is canvas:click, the responsing function is onClick
			"edge:click": "onEdgeClick", // The event is edge:click, the responsing function is onEdgeClick
		};
	},
	// The responsing function for node:click defined in getEvents
	onClick(ev: IG6GraphEvent) {
		const self = this;
		const node = ev.item;
		const graph = self.graph as TreeGraph;
		// const itemController = graph.get("itemController");
		// itemController.removeItem(node);
		//graph.removeItem(node as INode);
		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);
	},
});

// Initial data
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", {
			// Set the events and the corresponding responsing function for this behavior
			getEvents() {
				return {
					"node:click": "onClick", // The event is canvas:click, the responsing function is onClick
				};
			},
			// The responsing function for node:click defined in getEvents
			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") {
					//将节点的值赋给input并绑上onchange给它
					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); //要调用更新,最小是item item才有update
				}
			},
		});
	}, []);

	useEffect(() => {
		graph = new G6.TreeGraph({
			container: ref.current!,
			width: 800,
			height: 800,
			// The sets of behavior modes
			modes: {
				// Defualt mode
				default: [
					"drag-node",
					"drag-canvas",
					"zoom-canvas",
					"collapse-expand",
				],
				// Adding node mode
				addNode: ["click-add-node", "click-select"],
				// Adding edge mode
				del: ["del-node-and-edge"],
				addChild: ["click-add-child", "click-select"],
				changeStyle: ["click-change-style"],
			},
			// The node styles in different states
			nodeStateStyles: {
				// The node styles in selected state
				selected: {
					stroke: "#666",
					lineWidth: 2,
					fill: "steelblue",
				},
			},
			layout: {
				type: "mindmap",
				direction: state, // H / V / LR / RL / TB / BT
				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);
		//修改state后改变节点Label
		if (changeItem) {
			//其他样式控制同理这么操作
			const model = changeItem.getModel();
			model.label = e.target.value;
			changeItem.update(model);
		}
	};
	const handleColor = (e: React.ChangeEvent<HTMLInputElement>) => {
		setfill(e.target.value);
		//修改state后改变节点Label
		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);
		//修改state后改变节点Label
		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;

你可能感兴趣的:(G6,可视化)