edge.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. /* eslint-disable standard/array-bracket-even-spacing */
  2. import editorStyle from '../util/defaultStyle'
  3. const uniqBy = (arr, key) => {
  4. const result = []
  5. arr.forEach(i => {
  6. if (!result.find(r => r[key] === i[key])) { result.push(i) }
  7. })
  8. return result
  9. }
  10. export default function(G6) {
  11. G6.registerEdge('flow-polyline-round', {
  12. options: {
  13. style: {
  14. ...editorStyle.edgeStyle
  15. },
  16. stateStyles: {
  17. selected: {
  18. lineWidth: editorStyle.edgeSelectedStyle.lineWidth,
  19. stroke: editorStyle.edgeSelectedStyle.stroke
  20. },
  21. hover: {
  22. stroke: editorStyle.edgeActivedStyle.stroke
  23. }
  24. }
  25. },
  26. setState(name, value, item) {
  27. const group = item.getContainer()
  28. const path = group.getChildByIndex(0)
  29. if (name === 'selected') {
  30. if (value) {
  31. path.attr('lineWidth', this.options.stateStyles.selected.lineWidth)
  32. path.attr('stroke', this.options.stateStyles.selected.stroke)
  33. // path.attr('stroke', this.options.style.stroke)
  34. } else {
  35. path.attr('lineWidth', this.options.style.lineWidth)
  36. path.attr('stroke', this.options.style.stroke)
  37. }
  38. } else if (name === 'hover') {
  39. if (value) { path.attr('stroke', this.options.stateStyles.hover.stroke) } else { path.attr('stroke', this.options.style.stroke) }
  40. }
  41. },
  42. drawShape(cfg, group) {
  43. this.group = group
  44. const shapeStyle = this.getShapeStyle(cfg)
  45. const shape = group.addShape('path', {
  46. className: 'edge-shape',
  47. attrs: shapeStyle
  48. })
  49. return shape
  50. },
  51. drawLabel(cfg, group) {
  52. const labelCfg = cfg.labelCfg || {}
  53. const labelStyle = this.getLabelStyle(cfg, labelCfg, group)
  54. const label = group.addShape('text', {
  55. attrs: labelStyle
  56. })
  57. const labelBBox = label.getBBox()
  58. group.addShape('rect', {
  59. className: 'edge-labelRect',
  60. attrs: {
  61. x: labelBBox.x - editorStyle.edgeLabelRectPadding / 2,
  62. y: labelBBox.y - editorStyle.edgeLabelRectPadding / 2,
  63. width: labelBBox.width + editorStyle.edgeLabelRectPadding,
  64. height: labelBBox.height + editorStyle.edgeLabelRectPadding,
  65. fill: '#fff',
  66. stroke: '#fff'
  67. }
  68. })
  69. group.toBack()
  70. label.toFront()
  71. return label
  72. },
  73. afterUpdate(cfg, item) {
  74. const label = item.getContainer().findByClassName('edge-label')
  75. const labelRect = item.getContainer().findByClassName('edge-labelRect')
  76. if (label) {
  77. const labelBBox = label.getBBox()
  78. labelRect.attr({
  79. x: labelBBox.x - editorStyle.edgeLabelRectPadding / 2,
  80. y: labelBBox.y - editorStyle.edgeLabelRectPadding / 2,
  81. width: labelBBox.width + editorStyle.edgeLabelRectPadding,
  82. height: labelBBox.height + editorStyle.edgeLabelRectPadding
  83. })
  84. }
  85. },
  86. getShapeStyle(cfg) {
  87. cfg = this.getPathPoints(cfg)
  88. const startPoint = cfg.startPoint
  89. const endPoint = cfg.endPoint
  90. const controlPoints = this.getControlPoints(cfg)
  91. let points = [startPoint]
  92. if (controlPoints) {
  93. points = points.concat(controlPoints)
  94. }
  95. points.push(endPoint)
  96. const path = this.getPath(points)
  97. let style = this.options.style
  98. if (cfg.reverse) { style = { ...style, lineDash: [1, 3] } } else { style = { ...style, lineDash: null } }
  99. return {
  100. path,
  101. ...style,
  102. endArrow: {
  103. path: 'M 0,0 L -10,-4 S -8 0,-10 4 Z'
  104. }
  105. }
  106. },
  107. getPath(points) {
  108. const path = []
  109. for (let i = 0; i < points.length; i++) {
  110. const point = points[i]
  111. if (i === 0) {
  112. path.push(['M', point.x, point.y])
  113. } else if (i === points.length - 1) {
  114. path.push(['L', point.x, point.y])
  115. } else {
  116. const prevPoint = points[i - 1]
  117. const nextPoint = points[i + 1]
  118. let cornerLen = 5
  119. if (Math.abs(point.y - prevPoint.y) > cornerLen || Math.abs(point.x - prevPoint.x) > cornerLen) {
  120. if (prevPoint.x === point.x) {
  121. path.push(['L', point.x, point.y > prevPoint.y ? point.y - cornerLen : point.y + cornerLen])
  122. } else if (prevPoint.y === point.y) {
  123. path.push(['L', point.x > prevPoint.x ? point.x - cornerLen : point.x + cornerLen, point.y])
  124. }
  125. }
  126. const yLen = Math.abs(point.y - nextPoint.y)
  127. const xLen = Math.abs(point.x - nextPoint.x)
  128. if (yLen > 0 && yLen < cornerLen) {
  129. cornerLen = yLen
  130. } else if (xLen > 0 && xLen < cornerLen) {
  131. cornerLen = xLen
  132. }
  133. if (prevPoint.x !== nextPoint.x && nextPoint.x === point.x) {
  134. path.push(['Q', point.x, point.y, point.x, point.y > nextPoint.y ? point.y - cornerLen : point.y + cornerLen])
  135. } else if (prevPoint.y !== nextPoint.y && nextPoint.y === point.y) {
  136. path.push(['Q', point.x, point.y, point.x > nextPoint.x ? point.x - cornerLen : point.x + cornerLen, point.y])
  137. }
  138. }
  139. }
  140. return path
  141. },
  142. getControlPoints(cfg) {
  143. if (!cfg.sourceNode) {
  144. return cfg.controlPoints
  145. }
  146. return this.polylineFinding(cfg.sourceNode, cfg.targetNode, cfg.startPoint, cfg.endPoint, 15)
  147. },
  148. getExpandedBBox(bbox, offset) {
  149. return bbox.width === 0 && bbox.height === 0 ? bbox : {
  150. centerX: bbox.centerX,
  151. centerY: bbox.centerY,
  152. minX: bbox.minX - offset,
  153. minY: bbox.minY - offset,
  154. maxX: bbox.maxX + offset,
  155. maxY: bbox.maxY + offset,
  156. height: bbox.height + 2 * offset,
  157. width: bbox.width + 2 * offset
  158. }
  159. },
  160. getExpandedPort(bbox, point) {
  161. return Math.abs(point.x - bbox.centerX) / bbox.width > Math.abs(point.y - bbox.centerY) / bbox.height
  162. ? { x: point.x > bbox.centerX ? bbox.maxX : bbox.minX, y: point.y }
  163. : { x: point.x, y: point.y > bbox.centerY ? bbox.maxY : bbox.minY }
  164. },
  165. combineBBoxes(sBBox, tBBox) {
  166. const minX = Math.min(sBBox.minX, tBBox.minX); const minY = Math.min(sBBox.minY, tBBox.minY)
  167. const maxX = Math.max(sBBox.maxX, tBBox.maxX); const maxY = Math.max(sBBox.maxY, tBBox.maxY)
  168. return {
  169. centerX: (minX + maxX) / 2,
  170. centerY: (minY + maxY) / 2,
  171. minX: minX,
  172. minY: minY,
  173. maxX: maxX,
  174. maxY: maxY,
  175. height: maxY - minY,
  176. width: maxX - minX
  177. }
  178. },
  179. getBBoxFromVertexes(sPoint, tPoint) {
  180. const minX = Math.min(sPoint.x, tPoint.x); const maxX = Math.max(sPoint.x, tPoint.x)
  181. const minY = Math.min(sPoint.y, tPoint.y); const maxY = Math.max(sPoint.y, tPoint.y)
  182. return {
  183. centerX: (minX + maxX) / 2,
  184. centerY: (minY + maxY) / 2,
  185. maxX: maxX,
  186. maxY: maxY,
  187. minX: minX,
  188. minY: minY,
  189. height: maxY - minY,
  190. width: maxX - minX
  191. }
  192. },
  193. vertexOfBBox(bbox) {
  194. return [{ x: bbox.minX, y: bbox.minY }, { x: bbox.maxX, y: bbox.minY }, { x: bbox.maxX, y: bbox.maxY }, { x: bbox.minX, y: bbox.maxY }]
  195. },
  196. crossPointsByLineAndBBox(bbox, centerPoint) {
  197. let crossPoints = []
  198. if (!(centerPoint.x < bbox.minX || centerPoint.x > bbox.maxX)) { crossPoints = crossPoints.concat([{ x: centerPoint.x, y: bbox.minY }, { x: centerPoint.x, y: bbox.maxY }]) }
  199. if (!(centerPoint.y < bbox.minY || centerPoint.y > bbox.maxY)) { crossPoints = crossPoints.concat([{ x: bbox.minX, y: centerPoint.y }, { x: bbox.maxX, y: centerPoint.y }]) }
  200. return crossPoints
  201. },
  202. getConnectablePoints(sBBox, tBBox, sPoint, tPoint) {
  203. const lineBBox = this.getBBoxFromVertexes(sPoint, tPoint)
  204. const outerBBox = this.combineBBoxes(sBBox, tBBox)
  205. const sLineBBox = this.combineBBoxes(sBBox, lineBBox)
  206. const tLineBBox = this.combineBBoxes(tBBox, lineBBox)
  207. let points = []
  208. points = points.concat(this.vertexOfBBox(sLineBBox), this.vertexOfBBox(tLineBBox), this.vertexOfBBox(outerBBox))
  209. const centerPoint = { x: outerBBox.centerX, y: outerBBox.centerY };
  210. [outerBBox, sLineBBox, tLineBBox, lineBBox].forEach(bbox => {
  211. points = points.concat(this.crossPointsByLineAndBBox(bbox, centerPoint))
  212. })
  213. points.push({ x: sPoint.x, y: tPoint.y })
  214. points.push({ x: tPoint.x, y: sPoint.y })
  215. return points
  216. },
  217. filterConnectablePoints(points, bbox) {
  218. return points.filter(point => point.x <= bbox.minX || point.x >= bbox.maxX || point.y <= bbox.minY || point.y >= bbox.maxY)
  219. },
  220. AStar(points, sPoint, tPoint, sBBox, tBBox) {
  221. const openList = [sPoint]
  222. const closeList = []
  223. points = uniqBy(this.fillId(points), 'id')
  224. points.push(tPoint)
  225. let endPoint
  226. while (openList.length > 0) {
  227. let minCostPoint
  228. openList.forEach((p, i) => {
  229. if (!p.parent) { p.f = 0 }
  230. if (!minCostPoint) { minCostPoint = p }
  231. if (p.f < minCostPoint.f) { minCostPoint = p }
  232. })
  233. if (minCostPoint.x === tPoint.x && minCostPoint.y === tPoint.y) {
  234. endPoint = minCostPoint
  235. break
  236. }
  237. openList.splice(openList.findIndex(o => o.x === minCostPoint.x && o.y === minCostPoint.y), 1)
  238. closeList.push(minCostPoint)
  239. const neighbor = points.filter(p => (p.x === minCostPoint.x || p.y === minCostPoint.y) &&
  240. !(p.x === minCostPoint.x && p.y === minCostPoint.y) &&
  241. !this.crossBBox([sBBox, tBBox], minCostPoint, p))
  242. neighbor.forEach(p => {
  243. const inOpen = openList.find(o => o.x === p.x && o.y === p.y)
  244. const currentG = this.getCost(p, minCostPoint)
  245. // eslint-disable-next-line no-empty
  246. if (closeList.find(o => o.x === p.x && o.y === p.y)) {
  247. } else if (inOpen) {
  248. if (p.g > currentG) {
  249. p.parent = minCostPoint
  250. p.g = currentG
  251. p.f = p.g + p.h
  252. }
  253. } else {
  254. p.parent = minCostPoint
  255. p.g = currentG
  256. let h = this.getCost(p, tPoint)
  257. if (this.crossBBox([tBBox], p, tPoint)) {
  258. h += (tBBox.width / 2 + tBBox.height / 2) // 如果穿过bbox则增加该点的预估代价为bbox周长的一半
  259. }
  260. p.h = h
  261. p.f = p.g + p.h
  262. openList.push(p)
  263. }
  264. })
  265. }
  266. if (endPoint) {
  267. const result = []
  268. result.push({ x: endPoint.x, y: endPoint.y })
  269. while (endPoint.parent) {
  270. endPoint = endPoint.parent
  271. result.push({ x: endPoint.x, y: endPoint.y })
  272. }
  273. return result.reverse()
  274. }
  275. return []
  276. },
  277. crossBBox(bboxes, p1, p2) {
  278. for (let i = 0; i < bboxes.length; i++) {
  279. const bbox = bboxes[i]
  280. if (p1.x === p2.x && bbox.minX < p1.x && bbox.maxX > p1.x) {
  281. if ((p1.y < bbox.maxY && p2.y >= bbox.maxY) || (p2.y < bbox.maxY && p1.y >= bbox.maxY)) { return true }
  282. } else if (p1.y === p2.y && bbox.minY < p1.y && bbox.maxY > p1.y) {
  283. if ((p1.x < bbox.maxX && p2.x >= bbox.maxX) || (p2.x < bbox.maxX && p1.x >= bbox.maxX)) { return true }
  284. }
  285. }
  286. return false
  287. },
  288. getCost(p1, p2) {
  289. return Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y)
  290. },
  291. getPointBBox(t) {
  292. return { centerX: t.x, centerY: t.y, minX: t.x, minY: t.y, maxX: t.x, maxY: t.y, height: 0, width: 0 }
  293. },
  294. fillId(points) {
  295. points.forEach(p => {
  296. p.id = p.x + '-' + p.y
  297. })
  298. return points
  299. },
  300. polylineFinding(sNode, tNode, sPort, tPort, offset) {
  301. const sourceBBox = sNode && sNode.getBBox() ? sNode.getBBox() : this.getPointBBox(sPort)
  302. const targetBBox = tNode && tNode.getBBox() ? tNode.getBBox() : this.getPointBBox(tPort)
  303. const sBBox = this.getExpandedBBox(sourceBBox, offset)
  304. const tBBox = this.getExpandedBBox(targetBBox, offset)
  305. const sPoint = this.getExpandedPort(sBBox, sPort)
  306. const tPoint = this.getExpandedPort(tBBox, tPort)
  307. let points = this.getConnectablePoints(sBBox, tBBox, sPoint, tPoint)
  308. points = this.filterConnectablePoints(points, sBBox)
  309. points = this.filterConnectablePoints(points, tBBox)
  310. const polylinePoints = this.AStar(points, sPoint, tPoint, sBBox, tBBox)
  311. return polylinePoints
  312. }
  313. }, 'polyline')
  314. }