mirror of
https://github.com/chartjs/Chart.js.git
synced 2026-03-08 01:06:51 +01:00
Cleanup scales export for better import strategy (#5953)
Scales now export their class and associated defaults (`_defaults`), the registration being done globally in `src/chart.js`.
This commit is contained in:
20
src/chart.js
20
src/chart.js
@@ -26,12 +26,11 @@ Chart.Tooltip = require('./core/core.tooltip');
|
||||
|
||||
require('./core/core.controller')(Chart);
|
||||
|
||||
require('./scales/scale.linearbase')(Chart);
|
||||
require('./scales/scale.category')(Chart);
|
||||
require('./scales/scale.linear')(Chart);
|
||||
require('./scales/scale.logarithmic')(Chart);
|
||||
require('./scales/scale.radialLinear')(Chart);
|
||||
require('./scales/scale.time')(Chart);
|
||||
// Register built-in scales
|
||||
var scales = require('./scales');
|
||||
Chart.helpers.each(scales, function(scale, type) {
|
||||
Chart.scaleService.registerScaleType(type, scale, scale._defaults);
|
||||
});
|
||||
|
||||
// Loading built-in plugins
|
||||
var plugins = require('./plugins');
|
||||
@@ -105,6 +104,15 @@ Chart.canvasHelpers = Chart.helpers.canvas;
|
||||
*/
|
||||
Chart.layoutService = Chart.layouts;
|
||||
|
||||
/**
|
||||
* Provided for backward compatibility, not available anymore.
|
||||
* @namespace Chart.LinearScaleBase
|
||||
* @deprecated since version 2.8
|
||||
* @todo remove at version 3
|
||||
* @private
|
||||
*/
|
||||
Chart.LinearScaleBase = require('./scales/scale.linearbase');
|
||||
|
||||
/**
|
||||
* Provided for backward compatibility, instead we should create a new Chart
|
||||
* by setting the type in the config (`new Chart(id, {type: '{chart-type}'}`).
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
var bar = require('./controller.bar');
|
||||
var bubble = require('./controller.bubble');
|
||||
var doughnut = require('./controller.doughnut');
|
||||
var horizontalBar = require('./controller.horizontalBar');
|
||||
var line = require('./controller.line');
|
||||
var polarArea = require('./controller.polarArea');
|
||||
var pie = require('./controller.pie');
|
||||
var radar = require('./controller.radar');
|
||||
var scatter = require('./controller.scatter');
|
||||
|
||||
// NOTE export a map in which the key represents the controller type, not
|
||||
// the class, and so must be CamelCase in order to be correctly retrieved
|
||||
// by the controller in core.controller.js (`controllers[meta.type]`).
|
||||
|
||||
/* eslint-disable global-require */
|
||||
module.exports = {
|
||||
bar: require('./controller.bar'),
|
||||
bubble: require('./controller.bubble'),
|
||||
doughnut: require('./controller.doughnut'),
|
||||
horizontalBar: require('./controller.horizontalBar'),
|
||||
line: require('./controller.line'),
|
||||
polarArea: require('./controller.polarArea'),
|
||||
pie: require('./controller.pie'),
|
||||
radar: require('./controller.radar'),
|
||||
scatter: require('./controller.scatter')
|
||||
bar: bar,
|
||||
bubble: bubble,
|
||||
doughnut: doughnut,
|
||||
horizontalBar: horizontalBar,
|
||||
line: line,
|
||||
polarArea: polarArea,
|
||||
pie: pie,
|
||||
radar: radar,
|
||||
scatter: scatter
|
||||
};
|
||||
|
||||
15
src/scales/index.js
Normal file
15
src/scales/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
var category = require('./scale.category');
|
||||
var linear = require('./scale.linear');
|
||||
var logarithmic = require('./scale.logarithmic');
|
||||
var radialLinear = require('./scale.radialLinear');
|
||||
var time = require('./scale.time');
|
||||
|
||||
module.exports = {
|
||||
category: category,
|
||||
linear: linear,
|
||||
logarithmic: logarithmic,
|
||||
radialLinear: radialLinear,
|
||||
time: time
|
||||
};
|
||||
@@ -1,135 +1,134 @@
|
||||
'use strict';
|
||||
|
||||
var Scale = require('../core/core.scale');
|
||||
var scaleService = require('../core/core.scaleService');
|
||||
|
||||
module.exports = function() {
|
||||
|
||||
// Default config for a category scale
|
||||
var defaultConfig = {
|
||||
position: 'bottom'
|
||||
};
|
||||
|
||||
var DatasetScale = Scale.extend({
|
||||
/**
|
||||
* Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use those
|
||||
* else fall back to data.labels
|
||||
* @private
|
||||
*/
|
||||
getLabels: function() {
|
||||
var data = this.chart.data;
|
||||
return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels;
|
||||
},
|
||||
|
||||
determineDataLimits: function() {
|
||||
var me = this;
|
||||
var labels = me.getLabels();
|
||||
me.minIndex = 0;
|
||||
me.maxIndex = labels.length - 1;
|
||||
var findIndex;
|
||||
|
||||
if (me.options.ticks.min !== undefined) {
|
||||
// user specified min value
|
||||
findIndex = labels.indexOf(me.options.ticks.min);
|
||||
me.minIndex = findIndex !== -1 ? findIndex : me.minIndex;
|
||||
}
|
||||
|
||||
if (me.options.ticks.max !== undefined) {
|
||||
// user specified max value
|
||||
findIndex = labels.indexOf(me.options.ticks.max);
|
||||
me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex;
|
||||
}
|
||||
|
||||
me.min = labels[me.minIndex];
|
||||
me.max = labels[me.maxIndex];
|
||||
},
|
||||
|
||||
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);
|
||||
},
|
||||
|
||||
getLabelForIndex: function(index, datasetIndex) {
|
||||
var me = this;
|
||||
var data = me.chart.data;
|
||||
var isHorizontal = me.isHorizontal();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (me.isHorizontal()) {
|
||||
var valueWidth = me.width / offsetAmt;
|
||||
var widthOffset = (valueWidth * (index - me.minIndex));
|
||||
|
||||
if (offset) {
|
||||
widthOffset += (valueWidth / 2);
|
||||
}
|
||||
|
||||
return me.left + widthOffset;
|
||||
}
|
||||
var valueHeight = me.height / offsetAmt;
|
||||
var heightOffset = (valueHeight * (index - me.minIndex));
|
||||
|
||||
if (offset) {
|
||||
heightOffset += (valueHeight / 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;
|
||||
|
||||
if (offset) {
|
||||
pixel -= (valueDimension / 2);
|
||||
}
|
||||
|
||||
if (pixel <= 0) {
|
||||
value = 0;
|
||||
} else {
|
||||
value = Math.round(pixel / valueDimension);
|
||||
}
|
||||
|
||||
return value + me.minIndex;
|
||||
},
|
||||
getBasePixel: function() {
|
||||
return this.bottom;
|
||||
}
|
||||
});
|
||||
|
||||
scaleService.registerScaleType('category', DatasetScale, defaultConfig);
|
||||
var defaultConfig = {
|
||||
position: 'bottom'
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (me.options.ticks.max !== undefined) {
|
||||
// user specified max value
|
||||
findIndex = labels.indexOf(me.options.ticks.max);
|
||||
me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex;
|
||||
}
|
||||
|
||||
me.min = labels[me.minIndex];
|
||||
me.max = labels[me.maxIndex];
|
||||
},
|
||||
|
||||
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);
|
||||
},
|
||||
|
||||
getLabelForIndex: function(index, datasetIndex) {
|
||||
var me = this;
|
||||
var data = me.chart.data;
|
||||
var isHorizontal = me.isHorizontal();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (me.isHorizontal()) {
|
||||
var valueWidth = me.width / offsetAmt;
|
||||
var widthOffset = (valueWidth * (index - me.minIndex));
|
||||
|
||||
if (offset) {
|
||||
widthOffset += (valueWidth / 2);
|
||||
}
|
||||
|
||||
return me.left + widthOffset;
|
||||
}
|
||||
var valueHeight = me.height / offsetAmt;
|
||||
var heightOffset = (valueHeight * (index - me.minIndex));
|
||||
|
||||
if (offset) {
|
||||
heightOffset += (valueHeight / 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;
|
||||
|
||||
if (offset) {
|
||||
pixel -= (valueDimension / 2);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -1,187 +1,190 @@
|
||||
'use strict';
|
||||
|
||||
var helpers = require('../helpers/index');
|
||||
var scaleService = require('../core/core.scaleService');
|
||||
var LinearScaleBase = require('./scale.linearbase');
|
||||
var Ticks = require('../core/core.ticks');
|
||||
|
||||
module.exports = function(Chart) {
|
||||
|
||||
var defaultConfig = {
|
||||
position: 'left',
|
||||
ticks: {
|
||||
callback: Ticks.formatters.linear
|
||||
}
|
||||
};
|
||||
|
||||
var 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]);
|
||||
}
|
||||
});
|
||||
|
||||
scaleService.registerScaleType('linear', LinearScale, defaultConfig);
|
||||
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;
|
||||
}
|
||||
|
||||
// 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]);
|
||||
}
|
||||
});
|
||||
|
||||
// INTERNAL: static default options, registered in src/chart.js
|
||||
module.exports._defaults = defaultConfig;
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
var helpers = require('../helpers/index');
|
||||
var Scale = require('../core/core.scale');
|
||||
|
||||
var noop = helpers.noop;
|
||||
|
||||
/**
|
||||
* Generate a set of linear ticks
|
||||
* @param generationOptions the options used to generate the ticks
|
||||
@@ -74,156 +76,152 @@ function generateTicks(generationOptions, dataRange) {
|
||||
return ticks;
|
||||
}
|
||||
|
||||
module.exports = function(Chart) {
|
||||
|
||||
var noop = helpers.noop;
|
||||
|
||||
Chart.LinearScaleBase = Scale.extend({
|
||||
getRightValue: function(value) {
|
||||
if (typeof value === 'string') {
|
||||
return +value;
|
||||
}
|
||||
return Scale.prototype.getRightValue.call(this, value);
|
||||
},
|
||||
|
||||
handleTickRangeOptions: function() {
|
||||
var me = this;
|
||||
var opts = me.options;
|
||||
var tickOpts = opts.ticks;
|
||||
|
||||
// If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
|
||||
// do nothing since that would make the chart weird. If the user really wants a weird chart
|
||||
// axis, they can manually override it
|
||||
if (tickOpts.beginAtZero) {
|
||||
var minSign = helpers.sign(me.min);
|
||||
var maxSign = helpers.sign(me.max);
|
||||
|
||||
if (minSign < 0 && maxSign < 0) {
|
||||
// move the top up to 0
|
||||
me.max = 0;
|
||||
} else if (minSign > 0 && maxSign > 0) {
|
||||
// move the bottom down to 0
|
||||
me.min = 0;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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 (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 (me.min === me.max) {
|
||||
me.max++;
|
||||
|
||||
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();
|
||||
|
||||
// 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;
|
||||
}
|
||||
},
|
||||
convertTicksToLabels: function() {
|
||||
var me = this;
|
||||
me.ticksAsNumbers = me.ticks.slice();
|
||||
me.zeroLineIndex = me.ticks.indexOf(0);
|
||||
|
||||
Scale.prototype.convertTicksToLabels.call(me);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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 (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 (me.min === me.max) {
|
||||
me.max++;
|
||||
|
||||
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();
|
||||
|
||||
// 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;
|
||||
}
|
||||
},
|
||||
|
||||
convertTicksToLabels: function() {
|
||||
var me = this;
|
||||
me.ticksAsNumbers = me.ticks.slice();
|
||||
me.zeroLineIndex = me.ticks.indexOf(0);
|
||||
|
||||
Scale.prototype.convertTicksToLabels.call(me);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
var defaults = require('../core/core.defaults');
|
||||
var helpers = require('../helpers/index');
|
||||
var Scale = require('../core/core.scale');
|
||||
var scaleService = require('../core/core.scaleService');
|
||||
var Ticks = require('../core/core.ticks');
|
||||
|
||||
/**
|
||||
@@ -52,294 +52,299 @@ function generateTicks(generationOptions, dataRange) {
|
||||
return ticks;
|
||||
}
|
||||
|
||||
var defaultConfig = {
|
||||
position: 'left',
|
||||
|
||||
module.exports = function(Chart) {
|
||||
// label settings
|
||||
ticks: {
|
||||
callback: Ticks.formatters.logarithmic
|
||||
}
|
||||
};
|
||||
|
||||
var defaultConfig = {
|
||||
position: 'left',
|
||||
|
||||
// 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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] = [];
|
||||
}
|
||||
|
||||
var meta = chart.getDatasetMeta(datasetIndex);
|
||||
if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
|
||||
meta.stack !== undefined) {
|
||||
hasStacks = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
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;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (opts.stacked || hasStacks) {
|
||||
var valuesPerStack = {};
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
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] = [];
|
||||
} 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;
|
||||
});
|
||||
}
|
||||
});
|
||||
if (me.min === null) {
|
||||
me.min = value;
|
||||
} else if (value < me.min) {
|
||||
me.min = 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.max === null) {
|
||||
me.max = value;
|
||||
} else if (value > me.max) {
|
||||
me.max = 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 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 (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) {
|
||||
me.minNotZero = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
},
|
||||
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,
|
||||
Chart.defaults.global.defaultFontSize
|
||||
);
|
||||
innerDimension -= offset;
|
||||
start = firstTickValue;
|
||||
}
|
||||
if (value !== 0) {
|
||||
offset += innerDimension / (log10(end) - log10(start)) * (log10(value) - log10(start));
|
||||
}
|
||||
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,
|
||||
Chart.defaults.global.defaultFontSize
|
||||
);
|
||||
value -= offset;
|
||||
innerDimension -= offset;
|
||||
start = firstTickValue;
|
||||
}
|
||||
value *= log10(end) - log10(start);
|
||||
value /= innerDimension;
|
||||
value = Math.pow(10, log10(start) + value);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
scaleService.registerScaleType('logarithmic', LogarithmicScale, defaultConfig);
|
||||
};
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
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 (value !== 0) {
|
||||
offset += innerDimension / (log10(end) - log10(start)) * (log10(value) - log10(start));
|
||||
}
|
||||
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;
|
||||
}
|
||||
value *= log10(end) - log10(start);
|
||||
value /= innerDimension;
|
||||
value = Math.pow(10, log10(start) + value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
});
|
||||
|
||||
// INTERNAL: static default options, registered in src/chart.js
|
||||
module.exports._defaults = defaultConfig;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,6 @@ var moment = require('moment');
|
||||
var defaults = require('../core/core.defaults');
|
||||
var helpers = require('../helpers/index');
|
||||
var Scale = require('../core/core.scale');
|
||||
var scaleService = require('../core/core.scaleService');
|
||||
|
||||
// Integer constants are from the ES6 spec.
|
||||
var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991;
|
||||
@@ -426,369 +425,367 @@ function determineLabelFormat(data, timeOpts) {
|
||||
return 'MMM D, YYYY';
|
||||
}
|
||||
|
||||
module.exports = function() {
|
||||
var defaultConfig = {
|
||||
position: 'bottom',
|
||||
|
||||
var defaultConfig = {
|
||||
position: 'bottom',
|
||||
/**
|
||||
* Data distribution along the scale:
|
||||
* - 'linear': data are spread according to their time (distances can vary),
|
||||
* - 'series': data are spread at the same distance from each other.
|
||||
* @see https://github.com/chartjs/Chart.js/pull/4507
|
||||
* @since 2.7.0
|
||||
*/
|
||||
distribution: 'linear',
|
||||
|
||||
/**
|
||||
* Scale boundary strategy (bypassed by min/max time options)
|
||||
* - `data`: make sure data are fully visible, ticks outside are removed
|
||||
* - `ticks`: make sure ticks are fully visible, data outside are truncated
|
||||
* @see https://github.com/chartjs/Chart.js/pull/4556
|
||||
* @since 2.7.0
|
||||
*/
|
||||
bounds: 'data',
|
||||
|
||||
time: {
|
||||
parser: false, // false == a pattern string from https://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment
|
||||
format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from https://momentjs.com/docs/#/parsing/string-format/
|
||||
unit: false, // false == automatic or override with week, month, year, etc.
|
||||
round: false, // none, or override with week, month, year, etc.
|
||||
displayFormat: false, // DEPRECATED
|
||||
isoWeekday: false, // override week start day - see https://momentjs.com/docs/#/get-set/iso-weekday/
|
||||
minUnit: 'millisecond',
|
||||
|
||||
// defaults to unit's corresponding unitFormat below or override using pattern string from https://momentjs.com/docs/#/displaying/format/
|
||||
displayFormats: {
|
||||
millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM,
|
||||
second: 'h:mm:ss a', // 11:20:01 AM
|
||||
minute: 'h:mm a', // 11:20 AM
|
||||
hour: 'hA', // 5PM
|
||||
day: 'MMM D', // Sep 4
|
||||
week: 'll', // Week 46, or maybe "[W]WW - YYYY" ?
|
||||
month: 'MMM YYYY', // Sept 2015
|
||||
quarter: '[Q]Q - YYYY', // Q3
|
||||
year: 'YYYY' // 2015
|
||||
},
|
||||
},
|
||||
ticks: {
|
||||
autoSkip: false,
|
||||
|
||||
/**
|
||||
* Data distribution along the scale:
|
||||
* - 'linear': data are spread according to their time (distances can vary),
|
||||
* - 'series': data are spread at the same distance from each other.
|
||||
* Ticks generation input values:
|
||||
* - 'auto': generates "optimal" ticks based on scale size and time options.
|
||||
* - 'data': generates ticks from data (including labels from data {t|x|y} objects).
|
||||
* - 'labels': generates ticks from user given `data.labels` values ONLY.
|
||||
* @see https://github.com/chartjs/Chart.js/pull/4507
|
||||
* @since 2.7.0
|
||||
*/
|
||||
distribution: 'linear',
|
||||
source: 'auto',
|
||||
|
||||
/**
|
||||
* Scale boundary strategy (bypassed by min/max time options)
|
||||
* - `data`: make sure data are fully visible, ticks outside are removed
|
||||
* - `ticks`: make sure ticks are fully visible, data outside are truncated
|
||||
* @see https://github.com/chartjs/Chart.js/pull/4556
|
||||
* @since 2.7.0
|
||||
*/
|
||||
bounds: 'data',
|
||||
|
||||
time: {
|
||||
parser: false, // false == a pattern string from https://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment
|
||||
format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from https://momentjs.com/docs/#/parsing/string-format/
|
||||
unit: false, // false == automatic or override with week, month, year, etc.
|
||||
round: false, // none, or override with week, month, year, etc.
|
||||
displayFormat: false, // DEPRECATED
|
||||
isoWeekday: false, // override week start day - see https://momentjs.com/docs/#/get-set/iso-weekday/
|
||||
minUnit: 'millisecond',
|
||||
|
||||
// defaults to unit's corresponding unitFormat below or override using pattern string from https://momentjs.com/docs/#/displaying/format/
|
||||
displayFormats: {
|
||||
millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM,
|
||||
second: 'h:mm:ss a', // 11:20:01 AM
|
||||
minute: 'h:mm a', // 11:20 AM
|
||||
hour: 'hA', // 5PM
|
||||
day: 'MMM D', // Sep 4
|
||||
week: 'll', // Week 46, or maybe "[W]WW - YYYY" ?
|
||||
month: 'MMM YYYY', // Sept 2015
|
||||
quarter: '[Q]Q - YYYY', // Q3
|
||||
year: 'YYYY' // 2015
|
||||
},
|
||||
},
|
||||
ticks: {
|
||||
autoSkip: false,
|
||||
|
||||
/**
|
||||
* Ticks generation input values:
|
||||
* - 'auto': generates "optimal" ticks based on scale size and time options.
|
||||
* - 'data': generates ticks from data (including labels from data {t|x|y} objects).
|
||||
* - 'labels': generates ticks from user given `data.labels` values ONLY.
|
||||
* @see https://github.com/chartjs/Chart.js/pull/4507
|
||||
* @since 2.7.0
|
||||
*/
|
||||
source: 'auto',
|
||||
|
||||
major: {
|
||||
enabled: false
|
||||
}
|
||||
major: {
|
||||
enabled: false
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
var TimeScale = Scale.extend({
|
||||
initialize: function() {
|
||||
if (!moment) {
|
||||
throw new Error('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com');
|
||||
}
|
||||
module.exports = Scale.extend({
|
||||
initialize: function() {
|
||||
if (!moment) {
|
||||
throw new Error('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com');
|
||||
}
|
||||
|
||||
this.mergeTicksOptions();
|
||||
this.mergeTicksOptions();
|
||||
|
||||
Scale.prototype.initialize.call(this);
|
||||
},
|
||||
Scale.prototype.initialize.call(this);
|
||||
},
|
||||
|
||||
update: function() {
|
||||
var me = this;
|
||||
var options = me.options;
|
||||
update: function() {
|
||||
var me = this;
|
||||
var options = me.options;
|
||||
|
||||
// DEPRECATIONS: output a message only one time per update
|
||||
if (options.time && options.time.format) {
|
||||
console.warn('options.time.format is deprecated and replaced by options.time.parser.');
|
||||
}
|
||||
// DEPRECATIONS: output a message only one time per update
|
||||
if (options.time && options.time.format) {
|
||||
console.warn('options.time.format is deprecated and replaced by options.time.parser.');
|
||||
}
|
||||
|
||||
return Scale.prototype.update.apply(me, arguments);
|
||||
},
|
||||
return Scale.prototype.update.apply(me, arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Allows data to be referenced via 't' attribute
|
||||
*/
|
||||
getRightValue: function(rawValue) {
|
||||
if (rawValue && rawValue.t !== undefined) {
|
||||
rawValue = rawValue.t;
|
||||
}
|
||||
return Scale.prototype.getRightValue.call(this, rawValue);
|
||||
},
|
||||
/**
|
||||
* Allows data to be referenced via 't' attribute
|
||||
*/
|
||||
getRightValue: function(rawValue) {
|
||||
if (rawValue && rawValue.t !== undefined) {
|
||||
rawValue = rawValue.t;
|
||||
}
|
||||
return Scale.prototype.getRightValue.call(this, rawValue);
|
||||
},
|
||||
|
||||
determineDataLimits: function() {
|
||||
var me = this;
|
||||
var chart = me.chart;
|
||||
var timeOpts = me.options.time;
|
||||
var unit = timeOpts.unit || 'day';
|
||||
var min = MAX_INTEGER;
|
||||
var max = MIN_INTEGER;
|
||||
var timestamps = [];
|
||||
var datasets = [];
|
||||
var labels = [];
|
||||
var i, j, ilen, jlen, data, timestamp;
|
||||
var dataLabels = chart.data.labels || [];
|
||||
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 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;
|
||||
// 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] = [];
|
||||
// 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 (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
|
||||
};
|
||||
},
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (options.ticks.reverse) {
|
||||
ticks.reverse();
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
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);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
for (i = 0, ilen = ticks.length; i < ilen; ++i) {
|
||||
labels.push(this.tickFormatFunction(moment(ticks[i].value), i, ticks));
|
||||
}
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
getPixelForValue: function(value, index, datasetIndex) {
|
||||
var me = this;
|
||||
var time = null;
|
||||
|
||||
if (index !== undefined && datasetIndex !== undefined) {
|
||||
time = me._timestamps.datasets[datasetIndex][index];
|
||||
}
|
||||
|
||||
if (time === null) {
|
||||
time = parse(value, me);
|
||||
}
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
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);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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 capacity = Math.floor(innerWidth / tickLabelWidth);
|
||||
return capacity > 0 ? capacity : 1;
|
||||
}
|
||||
});
|
||||
|
||||
scaleService.registerScaleType('time', TimeScale, defaultConfig);
|
||||
};
|
||||
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]);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
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);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
for (i = 0, ilen = ticks.length; i < ilen; ++i) {
|
||||
labels.push(this.tickFormatFunction(moment(ticks[i].value), i, ticks));
|
||||
}
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
getPixelForValue: function(value, index, datasetIndex) {
|
||||
var me = this;
|
||||
var time = null;
|
||||
|
||||
if (index !== undefined && datasetIndex !== undefined) {
|
||||
time = me._timestamps.datasets[datasetIndex][index];
|
||||
}
|
||||
|
||||
if (time === null) {
|
||||
time = parse(value, me);
|
||||
}
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
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);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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 capacity = Math.floor(innerWidth / tickLabelWidth);
|
||||
return capacity > 0 ? capacity : 1;
|
||||
}
|
||||
});
|
||||
|
||||
// INTERNAL: static default options, registered in src/chart.js
|
||||
module.exports._defaults = defaultConfig;
|
||||
|
||||
@@ -31,6 +31,14 @@ describe('Deprecations', function() {
|
||||
expect(typeof Chart.helpers.aliasPixel).toBe('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Chart.LinearScaleBase', function() {
|
||||
it('should be defined and inherit from Chart.Scale', function() {
|
||||
expect(Chart.LinearScaleBase).toBeDefined();
|
||||
expect(typeof Chart.LinearScaleBase).toBe('function');
|
||||
expect(Chart.LinearScaleBase.prototype instanceof Chart.Scale).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Version 2.7.3', function() {
|
||||
|
||||
Reference in New Issue
Block a user