You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by sh...@apache.org on 2020/02/26 07:39:43 UTC

[incubator-echarts] branch typescript updated: ts: add types for Toolbox

This is an automated email from the ASF dual-hosted git repository.

shenyi pushed a commit to branch typescript
in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git


The following commit(s) were added to refs/heads/typescript by this push:
     new bed19de  ts: add types for Toolbox
bed19de is described below

commit bed19debd1fdb5d50a993e600bfe9bb628d6d55e
Author: pissang <bm...@gmail.com>
AuthorDate: Wed Feb 26 15:39:05 2020 +0800

    ts: add types for Toolbox
---
 src/chart/funnel/FunnelSeries.ts             |   4 +-
 src/chart/pie/labelLayout.ts                 |   6 +-
 src/component/legend/LegendModel.ts          |  16 +-
 src/component/legend/LegendView.ts           |  24 +-
 src/component/polar.ts                       |   2 -
 src/component/singleAxis.ts                  |   2 -
 src/component/toolbox/ToolboxModel.ts        |  80 +++++-
 src/component/toolbox/ToolboxView.ts         | 184 ++++++++-----
 src/component/toolbox/feature/Brush.ts       | 232 ++++++++--------
 src/component/toolbox/feature/DataView.ts    | 379 ++++++++++++++-------------
 src/component/toolbox/feature/DataZoom.ts    | 296 +++++++++++----------
 src/component/toolbox/feature/MagicType.ts   | 285 +++++++++++---------
 src/component/toolbox/feature/Restore.ts     |  48 ++--
 src/component/toolbox/feature/SaveAsImage.ts | 146 ++++++-----
 src/component/toolbox/featureManager.ts      |  96 ++++++-
 src/component/tooltip/TooltipModel.ts        |   7 +-
 src/component/tooltip/TooltipView.ts         |  15 +-
 src/coord/CoordinateSystem.ts                |   2 +
 src/coord/axisHelper.ts                      |   1 -
 src/data/DataDiffer.ts                       |   4 +-
 src/data/List.ts                             |   2 +-
 src/data/helper/dataProvider.ts              |   2 +-
 src/echarts.ts                               |  20 +-
 src/model/Global.ts                          |   2 +-
 src/theme/dark.ts                            |   4 +-
 src/util/animation.ts                        |  12 +-
 src/util/types.ts                            |  44 ++--
 27 files changed, 1108 insertions(+), 807 deletions(-)

diff --git a/src/chart/funnel/FunnelSeries.ts b/src/chart/funnel/FunnelSeries.ts
index cead1b7..04792b3 100644
--- a/src/chart/funnel/FunnelSeries.ts
+++ b/src/chart/funnel/FunnelSeries.ts
@@ -26,7 +26,7 @@ import SeriesModel from '../../model/Series';
 import {
     SeriesOption,
     BoxLayoutOptionMixin,
-    ZRAlign,
+    HorizontalAlign,
     LabelOption,
     LabelLineOption,
     ItemStyleOption,
@@ -73,7 +73,7 @@ export interface FunnelSeriesOption
 
     gap?: number
 
-    funnelAlign?: ZRAlign
+    funnelAlign?: HorizontalAlign
 
     label?: FunnelLabelOption
     labelLine?: LabelLineOption
diff --git a/src/chart/pie/labelLayout.ts b/src/chart/pie/labelLayout.ts
index b9b7e84..2501958 100644
--- a/src/chart/pie/labelLayout.ts
+++ b/src/chart/pie/labelLayout.ts
@@ -23,7 +23,7 @@ import * as textContain from 'zrender/src/contain/text';
 import {parsePercent} from '../../util/number';
 import PieSeriesModel, { PieSeriesOption } from './PieSeries';
 import { VectorArray } from 'zrender/src/core/vector';
-import { ZRAlign, ZRVerticalAlign, ZRRectLike } from '../../util/types';
+import { HorizontalAlign, VerticalAlign, ZRRectLike } from '../../util/types';
 
 var RADIAN = Math.PI / 180;
 
@@ -35,8 +35,8 @@ interface LabelLayout {
     len: number
     len2: number
     linePoints: VectorArray[]
-    textAlign: ZRAlign
-    verticalAlign: ZRVerticalAlign,
+    textAlign: HorizontalAlign
+    verticalAlign: VerticalAlign,
     rotation: number,
     inside: boolean,
     labelDistance: number,
diff --git a/src/component/legend/LegendModel.ts b/src/component/legend/LegendModel.ts
index e574406..528ab13 100644
--- a/src/component/legend/LegendModel.ts
+++ b/src/component/legend/LegendModel.ts
@@ -28,7 +28,9 @@ import {
     BorderOptionMixin,
     ColorString,
     ItemStyleOption,
-    LabelOption
+    LabelOption,
+    LayoutOrient,
+    CommonTooltipOption
 } from '../../util/types';
 import { Dictionary } from 'zrender/src/core/types';
 import GlobalModel from '../../model/Global';
@@ -60,10 +62,17 @@ interface DataItem {
     // TODO: TYPE tooltip
     tooltip?: unknown
 }
+
+export interface LegendTooltipFormatterParams {
+    componentType: 'legend'
+    legendIndex: number
+    name: string
+    $vars: ['name']
+}
 export interface LegendOption extends ComponentOption, BoxLayoutOptionMixin, BorderOptionMixin {
     show?: boolean
 
-    orient?: 'horizontal' | 'vertical'
+    orient?: LayoutOrient
 
     align?: 'auto' | 'left' | 'right'
 
@@ -152,8 +161,7 @@ export interface LegendOption extends ComponentOption, BoxLayoutOptionMixin, Bor
     /**
      * Tooltip option
      */
-    // TODO: TYPE tooltip
-    tooltip?: unknown
+    tooltip?: CommonTooltipOption<LegendTooltipFormatterParams>
 
 }
 
diff --git a/src/component/legend/LegendView.ts b/src/component/legend/LegendView.ts
index 35e0a16..ebc3838 100644
--- a/src/component/legend/LegendView.ts
+++ b/src/component/legend/LegendView.ts
@@ -24,7 +24,7 @@ import * as graphic from '../../util/graphic';
 import {makeBackground} from '../helper/listComponent';
 import * as layoutUtil from '../../util/layout';
 import ComponentView from '../../view/Component';
-import LegendModel, { LegendOption, LegendSelectorButtonOption } from './LegendModel';
+import LegendModel, { LegendOption, LegendSelectorButtonOption, LegendTooltipFormatterParams } from './LegendModel';
 import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../ExtensionAPI';
 import { ColorString, ZRTextAlign, ZRColor, ItemStyleOption, ZRRectLike, ECElement, CommonTooltipOption } from '../../util/types';
@@ -338,7 +338,7 @@ class LegendView extends ComponentView {
 
         var itemIcon = itemModel.get('icon');
 
-        var tooltipModel = itemModel.getModel('tooltip') as Model<CommonTooltipOption>;
+        var tooltipModel = itemModel.getModel('tooltip') as Model<CommonTooltipOption<LegendTooltipFormatterParams>>;
         var legendGlobalTooltipModel = tooltipModel.parentModel;
 
         // Use user given icon first
@@ -418,18 +418,20 @@ class LegendView extends ComponentView {
             invisible: true
         });
         if (tooltipModel.get('show')) {
+            const formatterParams: LegendTooltipFormatterParams = {
+                componentType: 'legend',
+                legendIndex: legendModel.componentIndex,
+                name: name,
+                $vars: ['name']
+            };
             (hitRect as ECElement).tooltip = zrUtil.extend({
                 content: name,
                 // Defaul formatter
-                formatter: legendGlobalTooltipModel.get('formatter', true) || function () {
-                    return name;
-                },
-                formatterParams: {
-                    componentType: 'legend',
-                    legendIndex: legendModel.componentIndex,
-                    name: name,
-                    $vars: ['name']
-                }
+                formatter: legendGlobalTooltipModel.get('formatter', true)
+                    || function (params: LegendTooltipFormatterParams) {
+                        return params.name;
+                    },
+                formatterParams: formatterParams
             }, tooltipModel.option);
         }
         itemGroup.add(hitRect);
diff --git a/src/component/polar.ts b/src/component/polar.ts
index d84672f..398015c 100644
--- a/src/component/polar.ts
+++ b/src/component/polar.ts
@@ -17,8 +17,6 @@
 * under the License.
 */
 
-// @ts-nocheck
-
 import * as echarts from '../echarts';
 import * as zrUtil from 'zrender/src/core/util';
 import barPolar from '../layout/barPolar';
diff --git a/src/component/singleAxis.ts b/src/component/singleAxis.ts
index 503351c..0723b33 100644
--- a/src/component/singleAxis.ts
+++ b/src/component/singleAxis.ts
@@ -17,8 +17,6 @@
 * under the License.
 */
 
-// @ts-nocheck
-
 import * as echarts from '../echarts';
 
 import '../coord/single/singleCreator';
diff --git a/src/component/toolbox/ToolboxModel.ts b/src/component/toolbox/ToolboxModel.ts
index 7b15220..724f01d 100644
--- a/src/component/toolbox/ToolboxModel.ts
+++ b/src/component/toolbox/ToolboxModel.ts
@@ -17,31 +17,83 @@
 * under the License.
 */
 
-// @ts-nocheck
-
-import * as echarts from '../../echarts';
 import * as zrUtil from 'zrender/src/core/util';
 import * as featureManager from './featureManager';
+import ComponentModel from '../../model/Component';
+import {
+    ComponentOption,
+    BoxLayoutOptionMixin,
+    LayoutOrient,
+    ZRColor,
+    BorderOptionMixin,
+    ItemStyleOption,
+    LabelOption,
+    CommonTooltipOption,
+    Dictionary
+} from '../../util/types';
+
+
+export interface ToolboxTooltipFormatterParams {
+    componentType: 'toolbox'
+    name: string
+    title: string
+    $vars: ['name', 'title']
+}
+export interface ToolboxOption extends ComponentOption,
+    BoxLayoutOptionMixin,
+    BorderOptionMixin {
+    show?: boolean
+
+    orient?: LayoutOrient
+
+    backgroundColor?: ZRColor
+
+    borderRadius?: number | number[]
+
+    padding?: number | number[]
+
+    itemSize?: number
+
+    itemGap?: number
 
-var ToolboxModel = echarts.extendComponentModel({
+    showTitle?: boolean
 
-    type: 'toolbox',
+    iconStyle?: ItemStyleOption
 
-    layoutMode: {
+    emphasis?: {
+        iconStyle?: ItemStyleOption
+    }
+
+    textStyle?: LabelOption
+
+    tooltip?: CommonTooltipOption<ToolboxTooltipFormatterParams>
+
+    /**
+     * Write all supported features in the final export option.
+     */
+    feature?: Dictionary<featureManager.ToolboxFeatureOption>
+}
+
+class ToolboxModel extends ComponentModel<ToolboxOption> {
+
+    static type = 'toolbox' as const
+    type = ToolboxModel.type
+
+    static layoutMode = {
         type: 'box',
         ignoreSize: true
-    },
+    } as const
 
-    optionUpdated: function () {
-        ToolboxModel.superApply(this, 'optionUpdated', arguments);
+    optionUpdated() {
+        super.optionUpdated.apply(this, arguments as any);
 
         zrUtil.each(this.option.feature, function (featureOpt, featureName) {
-            var Feature = featureManager.get(featureName);
+            var Feature = featureManager.getFeature(featureName);
             Feature && zrUtil.merge(featureOpt, Feature.defaultOption);
         });
-    },
+    }
 
-    defaultOption: {
+    static defaultOption: ToolboxOption = {
 
         show: true,
 
@@ -91,6 +143,8 @@ var ToolboxModel = echarts.extendComponentModel({
             show: false
         }
     }
-});
+}
+
+ComponentModel.registerClass(ToolboxModel);
 
 export default ToolboxModel;
\ No newline at end of file
diff --git a/src/component/toolbox/ToolboxView.ts b/src/component/toolbox/ToolboxView.ts
index 86d69fa..115d569 100644
--- a/src/component/toolbox/ToolboxView.ts
+++ b/src/component/toolbox/ToolboxView.ts
@@ -17,22 +17,47 @@
 * under the License.
 */
 
-// @ts-nocheck
-
-import * as echarts from '../../echarts';
 import * as zrUtil from 'zrender/src/core/util';
 import * as textContain from 'zrender/src/contain/text';
-import * as featureManager from './featureManager';
 import * as graphic from '../../util/graphic';
 import Model from '../../model/Model';
 import DataDiffer from '../../data/DataDiffer';
 import * as listComponentHelper from '../helper/listComponent';
+import ComponentView from '../../view/Component';
+import ToolboxModel from './ToolboxModel';
+import GlobalModel from '../../model/Global';
+import ExtensionAPI from '../../ExtensionAPI';
+import { DisplayState, Dictionary, ECElement, Payload } from '../../util/types';
+import {
+    ToolboxFeature,
+    getFeature,
+    ToolboxFeatureModel,
+    ToolboxFeatureOption,
+    UserDefinedToolboxFeature
+} from './featureManager';
+import { getUID } from '../../util/component';
+
+type IconPath = ToolboxFeatureModel['iconPaths'][string];
+
+type ExtendedPath = IconPath & {
+    __title: string
+}
+
+class ToolboxView extends ComponentView {
+    static type = 'toolbox' as const
 
-export default echarts.extendComponentView({
+    _features: Dictionary<ToolboxFeature | UserDefinedToolboxFeature>
 
-    type: 'toolbox',
+    _featureNames: string[]
 
-    render: function (toolboxModel, ecModel, api, payload) {
+    render(
+        toolboxModel: ToolboxModel,
+        ecModel: GlobalModel,
+        api: ExtensionAPI,
+        payload: Payload & {
+            newTitle?: ToolboxFeatureOption['title']
+        }
+    ) {
         var group = this.group;
         group.removeAll();
 
@@ -44,7 +69,7 @@ export default echarts.extendComponentView({
         var featureOpts = toolboxModel.get('feature') || {};
         var features = this._features || (this._features = {});
 
-        var featureNames = [];
+        var featureNames: string[] = [];
         zrUtil.each(featureOpts, function (opt, name) {
             featureNames.push(name);
         });
@@ -58,12 +83,12 @@ export default echarts.extendComponentView({
         // Keep for diff.
         this._featureNames = featureNames;
 
-        function processFeature(newIndex, oldIndex) {
+        function processFeature(newIndex: number, oldIndex?: number) {
             var featureName = featureNames[newIndex];
             var oldName = featureNames[oldIndex];
             var featureOpt = featureOpts[featureName];
-            var featureModel = new Model(featureOpt, toolboxModel, toolboxModel.ecModel);
-            var feature;
+            var featureModel = new Model(featureOpt, toolboxModel, toolboxModel.ecModel) as ToolboxFeatureModel;
+            var feature: ToolboxFeature | UserDefinedToolboxFeature;
 
             // FIX#11236, merge feature title from MagicType newOption. TODO: consider seriesIndex ?
             if (payload && payload.newTitle != null) {
@@ -73,17 +98,16 @@ export default echarts.extendComponentView({
             if (featureName && !oldName) { // Create
                 if (isUserFeatureName(featureName)) {
                     feature = {
-                        model: featureModel,
                         onclick: featureModel.option.onclick,
                         featureName: featureName
-                    };
+                    } as UserDefinedToolboxFeature;
                 }
                 else {
-                    var Feature = featureManager.get(featureName);
+                    var Feature = getFeature(featureName);
                     if (!Feature) {
                         return;
                     }
-                    feature = new Feature(featureModel, ecModel, api);
+                    feature = new Feature();
                 }
                 features[featureName] = feature;
             }
@@ -93,24 +117,26 @@ export default echarts.extendComponentView({
                 if (!feature) {
                     return;
                 }
-                feature.model = featureModel;
-                feature.ecModel = ecModel;
-                feature.api = api;
-            }
-
-            if (!featureName && oldName) {
-                feature.dispose && feature.dispose(ecModel, api);
-                return;
             }
+            feature.uid = getUID('toolbox-feature');
+            feature.model = featureModel;
+            feature.ecModel = ecModel;
+            feature.api = api;
+
+            if (feature instanceof ToolboxFeature) {
+                if (!featureName && oldName) {
+                    feature.dispose && feature.dispose(ecModel, api);
+                    return;
+                }
 
-            if (!featureModel.get('show') || feature.unusable) {
-                feature.remove && feature.remove(ecModel, api);
-                return;
+                if (!featureModel.get('show') || feature.unusable) {
+                    feature.remove && feature.remove(ecModel, api);
+                    return;
+                }
             }
-
             createIconPaths(featureModel, feature, featureName);
 
-            featureModel.setIconStatus = function (iconName, status) {
+            featureModel.setIconStatus = function (this: ToolboxFeatureModel, iconName: string, status: DisplayState) {
                 var option = this.option;
                 var iconPaths = this.iconPaths;
                 option.iconStatus = option.iconStatus || {};
@@ -119,14 +145,20 @@ export default echarts.extendComponentView({
                 iconPaths[iconName] && iconPaths[iconName].trigger(status);
             };
 
-            if (feature.render) {
-                feature.render(featureModel, ecModel, api, payload);
+            if (feature instanceof ToolboxFeature) {
+                if (feature.render) {
+                    feature.render(featureModel, ecModel, api, payload);
+                }
             }
         }
 
-        function createIconPaths(featureModel, feature, featureName) {
+        function createIconPaths(
+            featureModel: ToolboxFeatureModel,
+            feature: ToolboxFeature | UserDefinedToolboxFeature,
+            featureName: string
+        ) {
             var iconStyleModel = featureModel.getModel('iconStyle');
-            var iconStyleEmphasisModel = featureModel.getModel('emphasis.iconStyle');
+            var iconStyleEmphasisModel = featureModel.getModel(['emphasis', 'iconStyle']);
 
             // If one feature has mutiple icon. they are orginaized as
             // {
@@ -139,18 +171,27 @@ export default echarts.extendComponentView({
             //         bar: ''
             //     }
             // }
-            var icons = feature.getIcons ? feature.getIcons() : featureModel.get('icon');
+            var icons = (feature instanceof ToolboxFeature && feature.getIcons)
+                ? feature.getIcons() : featureModel.get('icon');
             var titles = featureModel.get('title') || {};
+            var iconsMap: Dictionary<string>;
+            var titlesMap: Dictionary<string>;
             if (typeof icons === 'string') {
-                var icon = icons;
-                var title = titles;
-                icons = {};
-                titles = {};
-                icons[featureName] = icon;
-                titles[featureName] = title;
+                iconsMap = {};
+                iconsMap[featureName] = icons;
             }
-            var iconPaths = featureModel.iconPaths = {};
-            zrUtil.each(icons, function (iconStr, iconName) {
+            else {
+                iconsMap = icons;
+            }
+            if (typeof titles === 'string') {
+                titlesMap = {};
+                titlesMap[featureName] = titles as string;
+            }
+            else {
+                titlesMap = titles;
+            }
+            var iconPaths: ToolboxFeatureModel['iconPaths'] = featureModel.iconPaths = {};
+            zrUtil.each(iconsMap, function (iconStr, iconName) {
                 var path = graphic.createIcon(
                     iconStr,
                     {},
@@ -166,7 +207,7 @@ export default echarts.extendComponentView({
 
                 // Text position calculation
                 path.setStyle({
-                    text: titles[iconName],
+                    text: titlesMap[iconName],
                     textAlign: iconStyleEmphasisModel.get('textAlign'),
                     textBorderRadius: iconStyleEmphasisModel.get('textBorderRadius'),
                     textPadding: iconStyleEmphasisModel.get('textPadding'),
@@ -175,33 +216,33 @@ export default echarts.extendComponentView({
 
                 var tooltipModel = toolboxModel.getModel('tooltip');
                 if (tooltipModel && tooltipModel.get('show')) {
-                    path.attr('tooltip', zrUtil.extend({
-                        content: titles[iconName],
+                    (path as ECElement).tooltip = zrUtil.extend({
+                        content: titlesMap[iconName],
                         formatter: tooltipModel.get('formatter', true)
                             || function () {
-                                return titles[iconName];
+                                return titlesMap[iconName];
                             },
                         formatterParams: {
                             componentType: 'toolbox',
                             name: iconName,
-                            title: titles[iconName],
+                            title: titlesMap[iconName],
                             $vars: ['name', 'title']
                         },
                         position: tooltipModel.get('position', true) || 'bottom'
-                    }, tooltipModel.option));
+                    }, tooltipModel.option);
                 }
 
                 graphic.setHoverStyle(path);
 
                 if (toolboxModel.get('showTitle')) {
-                    path.__title = titles[iconName];
-                    path.on('mouseover', function () {
+                    (path as ExtendedPath).__title = titlesMap[iconName];
+                    (path as graphic.Path).on('mouseover', function () {
                             // Should not reuse above hoverStyle, which might be modified.
                             var hoverStyle = iconStyleEmphasisModel.getItemStyle();
                             var defaultTextPosition = toolboxModel.get('orient') === 'vertical'
                                 ? (toolboxModel.get('right') == null ? 'right' : 'left')
                                 : (toolboxModel.get('bottom') == null ? 'bottom' : 'top');
-                            path.setStyle({
+                            (path as graphic.Path).setStyle({
                                 textFill: iconStyleEmphasisModel.get('textFill')
                                     || hoverStyle.fill || hoverStyle.stroke || '#000',
                                 textBackgroundColor: iconStyleEmphasisModel.get('textBackgroundColor'),
@@ -215,10 +256,10 @@ export default echarts.extendComponentView({
                             });
                         });
                 }
-                path.trigger(featureModel.get('iconStatus.' + iconName) || 'normal');
+                path.trigger(featureModel.get(['iconStatus', iconName]) || 'normal');
 
                 group.add(path);
-                path.on('click', zrUtil.bind(
+                (path as graphic.Path).on('click', zrUtil.bind(
                     feature.onclick, feature, ecModel, api, iconName
                 ));
 
@@ -232,8 +273,8 @@ export default echarts.extendComponentView({
         group.add(listComponentHelper.makeBackground(group.getBoundingRect(), toolboxModel));
 
         // Adjust icon title positions to avoid them out of screen
-        group.eachChild(function (icon) {
-            var titleText = icon.__title;
+        group.eachChild(function (icon: IconPath) {
+            var titleText = (icon as ExtendedPath).__title;
             var hoverStyle = icon.hoverStyle;
             // May be background element
             if (hoverStyle && titleText) {
@@ -259,34 +300,45 @@ export default echarts.extendComponentView({
                 }
             }
         });
-    },
+    }
 
-    updateView: function (toolboxModel, ecModel, api, payload) {
+    updateView(
+        toolboxModel: ToolboxModel,
+        ecModel: GlobalModel,
+        api: ExtensionAPI,
+        payload: unknown
+    ) {
         zrUtil.each(this._features, function (feature) {
-            feature.updateView && feature.updateView(feature.model, ecModel, api, payload);
+            feature instanceof ToolboxFeature
+                && feature.updateView && feature.updateView(feature.model, ecModel, api, payload);
         });
-    },
+    }
 
-    // updateLayout: function (toolboxModel, ecModel, api, payload) {
+    // updateLayout(toolboxModel, ecModel, api, payload) {
     //     zrUtil.each(this._features, function (feature) {
     //         feature.updateLayout && feature.updateLayout(feature.model, ecModel, api, payload);
     //     });
     // },
 
-    remove: function (ecModel, api) {
+    remove(ecModel: GlobalModel, api: ExtensionAPI) {
         zrUtil.each(this._features, function (feature) {
-            feature.remove && feature.remove(ecModel, api);
+            feature instanceof ToolboxFeature
+                && feature.remove && feature.remove(ecModel, api);
         });
         this.group.removeAll();
-    },
+    }
 
-    dispose: function (ecModel, api) {
+    dispose(ecModel: GlobalModel, api: ExtensionAPI) {
         zrUtil.each(this._features, function (feature) {
-            feature.dispose && feature.dispose(ecModel, api);
+            feature instanceof ToolboxFeature
+                && feature.dispose && feature.dispose(ecModel, api);
         });
     }
-});
+}
+
+ComponentView.registerClass(ToolboxView);
+
 
-function isUserFeatureName(featureName) {
+function isUserFeatureName(featureName: string): boolean {
     return featureName.indexOf('my') === 0;
 }
diff --git a/src/component/toolbox/feature/Brush.ts b/src/component/toolbox/feature/Brush.ts
index 3ba43a5..b126384 100644
--- a/src/component/toolbox/feature/Brush.ts
+++ b/src/component/toolbox/feature/Brush.ts
@@ -17,128 +17,140 @@
 * under the License.
 */
 
-// @ts-nocheck
-
 import * as zrUtil from 'zrender/src/core/util';
-import * as featureManager from '../featureManager';
 import lang from '../../../lang';
+import {
+    ToolboxFeatureModel,
+    ToolboxFeatureOption,
+    registerFeature,
+    ToolboxFeature
+} from '../featureManager';
+import GlobalModel from '../../../model/Global';
+import ExtensionAPI from '../../../ExtensionAPI';
 
 var brushLang = lang.toolbox.brush;
 
-function Brush(model, ecModel, api) {
-    this.model = model;
-    this.ecModel = ecModel;
-    this.api = api;
-
-    /**
-     * @private
-     * @type {string}
-     */
-    this._brushType;
-
-    /**
-     * @private
-     * @type {string}
-     */
-    this._brushMode;
+const ICON_TYPES =  ['rect', 'polygon', 'lineX', 'lineY', 'keep', 'clear'] as const;
+
+type IconType = typeof ICON_TYPES[number];
+
+interface ToolboxBrushFeatureOption extends ToolboxFeatureOption {
+    type?: IconType[]
+    icon?: {[key in IconType]?: string}
+    title?: {[key in IconType]?: string}
 }
 
-Brush.defaultOption = {
-    show: true,
-    type: ['rect', 'polygon', 'lineX', 'lineY', 'keep', 'clear'],
-    icon: {
-        /* eslint-disable */
-        rect: 'M7.3,34.7 M0.4,10V-0.2h9.8 M89.6,10V-0.2h-9.8 M0.4,60v10.2h9.8 M89.6,60v10.2h-9.8 M12.3,22.4V10.5h13.1 M33.6,10.5h7.8 M49.1,10.5h7.8 M77.5,22.4V10.5h-13 M12.3,31.1v8.2 M77.7,31.1v8.2 M12.3,47.6v11.9h13.1 M33.6,59.5h7.6 M49.1,59.5 h7.7 M77.5,47.6v11.9h-13', // jshint ignore:line
-        polygon: 'M55.2,34.9c1.7,0,3.1,1.4,3.1,3.1s-1.4,3.1-3.1,3.1 s-3.1-1.4-3.1-3.1S53.5,34.9,55.2,34.9z M50.4,51c1.7,0,3.1,1.4,3.1,3.1c0,1.7-1.4,3.1-3.1,3.1c-1.7,0-3.1-1.4-3.1-3.1 C47.3,52.4,48.7,51,50.4,51z M55.6,37.1l1.5-7.8 M60.1,13.5l1.6-8.7l-7.8,4 M59,19l-1,5.3 M24,16.1l6.4,4.9l6.4-3.3 M48.5,11.6 l-5.9,3.1 M19.1,12.8L9.7,5.1l1.1,7.7 M13.4,29.8l1,7.3l6.6,1.6 M11.6,18.4l1,6.1 M32.8,41.9 M26.6,40.4 M27.3,40.2l6.1,1.6 M49.9,52.1l-5.6-7.6l-4.9-1.2', // jshint ignore:line
-        lineX: 'M15.2,30 M19.7,15.6V1.9H29 M34.8,1.9H40.4 M55.3,15.6V1.9H45.9 M19.7,44.4V58.1H29 M34.8,58.1H40.4 M55.3,44.4 V58.1H45.9 M12.5,20.3l-9.4,9.6l9.6,9.8 M3.1,29.9h16.5 M62.5,20.3l9.4,9.6L62.3,39.7 M71.9,29.9H55.4', // jshint ignore:line
-        lineY: 'M38.8,7.7 M52.7,12h13.2v9 M65.9,26.6V32 M52.7,46.3h13.2v-9 M24.9,12H11.8v9 M11.8,26.6V32 M24.9,46.3H11.8v-9 M48.2,5.1l-9.3-9l-9.4,9.2 M38.9-3.9V12 M48.2,53.3l-9.3,9l-9.4-9.2 M38.9,62.3V46.4', // jshint ignore:line
-        keep: 'M4,10.5V1h10.3 M20.7,1h6.1 M33,1h6.1 M55.4,10.5V1H45.2 M4,17.3v6.6 M55.6,17.3v6.6 M4,30.5V40h10.3 M20.7,40 h6.1 M33,40h6.1 M55.4,30.5V40H45.2 M21,18.9h62.9v48.6H21V18.9z', // jshint ignore:line
-        clear: 'M22,14.7l30.9,31 M52.9,14.7L22,45.7 M4.7,16.8V4.2h13.1 M26,4.2h7.8 M41.6,4.2h7.8 M70.3,16.8V4.2H57.2 M4.7,25.9v8.6 M70.3,25.9v8.6 M4.7,43.2v12.6h13.1 M26,55.8h7.8 M41.6,55.8h7.8 M70.3,43.2v12.6H57.2' // jshint ignore:line
-        /* eslint-enable */
-    },
-    // `rect`, `polygon`, `lineX`, `lineY`, `keep`, `clear`
-    title: zrUtil.clone(brushLang.title)
-};
-
-var proto = Brush.prototype;
-
-// proto.updateLayout = function (featureModel, ecModel, api) {
-/* eslint-disable */
-proto.render =
-/* eslint-enable */
-proto.updateView = function (featureModel, ecModel, api) {
-    var brushType;
-    var brushMode;
-    var isBrushed;
-
-    ecModel.eachComponent({mainType: 'brush'}, function (brushModel) {
-        brushType = brushModel.brushType;
-        brushMode = brushModel.brushOption.brushMode || 'single';
-        isBrushed |= brushModel.areas.length;
-    });
-    this._brushType = brushType;
-    this._brushMode = brushMode;
-
-    zrUtil.each(featureModel.get('type', true), function (type) {
-        featureModel.setIconStatus(
-            type,
-            (
-                type === 'keep'
-                ? brushMode === 'multiple'
-                : type === 'clear'
-                ? isBrushed
-                : type === brushType
-            ) ? 'emphasis' : 'normal'
-        );
-    });
-};
-
-proto.getIcons = function () {
-    var model = this.model;
-    var availableIcons = model.get('icon', true);
-    var icons = {};
-    zrUtil.each(model.get('type', true), function (type) {
-        if (availableIcons[type]) {
-            icons[type] = availableIcons[type];
-        }
-    });
-    return icons;
-};
-
-proto.onclick = function (ecModel, api, type) {
-    var brushType = this._brushType;
-    var brushMode = this._brushMode;
-
-    if (type === 'clear') {
-        // Trigger parallel action firstly
-        api.dispatchAction({
-            type: 'axisAreaSelect',
-            intervals: []
+class BrushFeature extends ToolboxFeature<ToolboxBrushFeatureOption> {
+
+    private _brushType: string
+    private _brushMode: string
+
+    render(
+        featureModel: ToolboxFeatureModel<ToolboxBrushFeatureOption>,
+        ecModel: GlobalModel,
+        api: ExtensionAPI
+    ) {
+        var brushType: string;
+        var brushMode: string;
+        var isBrushed: boolean;
+
+        ecModel.eachComponent({mainType: 'brush'}, function (brushModel) {
+            // @ts-ignore BrushModel
+            brushType = brushModel.brushType;
+            // @ts-ignore BrushModel
+            brushMode = brushModel.brushOption.brushMode || 'single';
+            // @ts-ignore BrushModel
+            isBrushed = isBrushed || brushModel.areas.length;
         });
-
-        api.dispatchAction({
-            type: 'brush',
-            command: 'clear',
-            // Clear all areas of all brush components.
-            areas: []
+        this._brushType = brushType;
+        this._brushMode = brushMode;
+
+        zrUtil.each(featureModel.get('type', true), function (type) {
+            featureModel.setIconStatus(
+                type,
+                (
+                    type === 'keep'
+                    ? brushMode === 'multiple'
+                    : type === 'clear'
+                    ? isBrushed
+                    : type === brushType
+                ) ? 'emphasis' : 'normal'
+            );
         });
     }
-    else {
-        api.dispatchAction({
-            type: 'takeGlobalCursor',
-            key: 'brush',
-            brushOption: {
-                brushType: type === 'keep'
-                    ? brushType
-                    : (brushType === type ? false : type),
-                brushMode: type === 'keep'
-                    ? (brushMode === 'multiple' ? 'single' : 'multiple')
-                    : brushMode
+
+    updateView(
+        featureModel: ToolboxFeatureModel<ToolboxBrushFeatureOption>,
+        ecModel: GlobalModel,
+        api: ExtensionAPI
+    ) {
+        this.render(featureModel, ecModel, api);
+    }
+
+    getIcons() {
+        var model = this.model;
+        var availableIcons = model.get('icon', true);
+        var icons: ToolboxBrushFeatureOption['icon'] = {};
+        zrUtil.each(model.get('type', true), function (type) {
+            if (availableIcons[type]) {
+                icons[type] = availableIcons[type];
             }
         });
+        return icons;
+    };
+
+    onclick(ecModel: GlobalModel, api: ExtensionAPI, type: IconType) {
+        var brushType = this._brushType;
+        var brushMode = this._brushMode;
+
+        if (type === 'clear') {
+            // Trigger parallel action firstly
+            api.dispatchAction({
+                type: 'axisAreaSelect',
+                intervals: []
+            });
+
+            api.dispatchAction({
+                type: 'brush',
+                command: 'clear',
+                // Clear all areas of all brush components.
+                areas: []
+            });
+        }
+        else {
+            api.dispatchAction({
+                type: 'takeGlobalCursor',
+                key: 'brush',
+                brushOption: {
+                    brushType: type === 'keep'
+                        ? brushType
+                        : (brushType === type ? false : type),
+                    brushMode: type === 'keep'
+                        ? (brushMode === 'multiple' ? 'single' : 'multiple')
+                        : brushMode
+                }
+            });
+        }
+    };
+
+    static defaultOption: ToolboxBrushFeatureOption = {
+        show: true,
+        type: ICON_TYPES.slice(),
+        icon: {
+            /* eslint-disable */
+            rect: 'M7.3,34.7 M0.4,10V-0.2h9.8 M89.6,10V-0.2h-9.8 M0.4,60v10.2h9.8 M89.6,60v10.2h-9.8 M12.3,22.4V10.5h13.1 M33.6,10.5h7.8 M49.1,10.5h7.8 M77.5,22.4V10.5h-13 M12.3,31.1v8.2 M77.7,31.1v8.2 M12.3,47.6v11.9h13.1 M33.6,59.5h7.6 M49.1,59.5 h7.7 M77.5,47.6v11.9h-13', // jshint ignore:line
+            polygon: 'M55.2,34.9c1.7,0,3.1,1.4,3.1,3.1s-1.4,3.1-3.1,3.1 s-3.1-1.4-3.1-3.1S53.5,34.9,55.2,34.9z M50.4,51c1.7,0,3.1,1.4,3.1,3.1c0,1.7-1.4,3.1-3.1,3.1c-1.7,0-3.1-1.4-3.1-3.1 C47.3,52.4,48.7,51,50.4,51z M55.6,37.1l1.5-7.8 M60.1,13.5l1.6-8.7l-7.8,4 M59,19l-1,5.3 M24,16.1l6.4,4.9l6.4-3.3 M48.5,11.6 l-5.9,3.1 M19.1,12.8L9.7,5.1l1.1,7.7 M13.4,29.8l1,7.3l6.6,1.6 M11.6,18.4l1,6.1 M32.8,41.9 M26.6,40.4 M27.3,40.2l6.1,1.6 M49.9,52.1l-5.6-7.6l-4.9-1.2', // jshint ignore:line
+            lineX: 'M15.2,30 M19.7,15.6V1.9H29 M34.8,1.9H40.4 M55.3,15.6V1.9H45.9 M19.7,44.4V58.1H29 M34.8,58.1H40.4 M55.3,44.4 V58.1H45.9 M12.5,20.3l-9.4,9.6l9.6,9.8 M3.1,29.9h16.5 M62.5,20.3l9.4,9.6L62.3,39.7 M71.9,29.9H55.4', // jshint ignore:line
+            lineY: 'M38.8,7.7 M52.7,12h13.2v9 M65.9,26.6V32 M52.7,46.3h13.2v-9 M24.9,12H11.8v9 M11.8,26.6V32 M24.9,46.3H11.8v-9 M48.2,5.1l-9.3-9l-9.4,9.2 M38.9-3.9V12 M48.2,53.3l-9.3,9l-9.4-9.2 M38.9,62.3V46.4', // jshint ignore:line
+            keep: 'M4,10.5V1h10.3 M20.7,1h6.1 M33,1h6.1 M55.4,10.5V1H45.2 M4,17.3v6.6 M55.6,17.3v6.6 M4,30.5V40h10.3 M20.7,40 h6.1 M33,40h6.1 M55.4,30.5V40H45.2 M21,18.9h62.9v48.6H21V18.9z', // jshint ignore:line
+            clear: 'M22,14.7l30.9,31 M52.9,14.7L22,45.7 M4.7,16.8V4.2h13.1 M26,4.2h7.8 M41.6,4.2h7.8 M70.3,16.8V4.2H57.2 M4.7,25.9v8.6 M70.3,25.9v8.6 M4.7,43.2v12.6h13.1 M26,55.8h7.8 M41.6,55.8h7.8 M70.3,43.2v12.6H57.2' // jshint ignore:line
+            /* eslint-enable */
+        },
+        // `rect`, `polygon`, `lineX`, `lineY`, `keep`, `clear`
+        title: zrUtil.clone(brushLang.title)
     }
-};
+}
 
-featureManager.register('brush', Brush);
+registerFeature('brush', BrushFeature);
 
-export default Brush;
\ No newline at end of file
+export default BrushFeature;
\ No newline at end of file
diff --git a/src/component/toolbox/feature/DataView.ts b/src/component/toolbox/feature/DataView.ts
index e9ef708..2e4f4bc 100644
--- a/src/component/toolbox/feature/DataView.ts
+++ b/src/component/toolbox/feature/DataView.ts
@@ -17,36 +17,63 @@
 * under the License.
 */
 
-// @ts-nocheck
-
 import * as echarts from '../../../echarts';
 import * as zrUtil from 'zrender/src/core/util';
-import * as eventTool from 'zrender/src/core/event';
 import lang from '../../../lang';
-import * as featureManager from '../featureManager';
+import GlobalModel from '../../../model/Global';
+import SeriesModel from '../../../model/Series';
+import { ToolboxFeature, registerFeature, ToolboxFeatureOption } from '../featureManager';
+import { ColorString, ECUnitOption, SeriesOption, Payload, Dictionary } from '../../../util/types';
+import ExtensionAPI from '../../../ExtensionAPI';
+import { addEventListener } from 'zrender/src/core/event';
+import Axis from '../../../coord/Axis';
 
 var dataViewLang = lang.toolbox.dataView;
 
 var BLOCK_SPLITER = new Array(60).join('-');
 var ITEM_SPLITER = '\t';
+
+type DataItem = {
+    name: string
+    value: number[] | number
+}
+
+type DataList = (DataItem | number | number[])[];
+
+interface ChangeDataViewPayload extends Payload {
+    newOption: {
+        series: SeriesOption[]
+    }
+}
+
+interface SeriesGroupMeta {
+    axisDim: string
+    axisIndex: number
+}
+
+interface SeriesGroup {
+    series: SeriesModel[]
+    categoryAxis: Axis
+    valueAxis: Axis
+}
+
 /**
  * Group series into two types
  *  1. on category axis, like line, bar
  *  2. others, like scatter, pie
- * @param {module:echarts/model/Global} ecModel
- * @return {Object}
- * @inner
  */
-function groupSeries(ecModel) {
-    var seriesGroupByCategoryAxis = {};
-    var otherSeries = [];
-    var meta = [];
+function groupSeries(ecModel: GlobalModel) {
+    var seriesGroupByCategoryAxis: Dictionary<SeriesGroup> = {};
+    var otherSeries: SeriesModel[] = [];
+    var meta: SeriesGroupMeta[] = [];
     ecModel.eachRawSeries(function (seriesModel) {
         var coordSys = seriesModel.coordinateSystem;
 
         if (coordSys && (coordSys.type === 'cartesian2d' || coordSys.type === 'polar')) {
             var baseAxis = coordSys.getBaseAxis();
+            // @ts-ignore TODO Polar
             if (baseAxis.type === 'category') {
+                // @ts-ignore TODO Polar
                 var key = baseAxis.dim + '_' + baseAxis.index;
                 if (!seriesGroupByCategoryAxis[key]) {
                     seriesGroupByCategoryAxis[key] = {
@@ -56,6 +83,7 @@ function groupSeries(ecModel) {
                     };
                     meta.push({
                         axisDim: baseAxis.dim,
+                        // @ts-ignore TODO Polar
                         axisIndex: baseAxis.index
                     });
                 }
@@ -79,13 +107,11 @@ function groupSeries(ecModel) {
 
 /**
  * Assemble content of series on cateogory axis
- * @param {Array.<module:echarts/model/Series>} series
- * @return {string}
  * @inner
  */
-function assembleSeriesWithCategoryAxis(series) {
-    var tables = [];
-    zrUtil.each(series, function (group, key) {
+function assembleSeriesWithCategoryAxis(groups: Dictionary<SeriesGroup>): string {
+    var tables: string[] = [];
+    zrUtil.each(groups, function (group, key) {
         var categoryAxis = group.categoryAxis;
         var valueAxis = group.valueAxis;
         var valueAxisDim = valueAxis.dim;
@@ -93,6 +119,7 @@ function assembleSeriesWithCategoryAxis(series) {
         var headers = [' '].concat(zrUtil.map(group.series, function (series) {
             return series.name;
         }));
+        // @ts-ignore TODO Polar
         var columns = [categoryAxis.model.getCategories()];
         zrUtil.each(group.series, function (series) {
             columns.push(series.getRawData().mapArray(valueAxisDim, function (val) {
@@ -115,15 +142,12 @@ function assembleSeriesWithCategoryAxis(series) {
 
 /**
  * Assemble content of other series
- * @param {Array.<module:echarts/model/Series>} series
- * @return {string}
- * @inner
  */
-function assembleOtherSeries(series) {
+function assembleOtherSeries(series: SeriesModel[]) {
     return zrUtil.map(series, function (series) {
         var data = series.getRawData();
         var lines = [series.name];
-        var vals = [];
+        var vals: string[] = [];
         data.each(data.dimensions, function () {
             var argLen = arguments.length;
             var dataIndex = arguments[argLen - 1];
@@ -137,12 +161,7 @@ function assembleOtherSeries(series) {
     }).join('\n\n' + BLOCK_SPLITER + '\n\n');
 }
 
-/**
- * @param {module:echarts/model/Global}
- * @return {Object}
- * @inner
- */
-function getContentFromModel(ecModel) {
+function getContentFromModel(ecModel: GlobalModel) {
 
     var result = groupSeries(ecModel);
 
@@ -151,7 +170,7 @@ function getContentFromModel(ecModel) {
                 assembleSeriesWithCategoryAxis(result.seriesGroupByCategoryAxis),
                 assembleOtherSeries(result.other)
             ], function (str) {
-                return str.replace(/[\n\t\s]/g, '');
+                return !!str.replace(/[\n\t\s]/g, '');
             }).join('\n\n' + BLOCK_SPLITER + '\n\n'),
 
         meta: result.meta
@@ -159,13 +178,13 @@ function getContentFromModel(ecModel) {
 }
 
 
-function trim(str) {
+function trim(str: string) {
     return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
 }
 /**
  * If a block is tsv format
  */
-function isTSVFormat(block) {
+function isTSVFormat(block: string): boolean {
     // Simple method to find out if a block is tsv format
     var firstLine = block.slice(0, block.indexOf('\n'));
     if (firstLine.indexOf(ITEM_SPLITER) >= 0) {
@@ -178,12 +197,12 @@ var itemSplitRegex = new RegExp('[' + ITEM_SPLITER + ']+', 'g');
  * @param {string} tsv
  * @return {Object}
  */
-function parseTSVContents(tsv) {
+function parseTSVContents(tsv: string) {
     var tsvLines = tsv.split(/\n+/g);
     var headers = trim(tsvLines.shift()).split(itemSplitRegex);
 
-    var categories = [];
-    var series = zrUtil.map(headers, function (header) {
+    var categories: string[] = [];
+    var series: {name: string, data: string[]}[] = zrUtil.map(headers, function (header) {
         return {
             name: header,
             data: []
@@ -202,22 +221,17 @@ function parseTSVContents(tsv) {
     };
 }
 
-/**
- * @param {string} str
- * @return {Array.<Object>}
- * @inner
- */
-function parseListContents(str) {
+function parseListContents(str: string) {
     var lines = str.split(/\n+/g);
     var seriesName = trim(lines.shift());
 
-    var data = [];
+    var data: DataList = [];
     for (var i = 0; i < lines.length; i++) {
         var items = trim(lines[i]).split(itemSplitRegex);
         var name = '';
-        var value;
+        var value: number[];
         var hasName = false;
-        if (isNaN(items[0])) { // First item is name
+        if (isNaN(items[0] as unknown as number)) { // First item is name
             hasName = true;
             name = items[0];
             items = items.slice(1);
@@ -225,7 +239,7 @@ function parseListContents(str) {
                 name: name,
                 value: []
             };
-            value = data[i].value;
+            value = (data[i] as DataItem).value as number[];
         }
         else {
             value = data[i] = [];
@@ -234,7 +248,7 @@ function parseListContents(str) {
             value.push(+items[j]);
         }
         if (value.length === 1) {
-            hasName ? (data[i].value = value[0]) : (data[i] = value[0]);
+            hasName ? ((data[i] as DataItem).value = value[0]) : (data[i] = value[0]);
         }
     }
 
@@ -244,22 +258,16 @@ function parseListContents(str) {
     };
 }
 
-/**
- * @param {string} str
- * @param {Array.<Object>} blockMetaList
- * @return {Object}
- * @inner
- */
-function parseContents(str, blockMetaList) {
+function parseContents(str: string, blockMetaList: SeriesGroupMeta[]) {
     var blocks = str.split(new RegExp('\n*' + BLOCK_SPLITER + '\n*', 'g'));
-    var newOption = {
+    var newOption: ECUnitOption = {
         series: []
     };
     zrUtil.each(blocks, function (block, idx) {
         if (isTSVFormat(block)) {
-            var result = parseTSVContents(block);
-            var blockMeta = blockMetaList[idx];
-            var axisKey = blockMeta.axisDim + 'Axis';
+            const result = parseTSVContents(block);
+            const blockMeta = blockMetaList[idx];
+            const axisKey = blockMeta.axisDim + 'Axis';
 
             if (blockMeta) {
                 newOption[axisKey] = newOption[axisKey] || [];
@@ -270,161 +278,174 @@ function parseContents(str, blockMetaList) {
             }
         }
         else {
-            var result = parseListContents(block);
+            const result = parseListContents(block);
             newOption.series.push(result);
         }
     });
     return newOption;
 }
 
-/**
- * @alias {module:echarts/component/toolbox/feature/DataView}
- * @constructor
- * @param {module:echarts/model/Model} model
- */
-function DataView(model) {
+interface ToolboxDataViewFeatureOption extends ToolboxFeatureOption {
+    readOnly?: boolean
+
+    optionToContent?: (option: ECUnitOption) => string | HTMLElement
+    contentToOption?: (viewMain: HTMLDivElement, oldOption: ECUnitOption) => ECUnitOption
 
-    this._dom = null;
+    icon?: string
+    title?: string
+    lang?: string[]
 
-    this.model = model;
+    backgroundColor?: ColorString
+
+    textColor?: ColorString
+    textareaColor?: ColorString
+    textareaBorderColor?: ColorString
+
+    buttonColor?: ColorString
+    buttonTextColor?: ColorString
 }
 
-DataView.defaultOption = {
-    show: true,
-    readOnly: false,
-    optionToContent: null,
-    contentToOption: null,
-
-    icon: 'M17.5,17.3H33 M17.5,17.3H33 M45.4,29.5h-28 M11.5,2v56H51V14.8L38.4,2H11.5z M38.4,2.2v12.7H51 M45.4,41.7h-28',
-    title: zrUtil.clone(dataViewLang.title),
-    lang: zrUtil.clone(dataViewLang.lang),
-    backgroundColor: '#fff',
-    textColor: '#000',
-    textareaColor: '#fff',
-    textareaBorderColor: '#333',
-    buttonColor: '#c23531',
-    buttonTextColor: '#fff'
-};
-
-DataView.prototype.onclick = function (ecModel, api) {
-    var container = api.getDom();
-    var model = this.model;
-    if (this._dom) {
-        container.removeChild(this._dom);
-    }
-    var root = document.createElement('div');
-    root.style.cssText = 'position:absolute;left:5px;top:5px;bottom:5px;right:5px;';
-    root.style.backgroundColor = model.get('backgroundColor') || '#fff';
-
-    // Create elements
-    var header = document.createElement('h4');
-    var lang = model.get('lang') || [];
-    header.innerHTML = lang[0] || model.get('title');
-    header.style.cssText = 'margin: 10px 20px;';
-    header.style.color = model.get('textColor');
-
-    var viewMain = document.createElement('div');
-    var textarea = document.createElement('textarea');
-    viewMain.style.cssText = 'display:block;width:100%;overflow:auto;';
-
-    var optionToContent = model.get('optionToContent');
-    var contentToOption = model.get('contentToOption');
-    var result = getContentFromModel(ecModel);
-    if (typeof optionToContent === 'function') {
-        var htmlOrDom = optionToContent(api.getOption());
-        if (typeof htmlOrDom === 'string') {
-            viewMain.innerHTML = htmlOrDom;
+class DataView extends ToolboxFeature<ToolboxDataViewFeatureOption> {
+
+    private _dom: HTMLDivElement
+
+    onclick(ecModel: GlobalModel, api: ExtensionAPI) {
+        var container = api.getDom();
+        var model = this.model;
+        if (this._dom) {
+            container.removeChild(this._dom);
         }
-        else if (zrUtil.isDom(htmlOrDom)) {
-            viewMain.appendChild(htmlOrDom);
+        var root = document.createElement('div');
+        root.style.cssText = 'position:absolute;left:5px;top:5px;bottom:5px;right:5px;';
+        root.style.backgroundColor = model.get('backgroundColor') || '#fff';
+
+        // Create elements
+        var header = document.createElement('h4');
+        var lang = model.get('lang') || [];
+        header.innerHTML = lang[0] || model.get('title');
+        header.style.cssText = 'margin: 10px 20px;';
+        header.style.color = model.get('textColor');
+
+        var viewMain = document.createElement('div');
+        var textarea = document.createElement('textarea');
+        viewMain.style.cssText = 'display:block;width:100%;overflow:auto;';
+
+        var optionToContent = model.get('optionToContent');
+        var contentToOption = model.get('contentToOption');
+        var result = getContentFromModel(ecModel);
+        if (typeof optionToContent === 'function') {
+            var htmlOrDom = optionToContent(api.getOption());
+            if (typeof htmlOrDom === 'string') {
+                viewMain.innerHTML = htmlOrDom;
+            }
+            else if (zrUtil.isDom(htmlOrDom)) {
+                viewMain.appendChild(htmlOrDom);
+            }
+        }
+        else {
+            // Use default textarea
+            viewMain.appendChild(textarea);
+            textarea.readOnly = model.get('readOnly');
+            textarea.style.cssText = 'width:100%;height:100%;font-family:monospace;font-size:14px;line-height:1.6rem;';
+            textarea.style.color = model.get('textColor');
+            textarea.style.borderColor = model.get('textareaBorderColor');
+            textarea.style.backgroundColor = model.get('textareaColor');
+            textarea.value = result.value;
         }
-    }
-    else {
-        // Use default textarea
-        viewMain.appendChild(textarea);
-        textarea.readOnly = model.get('readOnly');
-        textarea.style.cssText = 'width:100%;height:100%;font-family:monospace;font-size:14px;line-height:1.6rem;';
-        textarea.style.color = model.get('textColor');
-        textarea.style.borderColor = model.get('textareaBorderColor');
-        textarea.style.backgroundColor = model.get('textareaColor');
-        textarea.value = result.value;
-    }
 
-    var blockMetaList = result.meta;
+        var blockMetaList = result.meta;
 
-    var buttonContainer = document.createElement('div');
-    buttonContainer.style.cssText = 'position:absolute;bottom:0;left:0;right:0;';
+        var buttonContainer = document.createElement('div');
+        buttonContainer.style.cssText = 'position:absolute;bottom:0;left:0;right:0;';
 
-    var buttonStyle = 'float:right;margin-right:20px;border:none;'
-        + 'cursor:pointer;padding:2px 5px;font-size:12px;border-radius:3px';
-    var closeButton = document.createElement('div');
-    var refreshButton = document.createElement('div');
+        var buttonStyle = 'float:right;margin-right:20px;border:none;'
+            + 'cursor:pointer;padding:2px 5px;font-size:12px;border-radius:3px';
+        var closeButton = document.createElement('div');
+        var refreshButton = document.createElement('div');
 
-    buttonStyle += ';background-color:' + model.get('buttonColor');
-    buttonStyle += ';color:' + model.get('buttonTextColor');
+        buttonStyle += ';background-color:' + model.get('buttonColor');
+        buttonStyle += ';color:' + model.get('buttonTextColor');
 
-    var self = this;
+        var self = this;
 
-    function close() {
-        container.removeChild(root);
-        self._dom = null;
-    }
-    eventTool.addEventListener(closeButton, 'click', close);
+        function close() {
+            container.removeChild(root);
+            self._dom = null;
+        }
+        addEventListener(closeButton, 'click', close);
 
-    eventTool.addEventListener(refreshButton, 'click', function () {
-        var newOption;
-        try {
-            if (typeof contentToOption === 'function') {
-                newOption = contentToOption(viewMain, api.getOption());
+        addEventListener(refreshButton, 'click', function () {
+            var newOption;
+            try {
+                if (typeof contentToOption === 'function') {
+                    newOption = contentToOption(viewMain, api.getOption());
+                }
+                else {
+                    newOption = parseContents(textarea.value, blockMetaList);
+                }
             }
-            else {
-                newOption = parseContents(textarea.value, blockMetaList);
+            catch (e) {
+                close();
+                throw new Error('Data view format error ' + e);
             }
-        }
-        catch (e) {
+            if (newOption) {
+                api.dispatchAction({
+                    type: 'changeDataView',
+                    newOption: newOption
+                });
+            }
+
             close();
-            throw new Error('Data view format error ' + e);
-        }
-        if (newOption) {
-            api.dispatchAction({
-                type: 'changeDataView',
-                newOption: newOption
-            });
-        }
+        });
 
-        close();
-    });
+        closeButton.innerHTML = lang[1];
+        refreshButton.innerHTML = lang[2];
+        refreshButton.style.cssText = buttonStyle;
+        closeButton.style.cssText = buttonStyle;
 
-    closeButton.innerHTML = lang[1];
-    refreshButton.innerHTML = lang[2];
-    refreshButton.style.cssText = buttonStyle;
-    closeButton.style.cssText = buttonStyle;
+        !model.get('readOnly') && buttonContainer.appendChild(refreshButton);
+        buttonContainer.appendChild(closeButton);
 
-    !model.get('readOnly') && buttonContainer.appendChild(refreshButton);
-    buttonContainer.appendChild(closeButton);
+        root.appendChild(header);
+        root.appendChild(viewMain);
+        root.appendChild(buttonContainer);
 
-    root.appendChild(header);
-    root.appendChild(viewMain);
-    root.appendChild(buttonContainer);
+        viewMain.style.height = (container.clientHeight - 80) + 'px';
 
-    viewMain.style.height = (container.clientHeight - 80) + 'px';
+        container.appendChild(root);
+        this._dom = root;
+    }
 
-    container.appendChild(root);
-    this._dom = root;
-};
+    remove(ecModel: GlobalModel, api: ExtensionAPI) {
+        this._dom && api.getDom().removeChild(this._dom);
+    }
 
-DataView.prototype.remove = function (ecModel, api) {
-    this._dom && api.getDom().removeChild(this._dom);
-};
+    dispose(ecModel: GlobalModel, api: ExtensionAPI) {
+        this.remove(ecModel, api);
+    }
 
-DataView.prototype.dispose = function (ecModel, api) {
-    this.remove(ecModel, api);
-};
+    static defaultOption: ToolboxDataViewFeatureOption = {
+        show: true,
+        readOnly: false,
+        optionToContent: null,
+        contentToOption: null,
+
+        icon: 'M17.5,17.3H33 M17.5,17.3H33 M45.4,29.5h-28 M11.5,2v56H51V14.8L38.4,2H11.5z M38.4,2.2v12.7H51 M45.4,41.7h-28',
+        title: zrUtil.clone(dataViewLang.title),
+        lang: zrUtil.clone(dataViewLang.lang),
+        backgroundColor: '#fff',
+        textColor: '#000',
+        textareaColor: '#fff',
+        textareaBorderColor: '#333',
+        buttonColor: '#c23531',
+        buttonTextColor: '#fff'
+    }
+}
 
 /**
  * @inner
  */
-function tryMergeDataOption(newData, originalData) {
+function tryMergeDataOption(newData: DataList, originalData: DataList) {
     return zrUtil.map(newData, function (newVal, idx) {
         var original = originalData && originalData[idx];
         if (zrUtil.isObject(original) && !zrUtil.isArray(original)) {
@@ -442,14 +463,14 @@ function tryMergeDataOption(newData, originalData) {
     });
 }
 
-featureManager.register('dataView', DataView);
+registerFeature('dataView', DataView);
 
 echarts.registerAction({
     type: 'changeDataView',
     event: 'dataViewChanged',
     update: 'prepareAndUpdate'
-}, function (payload, ecModel) {
-    var newSeriesOptList = [];
+}, function (payload: ChangeDataViewPayload, ecModel: GlobalModel) {
+    var newSeriesOptList: SeriesOption[] = [];
     zrUtil.each(payload.newOption.series, function (seriesOpt) {
         var seriesModel = ecModel.getSeriesByName(seriesOpt.name)[0];
         if (!seriesModel) {
diff --git a/src/component/toolbox/feature/DataZoom.ts b/src/component/toolbox/feature/DataZoom.ts
index b1f0741..e68e9f7 100644
--- a/src/component/toolbox/feature/DataZoom.ts
+++ b/src/component/toolbox/feature/DataZoom.ts
@@ -19,6 +19,7 @@
 
 // @ts-nocheck
 
+// TODO depends on DataZoom and Brush
 import * as echarts from '../../../echarts';
 import * as zrUtil from 'zrender/src/core/util';
 import BrushController from '../../helper/BrushController';
@@ -26,10 +27,16 @@ import BrushTargetManager from '../../helper/BrushTargetManager';
 import * as history from '../../dataZoom/history';
 import sliderMove from '../../helper/sliderMove';
 import lang from '../../../lang';
-import * as featureManager from '../featureManager';
-
 // Use dataZoomSelect
 import '../../dataZoomSelect';
+import {
+    ToolboxFeature,
+    ToolboxFeatureModel,
+    ToolboxFeatureOption,
+    registerFeature
+} from '../featureManager';
+import GlobalModel from '../../../model/Global';
+import ExtensionAPI from '../../../ExtensionAPI';
 
 var dataZoomLang = lang.toolbox.dataZoom;
 var each = zrUtil.each;
@@ -37,161 +44,171 @@ var each = zrUtil.each;
 // Spectial component id start with \0ec\0, see echarts/model/Global.js~hasInnerId
 var DATA_ZOOM_ID_BASE = '\0_ec_\0toolbox-dataZoom_';
 
-function DataZoom(model, ecModel, api) {
-
-    /**
-     * @private
-     * @type {module:echarts/component/helper/BrushController}
-     */
-    (this._brushController = new BrushController(api.getZr()))
-        .on('brush', zrUtil.bind(this._onBrush, this))
-        .mount();
-
-    /**
-     * @private
-     * @type {boolean}
-     */
-    this._isZoomActive;
+const ICON_TYPES = ['zoom', 'back'] as const;
+type IconType = typeof ICON_TYPES[number];
+
+interface ToolboxDataZoomFeatureOption extends ToolboxFeatureOption {
+    type?: IconType[]
+    icon?: {[key in IconType]?: string}
+    title?: {[key in IconType]?: string}
+    // TODO: TYPE Use type in dataZoom
+    filterMode?: 'filter' | 'weakFilter' | 'empty' | 'none'
 }
 
-DataZoom.defaultOption = {
-    show: true,
-    filterMode: 'filter',
-    // Icon group
-    icon: {
-        zoom: 'M0,13.5h26.9 M13.5,26.9V0 M32.1,13.5H58V58H13.5 V32.1',
-        back: 'M22,1.4L9.9,13.5l12.3,12.3 M10.3,13.5H54.9v44.6 H10.3v-26'
-    },
-    // `zoom`, `back`
-    title: zrUtil.clone(dataZoomLang.title)
-};
+type ToolboxDataZoomFeatureModel = ToolboxFeatureModel<ToolboxDataZoomFeatureOption>
 
-var proto = DataZoom.prototype;
+class DataZoomFeature extends ToolboxFeature<ToolboxDataZoomFeatureOption> {
 
-proto.render = function (featureModel, ecModel, api, payload) {
-    this.model = featureModel;
-    this.ecModel = ecModel;
-    this.api = api;
+    brushController: BrushController
 
-    updateZoomBtnStatus(featureModel, ecModel, this, payload, api);
-    updateBackBtnStatus(featureModel, ecModel);
-};
+    isZoomActive: boolean
 
-proto.onclick = function (ecModel, api, type) {
-    handlers[type].call(this);
-};
+    render(
+        featureModel: ToolboxDataZoomFeatureModel,
+        ecModel: GlobalModel,
+        api: ExtensionAPI,
+        payload
+    ) {
+        if (!this.brushController) {
+            this.brushController = new BrushController(api.getZr());
+            this.brushController.on('brush', zrUtil.bind(this._onBrush, this))
+                .mount();
+        }
+        updateZoomBtnStatus(featureModel, ecModel, this, payload, api);
+        updateBackBtnStatus(featureModel, ecModel);
+    }
 
-proto.remove = function (ecModel, api) {
-    this._brushController.unmount();
-};
+    onclick(
+        ecModel: GlobalModel,
+        api: ExtensionAPI,
+        type: IconType
+    ) {
+        handlers[type].call(this);
+    }
 
-proto.dispose = function (ecModel, api) {
-    this._brushController.dispose();
-};
+    remove(
+        ecModel: GlobalModel,
+        api: ExtensionAPI
+    ) {
+        this.brushController.unmount();
+    }
 
-/**
- * @private
- */
-var handlers = {
+    dispose(
+        ecModel: GlobalModel,
+        api: ExtensionAPI
+    ) {
+        this.brushController.dispose();
+    }
 
-    zoom: function () {
-        var nextActive = !this._isZoomActive;
+    private _onBrush(areas, opt) {
+        if (!opt.isEnd || !areas.length) {
+            return;
+        }
+        var snapshot = {};
+        var ecModel = this.ecModel;
 
-        this.api.dispatchAction({
-            type: 'takeGlobalCursor',
-            key: 'dataZoomSelect',
-            dataZoomSelectActive: nextActive
+        this.brushController.updateCovers([]); // remove cover
+
+        var brushTargetManager = new BrushTargetManager(
+            retrieveAxisSetting(this.model.option), ecModel, {include: ['grid']}
+        );
+        brushTargetManager.matchOutputRanges(areas, ecModel, function (area, coordRange, coordSys) {
+            if (coordSys.type !== 'cartesian2d') {
+                return;
+            }
+
+            var brushType = area.brushType;
+            if (brushType === 'rect') {
+                setBatch('x', coordSys, coordRange[0]);
+                setBatch('y', coordSys, coordRange[1]);
+            }
+            else {
+                setBatch(({
+                    lineX: 'x', lineY: 'y'
+                })[brushType], coordSys, coordRange);
+            }
         });
-    },
 
-    back: function () {
-        this._dispatchZoomAction(history.pop(this.ecModel));
-    }
-};
+        history.push(ecModel, snapshot);
 
-/**
- * @private
- */
-proto._onBrush = function (areas, opt) {
-    if (!opt.isEnd || !areas.length) {
-        return;
-    }
-    var snapshot = {};
-    var ecModel = this.ecModel;
+        this._dispatchZoomAction(snapshot);
 
-    this._brushController.updateCovers([]); // remove cover
+        function setBatch(dimName: string, coordSys, minMax: number[]) {
+            var axis = coordSys.getAxis(dimName);
+            var axisModel = axis.model;
+            var dataZoomModel = findDataZoom(dimName, axisModel, ecModel);
 
-    var brushTargetManager = new BrushTargetManager(
-        retrieveAxisSetting(this.model.option), ecModel, {include: ['grid']}
-    );
-    brushTargetManager.matchOutputRanges(areas, ecModel, function (area, coordRange, coordSys) {
-        if (coordSys.type !== 'cartesian2d') {
-            return;
-        }
+            // Restrict range.
+            var minMaxSpan = dataZoomModel.findRepresentativeAxisProxy(axisModel).getMinMaxSpan();
+            if (minMaxSpan.minValueSpan != null || minMaxSpan.maxValueSpan != null) {
+                minMax = sliderMove(
+                    0, minMax.slice(), axis.scale.getExtent(), 0,
+                    minMaxSpan.minValueSpan, minMaxSpan.maxValueSpan
+                );
+            }
 
-        var brushType = area.brushType;
-        if (brushType === 'rect') {
-            setBatch('x', coordSys, coordRange[0]);
-            setBatch('y', coordSys, coordRange[1]);
+            dataZoomModel && (snapshot[dataZoomModel.id] = {
+                dataZoomId: dataZoomModel.id,
+                startValue: minMax[0],
+                endValue: minMax[1]
+            });
         }
-        else {
-            setBatch(({lineX: 'x', lineY: 'y'})[brushType], coordSys, coordRange);
-        }
-    });
 
-    history.push(ecModel, snapshot);
-
-    this._dispatchZoomAction(snapshot);
+        function findDataZoom(dimName: string, axisModel, ecModel: GlobalModel) {
+            var found;
+            ecModel.eachComponent({mainType: 'dataZoom', subType: 'select'}, function (dzModel) {
+                var has = dzModel.getAxisModel(dimName, axisModel.componentIndex);
+                has && (found = dzModel);
+            });
+            return found;
+        }
+    };
 
-    function setBatch(dimName, coordSys, minMax) {
-        var axis = coordSys.getAxis(dimName);
-        var axisModel = axis.model;
-        var dataZoomModel = findDataZoom(dimName, axisModel, ecModel);
+    dispatchZoomAction(snapshot) {
+        var batch = [];
 
-        // Restrict range.
-        var minMaxSpan = dataZoomModel.findRepresentativeAxisProxy(axisModel).getMinMaxSpan();
-        if (minMaxSpan.minValueSpan != null || minMaxSpan.maxValueSpan != null) {
-            minMax = sliderMove(
-                0, minMax.slice(), axis.scale.getExtent(), 0,
-                minMaxSpan.minValueSpan, minMaxSpan.maxValueSpan
-            );
-        }
+        // Convert from hash map to array.
+        each(snapshot, function (batchItem, dataZoomId) {
+            batch.push(zrUtil.clone(batchItem));
+        });
 
-        dataZoomModel && (snapshot[dataZoomModel.id] = {
-            dataZoomId: dataZoomModel.id,
-            startValue: minMax[0],
-            endValue: minMax[1]
+        batch.length && this.api.dispatchAction({
+            type: 'dataZoom',
+            from: this.uid,
+            batch: batch
         });
     }
 
-    function findDataZoom(dimName, axisModel, ecModel) {
-        var found;
-        ecModel.eachComponent({mainType: 'dataZoom', subType: 'select'}, function (dzModel) {
-            var has = dzModel.getAxisModel(dimName, axisModel.componentIndex);
-            has && (found = dzModel);
-        });
-        return found;
+    static defaultOption: ToolboxDataZoomFeatureOption = {
+        show: true,
+        filterMode: 'filter',
+        // Icon group
+        icon: {
+            zoom: 'M0,13.5h26.9 M13.5,26.9V0 M32.1,13.5H58V58H13.5 V32.1',
+            back: 'M22,1.4L9.9,13.5l12.3,12.3 M10.3,13.5H54.9v44.6 H10.3v-26'
+        },
+        // `zoom`, `back`
+        title: zrUtil.clone(dataZoomLang.title)
     }
-};
+}
 
-/**
- * @private
- */
-proto._dispatchZoomAction = function (snapshot) {
-    var batch = [];
+const handlers: { [key in IconType]: (this: DataZoomFeature) => void } = {
+    zoom: function () {
+        var nextActive = !this.isZoomActive;
 
-    // Convert from hash map to array.
-    each(snapshot, function (batchItem, dataZoomId) {
-        batch.push(zrUtil.clone(batchItem));
-    });
+        this.api.dispatchAction({
+            type: 'takeGlobalCursor',
+            key: 'dataZoomSelect',
+            dataZoomSelectActive: nextActive
+        });
+    },
 
-    batch.length && this.api.dispatchAction({
-        type: 'dataZoom',
-        from: this.uid,
-        batch: batch
-    });
+    back: function () {
+        this.dispatchZoomAction(history.pop(this.ecModel));
+    }
 };
 
+
 function retrieveAxisSetting(option) {
     var setting = {};
     // Compatible with previous setting: null => all axis, false => no axis.
@@ -203,22 +220,31 @@ function retrieveAxisSetting(option) {
     return setting;
 }
 
-function updateBackBtnStatus(featureModel, ecModel) {
+function updateBackBtnStatus(
+    featureModel: ToolboxDataZoomFeatureModel,
+    ecModel: GlobalModel
+) {
     featureModel.setIconStatus(
         'back',
         history.count(ecModel) > 1 ? 'emphasis' : 'normal'
     );
 }
 
-function updateZoomBtnStatus(featureModel, ecModel, view, payload, api) {
-    var zoomActive = view._isZoomActive;
+function updateZoomBtnStatus(
+    featureModel: ToolboxDataZoomFeatureModel,
+    ecModel: GlobalModel,
+    view: DataZoomFeature,
+    payload,
+    api: ExtensionAPI
+) {
+    var zoomActive = view.isZoomActive;
 
     if (payload && payload.type === 'takeGlobalCursor') {
         zoomActive = payload.key === 'dataZoomSelect'
             ? payload.dataZoomSelectActive : false;
     }
 
-    view._isZoomActive = zoomActive;
+    view.isZoomActive = zoomActive;
 
     featureModel.setIconStatus('zoom', zoomActive ? 'emphasis' : 'normal');
 
@@ -226,7 +252,7 @@ function updateZoomBtnStatus(featureModel, ecModel, view, payload, api) {
         retrieveAxisSetting(featureModel.option), ecModel, {include: ['grid']}
     );
 
-    view._brushController
+    view.brushController
         .setPanels(brushTargetManager.makePanelOpts(api, function (targetInfo) {
             return (targetInfo.xAxisDeclared && !targetInfo.yAxisDeclared)
                 ? 'lineX'
@@ -249,7 +275,7 @@ function updateZoomBtnStatus(featureModel, ecModel, view, payload, api) {
 }
 
 
-featureManager.register('dataZoom', DataZoom);
+registerFeature('dataZoom', DataZoomFeature);
 
 
 // Create special dataZoom option for select
@@ -280,7 +306,7 @@ echarts.registerPreprocessor(function (option) {
         }
     }
 
-    function addForAxis(axisName, dataZoomOpt) {
+    function addForAxis(axisName: string, dataZoomOpt) {
         if (!dataZoomOpt) {
             return;
         }
@@ -317,7 +343,7 @@ echarts.registerPreprocessor(function (option) {
         });
     }
 
-    function forEachComponent(mainType, cb) {
+    function forEachComponent(mainType: string, cb) {
         var opts = option[mainType];
         if (!zrUtil.isArray(opts)) {
             opts = opts ? [opts] : [];
@@ -326,4 +352,4 @@ echarts.registerPreprocessor(function (option) {
     }
 });
 
-export default DataZoom;
\ No newline at end of file
+export default DataZoomFeature;
\ No newline at end of file
diff --git a/src/component/toolbox/feature/MagicType.ts b/src/component/toolbox/feature/MagicType.ts
index ee552e1..8740f70 100644
--- a/src/component/toolbox/feature/MagicType.ts
+++ b/src/component/toolbox/feature/MagicType.ts
@@ -17,52 +17,175 @@
 * under the License.
 */
 
-// @ts-nocheck
-
 import * as echarts from '../../../echarts';
 import * as zrUtil from 'zrender/src/core/util';
 import lang from '../../../lang';
-import * as featureManager from '../featureManager';
+import {ToolboxFeature, ToolboxFeatureOption, ToolboxFeatureModel, registerFeature} from '../featureManager';
+import { SeriesOption, ECUnitOption } from '../../../util/types';
+import GlobalModel from '../../../model/Global';
+import ExtensionAPI from '../../../ExtensionAPI';
+import SeriesModel from '../../../model/Series';
+
+const magicTypeLang = lang.toolbox.magicType;
+const INNER_STACK_KEYWORD = '__ec_magicType_stack__' as const;
 
-var magicTypeLang = lang.toolbox.magicType;
-var INNER_STACK_KEYWORD = '__ec_magicType_stack__';
+const ICON_TYPES =  ['line', 'bar', 'stack'] as const;
 
-function MagicType(model) {
-    this.model = model;
+const radioTypes = [
+    ['line', 'bar'],
+    ['stack']
+] as const;
+
+type IconType = typeof ICON_TYPES[number];
+
+export interface ToolboxMagicTypeFeatureOption extends ToolboxFeatureOption {
+    type?: IconType[]
+    /**
+     * Icon group
+     */
+    icon?: {[key in IconType]?: string}
+    title?: {[key in IconType]?: string}
+
+    // TODO LineSeriesOption, BarSeriesOption
+    option?: {[key in IconType]?: SeriesOption}
+
+    /**
+     * Map of seriesType: seriesIndex
+     */
+    seriesIndex?: {
+        line?: number
+        bar?: number
+    }
 }
 
-MagicType.defaultOption = {
-    show: true,
-    type: [],
-    // Icon group
-    icon: {
-        /* eslint-disable */
-        line: 'M4.1,28.9h7.1l9.3-22l7.4,38l9.7-19.7l3,12.8h14.9M4.1,58h51.4',
-        bar: 'M6.7,22.9h10V48h-10V22.9zM24.9,13h10v35h-10V13zM43.2,2h10v46h-10V2zM3.1,58h53.7',
-        stack: 'M8.2,38.4l-8.4,4.1l30.6,15.3L60,42.5l-8.1-4.1l-21.5,11L8.2,38.4z M51.9,30l-8.1,4.2l-13.4,6.9l-13.9-6.9L8.2,30l-8.4,4.2l8.4,4.2l22.2,11l21.5-11l8.1-4.2L51.9,30z M51.9,21.7l-8.1,4.2L35.7,30l-5.3,2.8L24.9,30l-8.4-4.1l-8.3-4.2l-8.4,4.2L8.2,30l8.3,4.2l13.9,6.9l13.4-6.9l8.1-4.2l8.1-4.1L51.9,21.7zM30.4,2.2L-0.2,17.5l8.4,4.1l8.3,4.2l8.4,4.2l5.5,2.7l5.3-2.7l8.1-4.2l8.1-4.2l8.1-4.1L30.4,2.2z' // jshint ignore:line
-        /* eslint-enable */
-    },
-    // `line`, `bar`, `stack`, `tiled`
-    title: zrUtil.clone(magicTypeLang.title),
-    option: {},
-    seriesIndex: {}
-};
 
-var proto = MagicType.prototype;
+class MagicType extends ToolboxFeature<ToolboxMagicTypeFeatureOption> {
+
+    getIcons() {
+        var model = this.model;
+        var availableIcons = model.get('icon');
+        var icons: ToolboxMagicTypeFeatureOption['icon'] = {};
+        zrUtil.each(model.get('type'), function (type) {
+            if (availableIcons[type]) {
+                icons[type] = availableIcons[type];
+            }
+        });
+        return icons;
+    }
+
+    static defaultOption: ToolboxMagicTypeFeatureOption = {
+        show: true,
+        type: [],
+        // Icon group
+        icon: {
+            /* eslint-disable */
+            line: 'M4.1,28.9h7.1l9.3-22l7.4,38l9.7-19.7l3,12.8h14.9M4.1,58h51.4',
+            bar: 'M6.7,22.9h10V48h-10V22.9zM24.9,13h10v35h-10V13zM43.2,2h10v46h-10V2zM3.1,58h53.7',
+            stack: 'M8.2,38.4l-8.4,4.1l30.6,15.3L60,42.5l-8.1-4.1l-21.5,11L8.2,38.4z M51.9,30l-8.1,4.2l-13.4,6.9l-13.9-6.9L8.2,30l-8.4,4.2l8.4,4.2l22.2,11l21.5-11l8.1-4.2L51.9,30z M51.9,21.7l-8.1,4.2L35.7,30l-5.3,2.8L24.9,30l-8.4-4.1l-8.3-4.2l-8.4,4.2L8.2,30l8.3,4.2l13.9,6.9l13.4-6.9l8.1-4.2l8.1-4.1L51.9,21.7zM30.4,2.2L-0.2,17.5l8.4,4.1l8.3,4.2l8.4,4.2l5.5,2.7l5.3-2.7l8.1-4.2l8.1-4.2l8.1-4.1L30.4,2.2z' // jshint ignore:line
+            /* eslint-enable */
+        },
+        // `line`, `bar`, `stack`, `tiled`
+        title: zrUtil.clone(magicTypeLang.title),
+        option: {},
+        seriesIndex: {}
+    }
 
-proto.getIcons = function () {
-    var model = this.model;
-    var availableIcons = model.get('icon');
-    var icons = {};
-    zrUtil.each(model.get('type'), function (type) {
-        if (availableIcons[type]) {
-            icons[type] = availableIcons[type];
+
+    onclick(ecModel: GlobalModel, api: ExtensionAPI, type: IconType) {
+        var model = this.model;
+        var seriesIndex = model.get(['seriesIndex', type as 'line' | 'bar']);
+        // Not supported magicType
+        if (!seriesOptGenreator[type]) {
+            return;
         }
-    });
-    return icons;
-};
+        var newOption: ECUnitOption = {
+            series: []
+        };
+        var generateNewSeriesTypes = function (seriesModel: SeriesModel) {
+            var seriesType = seriesModel.subType;
+            var seriesId = seriesModel.id;
+            var newSeriesOpt = seriesOptGenreator[type](
+                seriesType, seriesId, seriesModel, model
+            );
+            if (newSeriesOpt) {
+                // PENDING If merge original option?
+                zrUtil.defaults(newSeriesOpt, seriesModel.option);
+                newOption.series.push(newSeriesOpt);
+            }
+            // Modify boundaryGap
+            var coordSys = seriesModel.coordinateSystem;
+            if (coordSys && coordSys.type === 'cartesian2d' && (type === 'line' || type === 'bar')) {
+                var categoryAxis = coordSys.getAxesByScale('ordinal')[0];
+                if (categoryAxis) {
+                    var axisDim = categoryAxis.dim;
+                    var axisType = axisDim + 'Axis';
+                    var axisModel = ecModel.queryComponents({
+                        mainType: axisType,
+                        index: seriesModel.get(name + 'Index' as any),
+                        id: seriesModel.get(name + 'Id' as any)
+                    })[0];
+                    var axisIndex = axisModel.componentIndex;
+
+                    newOption[axisType] = newOption[axisType] || [];
+                    for (var i = 0; i <= axisIndex; i++) {
+                        newOption[axisType][axisIndex] = newOption[axisType][axisIndex] || {};
+                    }
+                    newOption[axisType][axisIndex].boundaryGap = type === 'bar';
+                }
+            }
+        };
+
+        zrUtil.each(radioTypes, function (radio) {
+            if (zrUtil.indexOf(radio, type) >= 0) {
+                zrUtil.each(radio, function (item) {
+                    model.setIconStatus(item, 'normal');
+                });
+            }
+        });
+
+        model.setIconStatus(type, 'emphasis');
+
+        ecModel.eachComponent(
+            {
+                mainType: 'series',
+                query: seriesIndex == null ? null : {
+                    seriesIndex: seriesIndex
+                }
+            }, generateNewSeriesTypes
+        );
 
-var seriesOptGenreator = {
+        var newTitle;
+        // Change title of stack
+        if (type === 'stack') {
+            var isStack = newOption.series && newOption.series[0] && newOption.series[0].stack === INNER_STACK_KEYWORD;
+            newTitle = isStack
+                ? zrUtil.merge({ stack: magicTypeLang.title.tiled }, magicTypeLang.title)
+                : zrUtil.clone(magicTypeLang.title);
+        }
+
+        api.dispatchAction({
+            type: 'changeMagicType',
+            currentType: type,
+            newOption: newOption,
+            newTitle: newTitle
+        });
+    }
+}
+
+
+type SeriesOptGenreator = (
+    seriesType: string,
+    seriesId: string,
+    seriesModel: SeriesModel<SeriesOption & {
+        // TODO: TYPE More specified series option
+        stack?: boolean | string
+        data?: any[]
+        markPoint?: any
+        markLine?: any
+    }>,
+    model: ToolboxFeatureModel<ToolboxMagicTypeFeatureOption>
+) => SeriesOption
+const seriesOptGenreator: {[key in IconType]: SeriesOptGenreator} = {
     'line': function (seriesType, seriesId, seriesModel, model) {
         if (seriesType === 'bar') {
             return zrUtil.merge({
@@ -73,7 +196,7 @@ var seriesOptGenreator = {
                 stack: seriesModel.get('stack'),
                 markPoint: seriesModel.get('markPoint'),
                 markLine: seriesModel.get('markLine')
-            }, model.get('option.line') || {}, true);
+            }, model.get(['option', 'line']) || {}, true);
         }
     },
     'bar': function (seriesType, seriesId, seriesModel, model) {
@@ -86,7 +209,7 @@ var seriesOptGenreator = {
                 stack: seriesModel.get('stack'),
                 markPoint: seriesModel.get('markPoint'),
                 markLine: seriesModel.get('markLine')
-            }, model.get('option.bar') || {}, true);
+            }, model.get(['option', 'bar']) || {}, true);
         }
     },
     'stack': function (seriesType, seriesId, seriesModel, model) {
@@ -96,95 +219,11 @@ var seriesOptGenreator = {
             return zrUtil.merge({
                 id: seriesId,
                 stack: isStack ? '' : INNER_STACK_KEYWORD
-            }, model.get('option.stack') || {}, true);
+            }, model.get(['option', 'stack']) || {}, true);
         }
     }
 };
 
-var radioTypes = [
-    ['line', 'bar'],
-    ['stack']
-];
-
-proto.onclick = function (ecModel, api, type) {
-    var model = this.model;
-    var seriesIndex = model.get('seriesIndex.' + type);
-    // Not supported magicType
-    if (!seriesOptGenreator[type]) {
-        return;
-    }
-    var newOption = {
-        series: []
-    };
-    var generateNewSeriesTypes = function (seriesModel) {
-        var seriesType = seriesModel.subType;
-        var seriesId = seriesModel.id;
-        var newSeriesOpt = seriesOptGenreator[type](
-            seriesType, seriesId, seriesModel, model
-        );
-        if (newSeriesOpt) {
-            // PENDING If merge original option?
-            zrUtil.defaults(newSeriesOpt, seriesModel.option);
-            newOption.series.push(newSeriesOpt);
-        }
-        // Modify boundaryGap
-        var coordSys = seriesModel.coordinateSystem;
-        if (coordSys && coordSys.type === 'cartesian2d' && (type === 'line' || type === 'bar')) {
-            var categoryAxis = coordSys.getAxesByScale('ordinal')[0];
-            if (categoryAxis) {
-                var axisDim = categoryAxis.dim;
-                var axisType = axisDim + 'Axis';
-                var axisModel = ecModel.queryComponents({
-                    mainType: axisType,
-                    index: seriesModel.get(name + 'Index'),
-                    id: seriesModel.get(name + 'Id')
-                })[0];
-                var axisIndex = axisModel.componentIndex;
-
-                newOption[axisType] = newOption[axisType] || [];
-                for (var i = 0; i <= axisIndex; i++) {
-                    newOption[axisType][axisIndex] = newOption[axisType][axisIndex] || {};
-                }
-                newOption[axisType][axisIndex].boundaryGap = type === 'bar';
-            }
-        }
-    };
-
-    zrUtil.each(radioTypes, function (radio) {
-        if (zrUtil.indexOf(radio, type) >= 0) {
-            zrUtil.each(radio, function (item) {
-                model.setIconStatus(item, 'normal');
-            });
-        }
-    });
-
-    model.setIconStatus(type, 'emphasis');
-
-    ecModel.eachComponent(
-        {
-            mainType: 'series',
-            query: seriesIndex == null ? null : {
-                seriesIndex: seriesIndex
-            }
-        }, generateNewSeriesTypes
-    );
-
-    var newTitle;
-    // Change title of stack
-    if (type === 'stack') {
-        var isStack = newOption.series && newOption.series[0] && newOption.series[0].stack === INNER_STACK_KEYWORD;
-        newTitle = isStack
-            ? zrUtil.merge({ stack: magicTypeLang.title.tiled }, magicTypeLang.title)
-            : zrUtil.clone(magicTypeLang.title);
-    }
-
-    api.dispatchAction({
-        type: 'changeMagicType',
-        currentType: type,
-        newOption: newOption,
-        newTitle: newTitle
-    });
-};
 
 echarts.registerAction({
     type: 'changeMagicType',
@@ -194,6 +233,6 @@ echarts.registerAction({
     ecModel.mergeOption(payload.newOption);
 });
 
-featureManager.register('magicType', MagicType);
+registerFeature('magicType', MagicType);
 
 export default MagicType;
\ No newline at end of file
diff --git a/src/component/toolbox/feature/Restore.ts b/src/component/toolbox/feature/Restore.ts
index ac5f217..41569b9 100644
--- a/src/component/toolbox/feature/Restore.ts
+++ b/src/component/toolbox/feature/Restore.ts
@@ -17,45 +17,45 @@
 * under the License.
 */
 
-// @ts-nocheck
-
 import * as echarts from '../../../echarts';
 import * as history from '../../dataZoom/history';
 import lang from '../../../lang';
-import * as featureManager from '../featureManager';
+import { ToolboxFeatureOption, ToolboxFeature, registerFeature } from '../featureManager';
+import ExtensionAPI from '../../../ExtensionAPI';
+import GlobalModel from '../../../model/Global';
 
 var restoreLang = lang.toolbox.restore;
 
-function Restore(model) {
-    this.model = model;
+export interface ToolboxRestoreFeatureOption extends ToolboxFeatureOption {
+    icon?: string
+    title?: string
 }
 
-Restore.defaultOption = {
-    show: true,
-    /* eslint-disable */
-    icon: 'M3.8,33.4 M47,18.9h9.8V8.7 M56.3,20.1 C52.1,9,40.5,0.6,26.8,2.1C12.6,3.7,1.6,16.2,2.1,30.6 M13,41.1H3.1v10.2 M3.7,39.9c4.2,11.1,15.8,19.5,29.5,18 c14.2-1.6,25.2-14.1,24.7-28.5',
-    /* eslint-enable */
-    title: restoreLang.title
-};
+class RestoreOption extends ToolboxFeature<ToolboxRestoreFeatureOption> {
 
-var proto = Restore.prototype;
+    onclick(ecModel: GlobalModel, api: ExtensionAPI) {
+        history.clear(ecModel);
 
-proto.onclick = function (ecModel, api, type) {
-    history.clear(ecModel);
+        api.dispatchAction({
+            type: 'restore',
+            from: this.uid
+        });
+    }
 
-    api.dispatchAction({
-        type: 'restore',
-        from: this.uid
-    });
-};
+    static defaultOption: ToolboxRestoreFeatureOption = {
+        show: true,
+        /* eslint-disable */
+        icon: 'M3.8,33.4 M47,18.9h9.8V8.7 M56.3,20.1 C52.1,9,40.5,0.6,26.8,2.1C12.6,3.7,1.6,16.2,2.1,30.6 M13,41.1H3.1v10.2 M3.7,39.9c4.2,11.1,15.8,19.5,29.5,18 c14.2-1.6,25.2-14.1,24.7-28.5',
+        /* eslint-enable */
+        title: restoreLang.title
+    }
+}
 
-featureManager.register('restore', Restore);
+registerFeature('restore', RestoreOption);
 
 echarts.registerAction(
     {type: 'restore', event: 'restore', update: 'prepareAndUpdate'},
     function (payload, ecModel) {
         ecModel.resetOption('recreate');
     }
-);
-
-export default Restore;
\ No newline at end of file
+);
\ No newline at end of file
diff --git a/src/component/toolbox/feature/SaveAsImage.ts b/src/component/toolbox/feature/SaveAsImage.ts
index d876a12..d57b299 100644
--- a/src/component/toolbox/feature/SaveAsImage.ts
+++ b/src/component/toolbox/feature/SaveAsImage.ts
@@ -17,89 +17,101 @@
 * under the License.
 */
 
-// @ts-nocheck
-
 /* global Uint8Array */
 
 import env from 'zrender/src/core/env';
 import lang from '../../../lang';
-import * as featureManager from '../featureManager';
+import { ToolboxFeature, ToolboxFeatureOption, registerFeature } from '../featureManager';
+import { ZRColor } from '../../../util/types';
+import GlobalModel from '../../../model/Global';
+import ExtensionAPI from '../../../ExtensionAPI';
 
 var saveAsImageLang = lang.toolbox.saveAsImage;
 
-function SaveAsImage(model) {
-    this.model = model;
-}
+export interface ToolboxSaveAsImageFeatureOption extends ToolboxFeatureOption {
+    icon?: string
+    title?: string
+    type?: 'png' | 'jpg'
 
-SaveAsImage.defaultOption = {
-    show: true,
-    icon: 'M4.7,22.9L29.3,45.5L54.7,23.4M4.6,43.6L4.6,58L53.8,58L53.8,43.6M29.2,45.1L29.2,0',
-    title: saveAsImageLang.title,
-    type: 'png',
-    // Default use option.backgroundColor
-    // backgroundColor: '#fff',
-    connectedBackgroundColor: '#fff',
-    name: '',
-    excludeComponents: ['toolbox'],
-    pixelRatio: 1,
-    lang: saveAsImageLang.lang.slice()
-};
+    backgroundColor?: ZRColor
+    connectedBackgroundColor?: ZRColor
 
-SaveAsImage.prototype.unusable = !env.canvasSupported;
+    name?: string
+    excludeComponents?: string[]
+
+    pixelRatio?: number
 
-var proto = SaveAsImage.prototype;
+    lang?: string[]
+}
+
+class SaveAsImage extends ToolboxFeature<ToolboxSaveAsImageFeatureOption> {
 
-proto.onclick = function (ecModel, api) {
-    var model = this.model;
-    var title = model.get('name') || ecModel.get('title.0.text') || 'echarts';
-    var type = model.get('type', true) || 'png';
-    var url = api.getConnectedDataURL({
-        type: type,
-        backgroundColor: model.get('backgroundColor', true)
-            || ecModel.get('backgroundColor') || '#fff',
-        connectedBackgroundColor: model.get('connectedBackgroundColor'),
-        excludeComponents: model.get('excludeComponents'),
-        pixelRatio: model.get('pixelRatio')
-    });
-    // Chrome and Firefox
-    if (typeof MouseEvent === 'function' && !env.browser.ie && !env.browser.edge) {
-        var $a = document.createElement('a');
-        $a.download = title + '.' + type;
-        $a.target = '_blank';
-        $a.href = url;
-        var evt = new MouseEvent('click', {
-            view: window,
-            bubbles: true,
-            cancelable: false
+    onclick(ecModel: GlobalModel, api: ExtensionAPI) {
+
+        var model = this.model;
+        var title = model.get('name') || ecModel.get('title.0.text') || 'echarts';
+        var type = model.get('type', true) || 'png';
+        var url = api.getConnectedDataURL({
+            type: type,
+            backgroundColor: model.get('backgroundColor', true)
+                || ecModel.get('backgroundColor') || '#fff',
+            connectedBackgroundColor: model.get('connectedBackgroundColor'),
+            excludeComponents: model.get('excludeComponents'),
+            pixelRatio: model.get('pixelRatio')
         });
-        $a.dispatchEvent(evt);
-    }
-    // IE
-    else {
-        if (window.navigator.msSaveOrOpenBlob) {
-            var bstr = atob(url.split(',')[1]);
-            var n = bstr.length;
-            var u8arr = new Uint8Array(n);
-            while (n--) {
-                u8arr[n] = bstr.charCodeAt(n);
-            }
-            var blob = new Blob([u8arr]);
-            window.navigator.msSaveOrOpenBlob(blob, title + '.' + type);
+        // Chrome and Firefox
+        if (typeof MouseEvent === 'function' && !env.browser.ie && !env.browser.edge) {
+            var $a = document.createElement('a');
+            $a.download = title + '.' + type;
+            $a.target = '_blank';
+            $a.href = url;
+            var evt = new MouseEvent('click', {
+                view: window,
+                bubbles: true,
+                cancelable: false
+            });
+            $a.dispatchEvent(evt);
         }
+        // IE
         else {
-            var lang = model.get('lang');
-            var html = ''
-                + '<body style="margin:0;">'
-                + '<img src="' + url + '" style="max-width:100%;" title="' + ((lang && lang[0]) || '') + '" />'
-                + '</body>';
-            var tab = window.open();
-            tab.document.write(html);
+            if (window.navigator.msSaveOrOpenBlob) {
+                var bstr = atob(url.split(',')[1]);
+                var n = bstr.length;
+                var u8arr = new Uint8Array(n);
+                while (n--) {
+                    u8arr[n] = bstr.charCodeAt(n);
+                }
+                var blob = new Blob([u8arr]);
+                window.navigator.msSaveOrOpenBlob(blob, title + '.' + type);
+            }
+            else {
+                var lang = model.get('lang');
+                var html = ''
+                    + '<body style="margin:0;">'
+                    + '<img src="' + url + '" style="max-width:100%;" title="' + ((lang && lang[0]) || '') + '" />'
+                    + '</body>';
+                var tab = window.open();
+                tab.document.write(html);
+            }
         }
     }
-};
 
-featureManager.register(
-    'saveAsImage', SaveAsImage
-);
+    static defaultOption: ToolboxSaveAsImageFeatureOption = {
+        show: true,
+        icon: 'M4.7,22.9L29.3,45.5L54.7,23.4M4.6,43.6L4.6,58L53.8,58L53.8,43.6M29.2,45.1L29.2,0',
+        title: saveAsImageLang.title,
+        type: 'png',
+        // Default use option.backgroundColor
+        // backgroundColor: '#fff',
+        connectedBackgroundColor: '#fff',
+        name: '',
+        excludeComponents: ['toolbox'],
+        pixelRatio: 1,
+        lang: saveAsImageLang.lang.slice()
+    }
+}
+SaveAsImage.prototype.unusable = !env.canvasSupported;
+
+registerFeature('saveAsImage', SaveAsImage);
 
 export default SaveAsImage;
diff --git a/src/component/toolbox/featureManager.ts b/src/component/toolbox/featureManager.ts
index dc1e19d..29e748d 100644
--- a/src/component/toolbox/featureManager.ts
+++ b/src/component/toolbox/featureManager.ts
@@ -16,16 +16,104 @@
 * specific language governing permissions and limitations
 * under the License.
 */
+import { Dictionary, DisplayState, ZRElementEvent, ItemStyleOption, ColorString, LabelOption } from '../../util/types';
+import Model from '../../model/Model';
+import GlobalModel from '../../model/Global';
+import ExtensionAPI from '../../ExtensionAPI';
+import * as graphic from '../../util/graphic';
 
-// @ts-nocheck
+type IconPath = ReturnType<typeof graphic.createIcon>;
 
+type IconStyle = ItemStyleOption & {
+    // TODO Move to a individual textStyle option
+    textFill?: LabelOption['color']
+    textBackgroundColor?: LabelOption['backgroundColor']
+    textPosition?: LabelOption['position']
+    textAlign?: LabelOption['align']
+    textBorderRadius?: LabelOption['borderRadius']
+    textPadding?: LabelOption['padding']
+}
+export interface ToolboxFeatureOption {
+
+    show?: boolean
+
+    title?: string | Dictionary<string>
+
+    icon?: string | Dictionary<string>
+
+    iconStyle?: IconStyle
+    emphasis?: {
+        iconStyle?: IconStyle
+    }
+
+    iconStatus?: Dictionary<DisplayState>
+
+    onclick?: (ecModel: GlobalModel, api: ExtensionAPI, type: string, event: ZRElementEvent) => void
+}
+
+export interface ToolboxFeatureModel<Opts extends ToolboxFeatureOption = ToolboxFeatureOption> extends Model<Opts> {
+
+    /**
+     * Collection of icon paths.
+     * Will be injected during rendering in the view.
+     */
+    iconPaths: Dictionary<IconPath>
+
+    setIconStatus(iconName: string, status: DisplayState): void
+}
+
+interface ToolboxFeature<Opts extends ToolboxFeatureOption = ToolboxFeatureOption> {
+    getIcons?(): Dictionary<string>
+
+    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
+    updateView?(featureModel: ToolboxFeatureModel, model: GlobalModel, api: ExtensionAPI, payload: unknown): void
+}
+abstract class ToolboxFeature<Opts extends ToolboxFeatureOption = ToolboxFeatureOption> {
+    uid: string
+
+    model: ToolboxFeatureModel<Opts>
+    ecModel: GlobalModel
+    api: ExtensionAPI
+
+    /**
+     * If toolbox feature can't be used on some platform.
+     */
+    unusable?: boolean
+}
+
+export {ToolboxFeature};
+
+export interface UserDefinedToolboxFeature {
+    uid: string
+
+    model: ToolboxFeatureModel
+    ecModel: GlobalModel
+    api: ExtensionAPI
+
+    featureName?: string
+
+    onclick(ecModel: GlobalModel, api: ExtensionAPI, type: string, event: ZRElementEvent): void
+}
+
+type ToolboxFeatureCtor = {
+    new(): ToolboxFeature
+    /**
+     * Static defaultOption property
+     */
+    defaultOption: ToolboxFeatureOption
+};
 
-var features = {};
+var features: Dictionary<ToolboxFeatureCtor> = {};
 
-export function register(name, ctor) {
+export function registerFeature(name: string, ctor: ToolboxFeatureCtor) {
     features[name] = ctor;
 }
 
-export function get(name) {
+export function getFeature(name: string) {
     return features[name];
 }
diff --git a/src/component/tooltip/TooltipModel.ts b/src/component/tooltip/TooltipModel.ts
index dd945f4..31742c7 100644
--- a/src/component/tooltip/TooltipModel.ts
+++ b/src/component/tooltip/TooltipModel.ts
@@ -23,12 +23,15 @@ import {
     LabelOption,
     LineStyleOption,
     CommonTooltipOption,
-    TooltipRenderMode
+    TooltipRenderMode,
+    CallbackDataParams
 } from '../../util/types';
 import {AxisPointerOption} from '../axisPointer/AxisPointerModel';
 
 
-export interface TooltipOption extends CommonTooltipOption, ComponentOption {
+type TopLevelFormatterParams = CallbackDataParams | CallbackDataParams[];
+
+export interface TooltipOption extends CommonTooltipOption<TopLevelFormatterParams>, ComponentOption {
 
     axisPointer?: AxisPointerOption & {
         axis?: 'auto' | 'x' | 'y' | 'angle' | 'radius'
diff --git a/src/component/tooltip/TooltipView.ts b/src/component/tooltip/TooltipView.ts
index 0cc6199..5b746ed 100644
--- a/src/component/tooltip/TooltipView.ts
+++ b/src/component/tooltip/TooltipView.ts
@@ -32,8 +32,8 @@ import * as axisPointerViewHelper from '../axisPointer/viewHelper';
 import { getTooltipRenderMode } from '../../util/model';
 import ComponentView from '../../view/Component';
 import {
-    ZRAlign,
-    ZRVerticalAlign,
+    HorizontalAlign,
+    VerticalAlign,
     ZRRectLike,
     BoxLayoutOptionMixin,
     CallbackDataParams,
@@ -91,7 +91,7 @@ interface ShowTipPayload {
 
     // Type 2
     dataByCoordSys?: DataByCoordSys[]
-    tooltipOption?: CommonTooltipOption
+    tooltipOption?: CommonTooltipOption<TooltipDataParams | TooltipDataParams[]>
 
     // Type 3
     seriesIndex?: number
@@ -123,7 +123,7 @@ interface TryShowParams {
      */
     dataByCoordSys?: DataByCoordSys[]
 
-    tooltipOption?: CommonTooltipOption
+    tooltipOption?: CommonTooltipOption<TooltipDataParams | TooltipDataParams[]>
 
     position?: TooltipOption['position']
 }
@@ -690,7 +690,8 @@ class TooltipView extends ComponentView {
 
         this._showOrMove(subTooltipModel, function (this: TooltipView) {
             this._showTooltipContent(
-                subTooltipModel, defaultHtml, subTooltipModel.get('formatterParams') || {},
+                // Use formatterParams from element defined in component
+                subTooltipModel, defaultHtml, subTooltipModel.get('formatterParams') as any || {},
                 asyncTicket, e.offsetX, e.offsetY, e.position, el
             );
         });
@@ -758,7 +759,7 @@ class TooltipView extends ComponentView {
         x: number,  // Mouse x
         y: number,  // Mouse y
         content: TooltipHTMLContent | TooltipRichContent,
-        params:  TooltipDataParams | TooltipDataParams[],
+        params: TooltipDataParams | TooltipDataParams[],
         el?: Element
     ) {
         var viewWidth = this._api.getWidth();
@@ -1006,7 +1007,7 @@ function calcTooltipPosition(
     return [x, y];
 }
 
-function isCenterAlign(align: ZRAlign | ZRVerticalAlign) {
+function isCenterAlign(align: HorizontalAlign | VerticalAlign) {
     return align === 'center' || align === 'middle';
 }
 
diff --git a/src/coord/CoordinateSystem.ts b/src/coord/CoordinateSystem.ts
index 18daac8..632f1e2 100644
--- a/src/coord/CoordinateSystem.ts
+++ b/src/coord/CoordinateSystem.ts
@@ -88,6 +88,8 @@ export interface CoordinateSystemMaster {
  */
 export interface CoordinateSystem {
 
+    type: string
+
     // Should be the same as its coordinateSystemCreator.
     dimensions: DimensionName[];
 
diff --git a/src/coord/axisHelper.ts b/src/coord/axisHelper.ts
index 383ca3f..7cb3fb4 100644
--- a/src/coord/axisHelper.ts
+++ b/src/coord/axisHelper.ts
@@ -32,7 +32,6 @@ import {
 } from '../layout/barGrid';
 import BoundingRect from 'zrender/src/core/BoundingRect';
 
-import '../scale/Time';
 import '../scale/Log';
 import TimeScale from '../scale/Time';
 import { ComponentOption } from '../util/types';
diff --git a/src/data/DataDiffer.ts b/src/data/DataDiffer.ts
index 76c75dc..0eb9012 100644
--- a/src/data/DataDiffer.ts
+++ b/src/data/DataDiffer.ts
@@ -49,8 +49,8 @@ class DataDiffer {
     constructor(
         oldArr: ArrayLike<any>,
         newArr: ArrayLike<any>,
-        oldKeyGetter: DiffKeyGetter,
-        newKeyGetter: DiffKeyGetter,
+        oldKeyGetter?: DiffKeyGetter,
+        newKeyGetter?: DiffKeyGetter,
         context?: any
     ) {
         this._old = oldArr;
diff --git a/src/data/List.ts b/src/data/List.ts
index 12bfee8..e82d3a6 100644
--- a/src/data/List.ts
+++ b/src/data/List.ts
@@ -1813,7 +1813,7 @@ class List <HostModel extends Model = Model> {
     // ----------------------------------------------------------
     // A work around for internal method visiting private member.
     // ----------------------------------------------------------
-    static internalField = (function () {
+    private static internalField = (function () {
 
         defaultDimValueGetters = {
 
diff --git a/src/data/helper/dataProvider.ts b/src/data/helper/dataProvider.ts
index 30e1b7e..7e4670c 100644
--- a/src/data/helper/dataProvider.ts
+++ b/src/data/helper/dataProvider.ts
@@ -135,7 +135,7 @@ export class DefaultDataProvider implements DataProvider {
     clean(): void {
     }
 
-    static internalField = (function () {
+    private static internalField = (function () {
 
         providerMethods = {
 
diff --git a/src/echarts.ts b/src/echarts.ts
index c21c4f7..c369088 100644
--- a/src/echarts.ts
+++ b/src/echarts.ts
@@ -507,8 +507,8 @@ class ECharts {
     }
 
     getDataURL(opts?: {
-        // file type 'png' by defualt
-        type?: string,
+        // file type 'png' by default
+        type?: 'png' | 'jpg',
         pixelRatio?: number,
         backgroundColor?: ZRColor,
         // component type array
@@ -550,19 +550,13 @@ class ECharts {
         return url;
     }
 
-    /**
-     * @return {string}
-     * @param {Object} opts
-     * @param {string} [opts.type='png']
-     * @param {string} [opts.pixelRatio=1]
-     * @param {string} [opts.backgroundColor]
-     */
     getConnectedDataURL(opts?: {
-        // file type 'png' by defualt
-        type?: string,
+        // file type 'png' by default
+        type?: 'png' | 'jpg',
         pixelRatio?: number,
         backgroundColor?: ZRColor,
-        connectedBackgroundColor?: string
+        connectedBackgroundColor?: ZRColor
+        excludeComponents?: string[]
     }): string {
         if (this._disposed) {
             disposedWarning(this.id);
@@ -1082,7 +1076,7 @@ class ECharts {
 
     // A work around for no `internal` modifier in ts yet but
     // need to strictly hide private methods to JS users.
-    static internalField = (function () {
+    private static internalField = (function () {
 
         prepare = function (ecIns: ECharts): void {
             var scheduler = ecIns._scheduler;
diff --git a/src/model/Global.ts b/src/model/Global.ts
index 39edddc..7f4c50f 100644
--- a/src/model/Global.ts
+++ b/src/model/Global.ts
@@ -614,7 +614,7 @@ class GlobalModel extends Model {
         );
     }
 
-    static internalField = (function () {
+    private static internalField = (function () {
 
         createSeriesIndices = function (ecModel: GlobalModel, seriesModels: ComponentModel[]): void {
             ecModel._seriesIndicesMap = createHashMap(
diff --git a/src/theme/dark.ts b/src/theme/dark.ts
index d97a787..0ed7109 100644
--- a/src/theme/dark.ts
+++ b/src/theme/dark.ts
@@ -17,8 +17,6 @@
 * under the License.
 */
 
-// @ts-nocheck
-
 var contrastColor = '#eee';
 var axisCommon = function () {
     return {
@@ -153,6 +151,6 @@ var theme = {
         }
     }
 };
-theme.categoryAxis.splitLine.show = false;
+(theme.categoryAxis.splitLine as any).show = false;
 
 export default theme;
\ No newline at end of file
diff --git a/src/util/animation.ts b/src/util/animation.ts
index d509531..27c44e1 100644
--- a/src/util/animation.ts
+++ b/src/util/animation.ts
@@ -24,11 +24,6 @@ import Element, { ElementProps } from 'zrender/src/Element';
 import { ZREasing } from './types';
 
 /**
- * @param {number} [time=500] Time in ms
- * @param {string} [easing='linear']
- * @param {number} [delay=0]
- * @param {Function} [callback]
- *
  * @example
  *  // Animate position
  *  animation
@@ -51,12 +46,7 @@ export function createWrap() {
          * might not be called. This method checks this (by el.id),
          * suppresses adding and returns false when existing el found.
          *
-         * @param {modele:zrender/Element} el
-         * @param {Object} target
-         * @param {number} [time=500]
-         * @param {number} [delay=0]
-         * @param {string} [easing='linear']
-         * @return {boolean} Whether adding succeeded.
+         * @return Whether adding succeeded.
          *
          * @example
          *     add(el, target, time, delay, easing);
diff --git a/src/util/types.ts b/src/util/types.ts
index 90c6490..059de8a 100644
--- a/src/util/types.ts
+++ b/src/util/types.ts
@@ -50,14 +50,18 @@ import ZRText from 'zrender/src/graphic/Text';
 // Common types and constants
 // ---------------------------
 
+export {Dictionary};
+
 export type RendererType = 'canvas' | 'svg';
 
+export type LayoutOrient = 'vertical' | 'horizontal'
+export type HorizontalAlign = 'left' | 'center' | 'right'
+export type VerticalAlign = 'top' | 'middle' | 'bottom'
+
 // Types from zrender
 export type ColorString = string;
 export type ZRColor = ColorString | LinearGradientObject | RadialGradientObject | PatternObject
 export type ZRLineType = 'solid' | 'dotted' | 'dashed'
-export type ZRAlign = 'left' | 'center' | 'right'
-export type ZRVerticalAlign = 'top' | 'middle' | 'bottom'
 
 export type ZRFontStyle = 'normal' | 'italic' | 'oblique'
 export type ZRFontWeight = 'normal' | 'bold' | 'bolder' | 'lighter' | number
@@ -96,10 +100,9 @@ export interface ECElement extends Element {
     eventData?: ECEventData;
     seriesIndex?: number;
     dataType?: string;
-    tooltip?: CommonTooltipOption & {
+    tooltip?: CommonTooltipOption<unknown> & {
         content?: string
-        // TODO: TYPE
-        formatterParams?: any
+        formatterParams?: unknown
     }
 }
 
@@ -298,8 +301,6 @@ export type SeriesLayoutBy = typeof SERIES_LAYOUT_BY_COLUMN | typeof SERIES_LAYO
 
 
 
-
-
 // --------------------------------------------
 // echarts option types (base and common part)
 // --------------------------------------------
@@ -685,10 +686,10 @@ export interface TextCommonOption extends ShadowOptionMixin {
     fontWeight?: ZRFontWeight
     fontFamily?: string
     fontSize?: number
-    align?: ZRAlign
-    verticalAlign?: ZRVerticalAlign
+    align?: HorizontalAlign
+    verticalAlign?: VerticalAlign
     // @deprecated
-    baseline?: ZRVerticalAlign
+    baseline?: VerticalAlign
 
     lineHeight?: number
     backgroundColor?: ColorString | {
@@ -721,7 +722,8 @@ export interface LabelOption extends TextCommonOption {
      */
     show?: boolean
     // TODO: TYPE More specified 'inside', 'insideTop'....
-    position?: string | number[] | string[]
+    // x, y can be both percent string or number px.
+    position?: string | (number | string)[]
     distance?: number
     rotate?: number | boolean
     offset?: number[]
@@ -740,17 +742,17 @@ export interface LabelLineOption {
     lineStyle?: LineStyleOption
 }
 
-interface TooltipFormatterCallback {
+interface TooltipFormatterCallback<T> {
     /**
      * For sync callback
      * params will be an array on axis trigger.
      */
-    (params: CallbackDataParams | CallbackDataParams[], asyncTicket: string): string
+    (params: T, asyncTicket: string): string
     /**
      * For async callback.
      * Returned html string will be a placeholder when callback is not invoked.
      */
-    (params: CallbackDataParams | CallbackDataParams[], asyncTicket: string, callback: (cbTicket: string, html: string) => void): string
+    (params: T, asyncTicket: string, callback: (cbTicket: string, html: string) => void): string
 }
 
 type TooltipBuiltinPosition = 'inside' | 'top' | 'left' | 'right' | 'bottom'
@@ -791,7 +793,7 @@ interface PositionCallback {
 /**
  * Common tooltip option
  */
-export interface CommonTooltipOption {
+export interface CommonTooltipOption<FormatterParams> {
 
     show?: boolean
 
@@ -804,7 +806,7 @@ export interface CommonTooltipOption {
      */
     alwaysShowContent?: boolean
 
-    formatter?: string | TooltipFormatterCallback
+    formatter?: string | TooltipFormatterCallback<FormatterParams>
     /**
      * Absolution pixel [x, y] array. Or relative percent string [x, y] array.
      * If trigger is 'item'. position can be set to 'inside' / 'top' / 'left' / 'right' / 'bottom',
@@ -819,9 +821,9 @@ export interface CommonTooltipOption {
     /**
      * Consider triggered from axisPointer handle, verticalAlign should be 'middle'
      */
-    align?: ZRAlign
+    align?: HorizontalAlign
 
-    verticalAlign?: ZRVerticalAlign
+    verticalAlign?: VerticalAlign
     /**
      * Delay of show. milesecond.
      */
@@ -868,7 +870,7 @@ export interface CommonTooltipOption {
 /**
  * Tooltip option configured on each series
  */
-export type SeriesTooltipOption = CommonTooltipOption
+export type SeriesTooltipOption = CommonTooltipOption<CallbackDataParams>
 
 export interface ComponentOption {
     type?: string;
@@ -885,12 +887,14 @@ export interface SeriesOption extends
     AnimationOptionMixin,
     ColorPaletteOptionMixin
 {
+    name?: string
+
     silent?: boolean
 
     blendMode?: string
 
     // Needs to be override
-    data?: unknown;
+    data?: any;
 
     legendHoverLink?: boolean
 


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org