--- /dev/null
+'use strict';
+
+module.exports = function(Chart) {
+
+ var helpers = Chart.helpers;
+
+ /**
+ * Namespace to hold static tick generation functions
+ * @namespace Chart.Ticks
+ */
+ Chart.Ticks = {
+ /**
+ * 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;
+ 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 getValueOrDefault = helpers.getValueOrDefault;
+
+ // 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 = getValueOrDefault(generationOptions.min, Math.pow(10, Math.floor(helpers.log10(dataRange.min))));
+
+ while (tickVal < dataRange.max) {
+ ticks.push(tickVal);
+
+ var exp;
+ var significand;
+
+ if (tickVal === 0) {
+ exp = Math.floor(helpers.log10(dataRange.minNotZero));
+ significand = Math.round(dataRange.minNotZero / Math.pow(10, exp));
+ } else {
+ exp = Math.floor(helpers.log10(tickVal));
+ significand = Math.floor(tickVal / Math.pow(10, exp)) + 1;
+ }
+
+ if (significand === 10) {
+ significand = 1;
+ ++exp;
+ }
+
+ tickVal = significand * Math.pow(10, exp);
+ }
+
+ var lastTick = getValueOrDefault(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 '';
+ }
+ }
+ };
+};
buildTicks: function() {
var me = this;
var opts = me.options;
- var ticks = me.ticks = [];
var tickOpts = opts.ticks;
- var getValueOrDefault = helpers.getValueOrDefault;
// 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
-
+ // the graph. Make sure we always have at least 2 ticks
var maxTicks = me.getTickLimit();
-
- // Make sure we always have at least 2 ticks
maxTicks = Math.max(2, maxTicks);
- // 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;
- var fixedStepSizeSet = (tickOpts.fixedStepSize && tickOpts.fixedStepSize > 0) || (tickOpts.stepSize && tickOpts.stepSize > 0);
- if (fixedStepSizeSet) {
- spacing = getValueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize);
- } else {
- var niceRange = helpers.niceNum(me.max - me.min, false);
- spacing = helpers.niceNum(niceRange / (maxTicks - 1), true);
- }
- var niceMin = Math.floor(me.min / spacing) * spacing;
- var niceMax = Math.ceil(me.max / spacing) * spacing;
- 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(tickOpts.min !== undefined ? tickOpts.min : niceMin);
- for (var j = 1; j < numSpaces; ++j) {
- ticks.push(niceMin + (j * spacing));
- }
- ticks.push(tickOpts.max !== undefined ? tickOpts.max : niceMax);
+ var numericGeneratorOptions = {
+ maxTicks: maxTicks,
+ min: tickOpts.min,
+ max: tickOpts.max,
+ stepSize: helpers.getValueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize)
+ };
+ var ticks = me.ticks = Chart.Ticks.generators.linear(numericGeneratorOptions, me);
me.handleDirectionalChanges();
// label settings
ticks: {
- callback: function(value, index, arr) {
- var remain = value / (Math.pow(10, Math.floor(helpers.log10(value))));
-
- if (value === 0) {
- return '0';
- } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === arr.length - 1) {
- return value.toExponential();
- }
- return '';
- }
+ callback: Chart.Ticks.formatters.logarithmic
}
};
var me = this;
var opts = me.options;
var tickOpts = opts.ticks;
- var getValueOrDefault = helpers.getValueOrDefault;
-
- // Reset the ticks array. Later on, we will draw a grid line at these positions
- // The array simply contains the numerical value of the spots where ticks will be
- var ticks = me.ticks = [];
-
- // 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 = getValueOrDefault(tickOpts.min, Math.pow(10, Math.floor(helpers.log10(me.min))));
-
- while (tickVal < me.max) {
- ticks.push(tickVal);
-
- var exp;
- var significand;
-
- if (tickVal === 0) {
- exp = Math.floor(helpers.log10(me.minNotZero));
- significand = Math.round(me.minNotZero / Math.pow(10, exp));
- } else {
- exp = Math.floor(helpers.log10(tickVal));
- significand = Math.floor(tickVal / Math.pow(10, exp)) + 1;
- }
-
- if (significand === 10) {
- significand = 1;
- ++exp;
- }
-
- tickVal = significand * Math.pow(10, exp);
- }
- var lastTick = getValueOrDefault(tickOpts.max, tickVal);
- ticks.push(lastTick);
+ var generationOptions = {
+ min: tickOpts.min,
+ max: tickOpts.max
+ };
+ var ticks = me.ticks = Chart.Ticks.generators.logarithmic(generationOptions, me);
if (!me.isHorizontal()) {
// We are in a vertical orientation. The top value is the highest. So reverse the array