graphinius
Version:
Generic graph library in Typescript
237 lines (190 loc) • 6.36 kB
text/typescript
import * as fs from 'fs';
import {IBaseEdge} from '../../core/base/BaseEdge';
import {IBaseNode} from "../../core/base/BaseNode";
import {ITypedNode} from "../../core/typed/TypedNode";
import {TypedGraph} from "../../core/typed/TypedGraph";
import {IGraph, BaseGraph} from '../../core/base/BaseGraph';
import * as $R from '../../utils/RemoteUtils';
import {labelKeys} from '../interfaces';
import {EdgeDupeChecker, PotentialEdgeInfo} from '../common/Dupes';
import * as uuid from 'uuid'
const v4 = uuid.v4;
import {Logger} from '../../utils/Logger';
const logger = new Logger();
const DEFAULT_WEIGHT: number = 1;
export interface JSONEdge {
to: string;
directed?: string;
weight?: string;
type?: string;
}
export interface JSONNode {
edges: Array<JSONEdge>;
coords?: { [key: string]: Number };
features?: { [key: string]: any };
}
export interface JSONGraph {
typeRLT: {nodes: {}, edges: {}};
name: string;
nodes: number;
edges: number;
data: { [key: string]: JSONNode };
}
export interface IJSONInConfig {
explicit_direction? : boolean;
directed? : boolean;
weighted? : boolean;
typed? : boolean;
dupeCheck? : boolean;
}
export interface IJSONInput {
_config: IJSONInConfig;
readFromJSONFile(file: string, graph?: IGraph): IGraph;
readFromJSON(json: {}, graph?: IGraph): IGraph;
readFromJSONURL(config: $R.RequestConfig, cb: Function, graph?: IGraph): void;
}
class JSONInput implements IJSONInput {
_config: IJSONInConfig;
constructor(config: IJSONInConfig = {}) {
this._config = {
explicit_direction: config.explicit_direction != null ? config.explicit_direction : true,
directed: config.directed != null ? config.directed : false,
weighted: config.weighted != null ? config.weighted : false,
dupeCheck: config.dupeCheck != null ? config.dupeCheck : true
};
}
readFromJSONFile(filepath: string, graph?: IGraph): IGraph {
$R.checkNodeEnvironment();
// TODO test for existing file...
let json = JSON.parse(fs.readFileSync(filepath).toString());
return this.readFromJSON(json, graph);
}
readFromJSONURL(config: $R.RequestConfig, cb: Function, graph?: IGraph): void {
const self = this;
// Assert we are in Node.js environment
$R.checkNodeEnvironment();
// Node.js
$R.retrieveRemoteFile(config, function (raw_graph) {
graph = self.readFromJSON(JSON.parse(raw_graph), graph);
cb(graph, undefined);
});
}
readFromJSON(json: JSONGraph, graph?: IGraph | TypedGraph): IGraph | TypedGraph {
graph = graph || new BaseGraph(json.name);
const edc = new EdgeDupeChecker(graph);
const rlt = json.typeRLT;
// logger.log(rlt);
this.addNodesToGraph(json, graph);
for (let node_id in json.data) {
let node = graph.getNodeById(node_id);
// Reading and instantiating edges
let edges = json.data[node_id][labelKeys.edges];
for (let e in edges) {
const edge_input = edges[e];
// BASE INFO
const target_node = this.getTargetNode(graph, edge_input);
const edge_label = edge_input[labelKeys.e_label];
const edge_type = rlt && rlt.edges[edge_input[labelKeys.e_type]] || null;
// DIRECTION
const directed = this._config.explicit_direction ? !!edge_input[labelKeys.e_dir] : this._config.directed;
// WEIGHTS
const weight_float = JSONInput.handleEdgeWeights(edge_input);
const weight_info = weight_float === weight_float ? weight_float : DEFAULT_WEIGHT;
const edge_weight = this._config.weighted ? weight_info : undefined;
// EDGE_ID creation
/**
* @todo replace with uuid v4() -> then clean up the mess... ;-)
*/
const target_node_id = edge_input[labelKeys.e_to];
const dir_char = directed ? 'd' : 'u';
const edge_id = node_id + "_" + target_node_id + "_" + dir_char;
// DUPLICATE or CREATE ??
const newEdge: PotentialEdgeInfo = {
a: node,
b: target_node,
label: edge_label,
dir: directed,
weighted: this._config.weighted,
weight: edge_weight,
typed: !!edge_type,
type: edge_type
};
if ( this._config.dupeCheck && edc.isDupe(newEdge) ) {
// Don't throw, just log
// logger.log(`Edge ${edge_id} is a duplicate according to assumptions... omitting.`);
continue;
}
graph.addEdgeByID(edge_id, node, target_node, {
label: edge_label,
directed: directed,
weighted: this._config.weighted,
weight: edge_weight,
typed: !!edge_type,
type: edge_type
});
}
}
return graph;
}
addNodesToGraph(json: JSONGraph, graph: IGraph) {
const rlt = json.typeRLT;
let
coords_json: { [key: string]: any },
coords: { [key: string]: Number },
coord_idx: string,
features: { [key: string]: any };
for (let node_id in json.data) {
const type = BaseGraph.isTyped(graph) ? rlt && rlt.nodes[json.data[node_id][labelKeys.n_type]] : null;
const label = json.data[node_id][labelKeys.n_label];
const node = graph.addNodeByID(node_id, {label, type});
// Here we set the reference...?
features = json.data[node_id][labelKeys.n_features];
if (features) {
node.setFeatures(features);
}
// Here we copy...?
coords_json = json.data[node_id][labelKeys.coords];
if (coords_json) {
coords = {};
for (coord_idx in coords_json) {
coords[coord_idx] = +coords_json[coord_idx];
}
node.setFeature(labelKeys.coords, coords);
}
}
}
/**
* @todo implicitly add nodes referenced by edge
* but not present in graph input JSON ?
*/
getTargetNode(graph, edge_input): IBaseNode | ITypedNode {
const target_node_id = edge_input[labelKeys.e_to];
const target_node = graph.getNodeById(target_node_id);
if (!target_node) {
throw new Error('Node referenced by edge does not exist');
}
return target_node;
}
/**
* Infinity & -Infinity cases are redundant, as JavaScript
* handles them correctly anyways (for now)
* @param edge_input
*/
static handleEdgeWeights(edge_input): number {
switch (edge_input[labelKeys.e_weight]) {
case "undefined":
return DEFAULT_WEIGHT;
case "Infinity":
return Number.POSITIVE_INFINITY;
case "-Infinity":
return Number.NEGATIVE_INFINITY;
case "MAX":
return Number.MAX_VALUE;
case "MIN":
return Number.MIN_VALUE;
default:
return parseFloat(edge_input[labelKeys.e_weight])
}
}
}
export {JSONInput}