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.

177 lines
4.9 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. StepNumber.prototype.isNumeric = function(n) {
  42. return !isNaN(parseFloat(n)) && isFinite(n);
  43. };
  44. /**
  45. * Set a new range: start, end and step.
  46. *
  47. * @param {Number} start The start value
  48. * @param {Number} end The end value
  49. * @param {Number} step Optional. Step size. Must be a positive value.
  50. * @param {boolean} prettyStep Optional. If true, the step size is rounded
  51. * To a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
  52. */
  53. StepNumber.prototype.setRange = function(start, end, step, prettyStep) {
  54. if (!this.isNumeric(start)) {
  55. throw new Error('Parameter \'start\' is not numeric; value: ' + start);
  56. }
  57. if (!this.isNumeric(end)) {
  58. throw new Error('Parameter \'end\' is not numeric; value: ' + start);
  59. }
  60. if (!this.isNumeric(step)) {
  61. throw new Error('Parameter \'step\' is not numeric; value: ' + start);
  62. }
  63. this._start = start ? start : 0;
  64. this._end = end ? end : 0;
  65. this.setStep(step, prettyStep);
  66. };
  67. /**
  68. * Set a new step size
  69. * @param {Number} step New step size. Must be a positive value
  70. * @param {boolean} prettyStep Optional. If true, the provided step is rounded
  71. * to a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
  72. */
  73. StepNumber.prototype.setStep = function(step, prettyStep) {
  74. if (step === undefined || step <= 0)
  75. return;
  76. if (prettyStep !== undefined)
  77. this.prettyStep = prettyStep;
  78. if (this.prettyStep === true)
  79. this._step = StepNumber.calculatePrettyStep(step);
  80. else
  81. this._step = step;
  82. };
  83. /**
  84. * Calculate a nice step size, closest to the desired step size.
  85. * Returns a value in one of the ranges 1*10^n, 2*10^n, or 5*10^n, where n is an
  86. * integer Number. For example 1, 2, 5, 10, 20, 50, etc...
  87. * @param {Number} step Desired step size
  88. * @return {Number} Nice step size
  89. */
  90. StepNumber.calculatePrettyStep = function (step) {
  91. var log10 = function (x) {return Math.log(x) / Math.LN10;};
  92. // try three steps (multiple of 1, 2, or 5
  93. var step1 = Math.pow(10, Math.round(log10(step))),
  94. step2 = 2 * Math.pow(10, Math.round(log10(step / 2))),
  95. step5 = 5 * Math.pow(10, Math.round(log10(step / 5)));
  96. // choose the best step (closest to minimum step)
  97. var prettyStep = step1;
  98. if (Math.abs(step2 - step) <= Math.abs(prettyStep - step)) prettyStep = step2;
  99. if (Math.abs(step5 - step) <= Math.abs(prettyStep - step)) prettyStep = step5;
  100. // for safety
  101. if (prettyStep <= 0) {
  102. prettyStep = 1;
  103. }
  104. return prettyStep;
  105. };
  106. /**
  107. * returns the current value of the step
  108. * @return {Number} current value
  109. */
  110. StepNumber.prototype.getCurrent = function () {
  111. return parseFloat(this._current.toPrecision(this.precision));
  112. };
  113. /**
  114. * returns the current step size
  115. * @return {Number} current step size
  116. */
  117. StepNumber.prototype.getStep = function () {
  118. return this._step;
  119. };
  120. /**
  121. * Set the current to its starting value.
  122. *
  123. * By default, this will be the largest value smaller than start, which
  124. * is a multiple of the step size.
  125. *
  126. * Parameters checkFirst is optional, default false.
  127. * If set to true, move the current value one step if smaller than start.
  128. */
  129. StepNumber.prototype.start = function(checkFirst) {
  130. if (checkFirst === undefined) {
  131. checkFirst = false;
  132. }
  133. this._current = this._start - this._start % this._step;
  134. if (checkFirst) {
  135. if (this.getCurrent() < this._start) {
  136. this.next();
  137. }
  138. }
  139. };
  140. /**
  141. * Do a step, add the step size to the current value
  142. */
  143. StepNumber.prototype.next = function () {
  144. this._current += this._step;
  145. };
  146. /**
  147. * Returns true whether the end is reached
  148. * @return {boolean} True if the current value has passed the end value.
  149. */
  150. StepNumber.prototype.end = function () {
  151. return (this._current > this._end);
  152. };
  153. module.exports = StepNumber;