Chart.js: [BUG] Updating scales/axes breaks

Created on 21 Jul 2017  Â·  5Comments  Â·  Source: chartjs/Chart.js

Expected Behavior

The scales/axes are updated

Current Behavior

An error is thrown Uncaught TypeError: Cannot set property 'options' of undefined

Steps to Reproduce (for bugs)

  1. http://plnkr.co/edit/YEunkUileubTZiv6rauM?p=preview
  2. Click Update

Context

I'm trying to have dynamic charts where configuration (not just data) is fed from an external source.

Environment

  • Chart.js version: 2.6.0
  • Browser name and version: Chrome 59.0.3071.115
help wanted enhancement

Most helpful comment

Using Developer tools with Firefox, clicking update on your example, I get

TypeError: t.scales[e.id] is undefined.

Somewhat browser specific error message. The cause though, appears to be that you are wiping out some of the internals of what chartjs setup during the initialization / creation of the original chart object. Add

console.log(JSON.stringify(chart.options.scales.yAxes));

to see what is already there before modifying the options. Way more than what you see in the creation options passed to the constructor. To do what you want, you need to merge your new settings. Not just overwrite with them.

Have a look at jQuery.extend(true, …) for one way to do this. Other implementations are around, including one in the internals of chartjs. Something like (with jQuery)

chart.options.scales.yAxes = $.extend({}, chart.options.scales.yAxes, {[{
  id: 'y-axis-1',
  position: 'right',
  ticks: {
    min: 0
  }
}]};

will probably do what you want. For this case. More complex situations are going to need more involved manipulation, to explicitly remove pieces that are not wanted in the update.

In this case, directly adding the new values should work fine. But that needs to be things like

chart.options.scales.yAxes[0].id =  'y-axis-1';

etc.

For the general case, generate the chart with the minimal options, save the section that is to be manipulated, then use a deep merge.

chart.options.scales.yAxes = $.extend( {}, savedBaseYaxes, newYaxesOptions );

Make sure that the save is a deep copy too, and there is always going to be the risk that the externally supplied options are going to be invalid, or inconsistent with the previous state of the chart instance. chart.update is intended for relatively small changes. Normally chart.update would be called just after modifying the object. So the settings change would be inside your update function. That is really the only safe way, since other things can trigger an update as well. Like resizing the browser, which changes the chart dimensions.

All 5 comments

Using Developer tools with Firefox, clicking update on your example, I get

TypeError: t.scales[e.id] is undefined.

Somewhat browser specific error message. The cause though, appears to be that you are wiping out some of the internals of what chartjs setup during the initialization / creation of the original chart object. Add

console.log(JSON.stringify(chart.options.scales.yAxes));

to see what is already there before modifying the options. Way more than what you see in the creation options passed to the constructor. To do what you want, you need to merge your new settings. Not just overwrite with them.

Have a look at jQuery.extend(true, …) for one way to do this. Other implementations are around, including one in the internals of chartjs. Something like (with jQuery)

chart.options.scales.yAxes = $.extend({}, chart.options.scales.yAxes, {[{
  id: 'y-axis-1',
  position: 'right',
  ticks: {
    min: 0
  }
}]};

will probably do what you want. For this case. More complex situations are going to need more involved manipulation, to explicitly remove pieces that are not wanted in the update.

In this case, directly adding the new values should work fine. But that needs to be things like

chart.options.scales.yAxes[0].id =  'y-axis-1';

etc.

For the general case, generate the chart with the minimal options, save the section that is to be manipulated, then use a deep merge.

chart.options.scales.yAxes = $.extend( {}, savedBaseYaxes, newYaxesOptions );

Make sure that the save is a deep copy too, and there is always going to be the risk that the externally supplied options are going to be invalid, or inconsistent with the previous state of the chart instance. chart.update is intended for relatively small changes. Normally chart.update would be called just after modifying the object. So the settings change would be inside your update function. That is really the only safe way, since other things can trigger an update as well. Like resizing the browser, which changes the chart dimensions.

@mMerlin is definitely on the right track with extending this rather than replacing the array. The items in this array are merged with the axis type defaults during initialization and replacing it means that that is lost.

However, simply changing the logic to push another object onto the array also doesn't work. The error that currently appears probably comes from https://github.com/chartjs/Chart.js/blob/master/src/core/core.controller.js#L53 because the updateConfig function is not currently designed to add or remove scales. I think with some modification it could easily work, but it would require changes elsewhere in update() to ensure that the necessary parts of buildScales() run.

As an aside, we want to get rid of the xAxes and yAxes arrays at the next major version and replace them with a map by axis ID. That will simplify a lot of the internal code and ensure that IDs are always present. It will also make updates like this a lot nicer to work with.

@etimberg any word on how far off this feature is?

In v3, we've moved from arrays to a map of <id: scaleOptions> and this works nicely in v3. http://plnkr.co/edit/QK8DNhNvGbokuNPk

Was this page helpful?
0 / 5 - 0 ratings

Related issues

HeinPauwelyn picture HeinPauwelyn  Â·  3Comments

JewelsJLF picture JewelsJLF  Â·  3Comments

benmccann picture benmccann  Â·  3Comments

gabrieldesouza picture gabrieldesouza  Â·  3Comments

frlinw picture frlinw  Â·  3Comments