RelationshipLayoutView.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. define([ "require", "backbone", "hbs!tmpl/graph/RelationshipLayoutView_tmpl", "collection/VLineageList", "models/VEntity", "utils/Utils", "utils/CommonViewFunction", "d3", "d3-tip", "utils/Enums", "utils/UrlLinks", "platform" ], function(require, Backbone, RelationshipLayoutViewtmpl, VLineageList, VEntity, Utils, CommonViewFunction, d3, d3Tip, Enums, UrlLinks, platform) {
  2. "use strict";
  3. var RelationshipLayoutView = Backbone.Marionette.LayoutView.extend({
  4. _viewName: "RelationshipLayoutView",
  5. template: RelationshipLayoutViewtmpl,
  6. className: "resizeGraph",
  7. regions: {},
  8. ui: {
  9. relationshipDetailClose: '[data-id="close"]',
  10. searchNode: '[data-id="searchNode"]',
  11. relationshipViewToggle: 'input[name="relationshipViewToggle"]',
  12. relationshipDetailTable: "[data-id='relationshipDetailTable']",
  13. relationshipSVG: "[data-id='relationshipSVG']",
  14. relationshipDetailValue: "[data-id='relationshipDetailValue']",
  15. zoomControl: "[data-id='zoomControl']",
  16. boxClose: '[data-id="box-close"]',
  17. noValueToggle: "[data-id='noValueToggle']"
  18. },
  19. events: function() {
  20. var events = {};
  21. return events["click " + this.ui.relationshipDetailClose] = function() {
  22. this.toggleInformationSlider({
  23. close: !0
  24. });
  25. }, events["keyup " + this.ui.searchNode] = "searchNode", events["click " + this.ui.boxClose] = "toggleBoxPanel",
  26. events["change " + this.ui.relationshipViewToggle] = function(e) {
  27. this.relationshipViewToggle(e.currentTarget.checked);
  28. }, events["click " + this.ui.noValueToggle] = function(e) {
  29. Utils.togglePropertyRelationshipTableEmptyValues({
  30. inputType: this.ui.noValueToggle,
  31. tableEl: this.ui.relationshipDetailValue
  32. });
  33. }, events;
  34. },
  35. initialize: function(options) {
  36. _.extend(this, _.pick(options, "entity", "entityName", "guid", "actionCallBack", "attributeDefs")),
  37. this.graphData = this.createData(this.entity);
  38. },
  39. createData: function(entity) {
  40. var that = this, links = [], nodes = {};
  41. return entity && entity.relationshipAttributes && _.each(entity.relationshipAttributes, function(obj, key) {
  42. _.isEmpty(obj) || links.push({
  43. source: nodes[that.entity.typeName] || (nodes[that.entity.typeName] = _.extend({
  44. name: that.entity.typeName
  45. }, {
  46. value: entity
  47. })),
  48. target: nodes[key] || (nodes[key] = _.extend({
  49. name: key
  50. }, {
  51. value: obj
  52. })),
  53. value: obj
  54. });
  55. }), {
  56. nodes: nodes,
  57. links: links
  58. };
  59. },
  60. onRender: function() {
  61. this.ui.zoomControl.hide(), this.$el.addClass("auto-height");
  62. },
  63. onShow: function(argument) {
  64. this.graphData && _.isEmpty(this.graphData.links) ? this.noRelationship() : this.createGraph(this.graphData),
  65. this.createTable();
  66. },
  67. noRelationship: function() {
  68. this.$("svg").html('<text x="50%" y="50%" alignment-baseline="middle" text-anchor="middle">No relationship data found</text>');
  69. },
  70. toggleInformationSlider: function(options) {
  71. options.open && !this.$(".relationship-details").hasClass("open") ? this.$(".relationship-details").addClass("open") : options.close && this.$(".relationship-details").hasClass("open") && (d3.selectAll("circle").attr("stroke", "none"),
  72. this.$(".relationship-details").removeClass("open"));
  73. },
  74. toggleBoxPanel: function(options) {
  75. var el = options && options.el;
  76. options && options.nodeDetailToggler, options.currentTarget;
  77. this.$el.find(".show-box-panel").removeClass("show-box-panel"), el && el.addClass && el.addClass("show-box-panel"),
  78. this.$("circle.node-detail-highlight").removeClass("node-detail-highlight");
  79. },
  80. searchNode: function(e) {
  81. var $el = $(e.currentTarget);
  82. this.updateRelationshipDetails(_.extend({}, $el.data(), {
  83. searchString: $el.val()
  84. }));
  85. },
  86. updateRelationshipDetails: function(options) {
  87. var data = options.obj.value, typeName = data.typeName || options.obj.name, searchString = _.escape(options.searchString), listString = "", getEntityTypelist = function(options) {
  88. var activeEntityColor = "#4a90e2", deletedEntityColor = "#BB5838", entityTypeHtml = "<pre>", getdefault = function(obj) {
  89. var options = obj.options, status = Enums.entityStateReadOnly[options.entityStatus || options.status] ? " deleted-relation" : "", guid = options.guid, entityColor = obj.color, name = obj.name, typeName = options.typeName;
  90. return "AtlasGlossaryTerm" === typeName ? "<li class=" + status + '><a style="color:' + entityColor + '" href="#!/glossary/' + guid + "?guid=" + guid + '&gType=term&viewType=term&fromView=entity">' + name + " (" + typeName + ")</a></li>" : "<li class=" + status + "><a style='color:" + entityColor + "' href=#!/detailPage/" + guid + "?tabActive=relationship>" + name + " (" + typeName + ")</a></li>";
  91. }, getWithButton = function(obj) {
  92. var options = obj.options, status = Enums.entityStateReadOnly[options.entityStatus || options.status] ? " deleted-relation" : "", entityColor = (options.guid,
  93. obj.color), name = obj.name, relationship = (options.typeName, obj.relationship || !1), icon = (obj.entity || !1,
  94. '<i class="fa fa-trash"></i>'), title = "Deleted";
  95. return relationship && (icon = '<i class="fa fa-long-arrow-right"></i>', status = Enums.entityStateReadOnly[options.relationshipStatus || options.status] ? "deleted-relation" : "",
  96. title = "关系已删除"), "<li class=" + status + "><a style='color:" + entityColor + "' href=#!/detailPage/" + options.guid + "?tabActive=relationship>" + _.escape(name) + " (" + options.typeName + ')</a><button type="button" title="' + title + '" class="btn btn-sm deleteBtn deletedTableBtn btn-action ">' + icon + "</button></li>";
  97. }, name = options.entityName ? options.entityName : Utils.getName(options, "displayText");
  98. return "ACTIVE" == options.entityStatus ? "ACTIVE" == options.relationshipStatus ? entityTypeHtml = getdefault({
  99. color: activeEntityColor,
  100. options: options,
  101. name: name
  102. }) : "DELETED" == options.relationshipStatus && (entityTypeHtml = getWithButton({
  103. color: activeEntityColor,
  104. options: options,
  105. name: name,
  106. relationship: !0
  107. })) : entityTypeHtml = "DELETED" == options.entityStatus ? getWithButton({
  108. color: deletedEntityColor,
  109. options: options,
  110. name: name,
  111. entity: !0
  112. }) : getdefault({
  113. color: activeEntityColor,
  114. options: options,
  115. name: name
  116. }), entityTypeHtml + "</pre>";
  117. };
  118. this.ui.searchNode.hide(), this.$("[data-id='typeName']").text(Utils.toChinese(typeName) );
  119. var getElement = function(options) {
  120. var entityTypeButton = (options.entityName ? options.entityName : Utils.getName(options, "displayText"),
  121. getEntityTypelist(options));
  122. return entityTypeButton;
  123. };
  124. _.isArray(data) ? (data.length > 1 && this.ui.searchNode.show(), _.each(_.sortBy(data, "displayText"), function(val) {
  125. var name = Utils.getName(val, "displayText"), valObj = _.extend({}, val, {
  126. entityName: name
  127. });
  128. if (searchString) {
  129. if (name.search(new RegExp(searchString, "i")) == -1) return;
  130. listString += getElement(valObj);
  131. } else listString += getElement(valObj);
  132. })) : listString += getElement(data), this.$("[data-id='entityList']").html(listString);
  133. },
  134. createGraph: function(data) {
  135. function update() {
  136. path = container.append("svg:g").selectAll("path").data(links).enter().append("svg:path").attr("class", "relatioship-link").attr("stroke", function(d) {
  137. return getPathColor({
  138. data: d,
  139. type: "path"
  140. });
  141. }).attr("marker-end", function(d) {
  142. return "url(#" + (isAllEntityRelationDeleted({
  143. data: d
  144. }) ? "deletedLink" : "activeLink") + ")";
  145. }), node = container.selectAll(".node").data(nodes).enter().append("g").attr("class", "node").on("mousedown", function() {
  146. console.log(d3.event), d3.event.preventDefault();
  147. }).on("click", function(d) {
  148. if (!d3.event.defaultPrevented) {
  149. if (d && d.value && d.value.guid == that.guid) return void that.ui.boxClose.trigger("click");
  150. that.toggleBoxPanel({
  151. el: that.$(".relationship-node-details")
  152. }), that.ui.searchNode.data({
  153. obj: d
  154. }), $(this).find("circle").addClass("node-detail-highlight"), that.updateRelationshipDetails({
  155. obj: d
  156. });
  157. }
  158. }).call(d3.drag().on("start", dragstarted).on("drag", dragged));
  159. var circleContainer = node.append("g");
  160. circleContainer.append("circle").attr("cx", 0).attr("cy", 0).attr("r", function(d) {
  161. return d.radius = 25, d.radius;
  162. }).attr("fill", function(d) {
  163. return d && d.value && d.value.guid == that.guid ? isAllEntityRelationDeleted({
  164. data: d,
  165. type: "node"
  166. }) ? deletedEntityColor : selectedNodeColor : isAllEntityRelationDeleted({
  167. data: d,
  168. type: "node"
  169. }) ? deletedEntityColor : activeEntityColor;
  170. }).attr("typename", function(d) {
  171. return d.name;
  172. }), circleContainer.append("text").attr("x", 0).attr("y", 0).attr("dy", 8).attr("text-anchor", "middle").style("font-family", "FontAwesome").style("font-size", function(d) {
  173. return "25px";
  174. }).text(function(d) {
  175. var iconObj = Enums.graphIcon[d.name];
  176. return iconObj && iconObj.textContent ? iconObj.textContent : d && _.isArray(d.value) && d.value.length > 1 ? "" : "";
  177. }).attr("fill", function(d) {
  178. return "#fff";
  179. });
  180. var countBox = circleContainer.append("g");
  181. countBox.append("circle").attr("cx", 18).attr("cy", -20).attr("r", function(d) {
  182. if (_.isArray(d.value) && d.value.length > 1) return 10;
  183. }), countBox.append("text").attr("dx", 18).attr("dy", -16).attr("text-anchor", "middle").attr("fill", defaultEntityColor).text(function(d) {
  184. if (_.isArray(d.value) && d.value.length > 1) return d.value.length;
  185. }), node.append("text").attr("x", -15).attr("y", "35").text(function(d) {
  186. return Utils.toChinese(d.name);
  187. }), simulation.nodes(nodes).on("tick", ticked), simulation.force("link").links(links);
  188. }
  189. function ticked() {
  190. path.attr("d", function(d) {
  191. var diffX = d.target.x - d.source.x, diffY = d.target.y - d.source.y, pathLength = Math.sqrt(diffX * diffX + diffY * diffY), offsetX = diffX * d.target.radius / pathLength, offsetY = diffY * d.target.radius / pathLength;
  192. return "M" + d.source.x + "," + d.source.y + "A" + pathLength + "," + pathLength + " 0 0,1 " + (d.target.x - offsetX) + "," + (d.target.y - offsetY);
  193. }), node.attr("transform", function(d) {
  194. return "translate(" + d.x + "," + d.y + ")";
  195. });
  196. }
  197. function dragstarted(d) {
  198. d3.event.sourceEvent.stopPropagation(), d && d.value && d.value.guid != that.guid && (d3.event.active || simulation.alphaTarget(.3).restart(),
  199. d.fx = d.x, d.fy = d.y);
  200. }
  201. function dragged(d) {
  202. d && d.value && d.value.guid != that.guid && (d.fx = d3.event.x, d.fy = d3.event.y);
  203. }
  204. function getPathColor(options) {
  205. return isAllEntityRelationDeleted(options) ? deletedEntityColor : activeEntityColor;
  206. }
  207. function isAllEntityRelationDeleted(options) {
  208. var data = options.data, type = options.type, d = $.extend(!0, {}, data);
  209. return d && !_.isArray(d.value) && (d.value = [ d.value ]), _.findIndex(d.value, function(val) {
  210. return "node" == type ? "ACTIVE" == (val.entityStatus || val.status) : "ACTIVE" == val.relationshipStatus;
  211. }) == -1;
  212. }
  213. var node, path, that = this, width = this.$("svg").width(), height = this.$("svg").height(), nodes = d3.values(data.nodes), links = data.links, activeEntityColor = "#00b98b", deletedEntityColor = "#BB5838", defaultEntityColor = "#e0e0e0", selectedNodeColor = "#4a90e2", svg = d3.select(this.$("svg")[0]).attr("viewBox", "0 0 " + width + " " + height).attr("enable-background", "new 0 0 " + width + " " + height), container = svg.append("g").attr("id", "container").attr("transform", "translate(0,0)scale(1,1)"), zoom = d3.zoom().scaleExtent([ .1, 4 ]).on("zoom", function() {
  214. container.attr("transform", d3.event.transform);
  215. });
  216. svg.call(zoom).on("dblclick.zoom", null), container.append("svg:defs").selectAll("marker").data([ "deletedLink", "activeLink" ]).enter().append("svg:marker").attr("id", String).attr("viewBox", "-0 -5 10 10").attr("refX", 10).attr("refY", -.5).attr("orient", "auto").attr("markerWidth", 6).attr("markerHeight", 6).append("svg:path").attr("d", "M 0,-5 L 10 ,0 L 0,5").attr("fill", function(d) {
  217. return "deletedLink" == d ? deletedEntityColor : activeEntityColor;
  218. }).style("stroke", "none");
  219. var forceLink = d3.forceLink().id(function(d) {
  220. return d.id;
  221. }).distance(function(d) {
  222. return 100;
  223. }).strength(1), simulation = d3.forceSimulation().force("link", forceLink).force("charge", d3.forceManyBody()).force("center", d3.forceCenter(width / 2, height / 2));
  224. update();
  225. var zoomClick = function() {
  226. var scaleFactor = .8;
  227. "zoom_in" === this.id && (scaleFactor = 1.3), zoom.scaleBy(svg.transition().duration(750), scaleFactor);
  228. };
  229. d3.selectAll(this.$(".lineageZoomButton")).on("click", zoomClick);
  230. },
  231. createTable: function() {
  232. this.entityModel = new VEntity({});
  233. var table = CommonViewFunction.propertyTable({
  234. scope: this,
  235. valueObject: this.entity.relationshipAttributes,
  236. attributeDefs: this.attributeDefs
  237. });
  238. this.ui.relationshipDetailValue.html(table), Utils.togglePropertyRelationshipTableEmptyValues({
  239. inputType: this.ui.noValueToggle,
  240. tableEl: this.ui.relationshipDetailValue
  241. });
  242. },
  243. relationshipViewToggle: function(checked) {
  244. this.ui.relationshipDetailTable.toggleClass("visible invisible"), this.ui.relationshipSVG.toggleClass("visible invisible"),
  245. checked ? (this.ui.zoomControl.hide(), this.$el.addClass("auto-height")) : (this.ui.zoomControl.show(),
  246. this.$el.removeClass("auto-height"));
  247. }
  248. });
  249. return RelationshipLayoutView;
  250. });