vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

182 lines
5.0 KiB

  1. /**
  2. * @prototype StepNumber
  3. * The class StepNumber is an iterator for Numbers. You provide a start and end
  4. * value, and a best step size. StepNumber itself rounds to fixed values and
  5. * a finds the step that best fits the provided step.
  6. *
  7. * If prettyStep is true, the step size is chosen as close as possible to the
  8. * provided step, but being a round value like 1, 2, 5, 10, 20, 50, ....
  9. *
  10. * Example usage:
  11. * var step = new StepNumber(0, 10, 2.5, true);
  12. * step.start();
  13. * while (!step.end()) {
  14. * alert(step.getCurrent());
  15. * step.next();
  16. * }
  17. *
  18. * Version: 1.0
  19. *
  20. * @param {number} start The start value
  21. * @param {number} end The end value
  22. * @param {number} step Optional. Step size. Must be a positive value.
  23. * @param {boolean} prettyStep Optional. If true, the step size is rounded
  24. * To a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
  25. */
  26. function StepNumber(start, end, step, prettyStep) {
  27. // set default values
  28. this._start = 0;
  29. this._end = 0;
  30. this._step = 1;
  31. this.prettyStep = true;
  32. this.precision = 5;
  33. this._current = 0;
  34. this.setRange(start, end, step, prettyStep);
  35. }
  36. /**
  37. * Check for input values, to prevent disasters from happening
  38. *
  39. * Source: http://stackoverflow.com/a/1830844
  40. *
  41. * @param {string} n
  42. * @returns {boolean}
  43. */
  44. StepNumber.prototype.isNumeric = function(n) {
  45. return !isNaN(parseFloat(n)) && isFinite(n);
  46. };
  47. /**
  48. * Set a new range: start, end and step.
  49. *
  50. * @param {number} start The start value
  51. * @param {number} end The end value
  52. * @param {number} step Optional. Step size. Must be a positive value.
  53. * @param {boolean} prettyStep Optional. If true, the step size is rounded
  54. * To a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
  55. */
  56. StepNumber.prototype.setRange = function(start, end, step, prettyStep) {
  57. if (!this.isNumeric(start)) {
  58. throw new Error('Parameter \'start\' is not numeric; value: ' + start);
  59. }
  60. if (!this.isNumeric(end)) {
  61. throw new Error('Parameter \'end\' is not numeric; value: ' + start);
  62. }
  63. if (!this.isNumeric(step)) {
  64. throw new Error('Parameter \'step\' is not numeric; value: ' + start);
  65. }
  66. this._start = start ? start : 0;
  67. this._end = end ? end : 0;
  68. this.setStep(step, prettyStep);
  69. };
  70. /**
  71. * Set a new step size
  72. * @param {number} step New step size. Must be a positive value
  73. * @param {boolean} prettyStep Optional. If true, the provided step is rounded
  74. * to a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
  75. */
  76. StepNumber.prototype.setStep = function(step, prettyStep) {
  77. if (step === undefined || step <= 0)
  78. return;
  79. if (prettyStep !== undefined)
  80. this.prettyStep = prettyStep;
  81. if (this.prettyStep === true)
  82. this._step = StepNumber.calculatePrettyStep(step);
  83. else
  84. this._step = step;
  85. };
  86. /**
  87. * Calculate a nice step size, closest to the desired step size.
  88. * Returns a value in one of the ranges 1*10^n, 2*10^n, or 5*10^n, where n is an
  89. * integer Number. For example 1, 2, 5, 10, 20, 50, etc...
  90. * @param {number} step Desired step size
  91. * @return {number} Nice step size
  92. */
  93. StepNumber.calculatePrettyStep = function (step) {
  94. var log10 = function (x) {return Math.log(x) / Math.LN10;};
  95. // try three steps (multiple of 1, 2, or 5
  96. var step1 = Math.pow(10, Math.round(log10(step))),
  97. step2 = 2 * Math.pow(10, Math.round(log10(step / 2))),
  98. step5 = 5 * Math.pow(10, Math.round(log10(step / 5)));
  99. // choose the best step (closest to minimum step)
  100. var prettyStep = step1;
  101. if (Math.abs(step2 - step) <= Math.abs(prettyStep - step)) prettyStep = step2;
  102. if (Math.abs(step5 - step) <= Math.abs(prettyStep - step)) prettyStep = step5;
  103. // for safety
  104. if (prettyStep <= 0) {
  105. prettyStep = 1;
  106. }
  107. return prettyStep;
  108. };
  109. /**
  110. * returns the current value of the step
  111. * @return {number} current value
  112. */
  113. StepNumber.prototype.getCurrent = function () {
  114. return parseFloat(this._current.toPrecision(this.precision));
  115. };
  116. /**
  117. * returns the current step size
  118. * @return {number} current step size
  119. */
  120. StepNumber.prototype.getStep = function () {
  121. return this._step;
  122. };
  123. /**
  124. * Set the current to its starting value.
  125. *
  126. * By default, this will be the largest value smaller than start, which
  127. * is a multiple of the step size.
  128. *
  129. * Parameters checkFirst is optional, default false.
  130. * If set to true, move the current value one step if smaller than start.
  131. *
  132. * @param {boolean} [checkFirst=false]
  133. */
  134. StepNumber.prototype.start = function(checkFirst) {
  135. if (checkFirst === undefined) {
  136. checkFirst = false;
  137. }
  138. this._current = this._start - this._start % this._step;
  139. if (checkFirst) {
  140. if (this.getCurrent() < this._start) {
  141. this.next();
  142. }
  143. }
  144. };
  145. /**
  146. * Do a step, add the step size to the current value
  147. */
  148. StepNumber.prototype.next = function () {
  149. this._current += this._step;
  150. };
  151. /**
  152. * Returns true whether the end is reached
  153. * @return {boolean} True if the current value has passed the end value.
  154. */
  155. StepNumber.prototype.end = function () {
  156. return (this._current > this._end);
  157. };
  158. module.exports = StepNumber;