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 |
|
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 | */
|
47 | StepNumber.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 | */
|
61 | StepNumber.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 | */
|
84 | StepNumber.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 | */
|
104 | StepNumber.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 | */
|
129 | StepNumber.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 | */
|
137 | StepNumber.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 | */
|
152 | StepNumber.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 | */
|
170 | StepNumber.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 | */
|
178 | StepNumber.prototype.end = function () {
|
179 | return (this._current > this._end);
|
180 | };
|
181 |
|
182 | module.exports = StepNumber;
|