From: Evert Timberg Date: Sat, 24 Sep 2016 20:56:16 +0000 (-0400) Subject: Better number -> string callback for the radial linear scale (#3281) X-Git-Tag: v2.4.0~1^2~42 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=d09a17a2b163df7280491ad6c5999d81e892f537;p=thirdparty%2FChart.js.git Better number -> string callback for the radial linear scale (#3281) Also create a new Chart.Ticks namespace to host common tick generators and formatters. --- diff --git a/src/chart.js b/src/chart.js index a12890aa0..aa0d46c45 100644 --- a/src/chart.js +++ b/src/chart.js @@ -12,6 +12,7 @@ require('./core/core.datasetController')(Chart); require('./core/core.layoutService')(Chart); require('./core/core.scaleService')(Chart); require('./core/core.plugin.js')(Chart); +require('./core/core.ticks.js')(Chart); require('./core/core.scale')(Chart); require('./core/core.title')(Chart); require('./core/core.legend')(Chart); diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 808e18e50..890e04c42 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -46,9 +46,7 @@ module.exports = function(Chart) { autoSkipPadding: 0, labelOffset: 0, // We pass through arrays to be rendered as multiline labels, we convert Others to strings here. - callback: function(value) { - return helpers.isArray(value) ? value : '' + value; - } + callback: Chart.Ticks.formatters.values } }; diff --git a/src/core/core.ticks.js b/src/core/core.ticks.js new file mode 100644 index 000000000..f55b8d40e --- /dev/null +++ b/src/core/core.ticks.js @@ -0,0 +1,193 @@ +'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} 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} 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} 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 ''; + } + } + }; +}; diff --git a/src/scales/scale.linear.js b/src/scales/scale.linear.js index f2700a3a4..e8afa84f8 100644 --- a/src/scales/scale.linear.js +++ b/src/scales/scale.linear.js @@ -7,31 +7,7 @@ module.exports = function(Chart) { var defaultConfig = { position: 'left', ticks: { - callback: 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; - } + callback: Chart.Ticks.formatters.linear } }; diff --git a/src/scales/scale.linearbase.js b/src/scales/scale.linearbase.js index 37caa0359..b8bc5c979 100644 --- a/src/scales/scale.linearbase.js +++ b/src/scales/scale.linearbase.js @@ -53,49 +53,22 @@ module.exports = function(Chart) { 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(); diff --git a/src/scales/scale.logarithmic.js b/src/scales/scale.logarithmic.js index 50417528a..a23f2eed4 100644 --- a/src/scales/scale.logarithmic.js +++ b/src/scales/scale.logarithmic.js @@ -9,16 +9,7 @@ module.exports = function(Chart) { // 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 } }; @@ -124,43 +115,12 @@ module.exports = function(Chart) { 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 diff --git a/src/scales/scale.radialLinear.js b/src/scales/scale.radialLinear.js index 361ad3cba..cc7c6557d 100644 --- a/src/scales/scale.radialLinear.js +++ b/src/scales/scale.radialLinear.js @@ -31,7 +31,9 @@ module.exports = function(Chart) { backdropPaddingY: 2, // Number - The backdrop padding to the side of the label in pixels - backdropPaddingX: 2 + backdropPaddingX: 2, + + callback: Chart.Ticks.formatters.linear }, pointLabels: {