blob: 448a95ae6eb51f32188ff722e2ec299a1121d55b [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.
*/
import { select, event } from "d3-selection";
import { zoom, zoomIdentity } from "d3-zoom";
import { drag } from "d3-drag";
import { line, curveBasis } from "d3-shape";
import platform from "platform";
import Enums from "../Enums";
const LineageUtils = {
/**
* [nodeArrowDistance variable use to define the distance between arrow and node]
* @type {Number}
*/
nodeArrowDistance: 24,
refreshGraphForSafari: function (options) {
var edgePathEl = options.edgeEl,
IEGraphRenderDone = 0;
edgePathEl.each(function (argument) {
var eleRef = this,
childNode = $(this).find("pattern");
setTimeout(function (argument) {
$(eleRef).find("defs").append(childNode);
}, 500);
});
},
refreshGraphForIE: function ({ edgePathEl }) {
var IEGraphRenderDone = 0;
edgePathEl.each(function (argument) {
var childNode = $(this).find("marker");
$(this).find("marker").remove();
var eleRef = this;
++IEGraphRenderDone;
setTimeout(function (argument) {
$(eleRef).find("defs").append(childNode);
--IEGraphRenderDone;
if (IEGraphRenderDone === 0) {
this.$(".fontLoader").hide();
this.$("svg").fadeTo(1000, 1);
}
}, 1000);
});
},
/**
* [dragNode description]
* @param {[type]} options.g [description]
* @param {[type]} options.svg [description]
* @param {[type]} options.guid [description]
* @param {[type]} options.edgePathEl [description]
* @return {[type]} [description]
*/
dragNode: function ({ g, svg, guid, edgePathEl }) {
var dragHelper = {
dragmove: function (el, d) {
var node = select(el),
selectedNode = g.node(d),
prevX = selectedNode.x,
prevY = selectedNode.y;
selectedNode.x += event.dx;
selectedNode.y += event.dy;
node.attr("transform", "translate(" + selectedNode.x + "," + selectedNode.y + ")");
var dx = selectedNode.x - prevX,
dy = selectedNode.y - prevY;
g.edges().forEach((e) => {
if (e.v == d || e.w == d) {
var edge = g.edge(e.v, e.w);
this.translateEdge(edge, dx, dy);
select(edge.elem).select("path").attr("d", this.calcPoints(e));
}
});
//LineageUtils.refreshGraphForIE({ edgePathEl: edgePathEl });
},
translateEdge: function (e, dx, dy) {
e.points.forEach(function (p) {
p.x = p.x + dx;
p.y = p.y + dy;
});
},
calcPoints: function (e) {
var edge = g.edge(e.v, e.w),
tail = g.node(e.v),
head = g.node(e.w),
points = edge.points.slice(1, edge.points.length - 1),
afterslice = edge.points.slice(1, edge.points.length - 1);
points.unshift(this.intersectRect(tail, points[0]));
points.push(this.intersectRect(head, points[points.length - 1]));
return line()
.x(function (d) {
return d.x;
})
.y(function (d) {
return d.y;
})
.curve(curveBasis)(points);
},
intersectRect: (node, point) => {
var x = node.x,
y = node.y,
dx = point.x - x,
dy = point.y - y,
nodeDistance = guid ? this.nodeArrowDistance + 3 : this.nodeArrowDistance,
w = nodeDistance,
h = nodeDistance,
sx = 0,
sy = 0;
if (Math.abs(dy) * w > Math.abs(dx) * h) {
// Intersection is top or bottom of rect.
if (dy < 0) {
h = -h;
}
sx = dy === 0 ? 0 : (h * dx) / dy;
sy = h;
} else {
// Intersection is left or right of rect.
if (dx < 0) {
w = -w;
}
sx = w;
sy = dx === 0 ? 0 : (w * dy) / dx;
}
return {
x: x + sx,
y: y + sy
};
}
};
var dragNodeHandler = drag().on("drag", function (d) {
dragHelper.dragmove.call(dragHelper, this, d);
}),
dragEdgePathHandler = drag().on("drag", function (d) {
dragHelper.translateEdge(g.edge(d.v, d.w), event.dx, event.dy);
var edgeObj = g.edge(d.v, d.w);
select(edgeObj.elem).select("path").attr("d", dragHelper.calcPoints(d));
});
dragNodeHandler(svg.selectAll("g.node"));
dragEdgePathHandler(svg.selectAll("g.edgePath"));
},
zoomIn: function ({ svg, scaleFactor = 1.3 }) {
this.d3Zoom.scaleBy(svg.transition().duration(750), scaleFactor);
},
zoomOut: function ({ svg, scaleFactor = 0.8 }) {
this.d3Zoom.scaleBy(svg.transition().duration(750), scaleFactor);
},
zoom: function ({ svg, xa, ya, scale }) {
svg.transition().duration(750).call(this.d3Zoom.transform, zoomIdentity.translate(xa, ya).scale(scale));
},
fitToScreen: function ({ svg }) {
var node = svg.node();
var bounds = node.getBBox();
var parent = node.parentElement,
fullWidth = parent.clientWidth,
fullHeight = parent.clientHeight;
var width = bounds.width,
height = bounds.height;
var midX = bounds.x + width / 2,
midY = bounds.y + height / 2;
var scale = (scale || 0.95) / Math.max(width / fullWidth, height / fullHeight),
xa = fullWidth / 2 - scale * midX,
ya = fullHeight / 2 - scale * midY;
this.zoom({ svg, xa, ya, scale });
},
/**
* [centerNode description]
* @param {[type]} options.guid [description]
* @param {[type]} options.g [description]
* @param {[type]} options.svg [description]
* @param {[type]} options.svgGroupEl [description]
* @param {[type]} options.edgePathEl [description]
* @param {[type]} options.width [description]
* @param {[type]} options.height [description]
* @param {[type]} options.onCenterZoomed [description]
* @return {[type]} [description]
*/
centerNode: function ({ guid, g, svg, svgGroupEl, edgePathEl, width, height, fitToScreen, onCenterZoomed }) {
this.d3Zoom = zoom();
svg.call(this.d3Zoom).on("dblclick.zoom", null);
// restrict events
let selectedNodeEl = svg.selectAll("g.nodes>g[id='" + guid + "']"),
zoomListener = this.d3Zoom.scaleExtent([0.01, 50]).on("zoom", function () {
svgGroupEl.attr("transform", event.transform);
}),
x = null,
y = null,
scale = 1.2;
if (selectedNodeEl.empty()) {
if (fitToScreen) {
this.fitToScreen({ svg });
return;
} else {
x = g.graph().width / 2;
y = g.graph().height / 2;
}
} else {
var matrix = selectedNodeEl
.attr("transform")
.replace(/[^0-9\-.,]/g, "")
.split(",");
// if (platform.name === "IE" || platform.name === "Microsoft Edge") {
// var matrix = selectedNode
// .attr("transform")
// .replace(/[a-z\()]/g, "")
// .split(" ");
// }
x = matrix[0];
y = matrix[1];
}
var xa = -(x * scale - width / 2),
ya = -(y * scale - height / 2);
this.zoom({ svg, xa, ya, scale });
svg.transition().duration(750).call(this.d3Zoom.transform, zoomIdentity.translate(xa, ya).scale(scale));
if (onCenterZoomed) {
onCenterZoomed({ newScale: scale, newTranslate: [xa, ya], d3Zoom: this.d3Zoom, selectedNodeEl });
}
// if (platform.name === "IE") {
// LineageUtils.refreshGraphForIE({ edgePathEl: edgePathEl });
// }
},
/**
* [getToolTipDirection description]
* @param {[type]} options.el [description]
* @return {[type]} [description]
*/
getToolTipDirection: function ({ el }) {
var width = select("body").node().getBoundingClientRect().width,
currentELWidth = select(el).node().getBoundingClientRect(),
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";
}
return direction;
},
/**
* [onHoverFade description]
* @param {[type]} options.svg [description]
* @param {[type]} options.g [description]
* @param {[type]} options.mouseenter [description]
* @param {[type]} options.opacity [description]
* @param {[type]} options.nodesToHighlight [description]
* @param {[type]} options.hoveredNode [description]
* @return {[type]} [description]
*/
onHoverFade: function ({ svg, g, mouseenter, nodesToHighlight, hoveredNode }) {
var node = svg.selectAll(".node"),
path = svg.selectAll(".edgePath"),
isConnected = function (a, b, o) {
if (a === o || (b && b.length && b.indexOf(o) != -1)) {
return true;
}
};
if (mouseenter) {
svg.classed("hover", true);
var nextNode = g.successors(hoveredNode),
previousNode = g.predecessors(hoveredNode),
nodesToHighlight = nextNode.concat(previousNode);
node.classed("hover-active-node", function (currentNode, i, nodes) {
if (isConnected(hoveredNode, nodesToHighlight, currentNode)) {
return true;
} else {
return false;
}
});
path.classed("hover-active-path", function (c) {
var _thisOpacity = c.v === hoveredNode || c.w === hoveredNode ? 1 : 0;
if (_thisOpacity) {
return true;
} else {
return false;
}
});
} else {
svg.classed("hover", false);
node.classed("hover-active-node", false);
path.classed("hover-active-path", false);
}
},
/**
* [getBaseUrl description]
* @param {[type]} path [description]
* @return {[type]} [description]
*/
getBaseUrl: function (url = window.location.pathname) {
return url.replace(/\/[\w-]+.(jsp|html)|\/+$/gi, "");
},
getEntityIconPath: function ({ entityData, errorUrl, imgBasePath }) {
var iconBasePath = this.getBaseUrl() + (imgBasePath || "/img/entity-icon/");
if (entityData) {
let { typeName, serviceType, status, isProcess } = entityData;
function getImgPath(imageName) {
return iconBasePath + (Enums.entityStateReadOnly[status] ? "disabled/" + imageName : imageName);
}
function getDefaultImgPath() {
if (isProcess) {
if (Enums.entityStateReadOnly[status]) {
return iconBasePath + "disabled/process.png";
} else {
return iconBasePath + "process.png";
}
} else {
if (Enums.entityStateReadOnly[status]) {
return iconBasePath + "disabled/table.png";
} else {
return iconBasePath + "table.png";
}
}
}
if (errorUrl) {
// Check if the default img path has error, if yes then stop recursion.
if (errorUrl.indexOf("table.png") > -1 || errorUrl.indexOf("process.png") > -1) {
return null;
}
var isErrorInTypeName = errorUrl && errorUrl.match("entity-icon/" + typeName + ".png|disabled/" + typeName + ".png") ? true : false;
if (serviceType && isErrorInTypeName) {
var imageName = serviceType + ".png";
return getImgPath(imageName);
} else {
return getDefaultImgPath();
}
} else if (typeName) {
var imageName = typeName + ".png";
return getImgPath(imageName);
} else if (serviceType) {
var imageName = serviceType + ".png";
return getImgPath(imageName);
} else {
return getDefaultImgPath();
}
}
},
base64Encode: function (file, callback) {
const reader = new FileReader();
reader.addEventListener("load", () => callback(reader.result));
reader.readAsDataURL(file);
},
imgShapeRender: function (parent, bbox, node, { dagreD3, defsEl, imgBasePath, guid, isRankdirToBottom }) {
var that = this,
viewGuid = guid,
imageIconPath = this.getEntityIconPath({ entityData: node, imgBasePath }),
imgName = imageIconPath.split("/").pop();
if (this.imageObject === undefined) {
this.imageObject = {};
}
if (node.isDeleted) {
imgName = "deleted_" + imgName;
}
if (node.id == viewGuid) {
var currentNode = true;
}
var shapeSvg = parent
.append("circle")
.attr("fill", "url(#img_" + encodeURI(imgName) + ")")
.attr("r", isRankdirToBottom ? "30px" : "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");
}
if (node.isIncomplete === true) {
parent.attr("class", "node isIncomplete show");
parent
.insert("foreignObject")
.attr("x", "-25")
.attr("y", "-25")
.attr("width", "50")
.attr("height", "50")
.append("xhtml:div")
.insert("i")
.attr("class", "fa fa-hourglass-half");
}
if (defsEl.select('pattern[id="img_' + imgName + '"]').empty()) {
defsEl
.append("pattern")
.attr("x", "0%")
.attr("y", "0%")
.attr("patternUnits", "objectBoundingBox")
.attr("id", "img_" + imgName)
.attr("width", "100%")
.attr("height", "100%")
.append("image")
.attr("href", function (d) {
var imgEl = this;
if (node) {
var getImageData = function (options) {
var imagePath = options.imagePath,
ajaxOptions = {
url: imagePath,
method: "GET",
cache: true
};
// if (platform.name !== "IE") {
// ajaxOptions["mimeType"] = "text/plain; charset=x-user-defined";
// }
shapeSvg.attr("data-iconpath", imagePath);
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
if (platform.name !== "IE") {
that.base64Encode(this.response, (url) => {
that.imageObject[imageIconPath] = url;
select(imgEl).attr("xlink:href", url);
});
} else {
that.imageObject[imageIconPath] = imagePath;
}
if (imageIconPath !== shapeSvg.attr("data-iconpath")) {
shapeSvg.attr("data-iconpathorigin", imageIconPath);
}
} else if (xhr.status === 404) {
const imgPath = that.getEntityIconPath({ entityData: node, errorUrl: imagePath });
if (imgPath === null) {
const patternEL = select(imgEl.parentElement);
patternEL.select("image").remove();
patternEL
.attr("patternContentUnits", "objectBoundingBox")
.append("circle")
.attr("r", "24px")
.attr("fill", "#e8e8e8");
} else {
getImageData({
imagePath: imgPath
});
}
}
}
};
xhr.responseType = "blob";
xhr.open(ajaxOptions.method, ajaxOptions.url, true);
xhr.send(null);
};
getImageData({
imagePath: imageIconPath
});
}
})
.attr("x", isRankdirToBottom ? "11" : "4")
.attr("y", isRankdirToBottom ? "20" : currentNode ? "3" : "4")
.attr("width", "40")
.attr("height", "40");
}
node.intersect = function (point) {
return dagreD3.intersect.circle(node, currentNode ? that.nodeArrowDistance + 3 : that.nodeArrowDistance, point);
};
return shapeSvg;
},
/**
* [arrowPointRender description]
* @param {[type]} {parent, id, edge, type, viewOptions [description]
* @return {[type]} [description]
*/
arrowPointRender: function (parent, id, edge, type, { dagreD3 }) {
var node = parent.node(),
parentNode = node ? node.parentNode : parent;
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"]);
},
/**
* [saveSvg description]
* @param {[type]} options.svg [description]
* @param {[type]} options.width [description]
* @param {[type]} options.height [description]
* @param {[type]} options.downloadFileName [description]
* @param {[type]} options.onExportLineage [description]
* @return {[type]} [description]
*/
saveSvg: function ({ svg, width, height, downloadFileName, onExportLineage }) {
var that = this,
svgClone = svg.clone(true).node(),
scaleFactor = 1;
setTimeout(function () {
if (platform.name === "Firefox") {
svgClone.setAttribute("width", width);
svgClone.setAttribute("height", height);
}
const hiddenSvgEl = select("body").append("div");
hiddenSvgEl.classed("hidden-svg", true);
hiddenSvgEl.node().appendChild(svgClone);
const svgCloneEl = select(".hidden-svg svg");
svgCloneEl.select("g").attr("transform", "scale(" + scaleFactor + ")");
svgCloneEl.select("foreignObject").remove();
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 canvas = document.createElement("canvas");
canvas.id = "canvas";
canvas.style.display = "none";
canvas.width = svgClone.getBBox().width * scaleFactor + canvasOffset.x;
canvas.height = svgClone.getBBox().height * scaleFactor + canvasOffset.y;
// Append Canvas in DOM
select("body").node().appendChild(canvas);
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" });
if (platform.name === "Safari") {
svgBlob = new Blob([data], { type: "image/svg+xml" });
}
var url = DOMURL.createObjectURL(svgBlob);
img.onload = function () {
try {
var a = document.createElement("a");
a.download = downloadFileName;
document.body.appendChild(a);
ctx.drawImage(img, 50, 50, canvas.width, canvas.height);
canvas.toBlob(function (blob) {
if (!blob) {
onExportLineage({ status: "failed", message: "There was an error in downloading Lineage!" });
return;
}
a.href = DOMURL.createObjectURL(blob);
if (blob.size > 10000000) {
onExportLineage({ status: "failed", message: "The Image size is huge, please open the image in a browser!" });
}
a.click();
onExportLineage({ status: "Success", message: "Successful" });
if (platform.name === "Safari") {
that.refreshGraphForSafari({
edgeEl: that.$("svg g.node")
});
}
}, "image/png");
hiddenSvgEl.remove();
canvas.remove();
} catch (err) {
onExportLineage({ status: "failed", message: "There was an error in downloading Lineage!" });
}
};
img.src = url;
}, 0);
}
};
export default LineageUtils;