DiagramContentProvider.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. /**
  2. * Licensed Materials - Property of IBM IBM Cognos Products: Modeling UI (C) Copyright IBM Corp. 2015, 2020
  3. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP
  4. * Schedule Contract with IBM Corp.
  5. */
  6. define([
  7. 'jquery',
  8. 'underscore',
  9. 'bi/commons/ui/core/Class',
  10. 'bi/commons/utils/Utils',
  11. '../ui/JoinPopupView',
  12. './StringUtils',
  13. './NodeStylesUtils',
  14. './utils',
  15. 'bi/commons/utils/BidiUtil',
  16. '../../StringResourcesBridge',
  17. ], function($, _, Class, CommonUtils, JoinPopupView, StringUtils, NodeStylesUtils, utils, BidiUtil, resources) {
  18. var d3;
  19. var DiagramContentProvider = Class.extend({
  20. CommonUtils: CommonUtils,
  21. init: function(options) {
  22. DiagramContentProvider.inherited('init', this);
  23. _.extend(this, options);
  24. d3 = options.d3;
  25. this._dblClicked = false;
  26. },
  27. setModel: function(model) {
  28. this._model = model;
  29. },
  30. getModel: function(){
  31. return this._model;
  32. },
  33. /**
  34. * Will draw links in the diagram based on this definition
  35. *
  36. * @param {array}
  37. * links - A d3 array of links in a force diagram
  38. */
  39. drawLinks: function(SVGContext, links) {
  40. var d3Link = SVGContext.diagramContainer.selectAll('.mui-diagram-link')
  41. .data(links, function(d) {
  42. return [d.identifier, d.instanceType, d.isValid, d.isLeftMincardZero, d.isRightMincardZero, d.sourceCardinality, d.targetCardinality];
  43. });
  44. // exit old links
  45. d3Link.exit().remove();
  46. var link = d3Link.enter().insert('g').attr(
  47. 'class', function(d) {
  48. var referenceClass = d.instanceType === 'reference' ? 'mui-reference-link ' : '';
  49. return 'mui-diagram-link ' + referenceClass + d.source.identifier + ' ' + d.target.identifier + ' ' + d.identifier;
  50. });
  51. link.append('line').attr('class', 'mui-diagram-link-interact-line');
  52. link.append('line').attr('class', 'mui-diagram-link-display-line');
  53. // validation icon
  54. var validationIconGroup = utils.d3Attrs(link.append('g'), NodeStylesUtils.diagramNodeStyles.validationLinkGroupIconStyle).on('click', function(data) {
  55. d3.event.stopPropagation();
  56. if (data.validationCallback) {
  57. data.validationCallback(d3.event);
  58. }
  59. });
  60. utils.d3Attrs(validationIconGroup.append('use'), NodeStylesUtils.diagramNodeStyles.validationLinkIconStyle);
  61. utils.d3Attrs(validationIconGroup.append('circle'), NodeStylesUtils.diagramNodeStyles.validationLinkCircleStyle);
  62. // group for link cardinality
  63. var linkCardinality = link.append('g').attr('class', 'mui-diagram-cardinality');
  64. this._drawLinkCardinalityNotation(linkCardinality, true);
  65. this._drawLinkCardinalityNotation(linkCardinality, false);
  66. this._attachLinkActions(link);
  67. // update links
  68. this._updateLinkProperties(link);
  69. this.animateLinkTick(link);
  70. return link.merge(d3Link);
  71. },
  72. /**
  73. * Update any existing properties on the links
  74. *
  75. * @param {array}
  76. * links - A d3 array of links in a force diagram
  77. */
  78. _updateLinkProperties: function(links) {
  79. links.each(function(d) {
  80. var node = d3.select(this);
  81. var representedLink = d.linksRepresented && d.linksRepresented[0] || {};
  82. // update cardinality source and target labels
  83. node.select('.mui-diagram-cardinality-source').classed('hidden', d.hideCardinality).classed('zero', representedLink.isLeftMincardZero).select('text')
  84. .text(d.sourceCardinality);
  85. node.select('.mui-diagram-cardinality-target').classed('hidden', d.hideCardinality).classed('zero', representedLink.isRightMincardZero).select('text')
  86. .text(d.targetCardinality);
  87. });
  88. },
  89. /**
  90. * A private method for styling cardinality markers
  91. * @param {SVG} linkGroup - an SVG element representing a link
  92. * @param {boolean} isSource - Indicates which end of the link a cardinality value belongs to ('true' is the source, 'false' is the target)
  93. */
  94. _drawLinkCardinalityNotation: function(linkGroup, isSource) {
  95. // group for cardinality
  96. var cardinalityGroup = linkGroup.append('g').attr('class', isSource ? 'mui-diagram-cardinality-source' : 'mui-diagram-cardinality-target');
  97. // rect styled as a circle
  98. // ... need to add the attributes directly on the node
  99. // as not all SVG element attributes can be styled with CSS
  100. // see: http://www.w3.org/TR/SVG/propidx.html
  101. utils.d3Attrs(cardinalityGroup.append('rect'), NodeStylesUtils.CardinalityStyle.boxStyle);
  102. // the cardinality relation text
  103. utils.d3Attrs(cardinalityGroup.append('text'), NodeStylesUtils.CardinalityStyle.textStyle);
  104. },
  105. /**
  106. * Will draw nodes in the diagram based on this definition
  107. *
  108. * @param {array}
  109. * d3Nodes - A d3 array of nodes in a force diagram
  110. */
  111. drawNodes: function(SVGContext, nodes) {
  112. var d3Node = SVGContext.diagramContainer.selectAll('.mui-diagram-node')
  113. .data(nodes, function(d) {
  114. return [d.tableRowCount, d.displayTableInfo, d.identifier, d.label, d.screenTip, d.filter, d.hidden, d.instanceType, d.isValid];
  115. });
  116. d3Node.exit().remove();
  117. var box = d3Node.enter().insert('g').attr(
  118. 'class', function(d) {
  119. return 'mui-diagram-node ' + d.identifier;
  120. });
  121. box.append('rect').attr('class', 'mui-diagram-node-box').append('title').text(function (d) {
  122. return BidiUtil.enforceTextDirection(d.screenTip ? (d.screenTip + ' : ' + d.label) : d.label);
  123. });
  124. var boxContent = box.append('g').attr('class', function(d) {
  125. return 'mui-diagram-node-content ' + d.identifier;
  126. });
  127. var validationIconGroup = boxContent.append('g').on('mousedown', function (data) {
  128. d3.event.stopPropagation();
  129. if (data.validationCallback) {
  130. data.validationCallback(d3.event);
  131. }
  132. });
  133. utils.d3Attrs(validationIconGroup.append('use'), NodeStylesUtils.diagramNodeStyles.validationNodeIconStyle);
  134. utils.d3Attrs(validationIconGroup.append('circle'), NodeStylesUtils.diagramNodeStyles.validationNodeCircleStyle);
  135. // text
  136. var self = this;
  137. boxContent.append('text').text(function(d) {
  138. var isIconPresent = this.__data__.filter || d.instanceType === 'reference' || d.instanceType === 'package' || !d.isValid;
  139. return BidiUtil.enforceTextDirection(StringUtils.shortenTextMidEllipsis(d.label, StringUtils.getMaxDiagramLabelLength(isIconPresent, d.label)));
  140. });
  141. // filter icon
  142. boxContent.append('use').attr('class', 'mui-diagram-filter');
  143. // reference icon
  144. boxContent.append('use').attr('class', 'mui-daigram-reference');
  145. // table details
  146. var boxTableDetailsContent = box.append('g').attr('class', function(d) {
  147. return 'mui-diagram-table-details ' + d.identifier;
  148. });
  149. boxTableDetailsContent.append('rect');
  150. boxTableDetailsContent.append('text').text(function(d) {
  151. return BidiUtil.enforceTextDirection(d.statisticsLoading ? resources.get('moduleRowCount', { rowCount: '--' }) : d.tableRowCount);
  152. });
  153. // style it
  154. this.styleNode(box);
  155. this._attachNodeActions(box);
  156. this.animateNodeTick(box);
  157. return box.merge(d3Node);
  158. },
  159. styleNode: function(box) {
  160. // rect
  161. utils.d3Attrs(box.select('.mui-diagram-node-box'), NodeStylesUtils.diagramNodeStyles.boxStyle);
  162. utils.d3Styles(box.select('.mui-diagram-node-box'), NodeStylesUtils.diagramNodeStyles.boxColorStyle);
  163. // text
  164. utils.d3Attrs(box.select('.mui-diagram-node-content').select('text'), NodeStylesUtils.diagramNodeStyles.textStyle);
  165. // filter icon
  166. utils.d3Attrs(box.select('.mui-diagram-filter'), NodeStylesUtils.diagramNodeStyles.filterIconStyle);
  167. // reference icon
  168. utils.d3Attrs(box.select('.mui-diagram-reference'), NodeStylesUtils.diagramNodeStyles.referenceIconStyle);
  169. // table details box
  170. utils.d3Attrs(box.select('.mui-diagram-table-details').select('rect'), NodeStylesUtils.diagramNodeStyles.tableDetailsBoxStyle);
  171. utils.d3Attrs(box.select('.mui-diagram-table-details').select('text'), NodeStylesUtils.diagramNodeStyles.tableDetailsTextStyle);
  172. },
  173. styleLink: function(box) {
  174. box.classed('mui-reference-link', function(d) {
  175. return d.instanceType === 'reference';
  176. });
  177. },
  178. _attachLinkActions: function(links) {
  179. var currentModel = this._model;
  180. var position = {};
  181. var handle;
  182. links.on(
  183. 'contextmenu', function(selectedLink, i, g){
  184. var event = d3.event;
  185. event.preventDefault();
  186. if (!utils.isOffset(position, {x: event.clientX, y: event.clientY})) {
  187. this.diagramStore.showContextMenu(event.pageX, event.pageY, [selectedLink.moserObject]);
  188. }
  189. return false;
  190. }.bind(this));
  191. links.on(
  192. 'click', function(selectedObjects, i, g){
  193. var event = d3.event;
  194. event.stopPropagation();
  195. this.diagramStore.hideContextMenu();
  196. setTimeout(function() {
  197. if (this._dblClicked) {
  198. return;
  199. }
  200. $(this.el).find('.mui_JoinPopup ').remove();
  201. var rect = this.el.getBoundingClientRect();
  202. var popupView = new JoinPopupView({
  203. relations: selectedObjects.linksRepresented,
  204. source: selectedObjects.source,
  205. target: selectedObjects.target,
  206. position: [event.clientX - rect.left, event.clientY - rect.top],
  207. $el: $(this.el),
  208. model: currentModel
  209. });
  210. popupView.render();
  211. }.bind(this), 300);
  212. }.bind(this));
  213. links.on(
  214. 'dblclick', function(selectedLink, i, g) {
  215. var event = d3.event;
  216. this._dblClicked = true;
  217. event.stopPropagation();
  218. this.diagramStore.editRelationship(selectedLink.moserObject).then(function() {
  219. this._dblClicked = false;
  220. }.bind(this));
  221. return false;
  222. }.bind(this));
  223. },
  224. handleDiagramNodeSelection: function(e){
  225. var isMultiSelect = this.CommonUtils.isControlKey(d3.event);
  226. var isRightClick = (d3.event.button === 2);
  227. var target = d3.select(d3.event.currentTarget);
  228. var targetIsSelected = target.classed('mui-diagram-selected');
  229. var targetIdentifier = e.identifier;
  230. var targetMoserObj = e.moserObject;
  231. if (isMultiSelect) {
  232. //don't de-select on a right click -> context menu will appear over unselected node
  233. if (!isRightClick || !targetIsSelected) {
  234. d3.select(d3.event.target.parentNode).classed('mui-diagram-selected', function(){
  235. return !d3.select(this).classed('mui-diagram-selected');
  236. });
  237. }
  238. if (target.classed('mui-diagram-selected')) {
  239. this.diagramStore.appendToSelection(targetMoserObj);
  240. } else {
  241. this.diagramStore.removeFromSelection(targetMoserObj);
  242. }
  243. //single click
  244. } else {
  245. //On sinle-click, deselect all other nodes than the target node.
  246. //on a right click where the target is already selected, do nothing
  247. if(!isRightClick || !targetIsSelected) {
  248. d3.select(d3.event.target.parentNode).classed('mui-diagram-selected', true);
  249. this.diagramStore.setSelection([targetMoserObj]);
  250. }
  251. }
  252. },
  253. /**
  254. * Defines action handlers for specific inputs and attaches them to actions on a node
  255. *
  256. * @param {array}
  257. * nodes - An array of d3 nodes in the diagram
  258. */
  259. _attachNodeActions: function(nodes) {
  260. var position = {};
  261. nodes.on(
  262. 'contextmenu', function(selectedNode, i, g) {
  263. var event = d3.event;
  264. event.preventDefault();
  265. if (!utils.isOffset(position, {x: event.clientX, y: event.clientY})) {
  266. var rect = this.el.getBoundingClientRect();
  267. this.handleDiagramNodeSelection(event.currentTarget.__data__);
  268. this.diagramStore.showContextMenu(event.pageX, event.pageY, undefined, {
  269. x: event.pageX - rect.left,
  270. y: event.pageY - rect.top
  271. }, selectedNode.moserObject);
  272. }
  273. return false;
  274. }.bind(this));
  275. nodes.on(
  276. 'click', function(selectedNode, i, g) {
  277. var event = d3.event;
  278. event.stopPropagation();
  279. !utils.isOffset(position, {x: event.clientX, y: event.clientY}) && this.handleDiagramNodeSelection(event.currentTarget.__data__);
  280. }.bind(this));
  281. nodes.on(
  282. 'mousedown', function(selectedNode, i, g) {
  283. var event = d3.event;
  284. position.x = event.clientX;
  285. position.y = event.clientY;
  286. });
  287. },
  288. /**
  289. * Defines how to animate links on a d3 'tick' event
  290. *
  291. * @param {array}
  292. * link - A d3 array of links in the diagram view
  293. */
  294. animateLinkTick: function(link) {
  295. var lineDefinition = {
  296. x1: function(linkData) {
  297. return (linkData.source.x);
  298. },
  299. x2: function(linkData) {
  300. return (linkData.target.x);
  301. },
  302. y1: function(linkData) {
  303. var tableDetailsOffset = NodeStylesUtils.diagramNodeStyles.tableDetailsBoxStyle.height / 2,
  304. source = linkData.source;
  305. return (source.y + (source.displayTableInfo ? tableDetailsOffset : 0));
  306. },
  307. y2: function(linkData) {
  308. var tableDetailsOffset = NodeStylesUtils.diagramNodeStyles.tableDetailsBoxStyle.height / 2,
  309. target = linkData.target;
  310. return (target.y + (target.displayTableInfo ? tableDetailsOffset : 0));
  311. }
  312. };
  313. utils.d3Attrs(link.select('.mui-diagram-link-display-line'), lineDefinition);
  314. utils.d3Attrs(link.select('.mui-diagram-link-interact-line'), lineDefinition);
  315. link.select('.mui_validationIcon').attr('transform', function(d) {
  316. return 'translate('+ (((d.source.x + d.target.x ) / 2 ) - 6) + ',' + ((( d.source.y + d.target.y ) / 2 ) - 6) + ')';
  317. });
  318. link.select('.mui-diagram-cardinality-source').attr('transform', function(linkData) {
  319. var coords = utils.getCardinalityPosition(linkData, 'source');
  320. return 'translate(' + coords.x + ',' + coords.y + ')';
  321. });
  322. link.select('.mui-diagram-cardinality-target').attr('transform', function(linkData) {
  323. var coords = utils.getCardinalityPosition(linkData, 'target');
  324. return 'translate(' + coords.x + ',' + coords.y + ')';
  325. });
  326. return link;
  327. },
  328. /**
  329. * Defines how to animate nodes on a d3 'tick' event
  330. *
  331. * @param {array}
  332. * node - A d3 array of nodes in the diagram view
  333. */
  334. animateNodeTick: function(node) {
  335. return node.attr('transform', function(d) {
  336. return 'translate(' + d.x + ', ' + d.y + ')';
  337. });
  338. }
  339. });
  340. return DiagramContentProvider;
  341. });