diff --git a/docs/configuration/README.md b/docs/configuration/README.md index 0a4dee450c9..db25736f1d2 100644 --- a/docs/configuration/README.md +++ b/docs/configuration/README.md @@ -31,3 +31,34 @@ var chartDifferentHoverMode = new Chart(ctx, { } }); ``` + +## Dataset Configuration + +Options may be configured direction on the dataset. Dataset defaults also allow for changing options globally across a dataset type avoiding the need to specify options for each dataset instance. + +Chart.js merges user-specified dataset configuration with the dataset defaults appropriately. This way you can be as specific as you would like in your individual dataset configuration, while still changing the defaults for all datasets where applicable. The dataset general options are defined in `Chart.defaults.datasets.type` where `type` corresponds to the dataset type. Dataset options take precedence over element options. If you set a dataset type default, it will override all corresponding element options. The defaults for each dataset type are discussed in the documentation for that chart type. + +The following example would set the `showLine` option to 'false' for all line datasets where this was not overridden by the options passed to the dataset on creation. + +```javascript +// Do not show lines for all datasets by default +Chart.defaults.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] + }] + } +}); +``` diff --git a/src/controllers/controller.line.js b/src/controllers/controller.line.js index 49665e2299a..bf104ea685f 100644 --- a/src/controllers/controller.line.js +++ b/src/controllers/controller.line.js @@ -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._cfg.showLine, options.showLines); var i, ilen; // Update Line @@ -179,7 +176,7 @@ module.exports = DatasetController.extend({ _resolveLineOptions: function(element) { var me = this; var chart = me.chart; - var dataset = chart.data.datasets[me.index]; + var datasetOpts = me._cfg; var custom = element.custom || {}; var options = chart.options; var elementOptions = options.elements.line; @@ -202,7 +199,7 @@ module.exports = DatasetController.extend({ key = keys[i]; values[key] = resolve([ custom[key], - dataset[key], + datasetOpts[key], elementOptions[key] ]); } @@ -210,9 +207,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 = resolve([datasetOpts.spanGaps, options.spanGaps]); + values.tension = resolve([datasetOpts.tension, elementOptions.tension]); + values.steppedLine = resolve([custom.steppedLine, datasetOpts.steppedLine, elementOptions.stepped]); return values; }, @@ -311,11 +308,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, { diff --git a/src/controllers/controller.scatter.js b/src/controllers/controller.scatter.js index 319296d33ba..7cdc088490a 100644 --- a/src/controllers/controller.scatter.js +++ b/src/controllers/controller.scatter.js @@ -21,8 +21,6 @@ defaults._set('scatter', { }] }, - showLines: false, - tooltips: { callbacks: { title: function() { @@ -35,5 +33,11 @@ defaults._set('scatter', { } }); +defaults._set('datasets', { + scatter: { + showLine: false + } +}); + // Scatter charts use line controllers module.exports = LineController; diff --git a/src/core/core.controller.js b/src/core/core.controller.js index a294b160031..05682e416cb 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -417,6 +417,7 @@ helpers.extend(Chart.prototype, /** @lends Chart */ { meta.controller = new ControllerClass(me, datasetIndex); newControllers.push(meta.controller); } + meta.controller._config(); }, me); return newControllers; diff --git a/src/core/core.datasetController.js b/src/core/core.datasetController.js index 6a7d36f196b..c923831e798 100644 --- a/src/core/core.datasetController.js +++ b/src/core/core.datasetController.js @@ -1,5 +1,6 @@ 'use strict'; +var defaults = require('./core.defaults'); var helpers = require('../helpers/index'); var resolve = helpers.options.resolve; @@ -100,6 +101,7 @@ helpers.extend(DatasetController.prototype, { me.index = datasetIndex; me.linkScales(); me.addElements(); + me._type = me.getMeta().type; }, updateIndex: function(datasetIndex) { @@ -234,6 +236,27 @@ helpers.extend(DatasetController.prototype, { me.resyncElements(); }, + /** + * Returns the merged user-supplied and default dataset-level options + * @private + */ + _config: function() { + var me = this; + var dataset = me.getDataset(); + var datasetDefaults = defaults.datasets[me._type]; + var userOpts = {}; + var keys = Object.keys(dataset); + var i, ilen, key; + for (i = 0, ilen = keys.length; i < ilen; i++) { + key = keys[i]; + if (key !== '_meta' && key !== 'data') { + userOpts[key] = helpers.clone(dataset[key]); + } + } + datasetDefaults = datasetDefaults !== undefined ? helpers.clone(datasetDefaults) : {}; + me._cfg = helpers.merge(datasetDefaults, userOpts); + }, + update: helpers.noop, transition: function(easingValue) { diff --git a/test/specs/controller.line.tests.js b/test/specs/controller.line.tests.js index 285f5c988a2..6599abf59fb 100644 --- a/test/specs/controller.line.tests.js +++ b/test/specs/controller.line.tests.js @@ -573,6 +573,96 @@ describe('Chart.controllers.line', function() { expect(meta.dataset._model.borderWidth).toBe(0.55); }); + describe('dataset defaults', function() { + beforeEach(function() { + this._defaults = Chart.helpers.clone(Chart.defaults.datasets.line); + }); + + afterEach(function() { + Chart.defaults.datasets.line = this._defaults; + delete this._defaults; + }); + + it('should utilize the dataset default options', function() { + Chart.defaults.datasets.line = Chart.defaults.datasets.line || {}; + var defaults = Chart.defaults.datasets.line; + defaults.spanGaps = true; + defaults.tension = 0.231; + defaults.backgroundColor = '#add'; + defaults.borderWidth = '#daa'; + defaults.borderColor = '#dad'; + defaults.borderCapStyle = 'round'; + defaults.borderDash = [0]; + defaults.borderDashOffset = 0.871; + defaults.borderJoinStyle = 'miter'; + defaults.fill = 'start'; + defaults.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 obey the dataset options', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [0, 0], + label: 'dataset1', + spanGaps: true, + tension: 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', diff --git a/test/specs/controller.scatter.test.js b/test/specs/controller.scatter.test.js index bc1ff18bf49..a2744b9e437 100644 --- a/test/specs/controller.scatter.test.js +++ b/test/specs/controller.scatter.test.js @@ -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); + }); }); }); diff --git a/test/specs/core.controller.tests.js b/test/specs/core.controller.tests.js index 819074393b1..caf94f8b584 100644 --- a/test/specs/core.controller.tests.js +++ b/test/specs/core.controller.tests.js @@ -64,13 +64,7 @@ describe('Chart', function() { }); it('should initialize config with 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; var chart = acquireChart({ type: 'line' @@ -78,19 +72,14 @@ describe('Chart', function() { var options = chart.options; expect(options.defaultFontSize).toBe(defaults.global.defaultFontSize); - expect(options.showLines).toBe(defaults.line.showLines); - expect(options.spanGaps).toBe(true); - expect(options.responsiveAnimationDuration).toBe(42); - expect(options.hover.onHover).toBe(callback); - expect(options.hover.mode).toBe('x-axis'); + expect(options.showLines).toBe(defaults.global.showLines); + expect(options.spanGaps).toBe(defaults.line.spanGaps); + expect(options.responsiveAnimationDuration).toBe(defaults.global.responsiveAnimationDuration); + expect(options.hover.onHover).toBe(defaults.global.hover.onHover); + expect(options.hover.mode).toBe(defaults.line.hover.mode); }); it('should override default options', function() { - var defaults = Chart.defaults; - defaults.global.responsiveAnimationDuration = 42; - defaults.line.hover.mode = 'x-axis'; - defaults.line.spanGaps = true; - var chart = acquireChart({ type: 'line', options: {