/* * 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); } } ]);