bbop-graph
Advanced tools
Comparing version 0.0.10 to 0.0.11
349
lib/graph.js
@@ -44,12 +44,9 @@ /** | ||
this._is_a = 'bbop-graph.node'; | ||
this._id = new_id || undefined; | ||
this._label = new_label || undefined; | ||
this._id = new_id || null; | ||
this._label = new_label || null; | ||
// Only have a real type if the constructor went properly. | ||
this._type = 'node'; | ||
if( ! new_id ){ | ||
this._type = undefined; | ||
} | ||
this._metadata = undefined; | ||
this._metadata = null; | ||
} | ||
@@ -95,3 +92,6 @@ | ||
node.prototype.metadata = function(value){ | ||
if(value) this._metadata = value; return this._metadata; | ||
if( typeof(value) !== 'undefined' ){ | ||
this._metadata = value; | ||
} | ||
return this._metadata; | ||
}; | ||
@@ -106,7 +106,16 @@ | ||
node.prototype.clone = function(){ | ||
var tmp_clone = new node(this.id()); | ||
tmp_clone.type(this.type()); | ||
tmp_clone.label(this.label()); | ||
tmp_clone.metadata(bbop.clone(this.metadata())); | ||
return tmp_clone; | ||
// Base. | ||
var new_clone = new node(this.id()); | ||
// Type. | ||
new_clone.type(this.type()); | ||
// Label. | ||
new_clone.label(this.label()); | ||
// Meta. | ||
new_clone.metadata(bbop.clone(this.metadata())); | ||
return new_clone; | ||
}; | ||
@@ -133,2 +142,6 @@ | ||
if( ! subject || ! object ){ | ||
throw new Error('incomplete arguments for new edge'); | ||
} | ||
/** | ||
@@ -144,17 +157,19 @@ * The predicate we'll use when none other is defined. You can | ||
// Either a string or a node. | ||
if( ! subject ){ | ||
this._subject_id = undefined; | ||
}else if( typeof subject == 'string' ){ | ||
if( typeof(subject) == 'string' ){ | ||
this._subject_id = subject; | ||
}else if( subject.id && typeof(subject.id) === 'function' ){ | ||
this._subject_id = subject.id(); | ||
}else{ | ||
this._subject_id = subject.id(); | ||
throw new Error('cannot parse subject argument for edge'); | ||
} | ||
// Either a string or a node. | ||
if( ! object ){ | ||
this._object_id = undefined; | ||
}else if( typeof object == 'string' ){ | ||
if( typeof(object) == 'string' ){ | ||
this._object_id = object; | ||
}else if( object.id && typeof(object.id) === 'function' ){ | ||
this._object_id = object.id(); | ||
}else{ | ||
this._object_id = object.id(); | ||
throw new Error('cannot parse object argument for edge'); | ||
} | ||
// Predicate default or incoming. | ||
@@ -168,8 +183,5 @@ this._predicate_id = this.default_predicate; | ||
this._type = 'edge'; | ||
if( ! subject || ! object ){ | ||
this._type = undefined; | ||
} | ||
// | ||
this._metadata = undefined; | ||
this._metadata = null; | ||
} | ||
@@ -201,3 +213,4 @@ | ||
edge.prototype.predicate_id = function(){ | ||
return this._predicate_id; }; | ||
return this._predicate_id; | ||
}; | ||
@@ -211,3 +224,7 @@ /** | ||
edge.prototype.type = function(value){ | ||
if(value) this._type = value; return this._type; }; | ||
if( typeof(value) !== 'undefined' ){ | ||
this._type = value; | ||
} | ||
return this._type; | ||
}; | ||
@@ -223,3 +240,7 @@ /** | ||
edge.prototype.metadata = function(value){ | ||
if(value) this._metadata = value; return this._metadata; }; | ||
if( typeof(value) !== 'undefined' ){ | ||
this._metadata = value; | ||
} | ||
return this._metadata; | ||
}; | ||
@@ -233,8 +254,18 @@ /** | ||
edge.prototype.clone = function(){ | ||
var tmp_clone = new edge(this.subject_id(), | ||
// Base. | ||
var new_clone = new edge(this.subject_id(), | ||
this.object_id(), | ||
this.predicate_id()); | ||
// Metadata kind of needs to be duped separately. | ||
tmp_clone.metadata(bbop.clone(this.metadata())); | ||
return tmp_clone; | ||
// Predicate. | ||
new_clone.default_predicate = this.default_predicate; | ||
// Type. | ||
new_clone.type(this.type()); | ||
// Metadata. | ||
new_clone.metadata(bbop.clone(this.metadata())); | ||
return new_clone; | ||
}; | ||
@@ -266,3 +297,3 @@ | ||
this._id = undefined; | ||
this._id = null; | ||
@@ -296,2 +327,34 @@ // A per-graph logger. | ||
/** | ||
* 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 the graph id. | ||
@@ -888,3 +951,3 @@ * | ||
* @param {String} in_pred - (optional) over this predicate, defaults to all | ||
* @returns {} array of <edge> | ||
* @returns {Array} array of <edge> | ||
*/ | ||
@@ -925,2 +988,45 @@ graph.prototype.get_parent_edges = function(nb_id, in_pred){ | ||
/** | ||
* 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 = []; | ||
// Get all children, or just parents from a specific relation. | ||
var preds_to_use = []; | ||
if( in_pred ){ | ||
preds_to_use.push(in_pred); | ||
}else{ | ||
preds_to_use = keys(this._predicates); | ||
} | ||
// Try all of our desired predicates. | ||
each(preds_to_use, function(pred){ | ||
// Scan the table for goodies; there really shouldn't be a | ||
// lot here. | ||
if( anchor._os_table[ nb_id ] ){ | ||
each(keys(anchor._os_table[nb_id] ), function(sub_id){ | ||
// If it looks like something is there, try to see | ||
// if there is an edge for our current pred. | ||
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, | ||
@@ -945,3 +1051,3 @@ * use the default one. | ||
if( tmp_node ){ | ||
results.push( anchor.get_node(obj_id) ); | ||
results.push( tmp_node ); | ||
} | ||
@@ -965,32 +1071,13 @@ }); | ||
var results = []; | ||
// Get all children, or just children from a specific relation. | ||
var preds_to_use = []; | ||
if( in_pred ){ | ||
preds_to_use.push(in_pred); | ||
}else{ | ||
preds_to_use = keys(anchor._predicates); | ||
} | ||
// Try all of our desired predicates. | ||
each(preds_to_use, function(pred){ | ||
// Scan the table for goodies; there really shouldn't be a | ||
// lot here. | ||
if( anchor._os_table[ nb_id ] ){ | ||
each(keys(anchor._os_table[nb_id]), function(sub_id){ | ||
// If it looks like something is there, try to see | ||
// if there is an edge for our current pred. | ||
if( anchor.get_edge(sub_id, nb_id, pred) ){ | ||
// Make sure that any found edges are in our | ||
// world (not dangling). | ||
var tmp_node = anchor.get_node(sub_id); | ||
if( tmp_node ){ | ||
results.push( anchor.get_node(sub_id) ); | ||
} | ||
} | ||
}); | ||
var edges = this.get_child_edges(nb_id, in_pred); | ||
each(edges, function(edge){ | ||
// Make sure that any found edges are in our | ||
// world. | ||
var sub_id = edge.subject_id(); | ||
var tmp_node = anchor.get_node(sub_id); | ||
if( tmp_node ){ | ||
results.push( tmp_node ); | ||
} | ||
}); | ||
return results; | ||
@@ -1000,11 +1087,16 @@ }; | ||
/** | ||
* Return new ancestors subgraph. Single id or id list as first | ||
* argument. Predicate string/id as optional second. | ||
* Return a list with two nested lists, the first is a list of nodes, | ||
* the second is a list of edges. | ||
* | ||
* @param {string|list} nb_id_or_list - the node id(s) to consider | ||
* @param {string} pid - (optional) over this predicate | ||
* @returns {graph} new bbop model graph | ||
* 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.get_ancestor_subgraph = function(nb_id_or_list, pid){ | ||
graph.prototype.walker = function(walking_fun, nb_id_or_list, pid){ | ||
var anchor = this; | ||
// Shared data structure to trim multiple paths. | ||
@@ -1016,14 +1108,14 @@ // Nodes: color to get through the graph quickly and w/o cycles. | ||
var seen_edge_list = []; | ||
var anchor = this; | ||
// Define recursive ascent. | ||
function rec_up(nid){ | ||
function rec_walk(nid){ | ||
//print('rec_up on: ' + nid); | ||
//console.log('rec_walk on: ' + nid); | ||
var results = []; | ||
var new_parent_edges = anchor.get_parent_edges(nid, pid); | ||
//var new_parent_edges = anchor.get_parent_edges(nid, pid); | ||
var new_area_edges = walking_fun.call(anchor, nid, pid); | ||
// Capture edge list for later adding. | ||
each(new_parent_edges, function(e){ | ||
each(new_area_edges, function(e){ | ||
seen_edge_list.push(e); | ||
@@ -1035,4 +1127,4 @@ }); | ||
// get_parent_edges (as all this is now implemented). | ||
var new_parents = []; | ||
each(new_parent_edges, function(edge){ | ||
var new_area_nodes = []; | ||
each(new_area_edges, function(edge){ | ||
// Make sure that any found edges are in our world. | ||
@@ -1042,3 +1134,3 @@ var obj_id = edge.object_id(); | ||
if( temp_node ){ | ||
new_parents.push( temp_node ); | ||
new_area_nodes.push( temp_node ); | ||
} | ||
@@ -1050,16 +1142,14 @@ }); | ||
if( tmp_node ){ | ||
new_parents.push( tmp_node ); | ||
new_area_nodes.push( tmp_node ); | ||
} | ||
// Recur on unseen things and mark the current as seen. | ||
if( new_parents.length != 0 ){ | ||
each(new_parents, function(new_anc){ | ||
// Only do things we haven't ever seen before. | ||
var new_anc_id = new_anc.id(); | ||
if( ! seen_node_hash[ new_anc_id ] ){ | ||
seen_node_hash[ new_anc_id ] = new_anc; | ||
rec_up(new_anc_id); | ||
} | ||
}); | ||
} | ||
each(new_area_nodes, function(new_node){ | ||
// Only do things we haven't ever seen before. | ||
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); | ||
} | ||
}); | ||
@@ -1071,18 +1161,40 @@ return results; | ||
// ids possible. | ||
//if( nb_id_or_list.length && nb_id_or_list.index ){ | ||
if( us.isArray(nb_id_or_list) ){ // verify listy-ness | ||
if( us.isArray(nb_id_or_list) ){ | ||
each(nb_id_or_list, function(item){ | ||
rec_up(item); | ||
rec_walk(item); | ||
}); | ||
}else{ | ||
rec_up(nb_id_or_list); | ||
rec_walk(nb_id_or_list); | ||
} | ||
return [ | ||
us.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(anchor.get_parent_edges, nb_id_or_list, pid); | ||
var walked_nodes = walk_results[0]; | ||
var walked_edges = walk_results[1]; | ||
// Build new graph using data. | ||
var new_graph = new graph(); | ||
each(keys(seen_node_hash), function(k){ | ||
new_graph.add_node(seen_node_hash[k]); | ||
var new_graph = anchor.create_graph(); | ||
each(walked_nodes, function(node){ | ||
new_graph.add_node(node.clone()); | ||
}); | ||
each(seen_edge_list, function(edge){ | ||
new_graph.add_edge(edge); | ||
each(walked_edges, function(edge){ | ||
new_graph.add_edge(edge.clone()); | ||
}); | ||
@@ -1094,8 +1206,34 @@ | ||
/** | ||
* 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(anchor.get_child_edges, nb_id_or_list, pid); | ||
var walked_nodes = walk_results[0]; | ||
var walked_edges = walk_results[1]; | ||
// Build new graph using data. | ||
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; | ||
}; | ||
/** | ||
* Add a graph to the current graph, without sharing any of the merged | ||
* in graph's structure. | ||
* | ||
* TODO: a work in progress 'type' not currently imported (just as | ||
* not exported) | ||
* | ||
* @param {graph} - graph | ||
@@ -1127,8 +1265,11 @@ * @returns {boolean} - true; side-effects: more graph | ||
* TODO: a work in progress 'type' not currently imported (just as | ||
* not exported) | ||
* not exported); actually, a lot not imported. | ||
* | ||
* 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_json = function(json_object){ | ||
graph.prototype.load_base_json = function(json_object){ | ||
@@ -1142,3 +1283,3 @@ var anchor = this; | ||
var nlabel = node_raw.lbl; | ||
var n = new node(nid, nlabel); | ||
var n = anchor.create_node(nid, nlabel); | ||
if(node_raw.meta){ n.metadata(node_raw.meta); } | ||
@@ -1152,3 +1293,3 @@ anchor.add_node(n); | ||
each(json_object.edges, function(edge_raw){ | ||
var e = new edge(edge_raw.sub, edge_raw.obj, edge_raw.pred); | ||
var e = anchor.create_edge(edge_raw.sub, edge_raw.obj, edge_raw.pred); | ||
// Copy out meta. | ||
@@ -1155,0 +1296,0 @@ if(edge_raw.meta){ e.metadata(edge_raw.meta); } |
{ | ||
"name": "bbop-graph", | ||
"version": "0.0.10", | ||
"version": "0.0.11", | ||
"license": "BSD-3-Clause", | ||
@@ -5,0 +5,0 @@ "description": "General purpose (mathematical) graph library in JavaScript.", |
@@ -5,3 +5,5 @@ //// | ||
var assert = require('chai').assert; | ||
var chai = require('chai'); | ||
chai.config.includeStack = true; | ||
assert = chai.assert; | ||
var model = new require('..'); | ||
@@ -251,3 +253,3 @@ | ||
g1 = new model.graph(); | ||
g1.load_json(jo); | ||
g1.load_base_json(jo); | ||
@@ -260,3 +262,3 @@ // A bit of GO. | ||
g2 = new model.graph(); | ||
g2.load_json(go); | ||
g2.load_base_json(go); | ||
}); | ||
@@ -332,3 +334,3 @@ | ||
var g = new model.graph(); | ||
var result2 = g.load_json(tax); | ||
var result2 = g.load_base_json(tax); | ||
@@ -358,3 +360,3 @@ assert.equal(g.all_dangling(), 0, 'nothing dangling'); | ||
var g = new model.graph(); | ||
var l = g.load_json(simp); | ||
var l = g.load_base_json(simp); | ||
var r = g.to_json(); | ||
@@ -550,1 +552,22 @@ assert.deepEqual(simp, r, 'round trip'); | ||
}); | ||
describe('clone wars', function(){ | ||
it('edge clones are perfect', function(){ | ||
var e = new model.edge('a', 'b', 'is_a'); | ||
e.type('foo'); | ||
e.metadata({'a': 1}); | ||
assert.deepEqual(e.clone(), e, 'clone is dupe'); | ||
}); | ||
it('node clones are perfect', function(){ | ||
var n = new model.edge('a', 'b'); | ||
n.type('foo'); | ||
n.metadata({'a': 1}); | ||
assert.deepEqual(n.clone(), n, 'clone is dupe'); | ||
}); | ||
}); |
6283132
25669