GraphStream

This commit is contained in:
2020-05-06 20:24:37 +02:00
parent acf8004497
commit 8d6d91e562
21 changed files with 357 additions and 81 deletions

View File

@ -1,10 +0,0 @@
export enum GraphLayouts {
neato = 'neato',
dot = 'dot',
circo = 'circo',
fdp = 'fdp',
sfdp = 'sfdp',
osage = 'osage',
twopi = 'twopi',
patchwork = 'patchwork'
}

10
lib/Helper.ts Normal file
View File

@ -0,0 +1,10 @@
function validate_name (name: string): string {
const new_name = name
.replace (/[^a-z0-9]/giu, '')
.replace (/^[0-9]+/iu, '');
if (new_name === '')
throw new Error (`invalid node name ${name}`);
return new_name;
}
export { validate_name };

View File

@ -1,33 +0,0 @@
enum EdgeStyles {
default = '',
solid = 'solid',
dashed = 'dashed',
dotted='dotted',
bold='bold'
}
enum NodeStyles {
default = '',
solid='solid',
dashed='dashed',
dotted='dotted',
bold='bold',
rounded='rounded',
diagonals='diagonals',
filled='filled',
striped='striped',
wedged='wedged',
invisible='invis'
}
enum GraphStyles {
solid = 'solid',
dashed = 'dashed',
dotted = 'dotted',
bold = 'bold',
rounded = 'rounded',
filled = 'filled',
striped = 'striped'
}
export { EdgeStyles, NodeStyles, GraphStyles };

View File

@ -1,4 +1,4 @@
import { EdgeStyles } from './Styles';
import { EdgeStyles } from '../enums/Styles';
import { Color } from './Color';
export class Edge {

View File

@ -1,3 +1,5 @@
import { validate_name } from '../Helper';
export class Element {
private _name = '';
protected parent_name: string;
@ -13,11 +15,7 @@ export class Element {
}
public set name (val: string) {
const new_name = val.replace (/[^a-z0-9]/giu, '')
.replace (/^[0-9]+/iu, '');
if (new_name === '')
throw new Error (`invalid node name ${val}`);
this._name = new_name;
this._name = validate_name (val);
}
public get parent (): string {

View File

@ -1,9 +1,9 @@
import { GraphStyles, NodeStyles } from '../enums/Styles';
import { GraphLayouts } from '../enums/GraphLayouts';
import { Element } from './Element';
import { Edge } from './Edge';
import { Node } from './Node';
import { GraphStyles, NodeStyles } from './Styles';
import { Color } from './Color';
import { GraphLayouts } from './GraphLayouts';
interface NodeOptions {
name: string;

160
lib/classes/GraphStream.ts Normal file
View File

@ -0,0 +1,160 @@
/* eslint-disable line-comment-position */
/* eslint-disable no-inline-comments */
import { Transform } from 'stream';
import { GraphStreamJSON } from '../interfaces/GraphStreamJSON';
import { GraphStreamCommand } from '../enums/GraphStreamCommand';
interface Stringable {
// eslint-disable-next-line @typescript-eslint/naming-convention
toString(): string;
}
export class GraphStream extends Transform {
private _path: string[] = [];
private _state = '';
private _directional = false;
public get path (): string {
return this._path.join ('_');
}
private get level (): string {
return ' '.repeat (this._path.length);
}
private expect_state (instr: GraphStreamCommand): void {
const states = [];
if ([
'cug',
'cdg'
].includes (instr))
states.push ('');
if ([
'eg',
'cn',
'csg',
'ce'
].includes (instr))
states.push ('en', 'at', 'eg', 'cug', 'cdg', 'csg', 'ce');
switch (instr) {
case 'en':
states.push ('cn', 'at');
break;
case 'at':
states.push ('cn', 'cug', 'cdg', 'csg');
break;
default:
break;
}
if (!states.includes (this._state)) {
throw new Error (`invalid state to execute command ${instr}
expected: ${states.join (', ')}`);
}
}
// eslint-disable-next-line max-len
// eslint-disable-next-line @typescript-eslint/naming-convention, complexity, max-lines-per-function
public _transform (
chunk: string,
encoding: string,
callback: ((error?: Error) => unknown)
): void {
const instr = JSON.parse (chunk) as GraphStreamJSON;
this.expect_state (instr.type);
switch (instr.type) {
case 'cug': // create unordered graph
this._path.push (instr.args[0]);
this.push (`graph ${this.path} {\n`);
this._directional = false;
break;
case 'cdg': // create directional graph
this._path.push (instr.args[0]);
this.push (`digraph ${this.path} {\n`);
this._directional = true;
break;
case 'csg': // create subgraph
this.push (
`${this.level}subgraph cluster_${this.path}_${instr.args[0]} {\n`
);
this._path.push (instr.args[0]);
break;
case 'eg': // end graph
this._path.pop ();
this.push (`${this.level}}\n\n`);
break;
case 'cn': // create node
this.push (`${this.level}${this.path}_${instr.args[0]}`);
break;
case 'en': // end node
this.push ('\n');
break;
case 'at': // add attributes
if (this._state === 'cn') {
this.push (` [${instr.args.join (', ')}]`);
}
else {
this.push (`${this.level}${
instr.args.join (`\n${this.level}`)
.replace (/"/gu, '')
}\n\n`);
}
break;
case 'ce':
this.push (`${this.level}${
instr.args[0]
} -${this._directional ? '>' : '-'} ${instr.args[1]}\n`);
break;
default:
break;
}
this._state = instr.type;
callback ();
}
public write (
instr: GraphStreamJSON
): boolean {
return super.write (JSON.stringify (instr), 'utf-8');
}
public create_node (name: string): boolean {
return this.write ({ type: 'cn', args: [ name ] });
}
public end_node (): boolean {
return this.write ({ type: 'en', args: [] });
}
public create_graph (name: string, type: 'u'|'d'|'s' = 's'): boolean {
const instr_type = `c${type}g` as GraphStreamCommand;
return this.write ({ type: instr_type, args: [ name ] });
}
public end_graph (): boolean {
return this.write ({ type: 'eg', args: [] });
}
public attributes (attrs: Record<string, Stringable>): boolean {
const solved = [];
for (const attr of Object.keys (attrs)) {
const val = attrs[attr].toString ();
if ((/\n/u).test (val))
solved.push (`${attr} = <${val}>`);
else
solved.push (`${attr} = "${val}"`);
}
return this.write ({ type: 'at', args: solved });
}
public create_edge (origin: string, target: string): boolean {
return this.write ({
type: 'ce',
args: [
origin,
target
]
});
}
}

View File

@ -1,5 +1,5 @@
import { NodeStyles } from '../enums/Styles';
import { Element } from './Element';
import { NodeStyles } from './Styles';
import { Color } from './Color';
export class Node extends Element {

View File

@ -0,0 +1,9 @@
export type GraphLayouts =
'neato'
| 'dot'
| 'circo'
| 'fdp'
| 'sfdp'
| 'osage'
| 'twopi'
| 'patchwork'

View File

@ -0,0 +1,11 @@
/* eslint-disable line-comment-position */
/* eslint-disable no-inline-comments */
export type GraphStreamCommand =
'cn'| // create node
'en'| // end node
'cug'| // create unordered graph
'cdg'| // create directional graph
'csg'| // create subgraph
'eg'| // end graph
'at'| // add attributes
'ce' // create edge

32
lib/enums/Styles.ts Normal file
View File

@ -0,0 +1,32 @@
type EdgeStyles =
''
|'solid'
|'dashed'
|'dotted'
|'bold'
type NodeStyles =
''
|'solid'
|'dashed'
|'dotted'
|'bold'
|'rounded'
|'diagonals'
|'filled'
|'striped'
|'wedged'
|'invis'
type GraphStyles =
'solid'
| 'dashed'
| 'dotted'
| 'bold'
| 'rounded'
| 'filled'
| 'striped'
export { EdgeStyles, NodeStyles, GraphStyles };

View File

@ -1,8 +1,12 @@
export { Graph } from './Graph';
export { Node } from './Node';
export { Element } from './Element';
export { Edge } from './Edge';
export { Color } from './Color';
export { EdgeStyles, NodeStyles, GraphStyles } from './Styles';
export { GraphLayouts } from './GraphLayouts';
export { Graphable } from './Graphable';
import { Graph } from './classes/Graph';
export * from './classes/Color';
export * from './classes/Edge';
export * from './classes/Element';
export { Graph };
export * from './classes/GraphStream';
export * from './classes/Node';
export * from './enums/GraphLayouts';
export * from './enums/Styles';
export * from './interfaces/Graphable';
export default Graph;

View File

@ -0,0 +1,6 @@
import { GraphStreamCommand } from '../enums/GraphStreamCommand';
export interface GraphStreamJSON {
type: GraphStreamCommand;
args: string[];
}

View File

@ -1,4 +1,4 @@
import { Graph } from './Graph';
import { Graph } from '../classes/Graph';
export interface Graphable {
to_graph(g: Graph, ...args: unknown[]): unknown;