From 1be4feceab29efd485eb806d7aae77d3c7f83e7e Mon Sep 17 00:00:00 2001 From: Zhe Wang Date: Wed, 27 Jun 2018 15:21:02 -0400 Subject: [PATCH 01/17] setcola barely works --- src/cola_layout.js | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/cola_layout.js b/src/cola_layout.js index dc5fac55..2fc8cac0 100644 --- a/src/cola_layout.js +++ b/src/cola_layout.js @@ -8,6 +8,7 @@ dc_graph.cola_layout = function(id) { var _layoutId = id || uuid(); var _d3cola = null; + var _setcola = null; var _dispatch = d3.dispatch('tick', 'start', 'end'); var _flowLayout; // node and edge objects shared with cola.js, preserved from one iteration @@ -20,6 +21,7 @@ dc_graph.cola_layout = function(id) { .avoidOverlaps(true) .size([options.width, options.height]) .handleDisconnected(options.handleDisconnected); + if(_d3cola.tickSize) // non-standard _d3cola.tickSize(options.tickSize); @@ -103,10 +105,36 @@ dc_graph.cola_layout = function(id) { }).on('end', /* _done = */ function() { dispatchState('end'); }); - _d3cola.nodes(wnodes) - .links(wedges) - .constraints(constraints) + + var setcolaSpec = [ + { + "name": "layer", + "sets": {"partition": "depth"}, + "forEach": [{"constraint": "align", "axis": "x"}] + }, + { + "name": "sort", + "sets": ["layer"], + "forEach": [{"constraint": "order", "axis": "y", "by": "depth"}] + } + ]; + + var setcola_result = setcola + .nodes(wnodes) // Set the graph nodes + .links(wedges) // Set the graph links + .constraints(setcolaSpec) // Set the constraints + .gap(10) + .layout(); + + _d3cola.nodes(setcola_result.nodes) + .links(setcola_result.links) + .constraints(setcola_result.constraints) .groups(groups); + + //_d3cola.nodes(wnodes) + //.links(wedges) + //.constraints(constraints) + //.groups(groups); } function start() { @@ -245,4 +273,4 @@ dc_graph.cola_layout = function(id) { return engine; }; -dc_graph.cola_layout.scripts = ['d3.js', 'cola.js']; +dc_graph.cola_layout.scripts = ['d3.js', 'cola.js', 'setcola.js']; From e98385c6398154dff06022fb2c2c3ab69285096d Mon Sep 17 00:00:00 2001 From: Zhe Wang Date: Wed, 27 Jun 2018 16:08:09 -0400 Subject: [PATCH 02/17] expose setcola config as setcolaSpec --- src/cola_layout.js | 48 ++++++++++++++++++---------------------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/src/cola_layout.js b/src/cola_layout.js index 2fc8cac0..93370f47 100644 --- a/src/cola_layout.js +++ b/src/cola_layout.js @@ -8,7 +8,6 @@ dc_graph.cola_layout = function(id) { var _layoutId = id || uuid(); var _d3cola = null; - var _setcola = null; var _dispatch = d3.dispatch('tick', 'start', 'end'); var _flowLayout; // node and edge objects shared with cola.js, preserved from one iteration @@ -106,35 +105,25 @@ dc_graph.cola_layout = function(id) { dispatchState('end'); }); - var setcolaSpec = [ - { - "name": "layer", - "sets": {"partition": "depth"}, - "forEach": [{"constraint": "align", "axis": "x"}] - }, - { - "name": "sort", - "sets": ["layer"], - "forEach": [{"constraint": "order", "axis": "y", "by": "depth"}] - } - ]; + if(engine.setcolaSpec !== []) { + var setcola_result = setcola + .nodes(wnodes) // Set the graph nodes + .links(wedges) // Set the graph links + .constraints(engine.setcolaSpec) // Set the constraints + .gap(10) + .layout(); - var setcola_result = setcola - .nodes(wnodes) // Set the graph nodes - .links(wedges) // Set the graph links - .constraints(setcolaSpec) // Set the constraints - .gap(10) - .layout(); - - _d3cola.nodes(setcola_result.nodes) - .links(setcola_result.links) - .constraints(setcola_result.constraints) - .groups(groups); + _d3cola.nodes(setcola_result.nodes) + .links(setcola_result.links) + .constraints(setcola_result.constraints) + .groups(groups); + } else { + _d3cola.nodes(wnodes) + .links(wedges) + .constraints(constraints) + .groups(groups); + } - //_d3cola.nodes(wnodes) - //.links(wedges) - //.constraints(constraints) - //.groups(groups); } function start() { @@ -268,7 +257,8 @@ dc_graph.cola_layout = function(id) { allConstraintsIterations: property(20), gridSnapIterations: property(0), tickSize: property(1), - groupConnected: property(false) + groupConnected: property(false), + setcolaSpec: property([]), }); return engine; }; From 1c229df8aec4a0c19bf54ca7a32070d1eed1ba96 Mon Sep 17 00:00:00 2001 From: Zhe Wang Date: Thu, 28 Jun 2018 13:39:14 -0400 Subject: [PATCH 03/17] support user defined attribute extractor --- src/cola_layout.js | 10 +++++++++- src/diagram.js | 12 ++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/cola_layout.js b/src/cola_layout.js index 93370f47..dd0d7f5b 100644 --- a/src/cola_layout.js +++ b/src/cola_layout.js @@ -52,6 +52,7 @@ dc_graph.cola_layout = function(id) { v1.width = v.width; v1.height = v.height; v1.fixed = !!v.dcg_nodeFixed; + v1.attrs = v.attrs; if(v1.fixed && typeof v.dcg_nodeFixed === 'object') { v1.x = v.dcg_nodeFixed.x; @@ -74,11 +75,14 @@ dc_graph.cola_layout = function(id) { e1.source = _nodes[e.dcg_edgeSource]; e1.target = _nodes[e.dcg_edgeTarget]; e1.dcg_edgeLength = e.dcg_edgeLength; + e1.attrs = e.attrs; }); // cola needs each node object to have an index property wnodes.forEach(function(v, i) { v.index = i; + //use user defined attribute extractor to get needed attributes + engine.extractNodeAttrs(v, v.attrs); }); var groups = null; @@ -110,9 +114,11 @@ dc_graph.cola_layout = function(id) { .nodes(wnodes) // Set the graph nodes .links(wedges) // Set the graph links .constraints(engine.setcolaSpec) // Set the constraints - .gap(10) + .gap(10) //default value is 10, can be customized in setcolaSpec .layout(); + console.log('applying setcola constrains'); + _d3cola.nodes(setcola_result.nodes) .links(setcola_result.links) .constraints(setcola_result.constraints) @@ -259,6 +265,8 @@ dc_graph.cola_layout = function(id) { tickSize: property(1), groupConnected: property(false), setcolaSpec: property([]), + extractNodeAttrs: function(_node, _attrs) {}, //add new attributes to _node from _attrs + extractEdgeAttrs: function(_edge, _attrs) {}, }); return engine; }; diff --git a/src/diagram.js b/src/diagram.js index 34685271..a5b9926b 100644 --- a/src/diagram.js +++ b/src/diagram.js @@ -1792,8 +1792,16 @@ dc_graph.diagram = function (parent, chartGroup) { _dispatch.start(); // cola doesn't seem to fire this itself? _diagram.layoutEngine().data( { width: _diagram.width(), height: _diagram.height() }, - wnodes.map(function(v) { return v.cola; }), - layout_edges.map(function(v) { return v.cola; }), + wnodes.map(function(v) { + var _v = v.cola; + _v.attrs = v.orig; + return _v; + }), + layout_edges.map(function(v) { + var _v = v.cola; + _v.attrs = v.orig; + return _v; + }), constraints ); _diagram.layoutEngine().start(); From 80413dccf9576ee1b5a0027acf7c658737cdcec8 Mon Sep 17 00:00:00 2001 From: Zhe Wang Date: Mon, 16 Jul 2018 13:54:23 -0400 Subject: [PATCH 04/17] fix if check bug --- src/cola_layout.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/cola_layout.js b/src/cola_layout.js index dd0d7f5b..c13b257a 100644 --- a/src/cola_layout.js +++ b/src/cola_layout.js @@ -109,10 +109,11 @@ dc_graph.cola_layout = function(id) { dispatchState('end'); }); - if(engine.setcolaSpec !== []) { + if(engine.setcolaSpec !== undefined) { var setcola_result = setcola .nodes(wnodes) // Set the graph nodes .links(wedges) // Set the graph links + .guides(engine.setcolaGuides) .constraints(engine.setcolaSpec) // Set the constraints .gap(10) //default value is 10, can be customized in setcolaSpec .layout(); @@ -264,7 +265,8 @@ dc_graph.cola_layout = function(id) { gridSnapIterations: property(0), tickSize: property(1), groupConnected: property(false), - setcolaSpec: property([]), + setcolaSpec: undefined, + setcolaGuides: undefined, extractNodeAttrs: function(_node, _attrs) {}, //add new attributes to _node from _attrs extractEdgeAttrs: function(_edge, _attrs) {}, }); From 74791334a6298f7684dcf171bdfffe8d2652d079 Mon Sep 17 00:00:00 2001 From: Zhe Wang Date: Mon, 23 Jul 2018 11:49:50 -0400 Subject: [PATCH 05/17] add multi_layout.js --- Gruntfile.js | 1 + src/engine.js | 8 +- src/multi_layout.js | 269 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 277 insertions(+), 1 deletion(-) create mode 100644 src/multi_layout.js diff --git a/Gruntfile.js b/Gruntfile.js index 8b55bcd4..baae9f75 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -345,6 +345,7 @@ module.exports.jsFiles = [ 'src/graphviz_layout.js', 'src/d3_force_layout.js', 'src/d3v4_force_layout.js', + 'src/multi_layout.js', 'src/flexbox_layout.js', 'src/manual_layout.js', 'src/place_ports.js', diff --git a/src/engine.js b/src/engine.js index eaed8b5a..63298984 100644 --- a/src/engine.js +++ b/src/engine.js @@ -46,7 +46,13 @@ dc_graph._engines = [ instantiate: function() { return dc_graph.cola_layout(); } - } + }, + { + name: 'multiAlg', + instantiate: function() { + return dc_graph.multi_layout(); + } + }, ]; dc_graph._default_engine = 'cola'; diff --git a/src/multi_layout.js b/src/multi_layout.js new file mode 100644 index 00000000..a6328df6 --- /dev/null +++ b/src/multi_layout.js @@ -0,0 +1,269 @@ +dc_graph.multi_layout = function(id) { + var _layoutId = id || uuid(); + var _d3cola = null; + var _dispatch = d3.dispatch('tick', 'start', 'end'); + var _flowLayout; + // node and edge objects shared with cola.js, preserved from one iteration + // to the next (as long as the object is still in the layout) + var _nodes = {}, _edges = {}; + + function init(options) { + // width, height, handleDisconnected, lengthStrategy, baseLength, flowLayout, tickSize + _d3cola = cola.d3adaptor() + .avoidOverlaps(true) + .size([options.width, options.height]) + .handleDisconnected(options.handleDisconnected); + + if(_d3cola.tickSize) // non-standard + _d3cola.tickSize(options.tickSize); + + switch(options.lengthStrategy) { + case 'symmetric': + _d3cola.symmetricDiffLinkLengths(options.baseLength); + break; + case 'jaccard': + _d3cola.jaccardLinkLengths(options.baseLength); + break; + case 'individual': + _d3cola.linkDistance(function(e) { + return e.dcg_edgeLength || options.baseLength; + }); + break; + case 'none': + default: + } + if(options.flowLayout) { + _d3cola.flowLayout(options.flowLayout.axis, options.flowLayout.minSeparation); + } + } + + function data(nodes, edges, constraints) { + var wnodes = regenerate_objects(_nodes, nodes, null, function(v) { + return v.dcg_nodeKey; + }, function(v1, v) { + v1.dcg_nodeKey = v.dcg_nodeKey; + v1.width = v.width; + v1.height = v.height; + v1.fixed = !!v.dcg_nodeFixed; + v1.attrs = v.attrs; + + if(v1.fixed && typeof v.dcg_nodeFixed === 'object') { + v1.x = v.dcg_nodeFixed.x; + v1.y = v.dcg_nodeFixed.y; + } + else { + // should we support e.g. null to unset x,y? + if(v.x !== undefined) + v1.x = v.x; + if(v.y !== undefined) + v1.y = v.y; + } + }); + var wedges = regenerate_objects(_edges, edges, null, function(e) { + return e.dcg_edgeKey; + }, function(e1, e) { + e1.dcg_edgeKey = e.dcg_edgeKey; + // cola edges can work with indices or with object references + // but it will replace indices with object references + e1.source = _nodes[e.dcg_edgeSource]; + e1.target = _nodes[e.dcg_edgeTarget]; + e1.dcg_edgeLength = e.dcg_edgeLength; + e1.attrs = e.attrs; + }); + + // cola needs each node object to have an index property + wnodes.forEach(function(v, i) { + v.index = i; + //use user defined attribute extractor to get needed attributes + engine.extractNodeAttrs(v, v.attrs); + }); + + var groups = null; + if(engine.groupConnected()) { + var components = cola.separateGraphs(wnodes, wedges); + groups = components.map(function(g) { + return {leaves: g.array.map(function(n) { return n.index; })}; + }); + } + + function dispatchState(event) { + _dispatch[event]( + wnodes, + wedges.map(function(e) { + return {dcg_edgeKey: e.dcg_edgeKey}; + }) + ); + } + _d3cola.on('tick', /* _tick = */ function() { + dispatchState('tick'); + }).on('start', function() { + _dispatch.start(); + }).on('end', /* _done = */ function() { + dispatchState('end'); + }); + + if(engine.setcolaSpec !== undefined) { + var setcola_result = setcola + .nodes(wnodes) // Set the graph nodes + .links(wedges) // Set the graph links + .guides(engine.setcolaGuides) + .constraints(engine.setcolaSpec) // Set the constraints + .gap(10) //default value is 10, can be customized in setcolaSpec + .layout(); + + console.log('applying setcola constrains'); + + _d3cola.nodes(setcola_result.nodes) + .links(setcola_result.links) + .constraints(setcola_result.constraints) + .groups(groups); + } else { + _d3cola.nodes(wnodes) + .links(wedges) + .constraints(constraints) + .groups(groups); + } + + } + + function start() { + _d3cola.start(engine.unconstrainedIterations(), + engine.userConstraintIterations(), + engine.allConstraintsIterations(), + engine.gridSnapIterations()); + } + + function stop() { + if(_d3cola) + _d3cola.stop(); + } + + var graphviz = dc_graph.graphviz_attrs(), graphviz_keys = Object.keys(graphviz); + graphviz.rankdir(null); + + var engine = Object.assign(graphviz, { + layoutAlgorithm: function() { + return 'multi'; + }, + layoutId: function() { + return _layoutId; + }, + supportsWebworker: function() { + return true; + }, + needsStage: function(stage) { // stopgap until we have engine chaining + return stage === 'ports' || stage === 'edgepos'; + }, + parent: property(null), + 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, constraints) { + data(nodes, edges, constraints); + }, + start: function() { + start(); + }, + stop: function() { + stop(); + }, + optionNames: function() { + return ['handleDisconnected', 'lengthStrategy', 'baseLength', 'flowLayout', 'tickSize', 'groupConnected'] + .concat(graphviz_keys); + }, + populateLayoutNode: function() {}, + populateLayoutEdge: function() {}, + /** + * Instructs cola.js to fit the connected components. + * @method handleDisconnected + * @memberof dc_graph.cola_layout + * @instance + * @param {Boolean} [handleDisconnected=true] + * @return {Boolean} + * @return {dc_graph.cola_layout} + **/ + handleDisconnected: property(true), + /** + * Currently, three strategies are supported for specifying the lengths of edges: + * * 'individual' - uses the `edgeLength` for each edge. If it returns falsy, uses the + * `baseLength` + * * 'symmetric', 'jaccard' - compute the edge length based on the graph structure around + * the edge. See + * {@link https://github.com/tgdwyer/WebCola/wiki/link-lengths the cola.js wiki} + * for more details. + * 'none' - no edge lengths will be specified + * @method lengthStrategy + * @memberof dc_graph.cola_layout + * @instance + * @param {Function|String} [lengthStrategy='symmetric'] + * @return {Function|String} + * @return {dc_graph.cola_layout} + **/ + lengthStrategy: property('symmetric'), + /** + * Gets or sets the default edge length (in pixels) when the `.lengthStrategy` is + * 'individual', and the base value to be multiplied for 'symmetric' and 'jaccard' edge + * lengths. + * @method baseLength + * @memberof dc_graph.cola_layout + * @instance + * @param {Number} [baseLength=30] + * @return {Number} + * @return {dc_graph.cola_layout} + **/ + baseLength: property(30), + /** + * If `flowLayout` is set, it determines the axis and separation for + * {@link http://marvl.infotech.monash.edu/webcola/doc/classes/cola.layout.html#flowlayout cola flow layout}. + * If it is not set, `flowLayout` will be calculated from the {@link dc_graph.graphviz_attrs#rankdir rankdir} + * and {@link dc_graph.graphviz_attrs#ranksep ranksep}; if `rankdir` is also null (the + * default for cola layout), then there will be no flow. + * @method flowLayout + * @memberof dc_graph.cola_layout + * @instance + * @param {Object} [flowLayout=null] + * @example + * // No flow (default) + * diagram.flowLayout(null) + * // flow in x with min separation 200 + * diagram.flowLayout({axis: 'x', minSeparation: 200}) + **/ + flowLayout: function(flow) { + if(!arguments.length) { + if(_flowLayout) + return _flowLayout; + var dir = engine.rankdir(); + switch(dir) { + case 'LR': return {axis: 'x', minSeparation: engine.ranksep() + engine.parent().nodeRadius()*2}; + case 'TB': return {axis: 'y', minSeparation: engine.ranksep() + engine.parent().nodeRadius()*2}; + default: return null; // RL, BT do not appear to be possible (negative separation) (?) + } + } + _flowLayout = flow; + return this; + }, + unconstrainedIterations: property(10), + userConstraintIterations: property(20), + allConstraintsIterations: property(20), + gridSnapIterations: property(0), + tickSize: property(1), + groupConnected: property(false), + setcolaSpec: undefined, + setcolaGuides: undefined, + extractNodeAttrs: function(_node, _attrs) {}, //add new attributes to _node from _attrs + extractEdgeAttrs: function(_edge, _attrs) {}, + }); + return engine; +}; + +dc_graph.multi_layout.scripts = ['d3.js', 'cola.js', 'setcola.js', 'd3v4-force.js']; From a65c5ba709dfeabce49a50e8bf2da6ca0d45e178 Mon Sep 17 00:00:00 2001 From: Zhe Wang Date: Mon, 23 Jul 2018 15:45:18 -0400 Subject: [PATCH 06/17] calling to d3v4 force works --- src/multi_layout.js | 137 ++++++-------------------------------------- 1 file changed, 16 insertions(+), 121 deletions(-) diff --git a/src/multi_layout.js b/src/multi_layout.js index a6328df6..5ff78213 100644 --- a/src/multi_layout.js +++ b/src/multi_layout.js @@ -1,141 +1,36 @@ dc_graph.multi_layout = function(id) { var _layoutId = id || uuid(); - var _d3cola = null; + var _engine = null; var _dispatch = d3.dispatch('tick', 'start', 'end'); var _flowLayout; - // node and edge objects shared with cola.js, preserved from one iteration - // to the next (as long as the object is still in the layout) var _nodes = {}, _edges = {}; function init(options) { // width, height, handleDisconnected, lengthStrategy, baseLength, flowLayout, tickSize - _d3cola = cola.d3adaptor() - .avoidOverlaps(true) - .size([options.width, options.height]) - .handleDisconnected(options.handleDisconnected); - - if(_d3cola.tickSize) // non-standard - _d3cola.tickSize(options.tickSize); - - switch(options.lengthStrategy) { - case 'symmetric': - _d3cola.symmetricDiffLinkLengths(options.baseLength); - break; - case 'jaccard': - _d3cola.jaccardLinkLengths(options.baseLength); - break; - case 'individual': - _d3cola.linkDistance(function(e) { - return e.dcg_edgeLength || options.baseLength; - }); - break; - case 'none': - default: - } - if(options.flowLayout) { - _d3cola.flowLayout(options.flowLayout.axis, options.flowLayout.minSeparation); - } + _engine = dc_graph.d3v4_force_layout(); + _engine.init(options); + _engine.on('end', function(nodes, edges) { + _dispatch['end'](nodes, edges); + }); } function data(nodes, edges, constraints) { - var wnodes = regenerate_objects(_nodes, nodes, null, function(v) { - return v.dcg_nodeKey; - }, function(v1, v) { - v1.dcg_nodeKey = v.dcg_nodeKey; - v1.width = v.width; - v1.height = v.height; - v1.fixed = !!v.dcg_nodeFixed; - v1.attrs = v.attrs; - - if(v1.fixed && typeof v.dcg_nodeFixed === 'object') { - v1.x = v.dcg_nodeFixed.x; - v1.y = v.dcg_nodeFixed.y; - } - else { - // should we support e.g. null to unset x,y? - if(v.x !== undefined) - v1.x = v.x; - if(v.y !== undefined) - v1.y = v.y; - } - }); - var wedges = regenerate_objects(_edges, edges, null, function(e) { - return e.dcg_edgeKey; - }, function(e1, e) { - e1.dcg_edgeKey = e.dcg_edgeKey; - // cola edges can work with indices or with object references - // but it will replace indices with object references - e1.source = _nodes[e.dcg_edgeSource]; - e1.target = _nodes[e.dcg_edgeTarget]; - e1.dcg_edgeLength = e.dcg_edgeLength; - e1.attrs = e.attrs; - }); - - // cola needs each node object to have an index property - wnodes.forEach(function(v, i) { - v.index = i; - //use user defined attribute extractor to get needed attributes - engine.extractNodeAttrs(v, v.attrs); - }); - - var groups = null; - if(engine.groupConnected()) { - var components = cola.separateGraphs(wnodes, wedges); - groups = components.map(function(g) { - return {leaves: g.array.map(function(n) { return n.index; })}; - }); - } - - function dispatchState(event) { - _dispatch[event]( - wnodes, - wedges.map(function(e) { - return {dcg_edgeKey: e.dcg_edgeKey}; - }) - ); - } - _d3cola.on('tick', /* _tick = */ function() { - dispatchState('tick'); - }).on('start', function() { - _dispatch.start(); - }).on('end', /* _done = */ function() { - dispatchState('end'); - }); - - if(engine.setcolaSpec !== undefined) { - var setcola_result = setcola - .nodes(wnodes) // Set the graph nodes - .links(wedges) // Set the graph links - .guides(engine.setcolaGuides) - .constraints(engine.setcolaSpec) // Set the constraints - .gap(10) //default value is 10, can be customized in setcolaSpec - .layout(); - - console.log('applying setcola constrains'); - - _d3cola.nodes(setcola_result.nodes) - .links(setcola_result.links) - .constraints(setcola_result.constraints) - .groups(groups); - } else { - _d3cola.nodes(wnodes) - .links(wedges) - .constraints(constraints) - .groups(groups); - } - + // TODO creat a set of different layouts hierarchically + _nodes = nodes; + _edges = edges; + _engine.data({}, nodes, edges, constraints); } function start() { - _d3cola.start(engine.unconstrainedIterations(), - engine.userConstraintIterations(), - engine.allConstraintsIterations(), - engine.gridSnapIterations()); + // TODO execute the layout algorithms bottom-up + _engine.start(); + // get the positions of nodes + // _d3cola._ndoes } function stop() { - if(_d3cola) - _d3cola.stop(); + if(_engine) + _engine.stop(); } var graphviz = dc_graph.graphviz_attrs(), graphviz_keys = Object.keys(graphviz); From 433f81287e1fb0ab872da5dc75076b1d2c5f3b15 Mon Sep 17 00:00:00 2001 From: Zhe Wang Date: Wed, 25 Jul 2018 14:40:02 -0400 Subject: [PATCH 07/17] use promise --- src/multi_layout.js | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/src/multi_layout.js b/src/multi_layout.js index 5ff78213..6af5137b 100644 --- a/src/multi_layout.js +++ b/src/multi_layout.js @@ -1,36 +1,58 @@ dc_graph.multi_layout = function(id) { var _layoutId = id || uuid(); - var _engine = null; + var _engines = []; var _dispatch = d3.dispatch('tick', 'start', 'end'); var _flowLayout; var _nodes = {}, _edges = {}; + var onEnd = null; function init(options) { - // width, height, handleDisconnected, lengthStrategy, baseLength, flowLayout, tickSize - _engine = dc_graph.d3v4_force_layout(); - _engine.init(options); - _engine.on('end', function(nodes, edges) { - _dispatch['end'](nodes, edges); + // + _engines = []; + _engines.push(dc_graph.d3v4_force_layout()); + _engines[0].init(options); + // TODO use promises + onEnd = new Promise(function(resolve){ + _engines[0].on('end', function(nodes, edges) { + resolve([nodes, edges]); + }); }); + onEnd.then(function(args) { + _dispatch['end'](args[0], args[1]); + }); + //_engines[0].on('end', function(nodes, edges) { + //_dispatch['end'](nodes, edges); + //}); + + //_engines.push(dc_graph.cola_layout()); + //_engines[1].init(options); + //_engines[1].on('end', function(nodes, edges) { + //_dispatch['end'](nodes, edges); + //}); + } function data(nodes, edges, constraints) { // TODO creat a set of different layouts hierarchically _nodes = nodes; _edges = edges; - _engine.data({}, nodes, edges, constraints); + _engines[0].data({}, nodes, edges, constraints); } function start() { // TODO execute the layout algorithms bottom-up - _engine.start(); + for(var i = 0; i < _engines.length; i ++) { + _engines[i].start(); + } // get the positions of nodes // _d3cola._ndoes } function stop() { - if(_engine) - _engine.stop(); + for(var i = 0; i < _engines.length; i ++) { + if(_engines[i]) + _engines[i].stop(); + } } var graphviz = dc_graph.graphviz_attrs(), graphviz_keys = Object.keys(graphviz); From c6bd4826d19aaa43913edfbacdb57763066f7442 Mon Sep 17 00:00:00 2001 From: Zhe Wang Date: Mon, 30 Jul 2018 15:26:27 -0400 Subject: [PATCH 08/17] two level calculation --- src/multi_layout.js | 110 +++++++++++++++++++++++++++++++++----------- 1 file changed, 84 insertions(+), 26 deletions(-) diff --git a/src/multi_layout.js b/src/multi_layout.js index 6af5137b..0bf63d73 100644 --- a/src/multi_layout.js +++ b/src/multi_layout.js @@ -4,39 +4,98 @@ dc_graph.multi_layout = function(id) { var _dispatch = d3.dispatch('tick', 'start', 'end'); var _flowLayout; var _nodes = {}, _edges = {}; - var onEnd = null; + var _options = null; + var _level1 = []; + var _level2 = []; // TODO support multiple levels + //var _nodes = {}, _edges = {}; function init(options) { - // + console.log(options); + _options = options; + } + + function data(nodes, edges, constraints) { + + var subgroups = {}; + var nodeTypeMap = {}; + + for(var i = 0; i < nodes.length; i ++) { + var tp = engine.getNodeType(nodes[i]); + nodeTypeMap[nodes[i].dcg_nodeKey] = tp; + if( !(tp in subgroups)) { + subgroups[tp] = {'nodes':[], 'edges':[]}; + } + subgroups[tp].nodes.push(nodes[i]); + } + + for(var i = 0; i < edges.length; i ++) { + var sourceType = nodeTypeMap[edges[i].dcg_edgeSource]; + var targetType = nodeTypeMap[edges[i].dcg_edgeTarget]; + if( sourceType === targetType ) { + subgroups[sourceType].edges.push(edges[i]); + } + } + _engines = []; - _engines.push(dc_graph.d3v4_force_layout()); - _engines[0].init(options); - // TODO use promises - onEnd = new Promise(function(resolve){ - _engines[0].on('end', function(nodes, edges) { - resolve([nodes, edges]); + _level1 = []; + _level2 = []; + + var createOnEndPromise = function(_e, _key) { + var onEnd = new Promise(function(resolve){ + _e.on('end', function(nodes, edges) { + resolve([nodes, edges, _key]); + }); }); + return onEnd; + }; + + // create layout engine for each subgroups in level1 + for(var type in subgroups) { + var _e = dc_graph.d3v4_force_layout(); + _e.init(_options); + _e.data(null, subgroups[type].nodes, subgroups[type].edges, constraints); + _engines.push(_e); + + _level1.push(createOnEndPromise(_e, type)); + } + + // create layout engine for level2 + var _l2e = dc_graph.d3v4_force_layout(); + _l2e.init(_options); + _level2.push(createOnEndPromise(_l2e, 'level2')); + + Promise.all(_level1).then(function(results){ + var superNodes = []; + for(var i = 0; i < results.length; i ++) { + subgroups[results[i][2]].nodes = results[i][0]; + subgroups[results[i][2]].edges = results[i][1]; + var sn = calSuperNode(results[i][0]); + sn.dcg_nodeKey = 'superNode'+i; + superNodes.push(sn); + } + console.log(superNodes); + // create layout engine for super nodes + _l2e.data(null, superNodes, [], constraints); + _l2e.start(); + }); - onEnd.then(function(args) { - _dispatch['end'](args[0], args[1]); - }); - //_engines[0].on('end', function(nodes, edges) { - //_dispatch['end'](nodes, edges); - //}); - //_engines.push(dc_graph.cola_layout()); - //_engines[1].init(options); - //_engines[1].on('end', function(nodes, edges) { + Promise.all(_level2).then(function(results){ + console.log("level2 done"); + console.log(results); + // TODO add offsets to subgroups + // TODO assemble all nodes and edges //_dispatch['end'](nodes, edges); - //}); - + }); } - function data(nodes, edges, constraints) { - // TODO creat a set of different layouts hierarchically - _nodes = nodes; - _edges = edges; - _engines[0].data({}, nodes, edges, constraints); + function calSuperNode(nodes) { + var minX = Math.min.apply(null, nodes.map(function(e){return e.x})); + var maxX = Math.max.apply(null, nodes.map(function(e){return e.x})); + var minY = Math.min.apply(null, nodes.map(function(e){return e.y})); + var maxY = Math.max.apply(null, nodes.map(function(e){return e.y})); + var n = {x: (maxX+minX)/2, y: (minY+maxY)/2, r: Math.max((maxX-minX)/2, (maxY-minY)/2)}; + return n; } function start() { @@ -44,8 +103,6 @@ dc_graph.multi_layout = function(id) { for(var i = 0; i < _engines.length; i ++) { _engines[i].start(); } - // get the positions of nodes - // _d3cola._ndoes } function stop() { @@ -179,6 +236,7 @@ dc_graph.multi_layout = function(id) { setcolaGuides: undefined, extractNodeAttrs: function(_node, _attrs) {}, //add new attributes to _node from _attrs extractEdgeAttrs: function(_edge, _attrs) {}, + getNodeType: function(_node) {}, }); return engine; }; From bd698c20650e643a9df7b259d63cbe1d4984b951 Mon Sep 17 00:00:00 2001 From: Zhe Wang Date: Mon, 30 Jul 2018 15:29:27 -0400 Subject: [PATCH 09/17] rename to nested layout --- Gruntfile.js | 2 +- src/engine.js | 4 ++-- src/{multi_layout.js => nested_layout.js} | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename src/{multi_layout.js => nested_layout.js} (99%) diff --git a/Gruntfile.js b/Gruntfile.js index baae9f75..4a6d0800 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -345,7 +345,7 @@ module.exports.jsFiles = [ 'src/graphviz_layout.js', 'src/d3_force_layout.js', 'src/d3v4_force_layout.js', - 'src/multi_layout.js', + 'src/nested_layout.js', 'src/flexbox_layout.js', 'src/manual_layout.js', 'src/place_ports.js', diff --git a/src/engine.js b/src/engine.js index 63298984..e6456c57 100644 --- a/src/engine.js +++ b/src/engine.js @@ -48,9 +48,9 @@ dc_graph._engines = [ } }, { - name: 'multiAlg', + name: 'nested', instantiate: function() { - return dc_graph.multi_layout(); + return dc_graph.nested_layout(); } }, ]; diff --git a/src/multi_layout.js b/src/nested_layout.js similarity index 99% rename from src/multi_layout.js rename to src/nested_layout.js index 0bf63d73..d8d1aa89 100644 --- a/src/multi_layout.js +++ b/src/nested_layout.js @@ -1,4 +1,4 @@ -dc_graph.multi_layout = function(id) { +dc_graph.nested_layout = function(id) { var _layoutId = id || uuid(); var _engines = []; var _dispatch = d3.dispatch('tick', 'start', 'end'); From b26c429860b890c3c648e7df9880a7e53378df3f Mon Sep 17 00:00:00 2001 From: Zhe Wang Date: Mon, 30 Jul 2018 15:37:53 -0400 Subject: [PATCH 10/17] clean code --- src/nested_layout.js | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/nested_layout.js b/src/nested_layout.js index d8d1aa89..3e244617 100644 --- a/src/nested_layout.js +++ b/src/nested_layout.js @@ -1,12 +1,13 @@ dc_graph.nested_layout = function(id) { var _layoutId = id || uuid(); - var _engines = []; var _dispatch = d3.dispatch('tick', 'start', 'end'); var _flowLayout; var _nodes = {}, _edges = {}; var _options = null; - var _level1 = []; - var _level2 = []; // TODO support multiple levels + var _engines_l1 = []; // level1 engines + var _engines_l2 = []; // level2 engines + var _level1 = []; // level1 promises + var _level2 = []; // level2 promises //var _nodes = {}, _edges = {}; function init(options) { @@ -36,7 +37,8 @@ dc_graph.nested_layout = function(id) { } } - _engines = []; + _engines_l1 = []; + _engines_l2 = []; _level1 = []; _level2 = []; @@ -54,14 +56,14 @@ dc_graph.nested_layout = function(id) { var _e = dc_graph.d3v4_force_layout(); _e.init(_options); _e.data(null, subgroups[type].nodes, subgroups[type].edges, constraints); - _engines.push(_e); - + _engines_l1.push(_e); _level1.push(createOnEndPromise(_e, type)); } // create layout engine for level2 var _l2e = dc_graph.d3v4_force_layout(); _l2e.init(_options); + _engines_l2.push(_l2e); _level2.push(createOnEndPromise(_l2e, 'level2')); Promise.all(_level1).then(function(results){ @@ -74,9 +76,12 @@ dc_graph.nested_layout = function(id) { superNodes.push(sn); } console.log(superNodes); - // create layout engine for super nodes - _l2e.data(null, superNodes, [], constraints); - _l2e.start(); + + // now we have data for higher level layouts + _engines_l2[0].data(null, superNodes, [], constraints); + for(var i = 0; i < _engines_l2.length; i ++) { + _engines_l2[i].start(); + } }); @@ -99,17 +104,21 @@ dc_graph.nested_layout = function(id) { } function start() { - // TODO execute the layout algorithms bottom-up - for(var i = 0; i < _engines.length; i ++) { - _engines[i].start(); + // execute the layout algorithms + for(var i = 0; i < _engines_l1.length; i ++) { + _engines_l1[i].start(); } } function stop() { + var stopEngines = function(_engines) { for(var i = 0; i < _engines.length; i ++) { if(_engines[i]) _engines[i].stop(); } + } + stopEngines(_engines_l1); + stopEngines(_engines_l2); } var graphviz = dc_graph.graphviz_attrs(), graphviz_keys = Object.keys(graphviz); @@ -241,4 +250,4 @@ dc_graph.nested_layout = function(id) { return engine; }; -dc_graph.multi_layout.scripts = ['d3.js', 'cola.js', 'setcola.js', 'd3v4-force.js']; +dc_graph.nested_layout.scripts = ['d3.js', 'cola.js', 'setcola.js', 'd3v4-force.js']; From dadc143b79d4f713394c0b0c32e088b3c02168c4 Mon Sep 17 00:00:00 2001 From: Zhe Wang Date: Wed, 1 Aug 2018 17:02:01 -0400 Subject: [PATCH 11/17] add offset to nodes and assemble each group --- src/nested_layout.js | 44 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/src/nested_layout.js b/src/nested_layout.js index 3e244617..284099da 100644 --- a/src/nested_layout.js +++ b/src/nested_layout.js @@ -70,12 +70,11 @@ dc_graph.nested_layout = function(id) { var superNodes = []; for(var i = 0; i < results.length; i ++) { subgroups[results[i][2]].nodes = results[i][0]; - subgroups[results[i][2]].edges = results[i][1]; + //subgroups[results[i][2]].edges = results[i][1]; var sn = calSuperNode(results[i][0]); - sn.dcg_nodeKey = 'superNode'+i; + sn.dcg_nodeKey = results[i][2]; superNodes.push(sn); } - console.log(superNodes); // now we have data for higher level layouts _engines_l2[0].data(null, superNodes, [], constraints); @@ -86,11 +85,30 @@ dc_graph.nested_layout = function(id) { }); Promise.all(_level2).then(function(results){ - console.log("level2 done"); - console.log(results); - // TODO add offsets to subgroups - // TODO assemble all nodes and edges - //_dispatch['end'](nodes, edges); + // add offsets to subgroups + // only support one higher level + for(var level = 0; level < results.length; level++) { + for(var i = 0; i < results[level][0].length; i ++) { + var sn = results[level][0][i]; + var groupName = sn.dcg_nodeKey; + var offX = sn.x; + var offY = sn.y; + + for(var j = 0; j < subgroups[groupName].nodes.length; j ++) { + subgroups[groupName].nodes[j].x += offX; + subgroups[groupName].nodes[j].y += offY; + } + } + } + + // assemble all nodes and edges + console.log(subgroups); + var allNodes = []; + for(var key in subgroups) { + allNodes = allNodes.concat(subgroups[key].nodes); + } + + _dispatch['end'](allNodes, edges); }); } @@ -99,7 +117,15 @@ dc_graph.nested_layout = function(id) { var maxX = Math.max.apply(null, nodes.map(function(e){return e.x})); var minY = Math.min.apply(null, nodes.map(function(e){return e.y})); var maxY = Math.max.apply(null, nodes.map(function(e){return e.y})); - var n = {x: (maxX+minX)/2, y: (minY+maxY)/2, r: Math.max((maxX-minX)/2, (maxY-minY)/2)}; + // center nodes + var centerX = (maxX+minX)/2; + var centerY = (maxY+minY)/2; + for(var i = 0; i < nodes.length; i ++) { + nodes[i].x -= centerX; + nodes[i].y -= centerY; + } + + var n = {width: maxX-minX, height: maxY-minY}; return n; } From 182246347ca1ca48ebafb0d3946c2ca7c49f4a8f Mon Sep 17 00:00:00 2001 From: Zhe Wang Date: Thu, 2 Aug 2018 15:32:27 -0400 Subject: [PATCH 12/17] nested cola and d3force works --- src/nested_layout.js | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/nested_layout.js b/src/nested_layout.js index 284099da..ed54fae3 100644 --- a/src/nested_layout.js +++ b/src/nested_layout.js @@ -11,14 +11,15 @@ dc_graph.nested_layout = function(id) { //var _nodes = {}, _edges = {}; function init(options) { - console.log(options); _options = options; + console.log('applying nested layout'); } function data(nodes, edges, constraints) { var subgroups = {}; var nodeTypeMap = {}; + var superEdges = []; for(var i = 0; i < nodes.length; i ++) { var tp = engine.getNodeType(nodes[i]); @@ -34,6 +35,13 @@ dc_graph.nested_layout = function(id) { var targetType = nodeTypeMap[edges[i].dcg_edgeTarget]; if( sourceType === targetType ) { subgroups[sourceType].edges.push(edges[i]); + } else { + superEdges.push({ + dcg_edgeKey: edges[i].dcg_edgeKey, + dcg_edgeSource: sourceType, + dcg_edgeTarget: targetType, + dcg_edgeLength: edges[i].dcg_edgeLength, + }); } } @@ -53,7 +61,8 @@ dc_graph.nested_layout = function(id) { // create layout engine for each subgroups in level1 for(var type in subgroups) { - var _e = dc_graph.d3v4_force_layout(); + //var _e = dc_graph.d3v4_force_layout(); + var _e = dc_graph.spawn_engine(engine.nestedSpec.level1, {}, false); _e.init(_options); _e.data(null, subgroups[type].nodes, subgroups[type].edges, constraints); _engines_l1.push(_e); @@ -61,7 +70,16 @@ dc_graph.nested_layout = function(id) { } // create layout engine for level2 - var _l2e = dc_graph.d3v4_force_layout(); + var _l2e = dc_graph.spawn_engine(engine.nestedSpec.level2, {}, false); + if(engine.nestedSpec.level2 === 'cola') { + _l2e.setcolaSpec = engine.setcolaSpec; + _l2e.setcolaGuides = engine.setcolaGuides; + _l2e.extractNodeAttrs = engine.extractNodeAttrs; + _l2e.extractEdgeAttrs = engine.extractEdgeAttrs; + _l2e.getNodeType = engine.getNodeType; + _l2e.lengthStrategy = engine.lengthStrategy; + } + _l2e.init(_options); _engines_l2.push(_l2e); _level2.push(createOnEndPromise(_l2e, 'level2')); @@ -77,7 +95,7 @@ dc_graph.nested_layout = function(id) { } // now we have data for higher level layouts - _engines_l2[0].data(null, superNodes, [], constraints); + _engines_l2[0].data(null, superNodes, superEdges, constraints); for(var i = 0; i < _engines_l2.length; i ++) { _engines_l2[i].start(); } @@ -102,7 +120,6 @@ dc_graph.nested_layout = function(id) { } // assemble all nodes and edges - console.log(subgroups); var allNodes = []; for(var key in subgroups) { allNodes = allNodes.concat(subgroups[key].nodes); @@ -125,7 +142,8 @@ dc_graph.nested_layout = function(id) { nodes[i].y -= centerY; } - var n = {width: maxX-minX, height: maxY-minY}; + var n = {r: Math.max((maxX-minX)/2, (maxY-minY)/2)}; + //var n = {}; return n; } @@ -272,6 +290,7 @@ dc_graph.nested_layout = function(id) { extractNodeAttrs: function(_node, _attrs) {}, //add new attributes to _node from _attrs extractEdgeAttrs: function(_edge, _attrs) {}, getNodeType: function(_node) {}, + nestedSpec: undefined, }); return engine; }; From 3769119ddfa6894f26a5b9865c4f5be5b17f374d Mon Sep 17 00:00:00 2001 From: Zhe Wang Date: Thu, 2 Aug 2018 16:22:33 -0400 Subject: [PATCH 13/17] dynamically change collision radius --- src/nested_layout.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/nested_layout.js b/src/nested_layout.js index ed54fae3..e6b29ea6 100644 --- a/src/nested_layout.js +++ b/src/nested_layout.js @@ -79,23 +79,32 @@ dc_graph.nested_layout = function(id) { _l2e.getNodeType = engine.getNodeType; _l2e.lengthStrategy = engine.lengthStrategy; } - - _l2e.init(_options); _engines_l2.push(_l2e); _level2.push(createOnEndPromise(_l2e, 'level2')); Promise.all(_level1).then(function(results){ var superNodes = []; + var maxRadius = 0; for(var i = 0; i < results.length; i ++) { subgroups[results[i][2]].nodes = results[i][0]; //subgroups[results[i][2]].edges = results[i][1]; var sn = calSuperNode(results[i][0]); sn.dcg_nodeKey = results[i][2]; superNodes.push(sn); + maxRadius = Math.max(maxRadius, sn.r); + } + if(engine.nestedSpec.level2 === 'd3v4force') { + // TODO set accessor for each super nodes + //_options.radiusAccessor = function(e){ + //return e.r; + //}; + _options.collisionRadius = maxRadius+10; } + _engines_l2[0].init(_options); // now we have data for higher level layouts _engines_l2[0].data(null, superNodes, superEdges, constraints); + for(var i = 0; i < _engines_l2.length; i ++) { _engines_l2[i].start(); } From ce73f37116cf2828337d9d7fa5348eccafccfebf Mon Sep 17 00:00:00 2001 From: Zhe Wang Date: Fri, 3 Aug 2018 10:59:56 -0400 Subject: [PATCH 14/17] set radius accessor for super nodes --- src/d3v4_force_layout.js | 8 +++++++- src/nested_layout.js | 9 ++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/d3v4_force_layout.js b/src/d3v4_force_layout.js index 13975f19..3e256637 100644 --- a/src/d3v4_force_layout.js +++ b/src/d3v4_force_layout.js @@ -19,12 +19,17 @@ dc_graph.d3v4_force_layout = function(id) { function init(options) { _options = options; + var collideFunc = d3v4.forceCollide(_options.collisionRadius); + if(_options.radiusAccessor) { + collideFunc = d3v4.forceCollide().radius(_options.radiusAccessor); + } + _simulation = d3v4.forceSimulation() .force('link', d3v4.forceLink()) .force('center', d3v4.forceCenter(options.width / 2, options.height / 2)) .force('gravityX', d3v4.forceX(options.width / 2).strength(_options.gravityStrength)) .force('gravityY', d3v4.forceY(options.height / 2).strength(_options.gravityStrength)) - .force('collision', d3v4.forceCollide(_options.collisionRadius)) + .force('collision', collideFunc) .force('charge', d3v4.forceManyBody()) .stop(); } @@ -51,6 +56,7 @@ dc_graph.d3v4_force_layout = function(id) { v1.width = v.width; v1.height = v.height; v1.id = v.dcg_nodeKey; + v1.r = v.r; if(v.dcg_nodeFixed) { v1.fx = v.dcg_nodeFixed.x; v1.fy = v.dcg_nodeFixed.y; diff --git a/src/nested_layout.js b/src/nested_layout.js index e6b29ea6..8cd6530a 100644 --- a/src/nested_layout.js +++ b/src/nested_layout.js @@ -94,11 +94,10 @@ dc_graph.nested_layout = function(id) { maxRadius = Math.max(maxRadius, sn.r); } if(engine.nestedSpec.level2 === 'd3v4force') { - // TODO set accessor for each super nodes - //_options.radiusAccessor = function(e){ - //return e.r; - //}; - _options.collisionRadius = maxRadius+10; + // set accessor for each super nodes + _options.radiusAccessor = function(e){ + return e.r+50; + }; } _engines_l2[0].init(_options); From d6d8d09ef85b8172c6b3882c822aaf29a5c83ad5 Mon Sep 17 00:00:00 2001 From: Zhe Wang Date: Fri, 3 Aug 2018 13:49:52 -0400 Subject: [PATCH 15/17] move config to user side --- src/nested_layout.js | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/nested_layout.js b/src/nested_layout.js index 8cd6530a..1bd428d3 100644 --- a/src/nested_layout.js +++ b/src/nested_layout.js @@ -62,7 +62,11 @@ dc_graph.nested_layout = function(id) { // create layout engine for each subgroups in level1 for(var type in subgroups) { //var _e = dc_graph.d3v4_force_layout(); - var _e = dc_graph.spawn_engine(engine.nestedSpec.level1, {}, false); + var _e = dc_graph.spawn_engine(engine.nestedSpec.level1.engine, {}, false); + if(engine.nestedSpec.level1.engine == 'cola') { + _e.setcolaSpec = engine.nestedSpec.level1.setcolaSpec || undefined; + _e.setcolaGuides = engine.nestedSpec.level1.setcolaGuides || []; + } _e.init(_options); _e.data(null, subgroups[type].nodes, subgroups[type].edges, constraints); _engines_l1.push(_e); @@ -70,14 +74,13 @@ dc_graph.nested_layout = function(id) { } // create layout engine for level2 - var _l2e = dc_graph.spawn_engine(engine.nestedSpec.level2, {}, false); - if(engine.nestedSpec.level2 === 'cola') { - _l2e.setcolaSpec = engine.setcolaSpec; - _l2e.setcolaGuides = engine.setcolaGuides; - _l2e.extractNodeAttrs = engine.extractNodeAttrs; - _l2e.extractEdgeAttrs = engine.extractEdgeAttrs; - _l2e.getNodeType = engine.getNodeType; - _l2e.lengthStrategy = engine.lengthStrategy; + var _l2e = dc_graph.spawn_engine(engine.nestedSpec.level2.engine, {}, false); + if(engine.nestedSpec.level2.engine === 'cola') { + // TODO generate secolaSpec + _l2e.setcolaSpec = engine.nestedSpec.level2.setcolaSpec; + _l2e.setcolaGuides = engine.nestedSpec.level2.setcolaGuides || []; + _l2e.getNodeType = engine.nestedSpec.level2.getNodeType; + //_l2e.lengthStrategy = engine.lengthStrategy; } _engines_l2.push(_l2e); _level2.push(createOnEndPromise(_l2e, 'level2')); @@ -93,10 +96,10 @@ dc_graph.nested_layout = function(id) { superNodes.push(sn); maxRadius = Math.max(maxRadius, sn.r); } - if(engine.nestedSpec.level2 === 'd3v4force') { + if(engine.nestedSpec.level2.engine === 'd3v4force') { // set accessor for each super nodes _options.radiusAccessor = function(e){ - return e.r+50; + return e.r + engine.nestedSpec.level2.collisionMargin || 0; }; } From 24d02e95ec2f47cd13082a162d210229ec168296 Mon Sep 17 00:00:00 2001 From: Zhe Wang Date: Fri, 3 Aug 2018 14:14:28 -0400 Subject: [PATCH 16/17] support per type engine at level1 --- src/nested_layout.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/nested_layout.js b/src/nested_layout.js index 1bd428d3..42b925e6 100644 --- a/src/nested_layout.js +++ b/src/nested_layout.js @@ -62,10 +62,14 @@ dc_graph.nested_layout = function(id) { // create layout engine for each subgroups in level1 for(var type in subgroups) { //var _e = dc_graph.d3v4_force_layout(); - var _e = dc_graph.spawn_engine(engine.nestedSpec.level1.engine, {}, false); - if(engine.nestedSpec.level1.engine == 'cola') { - _e.setcolaSpec = engine.nestedSpec.level1.setcolaSpec || undefined; - _e.setcolaGuides = engine.nestedSpec.level1.setcolaGuides || []; + var current_engine = engine.nestedSpec.level1.default_engine; + if(engine.nestedSpec.level1.engines && type in engine.nestedSpec.level1.engines) { + current_engine = engine.nestedSpec.level1.engines[type]; + } + var _e = dc_graph.spawn_engine(current_engine.engine, {}, false); + if(current_engine.engine == 'cola') { + _e.setcolaSpec = current_engine.setcolaSpec || undefined; + _e.setcolaGuides = current_engine.setcolaGuides || []; } _e.init(_options); _e.data(null, subgroups[type].nodes, subgroups[type].edges, constraints); From 3d1896e6bac1d5f2c2e420e42b4fec88c555fd3d Mon Sep 17 00:00:00 2001 From: Zhe Wang Date: Wed, 8 Aug 2018 13:44:51 -0400 Subject: [PATCH 17/17] save current changes for second pass --- src/d3v4_force_layout.js | 1 + src/nested_layout.js | 232 ++++++++++++++++++++++++++++++++------- 2 files changed, 194 insertions(+), 39 deletions(-) diff --git a/src/d3v4_force_layout.js b/src/d3v4_force_layout.js index 3e256637..ce4d24e7 100644 --- a/src/d3v4_force_layout.js +++ b/src/d3v4_force_layout.js @@ -57,6 +57,7 @@ dc_graph.d3v4_force_layout = function(id) { v1.height = v.height; v1.id = v.dcg_nodeKey; v1.r = v.r; + v1.attrs = v.attrs; if(v.dcg_nodeFixed) { v1.fx = v.dcg_nodeFixed.x; v1.fy = v.dcg_nodeFixed.y; diff --git a/src/nested_layout.js b/src/nested_layout.js index 42b925e6..72db682b 100644 --- a/src/nested_layout.js +++ b/src/nested_layout.js @@ -4,18 +4,52 @@ dc_graph.nested_layout = function(id) { var _flowLayout; var _nodes = {}, _edges = {}; var _options = null; - var _engines_l1 = []; // level1 engines + var _engines_l1 = {}; // level1 engines var _engines_l2 = []; // level2 engines + var _engines_l1_p2 = {}; // level1 engines + var _engines_l2_p2 = []; // level2 engines var _level1 = []; // level1 promises var _level2 = []; // level2 promises - //var _nodes = {}, _edges = {}; + var _level1_p2 = []; // level1 promises + var _level2_p2 = []; // level2 promises function init(options) { _options = options; console.log('applying nested layout'); } - function data(nodes, edges, constraints) { + function createEngines(subgroups, constraints) { + // create layout engine for each subgroups in level1 + for(var type in subgroups) { + //var _e = dc_graph.d3v4_force_layout(); + var current_engine = engine.nestedSpec.level1.default_engine; + if(engine.nestedSpec.level1.engines && type in engine.nestedSpec.level1.engines) { + current_engine = engine.nestedSpec.level1.engines[type]; + } + var _e = dc_graph.spawn_engine(current_engine.engine, {}, false); + if(current_engine.engine == 'cola') { + _e.setcolaSpec = current_engine.setcolaSpec || undefined; + _e.setcolaGuides = current_engine.setcolaGuides || []; + } + _e.init(_options); + _engines_l1[type] = _e; + _engines_l1_p2[type] = Object.assign(_e); + } + + // create layout engine for level2 + var _l2e = dc_graph.spawn_engine(engine.nestedSpec.level2.engine, {}, false); + if(engine.nestedSpec.level2.engine === 'cola') { + // TODO generate secolaSpec + _l2e.setcolaSpec = engine.nestedSpec.level2.setcolaSpec; + _l2e.setcolaGuides = engine.nestedSpec.level2.setcolaGuides || []; + _l2e.getNodeType = engine.nestedSpec.level2.getNodeType; + //_l2e.lengthStrategy = engine.lengthStrategy; + } + _engines_l2.push(_l2e); + _engines_l2_p2.push(Object.assign(_l2e)); + } + + function runLayout(nodes, edges, constraints) { var subgroups = {}; var nodeTypeMap = {}; @@ -45,11 +79,6 @@ dc_graph.nested_layout = function(id) { } } - _engines_l1 = []; - _engines_l2 = []; - _level1 = []; - _level2 = []; - var createOnEndPromise = function(_e, _key) { var onEnd = new Promise(function(resolve){ _e.on('end', function(nodes, edges) { @@ -59,35 +88,16 @@ dc_graph.nested_layout = function(id) { return onEnd; }; - // create layout engine for each subgroups in level1 for(var type in subgroups) { - //var _e = dc_graph.d3v4_force_layout(); - var current_engine = engine.nestedSpec.level1.default_engine; - if(engine.nestedSpec.level1.engines && type in engine.nestedSpec.level1.engines) { - current_engine = engine.nestedSpec.level1.engines[type]; - } - var _e = dc_graph.spawn_engine(current_engine.engine, {}, false); - if(current_engine.engine == 'cola') { - _e.setcolaSpec = current_engine.setcolaSpec || undefined; - _e.setcolaGuides = current_engine.setcolaGuides || []; - } - _e.init(_options); - _e.data(null, subgroups[type].nodes, subgroups[type].edges, constraints); - _engines_l1.push(_e); - _level1.push(createOnEndPromise(_e, type)); + _engines_l1[type].data(null, subgroups[type].nodes, subgroups[type].edges, constraints); + _level1.push(createOnEndPromise(_engines_l1[type], type)); + _level1_p2.push(createOnEndPromise(_engines_l1_p2[type], type)); } - // create layout engine for level2 - var _l2e = dc_graph.spawn_engine(engine.nestedSpec.level2.engine, {}, false); - if(engine.nestedSpec.level2.engine === 'cola') { - // TODO generate secolaSpec - _l2e.setcolaSpec = engine.nestedSpec.level2.setcolaSpec; - _l2e.setcolaGuides = engine.nestedSpec.level2.setcolaGuides || []; - _l2e.getNodeType = engine.nestedSpec.level2.getNodeType; - //_l2e.lengthStrategy = engine.lengthStrategy; + for(var i = 0; i < _engines_l2.length; i ++) { + _level2.push(createOnEndPromise(_engines_l2[i], 'level2')); + _level2_p2.push(createOnEndPromise(_engines_l2_p2[i], 'level2')); } - _engines_l2.push(_l2e); - _level2.push(createOnEndPromise(_l2e, 'level2')); Promise.all(_level1).then(function(results){ var superNodes = []; @@ -140,15 +150,158 @@ dc_graph.nested_layout = function(id) { allNodes = allNodes.concat(subgroups[key].nodes); } + secondPass(allNodes, edges, constraints); + + for(var key in _engines_l1_p2) { + _engines_l1_p2[key].start(); + } + //_dispatch['end'](allNodes, edges); + }); + + Promise.all(_level1_p2).then(function(results) { + console.log('level1 p2 finished'); + console.log(results); + var superNodes = []; + var maxRadius = 0; + for(var i = 0; i < results.length; i ++) { + subgroups[results[i][2]].nodes = results[i][0]; + //subgroups[results[i][2]].edges = results[i][1]; + var sn = calSuperNode(results[i][0]); + sn.dcg_nodeKey = results[i][2]; + superNodes.push(sn); + maxRadius = Math.max(maxRadius, sn.r); + } + if(engine.nestedSpec.level2.engine === 'd3v4force') { + // set accessor for each super nodes + _options.radiusAccessor = function(e){ + return e.r + engine.nestedSpec.level2.collisionMargin || 0; + }; + } + + _engines_l2_p2[0].init(_options); + // now we have data for higher level layouts + _engines_l2_p2[0].data(null, superNodes, superEdges, constraints); + + for(var i = 0; i < _engines_l2.length; i ++) { + _engines_l2_p2[i].start(); + } + + }); + + Promise.all(_level2_p2).then(function(results){ + console.log('level2 p2 finished'); + for(var level = 0; level < results.length; level++) { + for(var i = 0; i < results[level][0].length; i ++) { + var sn = results[level][0][i]; + var groupName = sn.dcg_nodeKey; + var offX = sn.x; + var offY = sn.y; + + for(var j = 0; j < subgroups[groupName].nodes.length; j ++) { + subgroups[groupName].nodes[j].x += offX; + subgroups[groupName].nodes[j].y += offY; + } + } + } + + // assemble all nodes and edges + var allNodes = []; + for(var key in subgroups) { + allNodes = allNodes.concat(subgroups[key].nodes); + } + _dispatch['end'](allNodes, edges); }); } + function secondPass(nodes, edges, constraints) { + _level1_p2 = []; // level1 promises + _level2_p2 = []; // level2 promises + + var subgroups = {}; + var nodeTypeMap = {}; + var nodeMap = {}; + var superEdges = []; + + for(var i = 0; i < nodes.length; i ++) { + var tp = engine.getNodeType(nodes[i]); + nodeTypeMap[nodes[i].dcg_nodeKey] = tp; + nodeMap[nodes[i].dcg_nodeKey] = nodes[i]; + if( !(tp in subgroups)) { + subgroups[tp] = {'nodes':[], 'edges':[]}; + } + subgroups[tp].nodes.push(nodes[i]); + } + + for(var i = 0; i < edges.length; i ++) { + var sourceType = nodeTypeMap[edges[i].dcg_edgeSource]; + var targetType = nodeTypeMap[edges[i].dcg_edgeTarget]; + if( sourceType === targetType ) { + subgroups[sourceType].edges.push(edges[i]); + } else { + // insert virtual nodes + var sourceNode = nodeMap[edges[i].dcg_edgeSource]; + var targetNode = nodeMap[edges[i].dcg_edgeTarget]; + + var sourceVirtualNode = Object.assign( + sourceNode, + { + 'virtual': true, + 'dcg_nodeFixed': {'x': sourceNode.x, 'y': sourceNode.y} + } + ); + + var targetVirtualNode = Object.assign( + targetNode, + { + 'virtual': true, + 'dcg_nodeFixed': {'x': targetNode.x, 'y': targetNode.y} + } + ); + + subgroups[sourceType].nodes.push(targetVirtualNode); + subgroups[sourceType].edges.push(edges[i]); + + subgroups[targetType].nodes.push(sourceVirtualNode); + subgroups[targetType].edges.push(edges[i]); + + superEdges.push({ + dcg_edgeKey: edges[i].dcg_edgeKey, + dcg_edgeSource: sourceType, + dcg_edgeTarget: targetType, + dcg_edgeLength: edges[i].dcg_edgeLength, + }); + } + } + + for(var type in subgroups) { + _engines_l1_p2[type].data(null, subgroups[type].nodes, subgroups[type].edges, constraints); + } + } + + function data(nodes, edges, constraints) { + // reset engines + _engines_l1 = {}; // level1 engines + _engines_l2 = []; // level2 engines + _level1 = []; // level1 promises + _level2 = []; // level2 promises + + var groups = {}; + for(var i = 0; i < nodes.length; i ++) { + var tp = engine.getNodeType(nodes[i]); + if( !(tp in groups)) { + groups[tp] = true; + } + } + createEngines(groups, constraints); + runLayout(nodes, edges, constraints); + } + function calSuperNode(nodes) { - var minX = Math.min.apply(null, nodes.map(function(e){return e.x})); - var maxX = Math.max.apply(null, nodes.map(function(e){return e.x})); - var minY = Math.min.apply(null, nodes.map(function(e){return e.y})); - var maxY = Math.max.apply(null, nodes.map(function(e){return e.y})); + var minX = Math.min.apply(null, nodes.filter(function(d){return d.virtual !== true}).map(function(e){return e.x})); + var maxX = Math.max.apply(null, nodes.filter(function(d){return d.virtual !== true}).map(function(e){return e.x})); + var minY = Math.min.apply(null, nodes.filter(function(d){return d.virtual !== true}).map(function(e){return e.y})); + var maxY = Math.max.apply(null, nodes.filter(function(d){return d.virtual !== true}).map(function(e){return e.y})); // center nodes var centerX = (maxX+minX)/2; var centerY = (maxY+minY)/2; @@ -164,9 +317,10 @@ dc_graph.nested_layout = function(id) { function start() { // execute the layout algorithms - for(var i = 0; i < _engines_l1.length; i ++) { - _engines_l1[i].start(); + for(var key in _engines_l1) { + _engines_l1[key].start(); } + } function stop() {