Make Chart.controllers.* importable (#5871)

`controllers.*.js` and `core.datasetController.js` are now importable (no more function export), that's why there is so many changes mainly due to one indentation level removed. Split code for `bar/horizontalBar` and `doughnut/pie` in separate files, added a global controllers import (`src/controllers/index.js`) and add tests to check that all dataset controllers are correctly registered under `chart.controllers.{type}`.
This commit is contained in:
Simon Brunel 2018-11-29 21:06:34 +01:00 committed by GitHub
parent c84d249035
commit be8d78a900
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1607 additions and 1585 deletions

View File

@ -10,6 +10,8 @@ require('./core/core.helpers')(Chart);
Chart.Animation = require('./core/core.animation');
Chart.animationService = require('./core/core.animations');
Chart.controllers = require('./controllers/index');
Chart.DatasetController = require('./core/core.datasetController');
Chart.defaults = require('./core/core.defaults');
Chart.Element = require('./core/core.element');
Chart.elements = require('./elements/index');
@ -23,7 +25,6 @@ Chart.Ticks = require('./core/core.ticks');
Chart.Tooltip = require('./core/core.tooltip');
require('./core/core.controller')(Chart);
require('./core/core.datasetController')(Chart);
require('./scales/scale.linearbase')(Chart);
require('./scales/scale.category')(Chart);
@ -32,16 +33,6 @@ require('./scales/scale.logarithmic')(Chart);
require('./scales/scale.radialLinear')(Chart);
require('./scales/scale.time')(Chart);
// Controllers must be loaded after elements
// See Chart.core.datasetController.dataElementType
require('./controllers/controller.bar')(Chart);
require('./controllers/controller.bubble')(Chart);
require('./controllers/controller.doughnut')(Chart);
require('./controllers/controller.line')(Chart);
require('./controllers/controller.polarArea')(Chart);
require('./controllers/controller.radar')(Chart);
require('./controllers/controller.scatter')(Chart);
// Loading built-in plugins
var plugins = require('./plugins');
for (var k in plugins) {

View File

@ -1,5 +1,6 @@
'use strict';
var DatasetController = require('../core/core.datasetController');
var defaults = require('../core/core.defaults');
var elements = require('../elements/index');
var helpers = require('../helpers/index');
@ -12,15 +13,9 @@ defaults._set('bar', {
scales: {
xAxes: [{
type: 'category',
// Specific to Bar Controller
categoryPercentage: 0.8,
barPercentage: 0.9,
// offset settings
offset: true,
// grid line settings
gridLines: {
offsetGridLines: true
}
@ -32,69 +27,6 @@ defaults._set('bar', {
}
});
defaults._set('horizontalBar', {
hover: {
mode: 'index',
axis: 'y'
},
scales: {
xAxes: [{
type: 'linear',
position: 'bottom'
}],
yAxes: [{
position: 'left',
type: 'category',
// Specific to Horizontal Bar Controller
categoryPercentage: 0.8,
barPercentage: 0.9,
// offset settings
offset: true,
// grid line settings
gridLines: {
offsetGridLines: true
}
}]
},
elements: {
rectangle: {
borderSkipped: 'left'
}
},
tooltips: {
callbacks: {
title: function(item, data) {
// Pick first xLabel for now
var title = '';
if (item.length > 0) {
if (item[0].yLabel) {
title = item[0].yLabel;
} else if (data.labels.length > 0 && item[0].index < data.labels.length) {
title = data.labels[item[0].index];
}
}
return title;
},
label: function(item, data) {
var datasetLabel = data.datasets[item.datasetIndex].label || '';
return datasetLabel + ': ' + item.xLabel;
}
},
mode: 'index',
axis: 'y'
}
});
/**
* Computes the "optimal" sample size to maintain bars equally sized while preventing overlap.
* @private
@ -182,349 +114,330 @@ function computeFlexCategoryTraits(index, ruler, options) {
};
}
module.exports = function(Chart) {
module.exports = DatasetController.extend({
Chart.controllers.bar = Chart.DatasetController.extend({
dataElementType: elements.Rectangle,
dataElementType: elements.Rectangle,
initialize: function() {
var me = this;
var meta;
initialize: function() {
var me = this;
var meta;
DatasetController.prototype.initialize.apply(me, arguments);
Chart.DatasetController.prototype.initialize.apply(me, arguments);
meta = me.getMeta();
meta.stack = me.getDataset().stack;
meta.bar = true;
},
meta = me.getMeta();
meta.stack = me.getDataset().stack;
meta.bar = true;
},
update: function(reset) {
var me = this;
var rects = me.getMeta().data;
var i, ilen;
update: function(reset) {
var me = this;
var rects = me.getMeta().data;
var i, ilen;
me._ruler = me.getRuler();
me._ruler = me.getRuler();
for (i = 0, ilen = rects.length; i < ilen; ++i) {
me.updateElement(rects[i], i, reset);
}
},
for (i = 0, ilen = rects.length; i < ilen; ++i) {
me.updateElement(rects[i], i, reset);
updateElement: function(rectangle, index, reset) {
var me = this;
var meta = me.getMeta();
var dataset = me.getDataset();
var options = me._resolveElementOptions(rectangle, index);
rectangle._xScale = me.getScaleForId(meta.xAxisID);
rectangle._yScale = me.getScaleForId(meta.yAxisID);
rectangle._datasetIndex = me.index;
rectangle._index = index;
rectangle._model = {
backgroundColor: options.backgroundColor,
borderColor: options.borderColor,
borderSkipped: options.borderSkipped,
borderWidth: options.borderWidth,
datasetLabel: dataset.label,
label: me.chart.data.labels[index]
};
me._updateElementGeometry(rectangle, index, reset);
rectangle.pivot();
},
/**
* @private
*/
_updateElementGeometry: function(rectangle, index, reset) {
var me = this;
var model = rectangle._model;
var vscale = me.getValueScale();
var base = vscale.getBasePixel();
var horizontal = vscale.isHorizontal();
var ruler = me._ruler || me.getRuler();
var vpixels = me.calculateBarValuePixels(me.index, index);
var ipixels = me.calculateBarIndexPixels(me.index, index, ruler);
model.horizontal = horizontal;
model.base = reset ? base : vpixels.base;
model.x = horizontal ? reset ? base : vpixels.head : ipixels.center;
model.y = horizontal ? ipixels.center : reset ? base : vpixels.head;
model.height = horizontal ? ipixels.size : undefined;
model.width = horizontal ? undefined : ipixels.size;
},
/**
* @private
*/
getValueScaleId: function() {
return this.getMeta().yAxisID;
},
/**
* @private
*/
getIndexScaleId: function() {
return this.getMeta().xAxisID;
},
/**
* @private
*/
getValueScale: function() {
return this.getScaleForId(this.getValueScaleId());
},
/**
* @private
*/
getIndexScale: function() {
return this.getScaleForId(this.getIndexScaleId());
},
/**
* Returns the stacks based on groups and bar visibility.
* @param {Number} [last] - The dataset index
* @returns {Array} The stack list
* @private
*/
_getStacks: function(last) {
var me = this;
var chart = me.chart;
var scale = me.getIndexScale();
var stacked = scale.options.stacked;
var ilen = last === undefined ? chart.data.datasets.length : last + 1;
var stacks = [];
var i, meta;
for (i = 0; i < ilen; ++i) {
meta = chart.getDatasetMeta(i);
if (meta.bar && chart.isDatasetVisible(i) &&
(stacked === false ||
(stacked === true && stacks.indexOf(meta.stack) === -1) ||
(stacked === undefined && (meta.stack === undefined || stacks.indexOf(meta.stack) === -1)))) {
stacks.push(meta.stack);
}
},
}
updateElement: function(rectangle, index, reset) {
var me = this;
var meta = me.getMeta();
var dataset = me.getDataset();
var options = me._resolveElementOptions(rectangle, index);
return stacks;
},
rectangle._xScale = me.getScaleForId(meta.xAxisID);
rectangle._yScale = me.getScaleForId(meta.yAxisID);
rectangle._datasetIndex = me.index;
rectangle._index = index;
rectangle._model = {
backgroundColor: options.backgroundColor,
borderColor: options.borderColor,
borderSkipped: options.borderSkipped,
borderWidth: options.borderWidth,
datasetLabel: dataset.label,
label: me.chart.data.labels[index]
};
/**
* Returns the effective number of stacks based on groups and bar visibility.
* @private
*/
getStackCount: function() {
return this._getStacks().length;
},
me._updateElementGeometry(rectangle, index, reset);
/**
* Returns the stack index for the given dataset based on groups and bar visibility.
* @param {Number} [datasetIndex] - The dataset index
* @param {String} [name] - The stack name to find
* @returns {Number} The stack index
* @private
*/
getStackIndex: function(datasetIndex, name) {
var stacks = this._getStacks(datasetIndex);
var index = (name !== undefined)
? stacks.indexOf(name)
: -1; // indexOf returns -1 if element is not present
rectangle.pivot();
},
return (index === -1)
? stacks.length - 1
: index;
},
/**
* @private
*/
_updateElementGeometry: function(rectangle, index, reset) {
var me = this;
var model = rectangle._model;
var vscale = me.getValueScale();
var base = vscale.getBasePixel();
var horizontal = vscale.isHorizontal();
var ruler = me._ruler || me.getRuler();
var vpixels = me.calculateBarValuePixels(me.index, index);
var ipixels = me.calculateBarIndexPixels(me.index, index, ruler);
/**
* @private
*/
getRuler: function() {
var me = this;
var scale = me.getIndexScale();
var stackCount = me.getStackCount();
var datasetIndex = me.index;
var isHorizontal = scale.isHorizontal();
var start = isHorizontal ? scale.left : scale.top;
var end = start + (isHorizontal ? scale.width : scale.height);
var pixels = [];
var i, ilen, min;
model.horizontal = horizontal;
model.base = reset ? base : vpixels.base;
model.x = horizontal ? reset ? base : vpixels.head : ipixels.center;
model.y = horizontal ? ipixels.center : reset ? base : vpixels.head;
model.height = horizontal ? ipixels.size : undefined;
model.width = horizontal ? undefined : ipixels.size;
},
for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) {
pixels.push(scale.getPixelForValue(null, i, datasetIndex));
}
/**
* @private
*/
getValueScaleId: function() {
return this.getMeta().yAxisID;
},
min = helpers.isNullOrUndef(scale.options.barThickness)
? computeMinSampleSize(scale, pixels)
: -1;
/**
* @private
*/
getIndexScaleId: function() {
return this.getMeta().xAxisID;
},
return {
min: min,
pixels: pixels,
start: start,
end: end,
stackCount: stackCount,
scale: scale
};
},
/**
* @private
*/
getValueScale: function() {
return this.getScaleForId(this.getValueScaleId());
},
/**
* Note: pixel values are not clamped to the scale area.
* @private
*/
calculateBarValuePixels: function(datasetIndex, index) {
var me = this;
var chart = me.chart;
var meta = me.getMeta();
var scale = me.getValueScale();
var isHorizontal = scale.isHorizontal();
var datasets = chart.data.datasets;
var value = scale.getRightValue(datasets[datasetIndex].data[index]);
var minBarLength = scale.options.minBarLength;
var stacked = scale.options.stacked;
var stack = meta.stack;
var start = 0;
var i, imeta, ivalue, base, head, size;
/**
* @private
*/
getIndexScale: function() {
return this.getScaleForId(this.getIndexScaleId());
},
if (stacked || (stacked === undefined && stack !== undefined)) {
for (i = 0; i < datasetIndex; ++i) {
imeta = chart.getDatasetMeta(i);
/**
* Returns the stacks based on groups and bar visibility.
* @param {Number} [last] - The dataset index
* @returns {Array} The stack list
* @private
*/
_getStacks: function(last) {
var me = this;
var chart = me.chart;
var scale = me.getIndexScale();
var stacked = scale.options.stacked;
var ilen = last === undefined ? chart.data.datasets.length : last + 1;
var stacks = [];
var i, meta;
if (imeta.bar &&
imeta.stack === stack &&
imeta.controller.getValueScaleId() === scale.id &&
chart.isDatasetVisible(i)) {
for (i = 0; i < ilen; ++i) {
meta = chart.getDatasetMeta(i);
if (meta.bar && chart.isDatasetVisible(i) &&
(stacked === false ||
(stacked === true && stacks.indexOf(meta.stack) === -1) ||
(stacked === undefined && (meta.stack === undefined || stacks.indexOf(meta.stack) === -1)))) {
stacks.push(meta.stack);
}
}
return stacks;
},
/**
* Returns the effective number of stacks based on groups and bar visibility.
* @private
*/
getStackCount: function() {
return this._getStacks().length;
},
/**
* Returns the stack index for the given dataset based on groups and bar visibility.
* @param {Number} [datasetIndex] - The dataset index
* @param {String} [name] - The stack name to find
* @returns {Number} The stack index
* @private
*/
getStackIndex: function(datasetIndex, name) {
var stacks = this._getStacks(datasetIndex);
var index = (name !== undefined)
? stacks.indexOf(name)
: -1; // indexOf returns -1 if element is not present
return (index === -1)
? stacks.length - 1
: index;
},
/**
* @private
*/
getRuler: function() {
var me = this;
var scale = me.getIndexScale();
var stackCount = me.getStackCount();
var datasetIndex = me.index;
var isHorizontal = scale.isHorizontal();
var start = isHorizontal ? scale.left : scale.top;
var end = start + (isHorizontal ? scale.width : scale.height);
var pixels = [];
var i, ilen, min;
for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) {
pixels.push(scale.getPixelForValue(null, i, datasetIndex));
}
min = helpers.isNullOrUndef(scale.options.barThickness)
? computeMinSampleSize(scale, pixels)
: -1;
return {
min: min,
pixels: pixels,
start: start,
end: end,
stackCount: stackCount,
scale: scale
};
},
/**
* Note: pixel values are not clamped to the scale area.
* @private
*/
calculateBarValuePixels: function(datasetIndex, index) {
var me = this;
var chart = me.chart;
var meta = me.getMeta();
var scale = me.getValueScale();
var isHorizontal = scale.isHorizontal();
var datasets = chart.data.datasets;
var value = scale.getRightValue(datasets[datasetIndex].data[index]);
var minBarLength = scale.options.minBarLength;
var stacked = scale.options.stacked;
var stack = meta.stack;
var start = 0;
var i, imeta, ivalue, base, head, size;
if (stacked || (stacked === undefined && stack !== undefined)) {
for (i = 0; i < datasetIndex; ++i) {
imeta = chart.getDatasetMeta(i);
if (imeta.bar &&
imeta.stack === stack &&
imeta.controller.getValueScaleId() === scale.id &&
chart.isDatasetVisible(i)) {
ivalue = scale.getRightValue(datasets[i].data[index]);
if ((value < 0 && ivalue < 0) || (value >= 0 && ivalue > 0)) {
start += ivalue;
}
ivalue = scale.getRightValue(datasets[i].data[index]);
if ((value < 0 && ivalue < 0) || (value >= 0 && ivalue > 0)) {
start += ivalue;
}
}
}
base = scale.getPixelForValue(start);
head = scale.getPixelForValue(start + value);
size = (head - base) / 2;
if (minBarLength !== undefined && Math.abs(size) < minBarLength) {
size = minBarLength;
if (value >= 0 && !isHorizontal || value < 0 && isHorizontal) {
head = base - minBarLength;
} else {
head = base + minBarLength;
}
}
return {
size: size,
base: base,
head: head,
center: head + size / 2
};
},
/**
* @private
*/
calculateBarIndexPixels: function(datasetIndex, index, ruler) {
var me = this;
var options = ruler.scale.options;
var range = options.barThickness === 'flex'
? computeFlexCategoryTraits(index, ruler, options)
: computeFitCategoryTraits(index, ruler, options);
var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack);
var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2);
var size = Math.min(
helpers.valueOrDefault(options.maxBarThickness, Infinity),
range.chunk * range.ratio);
return {
base: center - size / 2,
head: center + size / 2,
center: center,
size: size
};
},
draw: function() {
var me = this;
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) {
if (!isNaN(scale.getRightValue(dataset.data[i]))) {
rects[i].draw();
}
}
helpers.canvas.unclipArea(chart.ctx);
},
/**
* @private
*/
_resolveElementOptions: function(rectangle, index) {
var me = this;
var chart = me.chart;
var datasets = chart.data.datasets;
var dataset = datasets[me.index];
var custom = rectangle.custom || {};
var options = chart.options.elements.rectangle;
var resolve = helpers.options.resolve;
var values = {};
var i, ilen, key;
// Scriptable options
var context = {
chart: chart,
dataIndex: index,
dataset: dataset,
datasetIndex: me.index
};
var keys = [
'backgroundColor',
'borderColor',
'borderSkipped',
'borderWidth'
];
for (i = 0, ilen = keys.length; i < ilen; ++i) {
key = keys[i];
values[key] = resolve([
custom[key],
dataset[key],
options[key]
], context, index);
}
return values;
}
});
Chart.controllers.horizontalBar = Chart.controllers.bar.extend({
/**
* @private
*/
getValueScaleId: function() {
return this.getMeta().xAxisID;
},
base = scale.getPixelForValue(start);
head = scale.getPixelForValue(start + value);
size = (head - base) / 2;
/**
* @private
*/
getIndexScaleId: function() {
return this.getMeta().yAxisID;
if (minBarLength !== undefined && Math.abs(size) < minBarLength) {
size = minBarLength;
if (value >= 0 && !isHorizontal || value < 0 && isHorizontal) {
head = base - minBarLength;
} else {
head = base + minBarLength;
}
}
});
};
return {
size: size,
base: base,
head: head,
center: head + size / 2
};
},
/**
* @private
*/
calculateBarIndexPixels: function(datasetIndex, index, ruler) {
var me = this;
var options = ruler.scale.options;
var range = options.barThickness === 'flex'
? computeFlexCategoryTraits(index, ruler, options)
: computeFitCategoryTraits(index, ruler, options);
var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack);
var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2);
var size = Math.min(
helpers.valueOrDefault(options.maxBarThickness, Infinity),
range.chunk * range.ratio);
return {
base: center - size / 2,
head: center + size / 2,
center: center,
size: size
};
},
draw: function() {
var me = this;
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) {
if (!isNaN(scale.getRightValue(dataset.data[i]))) {
rects[i].draw();
}
}
helpers.canvas.unclipArea(chart.ctx);
},
/**
* @private
*/
_resolveElementOptions: function(rectangle, index) {
var me = this;
var chart = me.chart;
var datasets = chart.data.datasets;
var dataset = datasets[me.index];
var custom = rectangle.custom || {};
var options = chart.options.elements.rectangle;
var resolve = helpers.options.resolve;
var values = {};
var i, ilen, key;
// Scriptable options
var context = {
chart: chart,
dataIndex: index,
dataset: dataset,
datasetIndex: me.index
};
var keys = [
'backgroundColor',
'borderColor',
'borderSkipped',
'borderWidth'
];
for (i = 0, ilen = keys.length; i < ilen; ++i) {
key = keys[i];
values[key] = resolve([
custom[key],
dataset[key],
options[key]
], context, index);
}
return values;
}
});

View File

@ -1,5 +1,6 @@
'use strict';
var DatasetController = require('../core/core.datasetController');
var defaults = require('../core/core.defaults');
var elements = require('../elements/index');
var helpers = require('../helpers/index');
@ -37,140 +38,136 @@ defaults._set('bubble', {
}
});
module.exports = DatasetController.extend({
/**
* @protected
*/
dataElementType: elements.Point,
module.exports = function(Chart) {
/**
* @protected
*/
update: function(reset) {
var me = this;
var meta = me.getMeta();
var points = meta.data;
Chart.controllers.bubble = Chart.DatasetController.extend({
/**
* @protected
*/
dataElementType: elements.Point,
// Update Points
helpers.each(points, function(point, index) {
me.updateElement(point, index, reset);
});
},
/**
* @protected
*/
update: function(reset) {
var me = this;
var meta = me.getMeta();
var points = meta.data;
/**
* @protected
*/
updateElement: function(point, index, reset) {
var me = this;
var meta = me.getMeta();
var custom = point.custom || {};
var xScale = me.getScaleForId(meta.xAxisID);
var yScale = me.getScaleForId(meta.yAxisID);
var options = me._resolveElementOptions(point, index);
var data = me.getDataset().data[index];
var dsIndex = me.index;
// Update Points
helpers.each(points, function(point, index) {
me.updateElement(point, index, reset);
});
},
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);
/**
* @protected
*/
updateElement: function(point, index, reset) {
var me = this;
var meta = me.getMeta();
var custom = point.custom || {};
var xScale = me.getScaleForId(meta.xAxisID);
var yScale = me.getScaleForId(meta.yAxisID);
var options = me._resolveElementOptions(point, index);
var data = me.getDataset().data[index];
var dsIndex = me.index;
point._xScale = xScale;
point._yScale = yScale;
point._options = options;
point._datasetIndex = dsIndex;
point._index = index;
point._model = {
backgroundColor: options.backgroundColor,
borderColor: options.borderColor,
borderWidth: options.borderWidth,
hitRadius: options.hitRadius,
pointStyle: options.pointStyle,
rotation: options.rotation,
radius: reset ? 0 : options.radius,
skip: custom.skip || isNaN(x) || isNaN(y),
x: x,
y: y,
};
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);
point.pivot();
},
point._xScale = xScale;
point._yScale = yScale;
point._options = options;
point._datasetIndex = dsIndex;
point._index = index;
point._model = {
backgroundColor: options.backgroundColor,
borderColor: options.borderColor,
borderWidth: options.borderWidth,
hitRadius: options.hitRadius,
pointStyle: options.pointStyle,
rotation: options.rotation,
radius: reset ? 0 : options.radius,
skip: custom.skip || isNaN(x) || isNaN(y),
x: x,
y: y,
};
/**
* @protected
*/
setHoverStyle: function(point) {
var model = point._model;
var options = point._options;
point.pivot();
},
point.$previousStyle = {
backgroundColor: model.backgroundColor,
borderColor: model.borderColor,
borderWidth: model.borderWidth,
radius: model.radius
};
/**
* @protected
*/
setHoverStyle: function(point) {
var model = point._model;
var options = point._options;
model.backgroundColor = helpers.valueOrDefault(options.hoverBackgroundColor, helpers.getHoverColor(options.backgroundColor));
model.borderColor = helpers.valueOrDefault(options.hoverBorderColor, helpers.getHoverColor(options.borderColor));
model.borderWidth = helpers.valueOrDefault(options.hoverBorderWidth, options.borderWidth);
model.radius = options.radius + options.hoverRadius;
},
point.$previousStyle = {
backgroundColor: model.backgroundColor,
borderColor: model.borderColor,
borderWidth: model.borderWidth,
radius: model.radius
};
/**
* @private
*/
_resolveElementOptions: function(point, index) {
var me = this;
var chart = me.chart;
var datasets = chart.data.datasets;
var dataset = datasets[me.index];
var custom = point.custom || {};
var options = chart.options.elements.point;
var resolve = helpers.options.resolve;
var data = dataset.data[index];
var values = {};
var i, ilen, key;
model.backgroundColor = helpers.valueOrDefault(options.hoverBackgroundColor, helpers.getHoverColor(options.backgroundColor));
model.borderColor = helpers.valueOrDefault(options.hoverBorderColor, helpers.getHoverColor(options.borderColor));
model.borderWidth = helpers.valueOrDefault(options.hoverBorderWidth, options.borderWidth);
model.radius = options.radius + options.hoverRadius;
},
// Scriptable options
var context = {
chart: chart,
dataIndex: index,
dataset: dataset,
datasetIndex: me.index
};
/**
* @private
*/
_resolveElementOptions: function(point, index) {
var me = this;
var chart = me.chart;
var datasets = chart.data.datasets;
var dataset = datasets[me.index];
var custom = point.custom || {};
var options = chart.options.elements.point;
var resolve = helpers.options.resolve;
var data = dataset.data[index];
var values = {};
var i, ilen, key;
var keys = [
'backgroundColor',
'borderColor',
'borderWidth',
'hoverBackgroundColor',
'hoverBorderColor',
'hoverBorderWidth',
'hoverRadius',
'hitRadius',
'pointStyle',
'rotation'
];
// Scriptable options
var context = {
chart: chart,
dataIndex: index,
dataset: dataset,
datasetIndex: me.index
};
var keys = [
'backgroundColor',
'borderColor',
'borderWidth',
'hoverBackgroundColor',
'hoverBorderColor',
'hoverBorderWidth',
'hoverRadius',
'hitRadius',
'pointStyle',
'rotation'
];
for (i = 0, ilen = keys.length; i < ilen; ++i) {
key = keys[i];
values[key] = resolve([
custom[key],
dataset[key],
options[key]
], context, index);
}
// Custom radius resolution
values.radius = resolve([
custom.radius,
data ? data.r : undefined,
dataset.radius,
options.radius
for (i = 0, ilen = keys.length; i < ilen; ++i) {
key = keys[i];
values[key] = resolve([
custom[key],
dataset[key],
options[key]
], context, index);
return values;
}
});
};
// Custom radius resolution
values.radius = resolve([
custom.radius,
data ? data.r : undefined,
dataset.radius,
options.radius
], context, index);
return values;
}
});

View File

@ -1,5 +1,6 @@
'use strict';
var DatasetController = require('../core/core.datasetController');
var defaults = require('../core/core.defaults');
var elements = require('../elements/index');
var helpers = require('../helpers/index');
@ -118,184 +119,176 @@ defaults._set('doughnut', {
}
});
defaults._set('pie', helpers.clone(defaults.doughnut));
defaults._set('pie', {
cutoutPercentage: 0
});
module.exports = DatasetController.extend({
module.exports = function(Chart) {
dataElementType: elements.Arc,
Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({
linkScales: helpers.noop,
dataElementType: elements.Arc,
// 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;
linkScales: helpers.noop,
// 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;
for (var j = 0; j < datasetIndex; ++j) {
if (this.chart.isDatasetVisible(j)) {
++ringIndex;
}
for (var j = 0; j < datasetIndex; ++j) {
if (this.chart.isDatasetVisible(j)) {
++ringIndex;
}
return ringIndex;
},
update: function(reset) {
var me = this;
var chart = me.chart;
var chartArea = chart.chartArea;
var opts = chart.options;
var arcOpts = opts.elements.arc;
var availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth;
var availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth;
var minSize = Math.min(availableWidth, availableHeight);
var offset = {x: 0, y: 0};
var meta = me.getMeta();
var cutoutPercentage = opts.cutoutPercentage;
var circumference = opts.circumference;
// If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc
if (circumference < Math.PI * 2.0) {
var startAngle = opts.rotation % (Math.PI * 2.0);
startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0);
var endAngle = startAngle + circumference;
var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)};
var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)};
var contains0 = (startAngle <= 0 && endAngle >= 0) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle);
var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle);
var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle);
var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle);
var cutout = cutoutPercentage / 100.0;
var min = {x: contains180 ? -1 : Math.min(start.x * (start.x < 0 ? 1 : cutout), end.x * (end.x < 0 ? 1 : cutout)), y: contains270 ? -1 : Math.min(start.y * (start.y < 0 ? 1 : cutout), end.y * (end.y < 0 ? 1 : cutout))};
var max = {x: contains0 ? 1 : Math.max(start.x * (start.x > 0 ? 1 : cutout), end.x * (end.x > 0 ? 1 : cutout)), y: contains90 ? 1 : Math.max(start.y * (start.y > 0 ? 1 : cutout), end.y * (end.y > 0 ? 1 : cutout))};
var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5};
minSize = Math.min(availableWidth / size.width, availableHeight / size.height);
offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5};
}
chart.borderWidth = me.getMaxBorderWidth(meta.data);
chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0);
chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0);
chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
chart.offsetX = offset.x * chart.outerRadius;
chart.offsetY = offset.y * chart.outerRadius;
meta.total = me.calculateTotal();
me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index));
me.innerRadius = Math.max(me.outerRadius - chart.radiusLength, 0);
helpers.each(meta.data, function(arc, index) {
me.updateElement(arc, index, reset);
});
},
updateElement: function(arc, index, reset) {
var me = this;
var chart = me.chart;
var chartArea = chart.chartArea;
var opts = chart.options;
var animationOpts = opts.animation;
var centerX = (chartArea.left + chartArea.right) / 2;
var centerY = (chartArea.top + chartArea.bottom) / 2;
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 / (2.0 * Math.PI));
var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius;
var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius;
var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
helpers.extend(arc, {
// Utility
_datasetIndex: me.index,
_index: index,
// Desired view properties
_model: {
x: centerX + chart.offsetX,
y: centerY + chart.offsetY,
startAngle: startAngle,
endAngle: endAngle,
circumference: circumference,
outerRadius: outerRadius,
innerRadius: innerRadius,
label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index])
}
});
var model = arc._model;
// Resets the visual styles
var custom = arc.custom || {};
var valueOrDefault = helpers.valueAtIndexOrDefault;
var elementOpts = this.chart.options.elements.arc;
model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor);
model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor);
model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth);
// Set correct angles if not resetting
if (!reset || !animationOpts.animateRotate) {
if (index === 0) {
model.startAngle = opts.rotation;
} else {
model.startAngle = me.getMeta().data[index - 1]._model.endAngle;
}
model.endAngle = model.startAngle + model.circumference;
}
arc.pivot();
},
calculateTotal: function() {
var dataset = this.getDataset();
var meta = this.getMeta();
var total = 0;
var value;
helpers.each(meta.data, function(element, index) {
value = dataset.data[index];
if (!isNaN(value) && !element.hidden) {
total += Math.abs(value);
}
});
/* if (total === 0) {
total = NaN;
}*/
return total;
},
calculateCircumference: function(value) {
var total = this.getMeta().total;
if (total > 0 && !isNaN(value)) {
return (Math.PI * 2.0) * (Math.abs(value) / total);
}
return 0;
},
// gets the max border or hover width to properly scale pie charts
getMaxBorderWidth: function(arcs) {
var max = 0;
var index = this.index;
var length = arcs.length;
var borderWidth;
var hoverWidth;
for (var i = 0; i < length; i++) {
borderWidth = arcs[i]._model ? arcs[i]._model.borderWidth : 0;
hoverWidth = arcs[i]._chart ? arcs[i]._chart.config.data.datasets[index].hoverBorderWidth : 0;
max = borderWidth > max ? borderWidth : max;
max = hoverWidth > max ? hoverWidth : max;
}
return max;
}
});
};
return ringIndex;
},
update: function(reset) {
var me = this;
var chart = me.chart;
var chartArea = chart.chartArea;
var opts = chart.options;
var arcOpts = opts.elements.arc;
var availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth;
var availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth;
var minSize = Math.min(availableWidth, availableHeight);
var offset = {x: 0, y: 0};
var meta = me.getMeta();
var cutoutPercentage = opts.cutoutPercentage;
var circumference = opts.circumference;
// If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc
if (circumference < Math.PI * 2.0) {
var startAngle = opts.rotation % (Math.PI * 2.0);
startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0);
var endAngle = startAngle + circumference;
var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)};
var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)};
var contains0 = (startAngle <= 0 && endAngle >= 0) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle);
var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle);
var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle);
var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle);
var cutout = cutoutPercentage / 100.0;
var min = {x: contains180 ? -1 : Math.min(start.x * (start.x < 0 ? 1 : cutout), end.x * (end.x < 0 ? 1 : cutout)), y: contains270 ? -1 : Math.min(start.y * (start.y < 0 ? 1 : cutout), end.y * (end.y < 0 ? 1 : cutout))};
var max = {x: contains0 ? 1 : Math.max(start.x * (start.x > 0 ? 1 : cutout), end.x * (end.x > 0 ? 1 : cutout)), y: contains90 ? 1 : Math.max(start.y * (start.y > 0 ? 1 : cutout), end.y * (end.y > 0 ? 1 : cutout))};
var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5};
minSize = Math.min(availableWidth / size.width, availableHeight / size.height);
offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5};
}
chart.borderWidth = me.getMaxBorderWidth(meta.data);
chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0);
chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0);
chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
chart.offsetX = offset.x * chart.outerRadius;
chart.offsetY = offset.y * chart.outerRadius;
meta.total = me.calculateTotal();
me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index));
me.innerRadius = Math.max(me.outerRadius - chart.radiusLength, 0);
helpers.each(meta.data, function(arc, index) {
me.updateElement(arc, index, reset);
});
},
updateElement: function(arc, index, reset) {
var me = this;
var chart = me.chart;
var chartArea = chart.chartArea;
var opts = chart.options;
var animationOpts = opts.animation;
var centerX = (chartArea.left + chartArea.right) / 2;
var centerY = (chartArea.top + chartArea.bottom) / 2;
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 / (2.0 * Math.PI));
var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius;
var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius;
var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
helpers.extend(arc, {
// Utility
_datasetIndex: me.index,
_index: index,
// Desired view properties
_model: {
x: centerX + chart.offsetX,
y: centerY + chart.offsetY,
startAngle: startAngle,
endAngle: endAngle,
circumference: circumference,
outerRadius: outerRadius,
innerRadius: innerRadius,
label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index])
}
});
var model = arc._model;
// Resets the visual styles
var custom = arc.custom || {};
var valueOrDefault = helpers.valueAtIndexOrDefault;
var elementOpts = this.chart.options.elements.arc;
model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor);
model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor);
model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth);
// Set correct angles if not resetting
if (!reset || !animationOpts.animateRotate) {
if (index === 0) {
model.startAngle = opts.rotation;
} else {
model.startAngle = me.getMeta().data[index - 1]._model.endAngle;
}
model.endAngle = model.startAngle + model.circumference;
}
arc.pivot();
},
calculateTotal: function() {
var dataset = this.getDataset();
var meta = this.getMeta();
var total = 0;
var value;
helpers.each(meta.data, function(element, index) {
value = dataset.data[index];
if (!isNaN(value) && !element.hidden) {
total += Math.abs(value);
}
});
/* if (total === 0) {
total = NaN;
}*/
return total;
},
calculateCircumference: function(value) {
var total = this.getMeta().total;
if (total > 0 && !isNaN(value)) {
return (Math.PI * 2.0) * (Math.abs(value) / total);
}
return 0;
},
// gets the max border or hover width to properly scale pie charts
getMaxBorderWidth: function(arcs) {
var max = 0;
var index = this.index;
var length = arcs.length;
var borderWidth;
var hoverWidth;
for (var i = 0; i < length; i++) {
borderWidth = arcs[i]._model ? arcs[i]._model.borderWidth : 0;
hoverWidth = arcs[i]._chart ? arcs[i]._chart.config.data.datasets[index].hoverBorderWidth : 0;
max = borderWidth > max ? borderWidth : max;
max = hoverWidth > max ? hoverWidth : max;
}
return max;
}
});

View File

@ -0,0 +1,79 @@
'use strict';
var BarController = require('./controller.bar');
var defaults = require('../core/core.defaults');
defaults._set('horizontalBar', {
hover: {
mode: 'index',
axis: 'y'
},
scales: {
xAxes: [{
type: 'linear',
position: 'bottom'
}],
yAxes: [{
type: 'category',
position: 'left',
categoryPercentage: 0.8,
barPercentage: 0.9,
offset: true,
gridLines: {
offsetGridLines: true
}
}]
},
elements: {
rectangle: {
borderSkipped: 'left'
}
},
tooltips: {
callbacks: {
title: function(item, data) {
// Pick first xLabel for now
var title = '';
if (item.length > 0) {
if (item[0].yLabel) {
title = item[0].yLabel;
} else if (data.labels.length > 0 && item[0].index < data.labels.length) {
title = data.labels[item[0].index];
}
}
return title;
},
label: function(item, data) {
var datasetLabel = data.datasets[item.datasetIndex].label || '';
return datasetLabel + ': ' + item.xLabel;
}
},
mode: 'index',
axis: 'y'
}
});
module.exports = BarController.extend({
/**
* @private
*/
getValueScaleId: function() {
return this.getMeta().xAxisID;
},
/**
* @private
*/
getIndexScaleId: function() {
return this.getMeta().yAxisID;
}
});

View File

@ -1,5 +1,6 @@
'use strict';
var DatasetController = require('../core/core.datasetController');
var defaults = require('../core/core.defaults');
var elements = require('../elements/index');
var helpers = require('../helpers/index');
@ -24,321 +25,318 @@ defaults._set('line', {
}
});
module.exports = function(Chart) {
function lineEnabled(dataset, options) {
return helpers.valueOrDefault(dataset.showLine, options.showLines);
}
function lineEnabled(dataset, options) {
return helpers.valueOrDefault(dataset.showLine, options.showLines);
}
module.exports = DatasetController.extend({
Chart.controllers.line = Chart.DatasetController.extend({
datasetElementType: elements.Line,
datasetElementType: elements.Line,
dataElementType: elements.Point,
dataElementType: elements.Point,
update: function(reset) {
var me = this;
var meta = me.getMeta();
var line = meta.dataset;
var points = meta.data || [];
var options = me.chart.options;
var lineElementOptions = options.elements.line;
var scale = me.getScaleForId(meta.yAxisID);
var i, ilen, custom;
var dataset = me.getDataset();
var showLine = lineEnabled(dataset, options);
update: function(reset) {
var me = this;
var meta = me.getMeta();
var line = meta.dataset;
var points = meta.data || [];
var options = me.chart.options;
var lineElementOptions = options.elements.line;
var scale = me.getScaleForId(meta.yAxisID);
var i, ilen, custom;
var dataset = me.getDataset();
var showLine = lineEnabled(dataset, options);
// Update Line
if (showLine) {
custom = line.custom || {};
// Compatibility: If the properties are defined with only the old name, use those values
if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
dataset.lineTension = dataset.tension;
}
// Utility
line._scale = scale;
line._datasetIndex = me.index;
// Data
line._children = points;
// Model
line._model = {
// Appearance
// The default behavior of lines is to break at null values, according
// to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158
// This option gives lines the ability to span gaps
spanGaps: dataset.spanGaps ? dataset.spanGaps : options.spanGaps,
tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension),
backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
steppedLine: custom.steppedLine ? custom.steppedLine : helpers.valueOrDefault(dataset.steppedLine, lineElementOptions.stepped),
cubicInterpolationMode: custom.cubicInterpolationMode ? custom.cubicInterpolationMode : helpers.valueOrDefault(dataset.cubicInterpolationMode, lineElementOptions.cubicInterpolationMode),
};
line.pivot();
}
// Update Points
for (i = 0, ilen = points.length; i < ilen; ++i) {
me.updateElement(points[i], i, reset);
}
if (showLine && line._model.tension !== 0) {
me.updateBezierControlPoints();
}
// Now pivot the point for animation
for (i = 0, ilen = points.length; i < ilen; ++i) {
points[i].pivot();
}
},
getPointBackgroundColor: function(point, index) {
var backgroundColor = this.chart.options.elements.point.backgroundColor;
var dataset = this.getDataset();
var custom = point.custom || {};
if (custom.backgroundColor) {
backgroundColor = custom.backgroundColor;
} else if (dataset.pointBackgroundColor) {
backgroundColor = helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor);
} else if (dataset.backgroundColor) {
backgroundColor = dataset.backgroundColor;
}
return backgroundColor;
},
getPointBorderColor: function(point, index) {
var borderColor = this.chart.options.elements.point.borderColor;
var dataset = this.getDataset();
var custom = point.custom || {};
if (custom.borderColor) {
borderColor = custom.borderColor;
} else if (dataset.pointBorderColor) {
borderColor = helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor);
} else if (dataset.borderColor) {
borderColor = dataset.borderColor;
}
return borderColor;
},
getPointBorderWidth: function(point, index) {
var borderWidth = this.chart.options.elements.point.borderWidth;
var dataset = this.getDataset();
var custom = point.custom || {};
if (!isNaN(custom.borderWidth)) {
borderWidth = custom.borderWidth;
} else if (!isNaN(dataset.pointBorderWidth) || helpers.isArray(dataset.pointBorderWidth)) {
borderWidth = helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth);
} else if (!isNaN(dataset.borderWidth)) {
borderWidth = dataset.borderWidth;
}
return borderWidth;
},
getPointRotation: function(point, index) {
var pointRotation = this.chart.options.elements.point.rotation;
var dataset = this.getDataset();
var custom = point.custom || {};
if (!isNaN(custom.rotation)) {
pointRotation = custom.rotation;
} else if (!isNaN(dataset.pointRotation) || helpers.isArray(dataset.pointRotation)) {
pointRotation = helpers.valueAtIndexOrDefault(dataset.pointRotation, index, pointRotation);
}
return pointRotation;
},
updateElement: function(point, index, reset) {
var me = this;
var meta = me.getMeta();
var custom = point.custom || {};
var dataset = me.getDataset();
var datasetIndex = me.index;
var value = dataset.data[index];
var yScale = me.getScaleForId(meta.yAxisID);
var xScale = me.getScaleForId(meta.xAxisID);
var pointOptions = me.chart.options.elements.point;
var x, y;
// Update Line
if (showLine) {
custom = line.custom || {};
// Compatibility: If the properties are defined with only the old name, use those values
if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
dataset.pointRadius = dataset.radius;
if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
dataset.lineTension = dataset.tension;
}
if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
dataset.pointHitRadius = dataset.hitRadius;
}
x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex);
y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex);
// Utility
point._xScale = xScale;
point._yScale = yScale;
point._datasetIndex = datasetIndex;
point._index = index;
// Desired view properties
point._model = {
x: x,
y: y,
skip: custom.skip || isNaN(x) || isNaN(y),
line._scale = scale;
line._datasetIndex = me.index;
// Data
line._children = points;
// Model
line._model = {
// Appearance
radius: custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius),
pointStyle: custom.pointStyle || helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle),
rotation: me.getPointRotation(point, index),
backgroundColor: me.getPointBackgroundColor(point, index),
borderColor: me.getPointBorderColor(point, index),
borderWidth: me.getPointBorderWidth(point, index),
tension: meta.dataset._model ? meta.dataset._model.tension : 0,
steppedLine: meta.dataset._model ? meta.dataset._model.steppedLine : false,
// Tooltip
hitRadius: custom.hitRadius || helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius)
// The default behavior of lines is to break at null values, according
// to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158
// This option gives lines the ability to span gaps
spanGaps: dataset.spanGaps ? dataset.spanGaps : options.spanGaps,
tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension),
backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
steppedLine: custom.steppedLine ? custom.steppedLine : helpers.valueOrDefault(dataset.steppedLine, lineElementOptions.stepped),
cubicInterpolationMode: custom.cubicInterpolationMode ? custom.cubicInterpolationMode : helpers.valueOrDefault(dataset.cubicInterpolationMode, lineElementOptions.cubicInterpolationMode),
};
},
calculatePointY: function(value, index, datasetIndex) {
var me = this;
var chart = me.chart;
var meta = me.getMeta();
var yScale = me.getScaleForId(meta.yAxisID);
var sumPos = 0;
var sumNeg = 0;
var i, ds, dsMeta;
line.pivot();
}
if (yScale.options.stacked) {
for (i = 0; i < datasetIndex; i++) {
ds = chart.data.datasets[i];
dsMeta = chart.getDatasetMeta(i);
if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) {
var stackedRightValue = Number(yScale.getRightValue(ds.data[index]));
if (stackedRightValue < 0) {
sumNeg += stackedRightValue || 0;
} else {
sumPos += stackedRightValue || 0;
}
// Update Points
for (i = 0, ilen = points.length; i < ilen; ++i) {
me.updateElement(points[i], i, reset);
}
if (showLine && line._model.tension !== 0) {
me.updateBezierControlPoints();
}
// Now pivot the point for animation
for (i = 0, ilen = points.length; i < ilen; ++i) {
points[i].pivot();
}
},
getPointBackgroundColor: function(point, index) {
var backgroundColor = this.chart.options.elements.point.backgroundColor;
var dataset = this.getDataset();
var custom = point.custom || {};
if (custom.backgroundColor) {
backgroundColor = custom.backgroundColor;
} else if (dataset.pointBackgroundColor) {
backgroundColor = helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor);
} else if (dataset.backgroundColor) {
backgroundColor = dataset.backgroundColor;
}
return backgroundColor;
},
getPointBorderColor: function(point, index) {
var borderColor = this.chart.options.elements.point.borderColor;
var dataset = this.getDataset();
var custom = point.custom || {};
if (custom.borderColor) {
borderColor = custom.borderColor;
} else if (dataset.pointBorderColor) {
borderColor = helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor);
} else if (dataset.borderColor) {
borderColor = dataset.borderColor;
}
return borderColor;
},
getPointBorderWidth: function(point, index) {
var borderWidth = this.chart.options.elements.point.borderWidth;
var dataset = this.getDataset();
var custom = point.custom || {};
if (!isNaN(custom.borderWidth)) {
borderWidth = custom.borderWidth;
} else if (!isNaN(dataset.pointBorderWidth) || helpers.isArray(dataset.pointBorderWidth)) {
borderWidth = helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth);
} else if (!isNaN(dataset.borderWidth)) {
borderWidth = dataset.borderWidth;
}
return borderWidth;
},
getPointRotation: function(point, index) {
var pointRotation = this.chart.options.elements.point.rotation;
var dataset = this.getDataset();
var custom = point.custom || {};
if (!isNaN(custom.rotation)) {
pointRotation = custom.rotation;
} else if (!isNaN(dataset.pointRotation) || helpers.isArray(dataset.pointRotation)) {
pointRotation = helpers.valueAtIndexOrDefault(dataset.pointRotation, index, pointRotation);
}
return pointRotation;
},
updateElement: function(point, index, reset) {
var me = this;
var meta = me.getMeta();
var custom = point.custom || {};
var dataset = me.getDataset();
var datasetIndex = me.index;
var value = dataset.data[index];
var yScale = me.getScaleForId(meta.yAxisID);
var xScale = me.getScaleForId(meta.xAxisID);
var pointOptions = me.chart.options.elements.point;
var x, y;
// Compatibility: If the properties are defined with only the old name, use those values
if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
dataset.pointRadius = dataset.radius;
}
if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
dataset.pointHitRadius = dataset.hitRadius;
}
x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex);
y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex);
// Utility
point._xScale = xScale;
point._yScale = yScale;
point._datasetIndex = datasetIndex;
point._index = index;
// Desired view properties
point._model = {
x: x,
y: y,
skip: custom.skip || isNaN(x) || isNaN(y),
// Appearance
radius: custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius),
pointStyle: custom.pointStyle || helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle),
rotation: me.getPointRotation(point, index),
backgroundColor: me.getPointBackgroundColor(point, index),
borderColor: me.getPointBorderColor(point, index),
borderWidth: me.getPointBorderWidth(point, index),
tension: meta.dataset._model ? meta.dataset._model.tension : 0,
steppedLine: meta.dataset._model ? meta.dataset._model.steppedLine : false,
// Tooltip
hitRadius: custom.hitRadius || helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius)
};
},
calculatePointY: function(value, index, datasetIndex) {
var me = this;
var chart = me.chart;
var meta = me.getMeta();
var yScale = me.getScaleForId(meta.yAxisID);
var sumPos = 0;
var sumNeg = 0;
var i, ds, dsMeta;
if (yScale.options.stacked) {
for (i = 0; i < datasetIndex; i++) {
ds = chart.data.datasets[i];
dsMeta = chart.getDatasetMeta(i);
if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) {
var stackedRightValue = Number(yScale.getRightValue(ds.data[index]));
if (stackedRightValue < 0) {
sumNeg += stackedRightValue || 0;
} else {
sumPos += stackedRightValue || 0;
}
}
var rightValue = Number(yScale.getRightValue(value));
if (rightValue < 0) {
return yScale.getPixelForValue(sumNeg + rightValue);
}
return yScale.getPixelForValue(sumPos + rightValue);
}
return yScale.getPixelForValue(value);
},
updateBezierControlPoints: function() {
var me = this;
var meta = me.getMeta();
var area = me.chart.chartArea;
var points = (meta.data || []);
var i, ilen, point, model, controlPoints;
// Only consider points that are drawn in case the spanGaps option is used
if (meta.dataset._model.spanGaps) {
points = points.filter(function(pt) {
return !pt._model.skip;
});
var rightValue = Number(yScale.getRightValue(value));
if (rightValue < 0) {
return yScale.getPixelForValue(sumNeg + rightValue);
}
return yScale.getPixelForValue(sumPos + rightValue);
}
function capControlPoint(pt, min, max) {
return Math.max(Math.min(pt, max), min);
return yScale.getPixelForValue(value);
},
updateBezierControlPoints: function() {
var me = this;
var meta = me.getMeta();
var area = me.chart.chartArea;
var points = (meta.data || []);
var i, ilen, point, model, controlPoints;
// Only consider points that are drawn in case the spanGaps option is used
if (meta.dataset._model.spanGaps) {
points = points.filter(function(pt) {
return !pt._model.skip;
});
}
function capControlPoint(pt, min, max) {
return Math.max(Math.min(pt, max), min);
}
if (meta.dataset._model.cubicInterpolationMode === 'monotone') {
helpers.splineCurveMonotone(points);
} else {
for (i = 0, ilen = points.length; i < ilen; ++i) {
point = points[i];
model = point._model;
controlPoints = helpers.splineCurve(
helpers.previousItem(points, i)._model,
model,
helpers.nextItem(points, i)._model,
meta.dataset._model.tension
);
model.controlPointPreviousX = controlPoints.previous.x;
model.controlPointPreviousY = controlPoints.previous.y;
model.controlPointNextX = controlPoints.next.x;
model.controlPointNextY = controlPoints.next.y;
}
}
if (meta.dataset._model.cubicInterpolationMode === 'monotone') {
helpers.splineCurveMonotone(points);
} else {
for (i = 0, ilen = points.length; i < ilen; ++i) {
point = points[i];
model = point._model;
controlPoints = helpers.splineCurve(
helpers.previousItem(points, i)._model,
model,
helpers.nextItem(points, i)._model,
meta.dataset._model.tension
);
model.controlPointPreviousX = controlPoints.previous.x;
model.controlPointPreviousY = controlPoints.previous.y;
model.controlPointNextX = controlPoints.next.x;
model.controlPointNextY = controlPoints.next.y;
}
if (me.chart.options.elements.line.capBezierPoints) {
for (i = 0, ilen = points.length; i < ilen; ++i) {
model = points[i]._model;
model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right);
model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom);
model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right);
model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom);
}
}
},
if (me.chart.options.elements.line.capBezierPoints) {
for (i = 0, ilen = points.length; i < ilen; ++i) {
model = points[i]._model;
model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right);
model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom);
model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right);
model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom);
}
}
},
draw: function() {
var me = this;
var chart = me.chart;
var meta = me.getMeta();
var points = meta.data || [];
var area = chart.chartArea;
var ilen = points.length;
var halfBorderWidth;
var i = 0;
draw: function() {
var me = this;
var chart = me.chart;
var meta = me.getMeta();
var points = meta.data || [];
var area = chart.chartArea;
var ilen = points.length;
var halfBorderWidth;
var i = 0;
if (lineEnabled(me.getDataset(), chart.options)) {
halfBorderWidth = (meta.dataset._model.borderWidth || 0) / 2;
if (lineEnabled(me.getDataset(), chart.options)) {
halfBorderWidth = (meta.dataset._model.borderWidth || 0) / 2;
helpers.canvas.clipArea(chart.ctx, {
left: area.left,
right: area.right,
top: area.top - halfBorderWidth,
bottom: area.bottom + halfBorderWidth
});
helpers.canvas.clipArea(chart.ctx, {
left: area.left,
right: area.right,
top: area.top - halfBorderWidth,
bottom: area.bottom + halfBorderWidth
});
meta.dataset.draw();
meta.dataset.draw();
helpers.canvas.unclipArea(chart.ctx);
}
helpers.canvas.unclipArea(chart.ctx);
}
// Draw the points
for (; i < ilen; ++i) {
points[i].draw(area);
}
},
// Draw the points
for (; i < ilen; ++i) {
points[i].draw(area);
}
},
setHoverStyle: function(element) {
// Point
var dataset = this.chart.data.datasets[element._datasetIndex];
var index = element._index;
var custom = element.custom || {};
var model = element._model;
setHoverStyle: function(element) {
// Point
var dataset = this.chart.data.datasets[element._datasetIndex];
var index = element._index;
var custom = element.custom || {};
var model = element._model;
element.$previousStyle = {
backgroundColor: model.backgroundColor,
borderColor: model.borderColor,
borderWidth: model.borderWidth,
radius: model.radius
};
element.$previousStyle = {
backgroundColor: model.backgroundColor,
borderColor: model.borderColor,
borderWidth: model.borderWidth,
radius: model.radius
};
model.backgroundColor = custom.hoverBackgroundColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
model.borderColor = custom.hoverBorderColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
model.borderWidth = custom.hoverBorderWidth || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
model.radius = custom.hoverRadius || helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
},
});
};
model.backgroundColor = custom.hoverBackgroundColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
model.borderColor = custom.hoverBorderColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
model.borderWidth = custom.hoverBorderWidth || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
model.radius = custom.hoverRadius || helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
}
});

View File

@ -0,0 +1,13 @@
'use strict';
var DoughnutController = require('./controller.doughnut');
var defaults = require('../core/core.defaults');
var helpers = require('../helpers/index');
defaults._set('pie', helpers.clone(defaults.doughnut));
defaults._set('pie', {
cutoutPercentage: 0
});
// Pie charts are Doughnut chart with different defaults
module.exports = DoughnutController;

View File

@ -1,5 +1,6 @@
'use strict';
var DatasetController = require('../core/core.datasetController');
var defaults = require('../core/core.defaults');
var elements = require('../elements/index');
var helpers = require('../helpers/index');
@ -108,148 +109,145 @@ defaults._set('polarArea', {
}
});
module.exports = function(Chart) {
module.exports = DatasetController.extend({
Chart.controllers.polarArea = Chart.DatasetController.extend({
dataElementType: elements.Arc,
dataElementType: elements.Arc,
linkScales: helpers.noop,
linkScales: helpers.noop,
update: function(reset) {
var me = this;
var dataset = me.getDataset();
var meta = me.getMeta();
var start = me.chart.options.startAngle || 0;
var starts = me._starts = [];
var angles = me._angles = [];
var i, ilen, angle;
update: function(reset) {
var me = this;
var dataset = me.getDataset();
var meta = me.getMeta();
var start = me.chart.options.startAngle || 0;
var starts = me._starts = [];
var angles = me._angles = [];
var i, ilen, angle;
me._updateRadius();
me._updateRadius();
meta.count = me.countVisibleElements();
meta.count = me.countVisibleElements();
for (i = 0, ilen = dataset.data.length; i < ilen; i++) {
starts[i] = start;
angle = me._computeAngle(i);
angles[i] = angle;
start += angle;
}
helpers.each(meta.data, function(arc, index) {
me.updateElement(arc, index, reset);
});
},
/**
* @private
*/
_updateRadius: function() {
var me = this;
var chart = me.chart;
var chartArea = chart.chartArea;
var opts = chart.options;
var arcOpts = opts.elements.arc;
var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0);
chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);
chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index);
me.innerRadius = me.outerRadius - chart.radiusLength;
},
updateElement: function(arc, index, reset) {
var me = this;
var chart = me.chart;
var dataset = me.getDataset();
var opts = chart.options;
var animationOpts = opts.animation;
var scale = chart.scale;
var labels = chart.data.labels;
var centerX = scale.xCenter;
var centerY = scale.yCenter;
// var negHalfPI = -0.5 * Math.PI;
var datasetStartAngle = opts.startAngle;
var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
var startAngle = me._starts[index];
var endAngle = startAngle + (arc.hidden ? 0 : me._angles[index]);
var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
helpers.extend(arc, {
// Utility
_datasetIndex: me.index,
_index: index,
_scale: scale,
// Desired view properties
_model: {
x: centerX,
y: centerY,
innerRadius: 0,
outerRadius: reset ? resetRadius : distance,
startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle,
endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle,
label: helpers.valueAtIndexOrDefault(labels, index, labels[index])
}
});
// Apply border and fill style
var elementOpts = this.chart.options.elements.arc;
var custom = arc.custom || {};
var valueOrDefault = helpers.valueAtIndexOrDefault;
var model = arc._model;
model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor);
model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor);
model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth);
arc.pivot();
},
countVisibleElements: function() {
var dataset = this.getDataset();
var meta = this.getMeta();
var count = 0;
helpers.each(meta.data, function(element, index) {
if (!isNaN(dataset.data[index]) && !element.hidden) {
count++;
}
});
return count;
},
/**
* @private
*/
_computeAngle: function(index) {
var me = this;
var count = this.getMeta().count;
var dataset = me.getDataset();
var meta = me.getMeta();
if (isNaN(dataset.data[index]) || meta.data[index].hidden) {
return 0;
}
// Scriptable options
var context = {
chart: me.chart,
dataIndex: index,
dataset: dataset,
datasetIndex: me.index
};
return helpers.options.resolve([
me.chart.options.elements.arc.angle,
(2 * Math.PI) / count
], context, index);
for (i = 0, ilen = dataset.data.length; i < ilen; i++) {
starts[i] = start;
angle = me._computeAngle(i);
angles[i] = angle;
start += angle;
}
});
};
helpers.each(meta.data, function(arc, index) {
me.updateElement(arc, index, reset);
});
},
/**
* @private
*/
_updateRadius: function() {
var me = this;
var chart = me.chart;
var chartArea = chart.chartArea;
var opts = chart.options;
var arcOpts = opts.elements.arc;
var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0);
chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);
chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index);
me.innerRadius = me.outerRadius - chart.radiusLength;
},
updateElement: function(arc, index, reset) {
var me = this;
var chart = me.chart;
var dataset = me.getDataset();
var opts = chart.options;
var animationOpts = opts.animation;
var scale = chart.scale;
var labels = chart.data.labels;
var centerX = scale.xCenter;
var centerY = scale.yCenter;
// var negHalfPI = -0.5 * Math.PI;
var datasetStartAngle = opts.startAngle;
var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
var startAngle = me._starts[index];
var endAngle = startAngle + (arc.hidden ? 0 : me._angles[index]);
var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
helpers.extend(arc, {
// Utility
_datasetIndex: me.index,
_index: index,
_scale: scale,
// Desired view properties
_model: {
x: centerX,
y: centerY,
innerRadius: 0,
outerRadius: reset ? resetRadius : distance,
startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle,
endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle,
label: helpers.valueAtIndexOrDefault(labels, index, labels[index])
}
});
// Apply border and fill style
var elementOpts = this.chart.options.elements.arc;
var custom = arc.custom || {};
var valueOrDefault = helpers.valueAtIndexOrDefault;
var model = arc._model;
model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor);
model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor);
model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth);
arc.pivot();
},
countVisibleElements: function() {
var dataset = this.getDataset();
var meta = this.getMeta();
var count = 0;
helpers.each(meta.data, function(element, index) {
if (!isNaN(dataset.data[index]) && !element.hidden) {
count++;
}
});
return count;
},
/**
* @private
*/
_computeAngle: function(index) {
var me = this;
var count = this.getMeta().count;
var dataset = me.getDataset();
var meta = me.getMeta();
if (isNaN(dataset.data[index]) || meta.data[index].hidden) {
return 0;
}
// Scriptable options
var context = {
chart: me.chart,
dataIndex: index,
dataset: dataset,
datasetIndex: me.index
};
return helpers.options.resolve([
me.chart.options.elements.arc.angle,
(2 * Math.PI) / count
], context, index);
}
});

View File

@ -1,5 +1,6 @@
'use strict';
var DatasetController = require('../core/core.datasetController');
var defaults = require('../core/core.defaults');
var elements = require('../elements/index');
var helpers = require('../helpers/index');
@ -15,158 +16,157 @@ defaults._set('radar', {
}
});
module.exports = function(Chart) {
module.exports = DatasetController.extend({
Chart.controllers.radar = Chart.DatasetController.extend({
datasetElementType: elements.Line,
datasetElementType: elements.Line,
dataElementType: elements.Point,
dataElementType: elements.Point,
linkScales: helpers.noop,
linkScales: helpers.noop,
update: function(reset) {
var me = this;
var meta = me.getMeta();
var line = meta.dataset;
var points = meta.data || [];
var custom = line.custom || {};
var dataset = me.getDataset();
var lineElementOptions = me.chart.options.elements.line;
var scale = me.chart.scale;
var i, ilen;
update: function(reset) {
var me = this;
var meta = me.getMeta();
var line = meta.dataset;
var points = meta.data || [];
var custom = line.custom || {};
var dataset = me.getDataset();
var lineElementOptions = me.chart.options.elements.line;
var scale = me.chart.scale;
var i, ilen;
// Compatibility: If the properties are defined with only the old name, use those values
if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
dataset.lineTension = dataset.tension;
}
// Compatibility: If the properties are defined with only the old name, use those values
if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
dataset.lineTension = dataset.tension;
helpers.extend(meta.dataset, {
// Utility
_datasetIndex: me.index,
_scale: scale,
// Data
_children: points,
_loop: true,
// Model
_model: {
// Appearance
tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension),
backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
}
});
helpers.extend(meta.dataset, {
// Utility
_datasetIndex: me.index,
_scale: scale,
// Data
_children: points,
_loop: true,
// Model
_model: {
// Appearance
tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension),
backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
}
});
meta.dataset.pivot();
meta.dataset.pivot();
// Update Points
for (i = 0, ilen = points.length; i < ilen; i++) {
me.updateElement(points[i], i, reset);
}
// Update Points
for (i = 0, ilen = points.length; i < ilen; i++) {
me.updateElement(points[i], i, reset);
// Update bezier control points
me.updateBezierControlPoints();
// Now pivot the point for animation
for (i = 0, ilen = points.length; i < ilen; i++) {
points[i].pivot();
}
},
updateElement: function(point, index, reset) {
var me = this;
var custom = point.custom || {};
var dataset = me.getDataset();
var scale = me.chart.scale;
var pointElementOptions = me.chart.options.elements.point;
var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]);
// Compatibility: If the properties are defined with only the old name, use those values
if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
dataset.pointRadius = dataset.radius;
}
if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
dataset.pointHitRadius = dataset.hitRadius;
}
helpers.extend(point, {
// Utility
_datasetIndex: me.index,
_index: index,
_scale: scale,
// Desired view properties
_model: {
x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales
y: reset ? scale.yCenter : pointPosition.y,
// Appearance
tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, me.chart.options.elements.line.tension),
radius: custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius),
backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor),
borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor),
borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth),
pointStyle: custom.pointStyle ? custom.pointStyle : helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle),
rotation: custom.rotation ? custom.rotation : helpers.valueAtIndexOrDefault(dataset.pointRotation, index, pointElementOptions.rotation),
// Tooltip
hitRadius: custom.hitRadius ? custom.hitRadius : helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointElementOptions.hitRadius)
}
});
// Update bezier control points
me.updateBezierControlPoints();
point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));
},
// Now pivot the point for animation
for (i = 0, ilen = points.length; i < ilen; i++) {
points[i].pivot();
}
},
updateElement: function(point, index, reset) {
var me = this;
var custom = point.custom || {};
var dataset = me.getDataset();
var scale = me.chart.scale;
var pointElementOptions = me.chart.options.elements.point;
var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]);
updateBezierControlPoints: function() {
var me = this;
var meta = me.getMeta();
var area = me.chart.chartArea;
var points = meta.data || [];
var i, ilen, model, controlPoints;
// Compatibility: If the properties are defined with only the old name, use those values
if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
dataset.pointRadius = dataset.radius;
}
if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
dataset.pointHitRadius = dataset.hitRadius;
}
function capControlPoint(pt, min, max) {
return Math.max(Math.min(pt, max), min);
}
helpers.extend(point, {
// Utility
_datasetIndex: me.index,
_index: index,
_scale: scale,
for (i = 0, ilen = points.length; i < ilen; i++) {
model = points[i]._model;
controlPoints = helpers.splineCurve(
helpers.previousItem(points, i, true)._model,
model,
helpers.nextItem(points, i, true)._model,
model.tension
);
// Desired view properties
_model: {
x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales
y: reset ? scale.yCenter : pointPosition.y,
// Prevent the bezier going outside of the bounds of the graph
model.controlPointPreviousX = capControlPoint(controlPoints.previous.x, area.left, area.right);
model.controlPointPreviousY = capControlPoint(controlPoints.previous.y, area.top, area.bottom);
model.controlPointNextX = capControlPoint(controlPoints.next.x, area.left, area.right);
model.controlPointNextY = capControlPoint(controlPoints.next.y, area.top, area.bottom);
}
},
// Appearance
tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, me.chart.options.elements.line.tension),
radius: custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius),
backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor),
borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor),
borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth),
pointStyle: custom.pointStyle ? custom.pointStyle : helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle),
rotation: custom.rotation ? custom.rotation : helpers.valueAtIndexOrDefault(dataset.pointRotation, index, pointElementOptions.rotation),
setHoverStyle: function(point) {
// Point
var dataset = this.chart.data.datasets[point._datasetIndex];
var custom = point.custom || {};
var index = point._index;
var model = point._model;
// Tooltip
hitRadius: custom.hitRadius ? custom.hitRadius : helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointElementOptions.hitRadius)
}
});
point.$previousStyle = {
backgroundColor: model.backgroundColor,
borderColor: model.borderColor,
borderWidth: model.borderWidth,
radius: model.radius
};
point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));
},
updateBezierControlPoints: function() {
var me = this;
var meta = me.getMeta();
var area = me.chart.chartArea;
var points = meta.data || [];
var i, ilen, model, controlPoints;
function capControlPoint(pt, min, max) {
return Math.max(Math.min(pt, max), min);
}
for (i = 0, ilen = points.length; i < ilen; i++) {
model = points[i]._model;
controlPoints = helpers.splineCurve(
helpers.previousItem(points, i, true)._model,
model,
helpers.nextItem(points, i, true)._model,
model.tension
);
// Prevent the bezier going outside of the bounds of the graph
model.controlPointPreviousX = capControlPoint(controlPoints.previous.x, area.left, area.right);
model.controlPointPreviousY = capControlPoint(controlPoints.previous.y, area.top, area.bottom);
model.controlPointNextX = capControlPoint(controlPoints.next.x, area.left, area.right);
model.controlPointNextY = capControlPoint(controlPoints.next.y, area.top, area.bottom);
}
},
setHoverStyle: function(point) {
// Point
var dataset = this.chart.data.datasets[point._datasetIndex];
var custom = point.custom || {};
var index = point._index;
var model = point._model;
point.$previousStyle = {
backgroundColor: model.backgroundColor,
borderColor: model.borderColor,
borderWidth: model.borderWidth,
radius: model.radius
};
model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
},
});
};
model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
}
});

View File

@ -1,5 +1,6 @@
'use strict';
var LineController = require('./controller.line');
var defaults = require('../core/core.defaults');
defaults._set('scatter', {
@ -34,9 +35,5 @@ defaults._set('scatter', {
}
});
module.exports = function(Chart) {
// Scatter charts use line controllers
Chart.controllers.scatter = Chart.controllers.line;
};
// Scatter charts use line controllers
module.exports = LineController;

18
src/controllers/index.js Normal file
View File

@ -0,0 +1,18 @@
'use strict';
// NOTE export a map in which the key represents the controller type, not
// the class, and so must be CamelCase in order to be correctly retrieved
// by the controller in core.controller.js (`controllers[meta.type]`).
/* eslint-disable global-require */
module.exports = {
bar: require('./controller.bar'),
bubble: require('./controller.bubble'),
doughnut: require('./controller.doughnut'),
horizontalBar: require('./controller.horizontalBar'),
line: require('./controller.line'),
polarArea: require('./controller.polarArea'),
pie: require('./controller.pie'),
radar: require('./controller.radar'),
scatter: require('./controller.scatter')
};

View File

@ -2,6 +2,7 @@
var Animation = require('./core.animation');
var animations = require('./core.animations');
var controllers = require('../controllers/index');
var defaults = require('./core.defaults');
var helpers = require('../helpers/index');
var Interaction = require('./core.interaction');
@ -20,9 +21,6 @@ module.exports = function(Chart) {
// Destroy method on the chart will remove the instance of the chart from this reference.
Chart.instances = {};
// Controllers available for dataset visualization eg. bar, line, slice, etc.
Chart.controllers = {};
/**
* Initializes the given config with global and chart default values.
*/
@ -337,7 +335,7 @@ module.exports = function(Chart) {
meta.controller.updateIndex(datasetIndex);
meta.controller.linkScales();
} else {
var ControllerClass = Chart.controllers[meta.type];
var ControllerClass = controllers[meta.type];
if (ControllerClass === undefined) {
throw new Error('"' + meta.type + '" is not a chart type.');
}

View File

@ -2,328 +2,325 @@
var helpers = require('../helpers/index');
module.exports = function(Chart) {
var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift'];
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;
}
});
});
/**
* 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;
}
/**
* 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;
}
// Base class for all dataset controllers (line, bar, etc)
Chart.DatasetController = function(chart, datasetIndex) {
this.initialize(chart, datasetIndex);
};
helpers.extend(Chart.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,
initialize: function(chart, datasetIndex) {
var me = this;
me.chart = chart;
me.index = datasetIndex;
me.linkScales();
me.addElements();
},
updateIndex: function(datasetIndex) {
this.index = datasetIndex;
},
linkScales: function() {
var me = this;
var meta = me.getMeta();
var dataset = me.getDataset();
if (meta.xAxisID === null || !(meta.xAxisID in me.chart.scales)) {
meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id;
}
if (meta.yAxisID === null || !(meta.yAxisID in me.chart.scales)) {
meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id;
}
},
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];
},
reset: function() {
this.update(true);
},
/**
* @private
*/
destroy: function() {
if (this._data) {
unlistenArrayEvents(this._data, this);
}
},
createMetaDataset: function() {
var me = this;
var type = me.datasetElementType;
return type && new type({
_chart: me.chart,
_datasetIndex: me.index
});
},
createMetaData: function(index) {
var me = this;
var type = me.dataElementType;
return type && new type({
_chart: me.chart,
_datasetIndex: me.index,
_index: index
});
},
addElements: function() {
var me = this;
var meta = me.getMeta();
var data = me.getDataset().data || [];
var metaData = meta.data;
var i, ilen;
for (i = 0, ilen = data.length; i < ilen; ++i) {
metaData[i] = metaData[i] || me.createMetaData(i);
}
meta.dataset = meta.dataset || me.createMetaDataset();
},
addElementAndReset: function(index) {
var element = this.createMetaData(index);
this.getMeta().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 = []);
// 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);
}
listenArrayEvents(data, me);
me._data = data;
}
// 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();
},
update: helpers.noop,
transition: function(easingValue) {
var meta = this.getMeta();
var elements = meta.data || [];
var ilen = elements.length;
var i = 0;
for (; i < ilen; ++i) {
elements[i].transition(easingValue);
}
if (meta.dataset) {
meta.dataset.transition(easingValue);
}
},
draw: function() {
var meta = this.getMeta();
var elements = meta.data || [];
var ilen = elements.length;
var i = 0;
if (meta.dataset) {
meta.dataset.draw();
}
for (; i < ilen; ++i) {
elements[i].draw();
}
},
removeHoverStyle: function(element) {
helpers.merge(element._model, element.$previousStyle || {});
delete element.$previousStyle;
},
setHoverStyle: function(element) {
var dataset = this.chart.data.datasets[element._datasetIndex];
var index = element._index;
var custom = element.custom || {};
var valueOrDefault = helpers.valueAtIndexOrDefault;
var getHoverColor = helpers.getHoverColor;
var model = element._model;
element.$previousStyle = {
backgroundColor: model.backgroundColor,
borderColor: model.borderColor,
borderWidth: model.borderWidth
};
model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor));
model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor));
model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
},
/**
* @private
*/
resyncElements: function() {
var me = this;
var meta = me.getMeta();
var data = me.getDataset().data;
var numMeta = meta.data.length;
var numData = data.length;
if (numData < numMeta) {
meta.data.splice(numData, numMeta - numData);
} else if (numData > numMeta) {
me.insertElements(numMeta, numData - numMeta);
}
},
/**
* @private
*/
insertElements: function(start, count) {
for (var i = 0; i < count; ++i) {
this.addElementAndReset(start + i);
}
},
/**
* @private
*/
onDataPush: function() {
this.insertElements(this.getDataset().data.length - 1, arguments.length);
},
/**
* @private
*/
onDataPop: function() {
this.getMeta().data.pop();
},
/**
* @private
*/
onDataShift: function() {
this.getMeta().data.shift();
},
/**
* @private
*/
onDataSplice: function(start, count) {
this.getMeta().data.splice(start, count);
this.insertElements(start, arguments.length - 2);
},
/**
* @private
*/
onDataUnshift: function() {
this.insertElements(0, arguments.length);
Object.defineProperty(array, '_chartjs', {
configurable: true,
enumerable: false,
value: {
listeners: [listener]
}
});
Chart.DatasetController.extend = helpers.inherits;
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;
}
});
});
}
/**
* 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;
}
// Base class for all dataset controllers (line, bar, etc)
var DatasetController = module.exports = 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,
initialize: function(chart, datasetIndex) {
var me = this;
me.chart = chart;
me.index = datasetIndex;
me.linkScales();
me.addElements();
},
updateIndex: function(datasetIndex) {
this.index = datasetIndex;
},
linkScales: function() {
var me = this;
var meta = me.getMeta();
var dataset = me.getDataset();
if (meta.xAxisID === null || !(meta.xAxisID in me.chart.scales)) {
meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id;
}
if (meta.yAxisID === null || !(meta.yAxisID in me.chart.scales)) {
meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id;
}
},
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];
},
reset: function() {
this.update(true);
},
/**
* @private
*/
destroy: function() {
if (this._data) {
unlistenArrayEvents(this._data, this);
}
},
createMetaDataset: function() {
var me = this;
var type = me.datasetElementType;
return type && new type({
_chart: me.chart,
_datasetIndex: me.index
});
},
createMetaData: function(index) {
var me = this;
var type = me.dataElementType;
return type && new type({
_chart: me.chart,
_datasetIndex: me.index,
_index: index
});
},
addElements: function() {
var me = this;
var meta = me.getMeta();
var data = me.getDataset().data || [];
var metaData = meta.data;
var i, ilen;
for (i = 0, ilen = data.length; i < ilen; ++i) {
metaData[i] = metaData[i] || me.createMetaData(i);
}
meta.dataset = meta.dataset || me.createMetaDataset();
},
addElementAndReset: function(index) {
var element = this.createMetaData(index);
this.getMeta().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 = []);
// 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);
}
listenArrayEvents(data, me);
me._data = data;
}
// 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();
},
update: helpers.noop,
transition: function(easingValue) {
var meta = this.getMeta();
var elements = meta.data || [];
var ilen = elements.length;
var i = 0;
for (; i < ilen; ++i) {
elements[i].transition(easingValue);
}
if (meta.dataset) {
meta.dataset.transition(easingValue);
}
},
draw: function() {
var meta = this.getMeta();
var elements = meta.data || [];
var ilen = elements.length;
var i = 0;
if (meta.dataset) {
meta.dataset.draw();
}
for (; i < ilen; ++i) {
elements[i].draw();
}
},
removeHoverStyle: function(element) {
helpers.merge(element._model, element.$previousStyle || {});
delete element.$previousStyle;
},
setHoverStyle: function(element) {
var dataset = this.chart.data.datasets[element._datasetIndex];
var index = element._index;
var custom = element.custom || {};
var valueOrDefault = helpers.valueAtIndexOrDefault;
var getHoverColor = helpers.getHoverColor;
var model = element._model;
element.$previousStyle = {
backgroundColor: model.backgroundColor,
borderColor: model.borderColor,
borderWidth: model.borderWidth
};
model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor));
model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor));
model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
},
/**
* @private
*/
resyncElements: function() {
var me = this;
var meta = me.getMeta();
var data = me.getDataset().data;
var numMeta = meta.data.length;
var numData = data.length;
if (numData < numMeta) {
meta.data.splice(numData, numMeta - numData);
} else if (numData > numMeta) {
me.insertElements(numMeta, numData - numMeta);
}
},
/**
* @private
*/
insertElements: function(start, count) {
for (var i = 0; i < count; ++i) {
this.addElementAndReset(start + i);
}
},
/**
* @private
*/
onDataPush: function() {
this.insertElements(this.getDataset().data.length - 1, arguments.length);
},
/**
* @private
*/
onDataPop: function() {
this.getMeta().data.pop();
},
/**
* @private
*/
onDataShift: function() {
this.getMeta().data.shift();
},
/**
* @private
*/
onDataSplice: function(start, count) {
this.getMeta().data.splice(start, count);
this.insertElements(start, arguments.length - 2);
},
/**
* @private
*/
onDataUnshift: function() {
this.insertElements(0, arguments.length);
}
});
DatasetController.extend = helpers.inherits;

View File

@ -1,6 +1,11 @@
describe('Chart.controllers.bar', function() {
describe('auto', jasmine.fixture.specs('controller.bar'));
it('should be registered as dataset controller', function() {
expect(typeof Chart.controllers.bar).toBe('function');
expect(typeof Chart.controllers.horizontalBar).toBe('function');
});
it('should be constructed', function() {
var chart = window.acquireChart({
type: 'bar',

View File

@ -1,6 +1,10 @@
describe('Chart.controllers.bubble', function() {
describe('auto', jasmine.fixture.specs('controller.bubble'));
it('should be registered as dataset controller', function() {
expect(typeof Chart.controllers.bubble).toBe('function');
});
it('should be constructed', function() {
var chart = window.acquireChart({
type: 'bubble',

View File

@ -1,4 +1,9 @@
describe('Chart.controllers.doughnut', function() {
it('should be registered as dataset controller', function() {
expect(typeof Chart.controllers.doughnut).toBe('function');
expect(Chart.controllers.doughnut).toBe(Chart.controllers.pie);
});
it('should be constructed', function() {
var chart = window.acquireChart({
type: 'doughnut',

View File

@ -1,6 +1,10 @@
describe('Chart.controllers.line', function() {
describe('auto', jasmine.fixture.specs('controller.line'));
it('should be registered as dataset controller', function() {
expect(typeof Chart.controllers.line).toBe('function');
});
it('should be constructed', function() {
var chart = window.acquireChart({
type: 'line',

View File

@ -1,6 +1,10 @@
describe('auto', jasmine.fixture.specs('controller.polarArea'));
describe('Chart.controllers.polarArea', function() {
describe('auto', jasmine.fixture.specs('controller.polarArea'));
it('should be registered as dataset controller', function() {
expect(typeof Chart.controllers.polarArea).toBe('function');
});
it('should be constructed', function() {
var chart = window.acquireChart({
type: 'polarArea',

View File

@ -1,6 +1,10 @@
describe('Chart.controllers.radar', function() {
describe('auto', jasmine.fixture.specs('controller.radar'));
it('should be registered as dataset controller', function() {
expect(typeof Chart.controllers.radar).toBe('function');
});
it('Should be constructed', function() {
var chart = window.acquireChart({
type: 'radar',

View File

@ -1,4 +1,8 @@
describe('Chart.controllers.scatter', function() {
it('should be registered as dataset controller', function() {
expect(typeof Chart.controllers.scatter).toBe('function');
});
describe('showLines option', function() {
it('should not draw a line if undefined', function() {
var chart = window.acquireChart({