mirror of
https://github.com/chartjs/Chart.js.git
synced 2026-03-15 04:36:51 +01:00
* Convert axis options from arrays to objects * Updated all chart type defaults * Throw errors when axis type or position are not specified * Avoid raising unnecessary errors when merging options into the default configs * Fix additional tests * Ensure scale defaults are set if type is not explicitly defined * Another step * Include `scale` as `firstIDs.r` * update docs * Update for buildOrUpdateScales * Update migration guide * Add test back
1027 lines
24 KiB
JavaScript
1027 lines
24 KiB
JavaScript
'use strict';
|
|
|
|
var helpers = require('../helpers/index');
|
|
|
|
var resolve = helpers.options.resolve;
|
|
|
|
var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift'];
|
|
|
|
/**
|
|
* Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice',
|
|
* 'unshift') and notify the listener AFTER the array has been altered. Listeners are
|
|
* called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments.
|
|
*/
|
|
function listenArrayEvents(array, listener) {
|
|
if (array._chartjs) {
|
|
array._chartjs.listeners.push(listener);
|
|
return;
|
|
}
|
|
|
|
Object.defineProperty(array, '_chartjs', {
|
|
configurable: true,
|
|
enumerable: false,
|
|
value: {
|
|
listeners: [listener]
|
|
}
|
|
});
|
|
|
|
arrayEvents.forEach(function(key) {
|
|
var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1);
|
|
var base = array[key];
|
|
|
|
Object.defineProperty(array, key, {
|
|
configurable: true,
|
|
enumerable: false,
|
|
value: function() {
|
|
var args = Array.prototype.slice.call(arguments);
|
|
var res = base.apply(this, args);
|
|
|
|
helpers.each(array._chartjs.listeners, function(object) {
|
|
if (typeof object[method] === 'function') {
|
|
object[method].apply(object, args);
|
|
}
|
|
});
|
|
|
|
return res;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function scaleClip(scale, allowedOverflow) {
|
|
var opts = scale && scale.options || {};
|
|
var reverse = opts.reverse;
|
|
var min = opts.min === undefined ? allowedOverflow : 0;
|
|
var max = opts.max === undefined ? allowedOverflow : 0;
|
|
return {
|
|
start: reverse ? max : min,
|
|
end: reverse ? min : max
|
|
};
|
|
}
|
|
|
|
function defaultClip(xScale, yScale, allowedOverflow) {
|
|
if (allowedOverflow === false) {
|
|
return false;
|
|
}
|
|
var x = scaleClip(xScale, allowedOverflow);
|
|
var y = scaleClip(yScale, allowedOverflow);
|
|
|
|
return {
|
|
top: y.end,
|
|
right: x.end,
|
|
bottom: y.start,
|
|
left: x.start
|
|
};
|
|
}
|
|
|
|
function toClip(value) {
|
|
var t, r, b, l;
|
|
|
|
if (helpers.isObject(value)) {
|
|
t = value.top;
|
|
r = value.right;
|
|
b = value.bottom;
|
|
l = value.left;
|
|
} else {
|
|
t = r = b = l = value;
|
|
}
|
|
|
|
return {
|
|
top: t,
|
|
right: r,
|
|
bottom: b,
|
|
left: l
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Removes the given array event listener and cleanup extra attached properties (such as
|
|
* the _chartjs stub and overridden methods) if array doesn't have any more listeners.
|
|
*/
|
|
function unlistenArrayEvents(array, listener) {
|
|
var stub = array._chartjs;
|
|
if (!stub) {
|
|
return;
|
|
}
|
|
|
|
var listeners = stub.listeners;
|
|
var index = listeners.indexOf(listener);
|
|
if (index !== -1) {
|
|
listeners.splice(index, 1);
|
|
}
|
|
|
|
if (listeners.length > 0) {
|
|
return;
|
|
}
|
|
|
|
arrayEvents.forEach(function(key) {
|
|
delete array[key];
|
|
});
|
|
|
|
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 || helpers.sign(value) === helpers.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(indexScale, valueScale, meta) {
|
|
return indexScale.id + '.' + valueScale.id + '.' + meta.stack + '.' + meta.type;
|
|
}
|
|
|
|
function getUserBounds(scale) {
|
|
var {min, max, minDefined, maxDefined} = scale._getUserBounds();
|
|
return {
|
|
min: minDefined ? min : Number.NEGATIVE_INFINITY,
|
|
max: maxDefined ? max : Number.POSITIVE_INFINITY
|
|
};
|
|
}
|
|
|
|
function getOrCreateStack(stacks, stackKey, indexValue) {
|
|
const subStack = stacks[stackKey] || (stacks[stackKey] = {});
|
|
return subStack[indexValue] || (subStack[indexValue] = {});
|
|
}
|
|
|
|
function updateStacks(controller, parsed) {
|
|
const chart = controller.chart;
|
|
const meta = controller._cachedMeta;
|
|
const stacks = chart._stacks || (chart._stacks = {}); // map structure is {stackKey: {datasetIndex: value}}
|
|
const xScale = meta.xScale;
|
|
const yScale = meta.yScale;
|
|
const xId = xScale.id;
|
|
const yId = yScale.id;
|
|
const xStacked = isStacked(xScale, meta);
|
|
const yStacked = isStacked(yScale, meta);
|
|
const xKey = yStacked && getStackKey(xScale, yScale, meta);
|
|
const yKey = xStacked && getStackKey(yScale, xScale, meta);
|
|
const ilen = parsed.length;
|
|
const datasetIndex = meta.index;
|
|
let stack;
|
|
|
|
for (let i = 0; i < ilen; ++i) {
|
|
const item = parsed[i];
|
|
const x = item[xId];
|
|
const y = item[yId];
|
|
const itemStacks = item._stacks || (item._stacks = {});
|
|
|
|
if (yStacked) {
|
|
stack = itemStacks[yId] = getOrCreateStack(stacks, xKey, x);
|
|
stack[datasetIndex] = y;
|
|
}
|
|
if (xStacked) {
|
|
stack = itemStacks[xId] = getOrCreateStack(stacks, yKey, y);
|
|
stack[datasetIndex] = x;
|
|
}
|
|
}
|
|
}
|
|
|
|
function getFirstScaleId(chart, axis) {
|
|
const scales = chart.scales;
|
|
return Object.keys(scales).filter(key => {
|
|
return scales[key].axis === axis;
|
|
}).shift();
|
|
}
|
|
|
|
// Base class for all dataset controllers (line, bar, etc)
|
|
var DatasetController = function(chart, datasetIndex) {
|
|
this.initialize(chart, datasetIndex);
|
|
};
|
|
|
|
helpers.extend(DatasetController.prototype, {
|
|
|
|
/**
|
|
* Element type used to generate a meta dataset (e.g. Chart.element.Line).
|
|
* @type {Chart.core.element}
|
|
*/
|
|
datasetElementType: null,
|
|
|
|
/**
|
|
* Element type used to generate a meta data (e.g. Chart.element.Point).
|
|
* @type {Chart.core.element}
|
|
*/
|
|
dataElementType: null,
|
|
|
|
/**
|
|
* Dataset element option keys to be resolved in _resolveDatasetElementOptions.
|
|
* A derived controller may override this to resolve controller-specific options.
|
|
* The keys defined here are for backward compatibility for legend styles.
|
|
* @private
|
|
*/
|
|
_datasetElementOptions: [
|
|
'backgroundColor',
|
|
'borderCapStyle',
|
|
'borderColor',
|
|
'borderDash',
|
|
'borderDashOffset',
|
|
'borderJoinStyle',
|
|
'borderWidth'
|
|
],
|
|
|
|
/**
|
|
* Data element option keys to be resolved in _resolveDataElementOptions.
|
|
* A derived controller may override this to resolve controller-specific options.
|
|
* The keys defined here are for backward compatibility for legend styles.
|
|
* @private
|
|
*/
|
|
_dataElementOptions: [
|
|
'backgroundColor',
|
|
'borderColor',
|
|
'borderWidth',
|
|
'pointStyle'
|
|
],
|
|
|
|
initialize: function(chart, datasetIndex) {
|
|
const me = this;
|
|
let meta;
|
|
me.chart = chart;
|
|
me._ctx = chart.ctx;
|
|
me.index = datasetIndex;
|
|
me._cachedMeta = meta = me.getMeta();
|
|
me._type = meta.type;
|
|
me.linkScales();
|
|
meta._stacked = isStacked(meta.vScale, meta);
|
|
me.addElements();
|
|
},
|
|
|
|
updateIndex: function(datasetIndex) {
|
|
this.index = datasetIndex;
|
|
},
|
|
|
|
linkScales: function() {
|
|
const me = this;
|
|
const chart = me.chart;
|
|
const meta = me._cachedMeta;
|
|
const dataset = me.getDataset();
|
|
|
|
const xid = meta.xAxisID = dataset.xAxisID || getFirstScaleId(chart, 'x');
|
|
const yid = meta.yAxisID = dataset.yAxisID || getFirstScaleId(chart, 'y');
|
|
const rid = meta.rAxisID = dataset.rAxisID || getFirstScaleId(chart, 'r');
|
|
meta.xScale = me.getScaleForId(xid);
|
|
meta.yScale = me.getScaleForId(yid);
|
|
meta.rScale = me.getScaleForId(rid);
|
|
meta.iScale = me._getIndexScale();
|
|
meta.vScale = me._getValueScale();
|
|
},
|
|
|
|
getDataset: function() {
|
|
return this.chart.data.datasets[this.index];
|
|
},
|
|
|
|
getMeta: function() {
|
|
return this.chart.getDatasetMeta(this.index);
|
|
},
|
|
|
|
getScaleForId: function(scaleID) {
|
|
return this.chart.scales[scaleID];
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getValueScaleId: function() {
|
|
return this._cachedMeta.yAxisID;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getIndexScaleId: function() {
|
|
return this._cachedMeta.xAxisID;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getValueScale: function() {
|
|
return this.getScaleForId(this._getValueScaleId());
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getIndexScale: function() {
|
|
return this.getScaleForId(this._getIndexScaleId());
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getOtherScale: function(scale) {
|
|
const meta = this._cachedMeta;
|
|
return scale === meta.iScale
|
|
? meta.vScale
|
|
: meta.iScale;
|
|
},
|
|
|
|
reset: function() {
|
|
this._update(true);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
destroy: function() {
|
|
if (this._data) {
|
|
unlistenArrayEvents(this._data, this);
|
|
}
|
|
},
|
|
|
|
createElement: function(type) {
|
|
return type && new type();
|
|
},
|
|
|
|
/**
|
|
* @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 && helpers.arrayEquals(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() {
|
|
const me = this;
|
|
const iScale = me._cachedMeta.iScale;
|
|
const labels = iScale ? iScale._getLabels() : me.chart.data.labels;
|
|
|
|
if (me._labels === labels) {
|
|
return false;
|
|
}
|
|
|
|
me._labels = labels;
|
|
return true;
|
|
},
|
|
|
|
addElements: function() {
|
|
var me = this;
|
|
var meta = me._cachedMeta;
|
|
var metaData = meta.data;
|
|
var i, ilen, data;
|
|
|
|
me._dataCheck();
|
|
data = me._data;
|
|
|
|
for (i = 0, ilen = data.length; i < ilen; ++i) {
|
|
metaData[i] = metaData[i] || me.createElement(me.dataElementType);
|
|
}
|
|
|
|
meta.dataset = meta.dataset || me.createElement(me.datasetElementType);
|
|
},
|
|
|
|
buildOrUpdateElements: function() {
|
|
const me = this;
|
|
const dataChanged = me._dataCheck();
|
|
const labelsChanged = me._labelCheck();
|
|
const scaleChanged = me._scaleCheck();
|
|
const meta = me._cachedMeta;
|
|
|
|
// make sure cached _stacked status is current
|
|
meta._stacked = isStacked(meta.vScale, 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(dataChanged | labelsChanged | scaleChanged);
|
|
},
|
|
|
|
/**
|
|
* Returns the merged user-supplied and default dataset-level options
|
|
* @private
|
|
*/
|
|
_configure: function() {
|
|
const me = this;
|
|
me._config = helpers.merge({}, [
|
|
me.chart.options.datasets[me._type],
|
|
me.getDataset(),
|
|
], {
|
|
merger: function(key, target, source) {
|
|
if (key !== 'data') {
|
|
helpers._merger(key, target, source);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_parse: function(start, count) {
|
|
const me = this;
|
|
const meta = me._cachedMeta;
|
|
const data = me._data;
|
|
const iScale = meta.iScale;
|
|
const vScale = meta.vScale;
|
|
const stacked = isStacked(iScale, meta) || isStacked(vScale, meta);
|
|
let i, ilen, parsed;
|
|
|
|
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);
|
|
}
|
|
|
|
for (i = 0, ilen = parsed.length; i < ilen; ++i) {
|
|
meta.data[start + i]._parsed = parsed[i];
|
|
}
|
|
if (stacked) {
|
|
updateStacks(me, parsed);
|
|
}
|
|
|
|
iScale._invalidateCaches();
|
|
if (vScale !== iScale) {
|
|
vScale._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) {
|
|
const iScale = meta.iScale;
|
|
const vScale = meta.vScale;
|
|
const labels = iScale._getLabels();
|
|
const singleScale = iScale === vScale;
|
|
const parsed = [];
|
|
let 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) {
|
|
const xScale = meta.xScale;
|
|
const yScale = meta.yScale;
|
|
const parsed = [];
|
|
let 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) {
|
|
const xScale = meta.xScale;
|
|
const yScale = meta.yScale;
|
|
const parsed = [];
|
|
let 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) {
|
|
const data = this._cachedMeta.data;
|
|
if (index < 0 || index >= data.length) {
|
|
return;
|
|
}
|
|
return data[index]._parsed;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_applyStack: function(scale, parsed) {
|
|
const chart = this.chart;
|
|
const meta = this._cachedMeta;
|
|
const value = parsed[scale.id];
|
|
const stack = {
|
|
keys: getSortedDatasetIndices(chart, true),
|
|
values: parsed._stacks[scale.id]
|
|
};
|
|
return applyStack(stack, value, meta.index);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getMinMax: function(scale, canStack) {
|
|
const chart = this.chart;
|
|
const meta = this._cachedMeta;
|
|
const metaData = meta.data;
|
|
const ilen = metaData.length;
|
|
const stacked = canStack && meta._stacked;
|
|
const indices = getSortedDatasetIndices(chart, true);
|
|
const otherScale = this._getOtherScale(scale);
|
|
let max = Number.NEGATIVE_INFINITY;
|
|
let {min: otherMin, max: otherMax} = getUserBounds(otherScale);
|
|
let i, item, value, parsed, stack, min, minPositive, otherValue;
|
|
|
|
min = minPositive = Number.POSITIVE_INFINITY;
|
|
|
|
for (i = 0; i < ilen; ++i) {
|
|
item = metaData[i];
|
|
parsed = item._parsed;
|
|
value = parsed[scale.id];
|
|
otherValue = parsed[otherScale.id];
|
|
if (item.hidden || isNaN(value) ||
|
|
otherMin > otherValue || otherMax < otherValue) {
|
|
continue;
|
|
}
|
|
if (stacked) {
|
|
stack = {
|
|
keys: indices,
|
|
values: parsed._stacks[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
|
|
};
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getAllParsedValues: function(scale) {
|
|
const meta = this._cachedMeta;
|
|
const metaData = meta.data;
|
|
const values = [];
|
|
let 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;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_cacheScaleStackStatus: function() {
|
|
const me = this;
|
|
const meta = me._cachedMeta;
|
|
const iScale = meta.iScale;
|
|
const vScale = meta.vScale;
|
|
const cache = me._scaleStacked = {};
|
|
if (iScale && vScale) {
|
|
cache[iScale.id] = iScale.options.stacked;
|
|
cache[vScale.id] = vScale.options.stacked;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_scaleCheck: function() {
|
|
const me = this;
|
|
const meta = me._cachedMeta;
|
|
const iScale = meta.iScale;
|
|
const vScale = meta.vScale;
|
|
const cache = me._scaleStacked;
|
|
return !cache ||
|
|
!iScale ||
|
|
!vScale ||
|
|
cache[iScale.id] !== iScale.options.stacked ||
|
|
cache[vScale.id] !== vScale.options.stacked;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getMaxOverflow: function() {
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getLabelAndValue: function(index) {
|
|
const me = this;
|
|
const meta = me._cachedMeta;
|
|
const iScale = meta.iScale;
|
|
const vScale = meta.vScale;
|
|
const parsed = me._getParsed(index);
|
|
return {
|
|
label: iScale ? '' + iScale.getLabelForValue(parsed[iScale.id]) : '',
|
|
value: vScale ? '' + vScale.getLabelForValue(parsed[vScale.id]) : ''
|
|
};
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_update: function(reset) {
|
|
const me = this;
|
|
const meta = me._cachedMeta;
|
|
me._configure();
|
|
me._cachedDataOpts = null;
|
|
me.update(reset);
|
|
meta._clip = toClip(helpers.valueOrDefault(me._config.clip, defaultClip(meta.xScale, meta.yScale, me._getMaxOverflow())));
|
|
me._cacheScaleStackStatus();
|
|
},
|
|
|
|
update: helpers.noop,
|
|
|
|
transition: function(easingValue) {
|
|
const meta = this._cachedMeta;
|
|
const elements = meta.data || [];
|
|
const ilen = elements.length;
|
|
let i = 0;
|
|
|
|
for (; i < ilen; ++i) {
|
|
elements[i].transition(easingValue);
|
|
}
|
|
|
|
if (meta.dataset) {
|
|
meta.dataset.transition(easingValue);
|
|
}
|
|
},
|
|
|
|
draw: function() {
|
|
const ctx = this._ctx;
|
|
const meta = this._cachedMeta;
|
|
const elements = meta.data || [];
|
|
const ilen = elements.length;
|
|
let i = 0;
|
|
|
|
if (meta.dataset) {
|
|
meta.dataset.draw(ctx);
|
|
}
|
|
|
|
for (; i < ilen; ++i) {
|
|
elements[i].draw(ctx);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Returns a set of predefined style properties that should be used to represent the dataset
|
|
* or the data if the index is specified
|
|
* @param {number} index - data index
|
|
* @return {IStyleInterface} style object
|
|
*/
|
|
getStyle: function(index) {
|
|
const me = this;
|
|
const meta = me._cachedMeta;
|
|
const dataset = meta.dataset;
|
|
let style;
|
|
|
|
me._configure();
|
|
if (dataset && index === undefined) {
|
|
style = me._resolveDatasetElementOptions();
|
|
} else {
|
|
index = index || 0;
|
|
style = me._resolveDataElementOptions(index);
|
|
}
|
|
|
|
if (style.fill === false || style.fill === null) {
|
|
style.backgroundColor = style.borderColor;
|
|
}
|
|
|
|
return style;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_resolveDatasetElementOptions: function(active) {
|
|
const me = this;
|
|
const chart = me.chart;
|
|
const datasetOpts = me._config;
|
|
const options = chart.options.elements[me.datasetElementType.prototype._type] || {};
|
|
const elementOptions = me._datasetElementOptions;
|
|
const values = {};
|
|
const context = {
|
|
chart,
|
|
dataset: me.getDataset(),
|
|
datasetIndex: me.index,
|
|
active
|
|
};
|
|
let i, ilen, key, readKey;
|
|
|
|
for (i = 0, ilen = elementOptions.length; i < ilen; ++i) {
|
|
key = elementOptions[i];
|
|
readKey = active ? 'hover' + key.charAt(0).toUpperCase() + key.slice(1) : key;
|
|
values[key] = resolve([
|
|
datasetOpts[readKey],
|
|
options[readKey]
|
|
], context);
|
|
}
|
|
|
|
return values;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_resolveDataElementOptions: function(index) {
|
|
const me = this;
|
|
const cached = me._cachedDataOpts;
|
|
if (cached) {
|
|
return cached;
|
|
}
|
|
const chart = me.chart;
|
|
const datasetOpts = me._config;
|
|
const options = chart.options.elements[me.dataElementType.prototype._type] || {};
|
|
const elementOptions = me._dataElementOptions;
|
|
const values = {};
|
|
const context = {
|
|
chart: chart,
|
|
dataIndex: index,
|
|
dataset: me.getDataset(),
|
|
datasetIndex: me.index
|
|
};
|
|
const info = {cacheable: true};
|
|
let keys, i, ilen, key;
|
|
|
|
if (helpers.isArray(elementOptions)) {
|
|
for (i = 0, ilen = elementOptions.length; i < ilen; ++i) {
|
|
key = elementOptions[i];
|
|
values[key] = resolve([
|
|
datasetOpts[key],
|
|
options[key]
|
|
], context, index, info);
|
|
}
|
|
} else {
|
|
keys = Object.keys(elementOptions);
|
|
for (i = 0, ilen = keys.length; i < ilen; ++i) {
|
|
key = keys[i];
|
|
values[key] = resolve([
|
|
datasetOpts[elementOptions[key]],
|
|
datasetOpts[key],
|
|
options[key]
|
|
], context, index, info);
|
|
}
|
|
}
|
|
|
|
if (info.cacheable) {
|
|
me._cachedDataOpts = Object.freeze(values);
|
|
}
|
|
|
|
return values;
|
|
},
|
|
|
|
removeHoverStyle: function(element) {
|
|
helpers.merge(element._model, element.$previousStyle || {});
|
|
delete element.$previousStyle;
|
|
},
|
|
|
|
setHoverStyle: function(element, datasetIndex, index) {
|
|
const dataset = this.chart.data.datasets[datasetIndex];
|
|
const model = element._model;
|
|
const getHoverColor = helpers.getHoverColor;
|
|
|
|
element.$previousStyle = {
|
|
backgroundColor: model.backgroundColor,
|
|
borderColor: model.borderColor,
|
|
borderWidth: model.borderWidth
|
|
};
|
|
|
|
model.backgroundColor = resolve([dataset.hoverBackgroundColor, getHoverColor(model.backgroundColor)], undefined, index);
|
|
model.borderColor = resolve([dataset.hoverBorderColor, getHoverColor(model.borderColor)], undefined, index);
|
|
model.borderWidth = resolve([dataset.hoverBorderWidth, model.borderWidth], undefined, index);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_removeDatasetHoverStyle: function() {
|
|
const element = this._cachedMeta.dataset;
|
|
|
|
if (element) {
|
|
this.removeHoverStyle(element);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_setDatasetHoverStyle: function() {
|
|
const element = this._cachedMeta.dataset;
|
|
const prev = {};
|
|
let i, ilen, key, keys, hoverOptions, model;
|
|
|
|
if (!element) {
|
|
return;
|
|
}
|
|
|
|
model = element._model;
|
|
hoverOptions = this._resolveDatasetElementOptions(true);
|
|
|
|
keys = Object.keys(hoverOptions);
|
|
for (i = 0, ilen = keys.length; i < ilen; ++i) {
|
|
key = keys[i];
|
|
prev[key] = model[key];
|
|
model[key] = hoverOptions[key];
|
|
}
|
|
|
|
element.$previousStyle = prev;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
resyncElements: function(changed) {
|
|
const me = this;
|
|
const meta = me._cachedMeta;
|
|
const numMeta = meta.data.length;
|
|
const numData = me._data.length;
|
|
|
|
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);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
insertElements: function(start, count) {
|
|
const me = this;
|
|
const elements = [];
|
|
const data = me._cachedMeta.data;
|
|
let i;
|
|
|
|
for (i = start; i < start + count; ++i) {
|
|
elements.push(me.createElement(me.dataElementType));
|
|
}
|
|
data.splice(start, 0, ...elements);
|
|
me._parse(start, count);
|
|
me.updateElements(data, start, count);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
onDataPush: function() {
|
|
const count = arguments.length;
|
|
this.insertElements(this.getDataset().data.length - count, count);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
onDataPop: function() {
|
|
this._cachedMeta.data.pop();
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
onDataShift: function() {
|
|
this._cachedMeta.data.shift();
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
onDataSplice: function(start, count) {
|
|
this._cachedMeta.data.splice(start, count);
|
|
this.insertElements(start, arguments.length - 2);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
onDataUnshift: function() {
|
|
this.insertElements(0, arguments.length);
|
|
}
|
|
});
|
|
|
|
DatasetController.extend = helpers.inherits;
|
|
|
|
module.exports = DatasetController;
|