diff --git a/src/chart.js b/src/chart.js index acb57862c50..b60129f2cf5 100644 --- a/src/chart.js +++ b/src/chart.js @@ -26,12 +26,11 @@ Chart.Tooltip = require('./core/core.tooltip'); require('./core/core.controller')(Chart); -require('./scales/scale.linearbase')(Chart); -require('./scales/scale.category')(Chart); -require('./scales/scale.linear')(Chart); -require('./scales/scale.logarithmic')(Chart); -require('./scales/scale.radialLinear')(Chart); -require('./scales/scale.time')(Chart); +// Register built-in scales +var scales = require('./scales'); +Chart.helpers.each(scales, function(scale, type) { + Chart.scaleService.registerScaleType(type, scale, scale._defaults); +}); // Loading built-in plugins var plugins = require('./plugins'); @@ -105,6 +104,15 @@ Chart.canvasHelpers = Chart.helpers.canvas; */ Chart.layoutService = Chart.layouts; +/** + * Provided for backward compatibility, not available anymore. + * @namespace Chart.LinearScaleBase + * @deprecated since version 2.8 + * @todo remove at version 3 + * @private + */ +Chart.LinearScaleBase = require('./scales/scale.linearbase'); + /** * Provided for backward compatibility, instead we should create a new Chart * by setting the type in the config (`new Chart(id, {type: '{chart-type}'}`). diff --git a/src/controllers/index.js b/src/controllers/index.js index d2fa6c96e3b..b80faaab690 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -1,18 +1,27 @@ 'use strict'; +var bar = require('./controller.bar'); +var bubble = require('./controller.bubble'); +var doughnut = require('./controller.doughnut'); +var horizontalBar = require('./controller.horizontalBar'); +var line = require('./controller.line'); +var polarArea = require('./controller.polarArea'); +var pie = require('./controller.pie'); +var radar = require('./controller.radar'); +var scatter = require('./controller.scatter'); + // NOTE export a map in which the key represents the controller type, not // the class, and so must be CamelCase in order to be correctly retrieved // by the controller in core.controller.js (`controllers[meta.type]`). -/* eslint-disable global-require */ module.exports = { - bar: require('./controller.bar'), - bubble: require('./controller.bubble'), - doughnut: require('./controller.doughnut'), - horizontalBar: require('./controller.horizontalBar'), - line: require('./controller.line'), - polarArea: require('./controller.polarArea'), - pie: require('./controller.pie'), - radar: require('./controller.radar'), - scatter: require('./controller.scatter') + bar: bar, + bubble: bubble, + doughnut: doughnut, + horizontalBar: horizontalBar, + line: line, + polarArea: polarArea, + pie: pie, + radar: radar, + scatter: scatter }; diff --git a/src/scales/index.js b/src/scales/index.js new file mode 100644 index 00000000000..6de575b6217 --- /dev/null +++ b/src/scales/index.js @@ -0,0 +1,15 @@ +'use strict'; + +var category = require('./scale.category'); +var linear = require('./scale.linear'); +var logarithmic = require('./scale.logarithmic'); +var radialLinear = require('./scale.radialLinear'); +var time = require('./scale.time'); + +module.exports = { + category: category, + linear: linear, + logarithmic: logarithmic, + radialLinear: radialLinear, + time: time +}; diff --git a/src/scales/scale.category.js b/src/scales/scale.category.js index 33a91f3a2c6..3a4d20645b3 100644 --- a/src/scales/scale.category.js +++ b/src/scales/scale.category.js @@ -1,135 +1,134 @@ 'use strict'; var Scale = require('../core/core.scale'); -var scaleService = require('../core/core.scaleService'); - -module.exports = function() { - - // Default config for a category scale - var defaultConfig = { - position: 'bottom' - }; - - var DatasetScale = Scale.extend({ - /** - * Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use those - * else fall back to data.labels - * @private - */ - getLabels: function() { - var data = this.chart.data; - return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels; - }, - - determineDataLimits: function() { - var me = this; - var labels = me.getLabels(); - me.minIndex = 0; - me.maxIndex = labels.length - 1; - var findIndex; - - if (me.options.ticks.min !== undefined) { - // user specified min value - findIndex = labels.indexOf(me.options.ticks.min); - me.minIndex = findIndex !== -1 ? findIndex : me.minIndex; - } - if (me.options.ticks.max !== undefined) { - // user specified max value - findIndex = labels.indexOf(me.options.ticks.max); - me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex; - } +var defaultConfig = { + position: 'bottom' +}; - me.min = labels[me.minIndex]; - me.max = labels[me.maxIndex]; - }, +module.exports = Scale.extend({ + /** + * Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use those + * else fall back to data.labels + * @private + */ + getLabels: function() { + var data = this.chart.data; + return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels; + }, + + determineDataLimits: function() { + var me = this; + var labels = me.getLabels(); + me.minIndex = 0; + me.maxIndex = labels.length - 1; + var findIndex; + + if (me.options.ticks.min !== undefined) { + // user specified min value + findIndex = labels.indexOf(me.options.ticks.min); + me.minIndex = findIndex !== -1 ? findIndex : me.minIndex; + } - buildTicks: function() { - var me = this; - var labels = me.getLabels(); - // If we are viewing some subset of labels, slice the original array - me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1); - }, + if (me.options.ticks.max !== undefined) { + // user specified max value + findIndex = labels.indexOf(me.options.ticks.max); + me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex; + } - getLabelForIndex: function(index, datasetIndex) { - var me = this; - var data = me.chart.data; - var isHorizontal = me.isHorizontal(); + me.min = labels[me.minIndex]; + me.max = labels[me.maxIndex]; + }, - if (data.yLabels && !isHorizontal) { - return me.getRightValue(data.datasets[datasetIndex].data[index]); - } - return me.ticks[index - me.minIndex]; - }, - - // Used to get data value locations. Value can either be an index or a numerical value - getPixelForValue: function(value, index) { - var me = this; - var offset = me.options.offset; - // 1 is added because we need the length but we have the indexes - var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - (offset ? 0 : 1)), 1); - - // If value is a data object, then index is the index in the data array, - // not the index of the scale. We need to change that. - var valueCategory; - if (value !== undefined && value !== null) { - valueCategory = me.isHorizontal() ? value.x : value.y; - } - if (valueCategory !== undefined || (value !== undefined && isNaN(index))) { - var labels = me.getLabels(); - value = valueCategory || value; - var idx = labels.indexOf(value); - index = idx !== -1 ? idx : index; - } + buildTicks: function() { + var me = this; + var labels = me.getLabels(); + // If we are viewing some subset of labels, slice the original array + me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1); + }, - if (me.isHorizontal()) { - var valueWidth = me.width / offsetAmt; - var widthOffset = (valueWidth * (index - me.minIndex)); + getLabelForIndex: function(index, datasetIndex) { + var me = this; + var data = me.chart.data; + var isHorizontal = me.isHorizontal(); - if (offset) { - widthOffset += (valueWidth / 2); - } + if (data.yLabels && !isHorizontal) { + return me.getRightValue(data.datasets[datasetIndex].data[index]); + } + return me.ticks[index - me.minIndex]; + }, + + // Used to get data value locations. Value can either be an index or a numerical value + getPixelForValue: function(value, index) { + var me = this; + var offset = me.options.offset; + // 1 is added because we need the length but we have the indexes + var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - (offset ? 0 : 1)), 1); + + // If value is a data object, then index is the index in the data array, + // not the index of the scale. We need to change that. + var valueCategory; + if (value !== undefined && value !== null) { + valueCategory = me.isHorizontal() ? value.x : value.y; + } + if (valueCategory !== undefined || (value !== undefined && isNaN(index))) { + var labels = me.getLabels(); + value = valueCategory || value; + var idx = labels.indexOf(value); + index = idx !== -1 ? idx : index; + } - return me.left + widthOffset; - } - var valueHeight = me.height / offsetAmt; - var heightOffset = (valueHeight * (index - me.minIndex)); + if (me.isHorizontal()) { + var valueWidth = me.width / offsetAmt; + var widthOffset = (valueWidth * (index - me.minIndex)); if (offset) { - heightOffset += (valueHeight / 2); + widthOffset += (valueWidth / 2); } - return me.top + heightOffset; - }, - getPixelForTick: function(index) { - return this.getPixelForValue(this.ticks[index], index + this.minIndex, null); - }, - getValueForPixel: function(pixel) { - var me = this; - var offset = me.options.offset; - var value; - var offsetAmt = Math.max((me._ticks.length - (offset ? 0 : 1)), 1); - var horz = me.isHorizontal(); - var valueDimension = (horz ? me.width : me.height) / offsetAmt; - - pixel -= horz ? me.left : me.top; + return me.left + widthOffset; + } + var valueHeight = me.height / offsetAmt; + var heightOffset = (valueHeight * (index - me.minIndex)); - if (offset) { - pixel -= (valueDimension / 2); - } + if (offset) { + heightOffset += (valueHeight / 2); + } - if (pixel <= 0) { - value = 0; - } else { - value = Math.round(pixel / valueDimension); - } + return me.top + heightOffset; + }, + + getPixelForTick: function(index) { + return this.getPixelForValue(this.ticks[index], index + this.minIndex, null); + }, - return value + me.minIndex; - }, - getBasePixel: function() { - return this.bottom; + getValueForPixel: function(pixel) { + var me = this; + var offset = me.options.offset; + var value; + var offsetAmt = Math.max((me._ticks.length - (offset ? 0 : 1)), 1); + var horz = me.isHorizontal(); + var valueDimension = (horz ? me.width : me.height) / offsetAmt; + + pixel -= horz ? me.left : me.top; + + if (offset) { + pixel -= (valueDimension / 2); } - }); - scaleService.registerScaleType('category', DatasetScale, defaultConfig); -}; + if (pixel <= 0) { + value = 0; + } else { + value = Math.round(pixel / valueDimension); + } + + return value + me.minIndex; + }, + + getBasePixel: function() { + return this.bottom; + } +}); + +// INTERNAL: static default options, registered in src/chart.js +module.exports._defaults = defaultConfig; diff --git a/src/scales/scale.linear.js b/src/scales/scale.linear.js index 60a291e626f..41a4e4168a3 100644 --- a/src/scales/scale.linear.js +++ b/src/scales/scale.linear.js @@ -1,187 +1,190 @@ 'use strict'; var helpers = require('../helpers/index'); -var scaleService = require('../core/core.scaleService'); +var LinearScaleBase = require('./scale.linearbase'); var Ticks = require('../core/core.ticks'); -module.exports = function(Chart) { +var defaultConfig = { + position: 'left', + ticks: { + callback: Ticks.formatters.linear + } +}; - var defaultConfig = { - position: 'left', - ticks: { - callback: Ticks.formatters.linear +module.exports = LinearScaleBase.extend({ + determineDataLimits: function() { + var me = this; + var opts = me.options; + var chart = me.chart; + var data = chart.data; + var datasets = data.datasets; + var isHorizontal = me.isHorizontal(); + var DEFAULT_MIN = 0; + var DEFAULT_MAX = 1; + + function IDMatches(meta) { + return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; } - }; - - var LinearScale = Chart.LinearScaleBase.extend({ - - determineDataLimits: function() { - var me = this; - var opts = me.options; - var chart = me.chart; - var data = chart.data; - var datasets = data.datasets; - var isHorizontal = me.isHorizontal(); - var DEFAULT_MIN = 0; - var DEFAULT_MAX = 1; - - function IDMatches(meta) { - return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; - } - - // First Calculate the range - me.min = null; - me.max = null; - - var hasStacks = opts.stacked; - if (hasStacks === undefined) { - helpers.each(datasets, function(dataset, datasetIndex) { - if (hasStacks) { - return; - } - - var meta = chart.getDatasetMeta(datasetIndex); - if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && - meta.stack !== undefined) { - hasStacks = true; - } - }); - } - - if (opts.stacked || hasStacks) { - var valuesPerStack = {}; - - helpers.each(datasets, function(dataset, datasetIndex) { - var meta = chart.getDatasetMeta(datasetIndex); - var key = [ - meta.type, - // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined - ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), - meta.stack - ].join('.'); - - if (valuesPerStack[key] === undefined) { - valuesPerStack[key] = { - positiveValues: [], - negativeValues: [] - }; - } - - // Store these per type - var positiveValues = valuesPerStack[key].positiveValues; - var negativeValues = valuesPerStack[key].negativeValues; - - if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { - helpers.each(dataset.data, function(rawValue, index) { - var value = +me.getRightValue(rawValue); - if (isNaN(value) || meta.data[index].hidden) { - return; - } - - positiveValues[index] = positiveValues[index] || 0; - negativeValues[index] = negativeValues[index] || 0; - - if (opts.relativePoints) { - positiveValues[index] = 100; - } else if (value < 0) { - negativeValues[index] += value; - } else { - positiveValues[index] += value; - } - }); - } - }); - - helpers.each(valuesPerStack, function(valuesForType) { - var values = valuesForType.positiveValues.concat(valuesForType.negativeValues); - var minVal = helpers.min(values); - var maxVal = helpers.max(values); - me.min = me.min === null ? minVal : Math.min(me.min, minVal); - me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); - }); - - } else { - helpers.each(datasets, function(dataset, datasetIndex) { - var meta = chart.getDatasetMeta(datasetIndex); - if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { - helpers.each(dataset.data, function(rawValue, index) { - var value = +me.getRightValue(rawValue); - if (isNaN(value) || meta.data[index].hidden) { - return; - } - - if (me.min === null) { - me.min = value; - } else if (value < me.min) { - me.min = value; - } - - if (me.max === null) { - me.max = value; - } else if (value > me.max) { - me.max = value; - } - }); - } - }); - } - - me.min = isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN; - me.max = isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX; - - // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero - this.handleTickRangeOptions(); - }, - // Returns the maximum number of ticks based on the scale dimension - _computeTickLimit: function() { - var me = this; - var tickFont; - - if (me.isHorizontal()) { - return Math.ceil(me.width / 40); - } - tickFont = helpers.options._parseFont(me.options.ticks); - return Math.ceil(me.height / tickFont.lineHeight); - }, - // Called after the ticks are built. We need - handleDirectionalChanges: function() { - if (!this.isHorizontal()) { - // We are in a vertical orientation. The top value is the highest. So reverse the array - this.ticks.reverse(); - } - }, - getLabelForIndex: function(index, datasetIndex) { - return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); - }, - // Utils - getPixelForValue: function(value) { - // This must be called after fit has been run so that - // this.left, this.top, this.right, and this.bottom have been defined - var me = this; - var start = me.start; - - var rightValue = +me.getRightValue(value); - var pixel; - var range = me.end - start; - - if (me.isHorizontal()) { - pixel = me.left + (me.width / range * (rightValue - start)); - } else { - pixel = me.bottom - (me.height / range * (rightValue - start)); - } - return pixel; - }, - getValueForPixel: function(pixel) { - var me = this; - var isHorizontal = me.isHorizontal(); - var innerDimension = isHorizontal ? me.width : me.height; - var offset = (isHorizontal ? pixel - me.left : me.bottom - pixel) / innerDimension; - return me.start + ((me.end - me.start) * offset); - }, - getPixelForTick: function(index) { - return this.getPixelForValue(this.ticksAsNumbers[index]); + + // First Calculate the range + me.min = null; + me.max = null; + + var hasStacks = opts.stacked; + if (hasStacks === undefined) { + helpers.each(datasets, function(dataset, datasetIndex) { + if (hasStacks) { + return; + } + + var meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && + meta.stack !== undefined) { + hasStacks = true; + } + }); } - }); - scaleService.registerScaleType('linear', LinearScale, defaultConfig); -}; + if (opts.stacked || hasStacks) { + var valuesPerStack = {}; + + helpers.each(datasets, function(dataset, datasetIndex) { + var meta = chart.getDatasetMeta(datasetIndex); + var key = [ + meta.type, + // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined + ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), + meta.stack + ].join('.'); + + if (valuesPerStack[key] === undefined) { + valuesPerStack[key] = { + positiveValues: [], + negativeValues: [] + }; + } + + // Store these per type + var positiveValues = valuesPerStack[key].positiveValues; + var negativeValues = valuesPerStack[key].negativeValues; + + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { + helpers.each(dataset.data, function(rawValue, index) { + var value = +me.getRightValue(rawValue); + if (isNaN(value) || meta.data[index].hidden) { + return; + } + + positiveValues[index] = positiveValues[index] || 0; + negativeValues[index] = negativeValues[index] || 0; + + if (opts.relativePoints) { + positiveValues[index] = 100; + } else if (value < 0) { + negativeValues[index] += value; + } else { + positiveValues[index] += value; + } + }); + } + }); + + helpers.each(valuesPerStack, function(valuesForType) { + var values = valuesForType.positiveValues.concat(valuesForType.negativeValues); + var minVal = helpers.min(values); + var maxVal = helpers.max(values); + me.min = me.min === null ? minVal : Math.min(me.min, minVal); + me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); + }); + + } else { + helpers.each(datasets, function(dataset, datasetIndex) { + var meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { + helpers.each(dataset.data, function(rawValue, index) { + var value = +me.getRightValue(rawValue); + if (isNaN(value) || meta.data[index].hidden) { + return; + } + + if (me.min === null) { + me.min = value; + } else if (value < me.min) { + me.min = value; + } + + if (me.max === null) { + me.max = value; + } else if (value > me.max) { + me.max = value; + } + }); + } + }); + } + + me.min = isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN; + me.max = isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX; + + // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero + this.handleTickRangeOptions(); + }, + + // Returns the maximum number of ticks based on the scale dimension + _computeTickLimit: function() { + var me = this; + var tickFont; + + if (me.isHorizontal()) { + return Math.ceil(me.width / 40); + } + tickFont = helpers.options._parseFont(me.options.ticks); + return Math.ceil(me.height / tickFont.lineHeight); + }, + + // Called after the ticks are built. We need + handleDirectionalChanges: function() { + if (!this.isHorizontal()) { + // We are in a vertical orientation. The top value is the highest. So reverse the array + this.ticks.reverse(); + } + }, + + getLabelForIndex: function(index, datasetIndex) { + return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); + }, + + // Utils + getPixelForValue: function(value) { + // This must be called after fit has been run so that + // this.left, this.top, this.right, and this.bottom have been defined + var me = this; + var start = me.start; + + var rightValue = +me.getRightValue(value); + var pixel; + var range = me.end - start; + + if (me.isHorizontal()) { + pixel = me.left + (me.width / range * (rightValue - start)); + } else { + pixel = me.bottom - (me.height / range * (rightValue - start)); + } + return pixel; + }, + + getValueForPixel: function(pixel) { + var me = this; + var isHorizontal = me.isHorizontal(); + var innerDimension = isHorizontal ? me.width : me.height; + var offset = (isHorizontal ? pixel - me.left : me.bottom - pixel) / innerDimension; + return me.start + ((me.end - me.start) * offset); + }, + + getPixelForTick: function(index) { + return this.getPixelForValue(this.ticksAsNumbers[index]); + } +}); + +// INTERNAL: static default options, registered in src/chart.js +module.exports._defaults = defaultConfig; diff --git a/src/scales/scale.linearbase.js b/src/scales/scale.linearbase.js index 8dfe15a08cb..808f454407a 100644 --- a/src/scales/scale.linearbase.js +++ b/src/scales/scale.linearbase.js @@ -3,6 +3,8 @@ var helpers = require('../helpers/index'); var Scale = require('../core/core.scale'); +var noop = helpers.noop; + /** * Generate a set of linear ticks * @param generationOptions the options used to generate the ticks @@ -74,156 +76,152 @@ function generateTicks(generationOptions, dataRange) { return ticks; } -module.exports = function(Chart) { - - var noop = helpers.noop; - - Chart.LinearScaleBase = Scale.extend({ - getRightValue: function(value) { - if (typeof value === 'string') { - return +value; - } - return Scale.prototype.getRightValue.call(this, value); - }, - - handleTickRangeOptions: function() { - var me = this; - var opts = me.options; - var tickOpts = opts.ticks; - - // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, - // do nothing since that would make the chart weird. If the user really wants a weird chart - // axis, they can manually override it - if (tickOpts.beginAtZero) { - var minSign = helpers.sign(me.min); - var maxSign = helpers.sign(me.max); - - if (minSign < 0 && maxSign < 0) { - // move the top up to 0 - me.max = 0; - } else if (minSign > 0 && maxSign > 0) { - // move the bottom down to 0 - me.min = 0; - } +module.exports = Scale.extend({ + getRightValue: function(value) { + if (typeof value === 'string') { + return +value; + } + return Scale.prototype.getRightValue.call(this, value); + }, + + handleTickRangeOptions: function() { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; + + // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, + // do nothing since that would make the chart weird. If the user really wants a weird chart + // axis, they can manually override it + if (tickOpts.beginAtZero) { + var minSign = helpers.sign(me.min); + var maxSign = helpers.sign(me.max); + + if (minSign < 0 && maxSign < 0) { + // move the top up to 0 + me.max = 0; + } else if (minSign > 0 && maxSign > 0) { + // move the bottom down to 0 + me.min = 0; } + } - var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined; - var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined; - - if (tickOpts.min !== undefined) { - me.min = tickOpts.min; - } else if (tickOpts.suggestedMin !== undefined) { - if (me.min === null) { - me.min = tickOpts.suggestedMin; - } else { - me.min = Math.min(me.min, tickOpts.suggestedMin); - } - } + var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined; + var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined; - if (tickOpts.max !== undefined) { - me.max = tickOpts.max; - } else if (tickOpts.suggestedMax !== undefined) { - if (me.max === null) { - me.max = tickOpts.suggestedMax; - } else { - me.max = Math.max(me.max, tickOpts.suggestedMax); - } + if (tickOpts.min !== undefined) { + me.min = tickOpts.min; + } else if (tickOpts.suggestedMin !== undefined) { + if (me.min === null) { + me.min = tickOpts.suggestedMin; + } else { + me.min = Math.min(me.min, tickOpts.suggestedMin); } + } - if (setMin !== setMax) { - // We set the min or the max but not both. - // So ensure that our range is good - // Inverted or 0 length range can happen when - // ticks.min is set, and no datasets are visible - if (me.min >= me.max) { - if (setMin) { - me.max = me.min + 1; - } else { - me.min = me.max - 1; - } - } + if (tickOpts.max !== undefined) { + me.max = tickOpts.max; + } else if (tickOpts.suggestedMax !== undefined) { + if (me.max === null) { + me.max = tickOpts.suggestedMax; + } else { + me.max = Math.max(me.max, tickOpts.suggestedMax); } + } - if (me.min === me.max) { - me.max++; - - if (!tickOpts.beginAtZero) { - me.min--; + if (setMin !== setMax) { + // We set the min or the max but not both. + // So ensure that our range is good + // Inverted or 0 length range can happen when + // ticks.min is set, and no datasets are visible + if (me.min >= me.max) { + if (setMin) { + me.max = me.min + 1; + } else { + me.min = me.max - 1; } } - }, + } - getTickLimit: function() { - var me = this; - var tickOpts = me.options.ticks; - var stepSize = tickOpts.stepSize; - var maxTicksLimit = tickOpts.maxTicksLimit; - var maxTicks; + if (me.min === me.max) { + me.max++; - if (stepSize) { - maxTicks = Math.ceil(me.max / stepSize) - Math.floor(me.min / stepSize) + 1; - } else { - maxTicks = me._computeTickLimit(); - maxTicksLimit = maxTicksLimit || 11; + if (!tickOpts.beginAtZero) { + me.min--; } + } + }, + + getTickLimit: function() { + var me = this; + var tickOpts = me.options.ticks; + var stepSize = tickOpts.stepSize; + var maxTicksLimit = tickOpts.maxTicksLimit; + var maxTicks; + + if (stepSize) { + maxTicks = Math.ceil(me.max / stepSize) - Math.floor(me.min / stepSize) + 1; + } else { + maxTicks = me._computeTickLimit(); + maxTicksLimit = maxTicksLimit || 11; + } - if (maxTicksLimit) { - maxTicks = Math.min(maxTicksLimit, maxTicks); - } - - return maxTicks; - }, - - _computeTickLimit: function() { - return Number.POSITIVE_INFINITY; - }, - - handleDirectionalChanges: noop, - - buildTicks: function() { - var me = this; - var opts = me.options; - var tickOpts = opts.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 40 - // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on - // the graph. Make sure we always have at least 2 ticks - var maxTicks = me.getTickLimit(); - maxTicks = Math.max(2, maxTicks); - - var numericGeneratorOptions = { - maxTicks: maxTicks, - min: tickOpts.min, - max: tickOpts.max, - precision: tickOpts.precision, - stepSize: helpers.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize) - }; - var ticks = me.ticks = generateTicks(numericGeneratorOptions, me); - - me.handleDirectionalChanges(); + if (maxTicksLimit) { + maxTicks = Math.min(maxTicksLimit, maxTicks); + } - // At this point, we need to update our max and min given the tick values since we have expanded the - // range of the scale - me.max = helpers.max(ticks); - me.min = helpers.min(ticks); + return maxTicks; + }, + + _computeTickLimit: function() { + return Number.POSITIVE_INFINITY; + }, + + handleDirectionalChanges: noop, + + buildTicks: function() { + var me = this; + var opts = me.options; + var tickOpts = opts.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 40 + // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on + // the graph. Make sure we always have at least 2 ticks + var maxTicks = me.getTickLimit(); + maxTicks = Math.max(2, maxTicks); + + var numericGeneratorOptions = { + maxTicks: maxTicks, + min: tickOpts.min, + max: tickOpts.max, + precision: tickOpts.precision, + stepSize: helpers.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize) + }; + var ticks = me.ticks = generateTicks(numericGeneratorOptions, me); + + me.handleDirectionalChanges(); + + // At this point, we need to update our max and min given the tick values since we have expanded the + // range of the scale + me.max = helpers.max(ticks); + me.min = helpers.min(ticks); + + if (tickOpts.reverse) { + ticks.reverse(); + + me.start = me.max; + me.end = me.min; + } else { + me.start = me.min; + me.end = me.max; + } + }, - if (tickOpts.reverse) { - ticks.reverse(); + convertTicksToLabels: function() { + var me = this; + me.ticksAsNumbers = me.ticks.slice(); + me.zeroLineIndex = me.ticks.indexOf(0); - me.start = me.max; - me.end = me.min; - } else { - me.start = me.min; - me.end = me.max; - } - }, - convertTicksToLabels: function() { - var me = this; - me.ticksAsNumbers = me.ticks.slice(); - me.zeroLineIndex = me.ticks.indexOf(0); - - Scale.prototype.convertTicksToLabels.call(me); - } - }); -}; + Scale.prototype.convertTicksToLabels.call(me); + } +}); diff --git a/src/scales/scale.logarithmic.js b/src/scales/scale.logarithmic.js index 240df0faf46..5dd5fe80e30 100644 --- a/src/scales/scale.logarithmic.js +++ b/src/scales/scale.logarithmic.js @@ -1,8 +1,8 @@ 'use strict'; +var defaults = require('../core/core.defaults'); var helpers = require('../helpers/index'); var Scale = require('../core/core.scale'); -var scaleService = require('../core/core.scaleService'); var Ticks = require('../core/core.ticks'); /** @@ -52,294 +52,299 @@ function generateTicks(generationOptions, dataRange) { return ticks; } +var defaultConfig = { + position: 'left', -module.exports = function(Chart) { - - var defaultConfig = { - position: 'left', + // label settings + ticks: { + callback: Ticks.formatters.logarithmic + } +}; - // label settings - ticks: { - callback: Ticks.formatters.logarithmic +module.exports = Scale.extend({ + determineDataLimits: function() { + var me = this; + var opts = me.options; + var chart = me.chart; + var data = chart.data; + var datasets = data.datasets; + var isHorizontal = me.isHorizontal(); + function IDMatches(meta) { + return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; } - }; - - var LogarithmicScale = Scale.extend({ - determineDataLimits: function() { - var me = this; - var opts = me.options; - var chart = me.chart; - var data = chart.data; - var datasets = data.datasets; - var isHorizontal = me.isHorizontal(); - function IDMatches(meta) { - return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; - } - // Calculate Range - me.min = null; - me.max = null; - me.minNotZero = null; + // Calculate Range + me.min = null; + me.max = null; + me.minNotZero = null; - var hasStacks = opts.stacked; - if (hasStacks === undefined) { - helpers.each(datasets, function(dataset, datasetIndex) { - if (hasStacks) { - return; - } + var hasStacks = opts.stacked; + if (hasStacks === undefined) { + helpers.each(datasets, function(dataset, datasetIndex) { + if (hasStacks) { + return; + } - var meta = chart.getDatasetMeta(datasetIndex); - if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && - meta.stack !== undefined) { - hasStacks = true; + var meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && + meta.stack !== undefined) { + hasStacks = true; + } + }); + } + + if (opts.stacked || hasStacks) { + var valuesPerStack = {}; + + helpers.each(datasets, function(dataset, datasetIndex) { + var meta = chart.getDatasetMeta(datasetIndex); + var key = [ + meta.type, + // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined + ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), + meta.stack + ].join('.'); + + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { + if (valuesPerStack[key] === undefined) { + valuesPerStack[key] = []; } - }); - } - if (opts.stacked || hasStacks) { - var valuesPerStack = {}; - - helpers.each(datasets, function(dataset, datasetIndex) { - var meta = chart.getDatasetMeta(datasetIndex); - var key = [ - meta.type, - // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined - ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), - meta.stack - ].join('.'); - - if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { - if (valuesPerStack[key] === undefined) { - valuesPerStack[key] = []; + helpers.each(dataset.data, function(rawValue, index) { + var values = valuesPerStack[key]; + var value = +me.getRightValue(rawValue); + // invalid, hidden and negative values are ignored + if (isNaN(value) || meta.data[index].hidden || value < 0) { + return; + } + values[index] = values[index] || 0; + values[index] += value; + }); + } + }); + + helpers.each(valuesPerStack, function(valuesForType) { + if (valuesForType.length > 0) { + var minVal = helpers.min(valuesForType); + var maxVal = helpers.max(valuesForType); + me.min = me.min === null ? minVal : Math.min(me.min, minVal); + me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); + } + }); + + } else { + helpers.each(datasets, function(dataset, datasetIndex) { + var meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { + helpers.each(dataset.data, function(rawValue, index) { + var value = +me.getRightValue(rawValue); + // invalid, hidden and negative values are ignored + if (isNaN(value) || meta.data[index].hidden || value < 0) { + return; } - helpers.each(dataset.data, function(rawValue, index) { - var values = valuesPerStack[key]; - var value = +me.getRightValue(rawValue); - // invalid, hidden and negative values are ignored - if (isNaN(value) || meta.data[index].hidden || value < 0) { - return; - } - values[index] = values[index] || 0; - values[index] += value; - }); - } - }); - - helpers.each(valuesPerStack, function(valuesForType) { - if (valuesForType.length > 0) { - var minVal = helpers.min(valuesForType); - var maxVal = helpers.max(valuesForType); - me.min = me.min === null ? minVal : Math.min(me.min, minVal); - me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); - } - }); + if (me.min === null) { + me.min = value; + } else if (value < me.min) { + me.min = value; + } - } else { - helpers.each(datasets, function(dataset, datasetIndex) { - var meta = chart.getDatasetMeta(datasetIndex); - if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { - helpers.each(dataset.data, function(rawValue, index) { - var value = +me.getRightValue(rawValue); - // invalid, hidden and negative values are ignored - if (isNaN(value) || meta.data[index].hidden || value < 0) { - return; - } - - if (me.min === null) { - me.min = value; - } else if (value < me.min) { - me.min = value; - } - - if (me.max === null) { - me.max = value; - } else if (value > me.max) { - me.max = value; - } - - if (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) { - me.minNotZero = value; - } - }); - } - }); - } + if (me.max === null) { + me.max = value; + } else if (value > me.max) { + me.max = value; + } - // Common base implementation to handle ticks.min, ticks.max - this.handleTickRangeOptions(); - }, - handleTickRangeOptions: function() { - var me = this; - var opts = me.options; - var tickOpts = opts.ticks; - var valueOrDefault = helpers.valueOrDefault; - var DEFAULT_MIN = 1; - var DEFAULT_MAX = 10; - - me.min = valueOrDefault(tickOpts.min, me.min); - me.max = valueOrDefault(tickOpts.max, me.max); - - if (me.min === me.max) { - if (me.min !== 0 && me.min !== null) { - me.min = Math.pow(10, Math.floor(helpers.log10(me.min)) - 1); - me.max = Math.pow(10, Math.floor(helpers.log10(me.max)) + 1); - } else { - me.min = DEFAULT_MIN; - me.max = DEFAULT_MAX; - } - } - if (me.min === null) { - me.min = Math.pow(10, Math.floor(helpers.log10(me.max)) - 1); - } - if (me.max === null) { - me.max = me.min !== 0 - ? Math.pow(10, Math.floor(helpers.log10(me.min)) + 1) - : DEFAULT_MAX; - } - if (me.minNotZero === null) { - if (me.min > 0) { - me.minNotZero = me.min; - } else if (me.max < 1) { - me.minNotZero = Math.pow(10, Math.floor(helpers.log10(me.max))); - } else { - me.minNotZero = DEFAULT_MIN; + if (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) { + me.minNotZero = value; + } + }); } - } - }, - buildTicks: function() { - var me = this; - var opts = me.options; - var tickOpts = opts.ticks; - var reverse = !me.isHorizontal(); - - var generationOptions = { - min: tickOpts.min, - max: tickOpts.max - }; - var ticks = me.ticks = generateTicks(generationOptions, me); - - // At this point, we need to update our max and min given the tick values since we have expanded the - // range of the scale - me.max = helpers.max(ticks); - me.min = helpers.min(ticks); - - if (tickOpts.reverse) { - reverse = !reverse; - me.start = me.max; - me.end = me.min; - } else { - me.start = me.min; - me.end = me.max; - } - if (reverse) { - ticks.reverse(); - } - }, - convertTicksToLabels: function() { - this.tickValues = this.ticks.slice(); - - Scale.prototype.convertTicksToLabels.call(this); - }, - // Get the correct tooltip label - getLabelForIndex: function(index, datasetIndex) { - return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); - }, - getPixelForTick: function(index) { - return this.getPixelForValue(this.tickValues[index]); - }, - /** - * Returns the value of the first tick. - * @param {Number} value - The minimum not zero value. - * @return {Number} The first tick value. - * @private - */ - _getFirstTickValue: function(value) { - var exp = Math.floor(helpers.log10(value)); - var significand = Math.floor(value / Math.pow(10, exp)); - - return significand * Math.pow(10, exp); - }, - getPixelForValue: function(value) { - var me = this; - var reverse = me.options.ticks.reverse; - var log10 = helpers.log10; - var firstTickValue = me._getFirstTickValue(me.minNotZero); - var offset = 0; - var innerDimension, pixel, start, end, sign; - - value = +me.getRightValue(value); - if (reverse) { - start = me.end; - end = me.start; - sign = -1; + }); + } + + // Common base implementation to handle ticks.min, ticks.max + this.handleTickRangeOptions(); + }, + + handleTickRangeOptions: function() { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; + var valueOrDefault = helpers.valueOrDefault; + var DEFAULT_MIN = 1; + var DEFAULT_MAX = 10; + + me.min = valueOrDefault(tickOpts.min, me.min); + me.max = valueOrDefault(tickOpts.max, me.max); + + if (me.min === me.max) { + if (me.min !== 0 && me.min !== null) { + me.min = Math.pow(10, Math.floor(helpers.log10(me.min)) - 1); + me.max = Math.pow(10, Math.floor(helpers.log10(me.max)) + 1); } else { - start = me.start; - end = me.end; - sign = 1; + me.min = DEFAULT_MIN; + me.max = DEFAULT_MAX; } - if (me.isHorizontal()) { - innerDimension = me.width; - pixel = reverse ? me.right : me.left; + } + if (me.min === null) { + me.min = Math.pow(10, Math.floor(helpers.log10(me.max)) - 1); + } + if (me.max === null) { + me.max = me.min !== 0 + ? Math.pow(10, Math.floor(helpers.log10(me.min)) + 1) + : DEFAULT_MAX; + } + if (me.minNotZero === null) { + if (me.min > 0) { + me.minNotZero = me.min; + } else if (me.max < 1) { + me.minNotZero = Math.pow(10, Math.floor(helpers.log10(me.max))); } else { - innerDimension = me.height; - sign *= -1; // invert, since the upper-left corner of the canvas is at pixel (0, 0) - pixel = reverse ? me.top : me.bottom; - } - if (value !== start) { - if (start === 0) { // include zero tick - offset = helpers.getValueOrDefault( - me.options.ticks.fontSize, - Chart.defaults.global.defaultFontSize - ); - innerDimension -= offset; - start = firstTickValue; - } - if (value !== 0) { - offset += innerDimension / (log10(end) - log10(start)) * (log10(value) - log10(start)); - } - pixel += sign * offset; + me.minNotZero = DEFAULT_MIN; } - return pixel; - }, - getValueForPixel: function(pixel) { - var me = this; - var reverse = me.options.ticks.reverse; - var log10 = helpers.log10; - var firstTickValue = me._getFirstTickValue(me.minNotZero); - var innerDimension, start, end, value; - - if (reverse) { - start = me.end; - end = me.start; - } else { - start = me.start; - end = me.end; + } + }, + + buildTicks: function() { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; + var reverse = !me.isHorizontal(); + + var generationOptions = { + min: tickOpts.min, + max: tickOpts.max + }; + var ticks = me.ticks = generateTicks(generationOptions, me); + + // At this point, we need to update our max and min given the tick values since we have expanded the + // range of the scale + me.max = helpers.max(ticks); + me.min = helpers.min(ticks); + + if (tickOpts.reverse) { + reverse = !reverse; + me.start = me.max; + me.end = me.min; + } else { + me.start = me.min; + me.end = me.max; + } + if (reverse) { + ticks.reverse(); + } + }, + + convertTicksToLabels: function() { + this.tickValues = this.ticks.slice(); + + Scale.prototype.convertTicksToLabels.call(this); + }, + + // Get the correct tooltip label + getLabelForIndex: function(index, datasetIndex) { + return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); + }, + + getPixelForTick: function(index) { + return this.getPixelForValue(this.tickValues[index]); + }, + + /** + * Returns the value of the first tick. + * @param {Number} value - The minimum not zero value. + * @return {Number} The first tick value. + * @private + */ + _getFirstTickValue: function(value) { + var exp = Math.floor(helpers.log10(value)); + var significand = Math.floor(value / Math.pow(10, exp)); + + return significand * Math.pow(10, exp); + }, + + getPixelForValue: function(value) { + var me = this; + var reverse = me.options.ticks.reverse; + var log10 = helpers.log10; + var firstTickValue = me._getFirstTickValue(me.minNotZero); + var offset = 0; + var innerDimension, pixel, start, end, sign; + + value = +me.getRightValue(value); + if (reverse) { + start = me.end; + end = me.start; + sign = -1; + } else { + start = me.start; + end = me.end; + sign = 1; + } + if (me.isHorizontal()) { + innerDimension = me.width; + pixel = reverse ? me.right : me.left; + } else { + innerDimension = me.height; + sign *= -1; // invert, since the upper-left corner of the canvas is at pixel (0, 0) + pixel = reverse ? me.top : me.bottom; + } + if (value !== start) { + if (start === 0) { // include zero tick + offset = helpers.getValueOrDefault( + me.options.ticks.fontSize, + defaults.global.defaultFontSize + ); + innerDimension -= offset; + start = firstTickValue; } - if (me.isHorizontal()) { - innerDimension = me.width; - value = reverse ? me.right - pixel : pixel - me.left; - } else { - innerDimension = me.height; - value = reverse ? pixel - me.top : me.bottom - pixel; + if (value !== 0) { + offset += innerDimension / (log10(end) - log10(start)) * (log10(value) - log10(start)); } - if (value !== start) { - if (start === 0) { // include zero tick - var offset = helpers.getValueOrDefault( - me.options.ticks.fontSize, - Chart.defaults.global.defaultFontSize - ); - value -= offset; - innerDimension -= offset; - start = firstTickValue; - } - value *= log10(end) - log10(start); - value /= innerDimension; - value = Math.pow(10, log10(start) + value); + pixel += sign * offset; + } + return pixel; + }, + + getValueForPixel: function(pixel) { + var me = this; + var reverse = me.options.ticks.reverse; + var log10 = helpers.log10; + var firstTickValue = me._getFirstTickValue(me.minNotZero); + var innerDimension, start, end, value; + + if (reverse) { + start = me.end; + end = me.start; + } else { + start = me.start; + end = me.end; + } + if (me.isHorizontal()) { + innerDimension = me.width; + value = reverse ? me.right - pixel : pixel - me.left; + } else { + innerDimension = me.height; + value = reverse ? pixel - me.top : me.bottom - pixel; + } + if (value !== start) { + if (start === 0) { // include zero tick + var offset = helpers.getValueOrDefault( + me.options.ticks.fontSize, + defaults.global.defaultFontSize + ); + value -= offset; + innerDimension -= offset; + start = firstTickValue; } - return value; + value *= log10(end) - log10(start); + value /= innerDimension; + value = Math.pow(10, log10(start) + value); } - }); + return value; + } +}); - scaleService.registerScaleType('logarithmic', LogarithmicScale, defaultConfig); -}; +// INTERNAL: static default options, registered in src/chart.js +module.exports._defaults = defaultConfig; diff --git a/src/scales/scale.radialLinear.js b/src/scales/scale.radialLinear.js index bc8129845ce..a407ee0295c 100644 --- a/src/scales/scale.radialLinear.js +++ b/src/scales/scale.radialLinear.js @@ -2,523 +2,530 @@ var defaults = require('../core/core.defaults'); var helpers = require('../helpers/index'); -var scaleService = require('../core/core.scaleService'); +var LinearScaleBase = require('./scale.linearbase'); var Ticks = require('../core/core.ticks'); -module.exports = function(Chart) { +var defaultConfig = { + display: true, - var defaultConfig = { - display: true, - - // Boolean - Whether to animate scaling the chart from the centre - animate: true, - position: 'chartArea', + // Boolean - Whether to animate scaling the chart from the centre + animate: true, + position: 'chartArea', - angleLines: { - display: true, - color: 'rgba(0, 0, 0, 0.1)', - lineWidth: 1, - borderDash: [], - borderDashOffset: 0.0 - }, + angleLines: { + display: true, + color: 'rgba(0, 0, 0, 0.1)', + lineWidth: 1, + borderDash: [], + borderDashOffset: 0.0 + }, - gridLines: { - circular: false - }, + gridLines: { + circular: false + }, - // label settings - ticks: { - // Boolean - Show a backdrop to the scale label - showLabelBackdrop: true, + // label settings + ticks: { + // Boolean - Show a backdrop to the scale label + showLabelBackdrop: true, - // String - The colour of the label backdrop - backdropColor: 'rgba(255,255,255,0.75)', + // String - The colour of the label backdrop + backdropColor: 'rgba(255,255,255,0.75)', - // Number - The backdrop padding above & below the label in pixels - backdropPaddingY: 2, + // Number - The backdrop padding above & below the label in pixels + backdropPaddingY: 2, - // Number - The backdrop padding to the side of the label in pixels - backdropPaddingX: 2, + // Number - The backdrop padding to the side of the label in pixels + backdropPaddingX: 2, - callback: Ticks.formatters.linear - }, + callback: Ticks.formatters.linear + }, - pointLabels: { - // Boolean - if true, show point labels - display: true, + pointLabels: { + // Boolean - if true, show point labels + display: true, - // Number - Point label font size in pixels - fontSize: 10, + // Number - Point label font size in pixels + fontSize: 10, - // Function - Used to convert point labels - callback: function(label) { - return label; - } + // Function - Used to convert point labels + callback: function(label) { + return label; } - }; - - function getValueCount(scale) { - var opts = scale.options; - return opts.angleLines.display || opts.pointLabels.display ? scale.chart.data.labels.length : 0; } +}; - function getTickBackdropHeight(opts) { - var tickOpts = opts.ticks; +function getValueCount(scale) { + var opts = scale.options; + return opts.angleLines.display || opts.pointLabels.display ? scale.chart.data.labels.length : 0; +} - if (tickOpts.display && opts.display) { - return helpers.valueOrDefault(tickOpts.fontSize, defaults.global.defaultFontSize) + tickOpts.backdropPaddingY * 2; - } - return 0; - } +function getTickBackdropHeight(opts) { + var tickOpts = opts.ticks; - function measureLabelSize(ctx, lineHeight, label) { - if (helpers.isArray(label)) { - return { - w: helpers.longestText(ctx, ctx.font, label), - h: label.length * lineHeight - }; - } + if (tickOpts.display && opts.display) { + return helpers.valueOrDefault(tickOpts.fontSize, defaults.global.defaultFontSize) + tickOpts.backdropPaddingY * 2; + } + return 0; +} +function measureLabelSize(ctx, lineHeight, label) { + if (helpers.isArray(label)) { return { - w: ctx.measureText(label).width, - h: lineHeight + w: helpers.longestText(ctx, ctx.font, label), + h: label.length * lineHeight }; } - function determineLimits(angle, pos, size, min, max) { - if (angle === min || angle === max) { - return { - start: pos - (size / 2), - end: pos + (size / 2) - }; - } else if (angle < min || angle > max) { - return { - start: pos - size, - end: pos - }; - } + return { + w: ctx.measureText(label).width, + h: lineHeight + }; +} +function determineLimits(angle, pos, size, min, max) { + if (angle === min || angle === max) { + return { + start: pos - (size / 2), + end: pos + (size / 2) + }; + } else if (angle < min || angle > max) { return { - start: pos, - end: pos + size + start: pos - size, + end: pos }; } - /** - * Helper function to fit a radial linear scale with point labels - */ - function fitWithPointLabels(scale) { - /* - * Right, this is really confusing and there is a lot of maths going on here - * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9 - * - * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif - * - * Solution: - * - * We assume the radius of the polygon is half the size of the canvas at first - * at each index we check if the text overlaps. - * - * Where it does, we store that angle and that index. - * - * After finding the largest index and angle we calculate how much we need to remove - * from the shape radius to move the point inwards by that x. - * - * We average the left and right distances to get the maximum shape radius that can fit in the box - * along with labels. - * - * Once we have that, we can find the centre point for the chart, by taking the x text protrusion - * on each side, removing that from the size, halving it and adding the left x protrusion width. - * - * This will mean we have a shape fitted to the canvas, as large as it can be with the labels - * and position it in the most space efficient manner - * - * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif - */ - - var plFont = helpers.options._parseFont(scale.options.pointLabels); - - // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. - // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points - var furthestLimits = { - l: 0, - r: scale.width, - t: 0, - b: scale.height - }; - var furthestAngles = {}; - var i, textSize, pointPosition; + return { + start: pos, + end: pos + size + }; +} + +/** + * Helper function to fit a radial linear scale with point labels + */ +function fitWithPointLabels(scale) { + + // Right, this is really confusing and there is a lot of maths going on here + // The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9 + // + // Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif + // + // Solution: + // + // We assume the radius of the polygon is half the size of the canvas at first + // at each index we check if the text overlaps. + // + // Where it does, we store that angle and that index. + // + // After finding the largest index and angle we calculate how much we need to remove + // from the shape radius to move the point inwards by that x. + // + // We average the left and right distances to get the maximum shape radius that can fit in the box + // along with labels. + // + // Once we have that, we can find the centre point for the chart, by taking the x text protrusion + // on each side, removing that from the size, halving it and adding the left x protrusion width. + // + // This will mean we have a shape fitted to the canvas, as large as it can be with the labels + // and position it in the most space efficient manner + // + // https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif + + var plFont = helpers.options._parseFont(scale.options.pointLabels); + + // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. + // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points + var furthestLimits = { + l: 0, + r: scale.width, + t: 0, + b: scale.height + }; + var furthestAngles = {}; + var i, textSize, pointPosition; + + scale.ctx.font = plFont.string; + scale._pointLabelSizes = []; + + var valueCount = getValueCount(scale); + for (i = 0; i < valueCount; i++) { + pointPosition = scale.getPointPosition(i, scale.drawingArea + 5); + textSize = measureLabelSize(scale.ctx, plFont.lineHeight, scale.pointLabels[i] || ''); + scale._pointLabelSizes[i] = textSize; + + // Add quarter circle to make degree 0 mean top of circle + var angleRadians = scale.getIndexAngle(i); + var angle = helpers.toDegrees(angleRadians) % 360; + var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180); + var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270); + + if (hLimits.start < furthestLimits.l) { + furthestLimits.l = hLimits.start; + furthestAngles.l = angleRadians; + } - scale.ctx.font = plFont.string; - scale._pointLabelSizes = []; + if (hLimits.end > furthestLimits.r) { + furthestLimits.r = hLimits.end; + furthestAngles.r = angleRadians; + } - var valueCount = getValueCount(scale); - for (i = 0; i < valueCount; i++) { - pointPosition = scale.getPointPosition(i, scale.drawingArea + 5); - textSize = measureLabelSize(scale.ctx, plFont.lineHeight, scale.pointLabels[i] || ''); - scale._pointLabelSizes[i] = textSize; + if (vLimits.start < furthestLimits.t) { + furthestLimits.t = vLimits.start; + furthestAngles.t = angleRadians; + } - // Add quarter circle to make degree 0 mean top of circle - var angleRadians = scale.getIndexAngle(i); - var angle = helpers.toDegrees(angleRadians) % 360; - var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180); - var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270); + if (vLimits.end > furthestLimits.b) { + furthestLimits.b = vLimits.end; + furthestAngles.b = angleRadians; + } + } - if (hLimits.start < furthestLimits.l) { - furthestLimits.l = hLimits.start; - furthestAngles.l = angleRadians; - } + scale.setReductions(scale.drawingArea, furthestLimits, furthestAngles); +} - if (hLimits.end > furthestLimits.r) { - furthestLimits.r = hLimits.end; - furthestAngles.r = angleRadians; - } +function getTextAlignForAngle(angle) { + if (angle === 0 || angle === 180) { + return 'center'; + } else if (angle < 180) { + return 'left'; + } - if (vLimits.start < furthestLimits.t) { - furthestLimits.t = vLimits.start; - furthestAngles.t = angleRadians; - } + return 'right'; +} - if (vLimits.end > furthestLimits.b) { - furthestLimits.b = vLimits.end; - furthestAngles.b = angleRadians; - } +function fillText(ctx, text, position, lineHeight) { + var y = position.y + lineHeight / 2; + var i, ilen; + + if (helpers.isArray(text)) { + for (i = 0, ilen = text.length; i < ilen; ++i) { + ctx.fillText(text[i], position.x, y); + y += lineHeight; } + } else { + ctx.fillText(text, position.x, y); + } +} - scale.setReductions(scale.drawingArea, furthestLimits, furthestAngles); +function adjustPointPositionForLabelHeight(angle, textSize, position) { + if (angle === 90 || angle === 270) { + position.y -= (textSize.h / 2); + } else if (angle > 270 || angle < 90) { + position.y -= textSize.h; + } +} + +function drawPointLabels(scale) { + var ctx = scale.ctx; + var opts = scale.options; + var angleLineOpts = opts.angleLines; + var gridLineOpts = opts.gridLines; + var pointLabelOpts = opts.pointLabels; + var lineWidth = helpers.valueOrDefault(angleLineOpts.lineWidth, gridLineOpts.lineWidth); + var lineColor = helpers.valueOrDefault(angleLineOpts.color, gridLineOpts.color); + var tickBackdropHeight = getTickBackdropHeight(opts); + + ctx.save(); + ctx.lineWidth = lineWidth; + ctx.strokeStyle = lineColor; + if (ctx.setLineDash) { + ctx.setLineDash(helpers.valueOrDefault(angleLineOpts.borderDash, gridLineOpts.borderDash) || []); + ctx.lineDashOffset = helpers.valueOrDefault(angleLineOpts.borderDashOffset, gridLineOpts.borderDashOffset) || 0.0; } - function getTextAlignForAngle(angle) { - if (angle === 0 || angle === 180) { - return 'center'; - } else if (angle < 180) { - return 'left'; + var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max); + + // Point Label Font + var plFont = helpers.options._parseFont(pointLabelOpts); + + ctx.font = plFont.string; + ctx.textBaseline = 'middle'; + + for (var i = getValueCount(scale) - 1; i >= 0; i--) { + if (angleLineOpts.display && lineWidth && lineColor) { + var outerPosition = scale.getPointPosition(i, outerDistance); + ctx.beginPath(); + ctx.moveTo(scale.xCenter, scale.yCenter); + ctx.lineTo(outerPosition.x, outerPosition.y); + ctx.stroke(); } - return 'right'; - } + if (pointLabelOpts.display) { + // Extra pixels out for some label spacing + var extra = (i === 0 ? tickBackdropHeight / 2 : 0); + var pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + 5); - function fillText(ctx, text, position, lineHeight) { - var y = position.y + lineHeight / 2; - var i, ilen; + // Keep this in loop since we may support array properties here + var pointLabelFontColor = helpers.valueAtIndexOrDefault(pointLabelOpts.fontColor, i, defaults.global.defaultFontColor); + ctx.fillStyle = pointLabelFontColor; - if (helpers.isArray(text)) { - for (i = 0, ilen = text.length; i < ilen; ++i) { - ctx.fillText(text[i], position.x, y); - y += lineHeight; - } - } else { - ctx.fillText(text, position.x, y); + var angleRadians = scale.getIndexAngle(i); + var angle = helpers.toDegrees(angleRadians); + ctx.textAlign = getTextAlignForAngle(angle); + adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition); + fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.lineHeight); } } + ctx.restore(); +} + +function drawRadiusLine(scale, gridLineOpts, radius, index) { + var ctx = scale.ctx; + var circular = gridLineOpts.circular; + var valueCount = getValueCount(scale); + var lineColor = helpers.valueAtIndexOrDefault(gridLineOpts.color, index - 1); + var lineWidth = helpers.valueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1); + var pointPosition; + + if ((!circular && !valueCount) || !lineColor || !lineWidth) { + return; + } - function adjustPointPositionForLabelHeight(angle, textSize, position) { - if (angle === 90 || angle === 270) { - position.y -= (textSize.h / 2); - } else if (angle > 270 || angle < 90) { - position.y -= textSize.h; - } + ctx.save(); + ctx.strokeStyle = lineColor; + ctx.lineWidth = lineWidth; + if (ctx.setLineDash) { + ctx.setLineDash(gridLineOpts.borderDash || []); + ctx.lineDashOffset = gridLineOpts.borderDashOffset || 0.0; } - function drawPointLabels(scale) { - var ctx = scale.ctx; - var opts = scale.options; - var angleLineOpts = opts.angleLines; - var gridLineOpts = opts.gridLines; - var pointLabelOpts = opts.pointLabels; - var lineWidth = helpers.valueOrDefault(angleLineOpts.lineWidth, gridLineOpts.lineWidth); - var lineColor = helpers.valueOrDefault(angleLineOpts.color, gridLineOpts.color); - var tickBackdropHeight = getTickBackdropHeight(opts); - - ctx.save(); - ctx.lineWidth = lineWidth; - ctx.strokeStyle = lineColor; - if (ctx.setLineDash) { - ctx.setLineDash(helpers.valueOrDefault(angleLineOpts.borderDash, gridLineOpts.borderDash) || []); - ctx.lineDashOffset = helpers.valueOrDefault(angleLineOpts.borderDashOffset, gridLineOpts.borderDashOffset) || 0.0; + ctx.beginPath(); + if (circular) { + // Draw circular arcs between the points + ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2); + } else { + // Draw straight lines connecting each index + pointPosition = scale.getPointPosition(0, radius); + ctx.moveTo(pointPosition.x, pointPosition.y); + + for (var i = 1; i < valueCount; i++) { + pointPosition = scale.getPointPosition(i, radius); + ctx.lineTo(pointPosition.x, pointPosition.y); } + } + ctx.closePath(); + ctx.stroke(); + ctx.restore(); +} + +function numberOrZero(param) { + return helpers.isNumber(param) ? param : 0; +} + +module.exports = LinearScaleBase.extend({ + setDimensions: function() { + var me = this; + + // Set the unconstrained dimension before label rotation + me.width = me.maxWidth; + me.height = me.maxHeight; + me.paddingTop = getTickBackdropHeight(me.options) / 2; + me.xCenter = Math.floor(me.width / 2); + me.yCenter = Math.floor((me.height - me.paddingTop) / 2); + me.drawingArea = Math.min(me.height - me.paddingTop, me.width) / 2; + }, + + determineDataLimits: function() { + var me = this; + var chart = me.chart; + var min = Number.POSITIVE_INFINITY; + var max = Number.NEGATIVE_INFINITY; + + helpers.each(chart.data.datasets, function(dataset, datasetIndex) { + if (chart.isDatasetVisible(datasetIndex)) { + var meta = chart.getDatasetMeta(datasetIndex); + + helpers.each(dataset.data, function(rawValue, index) { + var value = +me.getRightValue(rawValue); + if (isNaN(value) || meta.data[index].hidden) { + return; + } - var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max); + min = Math.min(value, min); + max = Math.max(value, max); + }); + } + }); - // Point Label Font - var plFont = helpers.options._parseFont(pointLabelOpts); + me.min = (min === Number.POSITIVE_INFINITY ? 0 : min); + me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max); - ctx.font = plFont.string; - ctx.textBaseline = 'middle'; + // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero + me.handleTickRangeOptions(); + }, - for (var i = getValueCount(scale) - 1; i >= 0; i--) { - if (angleLineOpts.display && lineWidth && lineColor) { - var outerPosition = scale.getPointPosition(i, outerDistance); - ctx.beginPath(); - ctx.moveTo(scale.xCenter, scale.yCenter); - ctx.lineTo(outerPosition.x, outerPosition.y); - ctx.stroke(); - } + // Returns the maximum number of ticks based on the scale dimension + _computeTickLimit: function() { + return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options)); + }, - if (pointLabelOpts.display) { - // Extra pixels out for some label spacing - var extra = (i === 0 ? tickBackdropHeight / 2 : 0); - var pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + 5); + convertTicksToLabels: function() { + var me = this; - // Keep this in loop since we may support array properties here - var pointLabelFontColor = helpers.valueAtIndexOrDefault(pointLabelOpts.fontColor, i, defaults.global.defaultFontColor); - ctx.fillStyle = pointLabelFontColor; + LinearScaleBase.prototype.convertTicksToLabels.call(me); - var angleRadians = scale.getIndexAngle(i); - var angle = helpers.toDegrees(angleRadians); - ctx.textAlign = getTextAlignForAngle(angle); - adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition); - fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.lineHeight); - } - } - ctx.restore(); - } + // Point labels + me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me); + }, - function drawRadiusLine(scale, gridLineOpts, radius, index) { - var ctx = scale.ctx; - var circular = gridLineOpts.circular; - var valueCount = getValueCount(scale); - var lineColor = helpers.valueAtIndexOrDefault(gridLineOpts.color, index - 1); - var lineWidth = helpers.valueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1); - var pointPosition; + getLabelForIndex: function(index, datasetIndex) { + return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); + }, - if ((!circular && !valueCount) || !lineColor || !lineWidth) { - return; - } - - ctx.save(); - ctx.strokeStyle = lineColor; - ctx.lineWidth = lineWidth; - if (ctx.setLineDash) { - ctx.setLineDash(gridLineOpts.borderDash || []); - ctx.lineDashOffset = gridLineOpts.borderDashOffset || 0.0; - } + fit: function() { + var me = this; + var opts = me.options; - ctx.beginPath(); - if (circular) { - // Draw circular arcs between the points - ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2); + if (opts.display && opts.pointLabels.display) { + fitWithPointLabels(me); } else { - // Draw straight lines connecting each index - pointPosition = scale.getPointPosition(0, radius); - ctx.moveTo(pointPosition.x, pointPosition.y); + me.setCenterPoint(0, 0, 0, 0); + } + }, - for (var i = 1; i < valueCount; i++) { - pointPosition = scale.getPointPosition(i, radius); - ctx.lineTo(pointPosition.x, pointPosition.y); - } + /** + * Set radius reductions and determine new radius and center point + * @private + */ + setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) { + var me = this; + var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l); + var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r); + var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t); + var radiusReductionBottom = -Math.max(furthestLimits.b - (me.height - me.paddingTop), 0) / Math.cos(furthestAngles.b); + + radiusReductionLeft = numberOrZero(radiusReductionLeft); + radiusReductionRight = numberOrZero(radiusReductionRight); + radiusReductionTop = numberOrZero(radiusReductionTop); + radiusReductionBottom = numberOrZero(radiusReductionBottom); + + me.drawingArea = Math.min( + Math.floor(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2), + Math.floor(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2)); + me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom); + }, + + setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) { + var me = this; + var maxRight = me.width - rightMovement - me.drawingArea; + var maxLeft = leftMovement + me.drawingArea; + var maxTop = topMovement + me.drawingArea; + var maxBottom = (me.height - me.paddingTop) - bottomMovement - me.drawingArea; + + me.xCenter = Math.floor(((maxLeft + maxRight) / 2) + me.left); + me.yCenter = Math.floor(((maxTop + maxBottom) / 2) + me.top + me.paddingTop); + }, + + getIndexAngle: function(index) { + var angleMultiplier = (Math.PI * 2) / getValueCount(this); + var startAngle = this.chart.options && this.chart.options.startAngle ? + this.chart.options.startAngle : + 0; + + var startAngleRadians = startAngle * Math.PI * 2 / 360; + + // Start from the top instead of right, so remove a quarter of the circle + return index * angleMultiplier + startAngleRadians; + }, + + getDistanceFromCenterForValue: function(value) { + var me = this; + + if (value === null) { + return 0; // null always in center } - ctx.closePath(); - ctx.stroke(); - ctx.restore(); - } - function numberOrZero(param) { - return helpers.isNumber(param) ? param : 0; - } + // Take into account half font size + the yPadding of the top value + var scalingFactor = me.drawingArea / (me.max - me.min); + if (me.options.ticks.reverse) { + return (me.max - value) * scalingFactor; + } + return (value - me.min) * scalingFactor; + }, - var LinearRadialScale = Chart.LinearScaleBase.extend({ - setDimensions: function() { - var me = this; - - // Set the unconstrained dimension before label rotation - me.width = me.maxWidth; - me.height = me.maxHeight; - me.paddingTop = getTickBackdropHeight(me.options) / 2; - me.xCenter = Math.floor(me.width / 2); - me.yCenter = Math.floor((me.height - me.paddingTop) / 2); - me.drawingArea = Math.min(me.height - me.paddingTop, me.width) / 2; - }, - determineDataLimits: function() { - var me = this; - var chart = me.chart; - var min = Number.POSITIVE_INFINITY; - var max = Number.NEGATIVE_INFINITY; - - helpers.each(chart.data.datasets, function(dataset, datasetIndex) { - if (chart.isDatasetVisible(datasetIndex)) { - var meta = chart.getDatasetMeta(datasetIndex); - - helpers.each(dataset.data, function(rawValue, index) { - var value = +me.getRightValue(rawValue); - if (isNaN(value) || meta.data[index].hidden) { - return; - } + getPointPosition: function(index, distanceFromCenter) { + var me = this; + var thisAngle = me.getIndexAngle(index) - (Math.PI / 2); + return { + x: Math.cos(thisAngle) * distanceFromCenter + me.xCenter, + y: Math.sin(thisAngle) * distanceFromCenter + me.yCenter + }; + }, + + getPointPositionForValue: function(index, value) { + return this.getPointPosition(index, this.getDistanceFromCenterForValue(value)); + }, + + getBasePosition: function() { + var me = this; + var min = me.min; + var max = me.max; + + return me.getPointPositionForValue(0, + me.beginAtZero ? 0 : + min < 0 && max < 0 ? max : + min > 0 && max > 0 ? min : + 0); + }, + + draw: function() { + var me = this; + var opts = me.options; + var gridLineOpts = opts.gridLines; + var tickOpts = opts.ticks; + var valueOrDefault = helpers.valueOrDefault; - min = Math.min(value, min); - max = Math.max(value, max); - }); - } - }); + if (opts.display) { + var ctx = me.ctx; + var startAngle = this.getIndexAngle(0); + var tickFont = helpers.options._parseFont(tickOpts); - me.min = (min === Number.POSITIVE_INFINITY ? 0 : min); - me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max); - - // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero - me.handleTickRangeOptions(); - }, - // Returns the maximum number of ticks based on the scale dimension - _computeTickLimit: function() { - return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options)); - }, - convertTicksToLabels: function() { - var me = this; - - Chart.LinearScaleBase.prototype.convertTicksToLabels.call(me); - - // Point labels - me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me); - }, - getLabelForIndex: function(index, datasetIndex) { - return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); - }, - fit: function() { - var me = this; - var opts = me.options; - - if (opts.display && opts.pointLabels.display) { - fitWithPointLabels(me); - } else { - me.setCenterPoint(0, 0, 0, 0); - } - }, - /** - * Set radius reductions and determine new radius and center point - * @private - */ - setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) { - var me = this; - var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l); - var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r); - var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t); - var radiusReductionBottom = -Math.max(furthestLimits.b - (me.height - me.paddingTop), 0) / Math.cos(furthestAngles.b); - - radiusReductionLeft = numberOrZero(radiusReductionLeft); - radiusReductionRight = numberOrZero(radiusReductionRight); - radiusReductionTop = numberOrZero(radiusReductionTop); - radiusReductionBottom = numberOrZero(radiusReductionBottom); - - me.drawingArea = Math.min( - Math.floor(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2), - Math.floor(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2)); - me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom); - }, - setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) { - var me = this; - var maxRight = me.width - rightMovement - me.drawingArea; - var maxLeft = leftMovement + me.drawingArea; - var maxTop = topMovement + me.drawingArea; - var maxBottom = (me.height - me.paddingTop) - bottomMovement - me.drawingArea; - - me.xCenter = Math.floor(((maxLeft + maxRight) / 2) + me.left); - me.yCenter = Math.floor(((maxTop + maxBottom) / 2) + me.top + me.paddingTop); - }, - - getIndexAngle: function(index) { - var angleMultiplier = (Math.PI * 2) / getValueCount(this); - var startAngle = this.chart.options && this.chart.options.startAngle ? - this.chart.options.startAngle : - 0; - - var startAngleRadians = startAngle * Math.PI * 2 / 360; - - // Start from the top instead of right, so remove a quarter of the circle - return index * angleMultiplier + startAngleRadians; - }, - getDistanceFromCenterForValue: function(value) { - var me = this; - - if (value === null) { - return 0; // null always in center + if (opts.angleLines.display || opts.pointLabels.display) { + drawPointLabels(me); } - // Take into account half font size + the yPadding of the top value - var scalingFactor = me.drawingArea / (me.max - me.min); - if (me.options.ticks.reverse) { - return (me.max - value) * scalingFactor; - } - return (value - me.min) * scalingFactor; - }, - getPointPosition: function(index, distanceFromCenter) { - var me = this; - var thisAngle = me.getIndexAngle(index) - (Math.PI / 2); - return { - x: Math.cos(thisAngle) * distanceFromCenter + me.xCenter, - y: Math.sin(thisAngle) * distanceFromCenter + me.yCenter - }; - }, - getPointPositionForValue: function(index, value) { - return this.getPointPosition(index, this.getDistanceFromCenterForValue(value)); - }, - - getBasePosition: function() { - var me = this; - var min = me.min; - var max = me.max; - - return me.getPointPositionForValue(0, - me.beginAtZero ? 0 : - min < 0 && max < 0 ? max : - min > 0 && max > 0 ? min : - 0); - }, - - draw: function() { - var me = this; - var opts = me.options; - var gridLineOpts = opts.gridLines; - var tickOpts = opts.ticks; - var valueOrDefault = helpers.valueOrDefault; - - if (opts.display) { - var ctx = me.ctx; - var startAngle = this.getIndexAngle(0); - var tickFont = helpers.options._parseFont(tickOpts); - - if (opts.angleLines.display || opts.pointLabels.display) { - drawPointLabels(me); - } + helpers.each(me.ticks, function(label, index) { + // Don't draw a centre value (if it is minimum) + if (index > 0 || tickOpts.reverse) { + var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]); - helpers.each(me.ticks, function(label, index) { - // Don't draw a centre value (if it is minimum) - if (index > 0 || tickOpts.reverse) { - var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]); + // Draw circular lines around the scale + if (gridLineOpts.display && index !== 0) { + drawRadiusLine(me, gridLineOpts, yCenterOffset, index); + } - // Draw circular lines around the scale - if (gridLineOpts.display && index !== 0) { - drawRadiusLine(me, gridLineOpts, yCenterOffset, index); + if (tickOpts.display) { + var tickFontColor = valueOrDefault(tickOpts.fontColor, defaults.global.defaultFontColor); + ctx.font = tickFont.string; + + ctx.save(); + ctx.translate(me.xCenter, me.yCenter); + ctx.rotate(startAngle); + + if (tickOpts.showLabelBackdrop) { + var labelWidth = ctx.measureText(label).width; + ctx.fillStyle = tickOpts.backdropColor; + ctx.fillRect( + -labelWidth / 2 - tickOpts.backdropPaddingX, + -yCenterOffset - tickFont.size / 2 - tickOpts.backdropPaddingY, + labelWidth + tickOpts.backdropPaddingX * 2, + tickFont.size + tickOpts.backdropPaddingY * 2 + ); } - if (tickOpts.display) { - var tickFontColor = valueOrDefault(tickOpts.fontColor, defaults.global.defaultFontColor); - ctx.font = tickFont.string; - - ctx.save(); - ctx.translate(me.xCenter, me.yCenter); - ctx.rotate(startAngle); - - if (tickOpts.showLabelBackdrop) { - var labelWidth = ctx.measureText(label).width; - ctx.fillStyle = tickOpts.backdropColor; - ctx.fillRect( - -labelWidth / 2 - tickOpts.backdropPaddingX, - -yCenterOffset - tickFont.size / 2 - tickOpts.backdropPaddingY, - labelWidth + tickOpts.backdropPaddingX * 2, - tickFont.size + tickOpts.backdropPaddingY * 2 - ); - } - - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - ctx.fillStyle = tickFontColor; - ctx.fillText(label, 0, -yCenterOffset); - ctx.restore(); - } + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillStyle = tickFontColor; + ctx.fillText(label, 0, -yCenterOffset); + ctx.restore(); } - }); - } + } + }); } - }); + } +}); - scaleService.registerScaleType('radialLinear', LinearRadialScale, defaultConfig); -}; +// INTERNAL: static default options, registered in src/chart.js +module.exports._defaults = defaultConfig; diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index 3c698f57db0..c7c49eb6b1b 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -5,7 +5,6 @@ var moment = require('moment'); var defaults = require('../core/core.defaults'); var helpers = require('../helpers/index'); var Scale = require('../core/core.scale'); -var scaleService = require('../core/core.scaleService'); // Integer constants are from the ES6 spec. var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991; @@ -426,369 +425,367 @@ function determineLabelFormat(data, timeOpts) { return 'MMM D, YYYY'; } -module.exports = function() { - - var defaultConfig = { - position: 'bottom', +var defaultConfig = { + position: 'bottom', + + /** + * Data distribution along the scale: + * - 'linear': data are spread according to their time (distances can vary), + * - 'series': data are spread at the same distance from each other. + * @see https://github.com/chartjs/Chart.js/pull/4507 + * @since 2.7.0 + */ + distribution: 'linear', + + /** + * Scale boundary strategy (bypassed by min/max time options) + * - `data`: make sure data are fully visible, ticks outside are removed + * - `ticks`: make sure ticks are fully visible, data outside are truncated + * @see https://github.com/chartjs/Chart.js/pull/4556 + * @since 2.7.0 + */ + bounds: 'data', + + time: { + parser: false, // false == a pattern string from https://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment + format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from https://momentjs.com/docs/#/parsing/string-format/ + unit: false, // false == automatic or override with week, month, year, etc. + round: false, // none, or override with week, month, year, etc. + displayFormat: false, // DEPRECATED + isoWeekday: false, // override week start day - see https://momentjs.com/docs/#/get-set/iso-weekday/ + minUnit: 'millisecond', + + // defaults to unit's corresponding unitFormat below or override using pattern string from https://momentjs.com/docs/#/displaying/format/ + displayFormats: { + millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM, + second: 'h:mm:ss a', // 11:20:01 AM + minute: 'h:mm a', // 11:20 AM + hour: 'hA', // 5PM + day: 'MMM D', // Sep 4 + week: 'll', // Week 46, or maybe "[W]WW - YYYY" ? + month: 'MMM YYYY', // Sept 2015 + quarter: '[Q]Q - YYYY', // Q3 + year: 'YYYY' // 2015 + }, + }, + ticks: { + autoSkip: false, /** - * Data distribution along the scale: - * - 'linear': data are spread according to their time (distances can vary), - * - 'series': data are spread at the same distance from each other. + * Ticks generation input values: + * - 'auto': generates "optimal" ticks based on scale size and time options. + * - 'data': generates ticks from data (including labels from data {t|x|y} objects). + * - 'labels': generates ticks from user given `data.labels` values ONLY. * @see https://github.com/chartjs/Chart.js/pull/4507 * @since 2.7.0 */ - distribution: 'linear', + source: 'auto', - /** - * Scale boundary strategy (bypassed by min/max time options) - * - `data`: make sure data are fully visible, ticks outside are removed - * - `ticks`: make sure ticks are fully visible, data outside are truncated - * @see https://github.com/chartjs/Chart.js/pull/4556 - * @since 2.7.0 - */ - bounds: 'data', - - time: { - parser: false, // false == a pattern string from https://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment - format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from https://momentjs.com/docs/#/parsing/string-format/ - unit: false, // false == automatic or override with week, month, year, etc. - round: false, // none, or override with week, month, year, etc. - displayFormat: false, // DEPRECATED - isoWeekday: false, // override week start day - see https://momentjs.com/docs/#/get-set/iso-weekday/ - minUnit: 'millisecond', - - // defaults to unit's corresponding unitFormat below or override using pattern string from https://momentjs.com/docs/#/displaying/format/ - displayFormats: { - millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM, - second: 'h:mm:ss a', // 11:20:01 AM - minute: 'h:mm a', // 11:20 AM - hour: 'hA', // 5PM - day: 'MMM D', // Sep 4 - week: 'll', // Week 46, or maybe "[W]WW - YYYY" ? - month: 'MMM YYYY', // Sept 2015 - quarter: '[Q]Q - YYYY', // Q3 - year: 'YYYY' // 2015 - }, - }, - ticks: { - autoSkip: false, - - /** - * Ticks generation input values: - * - 'auto': generates "optimal" ticks based on scale size and time options. - * - 'data': generates ticks from data (including labels from data {t|x|y} objects). - * - 'labels': generates ticks from user given `data.labels` values ONLY. - * @see https://github.com/chartjs/Chart.js/pull/4507 - * @since 2.7.0 - */ - source: 'auto', - - major: { - enabled: false - } + major: { + enabled: false } - }; + } +}; - var TimeScale = Scale.extend({ - initialize: function() { - if (!moment) { - throw new Error('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com'); - } +module.exports = Scale.extend({ + initialize: function() { + if (!moment) { + throw new Error('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com'); + } - this.mergeTicksOptions(); + this.mergeTicksOptions(); - Scale.prototype.initialize.call(this); - }, + Scale.prototype.initialize.call(this); + }, - update: function() { - var me = this; - var options = me.options; + update: function() { + var me = this; + var options = me.options; - // DEPRECATIONS: output a message only one time per update - if (options.time && options.time.format) { - console.warn('options.time.format is deprecated and replaced by options.time.parser.'); - } + // DEPRECATIONS: output a message only one time per update + if (options.time && options.time.format) { + console.warn('options.time.format is deprecated and replaced by options.time.parser.'); + } - return Scale.prototype.update.apply(me, arguments); - }, + return Scale.prototype.update.apply(me, arguments); + }, - /** - * Allows data to be referenced via 't' attribute - */ - getRightValue: function(rawValue) { - if (rawValue && rawValue.t !== undefined) { - rawValue = rawValue.t; - } - return Scale.prototype.getRightValue.call(this, rawValue); - }, + /** + * Allows data to be referenced via 't' attribute + */ + getRightValue: function(rawValue) { + if (rawValue && rawValue.t !== undefined) { + rawValue = rawValue.t; + } + return Scale.prototype.getRightValue.call(this, rawValue); + }, - determineDataLimits: function() { - var me = this; - var chart = me.chart; - var timeOpts = me.options.time; - var unit = timeOpts.unit || 'day'; - var min = MAX_INTEGER; - var max = MIN_INTEGER; - var timestamps = []; - var datasets = []; - var labels = []; - var i, j, ilen, jlen, data, timestamp; - var dataLabels = chart.data.labels || []; - - // Convert labels to timestamps - for (i = 0, ilen = dataLabels.length; i < ilen; ++i) { - labels.push(parse(dataLabels[i], me)); - } + determineDataLimits: function() { + var me = this; + var chart = me.chart; + var timeOpts = me.options.time; + var unit = timeOpts.unit || 'day'; + var min = MAX_INTEGER; + var max = MIN_INTEGER; + var timestamps = []; + var datasets = []; + var labels = []; + var i, j, ilen, jlen, data, timestamp; + var dataLabels = chart.data.labels || []; + + // Convert labels to timestamps + for (i = 0, ilen = dataLabels.length; i < ilen; ++i) { + labels.push(parse(dataLabels[i], me)); + } + + // Convert data to timestamps + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + if (chart.isDatasetVisible(i)) { + data = chart.data.datasets[i].data; + + // Let's consider that all data have the same format. + if (helpers.isObject(data[0])) { + datasets[i] = []; - // Convert data to timestamps - for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { - if (chart.isDatasetVisible(i)) { - data = chart.data.datasets[i].data; - - // Let's consider that all data have the same format. - if (helpers.isObject(data[0])) { - datasets[i] = []; - - for (j = 0, jlen = data.length; j < jlen; ++j) { - timestamp = parse(data[j], me); - timestamps.push(timestamp); - datasets[i][j] = timestamp; - } - } else { - for (j = 0, jlen = labels.length; j < jlen; ++j) { - timestamps.push(labels[j]); - } - datasets[i] = labels.slice(0); + for (j = 0, jlen = data.length; j < jlen; ++j) { + timestamp = parse(data[j], me); + timestamps.push(timestamp); + datasets[i][j] = timestamp; } } else { - datasets[i] = []; + for (j = 0, jlen = labels.length; j < jlen; ++j) { + timestamps.push(labels[j]); + } + datasets[i] = labels.slice(0); } + } else { + datasets[i] = []; } + } - if (labels.length) { - // Sort labels **after** data have been converted - labels = arrayUnique(labels).sort(sorter); - min = Math.min(min, labels[0]); - max = Math.max(max, labels[labels.length - 1]); - } + if (labels.length) { + // Sort labels **after** data have been converted + labels = arrayUnique(labels).sort(sorter); + min = Math.min(min, labels[0]); + max = Math.max(max, labels[labels.length - 1]); + } - if (timestamps.length) { - timestamps = arrayUnique(timestamps).sort(sorter); - min = Math.min(min, timestamps[0]); - max = Math.max(max, timestamps[timestamps.length - 1]); - } + if (timestamps.length) { + timestamps = arrayUnique(timestamps).sort(sorter); + min = Math.min(min, timestamps[0]); + max = Math.max(max, timestamps[timestamps.length - 1]); + } - min = parse(timeOpts.min, me) || min; - max = parse(timeOpts.max, me) || max; - - // In case there is no valid min/max, set limits based on unit time option - min = min === MAX_INTEGER ? +moment().startOf(unit) : min; - max = max === MIN_INTEGER ? +moment().endOf(unit) + 1 : max; - - // Make sure that max is strictly higher than min (required by the lookup table) - me.min = Math.min(min, max); - me.max = Math.max(min + 1, max); - - // PRIVATE - me._horizontal = me.isHorizontal(); - me._table = []; - me._timestamps = { - data: timestamps, - datasets: datasets, - labels: labels - }; - }, + min = parse(timeOpts.min, me) || min; + max = parse(timeOpts.max, me) || max; + + // In case there is no valid min/max, set limits based on unit time option + min = min === MAX_INTEGER ? +moment().startOf(unit) : min; + max = max === MIN_INTEGER ? +moment().endOf(unit) + 1 : max; + + // Make sure that max is strictly higher than min (required by the lookup table) + me.min = Math.min(min, max); + me.max = Math.max(min + 1, max); + + // PRIVATE + me._horizontal = me.isHorizontal(); + me._table = []; + me._timestamps = { + data: timestamps, + datasets: datasets, + labels: labels + }; + }, - buildTicks: function() { - var me = this; - var min = me.min; - var max = me.max; - var options = me.options; - var timeOpts = options.time; - var timestamps = []; - var ticks = []; - var i, ilen, timestamp; - - switch (options.ticks.source) { - case 'data': - timestamps = me._timestamps.data; - break; - case 'labels': - timestamps = me._timestamps.labels; - break; - case 'auto': - default: - timestamps = generate(min, max, me.getLabelCapacity(min), options); - } + buildTicks: function() { + var me = this; + var min = me.min; + var max = me.max; + var options = me.options; + var timeOpts = options.time; + var timestamps = []; + var ticks = []; + var i, ilen, timestamp; + + switch (options.ticks.source) { + case 'data': + timestamps = me._timestamps.data; + break; + case 'labels': + timestamps = me._timestamps.labels; + break; + case 'auto': + default: + timestamps = generate(min, max, me.getLabelCapacity(min), options); + } - if (options.bounds === 'ticks' && timestamps.length) { - min = timestamps[0]; - max = timestamps[timestamps.length - 1]; - } + if (options.bounds === 'ticks' && timestamps.length) { + min = timestamps[0]; + max = timestamps[timestamps.length - 1]; + } - // Enforce limits with user min/max options - min = parse(timeOpts.min, me) || min; - max = parse(timeOpts.max, me) || max; + // Enforce limits with user min/max options + min = parse(timeOpts.min, me) || min; + max = parse(timeOpts.max, me) || max; - // Remove ticks outside the min/max range - for (i = 0, ilen = timestamps.length; i < ilen; ++i) { - timestamp = timestamps[i]; - if (timestamp >= min && timestamp <= max) { - ticks.push(timestamp); - } + // Remove ticks outside the min/max range + for (i = 0, ilen = timestamps.length; i < ilen; ++i) { + timestamp = timestamps[i]; + if (timestamp >= min && timestamp <= max) { + ticks.push(timestamp); } + } - me.min = min; - me.max = max; + me.min = min; + me.max = max; - // PRIVATE - me._unit = timeOpts.unit || determineUnitForFormatting(ticks, timeOpts.minUnit, me.min, me.max); - me._majorUnit = determineMajorUnit(me._unit); - me._table = buildLookupTable(me._timestamps.data, min, max, options.distribution); - me._offsets = computeOffsets(me._table, ticks, min, max, options); - me._labelFormat = determineLabelFormat(me._timestamps.data, timeOpts); + // PRIVATE + me._unit = timeOpts.unit || determineUnitForFormatting(ticks, timeOpts.minUnit, me.min, me.max); + me._majorUnit = determineMajorUnit(me._unit); + me._table = buildLookupTable(me._timestamps.data, min, max, options.distribution); + me._offsets = computeOffsets(me._table, ticks, min, max, options); + me._labelFormat = determineLabelFormat(me._timestamps.data, timeOpts); - if (options.ticks.reverse) { - ticks.reverse(); - } + if (options.ticks.reverse) { + ticks.reverse(); + } - return ticksFromTimestamps(ticks, me._majorUnit); - }, + return ticksFromTimestamps(ticks, me._majorUnit); + }, - getLabelForIndex: function(index, datasetIndex) { - var me = this; - var data = me.chart.data; - var timeOpts = me.options.time; - var label = data.labels && index < data.labels.length ? data.labels[index] : ''; - var value = data.datasets[datasetIndex].data[index]; + getLabelForIndex: function(index, datasetIndex) { + var me = this; + var data = me.chart.data; + var timeOpts = me.options.time; + var label = data.labels && index < data.labels.length ? data.labels[index] : ''; + var value = data.datasets[datasetIndex].data[index]; - if (helpers.isObject(value)) { - label = me.getRightValue(value); - } - if (timeOpts.tooltipFormat) { - return momentify(label, timeOpts).format(timeOpts.tooltipFormat); - } - if (typeof label === 'string') { - return label; - } + if (helpers.isObject(value)) { + label = me.getRightValue(value); + } + if (timeOpts.tooltipFormat) { + return momentify(label, timeOpts).format(timeOpts.tooltipFormat); + } + if (typeof label === 'string') { + return label; + } - return momentify(label, timeOpts).format(me._labelFormat); - }, + return momentify(label, timeOpts).format(me._labelFormat); + }, - /** - * Function to format an individual tick mark - * @private - */ - tickFormatFunction: function(tick, index, ticks, formatOverride) { - var me = this; - var options = me.options; - var time = tick.valueOf(); - var formats = options.time.displayFormats; - var minorFormat = formats[me._unit]; - var majorUnit = me._majorUnit; - var majorFormat = formats[majorUnit]; - var majorTime = tick.clone().startOf(majorUnit).valueOf(); - var majorTickOpts = options.ticks.major; - var major = majorTickOpts.enabled && majorUnit && majorFormat && time === majorTime; - var label = tick.format(formatOverride ? formatOverride : major ? majorFormat : minorFormat); - var tickOpts = major ? majorTickOpts : options.ticks.minor; - var formatter = helpers.valueOrDefault(tickOpts.callback, tickOpts.userCallback); - - return formatter ? formatter(label, index, ticks) : label; - }, + /** + * Function to format an individual tick mark + * @private + */ + tickFormatFunction: function(tick, index, ticks, formatOverride) { + var me = this; + var options = me.options; + var time = tick.valueOf(); + var formats = options.time.displayFormats; + var minorFormat = formats[me._unit]; + var majorUnit = me._majorUnit; + var majorFormat = formats[majorUnit]; + var majorTime = tick.clone().startOf(majorUnit).valueOf(); + var majorTickOpts = options.ticks.major; + var major = majorTickOpts.enabled && majorUnit && majorFormat && time === majorTime; + var label = tick.format(formatOverride ? formatOverride : major ? majorFormat : minorFormat); + var tickOpts = major ? majorTickOpts : options.ticks.minor; + var formatter = helpers.valueOrDefault(tickOpts.callback, tickOpts.userCallback); + + return formatter ? formatter(label, index, ticks) : label; + }, - convertTicksToLabels: function(ticks) { - var labels = []; - var i, ilen; + convertTicksToLabels: function(ticks) { + var labels = []; + var i, ilen; - for (i = 0, ilen = ticks.length; i < ilen; ++i) { - labels.push(this.tickFormatFunction(moment(ticks[i].value), i, ticks)); - } + for (i = 0, ilen = ticks.length; i < ilen; ++i) { + labels.push(this.tickFormatFunction(moment(ticks[i].value), i, ticks)); + } - return labels; - }, + return labels; + }, - /** - * @private - */ - getPixelForOffset: function(time) { - var me = this; - var isReverse = me.options.ticks.reverse; - var size = me._horizontal ? me.width : me.height; - var start = me._horizontal ? isReverse ? me.right : me.left : isReverse ? me.bottom : me.top; - var pos = interpolate(me._table, 'time', time, 'pos'); - var offset = size * (me._offsets.start + pos) / (me._offsets.start + 1 + me._offsets.end); - - return isReverse ? start - offset : start + offset; - }, + /** + * @private + */ + getPixelForOffset: function(time) { + var me = this; + var isReverse = me.options.ticks.reverse; + var size = me._horizontal ? me.width : me.height; + var start = me._horizontal ? isReverse ? me.right : me.left : isReverse ? me.bottom : me.top; + var pos = interpolate(me._table, 'time', time, 'pos'); + var offset = size * (me._offsets.start + pos) / (me._offsets.start + 1 + me._offsets.end); + + return isReverse ? start - offset : start + offset; + }, - getPixelForValue: function(value, index, datasetIndex) { - var me = this; - var time = null; + getPixelForValue: function(value, index, datasetIndex) { + var me = this; + var time = null; - if (index !== undefined && datasetIndex !== undefined) { - time = me._timestamps.datasets[datasetIndex][index]; - } + if (index !== undefined && datasetIndex !== undefined) { + time = me._timestamps.datasets[datasetIndex][index]; + } - if (time === null) { - time = parse(value, me); - } + if (time === null) { + time = parse(value, me); + } - if (time !== null) { - return me.getPixelForOffset(time); - } - }, + if (time !== null) { + return me.getPixelForOffset(time); + } + }, - getPixelForTick: function(index) { - var ticks = this.getTicks(); - return index >= 0 && index < ticks.length ? - this.getPixelForOffset(ticks[index].value) : - null; - }, + getPixelForTick: function(index) { + var ticks = this.getTicks(); + return index >= 0 && index < ticks.length ? + this.getPixelForOffset(ticks[index].value) : + null; + }, - getValueForPixel: function(pixel) { - var me = this; - var size = me._horizontal ? me.width : me.height; - var start = me._horizontal ? me.left : me.top; - var pos = (size ? (pixel - start) / size : 0) * (me._offsets.start + 1 + me._offsets.start) - me._offsets.end; - var time = interpolate(me._table, 'pos', pos, 'time'); + getValueForPixel: function(pixel) { + var me = this; + var size = me._horizontal ? me.width : me.height; + var start = me._horizontal ? me.left : me.top; + var pos = (size ? (pixel - start) / size : 0) * (me._offsets.start + 1 + me._offsets.start) - me._offsets.end; + var time = interpolate(me._table, 'pos', pos, 'time'); - return moment(time); - }, + return moment(time); + }, - /** - * Crude approximation of what the label width might be - * @private - */ - getLabelWidth: function(label) { - var me = this; - var ticksOpts = me.options.ticks; - var tickLabelWidth = me.ctx.measureText(label).width; - var angle = helpers.toRadians(ticksOpts.maxRotation); - var cosRotation = Math.cos(angle); - var sinRotation = Math.sin(angle); - var tickFontSize = helpers.valueOrDefault(ticksOpts.fontSize, defaults.global.defaultFontSize); - - return (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation); - }, + /** + * Crude approximation of what the label width might be + * @private + */ + getLabelWidth: function(label) { + var me = this; + var ticksOpts = me.options.ticks; + var tickLabelWidth = me.ctx.measureText(label).width; + var angle = helpers.toRadians(ticksOpts.maxRotation); + var cosRotation = Math.cos(angle); + var sinRotation = Math.sin(angle); + var tickFontSize = helpers.valueOrDefault(ticksOpts.fontSize, defaults.global.defaultFontSize); + + return (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation); + }, - /** - * @private - */ - getLabelCapacity: function(exampleTime) { - var me = this; + /** + * @private + */ + getLabelCapacity: function(exampleTime) { + var me = this; - var formatOverride = me.options.time.displayFormats.millisecond; // Pick the longest format for guestimation + var formatOverride = me.options.time.displayFormats.millisecond; // Pick the longest format for guestimation - var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, [], formatOverride); - var tickLabelWidth = me.getLabelWidth(exampleLabel); - var innerWidth = me.isHorizontal() ? me.width : me.height; + var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, [], formatOverride); + var tickLabelWidth = me.getLabelWidth(exampleLabel); + var innerWidth = me.isHorizontal() ? me.width : me.height; - var capacity = Math.floor(innerWidth / tickLabelWidth); - return capacity > 0 ? capacity : 1; - } - }); + var capacity = Math.floor(innerWidth / tickLabelWidth); + return capacity > 0 ? capacity : 1; + } +}); - scaleService.registerScaleType('time', TimeScale, defaultConfig); -}; +// INTERNAL: static default options, registered in src/chart.js +module.exports._defaults = defaultConfig; diff --git a/test/specs/global.deprecations.tests.js b/test/specs/global.deprecations.tests.js index eeda99f4b0a..822b234b417 100644 --- a/test/specs/global.deprecations.tests.js +++ b/test/specs/global.deprecations.tests.js @@ -31,6 +31,14 @@ describe('Deprecations', function() { expect(typeof Chart.helpers.aliasPixel).toBe('function'); }); }); + + describe('Chart.LinearScaleBase', function() { + it('should be defined and inherit from Chart.Scale', function() { + expect(Chart.LinearScaleBase).toBeDefined(); + expect(typeof Chart.LinearScaleBase).toBe('function'); + expect(Chart.LinearScaleBase.prototype instanceof Chart.Scale).toBeTruthy(); + }); + }); }); describe('Version 2.7.3', function() {