节点与边的层级

阅读时间约 8 分钟

节点与边在视觉上的层级涉及到了它们相对应的 图形分组 Group。本文提到的所有分组 Group 都为 G6 的图形分组 Group,而非 G6 的  节点分组 Combo,其区别在 图形分组 Group  中说明。

图形分组 Group 中我们提到:在 G6 中,Graph 的一个实例中的所有节点属于同一个变量名为 nodeGroup 的 group,所有的边属于同一个变量名为 edgeGroup 的 group。节点 group 在视觉上的层级(zIndex)高于边 group,即所有节点会绘制在所有边的上层。

但有时,我们需要让边在视觉上在节点上层。例如,高亮节点及其相关边和邻居、高亮一条边等。可以通过配合图实例的配置项  groupByTypes 以及节点和边的 toFront()toBack() 函数实现。为实现如下效果:鼠标进入节点时,提升相关边以及邻居节点的层级;离开节点时恢复;鼠标进入边时,提升边及其两端点的层级;离开边时恢复。Demo 完整代码
img

要实现上图效果,需要以下步骤:

  • Step 1:实例化图时配置 groupByTypesfalse
  • Step 2:将节点放置在边上层;
  • Step 3:监听鼠标事件并改变目标元素层级。

前提代码

下面代码完成了引入 G6、数据设置、实例化图、渲染图的命令等。后文将修改下面这份代码中以达到上图高亮效果。

// 数据源
const data = {
  nodes: [
    {
      id: 'node0',
      x: 100,
      y: 100,
      size: 20,
    },
    {
      id: 'node1',
      x: 200,
      y: 200,
      size: 20,
    },
    {
      id: 'node2',
      x: 150,
      y: 150,
      size: 20,
    },
    {
      id: 'node3',
      x: 150,
      y: 250,
      size: 20,
    },
    {
      id: 'node4',
      x: 150,
      y: 200,
      size: 20,
    },
  ],
  edges: [
    {
      id: 'edge0',
      source: 'node0',
      target: 'node1',
    },
    {
      id: 'edge1',
      source: 'node2',
      target: 'node3',
    },
  ],
};

// 实例化图
const graph = new G6.Graph({
  container: 'mountNode',
  width: 800,
  height: 600,
  // 为方便演示,加粗边
  defaultEdge: {
    style: {
      lineWidth: 2,
    },
  },
});

// 读取数据
graph.data(data);
// 渲染图
graph.render();

Step 1 实例化图时的配置

groupByTypes 是图的一个配置项,当其为默认值 true 时,所有节点在一个名为 nodeGroup 的分组,所有边在另一个名为 edgeGroup 的分组,且 nodeGroupedgeGroup 上层。将其设置为 false 后,将不存在 nodeGroupedgeGroup,所有节点和边在同一个分组,它们的层级根据生成的顺序决定。

参数描述

名称类型默认值描述
groupByTypesBooleantrue各种元素是否在一个分组内,决定节点和边的层级问题,默认情况下所有的节点在一个分组中,所有的边在一个分组中,当这个参数为 false 时,节点和边的层级根据生成的顺序确定。

使用方式

更改 前提代码 中实例化图部分代码,添加 groupByTypes 配置项,并设置为 false

const graph = new G6.Graph({
  // ...  // 其他配置
  groupByTypes: false,
});

此时,将会得到如下效果:
img

Step 2 将节点放置在边上层

上一步结果中节点在边的下层不符合常规,是由于 groupByTypesfalse 时,节点和边的层级根据生成的顺序确定,而边的生成顺序在节点之后,因此所有边被绘制到了节点上方。为了使图符合常规——节点在上层,边在下层,可以在 graph.render() 之后将所有的节点通过 toFront() 函数提前。

函数描述

// 将节点实例 nodeItem 提前到其父级 group 的最前面
nodeItem.toFront();
// 将节点实例 nodeItem 放置到其父级 group 的最后面
nodeItem.toBack();
// 将边实例 edgeItem 提前到其父级 group 的最前面
edgeItem.toFront();
// 将边实例 edgeItem 放置到其父级 group 的最后面
edgeItem.toBack();

使用方法

// const graph = ...
graph.data(data);
graph.render();
// 获取图上的所有节点实例
const nodes = graph.getNodes();
// 遍历节点实例,将所有节点提前。
nodes.forEach((node) => {
  node.toFront();
});
// 更改层级后需要重新绘制图
graph.paint();


这样,所有节点被绘制在边上层:
img

Step 3 监听鼠标事件并改变目标元素层级

在效果图中,鼠标进入节点时,相关边和节点的层级被提升到最上层,鼠标离开节点使恢复。边同理。这一步将实现这一交互效果。

函数描述

使用下面四个函数监听鼠标的进入、离开元素的事件:

// 鼠标进入节点事件
graph.on('node:mouseenter', (ev) => {
  // ...
});

// 鼠标离开节点事件
graph.on('node:mouseleave', (ev) => {
  // ...
});

// 鼠标进入边事件
graph.on('edge:mouseenter', (ev) => {
  // ...
});

// 鼠标离开边事件
graph.on('edge:mouseleave', (ev) => {
  // ...
});

使用方法

// 鼠标进入节点事件
graph.on('edge:mouseenter', (ev) => {
  // 获得鼠标当前目标边
  const edge = ev.item;
  // 该边的起始点
  const source = edge.getSource();
  // 该边的结束点
  const target = edge.getTarget();
  // 先将边提前,再将端点提前。这样该边两个端点还是在该边上层,较符合常规。
  edge.toFront();
  source.toFront();
  target.toFront();
  // 注意:必须调用以根据新的层级顺序重绘
  graph.paint();
});

graph.on('edge:mouseleave', (ev) => {
  // 获得图上所有边实例
  const edges = graph.getEdges();
  // 遍历边,将所有边的层级放置在后方,以恢复原样
  edges.forEach((edge) => {
    edge.toBack();
  });
  // 注意:必须调用以根据新的层级顺序重绘
  graph.paint();
});

graph.on('node:mouseenter', (ev) => {
  // 获得鼠标当前目标节点
  const node = ev.item;
  // 获取该节点的所有相关边
  const edges = node.getEdges();
  // 遍历相关边,将所有相关边提前,再将相关边的两个端点提前,以保证相关边的端点在边的上方常规效果
  edges.forEach((edge) => {
    edge.toFront();
    edge.getSource().toFront();
    edge.getTarget().toFront();
  });
  // 注意:必须调用以根据新的层级顺序重绘
  graph.paint();
});

graph.on('node:mouseleave', (ev) => {
  // 获得图上所有边实例
  const edges = graph.getEdges();
  // 遍历边,将所有边的层级放置在后方,以恢复原样
  edges.forEach((edge) => {
    edge.toBack();
  });
  // 注意:必须调用以根据新的层级顺序重绘
  graph.paint();
});