diff --git a/docs/developers/updates.md b/docs/developers/updates.md index 06550ce0cec..b65757f856b 100644 --- a/docs/developers/updates.md +++ b/docs/developers/updates.md @@ -1,6 +1,6 @@ # Updating Charts -It's pretty common to want to update charts after they've been created. When the chart data is changed, Chart.js will animate to the new data values. +It's pretty common to want to update charts after they've been created. When the chart data or options are changed, Chart.js will animate to the new data values and options. ## Adding or Removing Data @@ -14,9 +14,7 @@ function addData(chart, label, data) { }); chart.update(); } -``` -```javascript function removeData(chart) { chart.data.labels.pop(); chart.data.datasets.forEach((dataset) => { @@ -26,6 +24,78 @@ function removeData(chart) { } ``` +## Updating Options + +To update the options, mutating the options property in place or passing in a new options object are supported. + +- If the options are mutated in place, other option properties would be preserved, including those calculated by Chart.js. +- If created as a new object, it would be like creating a new chart with the options - old options would be discarded. + +```javascript +function updateConfigByMutating(chart) { + chart.options.title.text = 'new title'; + chart.update(); +} + +function updateConfigAsNewObject(chart) { + chart.options = { + responsive: true, + title:{ + display:true, + text: 'Chart.js' + }, + scales: { + xAxes: [{ + display: true + }], + yAxes: [{ + display: true + }] + } + } + chart.update(); +} +``` + +Scales can be updated separately without changing other options. +To update the scales, pass in an object containing all the customization including those unchanged ones. + +Variables referencing any one from `chart.scales` would be lost after updating scales with a new `id` or the changed `type`. + +```javascript +function updateScales(chart) { + var xScale = chart.scales['x-axis-0']; + var yScale = chart.scales['y-axis-0']; + chart.options.scales = { + xAxes: [{ + id: 'newId', + display: true + }], + yAxes: [{ + display: true, + type: 'logarithmic' + }] + } + chart.update(); + // need to update the reference + xScale = chart.scales['newId']; + yScale = chart.scales['y-axis-0']; +} +``` + +You can also update a specific scale either by specifying its index or id. + +```javascript +function updateScale(chart) { + chart.options.scales.yAxes[0] = { + type: 'logarithmic' + } + chart.update(); +} +``` + +Code sample for updating options can be found in [toggle-scale-type.html](../../samples/scales/toggle-scale-type.html). + ## Preventing Animations Sometimes when a chart updates, you may not want an animation. To achieve this you can call `update` with a duration of `0`. This will render the chart synchronously and without an animation. \ No newline at end of file diff --git a/samples/samples.js b/samples/samples.js index 2d11e9d48c9..b818827c6d2 100644 --- a/samples/samples.js +++ b/samples/samples.js @@ -136,6 +136,9 @@ }, { title: 'Non numeric Y Axis', path: 'scales/non-numeric-y.html' + }, { + title: 'Toggle Scale Type', + path: 'scales/toggle-scale-type.html' }] }, { title: 'Legend', diff --git a/samples/scales/toggle-scale-type.html b/samples/scales/toggle-scale-type.html new file mode 100644 index 00000000000..b46687e823a --- /dev/null +++ b/samples/scales/toggle-scale-type.html @@ -0,0 +1,99 @@ + + + + + Toggle Scale Type + + + + + + +
+ +
+ + + + + diff --git a/src/core/core.controller.js b/src/core/core.controller.js index 5fd4b2d6aa6..17e6c3bfa67 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -45,17 +45,21 @@ module.exports = function(Chart) { function updateConfig(chart) { var newOptions = chart.options; - // Update Scale(s) with options - if (newOptions.scale) { - chart.scale.options = newOptions.scale; - } else if (newOptions.scales) { - newOptions.scales.xAxes.concat(newOptions.scales.yAxes).forEach(function(scaleOptions) { - chart.scales[scaleOptions.id].options = scaleOptions; - }); - } - + helpers.each(chart.scales, function(scale) { + Chart.layoutService.removeBox(chart, scale); + }); + + newOptions = helpers.configMerge( + Chart.defaults.global, + Chart.defaults[chart.config.type], + newOptions); + + chart.options = chart.config.options = newOptions; + chart.ensureScalesHaveIDs(); + chart.buildOrUpdateScales(); // Tooltip chart.tooltip._options = newOptions.tooltips; + chart.tooltip.initialize(); } function positionIsHorizontal(position) { @@ -143,7 +147,7 @@ module.exports = function(Chart) { // Make sure scales have IDs and are built before we build any controllers. me.ensureScalesHaveIDs(); - me.buildScales(); + me.buildOrUpdateScales(); me.initToolTip(); // After init plugin notification @@ -223,11 +227,15 @@ module.exports = function(Chart) { /** * Builds a map of scale ID to scale object for future lookup. */ - buildScales: function() { + buildOrUpdateScales: function() { var me = this; var options = me.options; - var scales = me.scales = {}; + var scales = me.scales || {}; var items = []; + var updated = Object.keys(scales).reduce(function(obj, id) { + obj[id] = false; + return obj; + }, {}); if (options.scales) { items = items.concat( @@ -251,24 +259,35 @@ module.exports = function(Chart) { helpers.each(items, function(item) { var scaleOptions = item.options; + var id = scaleOptions.id; var scaleType = helpers.valueOrDefault(scaleOptions.type, item.dtype); - var scaleClass = Chart.scaleService.getScaleConstructor(scaleType); - if (!scaleClass) { - return; - } if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) { scaleOptions.position = item.dposition; } - var scale = new scaleClass({ - id: scaleOptions.id, - options: scaleOptions, - ctx: me.ctx, - chart: me - }); + updated[id] = true; + var scale = null; + if (id in scales && scales[id].type === scaleType) { + scale = scales[id]; + scale.options = scaleOptions; + scale.ctx = me.ctx; + scale.chart = me; + } else { + var scaleClass = Chart.scaleService.getScaleConstructor(scaleType); + if (!scaleClass) { + return; + } + scale = new scaleClass({ + id: id, + type: scaleType, + options: scaleOptions, + ctx: me.ctx, + chart: me + }); + scales[scale.id] = scale; + } - scales[scale.id] = scale; scale.mergeTicksOptions(); // TODO(SB): I think we should be able to remove this custom case (options.scale) @@ -278,6 +297,14 @@ module.exports = function(Chart) { me.scale = scale; } }); + // clear up discarded scales + helpers.each(updated, function(hasUpdated, id) { + if (!hasUpdated) { + delete scales[id]; + } + }); + + me.scales = scales; Chart.scaleService.addScalesToLayout(this); }, @@ -301,6 +328,7 @@ module.exports = function(Chart) { if (meta.controller) { meta.controller.updateIndex(datasetIndex); + meta.controller.linkScales(); } else { var ControllerClass = Chart.controllers[meta.type]; if (ControllerClass === undefined) { diff --git a/src/core/core.datasetController.js b/src/core/core.datasetController.js index 67dbe27f200..ee6158c35b1 100644 --- a/src/core/core.datasetController.js +++ b/src/core/core.datasetController.js @@ -111,10 +111,10 @@ module.exports = function(Chart) { var meta = me.getMeta(); var dataset = me.getDataset(); - if (meta.xAxisID === null) { + 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) { + if (meta.yAxisID === null || !(meta.yAxisID in me.chart.scales)) { meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id; } }, diff --git a/test/specs/core.controller.tests.js b/test/specs/core.controller.tests.js index b2615f14eec..1ca699bc132 100644 --- a/test/specs/core.controller.tests.js +++ b/test/specs/core.controller.tests.js @@ -775,6 +775,38 @@ describe('Chart', function() { }); describe('config update', function() { + it ('should update options', function() { + var chart = acquireChart({ + type: 'line', + data: { + labels: ['A', 'B', 'C', 'D'], + datasets: [{ + data: [10, 20, 30, 100] + }] + }, + options: { + responsive: true + } + }); + + chart.options = { + responsive: false, + scales: { + yAxes: [{ + ticks: { + min: 0, + max: 10 + } + }] + } + }; + chart.update(); + + var yScale = chart.scales['y-axis-0']; + expect(yScale.options.ticks.min).toBe(0); + expect(yScale.options.ticks.max).toBe(10); + }); + it ('should update scales options', function() { var chart = acquireChart({ type: 'line', @@ -798,6 +830,79 @@ describe('Chart', function() { expect(yScale.options.ticks.max).toBe(10); }); + it ('should update scales options from new object', function() { + var chart = acquireChart({ + type: 'line', + data: { + labels: ['A', 'B', 'C', 'D'], + datasets: [{ + data: [10, 20, 30, 100] + }] + }, + options: { + responsive: true + } + }); + + var newScalesConfig = { + yAxes: [{ + ticks: { + min: 0, + max: 10 + } + }] + }; + chart.options.scales = newScalesConfig; + + chart.update(); + + var yScale = chart.scales['y-axis-0']; + expect(yScale.options.ticks.min).toBe(0); + expect(yScale.options.ticks.max).toBe(10); + }); + + it ('should remove discarded scale', function() { + var chart = acquireChart({ + type: 'line', + data: { + labels: ['A', 'B', 'C', 'D'], + datasets: [{ + data: [10, 20, 30, 100] + }] + }, + options: { + responsive: true, + scales: { + yAxes: [{ + id: 'yAxis0', + ticks: { + min: 0, + max: 10 + } + }] + } + } + }); + + var newScalesConfig = { + yAxes: [{ + ticks: { + min: 0, + max: 10 + } + }] + }; + chart.options.scales = newScalesConfig; + + chart.update(); + + var yScale = chart.scales.yAxis0; + expect(yScale).toBeUndefined(); + var newyScale = chart.scales['y-axis-0']; + expect(newyScale.options.ticks.min).toBe(0); + expect(newyScale.options.ticks.max).toBe(10); + }); + it ('should update tooltip options', function() { var chart = acquireChart({ type: 'line',