Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix scale options update error #4198

Merged
merged 3 commits into from Nov 29, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
76 changes: 73 additions & 3 deletions 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

Expand All @@ -14,9 +14,7 @@ function addData(chart, label, data) {
});
chart.update();
}
```

```javascript
function removeData(chart) {
chart.data.labels.pop();
chart.data.datasets.forEach((dataset) => {
Expand All @@ -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.
3 changes: 3 additions & 0 deletions samples/samples.js
Expand Up @@ -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',
Expand Down
99 changes: 99 additions & 0 deletions samples/scales/toggle-scale-type.html
@@ -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>
74 changes: 51 additions & 23 deletions src/core/core.controller.js
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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)
Expand All @@ -278,6 +297,14 @@ module.exports = function(Chart) {
me.scale = scale;
}
});
// clear up discarded scales
helpers.each(updated, function(hasUpdated, id) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@etimberg last commit here was definitely a mistake. Fixed here.
So now whenever user specify a scale with a new id or the type is changed, user lose the reference to the old scale.

if (!hasUpdated) {
delete scales[id];
}
});

me.scales = scales;

Chart.scaleService.addScalesToLayout(this);
},
Expand All @@ -301,6 +328,7 @@ module.exports = function(Chart) {

if (meta.controller) {
meta.controller.updateIndex(datasetIndex);
meta.controller.linkScales();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I came across this when I tried to update scales with different id - dataset._meta contains scales id which didn't update.
I check the scales id of meta here, relink the scale if discarded.

I'm not sure whether I missed anything else, please point out if there are. It's a little bit too many configs here : P

} else {
var ControllerClass = Chart.controllers[meta.type];
if (ControllerClass === undefined) {
Expand Down
4 changes: 2 additions & 2 deletions src/core/core.datasetController.js
Expand Up @@ -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;
}
},
Expand Down