UNPKG

jsnetworkx

Version:

A graph processing and visualization library for JavaScript (port of NetworkX for Python).

309 lines (281 loc) 9.86 kB
'use strict'; var _Object$assign = require('babel-runtime/core-js/object/assign')['default']; var _Array$from = require('babel-runtime/core-js/array/from')['default']; var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default']; Object.defineProperty(exports, '__esModule', { value: true }); exports.relabelNodes = relabelNodes; exports.convertNodeLabelsToIntegers = convertNodeLabelsToIntegers; var _classesDiGraph = require('./classes/DiGraph'); var _classesDiGraph2 = _interopRequireDefault(_classesDiGraph); var _exceptions = require('./exceptions'); var _internals = require('./_internals'); /** * Relabel the nodes of the graph G. * * ### Examples * * ``` * var G = jsnx.pathGraph(3); // nodes 0-1-2 * var mapping = {0: 'a', 1: 'b', 2: 'c'}; * // var mapping = new Map([[0, 'a'], [1, 'b'], [2, 'c']]); * var H = jsnx.relabelNodes(G, mapping); * H.nodes(); * // ['a', 'b', 'c'] * ``` * * Partial in-place mapping * * ``` * var G = jsnx.pathGraph(3); // nodes 0-1-2 * var mapping = {0: 'a', 1: 'b'}; * // var mapping = new Map([[0, 'a'], [1, 'b']]); * var H = jsnx.relabelNodes(G, mapping, false); * H.nodes(); * // [2, 'b', 'c'] * ``` * * Mapping as function: * * ``` * var G = jsnx.pathGraph(3); * var H = jsnx.relabelNodes(G, x => Math.pow(x, 2)); * H.nodes() * // [0, 1, 4] * ``` * * ### Notes * * Only the nodes specified in the mapping will be relabeled. * * The setting `copy=false` modifies the graph in place. * This is not always possible if the mapping is circular. * In that case use copy=true. * * @see #convertNodeLabelsToIntegers * * @param {Graph} G A JSNetworkX graph * @param {(Object|Map|function(Node):Node)} mapping * A dictionary with the old labels as keys and new labels as values. * A partial mapping is allowed. * @param {boolean=} optCopy * If `true` return a copy or if `false` relabel the nodes in place. * @return {Graph} */ function relabelNodes(G, mapping) { var optCopy = arguments.length <= 2 || arguments[2] === undefined ? true : arguments[2]; // you can pass a function f(oldLabel)->newLabel // but we'll just make a dictionary here regardless var m = mapping; if (typeof mapping !== 'function') { if (!(0, _internals.isMap)(m)) { m = new _internals.Map(m); } } else { m = new _internals.Map((0, _internals.mapIterator)(G.nodesIter(), function (n) { return (0, _internals.tuple2)(n, mapping(n)); })); } return optCopy ? relabelCopy(G, m) : relabelInplace(G, m); } /** * @param {Graph} G A JSNetworkX graph * @param {Map} mapping * A dictionary with the old labels as keys and new labels as values. * A partial mapping is allowed. * * @return .Graph} * @private */ function relabelInplace(G, mapping) { var oldLabels = new _internals.Set(mapping.keys()); var nodes; if ((0, _internals.someIterator)(mapping.values(), function (v) { return oldLabels.has(v); })) { // labels sets overlap // can we topological sort and still do the relabeling? var D = new _classesDiGraph2['default'](mapping); D.removeEdgesFrom(D.selfloopEdges()); try { nodes = (0, _internals.topologicalSort)(D); } catch (e) { if (e instanceof _exceptions.JSNetworkXUnfeasible) { throw new _exceptions.JSNetworkXUnfeasible('The node label sets are overlapping and' + ' no ordering can resolve the mapping.' + ' Use copy=True.'); } } nodes.reverse(); // reverse topological order } else { // non-overlapping label sets nodes = oldLabels.values(); } var multigraph = G.isMultigraph(); var directed = G.isDirected(); var newEdges; (0, _internals.forEach)(nodes, function (old) { var newMapping; if (mapping.has(old)) { newMapping = mapping.get(old); } else { return; // continue } if (!G.hasNode(old)) { throw new _exceptions.JSNetworkXError((0, _internals.sprintf)('Node %j is not in the graph.', old)); } G.addNode(newMapping, G.node.get(old)); if (multigraph) { newEdges = G.edges(old, true, true).map(function (d) { return (0, _internals.tuple4c)(newMapping, d[1], d[2], d[3], d); }); if (directed) { newEdges = newEdges.concat(G.inEdges(old, true, true).map(function (d) { return (0, _internals.tuple4c)(d[0], newMapping, d[2], d[3], d); })); } } else { newEdges = G.edges(old, true).map(function (d) { return (0, _internals.tuple3c)(newMapping, d[1], d[2], d); }); if (directed) { newEdges = newEdges.concat(G.inEdges(old, true).map(function (d) { return (0, _internals.tuple3c)(d[0], newMapping, d[2], d); })); } } G.removeNode(old); G.addEdgesFrom(newEdges); }); return G; } /** * @param {Graph} G A JSNetworkX graph * @param {Map} mapping * A dictionary with the old labels as keys and new labels as values. * A partial mapping is allowed. * * @return {Graph} * @private */ function relabelCopy(G, mapping) { var H = new G.constructor(); H.name = '(' + G.name + ')'; if (G.isMultigraph()) { H.addEdgesFrom((0, _internals.mapIterator)(G.edgesIter(null, true, true), function (d) { return (0, _internals.tuple4c)(mapping.has(d[0]) ? mapping.get(d[0]) : d[0], mapping.has(d[1]) ? mapping.get(d[1]) : d[1], d[2], (0, _internals.clone)(d[3]), d); })); } else { H.addEdgesFrom((0, _internals.mapIterator)(G.edgesIter(null, true), function (d) { return (0, _internals.tuple3c)(mapping.has(d[0]) ? mapping.get(d[0]) : d[0], mapping.has(d[1]) ? mapping.get(d[1]) : d[1], (0, _internals.clone)(d[3]), d); })); } G.node.forEach(function (data, n) { return H.addNode(mapping.has(n) ? mapping.get(n) : n, (0, _internals.clone)(data)); }); _Object$assign(H.graph, (0, _internals.clone)(G.graph)); return H; } /** * Return a copy of G node labels replaced with integers. * * ### Notes * * Node and edge attribute data are copied to the new (relabeled) graph. * * @param {Graph} G A JSNetworkX graph * @param {?number=} optFirstLabel * An integer specifying the offset in numbering nodes. * The n new integer labels are numbered firstLabel, ..., n-1+firstLabel. * @param {?string=} optOrdering * - "default" : inherit node ordering from `G.nodes()` * - "sorted" : inherit node ordering from `G.nodes().sort()` * - "increasing degree" : nodes are sorted by increasing degree * - "decreasing degree" : nodes are sorted by decreasing degree * @param {?boolean=} optDiscardOldLabels * If true discard old labels. If false, create a node attribute * 'oldLabel' to hold the old labels. * @return {Graph} */ function convertNodeLabelsToIntegers(G) { var optFirstLabel = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1]; var optOrdering = arguments.length <= 2 || arguments[2] === undefined ? 'default' : arguments[2]; var optDiscardOldLabels = arguments.length <= 3 || arguments[3] === undefined ? true : arguments[3]; // This function strips information attached to the nodes and/or // edges of a graph, and returns a graph with appropriate integer // labels. One can view this as a re-labeling of the nodes. Be // warned that the term "labeled graph" has a loaded meaning // in graph theory. The fundamental issue is whether the names // (labels) of the nodes (and edges) matter in deciding when two // graphs are the same. For example, in problems of graph enumeration // there is a distinct difference in techniques required when // counting labeled vs. unlabeled graphs. // // When implementing graph // algorithms it is often convenient to strip off the original node // and edge information and appropriately relabel the n nodes with // the integer values 1,..,n. This is the purpose of this function, // and it provides the option (see discardOldLabels variable) to either // preserve the original labels in separate dicts (these are not // returned but made an attribute of the new graph. if (typeof optOrdering === 'boolean') { optDiscardOldLabels = optOrdering; optOrdering = 'default'; } switch (typeof optFirstLabel) { case 'string': optOrdering = optFirstLabel; optFirstLabel = 0; break; case 'boolean': optDiscardOldLabels = optFirstLabel; optFirstLabel = 0; break; } var mapping = new _internals.Map(); var nodes; var dvPairs; var i; var j; var l; switch (optOrdering) { case 'default': nodes = G.nodes(); for (i = 0, j = optFirstLabel, l = nodes.length; i < l; i++, j++) { mapping.set(nodes[i], j); } break; case 'sorted': nodes = G.nodes(); nodes.sort(); for (i = 0, j = optFirstLabel, l = nodes.length; i < l; i++, j++) { mapping.set(nodes[i], j); } break; case 'increasing degree': dvPairs = _Array$from(G.degreeIter()); dvPairs.sort(function (a, b) { return a[1] - b[1]; }); for (i = 0, j = optFirstLabel, l = dvPairs.length; i < l; i++, j++) { mapping.set(dvPairs[i][0], j); } break; case 'decreasing degree': dvPairs = _Array$from(G.degreeIter()); dvPairs.sort(function (a, b) { return b[1] - a[1]; }); for (i = 0, j = optFirstLabel, l = dvPairs.length; i < l; i++, j++) { mapping.set(dvPairs[i][0], j); } break; default: throw new _exceptions.JSNetworkXError((0, _internals.sprintf)('Unkown node ordering: "%s"', optOrdering)); } var H = relabelNodes(G, mapping); H.name = '(' + G.name + ')WithIntLabels'; if (!optDiscardOldLabels) { H.nodeLabels = mapping; } return H; }