blob: 6c7131945de1350e0a7dcfaff22677e1955cbe38 [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.
*/
define(['require',
'backbone',
'hbs!tmpl/graph/LineageLayoutView_tmpl',
'collection/VLineageList',
'models/VEntity',
'utils/Utils',
'views/graph/LineageUtils',
'dagreD3',
'd3-tip',
'utils/Enums',
'utils/UrlLinks',
'utils/Globals',
'utils/CommonViewFunction',
'platform',
'jquery-ui'
], function(require, Backbone, LineageLayoutViewtmpl, VLineageList, VEntity, Utils, LineageUtils, dagreD3, d3Tip, Enums, UrlLinks, Globals, CommonViewFunction, platform) {
'use strict';
var LineageLayoutView = Backbone.Marionette.LayoutView.extend(
/** @lends LineageLayoutView */
{
_viewName: 'LineageLayoutView',
template: LineageLayoutViewtmpl,
className: "resizeGraph",
/** Layout sub regions */
regions: {},
/** ui selector cache */
ui: {
graph: ".graph",
checkHideProcess: "[data-id='checkHideProcess']",
checkDeletedEntity: "[data-id='checkDeletedEntity']",
selectDepth: 'select[data-id="selectDepth"]',
filterToggler: '[data-id="filter-toggler"]',
settingToggler: '[data-id="setting-toggler"]',
searchToggler: '[data-id="search-toggler"]',
boxClose: '[data-id="box-close"]',
lineageFullscreenToggler: '[data-id="fullScreen-toggler"]',
filterBox: '.filter-box',
searchBox: '.search-box',
settingBox: '.setting-box',
lineageTypeSearch: '[data-id="typeSearch"]',
searchNode: '[data-id="searchNode"]',
nodeDetailTable: '[data-id="nodeDetailTable"]',
showOnlyHoverPath: '[data-id="showOnlyHoverPath"]',
showTooltip: '[data-id="showTooltip"]',
saveSvg: '[data-id="saveSvg"]',
resetLineage: '[data-id="resetLineage"]'
},
templateHelpers: function() {
return {
width: "100%",
height: "100%"
};
},
/** ui events hash */
events: function() {
var events = {};
events["click " + this.ui.checkHideProcess] = 'onCheckUnwantedEntity';
events["click " + this.ui.checkDeletedEntity] = 'onCheckUnwantedEntity';
events['change ' + this.ui.selectDepth] = 'onSelectDepthChange';
events["click " + this.ui.filterToggler] = 'onClickFilterToggler';
events["click " + this.ui.boxClose] = 'toggleBoxPanel';
events["click " + this.ui.settingToggler] = 'onClickSettingToggler';
events["click " + this.ui.lineageFullscreenToggler] = 'onClickLineageFullscreenToggler';
events["click " + this.ui.searchToggler] = 'onClickSearchToggler';
events["click " + this.ui.saveSvg] = 'onClickSaveSvg';
events["click " + this.ui.resetLineage] = 'onClickResetLineage';
return events;
},
/**
* intialize a new LineageLayoutView Layout
* @constructs
*/
initialize: function(options) {
_.extend(this, _.pick(options, 'processCheck', 'guid', 'entity', 'entityName', 'entityDefCollection', 'actionCallBack', 'fetchCollection', 'attributeDefs'));
this.collection = new VLineageList();
this.lineageData = null;
this.typeMap = {};
this.apiGuid = {};
this.edgeCall;
this.filterObj = {
isProcessHideCheck: false,
isDeletedEntityHideCheck: false,
depthCount: ''
};
this.searchNodeObj = {
selectedNode: ''
}
},
initializeGraph: function() {
this.g = {};
this.g = new dagreD3.graphlib.Graph()
.setGraph({
nodesep: 50,
ranksep: 90,
rankdir: "LR",
marginx: 20,
marginy: 20,
transition: function transition(selection) {
return selection.transition().duration(500);
}
})
.setDefaultEdgeLabel(function() {
return {};
});
},
onRender: function() {
var that = this;
this.fetchGraphData();
if (platform.name === "IE") {
this.$('svg').css('opacity', '0');
}
if (this.layoutRendered) {
this.layoutRendered();
}
if (this.processCheck) {
this.hideCheckForProcess();
}
this.initializeGraph();
this.ui.selectDepth.select2({
data: _.sortBy([3, 6, 9, 12, 15, 18, 21]),
tags: true,
dropdownCssClass: "number-input",
multiple: false
});
},
onShow: function() {
this.$('.fontLoader').show();
this.$el.resizable({
handles: ' s',
minHeight: 375,
stop: function(event, ui) {
ui.element.height(($(this).height()));
},
});
},
onClickLineageFullscreenToggler: function(e) {
var icon = $(e.currentTarget).find('i'),
panel = $(e.target).parents('.tab-pane').first();
icon.toggleClass('fa-expand fa-compress');
if(icon.hasClass('fa-expand')){
icon.parent('button').attr("data-original-title","Full Screen");
}else{
icon.parent('button').attr("data-original-title","Default View");
}
panel.toggleClass('fullscreen-mode');
},
onCheckUnwantedEntity: function(e) {
var data = $.extend(true, {}, this.lineageData);
//this.fromToNodeData = {};
this.initializeGraph();
if ($(e.target).data("id") === "checkHideProcess") {
this.filterObj.isProcessHideCheck = e.target.checked;
} else {
this.filterObj.isDeletedEntityHideCheck = e.target.checked;
}
this.generateData({ "relationshipMap": this.relationshipMap, "guidEntityMap": this.guidEntityMap });
},
toggleBoxPanel: function(options) {
var el = options && options.el,
nodeDetailToggler = options && options.nodeDetailToggler,
currentTarget = options.currentTarget;
this.$el.find('.show-box-panel').removeClass('show-box-panel');
if (el && el.addClass) {
el.addClass('show-box-panel');
}
this.$('circle.node-detail-highlight').removeClass("node-detail-highlight");
},
onClickNodeToggler: function(options) {
this.toggleBoxPanel({ el: this.$('.lineage-node-detail'), nodeDetailToggler: true });
},
onClickFilterToggler: function() {
this.toggleBoxPanel({ el: this.ui.filterBox });
},
onClickSettingToggler: function() {
this.toggleBoxPanel({ el: this.ui.settingBox });
},
onClickSearchToggler: function() {
this.toggleBoxPanel({ el: this.ui.searchBox });
},
onSelectDepthChange: function(e, options) {
this.initializeGraph();
this.filterObj.depthCount = e.currentTarget.value;
this.fetchGraphData({ queryParam: { 'depth': this.filterObj.depthCount } });
},
fetchGraphData: function(options) {
var that = this,
queryParam = options && options.queryParam || {};
this.fromToNodeData = {};
this.$('.fontLoader').show();
this.$('svg>g').hide();
this.toggleDisableState({
"el": that.$(".graph-button-group button,select[data-id='selectDepth']")
});
this.collection.getLineage(this.guid, {
skipDefaultError: true,
queryParam: queryParam,
success: function(data) {
if (data.relations.length) {
that.lineageData = $.extend(true, {}, data);
that.relationshipMap = that.crateLineageRelationshipHashMap(data);
that.guidEntityMap = $.extend(true, {}, data.guidEntityMap);
that.generateData({ "relationshipMap": that.relationshipMap, "guidEntityMap": that.guidEntityMap });
that.toggleDisableState({
"el": that.$(".graph-button-group button,select[data-id='selectDepth']")
});
} else {
that.noLineage();
that.hideCheckForProcess();
}
},
cust_error: function(model, response) {
that.lineageData = [];
that.noLineage();
},
complete: function() {
that.$('.fontLoader').hide();
that.$('svg>g').show();
}
})
},
noLineage: function() {
this.$('.fontLoader').hide();
this.$('.depth-container').hide();
this.$('svg').html('<text x="50%" y="50%" alignment-baseline="middle" text-anchor="middle">No lineage data found</text>');
if (this.actionCallBack) {
this.actionCallBack();
}
},
hideCheckForProcess: function() {
this.$('.hideProcessContainer').hide();
},
isProcess: function(node) {
var typeName = node.typeName,
superTypes = node.superTypes,
entityDef = node.entityDef;
if (typeName == "Process") {
return true;
}
return _.contains(superTypes, "Process");
},
isDeleted: function(node) {
if (_.isUndefined(node)) {
return
}
return Enums.entityStateReadOnly[node.status];
},
isNodeToBeUpdated: function(node) {
var isProcessHideCheck = this.filterObj.isProcessHideCheck,
isDeletedEntityHideCheck = this.filterObj.isDeletedEntityHideCheck
var returnObj = {
isProcess: (isProcessHideCheck && node.isProcess),
isDeleted: (isDeletedEntityHideCheck && node.isDeleted)
};
returnObj["update"] = returnObj.isProcess || returnObj.isDeleted;
return returnObj;
},
getNestedSuperTypes: function(options) {
var entityDef = options.entityDef;
return Utils.getNestedSuperTypes({ data: entityDef, collection: this.entityDefCollection })
},
getEntityDef: function(typeName) {
var entityDef = null;
if (typeName) {
entityDef = this.entityDefCollection.fullCollection.find({ name: typeName });
entityDef = entityDef ? entityDef.toJSON() : entityDef;
}
return entityDef;
},
getServiceType: function(options) {
if (!options) {
return;
}
var typeName = options.typeName,
entityDef = options.entityDef,
serviceType = null;
if (typeName) {
if (entityDef) {
serviceType = entityDef.serviceType || null;
}
}
return serviceType;
},
crateLineageRelationshipHashMap: function(data) {
var that = this,
relations = data && data.relations,
guidEntityMap = data && data.guidEntityMap,
makeNodeData = function(relationObj) {
var obj = $.extend(true, {
shape: "img",
label: relationObj.displayText.trunc(18),
toolTipLabel: relationObj.displayText,
id: relationObj.guid,
isLineage: true,
entityDef: this.getEntityDef(relationObj.typeName)
}, relationObj);
obj["serviceType"] = this.getServiceType({ typeName: relationObj.typeName, entityDef: obj.entityDef });
obj["superTypes"] = this.getNestedSuperTypes({ entityDef: obj.entityDef });
obj['isProcess'] = this.isProcess(obj);
obj['isDeleted'] = this.isDeleted(obj);
return obj;
}.bind(this),
newHashMap = {};
_.each(relations, function(obj) {
if (!that.fromToNodeData[obj.fromEntityId]) {
that.fromToNodeData[obj.fromEntityId] = makeNodeData(guidEntityMap[obj.fromEntityId]);
}
if (!that.fromToNodeData[obj.toEntityId]) {
that.fromToNodeData[obj.toEntityId] = makeNodeData(guidEntityMap[obj.toEntityId]);
}
if (newHashMap[obj.fromEntityId]) {
newHashMap[obj.fromEntityId].push(obj.toEntityId);
} else {
newHashMap[obj.fromEntityId] = [obj.toEntityId];
}
});
return newHashMap;
},
generateData: function(options) {
var that = this,
relationshipMap = options && $.extend(true, {}, options.relationshipMap) || {},
guidEntityMap = options && options.guidEntityMap || {},
styleObj = {
fill: 'none',
stroke: '#ffb203',
width: 3
},
getStyleObjStr = function(styleObj) {
return 'fill:' + styleObj.fill + ';stroke:' + styleObj.stroke + ';stroke-width:' + styleObj.width;
},
filterRelationshipMap = relationshipMap,
isHideFilterOn = this.filterObj.isProcessHideCheck || this.filterObj.isDeletedEntityHideCheck,
getNewToNodeRelationship = function(toNodeGuid) {
if (toNodeGuid && relationshipMap[toNodeGuid]) {
var newRelationship = [];
_.each(relationshipMap[toNodeGuid], function(guid) {
var nodeToBeUpdated = that.isNodeToBeUpdated(that.fromToNodeData[guid]);
if (nodeToBeUpdated.update) {
var newRelation = getNewToNodeRelationship(guid);
if (newRelation) {
newRelationship = newRelationship.concat(newRelation);
}
} else {
newRelationship.push(guid);
}
});
return newRelationship;
} else {
return null;
}
},
getToNodeRelation = function(toNodes, fromNodeToBeUpdated) {
var toNodeRelationship = [];
_.each(toNodes, function(toNodeGuid) {
var toNodeToBeUpdated = that.isNodeToBeUpdated(that.fromToNodeData[toNodeGuid]);
if (toNodeToBeUpdated.update) {
// To node need to updated
if (pendingFromRelationship[toNodeGuid]) {
toNodeRelationship = toNodeRelationship.concat(pendingFromRelationship[toNodeGuid]);
} else {
var newToNodeRelationship = getNewToNodeRelationship(toNodeGuid);
if (newToNodeRelationship) {
toNodeRelationship = toNodeRelationship.concat(newToNodeRelationship);
}
}
} else {
//when bothe node not to be updated.
toNodeRelationship.push(toNodeGuid);
}
});
return toNodeRelationship;
},
pendingFromRelationship = {};
if (isHideFilterOn) {
filterRelationshipMap = {};
_.each(relationshipMap, function(toNodes, fromNodeGuid) {
var fromNodeToBeUpdated = that.isNodeToBeUpdated(that.fromToNodeData[fromNodeGuid]),
toNodeList = getToNodeRelation(toNodes, fromNodeToBeUpdated);
if (fromNodeToBeUpdated.update) {
if (pendingFromRelationship[fromNodeGuid]) {
pendingFromRelationship[fromNodeGuid] = pendingFromRelationship[fromNodeGuid].concat(toNodeList);
} else {
pendingFromRelationship[fromNodeGuid] = toNodeList;
}
} else {
if (filterRelationshipMap[fromNodeGuid]) {
filterRelationshipMap[fromNodeGuid] = filterRelationshipMap[fromNodeGuid].concat(toNodeList);
} else {
filterRelationshipMap[fromNodeGuid] = toNodeList;
}
}
})
}
_.each(filterRelationshipMap, function(toNodesList, fromNodeGuid) {
if (!that.g._nodes[fromNodeGuid]) {
that.g.setNode(fromNodeGuid, that.fromToNodeData[fromNodeGuid]);
}
_.each(toNodesList, function(toNodeGuid) {
if (!that.g._nodes[toNodeGuid]) {
that.g.setNode(toNodeGuid, that.fromToNodeData[toNodeGuid]);
}
that.g.setEdge(fromNodeGuid, toNodeGuid, {
"arrowhead": 'arrowPoint',
"lineInterpolate": 'basis',
"style": getStyleObjStr(styleObj),
'styleObj': styleObj
});
})
})
//if no relations found
if (_.isEmpty(filterRelationshipMap)) {
this.$('svg').html('<text x="50%" y="50%" alignment-baseline="middle" text-anchor="middle">No relations to display</text>');
}
if (this.fromToNodeData[this.guid]) {
this.fromToNodeData[this.guid]['isLineage'] = false;
this.findImpactNodeAndUpdateData({ "relationshipMap": filterRelationshipMap, "guid": this.guid, "getStyleObjStr": getStyleObjStr });
}
this.renderLineageTypeSearch();
this.createGraph();
},
findImpactNodeAndUpdateData: function(options) {
var that = this,
relationshipMap = options.relationshipMap,
fromNodeGuid = options.guid,
getStyleObjStr = options.getStyleObjStr,
toNodeList = relationshipMap[fromNodeGuid];
if (toNodeList && toNodeList.length) {
if (!relationshipMap[fromNodeGuid]["traversed"]) {
relationshipMap[fromNodeGuid]["traversed"] = true;
_.each(toNodeList, function(toNodeGuid) {
that.fromToNodeData[toNodeGuid]['isLineage'] = false;
var styleObj = {
fill: 'none',
stroke: '#fb4200',
width: 3
}
that.g.setEdge(fromNodeGuid, toNodeGuid, {
"arrowhead": 'arrowPoint',
"lineInterpolate": 'basis',
"style": getStyleObjStr(styleObj),
'styleObj': styleObj
});
that.findImpactNodeAndUpdateData({
"relationshipMap": relationshipMap,
"guid": toNodeGuid,
"getStyleObjStr": getStyleObjStr
});
});
}
}
},
zoomed: function(that) {
this.$('svg').find('>g').attr("transform",
"translate(" + this.zoom.translate() + ")" +
"scale(" + this.zoom.scale() + ")"
);
},
interpolateZoom: function(translate, scale, that, zoom) {
return d3.transition().duration(350).tween("zoom", function() {
var iTranslate = d3.interpolate(zoom.translate(), translate),
iScale = d3.interpolate(zoom.scale(), scale);
return function(t) {
zoom
.scale(iScale(t))
.translate(iTranslate(t));
that.zoomed();
};
});
},
createGraph: function() {
var that = this,
width = this.$('svg').width(),
height = this.$('svg').height(),
imageObject = {};
this.g.nodes().forEach(function(v) {
var node = that.g.node(v);
// Round the corners of the nodes
if (node) {
node.rx = node.ry = 5;
}
});
// Create the renderer
var render = new dagreD3.render();
// Add our custom arrow (a hollow-point)
render.arrows().arrowPoint = function normal(parent, id, edge, type) {
var parentNode = parent && parent[0] && parent[0][0] && parent[0][0].parentNode ? parent[0][0].parentNode : parent;
d3.select(parentNode).select('path.path').attr('marker-end', "url(#" + id + ")");
var marker = parent.append("marker")
.attr("id", id)
.attr("viewBox", "0 0 10 10")
.attr("refX", 8)
.attr("refY", 5)
.attr("markerUnits", "strokeWidth")
.attr("markerWidth", 4)
.attr("markerHeight", 4)
.attr("orient", "auto");
var path = marker.append("path")
.attr("d", "M 0 0 L 10 5 L 0 10 z")
.style("fill", edge.styleObj.stroke);
dagreD3.util.applyStyle(path, edge[type + "Style"]);
};
render.shapes().img = function circle(parent, bbox, node) {
//var r = Math.max(bbox.width, bbox.height) / 2,
if (node.id == that.guid) {
var currentNode = true
}
var shapeSvg = parent.append('circle')
.attr('fill', 'url(#img_' + node.id + ')')
.attr('r', '24px')
.attr('data-stroke', node.id)
.attr('stroke-width', "2px")
.attr("class", "nodeImage " + (currentNode ? "currentNode" : (node.isProcess ? "process" : "node")));
if (currentNode) {
shapeSvg.attr("stroke", "#fb4200")
}
parent.insert("defs")
.append("pattern")
.attr("x", "0%")
.attr("y", "0%")
.attr("patternUnits", "objectBoundingBox")
.attr("id", "img_" + node.id)
.attr("width", "100%")
.attr("height", "100%")
.append('image')
.attr("xlink:href", function(d) {
var that = this;
if (node) {
var imageIconPath = Utils.getEntityIconPath({ entityData: node }),
imagePath = ((window.location.origin + Utils.getBaseUrl(window.location.pathname)) + imageIconPath);
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.onload = function() {
var reader = new FileReader();
reader.onloadend = function() {
_.each(imageObject[imageIconPath], function(obj) {
obj.attr("xlink:href", reader.result);
});
imageObject[imageIconPath] = reader.result;
}
if (xhr.status != 404) {
reader.readAsDataURL(xhr.response);
} else {
xhr.open('GET',
Utils.getEntityIconPath({ entityData: node, errorUrl: this.responseURL }),
true);
xhr.send();
}
}
if (_.isUndefined(imageObject[imageIconPath])) {
// before img success
imageObject[imageIconPath] = [d3.select(that)];
xhr.open('GET', imagePath, true);
xhr.send();
} else if (_.isArray(imageObject[imageIconPath])) {
// before img success
imageObject[imageIconPath].push(d3.select(that));
} else {
d3.select(that).attr("xlink:href", imageObject[imageIconPath]);
return imageObject[imageIconPath];
}
}
})
.attr("x", "4")
.attr("y", currentNode ? "3" : "4").attr("width", "40")
.attr("height", "40");
node.intersect = function(point) {
return dagreD3.intersect.circle(node, currentNode ? 24 : 21, point);
};
return shapeSvg;
};
// Set up an SVG group so that we can translate the final graph.
if (this.$("svg").find('.output').length) {
this.$("svg").find('.output').parent('g').remove();
}
var svg = this.svg = d3.select(this.$("svg")[0])
.attr("viewBox", "0 0 " + width + " " + height)
.attr("enable-background", "new 0 0 " + width + " " + height),
svgGroup = svg.append("g");
var zoom = this.zoom = d3.behavior.zoom()
.center([width / 2, height / 2])
.scaleExtent([0.01, 50])
.on("zoom", that.zoomed.bind(this));
function zoomClick() {
var clicked = d3.event.target,
direction = 1,
factor = 0.5,
target_zoom = 1,
center = [width / 2, height / 2],
translate = zoom.translate(),
translate0 = [],
l = [],
view = { x: translate[0], y: translate[1], k: zoom.scale() };
d3.event.preventDefault();
direction = (this.id === 'zoom_in') ? 1 : -1;
target_zoom = zoom.scale() * (1 + factor * direction);
translate0 = [(center[0] - view.x) / view.k, (center[1] - view.y) / view.k];
view.k = target_zoom;
l = [translate0[0] * view.k + view.x, translate0[1] * view.k + view.y];
view.x += center[0] - l[0];
view.y += center[1] - l[1];
that.interpolateZoom([view.x, view.y], view.k, that, zoom);
}
d3.selectAll(this.$('.lineageZoomButton')).on('click', zoomClick);
var tooltip = d3Tip()
.attr('class', 'd3-tip')
.offset([10, 0])
.html(function(d) {
var value = that.g.node(d);
var htmlStr = "";
if (value.id !== that.guid) {
htmlStr = "<h5 style='text-align: center;'>" + (value.isLineage ? "Lineage" : "Impact") + "</h5>";
}
htmlStr += "<h5 class='text-center'><span style='color:#359f89'>" + value.toolTipLabel + "</span></h5> ";
if (value.typeName) {
htmlStr += "<h5 class='text-center'><span>(" + value.typeName + ")</span></h5> ";
}
if (value.queryText) {
htmlStr += "<h5>Query: <span style='color:#359f89'>" + value.queryText + "</span></h5> ";
}
return "<div class='tip-inner-scroll'>" + htmlStr + "</div>";
});
svg.call(zoom)
.call(tooltip);
if (platform.name !== "IE") {
this.$('.fontLoader').hide();
}
render(svgGroup, this.g);
svg.on("dblclick.zoom", null)
// .on("wheel.zoom", null);
//change text postion
svgGroup.selectAll("g.nodes g.label")
.attr("transform", "translate(2,-35)");
var waitForDoubleClick = null;
svgGroup.selectAll("g.nodes g.node")
.on('mouseenter', function(d) {
that.activeNode = true;
var matrix = this.getScreenCTM()
.translate(+this.getAttribute("cx"), +this.getAttribute("cy"));
that.$('svg').find('.node').removeClass('active');
$(this).addClass('active');
// Fix
var width = $('body').width();
var currentELWidth = $(this).offset();
var direction = 'e';
if (((width - currentELWidth.left) < 330)) {
direction = (((width - currentELWidth.left) < 330) && ((currentELWidth.top) < 400)) ? 'sw' : 'w';
if (((width - currentELWidth.left) < 330) && ((currentELWidth.top) > 600)) {
direction = 'nw';
}
} else if (((currentELWidth.top) > 600)) {
direction = (((width - currentELWidth.left) < 330) && ((currentELWidth.top) > 600)) ? 'nw' : 'n';
if ((currentELWidth.left) < 50) {
direction = 'ne'
}
} else if ((currentELWidth.top) < 400) {
direction = ((currentELWidth.left) < 50) ? 'se' : 's';
}
if (that.ui.showTooltip.prop('checked')) {
tooltip.direction(direction).show(d);
}
if (!that.ui.showOnlyHoverPath.prop('checked')) {
return;
}
that.$('svg').addClass('hover');
var nextNode = that.g.successors(d),
previousNode = that.g.predecessors(d),
nodesToHighlight = nextNode.concat(previousNode);
LineageUtils.onHoverFade({
opacity: 0.3,
selectedNode: d,
highlight: nodesToHighlight,
svg: that.svg
}).init();
})
.on('mouseleave', function(d) {
that.activeNode = false;
var nodeEL = this;
setTimeout(function(argument) {
if (!(that.activeTip || that.activeNode)) {
$(nodeEL).removeClass('active');
if (that.ui.showTooltip.prop('checked')) {
tooltip.hide(d);
}
}
}, 150);
if (!that.ui.showOnlyHoverPath.prop('checked')) {
return;
}
that.$('svg').removeClass('hover');
LineageUtils.onHoverFade({
opacity: 1,
selectedNode: d,
svg: that.svg
}).init();
})
.on('click', function(d) {
var el = this;
if (d3.event.defaultPrevented) return; // ignore drag
d3.event.preventDefault();
if (waitForDoubleClick != null) {
clearTimeout(waitForDoubleClick)
waitForDoubleClick = null;
tooltip.hide(d);
Utils.setUrl({
url: '#!/detailPage/' + d + '?tabActive=lineage',
mergeBrowserUrl: false,
trigger: true
});
} else {
var currentEvent = d3.event
waitForDoubleClick = setTimeout(function() {
tooltip.hide(d);
that.onClickNodeToggler({ obj: d });
$(el).find('circle').addClass('node-detail-highlight');
that.updateRelationshipDetails({ guid: d });
waitForDoubleClick = null;
}, 150)
}
});
svgGroup.selectAll("g.edgePath path.path").on('click', function(d) {
var data = { obj: _.find(that.lineageData.relations, { "fromEntityId": d.v, "toEntityId": d.w }) };
if (data.obj) {
var relationshipId = data.obj.relationshipId;
require(['views/graph/PropagationPropertyModal'], function(PropagationPropertyModal) {
var view = new PropagationPropertyModal({
edgeInfo: data,
relationshipId: relationshipId,
lineageData: that.lineageData,
apiGuid: that.apiGuid,
detailPageFetchCollection: that.fetchCollection
});
});
}
})
$('body').on('mouseover', '.d3-tip', function(el) {
that.activeTip = true;
});
$('body').on('mouseleave', '.d3-tip', function(el) {
that.activeTip = false;
that.$('svg').find('.node').removeClass('active');
tooltip.hide();
});
// Center the graph
LineageUtils.centerNode({
guid: that.guid,
svg: that.$('svg'),
g: this.g,
afterCenterZoomed: function(options) {
var newScale = options.newScale,
newTranslate = options.newTranslate;
that.zoom.scale(newScale);
that.zoom.translate(newTranslate);
}
}).init();
zoom.event(svg);
//svg.attr('height', this.g.graph().height * initialScale + 40);
if (platform.name === "IE") {
this.IEGraphRenderDone = 0;
this.$('svg .edgePath').each(function(argument) {
var childNode = $(this).find('marker');
$(this).find('marker').remove();
var eleRef = this;
++that.IEGraphRenderDone;
setTimeout(function(argument) {
$(eleRef).find('defs').append(childNode);
--that.IEGraphRenderDone;
if (that.IEGraphRenderDone === 0) {
this.$('.fontLoader').hide();
this.$('svg').fadeTo(1000, 1)
}
}, 1000);
});
}
LineageUtils.DragNode({
svg: this.svg,
g: this.g,
guid: this.guid
}).init();
},
renderLineageTypeSearch: function() {
var that = this,
lineageData = $.extend(true, {}, this.lineageData),
data = [],
typeStr = '<option></option>';
if (!_.isEmpty(lineageData)) {
_.each(lineageData.guidEntityMap, function(obj, index) {
var nodeData = that.fromToNodeData[obj.guid];
if (that.filterObj.isProcessHideCheck && nodeData && nodeData.isProcess) {
return;
} else if (that.filterObj.isDeletedEntityHideCheck && nodeData && nodeData.isDeleted) {
return
}
typeStr += '<option value="' + obj.guid + '">' + obj.attributes.name + '</option>';
});
}
that.ui.lineageTypeSearch.html(typeStr);
this.initilizelineageTypeSearch();
},
initilizelineageTypeSearch: function() {
var that = this;
that.ui.lineageTypeSearch.select2({
closeOnSelect: true,
placeholder: 'Select Node'
}).on('change.select2', function(e) {
e.stopPropagation();
e.stopImmediatePropagation();
d3.selectAll(".serach-rect").remove();
var selectedNode = $('[data-id="typeSearch"]').val();
that.searchNodeObj.selectedNode = selectedNode;
LineageUtils.centerNode({
guid: selectedNode,
svg: $(that.svg[0]),
g: that.g,
afterCenterZoomed: function(options) {
var newScale = options.newScale,
newTranslate = options.newTranslate;
that.zoom.scale(newScale);
that.zoom.translate(newTranslate);
}
}).init();
that.svg.selectAll('.nodes g.label').attr('stroke', function(c, d) {
if (c == selectedNode) {
return "#316132";
} else {
return 'none';
}
});
// Using jquery for selector because d3 select is not working for few process entities.
d3.select($(".node#" + selectedNode)[0]).insert("rect", "circle")
.attr("class", "serach-rect")
.attr("x", -50)
.attr("y", -27.5)
.attr("width", 100)
.attr("height", 55);
d3.selectAll(".nodes circle").classed("wobble", function(d, i, nodes) {
if (d == selectedNode) {
return true;
} else {
return false;
}
});
});
if (that.searchNodeObj.selectedNode) {
that.ui.lineageTypeSearch.val(that.searchNodeObj.selectedNode);
that.ui.lineageTypeSearch.trigger("change.select2");
}
},
updateRelationshipDetails: function(options) {
var that = this,
guid = options.guid,
initialData = that.guidEntityMap[guid],
typeName = initialData.typeName || guid,
attributeDefs = that.g._nodes[guid] && that.g._nodes[guid].entityDef ? that.g._nodes[guid].entityDef.attributeDefs : null;
this.$("[data-id='typeName']").text(typeName);
this.entityModel = new VEntity({});
var config = {
guid: 'guid',
typeName: 'typeName',
name: 'name',
qualifiedName: 'qualifiedName',
owner: 'owner',
createTime: 'createTime',
status: 'status',
classificationNames: 'classifications',
meanings: 'term'
};
var data = {};
_.each(config, function(valKey, key) {
var val = initialData[key];
if (_.isUndefined(val) && initialData.attributes[key]) {
val = initialData.attributes[key];
}
if (val) {
data[valKey] = val;
}
});
this.ui.nodeDetailTable.html(CommonViewFunction.propertyTable({
"scope": this,
"valueObject": data,
"attributeDefs": attributeDefs,
"sortBy": false
}));
},
onClickSaveSvg: function(e, a) {
var that = this;
var loaderTargetDiv = $(e.currentTarget).find('>i');
if (loaderTargetDiv.hasClass('fa-refresh')) {
Utils.notifyWarn({
content: "Please wait while the lineage gets downloaded"
});
return false; // return if the lineage is not loaded.
}
that.toggleLoader(loaderTargetDiv);
Utils.notifyInfo({
content: "Lineage will be downloaded in a moment."
});
setTimeout(function() {
var svg = that.$('svg')[0],
svgClone = svg.cloneNode(true),
scaleFactor = 1,
svgWidth = that.$('svg').width(),
svgHeight = that.$('svg').height();
if (platform.name != "Chrome") {
svgClone.setAttribute('width', svgWidth);
svgClone.setAttribute('height', svgHeight);
}
$('.hidden-svg').html(svgClone);
$(svgClone).find('>g').attr("transform", "scale(" + scaleFactor + ")");
var canvasOffset = { x: 150, y: 150 },
setWidth = (svgClone.getBBox().width + (canvasOffset.x)),
setHeight = (svgClone.getBBox().height + (canvasOffset.y)),
xAxis = svgClone.getBBox().x,
yAxis = svgClone.getBBox().y;
svgClone.attributes.viewBox.value = xAxis + "," + yAxis + "," + setWidth + "," + setHeight;
var createCanvas = document.createElement('canvas');
createCanvas.id = "canvas";
createCanvas.style.display = 'none';
var body = $('body').append(createCanvas),
canvas = $('canvas')[0];
canvas.width = (svgClone.getBBox().width * scaleFactor) + canvasOffset.x;
canvas.height = (svgClone.getBBox().height * scaleFactor) + canvasOffset.y;
var ctx = canvas.getContext('2d'),
data = (new XMLSerializer()).serializeToString(svgClone),
DOMURL = window.URL || window.webkitURL || window;
ctx.fillStyle = "#FFFFFF";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.strokeRect(0, 0, canvas.width, canvas.height);
ctx.restore();
var img = new Image(canvas.width, canvas.height);
var svgBlob = new Blob([data], { type: 'image/svg+xml;base64' });
var url = DOMURL.createObjectURL(svgBlob);
img.onload = function() {
try {
var a = document.createElement("a"),
entityAttributes = that.entity && that.entity.attributes;
a.download = ((entityAttributes && entityAttributes.qualifiedName) || "lineage_export") + ".png";
document.body.appendChild(a);
ctx.drawImage(img, 50, 50, canvas.width, canvas.height);
canvas.toBlob(function(blob) {
if (!blob) {
Utils.notifyError({
content: "There was an error in downloading Lineage!"
});
that.toggleLoader(loaderTargetDiv);
return;
}
a.href = DOMURL.createObjectURL(blob);
if (blob.size > 10000000) {
Utils.notifyWarn({
content: "The Image size is huge, please open the image in a browser!"
});
}
a.click();
that.toggleLoader(loaderTargetDiv);
}, 'image/png');
$('.hidden-svg').html('');
createCanvas.remove();
} catch (err) {
Utils.notifyError({
content: "There was an error in downloading Lineage!"
});
that.toggleLoader(loaderTargetDiv);
}
};
img.src = url;
}, 0)
},
toggleLoader: function(element) {
if ((element).hasClass('fa-camera')) {
(element).removeClass('fa-camera').addClass("fa-spin-custom fa-refresh");
} else {
(element).removeClass("fa-spin-custom fa-refresh").addClass('fa-camera');
}
},
onClickResetLineage: function() {
this.createGraph()
},
toggleDisableState: function(options) {
var el = options.el;
if (el && el.prop) {
el.prop("disabled", !el.prop("disabled"));
}
}
});
return LineageLayoutView;
});