【G6】G6学习笔记(二)

前言

  • 再次稍微研究了下这玩意,这玩意难用的地方在于,第一、文档写的很混乱,有很多漏写的。 第二、报错经常是它内部报错,得看它里面到底咋回事才知道为啥出现错误。第三、卸载比较诡异,如果有路由相互切换的话,可能得踩点坑。

事件

  • 首先事件方面,可以通过graph.on监听事件,可以监听这些:
		// 	   click?: string;
		//     mousedown?: string;
		//     mouseup?: string;
		//     dblclick?: string;
		//     contextmenu?: string;
		//     mouseenter?: string;
		//     mouseout?: string;
		//     mouseover?: string;
		//     mousemove?: string;
		//     mouseleave?: string;
		//     dragstart?: string;
		//     dragend?: string;
		//     drag?: string;
		//     dragenter?: string;
		//     dragleave?: string;
		//     dragover?: string;
		//     dragout?: string;
		//     drop?: string;
		//     keyup?: string;
		//     keydown?: string;
		//     wheel?: string;
		//     focus?: string;
		//     "node:click"?: string;
		//     "node:contextmenu"?: string;
		//     "node:dblclick"?: string;
		//     "node:dragstart"?: string;
		//     "node:drag"?: string;
		//     "node:dragend"?: string;
		//     "node:mouseenter"?: string;
		//     "node:mouseleave"?: string;
		//     "node:mousemove"?: string;
		//     "node:drop"?: string;
		//     "node:dragenter"?: string;
		//     "node:dragleave"?: string;
		//     "edge:click"?: string;
		//     "edge:contextmenu"?: string;
		//     "edge:dblclick"?: string;
		//     "edge:mouseenter"?: string;
		//     "edge:mouseleave"?: string;
		//     "edge:mousemove"?: string;
		//     "canvas:mousedown"?: string;
		//     "canvas:mousemove"?: string;
		//     "canvas:mouseup"?: string;
		//     "canvas:click"?: string;
		//     "canvas:mouseleave"?: string;
		//     "canvas:dragstart"?: string;
		//     "canvas:drag"?: string;
		//     "canvas:dragend"?: string;
		//     "combo:click"?: string;
		//     "combo:contextmenu"?: string;
		//     "combo:dblclick"?: string;
		//     "combo:dragstart"?: string;
		//     "combo:drag"?: string;
		//     "combo:dragend"?: string;
		//     "combo:mouseenter"?: string;
		//     "combo:mouseleave"?: string;
		//     "combo:mousemove"?: string;
		//     "combo:drop"?: string;
		//     "combo:dragover"?: string;
		//     "combo:dragleave"?: string;
		//     "combo:dragenter"?: string;
  • 主要先研究下脑图,脑图有个特点,就是传入data可以不需要提供x,y,new了graph之后,自动生成了x,y,然后方向可以在node里进行配置:
	let centerX = 0;
		graph.node(function (node: NodeConfig) {
			if (node.id === "root") {
				centerX = node.x as number;//把第一个节点的x作为center
			}

			return {
				label: node.id,
				labelCfg: {
					position://看有没有孩子和孩子数量配置左右
						node.children && node.children.length > 0
							? "right"
							: (node.x as number) > centerX
							? "right"
							: "left",
					offset: 5,
				},
			};
		});
  • 节点拖拽移动。主要监听node:drag事件,然后去取鼠标的xy,转换为graph的xy,最后赋给节点。
graph.on("node:dragstart", (e: any) => {
			const item = e.item;
			const model = item.getModel();
			model.style.cursor = "grab";
			graph.update(item, model);
			graph.paint();
		});

		graph.on("node:drag", (e: any) => {
			// 鼠标所在位置 转化为现在目标节点所在位置
			const { clientX, clientY } = e;
			// 将视口坐标转换为屏幕/页面坐标。
			const point = graph.getPointByClient(clientX, clientY);
			const item = e.item;
			const model = item.getModel();
			item.updatePosition(point);
			graph.update(item, model);
			graph.paint();
		});

		graph.on("node:dragend", (e: any) => {
			const item = e.item;
			const model = item.getModel(); //直接取得model没style。。。
			model.style.cursor = "default";
			graph.update(item, model);
			graph.paint();
		});
		graph.on("canvas:drag", (e: any) => {
			//	console.log(e);
		});
		graph.on("dragstart", (e: any) => {
			//比node:dragstart先
		});
		graph.on("mousedown", (e: any) => {
			//比dragstart先
			const item = e.item;
			if (item) {
				const model = item.getModel();

				model.style.cursor = "grab";
				graph.update(item, model);
				graph.paint();
			}
		});

自定义节点

  • 其实是先注册个节点,注册节点时,可以利用addshape做节点样子,同时可以绑定事件,给节点赋文本。
  • 需要注意是这里用的是图形分组概念,g6里面不知道哪个人搞了那么多名字全是group要么是groups很难区分。
  • 通过一个g6实例的group,可以找到其所属的item。
import React, { useEffect, useRef } from "react";
import G6 from "@antv/g6";
import { NodeConfig } from "@antv/g6/lib/types";
import GGroup from "@antv/g-canvas/lib/group";
import { IShape } from "@antv/g-canvas/lib/interfaces";
const data = {
	nodes: [
		{
			id: "Model",
			type: "model-node", //这个就是注册的
			x: 100,
			y: 100,
			style: {
				width: 160,
				height: 100,
				fill: "#f1b953",
				stroke: "#f1b953",
			},
			openIcon: {
				x: 180, // 控制图标在横轴上的位置
				y: 45, // 控制图标在纵轴上的位置
				fontSize: 20,
				style: {
					fill: "#fc0",
				},
			},
			hideIcon: {
				x: 180, // 控制图标在横轴上的位置
				y: 45, // 控制图标在纵轴上的位置
				fontSize: 20,
				style: {
					fill: "#666",
				},
			},
			labels: [
				{
					x: 10,
					y: 20,
					label: "标题,最长10个字符~~",
					labelCfg: {
						fill: "#666",
						fontSize: 14,
						maxlength: 10,
					},
				},
				{
					x: 10,
					y: 40,
					label: "描述,最长12个字333符~~~",
					labelCfg: {
						fontSize: 12,
						fill: "#999",
						maxlength: 12,
					},
				},
			],
		},
		{
			id: "node1", // String,该节点存在则必须,节点的唯一标识
			x: 100, // Number,可选,节点位置的 x 值
			y: 200, // Number,可选,节点位置的 y 值
		},
	],
};

interface modelNodeType extends NodeConfig {
	openIcon: {
		x: number;
		y: number;
		fontSize: number;
		style: Object;
	};
	hideIcon: {
		x: number;
		y: number;
		fontSize: number;
		style: Object;
	};
	labels: Array<any>;
}

// 注册自定义节点
G6.registerNode(
	"model-node",
	{
		drawShape(cfg: modelNodeType, group) {
			const opts = cfg;
			const openIcon = opts.openIcon;
			const hideIcon = opts.hideIcon;
			// 添加节点
			const shape = group!.addShape("rect", {
				name: "model-node",
				draggable: true, // 让自定义节点支持拖拽
				attrs: cfg.style,
			});

			const openSwitch = group!.addShape("circle", {
				draggable: true,
				attrs: {
					r: 10,
					...openIcon,
					...openIcon.style,
				},
				className: "state-open",
			});

			const hideSwitch = group!.addShape("circle", {
				draggable: true,
				attrs: {
					r: 10,
					...hideIcon,
					...hideIcon.style,
				},
				className: "state-hide",
			});

			// 添加多行文本
			for (let i = 0; i < cfg.labels.length; i++) {
				const item = cfg.labels[i];
				const {
					label,
					labelCfg: { maxlength },
				} = item;

				let text = maxlength ? label.substr(0, maxlength) : label || "";

				if (label.length > maxlength) {
					text = `${text}...`;
				}

				group!.addShape("text", {
					attrs: {
						text,
						...item,
						...item.labelCfg,
					},
				});
			}
			this.bindEvent(group, openSwitch);
			this.bindEvent(group, hideSwitch);

			return shape;
		},
		bindEvent(group: GGroup, btn: IShape) {
			//ggroup就是graphics group缩写
			btn.on("click", () => {
				const open = group
					.get("children")
					.find((child: any) => child.cfg.className === "state-open");
				const close = group
					.get("children")
					.find((child: any) => child.cfg.className === "state-hide");
				if (btn.cfg.className === "state-open") {
					const item = group.get("item"); //在这个图形分组下的item
					const model = item.getModel();
					open.toBack();
					close.toFront(); //这个是让2个圆z轴位置变化 item上的方法
					model.style.height = 100;
					item.update(model);
				} else if (btn.cfg.className === "state-hide") {
					const item = group.get("item");
					const model = item.getModel();
					close.toBack(); //Item 上的方法
					open.toFront(); //item上的方法
					model.style.height = 50;
					item.update(model); //item上的方法
				}
			});
		},
	},
	"single-node"
); // 继承自内置节点
function App() {
	const ref = useRef<HTMLDivElement>(null);
	useEffect(() => {
		const graph = new G6.Graph({
			container: ref.current!,
			width: 800,
			height: 800,
			// renderer: 'svg',
			fitCenter: true,
			modes: {
				default: ["drag-canvas", "zoom-canvas", "drag-node"],
			},
		});
		// 传入数据
		graph.data(data);
		// 执行渲染
		graph.render();
		// graph.fitView();
	}, []);

	return <div ref={ref} id="container"></div>;
}

export default App;
  • g6的自定义边跟自定义节点是类似操作。
G6.registerEdge("hvh", {
	draw(cfg, group) {
		const startPoint = cfg!.startPoint!;
		const endPoint = cfg!.endPoint!;
		const startArrow = (cfg!.style && cfg!.style.startArrow) || undefined;
		const endArrow = (cfg!.style && cfg!.style.endArrow) || undefined;
		const shape = group!.addShape("path", {
			attrs: {
				stroke: "#333",
				path: [
					["M", startPoint.x, startPoint.y],
					[
						"L",
						endPoint.x / 3 + (1 / 3) * startPoint.x,
						endPoint.y / 2 + (1 / 3) * startPoint.y,
					],
					[
						"L",
						endPoint.x * 1.1 + (2 / 3) * startPoint.x,
						endPoint.y / 2 + (2 / 3) * startPoint.y,
					],

					["L", endPoint.x, endPoint.y],
				],
				startArrow, //初始化配统一的箭头
				endArrow,
			},
			// must be assigned in G6 3.3 and later versions. it can be any value you want
			name: "path-shape",
		});
		return shape;
	},
	setState(name, value, item) { //这个方法在3.3后可以不用,改为直接设置全局node/edge StateStyles
		const group = item!.getContainer();
		const shape = group.get("children")[0]; // 顺序根据 draw 时确定
		if (name === "active") {
			if (value) {
				//
				shape.attr("stroke", "red");
				shape.attr("lineWidth", 3);
			} else {
				shape.attr("stroke", "#333");
				shape.attr("lineWidth", 1);
			}
		}
		if (name === "selected") {
			if (value) {
				shape.attr("lineWidth", 3);
			} else {
				shape.attr("lineWidth", 1);
			}
		}
	},
});
  • 初始化可以统一配个箭头样式:
	defaultEdge: {
				type: "line-arrow",
				style: {
					stroke: "#F6BD16",
					startArrow: {
						path: "M 0,0 L 12,6 L 9,0 L 12,-6 Z",
						fill: "#F6BD16",
					},
					endArrow: {
						path: "M 0,0 L 12,6 L 9,0 L 12,-6 Z",
						fill: "#F6BD16",
					},
				},
			},

State

  • g6里为了传递信息,设置了state,这个状态有全局设置或者单个设置。一般来说,统一用全局设置。这个主要用来判断点击了没有hover了没有之类。
  • 有点诡异的是好像相同行为触发的不同状态之间会冲突,所以最好是1个状态对应多值而不是多个状态对应二值。
  • 然后文档全篇在说怎么设置值,没说怎么获取值。。。。。。。后来发现item里写了个方法getState可以拿到state所有值,我也是服了。。state的概念里不写怎么获取,只写hasState是用来判断二值的。然后这个方法获取的值也很诡异,是个数组字符串,比如设置的值是active , ‘1’ ,那么数组里面值是:active:1的字符串。(这设计谁想出来的。。。不能做成key value?不能按key取值?实在不行按active:1字符串取值也行啊)
  • 比如上面那个例子就有冲突,监听那里改下:
		graph.on("edge:click", (ev: IG6GraphEvent) => {
			const edge = ev.item;
			const value = edge!.getStates()[0];
			if (value !== "active:0") {
				if (value === "active:2") {
					graph.setItemState(edge!, "active", "0");
				} else {
					graph.setItemState(edge!, "active", "2"); // 切换选中
				}
			}
		});

		graph.on("edge:mouseenter", (ev: IG6GraphEvent) => {
			const edge = ev.item;
			const value = edge!.getStates()[0];
			if (value !== "active:2") {
				graph.setItemState(edge!, "active", "1");
			}
		});

		graph.on("edge:mouseleave", (ev: IG6GraphEvent) => {
			const edge = ev.item;
			const value = edge!.getStates()[0];
			if (value !== "active:2") {
				graph.setItemState(edge!, "active", "0");
			}
		});

编辑器

  • 编辑器功能基本上是大家最关心的功能,查阅资料发现他们以前做过编辑器且没开源,然后又不搞了,让自己封装。
  • 对于编辑,我摸索了一番,实际就是要自己做个dom面板来进行编辑,我以右键弹出选框选择编辑label为例(实际应该点击后把input设置成显示,修改完毕搞个按钮然后点击保存。再关闭input)。我直接就input不操作显示与否了:
import React, { useEffect, useRef, useState } from "react";
import G6 from "@antv/g6";
import { NodeConfig, Item, IG6GraphEvent } from "@antv/g6/lib/types";
import GGroup from "@antv/g-canvas/lib/group";
import { IShape } from "@antv/g-canvas/lib/interfaces";
const data = {
	nodes: [
		{
			id: "Model",
			type: "model-node", //这个就是注册的
			x: 200,
			y: 100,
			style: {
				width: 160,
				height: 100,
				fill: "#f1b953",
				stroke: "#f1b953",
			},
			openIcon: {
				x: 180, // 控制图标在横轴上的位置
				y: 45, // 控制图标在纵轴上的位置
				fontSize: 20,
				style: {
					fill: "#fc0",
				},
			},
			hideIcon: {
				x: 180, // 控制图标在横轴上的位置
				y: 45, // 控制图标在纵轴上的位置
				fontSize: 20,
				style: {
					fill: "#666",
				},
			},
			labels: [
				{
					x: 10,
					y: 20,
					label: "标题,最长10个字符~~",
					labelCfg: {
						fill: "#666",
						fontSize: 14,
						maxlength: 10,
					},
				},
				{
					x: 10,
					y: 40,
					label: "描述,最长12个字333符~~~",
					labelCfg: {
						fontSize: 12,
						fill: "#999",
						maxlength: 12,
					},
				},
			],
			anchorPoints: [
				//这属性用来设定边的连接中心
				[0, 0.5],
				[0, 1],
			],
		},
		{
			id: "node1", // String,该节点存在则必须,节点的唯一标识
			label: "node1",
			x: 10, // Number,可选,节点位置的 x 值
			y: 200, // Number,可选,节点位置的 y 值
			size: 50,
			anchorPoints: [
				//这属性用来设定边的连接中心
				[1, 0.5],
				[1, 0.8],
			],
		},
		{
			id: "node2", // String,该节点存在则必须,节点的唯一标识
			label: "node2",
			size: 50,
			x: 70, // Number,可选,节点位置的 x 值
			y: 20, // Number,可选,节点位置的 y 值
			anchorPoints: [
				//这属性用来设定边的连接中心
				[1, 0.5],
				[0, 0.5],
				[0.5, 1],
			],
		},
	],
	edges: [
		{
			id: "edge1",
			target: "Model",
			source: "node1",
			type: "hvh",
			// 该边连入 source 点的第 0 个 anchorPoint,
			sourceAnchor: 1,
			// 该边连入 target 点的第1个 anchorPoint,
			targetAnchor: 1,
		},
		{
			id: "edge2",
			target: "node2",
			source: "node1",
			type: "hvh",
		},
		{
			id: "edge3",
			target: "node2",
			source: "Model",
			type: "hvh",
			targetAnchor: 2,
		},
	],
};

interface modelNodeType extends NodeConfig {
	openIcon: {
		x: number;
		y: number;
		fontSize: number;
		style: Object;
	};
	hideIcon: {
		x: number;
		y: number;
		fontSize: number;
		style: Object;
	};
	labels: Array<any>;
}

function shapesAddAttr(shape: Array<any>, key: string, value: any) {
	shape.forEach((v) => v.attr(key, value));
}

G6.registerEdge("hvh", {
	draw(cfg, group) {
		const startPoint = cfg!.startPoint!;
		const endPoint = cfg!.endPoint!;
		const startArrow = (cfg!.style && cfg!.style.startArrow) || undefined;
		const endArrow = (cfg!.style && cfg!.style.endArrow) || undefined;
		const shape = group!.addShape("path", {
			attrs: {
				stroke: "#333",
				path: [
					["M", startPoint.x, startPoint.y],
					[
						"L",
						endPoint.x / 3 + (1 / 3) * startPoint.x,
						endPoint.y / 2 + (1 / 3) * startPoint.y,
					],
					[
						"L",
						endPoint.x * 1.1 + (2 / 3) * startPoint.x,
						endPoint.y / 2 + (2 / 3) * startPoint.y,
					],

					["L", endPoint.x, endPoint.y],
				],
				startArrow, //初始化配统一的箭头
				endArrow,
			},
			// must be assigned in G6 3.3 and later versions. it can be any value you want
			name: "path-shape",
		});
		return shape;
	},
	setState(name, value, item) {
		//这个方法在3.3后可以不用,改为直接设置全局node/edge StateStyles
		const group = item!.getContainer();
		const shape = group.get("children"); // 顺序根据 draw 时确定
		if (name === "active") {
			switch (value) {
				case "0":
					shapesAddAttr(shape, "stroke", "#333");
					shapesAddAttr(shape, "lineWidth", 1);
					break;
				case "1":
					shapesAddAttr(shape, "stroke", "red");
					shapesAddAttr(shape, "lineWidth", 3);
					break;
				case "2":
					shapesAddAttr(shape, "stroke", "blue");
					shapesAddAttr(shape, "lineWidth", 3);
					break;
				default:
					return;
			}
		}
	},
});

// 注册自定义节点
G6.registerNode(
	"model-node",
	{
		drawShape(cfg: modelNodeType, group) {
			const opts = cfg;
			const openIcon = opts.openIcon;
			const hideIcon = opts.hideIcon;
			// 添加节点
			const shape = group!.addShape("rect", {
				name: "model-node",
				draggable: true, // 让自定义节点支持拖拽
				attrs: cfg.style,
			});

			const openSwitch = group!.addShape("circle", {
				draggable: true,
				attrs: {
					r: 10,
					...openIcon,
					...openIcon.style,
				},
				className: "state-open",
			});

			const hideSwitch = group!.addShape("circle", {
				draggable: true,
				attrs: {
					r: 10,
					...hideIcon,
					...hideIcon.style,
				},
				className: "state-hide",
			});

			// 添加多行文本
			for (let i = 0; i < cfg.labels.length; i++) {
				const item = cfg.labels[i];
				const {
					label,
					labelCfg: { maxlength },
				} = item;

				let text = maxlength ? label.substr(0, maxlength) : label || "";

				if (label.length > maxlength) {
					text = `${text}...`;
				}

				group!.addShape("text", {
					attrs: {
						text,
						...item,
						...item.labelCfg,
					},
				});
			}
			this.bindEvent(group, openSwitch);
			this.bindEvent(group, hideSwitch);

			return shape;
		},
		bindEvent(group: GGroup, btn: IShape) {
			//ggroup就是graphics group缩写
			btn.on("click", () => {
				const open = group
					.get("children")
					.find((child: any) => child.cfg.className === "state-open");
				const close = group
					.get("children")
					.find((child: any) => child.cfg.className === "state-hide");
				if (btn.cfg.className === "state-open") {
					const item = group.get("item"); //在这个图形分组下的item
					const model = item.getModel();
					open.toBack();
					close.toFront(); //这个是让2个圆z轴位置变化 item上的方法
					model.style.height = 100;
					item.update(model);
				} else if (btn.cfg.className === "state-hide") {
					const item = group.get("item");
					const model = item.getModel();
					close.toBack(); //Item 上的方法
					open.toFront(); //item上的方法
					model.style.height = 50;
					item.update(model); //item上的方法
				}
			});
		},
	},
	"single-node"
); // 继承自内置节点

function App() {
	const ref = useRef<HTMLDivElement>(null);
	const [state, setState] = useState("");
	const [changeItem, setChangeItem] = useState<Item>();
	useEffect(() => {
		const contextMenu = new G6.Menu({
			getContent(graph) {
				console.log("graph", graph);
				return `<div>编辑lable</div>`;
			},
			handleMenuClick: (target, item) => {
				//target是dom item 是Item
				//只有click了才知道是哪个节点触发的
				const model = item.getModel();
				const value = model.label;
				if (typeof value === "string") {
					//将节点的值赋给input并绑上onchange给它
					setState(value);
					setChangeItem(item); //要调用更新,最小是item item才有update
				}
			},
		});
		const graph = new G6.Graph({
			container: ref.current!,
			width: 800,
			height: 800,
			// renderer: 'svg',
			fitCenter: true,
			modes: {
				default: ["drag-canvas", "zoom-canvas", "drag-node"],
				//edit: ["click-select", "click-add-node"],
			},
			defaultEdge: {
				type: "line-arrow",
				style: {
					stroke: "#F6BD16",
					startArrow: {
						path: "M 0,0 L 12,6 L 9,0 L 12,-6 Z",
						fill: "#F6BD16",
					},
					endArrow: {
						path: "M 0,0 L 12,6 L 9,0 L 12,-6 Z",
						fill: "#F6BD16",
					},
				},
			},
			plugins: [contextMenu],
		});

		// graph.on("node:click", (ev: any) => {
		// 	console.log(ev);
		// 	graph.setMode("edit");
		// });

		graph.on("edge:click", (ev: IG6GraphEvent) => {
			const edge = ev.item;
			const value = edge!.getStates()[0];
			if (value !== "active:0") {
				if (value === "active:2") {
					graph.setItemState(edge!, "active", "0");
				} else {
					graph.setItemState(edge!, "active", "2"); // 切换选中
				}
			}
		});

		graph.on("edge:mouseenter", (ev: IG6GraphEvent) => {
			const edge = ev.item;
			const value = edge!.getStates()[0];
			if (value !== "active:2") {
				graph.setItemState(edge!, "active", "1");
			}
		});

		graph.on("edge:mouseleave", (ev: IG6GraphEvent) => {
			const edge = ev.item;
			const value = edge!.getStates()[0];
			if (value !== "active:2") {
				graph.setItemState(edge!, "active", "0");
			}
		});

		
		// 传入数据
		graph.data(data);
		// 执行渲染
		graph.render();
		// graph.fitView();
	}, []);

	const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
		setState(e.target.value);
		//修改state后改变节点Label
		if (changeItem) {//其他样式控制同理这么操作
			const model = changeItem.getModel();
			model.label = e.target.value;
			changeItem.update(model);
		}
	};

	return (
		<div>
			<div id="editor">
				<span>修改label :</span>
				<input  value={state} onChange={handleChange}></input>
			</div>
			<div ref={ref} id="container"></div>
		</div>
	);
}

export default App;
  • 最后需要注意下自定义节点的格式,如果像我这么写自定义节点,那么label很可能就不对或者没有,所以事先需要规划好到底哪些属性应该去配置,哪些属性可以配置。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

业火之理

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值