From 979c6067b306462270faf9482130c13642ac785f Mon Sep 17 00:00:00 2001 From: Xingan Wang Date: Sun, 20 Aug 2017 22:31:25 -0700 Subject: [PATCH 1/3] Introduce option update - allow options be updated inplace or as a new object - remerge new options and rebuild scales&tooltips - preserve reference to old scale if id/type not changed - related tests and new sample also added. --- samples/samples.js | 3 + samples/scales/toggle-scale-type.html | 104 +++++++++++++++++++++++++ src/core/core.controller.js | 74 ++++++++++++------ src/core/core.datasetController.js | 4 +- test/specs/core.controller.tests.js | 105 ++++++++++++++++++++++++++ 5 files changed, 265 insertions(+), 25 deletions(-) create mode 100644 samples/scales/toggle-scale-type.html 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..e6bcd25a843 --- /dev/null +++ b/samples/scales/toggle-scale-type.html @@ -0,0 +1,104 @@ + + + + + Toggle Scale Type + + + + + + +
+ +
+ + + + + diff --git a/src/core/core.controller.js b/src/core/core.controller.js index 9e4984a9af6..759f490642a 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 3ec8da50b76..bc018eea01f 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', From 4b1ba375783af863576b99b9f10201aeb928dbb6 Mon Sep 17 00:00:00 2001 From: Xingan Wang Date: Sun, 20 Aug 2017 23:15:21 -0700 Subject: [PATCH 2/3] update document about options update --- docs/developers/updates.md | 65 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/docs/developers/updates.md b/docs/developers/updates.md index 06550ce0cec..00723001a30 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,67 @@ 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. +- 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']; +} +``` + +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 From af3cb9da22747ba466e018c77083c13029ec7a1f Mon Sep 17 00:00:00 2001 From: Xingan Wang Date: Sun, 12 Nov 2017 10:37:48 -0800 Subject: [PATCH 3/3] update doc and example --- docs/developers/updates.md | 15 +++++++++++++-- samples/scales/toggle-scale-type.html | 7 +------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/docs/developers/updates.md b/docs/developers/updates.md index 00723001a30..b65757f856b 100644 --- a/docs/developers/updates.md +++ b/docs/developers/updates.md @@ -28,7 +28,7 @@ function removeData(chart) { 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. +- 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 @@ -60,7 +60,7 @@ function updateConfigAsNewObject(chart) { 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. +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) { @@ -83,6 +83,17 @@ function updateScales(chart) { } ``` +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 diff --git a/samples/scales/toggle-scale-type.html b/samples/scales/toggle-scale-type.html index e6bcd25a843..b46687e823a 100644 --- a/samples/scales/toggle-scale-type.html +++ b/samples/scales/toggle-scale-type.html @@ -86,14 +86,9 @@ document.getElementById('toggleScale').addEventListener('click', function() { type = type === 'linear' ? 'logarithmic' : 'linear'; window.myLine.options.title.text = 'Chart.js Line Chart - ' + type; - window.myLine.options.scales = { - xAxes: [{ - display: true - }], - yAxes: [{ + window.myLine.options.scales.yAxes[0] = { display: true, type: type - }] } window.myLine.update();