File: ia\thematics\NumericClassifier.js
/**
* Classifies numeric data.
*
* @author J Clare
* @class ia.NumericClassifier
* @constructor
* @param {Number[]} data The data array.
*/
ia.NumericClassifier = function(data)
{
this._breaks = new Array();
this._classes = new Array();
this._adjustedNoClasses = 0;
this._calculator = new ia.BreaksCalculator();
this._data = new Array();
this.formatter = new ia.Formatter();
this.colorPalette = new ia.ColorPalette();
this.sizePalette = new ia.SizePalette();
this.classificationName = ia.Thematic.EQUAL_INTERVAL;
this.labels = new Array();
this.sdLabels = new Array("> 2 SD below mean",
"1 - 2 SD below mean",
"0 - 1 SD below mean",
"0 - 1 SD above mean",
"1 - 2 SD above mean",
"> 2 SD above mean");
this.noClasses = 5;
this.minRule = ia.RangeClass.GREATER_THAN_OR_EQUAL_TO;
this.maxRule = ia.RangeClass.LESS_THAN_OR_EQUAL_TO;
if (data !== undefined) this.setData(data);
};
/**
* The formatter.
*
* @property formatter
* @type ia.Formatter
*/
ia.NumericClassifier.prototype.formatter;
/**
* Used to classify colors.
*
* @property colorPalette
* @type ia.ColorPalette
*/
ia.NumericClassifier.prototype.colorPalette;
/**
* Used to classify sizes.
*
* @property sizePalette
* @type ia.SizePalette
*/
ia.NumericClassifier.prototype.sizePalette;
/**
* The name of the classification to use when calculating breaks.
*
* <p>Possible values include:
* <ul>
* <li><code>ia.Thematic.CONTINUOUS</code></li>
* <li><code>ia.Thematic.EQUAL_INTERVAL</code></li>
* <li><code>ia.Thematic.QUANTILE</code></li>
* <li><code>ia.Thematic.NATURAL</code></li>
* <li><code>ia.Thematic.STANDARD_DEVIATION</code></li>
* </ul>
* </p>
*
* @property classificationName
* @type String
* @default ia.Thematic.EQUAL_INTERVAL
*/
ia.NumericClassifier.prototype.classificationName;
/**
* A list of labels used in place of the default values.
*
* @property labels
* @type String[]
*/
ia.NumericClassifier.prototype.labels;
/**
* The list of standard deviation labels.
*
* @property sdLabels
* @type String[]
* @default ["> 2 SD below mean","1 - 2 SD below mean","0 - 1 SD below mean","0 - 1 SD above mean","1 - 2 SD above mean","> 2 SD above mean"]
*/
ia.NumericClassifier.prototype.sdLabels;
/**
* The number of classes to calculate breaks for.
*
* @property noClasses
* @type Number
* @default 5
*/
ia.NumericClassifier.prototype.noClasses;
/**
* The precision.
*
* @property precision
* @type Number
*/
ia.NumericClassifier.prototype.precision;
/**
* The minimum rule for range classes. Valid rules are ">=" or ">".
*
* @property minRule
* @type String
* @default ">="
*/
ia.NumericClassifier.prototype.minRule;
/**
* The maximum rule classes. Valid rules are "<=" or "<".
*
* @property maxRule
* @type String
* @default "<="
*/
ia.NumericClassifier.prototype.maxRule;
/**
* Gets the data.
*
* @method getData
* @return {Number[]} The data.
*/
ia.NumericClassifier.prototype.getData = function()
{
return this._data;
};
/**
* Sets the data.
*
* @method setData
* @param {Number[]} value The data.
*/
ia.NumericClassifier.prototype.setData = function(value)
{
this._data = value;
this._calculator.setData(this._data);
};
/**
* The list of breaks
*
* @method getBreaks
* @return {Number[]} The breaks.
*/
ia.NumericClassifier.prototype.getBreaks = function()
{
return this._breaks;
};
/**
* Used for calculating the breaks.
*
* @method getCalculator
* @return {ia.Calculator} The calculator.
*/
ia.NumericClassifier.prototype.getCalculator = function()
{
return this._calculator;
};
/**
* An array of legend classes contained in the classifier.
*
* @method getClasses
* @return {ia.NumberClass[]} The classes.
*/
ia.NumericClassifier.prototype.getClasses = function()
{
return this._classes;
};
/**
* Call this to commit any changes.
*
* @method commitChanges
*/
ia.NumericClassifier.prototype.commitChanges = function()
{
this._buildClasses();
this._buildPalettes();
};
/**
* Gets the legend class for the given value.
*
* @method getClass
* @param {Number} value The value.
* @return {ia.NumberClass} The legend class that contains the value.
*/
ia.NumericClassifier.prototype.getClass = function(value)
{
for (var i = 0; i < this._classes.length; i++)
{
var legendClass = this._classes[i];
if (legendClass.contains(value)) return legendClass;
}
// Return null if nothing found.
return null;
};
/**
* Rebuilds the classes.
*
* @method _buildClasses
* @param {Number} p An optional precision.
* @private
*/
ia.NumericClassifier.prototype._buildClasses = function(p)
{
var nClasses;
var nClass;
var rangeError = false;
var precision;
this._classes = [];
this._breaks = this._calculator.getBreaks(this.noClasses, this.classificationName);
if (this._breaks != null)
{
if (this.precision !== undefined) precision = this.precision;
else precision = ia.getPrecision(this._breaks);
if (this.classificationName === ia.Thematic.CONTINUOUS)
{
nClasses = this._breaks.length;
}
else
{
// When you get breaks the number of classes may change.
// So use the length of the returned breaks array - 1 as
// the number of classes.
if (this._breaks.length === 1) nClasses = 1;
else nClasses = this._breaks.length - 1;
}
if (this._breaks.length === 1)
{
nClass = new ia.NumberClass(this._breaks[0]);
nClass.formatter = this.formatter;
this._classes.push(nClass);
}
else
{
for (var i = 0; i < nClasses; i++)
{
if (this.classificationName === ia.Thematic.CONTINUOUS)
{
nClass = new ia.NumberClass(this._breaks[i]);
nClass.formatter = this.formatter;
this._classes.push(nClass);
}
else
{
var minValue = this._breaks[i];
var maxValue = this._breaks[i+1];
var rClass = new ia.RangeClass(minValue, maxValue);
rClass.minRule = this.minRule;
rClass.maxRule = this.maxRule;
rClass.formatter = this.formatter;
this._classes.push(rClass);
if (this.labels.length > i) rClass.setLabel(this.labels[i]);
else if (this.classificationName === ia.Thematic.STANDARD_DEVIATION && this.sdLabels.length > i)
{
rClass.setLabel(this.sdLabels[i]);
}
else
{
var minFormatted = this.formatter.format(minValue, precision);
var maxFormatted = this.formatter.format(maxValue, precision);
// Adjust the break.
if (this.minRule === ia.RangeClass.GREATER_THAN_OR_EQUAL_TO
&& this.maxRule === ia.RangeClass.LESS_THAN_OR_EQUAL_TO
&& i !== 0) minFormatted = this.formatter.format(this._adjustBreak(minFormatted), precision);
// Check the breaks are valid after adjustment.
if (this.formatter.unformat(minFormatted) > this.formatter.unformat(maxFormatted))
{
rangeError = true;
break;
}
else if (minFormatted === maxFormatted)
rClass.setLabel(minFormatted);
else
rClass.setLabel(minFormatted + " - " + maxFormatted);
}
}
}
}
}
else
{
this._breaks = [];
this._classes = [];
nClasses = 0;
}
this._adjustedNoClasses = nClasses;
if (rangeError) this._buildClasses(precision+1);
};
/**
* Adjusts a break for a range class.
*
* @method _adjustBreak
* @param {String} formattedValue The formatted value
* @return {Number} The adjusted value.
* @private
*/
ia.NumericClassifier.prototype._adjustBreak = function(formattedValue)
{
// Convert back to number.
var value = this.formatter.unformat(formattedValue);
var newValue;
var isDecimal = (formattedValue.indexOf(this.formatter.decimalSeparatorTo) > -1);
if (isDecimal)
{
var noDecimalPlaces = formattedValue.length - formattedValue.toString().indexOf(this.formatter.decimalSeparatorTo) - 1;
var valueString = "0"+".";
for (var j = 0; j < noDecimalPlaces; j++)
{
if (j === (noDecimalPlaces-1)) valueString += "1"
else valueString += "0"
}
newValue = parseFloat(value) + parseFloat(valueString);
}
else
{
newValue = parseFloat(value) + 1;
}
return newValue;
};
/**
* Rebuilds the breaks.
*
* @method _buildPalettes
* @private
*/
ia.NumericClassifier.prototype._buildPalettes = function()
{
var colors;
var sizes;
if (this.classificationName === ia.Thematic.CONTINUOUS)
{
var n = this._breaks.length;
var range = this._breaks[n-1] - this._breaks[0];
colors = [];
this.sizePalette.useSqrRoot = true;
sizes = [];
for (var i = 0; i < n; i++)
{
var f = (this._breaks[i] - this._breaks[0]) / range;
if (!ia.isNumber(f)) f = 0;
colors.push(this.colorPalette.getColor(f));
sizes.push(this.sizePalette.getSize(f));
}
}
else
{
colors = this.colorPalette.getInterpolatedColors(this._adjustedNoClasses);
this.sizePalette.useSqrRoot = false;
sizes = this.sizePalette.getInterpolatedSizes(this._adjustedNoClasses);
}
for (var i = 0; i < this._classes.length; i++)
{
var legendClass = this._classes[i];
legendClass.color = colors[i];
legendClass.size = sizes[i];
}
};