CrosstabControl.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. 'use strict';
  2. /**
  3. * Licensed Materials - Property of IBM
  4. * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2016, 2019
  5. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  6. *
  7. */
  8. define(['../../../lib/@waca/core-client/js/core-client/ui/core/Class', 'jquery', '../../../lib/@waca/dashboard-common/dist/utils/Flyout', 'underscore', '../../../widgets/livewidget/nls/StringResources'], function (Class, $, Flyout, _, StringResources) {
  9. 'use strict';
  10. //The cross tab vis control object.
  11. var CrosstabControl = Class.extend({
  12. init: function init(options) {
  13. CrosstabControl.inherited('init', this, arguments);
  14. this.domNode = options.domNode;
  15. this.visualization = options.visualization;
  16. this._factSlots = null; // Array of fact type names.
  17. },
  18. /**
  19. * should be called before the first call to addDataRows
  20. */
  21. resetData: function resetData(visModel) {
  22. this.visModel = visModel;
  23. this._resetInternalData();
  24. },
  25. _resetInternalData: function _resetInternalData() {
  26. this._slots = this.visualization.getSlots().getSlotList();
  27. for (var idx = 0; idx < this._slots.length; idx++) {
  28. if (!this._slotRowSortOrder && this._slots[idx].getId().indexOf('row') > -1) {
  29. this._slotRowSortOrder = this._slots[idx].getDefinition().getSortOrder();
  30. this._slotRowStartIndex = idx;
  31. } else if (this._slots[idx].getId().indexOf('value') > -1) {
  32. this._slotValueStartIndex = idx;
  33. break;
  34. }
  35. }
  36. this._populateFactSlots();
  37. },
  38. clear: function clear() {
  39. var $node = $(this.domNode);
  40. $node.empty();
  41. },
  42. /**
  43. * @public
  44. * Pass the required parameter to vis control.
  45. */
  46. setGridData: function setGridData(crosstabGrid) {
  47. this._crosstabGrid = crosstabGrid;
  48. },
  49. _removeNonProjectedSelections: function _removeNonProjectedSelections(aSelectionTuple) {
  50. for (var i = 0; i < aSelectionTuple.length; i++) {
  51. var selections = aSelectionTuple[i];
  52. //the key for selections is slotIndex which is set to -1 if column is not projected.
  53. if (selections[-1]) {
  54. delete selections[-1];
  55. }
  56. }
  57. return aSelectionTuple;
  58. },
  59. /**
  60. * Apply highlight decoration to the selected measure header cell
  61. * @param $node: the node has been selected
  62. */
  63. decorateMeasureHeader: function decorateMeasureHeader(node) {
  64. this._crosstabGrid.setMeasureHeaderDecorations(node);
  65. },
  66. /**
  67. * Clear selected measure header cell decoration.
  68. */
  69. resetMeasureHeaderDecorations: function resetMeasureHeaderDecorations() {
  70. this._crosstabGrid.clearMeasureHeaderDecorations();
  71. },
  72. /**
  73. * Apply highlight decoration to the summary cells
  74. */
  75. highlightSummary: function highlightSummary(node) {
  76. this._crosstabGrid.highlightSummary(node);
  77. },
  78. /**
  79. * Clear summary cell hightlight.
  80. */
  81. resetSummaryHighlight: function resetSummaryHighlight() {
  82. this._crosstabGrid.resetSummaryHighlight();
  83. },
  84. clearSelection: function clearSelection(options) {
  85. this._crosstabGrid.setSelections({
  86. colSelection: null,
  87. datapointSelection: null,
  88. rowSelection: null
  89. }, options);
  90. },
  91. /**
  92. * @returns true if the current node includes the 'selected' or 'selected_secondary' or 'implied_selection' class.
  93. */
  94. isCellSelected: function isCellSelected(node) {
  95. return $(node).hasClass('dashboard-grid-cell-selected');
  96. },
  97. isEdgeNodeSelected: function isEdgeNodeSelected(node) {
  98. var $node = $(node);
  99. return $node.attr('area') === 'values' || $node.attr('area') === 'cell_edge_column' || $node.attr('area') === 'cell_edge_row';
  100. },
  101. isCrosstaMeasureNodeSelected: function isCrosstaMeasureNodeSelected(node) {
  102. return node.textContent && $(node).attr('area') === 'values';
  103. },
  104. /**
  105. * @returns a map that with the vis slotSortOrder as the key and the cell clicked as the value.
  106. *
  107. */
  108. nodeToSelection: function nodeToSelection(event) {
  109. if (this._crosstabGrid && this._crosstabGrid.data) {
  110. var oDataSelection = {};
  111. var selectionKey = [];
  112. var $target = $(event.target);
  113. var selectedColumnIdx = $target.attr('col') || $target.parent().attr('col');
  114. var selectedRowIdx = $target.attr('row') || $target.parent().attr('row');
  115. if (selectedColumnIdx !== undefined && selectedRowIdx !== undefined) {
  116. var dataCell = this._crosstabGrid.data[selectedRowIdx][selectedColumnIdx];
  117. if (dataCell.ancestor_or_self && dataCell.ancestor_or_self.length >= 2) {
  118. oDataSelection.isInnerEdge = true;
  119. }
  120. if (dataCell.columnEdge) {
  121. this._getSelectionOnEdges(oDataSelection, dataCell.columnEdge.descendant_or_self, true, dataCell, selectionKey, 'column_level1');
  122. }
  123. if (dataCell.rowEdge) {
  124. this._getSelectionOnEdges(oDataSelection, dataCell.rowEdge.descendant_or_self, false, dataCell, selectionKey, 'row_level1');
  125. }
  126. if (!dataCell.rowEdge) {
  127. this._getSelectionOnEdges(oDataSelection, dataCell.ancestor_or_self, dataCell.type === 'column', dataCell, selectionKey);
  128. }
  129. if (dataCell.type !== 'column' && dataCell.type !== 'row') {
  130. oDataSelection.datapoint = dataCell.value;
  131. }
  132. if (this.isCrosstaMeasureNodeSelected(event.target) || $(event.target).attr('measure') === 'true') {
  133. this._getMeasureHeaderItemValues(oDataSelection, event);
  134. }
  135. }
  136. return oDataSelection;
  137. }
  138. },
  139. _getSelectionOnEdges: function _getSelectionOnEdges(oDataSelection, values, isColumnMemmber, dataCell, selectionKey) {
  140. var sSlotOfItem = isColumnMemmber ? 'column_level1' : 'row_level1';
  141. oDataSelection.selectionContext = {
  142. 'slotID': sSlotOfItem,
  143. 'level': dataCell.nestLevel
  144. };
  145. _.each(values, function (value) {
  146. if (!this.isFactSlot(value)) {
  147. selectionKey.push(value.u);
  148. if (!oDataSelection[sSlotOfItem]) {
  149. oDataSelection[sSlotOfItem] = { 'value': [value] };
  150. } else {
  151. oDataSelection[sSlotOfItem].value.push(value);
  152. }
  153. }
  154. }.bind(this));
  155. },
  156. _populateFactSlots: function _populateFactSlots() {
  157. var facts = [];
  158. var slots = this._slots;
  159. var factDataslots = [];
  160. for (var i = 0; i < slots.length; ++i) {
  161. if (slots[i].getDefinition().getType() === 'ordinal') {
  162. _.each(slots[i].getDataItemList(), function (entry) {
  163. facts.push(entry.getLabel()); //TODO: Review this legacy
  164. });
  165. factDataslots.push(slots[i]);
  166. }
  167. }
  168. this._factSlots = facts;
  169. this._factDataslots = factDataslots;
  170. },
  171. isFactSlot: function isFactSlot(name) {
  172. if (!this._factSlots) {
  173. this._populateFactSlots();
  174. }
  175. return this._factSlots.indexOf(name) >= 0;
  176. },
  177. edgeNodeToSelectionObject: function edgeNodeToSelectionObject($target, slot) {
  178. var selectedColumnIdx = $target.attr('col') || $target.parent().attr('col');
  179. var selectedRowIdx = $target.attr('row') || $target.parent().attr('row');
  180. var dataCell = this._crosstabGrid.data[selectedRowIdx][selectedColumnIdx];
  181. if (!dataCell.type) {
  182. return;
  183. }
  184. var selectionObj = {
  185. 'attributeSelections': [{
  186. column: slot.getMapping(),
  187. value: dataCell.value,
  188. edgeType: dataCell.type
  189. }]
  190. };
  191. return selectionObj;
  192. },
  193. /**
  194. * @returns an array of selected slots mappings for a given data point.
  195. */
  196. dataPointNodeToSlotsSelections: function dataPointNodeToSlotsSelections(event) {
  197. var $target = $(event.target);
  198. var selectedColumnIdx = $target.attr('col') || $target.parent().attr('col');
  199. var selectedRowIdx = $target.attr('row') || $target.parent().attr('row');
  200. var dataCell = this._crosstabGrid.data[selectedRowIdx][selectedColumnIdx];
  201. if (dataCell.type) {
  202. return;
  203. }
  204. var aSlotsSelections = [];
  205. this._getSlotsOnEdges(event, aSlotsSelections);
  206. if (aSlotsSelections.length === 0) {
  207. return;
  208. }
  209. // Push the cell slot as the last selection.
  210. var factSlotSelection = void 0;
  211. for (var i = 0; i < aSlotsSelections.length; i++) {
  212. if (this.isFactSlot(aSlotsSelections[i].value)) {
  213. break;
  214. }
  215. }
  216. if (i < aSlotsSelections.length - 1) {
  217. factSlotSelection = aSlotsSelections.splice(i, 1)[0];
  218. }
  219. var factSelections = [];
  220. var fSelection = void 0;
  221. var selectionContext = void 0;
  222. if (factSlotSelection) {
  223. fSelection = {
  224. columnName: factSlotSelection.value,
  225. value: dataCell.rawValue
  226. };
  227. // The mapIndex of the data item within the values slot that this cell corresponds to
  228. selectionContext = this._getFactDataItemIndex(dataCell);
  229. } else {
  230. // When values are not on (column) edge, it's at the corner edge. find and add the first value slot mapping.
  231. var valueSlot = this._slots[2]; //HardCoded
  232. if (valueSlot) {
  233. fSelection = {
  234. columnName: valueSlot.getDataItemList()[0].getLabel(),
  235. value: dataCell.rawValue
  236. };
  237. }
  238. }
  239. factSelections.push(fSelection);
  240. if (dataCell) {
  241. var heatSlot = _.find(this._slots, function (slot) {
  242. return slot.getId() === 'heat';
  243. });
  244. var heatDataItemList = heatSlot && heatSlot.getDataItemList();
  245. if (fSelection && heatDataItemList && heatDataItemList.length > 0 && heatDataItemList[0].getLabel() !== fSelection.columnName) {
  246. factSelections.push({
  247. columnName: heatDataItemList[0].getLabel(),
  248. value: dataCell.condValue ? dataCell.condValue.formatted : dataCell.value
  249. });
  250. }
  251. }
  252. return {
  253. attributeSelections: aSlotsSelections,
  254. factSelections: factSelections,
  255. selectionContext: selectionContext
  256. };
  257. },
  258. _getFactDataItemIndex: function _getFactDataItemIndex(dataCell) {
  259. // The lowest level subCategory will have the index of the data item within the slot
  260. var dataCellCategory = dataCell.columnEdge;
  261. while (dataCellCategory.subCategory) {
  262. dataCellCategory = dataCellCategory.subCategory;
  263. }
  264. return dataCellCategory.index;
  265. },
  266. _getFactDataslot: function _getFactDataslot(name) {
  267. return _.find(this._factDataslots, function (slot) {
  268. var mapping = slot.getMapping();
  269. return this.visModel.getLabelByMapping(mapping) === name;
  270. }.bind(this));
  271. },
  272. isSummaryNodeSelected: function isSummaryNodeSelected(node) {
  273. var name = node.textContent;
  274. return name === StringResources.get('summaryCaption');
  275. },
  276. _getSlotsOnEdges: function _getSlotsOnEdges(event, oDataSelection) {
  277. var $target = $(event.target);
  278. var selectedColumnIdx = $target.attr('col') || $target.parent().attr('col');
  279. var selectedRowIdx = $target.attr('row') || $target.parent().attr('row');
  280. var dataCell = this._crosstabGrid.data[selectedRowIdx][selectedColumnIdx];
  281. if (dataCell.columnEdge) {
  282. this._getSlotsSelectionsOnEdges(oDataSelection, dataCell.columnEdge);
  283. }
  284. if (dataCell.rowEdge) {
  285. this._getSlotsSelectionsOnEdges(oDataSelection, dataCell.rowEdge);
  286. }
  287. if (!dataCell.rowEdge) {
  288. this._getSlotsSelectionsOnEdges(oDataSelection, dataCell);
  289. }
  290. },
  291. edgeNodeToDataslots: function edgeNodeToDataslots(node) {
  292. if (!this.isEdgeNodeSelected(node)) {
  293. return;
  294. }
  295. var name = node.textContent;
  296. if (this.isFactSlot(name)) {
  297. return this._getFactDataslot(name);
  298. }
  299. var $node = $(node);
  300. var slotIdx = Number($node.attr('nestLevel'));
  301. if ($(node).hasClass('row')) {
  302. slotIdx += this._slotRowStartIndex;
  303. }
  304. return this._slots[slotIdx];
  305. },
  306. measureNodeToDataslots: function measureNodeToDataslots(node) {
  307. if (!this.isCrosstaMeasureNodeSelected(node)) {
  308. return;
  309. }
  310. return this._slots[this._slotValueStartIndex];
  311. },
  312. _getSlotsSelectionsOnEdges: function _getSlotsSelectionsOnEdges(aSlotsSelections, edge) {
  313. var category = edge;
  314. while (category) {
  315. var slot;
  316. if (category.isMeasure) {
  317. // the edge is a measure
  318. slot = this.visualization.getSlots().getSlot('values');
  319. } else {
  320. var slotName = category.type === 'column' ? 'column_level1' : 'row_level1';
  321. slot = this.visualization.getSlots().getSlot(slotName);
  322. }
  323. aSlotsSelections.push({
  324. column: slot.getDataItemList()[0],
  325. value: category.value,
  326. edgeType: category.type
  327. });
  328. category = category.subCategory;
  329. }
  330. },
  331. _getMeasureHeaderItemValues: function _getMeasureHeaderItemValues(oDataSelection, event) {
  332. var _this = this;
  333. var targets = void 0;
  334. var itemValues = [];
  335. // If the header selected and another measure is selected, obtain items for both (for calculation action)
  336. var selectedHeaders = this.getSelectedMeasureNodes();
  337. if (selectedHeaders && selectedHeaders.length === 2 && selectedHeaders[0] !== selectedHeaders[1]) {
  338. targets = selectedHeaders;
  339. } else {
  340. targets = [$(event.target)];
  341. }
  342. _.each(targets, function (target) {
  343. var slotIdx = void 0;
  344. var slots = _this.visualization.getSlots().getMappedSlotList();
  345. _.find(slots, function (slot, index) {
  346. if (slot.getId() === 'values') {
  347. slotIdx = index;
  348. return true;
  349. }
  350. });
  351. var $target = $(target);
  352. var dataCell = _this._crosstabGrid.data[$target.attr('row')][$target.attr('col')];
  353. var dataItems = _this.visualization.getSlots().getSlot('values').getDataItemList();
  354. var dataItemIdx = void 0;
  355. _.find(dataItems, function (dataItem, index) {
  356. if (dataItem.getLabel() === dataCell.value) {
  357. dataItemIdx = index;
  358. return true;
  359. }
  360. });
  361. var oMeasure = dataItems[dataItemIdx];
  362. var oItemClass = { 'u': oMeasure.getColumnId(), 'd': oMeasure.getLabel(), 'mapIndex': dataItemIdx };
  363. if (!itemValues[slotIdx]) {
  364. itemValues[slotIdx] = [];
  365. }
  366. itemValues[slotIdx].push(oItemClass);
  367. });
  368. oDataSelection.itemClassValues = itemValues;
  369. },
  370. /**
  371. * Gets a list of the nodes that have been selected
  372. * @return {Array.node} - an array of nodes
  373. */
  374. getSelectedMeasureNodes: function getSelectedMeasureNodes() {
  375. return this._crosstabGrid.getSelectedMeasureNodes();
  376. }
  377. });
  378. return CrosstabControl;
  379. });
  380. //# sourceMappingURL=CrosstabControl.js.map