blob: 6e4e1e34a22649b8f41ef998b65a1490765d4b51 [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.
*/
'use strict';
angular.module('dgc.lineage').controller('LineageController', ['$element', '$scope', '$state', '$stateParams', 'lodash', 'LineageResource', 'd3', 'DetailsResource', '$q',
function($element, $scope, $state, $stateParams, _, LineageResource, d3, DetailsResource, $q) {
var guidsList = [];
function getLineageData(tableData, callRender) {
LineageResource.get({
tableName: tableData.tableName,
type: tableData.type
}, function lineageSuccess(response) {
if (!_.isEmpty(response.results.values.vertices)) {
var allGuids = loadProcess(response.results.values.edges, response.results.values.vertices);
allGuids.then(function(res) {
guidsList = res;
$scope.lineageData = transformData(response.results);
if (callRender) {
render();
}
});
}
$scope.requested = false;
});
}
function loadProcess(edges, vertices) {
var urlCalls = [];
var deferred = $q.defer();
for (var guid in edges) {
if (!vertices.hasOwnProperty(guid)) {
urlCalls.push(DetailsResource.get({
id: guid
}).$promise);
}
}
$q.all(urlCalls)
.then(function(results) {
deferred.resolve(results);
});
return deferred.promise;
}
$scope.type = $element.parent().attr('data-table-type');
$scope.requested = false;
function render() {
renderGraph($scope.lineageData, {
element: $element[0],
height: $element[0].offsetHeight,
width: $element[0].offsetWidth
});
$scope.rendered = true;
}
$scope.$on('render-lineage', function(event, lineageData) {
if (lineageData.type === $scope.type) {
if (!$scope.lineageData) {
if (!$scope.requested) {
getLineageData(lineageData, true);
$scope.requested = true;
}
} else {
render();
}
}
});
function transformData(metaData) {
var edges = metaData.values.edges,
vertices = metaData.values.vertices,
nodes = {};
function getNode(guid) {
var name, type, tip;
if (vertices.hasOwnProperty(guid)) {
name = vertices[guid].values.name;
type = vertices[guid].values.vertexId.values.typeName;
} else {
var loadProcess = getLoadProcessTypes(guid);
if (typeof loadProcess !== "undefined") {
name = loadProcess.name;
type = loadProcess.typeName;
tip = loadProcess.tip;
} else {
name = 'Load Process';
type = 'Load Process';
}
}
var vertex = {
guid: guid,
name: name,
type: type,
tip: tip
};
if (!nodes.hasOwnProperty(guid)) {
nodes[guid] = vertex;
}
return nodes[guid];
}
function getLoadProcessTypes(guid) {
var procesRes = [];
angular.forEach(guidsList, function(value) {
if (value.id.id === guid) {
procesRes.name = value.values.name;
procesRes.typeName = value.typeName;
procesRes.tip = value.values.queryText;
}
});
return procesRes;
}
function attachParent(edge, node) {
edge.forEach(function eachPoint(childGuid) {
var childNode = getNode(childGuid);
node.children = node.children || [];
node.children.push(childNode);
childNode.parent = node.guid;
});
}
/* Loop through all edges and attach them to correct parent */
for (var guid in edges) {
var edge = edges[guid],
node = getNode(guid);
/* Attach parent to each endpoint of edge */
attachParent(edge, node);
}
/* Return the first node w/o parent, this is root node*/
return _.find(nodes, function(node) {
return !node.hasOwnProperty('parent');
});
}
function renderGraph(data, container) {
// ************** Generate the tree diagram *****************
var element = d3.select(container.element),
width = Math.max(container.width, 960),
height = Math.max(container.height, 350);
var margin = {
top: 100,
right: 80,
bottom: 30,
left: 80
};
width = width - margin.right - margin.left;
height = height - margin.top - margin.bottom;
var i = 0;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
/* Initialize tooltip */
var tooltip = d3.tip()
.attr('class', 'd3-tip')
.html(function(d) {
return '<pre class="alert alert-success">' + d.tip + '</pre>';
});
var svg = element.select('svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
/* Invoke the tip in the context of your visualization */
.call(tooltip)
.select('g')
.attr('transform',
'translate(' + margin.left + ',' + margin.right + ')');
//arrow
svg.append("svg:defs").append("svg:marker").attr("id", "arrow").attr("viewBox", "0 0 10 10").attr("refX", 26).attr("refY", 5).attr("markerUnits", "strokeWidth").attr("markerWidth", 6).attr("markerHeight", 9).attr("orient", "auto").append("svg:path").attr("d", "M 0 0 L 10 5 L 0 10 z");
//marker for input type graph
svg.append("svg:defs")
.append("svg:marker")
.attr("id", "input-arrow")
.attr("viewBox", "0 0 10 10")
.attr("refX", -15)
.attr("refY", 5)
.attr("markerUnits", "strokeWidth")
.attr("markerWidth", 6)
.attr("markerHeight", 9)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M -2 5 L 8 0 L 8 10 z");
var root = data;
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(source).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 180;
});
// Declare the nodes…
var node = svg.selectAll('g.node')
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter the nodes.
var nodeEnter = node.enter().append('g')
.attr('class', 'node')
.attr('transform', function(d) {
return 'translate(' + d.y + ',' + d.x + ')';
});
nodeEnter.append("image")
.attr("xlink:href", function(d) {
//return d.icon;
return d.type === 'Table' ? '../img/tableicon.png' : '../img/process.png';
})
.on('mouseover', function(d) {
if (d.type === 'LoadProcess') {
tooltip.show(d);
}
})
.on('mouseout', function(d) {
if (d.type === 'LoadProcess') {
tooltip.hide(d);
}
})
.attr("x", "-18px")
.attr("y", "-18px")
.attr("width", "34px")
.attr("height", "34px");
nodeEnter.append('text')
.attr('x', function(d) {
return d.children || d._children ?
(5) * -1 : +15;
})
.attr('dy', '-1.75em')
.attr('text-anchor', function(d) {
return d.children || d._children ? 'middle' : 'middle';
})
.text(function(d) {
return d.name;
})
.style('fill-opacity', 1);
// Declare the links…
var link = svg.selectAll('path.link')
.data(links, function(d) {
return d.target.id;
});
link.enter().insert('path', 'g')
.attr('class', 'link')
//.style('stroke', function(d) { return d.target.level; })
.style('stroke', 'green')
.attr('d', diagonal);
if($scope.type === 'inputs') {
link.attr("marker-start", "url(#input-arrow)");//if input
} else {
link.attr("marker-end", "url(#arrow)");//if input
}
}
update(root);
}
}
]);