| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121 |
- import _ from 'lodash'
- /*
- * Given a list of entries of the form {v, barycenter, weight} and a
- * constraint graph this function will resolve any conflicts between the
- * constraint graph and the barycenters for the entries. If the barycenters for
- * an entry would violate a constraint in the constraint graph then we coalesce
- * the nodes in the conflict into a new node that respects the contraint and
- * aggregates barycenter and weight information.
- *
- * This implementation is based on the description in Forster, "A Fast and
- * Simple Hueristic for Constrained Two-Level Crossing Reduction," thought it
- * differs in some specific details.
- *
- * Pre-conditions:
- *
- * 1. Each entry has the form {v, barycenter, weight}, or if the node has
- * no barycenter, then {v}.
- *
- * Returns:
- *
- * A new list of entries of the form {vs, i, barycenter, weight}. The list
- * `vs` may either be a singleton or it may be an aggregation of nodes
- * ordered such that they do not violate constraints from the constraint
- * graph. The property `i` is the lowest original index of any of the
- * elements in `vs`.
- */
- function resolveConflicts (entries, cg) {
- const mappedEntries = {}
- _.forEach(entries, function (entry, i) {
- const tmp = mappedEntries[entry.v] = {
- indegree: 0,
- 'in': [],
- out: [],
- vs: [entry.v],
- i: i
- }
- if (!_.isUndefined(entry.barycenter)) {
- tmp.barycenter = entry.barycenter
- tmp.weight = entry.weight
- }
- })
- _.forEach(cg.edges(), function (e) {
- const entryV = mappedEntries[e.v]
- const entryW = mappedEntries[e.w]
- if (!_.isUndefined(entryV) && !_.isUndefined(entryW)) {
- entryW.indegree++
- entryV.out.push(mappedEntries[e.w])
- }
- })
- const sourceSet = _.filter(mappedEntries, function (entry) {
- return !entry.indegree
- })
- return doResolveConflicts(sourceSet)
- }
- function doResolveConflicts (sourceSet) {
- const entries = []
- function handleIn (vEntry) {
- return function (uEntry) {
- if (uEntry.merged) {
- return
- }
- if (_.isUndefined(uEntry.barycenter) ||
- _.isUndefined(vEntry.barycenter) ||
- uEntry.barycenter >= vEntry.barycenter) {
- mergeEntries(vEntry, uEntry)
- }
- }
- }
- function handleOut (vEntry) {
- return function (wEntry) {
- wEntry['in'].push(vEntry)
- if (--wEntry.indegree === 0) {
- sourceSet.push(wEntry)
- }
- }
- }
- while (sourceSet.length) {
- const entry = sourceSet.pop()
- entries.push(entry)
- _.forEach(entry['in'].reverse(), handleIn(entry))
- _.forEach(entry.out, handleOut(entry))
- }
- return _.chain(entries)
- .filter(function (entry) { return !entry.merged })
- .map(function (entry) {
- return _.pick(entry, ['vs', 'i', 'barycenter', 'weight'])
- })
- .value()
- }
- function mergeEntries (target, source) {
- let sum = 0
- let weight = 0
- if (target.weight) {
- sum += target.barycenter * target.weight
- weight += target.weight
- }
- if (source.weight) {
- sum += source.barycenter * source.weight
- weight += source.weight
- }
- target.vs = source.vs.concat(target.vs)
- target.barycenter = sum / weight
- target.weight = weight
- target.i = Math.min(source.i, target.i)
- source.merged = true
- }
- export default resolveConflicts
|