/** * @prototype StepNumber * The class StepNumber is an iterator for Numbers. You provide a start and end * value, and a best step size. StepNumber itself rounds to fixed values and * a finds the step that best fits the provided step. * * If prettyStep is true, the step size is chosen as close as possible to the * provided step, but being a round value like 1, 2, 5, 10, 20, 50, .... * * Example usage: * var step = new StepNumber(0, 10, 2.5, true); * step.start(); * while (!step.end()) { * alert(step.getCurrent()); * step.next(); * } * * Version: 1.0 * * @param {Number} start The start value * @param {Number} end The end value * @param {Number} step Optional. Step size. Must be a positive value. * @param {boolean} prettyStep Optional. If true, the step size is rounded * To a pretty step size (like 1, 2, 5, 10, 20, 50, ...) */ function StepNumber(start, end, step, prettyStep) { // set default values this._start = 0; this._end = 0; this._step = 1; this.prettyStep = true; this.precision = 5; this._current = 0; this.setRange(start, end, step, prettyStep); } /** * Check for input values, to prevent disasters from happening * * Source: http://stackoverflow.com/a/1830844 */ StepNumber.prototype.isNumeric = function(n) { return !isNaN(parseFloat(n)) && isFinite(n); }; /** * Set a new range: start, end and step. * * @param {Number} start The start value * @param {Number} end The end value * @param {Number} step Optional. Step size. Must be a positive value. * @param {boolean} prettyStep Optional. If true, the step size is rounded * To a pretty step size (like 1, 2, 5, 10, 20, 50, ...) */ StepNumber.prototype.setRange = function(start, end, step, prettyStep) { if (!this.isNumeric(start)) { throw new Error('Parameter \'start\' is not numeric; value: ' + start); } if (!this.isNumeric(end)) { throw new Error('Parameter \'end\' is not numeric; value: ' + start); } if (!this.isNumeric(step)) { throw new Error('Parameter \'step\' is not numeric; value: ' + start); } this._start = start ? start : 0; this._end = end ? end : 0; this.setStep(step, prettyStep); }; /** * Set a new step size * @param {Number} step New step size. Must be a positive value * @param {boolean} prettyStep Optional. If true, the provided step is rounded * to a pretty step size (like 1, 2, 5, 10, 20, 50, ...) */ StepNumber.prototype.setStep = function(step, prettyStep) { if (step === undefined || step <= 0) return; if (prettyStep !== undefined) this.prettyStep = prettyStep; if (this.prettyStep === true) this._step = StepNumber.calculatePrettyStep(step); else this._step = step; }; /** * Calculate a nice step size, closest to the desired step size. * Returns a value in one of the ranges 1*10^n, 2*10^n, or 5*10^n, where n is an * integer Number. For example 1, 2, 5, 10, 20, 50, etc... * @param {Number} step Desired step size * @return {Number} Nice step size */ StepNumber.calculatePrettyStep = function (step) { var log10 = function (x) {return Math.log(x) / Math.LN10;}; // try three steps (multiple of 1, 2, or 5 var step1 = Math.pow(10, Math.round(log10(step))), step2 = 2 * Math.pow(10, Math.round(log10(step / 2))), step5 = 5 * Math.pow(10, Math.round(log10(step / 5))); // choose the best step (closest to minimum step) var prettyStep = step1; if (Math.abs(step2 - step) <= Math.abs(prettyStep - step)) prettyStep = step2; if (Math.abs(step5 - step) <= Math.abs(prettyStep - step)) prettyStep = step5; // for safety if (prettyStep <= 0) { prettyStep = 1; } return prettyStep; }; /** * returns the current value of the step * @return {Number} current value */ StepNumber.prototype.getCurrent = function () { return parseFloat(this._current.toPrecision(this.precision)); }; /** * returns the current step size * @return {Number} current step size */ StepNumber.prototype.getStep = function () { return this._step; }; /** * Set the current to its starting value. * * By default, this will be the largest value smaller than start, which * is a multiple of the step size. * * Parameters checkFirst is optional, default false. * If set to true, move the current value one step if smaller than start. */ StepNumber.prototype.start = function(checkFirst) { if (checkFirst === undefined) { checkFirst = false; } this._current = this._start - this._start % this._step; if (checkFirst) { if (this.getCurrent() < this._start) { this.next(); } } }; /** * Do a step, add the step size to the current value */ StepNumber.prototype.next = function () { this._current += this._step; }; /** * Returns true whether the end is reached * @return {boolean} True if the current value has passed the end value. */ StepNumber.prototype.end = function () { return (this._current > this._end); }; module.exports = StepNumber;