// 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
* `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
### 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
};
}
+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,
'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;
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;
}
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;
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 {
* 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;
-
- 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;
+ 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 (length !== value) {
+ start = length - value;
+ length = value;
+ }
- if ((value.min < 0 && ivalue < 0) || (value.max >= 0 && ivalue > 0)) {
- start += ivalue;
- }
- }
+ 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 = scale.getPixelForValue(start);
- head = scale.getPixelForValue(start + length);
+ 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 {
/**
* @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),
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();
}
}
},
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 + ')';
}
}
'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
*/
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,
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
// Custom radius resolution
values.radius = resolve([
- data.r,
+ parsed && parsed._custom,
me._config.radius,
chart.options.elements.point.radius
], context, index);
'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;
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 || {};
},
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);
}
});
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;
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;
dataElementType: elements.Arc,
- linkScales: helpers.noop,
-
/**
* @private
*/
dataElementType: elements.Point,
- linkScales: helpers.noop,
-
/**
* @private
*/
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);
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) {
},
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() {
* @private
*/
_getValueScaleId: function() {
- return this.getMeta().yAxisID;
+ return this._cachedMeta.yAxisID;
},
/**
* @private
*/
_getIndexScaleId: function() {
- return this.getMeta().xAxisID;
+ return this._cachedMeta.xAxisID;
},
/**
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);
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);
},
/**
});
},
+ /**
+ * @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;
},
draw: function() {
- var meta = this.getMeta();
+ var meta = this._cachedMeta;
var elements = meta.data || [];
var ilen = elements.length;
var i = 0;
*/
getStyle: function(index) {
var me = this;
- var meta = me.getMeta();
+ var meta = me._cachedMeta;
var dataset = meta.dataset;
var style;
/**
* @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);
}
},
for (var i = 0; i < count; ++i) {
this.addElementAndReset(start + i);
}
+ this._parse(start, count);
},
/**
* @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);
},
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
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;
},
/**
- * @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
- };
- },
-
- /**
- * @private
- */
- _getScaleLabel: function(rawValue) {
- var v = this._parseValue(rawValue);
- if (v.start !== undefined) {
- return '[' + v.start + ', ' + v.end + ']';
- }
-
- return +this.getRightValue(rawValue);
+ getLabelForValue: function(value) {
+ return value;
},
- /**
- * 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
* @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;
},
/**
/**
* @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;
}
});
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,
'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();
});
},
- 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() {
},
// 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) {
}
};
-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;
-
- me.min = Number.POSITIVE_INFINITY;
- me.max = Number.NEGATIVE_INFINITY;
-
- if (hasStacks === undefined) {
- for (i = 0; !hasStacks && i < ilen; ++i) {
- meta = metasets[i];
- hasStacks = meta.stack !== undefined;
- }
+ var DEFAULT_MIN = 0;
+ var DEFAULT_MAX = 1;
+ var minmax = me._getMinMax(true);
+ var min = minmax.min;
+ var max = minmax.max;
+
+ me.min = helpers.isFinite(min) && !isNaN(min) ? min : DEFAULT_MIN;
+ me.max = helpers.isFinite(max) && !isNaN(max) ? max : DEFAULT_MAX;
+
+ // 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();
},
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) {
}
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() {
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;
}
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;
-
- // Calculate Range
- me.min = Number.POSITIVE_INFINITY;
- me.max = Number.NEGATIVE_INFINITY;
- me.minNotZero = Number.POSITIVE_INFINITY;
-
- 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);
- }
- });
+ var minmax = me._getMinMax(true);
+ var min = minmax.min;
+ var max = minmax.max;
+ var minPositive = minmax.minPositive;
- } 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;
+ 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;
- // Common base implementation to handle ticks.min, ticks.max
- this.handleTickRangeOptions();
+ me.handleTickRangeOptions();
},
handleTickRangeOptions: function() {
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) {
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;
}
determineDataLimits: function() {
var me = this;
- var chart = me.chart;
- var min = Number.POSITIVE_INFINITY;
- var max = Number.NEGATIVE_INFINITY;
-
- helpers.each(chart.data.datasets, function(dataset, datasetIndex) {
- if (chart.isDatasetVisible(datasetIndex)) {
- var meta = chart.getDatasetMeta(datasetIndex);
-
- helpers.each(dataset.data, function(rawValue, index) {
- var value = +me.getRightValue(rawValue);
- if (isNaN(value) || meta.data[index].hidden) {
- return;
- }
-
- min = Math.min(value, min);
- max = Math.max(value, max);
- });
- }
- });
+ var minmax = me._getMinMax(false);
+ var min = minmax.min;
+ var max = minmax.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();
});
},
- getLabelForIndex: function(index, datasetIndex) {
- return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
- },
-
fit: function() {
var me = this;
var opts = me.options;
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 = {
}
var options = scale.options.time;
- var value = toTimestamp(scale, scale.getRightValue(input));
+ var value = toTimestamp(scale, input);
if (value === null) {
return value;
}
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',
};
module.exports = Scale.extend({
+ _parse: function(raw, index) { // eslint-disable-line no-unused-vars
+ if (raw === undefined) {
+ return NaN;
+ }
+ return toTimestamp(this, raw);
+ },
+
+ _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;
+ },
- update: function() {
+ _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);
- },
+ helpers.mergeIf(time.displayFormats, adapter.formats());
- /**
- * 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();
-
- for (i = 0, ilen = dataLabels.length; i < ilen; ++i) {
- labels.push(parse(me, dataLabels[i]));
+ var min = Number.POSITIVE_INFINITY;
+ var max = Number.NEGATIVE_INFINITY;
+ var minmax = me._getMinMax(false);
+ var i, ilen, labels;
+
+ 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;
-
- // 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);
- }
- }
+ min = me.min;
+ max = me.max;
- 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) {
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);
- }
- if (typeof label === 'string') {
- return label;
+ return adapter.format(value, timeOpts.tooltipFormat);
}
- return adapter.format(toTimestamp(me, label), timeOpts.displayFormats.datetime);
+ return adapter.format(value, timeOpts.displayFormats.datetime);
},
/**
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 (time === null) {
- time = parse(me, value);
+ if (typeof value !== 'number') {
+ value = parse(me, value);
}
- if (time !== null) {
- return me.getPixelForOffset(time);
+ if (value !== null) {
+ return me.getPixelForOffset(value);
}
},
--- /dev/null
+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
+ }
+ }
+};
--- /dev/null
+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
+ }
+ }
+};
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() {
});
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);
});
});
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() {
});
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() {
});
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() {
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() {
});
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() {
});
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() {
});
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() {
});
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);
});
});
});
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() {
});
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);
}
});
- expect(chart.scales.yScale0.getLabelForIndex(0, 2)).toBe(150);
+ expect(chart.scales.yScale0.getLabelForValue(150)).toBe(150);
});
describe('when', 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);
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);
});
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);
});
}
}
});
- 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() {
// 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) {
xAxes: [{
id: 'xScale0',
type: 'time',
- position: 'bottom'
+ position: 'bottom',
}],
}
}
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'
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() {
});
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() {
});
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');
});
});
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() {