HistogramView.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. 'use strict';
  2. /**
  3. * Licensed Materials - Property of IBM
  4. *
  5. * IBM Cognos Products: Dashboard
  6. *
  7. * (C) Copyright IBM Corp. 2017, 2020
  8. *
  9. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  10. */
  11. define(['../../../lib/@waca/core-client/js/core-client/ui/core/View', '../../../widgets/livewidget/nls/StringResources', './ConditionalPalettePicker', 'jquery', 'underscore', 'rave', '../../../lib/@waca/dashboard-common/dist/utils/Rave2RenderHelper', 'text!./templates/HistogramView.html', '../../../widgets/livewidget/query/VisQueryBuilder', '../../../DynamicFileLoader'], function (BaseView, resources, ConditionalPalettePicker, $, _, Rave2, Rave2RenderHelper, template, QueryBuilder, DynamicFileLoader) {
  12. 'use strict';
  13. var HistogramView = BaseView.extend({
  14. templateString: template,
  15. _isRendering: false,
  16. init: function init() {
  17. var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  18. HistogramView.inherited('init', this, arguments);
  19. this.visualization = options.visualization;
  20. this.data = this._getConditionalData(this.visualization.getSlots().getMappedSlotList(), options.data);
  21. this.visModel = options.visModel;
  22. this.heatDataItem = this.visualization.getSlots().getSlot('heat').getDataItemList()[0];
  23. this.visQueryBuilder = new QueryBuilder({ 'visAPI': options.visModel });
  24. this.icons = options.icons;
  25. this._renderHelper = new Rave2RenderHelper({
  26. 'raveView': this
  27. });
  28. this.$el.addClass('conditionalFormatView');
  29. },
  30. render: function render(options) {
  31. var _this = this;
  32. // If already rendering, don't start a new one as this
  33. // will mess up the conditional picker render process.
  34. if (this._isRendering) {
  35. return;
  36. }
  37. this._isRendering = true;
  38. // show borders (selected) by default
  39. options = options || {
  40. select: true
  41. };
  42. return this.visModel.getConditionalMinMaxValue().then(function (data) {
  43. _this._setMinMax(data.min, data.max);
  44. return _this._initHistogram(options).then(function (rave2Vis) {
  45. var coordinates = this._renderHistogram(rave2Vis);
  46. return this._renderConditionalPicker(coordinates);
  47. }.bind(_this));
  48. }).catch(function (error) {
  49. _this._isRendering = false;
  50. return Promise.reject(error);
  51. });
  52. },
  53. remove: function remove() {
  54. if (this.palettePicker) {
  55. this.palettePicker.remove();
  56. }
  57. this.visQueryBuilder = null;
  58. this._renderHelper = null;
  59. this.$el && this.$el.parent() && this.$el.parent().off('mousedown.histogramView dragstart.histogramView touchstart.histogramView touchmove.histogramView');
  60. HistogramView.inherited('remove', this, arguments);
  61. },
  62. _setMinMax: function _setMinMax(min, max) {
  63. var palette = this._getConditionalPalette();
  64. if (palette) {
  65. var paletteSize = palette.getSize();
  66. if (paletteSize > 0) {
  67. this.min = Math.min(min, palette.getColor(0).getValue());
  68. this.max = Math.max(max, palette.getColor(paletteSize - 1).getValue());
  69. } else {
  70. this.min = min;
  71. this.max = max;
  72. }
  73. this.min = Math.floor(this.min);
  74. this.max = Math.ceil(this.max);
  75. }
  76. },
  77. refresh: function refresh(options) {
  78. options = options || {};
  79. if (!options.force && (this.width !== this.$el.width() || this._hasPaletteColorCountChanged())) {
  80. options.force = true;
  81. }
  82. if (options.force || options.data) {
  83. if (options.data) {
  84. this.data = this._getConditionalData(this.visualization.getSlots().getMappedSlotList(), options.data);
  85. }
  86. if (options.force || this._dataChanged) {
  87. this.$el.empty();
  88. return this.render(options);
  89. } else {
  90. return Promise.resolve(this);
  91. }
  92. }
  93. // toggle the borders based on the selection
  94. this._toggleBorders(options.select);
  95. return Promise.resolve(this);
  96. },
  97. _hasPaletteColorCountChanged: function _hasPaletteColorCountChanged() {
  98. if (this.palettePicker && this.palettePicker.slider && this.visModel) {
  99. return this.palettePicker.slider.maxHandles !== this.visModel.getConditionalPaletteLength() + 1;
  100. }
  101. return false;
  102. },
  103. getVisModel: function getVisModel() {
  104. return this.visModel;
  105. },
  106. _toggleBorders: function _toggleBorders(show) {
  107. if (show) {
  108. this.$el.find('.border').show();
  109. } else {
  110. this.$el.find('.border').hide();
  111. }
  112. },
  113. _getConditionalData: function _getConditionalData(mappings, data) {
  114. var _this2 = this;
  115. this._dataChanged = false;
  116. var index = -1,
  117. heatDataItemIndex = void 0,
  118. rankColumnCount = 0;
  119. // consider the data has changed
  120. var dataObj = data.getResult();
  121. var dataRowSize = dataObj.getRowCount();
  122. if (!this.data || dataRowSize !== this.data.length) {
  123. this._dataChanged = true;
  124. }
  125. // find the heat data item index
  126. // slot prior to the heat slot may contain more than one dataitems
  127. _.each(mappings, function (mapping) {
  128. if (mapping.getId() === 'heat') {
  129. heatDataItemIndex = ++index;
  130. } else if (mapping.getDefinition().getProperty('repeats')) {
  131. //the query response for repeating slots will have a data point for every data item in the slot
  132. index += mapping.getDataItemList().length;
  133. } else {
  134. index++;
  135. }
  136. rankColumnCount += _this2._getRankColumnCount(mapping);
  137. });
  138. // need to add the count of the rank columns to the heatDataItemIndex
  139. heatDataItemIndex += rankColumnCount;
  140. // extract the conditional values
  141. var result = [];
  142. if (heatDataItemIndex > -1) {
  143. for (var i = 0; i < dataRowSize; i++) {
  144. var dataCell = dataObj.getValue(i, heatDataItemIndex);
  145. if (!this._dataChanged && this.data) {
  146. // check whether the data has changed
  147. this._dataChanged = dataCell.value !== this.data[i];
  148. }
  149. result.push(dataCell.value);
  150. }
  151. }
  152. return result;
  153. },
  154. _getRankColumnCount: function _getRankColumnCount(mapping) {
  155. var rankColumnCount = 0;
  156. var dataItemAPIs = mapping.getDataItemList();
  157. _.each(dataItemAPIs, function (dataItem) {
  158. var topBottomInfo = dataItem.getTopBottom();
  159. if (topBottomInfo && topBottomInfo.rank) {
  160. rankColumnCount++;
  161. }
  162. });
  163. return rankColumnCount;
  164. },
  165. _getConditionalMapping: function _getConditionalMapping(mapping) {
  166. return _.find(mapping, function (m) {
  167. return m.getId() === 'heat';
  168. });
  169. },
  170. _shapeHistogramData: function _shapeHistogramData() {
  171. var max_buckets = 10;
  172. var histogramData = [];
  173. var delta = (this.max - this.min) / max_buckets;
  174. // init with zero
  175. for (var i = 0; i < max_buckets; i++) {
  176. histogramData[i] = [String(i * delta), 0];
  177. }
  178. _.each(this.data, function (row) {
  179. if (row !== undefined && row !== null) {
  180. var index = Math.min(max_buckets - 1, Math.floor((row - this.min) / delta));
  181. index = index < 0 ? 0 : index;
  182. histogramData[index][1]++;
  183. }
  184. }.bind(this));
  185. return histogramData;
  186. },
  187. _setHistogramProps: function _setHistogramProps(rave2Vis) {
  188. var props = [
  189. // x axis props
  190. {
  191. name: 'axis.x.title.display',
  192. value: false
  193. }, {
  194. name: 'axis.x.line.display',
  195. value: false
  196. }, {
  197. name: 'axis.x.labels.display',
  198. value: false
  199. }, {
  200. name: 'axis.x.gridlines.display',
  201. value: false
  202. }, {
  203. name: 'axis.x.scale.includeZero',
  204. value: true
  205. },
  206. // y axis props
  207. {
  208. name: 'axis.y.title.display',
  209. value: true
  210. }, {
  211. name: 'axis.y.line.display',
  212. value: false
  213. }, {
  214. name: 'axis.y.labels.display',
  215. value: false
  216. }, {
  217. name: 'axis.y.gridlines.display',
  218. value: true
  219. }, {
  220. name: 'axis.y.scale.includeZero',
  221. value: true
  222. }, {
  223. name: 'axis.y.title.text',
  224. value: resources.get('HistogramYAxisTitle')
  225. }, {
  226. name: 'axis.y.title.style.font-size',
  227. value: '11px'
  228. },
  229. // layout props
  230. {
  231. name: 'layout.axissize.left.preferred',
  232. value: '0'
  233. }, {
  234. name: 'layout.axissize.left.min',
  235. value: '0'
  236. }, {
  237. name: 'layout.axissize.bottom.preferred',
  238. value: '0'
  239. }, {
  240. name: 'layout.axissize.bottom.min',
  241. value: '0'
  242. }, {
  243. name: 'layout.legendsize.preferred',
  244. value: '0'
  245. }, {
  246. name: 'layout.legendsize.min',
  247. value: '0'
  248. }, {
  249. name: 'layout.padding',
  250. value: '0'
  251. }, {
  252. name: 'layout.chart.padding.top',
  253. value: '0'
  254. }, {
  255. name: 'layout.chart.padding.left',
  256. value: '0'
  257. }, {
  258. name: 'layout.chart.padding.bottom',
  259. value: '1'
  260. }, {
  261. name: 'layout.chart.padding.right',
  262. value: '0'
  263. },
  264. // legend props
  265. {
  266. name: 'legend.display',
  267. value: false
  268. },
  269. // line props
  270. {
  271. name: 'lineWithPoints.interpolate',
  272. value: 'basis'
  273. }, {
  274. name: 'lineWithPoints.display',
  275. value: 'line'
  276. }];
  277. _.each(props, function (prop) {
  278. rave2Vis.property(prop.name, prop.value);
  279. }.bind(this));
  280. },
  281. _initCanvas: function _initCanvas(options) {
  282. this.$el.empty();
  283. options.title = resources.get('conditionalPalettePickerLabel', {
  284. title: this.heatDataItem.getLabel()
  285. });
  286. this.$el.append(this.dotTemplate(options));
  287. this.$el.parent().on('mousedown.histogramView dragstart.histogramView touchstart.histogramView touchmove.histogramView', function (event) {
  288. $('.popover').popover('hide');
  289. event.stopPropagation();
  290. event.preventDefault(); // Stops the widget from moving when in authoring mode
  291. });
  292. this.width = this.$el.width();
  293. },
  294. _initHistogram: function _initHistogram(options) {
  295. var _this3 = this;
  296. return DynamicFileLoader.load(['rave-library-line']).then(function (modules) {
  297. var Bundle = modules[0];
  298. _this3._rave2Vis = Bundle.create('line');
  299. _this3._visDataModel = _this3._rave2Vis.createDataModel(_this3._renderHelper.getDefaultDataModel().id());
  300. _this3._disableRaveActions(_this3._rave2Vis);
  301. _this3._setHistogramProps(_this3._rave2Vis);
  302. _this3._initCanvas(options);
  303. return _this3._rave2Vis;
  304. });
  305. },
  306. _renderHistogram: function _renderHistogram(rave2Vis) {
  307. var statsData = this._shapeHistogramData();
  308. // Create a dataset with the conditional measure stats
  309. var dataSet = this._renderHelper.getRAVE2DataSet();
  310. dataSet.data(statsData);
  311. dataSet.slot('x').push().type('string').accessor(function (dataRow) {
  312. return dataRow[0];
  313. });
  314. dataSet.slot('y').push().type('numeric').accessor(function (dataRow) {
  315. return dataRow[1];
  316. });
  317. rave2Vis.node(Rave2.select(this.$el[0]).append('svg:svg').attr('class', 'rave-svg histogram-svg'));
  318. rave2Vis.render();
  319. return this._calculateHistogramCoordinate();
  320. },
  321. /**
  322. * Parse the SVG path to determine the histogram coordinates
  323. *
  324. * Example of SVG path string
  325. * "M80.9,79.13834373316936L98.9,77.16140102203092 ... L1052.9,79.13834373316936"
  326. */
  327. _getHistogramChartElement: function _getHistogramChartElement() {
  328. return this.$el.find('.element-shape');
  329. },
  330. _calculateHistogramCoordinate: function _calculateHistogramCoordinate() {
  331. var element = this._getHistogramChartElement();
  332. if (element.length > 0) {
  333. var directions = element.attr('d');
  334. if (directions) {
  335. var coordinates = directions.split(/[CML]+/);
  336. // skip the first empty coordinate (empty split by starting 'M')
  337. var start = coordinates[1].split(',');
  338. var end = coordinates[coordinates.length - 1].split(',');
  339. return {
  340. x1: parseFloat(start[0]),
  341. y1: parseFloat(start[1]),
  342. x2: parseFloat(end[0]),
  343. y2: parseFloat(end[1])
  344. };
  345. }
  346. }
  347. return { x1: 0, y1: 0, x2: 0, y2: 0 };
  348. },
  349. _disableRaveActions: function _disableRaveActions(rave2Vis) {
  350. rave2Vis.action('highlight').autoBind(false);
  351. rave2Vis.action('unhighlight').autoBind(false);
  352. rave2Vis.action('toggleSelect').autoBind(false);
  353. rave2Vis.action('zoom').autoBind(false);
  354. },
  355. _conditionalPickerCallback: function _conditionalPickerCallback() {
  356. this.refresh({
  357. force: true
  358. });
  359. },
  360. _renderConditionalPicker: function _renderConditionalPicker(c) {
  361. var _this4 = this;
  362. if (this.palettePicker) {
  363. this.palettePicker.min = this.min;
  364. this.palettePicker.max = this.max;
  365. } else {
  366. this.palettePicker = new ConditionalPalettePicker({
  367. el: this.el,
  368. min: this.min,
  369. max: this.max,
  370. palette: this._getConditionalPalette(),
  371. format: this.heatDataItem.getFormat(),
  372. widget: this.visModel.ownerWidget,
  373. visModel: this.visModel,
  374. icons: this.icons,
  375. callback: this._conditionalPickerCallback.bind(this)
  376. });
  377. }
  378. return this.palettePicker.render(c).then(function () {
  379. _this4._isRendering = false;
  380. });
  381. },
  382. _getConditionalPalette: function _getConditionalPalette() {
  383. var conditions = this.visModel.getConditions();
  384. return conditions ? conditions.palette : null;
  385. }
  386. });
  387. return HistogramView;
  388. });
  389. //# sourceMappingURL=HistogramView.js.map