resolve-conflicts.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. import _ from 'lodash'
  2. /*
  3. * Given a list of entries of the form {v, barycenter, weight} and a
  4. * constraint graph this function will resolve any conflicts between the
  5. * constraint graph and the barycenters for the entries. If the barycenters for
  6. * an entry would violate a constraint in the constraint graph then we coalesce
  7. * the nodes in the conflict into a new node that respects the contraint and
  8. * aggregates barycenter and weight information.
  9. *
  10. * This implementation is based on the description in Forster, "A Fast and
  11. * Simple Hueristic for Constrained Two-Level Crossing Reduction," thought it
  12. * differs in some specific details.
  13. *
  14. * Pre-conditions:
  15. *
  16. * 1. Each entry has the form {v, barycenter, weight}, or if the node has
  17. * no barycenter, then {v}.
  18. *
  19. * Returns:
  20. *
  21. * A new list of entries of the form {vs, i, barycenter, weight}. The list
  22. * `vs` may either be a singleton or it may be an aggregation of nodes
  23. * ordered such that they do not violate constraints from the constraint
  24. * graph. The property `i` is the lowest original index of any of the
  25. * elements in `vs`.
  26. */
  27. function resolveConflicts (entries, cg) {
  28. const mappedEntries = {}
  29. _.forEach(entries, function (entry, i) {
  30. const tmp = mappedEntries[entry.v] = {
  31. indegree: 0,
  32. 'in': [],
  33. out: [],
  34. vs: [entry.v],
  35. i: i
  36. }
  37. if (!_.isUndefined(entry.barycenter)) {
  38. tmp.barycenter = entry.barycenter
  39. tmp.weight = entry.weight
  40. }
  41. })
  42. _.forEach(cg.edges(), function (e) {
  43. const entryV = mappedEntries[e.v]
  44. const entryW = mappedEntries[e.w]
  45. if (!_.isUndefined(entryV) && !_.isUndefined(entryW)) {
  46. entryW.indegree++
  47. entryV.out.push(mappedEntries[e.w])
  48. }
  49. })
  50. const sourceSet = _.filter(mappedEntries, function (entry) {
  51. return !entry.indegree
  52. })
  53. return doResolveConflicts(sourceSet)
  54. }
  55. function doResolveConflicts (sourceSet) {
  56. const entries = []
  57. function handleIn (vEntry) {
  58. return function (uEntry) {
  59. if (uEntry.merged) {
  60. return
  61. }
  62. if (_.isUndefined(uEntry.barycenter) ||
  63. _.isUndefined(vEntry.barycenter) ||
  64. uEntry.barycenter >= vEntry.barycenter) {
  65. mergeEntries(vEntry, uEntry)
  66. }
  67. }
  68. }
  69. function handleOut (vEntry) {
  70. return function (wEntry) {
  71. wEntry['in'].push(vEntry)
  72. if (--wEntry.indegree === 0) {
  73. sourceSet.push(wEntry)
  74. }
  75. }
  76. }
  77. while (sourceSet.length) {
  78. const entry = sourceSet.pop()
  79. entries.push(entry)
  80. _.forEach(entry['in'].reverse(), handleIn(entry))
  81. _.forEach(entry.out, handleOut(entry))
  82. }
  83. return _.chain(entries)
  84. .filter(function (entry) { return !entry.merged })
  85. .map(function (entry) {
  86. return _.pick(entry, ['vs', 'i', 'barycenter', 'weight'])
  87. })
  88. .value()
  89. }
  90. function mergeEntries (target, source) {
  91. let sum = 0
  92. let weight = 0
  93. if (target.weight) {
  94. sum += target.barycenter * target.weight
  95. weight += target.weight
  96. }
  97. if (source.weight) {
  98. sum += source.barycenter * source.weight
  99. weight += source.weight
  100. }
  101. target.vs = source.vs.concat(target.vs)
  102. target.barycenter = sum / weight
  103. target.weight = weight
  104. target.i = Math.min(source.i, target.i)
  105. source.merged = true
  106. }
  107. export default resolveConflicts