gojs 流程圖框架-節點裝飾器模板(二)

木子七發表於2019-03-03

上一章我們瞭解瞭如何使用 gojs 完成基本的節點和連線線的繪製, gojs 中還可以對節點或邊進行自由拖動, 編輯等功能; 本章將基於上一章編寫的流程圖程式碼, 為這些節點設定裝飾器模板

完成後的效果圖:

gojs 流程圖框架-節點裝飾器模板(二)

建議下載原始碼, 對照本文進行學習, 原始碼地址: github.com/muzqi/sampl…

選擇節點裝飾器

在預設情況下, 我們用滑鼠點選某個節點. 該節點會被一個藍色的框所包裹,

gojs 流程圖框架-節點裝飾器模板(二)

這個框就是 選擇節點裝飾器, 如果我們想改變這個框的樣式, 我們就需要為節點設定模板

編寫模板

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))
複製程式碼

程式碼註釋:

  1. 我們使用 go.Adornment 物件, 建立一個模板, 這實際上跟我們之前學到的節點模板思路是完全一樣的, 定義它的佈局方式, 再定義它的形狀等屬性
  2. 定義形狀, 在這裡我們定義了一個描邊為黃色的虛線選擇節點裝飾器
  3. 設定 go.Placeholder 物件的目的是, 讓裝飾器自適應節點的大小; 反之, 我們可以自定義裝飾器的大小

引入模板

我們來到定義節點模板的地方, 併為 go.Node 物件設定裝飾器模板

diagram.nodeTemplateMap.add(`node1`,
    $(go.Node, go.Panel.Position,
        // ...
        {
            // [1]
            selectable: true,
            // [2]
            selectionAdornmentTemplate: nodeSelectionAdornmentTemplate
        }
        // ...
    )
)
複製程式碼

程式碼註釋:

  1. selectable, 表示該節點是否能被選中, 預設為 true
  2. 我們將定義好的模板賦值給 selectionAdornmentTemplate 屬性, 此時我們再回到頁面, 點選節點
gojs 流程圖框架-節點裝飾器模板(二)

調整節點大小裝飾器

用過 ps 的同學應該都知道, Ctrl + T 後, 能夠喚出調整大小的操作裝飾器, 我們現在需要給節點新增一個這樣的裝飾器, 讓它支援 resize 操作

gojs 流程圖框架-節點裝飾器模板(二)

編寫模板

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))
)
複製程式碼

程式碼註釋:

  1. 同樣, 我們還是使用 go.Adornment 來定義裝飾器; 在這裡, 我們佈局方式使用了 go.Panel.Spot
  2. 我們定義了四個 go.Shape, 來表示裝飾器的四個拖拽頂點

引入模板

diagram.nodeTemplateMap.add(`node1`,
    $(go.Node, go.Panel.Position,
        // ...
        {
            resizable: true,
            resizeAdornmentTemplate: nodeResizeAdornmentTemplate
        }
        // ...
    )
)
複製程式碼
gojs 流程圖框架-節點裝飾器模板(二)

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` }
            // ...
        )
    )
)
複製程式碼

程式碼註釋:

  1. 設定 resizeObjectNameTEXT
  2. 將一個文字塊元素的名字設定為 TEXT
gojs 流程圖框架-節點裝飾器模板(二)

如上圖所示, 只有文字塊被允許設定大小了


旋轉節點裝飾器

相比較前面的裝飾器, 旋轉節點裝飾器存在一個巨坑;你會發現當你繪製出一個把手後, 你除了使用 Position 來絕對定位它, 否則你很難將其相對定位居中展示

官方的例項(其實他並沒有被寫到文件中去, 而是在demo檔案中找到的方法)是將 go.RotatingTool 這個物件給改寫掉了, 在初始化的時候, 就將把手的位置預設居中

gojs 流程圖框架-節點裝飾器模板(二)

請看以下程式碼:

編寫模板

// [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())
})
複製程式碼

程式碼註釋:

  1. 我們重新定義了一個類, 這個類繼承了 go.RotatingTool
  2. 我們在這個類中, 定義了把手的位置預設是在頂部居中
  3. 使用 go.Adornment 物件製作模板, 這個模板只負責把手最終長成什麼樣子
  4. 在初始化 diagram 的時候, 引用自定義的工具類 makeTopRotatingTool

引用模板

diagram.nodeTemplateMap.add(`node1`,
    $(go.Node, go.Panel.Position,
        // ...
        {
            rotatable: true,
            // [1]
            // rotateObjectName: `TEXT`
            rotateAdornmentTemplate: nodeRotateAdornmentTemplate,
            // [2]
            locationSpot: go.Spot.Center
        },
        // ...
    )
)
複製程式碼

程式碼註釋:

  1. 同 @調整節點大小裝飾器, 旋轉節點裝飾器也有 rotateObjectName 屬性, 用法與效果和前者是一模一樣的
  2. locationSpot 屬性決定了節點旋轉的錨點, 目前設定為以中心點旋轉

拖拽建立連線線

前面我們已經將所有節點的裝飾器新增完成, 最後, 我們需要實現從一個節點到另一個節點, 手動拖出連線線的功能;

為了更清晰的展示這個功能, 我們重新建立一塊畫布, 單獨講解;

實現原理

在 gojs 模板中, 任何一個元素, 都具備這三個屬性:

  • portId 該 port 點的名稱
  • fromLinkable 表示是否允許該節點接收拖過來的連線線
  • toLinkable 表示是否允許從該節點拖出連線線

只要具備這三個屬性, 任何元素都能夠拖出或者接收連線線, 並使兩個節點產生連線關係

簡單實現

gojs 流程圖框架-節點裝飾器模板(二)
<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),
  )
  
// ...
複製程式碼

實現效果如下:

gojs 流程圖框架-節點裝飾器模板(二)

下章繼續講解, 連線線的裝飾器模板

(未完待續)

相關文章