UNPKG

metagraph

Version:

A framework for building higher-order graph data structures

274 lines (269 loc) 8.76 kB
/*! * metagraph.js <%= conf.pkg.version %> * http://gordonwoodhull.github.io/metagraph.js/ * Copyright 2019 AT&T Intellectual Property * * Licensed under the MIT License * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ import { as_keyvalue, as_array, build_map } from './core.js'; function graph_options(opts) { return Object.assign({ nodeKey: (kv) => kv.key, edgeKey: (kv) => kv.key, nodeValue: (kv) => kv.value, edgeValue: (kv) => kv.value, edgeSource: (kv) => kv.value.source, edgeTarget: (kv) => kv.value.target }, opts || {}); } class NodeImpl { constructor(n, options, graph_ref, outs_map, ins_map) { this.n = n; this.options = options; this.graph_ref = graph_ref; this.outs_map = outs_map; this.ins_map = ins_map; } value() { return this.options.nodeValue(this.n); } key() { return this.options.nodeKey(this.n); } graph() { return this.graph_ref(); } outs() { return this.outs_map()[this.options.nodeKey(this.n)] || []; } ins() { return this.ins_map()[this.options.nodeKey(this.n)] || []; } } class EdgeImpl { constructor(e, options, graph_ref) { this.e = e; this.options = options; this.graph_ref = graph_ref; } value() { return this.options.edgeValue(this.e); } key() { return this.options.edgeKey(this.e); } graph() { return this.graph_ref(); } source() { return this.graph_ref().node(this.options.edgeSource(this.e)); } target() { return this.graph_ref().node(this.options.edgeTarget(this.e)); } } class GraphImpl { constructor(nodesList, edgesList, options) { this.nodesList = nodesList; this.edgesList = edgesList; this.options = options; } build_node_map() { if (this._nodeMap) return; this._nodeMap = build_map(this.nodesList, this.options.nodeKey, (n) => new NodeImpl(n, this.options, () => this, () => this.get_outs_map(), () => this.get_ins_map())); } build_edge_map() { if (this._edgeMap) return; this._edgeMap = build_map(this.edgesList, this.options.edgeKey, (e) => new EdgeImpl(e, this.options, () => this)); } build_nodes_list() { if (this._nodesList) return; this.build_node_map(); this._nodesList = this.nodesList.map(v => this.node(this.options.nodeKey(v))); } build_edges_list() { if (this._edgesList) return; this.build_edge_map(); this._edgesList = this.edgesList.map(v => this.edge(this.options.edgeKey(v))); } build_directional_edge_lists(acc) { this.build_edge_map(); return this.edgesList.reduce((o, v) => { const key = acc(v); const list = o[key] = o[key] || []; list.push(this.edge(this.options.edgeKey(v))); return o; }, {}); } build_outs_map() { if (this._outsList) return; this._outsList = this.build_directional_edge_lists(this.options.edgeSource); } build_ins_map() { if (this._insList) return; this._insList = this.build_directional_edge_lists(this.options.edgeTarget); } get_outs_map() { this.build_outs_map(); return this._outsList; } get_ins_map() { this.build_ins_map(); return this._insList; } node(key) { this.build_node_map(); return this._nodeMap[key]; } edge(key) { this.build_edge_map(); return this._edgeMap[key]; } nodes() { this.build_nodes_list(); return this._nodesList; } edges() { this.build_edges_list(); return this._edgesList; } } /** * Creates a graph data structure from nodes and edges with adjacency list representation. * Supports lazy evaluation and custom accessor functions for flexible data formats. * * @param nodes - Array of node objects or key-value record of nodes * @param edges - Array of edge objects or key-value record of edges * @param opts - Optional configuration for accessing node/edge properties * @returns A graph instance with nodes and edges * * @example * ```typescript * const g = graph( * [{key: 'a'}, {key: 'b'}], * [{key: 'e1', value: {source: 'a', target: 'b'}}] * ); * console.log(g.node('a').outs().length); // 1 * ``` */ function graph(nodes, edges, opts) { const nodesList = as_keyvalue(nodes); const edgesList = as_keyvalue(edges); const options = graph_options(opts); return new GraphImpl(nodesList, edgesList, options); } function incidence_options(opts) { const gropts = graph_options(opts); return Object.assign({ nodeIncidences: (n) => n && (n.edges || n.ins || n.outs) || [], incidencesOutward: (n) => { const v = gropts.nodeValue(n); return !v || !!(v.edges || v.outs); } }, gropts); } /** * Creates a graph from nodes with incidence information (edges defined within node data). * Automatically generates edges based on node incidence properties like 'edges', 'outs', or 'ins'. * * @param nodes - Array of nodes containing incidence information * @param opts - Optional configuration for accessing node properties and incidence direction * @returns A graph instance with generated edges from incidence data * * @example * ```typescript * const g = graph_incidence([ * {key: 'a', value: {edges: ['b', 'c']}}, * {key: 'b'}, * {key: 'c'} * ]); * console.log(g.edges().map(e => e.key())); // ['a-b', 'a-c'] * ``` */ function graph_incidence(nodes, opts) { const nodesList = as_keyvalue(nodes); const options = incidence_options(opts); const edges = []; function edge_value(outward, nk, ik) { return outward ? { source: nk, target: ik } : { source: ik, target: nk }; } function edge_key(outward, nk, ik) { return outward ? nk + '-' + ik : ik + '-' + nk; } nodesList.forEach(n => { const nk = options.nodeKey(n); const outward = options.incidencesOutward(n); as_array(options.nodeIncidences(options.nodeValue(n))) .forEach(ik => { edges.push({ key: edge_key(outward, nk, ik), value: edge_value(outward, nk, ik) }); }); }); return graph(nodesList, edges, opts); } /** * Automatically detects the graph format and creates the appropriate graph instance. * Supports both adjacency format (separate nodes/edges) and incidence format (edges in nodes). * * @param spec - Graph specification with either nodes/edges or incidences * @param opts - Optional configuration for accessing node/edge properties * @returns A graph instance created using the appropriate format * @throws Error if the graph format is not recognized * * @example * ```typescript * // Adjacency format * const g1 = graph_detect({ * nodes: [{key: 'a'}], * edges: [{key: 'e', value: {source: 'a', target: 'b'}}] * }); * * // Incidence format * const g2 = graph_detect({ * incidences: [{key: 'a', value: {edges: ['b']}}] * }); * ``` */ function graph_detect(spec, opts) { if (spec.incidences) { return graph_incidence(spec.incidences, opts); } else if (spec.nodes) { return graph(spec.nodes, spec.edges, opts); } throw new Error('did not recognize graph format'); } export { graph, graph_detect, graph_incidence }; //# sourceMappingURL=graph.js.map