上一章我们了解了如何使用 gojs 完成基本的节点和连接线的绘制, gojs 中还可以对节点或边进行自由拖动, 编辑等功能; 本章将基于上一章编写的流程图代码, 为这些节点设置装饰器模板
完成后的效果图:
建议下载源码, 对照本文进行学习, 源码地址: github.com/muzqi/sampl…
选择节点装饰器
在默认情况下, 我们用鼠标点击某个节点. 该节点会被一个蓝色的框所包裹,
这个框就是选择节点装饰器
, 如果我们想改变这个框的样式, 我们就需要为节点设置模板
编写模板
const nodeSelectionAdornmentTemplate =
// [1]
$(go.Adornment, go.Panel.Auto,
// [2]
$(go.Shape, {
fill: null,
stroke: 'yellow',
strokeWidth: 1,
strokeDashArray: [6, 6, 2, 2]
}),
// [3]
// { width: 500, height: 200 }
$(go.Placeholder))
复制代码
代码注释:
- 我们使用
go.Adornment
对象, 创建一个模板, 这实际上跟我们之前学到的节点模板思路是完全一样的, 定义它的布局方式, 再定义它的形状等属性 - 定义形状, 在这里我们定义了一个描边为黄色的虚线选择节点装饰器
- 设置
go.Placeholder
对象的目的是, 让装饰器自适应节点的大小; 反之, 我们可以自定义装饰器的大小
引入模板
我们来到定义节点模板的地方, 并为 go.Node
对象设置装饰器模板
diagram.nodeTemplateMap.add('node1',
$(go.Node, go.Panel.Position,
// ...
{
// [1]
selectable: true,
// [2]
selectionAdornmentTemplate: nodeSelectionAdornmentTemplate
}
// ...
)
)
复制代码
代码注释:
selectable
, 表示该节点是否能被选中, 默认为 true- 我们将定义好的模板赋值给
selectionAdornmentTemplate
属性, 此时我们再回到页面, 点击节点
调整节点大小装饰器
用过 ps 的同学应该都知道, Ctrl + T
后, 能够唤出调整大小的操作装饰器, 我们现在需要给节点添加一个这样的装饰器, 让它支持 resize
操作
编写模板
const makeNodeResizeShapeOption = (cursor, alignment) => ({
cursor,
alignment,
desiredSize: new go.Size(12, 12),
fill: 'lightyellow',
stroke: 'yellow'
})
const nodeResizeAdornmentTemplate =
// [1]
$(go.Adornment, go.Panel.Spot,
$(go.Placeholder),
// [2]
$(go.Shape, makeNodeResizeShapeOption('nw-resize', go.Spot.TopLeft)),
$(go.Shape, makeNodeResizeShapeOption('ne-resize', go.Spot.TopRight)),
$(go.Shape, makeNodeResizeShapeOption('se-resize', go.Spot.BottomLeft)),
$(go.Shape, makeNodeResizeShapeOption('sw-resize', go.Spot.BottomRight))
)
复制代码
代码注释:
- 同样, 我们还是使用
go.Adornment
来定义装饰器; 在这里, 我们布局方式使用了 go.Panel.Spot - 我们定义了四个
go.Shape
, 来表示装饰器的四个拖拽顶点
引入模板
diagram.nodeTemplateMap.add('node1',
$(go.Node, go.Panel.Position,
// ...
{
resizable: true,
resizeAdornmentTemplate: nodeResizeAdornmentTemplate
}
// ...
)
)
复制代码
resizeObjectName
调整大小装饰器引入时还可以传入一个 resizeObjectName
值, 表示指定需要应用装饰器的元素, 这个元素可以是 go.Shape
go.Picture
go.Text
任何 gojs
的元素
diagram.nodeTemplateMap.add('node1',
$(go.Node, go.Panel.Position,
// ...
{
resizable: true,
// [1]
resizeObjectName: 'TEXT'
resizeAdornmentTemplate: nodeResizeAdornmentTemplate
},
// ...
$(go.TextBlock,
// [2]
{ name: 'TEXT' }
// ...
)
)
)
复制代码
代码注释:
- 设置
resizeObjectName
为TEXT
- 将一个文字块元素的名字设置为
TEXT
如上图所示, 只有文字块被允许设置大小了
旋转节点装饰器
相比较前面的装饰器, 旋转节点装饰器存在一个巨坑;你会发现当你绘制出一个把手后, 你除了使用 Position
来绝对定位它, 否则你很难将其相对定位居中展示
官方的实例(其实他并没有被写到文档中去, 而是在demo文件中找到的方法)是将 go.RotatingTool
这个对象给改写掉了, 在初始化的时候, 就将把手的位置默认居中
请看以下代码:
编写模板
// [1]
const makeTopRotatingTool = () => (
class TopRotatingTool extends go.RotatingTool {
updateAdornments(part) {
go.RotatingTool.prototype.updateAdornments.call(this, part)
var adornment = part.findAdornment('Rotating')
if (adornment !== null) {
// [2]
adornment.location = part.rotateObject.getDocumentPoint(new go.Spot(0.5, 0, 0, -30)) // above middle top
}
}
rotate(newangle) {
go.RotatingTool.prototype.rotate.call(this, newangle + 90)
}
}
)
// [3]
const nodeRotateAdornmentTemplate =
$(go.Adornment,
$(go.Shape, 'Circle',
{
cursor: 'pointer',
desiredSize: new go.Size(7, 7),
fill: 'lightyellow',
stroke: 'yellow'
}),
$(go.Shape,
{
geometryString: 'M3.5 7 L3.5 30',
isGeometryPositioned: true,
stroke: 'yellow',
strokeWidth: 1.5,
strokeDashArray: [4, 2]
})
)
// [4]
const diagram = $(go.Diagram, 'diagram', {
'initialContentAlignment': go.Spot.Center,
'undoManager.isEnabled': true,
'rotatingTool': $(makeTopRotatingTool())
})
复制代码
代码注释:
- 我们重新定义了一个类, 这个类继承了
go.RotatingTool
- 我们在这个类中, 定义了把手的位置默认是在顶部居中
- 使用
go.Adornment
对象制作模板, 这个模板只负责把手最终长成什么样子 - 在初始化
diagram
的时候, 引用自定义的工具类makeTopRotatingTool
引用模板
diagram.nodeTemplateMap.add('node1',
$(go.Node, go.Panel.Position,
// ...
{
rotatable: true,
// [1]
// rotateObjectName: 'TEXT'
rotateAdornmentTemplate: nodeRotateAdornmentTemplate,
// [2]
locationSpot: go.Spot.Center
},
// ...
)
)
复制代码
代码注释:
- 同 @调整节点大小装饰器, 旋转节点装饰器也有
rotateObjectName
属性, 用法与效果和前者是一模一样的 locationSpot
属性决定了节点旋转的锚点, 目前设置为以中心点旋转
拖拽创建连接线
前面我们已经将所有节点的装饰器添加完成, 最后, 我们需要实现从一个节点到另一个节点, 手动拖出连接线的功能;
为了更清晰的展示这个功能, 我们重新建立一块画布, 单独讲解;
实现原理
在 gojs 模板中, 任何一个元素, 都具备这三个属性:
- portId 该
port
点的名称 - fromLinkable 表示是否允许该节点接收拖过来的连接线
- toLinkable 表示是否允许从该节点拖出连接线
只要具备这三个属性, 任何元素都能够拖出或者接收连接线, 并使两个节点产生连接关系
简单实现
<div id="port" style="width: 1000px; height: 500px"></div>
复制代码
const portDiagram = $(go.Diagram, 'port', {
'initialContentAlignment': go.Spot.Center,
'undoManager.isEnabled': true
})
// 指定被创建的连接线的模板
portDiagram.linkTemplate = $(go.Link,
$(go.Shape, { stroke: 'black', strokeWidth: 3 })
)
// 指定被创建的节点的模板
portDiagram.nodeTemplate = $(go.Node,
new go.Binding('position'),
$(go.Shape,
{
fill: 'blue',
fromLinkable: true,
toLinkable: true
},
new go.Binding('portId', 'key')
)
)
portDiagram.model = new go.GraphLinksModel(
[
{
key: '1',
position: new go.Point(500, 0)
},
{
key: '2',
position: new go.Point(0, 0)
}
]
)
复制代码
细心的同学就会发现了, 现在我们的整个节点作为一个 port
, 只要鼠标点击拖动节点, 就会拉出一条连接线, 但如果我们需要移动节点怎么办?
所以通常情况下, 我们会在节点中绘制几个点, 让这几个点具备拖拽接收连接线的功能;
改写上面的代码:
// ...
const makePort = (portId, spot) => (
$(go.Shape, {
cursor: 'pointer',
fill: 'red',
width: 10,
height: 10,
alignment: spot,
portId,
fromLinkable: true,
toLinkable: true
})
)
portDiagram.nodeTemplate =
$(go.Node, go.Panel.Spot,
new go.Binding('position'),
$(go.Shape, { fill: 'blue' }),
makePort('T', go.Spot.Top),
makePort('B', go.Spot.Bottom),
makePort('L', go.Spot.Left),
makePort('R', go.Spot.Right),
)
// ...
复制代码
实现效果如下:
下章继续讲解, 连接线的编辑模板
(未完待续)