Scales now export their class and associated defaults (`_defaults`), the registration being done globally in `src/chart.js`.
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');
*/
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}'}`).
'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
};
--- /dev/null
+'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
+};
'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;
'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;
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
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);
+ }
+});
'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');
/**
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;
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;
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;
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;
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() {