/**
|
|
* @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;
|