chart.js
Version:
Simple HTML5 charts using the canvas element.
205 lines (178 loc) • 6.81 kB
JavaScript
var helpers = require('../helpers/index');
/**
* Namespace to hold static tick generation functions
* @namespace Chart.Ticks
*/
module.exports = {
/**
* Namespace to hold generators for different types of ticks
* @namespace Chart.Ticks.generators
*/
generators: {
/**
* Interface for the options provided to the numeric tick generator
* @interface INumericTickGenerationOptions
*/
/**
* The maximum number of ticks to display
* @name INumericTickGenerationOptions#maxTicks
* @type Number
*/
/**
* The distance between each tick.
* @name INumericTickGenerationOptions#stepSize
* @type Number
* @optional
*/
/**
* Forced minimum for the ticks. If not specified, the minimum of the data range is used to calculate the tick minimum
* @name INumericTickGenerationOptions#min
* @type Number
* @optional
*/
/**
* The maximum value of the ticks. If not specified, the maximum of the data range is used to calculate the tick maximum
* @name INumericTickGenerationOptions#max
* @type Number
* @optional
*/
/**
* Generate a set of linear ticks
* @method Chart.Ticks.generators.linear
* @param generationOptions {INumericTickGenerationOptions} the options used to generate the ticks
* @param dataRange {IRange} the range of the data
* @returns {Array<Number>} array of tick values
*/
linear: function(generationOptions, dataRange) {
var ticks = [];
// To get a "nice" value for the tick spacing, we will use the appropriately named
// "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
// for details.
var spacing;
if (generationOptions.stepSize && generationOptions.stepSize > 0) {
spacing = generationOptions.stepSize;
} else {
var niceRange = helpers.niceNum(dataRange.max - dataRange.min, false);
spacing = helpers.niceNum(niceRange / (generationOptions.maxTicks - 1), true);
}
var niceMin = Math.floor(dataRange.min / spacing) * spacing;
var niceMax = Math.ceil(dataRange.max / spacing) * spacing;
// If min, max and stepSize is set and they make an evenly spaced scale use it.
if (generationOptions.min && generationOptions.max && generationOptions.stepSize) {
// If very close to our whole number, use it.
if (helpers.almostWhole((generationOptions.max - generationOptions.min) / generationOptions.stepSize, spacing / 1000)) {
niceMin = generationOptions.min;
niceMax = generationOptions.max;
}
}
var numSpaces = (niceMax - niceMin) / spacing;
// If very close to our rounded value, use it.
if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
numSpaces = Math.round(numSpaces);
} else {
numSpaces = Math.ceil(numSpaces);
}
// Put the values into the ticks array
ticks.push(generationOptions.min !== undefined ? generationOptions.min : niceMin);
for (var j = 1; j < numSpaces; ++j) {
ticks.push(niceMin + (j * spacing));
}
ticks.push(generationOptions.max !== undefined ? generationOptions.max : niceMax);
return ticks;
},
/**
* Generate a set of logarithmic ticks
* @method Chart.Ticks.generators.logarithmic
* @param generationOptions {INumericTickGenerationOptions} the options used to generate the ticks
* @param dataRange {IRange} the range of the data
* @returns {Array<Number>} array of tick values
*/
logarithmic: function(generationOptions, dataRange) {
var ticks = [];
var valueOrDefault = helpers.valueOrDefault;
// Figure out what the max number of ticks we can support it is based on the size of
// the axis area. For now, we say that the minimum tick spacing in pixels must be 50
// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
// the graph
var tickVal = valueOrDefault(generationOptions.min, Math.pow(10, Math.floor(helpers.log10(dataRange.min))));
var endExp = Math.floor(helpers.log10(dataRange.max));
var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp));
var exp, significand;
if (tickVal === 0) {
exp = Math.floor(helpers.log10(dataRange.minNotZero));
significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp));
ticks.push(tickVal);
tickVal = significand * Math.pow(10, exp);
} else {
exp = Math.floor(helpers.log10(tickVal));
significand = Math.floor(tickVal / Math.pow(10, exp));
}
do {
ticks.push(tickVal);
++significand;
if (significand === 10) {
significand = 1;
++exp;
}
tickVal = significand * Math.pow(10, exp);
} while (exp < endExp || (exp === endExp && significand < endSignificand));
var lastTick = valueOrDefault(generationOptions.max, tickVal);
ticks.push(lastTick);
return ticks;
}
},
/**
* Namespace to hold formatters for different types of ticks
* @namespace Chart.Ticks.formatters
*/
formatters: {
/**
* Formatter for value labels
* @method Chart.Ticks.formatters.values
* @param value the value to display
* @return {String|Array} the label to display
*/
values: function(value) {
return helpers.isArray(value) ? value : '' + value;
},
/**
* Formatter for linear numeric ticks
* @method Chart.Ticks.formatters.linear
* @param tickValue {Number} the value to be formatted
* @param index {Number} the position of the tickValue parameter in the ticks array
* @param ticks {Array<Number>} the list of ticks being converted
* @return {String} string representation of the tickValue parameter
*/
linear: function(tickValue, index, ticks) {
// If we have lots of ticks, don't use the ones
var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0];
// If we have a number like 2.5 as the delta, figure out how many decimal places we need
if (Math.abs(delta) > 1) {
if (tickValue !== Math.floor(tickValue)) {
// not an integer
delta = tickValue - Math.floor(tickValue);
}
}
var logDelta = helpers.log10(Math.abs(delta));
var tickString = '';
if (tickValue !== 0) {
var numDecimal = -1 * Math.floor(logDelta);
numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places
tickString = tickValue.toFixed(numDecimal);
} else {
tickString = '0'; // never show decimal places for 0
}
return tickString;
},
logarithmic: function(tickValue, index, ticks) {
var remain = tickValue / (Math.pow(10, Math.floor(helpers.log10(tickValue))));
if (tickValue === 0) {
return '0';
} else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) {
return tickValue.toExponential();
}
return '';
}
}
};
;