render.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. import _ from 'lodash'
  2. import dagre from 'dagre-layout'
  3. import * as d3 from 'd3'
  4. import positionNodes from './position-nodes'
  5. import positionEdgeLabels from './position-edge-labels'
  6. import positionClusters from './position-clusters'
  7. import createNodes from './create-nodes'
  8. import createClusters from './create-clusters'
  9. import createEdgeLabels from './create-edge-labels'
  10. import createEdgePaths from './create-edge-paths'
  11. import shapes from './shapes'
  12. import arrows from './arrows'
  13. // This design is based on http://bost.ocks.org/mike/chart/.
  14. function render () {
  15. let _createNodes = createNodes
  16. let _createClusters = createClusters
  17. let _createEdgeLabels = createEdgeLabels
  18. let _createEdgePaths = createEdgePaths
  19. let _shapes = shapes
  20. let _arrows = arrows
  21. const fn = function (svg, g) {
  22. preProcessGraph(g)
  23. svg.selectAll('*').remove()
  24. const outputGroup = createOrSelectGroup(svg, 'output')
  25. const clustersGroup = createOrSelectGroup(outputGroup, 'clusters')
  26. const edgePathsGroup = createOrSelectGroup(outputGroup, 'edgePaths')
  27. const edgeLabels = _createEdgeLabels(createOrSelectGroup(outputGroup, 'edgeLabels'), g)
  28. const nodes = _createNodes(createOrSelectGroup(outputGroup, 'nodes'), g, _shapes)
  29. dagre.layout(g)
  30. let minX = 1000
  31. let minY = 1000
  32. let maxX = -1000
  33. let maxY = -1000
  34. const graph = g
  35. graph.nodes().map(n => graph.node(n)).forEach(node => {
  36. minX = Math.min(minX, node.x - node.width / 2)
  37. minY = Math.min(minY, node.y - node.height / 2)
  38. maxX = Math.max(maxX, node.x + node.width / 2)
  39. maxY = Math.max(maxY, node.y + node.height / 2)
  40. })
  41. graph.edges().forEach(e => {
  42. const edge = graph.edge(e)
  43. if (edge.label !== undefined && edge.x !== undefined && edge.y !== undefined) {
  44. minX = Math.min(minX, edge.x - edge.width / 2)
  45. minY = Math.min(minY, edge.y - edge.height / 2)
  46. maxX = Math.max(maxX, edge.x + edge.width / 2)
  47. maxY = Math.max(maxY, edge.y + edge.height / 2)
  48. }
  49. const points = edge.points.slice(1, edge.points.length - 1) // intersetion points don't matter
  50. for (let i = 0; i < points.length; i++) {
  51. const point = points[i]
  52. minX = Math.min(minX, point.x)
  53. minY = Math.min(minY, point.y)
  54. maxX = Math.max(maxX, point.x)
  55. maxY = Math.max(maxY, point.y)
  56. }
  57. })
  58. graph.minX = minX
  59. graph.minY = minY
  60. graph.maxX = maxX
  61. graph.maxY = maxY
  62. positionNodes(nodes, g)
  63. positionEdgeLabels(edgeLabels, g)
  64. _createEdgePaths(edgePathsGroup, g, _arrows)
  65. const clusters = _createClusters(clustersGroup, g)
  66. positionClusters(clusters, g)
  67. postProcessGraph(g)
  68. }
  69. fn.createNodes = function (value) {
  70. if (!arguments.length) {
  71. return _createNodes
  72. }
  73. _createNodes = value
  74. return fn
  75. }
  76. fn.createClusters = function (value) {
  77. if (!arguments.length) {
  78. return _createClusters
  79. }
  80. _createClusters = value
  81. return fn
  82. }
  83. fn.createEdgeLabels = function (value) {
  84. if (!arguments.length) {
  85. return _createEdgeLabels
  86. }
  87. _createEdgeLabels = value
  88. return fn
  89. }
  90. fn.createEdgePaths = function (value) {
  91. if (!arguments.length) {
  92. return _createEdgePaths
  93. }
  94. _createEdgePaths = value
  95. return fn
  96. }
  97. fn.shapes = function (value) {
  98. if (!arguments.length) {
  99. return _shapes
  100. }
  101. _shapes = value
  102. return fn
  103. }
  104. fn.arrows = function (value) {
  105. if (!arguments.length) {
  106. return _arrows
  107. }
  108. _arrows = value
  109. return fn
  110. }
  111. return fn
  112. }
  113. const NODE_DEFAULT_ATTRS = {
  114. paddingLeft: 10,
  115. paddingRight: 10,
  116. paddingTop: 10,
  117. paddingBottom: 10,
  118. rx: 0,
  119. ry: 0,
  120. shape: 'rect'
  121. }
  122. const EDGE_DEFAULT_ATTRS = {
  123. arrowhead: 'normal',
  124. curve: d3.curveLinear
  125. }
  126. function preProcessGraph (g) {
  127. g.nodes().forEach(function (v) {
  128. const node = g.node(v)
  129. if (!_.has(node, 'label') && !g.children(v).length) { node.label = v }
  130. if (_.has(node, 'paddingX')) {
  131. _.defaults(node, {
  132. paddingLeft: node.paddingX,
  133. paddingRight: node.paddingX
  134. })
  135. }
  136. if (_.has(node, 'paddingY')) {
  137. _.defaults(node, {
  138. paddingTop: node.paddingY,
  139. paddingBottom: node.paddingY
  140. })
  141. }
  142. if (_.has(node, 'padding')) {
  143. _.defaults(node, {
  144. paddingLeft: node.padding,
  145. paddingRight: node.padding,
  146. paddingTop: node.padding,
  147. paddingBottom: node.padding
  148. })
  149. }
  150. _.defaults(node, NODE_DEFAULT_ATTRS)
  151. _.each(['paddingLeft', 'paddingRight', 'paddingTop', 'paddingBottom'], function (k) {
  152. node[k] = Number(node[k])
  153. })
  154. // Save dimensions for restore during post-processing
  155. if (_.has(node, 'width')) { node._prevWidth = node.width }
  156. if (_.has(node, 'height')) { node._prevHeight = node.height }
  157. })
  158. g.edges().forEach(function (e) {
  159. const edge = g.edge(e)
  160. if (!_.has(edge, 'label')) { edge.label = '' }
  161. _.defaults(edge, EDGE_DEFAULT_ATTRS)
  162. })
  163. }
  164. function postProcessGraph (g) {
  165. _.each(g.nodes(), function (v) {
  166. const node = g.node(v)
  167. // Restore original dimensions
  168. if (_.has(node, '_prevWidth')) {
  169. node.width = node._prevWidth
  170. } else {
  171. delete node.width
  172. }
  173. if (_.has(node, '_prevHeight')) {
  174. node.height = node._prevHeight
  175. } else {
  176. delete node.height
  177. }
  178. delete node._prevWidth
  179. delete node._prevHeight
  180. })
  181. }
  182. function createOrSelectGroup (root, name) {
  183. let selection = root.select('g.' + name)
  184. if (selection.empty()) {
  185. selection = root.append('g').attr('class', name)
  186. }
  187. return selection
  188. }
  189. export default render