mirror of
https://github.com/chartjs/Chart.js.git
synced 2025-12-08 20:36:08 +00:00
Early data parsing, stacking by value and support object data (#6576)
* Early data parsing + stacking by value * Review comments * review comments * Remove reduntant parsing * Couple CC warnings * Optimize filterBetween * More migration info
This commit is contained in:
parent
dd8d267956
commit
72df272234
@ -73,8 +73,8 @@ To work with Chart.js, custom scale types must implement the following interface
|
||||
// buildTicks() should create a ticks array on the axis instance, if you intend to use any of the implementations from the base class
|
||||
buildTicks: function() {},
|
||||
|
||||
// Get the value to show for the data at the given index of the the given dataset, ie this.chart.data.datasets[datasetIndex].data[index]
|
||||
getLabelForIndex: function(index, datasetIndex) {},
|
||||
// Get the label to show for the given value
|
||||
getLabelForValue: function(value) {},
|
||||
|
||||
// Get the pixel (x coordinate for horizontal axis, y coordinate for vertical axis) for a given value
|
||||
// @param index: index into the ticks array
|
||||
|
||||
@ -52,11 +52,13 @@ Chart.js is no longer providing the `Chart.bundle.js` and `Chart.bundle.min.js`.
|
||||
* `helpers.numberOfLabelLines`
|
||||
* `helpers.removeEvent`
|
||||
* `helpers.scaleMerge`
|
||||
* `scale.getRightValue`
|
||||
* `scale.mergeTicksOptions`
|
||||
* `scale.ticksAsNumbers`
|
||||
* `Chart.Controller`
|
||||
* `Chart.chart.chart`
|
||||
* `Chart.types`
|
||||
* `Line.calculatePointY`
|
||||
* Made `scale.handleDirectionalChanges` private
|
||||
* Made `scale.tickValues` private
|
||||
|
||||
@ -74,13 +76,18 @@ Chart.js is no longer providing the `Chart.bundle.js` and `Chart.bundle.min.js`.
|
||||
|
||||
### Changed
|
||||
|
||||
#### Ticks
|
||||
#### Scales
|
||||
|
||||
* `scale.getLabelForIndex` was replaced by `scale.getLabelForValue`
|
||||
* `scale.getPixelForValue` now has only one parameter
|
||||
|
||||
##### Ticks
|
||||
|
||||
* `scale.ticks` now contains objects instead of strings
|
||||
* `buildTicks` is now expected to return tick objects
|
||||
* `afterBuildTicks` now has no parameters like the other callbacks
|
||||
* `convertTicksToLabels` was renamed to `generateTickLabels`. It is now expected to set the label property on the ticks given as input
|
||||
|
||||
#### Time Scale
|
||||
##### Time Scale
|
||||
|
||||
* `getValueForPixel` now returns milliseconds since the epoch
|
||||
|
||||
@ -125,6 +125,57 @@ function computeFlexCategoryTraits(index, ruler, options) {
|
||||
};
|
||||
}
|
||||
|
||||
function parseFloatBar(arr, item, vScale, i) {
|
||||
var startValue = vScale._parse(arr[0], i);
|
||||
var endValue = vScale._parse(arr[1], i);
|
||||
var min = Math.min(startValue, endValue);
|
||||
var max = Math.max(startValue, endValue);
|
||||
var barStart = min;
|
||||
var barEnd = max;
|
||||
|
||||
if (Math.abs(min) > Math.abs(max)) {
|
||||
barStart = max;
|
||||
barEnd = min;
|
||||
}
|
||||
|
||||
// Store `barEnd` (furthest away from origin) as parsed value,
|
||||
// to make stacking straight forward
|
||||
item[vScale.id] = barEnd;
|
||||
|
||||
item._custom = {
|
||||
barStart: barStart,
|
||||
barEnd: barEnd,
|
||||
start: startValue,
|
||||
end: endValue,
|
||||
min: min,
|
||||
max: max
|
||||
};
|
||||
}
|
||||
|
||||
function parseArrayOrPrimitive(meta, data, start, count) {
|
||||
var iScale = this._getIndexScale();
|
||||
var vScale = this._getValueScale();
|
||||
var labels = iScale._getLabels();
|
||||
var singleScale = iScale === vScale;
|
||||
var parsed = [];
|
||||
var i, ilen, item, entry;
|
||||
|
||||
for (i = start, ilen = start + count; i < ilen; ++i) {
|
||||
entry = data[i];
|
||||
item = {};
|
||||
item[iScale.id] = singleScale || iScale._parse(labels[i], i);
|
||||
|
||||
if (helpers.isArray(entry)) {
|
||||
parseFloatBar(entry, item, vScale, i);
|
||||
} else {
|
||||
item[vScale.id] = vScale._parse(entry, i);
|
||||
}
|
||||
|
||||
parsed.push(item);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
module.exports = DatasetController.extend({
|
||||
|
||||
dataElementType: elements.Rectangle,
|
||||
@ -144,6 +195,24 @@ module.exports = DatasetController.extend({
|
||||
'minBarLength'
|
||||
],
|
||||
|
||||
/**
|
||||
* Overriding primitive data parsing since we support mixed primitive/array
|
||||
* data for float bars
|
||||
* @private
|
||||
*/
|
||||
_parsePrimitiveData: function() {
|
||||
return parseArrayOrPrimitive.apply(this, arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Overriding array data parsing since we support mixed primitive/array
|
||||
* data for float bars
|
||||
* @private
|
||||
*/
|
||||
_parseArrayData: function() {
|
||||
return parseArrayOrPrimitive.apply(this, arguments);
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
var me = this;
|
||||
var meta;
|
||||
@ -183,7 +252,8 @@ module.exports = DatasetController.extend({
|
||||
label: me.chart.data.labels[index]
|
||||
};
|
||||
|
||||
if (helpers.isArray(dataset.data[index])) {
|
||||
// all borders are drawn for floating bar
|
||||
if (me._getParsed(index)._custom) {
|
||||
rectangle._model.borderSkipped = null;
|
||||
}
|
||||
|
||||
@ -202,8 +272,8 @@ module.exports = DatasetController.extend({
|
||||
var base = vscale.getBasePixel();
|
||||
var horizontal = vscale.isHorizontal();
|
||||
var ruler = me._ruler || me.getRuler();
|
||||
var vpixels = me.calculateBarValuePixels(me.index, index, options);
|
||||
var ipixels = me.calculateBarIndexPixels(me.index, index, ruler, options);
|
||||
var vpixels = me.calculateBarValuePixels(index, options);
|
||||
var ipixels = me.calculateBarIndexPixels(index, ruler, options);
|
||||
|
||||
model.horizontal = horizontal;
|
||||
model.base = reset ? base : vpixels.base;
|
||||
@ -283,7 +353,7 @@ module.exports = DatasetController.extend({
|
||||
var i, ilen;
|
||||
|
||||
for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) {
|
||||
pixels.push(scale.getPixelForValue(null, i, me.index));
|
||||
pixels.push(scale.getPixelForValue(me._getParsed(i)[scale.id]));
|
||||
}
|
||||
|
||||
return {
|
||||
@ -299,52 +369,39 @@ module.exports = DatasetController.extend({
|
||||
* Note: pixel values are not clamped to the scale area.
|
||||
* @private
|
||||
*/
|
||||
calculateBarValuePixels: function(datasetIndex, index, options) {
|
||||
calculateBarValuePixels: function(index, options) {
|
||||
var me = this;
|
||||
var chart = me.chart;
|
||||
var scale = me._getValueScale();
|
||||
var isHorizontal = scale.isHorizontal();
|
||||
var datasets = chart.data.datasets;
|
||||
var metasets = scale._getMatchingVisibleMetas(me._type);
|
||||
var value = scale._parseValue(datasets[datasetIndex].data[index]);
|
||||
var valueScale = me._getValueScale();
|
||||
var minBarLength = options.minBarLength;
|
||||
var stacked = scale.options.stacked;
|
||||
var stack = me.getMeta().stack;
|
||||
var start = value.start === undefined ? 0 : value.max >= 0 && value.min >= 0 ? value.min : value.max;
|
||||
var length = value.start === undefined ? value.end : value.max >= 0 && value.min >= 0 ? value.max - value.min : value.min - value.max;
|
||||
var ilen = metasets.length;
|
||||
var i, imeta, ivalue, base, head, size, stackLength;
|
||||
var start = 0;
|
||||
var parsed = me._getParsed(index);
|
||||
var value = parsed[valueScale.id];
|
||||
var custom = parsed._custom;
|
||||
var length = me._cachedMeta._stacked ? me._applyStack(valueScale, parsed) : parsed[valueScale.id];
|
||||
var base, head, size;
|
||||
|
||||
if (stacked || (stacked === undefined && stack !== undefined)) {
|
||||
for (i = 0; i < ilen; ++i) {
|
||||
imeta = metasets[i];
|
||||
|
||||
if (imeta.index === datasetIndex) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (imeta.stack === stack) {
|
||||
stackLength = scale._parseValue(datasets[imeta.index].data[index]);
|
||||
ivalue = stackLength.start === undefined ? stackLength.end : stackLength.min >= 0 && stackLength.max >= 0 ? stackLength.max : stackLength.min;
|
||||
|
||||
if ((value.min < 0 && ivalue < 0) || (value.max >= 0 && ivalue > 0)) {
|
||||
start += ivalue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (length !== value) {
|
||||
start = length - value;
|
||||
length = value;
|
||||
}
|
||||
|
||||
base = scale.getPixelForValue(start);
|
||||
head = scale.getPixelForValue(start + length);
|
||||
if (custom && custom.barStart !== undefined && custom.barEnd !== undefined) {
|
||||
value = custom.barStart;
|
||||
length = custom.barEnd - custom.barStart;
|
||||
// bars crossing origin are not stacked
|
||||
if (value !== 0 && Math.sign(value) !== Math.sign(custom.barEnd)) {
|
||||
start = 0;
|
||||
}
|
||||
start += value;
|
||||
}
|
||||
|
||||
base = valueScale.getPixelForValue(start);
|
||||
head = valueScale.getPixelForValue(start + length);
|
||||
size = head - base;
|
||||
|
||||
if (minBarLength !== undefined && Math.abs(size) < minBarLength) {
|
||||
size = minBarLength;
|
||||
if (length >= 0 && !isHorizontal || length < 0 && isHorizontal) {
|
||||
head = base - minBarLength;
|
||||
} else {
|
||||
head = base + minBarLength;
|
||||
}
|
||||
size = size < 0 ? -minBarLength : minBarLength;
|
||||
head = base + size;
|
||||
}
|
||||
|
||||
return {
|
||||
@ -358,13 +415,13 @@ module.exports = DatasetController.extend({
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
calculateBarIndexPixels: function(datasetIndex, index, ruler, options) {
|
||||
calculateBarIndexPixels: function(index, ruler, options) {
|
||||
var me = this;
|
||||
var range = options.barThickness === 'flex'
|
||||
? computeFlexCategoryTraits(index, ruler, options)
|
||||
: computeFitCategoryTraits(index, ruler, options);
|
||||
|
||||
var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack);
|
||||
var stackIndex = me.getStackIndex(me.index, me.getMeta().stack);
|
||||
var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2);
|
||||
var size = Math.min(
|
||||
valueOrDefault(options.maxBarThickness, Infinity),
|
||||
@ -383,15 +440,13 @@ module.exports = DatasetController.extend({
|
||||
var chart = me.chart;
|
||||
var scale = me._getValueScale();
|
||||
var rects = me.getMeta().data;
|
||||
var dataset = me.getDataset();
|
||||
var ilen = rects.length;
|
||||
var i = 0;
|
||||
|
||||
helpers.canvas.clipArea(chart.ctx, chart.chartArea);
|
||||
|
||||
for (; i < ilen; ++i) {
|
||||
var val = scale._parseValue(dataset.data[i]);
|
||||
if (!isNaN(val.min) && !isNaN(val.max)) {
|
||||
if (!isNaN(me._getParsed(i)[scale.id])) {
|
||||
rects[i].draw();
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ defaults._set('bubble', {
|
||||
},
|
||||
label: function(item, data) {
|
||||
var datasetLabel = data.datasets[item.datasetIndex].label || '';
|
||||
var dataPoint = data.datasets[item.datasetIndex].data[item.index];
|
||||
var dataPoint = data.datasets[item.datasetIndex].data[item.index] || {r: '?'};
|
||||
return datasetLabel + ': (' + item.label + ', ' + item.value + ', ' + dataPoint.r + ')';
|
||||
}
|
||||
}
|
||||
@ -59,6 +59,26 @@ module.exports = DatasetController.extend({
|
||||
'rotation'
|
||||
],
|
||||
|
||||
/**
|
||||
* Parse array of objects
|
||||
* @private
|
||||
*/
|
||||
_parseObjectData: function(meta, data, start, count) {
|
||||
var xScale = this.getScaleForId(meta.xAxisID);
|
||||
var yScale = this.getScaleForId(meta.yAxisID);
|
||||
var parsed = [];
|
||||
var i, ilen, item, obj;
|
||||
for (i = start, ilen = start + count; i < ilen; ++i) {
|
||||
obj = data[i];
|
||||
item = {};
|
||||
item[xScale.id] = xScale._parseObject(obj, 'x', i);
|
||||
item[yScale.id] = yScale._parseObject(obj, 'y', i);
|
||||
item._custom = obj && obj.r && +obj.r;
|
||||
parsed.push(item);
|
||||
}
|
||||
return parsed;
|
||||
},
|
||||
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
@ -82,14 +102,12 @@ module.exports = DatasetController.extend({
|
||||
var xScale = me.getScaleForId(meta.xAxisID);
|
||||
var yScale = me.getScaleForId(meta.yAxisID);
|
||||
var options = me._resolveDataElementOptions(index);
|
||||
var data = me.getDataset().data[index];
|
||||
var dsIndex = me.index;
|
||||
|
||||
var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex);
|
||||
var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex);
|
||||
var parsed = !reset && me._getParsed(index);
|
||||
var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(parsed[xScale.id]);
|
||||
var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(parsed[yScale.id]);
|
||||
|
||||
point._options = options;
|
||||
point._datasetIndex = dsIndex;
|
||||
point._datasetIndex = me.index;
|
||||
point._index = index;
|
||||
point._model = {
|
||||
backgroundColor: options.backgroundColor,
|
||||
@ -135,7 +153,7 @@ module.exports = DatasetController.extend({
|
||||
var me = this;
|
||||
var chart = me.chart;
|
||||
var dataset = me.getDataset();
|
||||
var data = dataset.data[index] || {};
|
||||
var parsed = me._getParsed(index);
|
||||
var values = DatasetController.prototype._resolveDataElementOptions.apply(me, arguments);
|
||||
|
||||
// Scriptable options
|
||||
@ -153,7 +171,7 @@ module.exports = DatasetController.extend({
|
||||
|
||||
// Custom radius resolution
|
||||
values.radius = resolve([
|
||||
data.r,
|
||||
parsed && parsed._custom,
|
||||
me._config.radius,
|
||||
chart.options.elements.point.radius
|
||||
], context, index);
|
||||
|
||||
@ -134,6 +134,19 @@ module.exports = DatasetController.extend({
|
||||
'hoverBorderWidth',
|
||||
],
|
||||
|
||||
/**
|
||||
* Override data parsing, since we are not using scales
|
||||
* @private
|
||||
*/
|
||||
_parse: function(start, count) {
|
||||
var data = this.getDataset().data;
|
||||
var metaData = this.getMeta().data;
|
||||
var i, ilen;
|
||||
for (i = start, ilen = start + count; i < ilen; ++i) {
|
||||
metaData[i]._val = +data[i];
|
||||
}
|
||||
},
|
||||
|
||||
// Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly
|
||||
getRingIndex: function(datasetIndex) {
|
||||
var ringIndex = 0;
|
||||
@ -220,7 +233,7 @@ module.exports = DatasetController.extend({
|
||||
var startAngle = opts.rotation; // non reset case handled later
|
||||
var endAngle = opts.rotation; // non reset case handled later
|
||||
var dataset = me.getDataset();
|
||||
var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / DOUBLE_PI);
|
||||
var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(arc._val * opts.circumference / DOUBLE_PI);
|
||||
var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius;
|
||||
var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius;
|
||||
var options = arc._options || {};
|
||||
@ -264,14 +277,13 @@ module.exports = DatasetController.extend({
|
||||
},
|
||||
|
||||
calculateTotal: function() {
|
||||
var dataset = this.getDataset();
|
||||
var meta = this.getMeta();
|
||||
var metaData = this.getMeta().data;
|
||||
var total = 0;
|
||||
var value;
|
||||
|
||||
helpers.each(meta.data, function(element, index) {
|
||||
value = dataset.data[index];
|
||||
if (!isNaN(value) && !element.hidden) {
|
||||
helpers.each(metaData, function(arc) {
|
||||
value = arc ? arc._val : NaN;
|
||||
if (!isNaN(value) && !arc.hidden) {
|
||||
total += Math.abs(value);
|
||||
}
|
||||
});
|
||||
|
||||
@ -115,16 +115,15 @@ module.exports = DatasetController.extend({
|
||||
updateElement: function(point, index, reset) {
|
||||
var me = this;
|
||||
var meta = me.getMeta();
|
||||
var dataset = me.getDataset();
|
||||
var datasetIndex = me.index;
|
||||
var value = dataset.data[index];
|
||||
var xScale = me._xScale;
|
||||
var yScale = me._yScale;
|
||||
var lineModel = meta.dataset._model;
|
||||
var x, y;
|
||||
|
||||
var stacked = meta._stacked;
|
||||
var parsed = me._getParsed(index);
|
||||
var options = me._resolveDataElementOptions(index);
|
||||
|
||||
x = me._xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex);
|
||||
y = reset ? me._yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex);
|
||||
var x = xScale.getPixelForValue(parsed[xScale.id]);
|
||||
var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(stacked ? me._applyStack(yScale, parsed) : parsed[yScale.id]);
|
||||
|
||||
// Utility
|
||||
point._options = options;
|
||||
@ -170,43 +169,6 @@ module.exports = DatasetController.extend({
|
||||
return values;
|
||||
},
|
||||
|
||||
calculatePointY: function(value, index, datasetIndex) {
|
||||
var me = this;
|
||||
var chart = me.chart;
|
||||
var yScale = me._yScale;
|
||||
var sumPos = 0;
|
||||
var sumNeg = 0;
|
||||
var rightValue = +yScale.getRightValue(value);
|
||||
var metasets = chart._getSortedVisibleDatasetMetas();
|
||||
var ilen = metasets.length;
|
||||
var i, ds, dsMeta, stackedRightValue;
|
||||
|
||||
if (yScale.options.stacked) {
|
||||
for (i = 0; i < ilen; ++i) {
|
||||
dsMeta = metasets[i];
|
||||
if (dsMeta.index === datasetIndex) {
|
||||
break;
|
||||
}
|
||||
|
||||
ds = chart.data.datasets[dsMeta.index];
|
||||
if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id) {
|
||||
stackedRightValue = +yScale.getRightValue(ds.data[index]);
|
||||
if (stackedRightValue < 0) {
|
||||
sumNeg += stackedRightValue || 0;
|
||||
} else {
|
||||
sumPos += stackedRightValue || 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rightValue < 0) {
|
||||
return yScale.getPixelForValue(sumNeg + rightValue);
|
||||
}
|
||||
return yScale.getPixelForValue(sumPos + rightValue);
|
||||
}
|
||||
return yScale.getPixelForValue(value);
|
||||
},
|
||||
|
||||
updateBezierControlPoints: function() {
|
||||
var me = this;
|
||||
var chart = me.chart;
|
||||
|
||||
@ -108,8 +108,6 @@ module.exports = DatasetController.extend({
|
||||
|
||||
dataElementType: elements.Arc,
|
||||
|
||||
linkScales: helpers.noop,
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
|
||||
@ -24,8 +24,6 @@ module.exports = DatasetController.extend({
|
||||
|
||||
dataElementType: elements.Point,
|
||||
|
||||
linkScales: helpers.noop,
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
|
||||
@ -75,6 +75,85 @@ function unlistenArrayEvents(array, listener) {
|
||||
delete array._chartjs;
|
||||
}
|
||||
|
||||
function getSortedDatasetIndices(chart, filterVisible) {
|
||||
var keys = [];
|
||||
var metasets = chart._getSortedDatasetMetas(filterVisible);
|
||||
var i, ilen;
|
||||
|
||||
for (i = 0, ilen = metasets.length; i < ilen; ++i) {
|
||||
keys.push(metasets[i].index);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
function applyStack(stack, value, dsIndex, allOther) {
|
||||
var keys = stack.keys;
|
||||
var i, ilen, datasetIndex, otherValue;
|
||||
|
||||
for (i = 0, ilen = keys.length; i < ilen; ++i) {
|
||||
datasetIndex = +keys[i];
|
||||
if (datasetIndex === dsIndex) {
|
||||
if (allOther) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
otherValue = stack.values[datasetIndex];
|
||||
if (!isNaN(otherValue) && (value === 0 || Math.sign(value) === Math.sign(otherValue))) {
|
||||
value += otherValue;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function convertObjectDataToArray(data) {
|
||||
var keys = Object.keys(data);
|
||||
var adata = [];
|
||||
var i, ilen, key;
|
||||
for (i = 0, ilen = keys.length; i < ilen; ++i) {
|
||||
key = keys[i];
|
||||
adata.push({
|
||||
x: key,
|
||||
y: data[key]
|
||||
});
|
||||
}
|
||||
return adata;
|
||||
}
|
||||
|
||||
function isStacked(scale, meta) {
|
||||
var stacked = scale && scale.options.stacked;
|
||||
return stacked || (stacked === undefined && meta.stack !== undefined);
|
||||
}
|
||||
|
||||
function getStackKey(xScale, yScale, meta) {
|
||||
return isStacked(yScale, meta) && xScale.id + '.' + yScale.id + '.' + meta.stack + '.' + meta.type;
|
||||
}
|
||||
|
||||
function arraysEqual(array1, array2) {
|
||||
var ilen = array1.length;
|
||||
var i;
|
||||
|
||||
if (ilen !== array2.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i = 0; i < ilen; i++) {
|
||||
if (array1[i] !== array2[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function getFirstScaleId(chart, axis) {
|
||||
var scalesOpts = chart.options.scales;
|
||||
var scale = chart.options.scale;
|
||||
var scaleId = scale && scale.id;
|
||||
var prop = axis + 'Axes';
|
||||
|
||||
return (scalesOpts && scalesOpts[prop] && scalesOpts[prop].length && scalesOpts[prop][0].id) || scaleId;
|
||||
}
|
||||
|
||||
// Base class for all dataset controllers (line, bar, etc)
|
||||
var DatasetController = function(chart, datasetIndex) {
|
||||
this.initialize(chart, datasetIndex);
|
||||
@ -125,11 +204,14 @@ helpers.extend(DatasetController.prototype, {
|
||||
|
||||
initialize: function(chart, datasetIndex) {
|
||||
var me = this;
|
||||
var meta;
|
||||
me.chart = chart;
|
||||
me.index = datasetIndex;
|
||||
me._cachedMeta = meta = me.getMeta();
|
||||
me._type = meta.type;
|
||||
me.linkScales();
|
||||
meta._stacked = isStacked(me._getValueScale(), meta);
|
||||
me.addElements();
|
||||
me._type = me.getMeta().type;
|
||||
},
|
||||
|
||||
updateIndex: function(datasetIndex) {
|
||||
@ -137,19 +219,12 @@ helpers.extend(DatasetController.prototype, {
|
||||
},
|
||||
|
||||
linkScales: function() {
|
||||
var me = this;
|
||||
var meta = me.getMeta();
|
||||
var chart = me.chart;
|
||||
var scales = chart.scales;
|
||||
var dataset = me.getDataset();
|
||||
var scalesOpts = chart.options.scales;
|
||||
var chart = this.chart;
|
||||
var meta = this._cachedMeta;
|
||||
var dataset = this.getDataset();
|
||||
|
||||
if (meta.xAxisID === null || !(meta.xAxisID in scales) || dataset.xAxisID) {
|
||||
meta.xAxisID = dataset.xAxisID || scalesOpts.xAxes[0].id;
|
||||
}
|
||||
if (meta.yAxisID === null || !(meta.yAxisID in scales) || dataset.yAxisID) {
|
||||
meta.yAxisID = dataset.yAxisID || scalesOpts.yAxes[0].id;
|
||||
}
|
||||
meta.xAxisID = dataset.xAxisID || getFirstScaleId(chart, 'x');
|
||||
meta.yAxisID = dataset.yAxisID || getFirstScaleId(chart, 'y');
|
||||
},
|
||||
|
||||
getDataset: function() {
|
||||
@ -168,14 +243,14 @@ helpers.extend(DatasetController.prototype, {
|
||||
* @private
|
||||
*/
|
||||
_getValueScaleId: function() {
|
||||
return this.getMeta().yAxisID;
|
||||
return this._cachedMeta.yAxisID;
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_getIndexScaleId: function() {
|
||||
return this.getMeta().xAxisID;
|
||||
return this._cachedMeta.xAxisID;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -220,16 +295,76 @@ helpers.extend(DatasetController.prototype, {
|
||||
return type && new type({
|
||||
_ctx: me.chart.ctx,
|
||||
_datasetIndex: me.index,
|
||||
_index: index
|
||||
_index: index,
|
||||
_parsed: {}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_dataCheck: function() {
|
||||
var me = this;
|
||||
var dataset = me.getDataset();
|
||||
var data = dataset.data || (dataset.data = []);
|
||||
|
||||
// In order to correctly handle data addition/deletion animation (an thus simulate
|
||||
// real-time charts), we need to monitor these data modifications and synchronize
|
||||
// the internal meta data accordingly.
|
||||
|
||||
if (helpers.isObject(data)) {
|
||||
// Object data is currently monitored for replacement only
|
||||
if (me._objectData === data) {
|
||||
return false;
|
||||
}
|
||||
me._data = convertObjectDataToArray(data);
|
||||
me._objectData = data;
|
||||
} else {
|
||||
if (me._data === data && arraysEqual(data, me._dataCopy)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (me._data) {
|
||||
// This case happens when the user replaced the data array instance.
|
||||
unlistenArrayEvents(me._data, me);
|
||||
}
|
||||
|
||||
// Store a copy to detect direct modifications.
|
||||
// Note: This is suboptimal, but better than always parsing the data
|
||||
me._dataCopy = data.slice(0);
|
||||
|
||||
if (data && Object.isExtensible(data)) {
|
||||
listenArrayEvents(data, me);
|
||||
}
|
||||
me._data = data;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_labelCheck: function() {
|
||||
var me = this;
|
||||
var scale = me._getIndexScale();
|
||||
var labels = scale ? scale._getLabels() : me.chart.data.labels;
|
||||
|
||||
if (me._labels === labels) {
|
||||
return false;
|
||||
}
|
||||
|
||||
me._labels = labels;
|
||||
return true;
|
||||
},
|
||||
|
||||
addElements: function() {
|
||||
var me = this;
|
||||
var meta = me.getMeta();
|
||||
var data = me.getDataset().data || [];
|
||||
var meta = me._cachedMeta;
|
||||
var metaData = meta.data;
|
||||
var i, ilen;
|
||||
var i, ilen, data;
|
||||
|
||||
me._dataCheck();
|
||||
data = me._data;
|
||||
|
||||
for (i = 0, ilen = data.length; i < ilen; ++i) {
|
||||
metaData[i] = metaData[i] || me.createMetaData(i);
|
||||
@ -240,33 +375,23 @@ helpers.extend(DatasetController.prototype, {
|
||||
|
||||
addElementAndReset: function(index) {
|
||||
var element = this.createMetaData(index);
|
||||
this.getMeta().data.splice(index, 0, element);
|
||||
this._cachedMeta.data.splice(index, 0, element);
|
||||
this.updateElement(element, index, true);
|
||||
},
|
||||
|
||||
buildOrUpdateElements: function() {
|
||||
var me = this;
|
||||
var dataset = me.getDataset();
|
||||
var data = dataset.data || (dataset.data = []);
|
||||
var dataChanged = me._dataCheck();
|
||||
var labelsChanged = me._labelCheck();
|
||||
var scaleChanged = me._scaleCheck();
|
||||
var meta = me._cachedMeta;
|
||||
|
||||
// In order to correctly handle data addition/deletion animation (an thus simulate
|
||||
// real-time charts), we need to monitor these data modifications and synchronize
|
||||
// the internal meta data accordingly.
|
||||
if (me._data !== data) {
|
||||
if (me._data) {
|
||||
// This case happens when the user replaced the data array instance.
|
||||
unlistenArrayEvents(me._data, me);
|
||||
}
|
||||
|
||||
if (data && Object.isExtensible(data)) {
|
||||
listenArrayEvents(data, me);
|
||||
}
|
||||
me._data = data;
|
||||
}
|
||||
// make sure cached _stacked status is current
|
||||
meta._stacked = isStacked(me._getValueScale(), meta);
|
||||
|
||||
// Re-sync meta data in case the user replaced the data array or if we missed
|
||||
// any updates and so make sure that we handle number of datapoints changing.
|
||||
me.resyncElements();
|
||||
me.resyncElements(dataChanged | labelsChanged | scaleChanged);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -287,17 +412,256 @@ helpers.extend(DatasetController.prototype, {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_parse: function(start, count) {
|
||||
var me = this;
|
||||
var chart = me.chart;
|
||||
var meta = me._cachedMeta;
|
||||
var data = me._data;
|
||||
var crossRef = chart._xref || (chart._xref = {});
|
||||
var xScale = me._getIndexScale();
|
||||
var yScale = me._getValueScale();
|
||||
var xId = xScale.id;
|
||||
var yId = yScale.id;
|
||||
var xKey = getStackKey(xScale, yScale, meta);
|
||||
var yKey = getStackKey(yScale, xScale, meta);
|
||||
var stacks = xKey || yKey;
|
||||
var i, ilen, parsed, stack, item, x, y;
|
||||
|
||||
if (helpers.isArray(data[start])) {
|
||||
parsed = me._parseArrayData(meta, data, start, count);
|
||||
} else if (helpers.isObject(data[start])) {
|
||||
parsed = me._parseObjectData(meta, data, start, count);
|
||||
} else {
|
||||
parsed = me._parsePrimitiveData(meta, data, start, count);
|
||||
}
|
||||
|
||||
function storeStack(stackKey, indexValue, scaleId, value) {
|
||||
if (stackKey) {
|
||||
stackKey += '.' + indexValue;
|
||||
item._stackKeys[scaleId] = stackKey;
|
||||
stack = crossRef[stackKey] || (crossRef[stackKey] = {});
|
||||
stack[meta.index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0, ilen = parsed.length; i < ilen; ++i) {
|
||||
item = parsed[i];
|
||||
meta.data[start + i]._parsed = item;
|
||||
|
||||
if (stacks) {
|
||||
item._stackKeys = {};
|
||||
x = item[xId];
|
||||
y = item[yId];
|
||||
|
||||
storeStack(xKey, x, yId, y);
|
||||
storeStack(yKey, y, xId, x);
|
||||
}
|
||||
}
|
||||
|
||||
xScale._invalidateCaches();
|
||||
if (yScale !== xScale) {
|
||||
yScale._invalidateCaches();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse array of primitive values
|
||||
* @param {object} meta - dataset meta
|
||||
* @param {array} data - data array. Example [1,3,4]
|
||||
* @param {number} start - start index
|
||||
* @param {number} count - number of items to parse
|
||||
* @returns {object} parsed item - item containing index and a parsed value
|
||||
* for each scale id.
|
||||
* Example: {xScale0: 0, yScale0: 1}
|
||||
* @private
|
||||
*/
|
||||
_parsePrimitiveData: function(meta, data, start, count) {
|
||||
var iScale = this._getIndexScale();
|
||||
var vScale = this._getValueScale();
|
||||
var labels = iScale._getLabels();
|
||||
var singleScale = iScale === vScale;
|
||||
var parsed = [];
|
||||
var i, ilen, item;
|
||||
|
||||
for (i = start, ilen = start + count; i < ilen; ++i) {
|
||||
item = {};
|
||||
item[iScale.id] = singleScale || iScale._parse(labels[i], i);
|
||||
item[vScale.id] = vScale._parse(data[i], i);
|
||||
parsed.push(item);
|
||||
}
|
||||
return parsed;
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse array of arrays
|
||||
* @param {object} meta - dataset meta
|
||||
* @param {array} data - data array. Example [[1,2],[3,4]]
|
||||
* @param {number} start - start index
|
||||
* @param {number} count - number of items to parse
|
||||
* @returns {object} parsed item - item containing index and a parsed value
|
||||
* for each scale id.
|
||||
* Example: {xScale0: 0, yScale0: 1}
|
||||
* @private
|
||||
*/
|
||||
_parseArrayData: function(meta, data, start, count) {
|
||||
var xScale = this.getScaleForId(meta.xAxisID);
|
||||
var yScale = this.getScaleForId(meta.yAxisID);
|
||||
var parsed = [];
|
||||
var i, ilen, item, arr;
|
||||
for (i = start, ilen = start + count; i < ilen; ++i) {
|
||||
arr = data[i];
|
||||
item = {};
|
||||
item[xScale.id] = xScale._parse(arr[0], i);
|
||||
item[yScale.id] = yScale._parse(arr[1], i);
|
||||
parsed.push(item);
|
||||
}
|
||||
return parsed;
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse array of objects
|
||||
* @param {object} meta - dataset meta
|
||||
* @param {array} data - data array. Example [{x:1, y:5}, {x:2, y:10}]
|
||||
* @param {number} start - start index
|
||||
* @param {number} count - number of items to parse
|
||||
* @returns {object} parsed item - item containing index and a parsed value
|
||||
* for each scale id. _custom is optional
|
||||
* Example: {xScale0: 0, yScale0: 1, _custom: {r: 10, foo: 'bar'}}
|
||||
* @private
|
||||
*/
|
||||
_parseObjectData: function(meta, data, start, count) {
|
||||
var xScale = this.getScaleForId(meta.xAxisID);
|
||||
var yScale = this.getScaleForId(meta.yAxisID);
|
||||
var parsed = [];
|
||||
var i, ilen, item, obj;
|
||||
for (i = start, ilen = start + count; i < ilen; ++i) {
|
||||
obj = data[i];
|
||||
item = {};
|
||||
item[xScale.id] = xScale._parseObject(obj, 'x', i);
|
||||
item[yScale.id] = yScale._parseObject(obj, 'y', i);
|
||||
parsed.push(item);
|
||||
}
|
||||
return parsed;
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_getParsed: function(index) {
|
||||
var data = this._cachedMeta.data;
|
||||
if (index < 0 || index >= data.length) {
|
||||
return;
|
||||
}
|
||||
return data[index]._parsed;
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_applyStack: function(scale, parsed) {
|
||||
var chart = this.chart;
|
||||
var meta = this._cachedMeta;
|
||||
var value = parsed[scale.id];
|
||||
var stack = {
|
||||
keys: getSortedDatasetIndices(chart, true),
|
||||
values: chart._xref[parsed._stackKeys[scale.id]]
|
||||
};
|
||||
return applyStack(stack, value, meta.index);
|
||||
},
|
||||
|
||||
_getMinMax: function(scale, canStack) {
|
||||
var chart = this.chart;
|
||||
var meta = this._cachedMeta;
|
||||
var metaData = meta.data;
|
||||
var ilen = metaData.length;
|
||||
var crossRef = chart._xref || (chart._xref = {});
|
||||
var max = Number.NEGATIVE_INFINITY;
|
||||
var stacked = canStack && meta._stacked;
|
||||
var indices = getSortedDatasetIndices(chart, true);
|
||||
var i, item, value, parsed, stack, min, minPositive;
|
||||
|
||||
min = minPositive = Number.POSITIVE_INFINITY;
|
||||
|
||||
for (i = 0; i < ilen; ++i) {
|
||||
item = metaData[i];
|
||||
parsed = item._parsed;
|
||||
value = parsed[scale.id];
|
||||
if (item.hidden || isNaN(value)) {
|
||||
continue;
|
||||
}
|
||||
if (stacked) {
|
||||
stack = {
|
||||
keys: indices,
|
||||
values: crossRef[parsed._stackKeys[scale.id]]
|
||||
};
|
||||
value = applyStack(stack, value, meta.index, true);
|
||||
}
|
||||
min = Math.min(min, value);
|
||||
max = Math.max(max, value);
|
||||
if (value > 0) {
|
||||
minPositive = Math.min(minPositive, value);
|
||||
}
|
||||
}
|
||||
return {
|
||||
min: min,
|
||||
max: max,
|
||||
minPositive: minPositive
|
||||
};
|
||||
},
|
||||
|
||||
_getAllParsedValues: function(scale) {
|
||||
var meta = this._cachedMeta;
|
||||
var metaData = meta.data;
|
||||
var values = [];
|
||||
var i, ilen, value;
|
||||
|
||||
for (i = 0, ilen = metaData.length; i < ilen; ++i) {
|
||||
value = metaData[i]._parsed[scale.id];
|
||||
if (!isNaN(value)) {
|
||||
values.push(value);
|
||||
}
|
||||
}
|
||||
return values;
|
||||
},
|
||||
|
||||
_cacheScaleStackStatus: function() {
|
||||
var me = this;
|
||||
var indexScale = me._getIndexScale();
|
||||
var valueScale = me._getValueScale();
|
||||
var cache = me._scaleStacked = {};
|
||||
if (indexScale && valueScale) {
|
||||
cache[indexScale.id] = indexScale.options.stacked;
|
||||
cache[valueScale.id] = valueScale.options.stacked;
|
||||
}
|
||||
},
|
||||
|
||||
_scaleCheck: function() {
|
||||
var me = this;
|
||||
var indexScale = me._getIndexScale();
|
||||
var valueScale = me._getValueScale();
|
||||
var cache = me._scaleStacked;
|
||||
return !cache ||
|
||||
!indexScale ||
|
||||
!valueScale ||
|
||||
cache[indexScale.id] !== indexScale.options.stacked ||
|
||||
cache[valueScale.id] !== valueScale.options.stacked;
|
||||
},
|
||||
|
||||
_update: function(reset) {
|
||||
var me = this;
|
||||
me._configure();
|
||||
me._cachedDataOpts = null;
|
||||
me.update(reset);
|
||||
me._cacheScaleStackStatus();
|
||||
},
|
||||
|
||||
update: helpers.noop,
|
||||
|
||||
transition: function(easingValue) {
|
||||
var meta = this.getMeta();
|
||||
var meta = this._cachedMeta;
|
||||
var elements = meta.data || [];
|
||||
var ilen = elements.length;
|
||||
var i = 0;
|
||||
@ -312,7 +676,7 @@ helpers.extend(DatasetController.prototype, {
|
||||
},
|
||||
|
||||
draw: function() {
|
||||
var meta = this.getMeta();
|
||||
var meta = this._cachedMeta;
|
||||
var elements = meta.data || [];
|
||||
var ilen = elements.length;
|
||||
var i = 0;
|
||||
@ -334,7 +698,7 @@ helpers.extend(DatasetController.prototype, {
|
||||
*/
|
||||
getStyle: function(index) {
|
||||
var me = this;
|
||||
var meta = me.getMeta();
|
||||
var meta = me._cachedMeta;
|
||||
var dataset = meta.dataset;
|
||||
var style;
|
||||
|
||||
@ -501,17 +865,19 @@ helpers.extend(DatasetController.prototype, {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
resyncElements: function() {
|
||||
resyncElements: function(changed) {
|
||||
var me = this;
|
||||
var meta = me.getMeta();
|
||||
var data = me.getDataset().data;
|
||||
var meta = me._cachedMeta;
|
||||
var numMeta = meta.data.length;
|
||||
var numData = data.length;
|
||||
var numData = me._data.length;
|
||||
|
||||
if (numData < numMeta) {
|
||||
meta.data.splice(numData, numMeta - numData);
|
||||
} else if (numData > numMeta) {
|
||||
if (numData > numMeta) {
|
||||
me.insertElements(numMeta, numData - numMeta);
|
||||
} else if (numData < numMeta) {
|
||||
meta.data.splice(numData, numMeta - numData);
|
||||
me._parse(0, numData);
|
||||
} else if (changed) {
|
||||
me._parse(0, numData);
|
||||
}
|
||||
},
|
||||
|
||||
@ -522,6 +888,7 @@ helpers.extend(DatasetController.prototype, {
|
||||
for (var i = 0; i < count; ++i) {
|
||||
this.addElementAndReset(start + i);
|
||||
}
|
||||
this._parse(start, count);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -536,21 +903,21 @@ helpers.extend(DatasetController.prototype, {
|
||||
* @private
|
||||
*/
|
||||
onDataPop: function() {
|
||||
this.getMeta().data.pop();
|
||||
this._cachedMeta.data.pop();
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
onDataShift: function() {
|
||||
this.getMeta().data.shift();
|
||||
this._cachedMeta.data.shift();
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
onDataSplice: function(start, count) {
|
||||
this.getMeta().data.splice(start, count);
|
||||
this._cachedMeta.data.splice(start, count);
|
||||
this.insertElements(start, arguments.length - 2);
|
||||
},
|
||||
|
||||
|
||||
@ -327,6 +327,56 @@ var Scale = Element.extend({
|
||||
|
||||
zeroLineIndex: 0,
|
||||
|
||||
/**
|
||||
* Parse a supported input value to internal representation.
|
||||
* @param {*} raw
|
||||
* @param {number} index
|
||||
* @private
|
||||
* @since 3.0
|
||||
*/
|
||||
_parse: function(raw, index) { // eslint-disable-line no-unused-vars
|
||||
return raw;
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse an object for axis to internal representation.
|
||||
* @param {object} obj
|
||||
* @param {string} axis
|
||||
* @param {number} index
|
||||
* @private
|
||||
* @since 3.0
|
||||
*/
|
||||
_parseObject: function(obj, axis, index) {
|
||||
if (obj[axis] !== undefined) {
|
||||
return this._parse(obj[axis], index);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
_getMinMax: function(canStack) {
|
||||
var me = this;
|
||||
var metas = me._getMatchingVisibleMetas();
|
||||
var min = Number.POSITIVE_INFINITY;
|
||||
var max = Number.NEGATIVE_INFINITY;
|
||||
var minPositive = Number.POSITIVE_INFINITY;
|
||||
var i, ilen, minmax;
|
||||
|
||||
for (i = 0, ilen = metas.length; i < ilen; ++i) {
|
||||
minmax = metas[i].controller._getMinMax(me, canStack);
|
||||
min = Math.min(min, minmax.min);
|
||||
max = Math.max(max, minmax.max);
|
||||
minPositive = Math.min(minPositive, minmax.minPositive);
|
||||
}
|
||||
|
||||
return {
|
||||
min: min,
|
||||
max: max,
|
||||
minPositive: minPositive
|
||||
};
|
||||
},
|
||||
|
||||
_invalidateCaches: helpers.noop,
|
||||
|
||||
/**
|
||||
* Get the padding needed for the scale
|
||||
* @method getPadding
|
||||
@ -734,32 +784,6 @@ var Scale = Element.extend({
|
||||
return this.options.fullWidth;
|
||||
},
|
||||
|
||||
// Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not
|
||||
getRightValue: function(rawValue) {
|
||||
// Null and undefined values first
|
||||
if (isNullOrUndef(rawValue)) {
|
||||
return NaN;
|
||||
}
|
||||
// isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values
|
||||
if ((typeof rawValue === 'number' || rawValue instanceof Number) && !isFinite(rawValue)) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
// If it is in fact an object, dive in one more level
|
||||
if (rawValue) {
|
||||
if (this.isHorizontal()) {
|
||||
if (rawValue.x !== undefined) {
|
||||
return this.getRightValue(rawValue.x);
|
||||
}
|
||||
} else if (rawValue.y !== undefined) {
|
||||
return this.getRightValue(rawValue.y);
|
||||
}
|
||||
}
|
||||
|
||||
// Value is good, return it
|
||||
return rawValue;
|
||||
},
|
||||
|
||||
_convertTicksToLabels: function(ticks) {
|
||||
var me = this;
|
||||
|
||||
@ -786,51 +810,13 @@ var Scale = Element.extend({
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Used to get the label to display in the tooltip for the given value
|
||||
* @param value
|
||||
*/
|
||||
_parseValue: function(value) {
|
||||
var start, end, min, max;
|
||||
|
||||
if (isArray(value)) {
|
||||
start = +this.getRightValue(value[0]);
|
||||
end = +this.getRightValue(value[1]);
|
||||
min = Math.min(start, end);
|
||||
max = Math.max(start, end);
|
||||
} else {
|
||||
value = +this.getRightValue(value);
|
||||
start = undefined;
|
||||
end = value;
|
||||
min = value;
|
||||
max = value;
|
||||
}
|
||||
|
||||
return {
|
||||
min: min,
|
||||
max: max,
|
||||
start: start,
|
||||
end: end
|
||||
};
|
||||
getLabelForValue: function(value) {
|
||||
return value;
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_getScaleLabel: function(rawValue) {
|
||||
var v = this._parseValue(rawValue);
|
||||
if (v.start !== undefined) {
|
||||
return '[' + v.start + ', ' + v.end + ']';
|
||||
}
|
||||
|
||||
return +this.getRightValue(rawValue);
|
||||
},
|
||||
|
||||
/**
|
||||
* Used to get the value to display in the tooltip for the data at the given index
|
||||
* @param index
|
||||
* @param datasetIndex
|
||||
*/
|
||||
getLabelForIndex: helpers.noop,
|
||||
|
||||
/**
|
||||
* Returns the location of the given data point. Value can either be an index or a numerical value
|
||||
* The coordinate (0, 0) is at the upper-left corner of the canvas
|
||||
@ -963,26 +949,13 @@ var Scale = Element.extend({
|
||||
* @private
|
||||
*/
|
||||
_isVisible: function() {
|
||||
var me = this;
|
||||
var chart = me.chart;
|
||||
var display = me.options.display;
|
||||
var i, ilen, meta;
|
||||
var display = this.options.display;
|
||||
|
||||
if (display !== 'auto') {
|
||||
return !!display;
|
||||
}
|
||||
|
||||
// When 'auto', the scale is visible if at least one associated dataset is visible.
|
||||
for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {
|
||||
if (chart.isDatasetVisible(i)) {
|
||||
meta = chart.getDatasetMeta(i);
|
||||
if (meta.xAxisID === me.id || meta.yAxisID === me.id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return this._getMatchingVisibleMetas().length > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1402,14 +1375,29 @@ var Scale = Element.extend({
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_getAxisID: function() {
|
||||
return this.isHorizontal() ? 'xAxisID' : 'yAxisID';
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns visible dataset metas that are attached to this scale
|
||||
* @param {string} [type] - if specified, also filter by dataset type
|
||||
* @private
|
||||
*/
|
||||
_getMatchingVisibleMetas: function(type) {
|
||||
var me = this;
|
||||
var isHorizontal = me.isHorizontal();
|
||||
return me.chart._getSortedVisibleDatasetMetas()
|
||||
.filter(function(meta) {
|
||||
return (!type || meta.type === type)
|
||||
&& (isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id);
|
||||
});
|
||||
var metas = me.chart._getSortedVisibleDatasetMetas();
|
||||
var axisID = me._getAxisID();
|
||||
var result = [];
|
||||
var i, ilen, meta;
|
||||
|
||||
for (i = 0, ilen = metas.length; i < ilen; ++i) {
|
||||
meta = metas[i];
|
||||
if (meta[axisID] === me.id && (!type || meta.type === type)) {
|
||||
result.push(meta);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -210,10 +210,11 @@ function createTooltipItem(chart, element) {
|
||||
var controller = chart.getDatasetMeta(datasetIndex).controller;
|
||||
var indexScale = controller._getIndexScale();
|
||||
var valueScale = controller._getValueScale();
|
||||
var parsed = controller._getParsed(index);
|
||||
|
||||
return {
|
||||
label: indexScale ? '' + indexScale.getLabelForIndex(index, datasetIndex) : '',
|
||||
value: valueScale ? '' + valueScale.getLabelForIndex(index, datasetIndex) : '',
|
||||
label: indexScale ? '' + indexScale.getLabelForValue(parsed[indexScale.id]) : '',
|
||||
value: valueScale ? '' + valueScale.getLabelForValue(parsed[valueScale.id]) : '',
|
||||
index: index,
|
||||
datasetIndex: datasetIndex,
|
||||
x: element._model.x,
|
||||
|
||||
@ -1,15 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
var helpers = require('../helpers/index');
|
||||
var Scale = require('../core/core.scale');
|
||||
|
||||
var isNullOrUndef = helpers.isNullOrUndef;
|
||||
|
||||
var defaultConfig = {
|
||||
position: 'bottom'
|
||||
};
|
||||
|
||||
module.exports = Scale.extend({
|
||||
|
||||
_parse: function(raw, index) {
|
||||
var labels = this._getLabels();
|
||||
var first = labels.indexOf(raw);
|
||||
var last = labels.lastIndexOf(raw);
|
||||
return first === -1 || first !== last ? index : first;
|
||||
},
|
||||
|
||||
_parseObject: function(obj, axis, index) {
|
||||
if (obj[axis] !== undefined) {
|
||||
return this._parse(obj[axis], index);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
determineDataLimits: function() {
|
||||
var me = this;
|
||||
var labels = me._getLabels();
|
||||
@ -55,15 +67,14 @@ module.exports = Scale.extend({
|
||||
});
|
||||
},
|
||||
|
||||
getLabelForIndex: function(index, datasetIndex) {
|
||||
getLabelForValue: function(value) {
|
||||
var me = this;
|
||||
var chart = me.chart;
|
||||
var labels = me._getLabels();
|
||||
|
||||
if (chart.getDatasetMeta(datasetIndex).controller._getValueScaleId() === me.id) {
|
||||
return me.getRightValue(chart.data.datasets[datasetIndex].data[index]);
|
||||
if (value >= 0 && value < labels.length) {
|
||||
return labels[value];
|
||||
}
|
||||
|
||||
return me._getLabels()[index];
|
||||
return value;
|
||||
},
|
||||
|
||||
_configure: function() {
|
||||
@ -87,36 +98,21 @@ module.exports = Scale.extend({
|
||||
},
|
||||
|
||||
// Used to get data value locations. Value can either be an index or a numerical value
|
||||
getPixelForValue: function(value, index, datasetIndex) {
|
||||
getPixelForValue: function(value) {
|
||||
var me = this;
|
||||
var valueCategory, labels, idx;
|
||||
|
||||
if (!isNullOrUndef(index) && !isNullOrUndef(datasetIndex)) {
|
||||
value = me.chart.data.datasets[datasetIndex].data[index];
|
||||
if (typeof value !== 'number') {
|
||||
value = me._parse(value);
|
||||
}
|
||||
|
||||
// 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.
|
||||
if (!isNullOrUndef(value)) {
|
||||
valueCategory = me.isHorizontal() ? value.x : value.y;
|
||||
}
|
||||
if (valueCategory !== undefined || (value !== undefined && isNaN(index))) {
|
||||
labels = me._getLabels();
|
||||
value = helpers.valueOrDefault(valueCategory, value);
|
||||
idx = labels.indexOf(value);
|
||||
index = idx !== -1 ? idx : index;
|
||||
if (isNaN(index)) {
|
||||
index = value;
|
||||
}
|
||||
}
|
||||
return me.getPixelForDecimal((index - me._startValue) / me._valueRange);
|
||||
return me.getPixelForDecimal((value - me._startValue) / me._valueRange);
|
||||
},
|
||||
|
||||
getPixelForTick: function(index) {
|
||||
var ticks = this.ticks;
|
||||
return index < 0 || index > ticks.length - 1
|
||||
? null
|
||||
: this.getPixelForValue(ticks[index], index + this.minIndex);
|
||||
: this.getPixelForValue(index + this.minIndex);
|
||||
},
|
||||
|
||||
getValueForPixel: function(pixel) {
|
||||
|
||||
@ -11,108 +11,23 @@ var defaultConfig = {
|
||||
}
|
||||
};
|
||||
|
||||
var DEFAULT_MIN = 0;
|
||||
var DEFAULT_MAX = 1;
|
||||
|
||||
function getOrCreateStack(stacks, stacked, meta) {
|
||||
var key = [
|
||||
meta.type,
|
||||
// we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
|
||||
stacked === undefined && meta.stack === undefined ? meta.index : '',
|
||||
meta.stack
|
||||
].join('.');
|
||||
|
||||
if (stacks[key] === undefined) {
|
||||
stacks[key] = {
|
||||
pos: [],
|
||||
neg: []
|
||||
};
|
||||
}
|
||||
|
||||
return stacks[key];
|
||||
}
|
||||
|
||||
function stackData(scale, stacks, meta, data) {
|
||||
var opts = scale.options;
|
||||
var stacked = opts.stacked;
|
||||
var stack = getOrCreateStack(stacks, stacked, meta);
|
||||
var pos = stack.pos;
|
||||
var neg = stack.neg;
|
||||
var ilen = data.length;
|
||||
var i, value;
|
||||
|
||||
for (i = 0; i < ilen; ++i) {
|
||||
value = scale._parseValue(data[i]);
|
||||
if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pos[i] = pos[i] || 0;
|
||||
neg[i] = neg[i] || 0;
|
||||
|
||||
if (value.min < 0 || value.max < 0) {
|
||||
neg[i] += value.min;
|
||||
} else {
|
||||
pos[i] += value.max;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateMinMax(scale, meta, data) {
|
||||
var ilen = data.length;
|
||||
var i, value;
|
||||
|
||||
for (i = 0; i < ilen; ++i) {
|
||||
value = scale._parseValue(data[i]);
|
||||
if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden) {
|
||||
continue;
|
||||
}
|
||||
|
||||
scale.min = Math.min(scale.min, value.min);
|
||||
scale.max = Math.max(scale.max, value.max);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LinearScaleBase.extend({
|
||||
determineDataLimits: function() {
|
||||
var me = this;
|
||||
var opts = me.options;
|
||||
var chart = me.chart;
|
||||
var datasets = chart.data.datasets;
|
||||
var metasets = me._getMatchingVisibleMetas();
|
||||
var hasStacks = opts.stacked;
|
||||
var stacks = {};
|
||||
var ilen = metasets.length;
|
||||
var i, meta, data, values;
|
||||
var DEFAULT_MIN = 0;
|
||||
var DEFAULT_MAX = 1;
|
||||
var minmax = me._getMinMax(true);
|
||||
var min = minmax.min;
|
||||
var max = minmax.max;
|
||||
|
||||
me.min = Number.POSITIVE_INFINITY;
|
||||
me.max = Number.NEGATIVE_INFINITY;
|
||||
me.min = helpers.isFinite(min) && !isNaN(min) ? min : DEFAULT_MIN;
|
||||
me.max = helpers.isFinite(max) && !isNaN(max) ? max : DEFAULT_MAX;
|
||||
|
||||
if (hasStacks === undefined) {
|
||||
for (i = 0; !hasStacks && i < ilen; ++i) {
|
||||
meta = metasets[i];
|
||||
hasStacks = meta.stack !== undefined;
|
||||
}
|
||||
// Backward compatible inconsistent min for stacked
|
||||
if (me.options.stacked && min > 0) {
|
||||
me.min = 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < ilen; ++i) {
|
||||
meta = metasets[i];
|
||||
data = datasets[meta.index].data;
|
||||
if (hasStacks) {
|
||||
stackData(me, stacks, meta, data);
|
||||
} else {
|
||||
updateMinMax(me, meta, data);
|
||||
}
|
||||
}
|
||||
|
||||
helpers.each(stacks, function(stackValues) {
|
||||
values = stackValues.pos.concat(stackValues.neg);
|
||||
helpers._setMinAndMax(values, me);
|
||||
});
|
||||
|
||||
me.min = helpers.isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN;
|
||||
me.max = helpers.isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX;
|
||||
|
||||
// Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
|
||||
me.handleTickRangeOptions();
|
||||
},
|
||||
@ -138,14 +53,10 @@ module.exports = LinearScaleBase.extend({
|
||||
return this.isHorizontal() ? ticks : ticks.reverse();
|
||||
},
|
||||
|
||||
getLabelForIndex: function(index, datasetIndex) {
|
||||
return this._getScaleLabel(this.chart.data.datasets[datasetIndex].data[index]);
|
||||
},
|
||||
|
||||
// Utils
|
||||
getPixelForValue: function(value) {
|
||||
var me = this;
|
||||
return me.getPixelForDecimal((+me.getRightValue(value) - me._startValue) / me._valueRange);
|
||||
return me.getPixelForDecimal((value - me._startValue) / me._valueRange);
|
||||
},
|
||||
|
||||
getValueForPixel: function(pixel) {
|
||||
|
||||
@ -84,11 +84,15 @@ function generateTicks(generationOptions, dataRange) {
|
||||
}
|
||||
|
||||
module.exports = Scale.extend({
|
||||
getRightValue: function(value) {
|
||||
if (typeof value === 'string') {
|
||||
return +value;
|
||||
_parse: function(raw) {
|
||||
if (helpers.isNullOrUndef(raw)) {
|
||||
return NaN;
|
||||
}
|
||||
return Scale.prototype.getRightValue.call(this, value);
|
||||
if ((typeof raw === 'number' || raw instanceof Number) && !isFinite(raw)) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
return +raw;
|
||||
},
|
||||
|
||||
handleTickRangeOptions: function() {
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
var defaults = require('../core/core.defaults');
|
||||
var helpers = require('../helpers/index');
|
||||
var Scale = require('../core/core.scale');
|
||||
var LinearScaleBase = require('./scale.linearbase');
|
||||
var Ticks = require('../core/core.ticks');
|
||||
|
||||
var valueOrDefault = helpers.valueOrDefault;
|
||||
@ -69,100 +70,20 @@ function nonNegativeOrDefault(value, defaultValue) {
|
||||
}
|
||||
|
||||
module.exports = Scale.extend({
|
||||
_parse: LinearScaleBase.prototype._parse,
|
||||
|
||||
determineDataLimits: function() {
|
||||
var me = this;
|
||||
var opts = me.options;
|
||||
var chart = me.chart;
|
||||
var datasets = chart.data.datasets;
|
||||
var isHorizontal = me.isHorizontal();
|
||||
function IDMatches(meta) {
|
||||
return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
|
||||
}
|
||||
var datasetIndex, meta, value, data, i, ilen;
|
||||
var minmax = me._getMinMax(true);
|
||||
var min = minmax.min;
|
||||
var max = minmax.max;
|
||||
var minPositive = minmax.minPositive;
|
||||
|
||||
// Calculate Range
|
||||
me.min = Number.POSITIVE_INFINITY;
|
||||
me.max = Number.NEGATIVE_INFINITY;
|
||||
me.minNotZero = Number.POSITIVE_INFINITY;
|
||||
me.min = helpers.isFinite(min) ? Math.max(0, min) : null;
|
||||
me.max = helpers.isFinite(max) ? Math.max(0, max) : null;
|
||||
me.minNotZero = helpers.isFinite(minPositive) ? minPositive : null;
|
||||
|
||||
var hasStacks = opts.stacked;
|
||||
if (hasStacks === undefined) {
|
||||
for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) {
|
||||
meta = chart.getDatasetMeta(datasetIndex);
|
||||
if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
|
||||
meta.stack !== undefined) {
|
||||
hasStacks = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.stacked || hasStacks) {
|
||||
var valuesPerStack = {};
|
||||
|
||||
for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) {
|
||||
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] = [];
|
||||
}
|
||||
|
||||
data = datasets[datasetIndex].data;
|
||||
for (i = 0, ilen = data.length; i < ilen; i++) {
|
||||
var values = valuesPerStack[key];
|
||||
value = me._parseValue(data[i]);
|
||||
// invalid, hidden and negative values are ignored
|
||||
if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden || value.min < 0 || value.max < 0) {
|
||||
continue;
|
||||
}
|
||||
values[i] = values[i] || 0;
|
||||
values[i] += value.max;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
helpers.each(valuesPerStack, function(valuesForType) {
|
||||
if (valuesForType.length > 0) {
|
||||
helpers._setMinAndMax(valuesForType, me);
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) {
|
||||
meta = chart.getDatasetMeta(datasetIndex);
|
||||
if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
|
||||
data = datasets[datasetIndex].data;
|
||||
for (i = 0, ilen = data.length; i < ilen; i++) {
|
||||
value = me._parseValue(data[i]);
|
||||
// invalid, hidden and negative values are ignored
|
||||
if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden || value.min < 0 || value.max < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
me.min = Math.min(value.min, me.min);
|
||||
me.max = Math.max(value.max, me.max);
|
||||
|
||||
if (value.min !== 0) {
|
||||
me.minNotZero = Math.min(value.min, me.minNotZero);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
me.min = helpers.isFinite(me.min) ? me.min : null;
|
||||
me.max = helpers.isFinite(me.max) ? me.max : null;
|
||||
me.minNotZero = helpers.isFinite(me.minNotZero) ? me.minNotZero : null;
|
||||
|
||||
// Common base implementation to handle ticks.min, ticks.max
|
||||
this.handleTickRangeOptions();
|
||||
me.handleTickRangeOptions();
|
||||
},
|
||||
|
||||
handleTickRangeOptions: function() {
|
||||
@ -237,11 +158,6 @@ module.exports = Scale.extend({
|
||||
return Scale.prototype.generateTickLabels.call(this, ticks);
|
||||
},
|
||||
|
||||
// Get the correct tooltip label
|
||||
getLabelForIndex: function(index, datasetIndex) {
|
||||
return this._getScaleLabel(this.chart.data.datasets[datasetIndex].data[index]);
|
||||
},
|
||||
|
||||
getPixelForTick: function(index) {
|
||||
var ticks = this._tickValues;
|
||||
if (index < 0 || index > ticks.length - 1) {
|
||||
@ -284,8 +200,6 @@ module.exports = Scale.extend({
|
||||
var me = this;
|
||||
var decimal = 0;
|
||||
|
||||
value = +me.getRightValue(value);
|
||||
|
||||
if (value > me.min && value > 0) {
|
||||
decimal = (log10(value) - me._startValue) / me._valueRange + me._valueOffset;
|
||||
}
|
||||
|
||||
@ -305,28 +305,12 @@ module.exports = LinearScaleBase.extend({
|
||||
|
||||
determineDataLimits: function() {
|
||||
var me = this;
|
||||
var chart = me.chart;
|
||||
var min = Number.POSITIVE_INFINITY;
|
||||
var max = Number.NEGATIVE_INFINITY;
|
||||
var minmax = me._getMinMax(false);
|
||||
var min = minmax.min;
|
||||
var max = minmax.max;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
min = Math.min(value, min);
|
||||
max = Math.max(value, max);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
me.min = (min === Number.POSITIVE_INFINITY ? 0 : min);
|
||||
me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max);
|
||||
me.min = helpers.isFinite(min) && !isNaN(min) ? min : 0;
|
||||
me.max = helpers.isFinite(max) && !isNaN(max) ? max : 0;
|
||||
|
||||
// Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
|
||||
me.handleTickRangeOptions();
|
||||
@ -349,10 +333,6 @@ module.exports = LinearScaleBase.extend({
|
||||
});
|
||||
},
|
||||
|
||||
getLabelForIndex: function(index, datasetIndex) {
|
||||
return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
|
||||
},
|
||||
|
||||
fit: function() {
|
||||
var me = this;
|
||||
var opts = me.options;
|
||||
|
||||
@ -9,7 +9,6 @@ var resolve = helpers.options.resolve;
|
||||
var valueOrDefault = helpers.valueOrDefault;
|
||||
|
||||
// Integer constants are from the ES6 spec.
|
||||
var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991;
|
||||
var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
|
||||
|
||||
var INTERVALS = {
|
||||
@ -204,7 +203,7 @@ function parse(scale, input) {
|
||||
}
|
||||
|
||||
var options = scale.options.time;
|
||||
var value = toTimestamp(scale, scale.getRightValue(input));
|
||||
var value = toTimestamp(scale, input);
|
||||
if (value === null) {
|
||||
return value;
|
||||
}
|
||||
@ -365,6 +364,89 @@ function ticksFromTimestamps(scale, values, majorUnit) {
|
||||
return (ilen === 0 || !majorUnit) ? ticks : setMajorTicks(scale, ticks, map, majorUnit);
|
||||
}
|
||||
|
||||
|
||||
function getDataTimestamps(scale) {
|
||||
var timestamps = scale._cache.data || [];
|
||||
var i, ilen, metas;
|
||||
|
||||
if (!timestamps.length) {
|
||||
metas = scale._getMatchingVisibleMetas();
|
||||
for (i = 0, ilen = metas.length; i < ilen; ++i) {
|
||||
timestamps = timestamps.concat(metas[i].controller._getAllParsedValues(scale));
|
||||
}
|
||||
timestamps = scale._cache.data = arrayUnique(timestamps).sort(sorter);
|
||||
}
|
||||
return timestamps;
|
||||
}
|
||||
|
||||
function getLabelTimestamps(scale) {
|
||||
var timestamps = scale._cache.labels || [];
|
||||
var i, ilen, labels;
|
||||
|
||||
if (!timestamps.length) {
|
||||
labels = scale._getLabels();
|
||||
for (i = 0, ilen = labels.length; i < ilen; ++i) {
|
||||
timestamps.push(parse(scale, labels[i]));
|
||||
}
|
||||
timestamps = scale._cache.labels = arrayUnique(timestamps).sort(sorter);
|
||||
}
|
||||
return timestamps;
|
||||
}
|
||||
|
||||
function getAllTimestamps(scale) {
|
||||
var timestamps = scale._cache.all || [];
|
||||
|
||||
if (!timestamps.length) {
|
||||
timestamps = getDataTimestamps(scale).concat(getLabelTimestamps(scale));
|
||||
timestamps = scale._cache.all = arrayUnique(timestamps).sort(sorter);
|
||||
}
|
||||
return timestamps;
|
||||
}
|
||||
|
||||
|
||||
function getTimestampsForTicks(scale) {
|
||||
var min = scale.min;
|
||||
var max = scale.max;
|
||||
var options = scale.options;
|
||||
var capacity = scale.getLabelCapacity(min);
|
||||
var source = options.ticks.source;
|
||||
var timestamps;
|
||||
|
||||
if (source === 'data' || (source === 'auto' && options.distribution === 'series')) {
|
||||
timestamps = getAllTimestamps(scale);
|
||||
} else if (source === 'labels') {
|
||||
timestamps = getLabelTimestamps(scale);
|
||||
} else {
|
||||
timestamps = generate(scale, min, max, capacity, options);
|
||||
}
|
||||
|
||||
return timestamps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return subset of `timestamps` between `min` and `max`.
|
||||
* Timestamps are assumend to be in sorted order.
|
||||
* @param {int[]} timestamps - array of timestamps
|
||||
* @param {int} min - min value (timestamp)
|
||||
* @param {int} max - max value (timestamp)
|
||||
*/
|
||||
function filterBetween(timestamps, min, max) {
|
||||
var start = 0;
|
||||
var end = timestamps.length - 1;
|
||||
|
||||
while (start < end && timestamps[start] < min) {
|
||||
start++;
|
||||
}
|
||||
while (end > start && timestamps[end] > max) {
|
||||
end--;
|
||||
}
|
||||
end++; // slice does not include last element
|
||||
|
||||
return start > 0 || end < timestamps.length
|
||||
? timestamps.slice(start, end)
|
||||
: timestamps;
|
||||
}
|
||||
|
||||
var defaultConfig = {
|
||||
position: 'bottom',
|
||||
|
||||
@ -415,158 +497,107 @@ var defaultConfig = {
|
||||
};
|
||||
|
||||
module.exports = Scale.extend({
|
||||
_parse: function(raw, index) { // eslint-disable-line no-unused-vars
|
||||
if (raw === undefined) {
|
||||
return NaN;
|
||||
}
|
||||
return toTimestamp(this, raw);
|
||||
},
|
||||
|
||||
update: function() {
|
||||
_parseObject: function(obj, axis, index) {
|
||||
if (obj && obj.t) {
|
||||
return this._parse(obj.t, index);
|
||||
}
|
||||
if (obj[axis] !== undefined) {
|
||||
return this._parse(obj[axis], index);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
_invalidateCaches: function() {
|
||||
this._cache = {};
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
var me = this;
|
||||
var options = me.options;
|
||||
var time = options.time || (options.time = {});
|
||||
var adapter = me._adapter = new adapters._date(options.adapters.date);
|
||||
|
||||
|
||||
me._cache = {};
|
||||
|
||||
// Backward compatibility: before introducing adapter, `displayFormats` was
|
||||
// supposed to contain *all* unit/string pairs but this can't be resolved
|
||||
// when loading the scale (adapters are loaded afterward), so let's populate
|
||||
// missing formats on update
|
||||
|
||||
helpers.mergeIf(time.displayFormats, adapter.formats());
|
||||
|
||||
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);
|
||||
Scale.prototype.initialize.call(me);
|
||||
},
|
||||
|
||||
determineDataLimits: function() {
|
||||
var me = this;
|
||||
var chart = me.chart;
|
||||
var adapter = me._adapter;
|
||||
var options = me.options;
|
||||
var tickOpts = options.ticks;
|
||||
var unit = options.time.unit || 'day';
|
||||
var min = MAX_INTEGER;
|
||||
var max = MIN_INTEGER;
|
||||
var timestamps = [];
|
||||
var datasets = [];
|
||||
var labels = [];
|
||||
var i, j, ilen, jlen, data, timestamp, labelsAdded;
|
||||
var dataLabels = me._getLabels();
|
||||
var min = Number.POSITIVE_INFINITY;
|
||||
var max = Number.NEGATIVE_INFINITY;
|
||||
var minmax = me._getMinMax(false);
|
||||
var i, ilen, labels;
|
||||
|
||||
for (i = 0, ilen = dataLabels.length; i < ilen; ++i) {
|
||||
labels.push(parse(me, dataLabels[i]));
|
||||
min = Math.min(min, minmax.min);
|
||||
max = Math.max(max, minmax.max);
|
||||
|
||||
labels = getLabelTimestamps(me);
|
||||
for (i = 0, ilen = labels.length; i < ilen; ++i) {
|
||||
min = Math.min(min, labels[i]);
|
||||
max = Math.max(max, labels[i]);
|
||||
}
|
||||
|
||||
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(me, data[j]);
|
||||
timestamps.push(timestamp);
|
||||
datasets[i][j] = timestamp;
|
||||
}
|
||||
} else {
|
||||
datasets[i] = labels.slice(0);
|
||||
if (!labelsAdded) {
|
||||
timestamps = timestamps.concat(labels);
|
||||
labelsAdded = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
datasets[i] = [];
|
||||
}
|
||||
}
|
||||
|
||||
if (labels.length) {
|
||||
min = Math.min(min, labels[0]);
|
||||
max = Math.max(max, labels[labels.length - 1]);
|
||||
}
|
||||
|
||||
if (timestamps.length) {
|
||||
timestamps = ilen > 1 ? arrayUnique(timestamps).sort(sorter) : timestamps.sort(sorter);
|
||||
min = Math.min(min, timestamps[0]);
|
||||
max = Math.max(max, timestamps[timestamps.length - 1]);
|
||||
}
|
||||
min = helpers.isFinite(min) && !isNaN(min) ? min : +adapter.startOf(Date.now(), unit);
|
||||
max = helpers.isFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit) + 1;
|
||||
|
||||
min = parse(me, tickOpts.min) || min;
|
||||
max = parse(me, tickOpts.max) || max;
|
||||
|
||||
// In case there is no valid min/max, set limits based on unit time option
|
||||
min = min === MAX_INTEGER ? +adapter.startOf(Date.now(), unit) : min;
|
||||
max = max === MIN_INTEGER ? +adapter.endOf(Date.now(), 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._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 tickOpts = options.ticks;
|
||||
var timeOpts = options.time;
|
||||
var timestamps = me._timestamps;
|
||||
var ticks = [];
|
||||
var capacity = me.getLabelCapacity(min);
|
||||
var source = tickOpts.source;
|
||||
var tickOpts = options.ticks;
|
||||
var distribution = options.distribution;
|
||||
var i, ilen, timestamp;
|
||||
var ticks = [];
|
||||
var min, max, timestamps;
|
||||
|
||||
if (source === 'data' || (source === 'auto' && distribution === 'series')) {
|
||||
timestamps = timestamps.data;
|
||||
} else if (source === 'labels') {
|
||||
timestamps = timestamps.labels;
|
||||
} else {
|
||||
timestamps = generate(me, min, max, capacity, options);
|
||||
}
|
||||
timestamps = getTimestampsForTicks(me);
|
||||
|
||||
if (options.bounds === 'ticks' && timestamps.length) {
|
||||
min = timestamps[0];
|
||||
max = timestamps[timestamps.length - 1];
|
||||
me.min = parse(me, tickOpts.min) || timestamps[0];
|
||||
me.max = parse(me, tickOpts.max) || timestamps[timestamps.length - 1];
|
||||
}
|
||||
|
||||
// Enforce limits with user min/max options
|
||||
min = parse(me, tickOpts.min) || min;
|
||||
max = parse(me, tickOpts.max) || max;
|
||||
min = me.min;
|
||||
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;
|
||||
ticks = filterBetween(timestamps, min, max);
|
||||
|
||||
// PRIVATE
|
||||
// determineUnitForFormatting relies on the number of ticks so we don't use it when
|
||||
// autoSkip is enabled because we don't yet know what the final number of ticks will be
|
||||
me._unit = timeOpts.unit || (tickOpts.autoSkip
|
||||
? determineUnitForAutoTicks(timeOpts.minUnit, me.min, me.max, capacity)
|
||||
? determineUnitForAutoTicks(timeOpts.minUnit, me.min, me.max, me.getLabelCapacity(min))
|
||||
: determineUnitForFormatting(me, ticks.length, timeOpts.minUnit, me.min, me.max));
|
||||
me._majorUnit = !tickOpts.major.enabled || me._unit === 'year' ? undefined
|
||||
: determineMajorUnit(me._unit);
|
||||
me._table = buildLookupTable(me._timestamps.data, min, max, distribution);
|
||||
me._table = buildLookupTable(getAllTimestamps(me), min, max, distribution);
|
||||
me._offsets = computeOffsets(me._table, ticks, min, max, options);
|
||||
|
||||
if (tickOpts.reverse) {
|
||||
@ -576,24 +607,15 @@ module.exports = Scale.extend({
|
||||
return ticksFromTimestamps(me, ticks, me._majorUnit);
|
||||
},
|
||||
|
||||
getLabelForIndex: function(index, datasetIndex) {
|
||||
getLabelForValue: function(value) {
|
||||
var me = this;
|
||||
var adapter = me._adapter;
|
||||
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 adapter.format(toTimestamp(me, label), timeOpts.tooltipFormat);
|
||||
return adapter.format(value, timeOpts.tooltipFormat);
|
||||
}
|
||||
if (typeof label === 'string') {
|
||||
return label;
|
||||
}
|
||||
return adapter.format(toTimestamp(me, label), timeOpts.displayFormats.datetime);
|
||||
return adapter.format(value, timeOpts.displayFormats.datetime);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -640,20 +662,15 @@ module.exports = Scale.extend({
|
||||
return me.getPixelForDecimal((offsets.start + pos) * offsets.factor);
|
||||
},
|
||||
|
||||
getPixelForValue: function(value, index, datasetIndex) {
|
||||
getPixelForValue: function(value) {
|
||||
var me = this;
|
||||
var time = null;
|
||||
|
||||
if (index !== undefined && datasetIndex !== undefined) {
|
||||
time = me._timestamps.datasets[datasetIndex][index];
|
||||
if (typeof value !== 'number') {
|
||||
value = parse(me, value);
|
||||
}
|
||||
|
||||
if (time === null) {
|
||||
time = parse(me, value);
|
||||
}
|
||||
|
||||
if (time !== null) {
|
||||
return me.getPixelForOffset(time);
|
||||
if (value !== null) {
|
||||
return me.getPixelForOffset(value);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
32
test/fixtures/controller.bar/data/object.js
vendored
Normal file
32
test/fixtures/controller.bar/data/object.js
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
module.exports = {
|
||||
config: {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: ['a', 'b', 'c'],
|
||||
datasets: [
|
||||
{
|
||||
data: {a: 10, b: 2, c: -5},
|
||||
backgroundColor: '#ff0000'
|
||||
},
|
||||
{
|
||||
data: {a: 8, b: 12, c: 5},
|
||||
backgroundColor: '#00ff00'
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
legend: false,
|
||||
title: false,
|
||||
scales: {
|
||||
xAxes: [{display: false}],
|
||||
yAxes: [{display: false}]
|
||||
}
|
||||
}
|
||||
},
|
||||
options: {
|
||||
canvas: {
|
||||
height: 256,
|
||||
width: 512
|
||||
}
|
||||
}
|
||||
};
|
||||
BIN
test/fixtures/controller.bar/data/object.png
vendored
Normal file
BIN
test/fixtures/controller.bar/data/object.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 2.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 2.1 KiB |
47
test/fixtures/controller.bubble/radius-data.js
vendored
Normal file
47
test/fixtures/controller.bubble/radius-data.js
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
module.exports = {
|
||||
config: {
|
||||
type: 'bubble',
|
||||
data: {
|
||||
datasets: [{
|
||||
data: [
|
||||
{x: 0, y: 5, r: 1},
|
||||
{x: 1, y: 4, r: 2},
|
||||
{x: 2, y: 3, r: 6},
|
||||
{x: 3, y: 2},
|
||||
{x: 4, y: 1, r: 2},
|
||||
{x: 5, y: 0, r: NaN},
|
||||
{x: 6, y: -1, r: undefined},
|
||||
{x: 7, y: -2, r: null},
|
||||
{x: 8, y: -3, r: '4'},
|
||||
{x: 9, y: -4, r: '4px'},
|
||||
]
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
legend: false,
|
||||
title: false,
|
||||
scales: {
|
||||
xAxes: [{display: false}],
|
||||
yAxes: [{display: false}]
|
||||
},
|
||||
elements: {
|
||||
point: {
|
||||
backgroundColor: '#444',
|
||||
radius: 10
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
padding: {
|
||||
left: 24,
|
||||
right: 24
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
options: {
|
||||
canvas: {
|
||||
height: 128,
|
||||
width: 256
|
||||
}
|
||||
}
|
||||
};
|
||||
BIN
test/fixtures/controller.bubble/radius-data.png
vendored
Normal file
BIN
test/fixtures/controller.bubble/radius-data.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
@ -192,7 +192,7 @@ describe('Category scale tests', function() {
|
||||
|
||||
var scale = chart.scales.xScale0;
|
||||
|
||||
expect(scale.getLabelForIndex(1, 0)).toBe('tick2');
|
||||
expect(scale.getLabelForValue(1)).toBe('tick2');
|
||||
});
|
||||
|
||||
it('Should get the correct pixel for a value when horizontal', function() {
|
||||
@ -222,19 +222,19 @@ describe('Category scale tests', function() {
|
||||
});
|
||||
|
||||
var xScale = chart.scales.xScale0;
|
||||
expect(xScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(23 + 6); // plus lineHeight
|
||||
expect(xScale.getPixelForValue(0)).toBeCloseToPixel(23 + 6); // plus lineHeight
|
||||
expect(xScale.getValueForPixel(23)).toBe(0);
|
||||
|
||||
expect(xScale.getPixelForValue(0, 4, 0)).toBeCloseToPixel(487);
|
||||
expect(xScale.getPixelForValue(4)).toBeCloseToPixel(487);
|
||||
expect(xScale.getValueForPixel(487)).toBe(4);
|
||||
|
||||
xScale.options.offset = true;
|
||||
chart.update();
|
||||
|
||||
expect(xScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(71 + 6); // plus lineHeight
|
||||
expect(xScale.getPixelForValue(0)).toBeCloseToPixel(71 + 6); // plus lineHeight
|
||||
expect(xScale.getValueForPixel(69)).toBe(0);
|
||||
|
||||
expect(xScale.getPixelForValue(0, 4, 0)).toBeCloseToPixel(461);
|
||||
expect(xScale.getPixelForValue(4)).toBeCloseToPixel(461);
|
||||
expect(xScale.getValueForPixel(417)).toBe(4);
|
||||
});
|
||||
|
||||
@ -265,8 +265,7 @@ describe('Category scale tests', function() {
|
||||
});
|
||||
|
||||
var xScale = chart.scales.xScale0;
|
||||
expect(xScale.getPixelForValue('tick_1', 0, 0)).toBeCloseToPixel(23 + 6); // plus lineHeight
|
||||
expect(xScale.getPixelForValue('tick_1', 1, 0)).toBeCloseToPixel(143);
|
||||
expect(xScale.getPixelForValue('tick1')).toBeCloseToPixel(23 + 6); // plus lineHeight
|
||||
});
|
||||
|
||||
it('Should get the correct pixel for a value when horizontal and zoomed', function() {
|
||||
@ -300,14 +299,14 @@ describe('Category scale tests', function() {
|
||||
});
|
||||
|
||||
var xScale = chart.scales.xScale0;
|
||||
expect(xScale.getPixelForValue(0, 1, 0)).toBeCloseToPixel(23 + 6); // plus lineHeight
|
||||
expect(xScale.getPixelForValue(0, 3, 0)).toBeCloseToPixel(496);
|
||||
expect(xScale.getPixelForValue(1)).toBeCloseToPixel(23 + 6); // plus lineHeight
|
||||
expect(xScale.getPixelForValue(3)).toBeCloseToPixel(496);
|
||||
|
||||
xScale.options.offset = true;
|
||||
chart.update();
|
||||
|
||||
expect(xScale.getPixelForValue(0, 1, 0)).toBeCloseToPixel(103 + 6); // plus lineHeight
|
||||
expect(xScale.getPixelForValue(0, 3, 0)).toBeCloseToPixel(429);
|
||||
expect(xScale.getPixelForValue(1)).toBeCloseToPixel(103 + 6); // plus lineHeight
|
||||
expect(xScale.getPixelForValue(3)).toBeCloseToPixel(429);
|
||||
});
|
||||
|
||||
it('should get the correct pixel for a value when vertical', function() {
|
||||
@ -339,20 +338,20 @@ describe('Category scale tests', function() {
|
||||
});
|
||||
|
||||
var yScale = chart.scales.yScale0;
|
||||
expect(yScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(32);
|
||||
expect(yScale.getValueForPixel(32)).toBe(0);
|
||||
expect(yScale.getPixelForValue(0)).toBeCloseToPixel(32);
|
||||
expect(yScale.getValueForPixel(257)).toBe(2);
|
||||
|
||||
expect(yScale.getPixelForValue(0, 4, 0)).toBeCloseToPixel(484);
|
||||
expect(yScale.getValueForPixel(484)).toBe(4);
|
||||
expect(yScale.getPixelForValue(4)).toBeCloseToPixel(481);
|
||||
expect(yScale.getValueForPixel(144)).toBe(1);
|
||||
|
||||
yScale.options.offset = true;
|
||||
chart.update();
|
||||
|
||||
expect(yScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(77);
|
||||
expect(yScale.getValueForPixel(77)).toBe(0);
|
||||
expect(yScale.getPixelForValue(0)).toBeCloseToPixel(77);
|
||||
expect(yScale.getValueForPixel(256)).toBe(2);
|
||||
|
||||
expect(yScale.getPixelForValue(0, 4, 0)).toBeCloseToPixel(437);
|
||||
expect(yScale.getValueForPixel(437)).toBe(4);
|
||||
expect(yScale.getPixelForValue(4)).toBeCloseToPixel(436);
|
||||
expect(yScale.getValueForPixel(167)).toBe(1);
|
||||
});
|
||||
|
||||
it('should get the correct pixel for a value when vertical and zoomed', function() {
|
||||
@ -389,14 +388,14 @@ describe('Category scale tests', function() {
|
||||
|
||||
var yScale = chart.scales.yScale0;
|
||||
|
||||
expect(yScale.getPixelForValue(0, 1, 0)).toBeCloseToPixel(32);
|
||||
expect(yScale.getPixelForValue(0, 3, 0)).toBeCloseToPixel(484);
|
||||
expect(yScale.getPixelForValue(1)).toBeCloseToPixel(32);
|
||||
expect(yScale.getPixelForValue(3)).toBeCloseToPixel(482);
|
||||
|
||||
yScale.options.offset = true;
|
||||
chart.update();
|
||||
|
||||
expect(yScale.getPixelForValue(0, 1, 0)).toBeCloseToPixel(107);
|
||||
expect(yScale.getPixelForValue(0, 3, 0)).toBeCloseToPixel(407);
|
||||
expect(yScale.getPixelForValue(1)).toBeCloseToPixel(107);
|
||||
expect(yScale.getPixelForValue(3)).toBeCloseToPixel(407);
|
||||
});
|
||||
|
||||
it('Should get the correct pixel for an object value when horizontal', function() {
|
||||
@ -432,9 +431,9 @@ describe('Category scale tests', function() {
|
||||
});
|
||||
|
||||
var xScale = chart.scales.xScale0;
|
||||
expect(xScale.getPixelForValue({x: 0, y: 10}, 0, 0)).toBeCloseToPixel(29);
|
||||
expect(xScale.getPixelForValue({x: 3, y: 25}, 3, 0)).toBeCloseToPixel(506);
|
||||
expect(xScale.getPixelForValue({x: 0, y: 78}, 4, 0)).toBeCloseToPixel(29);
|
||||
expect(xScale.getPixelForValue(0)).toBeCloseToPixel(29);
|
||||
expect(xScale.getPixelForValue(3)).toBeCloseToPixel(506);
|
||||
expect(xScale.getPixelForValue(4)).toBeCloseToPixel(664);
|
||||
});
|
||||
|
||||
it('Should get the correct pixel for an object value when vertical', function() {
|
||||
@ -472,8 +471,8 @@ describe('Category scale tests', function() {
|
||||
});
|
||||
|
||||
var yScale = chart.scales.yScale0;
|
||||
expect(yScale.getPixelForValue({x: 0, y: 2}, 0, 0)).toBeCloseToPixel(257);
|
||||
expect(yScale.getPixelForValue({x: 0, y: 1}, 4, 0)).toBeCloseToPixel(144);
|
||||
expect(yScale.getPixelForValue(0)).toBeCloseToPixel(32);
|
||||
expect(yScale.getPixelForValue(4)).toBeCloseToPixel(481);
|
||||
});
|
||||
|
||||
it('Should get the correct pixel for an object value in a bar chart', function() {
|
||||
@ -509,9 +508,9 @@ describe('Category scale tests', function() {
|
||||
});
|
||||
|
||||
var xScale = chart.scales.xScale0;
|
||||
expect(xScale.getPixelForValue(null, 0, 0)).toBeCloseToPixel(89);
|
||||
expect(xScale.getPixelForValue(null, 3, 0)).toBeCloseToPixel(449);
|
||||
expect(xScale.getPixelForValue(null, 4, 0)).toBeCloseToPixel(89);
|
||||
expect(xScale.getPixelForValue(0)).toBeCloseToPixel(89);
|
||||
expect(xScale.getPixelForValue(3)).toBeCloseToPixel(449);
|
||||
expect(xScale.getPixelForValue(4)).toBeCloseToPixel(569);
|
||||
});
|
||||
|
||||
it('Should get the correct pixel for an object value in a horizontal bar chart', function() {
|
||||
@ -547,8 +546,8 @@ describe('Category scale tests', function() {
|
||||
});
|
||||
|
||||
var yScale = chart.scales.yScale0;
|
||||
expect(yScale.getPixelForValue(null, 0, 0)).toBeCloseToPixel(88);
|
||||
expect(yScale.getPixelForValue(null, 3, 0)).toBeCloseToPixel(426);
|
||||
expect(yScale.getPixelForValue(null, 4, 0)).toBeCloseToPixel(88);
|
||||
expect(yScale.getPixelForValue(0)).toBeCloseToPixel(88);
|
||||
expect(yScale.getPixelForValue(3)).toBeCloseToPixel(426);
|
||||
expect(yScale.getPixelForValue(4)).toBeCloseToPixel(538);
|
||||
});
|
||||
});
|
||||
|
||||
@ -323,7 +323,7 @@ describe('Linear Scale', function() {
|
||||
});
|
||||
chart.update();
|
||||
|
||||
expect(chart.scales.yScale0.getLabelForIndex(3, 0)).toBe(7);
|
||||
expect(chart.scales.yScale0.getLabelForValue(7)).toBe(7);
|
||||
});
|
||||
|
||||
it('Should correctly determine the min and max data values when stacked mode is turned on', function() {
|
||||
@ -857,18 +857,18 @@ describe('Linear Scale', function() {
|
||||
});
|
||||
|
||||
var xScale = chart.scales.xScale0;
|
||||
expect(xScale.getPixelForValue(1, 0, 0)).toBeCloseToPixel(501); // right - paddingRight
|
||||
expect(xScale.getPixelForValue(-1, 0, 0)).toBeCloseToPixel(31 + 6); // left + paddingLeft + lineSpace
|
||||
expect(xScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(266 + 6 / 2); // halfway*/
|
||||
expect(xScale.getPixelForValue(1)).toBeCloseToPixel(501); // right - paddingRight
|
||||
expect(xScale.getPixelForValue(-1)).toBeCloseToPixel(31 + 6); // left + paddingLeft + lineSpace
|
||||
expect(xScale.getPixelForValue(0)).toBeCloseToPixel(266 + 6 / 2); // halfway*/
|
||||
|
||||
expect(xScale.getValueForPixel(501)).toBeCloseTo(1, 1e-2);
|
||||
expect(xScale.getValueForPixel(31)).toBeCloseTo(-1, 1e-2);
|
||||
expect(xScale.getValueForPixel(266)).toBeCloseTo(0, 1e-2);
|
||||
|
||||
var yScale = chart.scales.yScale0;
|
||||
expect(yScale.getPixelForValue(1, 0, 0)).toBeCloseToPixel(32); // right - paddingRight
|
||||
expect(yScale.getPixelForValue(-1, 0, 0)).toBeCloseToPixel(484); // left + paddingLeft
|
||||
expect(yScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(258); // halfway*/
|
||||
expect(yScale.getPixelForValue(1)).toBeCloseToPixel(32); // right - paddingRight
|
||||
expect(yScale.getPixelForValue(-1)).toBeCloseToPixel(484); // left + paddingLeft
|
||||
expect(yScale.getPixelForValue(0)).toBeCloseToPixel(258); // halfway*/
|
||||
|
||||
expect(yScale.getValueForPixel(32)).toBeCloseTo(1, 1e-2);
|
||||
expect(yScale.getValueForPixel(484)).toBeCloseTo(-1, 1e-2);
|
||||
|
||||
@ -747,7 +747,7 @@ describe('Logarithmic Scale tests', function() {
|
||||
}
|
||||
});
|
||||
|
||||
expect(chart.scales.yScale0.getLabelForIndex(0, 2)).toBe(150);
|
||||
expect(chart.scales.yScale0.getLabelForValue(150)).toBe(150);
|
||||
});
|
||||
|
||||
describe('when', function() {
|
||||
@ -884,8 +884,8 @@ describe('Logarithmic Scale tests', function() {
|
||||
var start = chart.chartArea[chartStart];
|
||||
var end = chart.chartArea[chartEnd];
|
||||
|
||||
expect(scale.getPixelForValue(firstTick, 0, 0)).toBeCloseToPixel(start);
|
||||
expect(scale.getPixelForValue(lastTick, 0, 0)).toBeCloseToPixel(end);
|
||||
expect(scale.getPixelForValue(firstTick)).toBeCloseToPixel(start);
|
||||
expect(scale.getPixelForValue(lastTick)).toBeCloseToPixel(end);
|
||||
|
||||
expect(scale.getValueForPixel(start)).toBeCloseTo(firstTick, 4);
|
||||
expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4);
|
||||
@ -897,8 +897,8 @@ describe('Logarithmic Scale tests', function() {
|
||||
start = chart.chartArea[chartEnd];
|
||||
end = chart.chartArea[chartStart];
|
||||
|
||||
expect(scale.getPixelForValue(firstTick, 0, 0)).toBeCloseToPixel(start);
|
||||
expect(scale.getPixelForValue(lastTick, 0, 0)).toBeCloseToPixel(end);
|
||||
expect(scale.getPixelForValue(firstTick)).toBeCloseToPixel(start);
|
||||
expect(scale.getPixelForValue(lastTick)).toBeCloseToPixel(end);
|
||||
|
||||
expect(scale.getValueForPixel(start)).toBeCloseTo(firstTick, 4);
|
||||
expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4);
|
||||
|
||||
@ -157,25 +157,21 @@ describe('Test the radial linear scale', function() {
|
||||
});
|
||||
|
||||
it('Should ensure that the scale has a max and min that are not equal', function() {
|
||||
var scaleID = 'myScale';
|
||||
|
||||
var mockData = {
|
||||
datasets: [],
|
||||
labels: []
|
||||
};
|
||||
|
||||
var mockContext = window.createMockContext();
|
||||
var Constructor = Chart.scaleService.getScaleConstructor('radialLinear');
|
||||
var scale = new Constructor({
|
||||
ctx: mockContext,
|
||||
options: Chart.scaleService.getScaleDefaults('radialLinear'), // use default config for scale
|
||||
chart: {
|
||||
data: mockData
|
||||
var chart = window.acquireChart({
|
||||
type: 'radar',
|
||||
data: {
|
||||
datasets: [],
|
||||
labels: []
|
||||
},
|
||||
id: scaleID,
|
||||
options: {
|
||||
scale: {
|
||||
id: 'myScale'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
scale.update(200, 300);
|
||||
var scale = chart.scales.myScale;
|
||||
|
||||
expect(scale.min).toBe(-1);
|
||||
expect(scale.max).toBe(1);
|
||||
});
|
||||
@ -428,7 +424,7 @@ describe('Test the radial linear scale', function() {
|
||||
}
|
||||
}
|
||||
});
|
||||
expect(chart.scale.getLabelForIndex(1, 0)).toBe(5);
|
||||
expect(chart.scale.getLabelForValue(5)).toBe(5);
|
||||
});
|
||||
|
||||
it('should get the correct distance from the center point', function() {
|
||||
|
||||
@ -1,24 +1,25 @@
|
||||
// Time scale tests
|
||||
describe('Time scale tests', function() {
|
||||
function createScale(data, options, dimensions) {
|
||||
var scaleID = 'myScale';
|
||||
var mockContext = window.createMockContext();
|
||||
var Constructor = Chart.scaleService.getScaleConstructor('time');
|
||||
var width = (dimensions && dimensions.width) || 400;
|
||||
var height = (dimensions && dimensions.height) || 50;
|
||||
var scale = new Constructor({
|
||||
ctx: mockContext,
|
||||
options: options,
|
||||
chart: {
|
||||
data: data,
|
||||
width: width,
|
||||
height: height
|
||||
},
|
||||
id: scaleID
|
||||
});
|
||||
|
||||
scale.update(width, height);
|
||||
return scale;
|
||||
options = options || {};
|
||||
options.type = 'time';
|
||||
options.id = 'xScale0';
|
||||
|
||||
var chart = window.acquireChart({
|
||||
type: 'line',
|
||||
data: data,
|
||||
options: {
|
||||
scales: {
|
||||
xAxes: [options]
|
||||
}
|
||||
}
|
||||
}, {canvas: {width: width, height: height}});
|
||||
|
||||
|
||||
return chart.scales.xScale0;
|
||||
}
|
||||
|
||||
function getLabels(scale) {
|
||||
@ -538,7 +539,7 @@ describe('Time scale tests', function() {
|
||||
xAxes: [{
|
||||
id: 'xScale0',
|
||||
type: 'time',
|
||||
position: 'bottom'
|
||||
position: 'bottom',
|
||||
}],
|
||||
}
|
||||
}
|
||||
@ -564,14 +565,14 @@ describe('Time scale tests', function() {
|
||||
var lastPointOffsetMs = moment(chart.config.data.labels[chart.config.data.labels.length - 1]).valueOf() - scale.min;
|
||||
var lastPointPixel = scale.left + lastPointOffsetMs / msPerPix;
|
||||
|
||||
expect(scale.getPixelForValue('', 0, 0)).toBeCloseToPixel(firstPointPixel);
|
||||
expect(scale.getPixelForValue('2015-01-01T20:00:00')).toBeCloseToPixel(firstPointPixel);
|
||||
expect(scale.getPixelForValue(chart.data.labels[0])).toBeCloseToPixel(firstPointPixel);
|
||||
expect(scale.getValueForPixel(firstPointPixel)).toBeCloseToTime({
|
||||
value: moment(chart.data.labels[0]),
|
||||
unit: 'hour',
|
||||
});
|
||||
|
||||
expect(scale.getPixelForValue('', 6, 0)).toBeCloseToPixel(lastPointPixel);
|
||||
expect(scale.getPixelForValue('2015-01-10T12:00')).toBeCloseToPixel(lastPointPixel);
|
||||
expect(scale.getValueForPixel(lastPointPixel)).toBeCloseToTime({
|
||||
value: moment(chart.data.labels[6]),
|
||||
unit: 'hour'
|
||||
@ -654,16 +655,21 @@ describe('Time scale tests', function() {
|
||||
xAxes: [{
|
||||
id: 'xScale0',
|
||||
type: 'time',
|
||||
position: 'bottom'
|
||||
position: 'bottom',
|
||||
ticks: {
|
||||
source: 'labels',
|
||||
autoSkip: false
|
||||
}
|
||||
}],
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var xScale = chart.scales.xScale0;
|
||||
expect(xScale.getLabelForIndex(0, 0)).toBeTruthy();
|
||||
expect(xScale.getLabelForIndex(0, 0)).toBe('2015-01-01T20:00:00');
|
||||
expect(xScale.getLabelForIndex(6, 0)).toBe('2015-01-10T12:00');
|
||||
var controller = chart.getDatasetMeta(0).controller;
|
||||
expect(xScale.getLabelForValue(controller._getParsed(0)[xScale.id])).toBeTruthy();
|
||||
expect(xScale.getLabelForValue(controller._getParsed(0)[xScale.id])).toBe('Jan 1, 2015, 8:00:00 pm');
|
||||
expect(xScale.getLabelForValue(xScale.getValueForPixel(xScale.getPixelForTick(6)))).toBe('Jan 10, 2015, 12:00:00 pm');
|
||||
});
|
||||
|
||||
describe('when ticks.callback is specified', function() {
|
||||
@ -825,8 +831,10 @@ describe('Time scale tests', function() {
|
||||
});
|
||||
|
||||
var xScale = chart.scales.xScale0;
|
||||
expect(xScale.getLabelForIndex(0, 0)).toBeTruthy();
|
||||
expect(xScale.getLabelForIndex(0, 0)).toBe('2015-01-01T20:00:00');
|
||||
var controller = chart.getDatasetMeta(0).controller;
|
||||
var value = controller._getParsed(0)[xScale.id];
|
||||
expect(xScale.getLabelForValue(value)).toBeTruthy();
|
||||
expect(xScale.getLabelForValue(value)).toBe('Jan 1, 2015, 8:00:00 pm');
|
||||
});
|
||||
|
||||
it('should get the correct label for a timestamp', function() {
|
||||
@ -853,7 +861,8 @@ describe('Time scale tests', function() {
|
||||
});
|
||||
|
||||
var xScale = chart.scales.xScale0;
|
||||
var label = xScale.getLabelForIndex(0, 0);
|
||||
var controller = chart.getDatasetMeta(0).controller;
|
||||
var label = xScale.getLabelForValue(controller._getParsed(0)[xScale.id]);
|
||||
expect(label).toEqual('Jan 8, 2018, 5:14:23 am');
|
||||
});
|
||||
|
||||
@ -879,9 +888,9 @@ describe('Time scale tests', function() {
|
||||
});
|
||||
|
||||
var xScale = chart.scales.xScale0;
|
||||
var pixel = xScale.getPixelForValue('', 0, 0);
|
||||
var pixel = xScale.getPixelForValue('2016-05-27');
|
||||
|
||||
expect(xScale.getValueForPixel(pixel).valueOf()).toEqual(moment(chart.data.labels[0]).valueOf());
|
||||
expect(xScale.getValueForPixel(pixel)).toEqual(moment(chart.data.labels[0]).valueOf());
|
||||
});
|
||||
|
||||
it('does not create a negative width chart when hidden', function() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user