blob: 397deed3a02532c675e3a237c98f7b5dbb8d6109 [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/RelationshipLayoutView_tmpl',
'collection/VLineageList',
'models/VEntity',
'utils/Utils',
'd3-tip',
'utils/Enums',
'utils/UrlLinks',
'platform'
], function(require, Backbone, RelationshipLayoutViewtmpl, VLineageList, VEntity, Utils, d3Tip, Enums, UrlLinks, platform) {
'use strict';
var RelationshipLayoutView = Backbone.Marionette.LayoutView.extend(
/** @lends RelationshipLayoutView */
{
_viewName: 'RelationshipLayoutView',
template: RelationshipLayoutViewtmpl,
/** Layout sub regions */
regions: {},
/** ui selector cache */
ui: {
relationshipDetailClose: '[data-id="close"]',
searchNode: '[data-id="searchNode"]'
},
/** ui events hash */
events: function() {
var events = {};
events["click " + this.ui.relationshipDetailClose] = function() {
this.toggleInformationSlider({ close: true });
};
events["keyup " + this.ui.searchNode] = 'searchNode';
return events;
},
/**
* intialize a new RelationshipLayoutView Layout
* @constructs
*/
initialize: function(options) {
_.extend(this, _.pick(options, 'entity', 'entityName', 'guid', 'actionCallBack'));
this.graphData = this.createData(this.entity);
},
createData: function(entity) {
var that = this,
links = [],
nodes = {};
if (entity && entity.relationshipAttributes) {
_.each(entity.relationshipAttributes, function(obj, key) {
if (!_.isEmpty(obj)) {
links.push({
"source": nodes[that.entity.typeName] ||
(nodes[that.entity.typeName] = _.extend({ "name": that.entity.typeName }, { value: entity })),
"target": nodes[key] ||
(nodes[key] = _.extend({
"name": key
}, { value: obj })),
"value": obj
})
}
});
}
return { nodes: nodes, links: links };
},
onRender: function() {
},
onShow: function(argument) {
if (this.graphData && _.isEmpty(this.graphData.links)) {
this.noRelationship();
} else {
this.createGraph(this.graphData);
}
},
noRelationship: function() {
this.$('svg').html('<text x="50%" y="50%" alignment-baseline="middle" text-anchor="middle">No relationship data found</text>');
},
toggleInformationSlider: function(options) {
if (options.open && !this.$('.relationship-details').hasClass("open")) {
this.$('.relationship-details').addClass('open');
} else if (options.close && this.$('.relationship-details').hasClass("open")) {
d3.selectAll('circle').attr("stroke", "none");
this.$('.relationship-details').removeClass('open');
}
},
searchNode: function(e) {
var $el = $(e.currentTarget);
this.updateRelationshipDetails(_.extend({}, $el.data(), { searchString: $el.val() }))
},
updateRelationshipDetails: function(options) {
var data = options.obj.value,
typeName = data.typeName || options.obj.name,
searchString = options.searchString,
listString = "";
this.ui.searchNode.hide();
this.$("[data-id='typeName']").text(typeName);
var getElement = function(options) {
var name = options.entityName ? options.entityName : Utils.getName(options, "displayText");
return "<li><a href=#!/detailPage/" + options.guid + "?tabActive=relationship>" + _.escape(name) + " (" + options.typeName + ")</a></li>";
}
if (_.isArray(data)) {
if (data.length > 1) {
this.ui.searchNode.show();
}
_.each(_.sortBy(data, "displayText"), function(val) {
var name = Utils.getName(val, "displayText"),
valObj = _.extend({}, val, { entityName: name });
if (searchString) {
if (name.search(new RegExp(searchString, "i")) != -1) {
listString += getElement(valObj);
} else {
return;
}
} else {
listString += getElement(valObj);
}
});
} else {
listString += getElement(data);
}
this.$("[data-id='entityList']").html(listString);
},
createGraph: function(data) {
var that = this,
width = this.$('svg').width(),
height = this.$('svg').height();
var scale = 1.0,
activeEntityColor = "#00b98b";
var force = d3.layout.force()
.nodes(d3.values(data.nodes))
.links(data.links)
.size([width, height])
.linkDistance(200)
.charge(function(d) {
var charge = -500;
if (d.index === 0) charge = 100
return charge;
})
.on("tick", tick)
.start();
var zoom = d3.behavior.zoom()
.scale(scale)
.scaleExtent([1, 5])
.on("zoom", zoomed);
function zoomed() {
container.attr("transform",
"translate(" + zoom.translate() + ")" +
"scale(" + zoom.scale() + ")"
);
}
function interpolateZoom(translate, scale) {
var self = this;
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));
zoomed();
};
});
}
function zoomClick() {
var clicked = d3.event.target,
direction = 1,
factor = 0.5,
target_zoom = 1,
center = [width / 2, height / 2],
extent = zoom.scaleExtent(),
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);
if (target_zoom < extent[0] || target_zoom > extent[1]) { return false; }
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];
interpolateZoom([view.x, view.y], view.k);
}
d3.selectAll(this.$('span.lineageZoomButton')).on('click', zoomClick);
var svg = d3.select(this.$("svg")[0])
.attr("viewBox", "0 0 " + width + " " + height)
.attr("enable-background", "new 0 0 " + width + " " + height)
.call(zoom)
.on("dblclick.zoom", null),
drag = force.drag()
.on("dragstart", dragstart);
var container = svg.append("g")
.attr("id", "container")
.attr("transform", "translate(0,0)scale(1,1)");
// build the arrow.
container.append("svg:defs").selectAll("marker")
.data(["output"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 10)
.attr("refY", -0.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5")
.attr("fill", activeEntityColor);
// add the links and the arrows
var path = container.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("svg:path")
// .attr("class", function(d) { return "link " + d.type; })
.attr("class", "relatioship-link")
.attr("stroke", activeEntityColor)
.attr("marker-end", "url(#output)");
// define the nodes
var node = container.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.on('touchstart', function(d) {
if (d && d.value && d.value.guid != that.guid) {
d3.event.stopPropagation();
}
})
.on('mousedown', function(d) {
if (d && d.value && d.value.guid != that.guid) {
d3.event.stopPropagation();
}
})
.on('click', function(d) {
if (d3.event.defaultPrevented) return; // ignore drag
that.toggleInformationSlider({ open: true, obj: d });
that.ui.searchNode.data({ obj: d });
d3.selectAll('circle').attr("stroke", "none");
d3.select('circle[typename="' + d.name + '"]').attr("stroke", function(d) {
if (d && d.value && d.value.guid == that.guid) {
return "#316132";
} else {
return activeEntityColor;
}
});
that.updateRelationshipDetails({ obj: d });
}).call(force.drag);
// add the nodes
var circleContainer = node.append("g");
circleContainer.on("dblclick", function(d) {
if ((_.isArray(d.value) && d.value.length == 1) || d.value.guid) {
var guid = _.isArray(d.value) ? _.first(d.value).guid : d.value.guid;
Utils.setUrl({
url: '#!/detailPage/' + guid,
mergeBrowserUrl: false,
urlParams: { tabActive: 'relationship' },
trigger: true
});
}
})
circleContainer.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", function(d) {
d.radius = 25;
return d.radius;
})
.attr("fill", function(d) {
if (d && d.value && d.value.guid == that.guid) {
return activeEntityColor;
} else {
return "#e0e0e0";
}
})
.attr("typename", function(d) {
return d.name;
})
circleContainer.append("text")
.attr('x', 0)
.attr('y', 0)
.attr('dy', (25 - 17))
.attr("text-anchor", "middle")
.style("font-family", "FontAwesome")
.style('font-size', function(d) { return '25px'; })
.text(function(d) {
var iconObj = Enums.graphIcon[d.name];
if (iconObj && iconObj.textContent) {
return iconObj.textContent;
} else {
if (d && _.isArray(d.value) && d.value.length > 1) {
return '\uf0c5';
} else {
return '\uf016';
}
}
})
.attr("fill", function(d) {
if (d && d.value && d.value.guid == that.guid) {
return "#fff";
} else {
return "#000";
}
});
var countBox = circleContainer.append('g')
countBox.append("circle")
.attr("cx", 18)
.attr("cy", -20)
.attr("r", function(d) {
if (_.isArray(d.value) && d.value.length > 1) {
return 10;
}
});
countBox.append("text")
.attr('dx', 18)
.attr('dy', -16)
.attr("text-anchor", "middle")
.attr("fill", "#e0e0e0")
.text(function(d) {
if (_.isArray(d.value) && d.value.length > 1) {
return d.value.length;
}
});
// add the text
node.append("text")
.attr("x", -15)
.attr("y", "35")
.text(function(d) { return d.name; });
// add the curvy lines
function tick() {
path.attr("d", function(d) {
var diffX = d.target.x - d.source.x,
diffY = d.target.y - d.source.y,
// Length of path from center of source node to center of target node
pathLength = Math.sqrt((diffX * diffX) + (diffY * diffY)),
// x and y distances from center to outside edge of target node
offsetX = (diffX * d.target.radius) / pathLength,
offsetY = (diffY * d.target.radius) / pathLength;
return "M" + d.source.x + "," + d.source.y + "A" + pathLength + "," + pathLength + " 0 0,1 " + (d.target.x - offsetX) + "," + (d.target.y - offsetY)
});
node.attr("transform", function(d) {
// if (d && d.value && d.value.guid == that.guid) {
// Center fixed node
// var damper = 0.1;
// d.x = (width / 2)
// d.y = (height / 2)
// }
return "translate(" + d.x + "," + d.y + ")";
});
}
function dragstart(d) {
d3.select(this).classed("fixed", d.fixed = true);
}
}
});
return RelationshipLayoutView;
});