You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@atlas.apache.org by sa...@apache.org on 2019/03/20 19:21:25 UTC
[atlas] branch master updated: ATLAS-3031: UI : Allow user to
export the lineage in PNG formatt
This is an automated email from the ASF dual-hosted git repository.
sarath pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/atlas.git
The following commit(s) were added to refs/heads/master by this push:
new 27916dd ATLAS-3031: UI : Allow user to export the lineage in PNG formatt
27916dd is described below
commit 27916dd49f854678b84bde8023aee6e47cbeb7ce
Author: gutkaBinit <bi...@gmail.com>
AuthorDate: Wed Mar 20 12:20:44 2019 -0700
ATLAS-3031: UI : Allow user to export the lineage in PNG formatt
Signed-off-by: Sarath Subramanian <ss...@hortonworks.com>
---
dashboardv2/public/css/scss/graph.scss | 4 +
.../js/templates/graph/LineageLayoutView_tmpl.html | 10 +-
.../public/js/views/graph/LineageLayoutView.js | 156 +++++++++++++++++++--
3 files changed, 157 insertions(+), 13 deletions(-)
diff --git a/dashboardv2/public/css/scss/graph.scss b/dashboardv2/public/css/scss/graph.scss
index b72236a..3073aee 100644
--- a/dashboardv2/public/css/scss/graph.scss
+++ b/dashboardv2/public/css/scss/graph.scss
@@ -421,4 +421,8 @@ span#zoom_in {
word-break: break-all;
}
+}
+
+.hidden-svg {
+ visibility: hidden;
}
\ No newline at end of file
diff --git a/dashboardv2/public/js/templates/graph/LineageLayoutView_tmpl.html b/dashboardv2/public/js/templates/graph/LineageLayoutView_tmpl.html
index 8e10e7f..71edcba 100644
--- a/dashboardv2/public/js/templates/graph/LineageLayoutView_tmpl.html
+++ b/dashboardv2/public/js/templates/graph/LineageLayoutView_tmpl.html
@@ -90,6 +90,11 @@
</div>
<div class="graph-button-group pull-right">
<div>
+ <button data-id="saveSvg" class="disabled btn btn-action btn-gray btn-sm" title="Export to PNG">
+ <i class="fa fa-camera"></i>
+ </button>
+ </div>
+ <div>
<button type="button" data-id="setting-toggler" class="btn btn-action btn-gray btn-sm"><i class="fa fa-gear"></i></button>
</div>
<div>
@@ -127,12 +132,11 @@
<div class="fontLoader">
<i class="fa fa-refresh fa-spin-custom"></i>
</div>
- <canvas width="960" height="500" style="display:none; position: absolute;"></canvas>
<div class="legends pull-left" style="height: 25px; padding: 2px;">
<span style="margin-right: 8px; color:#fb4200;"><i class="fa fa-circle-o fa-fw" aria-hidden="true"></i>Current Entity</span>
<span style="margin-right: 8px; color:#df9b00;"><i class="fa fa-long-arrow-right fa-fw" aria-hidden="true"></i>Lineage</span>
<span style="margin-right: 8px; color:#fb4200;"><i class="fa fa-long-arrow-right fa-fw" aria-hidden="true"></i>Impact</span>
</div>
- <!-- <svg width="100%" height="calc(100% - 28px)" preserveAspectRatio="xMidYMid meet" viewBox="0 0 854 330" enable-background="new 0 0 854 330" xml:space="preserve"></svg> -->
<svg width="{{width}}" height="{{height}}" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"></svg>
-</div>
\ No newline at end of file
+</div>
+<div class="hidden-svg"></div>
\ No newline at end of file
diff --git a/dashboardv2/public/js/views/graph/LineageLayoutView.js b/dashboardv2/public/js/views/graph/LineageLayoutView.js
index 790e8cf..de510cb 100644
--- a/dashboardv2/public/js/views/graph/LineageLayoutView.js
+++ b/dashboardv2/public/js/views/graph/LineageLayoutView.js
@@ -63,7 +63,8 @@ define(['require',
searchNode: '[data-id="searchNode"]',
nodeDetailTable: '[data-id="nodeDetailTable"]',
showOnlyHoverPath: '[data-id="showOnlyHoverPath"]',
- showTooltip: '[data-id="showTooltip"]'
+ showTooltip: '[data-id="showTooltip"]',
+ saveSvg: '[data-id="saveSvg"]',
},
templateHelpers: function() {
return {
@@ -82,6 +83,7 @@ define(['require',
events["click " + this.ui.settingToggler] = 'onClickSettingToggler';
events["click " + this.ui.lineageFullscreenToggler] = 'onClickLineageFullscreenToggler';
events["click " + this.ui.searchToggler] = 'onClickSearchToggler';
+ events["click " + this.ui.saveSvg] = 'onClickSaveSvg';
return events;
},
@@ -488,7 +490,8 @@ define(['require',
createGraph: function() {
var that = this,
width = this.$('svg').width(),
- height = this.$('svg').height();
+ height = this.$('svg').height(),
+ imageObject = {};
this.g.nodes().forEach(function(v) {
var node = that.g.node(v);
// Round the corners of the nodes
@@ -500,6 +503,8 @@ define(['require',
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")
@@ -536,17 +541,49 @@ define(['require',
.attr("height", "100%")
.append('image')
.attr("xlink:href", function(d) {
+ var that = this;
if (node) {
- return Utils.getEntityIconPath({ entityData: 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")
- .on("error", function() {
- this.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Utils.getEntityIconPath({ entityData: node, errorUrl: this.href.baseVal }));
- });
+ .attr("y", currentNode ? "3" : "4").attr("width", "40")
+ .attr("height", "40");
node.intersect = function(point) {
return dagreD3.intersect.circle(node, currentNode ? 24 : 21, point);
@@ -770,6 +807,7 @@ define(['require',
g: this.g,
guid: this.guid
}).init();
+ this.$el.find('[data-id="saveSvg"]').removeClass('disabled')
},
renderLineageTypeSearch: function() {
var that = this;
@@ -870,7 +908,105 @@ define(['require',
"attributeDefs": attributeDefs,
"sortBy": false
}));
- }
+ },
+ onClickSaveSvg: function(e, a) {
+ var that = this;
+ var loaderTargetDiv = $(e.currentTarget).find('>i');
+ if ($(e.currentTarget).hasClass('disabled')) {
+ Utils.notifyWarn({
+ content: "Lineage can be downloaded once it is rendered."
+ });
+ return false; // return if the lineage is not loaded.
+ }
+
+ 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;
+
+ $('.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));
+ svgClone.attributes.viewBox.value = "-10,-10," + 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");
+ a.download = "download.png";
+ 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!"
+ });
+ 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');
+ }
+ },
});
return LineageLayoutView;
});
\ No newline at end of file