metagraph
Version:
A framework for building higher-order graph data structures
274 lines (269 loc) • 8.76 kB
JavaScript
/*!
* 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