use-canvas-init.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. import type { Markup, Node } from '@antv/x6'
  18. import { ref, onMounted, Ref } from 'vue'
  19. import { Graph } from '@antv/x6'
  20. import { NODE, EDGE, X6_NODE_NAME, X6_EDGE_NAME } from './dag-config'
  21. import { debounce } from 'lodash'
  22. import { useResizeObserver } from '@vueuse/core'
  23. import ContextMenuTool from './dag-context-menu'
  24. interface Options {
  25. readonly: Ref<boolean>
  26. graph: Ref<Graph | undefined>
  27. }
  28. /**
  29. * Canvas Init
  30. * 1. Bind the graph to the dom
  31. * 2. Redraw when the page is resized
  32. * 3. Register custom graphics
  33. */
  34. export function useCanvasInit(options: Options) {
  35. // Whether the graph can be operated
  36. const { readonly, graph } = options
  37. const paper = ref<HTMLElement>() // The graph mount HTMLElement
  38. const minimap = ref<HTMLElement>() // The minimap mount HTMLElement
  39. const container = ref<HTMLElement>() // The container of paper and minimap
  40. /**
  41. * Graph Init, bind graph to the dom
  42. */
  43. function graphInit() {
  44. Graph.registerNodeTool('contextmenu', ContextMenuTool, true)
  45. return new Graph({
  46. container: paper.value,
  47. selecting: {
  48. enabled: true,
  49. multiple: true,
  50. rubberband: true,
  51. rubberEdge: true,
  52. movable: true,
  53. showNodeSelectionBox: false
  54. },
  55. scaling: {
  56. min: 0.2,
  57. max: 2
  58. },
  59. mousewheel: {
  60. enabled: true,
  61. modifiers: ['ctrl', 'meta']
  62. },
  63. scroller: true,
  64. grid: {
  65. size: 10,
  66. visible: true
  67. },
  68. snapline: true,
  69. minimap: {
  70. enabled: true,
  71. container: minimap.value,
  72. scalable: false,
  73. width: 200,
  74. height: 120
  75. },
  76. interacting: {
  77. edgeLabelMovable: false,
  78. nodeMovable: !readonly.value,
  79. magnetConnectable: !readonly.value
  80. },
  81. connecting: {
  82. // Whether multiple edges can be created between the same start node and end
  83. allowMulti: false,
  84. // Whether a point is allowed to connect to a blank position on the canvas
  85. allowBlank: false,
  86. // The start node and the end node are the same node
  87. allowLoop: false,
  88. // Whether an edge is allowed to link to another edge
  89. allowEdge: false,
  90. // Whether edges are allowed to link to nodes
  91. allowNode: true,
  92. // Whether to allow edge links to ports
  93. allowPort: false,
  94. // Whether all available ports or nodes are highlighted when you drag the edge
  95. highlight: true,
  96. createEdge() {
  97. return graph.value?.createEdge({ shape: X6_EDGE_NAME })
  98. },
  99. validateConnection(data) {
  100. const { sourceCell, targetCell } = data
  101. if (
  102. sourceCell &&
  103. targetCell &&
  104. sourceCell.isNode() &&
  105. targetCell.isNode()
  106. ) {
  107. const sourceData = sourceCell.getData()
  108. if (!sourceData) return true
  109. if (sourceData.taskType !== 'CONDITIONS') return true
  110. return (graph.value?.getConnectedEdges(sourceCell).length || 0) <= 2
  111. }
  112. return true
  113. }
  114. },
  115. highlighting: {
  116. nodeAvailable: {
  117. name: 'className',
  118. args: {
  119. className: 'available'
  120. }
  121. },
  122. magnetAvailable: {
  123. name: 'className',
  124. args: {
  125. className: 'available'
  126. }
  127. },
  128. magnetAdsorbed: {
  129. name: 'className',
  130. args: {
  131. className: 'adsorbed'
  132. }
  133. }
  134. }
  135. })
  136. }
  137. onMounted(() => {
  138. graph.value = graphInit()
  139. // Make sure the edge starts with node, not port
  140. graph.value.on('edge:connected', ({ isNew, edge }) => {
  141. if (isNew) {
  142. const sourceNode = edge.getSourceNode() as Node
  143. edge.setSource(sourceNode)
  144. }
  145. })
  146. // Add a node tool when the mouse entering
  147. graph.value.on('node:mouseenter', ({ node }) => {
  148. const nodeName = node.getData().taskName
  149. const markup = node.getMarkup() as Markup.JSONMarkup[]
  150. const fo = markup.filter((m) => m.tagName === 'foreignObject')[0]
  151. node.addTools({
  152. name: 'button',
  153. args: {
  154. markup: [
  155. {
  156. tagName: 'text',
  157. textContent: nodeName,
  158. attrs: {
  159. fill: '#868686',
  160. 'font-size': 16,
  161. 'text-anchor': 'center'
  162. }
  163. }
  164. ],
  165. x: 0,
  166. y: 0,
  167. offset: { x: 0, y: fo ? -28 : -10 }
  168. }
  169. })
  170. })
  171. // Remove all tools when the mouse leaving
  172. graph.value.on('node:mouseleave', ({ node }) => {
  173. node.removeTool('button')
  174. })
  175. })
  176. /**
  177. * Redraw when the page is resized
  178. */
  179. const resize = debounce(() => {
  180. if (container.value && true) {
  181. const w = container.value.offsetWidth
  182. const h = container.value.offsetHeight
  183. graph.value?.resize(w, h)
  184. }
  185. }, 200)
  186. useResizeObserver(container, resize)
  187. /**
  188. * Register custom cells
  189. */
  190. function registerCustomCells() {
  191. Graph.unregisterNode(X6_NODE_NAME)
  192. Graph.unregisterEdge(X6_EDGE_NAME)
  193. Graph.registerNode(X6_NODE_NAME, { ...NODE })
  194. Graph.registerEdge(X6_EDGE_NAME, { ...EDGE })
  195. }
  196. onMounted(() => {
  197. registerCustomCells()
  198. })
  199. return {
  200. graph,
  201. paper,
  202. minimap,
  203. container
  204. }
  205. }