简介
在流程图中锚点是一个重要的概念,主要有两种作用:
- 限制节点和边的连接位置
- 参与一些交互,是一些交互的触发点和结束点
大多数情况下锚点只需要完成第一种功能即可,锚点也不需要展示出来。但是有一些更加复杂的情况:
- 锚点显示出来不参与交互,仅仅是作为标识作用
- 锚点有个性化形状样式的需求,单个图上有不同的锚点
- 锚点支持交互,在同一个图上的不同锚点,交互也不一样
所以本章主要讨论三个方面的问题:
- 节点的和边的连接
- 锚点的展示
- 锚点的操作
节点和边的连接
节点和边的连接可以分为两种场景:
- 不存在锚点的情况
- 存在锚点的情况
锚点使边在节点上的连接更加规整,但是会有一些隐含的含义:顺序、层次
不存在锚点的情况
大多数节点和边的连接不需要锚点,仅需要将边连接到节点,把同节点边框的交点作为连接点即可,一般用于仅表示节点之间关联关系的场景:
存在锚点的情况
而在需要展示节点之间存在流程关系,存在层次结构的场景下,一般需要锚点,将边收拢到一起:
上图的树所有的节点的输入和输出都有固定的点
流程上表示前后关系,甚至每个连接点都有意义时都要锚点
两种情况的示例
看一下下面这个节点,存在锚点和不存在锚点的连接情况就可以理解:
锚点的展示
讨论锚点的展示主要考虑三点:
- 锚点显示的位置
- 锚点的形状(样式)
-
锚点显示的位置
由于锚点是依存到节点上的,而节点的位置、大小经常变动,所以锚点的位置必须是一个相对位置概念,在 G6 中锚点的位置使用相对于节点包围盒的位置来表示。
上面的四个位置分别是:上 [0.5, 0], 右 [1, 0.5], 下 [0.5, 1], 左 [0, 0.5]锚点的形状和样式
在不同的关系图中锚点的形状可能会不一样,在交互过程中锚点的样式也可能会发生改变,在这里我们来看三方面的问题:
默认形状
- 自定义形状
- 样式修改
锚点的状态
锚点在交互过程中会有不同的状态详细的在后面的锚点的操作中讨论,但是在这里我们可以思考如何开发者方便的通过设置锚点的样式,来表示锚点的状态。所以在自定义锚点的形状时,需要提供设置状态时样式改变的接口。
锚点的操作
只有在编辑器的场景下锚点才需要参与交互,一般情况下有两种交互:
- 从锚点上拉出边
- 将边拉到锚点上进行连接
可以认为是一个连贯的过程:创建边、指定 source、指定 target。
在这个过程中需要考虑一些交互设计问题:
- 示能 + 意符:能够让用户知道能进行什么样的交互
- 映射:节点和锚点之间的映射关系,用户连接边时通过一种非常易于理解的方式进行连接
-
示能和意符
在交互设计中示能和意符是两个概念,但是在这里我们作为一个方面来讨论,都是让用户能够意识到能进行什么样的交互,无论是通过形状、动画还是文字表示,都需要对一些交互进行说明,提醒用户知道能干什么不能干什么。
映射
通过空间的映射,将锚点和节点关联起来,通过用户对边和节点位置的关系自动吸附到锚点上,这些都是映射相关的概念。
反馈
用户的每个操作都要有反馈,明确的告诉用户交互进行到了哪一步,是否允许继续进行,反馈是同交互过程中的所有概念连接在一起的。例如:鼠标移动到锚点上鼠标变成十字(示能),在旁边显示文本提示(意符),鼠标点下去进行拖动,出现边,拖拽过程中边不断延伸,移动到另一个节点附近,可以连接的锚点高亮,在这个节点上释放,边吸附到最近的锚点上(映射)。
从这些方面思考我们可以确定在 G6 中我们需要思考锚点的功能和防控锚点的功能
锚点的功能都是同边的创建、边更进行关联,我们在讨论锚点功能的时候必须从这两方面考虑。
从创建和变更边方面来看: 拖拽出边
改变边的锚点(业务中不需要通过拖动锚点改变边的指向,容易操作误操作,一般都是删除不需要的边,再重新连接)-
功能的反馈
我们从锚点的样式和鼠标形状方面来看在不同功能中的反馈:
锚点显示不同的状态:
- 默认状态(default)、
- 鼠标悬浮上的状态(hover)、
- 响应连接的状态(linkable)
- 交互过程中鼠标样式的变化
G6.registerNode(‘custom’, { getAnchorPoints(cfg) { // TODO
return [
[0, 0.5], [1, 0.5]
];
} }, ‘rect’);
- 仅考虑的连接,没有设想显示锚点不同形状,不同样式的需求
<a name="540c79c4"></a>
#### 锚点仅显示
仅显示锚点时,需要考虑锚点的样式,可以在位置后面加个锚点的配置项,例如:
```javascript
var nodes = [{
id: '1',
x: 10,
y: 10,
anchorPoints: [
[0, 0.5, {type: 'circle', style: {stroke: 'red', fill: 'white'}}],
[1, 0.5, {type: 'rect', style: {stroke: 'blue', fill: 'white'}}]
]
}];
-
支持交互
锚点如果要支持交互,同时能够分清楚不同的锚点,就需要每个锚点有对应的数据 (model)有两种方案:
在 anchorPoints 中设置 model
- 同 nodes 一样单独定义 anchors
在 anchorPoints 中定义
var nodes = [{
id: '1',
x: 10,
y: 10,
anchorPoints: [
[0, 0.5, {id: 'a1', shape: 'in', style: {stroke: 'red', fill: 'white'}}],
[1, 0.5, {id: 'a2', shape: 'out', style: {stroke: 'blue', fill: 'white'}}]
]
}];
单独定义 anchors
var nodes = [{
id: '1',
x: 10,
y: 10
}];
var achors = [
{id: 'a1', refNode: '1', shape: 'in', refX: 0, refY: 0.5,
style: {stroke: 'red', fill: 'white'}
},
{id: 'a2',refNode: '1', shape: 'out', refX: 1, refY: 0.5,
style: {stroke: 'blue', fill: 'white'}
}
];
- 这两种方案各有利弊,第一种可以同现有的数据定义兼容,第二种数据定义更加清晰,不需要对 nodes 进行加工
- 通过 shape 来找到自定义的锚点,状态改变和交互都可以按照 node 和 edge 的通用逻辑实现
连接
锚点的自定义
锚点的自定义比较简单,仅需要考虑绘制和状态更新即可,锚点的更新重绘即可。
定义锚点时,将hover、select和default时的样式都统一定义到style里面。G6.registerAnchor('anchorName', {
itemType: 'anchor',
meta: {
size: {
width: 184,
height: 40,
},
borderRadius: 4,
},
style: {
hover: {
isGroup: true,
rect: {
visible: true,
},
text: {
visible: true,
},
},
select: {
stroke: '#ccc',
fill: '#999'
},
default: {
isGroup: true,
rect: {
visible: false,
},
text: {
visible: false,
},
},
},
state: {},
draw(cfg, group) {},
setState(item, config) {}
});
一般来说锚点的更新主要发生在:
- 节点移动、放大、缩小时
- 节点更新时
锚点的交互
锚点事件的监听
可以通过监听 ‘edge:xxx’ 事件来监听 anchor 的事件
graph.on('edge:mouseenter', ev => {
var anchor = ev.item;
var node = anchor.getNode();
// graph.setItemState(anchor, 'active', true);
// TO DO
graph.setState(anchor, {
select: {},
hover: {}
});
});