GoJs
探索 GoJS:构建复杂交互图表的强大引擎
在前端开发中,我们熟知像 ECharts 这样优秀的数据图表库,用于展示折线图、柱状图等数据可视化形态。然而,当面临需要表达关系和结构的场景时——例如绘制流程图、组织架构图、UML图、思维导图乃至甘特图——我们就需要一款像 GoJS 这样的专业图表库。
与国产开源库(如 AntV G6/X6)相比,GoJS 的核心优势在于其清晰直观的设计理念和极高的自定义自由度。它通过模板化、数据绑定的方式构建元素,让开发者可以更轻松地理解并掌控图表的生成逻辑。无论是简单的节点连线,还是高度复杂的自定义布局算法,GoJS 都提供了强大而灵活的底层支持。
GoJS 的功能远比我们初识时想象的更强大,足以应对极其专业的图表需求,但许多实用的高级配置项缺乏直接的示例代码。这导致其深层次的应用潜力在社区中的开发度和讨论度相对较低,需要开发者深入文档进行探索。本文对GoJS开发时涉及的特性进行整理,供GoJS使用者查阅(【初始化】章节介绍基本用法,对于有经验者可跳过);
中文文档(2.0版本) Samples(2.0版本 github(2.0版本)
开始
安装引入
2.X版本
更多模块规范见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/height,desiredSize:期望大小,值为go.Size类实例,minSize/maxSize 限制大小范围,stretch:作为容器拉伸填充行为(go.GraphObject.Horizontal/Vertical)
文本换行 Wrap:None/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 类方法列表
布局生命周期
-
创建网络:
this.makeNetwork(coll)Network 是布局算法的核心数据结构,布局算法通过操作 Network 来计算节点和边的最终位置
-
执行布局算法:
this.assignLayers()与this.assignColumns() -
提交布局结果:
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; // 设置连线终止端延伸长度
}
})