util.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. import _ from 'lodash'
  2. import { Graph } from 'graphlibrary'
  3. /*
  4. * Adds a dummy node to the graph and return v.
  5. */
  6. export function addDummyNode (g, type, attrs, name) {
  7. let v
  8. do {
  9. v = _.uniqueId(name)
  10. } while (g.hasNode(v))
  11. attrs.dummy = type
  12. g.setNode(v, attrs)
  13. return v
  14. }
  15. /*
  16. * Returns a new graph with only simple edges. Handles aggregation of data
  17. * associated with multi-edges.
  18. */
  19. export function simplify (g) {
  20. const simplified = new Graph().setGraph(g.graph())
  21. _.forEach(g.nodes(), function (v) { simplified.setNode(v, g.node(v)) })
  22. _.forEach(g.edges(), function (e) {
  23. const simpleLabel = simplified.edge(e.v, e.w) || { weight: 0, minlen: 1 }
  24. const label = g.edge(e)
  25. simplified.setEdge(e.v, e.w, {
  26. weight: simpleLabel.weight + label.weight,
  27. minlen: Math.max(simpleLabel.minlen, label.minlen)
  28. })
  29. })
  30. return simplified
  31. }
  32. export function asNonCompoundGraph (g) {
  33. const simplified = new Graph({ multigraph: g.isMultigraph() }).setGraph(g.graph())
  34. _.forEach(g.nodes(), function (v) {
  35. if (!g.children(v).length) {
  36. simplified.setNode(v, g.node(v))
  37. }
  38. })
  39. _.forEach(g.edges(), function (e) {
  40. simplified.setEdge(e, g.edge(e))
  41. })
  42. return simplified
  43. }
  44. export function successorWeights (g) {
  45. const weightMap = _.map(g.nodes(), function (v) {
  46. const sucs = {}
  47. _.forEach(g.outEdges(v), function (e) {
  48. sucs[e.w] = (sucs[e.w] || 0) + g.edge(e).weight
  49. })
  50. return sucs
  51. })
  52. return _.zipObject(g.nodes(), weightMap)
  53. }
  54. export function predecessorWeights (g) {
  55. const weightMap = _.map(g.nodes(), function (v) {
  56. const preds = {}
  57. _.forEach(g.inEdges(v), function (e) {
  58. preds[e.v] = (preds[e.v] || 0) + g.edge(e).weight
  59. })
  60. return preds
  61. })
  62. return _.zipObject(g.nodes(), weightMap)
  63. }
  64. /*
  65. * Finds where a line starting at point ({x, y}) would intersect a rectangle
  66. * ({x, y, width, height}) if it were pointing at the rectangle's center.
  67. */
  68. export function intersectRect (rect, point) {
  69. const x = rect.x
  70. const y = rect.y
  71. // Rectangle intersection algorithm from:
  72. // http://math.stackexchange.com/questions/108113/find-edge-between-two-boxes
  73. const dx = point.x - x
  74. const dy = point.y - y
  75. let w = rect.width / 2
  76. let h = rect.height / 2
  77. if (!dx && !dy) {
  78. throw new Error('Not possible to find intersection inside of the rectangle')
  79. }
  80. let sx
  81. let sy
  82. if (Math.abs(dy) * w > Math.abs(dx) * h) {
  83. // Intersection is top or bottom of rect.
  84. if (dy < 0) {
  85. h = -h
  86. }
  87. sx = h * dx / dy
  88. sy = h
  89. } else {
  90. // Intersection is left or right of rect.
  91. if (dx < 0) {
  92. w = -w
  93. }
  94. sx = w
  95. sy = w * dy / dx
  96. }
  97. return { x: x + sx, y: y + sy }
  98. }
  99. /*
  100. * Given a DAG with each node assigned "rank" and "order" properties, this
  101. * function will produce a matrix with the ids of each node.
  102. */
  103. export function buildLayerMatrix (g) {
  104. const layering = _.map(_.range(maxRank(g) + 1), function () { return [] })
  105. _.forEach(g.nodes(), function (v) {
  106. const node = g.node(v)
  107. const rank = node.rank
  108. if (!_.isUndefined(rank)) {
  109. layering[rank][node.order] = v
  110. }
  111. })
  112. return layering
  113. }
  114. /*
  115. * Adjusts the ranks for all nodes in the graph such that all nodes v have
  116. * rank(v) >= 0 and at least one node w has rank(w) = 0.
  117. */
  118. export function normalizeRanks (g) {
  119. const min = _.min(_.map(g.nodes(), function (v) { return g.node(v).rank }))
  120. _.forEach(g.nodes(), function (v) {
  121. const node = g.node(v)
  122. if (_.has(node, 'rank')) {
  123. node.rank -= min
  124. }
  125. })
  126. }
  127. export function removeEmptyRanks (g) {
  128. // Ranks may not start at 0, so we need to offset them
  129. const offset = _.min(_.map(g.nodes(), function (v) { return g.node(v).rank }))
  130. const layers = []
  131. _.forEach(g.nodes(), function (v) {
  132. const rank = g.node(v).rank - offset
  133. if (!layers[rank]) {
  134. layers[rank] = []
  135. }
  136. layers[rank].push(v)
  137. })
  138. let delta = 0
  139. const nodeRankFactor = g.graph().nodeRankFactor
  140. _.forEach(layers, function (vs, i) {
  141. if (_.isUndefined(vs) && i % nodeRankFactor !== 0) {
  142. --delta
  143. } else if (delta) {
  144. _.forEach(vs, function (v) { g.node(v).rank += delta })
  145. }
  146. })
  147. }
  148. export function addBorderNode (g, prefix, rank, order) {
  149. const node = {
  150. width: 0,
  151. height: 0
  152. }
  153. if (arguments.length >= 4) {
  154. node.rank = rank
  155. node.order = order
  156. }
  157. return addDummyNode(g, 'border', node, prefix)
  158. }
  159. export function maxRank (g) {
  160. return _.max(_.map(g.nodes(), function (v) {
  161. const rank = g.node(v).rank
  162. if (!_.isUndefined(rank)) {
  163. return rank
  164. }
  165. }))
  166. }
  167. /*
  168. * Partition a collection into two groups: `lhs` and `rhs`. If the supplied
  169. * function returns true for an entry it goes into `lhs`. Otherwise it goes
  170. * into `rhs.
  171. */
  172. export function partition (collection, fn) {
  173. const result = { lhs: [], rhs: [] }
  174. _.forEach(collection, function (value) {
  175. if (fn(value)) {
  176. result.lhs.push(value)
  177. } else {
  178. result.rhs.push(value)
  179. }
  180. })
  181. return result
  182. }
  183. /*
  184. * Returns a new function that wraps `fn` with a timer. The wrapper logs the
  185. * time it takes to execute the function.
  186. */
  187. export function time (name, fn) {
  188. const start = _.now()
  189. try {
  190. return fn()
  191. } finally {
  192. console.log(name + ' time: ' + (_.now() - start) + 'ms')
  193. }
  194. }
  195. export function notime (name, fn) {
  196. return fn()
  197. }
  198. export default {
  199. addDummyNode,
  200. simplify,
  201. asNonCompoundGraph,
  202. successorWeights,
  203. predecessorWeights,
  204. intersectRect,
  205. buildLayerMatrix,
  206. normalizeRanks,
  207. removeEmptyRanks,
  208. addBorderNode,
  209. maxRank,
  210. partition,
  211. time,
  212. notime
  213. }