Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better number -> string callback for the radial linear scale #3281

Merged
merged 6 commits into from
Sep 24, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 1 addition & 3 deletions src/core/core.scale.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
};

Expand Down
193 changes: 193 additions & 0 deletions src/core/core.ticks.js
Original file line number Diff line number Diff line change
@@ -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<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 '';
}
}
};
};
26 changes: 1 addition & 25 deletions src/scales/scale.linear.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
};

Expand Down
43 changes: 8 additions & 35 deletions src/scales/scale.linearbase.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
52 changes: 6 additions & 46 deletions src/scales/scale.logarithmic.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
};

Expand Down Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion src/scales/scale.radialLinear.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down