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/22 15:46:40 UTC

[incubator-echarts] branch typescript updated: ts: add types for legend and title.

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 54fd540  ts: add types for legend and title.
54fd540 is described below

commit 54fd540417177f80caf4c559ca96a592d4a3680c
Author: pissang <bm...@gmail.com>
AuthorDate: Sat Feb 22 23:46:03 2020 +0800

    ts: add types for legend and title.
---
 src/action/createDataSelectAction.ts           |  24 ++-
 src/component/legend.ts                        |   6 +-
 src/component/legend/LegendModel.ts            | 258 ++++++++++++++++---------
 src/component/legend/LegendView.ts             | 216 +++++++++++++--------
 src/component/legend/ScrollableLegendModel.ts  | 103 ++++++----
 src/component/legend/ScrollableLegendView.ts   | 214 ++++++++++++--------
 src/component/legend/legendFilter.ts           |  12 +-
 src/component/legend/scrollableLegendAction.ts |   5 +-
 src/component/title.ts                         | 122 +++++++-----
 src/component/visualMap/VisualMapModel.ts      |   2 +-
 src/util/graphic.ts                            |   8 +-
 src/util/types.ts                              |   6 +-
 12 files changed, 616 insertions(+), 360 deletions(-)

diff --git a/src/action/createDataSelectAction.ts b/src/action/createDataSelectAction.ts
index aef0d64..9b157bd 100644
--- a/src/action/createDataSelectAction.ts
+++ b/src/action/createDataSelectAction.ts
@@ -17,12 +17,21 @@
 * under the License.
 */
 
-// @ts-nocheck
-
 import * as echarts from '../echarts';
 import * as zrUtil from 'zrender/src/core/util';
+import { ActionInfo } from '../util/types';
+import SeriesModel from '../model/Series';
+import { Dictionary } from 'zrender/src/core/types';
+import { DataSelectableMixin, DataSelectableOptionMixin } from '../component/helper/selectableMixin';
+
+
+type SelectableSeriesModel = SeriesModel & DataSelectableMixin<DataSelectableOptionMixin>;
+
+interface DataSelectAction extends ActionInfo {
+    method: string
+}
 
-export default function (seriesType, actionInfos) {
+export default function (seriesType: string, actionInfos: DataSelectAction[]) {
     zrUtil.each(actionInfos, function (actionInfo) {
         actionInfo.update = 'updateView';
         /**
@@ -31,12 +40,13 @@ export default function (seriesType, actionInfos) {
          * @property {string} name
          */
         echarts.registerAction(actionInfo, function (payload, ecModel) {
-            var selected = {};
+            var selected: Dictionary<Boolean> = {};
             ecModel.eachComponent(
                 {mainType: 'series', subType: seriesType, query: payload},
-                function (seriesModel) {
-                    if (seriesModel[actionInfo.method]) {
-                        seriesModel[actionInfo.method](
+                function (seriesModel: SelectableSeriesModel) {
+                    // TODO: TYPE method type checking
+                    if ((seriesModel as any)[actionInfo.method]) {
+                        (seriesModel as any)[actionInfo.method](
                             payload.name,
                             payload.dataIndex
                         );
diff --git a/src/component/legend.ts b/src/component/legend.ts
index f11bb5b..28d35be 100644
--- a/src/component/legend.ts
+++ b/src/component/legend.ts
@@ -17,8 +17,6 @@
 * under the License.
 */
 
-// @ts-nocheck
-
 // Do not contain scrollable legend, for sake of file size.
 
 import * as echarts from '../echarts';
@@ -28,12 +26,12 @@ import './legend/legendAction';
 import './legend/LegendView';
 
 import legendFilter from './legend/legendFilter';
-import Component from '../model/Component';
+import Component, { ComponentModelConstructor } from '../model/Component';
 
 // Series Filter
 echarts.registerProcessor(echarts.PRIORITY.PROCESSOR.SERIES_FILTER, legendFilter);
 
-Component.registerSubTypeDefaulter('legend', function () {
+(Component as ComponentModelConstructor).registerSubTypeDefaulter('legend', function () {
     // Default 'plain' when no type specified.
     return 'plain';
 });
diff --git a/src/component/legend/LegendModel.ts b/src/component/legend/LegendModel.ts
index e0533d9..067b4be 100644
--- a/src/component/legend/LegendModel.ts
+++ b/src/component/legend/LegendModel.ts
@@ -17,13 +17,21 @@
 * under the License.
 */
 
-// @ts-nocheck
-
-import * as echarts from '../../echarts';
 import * as zrUtil from 'zrender/src/core/util';
 import Model from '../../model/Model';
 import {isNameSpecified} from '../../util/model';
 import lang from '../../lang';
+import ComponentModel from '../../model/Component';
+import {
+    ComponentOption,
+    BoxLayoutOptionMixin,
+    BorderOptionMixin,
+    ColorString,
+    ItemStyleOption,
+    LabelOption
+} from '../../util/types';
+import { Dictionary } from 'zrender/src/core/types';
+import GlobalModel from '../../model/Global';
 
 var langSelector = lang.legend.selector;
 
@@ -38,13 +46,122 @@ var defaultSelectorOption = {
     }
 };
 
-var LegendModel = echarts.extendComponentModel({
+type SelectorType = 'all' | 'inverse'
+export interface LegendSelectorButtonOption {
+    type?: SelectorType
+    title?: string
+}
+
+interface DataItem {
+    name?: string
+    icon?: string
+    textStyle?: LabelOption
+
+    // TODO: TYPE tooltip
+    tooltip?: unknown
+}
+export interface LegendOption extends ComponentOption, BoxLayoutOptionMixin, BorderOptionMixin {
+    orient?: 'horizontal' | 'vertical'
+
+    align?: 'auto' | 'left' | 'right'
+
+    backgroundColor?: ColorString
+    /**
+     * Border radius of background rect
+     * @default 0
+     */
+    borderRadius?: number | number[]
 
-    type: 'legend.plain',
+    /**
+     * Padding between legend item and border.
+     * Support to be a single number or an array.
+     * @default 5
+     */
+    padding?: number | number[]
+    /**
+     * Gap between each legend item.
+     * @default 10
+     */
+    itemGap?: number
+    /**
+     * Width of legend symbol
+     */
+    itemWidth?: number
+    /**
+     * Height of legend symbol
+     */
+    itemHeight?: number
+    /**
+     * Color when legend item is not selected
+     */
+    inactiveColor?: ColorString
+    /**
+     * Border color when legend item is not selected
+     */
+    inactiveBorderColor?: ColorString
+
+    itemStyle?: ItemStyleOption
+
+    /**
+     * Legend label formatter
+     */
+    formatter?: string | ((name: string) => string)
+
+    textStyle?: LabelOption
+
+    selectedMode?: boolean | 'single' | 'multiple'
+    /**
+     * selected map of each item. Default to be selected if item is not in the map
+     */
+    selected?: Dictionary<boolean>
+
+    /**
+     * Buttons for all select or inverse select.
+     * @example
+     *  selector: [{type: 'all or inverse', title: xxx}]
+     *  selector: true
+     *  selector: ['all', 'inverse']
+     */
+    selector?: (LegendSelectorButtonOption | SelectorType)[] | boolean
+
+    selectorLabel?: LabelOption
+
+    emphasis?: {
+        selectorLabel?: LabelOption
+    }
+
+    /**
+     * Position of selector buttons.
+     */
+    selectorPosition?: 'auto' | 'start' | 'end'
+    /**
+     * Gap between each selector button
+     */
+    selectorItemGap?: number
+    /**
+     * Gap between selector buttons group and legend main items.
+     */
+    selectorButtonGap?: number
+
+    data?: (string | DataItem)[]
+
+    symbolKeepAspect?: boolean
+
+    /**
+     * Tooltip option
+     */
+    // TODO: TYPE tooltip
+    tooltip?: unknown
 
-    dependencies: ['series'],
+}
 
-    layoutMode: {
+class LegendModel<Ops extends LegendOption = LegendOption> extends ComponentModel<Ops> {
+    static type = 'legend.plain'
+    type = LegendModel.type
+
+    static readonly dependencies = ['series']
+
+    readonly layoutMode = {
         type: 'box',
         // legend.width/height are maxWidth/maxHeight actually,
         // whereas realy width/height is calculated by its content.
@@ -54,21 +171,25 @@ var LegendModel = echarts.extendComponentModel({
         // then `setOption({legend: {right: 10});`
         // The previous `left` should be cleared by setting `ignoreSize`.
         ignoreSize: true
-    },
+    } as const
 
-    init: function (option, parentModel, ecModel) {
+
+    private _data: Model<DataItem>[]
+    private _availableNames: string[]
+
+    init(option: Ops, parentModel: Model, ecModel: GlobalModel) {
         this.mergeDefaultAndTheme(option, ecModel);
 
         option.selected = option.selected || {};
         this._updateSelector(option);
-    },
+    }
 
-    mergeOption: function (option) {
-        LegendModel.superCall(this, 'mergeOption', option);
+    mergeOption(option: Ops, ecModel: GlobalModel) {
+        super.mergeOption(option, ecModel);
         this._updateSelector(option);
-    },
+    }
 
-    _updateSelector: function (option) {
+    _updateSelector(option: Ops) {
         var selector = option.selector;
         if (selector === true) {
             selector = option.selector = ['all', 'inverse'];
@@ -76,12 +197,14 @@ var LegendModel = echarts.extendComponentModel({
         if (zrUtil.isArray(selector)) {
             zrUtil.each(selector, function (item, index) {
                 zrUtil.isString(item) && (item = {type: item});
-                selector[index] = zrUtil.merge(item, defaultSelectorOption[item.type]);
+                (selector as LegendSelectorButtonOption[])[index] = zrUtil.merge(
+                    item, defaultSelectorOption[item.type]
+                );
             });
         }
-    },
+    }
 
-    optionUpdated: function () {
+    optionUpdated() {
         this._updateData(this.ecModel);
 
         var legendData = this._data;
@@ -102,11 +225,11 @@ var LegendModel = echarts.extendComponentModel({
             // Try select the first if selectedMode is single
             !hasSelected && this.select(legendData[0].get('name'));
         }
-    },
+    }
 
-    _updateData: function (ecModel) {
-        var potentialData = [];
-        var availableNames = [];
+    _updateData(ecModel: GlobalModel) {
+        var potentialData: string[] = [];
+        var availableNames: string[] = [];
 
         ecModel.eachRawSeries(function (seriesModel) {
             var seriesName = seriesModel.name;
@@ -162,19 +285,13 @@ var LegendModel = echarts.extendComponentModel({
          * @private
          */
         this._data = legendData;
-    },
+    }
 
-    /**
-     * @return {Array.<module:echarts/model/Model>}
-     */
-    getData: function () {
+    getData() {
         return this._data;
-    },
+    }
 
-    /**
-     * @param {string} name
-     */
-    select: function (name) {
+    select(name: string) {
         var selected = this.option.selected;
         var selectedMode = this.get('selectedMode');
         if (selectedMode === 'single') {
@@ -184,38 +301,32 @@ var LegendModel = echarts.extendComponentModel({
             });
         }
         selected[name] = true;
-    },
+    }
 
-    /**
-     * @param {string} name
-     */
-    unSelect: function (name) {
+    unSelect(name: string) {
         if (this.get('selectedMode') !== 'single') {
             this.option.selected[name] = false;
         }
-    },
+    }
 
-    /**
-     * @param {string} name
-     */
-    toggleSelected: function (name) {
+    toggleSelected(name: string) {
         var selected = this.option.selected;
         // Default is true
         if (!selected.hasOwnProperty(name)) {
             selected[name] = true;
         }
         this[selected[name] ? 'unSelect' : 'select'](name);
-    },
+    }
 
-    allSelect: function () {
+    allSelect() {
         var data = this._data;
         var selected = this.option.selected;
         zrUtil.each(data, function (dataItem) {
             selected[dataItem.get('name', true)] = true;
         });
-    },
+    }
 
-    inverseSelect: function () {
+    inverseSelect() {
         var data = this._data;
         var selected = this.option.selected;
         zrUtil.each(data, function (dataItem) {
@@ -226,91 +337,58 @@ var LegendModel = echarts.extendComponentModel({
             }
             selected[name] = !selected[name];
         });
-    },
+    }
 
-    /**
-     * @param {string} name
-     */
-    isSelected: function (name) {
+    isSelected(name: string) {
         var selected = this.option.selected;
         return !(selected.hasOwnProperty(name) && !selected[name])
             && zrUtil.indexOf(this._availableNames, name) >= 0;
-    },
+    }
 
-    getOrient: function () {
+    getOrient(): {index: 0, name: 'horizontal'}
+    getOrient(): {index: 1, name: 'vertical'}
+    getOrient() {
         return this.get('orient') === 'vertical'
             ? {index: 1, name: 'vertical'}
             : {index: 0, name: 'horizontal'};
-    },
+    }
 
-    defaultOption: {
-        // 一级层叠
+    static defaultOption: LegendOption = {
         zlevel: 0,
-        // 二级层叠
         z: 4,
         show: true,
 
-        // 布局方式,默认为水平布局,可选为:
-        // 'horizontal' | 'vertical'
         orient: 'horizontal',
 
         left: 'center',
         // right: 'center',
-
         top: 0,
         // bottom: null,
 
-        // 水平对齐
-        // 'auto' | 'left' | 'right'
-        // 默认为 'auto', 根据 x 的位置判断是左对齐还是右对齐
         align: 'auto',
 
         backgroundColor: 'rgba(0,0,0,0)',
-        // 图例边框颜色
         borderColor: '#ccc',
         borderRadius: 0,
-        // 图例边框线宽,单位px,默认为0(无边框)
         borderWidth: 0,
-        // 图例内边距,单位px,默认各方向内边距为5,
-        // 接受数组分别设定上右下左边距,同css
         padding: 5,
-        // 各个item之间的间隔,单位px,默认为10,
-        // 横向布局时为水平间隔,纵向布局时为纵向间隔
         itemGap: 10,
-        // the width of legend symbol
         itemWidth: 25,
-        // the height of legend symbol
         itemHeight: 14,
 
-        // the color of unselected legend symbol
         inactiveColor: '#ccc',
 
-        // the borderColor of unselected legend symbol
         inactiveBorderColor: '#ccc',
 
         itemStyle: {
-            // the default borderWidth of legend symbol
             borderWidth: 0
         },
 
         textStyle: {
-            // 图例文字颜色
             color: '#333'
         },
-        // formatter: '',
-        // 选择模式,默认开启图例开关
         selectedMode: true,
-        // 配置默认选中状态,可配合LEGEND.SELECTED事件做动态数据载入
-        // selected: null,
-        // 图例内容(详见legend.data,数组中每一项代表一个item
-        // data: [],
-
-        // Usage:
-        // selector: [{type: 'all or inverse', title: xxx}]
-        // or
-        // selector: true
-        // or
-        // selector: ['all', 'inverse']
+
         selector: false,
 
         selectorLabel: {
@@ -332,18 +410,18 @@ var LegendModel = echarts.extendComponentModel({
             }
         },
 
-        // Value can be 'start' or 'end'
         selectorPosition: 'auto',
 
         selectorItemGap: 7,
 
         selectorButtonGap: 10,
 
-        // Tooltip 相关配置
         tooltip: {
             show: false
         }
     }
-});
+}
+
+ComponentModel.registerClass(LegendModel);
 
 export default LegendModel;
\ No newline at end of file
diff --git a/src/component/legend/LegendView.ts b/src/component/legend/LegendView.ts
index 3fec7fe..04402d1 100644
--- a/src/component/legend/LegendView.ts
+++ b/src/component/legend/LegendView.ts
@@ -17,76 +17,73 @@
 * under the License.
 */
 
-// @ts-nocheck
-
 import {__DEV__} from '../../config';
-import * as echarts from '../../echarts';
 import * as zrUtil from 'zrender/src/core/util';
 import {createSymbol} from '../../util/symbol';
 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 GlobalModel from '../../model/Global';
+import ExtensionAPI from '../../ExtensionAPI';
+import { RectLike } from 'zrender/src/core/BoundingRect';
+import { ColorString, ZRTextAlign, ZRColor, ItemStyleOption } from '../../util/types';
+import Model from '../../model/Model';
+import ZImage from 'zrender/src/graphic/Image';
 
 var curry = zrUtil.curry;
 var each = zrUtil.each;
 var Group = graphic.Group;
 
-export default echarts.extendComponentView({
+class LegendView extends ComponentView {
+    static type = 'legend.plain'
+    type = LegendView.type
+
+    newlineDisabled = false
+
+    private _contentGroup: graphic.Group
 
-    type: 'legend.plain',
+    private _backgroundEl: graphic.Rect
 
-    newlineDisabled: false,
+    private _selectorGroup: graphic.Group
 
     /**
-     * @override
+     * If first rendering, `contentGroup.position` is [0, 0], which
+     * does not make sense and may cause unexepcted animation if adopted.
      */
-    init: function () {
+    private _isFirstRender: boolean
 
-        /**
-         * @private
-         * @type {module:zrender/container/Group}
-         */
-        this.group.add(this._contentGroup = new Group());
+    init() {
 
-        /**
-         * @private
-         * @type {module:zrender/Element}
-         */
-        this._backgroundEl;
-
-        /**
-         * @private
-         * @type {module:zrender/container/Group}
-         */
+        this.group.add(this._contentGroup = new Group());
         this.group.add(this._selectorGroup = new Group());
 
-        /**
-         * If first rendering, `contentGroup.position` is [0, 0], which
-         * does not make sense and may cause unexepcted animation if adopted.
-         * @private
-         * @type {boolean}
-         */
         this._isFirstRender = true;
-    },
+    }
 
     /**
      * @protected
      */
-    getContentGroup: function () {
+    getContentGroup() {
         return this._contentGroup;
-    },
+    }
 
     /**
      * @protected
      */
-    getSelectorGroup: function () {
+    getSelectorGroup() {
         return this._selectorGroup;
-    },
+    }
 
     /**
      * @override
      */
-    render: function (legendModel, ecModel, api) {
+    render(
+        legendModel: LegendModel,
+        ecModel: GlobalModel,
+        api: ExtensionAPI
+    ) {
         var isFirstRender = this._isFirstRender;
         this._isFirstRender = false;
 
@@ -105,7 +102,8 @@ export default echarts.extendComponentView({
             ) ? 'right' : 'left';
         }
 
-        var selector = legendModel.get('selector', true);
+        // selector has been normalized to an array in model
+        var selector = legendModel.get('selector', true) as LegendSelectorButtonOption[];
         var selectorPosition = legendModel.get('selectorPosition', true);
         if (selector && (!selectorPosition || selectorPosition === 'auto')) {
             selectorPosition = orient === 'horizontal' ? 'end' : 'start';
@@ -124,7 +122,10 @@ export default echarts.extendComponentView({
 
         // Place mainGroup, based on the calculated `mainRect`.
         var layoutRect = layoutUtil.getLayoutRect(
-            zrUtil.defaults({width: mainRect.width, height: mainRect.height}, positionInfo),
+            zrUtil.defaults({
+                width: mainRect.width,
+                height: mainRect.height
+            }, positionInfo),
             viewportSize,
             padding
         );
@@ -134,26 +135,28 @@ export default echarts.extendComponentView({
         this.group.add(
             this._backgroundEl = makeBackground(mainRect, legendModel)
         );
-    },
+    }
 
-    /**
-     * @protected
-     */
-    resetInner: function () {
+    protected resetInner() {
         this.getContentGroup().removeAll();
         this._backgroundEl && this.group.remove(this._backgroundEl);
         this.getSelectorGroup().removeAll();
-    },
+    }
 
-    /**
-     * @protected
-     */
-    renderInner: function (itemAlign, legendModel, ecModel, api, selector, orient, selectorPosition) {
+    protected renderInner(
+        itemAlign: LegendOption['align'],
+        legendModel: LegendModel,
+        ecModel: GlobalModel,
+        api: ExtensionAPI,
+        selector: LegendSelectorButtonOption[],
+        orient: LegendOption['orient'],
+        selectorPosition: LegendOption['selectorPosition']
+    ) {
         var contentGroup = this.getContentGroup();
         var legendDrawnMap = zrUtil.createHashMap();
         var selectMode = legendModel.get('selectedMode');
 
-        var excludeSeriesId = [];
+        var excludeSeriesId: string[] = [];
         ecModel.eachRawSeries(function (seriesModel) {
             !seriesModel.get('legendHoverLink') && excludeSeriesId.push(seriesModel.id);
         });
@@ -163,9 +166,10 @@ export default echarts.extendComponentView({
 
             // Use empty string or \n as a newline string
             if (!this.newlineDisabled && (name === '' || name === '\n')) {
-                contentGroup.add(new Group({
-                    newline: true
-                }));
+                const g = new Group();
+                // @ts-ignore
+                g.newline = true;
+                contentGroup.add(g);
                 return;
             }
 
@@ -266,26 +270,28 @@ export default echarts.extendComponentView({
         if (selector) {
             this._createSelector(selector, legendModel, api, orient, selectorPosition);
         }
-    },
+    }
 
-    _createSelector: function (selector, legendModel, api, orient, selectorPosition) {
+    private _createSelector(
+        selector: LegendSelectorButtonOption[],
+        legendModel: LegendModel,
+        api: ExtensionAPI,
+        orient: LegendOption['orient'],
+        selectorPosition: LegendOption['selectorPosition']
+    ) {
         var selectorGroup = this.getSelectorGroup();
 
-        each(selector, function (selectorItem) {
-            createSelectorButton(selectorItem);
-        });
-
-        function createSelectorButton(selectorItem) {
+        each(selector, function createSelectorButton(selectorItem) {
             var type = selectorItem.type;
 
             var labelText = new graphic.Text({
                 style: {
                     x: 0,
                     y: 0,
-                    align: 'center',
-                    verticalAlign: 'middle'
+                    textAlign: 'center',
+                    textVerticalAlign: 'middle'
                 },
-                onclick: function () {
+                onclick() {
                     api.dispatchAction({
                         type: type === 'all' ? 'legendAllSelect' : 'legendInverseSelect'
                     });
@@ -295,7 +301,7 @@ export default echarts.extendComponentView({
             selectorGroup.add(labelText);
 
             var labelModel = legendModel.getModel('selectorLabel');
-            var emphasisLabelModel = legendModel.getModel('emphasis.selectorLabel');
+            var emphasisLabelModel = legendModel.getModel(['emphasis', 'selectorLabel']);
 
             graphic.setLabelStyle(
                 labelText.style, labelText.hoverStyle = {}, labelModel, emphasisLabelModel,
@@ -305,13 +311,20 @@ export default echarts.extendComponentView({
                 }
             );
             graphic.setHoverStyle(labelText);
-        }
-    },
+        });
+    }
 
-    _createItem: function (
-        name, dataIndex, itemModel, legendModel,
-        legendSymbolType, symbolType,
-        itemAlign, color, borderColor, selectMode
+    private _createItem(
+        name: string,
+        dataIndex: number,
+        itemModel: LegendModel['_data'][number],
+        legendModel: LegendModel,
+        legendSymbolType: string,
+        symbolType: string,
+        itemAlign: LegendOption['align'],
+        color: ColorString,
+        borderColor: ColorString,
+        selectMode: LegendOption['selectedMode']
     ) {
         var itemWidth = legendModel.get('itemWidth');
         var itemHeight = legendModel.get('itemHeight');
@@ -379,7 +392,7 @@ export default echarts.extendComponentView({
         }
 
         var textX = itemAlign === 'left' ? itemWidth + 5 : -5;
-        var textAlign = itemAlign;
+        var textAlign = itemAlign as ZRTextAlign;
 
         var formatter = legendModel.get('formatter');
         var content = name;
@@ -405,6 +418,7 @@ export default echarts.extendComponentView({
         var hitRect = new graphic.Rect({
             shape: itemGroup.getBoundingRect(),
             invisible: true,
+            // @ts-ignore
             tooltip: tooltipModel.get('show') ? zrUtil.extend({
                 content: name,
                 // Defaul formatter
@@ -431,15 +445,20 @@ export default echarts.extendComponentView({
 
         graphic.setHoverStyle(itemGroup);
 
+        // @ts-ignore
         itemGroup.__legendDataIndex = dataIndex;
 
         return itemGroup;
-    },
+    }
 
-    /**
-     * @protected
-     */
-    layoutInner: function (legendModel, itemAlign, maxSize, isFirstRender, selector, selectorPosition) {
+    protected layoutInner(
+        legendModel: LegendModel,
+        itemAlign: LegendOption['align'],
+        maxSize: { width: number, height: number },
+        isFirstRender: boolean,
+        selector: LegendOption['selector'],
+        selectorPosition: LegendOption['selectorPosition']
+    ): RectLike {
         var contentGroup = this.getContentGroup();
         var selectorGroup = this.getSelectorGroup();
 
@@ -469,9 +488,9 @@ export default echarts.extendComponentView({
             var selectorButtonGap = legendModel.get('selectorButtonGap', true);
 
             var orientIdx = legendModel.getOrient().index;
-            var wh = orientIdx === 0 ? 'width' : 'height';
-            var hw = orientIdx === 0 ? 'height' : 'width';
-            var yx = orientIdx === 0 ? 'y' : 'x';
+            var wh: 'width' | 'height' = orientIdx === 0 ? 'width' : 'height';
+            var hw: 'width' | 'height' = orientIdx === 0 ? 'height' : 'width';
+            var yx: 'x' | 'y' = orientIdx === 0 ? 'y' : 'x';
 
             if (selectorPosition === 'end') {
                 selectorPos[orientIdx] += contentRect[wh] + selectorButtonGap;
@@ -485,7 +504,7 @@ export default echarts.extendComponentView({
             selectorGroup.attr('position', selectorPos);
             contentGroup.attr('position', contentPos);
 
-            var mainRect = {x: 0, y: 0};
+            var mainRect = {x: 0, y: 0} as RectLike;
             mainRect[wh] = contentRect[wh] + selectorButtonGap + selectorRect[wh];
             mainRect[hw] = Math.max(contentRect[hw], selectorRect[hw]);
             mainRect[yx] = Math.min(0, selectorRect[yx] + selectorPos[1 - orientIdx]);
@@ -495,19 +514,26 @@ export default echarts.extendComponentView({
             contentGroup.attr('position', contentPos);
             return this.group.getBoundingRect();
         }
-    },
+    }
 
     /**
      * @protected
      */
-    remove: function () {
+    remove() {
         this.getContentGroup().removeAll();
         this._isFirstRender = true;
     }
 
-});
+}
 
-function setSymbolStyle(symbol, symbolType, legendModelItemStyle, borderColor, inactiveBorderColor, isSelected) {
+function setSymbolStyle(
+    symbol: graphic.Path | ZImage,
+    symbolType: string,
+    legendModelItemStyle: Model<ItemStyleOption>,
+    borderColor: ZRColor,
+    inactiveBorderColor: ZRColor,
+    isSelected: boolean
+) {
     var itemStyle;
     if (symbolType !== 'line' && symbolType.indexOf('empty') < 0) {
         itemStyle = legendModelItemStyle.getItemStyle();
@@ -522,7 +548,12 @@ function setSymbolStyle(symbol, symbolType, legendModelItemStyle, borderColor, i
     return symbol.setStyle(itemStyle);
 }
 
-function dispatchSelectAction(seriesName, dataName, api, excludeSeriesId) {
+function dispatchSelectAction(
+    seriesName: string,
+    dataName: string,
+    api: ExtensionAPI,
+    excludeSeriesId: string[]
+) {
     // downplay before unselect
     dispatchDownplayAction(seriesName, dataName, api, excludeSeriesId);
     api.dispatchAction({
@@ -533,7 +564,12 @@ function dispatchSelectAction(seriesName, dataName, api, excludeSeriesId) {
     dispatchHighlightAction(seriesName, dataName, api, excludeSeriesId);
 }
 
-function dispatchHighlightAction(seriesName, dataName, api, excludeSeriesId) {
+function dispatchHighlightAction(
+    seriesName: string,
+    dataName: string,
+    api: ExtensionAPI,
+    excludeSeriesId: string[]
+) {
     // If element hover will move to a hoverLayer.
     var el = api.getZr().storage.getDisplayList()[0];
     if (!(el && el.useHoverLayer)) {
@@ -546,7 +582,12 @@ function dispatchHighlightAction(seriesName, dataName, api, excludeSeriesId) {
     }
 }
 
-function dispatchDownplayAction(seriesName, dataName, api, excludeSeriesId) {
+function dispatchDownplayAction(
+    seriesName: string,
+    dataName: string,
+    api: ExtensionAPI,
+    excludeSeriesId: string[]
+) {
     // If element hover will move to a hoverLayer.
     var el = api.getZr().storage.getDisplayList()[0];
     if (!(el && el.useHoverLayer)) {
@@ -558,3 +599,8 @@ function dispatchDownplayAction(seriesName, dataName, api, excludeSeriesId) {
         });
     }
 }
+
+
+ComponentView.registerClass(LegendView);
+
+export default LegendView;
\ No newline at end of file
diff --git a/src/component/legend/ScrollableLegendModel.ts b/src/component/legend/ScrollableLegendModel.ts
index 4d64af9..04bf2ed 100644
--- a/src/component/legend/ScrollableLegendModel.ts
+++ b/src/component/legend/ScrollableLegendModel.ts
@@ -17,26 +17,78 @@
 * under the License.
 */
 
-// @ts-nocheck
-
-import LegendModel from './LegendModel';
+import LegendModel, {LegendOption} from './LegendModel';
 import {
     mergeLayoutParam,
     getLayoutParams
 } from '../../util/layout';
+import { ZRColor, LabelOption } from '../../util/types';
+import Model from '../../model/Model';
+import ComponentModel from '../../model/Component';
+import GlobalModel from '../../model/Global';
+import * as zrUtil from 'zrender/src/core/util';
 
-var ScrollableLegendModel = LegendModel.extend({
+export interface ScrollableLegendOption extends LegendOption {
+    scrollDataIndex?: number
+    /**
+     * Gap between each page button
+     */
+    pageButtonItemGap?: number
+    /**
+     * Gap between page buttons group and legend items.
+     */
+    pageButtonGap?: number
+    pageButtonPosition?: 'start' | 'end'
 
-    type: 'legend.scroll',
+    pageFormatter?: string | ((param: {current: number, total: number}) => string)
+    pageIcons?: {
+        horizontal?: string[]
+        vertical?: string[]
+    }
+    pageIconColor?: ZRColor
+    pageIconInactiveColor?: ZRColor
+    pageIconSize?: number
+    pageTextStyle?: LabelOption
+
+    animationDurationUpdate?: number
+}
+
+class ScrollableLegendModel extends LegendModel<ScrollableLegendOption> {
+
+    static type = 'legend.scroll' as const
+    type = ScrollableLegendModel.type
 
     /**
      * @param {number} scrollDataIndex
      */
-    setScrollDataIndex: function (scrollDataIndex) {
+    setScrollDataIndex(scrollDataIndex: number) {
         this.option.scrollDataIndex = scrollDataIndex;
-    },
+    }
+
+    init(
+        option: ScrollableLegendOption,
+        parentModel: Model,
+        ecModel: GlobalModel
+    ) {
+        var inputPositionParams = getLayoutParams(option);
+
+        super.init(option, parentModel, ecModel);
+
+        mergeAndNormalizeLayoutParams(this, option, inputPositionParams);
+    }
+
+    /**
+     * @override
+     */
+    mergeOption(option: ScrollableLegendOption, ecModel: GlobalModel) {
+        super.mergeOption(option, ecModel);
 
-    defaultOption: {
+        mergeAndNormalizeLayoutParams(this, this.option, option);
+    }
+
+    static defaultOption: ScrollableLegendOption = zrUtil.merge(
+        zrUtil.clone(LegendModel.defaultOption),
+    {
         scrollDataIndex: 0,
         pageButtonItemGap: 5,
         pageButtonGap: null,
@@ -54,38 +106,23 @@ var ScrollableLegendModel = LegendModel.extend({
         },
 
         animationDurationUpdate: 800
-    },
-
-    /**
-     * @override
-     */
-    init: function (option, parentModel, ecModel, extraOpt) {
-        var inputPositionParams = getLayoutParams(option);
-
-        ScrollableLegendModel.superCall(this, 'init', option, parentModel, ecModel, extraOpt);
-
-        mergeAndNormalizeLayoutParams(this, option, inputPositionParams);
-    },
-
-    /**
-     * @override
-     */
-    mergeOption: function (option, extraOpt) {
-        ScrollableLegendModel.superCall(this, 'mergeOption', option, extraOpt);
-
-        mergeAndNormalizeLayoutParams(this, this.option, option);
-    }
-
-});
+    })
+};
 
 // Do not `ignoreSize` to enable setting {left: 10, right: 10}.
-function mergeAndNormalizeLayoutParams(legendModel, target, raw) {
+function mergeAndNormalizeLayoutParams(
+    legendModel: ScrollableLegendModel,
+    target: ScrollableLegendOption,
+    raw: ScrollableLegendOption
+) {
     var orient = legendModel.getOrient();
     var ignoreSize = [1, 1];
     ignoreSize[orient.index] = 0;
     mergeLayoutParam(target, raw, {
-        type: 'box', ignoreSize: ignoreSize
+        type: 'box', ignoreSize: !!ignoreSize
     });
 }
 
+ComponentModel.registerClass(ScrollableLegendModel);
+
 export default ScrollableLegendModel;
\ No newline at end of file
diff --git a/src/component/legend/ScrollableLegendView.ts b/src/component/legend/ScrollableLegendView.ts
index dc9471e..04de2c7 100644
--- a/src/component/legend/ScrollableLegendView.ts
+++ b/src/component/legend/ScrollableLegendView.ts
@@ -17,8 +17,6 @@
 * under the License.
 */
 
-// @ts-nocheck
-
 /**
  * Separate legend and scrollable legend to reduce package size.
  */
@@ -27,77 +25,110 @@ import * as zrUtil from 'zrender/src/core/util';
 import * as graphic from '../../util/graphic';
 import * as layoutUtil from '../../util/layout';
 import LegendView from './LegendView';
+import { LegendSelectorButtonOption } from './LegendModel';
+import ExtensionAPI from '../../ExtensionAPI';
+import GlobalModel from '../../model/Global';
+import ScrollableLegendModel, {ScrollableLegendOption} from './ScrollableLegendModel';
+import { RectLike } from 'zrender/src/core/BoundingRect';
+import Displayable from 'zrender/src/graphic/Displayable';
+import ComponentView from '../../view/Component';
+import Element from 'zrender/src/Element';
 
 var Group = graphic.Group;
 
-var WH = ['width', 'height'];
-var XY = ['x', 'y'];
+var WH = ['width', 'height'] as const;
+var XY = ['x', 'y'] as const;
+
+interface PageInfo {
+    contentPosition: number[]
+    pageCount: number
+    pageIndex: number
+    pagePrevDataIndex: number
+    pageNextDataIndex: number
+}
+
+interface ItemInfo {
+    /**
+     * Start
+     */
+    s: number
+    /**
+     * End
+     */
+    e: number
+    /**
+     * Index
+     */
+    i: number
+}
+
+type LegendGroup = graphic.Group & {
+    __rectSize: number
+}
+
+type LegendItemElement = Element & {
+    __legendDataIndex: number
+}
+
+class ScrollableLegendView extends LegendView {
 
-var ScrollableLegendView = LegendView.extend({
+    static type = 'legend.scroll' as const
+    type = ScrollableLegendView.type
 
-    type: 'legend.scroll',
+    newlineDisabled = true
 
-    newlineDisabled: true,
+    private _containerGroup: LegendGroup
+    private _controllerGroup: graphic.Group
 
-    init: function () {
+    private _currentIndex: number = 0
 
-        ScrollableLegendView.superCall(this, 'init');
+    private _showController: boolean
 
-        /**
-         * @private
-         * @type {number} For `scroll`.
-         */
-        this._currentIndex = 0;
+    init() {
 
-        /**
-         * @private
-         * @type {module:zrender/container/Group}
-         */
-        this.group.add(this._containerGroup = new Group());
+        super.init();
+
+        this.group.add(this._containerGroup = new Group() as LegendGroup);
         this._containerGroup.add(this.getContentGroup());
 
-        /**
-         * @private
-         * @type {module:zrender/container/Group}
-         */
         this.group.add(this._controllerGroup = new Group());
-
-        /**
-         *
-         * @private
-         */
-        this._showController;
-    },
+    }
 
     /**
      * @override
      */
-    resetInner: function () {
-        ScrollableLegendView.superCall(this, 'resetInner');
+    resetInner() {
+        super.resetInner();
 
         this._controllerGroup.removeAll();
         this._containerGroup.removeClipPath();
         this._containerGroup.__rectSize = null;
-    },
+    }
 
     /**
      * @override
      */
-    renderInner: function (itemAlign, legendModel, ecModel, api, selector, orient, selectorPosition) {
-        var me = this;
+    renderInner(
+        itemAlign: ScrollableLegendOption['align'],
+        legendModel: ScrollableLegendModel,
+        ecModel: GlobalModel,
+        api: ExtensionAPI,
+        selector: LegendSelectorButtonOption[],
+        orient: ScrollableLegendOption['orient'],
+        selectorPosition: ScrollableLegendOption['selectorPosition']
+    ) {
+        var self = this;
 
         // Render content items.
-        ScrollableLegendView.superCall(this, 'renderInner', itemAlign,
-            legendModel, ecModel, api, selector, orient, selectorPosition);
+        super.renderInner(itemAlign, legendModel, ecModel, api, selector, orient, selectorPosition);
 
         var controllerGroup = this._controllerGroup;
 
         // FIXME: support be 'auto' adapt to size number text length,
         // e.g., '3/12345' should not overlap with the control arrow button.
-        var pageIconSize = legendModel.get('pageIconSize', true);
-        if (!zrUtil.isArray(pageIconSize)) {
-            pageIconSize = [pageIconSize, pageIconSize];
-        }
+        const pageIconSize = legendModel.get('pageIconSize', true);
+        let pageIconSizeArr: number[] = zrUtil.isArray(pageIconSize)
+            ? pageIconSize : [pageIconSize, pageIconSize];
 
         createPageButton('pagePrev', 0);
 
@@ -115,33 +146,40 @@ var ScrollableLegendView = LegendView.extend({
 
         createPageButton('pageNext', 1);
 
-        function createPageButton(name, iconIdx) {
-            var pageDataIndexName = name + 'DataIndex';
+        function createPageButton(name: string, iconIdx: number) {
+            var pageDataIndexName = (name + 'DataIndex') as 'pagePrevDataIndex' | 'pageNextDataIndex';
             var icon = graphic.createIcon(
                 legendModel.get('pageIcons', true)[legendModel.getOrient().name][iconIdx],
                 {
                     // Buttons will be created in each render, so we do not need
                     // to worry about avoiding using legendModel kept in scope.
                     onclick: zrUtil.bind(
-                        me._pageGo, me, pageDataIndexName, legendModel, api
+                        self._pageGo, self, pageDataIndexName, legendModel, api
                     )
                 },
                 {
-                    x: -pageIconSize[0] / 2,
-                    y: -pageIconSize[1] / 2,
-                    width: pageIconSize[0],
-                    height: pageIconSize[1]
+                    x: -pageIconSizeArr[0] / 2,
+                    y: -pageIconSizeArr[1] / 2,
+                    width: pageIconSizeArr[0],
+                    height: pageIconSizeArr[1]
                 }
             );
             icon.name = name;
             controllerGroup.add(icon);
         }
-    },
+    }
 
     /**
      * @override
      */
-    layoutInner: function (legendModel, itemAlign, maxSize, isFirstRender, selector, selectorPosition) {
+    layoutInner(
+        legendModel: ScrollableLegendModel,
+        itemAlign: ScrollableLegendOption['align'],
+        maxSize: { width: number, height: number },
+        isFirstRender: boolean,
+        selector: LegendSelectorButtonOption[],
+        selectorPosition: ScrollableLegendOption['selectorPosition']
+    ) {
         var selectorGroup = this.getSelectorGroup();
 
         var orientIdx = legendModel.getOrient().index;
@@ -187,9 +225,17 @@ var ScrollableLegendView = LegendView.extend({
         }
 
         return mainRect;
-    },
+    }
 
-    _layoutContentAndController: function (legendModel, isFirstRender, maxSize, orientIdx, wh, hw, yx) {
+    _layoutContentAndController(
+        legendModel: ScrollableLegendModel,
+        isFirstRender: boolean,
+        maxSize: { width: number, height: number },
+        orientIdx: 0 | 1,
+        wh: 'width' | 'height',
+        hw: 'width' | 'height',
+        yx: 'x' | 'y'
+    ) {
         var contentGroup = this.getContentGroup();
         var containerGroup = this._containerGroup;
         var controllerGroup = this._controllerGroup;
@@ -252,7 +298,7 @@ var ScrollableLegendView = LegendView.extend({
         // Calculate `mainRect` and set `clipPath`.
         // mainRect should not be calculated by `this.group.getBoundingRect()`
         // for sake of the overflow.
-        var mainRect = {x: 0, y: 0};
+        var mainRect = {x: 0, y: 0} as RectLike;
 
         // Consider content may be overflow (should be clipped).
         mainRect[wh] = showController ? maxSize[wh] : contentRect[wh];
@@ -263,7 +309,7 @@ var ScrollableLegendView = LegendView.extend({
 
         containerGroup.__rectSize = maxSize[wh];
         if (showController) {
-            var clipShape = {x: 0, y: 0};
+            var clipShape = {x: 0, y: 0} as graphic.Rect['shape'];
             clipShape[wh] = Math.max(maxSize[wh] - controllerRect[wh] - pageButtonGap, 0);
             clipShape[hw] = mainRect[hw];
             containerGroup.setClipPath(new graphic.Rect({shape: clipShape}));
@@ -273,8 +319,11 @@ var ScrollableLegendView = LegendView.extend({
         }
         else {
             // Do not remove or ignore controller. Keep them set as placeholders.
-            controllerGroup.eachChild(function (child) {
-                child.attr({invisible: true, silent: true});
+            controllerGroup.eachChild(function (child: Displayable) {
+                child.attr({
+                    invisible: true,
+                    silent: true
+                });
             });
         }
 
@@ -285,15 +334,19 @@ var ScrollableLegendView = LegendView.extend({
             {position: pageInfo.contentPosition},
             // When switch from "show controller" to "not show controller", view should be
             // updated immediately without animation, otherwise causes weird effect.
-            showController ? legendModel : false
+            showController ? legendModel : null
         );
 
         this._updatePageInfoView(legendModel, pageInfo);
 
         return mainRect;
-    },
+    }
 
-    _pageGo: function (to, legendModel, api) {
+    _pageGo(
+        to: 'pagePrevDataIndex' | 'pageNextDataIndex',
+        legendModel: ScrollableLegendModel,
+        api: ExtensionAPI
+    ) {
         var scrollDataIndex = this._getPageInfo(legendModel)[to];
 
         scrollDataIndex != null && api.dispatchAction({
@@ -301,14 +354,18 @@ var ScrollableLegendView = LegendView.extend({
             scrollDataIndex: scrollDataIndex,
             legendId: legendModel.id
         });
-    },
+    }
 
-    _updatePageInfoView: function (legendModel, pageInfo) {
+    _updatePageInfoView(
+        legendModel: ScrollableLegendModel,
+        pageInfo: PageInfo
+    ) {
         var controllerGroup = this._controllerGroup;
 
         zrUtil.each(['pagePrev', 'pageNext'], function (name) {
-            var canJump = pageInfo[name + 'DataIndex'] != null;
-            var icon = controllerGroup.childOfName(name);
+            const key = (name + 'DataIndex') as'pagePrevDataIndex' | 'pageNextDataIndex';
+            var canJump = pageInfo[key] != null;
+            var icon = controllerGroup.childOfName(name) as graphic.Path;
             if (icon) {
                 icon.setStyle(
                     'fill',
@@ -320,7 +377,7 @@ var ScrollableLegendView = LegendView.extend({
             }
         });
 
-        var pageText = controllerGroup.childOfName('pageText');
+        var pageText = controllerGroup.childOfName('pageText') as graphic.Text;
         var pageFormatter = legendModel.get('pageFormatter');
         var pageIndex = pageInfo.pageIndex;
         var current = pageIndex != null ? pageIndex + 1 : 0;
@@ -329,14 +386,12 @@ var ScrollableLegendView = LegendView.extend({
         pageText && pageFormatter && pageText.setStyle(
             'text',
             zrUtil.isString(pageFormatter)
-                ? pageFormatter.replace('{current}', current).replace('{total}', total)
+                ? pageFormatter.replace('{current}', current + '').replace('{total}', total + '')
                 : pageFormatter({current: current, total: total})
         );
-    },
+    }
 
     /**
-     * @param {module:echarts/model/Model} legendModel
-     * @return {Object} {
      *  contentPosition: Array.<number>, null when data item not found.
      *  pageIndex: number, null when data item not found.
      *  pageCount: number, always be a number, can be 0.
@@ -344,7 +399,7 @@ var ScrollableLegendView = LegendView.extend({
      *  pageNextDataIndex: number, null when no next page.
      * }
      */
-    _getPageInfo: function (legendModel) {
+    _getPageInfo(legendModel: ScrollableLegendModel): PageInfo {
         var scrollDataIndex = legendModel.get('scrollDataIndex', true);
         var contentGroup = this.getContentGroup();
         var containerRectSize = this._containerGroup.__rectSize;
@@ -358,8 +413,8 @@ var ScrollableLegendView = LegendView.extend({
         var itemCount = children.length;
         var pCount = !itemCount ? 0 : 1;
 
-        var result = {
-            contentPosition: contentGroup.position.slice(),
+        var result: PageInfo = {
+            contentPosition: zrUtil.slice(contentGroup.position),
             pageCount: pCount,
             pageIndex: pCount - 1,
             pagePrevDataIndex: null,
@@ -443,34 +498,34 @@ var ScrollableLegendView = LegendView.extend({
 
         return result;
 
-        function getItemInfo(el) {
+        function getItemInfo(el: Element): ItemInfo {
             if (el) {
                 var itemRect = el.getBoundingRect();
                 var start = itemRect[xy] + el.position[orientIdx];
                 return {
                     s: start,
                     e: start + itemRect[wh],
-                    i: el.__legendDataIndex
+                    i: (el as LegendItemElement).__legendDataIndex
                 };
             }
         }
 
-        function intersect(itemInfo, winStart) {
+        function intersect(itemInfo: ItemInfo, winStart: number) {
             return itemInfo.e >= winStart && itemInfo.s <= winStart + containerRectSize;
         }
-    },
+    }
 
-    _findTargetItemIndex: function (targetDataIndex) {
+    _findTargetItemIndex(targetDataIndex: number) {
         if (!this._showController) {
             return 0;
         }
 
         var index;
         var contentGroup = this.getContentGroup();
-        var defaultIndex;
+        var defaultIndex: number;
 
         contentGroup.eachChild(function (child, idx) {
-            var legendDataIdx = child.__legendDataIndex;
+            var legendDataIdx = (child as LegendItemElement).__legendDataIndex;
             // FIXME
             // If the given targetDataIndex (from model) is illegal,
             // we use defualtIndex. But the index on the legend model and
@@ -486,7 +541,8 @@ var ScrollableLegendView = LegendView.extend({
 
         return index != null ? index : defaultIndex;
     }
+}
 
-});
+ComponentView.registerClass(ScrollableLegendView);
 
 export default ScrollableLegendView;
diff --git a/src/component/legend/legendFilter.ts b/src/component/legend/legendFilter.ts
index ac13a55..09f2cce 100644
--- a/src/component/legend/legendFilter.ts
+++ b/src/component/legend/legendFilter.ts
@@ -1,3 +1,7 @@
+import SeriesModel from '../../model/Series';
+import GlobalModel from '../../model/Global';
+import LegendModel from './LegendModel';
+
 /*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
@@ -17,15 +21,13 @@
 * under the License.
 */
 
-// @ts-nocheck
-
-export default function (ecModel) {
+export default function (ecModel: GlobalModel) {
 
     var legendModels = ecModel.findComponents({
         mainType: 'legend'
-    });
+    }) as LegendModel[];
     if (legendModels && legendModels.length) {
-        ecModel.filterSeries(function (series) {
+        ecModel.filterSeries(function (series: SeriesModel) {
             // If in any legend component the status is not selected.
             // Because in legend series is assumed selected when it is not in the legend data.
             for (var i = 0; i < legendModels.length; i++) {
diff --git a/src/component/legend/scrollableLegendAction.ts b/src/component/legend/scrollableLegendAction.ts
index bf179fa..99c0bf7 100644
--- a/src/component/legend/scrollableLegendAction.ts
+++ b/src/component/legend/scrollableLegendAction.ts
@@ -17,9 +17,8 @@
 * under the License.
 */
 
-// @ts-nocheck
-
 import * as echarts from '../../echarts';
+import ScrollableLegendModel from './ScrollableLegendModel';
 
 /**
  * @event legendScroll
@@ -34,7 +33,7 @@ echarts.registerAction(
 
         scrollDataIndex != null && ecModel.eachComponent(
             {mainType: 'legend', subType: 'scroll', query: payload},
-            function (legendModel) {
+            function (legendModel: ScrollableLegendModel) {
                 legendModel.setScrollDataIndex(scrollDataIndex);
             }
         );
diff --git a/src/component/title.ts b/src/component/title.ts
index 329c3c8..4ca14a9 100644
--- a/src/component/title.ts
+++ b/src/component/title.ts
@@ -17,70 +17,90 @@
 * under the License.
 */
 
-// @ts-nocheck
-
 import * as zrUtil from 'zrender/src/core/util';
-import * as echarts from '../echarts';
 import * as graphic from '../util/graphic';
 import {getLayoutRect} from '../util/layout';
-
-// Model
-echarts.extendComponentModel({
-
-    type: 'title',
-
-    layoutMode: {type: 'box', ignoreSize: true},
-
-    defaultOption: {
-        // 一级层叠
+import ComponentModel from '../model/Component';
+import { ComponentOption, BoxLayoutOptionMixin, ZRTextAlign, ZRTextVerticalAlign, ZRColor, BorderOptionMixin, LabelOption, ECElement } from '../util/types';
+import ComponentView from '../view/Component';
+import GlobalModel from '../model/Global';
+import ExtensionAPI from '../ExtensionAPI';
+
+export interface TitleOption extends ComponentOption, BoxLayoutOptionMixin, BorderOptionMixin {
+    type?: 'title'
+
+    text?: string
+    /**
+     * Link to url
+     */
+    link?: string
+    target?: 'self' | 'blank'
+
+    subtext?: string
+    sublink?: string
+    subtarget?: 'self' | 'blank'
+
+    textAlign?: ZRTextAlign
+    textVerticalAlign?: ZRTextVerticalAlign
+
+    /**
+     * @deprecated Use textVerticalAlign instead
+     */
+    textBaseline?: ZRTextVerticalAlign
+
+    backgroundColor?: ZRColor
+    /**
+     * Padding between text and border.
+     * Support to be a single number or an array.
+     */
+    padding?: number | number[]
+    /**
+     * Gap between text and subtext
+     */
+    itemGap?: number
+
+    textStyle?: LabelOption
+
+    subtextStyle?: LabelOption
+
+    /**
+     * If trigger mouse or touch event
+     */
+    triggerEvent?: boolean
+
+    /**
+     * Radius of background border.
+     */
+    borderRadius?: number | number[]
+}
+class TitleModel extends ComponentModel<TitleOption> {
+    static type = 'title' as const
+    type = TitleModel.type
+
+    readonly layoutMode = {type: 'box', ignoreSize: true} as const
+
+    static defaultOption: TitleOption = {
         zlevel: 0,
-        // 二级层叠
         z: 6,
         show: true,
 
         text: '',
-        // 超链接跳转
-        // link: null,
-        // 仅支持self | blank
         target: 'blank',
         subtext: '',
 
-        // 超链接跳转
-        // sublink: null,
-        // 仅支持self | blank
         subtarget: 'blank',
 
-        // 'center' ¦ 'left' ¦ 'right'
-        // ¦ {number}(x坐标,单位px)
         left: 0,
-        // 'top' ¦ 'bottom' ¦ 'center'
-        // ¦ {number}(y坐标,单位px)
         top: 0,
 
-        // 水平对齐
-        // 'auto' | 'left' | 'right' | 'center'
-        // 默认根据 left 的位置判断是左对齐还是右对齐
-        // textAlign: null
-        //
-        // 垂直对齐
-        // 'auto' | 'top' | 'bottom' | 'middle'
-        // 默认根据 top 位置判断是上对齐还是下对齐
-        // textVerticalAlign: null
-        // textBaseline: null // The same as textVerticalAlign.
-
         backgroundColor: 'rgba(0,0,0,0)',
 
-        // 标题边框颜色
         borderColor: '#ccc',
 
-        // 标题边框线宽,单位px,默认为0(无边框)
         borderWidth: 0,
 
-        // 标题内边距,单位px,默认各方向内边距为5,
-        // 接受数组分别设定上右下左边距,同css
         padding: 5,
 
-        // 主副标题纵向间隔,单位px,默认为10,
         itemGap: 10,
         textStyle: {
             fontSize: 18,
@@ -91,14 +111,18 @@ echarts.extendComponentModel({
             color: '#aaa'
         }
     }
-});
+}
+ComponentModel.registerClass(TitleModel);
+
 
 // View
-echarts.extendComponentView({
+class TitleView extends ComponentView {
 
-    type: 'title',
+    static type = 'title' as const
+    type = TitleView.type
 
-    render: function (titleModel, ecModel, api) {
+
+    render(titleModel: TitleModel, ecModel: GlobalModel, api: ExtensionAPI) {
         this.group.removeAll();
 
         if (!titleModel.get('show')) {
@@ -154,7 +178,7 @@ echarts.extendComponentView({
             });
         }
 
-        textEl.eventData = subTextEl.eventData = triggerEvent
+        (textEl as ECElement).eventData = (subTextEl as ECElement).eventData = triggerEvent
             ? {
                 componentType: 'title',
                 componentIndex: titleModel.componentIndex
@@ -178,7 +202,7 @@ echarts.extendComponentView({
         // Adjust text align based on position
         if (!textAlign) {
             // Align left if title is on the left. center and right is same
-            textAlign = titleModel.get('left') || titleModel.get('right');
+            textAlign = (titleModel.get('left') || titleModel.get('right')) as ZRTextAlign;
             if (textAlign === 'middle') {
                 textAlign = 'center';
             }
@@ -191,7 +215,7 @@ echarts.extendComponentView({
             }
         }
         if (!textVerticalAlign) {
-            textVerticalAlign = titleModel.get('top') || titleModel.get('bottom');
+            textVerticalAlign = (titleModel.get('top') || titleModel.get('bottom')) as ZRTextVerticalAlign;
             if (textVerticalAlign === 'center') {
                 textVerticalAlign = 'middle';
             }
@@ -234,4 +258,6 @@ echarts.extendComponentView({
 
         group.add(rect);
     }
-});
\ No newline at end of file
+}
+
+ComponentView.registerClass(TitleView);
\ No newline at end of file
diff --git a/src/component/visualMap/VisualMapModel.ts b/src/component/visualMap/VisualMapModel.ts
index c2a9eb7..6776890 100644
--- a/src/component/visualMap/VisualMapModel.ts
+++ b/src/component/visualMap/VisualMapModel.ts
@@ -158,7 +158,7 @@ class VisualMapModel<Opts extends VisualMapOption = VisualMapOption> extends Com
     static type = 'visualMap'
     type = VisualMapModel.type
 
-    readonly dependencies = ['series']
+    static readonly dependencies = ['series']
 
     readonly stateList = ['inRange', 'outOfRange'] as const
 
diff --git a/src/util/graphic.ts b/src/util/graphic.ts
index 70b6439..58ccb1b 100644
--- a/src/util/graphic.ts
+++ b/src/util/graphic.ts
@@ -1160,10 +1160,10 @@ export function getFont(opt: LabelOption, ecModel: GlobalModel) {
 
 function animateOrSetProps<Props>(
     isUpdate: boolean,
-    el: Displayable<Props>,
+    el: Element<Props>,
     props: Props,
     animatableModel?: Model<AnimationOptionMixin> & {
-        getAnimationDelayParams?: (el: Displayable<Props>, dataIndex: number) => AnimationDelayCallbackParam
+        getAnimationDelayParams?: (el: Element<Props>, dataIndex: number) => AnimationDelayCallbackParam
     },
     dataIndex?: number,
     cb?: () => void
@@ -1227,7 +1227,7 @@ function animateOrSetProps<Props>(
  *     }, seriesModel, function () { console.log('Animation done!'); });
  */
 export function updateProps<Props>(
-    el: Displayable<Props>,
+    el: Element<Props>,
     props: Props,
     // TODO: TYPE AnimatableModel
     animatableModel?: Model<AnimationOptionMixin>,
@@ -1246,7 +1246,7 @@ export function updateProps<Props>(
  * animation starts, unless you know what you are doing.
  */
 export function initProps<Props>(
-    el: Displayable<Props>,
+    el: Element<Props>,
     props: Props,
     animatableModel?: Model<AnimationOptionMixin>,
     dataIndex?: number,
diff --git a/src/util/types.ts b/src/util/types.ts
index 05f16a9..26c249c 100644
--- a/src/util/types.ts
+++ b/src/util/types.ts
@@ -34,7 +34,7 @@ import SeriesModel from '../model/Series';
 import { createHashMap, HashMap } from 'zrender/src/core/util';
 import { TaskPlanCallbackReturn, TaskProgressParams } from '../stream/task';
 import List, {ListDimensionType} from '../data/List';
-import { Dictionary, ImageLike } from 'zrender/src/core/types';
+import { Dictionary, ImageLike, TextAlign, TextVerticalAlign } from 'zrender/src/core/types';
 import { PatternObject } from 'zrender/src/graphic/Pattern';
 import Source from '../data/Source';
 import { TooltipMarker } from './format';
@@ -50,6 +50,7 @@ import { RadialGradientObject } from 'zrender/src/graphic/RadialGradient';
 
 export type RendererType = 'canvas' | 'svg';
 
+// Types from zrender
 export type ColorString = string;
 export type ZRColor = ColorString | LinearGradientObject | RadialGradientObject | PatternObject
 export type ZRLineType = 'solid' | 'dotted'
@@ -61,6 +62,9 @@ export type ZRFontWeight = 'normal' | 'bold' | 'bolder' | 'lighter' | number
 
 export type ZREasing = easingType
 
+export type ZRTextAlign = TextAlign
+export type ZRTextVerticalAlign = TextVerticalAlign
+
 
 // ComponentFullType can be:
 //     'xxx.yyy': means ComponentMainType.ComponentSubType.


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