Implement per-dataset type (default and per-chart) options (#5999)

This commit is contained in:
Ben McCann 2019-05-06 13:52:40 -07:00 committed by Simon Brunel
parent 58e154c7da
commit d6a5ea0d36
15 changed files with 310 additions and 28 deletions

View File

@ -31,3 +31,40 @@ var chartDifferentHoverMode = new Chart(ctx, {
}
});
```
## Dataset Configuration
Options may be configured directly on the dataset. The dataset options can be changed at 3 different levels and are evaluated with the following priority:
- per dataset: dataset.*
- per chart: options.datasets[type].*
- or globally: Chart.defaults.global.datasets[type].*
where type corresponds to the dataset type.
*Note:* dataset options take precedence over element options.
The following example would set the `showLine` option to 'false' for all line datasets except for those overridden by options passed to the dataset on creation.
```javascript
// Do not show lines for all datasets by default
Chart.defaults.global.datasets.line.showLine = false;
// This chart would show a line only for the third dataset
var chart = new Chart(ctx, {
type: 'line',
data: {
datasets: [{
data: [0, 0],
}, {
data: [0, 1]
}, {
data: [1, 0],
showLine: true // overrides the `line` dataset default
}, {
type: 'scatter', // 'line' dataset default does not affect this dataset since it's a 'scatter'
data: [1, 1]
}]
}
});
```

View File

@ -382,6 +382,7 @@ module.exports = DatasetController.extend({
var chart = me.chart;
var datasets = chart.data.datasets;
var dataset = datasets[me.index];
var datasetOpts = me._config;
var custom = rectangle.custom || {};
var options = chart.options.elements.rectangle;
var values = {};
@ -406,7 +407,7 @@ module.exports = DatasetController.extend({
key = keys[i];
values[key] = resolve([
custom[key],
dataset[key],
datasetOpts[key],
options[key]
], context, index);
}

View File

@ -127,6 +127,7 @@ module.exports = DatasetController.extend({
var chart = me.chart;
var datasets = chart.data.datasets;
var dataset = datasets[me.index];
var datasetOpts = me._config;
var custom = point.custom || {};
var options = chart.options.elements.point;
var data = dataset.data[index];
@ -158,7 +159,7 @@ module.exports = DatasetController.extend({
key = keys[i];
values[key] = resolve([
custom[key],
dataset[key],
datasetOpts[key],
options[key]
], context, index);
}

View File

@ -315,7 +315,12 @@ module.exports = DatasetController.extend({
for (i = 0, ilen = arcs.length; i < ilen; ++i) {
arc = arcs[i];
options = controller ? controller._resolveElementOptions(arc, i) : arc._options;
if (controller) {
controller._configure();
options = controller._resolveElementOptions(arc, i);
} else {
options = arc._options;
}
if (options.borderAlign !== 'inner') {
borderWidth = options.borderWidth;
hoverWidth = options.hoverBorderWidth;
@ -353,6 +358,7 @@ module.exports = DatasetController.extend({
var me = this;
var chart = me.chart;
var dataset = me.getDataset();
var datasetOpts = me._config;
var custom = arc.custom || {};
var options = chart.options.elements.arc;
var values = {};
@ -380,7 +386,7 @@ module.exports = DatasetController.extend({
key = keys[i];
values[key] = resolve([
custom[key],
dataset[key],
datasetOpts[key],
options[key]
], context, index);
}

View File

@ -29,10 +29,6 @@ defaults._set('line', {
}
});
function lineEnabled(dataset, options) {
return valueOrDefault(dataset.showLine, options.showLines);
}
module.exports = DatasetController.extend({
datasetElementType: elements.Line,
@ -44,9 +40,10 @@ module.exports = DatasetController.extend({
var meta = me.getMeta();
var line = meta.dataset;
var points = meta.data || [];
var options = me.chart.options;
var scale = me.getScaleForId(meta.yAxisID);
var dataset = me.getDataset();
var showLine = lineEnabled(dataset, me.chart.options);
var showLine = me._showLine = valueOrDefault(me._config.showLine, options.showLines);
var i, ilen;
// Update Line
@ -132,6 +129,7 @@ module.exports = DatasetController.extend({
var me = this;
var chart = me.chart;
var dataset = chart.data.datasets[me.index];
var datasetOpts = me._config;
var custom = element.custom || {};
var options = chart.options.elements.point;
var values = {};
@ -164,8 +162,8 @@ module.exports = DatasetController.extend({
key = keys[i];
values[key] = resolve([
custom[key],
dataset[ELEMENT_OPTIONS[key]],
dataset[key],
datasetOpts[ELEMENT_OPTIONS[key]],
datasetOpts[key],
options[key]
], context, index);
}
@ -181,6 +179,7 @@ module.exports = DatasetController.extend({
var chart = me.chart;
var datasetIndex = me.index;
var dataset = chart.data.datasets[datasetIndex];
var datasetOpts = me._config;
var custom = element.custom || {};
var options = chart.options;
var elementOptions = options.elements.line;
@ -210,7 +209,7 @@ module.exports = DatasetController.extend({
key = keys[i];
values[key] = resolve([
custom[key],
dataset[key],
datasetOpts[key],
elementOptions[key]
], context);
}
@ -218,9 +217,9 @@ module.exports = DatasetController.extend({
// 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
values.spanGaps = valueOrDefault(dataset.spanGaps, options.spanGaps);
values.tension = valueOrDefault(dataset.lineTension, elementOptions.tension);
values.steppedLine = resolve([custom.steppedLine, dataset.steppedLine, elementOptions.stepped]);
values.spanGaps = valueOrDefault(datasetOpts.spanGaps, options.spanGaps);
values.tension = valueOrDefault(datasetOpts.lineTension, elementOptions.tension);
values.steppedLine = resolve([custom.steppedLine, datasetOpts.steppedLine, elementOptions.stepped]);
return values;
},
@ -319,11 +318,11 @@ module.exports = DatasetController.extend({
var meta = me.getMeta();
var points = meta.data || [];
var area = chart.chartArea;
var i = 0;
var ilen = points.length;
var halfBorderWidth;
var i = 0;
if (lineEnabled(me.getDataset(), chart.options)) {
if (me._showLine) {
halfBorderWidth = (meta.dataset._model.borderWidth || 0) / 2;
helpers.canvas.clipArea(chart.ctx, {

View File

@ -248,6 +248,7 @@ module.exports = DatasetController.extend({
var me = this;
var chart = me.chart;
var dataset = me.getDataset();
var datasetOpts = me._config;
var custom = arc.custom || {};
var options = chart.options.elements.arc;
var values = {};
@ -275,7 +276,7 @@ module.exports = DatasetController.extend({
key = keys[i];
values[key] = resolve([
custom[key],
dataset[key],
datasetOpts[key],
options[key]
], context, index);
}

View File

@ -115,6 +115,7 @@ module.exports = DatasetController.extend({
var me = this;
var chart = me.chart;
var dataset = chart.data.datasets[me.index];
var datasetOpts = me._config;
var custom = element.custom || {};
var options = chart.options.elements.point;
var values = {};
@ -147,8 +148,8 @@ module.exports = DatasetController.extend({
key = keys[i];
values[key] = resolve([
custom[key],
dataset[ELEMENT_OPTIONS[key]],
dataset[key],
datasetOpts[ELEMENT_OPTIONS[key]],
datasetOpts[key],
options[key]
], context, index);
}
@ -163,6 +164,7 @@ module.exports = DatasetController.extend({
var me = this;
var chart = me.chart;
var dataset = chart.data.datasets[me.index];
var datasetOpts = me._config;
var custom = element.custom || {};
var options = chart.options.elements.line;
var values = {};
@ -183,7 +185,7 @@ module.exports = DatasetController.extend({
key = keys[i];
values[key] = resolve([
custom[key],
dataset[key],
datasetOpts[key],
options[key]
]);
}

View File

@ -21,8 +21,6 @@ defaults._set('scatter', {
}]
},
showLines: false,
tooltips: {
callbacks: {
title: function() {
@ -35,5 +33,13 @@ defaults._set('scatter', {
}
});
defaults._set('global', {
datasets: {
scatter: {
showLine: false
}
}
});
// Scatter charts use line controllers
module.exports = LineController;

View File

@ -567,7 +567,7 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
return;
}
meta.controller.update();
meta.controller._update();
plugins.notify(me, 'afterDatasetUpdate', [args]);
},

View File

@ -100,6 +100,7 @@ helpers.extend(DatasetController.prototype, {
me.index = datasetIndex;
me.linkScales();
me.addElements();
me._type = me.getMeta().type;
},
updateIndex: function(datasetIndex) {
@ -160,7 +161,7 @@ helpers.extend(DatasetController.prototype, {
},
reset: function() {
this.update(true);
this._update(true);
},
/**
@ -236,6 +237,30 @@ helpers.extend(DatasetController.prototype, {
me.resyncElements();
},
/**
* Returns the merged user-supplied and default dataset-level options
* @private
*/
_configure: function() {
var me = this;
me._config = helpers.merge({}, [
me.chart.options.datasets[me._type],
me.getDataset(),
], {
merger: function(key, target, source) {
if (key !== '_meta' && key !== 'data') {
helpers._merger(key, target, source);
}
}
});
},
_update: function(reset) {
var me = this;
me._configure();
me.update(reset);
},
update: helpers.noop,
transition: function(easingValue) {

View File

@ -11,6 +11,8 @@ var defaults = {
}
};
// TODO(v3): remove 'global' from namespace. all default are global and
// there's inconsistency around which options are under 'global'
defaults._set('global', {
defaultColor: 'rgba(0,0,0,0.1)',
defaultFontColor: '#666',

View File

@ -573,6 +573,181 @@ describe('Chart.controllers.line', function() {
expect(meta.dataset._model.borderWidth).toBe(0.55);
});
describe('dataset global defaults', function() {
beforeEach(function() {
this._defaults = Chart.helpers.clone(Chart.defaults.global.datasets.line);
});
afterEach(function() {
Chart.defaults.global.datasets.line = this._defaults;
delete this._defaults;
});
it('should utilize the dataset global default options', function() {
Chart.defaults.global.datasets.line = Chart.defaults.global.datasets.line || {};
Chart.helpers.merge(Chart.defaults.global.datasets.line, {
spanGaps: true,
lineTension: 0.231,
backgroundColor: '#add',
borderWidth: '#daa',
borderColor: '#dad',
borderCapStyle: 'round',
borderDash: [0],
borderDashOffset: 0.871,
borderJoinStyle: 'miter',
fill: 'start',
cubicInterpolationMode: 'monotone'
});
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
data: [0, 0],
label: 'dataset1'
}],
labels: ['label1', 'label2']
}
});
var model = chart.getDatasetMeta(0).dataset._model;
expect(model.spanGaps).toBe(true);
expect(model.tension).toBe(0.231);
expect(model.backgroundColor).toBe('#add');
expect(model.borderWidth).toBe('#daa');
expect(model.borderColor).toBe('#dad');
expect(model.borderCapStyle).toBe('round');
expect(model.borderDash).toEqual([0]);
expect(model.borderDashOffset).toBe(0.871);
expect(model.borderJoinStyle).toBe('miter');
expect(model.fill).toBe('start');
expect(model.cubicInterpolationMode).toBe('monotone');
});
it('should be overriden by user-supplied values', function() {
Chart.defaults.global.datasets.line = Chart.defaults.global.datasets.line || {};
Chart.helpers.merge(Chart.defaults.global.datasets.line, {
spanGaps: true,
lineTension: 0.231
});
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
data: [0, 0],
label: 'dataset1',
spanGaps: true,
backgroundColor: '#dad'
}],
labels: ['label1', 'label2']
},
options: {
datasets: {
line: {
lineTension: 0.345,
backgroundColor: '#add'
}
}
}
});
var model = chart.getDatasetMeta(0).dataset._model;
// dataset-level option overrides global default
expect(model.spanGaps).toBe(true);
// chart-level default overrides global default
expect(model.tension).toBe(0.345);
// dataset-level option overrides chart-level default
expect(model.backgroundColor).toBe('#dad');
});
});
it('should obey the chart-level dataset options', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
data: [0, 0],
label: 'dataset1'
}],
labels: ['label1', 'label2']
},
options: {
datasets: {
line: {
spanGaps: true,
lineTension: 0.231,
backgroundColor: '#add',
borderWidth: '#daa',
borderColor: '#dad',
borderCapStyle: 'round',
borderDash: [0],
borderDashOffset: 0.871,
borderJoinStyle: 'miter',
fill: 'start',
cubicInterpolationMode: 'monotone'
}
}
}
});
var model = chart.getDatasetMeta(0).dataset._model;
expect(model.spanGaps).toBe(true);
expect(model.tension).toBe(0.231);
expect(model.backgroundColor).toBe('#add');
expect(model.borderWidth).toBe('#daa');
expect(model.borderColor).toBe('#dad');
expect(model.borderCapStyle).toBe('round');
expect(model.borderDash).toEqual([0]);
expect(model.borderDashOffset).toBe(0.871);
expect(model.borderJoinStyle).toBe('miter');
expect(model.fill).toBe('start');
expect(model.cubicInterpolationMode).toBe('monotone');
});
it('should obey the dataset options', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
data: [0, 0],
label: 'dataset1',
spanGaps: true,
lineTension: 0.231,
backgroundColor: '#add',
borderWidth: '#daa',
borderColor: '#dad',
borderCapStyle: 'round',
borderDash: [0],
borderDashOffset: 0.871,
borderJoinStyle: 'miter',
fill: 'start',
cubicInterpolationMode: 'monotone'
}],
labels: ['label1', 'label2']
}
});
var model = chart.getDatasetMeta(0).dataset._model;
expect(model.spanGaps).toBe(true);
expect(model.tension).toBe(0.231);
expect(model.backgroundColor).toBe('#add');
expect(model.borderWidth).toBe('#daa');
expect(model.borderColor).toBe('#dad');
expect(model.borderCapStyle).toBe('round');
expect(model.borderDash).toEqual([0]);
expect(model.borderDashOffset).toBe(0.871);
expect(model.borderJoinStyle).toBe('miter');
expect(model.fill).toBe('start');
expect(model.cubicInterpolationMode).toBe('monotone');
});
it('should handle number of data point changes in update', function() {
var chart = window.acquireChart({
type: 'line',

View File

@ -154,7 +154,7 @@ describe('Chart.controllers.radar', function() {
});
// Now update controller and ensure proper updates
meta.controller.update();
meta.controller._update();
[
{x: 256, y: 120, cppx: 246, cppy: 120, cpnx: 272, cpny: 120},
@ -198,7 +198,7 @@ describe('Chart.controllers.radar', function() {
chart.data.datasets[0].pointBorderColor = 'rgb(56, 57, 58)';
chart.data.datasets[0].pointBorderWidth = 1.123;
meta.controller.update();
meta.controller._update();
expect(meta.dataset._model).toEqual(jasmine.objectContaining({
backgroundColor: 'rgb(98, 98, 98)',
@ -256,7 +256,7 @@ describe('Chart.controllers.radar', function() {
hitRadius: 5,
};
meta.controller.update();
meta.controller._update();
expect(meta.dataset._model).toEqual(jasmine.objectContaining({
backgroundColor: 'rgb(55, 55, 54)',

View File

@ -47,5 +47,28 @@ describe('Chart.controllers.scatter', function() {
expect(meta.dataset.draw.calls.count()).toBe(0);
expect(meta.data[0].draw.calls.count()).toBe(1);
});
it('should draw a line if true', function() {
var chart = window.acquireChart({
type: 'scatter',
data: {
datasets: [{
data: [{x: 10, y: 15}],
showLine: true,
label: 'dataset1'
}],
},
options: {}
});
var meta = chart.getDatasetMeta(0);
spyOn(meta.dataset, 'draw');
spyOn(meta.data[0], 'draw');
chart.update();
expect(meta.dataset.draw.calls.count()).toBe(1);
expect(meta.data[0].draw.calls.count()).toBe(1);
});
});
});

View File

@ -91,9 +91,11 @@ describe('Chart', function() {
});
it('should override default options', function() {
var callback = function() {};
var defaults = Chart.defaults;
defaults.global.responsiveAnimationDuration = 42;
defaults.global.hover.onHover = callback;
defaults.line.hover.mode = 'x-axis';
defaults.line.spanGaps = true;
@ -113,11 +115,13 @@ describe('Chart', function() {
var options = chart.options;
expect(options.responsiveAnimationDuration).toBe(4242);
expect(options.showLines).toBe(defaults.global.showLines);
expect(options.spanGaps).toBe(false);
expect(options.hover.mode).toBe('dataset');
expect(options.title.position).toBe('bottom');
defaults.global.responsiveAnimationDuration = 0;
defaults.global.hover.onHover = null;
defaults.line.hover.mode = 'label';
defaults.line.spanGaps = false;
});