GoJS交互式图形库——构建流程图/组织架构图...

690 阅读6分钟

GoJs

探索 GoJS:构建复杂交互图表的强大引擎

在前端开发中,我们熟知像 ECharts 这样优秀的​​数据图表库​​,用于展示折线图、柱状图等数据可视化形态。然而,当面临需要表达​​关系​​和​​结构​​的场景时——例如绘制流程图、组织架构图、UML图、思维导图乃至甘特图——我们就需要一款像 ​​GoJS​​ 这样的专业图表库。

与国产开源库(如 AntV G6/X6)相比,GoJS 的核心优势在于其​​清晰直观的设计理念​​和​​极高的自定义自由度​​。它通过模板化、数据绑定的方式构建元素,让开发者可以更轻松地理解并掌控图表的生成逻辑。无论是简单的节点连线,还是高度复杂的​​自定义布局算法​​,GoJS 都提供了强大而灵活的底层支持。

GoJS 的功能远比我们初识时想象的更强大,足以应对极其专业的图表需求,但许多​​实用的高级配置项缺乏直接的示例代码​​。这导致其深层次的应用潜力在社区中的开发度和讨论度相对较低,需要开发者深入文档进行探索。本文对GoJS开发时涉及的特性进行整理,供GoJS使用者查阅(【初始化】章节介绍基本用法,对于有经验者可跳过);

屏幕截图 2025-09-16 225701.png

中文文档(2.0版本) Samples(2.0版本 github(2.0版本)

官方文档(3.0版本) Sample(3.0版本)

开始

安装引入

2.X版本

开发环境:unpkg.com/gojs@2/rele…

生产环境:unpkg.com/gojs@2/rele…

更多模块规范见release:GoJS/release at 2.3,本文以2.X版本为主

去水印

在库文件代码搜索 7eba17a4ca3b1a83460,将类似下面结构的代码进行替换:

a.ir=b.W[Ra("7eba17a4ca3b1a83460")][Ra("78a118b7")](b.W,ok,4,4)
// 替换为
a.ir=function(){return true;}; // a.ir(属性名) 保持一致就行

3.X版本

npm

npm install gojs --save

初始化

创建图(Diagram)
<script lang="ts" setup>
import go from "@/lib/go-debug.mjs";

const diagramContainerRef = ref();
const initGoJSDiagram = () => {
  const gojs_diagram = new go.Diagram(diagramContainerRef.value, {
    // options
  });
}
onMounted(initGoJSDiagram);
</script>

<template>
  <div class="flex justify-center items-center">
    <div id="diagram_container" ref="diagramContainerRef"></div>
  </div>
</template>

<style lang="less" scoped>
#diagram_container {
  width: 60vw;
  height: 65vh;
}
</style>
定义数据模型

图的节点和连接是有模型数据的可视化(model-view架构),通过自定义模型对象并描述节点和链接的数据实现视图;

const $ = go.GraphObject.make;
const initGoJSDiagram = () => {
  // 创建空图
  const gojs_diagram = new go.Diagram(diagramContainerRef.value);
  // 创建模型
  const diagram_model = $(go.Model);
  // 创建模型的节点数据
  const nodeData = [{key: "AAA"}, {key: "BBB"}, {key: "CCC"}];
  diagram_model.nodeDataArray = nodeData;
  // 使用模型
  gojs_diagram.model = diagram_model;
};

// gojs v3.0+ 写法
const gojs_diagram = new go.Diagram(diagramContainerRef.value);
const diagram_model = new go.Model([{key: "AAA"}, {key: "BBB"}, {key: "CCC"}]);
gojs_diagram.model = diagram_model;
定义节点(Node)

通过Node创建节点容器,并添加图形对象(GraphObject)作为节点内容:

// 定义节点模板
const nodeTemplate = $(
  go.Node, "Auto", { background: "#44CCFF" },
  $(go.Shape, "Rectangle", { fill: "white", stroke: "skyblue" }),
  $(
    go.TextBlock,
    { margin: 8, font: "bold 16px sans-serif", stroke: "#ccc" },
    new go.Binding("text", "text")
  )
);
gojs_diagram.nodeTemplate = nodeTemplate;

节点模板

通过添加节点模板映射(nodeTemplateMap)实现图上多种类型节点(Node)

// 声明节点模板
export const middlewareNodeTemplate = $(
  go.Node,
  'Auto',
  { ...节点属性 },
  // 声明节点内容的元素
);
export const originNodeTemplate = $(
  go.Node,
  // ...
)
// 为图定义节点模板
gojs_diagram.nodeTemplate.add('middleware', middlewareNodeTemplate);
gojs_diagram.nodeTemplate.add('origin', originNodeTemplate);
// 数据模型nodeDataArray:[{category: "origin", key: 1}, {category: 'middleware',key: 2}, ...]
组织结构相关模型(XxxModel)

gojs.Model 是基础的模型类,gojs支持常见的表示节点关系的模型:

GrpahLinksModel

GrpahLinksModel允许您在节点之间建立任意数量的链接,并且链接方向不限。

// 定义并描述模型
const diagram_model = $(go.GraphLinksModel);
const nodeData = [
  { key: 1, text: "AAA" },
  { key: 2, text: "BBB" },
  { key: 3, text: "CCC" }
];
diagram_model.nodeDataArray = nodeData;
gojs_diagram.model = diagram_model;

// 描述节点连接
const linkData = [
  { from: 1, to: 2 },
  { from: 2, to: 3 }
];
diagram_model.linkDataArray = linkData;

TreeModel

树模型中的链接是通过为节点数据指定“父”parent来创建树状关系;

// 定义并描述模型
const diagram_model = $(global.TreeModel);
const nodeData = [
  {key: "A", text: "AAA" },
  {key: "B", text: "BBB", parent: "A" },
  {key: "C", text: "CCC", parent: "A" },
]
diagram_model.nodeDataArray = nodeData;
gojs_diagram.model = diagram_model;
定义链接(Link)

通过构建链接模板(LinkTemplate)自定义节点连接线形状/路径等属性;

const linkTemplate = $(
  go.Link,
  { routing: go.Link.Orthogonal }, // 链接线路径类型
  $(go.Shape, { strokeWidth: 2, stroke: "#fff" }), // 链接图形
);
gojs_diagram.linkTemplate = linkTemplate;

更多链接相关见:Class Link | GoJs

自动布局(Layout)

图默认采用对未指定位置的节点网格排列的 规则布局,也支持显式地为节点提供一个位置值(实现固定位置);也可以通过指定图(Diagram)的布局(Layout)快速排列:

// 创建图时指定
const gojs_diagram = $(go.Diagram, diagramContainerRef.value, {
  // 默认从左到右,支持旋转角度调整排列方向
  layout: $(go.TreeLayout, { angle: 90, layerSpacing: 30 }),
});
// 或通过layout属性赋值
gojs_diagram.layout = $(go.TreeLayout); // 针对TreeModel的自然布局TreeLayout

更多自动布局见:布局|GoJS

群组/分组(Group)

定义组模板

const groupTitle = () => {
  return $(
    go.Panel, "Auto", { ... },
    $(go.Shape, { ... }),
    $(go.TextBlock, { ... }),
  )
}

const groupTemplate = $(
  go.Group,
  "Auto",
  { layout: $(go.TreeLayout) },
  groupShape(), // 群组形状
  // 定义群组内容排列
  $(go.Panel, "Vertical",
    { margin: 0.5 },
    groupTitle(), // 群组标题
    $(go.Placeholder, { padding: new Margin(20, 20, 30, 20)}) // 通常定义占位,用于控制子节点在群组内展示
  )
)

定义群组/分组类型节点

为节点定义 isGroup 属性声明群组节点,组内子节点定义group属性值为分组key即所属分组:

// 描述模型的群组节点群组子节点数据
const nodeData = [{ key: "AAA", group: "Group1"}, { key: "BBB"}, ...];
const groupData = [{ key: "Group1", isGroup: true }, ...];
diagram_model.nodeDataArray = [...nodeData, ...groupData];

应用群组模板

gojs_diagram.groupTemplate = groupTemplate; // 或自定义createGroupTemplate()方法

可折叠群组

定义折叠按钮,它将经控制分组的子节点是否被展示

const expandButton = () => {
  return $("SubGraphExpanderButton")
}

根据折叠状态自定义逻辑

const groupPlaceholder = () => {
  return $(
    go.Placeholder,
    { padding: 10 },
    // ofObejct()绑定到当前群组对象的isSubGraphExpanded属性
    // 根据群组张开状态设置palceholder内边距
    new go.Binding("padding", isSubGraphExpanded, (expanded) => expanded ? 10 : 0).ofObject()
  );
}

分组节点排序

对group的layout设置sorting + comparer 自定义排序算法

groupTemplate.layout = new GroupTreeLayout({
  // ......
  sorting: groupTemplate.TreeLayout.SortingDescending, // 节点降序排序
  // 声明节点排序规则
  comparer(va, vb){
    const da = va.node.data;
    const db = vb.node.data;
    if(da.total && db.total) return da.total - db.total; // 比对数据模型model的total值进行排序
    return 0;
  }
})

图表

初始化参数(options)

缩放相关

{
  allowZoom: true, // 允许通过滚轮或屏幕手势等交互进行缩放
  allMove: false, // 禁止移动节点
  autoScale: go.Diagram.Uniform, // 自动缩放
  minScale: 0.1, // 最小缩放级别
  maxScale: 2, // 最大缩放级别
  initialScale: 0.5, // 初始缩放级别
}

默认滚轮缩放和自动缩放不被同时开启,若需同时支持,可在 InitialLayoutCompleted 初始化布局完成回调关闭自动缩放后,开启手动缩放

diagram.addDiagramListener("InitialLayoutCompleted", (e) => {
  diagram.autoScale = go.Diagram.None; // 关闭自动缩放
  diagram.toolManager.mouseWheelBehavior = go.ToolManager.WheelZoom;
  diagram.allowZoom = true;
})

布局相关

{
  padding: 20, // 图表内边距
  contentAlignment: go.Spot.Center, // 内容对齐参考点,类似flex布局的align-items,默认中心位置
  layout: new go.TreeLayout({ angle: 0, layerSpacing: 50 }), // 图标布局
  isOngoing: false, // 关闭图表变化后自动更新布局
}

滚动相关

{
  scrollMode: go.Diagram.InfiniteScroll, // 无限滚轮滚动(不限制滚动边界),通常配合allowZoom: false
  "toolManager.mouseWheelBehavior": go.ToolManager.WheelZoom, // 设置滚轮行为,默认值go.ToolManager.WheelScroll
  allowHorizontalScroll: true, // 允许溢出边界时水平滚动
  allowVerticalScroll: true, // 允许溢出边界时垂直滚动
}

交互相关

"dragSelectingTool.isEnabled": false, // 禁用拖拽鼠标实现框线区域
"toolManager.hoverDelay": 300, // 触发hover逻辑延迟时长,如toolTip
事件监听(Listener)

初始化布局完成(InitialLayoutCompleted)

图表布局首次更新完成

布局完成事件(LayoutCompleted)

图表布局每次更新完成

对象单击事件(ObjectSingleClicked)

单击了图形对象(节点或连线等)后

diagram.addDiagramListener("ObjectSingleClicked", function(e) {
  const node = e.subject.part;
  if(node.category === 'list') { // 判断节点类型
    // 选择某类节点
  } else { } // 认为失焦
}

图背景单击事件(BackgroundSingleClicked)

diagram.addDiagramListener("BackgroundSingleClicked", function() {
  // 认为失焦
}
实例方法(method)

diagram.zoomToFit():手动缩放到区域填充大小

diagram.clear():清除图表对象,配合 diagram.div = null 销毁图表

布局

分层有向图(LayeredDiagraphLayout)
  • 分层:将节点按照层次(Layer)排列,通常是从上到下或从左到右。

  • 有向图:适用于有方向性的图,即图中的边具有明确的起点或终点

适用于常见的流程图/关系图

layout: $(go.LayeredDigraphLayout, { 
  direction: 90, // 默认0即从左到右,此处从上到下
  layerSpacing: 50, // 层间距
  columnSpacing: 50, // 列间距
  packOption: go.LayeredDigraphLayout.PackMedia, // 节点排列方式(优先空间还是弯曲边)
}),
树状布局(TreeLayout)
layout: $(go.TreeLayout, {
  angle: 90,
  layerSpacing: 50,
  arrangement: go.TreeLayout.ArrangementVertical, // 独立树/节点采用垂直布局
  arrangementSpacing: new go.Size(0, 50) // 独立树/节点之间的间距
})

配置项

breathLimit: 1:树的宽度限制,某一层子节点们超出宽度值时换行展示层级;

排序

通过布局类的 comparer 属性自定义分层有向图/树状图的children排序规则:

layout: {
  // ...
  sorting: gp.TreeLayout.SortingDescending, // 降序排序
  comparer(va, vb){
    const dataA = va.node.data; // 节点数据对象
    const dataB = vb.node.data;
    const linksA = va.node.findLinksInto(); // 获取节点连线
    const linksB = vb.node.findLinksInto(); // 可以根据连线数据进行排序,甚至上下游节点
    const valueA = dataA.xxx; // 自定义排序比值
    const valueB = dataB.xxx;
    return valueA - valueB;
  }
}

节点(Node)

内容类

Shape:用于显示预定义或自定义几何图形,并带有颜色

TextBlock:用于显示各种字体的文本(可能可编辑)

Picture:用于显示图像,source 属性设置资源地址

Panel:用于容纳其他对象集合的容器,这些对象可以根据面板类型以不同方式定位和调整大小(如表格Table,垂直堆栈Vertical和拉伸容器,是Node的父类)。

参数结构(options)

所有模型构建类使用类似 $(go.图形类, 图形类型type/默认值, 构建属性, 其他描述/内容)

图形类型(type)

Panel/Node:Auto叠加内容后自动调整,Horizontal/Vertical 内容一起水平/垂直排列

Shape:RoundedRectangle圆角矩形,Circle圆形,Diamond菱形

属性描述值(attr)

边距:margin 边距值(数值类型),或 new Margin(10, 0, 0, 10)padding 仅Panel及其子类支持

边框:stoke 边框色、strokeWidth 边框宽度、stokeDashArray: [6, 4] 虚线样式 [实线长,虚线长],parameter1/2/3/4:分别对应圆角参数值

背景:fill 图形填充色,background 文字等背景色

颜色:除了关键字和十六进制颜色,渐变色声明如下

const gradientBlue = $(go.Brush, 'Linear', {
  0: '#2dacf5', 1: '#057ae1', start: go.Spot.parse('Left'), end: go.Spot.parse('Right')
})

文字:font 文字相关,如 font: "bold 14px sans-serif"

段落行:lineHeight 行高,wrap Textblock换行行为(默认换行),maxLines 最大行数阶段省略号显示,居中:textAlign: "center"

固定大小:width/heightdesiredSize:期望大小,值为go.Size类实例,minSize/maxSize 限制大小范围,stretch:作为容器拉伸填充行为(go.GraphObject.Horizontal/Vertical)

文本换行 WrapNone/WrapFit 不换行/根据内容动态换行并调整宽度

允许链接点 fromSpot/toSpot:go.Spot.Right/Left/Top/Bottom/AllSides

选中:selectable:false禁止用户点击行为(点击不会修改元素isSelected) selectionAdorned:false即节点/连线选中时,不显示选中样式

数据绑定

通过 new go.Binding("property", "data model key") 描述图形,将数据模型的某个值与图形属性绑定(数据模型值修改后视图上对应属性更新)

$(go.Picture,
  { margin: 10, width: 50, height: 50, background: "red" },
  new go.Binding("source", "url") // 图片源从数据模型url属性取值
)
// 如Textblock中
new go.Binding("text", "extro.description", (value) => value || "default value")
// 如Shape中绑定isSelected选中状态
new go.Binding("stroke", "isSelected", (isSelected) => isSelected ? '#fff' : '#aaa').ofObjecct(''); // 和内容类实例属性挂钩

圆角实现

由于圆角一般只针对Shape设置有效,但我们通常使用Panel作为容器对内容布局,此时如何为Panel实现四周圆角?可以通过Panel内顶部和底部分别2个圆角Shape实现:

$(go.Panel, "Auto",
  { ...},
  $(go.Shape,
    { figure: "RoundedTopRectangle", parammeter1: 4 ... }, // 顶部圆角
  ),
  $(go.Shape,
    { figure: "RoundedBottomRectangle", parammeter1: 4 ... }, // 底部圆角
  ),
)

我们只需定义RoundedTop/BottomRectangle两个Figure即可,见 图形形状 | GoJS,引入go.Shape.defineFigureGenerator 相关代码即可;

事件

mouseEnter/mouseLeave

function mouseEnter(e, obj){
  if(obj.part && obj.part.isSelected) return;
  const shape = obj.findObject('SHAPE'); // 访问node的形状内容
  shape.stroke = 'blue';
  shape.fill = '#eee';
}
function mouseLeave(e, obj){
  if(obj.part && obj.part.isSelected) return;
  const shape = obj.findObject('SHAPE'); // 访问node的形状内容
  shape.stroke = 'black';
  shape.fill = '#fff';
}
$(go.Node, 'Auto', {  cursor: 'pointer', ... }, { mouseEnter, mouseLeave })

链接(Link)

链接线

定义链接线模板结构

const linkTemplate = $(go.Link,
  {
    routing: go.Routing.Orthogonal, // 路径类型:直角连线
    corner: 10, // 拐角圆角半径值
  },
  $(go.Shape, { strokeWidth: 2, stroke: '#000' }), // 默认作为链接线
  $(go.Shape, { fromArrow: 'Circle/Triangle', }), // fromArrow声明起始处箭头形状
  $(go.Shape, { fromArrow: 'Standard', }), // toArrow声明目标处箭头形状
  $(go.TextBlock, { // 默认作为链接线标签内容
    name: 'arrowText', // 元素标识,可用于自定义布局时访问元素引用
    segmentIndex: -1, // 标签在线段的索引,0即线段起点处,-1即线段终点处,其它值即第n线段
    segmentOffset: new go.Point(-30, -10), // 偏移量,调整文本标签的位置
    segmentFraction: 0.5, // 标签在线段的位置百分比,仅segmentIndex>0时有效
  },
  new go.Binding('text', 'text') // 标签文字绑定数据
  )
)

定义链接模型

var linkDataArray = [
  { from: "Alpha", to: "Beta" },
  // 定制一个链接线的出发线段和终止线段的长度(实现方式之一)
  { from: "Gamma", to: "Delta", fromEndSegmentLength: 4, toEndSegmentLength: 30 },
]

链接点(Spot)

支持自定义链接线连接节点时,从节点上发出/接受位置(gojs中通过Spot类表示点):

$(go.link,
  {
    fromSpot: go.Spot.Bottom,
    toSpot: go.Spot.Top,
  },
  // 或者绑定到linkDataArray模型上的值,如“Right”
  new go.Binding("fromSpot", "fromSpot", go.Spot.parse), // 字符传解析为Spot有效值
  new go.Binding("toSpot", "toSpot", go.Spot.parse),
)

定义在内容上的链接点,即链接线将连接搭配内部内容上

$(go.Node, "Vertical",
  $(go.Shape, "RoundedRectangle",
    {
      fill: "white",
      portId: "", // 通过定义一个空端口表示定义默认端口
      fromSpot: go.Spot.Right, // 定义发出/接收链接点
      toSpot: new go.Spot(0.5, 0.5, 0.25), // 相对于矩形位置的点 x,y,dx,dy
    }
  ),
  $(go.TextBlock, new go.Binding("text")),
)

节点端口(port)

仅设置连接线的位置都是作用于节点默认端口,支持定义节点多个发出/接收端口,供linkDataArray自由选择,myDiagram.linkTemplate 定义中绑定 fromSpot 即可;

节点定义多端口

// Vertical
// 定义名为A的接收/发出端口
$(go.Shape,
  { width: 6, height: 6, portId: "A", toSpot: go.Spot.Left, fromSpot: go.Spot.Left }, 
)
// 定义名为B的发出端口
$(go.Shape,
  { width: 6, height: 6, portId: "B", toSpot: go.Spot.Top }, 
),
// 定义名为B的接收端口
$(go.Shape,
  { width: 6, height: 6, portId: "B", toSpot: go.Spot.Bottom },
)
// 支持自定义 new go.Spot(x, y, offsetX, offsetY)作为fromSpot/toSpot位置

linkDataArray

linkDataArray: [
  // key1节点与key2节点相连,key1端口A连向key2端口A
  { from: "key1", to: "key2", fromPort: "A", toPort: "A" },
  // key2节点与key3节点相连,key2端口B连向key3端口A
  { from: "key2", to: "key3", fromPort: "B", toPort: "A" },
]

分组(Group)

排版容器(Panel)

属性描述值

alignment:go.Spot.Left等设置内容对齐方式

表格(Table)

通过Table类型的panel容器,实现支持跨行/跨列、分割线、边距的表格

$(go.Panel, "Table",
  {
    margin: 8,
    stretch: go.GraphObject.Fill, // panel 占满父容器
    defaultRowSeparatorStroke: "#000", // 行分隔线颜色
  },
  // 单独定义行列特性
  $(go.RowColumnDefinition, { row: 1, defaultRowSeparatorStroke: "#ccc" })
  $(go.TextBlock,
    {
      row: 0, // 该文本作为表格第1行
      col: 0, // 该文本作为表格第1列
      rowSpan: 2, // 该文本跨2行
      columnSpan: 2, // 该文本跨2列
      textAlign: "center",
      stretch: go.GraphObject.Horizontal, // 水平拉伸
    }
  ),
  // 逐个声明每个表格内容
)

toolTip

为元素添加toolTip属性实现鼠标悬停展示额外信息,如为 link 连线上文字增加详情浮层:

$(
  go.TextBlock,
  {
    name: "arrowText",
    // ...
    toolTip: $(
      go.Adornment, // 可自定义结构的装饰层
      'Auto',
      $(go.shape, 'RoundedRectangle', { fill: '#FFFFCC', parameter1: 5 }), // 层背景
      $(go.Panel, 'Vertical', { margin: 5 },  // 详情面板
        $(go.TextBlock, ...),
        //  ...更多详情内容
      )
    )
  }
)
// 外层配合绑定mouse事件的Panel,mouseEnter/Leave时改变Shape背景和arrowText文字颜色

自定义布局

自定义布局属于扩展 GoJS的内容,官方的参考示例:Serpentine Layout | GoJS

当想对图表自动布局的结果进行调整,我们可以通过定义布局类Layout中插入布局逻辑,实现自定义布局;如有分层有向布局:

myDiagram.layout = $(go.LayerDiagraphLayout, {
  layerSpacing: 60, // 层间距
  columnSpacing: 50, // 列间距
  aggressiveOption: go.LayeredDiagraphLayout.AggressiveNone, // 保持基本布局,不做优化调整
  packOption: globalThis.LayeredDiagraphLayout.PackMedian,
  setsPortSpots: false, // 不使用布局计算的连接点
})

我们自己定义一个布局 LayeredDiagraphLayout 类后,使用方式同理:

myDiagram.layout = new CustomLayeredDigraphLayout({
  layerSpacing: 60,
  columnSpacing: 50,
  aggressiveOption: globalThis.LayerDiagraphLayout.AggressiveOption.None,
  packOption: globalThis.LayerDiagraphLayout.PackMedian,
  setsPortSpots: false,
})

CustomLayeredDigraphLayout 基本结构

const { go } = window;

// LayeredDiagraphLayout 继承父类
class CustomLayeredDigraphLayout extends go.LayeredDigraphLayout {
  constructor(options) {
    super(options);
    // 其它自定义布局状态
    this.layoutInitialed = false;
    this.groupMaxItemsLength = 10;
  }

  // 布局计算应用到节点时
  commitNodes() {
    super.commitNodes(); // 调用父类的 commitNodes 方法
    // ...自定义Node相关布局逻辑
  }

  // 布局计算应用到链接时
  commitLinks(){
    super.commitLinks(); // 调用父类的 commitLinks 方法
    // ...自定义连接线相关布局逻辑
  }

  // 应用到布局时(交互导致重新布局也会触发)
  doLayout(coll) {
    super.doLayout(coll);
    // ...父类布局完成后调整逻辑
  }
}

关于gojs布局类的方法信息,见文档 Class Layout | GoJS 类方法列表

布局生命周期

  1. 创建网络:this.makeNetwork(coll)

    Network 是布局算法的核心数据结构,布局算法通过操作 Network 来计算节点和边的最终位置

  2. 执行布局算法:this.assignLayers()this.assignColumns()

  3. 提交布局结果:this.commitLayout() 内部调用 commitNodes commitLinks

布局生命周期决定自定义的处理插入在各自布局阶段,如连线调整最好在commitLinks方法中处理;

commitNodes

this.diagram.nodes:访问图中节点的 Iterator 对象(非JS迭代器)

group.memberParts:访问分组内所有元素(Link/Node)的Iterator对象

this.getLayoutBounds(node):获取节点布局的边界信息,适用于计算场景

part.position:获取元素的位置xy坐标值

node.actualBounds:访问节点实际边界,适用于仅访问

commitNodes() {
  super.commitNodes();
  const diagram = this.diagram; // 当前图表diagram对象
  diagram.startTransaction("CustomLayeredDigraphLayout commitNodes");

  // 调整图中TopLevel一级节点(不属于任一分组的节点)的位置
  diagram.nodes.each(node => {
    if (node && node.isTopLevel){
      const { x, y } = this.getLayoutBounds(node);
      node.move(new go.Point(x, 0)); // 设置节点y坐标0,使其顶端对齐(此处y值相对于布局参考点而非画布参考点)
    }
  })

  // 调整图中与category为"middleware"节点相连的节点的位置
  diagram.nodes.each(node => {
    if (node?.category === "middleware"){
      const connectedNodes = node.findNodesConnected(); // 所有与之相连节点/分组
      // ......
    }
  })

  // 调整图中group分组的子节点在分组内垂直居中对齐
  diagram.nodes.each(node => {
    if(node?.category === 'group'){
      const group = node;
      group.memberParts.each(member => {
        if(part instanceof go.Node){
          const groupBounds = this.getLayoutBounds(group);
          const partBounds = this.getLayoutBounds(part);
          const centerY = groupBounds.y + groupBounds.height / 2;
          const desiredY = centerY - partBounds.height / 2;
          part.position = new go.Point(partBounds.x, desiredY);
        }
      })
    }
  })

  diagram.nodes.commitTransaction("CustomLayeredDigraphLayout commitNodes");
}

node.findTreeChildrenNodes():获取节点子树的节点们

commitLinks

我们通过为图表的链接定义linkTemplate,实现设置链接线的属性

export const linkTemplate = $(
  go.Link,
  {
    routing: go.Link.Orthogonal, // 直角连线
    corner: 10,
    toEndSegmentLength: 40,
  },
  // 连线元素声明
)

如果要为图上满足某些规则的连线自定义属性,通过自定义布局 commitLinks 实现:

commitLinks() {
  super.commitLinks();
  const diagram = this.diagram; // 当前图表diagram对象
  diagram.startTransaction("CustomLayeredDigraphLayout commitLinks");

  // 遍历链接线
  this.diagram.links.each((link) => {
    const { fromNode, toNode } = link;
    if (fromNode && toNode) {
      // 起始连接点在底部、终止连接点在顶部的连线:
      if(link.fromSpot === go.Spot.Bottom && link.toSpot === go.Spot.Top) {
        const arrowText = link.findObject("arrowText");
        if (arrowText) {
          arrowText.segmentIndex = 2; // 设置文本块在第2线段
          const textWidth = arrowText.actualBounds.width;
          arrowText.segmentOffset = new go.Point(0, -Math.ceil(textWidth / 2) -10); // 根据文本宽度调整文字偏移
        }
      }
    }
    // middleware节点的连线
    if(fromNode?.category === 'middleware'){
      link.routing = go.Link.AvoidsNodes; // 设置避让节点
    }
    // origin节点和跨分组的middleware节点之间连线
    if(fromNode?.category === 'origin' && toNode.category === 'middleware' && toNode.containingGroup === 'group'){
      link.fromEndSegmentLength = 40; // 设置连线起始端延伸长度
      link.toEndSegmentLength = 0;  // 设置连线终止端延伸长度
    }
})