UNPKG

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