You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by su...@apache.org on 2026/03/15 20:09:58 UTC
(echarts) 29/32: fix(toolbox): Simplify toolbox and fix that toolbox throw error when remove DataZoom feature.
This is an automated email from the ASF dual-hosted git repository.
sushuang pushed a commit to branch PR/plainheart_fix/alignTicks-precision
in repository https://gitbox.apache.org/repos/asf/echarts.git
commit 6de824dc04448da902426e37b8988f9354884a36
Author: 100pah <su...@gmail.com>
AuthorDate: Tue Mar 10 21:39:33 2026 +0800
fix(toolbox): Simplify toolbox and fix that toolbox throw error when remove DataZoom feature.
---
src/component/toolbox/ToolboxView.ts | 151 ++++++++++++++++--------------
src/component/toolbox/feature/DataView.ts | 6 +-
src/component/toolbox/feature/DataZoom.ts | 7 --
src/component/toolbox/featureManager.ts | 8 +-
test/toolbox-custom.html | 1 +
5 files changed, 83 insertions(+), 90 deletions(-)
diff --git a/src/component/toolbox/ToolboxView.ts b/src/component/toolbox/ToolboxView.ts
index d3f96d1b4..8b7c82421 100644
--- a/src/component/toolbox/ToolboxView.ts
+++ b/src/component/toolbox/ToolboxView.ts
@@ -17,7 +17,6 @@
* under the License.
*/
-import * as zrUtil from 'zrender/src/core/util';
import * as textContain from 'zrender/src/contain/text';
import * as graphic from '../../util/graphic';
import { enterEmphasis, leaveEmphasis } from '../../util/states';
@@ -28,7 +27,7 @@ import ComponentView from '../../view/Component';
import ToolboxModel from './ToolboxModel';
import GlobalModel from '../../model/Global';
import ExtensionAPI from '../../core/ExtensionAPI';
-import { DisplayState, Dictionary, Payload } from '../../util/types';
+import { DisplayState, Dictionary, Payload, NullUndefined } from '../../util/types';
import {
ToolboxFeature,
getFeature,
@@ -42,8 +41,10 @@ import ZRText from 'zrender/src/graphic/Text';
import { getFont } from '../../label/labelStyle';
import { box, createBoxLayoutReference, getLayoutRect, positionElement } from '../../util/layout';
import tokens from '../../visual/tokens';
+import { bind, createHashMap, curry, each, filter, HashMap, isFunction, isString } from 'zrender/src/core/util';
type IconPath = ToolboxFeatureModel['iconPaths'][string];
+type FeatureName = string;
type ExtendedPath = IconPath & {
__title: string
@@ -52,9 +53,15 @@ type ExtendedPath = IconPath & {
class ToolboxView extends ComponentView {
static type = 'toolbox' as const;
- _features: Dictionary<ToolboxFeature | UserDefinedToolboxFeature>;
+ /**
+ * Current enabled features, including only features having `show: true`.
+ */
+ _features: HashMap<ToolboxFeature | UserDefinedToolboxFeature | NullUndefined, FeatureName>;
- _featureNames: string[];
+ /**
+ * Current enabled feature names, including only features having `show: true`.
+ */
+ _featureNames: FeatureName[];
render(
toolboxModel: ToolboxModel,
@@ -74,35 +81,46 @@ class ToolboxView extends ComponentView {
const itemSize = +toolboxModel.get('itemSize');
const isVertical = toolboxModel.get('orient') === 'vertical';
const featureOpts = toolboxModel.get('feature') || {};
- const features = this._features || (this._features = {});
+ const features = this._features || (this._features = createHashMap());
- const featureNames: string[] = [];
- zrUtil.each(featureOpts, function (opt, name) {
- featureNames.push(name);
+ const newFeatureNames: FeatureName[] = []; // Includes both `show: true/false`.
+ each(featureOpts, function (opt, name) {
+ newFeatureNames.push(name);
});
- (new DataDiffer(this._featureNames || [], featureNames))
+ // Diff by feature name.
+ (new DataDiffer(this._featureNames || [], newFeatureNames))
.add(processFeature)
.update(processFeature)
- .remove(zrUtil.curry(processFeature, null))
+ .remove(curry(processFeature, null))
.execute();
// Keep for diff.
- this._featureNames = featureNames;
+ this._featureNames = filter(newFeatureNames, function (name) {
+ return features.hasKey(name);
+ });
+
+ function processFeature(newIndex: number | NullUndefined, oldIndex?: number | NullUndefined) {
+ const isDiffAdd = newIndex != null && oldIndex == null;
+ const isDiffUpdate = newIndex != null && oldIndex != null;
+ const isDiffRemove = newIndex == null;
- function processFeature(newIndex: number, oldIndex?: number) {
- const featureName = featureNames[newIndex];
- const oldName = featureNames[oldIndex];
+ const featureName = (isDiffAdd || isDiffUpdate)
+ ? newFeatureNames[newIndex]
+ : newFeatureNames[oldIndex];
const featureOpt = featureOpts[featureName];
- const featureModel = new Model(featureOpt, toolboxModel, toolboxModel.ecModel) as ToolboxFeatureModel;
- let feature: ToolboxFeature | UserDefinedToolboxFeature;
+ const featureModel = (isDiffAdd || isDiffUpdate)
+ ? new Model(featureOpt, toolboxModel, ecModel) as ToolboxFeatureModel
+ : null;
+ // `.get('show')` Also considered UserDefinedToolboxFeature
+ const isFeatureShow = featureModel && featureModel.get('show');
- // FIX#11236, merge feature title from MagicType newOption. TODO: consider seriesIndex ?
- if (payload && payload.newTitle != null && payload.featureName === featureName) {
- featureOpt.title = payload.newTitle;
- }
+ let feature: ToolboxFeature | UserDefinedToolboxFeature;
- if (featureName && !oldName) { // Create
+ if (isDiffAdd) { // DIFF_ADD
+ if (!isFeatureShow) {
+ return;
+ }
if (isUserFeatureName(featureName)) {
feature = {
onclick: featureModel.option.onclick,
@@ -116,35 +134,33 @@ class ToolboxView extends ComponentView {
}
feature = new Feature();
}
- features[featureName] = feature;
+ features.set(featureName, feature);
}
- else {
- feature = features[oldName];
- // If feature does not exist.
- if (!feature) {
- return;
- }
+ else { // DIFF_UPDATE or DIFF_REMOVE
+ feature = features.get(featureName);
}
- feature.uid = getUID('toolbox-feature');
- feature.model = featureModel;
- feature.ecModel = ecModel;
- feature.api = api;
- const isToolboxFeature = feature instanceof ToolboxFeature;
- if (!featureName && oldName) {
- isToolboxFeature
- && (feature as ToolboxFeature).dispose
- && (feature as ToolboxFeature).dispose(ecModel, api);
+ if (isDiffRemove || !isFeatureShow) {
+ if (isTooltipFeature(feature) && feature.dispose) {
+ feature.dispose(ecModel, api);
+ }
+ features.removeKey(featureName);
return;
}
- if (!featureModel.get('show') || (isToolboxFeature && (feature as ToolboxFeature).unusable)) {
- isToolboxFeature
- && (feature as ToolboxFeature).remove
- && (feature as ToolboxFeature).remove(ecModel, api);
- return;
+ // FIX#11236, merge feature title from MagicType newOption. TODO: consider seriesIndex ?
+ if (payload && payload.newTitle != null && payload.featureName === featureName) {
+ // FIXME: ec option should not be modified here.
+ featureOpt.title = payload.newTitle;
}
+ if (isDiffAdd) {
+ feature.uid = getUID('toolbox-feature');
+ }
+ feature.model = featureModel;
+ feature.ecModel = ecModel;
+ feature.api = api;
+
createIconPaths(featureModel, feature, featureName);
featureModel.setIconStatus = function (this: ToolboxFeatureModel, iconName: string, status: DisplayState) {
@@ -157,10 +173,8 @@ class ToolboxView extends ComponentView {
}
};
- if (feature instanceof ToolboxFeature) {
- if (feature.render) {
- feature.render(featureModel, ecModel, api, payload);
- }
+ if (isTooltipFeature(feature) && feature.render) {
+ feature.render(featureModel, ecModel, api, payload);
}
}
@@ -188,14 +202,14 @@ class ToolboxView extends ComponentView {
const titles = featureModel.get('title') || {};
let iconsMap: Dictionary<string>;
let titlesMap: Dictionary<string>;
- if (zrUtil.isString(icons)) {
+ if (isString(icons)) {
iconsMap = {};
iconsMap[featureName] = icons;
}
else {
iconsMap = icons;
}
- if (zrUtil.isString(titles)) {
+ if (isString(titles)) {
titlesMap = {};
titlesMap[featureName] = titles as string;
}
@@ -203,7 +217,7 @@ class ToolboxView extends ComponentView {
titlesMap = titles;
}
const iconPaths: ToolboxFeatureModel['iconPaths'] = featureModel.iconPaths = {};
- zrUtil.each(iconsMap, function (iconStr, iconName) {
+ each(iconsMap, function (iconStr, iconName) {
const path = graphic.createIcon(
iconStr,
{},
@@ -286,7 +300,7 @@ class ToolboxView extends ComponentView {
(featureModel.get(['iconStatus', iconName]) === 'emphasis' ? enterEmphasis : leaveEmphasis)(path);
group.add(path);
- (path as graphic.Path).on('click', zrUtil.bind(
+ (path as graphic.Path).on('click', bind(
feature.onclick, feature, ecModel, api, iconName
));
@@ -331,7 +345,7 @@ class ToolboxView extends ComponentView {
const textContent = icon.getTextContent();
const emphasisTextState = textContent && textContent.ensureState('emphasis');
// May be background element
- if (emphasisTextState && !zrUtil.isFunction(emphasisTextState) && titleText) {
+ if (emphasisTextState && !isFunction(emphasisTextState) && titleText) {
const emphasisTextStyle = emphasisTextState.style || (emphasisTextState.style = {});
const rect = textContain.getBoundingRect(
titleText, ZRText.makeFont(emphasisTextStyle)
@@ -363,30 +377,20 @@ class ToolboxView extends ComponentView {
api: ExtensionAPI,
payload: unknown
) {
- zrUtil.each(this._features, function (feature) {
- feature instanceof ToolboxFeature
- && feature.updateView && feature.updateView(feature.model, ecModel, api, payload);
- });
- }
-
- // updateLayout(toolboxModel, ecModel, api, payload) {
- // zrUtil.each(this._features, function (feature) {
- // feature.updateLayout && feature.updateLayout(feature.model, ecModel, api, payload);
- // });
- // },
-
- remove(ecModel: GlobalModel, api: ExtensionAPI) {
- zrUtil.each(this._features, function (feature) {
- feature instanceof ToolboxFeature
- && feature.remove && feature.remove(ecModel, api);
+ each(this._features, function (feature) {
+ feature
+ && feature instanceof ToolboxFeature
+ && feature.updateView
+ && feature.updateView(feature.model, ecModel, api, payload);
});
- this.group.removeAll();
}
dispose(ecModel: GlobalModel, api: ExtensionAPI) {
- zrUtil.each(this._features, function (feature) {
- feature instanceof ToolboxFeature
- && feature.dispose && feature.dispose(ecModel, api);
+ each(this._features, function (feature) {
+ feature
+ && feature instanceof ToolboxFeature
+ && feature.dispose
+ && feature.dispose(ecModel, api);
});
}
}
@@ -395,4 +399,9 @@ class ToolboxView extends ComponentView {
function isUserFeatureName(featureName: string): boolean {
return featureName.indexOf('my') === 0;
}
+
+function isTooltipFeature(feature: ToolboxFeature | UserDefinedToolboxFeature): feature is ToolboxFeature {
+ return feature instanceof ToolboxFeature;
+}
+
export default ToolboxView;
diff --git a/src/component/toolbox/feature/DataView.ts b/src/component/toolbox/feature/DataView.ts
index def839af5..7f4c506fc 100644
--- a/src/component/toolbox/feature/DataView.ts
+++ b/src/component/toolbox/feature/DataView.ts
@@ -447,12 +447,8 @@ class DataView extends ToolboxFeature<ToolboxDataViewFeatureOption> {
this._dom = root;
}
- remove(ecModel: GlobalModel, api: ExtensionAPI) {
- this._dom && api.getDom().removeChild(this._dom);
- }
-
dispose(ecModel: GlobalModel, api: ExtensionAPI) {
- this.remove(ecModel, api);
+ this._dom && api.getDom().removeChild(this._dom);
}
static getDefaultOption(ecModel: GlobalModel) {
diff --git a/src/component/toolbox/feature/DataZoom.ts b/src/component/toolbox/feature/DataZoom.ts
index 8125b6dcc..0c57324de 100644
--- a/src/component/toolbox/feature/DataZoom.ts
+++ b/src/component/toolbox/feature/DataZoom.ts
@@ -104,13 +104,6 @@ class DataZoomFeature extends ToolboxFeature<ToolboxDataZoomFeatureOption> {
handlers[type].call(this);
}
- remove(
- ecModel: GlobalModel,
- api: ExtensionAPI
- ) {
- this._brushController && this._brushController.unmount();
- }
-
dispose(
ecModel: GlobalModel,
api: ExtensionAPI
diff --git a/src/component/toolbox/featureManager.ts b/src/component/toolbox/featureManager.ts
index 5b16acf61..a1f554355 100644
--- a/src/component/toolbox/featureManager.ts
+++ b/src/component/toolbox/featureManager.ts
@@ -73,9 +73,8 @@ interface ToolboxFeature<Opts extends ToolboxFeatureOption = ToolboxFeatureOptio
onclick(ecModel: GlobalModel, api: ExtensionAPI, type: string, event: ZRElementEvent): void
dispose?(ecModel: GlobalModel, api: ExtensionAPI): void
- remove?(ecModel: GlobalModel, api: ExtensionAPI): void
- render(featureModel: ToolboxFeatureModel, model: GlobalModel, api: ExtensionAPI, payload: unknown): void
+ render?(featureModel: ToolboxFeatureModel, model: GlobalModel, api: ExtensionAPI, payload: unknown): void
updateView?(featureModel: ToolboxFeatureModel, model: GlobalModel, api: ExtensionAPI, payload: unknown): void
}
abstract class ToolboxFeature<Opts extends ToolboxFeatureOption = ToolboxFeatureOption> {
@@ -84,11 +83,6 @@ abstract class ToolboxFeature<Opts extends ToolboxFeatureOption = ToolboxFeature
model: ToolboxFeatureModel<Opts>;
ecModel: GlobalModel;
api: ExtensionAPI;
-
- /**
- * If toolbox feature can't be used on some platform.
- */
- unusable?: boolean;
}
export {ToolboxFeature};
diff --git a/test/toolbox-custom.html b/test/toolbox-custom.html
index 91eee32b1..0a1b1209f 100644
--- a/test/toolbox-custom.html
+++ b/test/toolbox-custom.html
@@ -167,6 +167,7 @@ under the License.
function createOption() {
var option = {
+ tooltip: {},
xAxis: {},
yAxis: {},
series: {
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org