Source: dynagraph_layout.js

/**
 * `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'];