我正在参加「掘金·启航计划」
系列文章:
- 手摸手使用G6实现(轻)图编辑应用系列-初识G6
- 手摸手使用G6实现(轻)图编辑应用系列-自定义节点
- 手摸手使用G6实现(轻)图编辑应用系列-自定义边、箭头
- 手摸手使用G6实现(轻)图编辑应用系列-自定义行为
- 手摸手使用G6实现(轻)图编辑应用系列-后续优化
前言
上一节,自定义了边、箭头,接下来自定义行为
行为分析
-
操作行为
- 编辑模式下,从物料区拖拽到画布,创建指定节点
- 无连线节点鼠标移入点击删除操作图标
- 点击创建图标创建一个以点击节点为起点,以鼠标位置为终点的连接线
- 连接线需要线为虚线,箭头为实线,样式不同于其他线样式
- 在除节点外的地方点击,删除此连接线
- 点击任意节点指定区域时,建立连接线
- 用户填入参数,用来描述连接线
- 根据节点间边数决定边类型,进行更新效果
-
官方提供create-edge内置行为,没有使用主要有三个原因
- 边实例在边创建后才会触发事件传递出来,导致连线过程中,鼠标移动到自身节点时,线会变active样式(实际连接线在用户确定前样式不能变)
- 为了熟悉此扩展机制的使用方式
- 把从物料区拖拽创建节点以及删除节点也封装到自定义行为内
-
官方自定义行为方法
- 行为属于交互,交互起源于用户在系统上的所有事件,是否允许交互发生同事件密切相关;本质就是通过监听事件,对相应情况做出相应
G6.registerBehavior(
// 自定义行为名称
'activate-node',
{
//默认配置,会将其返回对象的key挂在到this自身上
getDefaultCfg(){
return {};
}
// 自定义行为涉及的事件
getEvents() {
return {};
},
}
)
- 自定义行为使用
- 图配置中存在交互模式Mode,模式其实就是行为的集合
- 将行为名称放入指定模式对应的数组中,图实例切换到此种模式即可生效
行为实现
监听事件
- 官方支持
- 全局事件
- canvas事件
- 节点、边、combo事件
- 图形事件
- 时机事件
- 自定义事件
- 实现思路
- 拖拽节点到画布,监听全局drop事件
- 删除节点,监听图形click事件
- 创建连接线,监听节点click事件
- 跟随鼠标连线,监听全局mousemove事件
- 删除线,监听线click事件
代码实现
定义行为涉及事件
getEvents() {
return {
drop: 'dropNode',
'text-shape-delete:click': 'deleteClick',
'node:click': 'onClick',
mousemove: 'onMousemove',
'edge:click': 'onEdgeClick',
};
},
创建节点
- 根据拖动时存储在dataTransfer中的数据,以及drop的事件对象的坐标来创建节点
- 监听不同对象拖拽结束获取的坐标有些区分
- 监听拖拽元素的拖拽结束事件,获取的坐标是基于浏览器窗口的 clientX/clientY
- 监听G6全局drop事件,获取的坐标是G6 pointX/pointY 坐标系
- G6的三种坐标系
- clientX/clientY
- canvasX/canvasY
- pointX/pointY
- 注:缩放平移改动的都是pointX/pointY,官方提供了各种坐标系间点的转换方法
- 代码实现
addNode (transferData, { x, y }) {
const nodeData = JSON.parse(transferData);
this.noConnectNode = this.noConnectNode.filter(item => item.id !== nodeData.id);
const model = {
...nodeData,
x,
y,
};
const item = graph.addItem('node', model);
......
},
dropNode(e){
const { originalEvent } = e;
if(originalEvent.dataTransfer) {
const transferData = originalEvent.dataTransfer.getData('dragComponent');
if(transferData) {
addNode(transferData, e);
}
}
},
删除节点
deleteClick(ev){
const node = ev.item;
this.graph.removeItem(node);
},
节点间连线
- 连接线(虚线,实线箭头)
connectLine:{
stroke: '#F6BD16',
lineWidth: 2,
lineDash: [5],
endArrow: {
path: G6.Arrow.vee(6, 10, 0),
d: 0,
fill: '#F6BD16',
lineDash: [0.000001]
}
},
- 线三种情况
- 创建线,通过点击的目标图形判断是否指定图形
- 不是 return
- 是,创建一个起点终点都是点击节点的线
- 移动线
- 通过移动事件获取鼠标的pointX/pointY,更新线的目标节点位置
- 连接线,通过点击的目标图形判断是否指定图形
- 不是 return
- 是,更新边的目标节点值,触发连线成功事件(要处理自连情况)
- 注:过程中需要存储的值都放到instance全局变量上
- 创建线,通过点击的目标图形判断是否指定图形
// 鼠标移动
onMousemove(ev) {
const self = this;
// 鼠标位置
const point = { x: ev.x, y: ev.y };
if (self.addingEdge && instance.currentEdge) {
self.graph.updateItem(instance.currentEdge, {
target: point,
});
}
},
onClick(ev) {
const self = this;
const targetShape = ev.shape;
const node = ev.item;
const graph = self.graph;
if(!self.addingEdge){
if (!targetShape || targetShape.get('name') !== 'text-shape-connect') return;
}else{
if(!targetShape || !['key-shape', 'center-image'].includes(targetShape.get('name'))) return;
}
const model = node.getModel();
if (self.addingEdge && instance.currentEdge) {
// 连线
const defaultModel = {
target: model.id,
}
if(instance.currentModel.id === model.id){
// 自连情况,不需要处理箭头
defaultModel.type = 'loop';
}
graph.updateItem(instance.currentEdge, defaultModel);
graph.emit('afterPaintEdge', {edge: instance.currentEdge});
self.addingEdge = false;
} else {
// 创建线
instance.currentModel = model;
instance.currentEdge = graph.addItem('edge', {
source: model.id,
target: model.id,
style: Object.assign(
{},
styleObj.connectLine
)
});
self.addingEdge = true;
}
},
删除线
- 鼠标移动过程中,单击任意位置,触发线的点击事件,若处于连线过程中并且点击的是连接线,则删除
onEdgeClick(ev) {
const self = this;
const currentEdge = ev.item;
if (self.addingEdge && instance.currentEdge === currentEdge) {
self.graph.removeItem(instance.currentEdge);
instance.currentEdge = null;
instance.currentModel = null;
self.addingEdge = false;
}
},
结尾
截至到此,自定义行为实现完成,下一节会介绍实现demo过程中功能点的优化
希望看完的朋友可以点个喜欢/关注,您的支持是对我最大的鼓励