上一章我們瞭解瞭如何使用 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),
)
// ...
複製程式碼
實現效果如下:
下章繼續講解, 連線線的裝飾器模板
(未完待續)