/**
* `dc_graph.dynagraph_layout` connects to dynagraph-wasm and does dynamic directed graph layout.
* @class dynagraph_layout
* @memberof dc_graph
* @param {String} [id=uuid()] - Unique identifier
* @return {dc_graph.dynagraph_layout}
**/
dc_graph.dynagraph_layout = function(id, layout) {
var _layoutId = id || uuid();
const _Gname = _layoutId;
var _layout;
var _dispatch = d3.dispatch('tick', 'start', 'end');
var _tick, _done;
var _nodes = {}, _edges = {};
var _linesOut = [], _incrIn = [], _opened = false, _open_graph;
var _lock = 0;
let bb = null;
// dg2incr
function dg2incr_coord(c) {
const [x, y] = c;
return [x, /*(bb && bb[0][1] || 0)*/ - y];
}
function dg2incr_graph_attrs() {
return [
['rankdir', _layout.rankdir()],
['resolution', [_layout.resolution().x, _layout.resolution().y]],
['defaultsize', [_layout.defaultsize().width, _layout.defaultsize().height]],
['separation', [_layout.separation().x, _layout.separation().y]],
];
}
function dg2incr_node_attrs(n) {
const attr_pairs = [];
if(n.x !== undefined && n.y !== undefined)
attr_pairs.push(['pos', dg2incr_coord([n.x, n.y]).map(String).join(',')]);
return attr_pairs;
}
function dg2incr_node_attrs_changed(n, n2) {
const attr_pairs = [];
if(n2.x !== undefined && n2.y !== undefined && (n2.x !== n.x || n2.y !== n.y))
attr_pairs.push(['pos', dg2incr_coord([n2.x, n2.y]).map(String).join(',')]);
return attr_pairs;
}
function dg2incr_edge_attrs(e) {
return [];
}
function mq(x) { // maybe quote
if(x === +x) // isNumber
return x;
else if(/^[A-Za-z_][A-Za-z0-9_]*$/.test(x))
return x;
else return '"' + x + '"';
}
function print_incr_attrs(attr_pairs) {
return '[' + attr_pairs.map(([a,b]) => `${mq(a)}=${mq(b)}`).join(', ') + ']';
}
// incr2dg
function incr2dg_coord(c) {
const [x, y] = c;
return [+x, /*(bb && bb[0][1] || 0)*/ - y];
}
function incr2dg_bb(bb) {
const [x1,y1,x2,y2] = bb.split(',');
return [incr2dg_coord([x1,y1]), incr2dg_coord([x2,y2])];
}
function incr2dg_node_attrs(n) {
const attrs = {};
if(n.pos)
[attrs.x, attrs.y] = incr2dg_coord(n.pos.split(',').map(Number));
return attrs;
}
function incr2dg_edge_attrs(e) {
const attrs = {};
if(e.pos)
attrs.points = e.pos.split(' ')
.map(coord => coord.split(',').map(Number))
.map(incr2dg_coord)
.map(([x,y]) => ({x,y}));
return attrs;
}
function runCommands(cmds) {
for(const cmd of cmds) {
const {action, kind} = cmd;
switch(`${action}_${kind}`) {
case 'open_graph': {
const {attrs} = cmd;
console.log('open graph', attrs);
console.log('open graph bb', bb)
bb = incr2dg_bb(attrs.bb)
console.log('open graph bb', bb)
break;
}
case 'modify_graph': {
const {attrs} = cmd;
console.log('modify graph', attrs);
console.log('modify graph bb', bb)
bb = incr2dg_bb(attrs.bb)
console.log('modify graph bb', bb)
break;
}
case 'close_graph': {
console.log('close graph');
break;
}
case 'insert_node': {
const {node, attrs} = cmd;
console.log('insert node', node, attrs);
console.log('insert node2', _nodes[node])
Object.assign(_nodes[node], incr2dg_node_attrs(attrs));
console.log('insert node3', _nodes[node])
break;
}
case 'modify_node': {
const {node, attrs} = cmd;
console.log('modify node', node, attrs);
console.log('modify node2', _nodes[node])
Object.assign(_nodes[node], incr2dg_node_attrs(attrs));
console.log('modify node3', _nodes[node])
break;
}
case 'delete_node': {
const {node} = cmd;
console.log('delete node', node);
break;
}
case 'insert_edge': {
const {edge, source, target, attrs} = cmd;
console.log('insert edge', edge, source, target, attrs);
console.log('insert edge2', _edges[edge])
Object.assign(_edges[edge], incr2dg_edge_attrs(attrs));
console.log('insert edge3', _edges[edge])
break;
}
case 'modify_edge': {
const {edge, attrs} = cmd;
console.log('modify edge', edge, attrs);
console.log('modify edge2', _edges[edge])
Object.assign(_edges[edge], incr2dg_edge_attrs(attrs));
console.log('modify edge3', _edges[edge])
break;
}
case 'delete_edge': {
const {edge} = cmd;
console.log('delete edge', edge);
break;
}
}
}
}
function receiveIncr(text) {
console.log(text);
let cmds = null;
try {
const parseIncrface = self.parseIncrface || (self.incrface && self.incrface.parse);
if(!parseIncrface) {
console.log('parseIncrface not available, skipping');
return;
}
cmds = parseIncrface(text);
} catch(xep) {
console.log('incrface parse failed', xep)
}
if (!cmds)
return;
for(const cmd of cmds) {
const {action, kind, graph} = cmd;
if(action === 'message') {
console.warn('dynagraph message', cmd.message);
continue;
}
if(graph !== _Gname) {
console.warn('graph name mismatch', _Gname, graph);
continue;
}
switch(`${action}_${kind}`) {
case 'lock_graph':
_lock++;
break;
case 'unlock_graph':
// maybe error on negative lock?
if(--_lock <= 0) {
runCommands(_incrIn);
_incrIn = []
}
break;
default:
if(_lock > 0)
_incrIn.push(cmd);
else
runCommands([cmd]);
}
}
_done();
}
function init(options) {
self.receiveIncr = receiveIncr;
_opened = false;
_open_graph = `open graph ${mq(_Gname)} ${print_incr_attrs(dg2incr_graph_attrs())}`
}
function data(nodes, edges, clusters) {
const linesOutDeleteNode = [];
var wnodes = regenerate_objects(_nodes, nodes, null,
function key(v) {
return v.dcg_nodeKey;
}, function assign(v1, v) {
v1.dcg_nodeKey = v.dcg_nodeKey;
v1.width = v.width;
v1.height = v.height;
if(v.dcg_nodeFixed) {
v1.x = v.dcg_nodeFixed.x;
v1.y = v.dcg_nodeFixed.y;
}
const na = dg2incr_node_attrs_changed(v1, v);
if(na.length)
_linesOut.push(`modify node ${mq(_Gname)} ${mq(v1.dcg_nodeKey)} ${print_incr_attrs(na)}`);
}, function create(k, o) {
_linesOut.push(`insert node ${mq(_Gname)} ${mq(k)} ${print_incr_attrs(dg2incr_node_attrs(o))}`);
}, function destroy(k) {
linesOutDeleteNode.push(`delete node ${mq(_Gname)} ${mq(k)}`);
});
var wedges = regenerate_objects(_edges, edges, null, function key(e) {
return e.dcg_edgeKey;
}, function assign(e1, e) {
e1.dcg_edgeKey = e.dcg_edgeKey;
e1.dcg_edgeSource = e.dcg_edgeSource;
e1.dcg_edgeTarget = e.dcg_edgeTarget;
}, function create(k, o, e) {
_linesOut.push(`insert edge ${mq(_Gname)} ${mq(k)} ${mq(e.dcg_edgeSource)} ${mq(e.dcg_edgeTarget)} ${print_incr_attrs(dg2incr_edge_attrs(e))}`);
}, function destroy(k, e) {
_linesOut.push(`delete edge ${mq(_Gname)} ${k}`);
});
_linesOut.push(...linesOutDeleteNode);
function dispatchState(event) {
_dispatch[event](
wnodes,
wedges
);
}
_tick = function() {
dispatchState('tick');
};
_done = function() {
dispatchState('end');
};
}
function start() {
if(_linesOut.length) {
const open = _opened ? [] : [_open_graph];
_opened = true;
const actions = _linesOut.length > 1 ? [
`lock graph ${mq(_Gname)}`,
... _linesOut,
`unlock graph ${mq(_Gname)}`
] : _linesOut;
const input = [...open, ...actions].join('\n');
console.log('dynagraph input:', input);
self.incrface_input = input;
_linesOut = [];
}
else _done();
}
function stop() {
}
_layout = {
...dc_graph.graphviz_attrs(),
layoutAlgorithm: function() {
return layout;
},
layoutId: function() {
return _layoutId;
},
supportsWebworker: function() {
return true;
},
resolution: property({x: 5, y: 5}),
defaultsize: property({width: 50, height: 50}),
separation: property({x: 20, y: 20}),
on: function(event, f) {
if(arguments.length === 1)
return _dispatch.on(event);
_dispatch.on(event, f);
return this;
},
init: function(options) {
this.optionNames().forEach(function(option) {
options[option] = options[option] || this[option]();
}.bind(this));
init(options);
return this;
},
data: function(graph, nodes, edges) {
data(nodes, edges);
},
start: function() {
start();
},
stop: function() {
stop();
},
optionNames: function() {
return ['resolution', 'defaultsize', 'separation'];
},
populateLayoutNode: function(layout, node) {},
populateLayoutEdge: function() {}
};
return _layout;
};
dc_graph.dynagraph_layout.scripts = ['d3.js', 'dynagraph-wasm.js', 'incrface-umd.js'];