Router

阅读时间约 4 分钟

路由将边的路径点 vertices 做进一步转换处理,并在必要时添加额外的点,然后返回处理后的点(不包含边的起点和终点)。例如,经过 orth 路由处理后,边的每一条线段都是水平或垂直的正交线段。

我们在 Registry.Router.presets 命名空间中提供了以下几种路由。

路由名称说明
normal默认路由,原样返回路径点。
orth正交路由,由水平或垂直的正交线段组成。
oneSide受限正交路由,由受限的三段水平或垂直的正交线段组成。
manhattan智能正交路由,由水平或垂直的正交线段组成,并自动避开路径上的其他节点(障碍)。
metro智能地铁线路由,由水平或垂直的正交线段和斜角线段组成,类似地铁轨道图,并自动避开路径上的其他节点(障碍)。
er实体关系路由,由 Z 字形的斜角线段组成。

在使用时,可以为边设置路由:

const edge = graph.addEdge({
  source,
  target,
  router: {
    name: 'oneSide',
    args: {
      side: 'right',
    },
  },
})

当路由没有参数时,也可以简化为:

const edge = graph.addEdge({
  source,
  target,
  router: 'oneSide',
})

也可以调用 edge.setRouter 方法来设置路由:

edge.setRouter('oneSide', { side: 'right' })

在创建画布时,可以通过 connecting 选项来设置全局默认路由(画布的默认路由是 'normal'):

new Graph({
  connecting: {
    router: { 
      name: 'oneSide',
      args: {
        side: 'right',
      },
    },
  },
})

当路由没有参数时,也可以简化为:

new Graph({
  connecting: {
    router: 'orth',
  },
})

下面我们一起来看看如何使用内置路由,以及如何自定并注册自定义路由。

presets

normal

系统的默认路由,该路由原样返回传入的 vertices 路径点。

orth

正交路由,该路由在路径上添加额外的一些点,使边的每一条线段都水平或垂直正交。

支持的参数如下表:

参数名参数类型是否必选默认值参数说明
paddingSideOptions20设置锚点距离转角的最小距离。

SideOptions 定义如下:

export type SideOptions =
  | number
  | {
      vertical?: number
      horizontal?: number
      left?: number
      top?: number
      right?: number
      bottom?: number
    }

例如:

graph.addEdge({
  source,
  target,
  vertices: [
    { x: 100, y: 200 }, 
    { x: 300, y: 120 },
  ],
  router: {
    name: 'orth',
    args: {
      padding: {
        left: 50,
      }
    }
  },
});

oneSide

'oneSide' 路由是正交路由 'orth' 的受限版本,该路生成一个严格的三段路由:从起始节点的 side 侧开始,经过中间段,再从终止节点的 side 侧结束路由。需要特别注意的是,使用该路由时请不要同时指定 vertices,否则路由效果会非常差。

支持的参数如下表:

参数名参数类型是否必选默认值参数说明
side'left' | 'right' | 'top' | 'bottom''bottom'路由的起始/结束方向,默认值为 'bottom'
paddingSideOptions20设置锚点距离转角的最小距离。

例如:

graph.addEdge({
  source,
  target,
  router: {
    name: 'oneSide',
    args: { side: 'right' },
  },
})

manhattan

曼哈顿路由 'manhattan' 路由是正交路由 'orth' 的智能版本,该路由由水平或垂直的正交线段组成,并自动避开路径上的其他节点(障碍)。

我们为该路由算法提供了丰富的选项:

参数名参数类型是否必选默认值参数说明
stepnumber10路由算法步进步长,其值越小计算量越大。
推荐使用画布的网格大小(graph.options.grid.size)。
maximumLoopsnumber2000最大迭代次数,到达最大迭代次数后将使用候补路由。
maxDirectionChangenumber90最大旋转角度。
excludeTerminals('source' | 'target')[][]忽略起始或终止节点,忽略后不参与障碍物计算。
excludeShapesstring[][]忽略指定形状的节点,忽略后不参与障碍物计算。
excludeHiddenNodesbooleanfalse忽略隐藏的节点,忽略后不参与障碍物计算。
startDirectionsstring[]['top', 'right', 'bottom', 'left']支持从哪些方向开始路由。
endDirectionsstring[]['top', 'right', 'bottom', 'left']支持从哪些方向结束路由。

例如:

graph.addEdge({
  source,
  target,
  router: {
    name: 'manhattan',
    args: { 
      startDirections: ['top'],
      endDirections: ['bottom'],
    },
  },
})

metro

地铁路由 'metro' 是曼哈顿路由 manhattan 的一个变种,它由水平或垂直的正交线段和斜角线段组成,类似地铁轨道图,并自动避开路径上的其他节点(障碍)。其选项与 manhattan 路由一样,但 maxDirectionChange 的默认值为 45,表示路由线段的最大倾斜角度为 45 度。

例如:

graph.addEdge({
  source,
  target,
  router: {
    name: 'metro',
    args: { 
      startDirections: ['top'],
      endDirections: ['bottom'],
    },
  },
})

er

实体关系路由 'er' 由 Z 字形的斜角线段组成,常用于表示 ER 图中的实体之间的连线。

支持的参数如下表:

参数名参数类型是否必选默认值参数说明
offsetnumber | 'center'32路由的第一个点和最后一个点与节点之间的距离。当取值为 'center' 时,节点距离的中心作为路由点坐标。
minnumber16路由的第一个点和最后一个点与节点之间的最小距离。
direction'T' | 'B' | 'L' | 'R' | 'H' | 'V'undefined路由方向,缺省时将自动选择最优方向。

例如:

graph.addEdge({
  source,
  target,
  router: {
    name: 'er',
    args: {
      offset: 24,
    },
  },
})

registry

路由实际上是一个具有如下签名的函数,返回加工后的点。

export type Definition<T> = (
  this: EdgeView,
  vertices: Point.PointLike[],
  args: T,
  edgeView: EdgeView,
) => Point.PointLike[]
参数名参数类型参数说明
thisEdgeView边的视图。
verticesPoint.PointLike[]边的路径点。
argsT路由参数。
edgeViewEdgeView边的视图。

并在 Registry.Router.registry 对象上提供了 registerunregister 两个方法来注册和删除路由。

register

register(entities: { [name: string]: Definition }, force?: boolean): void
register(name: string, entity: Definition, force?: boolean): Definition

注册路由。

unregister

unregister(name: string): Definition | null

取消注册路由。

自定义路由

我们可以按照上面规则来创建自定义路由,例如,实现随机走线的路由:

// 路由参数
interface RandomRouterArgs {
  bounces?: number
}

function randomRouter(vertices: Point.PointLike[], args: RandomRouterArgs, view: EdgeView) {
  const bounces = args.bounces || 20
  const points = vertices.map((p) => Point.create(p))

  for (var i = 0; i < bounces; i++) {
    const sourceCorner = view.sourceBBox.getCenter()
    const targetCorner = view.targetBBox.getCenter()
    const randomPoint = Point.random(
      sourceCorner.x,
      targetCorner.x,
      sourceCorner.y,
      targetCorner.y,
    )
    points.push(randomPoint)
  }

  return points
}

实际上,我们将 Registry.Router.registry 对象的 registerunregister 方法分别挂载为 Graph 的两个静态方法 Graph.registerRouterGraph.unregisterRouter,所以我们可以像下面这样来注册路由:

Graph.registerRouter('random', randomRouter)

注册以后我们就可以通过路由名称来使用:

edge.setRouter('random', { bounces: 3 })