# 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
});
chart.update();
}
-```
-```javascript
function removeData(chart) {
chart.data.labels.pop();
chart.data.datasets.forEach((dataset) => {
}
```
+## 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
}, {
title: 'Non numeric Y Axis',
path: 'scales/non-numeric-y.html'
+ }, {
+ title: 'Toggle Scale Type',
+ path: 'scales/toggle-scale-type.html'
}]
}, {
title: 'Legend',
--- /dev/null
+<!doctype html>
+<html>
+
+<head>
+ <title>Toggle Scale Type</title>
+ <script src="../../dist/Chart.bundle.js"></script>
+ <script src="../utils.js"></script>
+ <style>
+ canvas {
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ }
+ </style>
+</head>
+
+<body>
+ <div style="width:75%;">
+ <canvas id="canvas"></canvas>
+ </div>
+ <button id="toggleScale">Toggle Scale Type</button>
+ <script>
+ var randomScalingFactor = function() {
+ return Math.ceil(Math.random() * 10.0) * Math.pow(10, Math.ceil(Math.random() * 5));
+ };
+
+ var type = 'linear';
+
+ var config = {
+ type: 'line',
+ data: {
+ labels: ["January", "February", "March", "April", "May", "June", "July"],
+ datasets: [{
+ label: "My First dataset",
+ backgroundColor: window.chartColors.red,
+ borderColor: window.chartColors.red,
+ fill: false,
+ data: [
+ randomScalingFactor(),
+ randomScalingFactor(),
+ randomScalingFactor(),
+ randomScalingFactor(),
+ randomScalingFactor(),
+ randomScalingFactor(),
+ randomScalingFactor()
+ ],
+ }, {
+ label: "My Second dataset",
+ backgroundColor: window.chartColors.blue,
+ borderColor: window.chartColors.blue,
+ fill: false,
+ data: [
+ randomScalingFactor(),
+ randomScalingFactor(),
+ randomScalingFactor(),
+ randomScalingFactor(),
+ randomScalingFactor(),
+ randomScalingFactor(),
+ randomScalingFactor()
+ ],
+ }]
+ },
+ options: {
+ responsive: true,
+ title:{
+ display: true,
+ text: 'Chart.js Line Chart - ' + type
+ },
+ scales: {
+ xAxes: [{
+ display: true,
+ }],
+ yAxes: [{
+ display: true,
+ type: type
+ }]
+ }
+ }
+ };
+
+ window.onload = function() {
+ var ctx = document.getElementById("canvas").getContext("2d");
+ window.myLine = new Chart(ctx, config);
+ };
+
+ 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.yAxes[0] = {
+ display: true,
+ type: type
+ }
+
+ window.myLine.update();
+ });
+ </script>
+</body>
+
+</html>
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) {
// 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
/**
* 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(
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)
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);
},
if (meta.controller) {
meta.controller.updateIndex(datasetIndex);
+ meta.controller.linkScales();
} else {
var ControllerClass = Chart.controllers[meta.type];
if (ControllerClass === undefined) {
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;
}
},
});
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',
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',