| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- import _ from 'lodash'
- import { Graph } from 'graphlibrary'
- /*
- * Adds a dummy node to the graph and return v.
- */
- export function addDummyNode (g, type, attrs, name) {
- let v
- do {
- v = _.uniqueId(name)
- } while (g.hasNode(v))
- attrs.dummy = type
- g.setNode(v, attrs)
- return v
- }
- /*
- * Returns a new graph with only simple edges. Handles aggregation of data
- * associated with multi-edges.
- */
- export function simplify (g) {
- const simplified = new Graph().setGraph(g.graph())
- _.forEach(g.nodes(), function (v) { simplified.setNode(v, g.node(v)) })
- _.forEach(g.edges(), function (e) {
- const simpleLabel = simplified.edge(e.v, e.w) || { weight: 0, minlen: 1 }
- const label = g.edge(e)
- simplified.setEdge(e.v, e.w, {
- weight: simpleLabel.weight + label.weight,
- minlen: Math.max(simpleLabel.minlen, label.minlen)
- })
- })
- return simplified
- }
- export function asNonCompoundGraph (g) {
- const simplified = new Graph({ multigraph: g.isMultigraph() }).setGraph(g.graph())
- _.forEach(g.nodes(), function (v) {
- if (!g.children(v).length) {
- simplified.setNode(v, g.node(v))
- }
- })
- _.forEach(g.edges(), function (e) {
- simplified.setEdge(e, g.edge(e))
- })
- return simplified
- }
- export function successorWeights (g) {
- const weightMap = _.map(g.nodes(), function (v) {
- const sucs = {}
- _.forEach(g.outEdges(v), function (e) {
- sucs[e.w] = (sucs[e.w] || 0) + g.edge(e).weight
- })
- return sucs
- })
- return _.zipObject(g.nodes(), weightMap)
- }
- export function predecessorWeights (g) {
- const weightMap = _.map(g.nodes(), function (v) {
- const preds = {}
- _.forEach(g.inEdges(v), function (e) {
- preds[e.v] = (preds[e.v] || 0) + g.edge(e).weight
- })
- return preds
- })
- return _.zipObject(g.nodes(), weightMap)
- }
- /*
- * Finds where a line starting at point ({x, y}) would intersect a rectangle
- * ({x, y, width, height}) if it were pointing at the rectangle's center.
- */
- export function intersectRect (rect, point) {
- const x = rect.x
- const y = rect.y
- // Rectangle intersection algorithm from:
- // http://math.stackexchange.com/questions/108113/find-edge-between-two-boxes
- const dx = point.x - x
- const dy = point.y - y
- let w = rect.width / 2
- let h = rect.height / 2
- if (!dx && !dy) {
- throw new Error('Not possible to find intersection inside of the rectangle')
- }
- let sx
- let sy
- if (Math.abs(dy) * w > Math.abs(dx) * h) {
- // Intersection is top or bottom of rect.
- if (dy < 0) {
- h = -h
- }
- sx = h * dx / dy
- sy = h
- } else {
- // Intersection is left or right of rect.
- if (dx < 0) {
- w = -w
- }
- sx = w
- sy = w * dy / dx
- }
- return { x: x + sx, y: y + sy }
- }
- /*
- * Given a DAG with each node assigned "rank" and "order" properties, this
- * function will produce a matrix with the ids of each node.
- */
- export function buildLayerMatrix (g) {
- const layering = _.map(_.range(maxRank(g) + 1), function () { return [] })
- _.forEach(g.nodes(), function (v) {
- const node = g.node(v)
- const rank = node.rank
- if (!_.isUndefined(rank)) {
- layering[rank][node.order] = v
- }
- })
- return layering
- }
- /*
- * Adjusts the ranks for all nodes in the graph such that all nodes v have
- * rank(v) >= 0 and at least one node w has rank(w) = 0.
- */
- export function normalizeRanks (g) {
- const min = _.min(_.map(g.nodes(), function (v) { return g.node(v).rank }))
- _.forEach(g.nodes(), function (v) {
- const node = g.node(v)
- if (_.has(node, 'rank')) {
- node.rank -= min
- }
- })
- }
- export function removeEmptyRanks (g) {
- // Ranks may not start at 0, so we need to offset them
- const offset = _.min(_.map(g.nodes(), function (v) { return g.node(v).rank }))
- const layers = []
- _.forEach(g.nodes(), function (v) {
- const rank = g.node(v).rank - offset
- if (!layers[rank]) {
- layers[rank] = []
- }
- layers[rank].push(v)
- })
- let delta = 0
- const nodeRankFactor = g.graph().nodeRankFactor
- _.forEach(layers, function (vs, i) {
- if (_.isUndefined(vs) && i % nodeRankFactor !== 0) {
- --delta
- } else if (delta) {
- _.forEach(vs, function (v) { g.node(v).rank += delta })
- }
- })
- }
- export function addBorderNode (g, prefix, rank, order) {
- const node = {
- width: 0,
- height: 0
- }
- if (arguments.length >= 4) {
- node.rank = rank
- node.order = order
- }
- return addDummyNode(g, 'border', node, prefix)
- }
- export function maxRank (g) {
- return _.max(_.map(g.nodes(), function (v) {
- const rank = g.node(v).rank
- if (!_.isUndefined(rank)) {
- return rank
- }
- }))
- }
- /*
- * Partition a collection into two groups: `lhs` and `rhs`. If the supplied
- * function returns true for an entry it goes into `lhs`. Otherwise it goes
- * into `rhs.
- */
- export function partition (collection, fn) {
- const result = { lhs: [], rhs: [] }
- _.forEach(collection, function (value) {
- if (fn(value)) {
- result.lhs.push(value)
- } else {
- result.rhs.push(value)
- }
- })
- return result
- }
- /*
- * Returns a new function that wraps `fn` with a timer. The wrapper logs the
- * time it takes to execute the function.
- */
- export function time (name, fn) {
- const start = _.now()
- try {
- return fn()
- } finally {
- console.log(name + ' time: ' + (_.now() - start) + 'ms')
- }
- }
- export function notime (name, fn) {
- return fn()
- }
- export default {
- addDummyNode,
- simplify,
- asNonCompoundGraph,
- successorWeights,
- predecessorWeights,
- intersectRect,
- buildLayerMatrix,
- normalizeRanks,
- removeEmptyRanks,
- addBorderNode,
- maxRank,
- partition,
- time,
- notime
- }
|