UNPKG

bbop-graph

Version:

General purpose (mathematical) graph library in JavaScript.

1,082 lines (1,081 loc) 34.8 kB
//#region \0rolldown/runtime.js var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { key = keys[i]; if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: ((k) => from[k]).bind(null, key), enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod)); //#endregion let underscore = require("underscore"); underscore = __toESM(underscore, 1); let bbop_core = require("bbop-core"); bbop_core = __toESM(bbop_core, 1); //#region src/graph.js /** * Purpose: Basic edged graph and operations. * * NOTE: A model instance may not be the whole graph, just a * subgraph--this is the difference between nodes and * named_nodes. nodes are real things, while named_nodes are things * referenced by edges. * * Check TODOs, we would like everything as linear as possible. * * TODO: memoize everything but add_*. Functional enough that it * should work if we just empty the caches after every add_* op. * * @module bbop-graph */ var each = underscore.default.each; var keys = underscore.default.keys; var default_predicate = "points_at"; /** * Contructor for a BBOP graph model node. * * @constructor * @param {string} new_id - a unique id for the node * @param {string} new_label - (optional) a user-friendly description of the node * @returns {this} new bbop model node */ function node(new_id, new_label) { this._is_a = "bbop-graph.node"; this._id = new_id || null; this._label = new_label || null; this._type = "node"; this._metadata = null; } /** * Getter/setter for node id. * * @param {string} value - (optional) new value for this property to take * @returns {string} string */ node.prototype.id = function(value) { if (value) this._id = value; return this._id; }; /** * Getter/setter for node type. * * @param {string} value - (optional) new value for this property to take * @returns {string} string */ node.prototype.type = function(value) { if (value) this._type = value; return this._type; }; /** * Getter/setter for node label. * * @param {string} value - (optional) new value for this property to take * @returns {string} string */ node.prototype.label = function(value) { if (value) this._label = value; return this._label; }; /** * Getter/setter for node metadata. * * The metadata value does not necessarily have to be an atomic type. * * @param {Object} value - (optional) new value for this property to take; only objects (not Arrays) * @returns {Object|null} value */ node.prototype.metadata = function(value) { if (underscore.default.isObject(value) && !underscore.default.isArray(value)) this._metadata = value; return this._metadata; }; /** * Get a fresh new copy of the current node (using bbop.clone for * metadata object). This includes copying the ID--you'd have to * change it on your own. * * @returns {node} node */ node.prototype.clone = function() { var new_clone = new node(); new_clone.id(this.id()); new_clone.label(this.label()); new_clone.type(this.type()); new_clone.metadata(bbop_core.default.clone(this.metadata())); return new_clone; }; /** * Contructor for a BBOP graph model edge. * * If no predicate is given, <default_predicate> is used. * Predicates are currently treated as raw strings. * * Note that these edges have no ID associated with them. * * @constructor * @param {string} subject - node id string or node * @param {string} object - node id string or node * @param {string} predicate - (optional) a user-friendly description of the node * @returns {edge} bbop model edge */ function edge(subject, object, predicate) { this._is_a = "bbop-graph.edge"; if (!subject || !object) throw new Error("incomplete arguments for new edge"); /** * The predicate we'll use when none other is defined. You can * probably safely ignore this if all of the edges in your graph are * the same. * * @variable */ this.default_predicate = default_predicate; if (typeof subject === "string") this._subject_id = subject; else if (subject.id && typeof subject.id === "function") this._subject_id = subject.id(); else throw new Error("cannot parse subject argument for edge"); if (typeof object === "string") this._object_id = object; else if (object.id && typeof object.id === "function") this._object_id = object.id(); else throw new Error("cannot parse object argument for edge"); this._predicate_id = this.default_predicate; if (predicate) this._predicate_id = predicate; this._type = "edge"; this._metadata = null; } /** * Getter/setter for edge subject id. * * @returns {string} string */ edge.prototype.subject_id = function() { return this._subject_id; }; /** * Getter/setter for edge object id. * * @returns {string} string */ edge.prototype.object_id = function() { return this._object_id; }; /** * Getter/setter for edge predicate id. * * @returns {string} string */ edge.prototype.predicate_id = function() { return this._predicate_id; }; /** * Getter/setter for edge type. * * @param {String} value - (optional) new value for this property to take * @returns {String} string */ edge.prototype.type = function(value) { if (typeof value !== "undefined") this._type = value; return this._type; }; /** * Getter/setter for edge metadata. * * The metadata value does not necessarily have to be an atomic type. * * @param {Object} value - (optional) new value for this property to take; only objects (not Arrays) * @returns {Object|null} value */ edge.prototype.metadata = function(value) { if (underscore.default.isObject(value) && !underscore.default.isArray(value)) this._metadata = value; return this._metadata; }; /** * Get a fresh new copy of the current edge (using bbop.clone for * metadata object). * * @returns {edge} - new copy of edge */ edge.prototype.clone = function() { var new_clone = new edge(this.subject_id(), this.object_id(), this.predicate_id()); new_clone.default_predicate = this.default_predicate; new_clone.type(this.type()); new_clone.metadata(bbop_core.default.clone(this.metadata())); return new_clone; }; /** * Contructor for a BBOP graph model graph. * * TODO: make compilation piecewise with every added node and edge. * * @constructor * @returns {graph} bbop model graph */ function graph() { this._is_a = "bbop-graph.graph"; /** * The predicate we'll use when none other is defined. You can * probably safely ignore this if all of the edges in your graph are * the same. * * @variable */ this.default_predicate = default_predicate; this._id = null; this._metadata = null; this._logger = new bbop_core.default.logger(this._is_a); this._logger.DEBUG = true; this._nodes = {}; this._edge_list = []; this._predicates = {}; this._subjects = {}; this._objects = {}; this._so_table = {}; this._os_table = {}; this._sop_table = {}; this._is_a_singleton_lookup = {}; } /** * Create an edge for use in internal operations. * * @param {string} subject - node id string or node * @param {string} object - node id string or node * @param {string} predicate - (optional) a user-friendly description of the node * @returns {edge} bbop model edge */ graph.prototype.create_edge = function(subject, object, predicate) { return new edge(subject, object, predicate); }; /** * Create a node for use in internal operations. * * @param {string} new_id - a unique id for the node * @param {string} new_label - (optional) a user-friendly description of the node * @returns {node} new bbop model node */ graph.prototype.create_node = function(new_id, new_label) { return new node(new_id, new_label); }; /** * Create a graph for use in internal operations. * * @returns {graph} bbop model graph */ graph.prototype.create_graph = function() { return new graph(); }; /** * Getter/setter for graph metadata. * * The metadata value does not necessarily have to be an atomic type. * * @param {Object} value - (optional) new value for this property to take; only objects (not Arrays) * @returns {Object|null} value */ graph.prototype.metadata = function(value) { if (underscore.default.isObject(value) && !underscore.default.isArray(value)) this._metadata = value; return this._metadata; }; /** * Create a clone of the graph. * * @returns {graph} bbop model graph */ graph.prototype.clone = function() { var anchor = this; var new_graph = anchor.create_graph(); each(anchor.all_nodes(), function(node) { new_graph.add_node(node.clone()); }); each(anchor.all_edges(), function(edge) { new_graph.add_edge(edge.clone()); }); new_graph.default_predicate = anchor.default_predicate; new_graph._id = anchor._id; new_graph.metadata(bbop_core.default.clone(this.metadata())); return new_graph; }; /** * Getter/setter for the graph id. * * @param {string} value - (optional) new value for this property to take * @returns {string} string */ graph.prototype.id = function(value) { if (value) this._id = value; return this._id; }; /** * See if the graph self-reports as incomplete. * Should be true, unless metadata has * * @returns {Boolean} whether or not all nodes/edges are represented */ graph.prototype.incomplete_p = function() { var ret = false; if (this._metadata) { if (typeof this._metadata["incomplete-p"] !== "undefined") { var inc = this._metadata["incomplete-p"]; if (inc === true || inc === "true") ret = true; } } return ret; }; /** * Return the number of self-reported nodes. * Should be nodes.length, unless incomplete_p, in which case it will * search for a count in the metadata. * * @returns {Number} number of nodes represented */ graph.prototype.complete_node_count = function() { var ret = null; if (this.incomplete_p()) { if (typeof this._metadata["complete-node-count"] !== "undefined") { var cnt = this._metadata["complete-node-count"]; if (underscore.default.isNumber(cnt) || underscore.default.isString(cnt)) ret = parseInt(cnt); } } if (ret === null) ret = this.all_nodes().length; return ret; }; /** * Return the number of self-reported edges. * Should be edges.length, unless incomplete_p, in which case it will * search for a count in the metadata. * * @returns {Number} number of edges represented */ graph.prototype.complete_edge_count = function() { var ret = null; if (this.incomplete_p()) { if (typeof this._metadata["complete-edge-count"] !== "undefined") { var cnt = this._metadata["complete-edge-count"]; if (underscore.default.isNumber(cnt) || underscore.default.isString(cnt)) ret = parseInt(cnt); } } if (ret === null) ret = this.all_edges().length; return ret; }; /** * Add a node to the graph. * * @param {node} node - node to add to the graph */ graph.prototype.add_node = function(node) { if (!node.id()) throw new Error("no anonymous nodes: " + node.id()); else { var nid = node.id(); this._nodes[nid] = node; if (!this._subjects[nid] && !this._objects[nid]) this._is_a_singleton_lookup[nid] = true; } }; /** * Remove a node from the graph. * * @param {String} node_id - the id for a node * @param {Boolean} clean_p - (optional) remove all edges connects to node (default false) * @returns {Boolean} true if node found and destroyed */ graph.prototype.remove_node = function(node_id, clean_p) { var anchor = this; var ret = false; if (anchor.get_node(node_id)) { ret = true; delete anchor._nodes[node_id]; delete anchor._is_a_singleton_lookup[node_id]; if (clean_p) { var edge_pairs = []; if (anchor._so_table[node_id]) each(keys(anchor._so_table[node_id]), function(obj_id) { edge_pairs.push([node_id, obj_id]); }); if (anchor._os_table[node_id]) each(keys(anchor._os_table[node_id]), function(sub_id) { edge_pairs.push([sub_id, node_id]); }); each(edge_pairs, function(pair) { each(anchor.get_edges(pair[0], pair[1]), function(edge) { anchor.remove_edge(edge.subject_id(), edge.object_id(), edge.predicate_id()); }); }); } } return ret; }; /** * Add an edge to the graph. * * @param {edge} edge - edge to add to the graph */ graph.prototype.add_edge = function(edge) { var sub_id = edge.subject_id(); var obj_id = edge.object_id(); var pred_id = edge.predicate_id(); if (this.remove_edge(sub_id, obj_id, pred_id)) {} if (!this._so_table[sub_id]) this._so_table[sub_id] = {}; if (typeof this._so_table[sub_id][obj_id] === "undefined") this._so_table[sub_id][obj_id] = 1; else this._so_table[sub_id][obj_id]++; if (!this._os_table[obj_id]) this._os_table[obj_id] = {}; if (typeof this._os_table[obj_id][sub_id] === "undefined") this._os_table[obj_id][sub_id] = 1; else this._os_table[obj_id][sub_id]++; if (!this._sop_table[sub_id]) this._sop_table[sub_id] = {}; if (!this._sop_table[sub_id][obj_id]) this._sop_table[sub_id][obj_id] = {}; this._sop_table[sub_id][obj_id][pred_id] = edge; if (!this._predicates[pred_id]) this._predicates[pred_id] = 1; else this._predicates[pred_id]++; if (!this._subjects[sub_id]) this._subjects[sub_id] = 1; else this._subjects[sub_id]++; if (!this._objects[obj_id]) this._objects[obj_id] = 1; else this._objects[obj_id]++; if (this._is_a_singleton_lookup[sub_id]) delete this._is_a_singleton_lookup[sub_id]; if (this._is_a_singleton_lookup[obj_id]) delete this._is_a_singleton_lookup[obj_id]; this._edge_list.push(edge); }; /** * Remove an edge to the graph. * The edge as referenced. * * @param {String} subject_id - subject by ID * @param {String} object_id - object by ID * @param {String} predicate_id - (Optional) predicate ID or default * @returns {Boolean} true if such an edge was found and deleted, false otherwise */ graph.prototype.remove_edge = function(subject_id, object_id, predicate_id) { if (!predicate_id) predicate_id = this.default_predicate; var ret = false; if (this.get_edge(subject_id, object_id, predicate_id)) { ret = true; if (this._subjects[subject_id] === 1) delete this._subjects[subject_id]; else this._subjects[subject_id]--; if (this._objects[object_id] === 1) delete this._objects[object_id]; else this._objects[object_id]--; if (this._predicates[predicate_id] === 1) delete this._predicates[predicate_id]; else this._predicates[predicate_id]--; delete this._sop_table[subject_id][object_id][predicate_id]; this._edge_list = underscore.default.reject(this._edge_list, function(edge) { var ret = false; if (edge.subject_id() === subject_id && edge.object_id() === object_id && edge.predicate_id() === predicate_id) ret = true; return ret; }); if (this._so_table[subject_id][object_id] === 1) delete this._so_table[subject_id][object_id]; else this._so_table[subject_id][object_id]--; if (this._os_table[object_id][subject_id] === 1) delete this._os_table[object_id][subject_id]; else this._os_table[object_id][subject_id]--; if (this._nodes[subject_id] && !this._subjects[subject_id] && !this._objects[subject_id]) this._is_a_singleton_lookup[subject_id] = true; if (this._nodes[object_id] && !this._subjects[object_id] && !this._objects[object_id]) this._is_a_singleton_lookup[object_id] = true; } return ret; }; /** * Returns an /original/ list of all added nodes. * * @returns {Array} array of {node} */ graph.prototype.all_nodes = function() { return underscore.default.values(this._nodes); }; /** * Returns an /original/ list of all added edges. * * @returns {Array} array of {edge} */ graph.prototype.all_edges = function() { return this._edge_list; }; /** * Returns an /original/ list of all added predicates. * * @returns {Array} array of predicates (strings) */ graph.prototype.all_predicates = function() { return keys(this._predicates); }; /** * List all external nodes by referenced id. * * @returns {Array} array of strings: external nodes by id */ graph.prototype.all_dangling = function() { var anchor = this; var named_nodes = keys(this._subjects).concat(keys(this._objects)); var unnamed = []; each(named_nodes, function(named_id) { if (!anchor._nodes[named_id]) unnamed.push(named_id); }); return unnamed; }; /** * Any bad parts in graph? Essentially, make sure that there are no * weird references and nothing is dangling. * * @returns {Boolean} boolean */ graph.prototype.is_complete = function() { var retval = true; if (this.all_dangling().length > 0) retval = false; return retval; }; /** * Return a /copy/ of a node by id (not the original) if extant. * * @param {string} nid - the id of the node we're looking for * @returns {node} - copy of bbop model node */ graph.prototype.get_node = function(nid) { var retnode = null; if (this._nodes[nid]) retnode = this._nodes[nid].clone(); return retnode; }; /** * Return a /copy/ of an edge by ids (not the original) if extant. * * @param {string} sub_id - the subject_id of the edge we're looking for * @param {string} obj_id - the object_id of the edge we're looking for * @param {string} pred - (optional) the predicate of the edge we're looking for * * @returns {edge} - copy of bbop model edge */ graph.prototype.get_edge = function(sub_id, obj_id, pred) { if (!pred) pred = this.default_predicate; var ret_edge = null; if (this._sop_table[sub_id] && this._sop_table[sub_id][obj_id] && this._sop_table[sub_id][obj_id][pred]) ret_edge = this._sop_table[sub_id][obj_id][pred].clone(); return ret_edge; }; /** * Return all edges (copies) of given subject and object ids. Returns * entirely new edges. * * @param {String} sub_id - the subject_id of the edge we're looking for * @param {String} obj_id - the object_id of the edge we're looking for * @returns {Array} list of <edge> */ graph.prototype.get_edges = function(sub_id, obj_id) { var anchor = this; var retlist = []; if (anchor._sop_table[sub_id] && anchor._sop_table[sub_id][obj_id]) each(keys(anchor._sop_table[sub_id][obj_id]), function(pred) { var tmp_edge = anchor._sop_table[sub_id][obj_id][pred].clone(); retlist.push(tmp_edge); }); return retlist; }; /** * Return all edges (copies) of given subject id. Returns entirely new * edges. * * @param {String} sub_id - the subject_id of the edge we're looking for * @returns {Array} list of <edge> */ graph.prototype.get_edges_by_subject = function(sub_id) { var anchor = this; var retlist = []; if (anchor._so_table[sub_id]) each(keys(anchor._so_table[sub_id]), function(obj_id) { retlist = retlist.concat(anchor.get_edges(sub_id, obj_id)); }); return retlist; }; /** * Return all edges (copies) of given object id. Returns entirely new * edges. * * @param {String} obj_id - the object_id of the edge we're looking for * @returns {Array} list of <edge> */ graph.prototype.get_edges_by_object = function(obj_id) { var anchor = this; var retlist = []; if (anchor._os_table[obj_id]) each(keys(anchor._os_table[obj_id]), function(sub_id) { retlist = retlist.concat(anchor.get_edges(sub_id, obj_id)); }); return retlist; }; /** * Return all predicates of given subject and object ids. * * @param {String} sub_id - the subject_id of the edge we're looking for * @param {String} obj_id - the object_id of the edge we're looking for * @returns {Array} list of predicate ids (as strings) */ graph.prototype.get_predicates = function(sub_id, obj_id) { var anchor = this; var retlist = []; if (anchor._sop_table[sub_id] && anchor._sop_table[sub_id][obj_id]) each(keys(anchor._sop_table[sub_id][obj_id]), function(pred) { retlist.push(pred); }); return retlist; }; /** * Translate an edge array into extant (node) bodies, switching on * either 'subject' or 'object'. * * This will return the /original/ nodes. * * This will throw an error on any world issues that crop up. * * @param {Array} in_edges - list if {edge} we want the subjects or objects of * @param {String} target - 'subject' or 'object' * @returns {Array} list of {node} */ graph.prototype.edges_to_nodes = function(in_edges, target) { var anchor = this; if (target !== "subject" && target !== "object") throw new Error("Bad target for edges to bodies."); var results = []; each(in_edges, function(in_e) { var target_id = null; if (target === "subject") target_id = in_e.subject_id(); else target_id = in_e.object_id(); if (target_id && anchor._nodes[target_id]) results.push(anchor._nodes[target_id]); else throw new Error(target + " world issue"); }); return results; }; /** * Roots are defined as nodes who are the subject of nothing, * independent of predicate. * * @param {string} nb_id - id of the node to check * @returns {boolean} - boolean */ graph.prototype.is_root_node = function(nb_id) { var result = false; if (this._nodes[nb_id] && !this._subjects[nb_id]) result = true; return result; }; /** * Return a list of /copies/ of the root nodes. * * BUG/TODO: Could I speed this up by my moving some of the * calculation into the add_node and add_edge methods? O(|num(nodes)|) * * @returns {Array} list of {node} */ graph.prototype.get_root_nodes = function() { var anchor = this; var results = []; each(keys(anchor._nodes), function(nb_id) { if (anchor.is_root_node(nb_id)) results.push(anchor.get_node(nb_id).clone()); }); return results; }; /** * Leaves are defined as nodes who are the object of nothing, * independent of predicate. * * @param {string} nb_id - id of the node to check * @returns {boolean} - boolean */ graph.prototype.is_leaf_node = function(nb_id) { var result = false; if (this._nodes[nb_id] && !this._objects[nb_id]) result = true; return result; }; /** * Return a list of /copies/ of the leaf nodes. * * BUG/TODO: Could I speed this up by my moving some of the * calculation into the add_node and add_edge methods? O(|num(nodes)|) * * @returns {Array} list of {node} */ graph.prototype.get_leaf_nodes = function() { var anchor = this; var results = []; each(keys(anchor._nodes), function(nb_id) { if (anchor.is_leaf_node(nb_id)) results.push(anchor.get_node(nb_id).clone()); }); return results; }; /** * Find nodes that are roots and leaves over all relations. This * returns the /original/ node. * * Throws an error if there is a world issue. * * @returns {Array} array of {node} */ graph.prototype.get_singleton_nodes = function() { var anchor = this; var singleton_array = []; each(keys(anchor._is_a_singleton_lookup), function(singleton_id) { if (anchor._nodes[singleton_id]) singleton_array.push(anchor._nodes[singleton_id]); else throw new Error("world issue in get_singletons: " + singleton_id); }); return singleton_array; }; /** * Return all parent edges; the /originals/. If no predicate is given, * use the default one. * * TODO: it might be nice to memoize this since others depend on it. * * @param {String} nb_id - the node to consider * @param {String} in_pred - (optional) over this predicate, defaults to all * @returns {Array} array of <edge> */ graph.prototype.get_parent_edges = function(nb_id, in_pred) { var anchor = this; var results = []; var preds_to_use = []; if (in_pred) preds_to_use.push(in_pred); else preds_to_use = anchor.all_predicates(); each(preds_to_use, function(pred) { if (anchor._so_table[nb_id]) each(keys(anchor._so_table[nb_id]), function(obj_id) { var tmp_edge = anchor.get_edge(nb_id, obj_id, pred); if (tmp_edge) results.push(tmp_edge); }); }); return results; }; /** * Return all child edges; the /originals/. If no predicate is given, * use the default one. * * TODO: it might be nice to memoize this since others depend on it. * * @param {String} nb_id - the node to consider * @param {String} in_pred - (optional) over this predicate, defaults to all * @returns {Array} array of <edge> */ graph.prototype.get_child_edges = function(nb_id, in_pred) { var anchor = this; var results = []; var preds_to_use = []; if (in_pred) preds_to_use.push(in_pred); else preds_to_use = anchor.all_predicates(); each(preds_to_use, function(pred) { if (anchor._os_table[nb_id]) each(keys(anchor._os_table[nb_id]), function(sub_id) { var tmp_edge = anchor.get_edge(sub_id, nb_id, pred); if (tmp_edge) results.push(tmp_edge); }); }); return results; }; /** * Return all parent nodes; the /originals/. If no predicate is given, * use the default one. * * @param {String} nb_id - the node to consider * @param {String} in_pred - (optional) over this predicate, defaults to all * * @returns {Array} list of {node} */ graph.prototype.get_parent_nodes = function(nb_id, in_pred) { var anchor = this; var results = []; each(this.get_parent_edges(nb_id, in_pred), function(edge) { var obj_id = edge.object_id(); var tmp_node = anchor.get_node(obj_id); if (tmp_node) results.push(tmp_node); }); return results; }; /** * Return all child nodes; the /originals/. If no predicate is given, * use the default one. * * @param {String} nb_id - the node to consider * @param {String} in_pred - (optional) over this predicate, defaults to all * @returns {Array} list of {node} */ graph.prototype.get_child_nodes = function(nb_id, in_pred) { var anchor = this; var results = []; each(this.get_child_edges(nb_id, in_pred), function(edge) { var sub_id = edge.subject_id(); var tmp_node = anchor.get_node(sub_id); if (tmp_node) results.push(tmp_node); }); return results; }; /** * Walk up a graph. Subject to object. * * Return a list with two nested lists, the first is a list of nodes, * the second is a list of edges. * * The argument function takes a node id and 0 or 1 predicates, * returns a list of edges from the node in question. * * @param {Function} walking_fun - function as described above * @param {String|Array} nb_id_or_list - the node id(s) to consider * @param {String} pid - (optional) over this predicate * @returns {Array} as described above */ graph.prototype.walker_up = function(walking_fun, nb_id_or_list, pid) { var anchor = this; var seen_node_hash = {}; var seen_edge_list = []; function rec_walk(nid) { var results = []; var new_area_edges = walking_fun.call(anchor, nid, pid); each(new_area_edges, function(e) { seen_edge_list.push(e); }); var new_area_nodes = []; each(new_area_edges, function(edge) { var obj_id = edge.object_id(); var temp_node = anchor.get_node(obj_id); if (temp_node) new_area_nodes.push(temp_node); }); var tmp_node = anchor.get_node(nid); if (tmp_node) new_area_nodes.push(tmp_node); each(new_area_nodes, function(new_node) { var new_node_id = new_node.id(); if (!seen_node_hash[new_node_id]) { seen_node_hash[new_node_id] = new_node; rec_walk(new_node_id); } }); return results; } if (underscore.default.isArray(nb_id_or_list)) each(nb_id_or_list, function(item) { rec_walk(item); }); else rec_walk(nb_id_or_list); return [underscore.default.values(seen_node_hash), seen_edge_list]; }; /** * Function-preserving alias for walker_up. * * @param {Function} walking_fun - function as described above * @param {String|Array} nb_id_or_list - the node id(s) to consider * @param {String} pid - (optional) over this predicate * @returns {Array} as described above */ graph.prototype.walker = graph.prototype.walker_up; /** * Walk down a graph. Subject to object. * * Return a list with two nested lists, the first is a list of nodes, * the second is a list of edges. * * The argument function takes a node id and 0 or 1 predicates, * returns a list of edges from the node in question. * * @param {Function} walking_fun - function as described above * @param {String|Array} nb_id_or_list - the node id(s) to consider * @param {String} pid - (optional) over this predicate * @returns {Array} as described above */ graph.prototype.walker_down = function(walking_fun, nb_id_or_list, pid) { var anchor = this; var seen_node_hash = {}; var seen_edge_list = []; function rec_walk(nid) { var results = []; var new_area_edges = walking_fun.call(anchor, nid, pid); each(new_area_edges, function(e) { seen_edge_list.push(e); }); var new_area_nodes = []; each(new_area_edges, function(edge) { var sub_id = edge.subject_id(); var temp_node = anchor.get_node(sub_id); if (temp_node) new_area_nodes.push(temp_node); }); var tmp_node = anchor.get_node(nid); if (tmp_node) new_area_nodes.push(tmp_node); each(new_area_nodes, function(new_node) { var new_node_id = new_node.id(); if (!seen_node_hash[new_node_id]) { seen_node_hash[new_node_id] = new_node; rec_walk(new_node_id); } }); return results; } if (underscore.default.isArray(nb_id_or_list)) each(nb_id_or_list, function(item) { rec_walk(item); }); else rec_walk(nb_id_or_list); return [underscore.default.values(seen_node_hash), seen_edge_list]; }; /** * Return new ancestors subgraph. Single id or id list as first * argument. Predicate string/id is optional. * * @param {String|Array} nb_id_or_list - the node id(s) to consider * @param {String} pid - (optional) over this predicate * @returns {graph} new bbop model graph */ graph.prototype.get_ancestor_subgraph = function(nb_id_or_list, pid) { var anchor = this; var walk_results = anchor.walker_up(anchor.get_parent_edges, nb_id_or_list, pid); var walked_nodes = walk_results[0]; var walked_edges = walk_results[1]; var new_graph = anchor.create_graph(); each(walked_nodes, function(node) { new_graph.add_node(node.clone()); }); each(walked_edges, function(edge) { new_graph.add_edge(edge.clone()); }); return new_graph; }; /** * Return new descendents subgraph. Single id or id list as first * argument. Predicate string/id is optional. * * @param {String|Array} nb_id_or_list - the node id(s) to consider * @param {String} pid - (optional) over this predicate * @returns {graph} new bbop model graph */ graph.prototype.get_descendent_subgraph = function(nb_id_or_list, pid) { var anchor = this; var walk_results = anchor.walker_down(anchor.get_child_edges, nb_id_or_list, pid); var walked_nodes = walk_results[0]; var walked_edges = walk_results[1]; var new_graph = anchor.create_graph(); each(walked_nodes, function(node) { new_graph.add_node(node.clone()); }); each(walked_edges, function(edge) { new_graph.add_edge(edge.clone()); }); return new_graph; }; /** * True or false on whether or not a graph shares the same structure * as the current graph. This means that the (top-level) nodes have * the same IDs and every edge connects in the same way. * * This does not compare things like meta information, etc. * * BUG/TODO: This should probably be moved to the superclass. Would * have an easier time optimizing in there too. * * @param {graph} comp_graph graph to compare against * @returns {Boolean} well is it? */ graph.prototype.is_topologically_equal = function(comp_graph) { var anchor = this; var ret = false; var base_nodes = anchor.all_nodes(); var base_edges = anchor.all_edges(); var comp_nodes = comp_graph.all_nodes(); var comp_edges = comp_graph.all_edges(); if (base_nodes.length === comp_nodes.length && base_edges.length === comp_edges.length) { each(base_edges, function(base_edge) { if (!comp_graph.get_edge(base_edge.subject_id(), base_edge.object_id(), base_edge.predicate_id())) return false; }); each(base_nodes, function(base_node) { var base_node_id = base_node.id(); if (!comp_graph.get_node(base_node_id)) return false; }); ret = true; } return ret; }; /** * Add a graph to the current graph, without sharing any of the merged * in graph's structure. * * No graph metadata is imported unless current graph and metadata are * empty. * * @param {graph} - graph * @returns {boolean} - true; side-effects: more graph */ graph.prototype.merge_in = function(in_graph) { var anchor = this; if (anchor.all_nodes().length === 0 && anchor.all_edges().length === 0) { if (anchor.metadata() === null || underscore.default.isEmpty(anchor.metadata())) anchor.metadata(underscore.default.clone(in_graph.metadata())); } each(in_graph.all_nodes(), function(in_node) { var new_node = in_node.clone(); anchor.add_node(new_node); }); each(in_graph.all_edges(), function(in_edge) { var new_edge = in_edge.clone(); anchor.add_edge(new_edge); }); return true; }; /** * Load the graph from the specified JSON object (not string). * * TODO: a work in progress 'type' not currently imported (just as not * exported); actually, a lot not imported. * * No graph metadata is imported unless current graph and metadata are * empty. * * This is meant to be an minimal importer for a minimal * format. Subclasses should use something else. * * @param {object} - JSON object * @returns {boolean} - true; side-effects: creates the graph internally */ graph.prototype.load_base_json = function(json_object) { var anchor = this; if (anchor.all_nodes().length === 0 && anchor.all_edges().length === 0) { if (anchor.metadata() === null || underscore.default.isEmpty(anchor.metadata())) { if (json_object.meta) anchor.metadata(underscore.default.clone(json_object.meta)); } } if (json_object.nodes) each(json_object.nodes, function(node_raw) { var nid = node_raw.id; var nlabel = node_raw.lbl; var n = anchor.create_node(nid, nlabel); if (node_raw.meta) n.metadata(node_raw.meta); anchor.add_node(n); }); if (json_object.edges) each(json_object.edges, function(edge_raw) { var e = anchor.create_edge(edge_raw.sub, edge_raw.obj, edge_raw.pred); if (edge_raw.meta) e.metadata(edge_raw.meta); anchor.add_edge(e); }); return true; }; /** * Dump out the graph into a JSON-able object. * * TODO: a work in progress; 'type' not currently exported (just as * not imported). Graph metadata is exported. * * @returns {object} - an object that can be converted to a JSON string by dumping. */ graph.prototype.to_json = function() { var anchor = this; var nset = []; each(anchor.all_nodes(), function(raw_node) { var node = bbop_core.default.clone(raw_node); var ncopy = {}; var nid = node.id(); if (nid) ncopy["id"] = nid; var nlabel = node.label(); if (nlabel) ncopy["lbl"] = nlabel; var nmeta = node.metadata(); if (nmeta) ncopy["meta"] = nmeta; nset.push(ncopy); }); var eset = []; each(anchor.all_edges(), function(node) { var ecopy = {}; var s = node.subject_id(); if (s) ecopy["sub"] = s; var o = node.object_id(); if (o) ecopy["obj"] = o; var p = node.predicate_id(); if (p) ecopy["pred"] = p; eset.push(ecopy); }); var ret_obj = { nodes: nset, edges: eset }; if (anchor.metadata()) ret_obj["meta"] = underscore.default.clone(anchor.metadata()); return ret_obj; }; var graph_default = { node, edge, graph }; //#endregion module.exports = graph_default;