-
Notifications
You must be signed in to change notification settings - Fork 19.7k
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
[Feature] Add dynamic theme switching support #20705
Conversation
Thanks for your contribution! The pull request is marked to be Document changes are required in this PR. Please also make a PR to apache/echarts-doc for document changes and update the issue id in the PR description. When the doc PR is merged, the maintainers will remove the |
The changes brought by this PR can be previewed at: https://echarts.apache.org/examples/editor?version=PR-20705@21c5ffc |
src/model/Global.ts
Outdated
setTheme(theme: object) { | ||
this._theme = new Model(theme); | ||
// Merge theme with current option directly | ||
mergeTheme(this.option, this._theme.option); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The next sentence this._resetOption('recreate', null);
will recreate ecModel.option
, which makes this mergeTheme
ineffective.
I think it's OK to just remove this mergeTheme
.
And the next sentence will call initBase
to recreate a ecModel.option
and merge theme to it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. I tested it works without mergeTheme
. Thanks.
src/core/echarts.ts
Outdated
this._model.setTheme(this._theme); | ||
// Force refresh to apply theme changes | ||
updateMethods.prepareAndUpdate.call(this, { | ||
type: 'themeChanged' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the type: 'themeChanged'
is ineffective. It won't tigger an event. it is probably inappropriate to trigger an event in an explicit API call (which might lead to dead loop).
If needing to call updateMethods.prepareAndUpdate
or updateMethods.update
, it's OK to use null
as the 2nd parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean updateMethods.prepareAndUpdate.call(this);
? This causes optionChanged: payload.newOption != null
to have error since payload
is undefined
. It works if I add payload &&
as:
updateMethods.update.call(this, payload, payload && {
// Needs to mark option changed if newOption is given.
// It's from MagicType.
// TODO If use a separate flag optionChanged in payload?
optionChanged: payload.newOption != null
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've changed my understanding, it's OK to use
{
type: 'themeChanged'
}
But there is more details need to be considered, see comments below.
updateMethods.prepareAndUpdate.call(this, { | ||
type: 'themeChanged' | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we provide an API setTheme
, I think the behavior should better be similar to setOption
. that is, it modifies the model, and causes re-render either immediately or in a lazy way.
But in the current implementation, re-render will not be triggered.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By calling updateMethods.prepareAndUpdate
, it will re-render as soon as calling setTheme
. See the demo of test case:
onclick: function () {
const riverTheme = {
backgroundColor: '#ccf',
color: ['#00f'],
title: {
textStyle: {
color: '#00f'
}
}
};
chart.setTheme(riverTheme); // renders the chart at once
}
Anything else I need to change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After reviewing the implementation of ECharts#resize
, I updated by understanding.
- Whether to trigger a event when an API called? The current approach is that a parameter
silent
is provided, which is supported by all of theECharts#setOption
,ECharts#resize
andECharts#dispatchAction
. So insetTheme
we'd better follow the convention. - Whether to call
zr.flush()
to perform zr render before the API return?- The current impl is not consistent:
ECharts#setOption
: if not ssr, callzr.flush()
immediatelyECharts#dispatchAction
provide an parameterflush
to enable customization it.ECharts#resize
does not callzr.flush()
.
- If we do not intend to figure this inconsistency this time, I think that it's OK to follow what
ECharts#resize
does.
- The current impl is not consistent:
- Whether to use a nested-call-guard: a flag
IN_MAIN_PROCESS_KEY
is used to prevent from nested calling in bothECharts#setOption
,ECharts#resize
andECharts#dispatchAction
. So I think we can follow that if we do not want to renew the impl.
So IMO the impl of setTheme can be (modified based on the impl of ECharts#resize
):
export interface SetThemeOpts {
silent?: boolean // by default false.
};
setTheme(theme: string | ThemeOption, opts?: SetThemeOpts): void {
if (this[IN_MAIN_PROCESS_KEY]) {
if (__DEV__) {
error('`setTheme` should not be called during the main process.');
}
return;
}
if (this._disposed) {
disposedWarning(this.id);
return;
}
const ecModel = this._model;
if (!ecModel) {
return;
}
let silent = opts && opts.silent;
let updateParams = null as UpdateLifecycleParams;
// There is some real cases that:
// chart.setOption(option, { lazyUpdate: true });
// chart.setTheme('some_theme');
if (this[PENDING_UPDATE]) {
if (silent == null) {
silent = (this[PENDING_UPDATE] as ECharts[PENDING_UPDATE]).silent;
}
updateParams = (this[PENDING_UPDATE] as ECharts[PENDING_UPDATE]).updateParams;
this[PENDING_UPDATE] = null;
}
this[IN_MAIN_PROCESS_KEY] = true;
try {
this._updateTheme(theme);
ecModel.setTheme(this._theme);
prepare(this);
updateMethods.update.call(this, {type: 'setTheme'}, updateParams);
}
catch (e) {
this[IN_MAIN_PROCESS_KEY] = false;
throw e;
}
this[IN_MAIN_PROCESS_KEY] = false;
flushPendingActions.call(this, silent);
triggerUpdatedEvent.call(this, silent);
}
BTW, it seems that there is some defects in the impl of ECharts#resize
: this[PENDING_UPDATE].updateParams
is omitted in lazyUpdate
scenario. I've no idea whether it does that deliberately or to be a mistake.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for the help. This deepens my understanding a lot.
@@ -1660,7 +1715,7 @@ class ECharts extends Eventful<ECEventDefinition> { | |||
|
|||
prepareAndUpdate(this: ECharts, payload: Payload): void { | |||
prepare(this); | |||
updateMethods.update.call(this, payload, { | |||
updateMethods.update.call(this, payload, payload && { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kept this change to make the code more robust, although we are not calling this function now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Congratulations! Your PR has been merged. Thanks for your contribution! 👍 |
Brief Information
This pull request is in the type of:
What does this PR do?
Add support for dynamically changing themes after chart initialization.
Fixed issues
Details
Before: What was the problem?
Currently, themes can only be set before chart initialization.
There's no way to change themes dynamically after the chart is created.
The deprecated
setTheme
method in ECharts 3.0 was removed without a replacement.After: How does it behave after the fixing?
Now users can:
chart.setTheme('themeName')
orchart.setTheme(themeObject)
for registered themesthemeName
is used, both the themes registered before or after the chart was created can be applied.setOption
.Example usage:
Document Info
One of the following should be checked.
Misc
ZRender Changes
Related test cases or examples to use the new APIs
test/theme.html
Others
Merging options
Other information
This PR is a pre-requisite for design token feature, with which we can dynamically change themes using design tokens.