ScaleUtil.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. 'use strict';
  2. /**
  3. * Licensed Materials - Property of IBM
  4. * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2016, 2017
  5. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  6. */
  7. define(['../lib/@waca/core-client/js/core-client/ui/core/Class'], function (Class) {
  8. /**
  9. * Functions used to handle calculation of scale options for infographic display functionality
  10. *
  11. */
  12. var ScaleUtil = Class.extend({
  13. // Constants that handles conversion between numeric and text values. Used for readability, as numbers are used for scale slider min/max but they interpret as text values
  14. SCALE_VALUE_FEW: 0,
  15. SCALE_VALUE_DEFAULT: 1,
  16. SCALE_VALUE_MANY: 2,
  17. PERCENT_OPTIMAL_VALUE: 0.1,
  18. PERCENT_SCALE_OPTIONS: [0.01, 0.1, 1],
  19. /**
  20. * Calculates the default scale value for a given input number
  21. * @param rawNum - Input number to be infographically displayed
  22. * @param isPercent - If the raw number is used to represent a percentage infographic
  23. * @returns The default scale value to be used
  24. */
  25. calcOptimalValue: function calcOptimalValue(rawNum, isPercent) {
  26. // If rawNum is null (i.e. no value provided, treat same as zero case to avoid returning errors later on
  27. if (rawNum === null) {
  28. rawNum = 0;
  29. }
  30. // Null if not a number
  31. if (typeof rawNum !== 'number') {
  32. return null;
  33. }
  34. if (isPercent) {
  35. return this.PERCENT_OPTIMAL_VALUE;
  36. }
  37. // Take abs to handle negative numbers
  38. var absNum = Math.abs(rawNum);
  39. var sign = this.getSign(rawNum) || 1;
  40. var numLen = Math.ceil(Math.log(absNum + 1) / Math.LN10);
  41. // Default optimal value is 1.
  42. var optimalValue = 1;
  43. // If the number of digits is greater than 2 than calculate the optimal value
  44. if (numLen > 2) {
  45. optimalValue = Math.pow(10, numLen - 2);
  46. }
  47. return optimalValue * sign;
  48. },
  49. /**
  50. * Gets an object containing a list of scale options as well as the default for a given number
  51. * @param rawNum - Input number to be infographically displayed
  52. * @param isPercent - If the raw number is used to represent a percentage infographic
  53. * @returns An object containing an array of scaling options to be used, as well as the default scale to be shown
  54. */
  55. getScalingProperties: function getScalingProperties(rawNum, isPercent) {
  56. // If rawNum is null (i.e. no value provided, treat same as zero case to avoid returning errors later on
  57. if (rawNum === null) {
  58. rawNum = 0;
  59. }
  60. // Null if not a number
  61. if (typeof rawNum !== 'number') {
  62. return null;
  63. }
  64. if (isPercent) {
  65. return {
  66. availableScales: this.PERCENT_SCALE_OPTIONS,
  67. optimalScale: this.PERCENT_OPTIMAL_VALUE,
  68. scalingOptions: [this.SCALE_VALUE_FEW, this.SCALE_VALUE_DEFAULT, this.SCALE_VALUE_MANY]
  69. };
  70. }
  71. // get the absolute number
  72. var absNum = Math.abs(rawNum);
  73. // get the sign of the number to help handle negative numbers
  74. var sign = this.getSign(rawNum) || 1;
  75. // Get the ideal scale for the given number
  76. var optimalValue = this.calcOptimalValue(absNum);
  77. var availableScales = [optimalValue * sign];
  78. // Only add lower scale option if the default is greater than one
  79. if (optimalValue > 1) {
  80. availableScales.unshift(optimalValue / 10 * sign);
  81. }
  82. // Check if potential high value is valid scale option for input number
  83. var potentialHigh = optimalValue * 10;
  84. if (absNum / potentialHigh >= 1) {
  85. availableScales.push(potentialHigh * sign);
  86. }
  87. // Assemble both parts of final product to be returned to user
  88. var result = {
  89. availableScales: availableScales,
  90. optimalScale: optimalValue * sign
  91. };
  92. result.scalingOptions = this._getScaleOptions(result, isPercent);
  93. return result;
  94. },
  95. /**
  96. * Returns the corresponding min/max values for slider which user utilizes to select preferred scale option
  97. * @param {Number} rawNum - Raw input number which is being used to determine the scale
  98. * @param isPercent - If the raw number is used to represent a percentage infographic
  99. * @returns result object containing min and max value for slider
  100. */
  101. getMinMax: function getMinMax(rawNum, isPercent) {
  102. // If rawNum is null (i.e. no value provided, treat same as zero case to avoid returning errors later on
  103. if (rawNum === null) {
  104. rawNum = 0;
  105. }
  106. var scales = this.getScalingProperties(rawNum, isPercent);
  107. return {
  108. min: scales.scalingOptions[0],
  109. max: scales.scalingOptions[scales.scalingOptions.length - 1]
  110. };
  111. },
  112. /**
  113. * Given a corresponding value and scale to use, calculate the number of shapes and partial shapes to return to the user
  114. * @param value - The value we are calculating the number off of
  115. * @param scale - The scale that will be used
  116. * @param isPercent - If the raw number is used to represent a percentage infographic
  117. * @returns An object that contains the number of shapes and partial shapes to be displayed in the infographic summary
  118. */
  119. getScaleComponents: function getScaleComponents(value, scale, isPercent) {
  120. // If rawNum is null (i.e. no value provided, treat same as zero case to avoid returning errors later on
  121. if (value === null) {
  122. value = 0;
  123. }
  124. /**
  125. * Used to divide a number by a floating point decimal, which can sometimes lead to repeating decimal values using the normal '/' operator
  126. * @param numerator - Numerator of division expression
  127. * @param denominator - Denominator of division expression
  128. * @returns Result of numerator divided by denominator
  129. */
  130. function _division(numerator, denominator) {
  131. while (denominator < 1 && denominator > 0) {
  132. denominator *= 10;
  133. numerator *= 10;
  134. }
  135. return numerator / denominator;
  136. }
  137. /**
  138. * Used to find the remainder of a number by a floating point decimal, which can sometimes lead to repeating decimal values using the normal '%' operator
  139. * @param numerator - Numerator of remainder expression
  140. * @param denominator - Denominator of remainder expression
  141. * @returns remainder of expression
  142. */
  143. function _remainder(numerator, denominator) {
  144. return numerator - denominator * Math.floor(_division(numerator, denominator));
  145. }
  146. value = Math.abs(value);
  147. scale = Math.abs(scale);
  148. var numShapes = Math.floor(_division(value, scale));
  149. var partialValue = _division(_remainder(value, scale), scale);
  150. partialValue = parseFloat(partialValue.toFixed(2));
  151. var numGreyedShapes;
  152. if (isPercent) {
  153. numGreyedShapes = _division(1, scale) - numShapes - Math.ceil(partialValue);
  154. } else {
  155. numGreyedShapes = 0;
  156. }
  157. return {
  158. numShapes: numShapes,
  159. partialValue: partialValue,
  160. numGreyedShapes: numGreyedShapes
  161. };
  162. },
  163. _getScaleOptions: function _getScaleOptions(scaleData) {
  164. var numOfScales = scaleData.availableScales.length;
  165. var result;
  166. if (numOfScales === 3) {
  167. result = [this.SCALE_VALUE_FEW, this.SCALE_VALUE_DEFAULT, this.SCALE_VALUE_MANY];
  168. } else if (numOfScales === 1) {
  169. result = [this.SCALE_VALUE_DEFAULT];
  170. } else if (scaleData.availableScales.indexOf(scaleData.optimalScale) === 0) {
  171. result = [this.SCALE_VALUE_FEW, this.SCALE_VALUE_DEFAULT];
  172. } else if (scaleData.availableScales.indexOf(scaleData.optimalScale) === 1) {
  173. result = [this.SCALE_VALUE_DEFAULT, this.SCALE_VALUE_MANY];
  174. }
  175. return result;
  176. },
  177. getSign: function getSign(x) {
  178. x = +x; // convert to a number
  179. if (isNaN(x)) {
  180. return NaN;
  181. }
  182. return x >= 0 ? 1 : -1;
  183. }
  184. });
  185. return new ScaleUtil();
  186. });
  187. //# sourceMappingURL=ScaleUtil.js.map