mirror of
https://github.com/chartjs/Chart.js.git
synced 2024-10-06 04:09:08 +02:00
Fix scale options update (#4198)
- allow options to be updated in-place or as a new object - re-merge new options and rebuild scales & tooltips - preserve reference to old scale if id/type not changed - related tests and new sample also added. - update document about options update - update doc and example
This commit is contained in:
parent
42d3d91f52
commit
333f2eba99
@ -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.
|
@ -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',
|
||||
|
99
samples/scales/toggle-scale-type.html
Normal file
99
samples/scales/toggle-scale-type.html
Normal file
@ -0,0 +1,99 @@
|
||||
<!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>
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
},
|
||||
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user