import * as _ from 'lodash-es'; import { Graph } from '../graphlib/index.js'; export { addDummyNode, simplify, asNonCompoundGraph, successorWeights, predecessorWeights, intersectRect, buildLayerMatrix, normalizeRanks, removeEmptyRanks, addBorderNode, maxRank, partition, time, notime, }; /* * Adds a dummy node to the graph and return v. */ function addDummyNode(g, type, attrs, name) { var 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. */ function simplify(g) { var simplified = new Graph().setGraph(g.graph()); _.forEach(g.nodes(), function (v) { simplified.setNode(v, g.node(v)); }); _.forEach(g.edges(), function (e) { var simpleLabel = simplified.edge(e.v, e.w) || { weight: 0, minlen: 1 }; var label = g.edge(e); simplified.setEdge(e.v, e.w, { weight: simpleLabel.weight + label.weight, minlen: Math.max(simpleLabel.minlen, label.minlen), }); }); return simplified; } function asNonCompoundGraph(g) { var 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; } function successorWeights(g) { var weightMap = _.map(g.nodes(), function (v) { var 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); } function predecessorWeights(g) { var weightMap = _.map(g.nodes(), function (v) { var 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. */ function intersectRect(rect, point) { var x = rect.x; var y = rect.y; // Rectangle intersection algorithm from: // http://math.stackexchange.com/questions/108113/find-edge-between-two-boxes var dx = point.x - x; var dy = point.y - y; var w = rect.width / 2; var h = rect.height / 2; if (!dx && !dy) { throw new Error('Not possible to find intersection inside of the rectangle'); } var sx, 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. */ function buildLayerMatrix(g) { var layering = _.map(_.range(maxRank(g) + 1), function () { return []; }); _.forEach(g.nodes(), function (v) { var node = g.node(v); var 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. */ function normalizeRanks(g) { var min = _.min( _.map(g.nodes(), function (v) { return g.node(v).rank; }), ); _.forEach(g.nodes(), function (v) { var node = g.node(v); if (_.has(node, 'rank')) { node.rank -= min; } }); } function removeEmptyRanks(g) { // Ranks may not start at 0, so we need to offset them var offset = _.min( _.map(g.nodes(), function (v) { return g.node(v).rank; }), ); var layers = []; _.forEach(g.nodes(), function (v) { var rank = g.node(v).rank - offset; if (!layers[rank]) { layers[rank] = []; } layers[rank].push(v); }); var delta = 0; var 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; }); } }); } function addBorderNode(g, prefix, rank, order) { var node = { width: 0, height: 0, }; if (arguments.length >= 4) { node.rank = rank; node.order = order; } return addDummyNode(g, 'border', node, prefix); } function maxRank(g) { return _.max( _.map(g.nodes(), function (v) { var 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. */ function partition(collection, fn) { var 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. */ function time(name, fn) { var start = _.now(); try { return fn(); } finally { console.log(name + ' time: ' + (_.now() - start) + 'ms'); } } function notime(name, fn) { return fn(); }