blob: aeb014db90b07f8573bf4815601f20ced7009266 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function () {
'use strict';
var entitiesListModule = angular.module('app.directives.lineage-graph', ['app.services' ]);
entitiesListModule.controller('LineageGraphCtrl', ['$scope', 'Falcon', 'X2jsService', '$window', 'EncodeService',
function($scope, Falcon, X2jsService, $window, encodeService) {
}]);
entitiesListModule.directive('lineageGraph', ["$timeout", 'Falcon', '$filter', function($timeout, Falcon, $filter) {
return {
scope: {
type: "=",
name: "=",
instance: "=",
start: "=",
end: "="
},
controller: 'LineageGraphCtrl',
restrict: "EA",
templateUrl: 'html/directives/lineageGraphDv.html',
link: function (scope, element) {
var CIRCLE_RADIUS = 12, RANK_SEPARATION = 120, LABEL_WIDTH = 120, LABEL_HEIGHT = 80, LABEL_PADDING = 20;
var PREFIX = '/api/graphs/lineage';
var data = {
queue : {},
nodes : {},
edges : {},
active_node_id : null
};
function process_queue(done_cb) {
var q = data.queue;
if (q.length === 0) {
done_cb();
return;
}
function filter_node(n) {
return n.type === 'process-instance' || n.type === 'feed-instance';
}
function is_nonterminal_label(e) {
return e._label === 'input' || e._label === 'output';
}
function visit_neighbor(p) {
var vid = p.id, depth = p.depth;
if (depth === 0) {
process_queue(done_cb);
return;
}
Falcon.logRequest();
Falcon.getInstanceVerticesDirection(vid, 'both')
.success(function (resp) {
Falcon.logResponse('success', resp, false, true);
for (var i = 0; i < resp.results.length; ++i) {
var n = resp.results[i];
if (data.nodes[n._id] !== undefined || !filter_node(n)) {
continue;
}
data.nodes[n._id] = n;
q.push({'id': n._id, 'depth': depth - 1});
}
})
.error(function (err) {
Falcon.logResponse('error', err, false, true);
})
.finally(function () {
process_queue(done_cb);
});
}
var v = data.queue.pop();
Falcon.logRequest();
Falcon.getInstanceVerticesDirection(v.id, 'bothE')
.success(function (resp) {
Falcon.logResponse('success', resp, false, true);
var terminal_has_in_edge = false, terminal_has_out_edge = false;
var node = data.nodes[v.id];
for (var i = 0; i < resp.results.length; ++i) {
var e = resp.results[i];
if (is_nonterminal_label(e)) {
terminal_has_in_edge = terminal_has_in_edge || e._inV === v.id;
terminal_has_out_edge = terminal_has_out_edge || e._outV === v.id;
}
if (data.edges[e._id] !== undefined) {
continue;
}
data.edges[e._id] = e;
}
node.is_terminal = !(terminal_has_in_edge && terminal_has_out_edge);
})
.error(function (err) {
Falcon.logResponse('error', err, false, true);
})
.finally(function () {
visit_neighbor(v);
});
}
function draw_graph() {
var svg = d3.select('#lineage-graph').html('').append('svg:g');
var LINE_FUNCTION = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate('basis');
function draw_node_functor(node_map) {
function expand_node(d) {
return function() {
data.queue.push({'id': d._id, 'depth': 1});
update_graph();
};
}
function show_info_functor(d) {
return function() {
svg.selectAll('.lineage-node').classed('lineage-node-active', false);
d3.select(this).classed('lineage-node-active', true);
data.active_node_id = d._id;
Falcon.logRequest();
Falcon.getInstanceVerticesProps(d._id)
.success(function (resp) {
Falcon.logResponse('success', resp, false, true);
$('#lineage-info-panel').html(resp);
})
.error(function (err) {
Falcon.logResponse('error', err, false, true);
});
};
}
return function(id, d) {
var data = node_map[id];
var r = svg.append('g')
.attr('transform', 'translate(' + d.x + ',' + d.y + ')');
var c = r.append('circle')
.attr('data-node-id', id)
.attr('class', 'lineage-node lineage-node-' + data.type)
.attr('r', CIRCLE_RADIUS)
.on('click', show_info_functor(data));
if (!data.is_terminal) {
c.on('dblclick', expand_node(data));
} else {
c.classed('lineage-node-terminal', true);
}
var name = node_map[id].name;
r.append('title').text(name);
var fo = r.append('foreignObject')
.attr('transform', 'translate(' + (-LABEL_WIDTH / 2) + ', ' + LABEL_PADDING + ' )')
.attr('width', LABEL_WIDTH)
.attr('height', LABEL_HEIGHT);
fo.append('xhtml:div').text(name)
.attr('class', 'lineage-node-text');
};
}
function draw_edge(layout) {
return function(e, u, v, value) {
var r = svg.append('g').attr('class', 'lineage-link');
r.append('path')
.attr('marker-end', 'url(#arrowhead)')
.attr('d', function() {
var points = value.points;
var source = layout.node(u);
var target = layout.node(v);
var p = points.length === 0 ? source : points[points.length - 1];
var r = CIRCLE_RADIUS;
var sx = p.x, sy = p.y, tx = target.x, ty = target.y;
var l = Math.sqrt((tx - sx) * (tx - sx) + (ty - sy) * (ty - sy));
var dx = r / l * (tx - sx), dy = r / l * (ty - sy);
points.unshift({'x': source.x, 'y': source.y});
points.push({'x': target.x - dx, 'y': target.y - dy});
return LINE_FUNCTION(points);
});
};
}
var node_map = {};
var g = new dagre.Digraph();
for (var k in data.nodes) {
var n = data.nodes[k];
g.addNode(n._id, { 'width': CIRCLE_RADIUS * 2 + LABEL_WIDTH, 'height': CIRCLE_RADIUS * 2 + LABEL_HEIGHT});
node_map[n._id] = n;
}
for (var k in data.edges) {
var e = data.edges[k];
var src = node_map[e._inV], dst = node_map[e._outV];
if (src !== undefined && dst !== undefined) {
g.addEdge(null, e._outV, e._inV);
}
}
var layout = dagre.layout().rankSep(RANK_SEPARATION).rankDir('LR').run(g);
layout.eachEdge(draw_edge(layout));
layout.eachNode(draw_node_functor(node_map));
function post_render() {
svg
.append('svg:defs')
.append('svg:marker')
.attr('id', 'arrowhead')
.attr('viewBox', '0 0 10 10')
.attr('refX', 8)
.attr('refY', 5)
.attr('markerUnits', 'strokeWidth')
.attr('markerWidth', 8)
.attr('markerHeight', 5)
.attr('orient', 'auto')
//.attr('style', 'fill: #ccc')
.append('svg:path')
.attr('d', 'M 0 0 L 10 5 L 0 10 z');
}
var bb = layout.graph();
$('#lineage-graph').attr('width', bb.width);
//$('#lineage-graph').attr('width', '100%');
//$('#lineage-graph').attr('height', bb.height);
post_render();
}
function update_graph() {
process_queue(function() {
draw_graph();
var id = data.active_node_id;
if (id !== null) {
d3.select('.node[data-node-id="' + id + '"]').classed('node-active', true);
}
});
}
var loadLineageGraph = function(entityId, instance_name) {
var node_name = entityId + '/' + instance_name;
Falcon.logRequest();
Falcon.getInstanceVertices(node_name)
.success(function (resp) {
Falcon.logResponse('success', resp, false, true);
var n = resp.results[0];
data.queue = [{'id': n._id, 'depth': 1}];
data.nodes = {};
data.nodes[n._id] = n;
update_graph();
})
.error(function (err) {
Falcon.logResponse('error', err, false, true);
});
};
loadLineageGraph(scope.name, scope.instance);
}
};
}]);
})();