diff --git a/docs/configuration/README.md b/docs/configuration/README.md index 0a4dee450c9..9139d5f88e1 100644 --- a/docs/configuration/README.md +++ b/docs/configuration/README.md @@ -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] + }] + } +}); +``` diff --git a/src/controllers/controller.bar.js b/src/controllers/controller.bar.js index 992333a25c2..7130a9d0905 100644 --- a/src/controllers/controller.bar.js +++ b/src/controllers/controller.bar.js @@ -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); } diff --git a/src/controllers/controller.bubble.js b/src/controllers/controller.bubble.js index d40b4393b51..d724432f446 100644 --- a/src/controllers/controller.bubble.js +++ b/src/controllers/controller.bubble.js @@ -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); } diff --git a/src/controllers/controller.doughnut.js b/src/controllers/controller.doughnut.js index 688368edac4..daa1ed1bdfd 100644 --- a/src/controllers/controller.doughnut.js +++ b/src/controllers/controller.doughnut.js @@ -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); } diff --git a/src/controllers/controller.line.js b/src/controllers/controller.line.js index 8bc14c3decd..c671d657933 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._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, { diff --git a/src/controllers/controller.polarArea.js b/src/controllers/controller.polarArea.js index 19ceefddab7..582a0a78bf7 100644 --- a/src/controllers/controller.polarArea.js +++ b/src/controllers/controller.polarArea.js @@ -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); } diff --git a/src/controllers/controller.radar.js b/src/controllers/controller.radar.js index f11af4dcc7f..1e32dd0a424 100644 --- a/src/controllers/controller.radar.js +++ b/src/controllers/controller.radar.js @@ -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] ]); } diff --git a/src/controllers/controller.scatter.js b/src/controllers/controller.scatter.js index 319296d33ba..177e563203f 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,13 @@ defaults._set('scatter', { } }); +defaults._set('global', { + 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 92cfcd01794..8abe3978e1a 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -567,7 +567,7 @@ helpers.extend(Chart.prototype, /** @lends Chart */ { return; } - meta.controller.update(); + meta.controller._update(); plugins.notify(me, 'afterDatasetUpdate', [args]); }, diff --git a/src/core/core.datasetController.js b/src/core/core.datasetController.js index afd021c1db1..45a2e894236 100644 --- a/src/core/core.datasetController.js +++ b/src/core/core.datasetController.js @@ -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) { diff --git a/src/core/core.defaults.js b/src/core/core.defaults.js index 279491debae..6c8e996a1a8 100644 --- a/src/core/core.defaults.js +++ b/src/core/core.defaults.js @@ -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', diff --git a/test/specs/controller.line.tests.js b/test/specs/controller.line.tests.js index 285f5c988a2..953594deac1 100644 --- a/test/specs/controller.line.tests.js +++ b/test/specs/controller.line.tests.js @@ -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', diff --git a/test/specs/controller.radar.tests.js b/test/specs/controller.radar.tests.js index d00e3674a8c..8eff71b60dd 100644 --- a/test/specs/controller.radar.tests.js +++ b/test/specs/controller.radar.tests.js @@ -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)', 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 946e9fabec5..2fb982276ee 100644 --- a/test/specs/core.controller.tests.js +++ b/test/specs/core.controller.tests.js @@ -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; });