/**
* `dc_graph.graphviz_layout` is an adaptor for viz.js (graphviz) layouts in dc.graph.js
*
* In addition to the below layout attributes, `graphviz_layout` also implements the attributes from
* {@link dc_graph.graphviz_attrs graphviz_attrs}
* @class graphviz_layout
* @memberof dc_graph
* @param {String} [id=uuid()] - Unique identifier
* @return {dc_graph.graphviz_layout}
**/
dc_graph.graphviz_layout = function(id, layout, server) {
var _layoutId = id || uuid();
var _dispatch = d3.dispatch('tick', 'start', 'end');
var _dotInput, _dotString;
function init(options) {
}
function encode_name(name) {
return name.replace(/^%/, '%');
}
function decode_name(name) {
return name.replace(/^%/, '%');
}
function stringize_property(prop, value) {
return [prop, '"' + value + '"'].join('=');
}
function stringize_properties(props) {
return '[' + props.join(', ') + ']';
}
function data(nodes, edges, clusters) {
if(_dotInput) {
_dotString = _dotInput;
return;
}
var lines = [];
var directed = layout !== 'neato';
lines.push((directed ? 'digraph' : 'graph') + ' g {');
lines.push('graph ' + stringize_properties([
stringize_property('nodesep', graphviz.nodesep()/72),
stringize_property('ranksep', graphviz.ranksep()/72),
stringize_property('rankdir', graphviz.rankdir())
]));
var cluster_nodes = {};
nodes.forEach(function(n) {
var cl = n.dcg_nodeParentCluster;
if(cl) {
cluster_nodes[cl] = cluster_nodes[cl] || [];
cluster_nodes[cl].push(n.dcg_nodeKey);
}
});
var cluster_children = {}, tops = [];
clusters.forEach(function(c) {
var p = c.dcg_clusterParent;
if(p) {
cluster_children[p] = cluster_children[p] || [];
cluster_children[p].push(c.dcg_clusterKey);
} else tops.push(c.dcg_clusterKey);
});
function print_subgraph(i, c) {
var indent = ' '.repeat(i*2);
lines.push(indent + 'subgraph "' + c + '" {');
if(cluster_children[c])
cluster_children[c].forEach(print_subgraph.bind(null, i+1));
if(cluster_nodes[c])
lines.push(indent + ' ' + cluster_nodes[c].map(function (s) { return JSON.stringify(s) }).join(' '));
lines.push(indent + '}');
}
tops.forEach(print_subgraph.bind(null, 1));
lines = lines.concat(nodes.map(function(v) {
var props = [
stringize_property('width', v.width/72),
stringize_property('height', v.height/72),
stringize_property('fixedsize', 'shape'),
stringize_property('shape', v.abstract.shape)
];
if(v.dcg_nodeFixed)
props.push(stringize_property('pos', [
v.dcg_nodeFixed.x,
1000-v.dcg_nodeFixed.y
].join(',')));
return ' "' + encode_name(v.dcg_nodeKey) + '" ' + stringize_properties(props);
}));
lines = lines.concat(edges.map(function(e) {
return ' "' + encode_name(e.dcg_edgeSource) + (directed ? '" -> "' : '" -- "') +
encode_name(e.dcg_edgeTarget) + '" ' + stringize_properties([
stringize_property('id', encode_name(e.dcg_edgeKey)),
stringize_property('arrowhead', 'none'),
stringize_property('arrowtail', 'none')
]);
}));
lines.push('}');
lines.push('');
_dotString = lines.join('\n');
}
function process_response(error, result) {
if(error) {
console.warn("graphviz layout failed: ", error);
return;
}
_dispatch.start();
var bb = result.bb.split(',').map(function(x) { return +x; });
var nodes = (result.objects || []).filter(function(n) {
return n.pos; // remove non-nodes like clusters
}).map(function(n) {
var pos = n.pos.split(',');
if(isNaN(pos[0]) || isNaN(pos[1])) {
console.warn('got a NaN position from graphviz');
pos[0] = pos[1] = 0;
}
return {
dcg_nodeKey: decode_name(n.name),
x: +pos[0],
y: bb[3] - pos[1]
};
});
var clusters = (result.objects || []).filter(function(n) {
return /^cluster/.test(n.name) && n.bb;
});
clusters.forEach(function(c) {
c.dcg_clusterKey = c.name;
// gv: llx, lly, urx, ury, up-positive
var cbb = c.bb.split(',').map(function(s) { return +s; });
c.bounds = {left: cbb[0], top: bb[3] - cbb[3],
right: cbb[2], bottom: bb[3] - cbb[1]};
});
var edges = (result.edges || []).map(function(e) {
var e2 = {
dcg_edgeKey: decode_name(e.id || 'n' + e._gvid)
};
if(e._draw_) {
var directive = e._draw_.find(function(d) { return d.op && d.points; });
e2.points = directive.points.map(function(p) { return {x: p[0], y: bb[3] - p[1]}; });
}
return e2;
});
_dispatch.end(nodes, edges, clusters);
}
function start() {
if(server) {
d3.json(server)
.header("Content-type", "application/x-www-form-urlencoded")
.post('layouttool=' + layout + '&' + encodeURIComponent(_dotString), process_response);
}
else {
var result = Viz(_dotString, {format: 'json', engine: layout, totalMemory: 1 << 25});
result = JSON.parse(result);
process_response(null, result);
}
}
function stop() {
}
var graphviz = dc_graph.graphviz_attrs(), graphviz_keys = Object.keys(graphviz);
return Object.assign(graphviz, {
layoutAlgorithm: function() {
return layout;
},
layoutId: function() {
return _layoutId;
},
supportsWebworker: function() {
return false;
},
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, clusters) {
data(nodes, edges, clusters);
},
dotInput: function(text) {
_dotInput = text;
return this;
},
start: function() {
start();
},
stop: function() {
stop();
},
optionNames: function() {
return graphviz_keys;
},
populateLayoutNode: function() {},
populateLayoutEdge: function() {}
});
}