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:
Jukka Kurkela 2019-11-05 01:07:01 +02:00 committed by Evert Timberg
parent dd8d267956
commit 72df272234
28 changed files with 1036 additions and 725 deletions

View File

@ -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

View File

@ -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

View File

@ -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();
}
}

View File

@ -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);

View File

@ -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);
}
});

View File

@ -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;

View File

@ -108,8 +108,6 @@ module.exports = DatasetController.extend({
dataElementType: elements.Arc,
linkScales: helpers.noop,
/**
* @private
*/

View File

@ -24,8 +24,6 @@ module.exports = DatasetController.extend({
dataElementType: elements.Point,
linkScales: helpers.noop,
/**
* @private
*/

View File

@ -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);
},

View File

@ -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;
}
});

View File

@ -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,

View File

@ -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) {

View File

@ -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) {

View File

@ -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() {

View File

@ -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;
}

View File

@ -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;

View File

@ -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);
}
},

View 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
}
}
};

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

View 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
}
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -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);
});
});

View File

@ -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);

View File

@ -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);

View File

@ -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() {

View File

@ -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() {