You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by su...@apache.org on 2020/02/16 21:28:08 UTC

[incubator-echarts] 06/09: Migrate to TS -- Part I (some of the basic class and utils) Note: lots of the common type are put in `src/util/types.ts`.

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

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

commit 3229e88e2310d673d52c49c14b8605c85f0860b1
Author: 100pah <su...@gmail.com>
AuthorDate: Mon Feb 17 01:31:44 2020 +0800

    Migrate to TS -- Part I (some of the basic class and utils)
    Note: lots of the common type are put in `src/util/types.ts`.
---
 index.d.ts                                    |    1 +
 src/CoordinateSystem.ts                       |   43 +-
 src/ExtensionAPI.ts                           |   50 +-
 src/chart/custom.ts                           |    8 +-
 src/chart/graph/GraphSeries.ts                |    2 +-
 src/chart/helper/createListFromArray.ts       |    6 +-
 src/chart/helper/createRenderPlanner.ts       |   10 +-
 src/component/dataZoom/dataZoomProcessor.ts   |    5 +-
 src/component/dataset.ts                      |    4 +-
 src/component/helper/BrushController.ts       |    4 +-
 src/component/helper/RoamController.ts        |    4 +-
 src/component/marker/MarkerModel.ts           |    6 +-
 src/component/timeline/SliderTimelineModel.ts |    6 +-
 src/config.ts                                 |    4 +-
 src/coord/CoordinateSystem.ts                 |   86 +
 src/coord/ICoordinateSystem                   |   85 -
 src/coord/View.ts                             |    4 +-
 src/coord/geo/mapDataStorage.ts               |   87 +-
 src/data/DataDiffer.ts                        |  138 +-
 src/data/DataDimensionInfo.ts                 |   70 +-
 src/data/List.ts                              | 3430 +++++++++++++------------
 src/data/OrdinalMeta.ts                       |  178 +-
 src/data/Source.ts                            |  106 +-
 src/data/helper/completeDimensions.ts         |    6 +-
 src/data/helper/dataProvider.ts               |  493 ++--
 src/data/helper/dimensionHelper.ts            |   55 +-
 src/data/helper/sourceHelper.ts               |    8 +-
 src/data/helper/sourceType.ts                 |   30 -
 src/echarts.ts                                | 3352 ++++++++++++------------
 src/export.ts                                 |    4 +-
 src/loading/default.ts                        |   16 +-
 src/model/Component.ts                        |  222 +-
 src/model/Global.ts                           |  563 ++--
 src/model/Model.ts                            |  183 +-
 src/model/OptionManager.ts                    |  212 +-
 src/model/Series.ts                           |  307 +--
 src/model/mixin/colorPalette.ts               |   33 +-
 src/model/mixin/dataFormat.ts                 |  114 +-
 src/processor/dataSample.ts                   |    5 +-
 src/stream/Scheduler.ts                       |  915 ++++---
 src/stream/task.ts                            |  507 ++--
 src/util/ECEventProcessor.ts                  |  152 ++
 src/util/clazz.ts                             |  265 +-
 src/util/component.ts                         |  122 +-
 src/util/format.ts                            |   56 +-
 src/util/graphic.ts                           |    4 +-
 src/util/model.ts                             |  189 +-
 src/util/throttle.ts                          |    4 +-
 src/util/types.ts                             |  446 ++++
 src/view/Chart.ts                             |  241 +-
 src/view/Component.ts                         |   93 +-
 src/visual/LegendVisualProvider.ts            |   47 +-
 src/visual/dataColor.ts                       |    5 +-
 53 files changed, 7125 insertions(+), 5861 deletions(-)

diff --git a/index.d.ts b/index.d.ts
new file mode 100644
index 0000000..aa892ea
--- /dev/null
+++ b/index.d.ts
@@ -0,0 +1 @@
+export * from './echarts.all';
\ No newline at end of file
diff --git a/src/CoordinateSystem.ts b/src/CoordinateSystem.ts
index cec2173..6d75c47 100644
--- a/src/CoordinateSystem.ts
+++ b/src/CoordinateSystem.ts
@@ -18,45 +18,44 @@
 */
 
 import * as zrUtil from 'zrender/src/core/util';
+import GlobalModel from './model/Global';
+import ExtensionAPI from './ExtensionAPI';
+import { CoordinateSystemCreator, CoordinateSystem } from './coord/CoordinateSystem';
 
-var coordinateSystemCreators = {};
+var coordinateSystemCreators: {[type: string]: CoordinateSystemCreator} = {};
 
-function CoordinateSystemManager() {
+class CoordinateSystemManager {
 
-    this._coordinateSystems = [];
-}
-
-CoordinateSystemManager.prototype = {
-
-    constructor: CoordinateSystemManager,
+    private _coordinateSystems: CoordinateSystem[] = [];
 
-    create: function (ecModel, api) {
-        var coordinateSystems = [];
+    create(ecModel: GlobalModel, api: ExtensionAPI): void {
+        var coordinateSystems: CoordinateSystem[] = [];
         zrUtil.each(coordinateSystemCreators, function (creater, type) {
             var list = creater.create(ecModel, api);
             coordinateSystems = coordinateSystems.concat(list || []);
         });
 
         this._coordinateSystems = coordinateSystems;
-    },
+    }
 
-    update: function (ecModel, api) {
+    update(ecModel: GlobalModel, api: ExtensionAPI): void {
         zrUtil.each(this._coordinateSystems, function (coordSys) {
             coordSys.update && coordSys.update(ecModel, api);
         });
-    },
+    }
 
-    getCoordinateSystems: function () {
+    getCoordinateSystems(): CoordinateSystem[] {
         return this._coordinateSystems.slice();
     }
-};
 
-CoordinateSystemManager.register = function (type, coordinateSystemCreator) {
-    coordinateSystemCreators[type] = coordinateSystemCreator;
-};
+    static register = function (type: string, creator: CoordinateSystemCreator): void {
+        coordinateSystemCreators[type] = creator;
+    }
+
+    static get = function (type: string): CoordinateSystemCreator {
+        return coordinateSystemCreators[type];
+    }
 
-CoordinateSystemManager.get = function (type) {
-    return coordinateSystemCreators[type];
-};
+}
 
-export default CoordinateSystemManager;
\ No newline at end of file
+export default CoordinateSystemManager;
diff --git a/src/ExtensionAPI.ts b/src/ExtensionAPI.ts
index 80b552e..41fcd5f 100644
--- a/src/ExtensionAPI.ts
+++ b/src/ExtensionAPI.ts
@@ -18,18 +18,44 @@
 */
 
 import * as zrUtil from 'zrender/src/core/util';
+import {EChartsType} from './echarts';
+import {CoordinateSystem} from './coord/CoordinateSystem';
+import Element from 'zrender/src/Element';
+import ComponentModel from './model/Component';
 
-var echartsAPIList = [
-    'getDom', 'getZr', 'getWidth', 'getHeight', 'getDevicePixelRatio', 'dispatchAction', 'isDisposed',
-    'on', 'off', 'getDataURL', 'getConnectedDataURL', 'getModel', 'getOption',
-    'getViewOfComponentModel', 'getViewOfSeriesModel'
-];
-// And `getCoordinateSystems` and `getComponentByElement` will be injected in echarts.js
-
-function ExtensionAPI(chartInstance) {
-    zrUtil.each(echartsAPIList, function (name) {
-        this[name] = zrUtil.bind(chartInstance[name], chartInstance);
-    }, this);
+var availableMethods = {
+    getDom: 1,
+    getZr: 1,
+    getWidth: 1,
+    getHeight: 1,
+    getDevicePixelRatio: 1,
+    dispatchAction: 1,
+    isDisposed: 1,
+    on: 1,
+    off: 1,
+    getDataURL: 1,
+    getConnectedDataURL: 1,
+    getModel: 1,
+    getOption: 1,
+    getViewOfComponentModel: 1,
+    getViewOfSeriesModel: 1
+};
+
+interface ExtensionAPI extends Pick<EChartsType, keyof typeof availableMethods> {}
+
+abstract class ExtensionAPI {
+
+    constructor(ecInstance: EChartsType) {
+        zrUtil.each(availableMethods, function (v, name: string) {
+            (this as any)[name] = zrUtil.bind((ecInstance as any)[name], ecInstance);
+        }, this);
+    }
+
+    // Implemented in echarts.js
+    abstract getCoordinateSystems(): CoordinateSystem[];
+
+    // Implemented in echarts.js
+    abstract getComponentByElement(el: Element): ComponentModel;
 }
 
-export default ExtensionAPI;
\ No newline at end of file
+export default ExtensionAPI;
diff --git a/src/chart/custom.ts b/src/chart/custom.ts
index c1dd8be..726d2ac 100644
--- a/src/chart/custom.ts
+++ b/src/chart/custom.ts
@@ -17,6 +17,8 @@
 * under the License.
 */
 
+// @ts-nocheck
+
 import {__DEV__} from '../config';
 import * as zrUtil from 'zrender/src/core/util';
 import * as graphicUtil from '../util/graphic';
@@ -28,6 +30,8 @@ import SeriesModel from '../model/Series';
 import Model from '../model/Model';
 import ChartView from '../view/Chart';
 import {createClipPath} from './helper/createClipPathFromCoordSys';
+import {EventQueryItem, ECEvent} from '../util/types';
+import Element from 'zrender/src/Element';
 
 import prepareCartesian2d from '../coord/cartesian/prepareCustom';
 import prepareGeo from '../coord/geo/prepareCustom';
@@ -208,7 +212,9 @@ ChartView.extend({
     /**
      * @override
      */
-    filterForExposedEvent: function (eventType, query, targetEl, packedEvent) {
+    filterForExposedEvent: function (
+        eventType: string, query: EventQueryItem, targetEl: Element, packedEvent: ECEvent
+    ): boolean {
         var elementName = query.element;
         if (elementName == null || targetEl.name === elementName) {
             return true;
diff --git a/src/chart/graph/GraphSeries.ts b/src/chart/graph/GraphSeries.ts
index a0b5334..4cd1339 100644
--- a/src/chart/graph/GraphSeries.ts
+++ b/src/chart/graph/GraphSeries.ts
@@ -59,7 +59,7 @@ var GraphSeries = echarts.extendSeriesModel({
 
     mergeDefaultAndTheme: function (option) {
         GraphSeries.superApply(this, 'mergeDefaultAndTheme', arguments);
-        defaultEmphasis(option, ['edgeLabel'], ['show']);
+        defaultEmphasis(option, 'edgeLabel', ['show']);
     },
 
     getInitialData: function (option, ecModel) {
diff --git a/src/chart/helper/createListFromArray.ts b/src/chart/helper/createListFromArray.ts
index 2b6cadc..690d973 100644
--- a/src/chart/helper/createListFromArray.ts
+++ b/src/chart/helper/createListFromArray.ts
@@ -17,10 +17,11 @@
 * under the License.
 */
 
+// @ts-nocheck
+
 import * as zrUtil from 'zrender/src/core/util';
 import List from '../../data/List';
 import createDimensions from '../../data/helper/createDimensions';
-import {SOURCE_FORMAT_ORIGINAL} from '../../data/helper/sourceType';
 import {getDimensionTypeByAxis} from '../../data/helper/dimensionHelper';
 import {getDataItemValue} from '../../util/model';
 import CoordinateSystem from '../../CoordinateSystem';
@@ -28,6 +29,7 @@ import {getCoordSysInfoBySeries} from '../../model/referHelper';
 import Source from '../../data/Source';
 import {enableDataStack} from '../../data/helper/dataStackHelper';
 import {makeSeriesEncodeForAxisCoordSys} from '../../data/helper/sourceHelper';
+import { SOURCE_FORMAT_ORIGINAL } from '../../util/types';
 
 /**
  * @param {module:echarts/data/Source|Array} source Or raw data.
@@ -120,7 +122,7 @@ function createListFromArray(source, seriesModel, opt) {
     return list;
 }
 
-function isNeedCompleteOrdinalData(source) {
+function isNeedCompleteOrdinalData(source: Source) {
     if (source.sourceFormat === SOURCE_FORMAT_ORIGINAL) {
         var sampleItem = firstDataNotNull(source.data || []);
         return sampleItem != null
diff --git a/src/chart/helper/createRenderPlanner.ts b/src/chart/helper/createRenderPlanner.ts
index 0fc5041..ed7bc87 100644
--- a/src/chart/helper/createRenderPlanner.ts
+++ b/src/chart/helper/createRenderPlanner.ts
@@ -17,7 +17,11 @@
 * under the License.
 */
 
+// @ts-nocheck
+
 import {makeInner} from '../../util/model';
+import SeriesModel from '../../model/Series';
+import { StageHandlerPlanReturn, StageHandlerPlan } from '../../util/types';
 
 /**
  * @return {string} If large mode changed, return string 'reset';
@@ -25,7 +29,7 @@ import {makeInner} from '../../util/model';
 export default function () {
     var inner = makeInner();
 
-    return function (seriesModel) {
+    return function (seriesModel: SeriesModel): StageHandlerPlanReturn {
         var fields = inner(seriesModel);
         var pipelineContext = seriesModel.pipelineContext;
 
@@ -38,6 +42,8 @@ export default function () {
         var large = fields.large = pipelineContext && pipelineContext.large;
         var progressive = fields.progressiveRender = pipelineContext && pipelineContext.progressiveRender;
 
-        return !!((originalLarge ^ large) || (originalProgressive ^ progressive)) && 'reset';
+        return (
+            !!((originalLarge ^ large as any) || (originalProgressive ^ progressive as any)) && 'reset'
+        ) as StageHandlerPlanReturn;
     };
 }
diff --git a/src/component/dataZoom/dataZoomProcessor.ts b/src/component/dataZoom/dataZoomProcessor.ts
index cfd1406..c58e8b9 100644
--- a/src/component/dataZoom/dataZoomProcessor.ts
+++ b/src/component/dataZoom/dataZoomProcessor.ts
@@ -17,6 +17,8 @@
 * under the License.
 */
 
+// @ts-nocheck
+
 import * as echarts from '../../echarts';
 import {createHashMap, each} from 'zrender/src/core/util';
 
@@ -40,7 +42,8 @@ echarts.registerProcessor({
         return seriesModelMap;
     },
 
-    modifyOutputEnd: true,
+    // FIXME:TS never used, so comment it
+    // modifyOutputEnd: true,
 
     // Consider appendData, where filter should be performed. Because data process is
     // in block mode currently, it is not need to worry about that the overallProgress
diff --git a/src/component/dataset.ts b/src/component/dataset.ts
index 18f2022..dbae472 100644
--- a/src/component/dataset.ts
+++ b/src/component/dataset.ts
@@ -17,6 +17,8 @@
 * under the License.
 */
 
+// @ts-nocheck
+
 /**
  * This module is imported by echarts directly.
  *
@@ -29,7 +31,7 @@
 import ComponentModel from '../model/Component';
 import ComponentView from '../view/Component';
 import {detectSourceFormat} from '../data/helper/sourceHelper';
-import {SERIES_LAYOUT_BY_COLUMN} from '../data/helper/sourceType';
+import { SERIES_LAYOUT_BY_COLUMN } from '../util/types';
 
 ComponentModel.extend({
 
diff --git a/src/component/helper/BrushController.ts b/src/component/helper/BrushController.ts
index c8fb8cd..92d5c47 100644
--- a/src/component/helper/BrushController.ts
+++ b/src/component/helper/BrushController.ts
@@ -17,9 +17,11 @@
 * under the License.
 */
 
+// @ts-nocheck
+
 import {__DEV__} from '../../config';
 import * as zrUtil from 'zrender/src/core/util';
-import Eventful from 'zrender/src/mixin/Eventful';
+import Eventful from 'zrender/src/core/Eventful';
 import * as graphic from '../../util/graphic';
 import * as interactionMutex from './interactionMutex';
 import DataDiffer from '../../data/DataDiffer';
diff --git a/src/component/helper/RoamController.ts b/src/component/helper/RoamController.ts
index 2124837..1b1f00d 100644
--- a/src/component/helper/RoamController.ts
+++ b/src/component/helper/RoamController.ts
@@ -17,8 +17,10 @@
 * under the License.
 */
 
+// @ts-nocheck
+
 import * as zrUtil from 'zrender/src/core/util';
-import Eventful from 'zrender/src/mixin/Eventful';
+import Eventful from 'zrender/src/core/Eventful';
 import * as eventTool from 'zrender/src/core/event';
 import * as interactionMutex from './interactionMutex';
 
diff --git a/src/component/marker/MarkerModel.ts b/src/component/marker/MarkerModel.ts
index 8c3a314..d7269bc 100644
--- a/src/component/marker/MarkerModel.ts
+++ b/src/component/marker/MarkerModel.ts
@@ -17,13 +17,15 @@
 * under the License.
 */
 
+// @ts-nocheck
+
 import {__DEV__} from '../../config';
 import * as echarts from '../../echarts';
 import * as zrUtil from 'zrender/src/core/util';
 import env from 'zrender/src/core/env';
 import * as modelUtil from '../../util/model';
 import * as formatUtil from '../../util/format';
-import dataFormatMixin from '../../model/mixin/dataFormat';
+import DataFormatMixin from '../../model/mixin/dataFormat';
 
 var addCommas = formatUtil.addCommas;
 var encodeHTML = formatUtil.encodeHTML;
@@ -152,6 +154,6 @@ var MarkerModel = echarts.extendComponentModel({
     }
 });
 
-zrUtil.mixin(MarkerModel, dataFormatMixin);
+zrUtil.mixin(MarkerModel, DataFormatMixin.prototype);
 
 export default MarkerModel;
\ No newline at end of file
diff --git a/src/component/timeline/SliderTimelineModel.ts b/src/component/timeline/SliderTimelineModel.ts
index a70bab3..517ad43 100644
--- a/src/component/timeline/SliderTimelineModel.ts
+++ b/src/component/timeline/SliderTimelineModel.ts
@@ -17,9 +17,11 @@
 * under the License.
 */
 
+// @ts-nocheck
+
 import * as zrUtil from 'zrender/src/core/util';
 import TimelineModel from './TimelineModel';
-import dataFormatMixin from '../../model/mixin/dataFormat';
+import DataFormatMixin from '../../model/mixin/dataFormat';
 
 var SliderTimelineModel = TimelineModel.extend({
 
@@ -117,6 +119,6 @@ var SliderTimelineModel = TimelineModel.extend({
 
 });
 
-zrUtil.mixin(SliderTimelineModel, dataFormatMixin);
+zrUtil.mixin(SliderTimelineModel, DataFormatMixin.prototype);
 
 export default SliderTimelineModel;
\ No newline at end of file
diff --git a/src/config.ts b/src/config.ts
index 06283ed..430f86e 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -27,11 +27,11 @@ var dev;
 
 // In browser
 if (typeof window !== 'undefined') {
-    dev = window.__DEV__;
+    dev = (window as any).__DEV__;
 }
 // In node
 else if (typeof global !== 'undefined') {
-    dev = global.__DEV__;
+    dev = (global as any).__DEV__;
 }
 
 if (typeof dev === 'undefined') {
diff --git a/src/coord/CoordinateSystem.ts b/src/coord/CoordinateSystem.ts
new file mode 100644
index 0000000..6b98390
--- /dev/null
+++ b/src/coord/CoordinateSystem.ts
@@ -0,0 +1,86 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*   http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied.  See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+// @ts-nocheck
+
+import Model from '../model/Model';
+import GlobalModel from '../model/Global';
+import {ModelFinder} from '../util/model';
+import ExtensionAPI from '../ExtensionAPI';
+import { DimensionDefinitionLoose } from '../util/types';
+
+export interface CoordinateSystemCreator {
+
+    create: (ecModel: GlobalModel, api: ExtensionAPI) => CoordinateSystem;
+
+    // FIXME current dimensions must be string[].
+    // check and unify the definition.
+    dimensions: string[];
+
+    // dimensionsInfo like [{name: ..., type: ...}, 'xxx', ...]
+    getDimensionsInfo?: () => DimensionDefinitionLoose[];
+
+}
+
+export interface CoordinateSystem {
+
+    // FIXME current dimensions must be string[].
+    // check and unify the definition.
+    dimensions: string[];
+
+    model: Model;
+
+    update: (ecModel: GlobalModel, api: ExtensionAPI) => void;
+
+    // @return {module:echarts/coord/Axis}
+    getAxis: (dim: string) => any; // FIXME:TS temp any
+
+    // @return {Array.<module:echarts/coord/Axis>}
+    getAxes?: () => [] // FIXME:TS temp any
+
+    axisPointerEnabled?: () => boolean;
+
+    // @param {*|Array.<*>} data
+    // @param {*} Defined by the coordinate system itself
+    // @param {Array.<*>} out
+    // @return {Array.<number>} point Point in global pixel coordinate system.
+    dataToPoint: (...args) => number[];
+
+    // @param {Array.<number>} point Point in global pixel coordinate system.
+    // @param {*} Defined by the coordinate system itself
+    // @param {Array.<*>} out
+    // @return {*|Array.<*>} data
+    pointToData: (...args) => any;
+
+    // @param point Point in global pixel coordinate system.
+    containPoint: (point: number[]) => boolean;
+
+    // This methods is also responsible for determine whether this
+    // coodinate system is applicable to the given `finder`.
+    // Each coordinate system will be tried, util one returns none
+    // null/undefined value.
+    convertToPixel: (ecModel: any, finder: ModelFinder, value: any) => number | number[];
+
+    // This methods is also responsible for determine whether this
+    // coodinate system is applicable to the given `finder`.
+    // Each coordinate system will be tried, util one returns none
+    // null/undefined value.
+    convertFromPixel: (ecModel: any, finder: ModelFinder, pixelValue: number | number[]) => any;
+
+}
\ No newline at end of file
diff --git a/src/coord/ICoordinateSystem b/src/coord/ICoordinateSystem
deleted file mode 100644
index 592c85d..0000000
--- a/src/coord/ICoordinateSystem
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
-* Licensed to the Apache Software Foundation (ASF) under one
-* or more contributor license agreements.  See the NOTICE file
-* distributed with this work for additional information
-* regarding copyright ownership.  The ASF licenses this file
-* to you under the Apache License, Version 2.0 (the
-* "License"); you may not use this file except in compliance
-* with the License.  You may obtain a copy of the License at
-*
-*   http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing,
-* software distributed under the License is distributed on an
-* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-* KIND, either express or implied.  See the License for the
-* specific language governing permissions and limitations
-* under the License.
-*/
-
-/**
- * Coordinate System Interface:
- *
- *
- * Class members:
- *
- *  + dimensions {Array.<strign>}: mandatory
- *
- *
- * Instance members:
- *
- *  + dimensions {Array.<strign>}: mandatory
- *
- *  + model {module:echarts/model/Model}: mandatory
- *
- *  + create: mandatory
- *     @param {module:echarts/model/Global} ecModel
- *     @param {module:echarts/ExtensionAPI} api
- *     @return {Object} coordinate system instance
- *
- *  + update: mandatory
- *     @param {module:echarts/model/Global} ecModel
- *     @param {module:echarts/ExtensionAPI} api
- *
- *  + getAxis {Function}: mandatory
- *      @param {string} dim
- *      @return {module:echarts/coord/Axis}
- *
- *  + getAxes: {Function}: optional
- *      @return {Array.<module:echarts/coord/Axis>}
- *
- *  + axisPointerEnabled {boolean}
- *
- *  + dataToPoint {Function}: mandatory
- *      @param {*|Array.<*>} data
- *      @param {*} Defined by the coordinate system itself
- *      @param {Array.<*>} out
- *      @return {Array.<number>} point Point in global pixel coordinate system.
- *
- *  + pointToData {Function}: mandatory
- *      @param {Array.<number>} point Point in global pixel coordinate system.
- *      @param {*} Defined by the coordinate system itself
- *      @param {Array.<*>} out
- *      @return {*|Array.<*>} data
- *
- *  + containPoint {Function}: mandatory
- *      @param {Array.<number>} point Point in global pixel coordinate system.
- *      @return {boolean}
- *
- *  + getDimensionsInfo {Function}: optional
- *      @return {Array.<string|Object>} dimensionsInfo
- *              Like [{name: ..., type: ...}, 'xxx', ...]
- *
- *  + convertToPixel:
- *  + convertFromPixel:
- *        These two methods is also responsible for determine whether this
- *        coodinate system is applicable to the given `finder`.
- *        Each coordinate system will be tried, util one returns none
- *        null/undefined value.
- *        @param {module:echarts/model/Global} ecModel
- *        @param {Object} finder
- *        @param {Array|number} value
- *        @return {Array|number} convert result.
- *
- *
- */
diff --git a/src/coord/View.ts b/src/coord/View.ts
index 4043107..be65472 100644
--- a/src/coord/View.ts
+++ b/src/coord/View.ts
@@ -17,6 +17,8 @@
 * under the License.
 */
 
+// @ts-nocheck
+
 /**
  * Simple view coordinate system
  * Mapping given x, y to transformd view x, y
@@ -26,7 +28,7 @@ import * as zrUtil from 'zrender/src/core/util';
 import * as vector from 'zrender/src/core/vector';
 import * as matrix from 'zrender/src/core/matrix';
 import BoundingRect from 'zrender/src/core/BoundingRect';
-import Transformable from 'zrender/src/mixin/Transformable';
+import Transformable from 'zrender/src/core/Transformable';
 
 var v2ApplyTransform = vector.applyTransform;
 
diff --git a/src/coord/geo/mapDataStorage.ts b/src/coord/geo/mapDataStorage.ts
index 8f6de57..54be749 100644
--- a/src/coord/geo/mapDataStorage.ts
+++ b/src/coord/geo/mapDataStorage.ts
@@ -17,39 +17,94 @@
 * under the License.
 */
 
+// @ts-nocheck
+
 import {__DEV__} from '../../config';
 import {createHashMap, isString, isArray, each, assert} from 'zrender/src/core/util';
 import {parseXML} from 'zrender/src/tool/parseSVG';
 
-
-var storage = createHashMap();
-
 // For minimize the code size of common echarts package,
 // do not put too much logic in this module.
 
+export type GeoMapSVGSource = 'string' | Document;
+export type GeoMapGeoJSONSource = 'string' | object;
+export type GeoSpecialAreas = object;
+
+interface GeoMapGeoJSONDefinition {
+    geoJSON?: GeoMapGeoJSONSource;
+    geoJson?: GeoMapGeoJSONSource;
+    specialAreas?: GeoSpecialAreas;
+}
+interface GeoMapSVGDefinition {
+    svg?: GeoMapSVGSource;
+    specialAreas?: GeoSpecialAreas;
+}
+export type GeoMapDefinition = GeoMapGeoJSONDefinition | GeoMapSVGDefinition;
+
+interface GeoMapRecord {
+    type: 'geoJSON' | 'svg';
+    source: GeoMapGeoJSONSource | GeoMapSVGSource;
+    specialAreas: GeoSpecialAreas;
+    geoJSON: object;
+    svgXML: Node
+}
+
+
+var storage = createHashMap<GeoMapRecord[]>();
+
+
 export default {
 
-    // The format of record: see `echarts.registerMap`.
-    // Compatible with previous `echarts.registerMap`.
-    registerMap: function (mapName, rawGeoJson, rawSpecialAreas) {
+    /**
+     * Compatible with previous `echarts.registerMap`.
+     * @usage
+     * ```js
+     * $.get('USA.json', function (geoJson) {
+     *     echarts.registerMap('USA', geoJson);
+     *     // Or
+     *     echarts.registerMap('USA', {
+     *         geoJson: geoJson,
+     *         specialAreas: {}
+     *     })
+     * });
+     *
+     * $.get('airport.svg', function (svg) {
+     *     echarts.registerMap('airport', {
+     *         svg: svg
+     *     }
+     * });
+     *
+     * echarts.registerMap('eu', [
+     *     {svg: eu-topographic.svg},
+     *     {geoJSON: eu.json}
+     * ])
+     * ```
+     */
+    registerMap: function (
+        mapName: string,
+        rawGeoJson: GeoMapDefinition | GeoMapDefinition[] | GeoMapGeoJSONSource,
+        rawSpecialAreas?: GeoSpecialAreas
+    ): GeoMapRecord[] {
 
         var records;
 
         if (isArray(rawGeoJson)) {
             records = rawGeoJson;
         }
-        else if (rawGeoJson.svg) {
+        else if ((rawGeoJson as GeoMapSVGDefinition).svg) {
             records = [{
                 type: 'svg',
-                source: rawGeoJson.svg,
-                specialAreas: rawGeoJson.specialAreas
+                source: (rawGeoJson as GeoMapSVGDefinition).svg,
+                specialAreas: (rawGeoJson as GeoMapSVGDefinition).specialAreas
             }];
         }
         else {
             // Backward compatibility.
-            if (rawGeoJson.geoJson && !rawGeoJson.features) {
-                rawSpecialAreas = rawGeoJson.specialAreas;
-                rawGeoJson = rawGeoJson.geoJson;
+            var geoSource = (rawGeoJson as GeoMapGeoJSONDefinition).geoJson
+                || (rawGeoJson as GeoMapGeoJSONDefinition).geoJSON;
+            if (geoSource && !(rawGeoJson as any).features) {
+                rawSpecialAreas = (rawGeoJson as GeoMapGeoJSONDefinition).specialAreas;
+                rawGeoJson = geoSource;
             }
             records = [{
                 type: 'geoJSON',
@@ -74,7 +129,7 @@ export default {
         return storage.set(mapName, records);
     },
 
-    retrieveMap: function (mapName) {
+    retrieveMap: function (mapName: string): GeoMapRecord[] {
         return storage.get(mapName);
     }
 
@@ -82,7 +137,7 @@ export default {
 
 var parsers = {
 
-    geoJSON: function (record) {
+    geoJSON: function (record: GeoMapRecord): void {
         var source = record.source;
         record.geoJSON = !isString(source)
             ? source
@@ -97,8 +152,8 @@ var parsers = {
     // if we do it here, the clone of zrender elements has to be
     // required. So we do it once for each geo instance, util real
     // performance issues call for optimizing it.
-    svg: function (record) {
-        record.svgXML = parseXML(record.source);
+    svg: function (record: GeoMapRecord): void {
+        record.svgXML = parseXML(record.source as GeoMapSVGSource);
     }
 
 };
diff --git a/src/data/DataDiffer.ts b/src/data/DataDiffer.ts
index 27e66d8..76c75dc 100644
--- a/src/data/DataDiffer.ts
+++ b/src/data/DataDiffer.ts
@@ -17,68 +17,88 @@
 * under the License.
 */
 
+import {ArrayLike} from 'zrender/src/core/types';
 
-function defaultKeyGetter(item) {
+// return key.
+export type DiffKeyGetter = (this: DataDiffer, value: any, index: number) => string;
+export type DiffCallbackAdd = (newIndex: number) => void
+export type DiffCallbackUpdate = (newIndex: number, oldIndex: number) => void
+export type DiffCallbackRemove = (oldIndex: number) => void
+
+type DataIndexMap = {[key: string]: number | number[]};
+
+function defaultKeyGetter<T>(item: T): T {
     return item;
 }
 
-/**
- * @param {Array} oldArr
- * @param {Array} newArr
- * @param {Function} oldKeyGetter
- * @param {Function} newKeyGetter
- * @param {Object} [context] Can be visited by this.context in callback.
- */
-function DataDiffer(oldArr, newArr, oldKeyGetter, newKeyGetter, context) {
-    this._old = oldArr;
-    this._new = newArr;
-
-    this._oldKeyGetter = oldKeyGetter || defaultKeyGetter;
-    this._newKeyGetter = newKeyGetter || defaultKeyGetter;
-
-    this.context = context;
-}
+class DataDiffer {
+
+    private _old: ArrayLike<any>;
+    private _new: ArrayLike<any>;
+    private _oldKeyGetter: DiffKeyGetter;
+    private _newKeyGetter: DiffKeyGetter;
+    private _add: DiffCallbackAdd;
+    private _update: DiffCallbackUpdate;
+    private _remove: DiffCallbackRemove;
 
-DataDiffer.prototype = {
+    readonly context: any;
 
-    constructor: DataDiffer,
+    /**
+     * @param context Can be visited by this.context in callback.
+     */
+    constructor(
+        oldArr: ArrayLike<any>,
+        newArr: ArrayLike<any>,
+        oldKeyGetter: DiffKeyGetter,
+        newKeyGetter: DiffKeyGetter,
+        context?: any
+    ) {
+        this._old = oldArr;
+        this._new = newArr;
+
+        this._oldKeyGetter = oldKeyGetter || defaultKeyGetter;
+        this._newKeyGetter = newKeyGetter || defaultKeyGetter;
+
+        // Visible in callback via `this.context`;
+        this.context = context;
+    }
 
     /**
      * Callback function when add a data
      */
-    add: function (func) {
+    add(func: DiffCallbackAdd): DataDiffer {
         this._add = func;
         return this;
-    },
+    }
 
     /**
      * Callback function when update a data
      */
-    update: function (func) {
+    update(func: DiffCallbackUpdate): DataDiffer {
         this._update = func;
         return this;
-    },
+    }
 
     /**
      * Callback function when remove a data
      */
-    remove: function (func) {
+    remove(func: DiffCallbackRemove): DataDiffer {
         this._remove = func;
         return this;
-    },
+    }
 
-    execute: function () {
+    execute(): void {
         var oldArr = this._old;
         var newArr = this._new;
 
-        var oldDataIndexMap = {};
-        var newDataIndexMap = {};
-        var oldDataKeyArr = [];
-        var newDataKeyArr = [];
+        var oldDataIndexMap: DataIndexMap = {};
+        var newDataIndexMap: DataIndexMap = {};
+        var oldDataKeyArr: string[] = [];
+        var newDataKeyArr: string[] = [];
         var i;
 
-        initIndexMap(oldArr, oldDataIndexMap, oldDataKeyArr, '_oldKeyGetter', this);
-        initIndexMap(newArr, newDataIndexMap, newDataKeyArr, '_newKeyGetter', this);
+        this._initIndexMap(oldArr, oldDataIndexMap, oldDataKeyArr, '_oldKeyGetter');
+        this._initIndexMap(newArr, newDataIndexMap, newDataKeyArr, '_newKeyGetter');
 
         for (i = 0; i < oldArr.length; i++) {
             var key = oldDataKeyArr[i];
@@ -88,22 +108,22 @@ DataDiffer.prototype = {
             if (idx != null) {
                 // Consider there is duplicate key (for example, use dataItem.name as key).
                 // We should make sure every item in newArr and oldArr can be visited.
-                var len = idx.length;
+                var len = (idx as number[]).length;
                 if (len) {
                     len === 1 && (newDataIndexMap[key] = null);
-                    idx = idx.shift();
+                    idx = (idx as number[]).shift();
                 }
                 else {
                     newDataIndexMap[key] = null;
                 }
-                this._update && this._update(idx, i);
+                this._update && this._update(idx as number, i);
             }
             else {
                 this._remove && this._remove(i);
             }
         }
 
-        for (var i = 0; i < newDataKeyArr.length; i++) {
+        for (i = 0; i < newDataKeyArr.length; i++) {
             var key = newDataKeyArr[i];
             if (newDataIndexMap.hasOwnProperty(key)) {
                 var idx = newDataIndexMap[key];
@@ -111,35 +131,41 @@ DataDiffer.prototype = {
                     continue;
                 }
                 // idx can never be empty array here. see 'set null' logic above.
-                if (!idx.length) {
-                    this._add && this._add(idx);
+                if (!(idx as number[]).length) {
+                    this._add && this._add(idx as number);
                 }
                 else {
-                    for (var j = 0, len = idx.length; j < len; j++) {
-                        this._add && this._add(idx[j]);
+                    for (var j = 0, len = (idx as number[]).length; j < len; j++) {
+                        this._add && this._add((idx as number[])[j]);
                     }
                 }
             }
         }
     }
-};
-
-function initIndexMap(arr, map, keyArr, keyGetterName, dataDiffer) {
-    for (var i = 0; i < arr.length; i++) {
-        // Add prefix to avoid conflict with Object.prototype.
-        var key = '_ec_' + dataDiffer[keyGetterName](arr[i], i);
-        var existence = map[key];
-        if (existence == null) {
-            keyArr.push(key);
-            map[key] = i;
-        }
-        else {
-            if (!existence.length) {
-                map[key] = existence = [existence];
+
+    private _initIndexMap(
+        arr: ArrayLike<any>,
+        map: DataIndexMap,
+        keyArr: string[],
+        keyGetterName: '_oldKeyGetter' | '_newKeyGetter'
+    ): void {
+        for (var i = 0; i < arr.length; i++) {
+            // Add prefix to avoid conflict with Object.prototype.
+            var key = '_ec_' + this[keyGetterName](arr[i], i);
+            var existence = map[key];
+            if (existence == null) {
+                keyArr.push(key);
+                map[key] = i;
+            }
+            else {
+                if (!(existence as number[]).length) {
+                    map[key] = existence = [existence as number];
+                }
+                (existence as number[]).push(i);
             }
-            existence.push(i);
         }
     }
+
 }
 
-export default DataDiffer;
\ No newline at end of file
+export default DataDiffer;
diff --git a/src/data/DataDimensionInfo.ts b/src/data/DataDimensionInfo.ts
index 3bda37d..f27a5b4 100644
--- a/src/data/DataDimensionInfo.ts
+++ b/src/data/DataDimensionInfo.ts
@@ -18,30 +18,33 @@
 */
 
 import * as zrUtil from 'zrender/src/core/util';
+import OrdinalMeta from './OrdinalMeta';
+import { DataVisualDimensions, DimensionType } from '../util/types';
 
-/**
- * @class
- * @param {Object|DataDimensionInfo} [opt] All of the fields will be shallow copied.
- */
-function DataDimensionInfo(opt) {
-    if (opt != null) {
-        zrUtil.extend(this, opt);
-    }
+class DataDimensionInfo {
+
+    /**
+     * Dimension type. The enumerable values are the key of
+     * `dataCtors` of `data/List`.
+     * Optional.
+     */
+    type: DimensionType;
 
     /**
      * Dimension name.
      * Mandatory.
-     * @type {string}
      */
-    // this.name;
-
+    name: string;
     /**
      * The origin name in dimsDef, see source helper.
      * If displayName given, the tooltip will displayed vertically.
      * Optional.
-     * @type {string}
      */
-    // this.displayName;
+    displayName: string;
+
+    // FIXME: check whether it is still used.
+    // See Series.ts#formatArrayValue
+    tooltip?: boolean
 
     /**
      * Which coordSys dimension this dimension mapped to.
@@ -50,31 +53,20 @@ function DataDimensionInfo(opt) {
      * or an generated "extra coord name" if does not mapped to any "coordSysDim"
      * (That is determined by whether `isExtraCoord` is `true`).
      * Mandatory.
-     * @type {string}
      */
-    // this.coordDim;
+    coordDim: string;
 
     /**
      * The index of this dimension in `series.encode[coordDim]`.
      * Mandatory.
-     * @type {number}
      */
-    // this.coordDimIndex;
-
-    /**
-     * Dimension type. The enumerable values are the key of
-     * `dataCtors` of `data/List`.
-     * Optional.
-     * @type {string}
-     */
-    // this.type;
+    coordDimIndex: number;
 
     /**
      * This index of this dimension info in `data/List#_dimensionInfos`.
      * Mandatory after added to `data/List`.
-     * @type {number}
      */
-    // this.index;
+    index: number;
 
     /**
      * The format of `otherDims` is:
@@ -108,28 +100,34 @@ function DataDimensionInfo(opt) {
      * ```
      *
      * This prop should never be `null`/`undefined` after initialized.
-     * @type {Object}
      */
-    this.otherDims = {};
+    otherDims: DataVisualDimensions = {};
 
     /**
      * Be `true` if this dimension is not mapped to any "coordSysDim" that the
      * "coordSys" required.
      * Mandatory.
-     * @type {boolean}
      */
-    // this.isExtraCoord;
+    isExtraCoord: boolean;
+
+    defaultTooltip: boolean;
+
+    ordinalMeta: OrdinalMeta;
 
     /**
-     * @type {module:data/OrdinalMeta}
+     * Whether to create inverted indices.
      */
-    // this.ordinalMeta;
+    createInvertedIndices: boolean;
 
     /**
-     * Whether to create inverted indices.
-     * @type {boolean}
+     * @param opt All of the fields will be shallow copied.
      */
-    // this.createInvertedIndices;
+    constructor(opt: object | DataDimensionInfo) {
+        if (opt != null) {
+            zrUtil.extend(this, opt);
+        }
+    }
+
 };
 
 export default DataDimensionInfo;
diff --git a/src/data/List.ts b/src/data/List.ts
index 7ec46e6..f9d7ac1 100644
--- a/src/data/List.ts
+++ b/src/data/List.ts
@@ -28,10 +28,19 @@ import {__DEV__} from '../config';
 import * as zrUtil from 'zrender/src/core/util';
 import Model from '../model/Model';
 import DataDiffer from './DataDiffer';
-import Source from './Source';
-import {defaultDimValueGetters, DefaultDataProvider} from './helper/dataProvider';
-import {summarizeDimensions} from './helper/dimensionHelper';
+import Source, { SourceConstructor } from './Source';
+import {DefaultDataProvider, DataProvider} from './helper/dataProvider';
+import {summarizeDimensions, DimensionSummary, DimensionUserOuput} from './helper/dimensionHelper';
 import DataDimensionInfo from './DataDimensionInfo';
+import {ArrayLike, Dictionary, FunctionPropertyNames} from 'zrender/src/core/types';
+import Element from 'zrender/src/Element';
+import {
+    DimensionIndex, DimensionName, ECElement, DimensionLoose, OptionDataItem,
+    ParsedDataValue, ParsedDataNumeric, OrdinalRawValueIndex
+} from '../util/types';
+import {parseDate} from '../util/number';
+import {isDataItemOption} from '../util/model';
+
 
 var isObject = zrUtil.isObject;
 
@@ -53,22 +62,54 @@ var dataCtors = {
     'time': Array
 };
 
+export type ListDimensionType = keyof typeof dataCtors;
+
 // Caution: MUST not use `new CtorUint32Array(arr, 0, len)`, because the Ctor of array is
 // different from the Ctor of typed array.
 var CtorUint32Array = typeof Uint32Array === UNDEFINED ? Array : Uint32Array;
 var CtorInt32Array = typeof Int32Array === UNDEFINED ? Array : Int32Array;
 var CtorUint16Array = typeof Uint16Array === UNDEFINED ? Array : Uint16Array;
 
-function getIndicesCtor(list) {
-    // The possible max value in this._indicies is always this._rawCount despite of filtering.
-    return list._rawCount > 65535 ? CtorUint32Array : CtorUint16Array;
-}
+type DataTypedArray = Uint32Array | Int32Array | Uint16Array | Float64Array;
+type DataTypedArrayConstructor = typeof Uint32Array | typeof Int32Array | typeof Uint16Array | typeof Float64Array;
+type DataArrayLikeConstructor = typeof Array | DataTypedArrayConstructor;
+
+
+
+type DimValueGetter = (
+    this: List,
+    dataItem: any,
+    dimName: DimensionName,
+    dataIndex: number,
+    dimIndex: DimensionIndex
+) => ParsedDataValue;
+
+type DataValueChunk = ArrayLike<ParsedDataValue>;
+type DataStorage = {[dimName: string]: DataValueChunk[]};
+type NameRepeatCount = {[name: string]: number};
+
+
+type ItrParamDims = DimensionLoose | Array<DimensionLoose>;
+// If Ctx not specified, use List as Ctx
+type CtxOrList<Ctx> = unknown extends Ctx ? List : Ctx;
+type EachCb0<Ctx> = (this: CtxOrList<Ctx>, idx: number) => void;
+type EachCb1<Ctx> = (this: CtxOrList<Ctx>, x: ParsedDataValue, idx: number) => void;
+type EachCb2<Ctx> = (this: CtxOrList<Ctx>, x: ParsedDataValue, y: ParsedDataValue, idx: number) => void;
+type EachCb<Ctx> = (this: CtxOrList<Ctx>, ...args: any) => void;
+type FilterCb0<Ctx> = (this: CtxOrList<Ctx>, idx: number) => boolean;
+type FilterCb1<Ctx> = (this: CtxOrList<Ctx>, x: ParsedDataValue, idx: number) => boolean;
+type FilterCb2<Ctx> = (this: CtxOrList<Ctx>, x: ParsedDataValue, y: ParsedDataValue, idx: number) => boolean;
+type FilterCb<Ctx> = (this: CtxOrList<Ctx>, ...args: any) => boolean;
+type MapArrayCb0<Ctx> = (this: CtxOrList<Ctx>, idx: number) => any;
+type MapArrayCb1<Ctx> = (this: CtxOrList<Ctx>, x: ParsedDataValue, idx: number) => any;
+type MapArrayCb2<Ctx> = (this: CtxOrList<Ctx>, x: ParsedDataValue, y: ParsedDataValue, idx: number) => any;
+type MapArrayCb<Ctx> = (this: CtxOrList<Ctx>, ...args: any) => any;
+type MapCb1<Ctx> = (this: CtxOrList<Ctx>, x: ParsedDataValue, idx: number) => ParsedDataValue | ParsedDataValue[];
+type MapCb2<Ctx> = (this: CtxOrList<Ctx>, x: ParsedDataValue, y: ParsedDataValue, idx: number) =>
+    ParsedDataValue | ParsedDataValue[];
+type MapCb<Ctx> = (this: CtxOrList<Ctx>, ...args: any) => ParsedDataValue | ParsedDataValue[];
+
 
-function cloneChunk(originalChunk) {
-    var Ctor = originalChunk.constructor;
-    // Only shallow clone is enough when Array.
-    return Ctor === Array ? originalChunk.slice() : new Ctor(originalChunk);
-}
 
 var TRANSFERABLE_PROPERTIES = [
     'hasItemOption', '_nameList', '_idList', '_invertedIndicesMap',
@@ -79,1960 +120,2005 @@ var CLONE_PROPERTIES = [
     '_extent', '_approximateExtent', '_rawExtent'
 ];
 
-function transferProperties(target, source) {
-    zrUtil.each(TRANSFERABLE_PROPERTIES.concat(source.__wrappedMethods || []), function (propName) {
-        if (source.hasOwnProperty(propName)) {
-            target[propName] = source[propName];
-        }
-    });
 
-    target.__wrappedMethods = source.__wrappedMethods;
 
-    zrUtil.each(CLONE_PROPERTIES, function (propName) {
-        target[propName] = zrUtil.clone(source[propName]);
-    });
+class List {
 
-    target._calculationInfo = zrUtil.extend(source._calculationInfo);
-}
+    readonly type = 'list';
 
+    readonly dimensions: string[];
 
+    // Infomation of each data dimension, like data type.
+    private _dimensionInfos: {[dimName: string]: DataDimensionInfo};
 
+    readonly hostModel: Model;
 
+    readonly dataType: string;
 
-/**
- * @constructor
- * @alias module:echarts/data/List
- *
- * @param {Array.<string|Object|module:data/DataDimensionInfo>} dimensions
- *      For example, ['someDimName', {name: 'someDimName', type: 'someDimType'}, ...].
- *      Dimensions should be concrete names like x, y, z, lng, lat, angle, radius
- * @param {module:echarts/model/Model} hostModel
- */
-var List = function (dimensions, hostModel) {
+    // Indices stores the indices of data subset after filtered.
+    // This data subset will be used in chart.
+    private _indices: ArrayLike<any>;
 
-    dimensions = dimensions || ['x', 'y'];
+    private _count: number = 0;
+    private _rawCount: number = 0;
+    private _storage: DataStorage = {};
+    private _nameList: string[] = [];
+    private _idList: string[] = [];
 
-    var dimensionInfos = {};
-    var dimensionNames = [];
-    var invertedIndicesMap = {};
+    // Models of data option is stored sparse for optimizing memory cost
+    // Never used yet (not used yet).
+    // private _optionModels: Model[] = [];
 
-    for (var i = 0; i < dimensions.length; i++) {
-        // Use the original dimensions[i], where other flag props may exists.
-        var dimensionInfo = dimensions[i];
+    // Global visual properties after visual coding
+    private _visual: Dictionary<any> = {};
 
-        if (zrUtil.isString(dimensionInfo)) {
-            dimensionInfo = new DataDimensionInfo({name: dimensionInfo});
-        }
-        else if (!(dimensionInfo instanceof DataDimensionInfo)) {
-            dimensionInfo = new DataDimensionInfo(dimensionInfo);
-        }
+    // Globel layout properties.
+    private _layout: Dictionary<any> = {};
 
-        var dimensionName = dimensionInfo.name;
-        dimensionInfo.type = dimensionInfo.type || 'float';
-        if (!dimensionInfo.coordDim) {
-            dimensionInfo.coordDim = dimensionName;
-            dimensionInfo.coordDimIndex = 0;
-        }
+    // Item visual properties after visual coding
+    private _itemVisuals: Dictionary<any>[] = [];
 
-        dimensionInfo.otherDims = dimensionInfo.otherDims || {};
-        dimensionNames.push(dimensionName);
-        dimensionInfos[dimensionName] = dimensionInfo;
+    // Key: visual type, Value: boolean
+    // @readonly
+    hasItemVisual: Dictionary<boolean> = {};
 
-        dimensionInfo.index = i;
+    // Item layout properties after layout
+    private _itemLayouts: any[] = [];
 
-        if (dimensionInfo.createInvertedIndices) {
-            invertedIndicesMap[dimensionName] = [];
-        }
-    }
+    // Graphic elemnents
+    private _graphicEls: Element[] = [];
 
-    /**
-     * @readOnly
-     * @type {Array.<string>}
-     */
-    this.dimensions = dimensionNames;
+    // Max size of each chunk.
+    private _chunkSize: number = 1e5;
 
-    /**
-     * Infomation of each data dimension, like data type.
-     * @type {Object}
-     */
-    this._dimensionInfos = dimensionInfos;
+    private _chunkCount: number = 0;
 
-    /**
-     * @type {module:echarts/model/Model}
-     */
-    this.hostModel = hostModel;
+    private _rawData: DataProvider;
 
-    /**
-     * @type {module:echarts/model/Model}
-     */
-    this.dataType;
+    // Raw extent will not be cloned, but only transfered.
+    // It will not be calculated util needed.
+    private _rawExtent: {[dimName: string]: [number, number]} = {};
 
-    /**
-     * Indices stores the indices of data subset after filtered.
-     * This data subset will be used in chart.
-     * @type {Array.<number>}
-     * @readOnly
-     */
-    this._indices = null;
+    private _extent: {[dimName: string]: [number, number]} = {};
 
-    this._count = 0;
-    this._rawCount = 0;
+    // key: dim, value: extent
+    private _approximateExtent: {[dimName: string]: [number, number]} = {};
 
-    /**
-     * Data storage
-     * @type {Object.<key, Array.<TypedArray|Array>>}
-     * @private
-     */
-    this._storage = {};
+    private _dimensionsSummary: DimensionSummary;
 
-    /**
-     * @type {Array.<string>}
-     */
-    this._nameList = [];
-    /**
-     * @type {Array.<string>}
-     */
-    this._idList = [];
+    private _invertedIndicesMap: {[dimName: string]: ArrayLike<number>};
 
-    /**
-     * Models of data option is stored sparse for optimizing memory cost
-     * @type {Array.<module:echarts/model/Model>}
-     * @private
-     */
-    this._optionModels = [];
+    private _calculationInfo: {[key: string]: any} = {};
 
-    /**
-     * Global visual properties after visual coding
-     * @type {Object}
-     * @private
-     */
-    this._visual = {};
+    // User output info of this data.
+    // DO NOT use it in other places!
+    // When preparing user params for user callbacks, we have
+    // to clone these inner data structures to prevent users
+    // from modifying them to effect built-in logic. And for
+    // performance consideration we make this `userOutput` to
+    // avoid clone them too many times.
+    readonly userOutput: DimensionUserOuput;
 
-    /**
-     * Globel layout properties.
-     * @type {Object}
-     * @private
-     */
-    this._layout = {};
+    // If each data item has it's own option
+    hasItemOption: boolean = true;
 
-    /**
-     * Item visual properties after visual coding
-     * @type {Array.<Object>}
-     * @private
-     */
-    this._itemVisuals = [];
+    // @readonly
+    defaultDimValueGetter: DimValueGetter;
+    private _dimValueGetter: DimValueGetter;
+    private _dimValueGetterArrayRows: DimValueGetter;
 
-    /**
-     * Key: visual type, Value: boolean
-     * @type {Object}
-     * @readOnly
-     */
-    this.hasItemVisual = {};
+    private _nameRepeatCount: NameRepeatCount;
+    private _nameDimIdx: number;
+    private _idDimIdx: number;
 
-    /**
-     * Item layout properties after layout
-     * @type {Array.<Object>}
-     * @private
-     */
-    this._itemLayouts = [];
+    private __wrappedMethods: string[];
 
-    /**
-     * Graphic elemnents
-     * @type {Array.<module:zrender/Element>}
-     * @private
-     */
-    this._graphicEls = [];
+    // Methods that create a new list based on this list should be listed here.
+    // Notice that those method should `RETURN` the new list.
+    TRANSFERABLE_METHODS = ['cloneShallow', 'downSample', 'map'];
+    // Methods that change indices of this list should be listed here.
+    CHANGABLE_METHODS = ['filterSelf', 'selectRange'];
 
-    /**
-     * Max size of each chunk.
-     * @type {number}
-     * @private
-     */
-    this._chunkSize = 1e5;
 
     /**
-     * @type {number}
-     * @private
+     * @param dimensions
+     *        For example, ['someDimName', {name: 'someDimName', type: 'someDimType'}, ...].
+     *        Dimensions should be concrete names like x, y, z, lng, lat, angle, radius
      */
-    this._chunkCount = 0;
+    constructor(dimensions: Array<string | object | DataDimensionInfo>, hostModel: Model) {
+        dimensions = dimensions || ['x', 'y'];
+
+        var dimensionInfos: Dictionary<DataDimensionInfo> = {};
+        var dimensionNames = [];
+        var invertedIndicesMap: Dictionary<number[]> = {};
+
+        for (var i = 0; i < dimensions.length; i++) {
+            // Use the original dimensions[i], where other flag props may exists.
+            var dimInfoInput = dimensions[i];
+
+            var dimensionInfo: DataDimensionInfo =
+                zrUtil.isString(dimInfoInput)
+                ? new DataDimensionInfo({name: dimInfoInput})
+                : !(dimInfoInput instanceof DataDimensionInfo)
+                ? new DataDimensionInfo(dimInfoInput)
+                : dimInfoInput;
+
+            var dimensionName = dimensionInfo.name;
+            dimensionInfo.type = dimensionInfo.type || 'float';
+            if (!dimensionInfo.coordDim) {
+                dimensionInfo.coordDim = dimensionName;
+                dimensionInfo.coordDimIndex = 0;
+            }
 
-    /**
-     * @type {Array.<Array|Object>}
-     * @private
-     */
-    this._rawData;
+            dimensionInfo.otherDims = dimensionInfo.otherDims || {};
+            dimensionNames.push(dimensionName);
+            dimensionInfos[dimensionName] = dimensionInfo;
 
-    /**
-     * Raw extent will not be cloned, but only transfered.
-     * It will not be calculated util needed.
-     * key: dim,
-     * value: {end: number, extent: Array.<number>}
-     * @type {Object}
-     * @private
-     */
-    this._rawExtent = {};
+            dimensionInfo.index = i;
 
-    /**
-     * @type {Object}
-     * @private
-     */
-    this._extent = {};
+            if (dimensionInfo.createInvertedIndices) {
+                invertedIndicesMap[dimensionName] = [];
+            }
+        }
 
-    /**
-     * key: dim
-     * value: extent
-     * @type {Object}
-     * @private
-     */
-    this._approximateExtent = {};
+        this.dimensions = dimensionNames;
+        this._dimensionInfos = dimensionInfos;
+        this.hostModel = hostModel;
+
+        // Cache summary info for fast visit. See "dimensionHelper".
+        this._dimensionsSummary = summarizeDimensions(this);
+
+        this._invertedIndicesMap = invertedIndicesMap;
+
+        this.userOutput = this._dimensionsSummary.userOutput;
+    }
 
     /**
-     * Cache summary info for fast visit. See "dimensionHelper".
-     * @type {Object}
-     * @private
+     * The meanings of the input parameter `dim`:
+     *
+     * + If dim is a number (e.g., `1`), it means the index of the dimension.
+     *   For example, `getDimension(0)` will return 'x' or 'lng' or 'radius'.
+     * + If dim is a number-like string (e.g., `"1"`):
+     *     + If there is the same concrete dim name defined in `this.dimensions`, it means that concrete name.
+     *     + If not, it will be converted to a number, which means the index of the dimension.
+     *        (why? because of the backward compatbility. We have been tolerating number-like string in
+     *        dimension setting, although now it seems that it is not a good idea.)
+     *     For example, `visualMap[i].dimension: "1"` is the same meaning as `visualMap[i].dimension: 1`,
+     *     if no dimension name is defined as `"1"`.
+     * + If dim is a not-number-like string, it means the concrete dim name.
+     *   For example, it can be be default name `"x"`, `"y"`, `"z"`, `"lng"`, `"lat"`, `"angle"`, `"radius"`,
+     *   or customized in `dimensions` property of option like `"age"`.
+     *
+     * Get dimension name
+     * @param dim See above.
+     * @return Concrete dim name.
      */
-    this._dimensionsSummary = summarizeDimensions(this);
+    getDimension(dim: DimensionLoose): DimensionName {
+        if (typeof dim === 'number'
+            // If being a number-like string but not being defined a dimension name.
+            || (!isNaN(dim as any) && !this._dimensionInfos.hasOwnProperty(dim))
+        ) {
+            dim = this.dimensions[dim as DimensionIndex];
+        }
+        return dim as DimensionName;
+    }
 
     /**
-     * @type {Object.<Array|TypedArray>}
-     * @private
+     * Get type and calculation info of particular dimension
+     * @param dim
+     *        Dimension can be concrete names like x, y, z, lng, lat, angle, radius
+     *        Or a ordinal number. For example getDimensionInfo(0) will return 'x' or 'lng' or 'radius'
      */
-    this._invertedIndicesMap = invertedIndicesMap;
+    getDimensionInfo(dim: DimensionLoose): DataDimensionInfo {
+        // Do not clone, because there may be categories in dimInfo.
+        return this._dimensionInfos[this.getDimension(dim)];
+    }
 
     /**
-     * @type {Object}
-     * @private
+     * concrete dimension name list on coord.
      */
-    this._calculationInfo = {};
+    getDimensionsOnCoord(): DimensionName[] {
+        return this._dimensionsSummary.dataDimsOnCoord.slice();
+    }
 
     /**
-     * User output info of this data.
-     * DO NOT use it in other places!
-     *
-     * When preparing user params for user callbacks, we have
-     * to clone these inner data structures to prevent users
-     * from modifying them to effect built-in logic. And for
-     * performance consideration we make this `userOutput` to
-     * avoid clone them too many times.
-     *
-     * @type {Object}
-     * @readOnly
+     * @param coordDim
+     * @param idx A coordDim may map to more than one data dim.
+     *        If idx is `true`, return a array of all mapped dims.
+     *        If idx is not specified, return the first dim not extra.
+     * @return concrete data dim.
+     *        If idx is number, and not found, return null/undefined.
+     *        If idx is `true`, and not found, return empty array (always return array).
      */
-    this.userOutput = this._dimensionsSummary.userOutput;
-};
-
-var listProto = List.prototype;
-
-listProto.type = 'list';
-
-/**
- * If each data item has it's own option
- * @type {boolean}
- */
-listProto.hasItemOption = true;
+    mapDimension<Idx extends (number | true)>(
+        coordDim: DimensionName,
+        idx?: Idx
+    ): (true extends Idx ? DimensionName[] : DimensionName) {
+        var dimensionsSummary = this._dimensionsSummary;
+
+        if (idx == null) {
+            return dimensionsSummary.encodeFirstDimNotExtra[coordDim] as any;
+        }
 
-/**
- * The meanings of the input parameter `dim`:
- *
- * + If dim is a number (e.g., `1`), it means the index of the dimension.
- *   For example, `getDimension(0)` will return 'x' or 'lng' or 'radius'.
- * + If dim is a number-like string (e.g., `"1"`):
- *     + If there is the same concrete dim name defined in `this.dimensions`, it means that concrete name.
- *     + If not, it will be converted to a number, which means the index of the dimension.
- *        (why? because of the backward compatbility. We have been tolerating number-like string in
- *        dimension setting, although now it seems that it is not a good idea.)
- *     For example, `visualMap[i].dimension: "1"` is the same meaning as `visualMap[i].dimension: 1`,
- *     if no dimension name is defined as `"1"`.
- * + If dim is a not-number-like string, it means the concrete dim name.
- *   For example, it can be be default name `"x"`, `"y"`, `"z"`, `"lng"`, `"lat"`, `"angle"`, `"radius"`,
- *   or customized in `dimensions` property of option like `"age"`.
- *
- * Get dimension name
- * @param {string|number} dim See above.
- * @return {string} Concrete dim name.
- */
-listProto.getDimension = function (dim) {
-    if (typeof dim === 'number'
-        // If being a number-like string but not being defined a dimension name.
-        || (!isNaN(dim) && !this._dimensionInfos.hasOwnProperty(dim))
-    ) {
-        dim = this.dimensions[dim];
+        var dims = dimensionsSummary.encode[coordDim];
+        return idx === true
+            // always return array if idx is `true`
+            ? (dims || []).slice()
+            : (dims ? dims[idx as number] as any : null);
     }
-    return dim;
-};
 
-/**
- * Get type and calculation info of particular dimension
- * @param {string|number} dim
- *        Dimension can be concrete names like x, y, z, lng, lat, angle, radius
- *        Or a ordinal number. For example getDimensionInfo(0) will return 'x' or 'lng' or 'radius'
- */
-listProto.getDimensionInfo = function (dim) {
-    // Do not clone, because there may be categories in dimInfo.
-    return this._dimensionInfos[this.getDimension(dim)];
-};
+    /**
+     * Initialize from data
+     * @param data source or data or data provider.
+     * @param nameLIst The name of a datum is used on data diff and
+     *        defualt label/tooltip.
+     *        A name can be specified in encode.itemName,
+     *        or dataItem.name (only for series option data),
+     *        or provided in nameList from outside.
+     */
+    initData(
+        data: any,
+        nameList?: string[],
+        dimValueGetter?: DimValueGetter
+    ): void {
+
+        var notProvider = (Source as SourceConstructor).isInstance(data)
+            || zrUtil.isArrayLike(data);
+        if (notProvider) {
+            data = new DefaultDataProvider(data, this.dimensions.length);
+        }
 
-/**
- * @return {Array.<string>} concrete dimension name list on coord.
- */
-listProto.getDimensionsOnCoord = function () {
-    return this._dimensionsSummary.dataDimsOnCoord.slice();
-};
+        if (__DEV__) {
+            if (!notProvider
+                && (typeof data.getItem !== 'function' || typeof data.count !== 'function')
+            ) {
+                throw new Error('Inavlid data provider.');
+            }
+        }
 
-/**
- * @param {string} coordDim
- * @param {number} [idx] A coordDim may map to more than one data dim.
- *        If idx is `true`, return a array of all mapped dims.
- *        If idx is not specified, return the first dim not extra.
- * @return {string|Array.<string>} concrete data dim.
- *        If idx is number, and not found, return null/undefined.
- *        If idx is `true`, and not found, return empty array (always return array).
- */
-listProto.mapDimension = function (coordDim, idx) {
-    var dimensionsSummary = this._dimensionsSummary;
+        this._rawData = data;
 
-    if (idx == null) {
-        return dimensionsSummary.encodeFirstDimNotExtra[coordDim];
-    }
+        // Clear
+        this._storage = {};
+        this._indices = null;
 
-    var dims = dimensionsSummary.encode[coordDim];
-    return idx === true
-        // always return array if idx is `true`
-        ? (dims || []).slice()
-        : (dims && dims[idx]);
-};
+        this._nameList = nameList || [];
 
-/**
- * Initialize from data
- * @param {Array.<Object|number|Array>} data source or data or data provider.
- * @param {Array.<string>} [nameLIst] The name of a datum is used on data diff and
- *        defualt label/tooltip.
- *        A name can be specified in encode.itemName,
- *        or dataItem.name (only for series option data),
- *        or provided in nameList from outside.
- * @param {Function} [dimValueGetter] (dataItem, dimName, dataIndex, dimIndex) => number
- */
-listProto.initData = function (data, nameList, dimValueGetter) {
+        this._idList = [];
 
-    var notProvider = Source.isInstance(data) || zrUtil.isArrayLike(data);
-    if (notProvider) {
-        data = new DefaultDataProvider(data, this.dimensions.length);
-    }
+        this._nameRepeatCount = {};
 
-    if (__DEV__) {
-        if (!notProvider && (typeof data.getItem !== 'function' || typeof data.count !== 'function')) {
-            throw new Error('Inavlid data provider.');
+        if (!dimValueGetter) {
+            this.hasItemOption = false;
         }
-    }
-
-    this._rawData = data;
 
-    // Clear
-    this._storage = {};
-    this._indices = null;
+        this.defaultDimValueGetter = defaultDimValueGetters[
+            this._rawData.getSource().sourceFormat
+        ];
+        // Default dim value getter
+        this._dimValueGetter = dimValueGetter = dimValueGetter
+            || this.defaultDimValueGetter;
+        this._dimValueGetterArrayRows = defaultDimValueGetters.arrayRows;
 
-    this._nameList = nameList || [];
+        // Reset raw extent.
+        this._rawExtent = {};
 
-    this._idList = [];
+        this._initDataFromProvider(0, data.count());
 
-    this._nameRepeatCount = {};
+        // If data has no item option.
+        if (data.pure) {
+            this.hasItemOption = false;
+        }
+    }
 
-    if (!dimValueGetter) {
-        this.hasItemOption = false;
+    getProvider(): DataProvider {
+        return this._rawData;
     }
 
     /**
-     * @readOnly
+     * Caution: Can be only called on raw data (before `this._indices` created).
      */
-    this.defaultDimValueGetter = defaultDimValueGetters[
-        this._rawData.getSource().sourceFormat
-    ];
-    // Default dim value getter
-    this._dimValueGetter = dimValueGetter = dimValueGetter
-        || this.defaultDimValueGetter;
-    this._dimValueGetterArrayRows = defaultDimValueGetters.arrayRows;
-
-    // Reset raw extent.
-    this._rawExtent = {};
-
-    this._initDataFromProvider(0, data.count());
-
-    // If data has no item option.
-    if (data.pure) {
-        this.hasItemOption = false;
-    }
-};
-
-listProto.getProvider = function () {
-    return this._rawData;
-};
-
-/**
- * Caution: Can be only called on raw data (before `this._indices` created).
- */
-listProto.appendData = function (data) {
-    if (__DEV__) {
-        zrUtil.assert(!this._indices, 'appendData can only be called on raw data.');
-    }
+    appendData(data: ArrayLike<any>): void {
+        if (__DEV__) {
+            zrUtil.assert(!this._indices, 'appendData can only be called on raw data.');
+        }
 
-    var rawData = this._rawData;
-    var start = this.count();
-    rawData.appendData(data);
-    var end = rawData.count();
-    if (!rawData.persistent) {
-        end += start;
+        var rawData = this._rawData;
+        var start = this.count();
+        rawData.appendData(data);
+        var end = rawData.count();
+        if (!rawData.persistent) {
+            end += start;
+        }
+        this._initDataFromProvider(start, end);
     }
-    this._initDataFromProvider(start, end);
-};
 
-/**
- * Caution: Can be only called on raw data (before `this._indices` created).
- * This method does not modify `rawData` (`dataProvider`), but only
- * add values to storage.
- *
- * The final count will be increased by `Math.max(values.length, names.length)`.
- *
- * @param {Array.<Array.<*>>} values That is the SourceType: 'arrayRows', like
- *        [
- *            [12, 33, 44],
- *            [NaN, 43, 1],
- *            ['-', 'asdf', 0]
- *        ]
- *        Each item is exaclty cooresponding to a dimension.
- * @param {Array.<string>} [names]
- */
-listProto.appendValues = function (values, names) {
-    var chunkSize = this._chunkSize;
-    var storage = this._storage;
-    var dimensions = this.dimensions;
-    var dimLen = dimensions.length;
-    var rawExtent = this._rawExtent;
-
-    var start = this.count();
-    var end = start + Math.max(values.length, names ? names.length : 0);
-    var originalChunkCount = this._chunkCount;
-
-    for (var i = 0; i < dimLen; i++) {
-        var dim = dimensions[i];
-        if (!rawExtent[dim]) {
-            rawExtent[dim] = getInitialExtent();
-        }
-        if (!storage[dim]) {
-            storage[dim] = [];
+    /**
+     * Caution: Can be only called on raw data (before `this._indices` created).
+     * This method does not modify `rawData` (`dataProvider`), but only
+     * add values to storage.
+     *
+     * The final count will be increased by `Math.max(values.length, names.length)`.
+     *
+     * @param values That is the SourceType: 'arrayRows', like
+     *        [
+     *            [12, 33, 44],
+     *            [NaN, 43, 1],
+     *            ['-', 'asdf', 0]
+     *        ]
+     *        Each item is exaclty cooresponding to a dimension.
+     */
+    appendValues(values: any[][], names?: string[]): void {
+        var chunkSize = this._chunkSize;
+        var storage = this._storage;
+        var dimensions = this.dimensions;
+        var dimLen = dimensions.length;
+        var rawExtent = this._rawExtent;
+
+        var start = this.count();
+        var end = start + Math.max(values.length, names ? names.length : 0);
+        var originalChunkCount = this._chunkCount;
+
+        for (var i = 0; i < dimLen; i++) {
+            var dim = dimensions[i];
+            if (!rawExtent[dim]) {
+                rawExtent[dim] = getInitialExtent();
+            }
+            if (!storage[dim]) {
+                storage[dim] = [];
+            }
+            prepareChunks(storage, this._dimensionInfos[dim], chunkSize, originalChunkCount, end);
+            this._chunkCount = storage[dim].length;
         }
-        prepareChunks(storage, this._dimensionInfos[dim], chunkSize, originalChunkCount, end);
-        this._chunkCount = storage[dim].length;
-    }
 
-    var emptyDataItem = new Array(dimLen);
-    for (var idx = start; idx < end; idx++) {
-        var sourceIdx = idx - start;
-        var chunkIndex = Math.floor(idx / chunkSize);
-        var chunkOffset = idx % chunkSize;
-
-        // Store the data by dimensions
-        for (var k = 0; k < dimLen; k++) {
-            var dim = dimensions[k];
-            var val = this._dimValueGetterArrayRows(
-                values[sourceIdx] || emptyDataItem, dim, sourceIdx, k
-            );
-            storage[dim][chunkIndex][chunkOffset] = val;
+        var emptyDataItem = new Array(dimLen);
+        for (var idx = start; idx < end; idx++) {
+            var sourceIdx = idx - start;
+            var chunkIndex = Math.floor(idx / chunkSize);
+            var chunkOffset = idx % chunkSize;
+
+            // Store the data by dimensions
+            for (var k = 0; k < dimLen; k++) {
+                var dim = dimensions[k];
+                var val = this._dimValueGetterArrayRows(
+                    values[sourceIdx] || emptyDataItem, dim, sourceIdx, k
+                ) as ParsedDataNumeric;
+                storage[dim][chunkIndex][chunkOffset] = val;
+
+                var dimRawExtent = rawExtent[dim];
+                val < dimRawExtent[0] && (dimRawExtent[0] = val);
+                val > dimRawExtent[1] && (dimRawExtent[1] = val);
+            }
 
-            var dimRawExtent = rawExtent[dim];
-            val < dimRawExtent[0] && (dimRawExtent[0] = val);
-            val > dimRawExtent[1] && (dimRawExtent[1] = val);
+            if (names) {
+                this._nameList[idx] = names[sourceIdx];
+            }
         }
 
-        if (names) {
-            this._nameList[idx] = names[sourceIdx];
-        }
-    }
+        this._rawCount = this._count = end;
 
-    this._rawCount = this._count = end;
+        // Reset data extent
+        this._extent = {};
 
-    // Reset data extent
-    this._extent = {};
+        prepareInvertedIndex(this);
+    }
 
-    prepareInvertedIndex(this);
-};
+    private _initDataFromProvider(start: number, end: number): void {
+        // Optimize.
+        if (start >= end) {
+            return;
+        }
 
-listProto._initDataFromProvider = function (start, end) {
-    // Optimize.
-    if (start >= end) {
-        return;
-    }
+        var chunkSize = this._chunkSize;
+        var rawData = this._rawData;
+        var storage = this._storage;
+        var dimensions = this.dimensions;
+        var dimLen = dimensions.length;
+        var dimensionInfoMap = this._dimensionInfos;
+        var nameList = this._nameList;
+        var idList = this._idList;
+        var rawExtent = this._rawExtent;
+        var nameRepeatCount: NameRepeatCount = this._nameRepeatCount = {};
+        var nameDimIdx;
+
+        var originalChunkCount = this._chunkCount;
+        for (var i = 0; i < dimLen; i++) {
+            var dim = dimensions[i];
+            if (!rawExtent[dim]) {
+                rawExtent[dim] = getInitialExtent();
+            }
 
-    var chunkSize = this._chunkSize;
-    var rawData = this._rawData;
-    var storage = this._storage;
-    var dimensions = this.dimensions;
-    var dimLen = dimensions.length;
-    var dimensionInfoMap = this._dimensionInfos;
-    var nameList = this._nameList;
-    var idList = this._idList;
-    var rawExtent = this._rawExtent;
-    var nameRepeatCount = this._nameRepeatCount = {};
-    var nameDimIdx;
+            var dimInfo = dimensionInfoMap[dim];
+            if (dimInfo.otherDims.itemName === 0) {
+                nameDimIdx = this._nameDimIdx = i;
+            }
+            if (dimInfo.otherDims.itemId === 0) {
+                this._idDimIdx = i;
+            }
 
-    var originalChunkCount = this._chunkCount;
-    for (var i = 0; i < dimLen; i++) {
-        var dim = dimensions[i];
-        if (!rawExtent[dim]) {
-            rawExtent[dim] = getInitialExtent();
-        }
+            if (!storage[dim]) {
+                storage[dim] = [];
+            }
 
-        var dimInfo = dimensionInfoMap[dim];
-        if (dimInfo.otherDims.itemName === 0) {
-            nameDimIdx = this._nameDimIdx = i;
-        }
-        if (dimInfo.otherDims.itemId === 0) {
-            this._idDimIdx = i;
-        }
+            prepareChunks(storage, dimInfo, chunkSize, originalChunkCount, end);
 
-        if (!storage[dim]) {
-            storage[dim] = [];
+            this._chunkCount = storage[dim].length;
         }
 
-        prepareChunks(storage, dimInfo, chunkSize, originalChunkCount, end);
-
-        this._chunkCount = storage[dim].length;
-    }
+        var dataItem = new Array(dimLen) as OptionDataItem;
+        for (var idx = start; idx < end; idx++) {
+            // NOTICE: Try not to write things into dataItem
+            dataItem = rawData.getItem(idx, dataItem);
+            // Each data item is value
+            // [1, 2]
+            // 2
+            // Bar chart, line chart which uses category axis
+            // only gives the 'y' value. 'x' value is the indices of category
+            // Use a tempValue to normalize the value to be a (x, y) value
+            var chunkIndex = Math.floor(idx / chunkSize);
+            var chunkOffset = idx % chunkSize;
+
+            // Store the data by dimensions
+            for (var k = 0; k < dimLen; k++) {
+                var dim = dimensions[k];
+                var dimStorage = storage[dim][chunkIndex];
+                // PENDING NULL is empty or zero
+                var val = this._dimValueGetter(dataItem, dim, idx, k) as ParsedDataNumeric;
+                dimStorage[chunkOffset] = val;
+
+                var dimRawExtent = rawExtent[dim];
+                val < dimRawExtent[0] && (dimRawExtent[0] = val);
+                val > dimRawExtent[1] && (dimRawExtent[1] = val);
+            }
 
-    var dataItem = new Array(dimLen);
-    for (var idx = start; idx < end; idx++) {
-        // NOTICE: Try not to write things into dataItem
-        dataItem = rawData.getItem(idx, dataItem);
-        // Each data item is value
-        // [1, 2]
-        // 2
-        // Bar chart, line chart which uses category axis
-        // only gives the 'y' value. 'x' value is the indices of category
-        // Use a tempValue to normalize the value to be a (x, y) value
-        var chunkIndex = Math.floor(idx / chunkSize);
-        var chunkOffset = idx % chunkSize;
-
-        // Store the data by dimensions
-        for (var k = 0; k < dimLen; k++) {
-            var dim = dimensions[k];
-            var dimStorage = storage[dim][chunkIndex];
-            // PENDING NULL is empty or zero
-            var val = this._dimValueGetter(dataItem, dim, idx, k);
-            dimStorage[chunkOffset] = val;
-
-            var dimRawExtent = rawExtent[dim];
-            val < dimRawExtent[0] && (dimRawExtent[0] = val);
-            val > dimRawExtent[1] && (dimRawExtent[1] = val);
-        }
-
-        // ??? FIXME not check by pure but sourceFormat?
-        // TODO refactor these logic.
-        if (!rawData.pure) {
-            var name = nameList[idx];
-
-            if (dataItem && name == null) {
-                // If dataItem is {name: ...}, it has highest priority.
-                // That is appropriate for many common cases.
-                if (dataItem.name != null) {
-                    // There is no other place to persistent dataItem.name,
-                    // so save it to nameList.
-                    nameList[idx] = name = dataItem.name;
-                }
-                else if (nameDimIdx != null) {
-                    var nameDim = dimensions[nameDimIdx];
-                    var nameDimChunk = storage[nameDim][chunkIndex];
-                    if (nameDimChunk) {
-                        name = nameDimChunk[chunkOffset];
-                        var ordinalMeta = dimensionInfoMap[nameDim].ordinalMeta;
-                        if (ordinalMeta && ordinalMeta.categories.length) {
-                            name = ordinalMeta.categories[name];
+            // ??? FIXME not check by pure but sourceFormat?
+            // TODO refactor these logic.
+            if (!rawData.pure) {
+                var name: any = nameList[idx];
+
+                if (dataItem && name == null) {
+                    // If dataItem is {name: ...}, it has highest priority.
+                    // That is appropriate for many common cases.
+                    if ((dataItem as any).name != null) {
+                        // There is no other place to persistent dataItem.name,
+                        // so save it to nameList.
+                        nameList[idx] = name = (dataItem as any).name;
+                    }
+                    else if (nameDimIdx != null) {
+                        var nameDim = dimensions[nameDimIdx];
+                        var nameDimChunk = storage[nameDim][chunkIndex];
+                        if (nameDimChunk) {
+                            name = nameDimChunk[chunkOffset];
+                            var ordinalMeta = dimensionInfoMap[nameDim].ordinalMeta;
+                            if (ordinalMeta && ordinalMeta.categories.length) {
+                                name = ordinalMeta.categories[name];
+                            }
                         }
                     }
                 }
-            }
 
-            // Try using the id in option
-            // id or name is used on dynamical data, mapping old and new items.
-            var id = dataItem == null ? null : dataItem.id;
+                // Try using the id in option
+                // id or name is used on dynamical data, mapping old and new items.
+                var id = dataItem == null ? null : (dataItem as any).id;
 
-            if (id == null && name != null) {
-                // Use name as id and add counter to avoid same name
-                nameRepeatCount[name] = nameRepeatCount[name] || 0;
-                id = name;
-                if (nameRepeatCount[name] > 0) {
-                    id += '__ec__' + nameRepeatCount[name];
+                if (id == null && name != null) {
+                    // Use name as id and add counter to avoid same name
+                    nameRepeatCount[name] = nameRepeatCount[name] || 0;
+                    id = name;
+                    if (nameRepeatCount[name] > 0) {
+                        id += '__ec__' + nameRepeatCount[name];
+                    }
+                    nameRepeatCount[name]++;
                 }
-                nameRepeatCount[name]++;
+                id != null && (idList[idx] = id);
             }
-            id != null && (idList[idx] = id);
         }
-    }
-
-    if (!rawData.persistent && rawData.clean) {
-        // Clean unused data if data source is typed array.
-        rawData.clean();
-    }
 
-    this._rawCount = this._count = end;
+        if (!rawData.persistent && rawData.clean) {
+            // Clean unused data if data source is typed array.
+            rawData.clean();
+        }
 
-    // Reset data extent
-    this._extent = {};
+        this._rawCount = this._count = end;
 
-    prepareInvertedIndex(this);
-};
+        // Reset data extent
+        this._extent = {};
 
-function prepareChunks(storage, dimInfo, chunkSize, chunkCount, end) {
-    var DataCtor = dataCtors[dimInfo.type];
-    var lastChunkIndex = chunkCount - 1;
-    var dim = dimInfo.name;
-    var resizeChunkArray = storage[dim][lastChunkIndex];
-    if (resizeChunkArray && resizeChunkArray.length < chunkSize) {
-        var newStore = new DataCtor(Math.min(end - lastChunkIndex * chunkSize, chunkSize));
-        // The cost of the copy is probably inconsiderable
-        // within the initial chunkSize.
-        for (var j = 0; j < resizeChunkArray.length; j++) {
-            newStore[j] = resizeChunkArray[j];
-        }
-        storage[dim][lastChunkIndex] = newStore;
+        prepareInvertedIndex(this);
     }
 
-    // Create new chunks.
-    for (var k = chunkCount * chunkSize; k < end; k += chunkSize) {
-        storage[dim].push(new DataCtor(Math.min(end - k, chunkSize)));
+    count(): number {
+        return this._count;
     }
-}
 
-function prepareInvertedIndex(list) {
-    var invertedIndicesMap = list._invertedIndicesMap;
-    zrUtil.each(invertedIndicesMap, function (invertedIndices, dim) {
-        var dimInfo = list._dimensionInfos[dim];
+    getIndices(): ArrayLike<number> {
+        var newIndices;
 
-        // Currently, only dimensions that has ordinalMeta can create inverted indices.
-        var ordinalMeta = dimInfo.ordinalMeta;
-        if (ordinalMeta) {
-            invertedIndices = invertedIndicesMap[dim] = new CtorInt32Array(
-                ordinalMeta.categories.length
-            );
-            // The default value of TypedArray is 0. To avoid miss
-            // mapping to 0, we should set it as INDEX_NOT_FOUND.
-            for (var i = 0; i < invertedIndices.length; i++) {
-                invertedIndices[i] = INDEX_NOT_FOUND;
+        var indices = this._indices;
+        if (indices) {
+            var Ctor = indices.constructor as DataArrayLikeConstructor;
+            var thisCount = this._count;
+            // `new Array(a, b, c)` is different from `new Uint32Array(a, b, c)`.
+            if (Ctor === Array) {
+                newIndices = new Ctor(thisCount);
+                for (var i = 0; i < thisCount; i++) {
+                    newIndices[i] = indices[i];
+                }
             }
-            for (var i = 0; i < list._count; i++) {
-                // Only support the case that all values are distinct.
-                invertedIndices[list.get(dim, i)] = i;
+            else {
+                newIndices = new (Ctor as DataTypedArrayConstructor)(
+                    (indices as DataTypedArray).buffer, 0, thisCount
+                );
             }
         }
-    });
-}
-
-function getRawValueFromStore(list, dimIndex, rawIndex) {
-    var val;
-    if (dimIndex != null) {
-        var chunkSize = list._chunkSize;
-        var chunkIndex = Math.floor(rawIndex / chunkSize);
-        var chunkOffset = rawIndex % chunkSize;
-        var dim = list.dimensions[dimIndex];
-        var chunk = list._storage[dim][chunkIndex];
-        if (chunk) {
-            val = chunk[chunkOffset];
-            var ordinalMeta = list._dimensionInfos[dim].ordinalMeta;
-            if (ordinalMeta && ordinalMeta.categories.length) {
-                val = ordinalMeta.categories[val];
+        else {
+            var Ctor = getIndicesCtor(this);
+            newIndices = new Ctor(this.count());
+            for (var i = 0; i < newIndices.length; i++) {
+                newIndices[i] = i;
             }
         }
-    }
-    return val;
-}
 
-/**
- * @return {number}
- */
-listProto.count = function () {
-    return this._count;
-};
+        return newIndices;
+    }
 
-listProto.getIndices = function () {
-    var newIndices;
-
-    var indices = this._indices;
-    if (indices) {
-        var Ctor = indices.constructor;
-        var thisCount = this._count;
-        // `new Array(a, b, c)` is different from `new Uint32Array(a, b, c)`.
-        if (Ctor === Array) {
-            newIndices = new Ctor(thisCount);
-            for (var i = 0; i < thisCount; i++) {
-                newIndices[i] = indices[i];
-            }
-        }
-        else {
-            newIndices = new Ctor(indices.buffer, 0, thisCount);
+    /**
+     * Get value. Return NaN if idx is out of range.
+     * @param dim Dim must be concrete name.
+     */
+    get(dim: DimensionName, idx: number): ParsedDataValue {
+        if (!(idx >= 0 && idx < this._count)) {
+            return NaN;
         }
-    }
-    else {
-        var Ctor = getIndicesCtor(this);
-        var newIndices = new Ctor(this.count());
-        for (var i = 0; i < newIndices.length; i++) {
-            newIndices[i] = i;
+        var storage = this._storage;
+        if (!storage[dim]) {
+            // TODO Warn ?
+            return NaN;
         }
+
+        idx = this.getRawIndex(idx);
+
+        var chunkIndex = Math.floor(idx / this._chunkSize);
+        var chunkOffset = idx % this._chunkSize;
+
+        var chunkStore = storage[dim][chunkIndex];
+        var value = chunkStore[chunkOffset];
+        // FIXME ordinal data type is not stackable
+        // if (stack) {
+        //     var dimensionInfo = this._dimensionInfos[dim];
+        //     if (dimensionInfo && dimensionInfo.stackable) {
+        //         var stackedOn = this.stackedOn;
+        //         while (stackedOn) {
+        //             // Get no stacked data of stacked on
+        //             var stackedValue = stackedOn.get(dim, idx);
+        //             // Considering positive stack, negative stack and empty data
+        //             if ((value >= 0 && stackedValue > 0)  // Positive stack
+        //                 || (value <= 0 && stackedValue < 0) // Negative stack
+        //             ) {
+        //                 value += stackedValue;
+        //             }
+        //             stackedOn = stackedOn.stackedOn;
+        //         }
+        //     }
+        // }
+
+        return value;
     }
 
-    return newIndices;
-};
+    /**
+     * @param dim concrete dim
+     */
+    getByRawIndex(dim: DimensionName, rawIdx: number): ParsedDataValue {
+        if (!(rawIdx >= 0 && rawIdx < this._rawCount)) {
+            return NaN;
+        }
+        var dimStore = this._storage[dim];
+        if (!dimStore) {
+            // TODO Warn ?
+            return NaN;
+        }
 
-/**
- * Get value. Return NaN if idx is out of range.
- * @param {string} dim Dim must be concrete name.
- * @param {number} idx
- * @param {boolean} stack
- * @return {number}
- */
-listProto.get = function (dim, idx /*, stack */) {
-    if (!(idx >= 0 && idx < this._count)) {
-        return NaN;
+        var chunkIndex = Math.floor(rawIdx / this._chunkSize);
+        var chunkOffset = rawIdx % this._chunkSize;
+        var chunkStore = dimStore[chunkIndex];
+        return chunkStore[chunkOffset];
     }
-    var storage = this._storage;
-    if (!storage[dim]) {
-        // TODO Warn ?
-        return NaN;
+
+    /**
+     * FIXME Use `get` on chrome maybe slow(in filterSelf and selectRange).
+     * Hack a much simpler _getFast
+     */
+    private _getFast(dim: DimensionName, rawIdx: number): ParsedDataValue {
+        var chunkIndex = Math.floor(rawIdx / this._chunkSize);
+        var chunkOffset = rawIdx % this._chunkSize;
+        var chunkStore = this._storage[dim][chunkIndex];
+        return chunkStore[chunkOffset];
     }
 
-    idx = this.getRawIndex(idx);
-
-    var chunkIndex = Math.floor(idx / this._chunkSize);
-    var chunkOffset = idx % this._chunkSize;
-
-    var chunkStore = storage[dim][chunkIndex];
-    var value = chunkStore[chunkOffset];
-    // FIXME ordinal data type is not stackable
-    // if (stack) {
-    //     var dimensionInfo = this._dimensionInfos[dim];
-    //     if (dimensionInfo && dimensionInfo.stackable) {
-    //         var stackedOn = this.stackedOn;
-    //         while (stackedOn) {
-    //             // Get no stacked data of stacked on
-    //             var stackedValue = stackedOn.get(dim, idx);
-    //             // Considering positive stack, negative stack and empty data
-    //             if ((value >= 0 && stackedValue > 0)  // Positive stack
-    //                 || (value <= 0 && stackedValue < 0) // Negative stack
-    //             ) {
-    //                 value += stackedValue;
-    //             }
-    //             stackedOn = stackedOn.stackedOn;
-    //         }
-    //     }
-    // }
+    /**
+     * Get value for multi dimensions.
+     * @param dimensions If ignored, using all dimensions.
+     */
+    getValues(idx: number): ParsedDataValue[];
+    getValues(dimensions: DimensionName[], idx: number): ParsedDataValue[];
+    getValues(dimensions: DimensionName[] | number, idx?: number): ParsedDataValue[] {
+        var values = [];
+
+        if (!zrUtil.isArray(dimensions)) {
+            // stack = idx;
+            idx = dimensions;
+            dimensions = this.dimensions;
+        }
 
-    return value;
-};
+        for (var i = 0, len = dimensions.length; i < len; i++) {
+            values.push(this.get(dimensions[i], idx /*, stack */));
+        }
 
-/**
- * @param {string} dim concrete dim
- * @param {number} rawIndex
- * @return {number|string}
- */
-listProto.getByRawIndex = function (dim, rawIdx) {
-    if (!(rawIdx >= 0 && rawIdx < this._rawCount)) {
-        return NaN;
+        return values;
     }
-    var dimStore = this._storage[dim];
-    if (!dimStore) {
-        // TODO Warn ?
-        return NaN;
+
+    /**
+     * If value is NaN. Inlcuding '-'
+     * Only check the coord dimensions.
+     */
+    hasValue(idx: number): boolean {
+        var dataDimsOnCoord = this._dimensionsSummary.dataDimsOnCoord;
+        for (var i = 0, len = dataDimsOnCoord.length; i < len; i++) {
+            // Ordinal type originally can be string or number.
+            // But when an ordinal type is used on coord, it can
+            // not be string but only number. So we can also use isNaN.
+            if (isNaN(this.get(dataDimsOnCoord[i], idx) as any)) {
+                return false;
+            }
+        }
+        return true;
     }
 
-    var chunkIndex = Math.floor(rawIdx / this._chunkSize);
-    var chunkOffset = rawIdx % this._chunkSize;
-    var chunkStore = dimStore[chunkIndex];
-    return chunkStore[chunkOffset];
-};
+    /**
+     * Get extent of data in one dimension
+     */
+    getDataExtent(dim: DimensionLoose): [number, number] {
+        // Make sure use concrete dim as cache name.
+        dim = this.getDimension(dim);
+        var dimData = this._storage[dim];
+        var initialExtent = getInitialExtent();
 
-/**
- * FIXME Use `get` on chrome maybe slow(in filterSelf and selectRange).
- * Hack a much simpler _getFast
- * @private
- */
-listProto._getFast = function (dim, rawIdx) {
-    var chunkIndex = Math.floor(rawIdx / this._chunkSize);
-    var chunkOffset = rawIdx % this._chunkSize;
-    var chunkStore = this._storage[dim][chunkIndex];
-    return chunkStore[chunkOffset];
-};
+        // stack = !!((stack || false) && this.getCalculationInfo(dim));
 
-/**
- * Get value for multi dimensions.
- * @param {Array.<string>} [dimensions] If ignored, using all dimensions.
- * @param {number} idx
- * @return {number}
- */
-listProto.getValues = function (dimensions, idx /*, stack */) {
-    var values = [];
+        if (!dimData) {
+            return initialExtent;
+        }
 
-    if (!zrUtil.isArray(dimensions)) {
-        // stack = idx;
-        idx = dimensions;
-        dimensions = this.dimensions;
-    }
+        // Make more strict checkings to ensure hitting cache.
+        var currEnd = this.count();
+        // var cacheName = [dim, !!stack].join('_');
+        // var cacheName = dim;
 
-    for (var i = 0, len = dimensions.length; i < len; i++) {
-        values.push(this.get(dimensions[i], idx /*, stack */));
-    }
+        // Consider the most cases when using data zoom, `getDataExtent`
+        // happened before filtering. We cache raw extent, which is not
+        // necessary to be cleared and recalculated when restore data.
+        var useRaw = !this._indices; // && !stack;
+        var dimExtent: [number, number];
 
-    return values;
-};
+        if (useRaw) {
+            return this._rawExtent[dim].slice() as [number, number];
+        }
+        dimExtent = this._extent[dim];
+        if (dimExtent) {
+            return dimExtent.slice() as [number, number];
+        }
+        dimExtent = initialExtent;
 
-/**
- * If value is NaN. Inlcuding '-'
- * Only check the coord dimensions.
- * @param {string} dim
- * @param {number} idx
- * @return {number}
- */
-listProto.hasValue = function (idx) {
-    var dataDimsOnCoord = this._dimensionsSummary.dataDimsOnCoord;
-    for (var i = 0, len = dataDimsOnCoord.length; i < len; i++) {
-        // Ordinal type originally can be string or number.
-        // But when an ordinal type is used on coord, it can
-        // not be string but only number. So we can also use isNaN.
-        if (isNaN(this.get(dataDimsOnCoord[i], idx))) {
-            return false;
+        var min = dimExtent[0];
+        var max = dimExtent[1];
+
+        for (var i = 0; i < currEnd; i++) {
+            // var value = stack ? this.get(dim, i, true) : this._getFast(dim, this.getRawIndex(i));
+            var value = this._getFast(dim, this.getRawIndex(i)) as ParsedDataNumeric;
+            value < min && (min = value);
+            value > max && (max = value);
         }
-    }
-    return true;
-};
 
-/**
- * Get extent of data in one dimension
- * @param {string} dim
- * @param {boolean} stack
- */
-listProto.getDataExtent = function (dim /*, stack */) {
-    // Make sure use concrete dim as cache name.
-    dim = this.getDimension(dim);
-    var dimData = this._storage[dim];
-    var initialExtent = getInitialExtent();
+        dimExtent = [min, max];
 
-    // stack = !!((stack || false) && this.getCalculationInfo(dim));
+        this._extent[dim] = dimExtent;
 
-    if (!dimData) {
-        return initialExtent;
+        return dimExtent;
     }
 
-    // Make more strict checkings to ensure hitting cache.
-    var currEnd = this.count();
-    // var cacheName = [dim, !!stack].join('_');
-    // var cacheName = dim;
-
-    // Consider the most cases when using data zoom, `getDataExtent`
-    // happened before filtering. We cache raw extent, which is not
-    // necessary to be cleared and recalculated when restore data.
-    var useRaw = !this._indices; // && !stack;
-    var dimExtent;
+    /**
+     * Optimize for the scenario that data is filtered by a given extent.
+     * Consider that if data amount is more than hundreds of thousand,
+     * extent calculation will cost more than 10ms and the cache will
+     * be erased because of the filtering.
+     */
+    getApproximateExtent(dim: DimensionLoose): [number, number] {
+        dim = this.getDimension(dim);
+        return this._approximateExtent[dim] || this.getDataExtent(dim /*, stack */);
+    }
 
-    if (useRaw) {
-        return this._rawExtent[dim].slice();
+    setApproximateExtent(extent: [number, number], dim: DimensionLoose): void {
+        dim = this.getDimension(dim);
+        this._approximateExtent[dim] = extent.slice() as [number, number];
     }
-    dimExtent = this._extent[dim];
-    if (dimExtent) {
-        return dimExtent.slice();
+
+    getCalculationInfo(key: string): object {
+        return this._calculationInfo[key];
     }
-    dimExtent = initialExtent;
 
-    var min = dimExtent[0];
-    var max = dimExtent[1];
+    /**
+     * @param key or k-v object
+     */
+    setCalculationInfo(key: string | object, value?: any) {
+        isObject(key)
+            ? zrUtil.extend(this._calculationInfo, key as object)
+            : (this._calculationInfo[key] = value);
+    }
 
-    for (var i = 0; i < currEnd; i++) {
-        // var value = stack ? this.get(dim, i, true) : this._getFast(dim, this.getRawIndex(i));
-        var value = this._getFast(dim, this.getRawIndex(i));
-        value < min && (min = value);
-        value > max && (max = value);
+    /**
+     * Get sum of data in one dimension
+     */
+    getSum(dim: DimensionName): number {
+        var dimData = this._storage[dim];
+        var sum = 0;
+        if (dimData) {
+            for (var i = 0, len = this.count(); i < len; i++) {
+                var value = this.get(dim, i) as number;
+                if (!isNaN(value)) {
+                    sum += value;
+                }
+            }
+        }
+        return sum;
     }
 
-    dimExtent = [min, max];
+    /**
+     * Get median of data in one dimension
+     */
+    getMedian(dim: DimensionLoose): number {
+        var dimDataArray: ParsedDataValue[] = [];
+        // map all data of one dimension
+        this.each(dim, function (val) {
+            if (!isNaN(val as number)) {
+                dimDataArray.push(val);
+            }
+        });
+
+        // TODO
+        // Use quick select?
+
+        // immutability & sort
+        var sortedDimDataArray = [].concat(dimDataArray).sort(function (a, b) {
+            return a - b;
+        });
+        var len = this.count();
+        // calculate median
+        return len === 0
+            ? 0
+            : len % 2 === 1
+            ? sortedDimDataArray[(len - 1) / 2]
+            : (sortedDimDataArray[len / 2] + sortedDimDataArray[len / 2 - 1]) / 2;
+    }
+
+    // /**
+    //  * Retreive the index with given value
+    //  * @param {string} dim Concrete dimension.
+    //  * @param {number} value
+    //  * @return {number}
+    //  */
+    // Currently incorrect: should return dataIndex but not rawIndex.
+    // Do not fix it until this method is to be used somewhere.
+    // FIXME Precision of float value
+    // indexOf(dim, value) {
+    //     var storage = this._storage;
+    //     var dimData = storage[dim];
+    //     var chunkSize = this._chunkSize;
+    //     if (dimData) {
+    //         for (var i = 0, len = this.count(); i < len; i++) {
+    //             var chunkIndex = Math.floor(i / chunkSize);
+    //             var chunkOffset = i % chunkSize;
+    //             if (dimData[chunkIndex][chunkOffset] === value) {
+    //                 return i;
+    //             }
+    //         }
+    //     }
+    //     return -1;
+    // }
 
-    this._extent[dim] = dimExtent;
+    /**
+     * Only support the dimension which inverted index created.
+     * Do not support other cases until required.
+     * @param dim concrete dim
+     * @param value ordinal index
+     * @return rawIndex
+     */
+    rawIndexOf(dim: DimensionName, value: OrdinalRawValueIndex): number {
+        var invertedIndices = dim && this._invertedIndicesMap[dim];
+        if (__DEV__) {
+            if (!invertedIndices) {
+                throw new Error('Do not supported yet');
+            }
+        }
+        var rawIndex = invertedIndices[value];
+        if (rawIndex == null || isNaN(rawIndex)) {
+            return INDEX_NOT_FOUND;
+        }
+        return rawIndex;
+    }
 
-    return dimExtent;
-};
-
-/**
- * Optimize for the scenario that data is filtered by a given extent.
- * Consider that if data amount is more than hundreds of thousand,
- * extent calculation will cost more than 10ms and the cache will
- * be erased because of the filtering.
- */
-listProto.getApproximateExtent = function (dim /*, stack */) {
-    dim = this.getDimension(dim);
-    return this._approximateExtent[dim] || this.getDataExtent(dim /*, stack */);
-};
-
-listProto.setApproximateExtent = function (extent, dim /*, stack */) {
-    dim = this.getDimension(dim);
-    this._approximateExtent[dim] = extent.slice();
-};
-
-/**
- * @param {string} key
- * @return {*}
- */
-listProto.getCalculationInfo = function (key) {
-    return this._calculationInfo[key];
-};
-
-/**
- * @param {string|Object} key or k-v object
- * @param {*} [value]
- */
-listProto.setCalculationInfo = function (key, value) {
-    isObject(key)
-        ? zrUtil.extend(this._calculationInfo, key)
-        : (this._calculationInfo[key] = value);
-};
-
-/**
- * Get sum of data in one dimension
- * @param {string} dim
- */
-listProto.getSum = function (dim /*, stack */) {
-    var dimData = this._storage[dim];
-    var sum = 0;
-    if (dimData) {
+    /**
+     * Retreive the index with given name
+     */
+    indexOfName(name: string): number {
         for (var i = 0, len = this.count(); i < len; i++) {
-            var value = this.get(dim, i /*, stack */);
-            if (!isNaN(value)) {
-                sum += value;
+            if (this.getName(i) === name) {
+                return i;
             }
         }
-    }
-    return sum;
-};
-
-/**
- * Get median of data in one dimension
- * @param {string} dim
- */
-listProto.getMedian = function (dim /*, stack */) {
-    var dimDataArray = [];
-    // map all data of one dimension
-    this.each(dim, function (val, idx) {
-        if (!isNaN(val)) {
-            dimDataArray.push(val);
-        }
-    });
-
-    // TODO
-    // Use quick select?
-
-    // immutability & sort
-    var sortedDimDataArray = [].concat(dimDataArray).sort(function (a, b) {
-        return a - b;
-    });
-    var len = this.count();
-    // calculate median
-    return len === 0
-        ? 0
-        : len % 2 === 1
-        ? sortedDimDataArray[(len - 1) / 2]
-        : (sortedDimDataArray[len / 2] + sortedDimDataArray[len / 2 - 1]) / 2;
-};
 
-// /**
-//  * Retreive the index with given value
-//  * @param {string} dim Concrete dimension.
-//  * @param {number} value
-//  * @return {number}
-//  */
-// Currently incorrect: should return dataIndex but not rawIndex.
-// Do not fix it until this method is to be used somewhere.
-// FIXME Precision of float value
-// listProto.indexOf = function (dim, value) {
-//     var storage = this._storage;
-//     var dimData = storage[dim];
-//     var chunkSize = this._chunkSize;
-//     if (dimData) {
-//         for (var i = 0, len = this.count(); i < len; i++) {
-//             var chunkIndex = Math.floor(i / chunkSize);
-//             var chunkOffset = i % chunkSize;
-//             if (dimData[chunkIndex][chunkOffset] === value) {
-//                 return i;
-//             }
-//         }
-//     }
-//     return -1;
-// };
+        return -1;
+    }
 
-/**
- * Only support the dimension which inverted index created.
- * Do not support other cases until required.
- * @param {string} concrete dim
- * @param {number|string} value
- * @return {number} rawIndex
- */
-listProto.rawIndexOf = function (dim, value) {
-    var invertedIndices = dim && this._invertedIndicesMap[dim];
-    if (__DEV__) {
-        if (!invertedIndices) {
-            throw new Error('Do not supported yet');
+    /**
+     * Retreive the index with given raw data index
+     */
+    indexOfRawIndex(rawIndex: number): number {
+        if (rawIndex >= this._rawCount || rawIndex < 0) {
+            return -1;
         }
-    }
-    var rawIndex = invertedIndices[value];
-    if (rawIndex == null || isNaN(rawIndex)) {
-        return INDEX_NOT_FOUND;
-    }
-    return rawIndex;
-};
 
-/**
- * Retreive the index with given name
- * @param {number} idx
- * @param {number} name
- * @return {number}
- */
-listProto.indexOfName = function (name) {
-    for (var i = 0, len = this.count(); i < len; i++) {
-        if (this.getName(i) === name) {
-            return i;
+        if (!this._indices) {
+            return rawIndex;
         }
-    }
 
-    return -1;
-};
+        // Indices are ascending
+        var indices = this._indices;
 
-/**
- * Retreive the index with given raw data index
- * @param {number} idx
- * @param {number} name
- * @return {number}
- */
-listProto.indexOfRawIndex = function (rawIndex) {
-    if (rawIndex >= this._rawCount || rawIndex < 0) {
+        // If rawIndex === dataIndex
+        var rawDataIndex = indices[rawIndex];
+        if (rawDataIndex != null && rawDataIndex < this._count && rawDataIndex === rawIndex) {
+            return rawIndex;
+        }
+
+        var left = 0;
+        var right = this._count - 1;
+        while (left <= right) {
+            var mid = (left + right) / 2 | 0;
+            if (indices[mid] < rawIndex) {
+                left = mid + 1;
+            }
+            else if (indices[mid] > rawIndex) {
+                right = mid - 1;
+            }
+            else {
+                return mid;
+            }
+        }
         return -1;
     }
 
-    if (!this._indices) {
-        return rawIndex;
-    }
+    /**
+     * Retreive the index of nearest value
+     * @param dim
+     * @param value
+     * @param [maxDistance=Infinity]
+     * @return If and only if multiple indices has
+     *         the same value, they are put to the result.
+     */
+    indicesOfNearest(
+        dim: DimensionName, value: number, maxDistance?: number
+    ): number[] {
+        var storage = this._storage;
+        var dimData = storage[dim];
+        var nearestIndices: number[] = [];
+
+        if (!dimData) {
+            return nearestIndices;
+        }
 
-    // Indices are ascending
-    var indices = this._indices;
+        if (maxDistance == null) {
+            maxDistance = Infinity;
+        }
 
-    // If rawIndex === dataIndex
-    var rawDataIndex = indices[rawIndex];
-    if (rawDataIndex != null && rawDataIndex < this._count && rawDataIndex === rawIndex) {
-        return rawIndex;
-    }
+        var minDist = Infinity;
+        var minDiff = -1;
+        var nearestIndicesLen = 0;
 
-    var left = 0;
-    var right = this._count - 1;
-    while (left <= right) {
-        var mid = (left + right) / 2 | 0;
-        if (indices[mid] < rawIndex) {
-            left = mid + 1;
-        }
-        else if (indices[mid] > rawIndex) {
-            right = mid - 1;
-        }
-        else {
-            return mid;
+        // Check the test case of `test/ut/spec/data/List.js`.
+        for (var i = 0, len = this.count(); i < len; i++) {
+            var diff = value - (this.get(dim, i) as number);
+            var dist = Math.abs(diff);
+            if (dist <= maxDistance) {
+                // When the `value` is at the middle of `this.get(dim, i)` and `this.get(dim, i+1)`,
+                // we'd better not push both of them to `nearestIndices`, otherwise it is easy to
+                // get more than one item in `nearestIndices` (more specifically, in `tooltip`).
+                // So we chose the one that `diff >= 0` in this csae.
+                // But if `this.get(dim, i)` and `this.get(dim, j)` get the same value, both of them
+                // should be push to `nearestIndices`.
+                if (dist < minDist
+                    || (dist === minDist && diff >= 0 && minDiff < 0)
+                ) {
+                    minDist = dist;
+                    minDiff = diff;
+                    nearestIndicesLen = 0;
+                }
+                if (diff === minDiff) {
+                    nearestIndices[nearestIndicesLen++] = i;
+                }
+            }
         }
-    }
-    return -1;
-};
-
-/**
- * Retreive the index of nearest value
- * @param {string} dim
- * @param {number} value
- * @param {number} [maxDistance=Infinity]
- * @return {Array.<number>} If and only if multiple indices has
- *        the same value, they are put to the result.
- */
-listProto.indicesOfNearest = function (dim, value, maxDistance) {
-    var storage = this._storage;
-    var dimData = storage[dim];
-    var nearestIndices = [];
+        nearestIndices.length = nearestIndicesLen;
 
-    if (!dimData) {
         return nearestIndices;
     }
 
-    if (maxDistance == null) {
-        maxDistance = Infinity;
-    }
+    /**
+     * Get raw data index.
+     * Do not initialize.
+     * Default `getRawIndex`. And it can be changed.
+     */
+    getRawIndex: (idx: number) => number = getRawIndexWithoutIndices;
 
-    var minDist = Infinity;
-    var minDiff = -1;
-    var nearestIndicesLen = 0;
-
-    // Check the test case of `test/ut/spec/data/List.js`.
-    for (var i = 0, len = this.count(); i < len; i++) {
-        var diff = value - this.get(dim, i);
-        var dist = Math.abs(diff);
-        if (dist <= maxDistance) {
-            // When the `value` is at the middle of `this.get(dim, i)` and `this.get(dim, i+1)`,
-            // we'd better not push both of them to `nearestIndices`, otherwise it is easy to
-            // get more than one item in `nearestIndices` (more specifically, in `tooltip`).
-            // So we chose the one that `diff >= 0` in this csae.
-            // But if `this.get(dim, i)` and `this.get(dim, j)` get the same value, both of them
-            // should be push to `nearestIndices`.
-            if (dist < minDist
-                || (dist === minDist && diff >= 0 && minDiff < 0)
-            ) {
-                minDist = dist;
-                minDiff = diff;
-                nearestIndicesLen = 0;
-            }
-            if (diff === minDiff) {
-                nearestIndices[nearestIndicesLen++] = i;
+    /**
+     * Get raw data item
+     */
+    getRawDataItem(idx: number): OptionDataItem {
+        if (!this._rawData.persistent) {
+            var val = [];
+            for (var i = 0; i < this.dimensions.length; i++) {
+                var dim = this.dimensions[i];
+                val.push(this.get(dim, idx));
             }
+            return val;
+        }
+        else {
+            return this._rawData.getItem(this.getRawIndex(idx));
         }
     }
-    nearestIndices.length = nearestIndicesLen;
-
-    return nearestIndices;
-};
-
-/**
- * Get raw data index
- * @param {number} idx
- * @return {number}
- */
-listProto.getRawIndex = getRawIndexWithoutIndices;
 
-function getRawIndexWithoutIndices(idx) {
-    return idx;
-}
+    getName(idx: number): string {
+        var rawIndex = this.getRawIndex(idx);
+        return this._nameList[rawIndex]
+            || getRawValueFromStore(this, this._nameDimIdx, rawIndex)
+            || '';
+    }
 
-function getRawIndexWithIndices(idx) {
-    if (idx < this._count && idx >= 0) {
-        return this._indices[idx];
+    getId(idx: number): string {
+        return getId(this, this.getRawIndex(idx));
     }
-    return -1;
-}
 
-/**
- * Get raw data item
- * @param {number} idx
- * @return {number}
- */
-listProto.getRawDataItem = function (idx) {
-    if (!this._rawData.persistent) {
-        var val = [];
-        for (var i = 0; i < this.dimensions.length; i++) {
-            var dim = this.dimensions[i];
-            val.push(this.get(dim, idx));
+    /**
+     * Data iteration
+     * @param ctx default this
+     * @example
+     *  list.each('x', function (x, idx) {});
+     *  list.each(['x', 'y'], function (x, y, idx) {});
+     *  list.each(function (idx) {})
+     */
+    each<Ctx>(cb: EachCb0<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): void;
+    each<Ctx>(dims: DimensionLoose, cb: EachCb1<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): void;
+    each<Ctx>(dims: [DimensionLoose], cb: EachCb1<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): void;
+    each<Ctx>(dims: [DimensionLoose, DimensionLoose], cb: EachCb2<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): void;
+    each<Ctx>(dims: ItrParamDims, cb: EachCb<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): void;
+    each<Ctx>(
+        dims: ItrParamDims | EachCb<Ctx>,
+        cb: EachCb<Ctx> | Ctx,
+        ctx?: Ctx,
+        ctxCompat?: Ctx
+    ): void {
+        'use strict';
+
+        if (!this._count) {
+            return;
         }
-        return val;
-    }
-    else {
-        return this._rawData.getItem(this.getRawIndex(idx));
-    }
-};
 
-/**
- * @param {number} idx
- * @param {boolean} [notDefaultIdx=false]
- * @return {string}
- */
-listProto.getName = function (idx) {
-    var rawIndex = this.getRawIndex(idx);
-    return this._nameList[rawIndex]
-        || getRawValueFromStore(this, this._nameDimIdx, rawIndex)
-        || '';
-};
+        if (typeof dims === 'function') {
+            ctxCompat = ctx;
+            ctx = cb as Ctx;
+            cb = dims;
+            dims = [];
+        }
 
-/**
- * @param {number} idx
- * @param {boolean} [notDefaultIdx=false]
- * @return {string}
- */
-listProto.getId = function (idx) {
-    return getId(this, this.getRawIndex(idx));
-};
+        // ctxCompat just for compat echarts3
+        var fCtx = (ctx || ctxCompat || this) as CtxOrList<Ctx>;
 
-function getId(list, rawIndex) {
-    var id = list._idList[rawIndex];
-    if (id == null) {
-        id = getRawValueFromStore(list, list._idDimIdx, rawIndex);
-    }
-    if (id == null) {
-        // FIXME Check the usage in graph, should not use prefix.
-        id = ID_PREFIX + rawIndex;
-    }
-    return id;
-}
+        var dimNames = zrUtil.map(normalizeDimensions(dims), this.getDimension, this);
 
-function normalizeDimensions(dimensions) {
-    if (!zrUtil.isArray(dimensions)) {
-        dimensions = [dimensions];
-    }
-    return dimensions;
-}
+        if (__DEV__) {
+            validateDimensions(this, dimNames);
+        }
 
-function validateDimensions(list, dims) {
-    for (var i = 0; i < dims.length; i++) {
-        // stroage may be empty when no data, so use
-        // dimensionInfos to check.
-        if (!list._dimensionInfos[dims[i]]) {
-            console.error('Unkown dimension ' + dims[i]);
+        var dimSize = dimNames.length;
+
+        for (var i = 0; i < this.count(); i++) {
+            // Simple optimization
+            switch (dimSize) {
+                case 0:
+                    (cb as EachCb0<Ctx>).call(fCtx, i);
+                    break;
+                case 1:
+                    (cb as EachCb1<Ctx>).call(fCtx, this.get(dimNames[0], i), i);
+                    break;
+                case 2:
+                    (cb as EachCb2<Ctx>).call(fCtx, this.get(dimNames[0], i), this.get(dimNames[1], i), i);
+                    break;
+                default:
+                    var k = 0;
+                    var value = [];
+                    for (; k < dimSize; k++) {
+                        value[k] = this.get(dimNames[k], i);
+                    }
+                    // Index
+                    value[k] = i;
+                    (cb as EachCb<Ctx>).apply(fCtx, value);
+            }
         }
     }
-}
 
-/**
- * Data iteration
- * @param {string|Array.<string>}
- * @param {Function} cb
- * @param {*} [context=this]
- *
- * @example
- *  list.each('x', function (x, idx) {});
- *  list.each(['x', 'y'], function (x, y, idx) {});
- *  list.each(function (idx) {})
- */
-listProto.each = function (dims, cb, context, contextCompat) {
-    'use strict';
+    /**
+     * Data filter
+     */
+    filterSelf<Ctx>(cb: FilterCb0<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): List;
+    filterSelf<Ctx>(dims: DimensionLoose, cb: FilterCb1<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): List;
+    filterSelf<Ctx>(dims: [DimensionLoose], cb: FilterCb1<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): List;
+    filterSelf<Ctx>(dims: [DimensionLoose, DimensionLoose], cb: FilterCb2<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): List;
+    filterSelf<Ctx>(dims: ItrParamDims, cb: FilterCb<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): List;
+    filterSelf<Ctx>(
+        dims: ItrParamDims | FilterCb<Ctx>,
+        cb: FilterCb<Ctx> | Ctx,
+        ctx?: Ctx,
+        ctxCompat?: Ctx
+    ): List {
+        'use strict';
+
+        if (!this._count) {
+            return;
+        }
 
-    if (!this._count) {
-        return;
-    }
+        if (typeof dims === 'function') {
+            ctxCompat = ctx;
+            ctx = cb as Ctx;
+            cb = dims;
+            dims = [];
+        }
 
-    if (typeof dims === 'function') {
-        contextCompat = context;
-        context = cb;
-        cb = dims;
-        dims = [];
-    }
+        // ctxCompat just for compat echarts3
+        var fCtx = (ctx || ctxCompat || this) as CtxOrList<Ctx>;
 
-    // contextCompat just for compat echarts3
-    context = context || contextCompat || this;
+        var dimNames = zrUtil.map(
+            normalizeDimensions(dims), this.getDimension, this
+        );
 
-    dims = zrUtil.map(normalizeDimensions(dims), this.getDimension, this);
+        if (__DEV__) {
+            validateDimensions(this, dimNames);
+        }
 
-    if (__DEV__) {
-        validateDimensions(this, dims);
-    }
 
-    var dimSize = dims.length;
-
-    for (var i = 0; i < this.count(); i++) {
-        // Simple optimization
-        switch (dimSize) {
-            case 0:
-                cb.call(context, i);
-                break;
-            case 1:
-                cb.call(context, this.get(dims[0], i), i);
-                break;
-            case 2:
-                cb.call(context, this.get(dims[0], i), this.get(dims[1], i), i);
-                break;
-            default:
-                var k = 0;
-                var value = [];
-                for (; k < dimSize; k++) {
-                    value[k] = this.get(dims[k], i);
+        var count = this.count();
+        var Ctor = getIndicesCtor(this);
+        var newIndices = new Ctor(count);
+        var value = [];
+        var dimSize = dimNames.length;
+
+        var offset = 0;
+        var dim0 = dimNames[0];
+
+        for (var i = 0; i < count; i++) {
+            var keep;
+            var rawIdx = this.getRawIndex(i);
+            // Simple optimization
+            if (dimSize === 0) {
+                keep = (cb as FilterCb0<Ctx>).call(fCtx, i);
+            }
+            else if (dimSize === 1) {
+                var val = this._getFast(dim0, rawIdx);
+                keep = (cb as FilterCb1<Ctx>).call(fCtx, val, i);
+            }
+            else {
+                for (var k = 0; k < dimSize; k++) {
+                    value[k] = this._getFast(dim0, rawIdx);
                 }
-                // Index
                 value[k] = i;
-                cb.apply(context, value);
+                keep = (cb as FilterCb<Ctx>).apply(fCtx, value);
+            }
+            if (keep) {
+                newIndices[offset++] = rawIdx;
+            }
         }
-    }
-};
-
-/**
- * Data filter
- * @param {string|Array.<string>}
- * @param {Function} cb
- * @param {*} [context=this]
- */
-listProto.filterSelf = function (dimensions, cb, context, contextCompat) {
-    'use strict';
 
-    if (!this._count) {
-        return;
-    }
-
-    if (typeof dimensions === 'function') {
-        contextCompat = context;
-        context = cb;
-        cb = dimensions;
-        dimensions = [];
-    }
-
-    // contextCompat just for compat echarts3
-    context = context || contextCompat || this;
+        // Set indices after filtered.
+        if (offset < count) {
+            this._indices = newIndices;
+        }
+        this._count = offset;
+        // Reset data extent
+        this._extent = {};
 
-    dimensions = zrUtil.map(
-        normalizeDimensions(dimensions), this.getDimension, this
-    );
+        this.getRawIndex = this._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices;
 
-    if (__DEV__) {
-        validateDimensions(this, dimensions);
+        return this;
     }
 
+    /**
+     * Select data in range. (For optimization of filter)
+     * (Manually inline code, support 5 million data filtering in data zoom.)
+     */
+    selectRange(range: {[dimName: string]: string}): List {
+        'use strict';
 
-    var count = this.count();
-    var Ctor = getIndicesCtor(this);
-    var newIndices = new Ctor(count);
-    var value = [];
-    var dimSize = dimensions.length;
-
-    var offset = 0;
-    var dim0 = dimensions[0];
-
-    for (var i = 0; i < count; i++) {
-        var keep;
-        var rawIdx = this.getRawIndex(i);
-        // Simple optimization
-        if (dimSize === 0) {
-            keep = cb.call(context, i);
+        if (!this._count) {
+            return;
         }
-        else if (dimSize === 1) {
-            var val = this._getFast(dim0, rawIdx);
-            keep = cb.call(context, val, i);
-        }
-        else {
-            for (var k = 0; k < dimSize; k++) {
-                value[k] = this._getFast(dim0, rawIdx);
+
+        var dimensions = [];
+        for (var dim in range) {
+            if (range.hasOwnProperty(dim)) {
+                dimensions.push(dim);
             }
-            value[k] = i;
-            keep = cb.apply(context, value);
         }
-        if (keep) {
-            newIndices[offset++] = rawIdx;
-        }
-    }
-
-    // Set indices after filtered.
-    if (offset < count) {
-        this._indices = newIndices;
-    }
-    this._count = offset;
-    // Reset data extent
-    this._extent = {};
-
-    this.getRawIndex = this._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices;
-
-    return this;
-};
-
-/**
- * Select data in range. (For optimization of filter)
- * (Manually inline code, support 5 million data filtering in data zoom.)
- */
-listProto.selectRange = function (range) {
-    'use strict';
 
-    if (!this._count) {
-        return;
-    }
-
-    var dimensions = [];
-    for (var dim in range) {
-        if (range.hasOwnProperty(dim)) {
-            dimensions.push(dim);
+        if (__DEV__) {
+            validateDimensions(this, dimensions);
         }
-    }
 
-    if (__DEV__) {
-        validateDimensions(this, dimensions);
-    }
-
-    var dimSize = dimensions.length;
-    if (!dimSize) {
-        return;
-    }
+        var dimSize = dimensions.length;
+        if (!dimSize) {
+            return;
+        }
 
-    var originalCount = this.count();
-    var Ctor = getIndicesCtor(this);
-    var newIndices = new Ctor(originalCount);
-
-    var offset = 0;
-    var dim0 = dimensions[0];
-
-    var min = range[dim0][0];
-    var max = range[dim0][1];
-
-    var quickFinished = false;
-    if (!this._indices) {
-        // Extreme optimization for common case. About 2x faster in chrome.
-        var idx = 0;
-        if (dimSize === 1) {
-            var dimStorage = this._storage[dimensions[0]];
-            for (var k = 0; k < this._chunkCount; k++) {
-                var chunkStorage = dimStorage[k];
-                var len = Math.min(this._count - k * this._chunkSize, this._chunkSize);
-                for (var i = 0; i < len; i++) {
-                    var val = chunkStorage[i];
-                    // NaN will not be filtered. Consider the case, in line chart, empty
-                    // value indicates the line should be broken. But for the case like
-                    // scatter plot, a data item with empty value will not be rendered,
-                    // but the axis extent may be effected if some other dim of the data
-                    // item has value. Fortunately it is not a significant negative effect.
-                    if (
-                        (val >= min && val <= max) || isNaN(val)
-                    ) {
-                        newIndices[offset++] = idx;
+        var originalCount = this.count();
+        var Ctor = getIndicesCtor(this);
+        var newIndices = new Ctor(originalCount);
+
+        var offset = 0;
+        var dim0 = dimensions[0];
+
+        var min = range[dim0][0];
+        var max = range[dim0][1];
+
+        var quickFinished = false;
+        if (!this._indices) {
+            // Extreme optimization for common case. About 2x faster in chrome.
+            var idx = 0;
+            if (dimSize === 1) {
+                var dimStorage = this._storage[dimensions[0]];
+                for (var k = 0; k < this._chunkCount; k++) {
+                    var chunkStorage = dimStorage[k];
+                    var len = Math.min(this._count - k * this._chunkSize, this._chunkSize);
+                    for (var i = 0; i < len; i++) {
+                        var val = chunkStorage[i];
+                        // NaN will not be filtered. Consider the case, in line chart, empty
+                        // value indicates the line should be broken. But for the case like
+                        // scatter plot, a data item with empty value will not be rendered,
+                        // but the axis extent may be effected if some other dim of the data
+                        // item has value. Fortunately it is not a significant negative effect.
+                        if (
+                            (val >= min && val <= max) || isNaN(val as any)
+                        ) {
+                            newIndices[offset++] = idx;
+                        }
+                        idx++;
                     }
-                    idx++;
                 }
+                quickFinished = true;
             }
-            quickFinished = true;
-        }
-        else if (dimSize === 2) {
-            var dimStorage = this._storage[dim0];
-            var dimStorage2 = this._storage[dimensions[1]];
-            var min2 = range[dimensions[1]][0];
-            var max2 = range[dimensions[1]][1];
-            for (var k = 0; k < this._chunkCount; k++) {
-                var chunkStorage = dimStorage[k];
-                var chunkStorage2 = dimStorage2[k];
-                var len = Math.min(this._count - k * this._chunkSize, this._chunkSize);
-                for (var i = 0; i < len; i++) {
-                    var val = chunkStorage[i];
-                    var val2 = chunkStorage2[i];
-                    // Do not filter NaN, see comment above.
-                    if ((
-                            (val >= min && val <= max) || isNaN(val)
-                        )
-                        && (
-                            (val2 >= min2 && val2 <= max2) || isNaN(val2)
-                        )
-                    ) {
-                        newIndices[offset++] = idx;
+            else if (dimSize === 2) {
+                var dimStorage = this._storage[dim0];
+                var dimStorage2 = this._storage[dimensions[1]];
+                var min2 = range[dimensions[1]][0];
+                var max2 = range[dimensions[1]][1];
+                for (var k = 0; k < this._chunkCount; k++) {
+                    var chunkStorage = dimStorage[k];
+                    var chunkStorage2 = dimStorage2[k];
+                    var len = Math.min(this._count - k * this._chunkSize, this._chunkSize);
+                    for (var i = 0; i < len; i++) {
+                        var val = chunkStorage[i];
+                        var val2 = chunkStorage2[i];
+                        // Do not filter NaN, see comment above.
+                        if ((
+                                (val >= min && val <= max) || isNaN(val as any)
+                            )
+                            && (
+                                (val2 >= min2 && val2 <= max2) || isNaN(val2 as any)
+                            )
+                        ) {
+                            newIndices[offset++] = idx;
+                        }
+                        idx++;
                     }
-                    idx++;
-                }
-            }
-            quickFinished = true;
-        }
-    }
-    if (!quickFinished) {
-        if (dimSize === 1) {
-            for (var i = 0; i < originalCount; i++) {
-                var rawIndex = this.getRawIndex(i);
-                var val = this._getFast(dim0, rawIndex);
-                // Do not filter NaN, see comment above.
-                if (
-                    (val >= min && val <= max) || isNaN(val)
-                ) {
-                    newIndices[offset++] = rawIndex;
                 }
+                quickFinished = true;
             }
         }
-        else {
-            for (var i = 0; i < originalCount; i++) {
-                var keep = true;
-                var rawIndex = this.getRawIndex(i);
-                for (var k = 0; k < dimSize; k++) {
-                    var dimk = dimensions[k];
-                    var val = this._getFast(dim, rawIndex);
+        if (!quickFinished) {
+            if (dimSize === 1) {
+                for (var i = 0; i < originalCount; i++) {
+                    var rawIndex = this.getRawIndex(i);
+                    var val = this._getFast(dim0, rawIndex);
                     // Do not filter NaN, see comment above.
-                    if (val < range[dimk][0] || val > range[dimk][1]) {
-                        keep = false;
+                    if (
+                        (val >= min && val <= max) || isNaN(val as any)
+                    ) {
+                        newIndices[offset++] = rawIndex;
                     }
                 }
-                if (keep) {
-                    newIndices[offset++] = this.getRawIndex(i);
+            }
+            else {
+                for (var i = 0; i < originalCount; i++) {
+                    var keep = true;
+                    var rawIndex = this.getRawIndex(i);
+                    for (var k = 0; k < dimSize; k++) {
+                        var dimk = dimensions[k];
+                        var val = this._getFast(dim, rawIndex);
+                        // Do not filter NaN, see comment above.
+                        if (val < range[dimk][0] || val > range[dimk][1]) {
+                            keep = false;
+                        }
+                    }
+                    if (keep) {
+                        newIndices[offset++] = this.getRawIndex(i);
+                    }
                 }
             }
         }
-    }
 
-    // Set indices after filtered.
-    if (offset < originalCount) {
-        this._indices = newIndices;
+        // Set indices after filtered.
+        if (offset < originalCount) {
+            this._indices = newIndices;
+        }
+        this._count = offset;
+        // Reset data extent
+        this._extent = {};
+
+        this.getRawIndex = this._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices;
+
+        return this;
     }
-    this._count = offset;
-    // Reset data extent
-    this._extent = {};
 
-    this.getRawIndex = this._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices;
+    /**
+     * Data mapping to a plain array
+     */
+    mapArray<Ctx>(cb: MapArrayCb0<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): any[];
+    mapArray<Ctx>(dims: DimensionLoose, cb: MapArrayCb1<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): any[];
+    mapArray<Ctx>(dims: [DimensionLoose], cb: MapArrayCb1<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): any[];
+    mapArray<Ctx>(dims: [DimensionLoose, DimensionLoose], cb: MapArrayCb2<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): any[];
+    mapArray<Ctx>(dims: ItrParamDims, cb: MapArrayCb<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): any[];
+    mapArray<Ctx>(
+        dims: ItrParamDims | MapArrayCb<Ctx>,
+        cb: MapArrayCb<Ctx> | Ctx,
+        ctx?: Ctx,
+        ctxCompat?: Ctx
+    ): any[] {
+        'use strict';
+
+        if (typeof dims === 'function') {
+            ctxCompat = ctx;
+            ctx = cb as Ctx;
+            cb = dims;
+            dims = [];
+        }
 
-    return this;
-};
+        // ctxCompat just for compat echarts3
+        ctx = (ctx || ctxCompat || this) as Ctx;
 
-/**
- * Data mapping to a plain array
- * @param {string|Array.<string>} [dimensions]
- * @param {Function} cb
- * @param {*} [context=this]
- * @return {Array}
- */
-listProto.mapArray = function (dimensions, cb, context, contextCompat) {
-    'use strict';
-
-    if (typeof dimensions === 'function') {
-        contextCompat = context;
-        context = cb;
-        cb = dimensions;
-        dimensions = [];
+        var result: any[] = [];
+        this.each(dims, function () {
+            result.push(cb && (cb as MapArrayCb<Ctx>).apply(this, arguments));
+        }, ctx);
+        return result;
     }
 
-    // contextCompat just for compat echarts3
-    context = context || contextCompat || this;
+    /**
+     * Data mapping to a new List with given dimensions
+     */
+    map<Ctx>(dims: DimensionLoose, cb: MapCb1<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): List;
+    map<Ctx>(dims: [DimensionLoose], cb: MapCb1<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): List;
+    map<Ctx>(dims: [DimensionLoose, DimensionLoose], cb: MapCb2<Ctx>, ctx?: Ctx, ctxCompat?: Ctx): List;
+    map<Ctx>(
+        dims: ItrParamDims,
+        cb: MapCb<Ctx>,
+        ctx?: Ctx,
+        ctxCompat?: Ctx
+    ): List {
+        'use strict';
+
+        // ctxCompat just for compat echarts3
+        var fCtx = (ctx || ctxCompat || this) as CtxOrList<Ctx>;
+
+        var dimNames = zrUtil.map(
+            normalizeDimensions(dims), this.getDimension, this
+        );
+
+        if (__DEV__) {
+            validateDimensions(this, dimNames);
+        }
+
+        var list = cloneListForMapAndSample(this, dimNames);
 
-    var result = [];
-    this.each(dimensions, function () {
-        result.push(cb && cb.apply(this, arguments));
-    }, context);
-    return result;
-};
+        // Following properties are all immutable.
+        // So we can reference to the same value
+        list._indices = this._indices;
+        list.getRawIndex = list._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices;
+
+        var storage = list._storage;
 
-// Data in excludeDimensions is copied, otherwise transfered.
-function cloneListForMapAndSample(original, excludeDimensions) {
-    var allDimensions = original.dimensions;
-    var list = new List(
-        zrUtil.map(allDimensions, original.getDimensionInfo, original),
-        original.hostModel
-    );
-    // FIXME If needs stackedOn, value may already been stacked
-    transferProperties(list, original);
-
-    var storage = list._storage = {};
-    var originalStorage = original._storage;
-
-    // Init storage
-    for (var i = 0; i < allDimensions.length; i++) {
-        var dim = allDimensions[i];
-        if (originalStorage[dim]) {
-            // Notice that we do not reset invertedIndicesMap here, becuase
-            // there is no scenario of mapping or sampling ordinal dimension.
-            if (zrUtil.indexOf(excludeDimensions, dim) >= 0) {
-                storage[dim] = cloneDimStore(originalStorage[dim]);
-                list._rawExtent[dim] = getInitialExtent();
-                list._extent[dim] = null;
+        var tmpRetValue = [];
+        var chunkSize = this._chunkSize;
+        var dimSize = dimNames.length;
+        var dataCount = this.count();
+        var values = [];
+        var rawExtent = list._rawExtent;
+
+        for (var dataIndex = 0; dataIndex < dataCount; dataIndex++) {
+            for (var dimIndex = 0; dimIndex < dimSize; dimIndex++) {
+                values[dimIndex] = this.get(dimNames[dimIndex], dataIndex);
             }
-            else {
-                // Direct reference for other dimensions
-                storage[dim] = originalStorage[dim];
+            values[dimSize] = dataIndex;
+
+            var retValue = cb && cb.apply(fCtx, values);
+            if (retValue != null) {
+                // a number or string (in oridinal dimension)?
+                if (typeof retValue !== 'object') {
+                    tmpRetValue[0] = retValue;
+                    retValue = tmpRetValue;
+                }
+
+                var rawIndex = this.getRawIndex(dataIndex);
+                var chunkIndex = Math.floor(rawIndex / chunkSize);
+                var chunkOffset = rawIndex % chunkSize;
+
+                for (var i = 0; i < retValue.length; i++) {
+                    var dim = dimNames[i];
+                    var val = retValue[i];
+                    var rawExtentOnDim = rawExtent[dim];
+
+                    var dimStore = storage[dim];
+                    if (dimStore) {
+                        dimStore[chunkIndex][chunkOffset] = val;
+                    }
+
+                    if (val < rawExtentOnDim[0]) {
+                        rawExtentOnDim[0] = val as number;
+                    }
+                    if (val > rawExtentOnDim[1]) {
+                        rawExtentOnDim[1] = val as number;
+                    }
+                }
             }
         }
-    }
-    return list;
-}
 
-function cloneDimStore(originalDimStore) {
-    var newDimStore = new Array(originalDimStore.length);
-    for (var j = 0; j < originalDimStore.length; j++) {
-        newDimStore[j] = cloneChunk(originalDimStore[j]);
+        return list;
     }
-    return newDimStore;
-}
 
-function getInitialExtent() {
-    return [Infinity, -Infinity];
-}
+    /**
+     * Large data down sampling on given dimension
+     * @param sampleIndex Sample index for name and id
+     */
+    downSample(
+        dimension: DimensionName,
+        rate: number,
+        sampleValue: (frameValues: ParsedDataValue[]) => ParsedDataNumeric,
+        sampleIndex: (frameValues: ParsedDataValue[], value: ParsedDataNumeric) => number
+    ): List {
+        var list = cloneListForMapAndSample(this, [dimension]);
+        var targetStorage = list._storage;
+
+        var frameValues = [];
+        var frameSize = Math.floor(1 / rate);
+
+        var dimStore = targetStorage[dimension];
+        var len = this.count();
+        var chunkSize = this._chunkSize;
+        var rawExtentOnDim = list._rawExtent[dimension];
+
+        var newIndices = new (getIndicesCtor(this))(len);
+
+        var offset = 0;
+        for (var i = 0; i < len; i += frameSize) {
+            // Last frame
+            if (frameSize > len - i) {
+                frameSize = len - i;
+                frameValues.length = frameSize;
+            }
+            for (var k = 0; k < frameSize; k++) {
+                var dataIdx = this.getRawIndex(i + k);
+                var originalChunkIndex = Math.floor(dataIdx / chunkSize);
+                var originalChunkOffset = dataIdx % chunkSize;
+                frameValues[k] = dimStore[originalChunkIndex][originalChunkOffset];
+            }
+            var value = sampleValue(frameValues);
+            var sampleFrameIdx = this.getRawIndex(
+                Math.min(i + sampleIndex(frameValues, value) || 0, len - 1)
+            );
+            var sampleChunkIndex = Math.floor(sampleFrameIdx / chunkSize);
+            var sampleChunkOffset = sampleFrameIdx % chunkSize;
+            // Only write value on the filtered data
+            dimStore[sampleChunkIndex][sampleChunkOffset] = value;
 
-/**
- * Data mapping to a new List with given dimensions
- * @param {string|Array.<string>} dimensions
- * @param {Function} cb
- * @param {*} [context=this]
- * @return {Array}
- */
-listProto.map = function (dimensions, cb, context, contextCompat) {
-    'use strict';
+            if (value < rawExtentOnDim[0]) {
+                rawExtentOnDim[0] = value;
+            }
+            if (value > rawExtentOnDim[1]) {
+                rawExtentOnDim[1] = value;
+            }
+
+            newIndices[offset++] = sampleFrameIdx;
+        }
 
-    // contextCompat just for compat echarts3
-    context = context || contextCompat || this;
+        list._count = offset;
+        list._indices = newIndices;
 
-    dimensions = zrUtil.map(
-        normalizeDimensions(dimensions), this.getDimension, this
-    );
+        list.getRawIndex = getRawIndexWithIndices;
 
-    if (__DEV__) {
-        validateDimensions(this, dimensions);
+        return list;
     }
 
-    var list = cloneListForMapAndSample(this, dimensions);
-
-    // Following properties are all immutable.
-    // So we can reference to the same value
-    list._indices = this._indices;
-    list.getRawIndex = list._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices;
+    /**
+     * Get model of one data item.
+     */
+    // FIXME Model proxy ?
+    getItemModel(idx: number): Model {
+        var hostModel = this.hostModel;
+        return new Model(this.getRawDataItem(idx), hostModel, hostModel && hostModel.ecModel);
+    }
 
-    var storage = list._storage;
+    /**
+     * Create a data differ
+     */
+    diff(otherList: List): DataDiffer {
+        var thisList = this;
+
+        return new DataDiffer(
+            otherList ? otherList.getIndices() : [],
+            this.getIndices(),
+            function (idx) {
+                return getId(otherList, idx);
+            },
+            function (idx) {
+                return getId(thisList, idx);
+            }
+        );
+    }
 
-    var tmpRetValue = [];
-    var chunkSize = this._chunkSize;
-    var dimSize = dimensions.length;
-    var dataCount = this.count();
-    var values = [];
-    var rawExtent = list._rawExtent;
+    /**
+     * Get visual property.
+     */
+    getVisual(key: string): any {
+        var visual = this._visual;
+        return visual && visual[key];
+    }
 
-    for (var dataIndex = 0; dataIndex < dataCount; dataIndex++) {
-        for (var dimIndex = 0; dimIndex < dimSize; dimIndex++) {
-            values[dimIndex] = this.get(dimensions[dimIndex], dataIndex /*, stack */);
+    /**
+     * Set visual property
+     *
+     * @example
+     *  setVisual('color', color);
+     *  setVisual({
+     *      'color': color
+     *  });
+     */
+    setVisual(key: string, val: any): void;
+    setVisual(kvObj: Dictionary<any>): void;
+    setVisual(key: string | Dictionary<any>, val?: any): void {
+        if (isObject<Dictionary<any>>(key)) {
+            for (var name in key) {
+                if (key.hasOwnProperty(name)) {
+                    this.setVisual(name, key[name]);
+                }
+            }
+            return;
         }
-        values[dimSize] = dataIndex;
+        this._visual = this._visual || {};
+        this._visual[key] = val;
+    }
 
-        var retValue = cb && cb.apply(context, values);
-        if (retValue != null) {
-            // a number or string (in oridinal dimension)?
-            if (typeof retValue !== 'object') {
-                tmpRetValue[0] = retValue;
-                retValue = tmpRetValue;
+    /**
+     * Set layout property.
+     */
+    setLayout(key: string, val: any): void;
+    setLayout(kvObj: Dictionary<any>): void;
+    setLayout(key: string | Dictionary<any>, val?: any): void {
+        if (isObject<Dictionary<any>>(key)) {
+            for (var name in key) {
+                if (key.hasOwnProperty(name)) {
+                    this.setLayout(name, key[name]);
+                }
             }
+            return;
+        }
+        this._layout[key] = val;
+    }
+
+    /**
+     * Get layout property.
+     */
+    getLayout(key: string): any {
+        return this._layout[key];
+    }
 
-            var rawIndex = this.getRawIndex(dataIndex);
-            var chunkIndex = Math.floor(rawIndex / chunkSize);
-            var chunkOffset = rawIndex % chunkSize;
+    /**
+     * Get layout of single data item
+     */
+    getItemLayout(idx: number): Dictionary<any> {
+        return this._itemLayouts[idx];
+    }
 
-            for (var i = 0; i < retValue.length; i++) {
-                var dim = dimensions[i];
-                var val = retValue[i];
-                var rawExtentOnDim = rawExtent[dim];
+    /**
+     * Set layout of single data item
+     */
+    setItemLayout<M = false>(
+        idx: number,
+        layout: (M extends true ? Dictionary<any> : any),
+        merge?: M
+    ): void {
+        this._itemLayouts[idx] = merge
+            ? zrUtil.extend(this._itemLayouts[idx] || {}, layout)
+            : layout;
+    }
 
-                var dimStore = storage[dim];
-                if (dimStore) {
-                    dimStore[chunkIndex][chunkOffset] = val;
-                }
+    /**
+     * Clear all layout of single data item
+     */
+    clearItemLayouts(): void {
+        this._itemLayouts.length = 0;
+    }
 
-                if (val < rawExtentOnDim[0]) {
-                    rawExtentOnDim[0] = val;
-                }
-                if (val > rawExtentOnDim[1]) {
-                    rawExtentOnDim[1] = val;
+    /**
+     * Get visual property of single data item
+     */
+    getItemVisual(idx: number, key: string, ignoreParent?: boolean): any {
+        var itemVisual = this._itemVisuals[idx];
+        var val = itemVisual && itemVisual[key];
+        if (val == null && !ignoreParent) {
+            // Use global visual property
+            return this.getVisual(key);
+        }
+        return val;
+    }
+
+    /**
+     * Set visual property of single data item
+     *
+     * @param {number} idx
+     * @param {string|Object} key
+     * @param {*} [value]
+     *
+     * @example
+     *  setItemVisual(0, 'color', color);
+     *  setItemVisual(0, {
+     *      'color': color
+     *  });
+     */
+    setItemVisual(idx: number, key: string, value: any): void;
+    setItemVisual(idx: number, kvObject: Dictionary<any>): void;
+    setItemVisual(idx: number, key: string | Dictionary<any>, value?: any): void {
+        var itemVisual = this._itemVisuals[idx] || {};
+        var hasItemVisual = this.hasItemVisual;
+        this._itemVisuals[idx] = itemVisual;
+
+        if (isObject<Dictionary<any>>(key)) {
+            for (var name in key) {
+                if (key.hasOwnProperty(name)) {
+                    itemVisual[name] = key[name];
+                    hasItemVisual[name] = true;
                 }
             }
+            return;
         }
+        itemVisual[key] = value;
+        hasItemVisual[key] = true;
     }
 
-    return list;
-};
+    /**
+     * Clear itemVisuals and list visual.
+     */
+    clearAllVisual(): void {
+        this._visual = {};
+        this._itemVisuals = [];
+        this.hasItemVisual = {};
+    }
 
-/**
- * Large data down sampling on given dimension
- * @param {string} dimension
- * @param {number} rate
- * @param {Function} sampleValue
- * @param {Function} sampleIndex Sample index for name and id
- */
-listProto.downSample = function (dimension, rate, sampleValue, sampleIndex) {
-    var list = cloneListForMapAndSample(this, [dimension]);
-    var targetStorage = list._storage;
-
-    var frameValues = [];
-    var frameSize = Math.floor(1 / rate);
-
-    var dimStore = targetStorage[dimension];
-    var len = this.count();
-    var chunkSize = this._chunkSize;
-    var rawExtentOnDim = list._rawExtent[dimension];
-
-    var newIndices = new (getIndicesCtor(this))(len);
-
-    var offset = 0;
-    for (var i = 0; i < len; i += frameSize) {
-        // Last frame
-        if (frameSize > len - i) {
-            frameSize = len - i;
-            frameValues.length = frameSize;
-        }
-        for (var k = 0; k < frameSize; k++) {
-            var dataIdx = this.getRawIndex(i + k);
-            var originalChunkIndex = Math.floor(dataIdx / chunkSize);
-            var originalChunkOffset = dataIdx % chunkSize;
-            frameValues[k] = dimStore[originalChunkIndex][originalChunkOffset];
-        }
-        var value = sampleValue(frameValues);
-        var sampleFrameIdx = this.getRawIndex(
-            Math.min(i + sampleIndex(frameValues, value) || 0, len - 1)
-        );
-        var sampleChunkIndex = Math.floor(sampleFrameIdx / chunkSize);
-        var sampleChunkOffset = sampleFrameIdx % chunkSize;
-        // Only write value on the filtered data
-        dimStore[sampleChunkIndex][sampleChunkOffset] = value;
+    /**
+     * Set graphic element relative to data. It can be set as null
+     */
+    setItemGraphicEl(idx: number, el: Element): void {
+        var hostModel = this.hostModel;
 
-        if (value < rawExtentOnDim[0]) {
-            rawExtentOnDim[0] = value;
-        }
-        if (value > rawExtentOnDim[1]) {
-            rawExtentOnDim[1] = value;
+        if (el) {
+            // Add data index and series index for indexing the data by element
+            // Useful in tooltip
+            (el as ECElement).dataIndex = idx;
+            (el as ECElement).dataType = this.dataType;
+            (el as ECElement).seriesIndex = hostModel && (hostModel as any).seriesIndex;
+            if (el.type === 'group') {
+                el.traverse(setItemDataAndSeriesIndex, el);
+            }
         }
 
-        newIndices[offset++] = sampleFrameIdx;
+        this._graphicEls[idx] = el;
     }
 
-    list._count = offset;
-    list._indices = newIndices;
+    getItemGraphicEl(idx: number): Element {
+        return this._graphicEls[idx];
+    }
 
-    list.getRawIndex = getRawIndexWithIndices;
+    eachItemGraphicEl<Ctx = unknown>(
+        cb: (this: Ctx, el: Element, idx: number) => void,
+        context?: Ctx
+    ): void {
+        zrUtil.each(this._graphicEls, function (el, idx) {
+            if (el) {
+                cb && cb.call(context, el, idx);
+            }
+        });
+    }
 
-    return list;
-};
+    /**
+     * Shallow clone a new list except visual and layout properties, and graph elements.
+     * New list only change the indices.
+     */
+    cloneShallow(list?: List): List {
+        if (!list) {
+            var dimensionInfoList = zrUtil.map(this.dimensions, this.getDimensionInfo, this);
+            list = new List(dimensionInfoList, this.hostModel);
+        }
 
-/**
- * Get model of one data item.
- *
- * @param {number} idx
- */
-// FIXME Model proxy ?
-listProto.getItemModel = function (idx) {
-    var hostModel = this.hostModel;
-    return new Model(this.getRawDataItem(idx), hostModel, hostModel && hostModel.ecModel);
-};
+        // FIXME
+        list._storage = this._storage;
 
-/**
- * Create a data differ
- * @param {module:echarts/data/List} otherList
- * @return {module:echarts/data/DataDiffer}
- */
-listProto.diff = function (otherList) {
-    var thisList = this;
-
-    return new DataDiffer(
-        otherList ? otherList.getIndices() : [],
-        this.getIndices(),
-        function (idx) {
-            return getId(otherList, idx);
-        },
-        function (idx) {
-            return getId(thisList, idx);
-        }
-    );
-};
-/**
- * Get visual property.
- * @param {string} key
- */
-listProto.getVisual = function (key) {
-    var visual = this._visual;
-    return visual && visual[key];
-};
+        transferProperties(list, this);
 
-/**
- * Set visual property
- * @param {string|Object} key
- * @param {*} [value]
- *
- * @example
- *  setVisual('color', color);
- *  setVisual({
- *      'color': color
- *  });
- */
-listProto.setVisual = function (key, val) {
-    if (isObject(key)) {
-        for (var name in key) {
-            if (key.hasOwnProperty(name)) {
-                this.setVisual(name, key[name]);
+        // Clone will not change the data extent and indices
+        if (this._indices) {
+            var Ctor = this._indices.constructor as DataArrayLikeConstructor;
+            if (Ctor === Array) {
+                var thisCount = this._indices.length;
+                list._indices = new Ctor(thisCount);
+                for (var i = 0; i < thisCount; i++) {
+                    list._indices[i] = this._indices[i];
+                }
+            }
+            else {
+                list._indices = new (Ctor as DataTypedArrayConstructor)(this._indices);
             }
         }
-        return;
+        else {
+            list._indices = null;
+        }
+        list.getRawIndex = list._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices;
+
+        return list;
     }
-    this._visual = this._visual || {};
-    this._visual[key] = val;
-};
 
-/**
- * Set layout property.
- * @param {string|Object} key
- * @param {*} [val]
- */
-listProto.setLayout = function (key, val) {
-    if (isObject(key)) {
-        for (var name in key) {
-            if (key.hasOwnProperty(name)) {
-                this.setLayout(name, key[name]);
-            }
+    /**
+     * Wrap some method to add more feature
+     */
+    wrapMethod(
+        methodName: FunctionPropertyNames<List>,
+        injectFunction: (...args: any) => any
+    ): void {
+        var originalMethod = this[methodName];
+        if (typeof originalMethod !== 'function') {
+            return;
         }
-        return;
+        this.__wrappedMethods = this.__wrappedMethods || [];
+        this.__wrappedMethods.push(methodName);
+        this[methodName] = function () {
+            var res = (originalMethod as any).apply(this, arguments);
+            return injectFunction.apply(this, [res].concat(zrUtil.slice(arguments)));
+        };
     }
-    this._layout[key] = val;
-};
 
-/**
- * Get layout property.
- * @param  {string} key.
- * @return {*}
- */
-listProto.getLayout = function (key) {
-    return this._layout[key];
-};
 
-/**
- * Get layout of single data item
- * @param {number} idx
- */
-listProto.getItemLayout = function (idx) {
-    return this._itemLayouts[idx];
-};
+    // ----------------------------------------------------------
+    // A work around for internal method visiting private member.
+    // ----------------------------------------------------------
+    static internalField = (function () {
 
-/**
- * Set layout of single data item
- * @param {number} idx
- * @param {Object} layout
- * @param {boolean=} [merge=false]
- */
-listProto.setItemLayout = function (idx, layout, merge) {
-    this._itemLayouts[idx] = merge
-        ? zrUtil.extend(this._itemLayouts[idx] || {}, layout)
-        : layout;
-};
+        defaultDimValueGetters = {
 
-/**
- * Clear all layout of single data item
- */
-listProto.clearItemLayouts = function () {
-    this._itemLayouts.length = 0;
-};
+            arrayRows: getDimValueSimply,
 
-/**
- * Get visual property of single data item
- * @param {number} idx
- * @param {string} key
- * @param {boolean} [ignoreParent=false]
- */
-listProto.getItemVisual = function (idx, key, ignoreParent) {
-    var itemVisual = this._itemVisuals[idx];
-    var val = itemVisual && itemVisual[key];
-    if (val == null && !ignoreParent) {
-        // Use global visual property
-        return this.getVisual(key);
-    }
-    return val;
-};
+            objectRows: function (
+                this: List, dataItem: Dictionary<any>, dimName: string, dataIndex: number, dimIndex: number
+            ): ParsedDataValue {
+                return convertDataValue(dataItem[dimName], this._dimensionInfos[dimName]);
+            },
 
-/**
- * Set visual property of single data item
- *
- * @param {number} idx
- * @param {string|Object} key
- * @param {*} [value]
- *
- * @example
- *  setItemVisual(0, 'color', color);
- *  setItemVisual(0, {
- *      'color': color
- *  });
- */
-listProto.setItemVisual = function (idx, key, value) {
-    var itemVisual = this._itemVisuals[idx] || {};
-    var hasItemVisual = this.hasItemVisual;
-    this._itemVisuals[idx] = itemVisual;
-
-    if (isObject(key)) {
-        for (var name in key) {
-            if (key.hasOwnProperty(name)) {
-                itemVisual[name] = key[name];
-                hasItemVisual[name] = true;
+            keyedColumns: getDimValueSimply,
+
+            original: function (
+                this: List, dataItem: any, dimName: string, dataIndex: number, dimIndex: number
+            ): ParsedDataValue {
+                // Performance sensitive, do not use modelUtil.getDataItemValue.
+                // If dataItem is an plain object with no value field, the var `value`
+                // will be assigned with the object, but it will be tread correctly
+                // in the `convertDataValue`.
+                var value = dataItem && (dataItem.value == null ? dataItem : dataItem.value);
+
+                // If any dataItem is like { value: 10 }
+                if (!this._rawData.pure && isDataItemOption(dataItem)) {
+                    this.hasItemOption = true;
+                }
+                return convertDataValue(
+                    (value instanceof Array)
+                        ? value[dimIndex]
+                        // If value is a single number or something else not array.
+                        : value,
+                    this._dimensionInfos[dimName]
+                );
+            },
+
+            typedArray: function (
+                this: List, dataItem: any, dimName: string, dataIndex: number, dimIndex: number
+            ): ParsedDataValue {
+                return dataItem[dimIndex];
             }
-        }
-        return;
-    }
-    itemVisual[key] = value;
-    hasItemVisual[key] = true;
-};
 
-/**
- * Clear itemVisuals and list visual.
- */
-listProto.clearAllVisual = function () {
-    this._visual = {};
-    this._itemVisuals = [];
-    this.hasItemVisual = {};
-};
+        };
 
-var setItemDataAndSeriesIndex = function (child) {
-    child.seriesIndex = this.seriesIndex;
-    child.dataIndex = this.dataIndex;
-    child.dataType = this.dataType;
-};
-/**
- * Set graphic element relative to data. It can be set as null
- * @param {number} idx
- * @param {module:zrender/Element} [el]
- */
-listProto.setItemGraphicEl = function (idx, el) {
-    var hostModel = this.hostModel;
-
-    if (el) {
-        // Add data index and series index for indexing the data by element
-        // Useful in tooltip
-        el.dataIndex = idx;
-        el.dataType = this.dataType;
-        el.seriesIndex = hostModel && hostModel.seriesIndex;
-        if (el.type === 'group') {
-            el.traverse(setItemDataAndSeriesIndex, el);
+        function getDimValueSimply(
+            this: List, dataItem: any, dimName: string, dataIndex: number, dimIndex: number
+        ): ParsedDataValue {
+            return convertDataValue(dataItem[dimIndex], this._dimensionInfos[dimName]);
         }
-    }
 
-    this._graphicEls[idx] = el;
-};
+        /**
+         * Convert raw the value in to inner value in List.
+         * [Caution]: this is the key logic of user value parser.
+         * For backward compatibiliy, do not modify it until have to.
+         */
+        function convertDataValue(value: any, dimInfo: DataDimensionInfo): ParsedDataValue {
+            // Performance sensitive.
+            var dimType = dimInfo && dimInfo.type;
+            if (dimType === 'ordinal') {
+                // If given value is a category string
+                var ordinalMeta = dimInfo && dimInfo.ordinalMeta;
+                return ordinalMeta
+                    ? ordinalMeta.parseAndCollect(value)
+                    : value;
+            }
 
-/**
- * @param {number} idx
- * @return {module:zrender/Element}
- */
-listProto.getItemGraphicEl = function (idx) {
-    return this._graphicEls[idx];
-};
+            if (dimType === 'time'
+                // spead up when using timestamp
+                && typeof value !== 'number'
+                && value != null
+                && value !== '-'
+            ) {
+                value = +parseDate(value);
+            }
 
-/**
- * @param {Function} cb
- * @param {*} context
- */
-listProto.eachItemGraphicEl = function (cb, context) {
-    zrUtil.each(this._graphicEls, function (el, idx) {
-        if (el) {
-            cb && cb.call(context, el, idx);
+            // dimType defaults 'number'.
+            // If dimType is not ordinal and value is null or undefined or NaN or '-',
+            // parse to NaN.
+            return (value == null || value === '')
+                ? NaN
+                // If string (like '-'), using '+' parse to NaN
+                // If object, also parse to NaN
+                : +value;
+        };
+
+        prepareInvertedIndex = function (list: List): void {
+            var invertedIndicesMap = list._invertedIndicesMap;
+            zrUtil.each(invertedIndicesMap, function (invertedIndices, dim) {
+                var dimInfo = list._dimensionInfos[dim];
+
+                // Currently, only dimensions that has ordinalMeta can create inverted indices.
+                var ordinalMeta = dimInfo.ordinalMeta;
+                if (ordinalMeta) {
+                    invertedIndices = invertedIndicesMap[dim] = new CtorInt32Array(
+                        ordinalMeta.categories.length
+                    );
+                    // The default value of TypedArray is 0. To avoid miss
+                    // mapping to 0, we should set it as INDEX_NOT_FOUND.
+                    for (var i = 0; i < invertedIndices.length; i++) {
+                        invertedIndices[i] = INDEX_NOT_FOUND;
+                    }
+                    for (var i = 0; i < list._count; i++) {
+                        // Only support the case that all values are distinct.
+                        invertedIndices[list.get(dim, i) as number] = i;
+                    }
+                }
+            });
+        };
+
+        getRawValueFromStore = function (list: List, dimIndex: number, rawIndex: number): any {
+            var val;
+            if (dimIndex != null) {
+                var chunkSize = list._chunkSize;
+                var chunkIndex = Math.floor(rawIndex / chunkSize);
+                var chunkOffset = rawIndex % chunkSize;
+                var dim = list.dimensions[dimIndex];
+                var chunk = list._storage[dim][chunkIndex];
+                if (chunk) {
+                    val = chunk[chunkOffset];
+                    var ordinalMeta = list._dimensionInfos[dim].ordinalMeta;
+                    if (ordinalMeta && ordinalMeta.categories.length) {
+                        val = ordinalMeta.categories[val as OrdinalRawValueIndex];
+                    }
+                }
+            }
+            return val;
+        };
+
+        getIndicesCtor = function (list: List): DataArrayLikeConstructor {
+            // The possible max value in this._indicies is always this._rawCount despite of filtering.
+            return list._rawCount > 65535 ? CtorUint32Array : CtorUint16Array;
+        };
+
+        prepareChunks = function (
+            storage: DataStorage,
+            dimInfo: DataDimensionInfo,
+            chunkSize: number,
+            chunkCount: number,
+            end: number
+        ): void {
+            var DataCtor = dataCtors[dimInfo.type];
+            var lastChunkIndex = chunkCount - 1;
+            var dim = dimInfo.name;
+            var resizeChunkArray = storage[dim][lastChunkIndex];
+            if (resizeChunkArray && resizeChunkArray.length < chunkSize) {
+                var newStore = new DataCtor(Math.min(end - lastChunkIndex * chunkSize, chunkSize));
+                // The cost of the copy is probably inconsiderable
+                // within the initial chunkSize.
+                for (var j = 0; j < resizeChunkArray.length; j++) {
+                    newStore[j] = resizeChunkArray[j];
+                }
+                storage[dim][lastChunkIndex] = newStore;
+            }
+
+            // Create new chunks.
+            for (var k = chunkCount * chunkSize; k < end; k += chunkSize) {
+                storage[dim].push(new DataCtor(Math.min(end - k, chunkSize)));
+            }
+        };
+
+        getRawIndexWithoutIndices = function (this: List, idx: number): number {
+            return idx;
+        };
+
+        getRawIndexWithIndices = function (this: List, idx: number): number {
+            if (idx < this._count && idx >= 0) {
+                return this._indices[idx];
+            }
+            return -1;
+        };
+
+        getId = function (list: List, rawIndex: number): string {
+            var id = list._idList[rawIndex];
+            if (id == null) {
+                id = getRawValueFromStore(list, list._idDimIdx, rawIndex);
+            }
+            if (id == null) {
+                // FIXME Check the usage in graph, should not use prefix.
+                id = ID_PREFIX + rawIndex;
+            }
+            return id;
+        };
+
+        normalizeDimensions = function (
+            dimensions: ItrParamDims
+        ): Array<DimensionLoose> {
+            if (!zrUtil.isArray(dimensions)) {
+                dimensions = [dimensions];
+            }
+            return dimensions;
+        };
+
+        validateDimensions = function (list: List, dims: DimensionName[]): void {
+            for (var i = 0; i < dims.length; i++) {
+                // stroage may be empty when no data, so use
+                // dimensionInfos to check.
+                if (!list._dimensionInfos[dims[i]]) {
+                    console.error('Unkown dimension ' + dims[i]);
+                }
+            }
+        };
+
+        // Data in excludeDimensions is copied, otherwise transfered.
+        cloneListForMapAndSample = function (
+            original: List, excludeDimensions: DimensionName[]
+        ): List {
+            var allDimensions = original.dimensions;
+            var list = new List(
+                zrUtil.map(allDimensions, original.getDimensionInfo, original),
+                original.hostModel
+            );
+            // FIXME If needs stackedOn, value may already been stacked
+            transferProperties(list, original);
+
+            var storage = list._storage = {} as DataStorage;
+            var originalStorage = original._storage;
+
+            // Init storage
+            for (var i = 0; i < allDimensions.length; i++) {
+                var dim = allDimensions[i];
+                if (originalStorage[dim]) {
+                    // Notice that we do not reset invertedIndicesMap here, becuase
+                    // there is no scenario of mapping or sampling ordinal dimension.
+                    if (zrUtil.indexOf(excludeDimensions, dim) >= 0) {
+                        storage[dim] = cloneDimStore(originalStorage[dim]);
+                        list._rawExtent[dim] = getInitialExtent();
+                        list._extent[dim] = null;
+                    }
+                    else {
+                        // Direct reference for other dimensions
+                        storage[dim] = originalStorage[dim];
+                    }
+                }
+            }
+            return list;
+        };
+
+        cloneDimStore = function (originalDimStore: DataValueChunk[]): DataValueChunk[] {
+            var newDimStore = new Array(originalDimStore.length);
+            for (var j = 0; j < originalDimStore.length; j++) {
+                newDimStore[j] = cloneChunk(originalDimStore[j]);
+            }
+            return newDimStore;
+        };
+
+        function cloneChunk(originalChunk: DataValueChunk): DataValueChunk {
+            var Ctor = originalChunk.constructor;
+            // Only shallow clone is enough when Array.
+            return Ctor === Array
+                ? (originalChunk as Array<ParsedDataValue>).slice()
+                : new (Ctor as DataTypedArrayConstructor)(originalChunk as DataTypedArray);
         }
-    });
-};
 
-/**
- * Shallow clone a new list except visual and layout properties, and graph elements.
- * New list only change the indices.
- */
-listProto.cloneShallow = function (list) {
-    if (!list) {
-        var dimensionInfoList = zrUtil.map(this.dimensions, this.getDimensionInfo, this);
-        list = new List(dimensionInfoList, this.hostModel);
-    }
+        getInitialExtent = function (): [number, number] {
+            return [Infinity, -Infinity];
+        };
+
+        setItemDataAndSeriesIndex = function (this: Element, child: Element): void {
+            (child as ECElement).seriesIndex = (this as ECElement).seriesIndex;
+            (child as ECElement).dataIndex = (this as ECElement).dataIndex;
+            (child as ECElement).dataType = (this as ECElement).dataType;
+        };
+
+        transferProperties = function (target: List, source: List): void {
+            zrUtil.each(
+                TRANSFERABLE_PROPERTIES.concat(source.__wrappedMethods || []),
+                function (propName) {
+                    if (source.hasOwnProperty(propName)) {
+                        (target as any)[propName] = (source as any)[propName];
+                    }
+                }
+            );
 
-    // FIXME
-    list._storage = this._storage;
+            target.__wrappedMethods = source.__wrappedMethods;
 
-    transferProperties(list, this);
+            zrUtil.each(CLONE_PROPERTIES, function (propName) {
+                (target as any)[propName] = zrUtil.clone((source as any)[propName]);
+            });
 
-    // Clone will not change the data extent and indices
-    if (this._indices) {
-        var Ctor = this._indices.constructor;
-        list._indices = new Ctor(this._indices);
-    }
-    else {
-        list._indices = null;
-    }
-    list.getRawIndex = list._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices;
+            target._calculationInfo = zrUtil.extend({}, source._calculationInfo);
+        };
 
-    return list;
-};
+    })();
 
-/**
- * Wrap some method to add more feature
- * @param {string} methodName
- * @param {Function} injectFunction
- */
-listProto.wrapMethod = function (methodName, injectFunction) {
-    var originalMethod = this[methodName];
-    if (typeof originalMethod !== 'function') {
-        return;
-    }
-    this.__wrappedMethods = this.__wrappedMethods || [];
-    this.__wrappedMethods.push(methodName);
-    this[methodName] = function () {
-        var res = originalMethod.apply(this, arguments);
-        return injectFunction.apply(this, [res].concat(zrUtil.slice(arguments)));
-    };
-};
+}
+
+// -----------------------------
+// Internal method declarations:
+// -----------------------------
+var defaultDimValueGetters: {[sourceFormat: string]: DimValueGetter};
+var prepareInvertedIndex: (list: List) => void;
+var getRawValueFromStore: (list: List, dimIndex: number, rawIndex: number) => any;
+var getIndicesCtor: (list: List) => DataArrayLikeConstructor;
+var prepareChunks: (
+    storage: DataStorage, dimInfo: DataDimensionInfo, chunkSize: number, chunkCount: number, end: number
+) => void;
+var getRawIndexWithoutIndices: (this: List, idx: number) => number;
+var getRawIndexWithIndices: (this: List, idx: number) => number;
+var getId: (list: List, rawIndex: number) => string;
+var normalizeDimensions: (dimensions: ItrParamDims) => Array<DimensionLoose>;
+var validateDimensions: (list: List, dims: DimensionName[]) => void;
+var cloneListForMapAndSample: (original: List, excludeDimensions: DimensionName[]) => List;
+var cloneDimStore: (originalDimStore: DataValueChunk[]) => DataValueChunk[];
+var getInitialExtent: () => [number, number];
+var setItemDataAndSeriesIndex: (this: Element, child: Element) => void;
+var transferProperties: (target: List, source: List) => void;
 
-// Methods that create a new list based on this list should be listed here.
-// Notice that those method should `RETURN` the new list.
-listProto.TRANSFERABLE_METHODS = ['cloneShallow', 'downSample', 'map'];
-// Methods that change indices of this list should be listed here.
-listProto.CHANGABLE_METHODS = ['filterSelf', 'selectRange'];
 
 export default List;
diff --git a/src/data/OrdinalMeta.ts b/src/data/OrdinalMeta.ts
index d20c1d1..5beb8a1 100644
--- a/src/data/OrdinalMeta.ts
+++ b/src/data/OrdinalMeta.ts
@@ -17,125 +17,103 @@
 * under the License.
 */
 
-import {createHashMap, isObject, map} from 'zrender/src/core/util';
+import {createHashMap, isObject, map, HashMap} from 'zrender/src/core/util';
+import Model from '../model/Model';
 
-/**
- * @constructor
- * @param {Object} [opt]
- * @param {Object} [opt.categories=[]]
- * @param {Object} [opt.needCollect=false]
- * @param {Object} [opt.deduplication=false]
- */
-function OrdinalMeta(opt) {
+class OrdinalMeta {
 
-    /**
-     * @readOnly
-     * @type {Array.<string>}
-     */
-    this.categories = opt.categories || [];
+    readonly categories: string[];
 
-    /**
-     * @private
-     * @type {boolean}
-     */
-    this._needCollect = opt.needCollect;
+    private _needCollect: boolean;
 
-    /**
-     * @private
-     * @type {boolean}
-     */
-    this._deduplication = opt.deduplication;
+    private _deduplication: boolean;
+
+    private _map: HashMap<number>;
 
-    /**
-     * @private
-     * @type {boolean}
-     */
-    this._map;
-}
 
-/**
- * @param {module:echarts/model/Model} axisModel
- * @return {module:echarts/data/OrdinalMeta}
- */
-OrdinalMeta.createByAxisModel = function (axisModel) {
-    var option = axisModel.option;
-    var data = option.data;
-    var categories = data && map(data, getName);
-
-    return new OrdinalMeta({
-        categories: categories,
-        needCollect: !categories,
-        // deduplication is default in axis.
-        deduplication: option.dedplication !== false
-    });
-};
-
-var proto = OrdinalMeta.prototype;
-
-/**
- * @param {string} category
- * @return {number} ordinal
- */
-proto.getOrdinal = function (category) {
-    return getOrCreateMap(this).get(category);
-};
-
-/**
- * @param {*} category
- * @return {number} The ordinal. If not found, return NaN.
- */
-proto.parseAndCollect = function (category) {
-    var index;
-    var needCollect = this._needCollect;
-
-    // The value of category dim can be the index of the given category set.
-    // This feature is only supported when !needCollect, because we should
-    // consider a common case: a value is 2017, which is a number but is
-    // expected to be tread as a category. This case usually happen in dataset,
-    // where it happent to be no need of the index feature.
-    if (typeof category !== 'string' && !needCollect) {
-        return category;
+    constructor(opt: {
+        categories?: string[],
+        needCollect?: boolean
+        deduplication?: boolean
+    }) {
+        this.categories = opt.categories || [];
+        this._needCollect = opt.needCollect;
+        this._deduplication = opt.deduplication;
     }
 
-    // Optimize for the scenario:
-    // category is ['2012-01-01', '2012-01-02', ...], where the input
-    // data has been ensured not duplicate and is large data.
-    // Notice, if a dataset dimension provide categroies, usually echarts
-    // should remove duplication except user tell echarts dont do that
-    // (set axis.deduplication = false), because echarts do not know whether
-    // the values in the category dimension has duplication (consider the
-    // parallel-aqi example)
-    if (needCollect && !this._deduplication) {
-        index = this.categories.length;
-        this.categories[index] = category;
-        return index;
+    static createByAxisModel(axisModel: Model): OrdinalMeta {
+        var option = axisModel.option;
+        var data = option.data;
+        var categories = data && map(data, getName);
+
+        return new OrdinalMeta({
+            categories: categories,
+            needCollect: !categories,
+            // deduplication is default in axis.
+            deduplication: option.dedplication !== false
+        });
+    };
+
+    getOrdinal(category: string): number {
+        return this._getOrCreateMap().get(category);
     }
 
-    var map = getOrCreateMap(this);
-    index = map.get(category);
+    /**
+     * @return The ordinal. If not found, return NaN.
+     */
+    parseAndCollect(category: any): number {
+        var index;
+        var needCollect = this._needCollect;
+
+        // The value of category dim can be the index of the given category set.
+        // This feature is only supported when !needCollect, because we should
+        // consider a common case: a value is 2017, which is a number but is
+        // expected to be tread as a category. This case usually happen in dataset,
+        // where it happent to be no need of the index feature.
+        if (typeof category !== 'string' && !needCollect) {
+            return category;
+        }
 
-    if (index == null) {
-        if (needCollect) {
+        // Optimize for the scenario:
+        // category is ['2012-01-01', '2012-01-02', ...], where the input
+        // data has been ensured not duplicate and is large data.
+        // Notice, if a dataset dimension provide categroies, usually echarts
+        // should remove duplication except user tell echarts dont do that
+        // (set axis.deduplication = false), because echarts do not know whether
+        // the values in the category dimension has duplication (consider the
+        // parallel-aqi example)
+        if (needCollect && !this._deduplication) {
             index = this.categories.length;
             this.categories[index] = category;
-            map.set(category, index);
+            return index;
         }
-        else {
-            index = NaN;
+
+        var map = this._getOrCreateMap();
+        index = map.get(category);
+
+        if (index == null) {
+            if (needCollect) {
+                index = this.categories.length;
+                this.categories[index] = category;
+                map.set(category, index);
+            }
+            else {
+                index = NaN;
+            }
         }
-    }
 
-    return index;
-};
+        return index;
+    }
 
-// Consider big data, do not create map until needed.
-function getOrCreateMap(ordinalMeta) {
-    return ordinalMeta._map || (
-        ordinalMeta._map = createHashMap(ordinalMeta.categories)
-    );
+    // Consider big data, do not create map until needed.
+    private _getOrCreateMap(): HashMap<number> {
+        return this._map || (
+            this._map = createHashMap<number>(this.categories)
+        );
+    }
 }
 
-function getName(obj) {
+function getName(obj: any): string {
     if (isObject(obj) && obj.value != null) {
         return obj.value;
     }
diff --git a/src/data/Source.ts b/src/data/Source.ts
index 55acf3a..4d1b5fb 100644
--- a/src/data/Source.ts
+++ b/src/data/Source.ts
@@ -17,15 +17,17 @@
 * under the License.
 */
 
-import {createHashMap, isTypedArray} from 'zrender/src/core/util';
-import {enableClassCheck} from '../util/clazz';
+import {createHashMap, isTypedArray, HashMap} from 'zrender/src/core/util';
+import {enableClassCheck, CheckableConstructor} from '../util/clazz';
 import {
+    SourceFormat, SeriesLayoutBy, DimensionDefinition,
+    OptionEncodeValue, OptionSourceData, OptionEncode,
     SOURCE_FORMAT_ORIGINAL,
     SERIES_LAYOUT_BY_COLUMN,
     SOURCE_FORMAT_UNKNOWN,
-    SOURCE_FORMAT_TYPED_ARRAY,
-    SOURCE_FORMAT_KEYED_COLUMNS
-} from './helper/sourceType';
+    SOURCE_FORMAT_KEYED_COLUMNS,
+    SOURCE_FORMAT_TYPED_ARRAY
+} from '../util/types';
 
 /**
  * [sourceFormat]
@@ -63,87 +65,89 @@ import {
  * + "unknown"
  */
 
-/**
- * @constructor
- * @param {Object} fields
- * @param {string} fields.sourceFormat
- * @param {Array|Object} fields.fromDataset
- * @param {Array|Object} [fields.data]
- * @param {string} [seriesLayoutBy='column']
- * @param {Array.<Object|string>} [dimensionsDefine]
- * @param {Objet|HashMap} [encodeDefine]
- * @param {number} [startIndex=0]
- * @param {number} [dimensionsDetectCount]
- */
-function Source(fields) {
+class Source {
 
-    /**
-     * @type {boolean}
-     */
-    this.fromDataset = fields.fromDataset;
+    readonly fromDataset: boolean;
 
     /**
      * Not null/undefined.
      * @type {Array|Object}
      */
-    this.data = fields.data || (
-        fields.sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS ? {} : []
-    );
+    readonly data: OptionSourceData;
 
     /**
      * See also "detectSourceFormat".
      * Not null/undefined.
-     * @type {string}
      */
-    this.sourceFormat = fields.sourceFormat || SOURCE_FORMAT_UNKNOWN;
+    readonly sourceFormat: SourceFormat;
 
     /**
      * 'row' or 'column'
      * Not null/undefined.
-     * @type {string} seriesLayoutBy
      */
-    this.seriesLayoutBy = fields.seriesLayoutBy || SERIES_LAYOUT_BY_COLUMN;
+    readonly seriesLayoutBy: SeriesLayoutBy;
 
     /**
      * dimensions definition in option.
      * can be null/undefined.
-     * @type {Array.<Object|string>}
      */
-    this.dimensionsDefine = fields.dimensionsDefine;
+    readonly dimensionsDefine: DimensionDefinition[];
 
     /**
      * encode definition in option.
      * can be null/undefined.
-     * @type {Objet|HashMap}
      */
-    this.encodeDefine = fields.encodeDefine && createHashMap(fields.encodeDefine);
+    readonly encodeDefine: HashMap<OptionEncodeValue>;
 
     /**
      * Not null/undefined, uint.
-     * @type {number}
      */
-    this.startIndex = fields.startIndex || 0;
+    readonly startIndex: number;
 
     /**
      * Can be null/undefined (when unknown), uint.
-     * @type {number}
      */
-    this.dimensionsDetectCount = fields.dimensionsDetectCount;
+    readonly dimensionsDetectCount: number;
+
+
+    constructor(fields: {
+        fromDataset: boolean,
+        data?: OptionSourceData,
+        sourceFormat?: SourceFormat, // default: SOURCE_FORMAT_UNKNOWN
+        seriesLayoutBy?: SeriesLayoutBy, // default: 'column'
+        dimensionsDefine?: DimensionDefinition[],
+        encodeDefine?: OptionEncode,
+        startIndex?: number, // default: 0
+        dimensionsDetectCount?: number
+    }) {
+
+        this.fromDataset = fields.fromDataset;
+        this.data = fields.data || (
+            fields.sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS ? {} : []
+        );
+        this.sourceFormat = fields.sourceFormat || SOURCE_FORMAT_UNKNOWN;
+        this.seriesLayoutBy = fields.seriesLayoutBy || SERIES_LAYOUT_BY_COLUMN;
+        this.dimensionsDefine = fields.dimensionsDefine;
+        this.encodeDefine = fields.encodeDefine && createHashMap(fields.encodeDefine);
+        this.startIndex = fields.startIndex || 0;
+        this.dimensionsDetectCount = fields.dimensionsDetectCount;
+    }
+
+    /**
+     * Wrap original series data for some compatibility cases.
+     */
+    static seriesDataToSource(data: OptionSourceData): Source {
+        return new Source({
+            data: data,
+            sourceFormat: isTypedArray(data)
+                ? SOURCE_FORMAT_TYPED_ARRAY
+                : SOURCE_FORMAT_ORIGINAL,
+            fromDataset: false
+        });
+    };
 }
 
-/**
- * Wrap original series data for some compatibility cases.
- */
-Source.seriesDataToSource = function (data) {
-    return new Source({
-        data: data,
-        sourceFormat: isTypedArray(data)
-            ? SOURCE_FORMAT_TYPED_ARRAY
-            : SOURCE_FORMAT_ORIGINAL,
-        fromDataset: false
-    });
-};
-
-enableClassCheck(Source);
+export type SourceConstructor = typeof Source & CheckableConstructor;
+enableClassCheck(Source as SourceConstructor);
 
 export default Source;
\ No newline at end of file
diff --git a/src/data/helper/completeDimensions.ts b/src/data/helper/completeDimensions.ts
index 41fa8eb..24e766a 100644
--- a/src/data/helper/completeDimensions.ts
+++ b/src/data/helper/completeDimensions.ts
@@ -17,6 +17,8 @@
 * under the License.
 */
 
+// @ts-nocheck
+
 /**
  * @deprecated
  * Use `echarts/data/helper/createDimensions` instead.
@@ -26,7 +28,7 @@ import {createHashMap, each, isString, defaults, extend, isObject, clone} from '
 import {normalizeToArray} from '../../util/model';
 import {guessOrdinal, BE_ORDINAL} from './sourceHelper';
 import Source from '../Source';
-import {OTHER_DIMENSIONS} from './dimensionHelper';
+import {VISUAL_DIMENSIONS} from '../../util/types';
 import DataDimensionInfo from '../DataDimensionInfo';
 
 /**
@@ -190,7 +192,7 @@ function completeDimensions(sysDims, source, opt) {
     });
 
     function applyDim(resultItem, coordDim, coordDimIndex) {
-        if (OTHER_DIMENSIONS.get(coordDim) != null) {
+        if (VISUAL_DIMENSIONS.get(coordDim) != null) {
             resultItem.otherDims[coordDim] = coordDimIndex;
         }
         else {
diff --git a/src/data/helper/dataProvider.ts b/src/data/helper/dataProvider.ts
index bac6936..c6a9e04 100644
--- a/src/data/helper/dataProvider.ts
+++ b/src/data/helper/dataProvider.ts
@@ -23,292 +23,289 @@
 
 import {__DEV__} from '../../config';
 import {isTypedArray, extend, assert, each, isObject} from 'zrender/src/core/util';
-import {getDataItemValue, isDataItemOption} from '../../util/model';
-import {parseDate} from '../../util/number';
-import Source from '../Source';
+import {getDataItemValue} from '../../util/model';
+import Source, { SourceConstructor } from '../Source';
+import {ArrayLike, Dictionary} from 'zrender/src/core/types';
 import {
+    SOURCE_FORMAT_ORIGINAL,
+    SOURCE_FORMAT_OBJECT_ROWS,
+    SOURCE_FORMAT_KEYED_COLUMNS,
     SOURCE_FORMAT_TYPED_ARRAY,
     SOURCE_FORMAT_ARRAY_ROWS,
-    SOURCE_FORMAT_ORIGINAL,
-    SOURCE_FORMAT_OBJECT_ROWS
-} from './sourceType';
+    SERIES_LAYOUT_BY_COLUMN,
+    SERIES_LAYOUT_BY_ROW,
+    DimensionName, DimensionIndex, OptionSourceData,
+    DimensionIndexLoose, OptionDataItem, OptionDataPrimitive
+} from '../../util/types';
+import List from '../List';
+
+
+export interface DataProvider {
+    // If data is pure without style configuration
+    pure: boolean;
+    // If data is persistent and will not be released after use.
+    persistent: boolean;
+
+    getSource(): Source;
+    count(): number;
+    getItem(idx: number, out?: OptionDataItem): OptionDataItem;
+    appendData(newData: ArrayLike<OptionDataItem>): void;
+    clean(): void;
+}
 
 /**
  * If normal array used, mutable chunk size is supported.
  * If typed array used, chunk size must be fixed.
  */
-export function DefaultDataProvider(source, dimSize) {
-    if (!Source.isInstance(source)) {
-        source = Source.seriesDataToSource(source);
-    }
-    this._source = source;
+export class DefaultDataProvider implements DataProvider {
 
-    var data = this._data = source.data;
-    var sourceFormat = source.sourceFormat;
+    private _source: Source;
 
-    // Typed array. TODO IE10+?
-    if (sourceFormat === SOURCE_FORMAT_TYPED_ARRAY) {
-        if (__DEV__) {
-            if (dimSize == null) {
-                throw new Error('Typed array data must specify dimension size');
-            }
-        }
-        this._offset = 0;
-        this._dimSize = dimSize;
-        this._data = data;
-    }
+    private _data: OptionSourceData;
 
-    var methods = providerMethods[
-        sourceFormat === SOURCE_FORMAT_ARRAY_ROWS
-        ? sourceFormat + '_' + source.seriesLayoutBy
-        : sourceFormat
-    ];
+    private _offset: number;
 
-    if (__DEV__) {
-        assert(methods, 'Invalide sourceFormat: ' + sourceFormat);
-    }
+    private _dimSize: number;
 
-    extend(this, methods);
-}
+    pure: boolean;
 
-var providerProto = DefaultDataProvider.prototype;
-// If data is pure without style configuration
-providerProto.pure = false;
-// If data is persistent and will not be released after use.
-providerProto.persistent = true;
+    persistent: boolean;
 
-// ???! FIXME legacy data provider do not has method getSource
-providerProto.getSource = function () {
-    return this._source;
-};
+    static protoInitialize = (function () {
+        // PENDING: To avoid potential incompat (e.g., prototype
+        // is visited somewhere), still init them on prototype.
+        var proto = DefaultDataProvider.prototype;
+        proto.pure = false;
+        proto.persistent = true;
+    })();
 
-var providerMethods = {
-
-    'arrayRows_column': {
-        pure: true,
-        count: function () {
-            return Math.max(0, this._data.length - this._source.startIndex);
-        },
-        getItem: function (idx) {
-            return this._data[idx + this._source.startIndex];
-        },
-        appendData: appendDataSimply
-    },
 
-    'arrayRows_row': {
-        pure: true,
-        count: function () {
-            var row = this._data[0];
-            return row ? Math.max(0, row.length - this._source.startIndex) : 0;
-        },
-        getItem: function (idx) {
-            idx += this._source.startIndex;
-            var item = [];
-            var data = this._data;
-            for (var i = 0; i < data.length; i++) {
-                var row = data[i];
-                item.push(row ? row[idx] : null);
+    constructor(sourceParam: Source | OptionSourceData, dimSize?: number) {
+        // var source: Source;
+        var source: Source = !(Source as SourceConstructor).isInstance(sourceParam)
+            ? Source.seriesDataToSource(sourceParam as OptionSourceData)
+            : sourceParam as Source;
+
+        // declare source is Source;
+        this._source = source;
+
+        var data = this._data = source.data;
+        var sourceFormat = source.sourceFormat;
+
+        // Typed array. TODO IE10+?
+        if (sourceFormat === SOURCE_FORMAT_TYPED_ARRAY) {
+            if (__DEV__) {
+                if (dimSize == null) {
+                    throw new Error('Typed array data must specify dimension size');
+                }
             }
-            return item;
-        },
-        appendData: function () {
-            throw new Error('Do not support appendData when set seriesLayoutBy: "row".');
+            this._offset = 0;
+            this._dimSize = dimSize;
+            this._data = data;
         }
-    },
 
-    'objectRows': {
-        pure: true,
-        count: countSimply,
-        getItem: getItemSimply,
-        appendData: appendDataSimply
-    },
+        var methods = providerMethods[
+            sourceFormat === SOURCE_FORMAT_ARRAY_ROWS
+            ? sourceFormat + '_' + source.seriesLayoutBy
+            : sourceFormat
+        ];
 
-    'keyedColumns': {
-        pure: true,
-        count: function () {
-            var dimName = this._source.dimensionsDefine[0].name;
-            var col = this._data[dimName];
-            return col ? col.length : 0;
-        },
-        getItem: function (idx) {
-            var item = [];
-            var dims = this._source.dimensionsDefine;
-            for (var i = 0; i < dims.length; i++) {
-                var col = this._data[dims[i].name];
-                item.push(col ? col[idx] : null);
-            }
-            return item;
-        },
-        appendData: function (newData) {
-            var data = this._data;
-            each(newData, function (newCol, key) {
-                var oldCol = data[key] || (data[key] = []);
-                for (var i = 0; i < (newCol || []).length; i++) {
-                    oldCol.push(newCol[i]);
-                }
-            });
+        if (__DEV__) {
+            assert(methods, 'Invalide sourceFormat: ' + sourceFormat);
         }
-    },
 
-    'original': {
-        count: countSimply,
-        getItem: getItemSimply,
-        appendData: appendDataSimply
-    },
+        extend(this, methods);
+    }
 
-    'typedArray': {
-        persistent: false,
-        pure: true,
-        count: function () {
-            return this._data ? (this._data.length / this._dimSize) : 0;
-        },
-        getItem: function (idx, out) {
-            idx = idx - this._offset;
-            out = out || [];
-            var offset = this._dimSize * idx;
-            for (var i = 0; i < this._dimSize; i++) {
-                out[i] = this._data[offset + i];
-            }
-            return out;
-        },
-        appendData: function (newData) {
-            if (__DEV__) {
-                assert(
-                    isTypedArray(newData),
-                    'Added data must be TypedArray if data in initialization is TypedArray'
-                );
-            }
+    getSource(): Source {
+        return this._source;
+    }
 
-            this._data = newData;
-        },
+    count(): number {
+        return 0;
+    }
 
-        // Clean self if data is already used.
-        clean: function () {
-            // PENDING
-            this._offset += this.count();
-            this._data = null;
-        }
+    getItem(idx: number): OptionDataItem {
+        return;
     }
-};
 
-function countSimply() {
-    return this._data.length;
-}
-function getItemSimply(idx) {
-    return this._data[idx];
-}
-function appendDataSimply(newData) {
-    for (var i = 0; i < newData.length; i++) {
-        this._data.push(newData[i]);
+    appendData(newData: OptionSourceData): void {
     }
-}
 
+    clean(): void {
+    }
 
+    static internalField = (function () {
+
+        providerMethods = {
+
+            [SOURCE_FORMAT_ARRAY_ROWS + '_' + SERIES_LAYOUT_BY_COLUMN]: {
+                pure: true,
+                count: function (this: DefaultDataProvider): number {
+                    return Math.max(0, (this._data as OptionDataItem[][]).length - this._source.startIndex);
+                },
+                getItem: function (this: DefaultDataProvider, idx: number): OptionDataPrimitive[] {
+                    return (this._data as OptionDataPrimitive[][])[idx + this._source.startIndex];
+                },
+                appendData: appendDataSimply
+            },
+
+            [SOURCE_FORMAT_ARRAY_ROWS + '_' + SERIES_LAYOUT_BY_ROW]: {
+                pure: true,
+                count: function (this: DefaultDataProvider): number {
+                    var row = (this._data as OptionDataPrimitive[][])[0];
+                    return row ? Math.max(0, row.length - this._source.startIndex) : 0;
+                },
+                getItem: function (this: DefaultDataProvider, idx: number): OptionDataPrimitive[] {
+                    idx += this._source.startIndex;
+                    var item = [];
+                    var data = this._data as OptionDataPrimitive[][];
+                    for (var i = 0; i < data.length; i++) {
+                        var row = data[i];
+                        item.push(row ? row[idx] : null);
+                    }
+                    return item;
+                },
+                appendData: function () {
+                    throw new Error('Do not support appendData when set seriesLayoutBy: "row".');
+                }
+            },
+
+            [SOURCE_FORMAT_OBJECT_ROWS]: {
+                pure: true,
+                count: countSimply,
+                getItem: getItemSimply,
+                appendData: appendDataSimply
+            },
+
+            [SOURCE_FORMAT_KEYED_COLUMNS]: {
+                pure: true,
+                count: function (this: DefaultDataProvider): number {
+                    var dimName = this._source.dimensionsDefine[0].name;
+                    var col = (this._data as Dictionary<OptionDataPrimitive[]>)[dimName];
+                    return col ? col.length : 0;
+                },
+                getItem: function (this: DefaultDataProvider, idx: number): OptionDataPrimitive[] {
+                    var item = [];
+                    var dims = this._source.dimensionsDefine;
+                    for (var i = 0; i < dims.length; i++) {
+                        var col = (this._data as Dictionary<OptionDataPrimitive[]>)[dims[i].name];
+                        item.push(col ? col[idx] : null);
+                    }
+                    return item;
+                },
+                appendData: function (this: DefaultDataProvider, newData: OptionSourceData) {
+                    var data = this._data as Dictionary<OptionDataPrimitive[]>;
+                    each(newData, function (newCol, key) {
+                        var oldCol = data[key] || (data[key] = []);
+                        for (var i = 0; i < (newCol || []).length; i++) {
+                            oldCol.push(newCol[i]);
+                        }
+                    });
+                }
+            },
+
+            [SOURCE_FORMAT_ORIGINAL]: {
+                count: countSimply,
+                getItem: getItemSimply,
+                appendData: appendDataSimply
+            },
+
+            [SOURCE_FORMAT_TYPED_ARRAY]: {
+                persistent: false,
+                pure: true,
+                count: function (this: DefaultDataProvider): number {
+                    return this._data ? ((this._data as ArrayLike<number>).length / this._dimSize) : 0;
+                },
+                getItem: function (this: DefaultDataProvider, idx: number, out: ArrayLike<number>): ArrayLike<number> {
+                    idx = idx - this._offset;
+                    out = out || [];
+                    var offset = this._dimSize * idx;
+                    for (var i = 0; i < this._dimSize; i++) {
+                        out[i] = (this._data as ArrayLike<number>)[offset + i];
+                    }
+                    return out;
+                },
+                appendData: function (this: DefaultDataProvider, newData: ArrayLike<number>): void {
+                    if (__DEV__) {
+                        assert(
+                            isTypedArray(newData),
+                            'Added data must be TypedArray if data in initialization is TypedArray'
+                        );
+                    }
+
+                    this._data = newData;
+                },
+
+                // Clean self if data is already used.
+                clean: function (this: DefaultDataProvider): void {
+                    // PENDING
+                    this._offset += this.count();
+                    this._data = null;
+                }
+            }
+        };
+
+        function countSimply(this: DefaultDataProvider): number {
+            return (this._data as []).length;
+        }
+        function getItemSimply(this: DefaultDataProvider, idx: number): OptionDataItem {
+            return (this._data as [])[idx];
+        }
+        function appendDataSimply(this: DefaultDataProvider, newData: ArrayLike<OptionDataItem>): void {
+            for (var i = 0; i < newData.length; i++) {
+                (this._data as any[]).push(newData[i]);
+            }
+        }
 
-var rawValueGetters = {
+    })()
+}
 
-    arrayRows: getRawValueSimply,
+var providerMethods: Dictionary<any>;
 
-    objectRows: function (dataItem, dataIndex, dimIndex, dimName) {
+// TODO
+// merge it to dataProvider?
+type RawValueGetter = (
+    dataItem: OptionDataItem,
+    dataIndex: number,
+    dimIndex: DimensionIndex,
+    dimName: DimensionName
+    // If dimIndex not provided, return OptionDataItem.
+    // If dimIndex provided, return OptionDataPrimitive.
+) => OptionDataPrimitive | OptionDataItem;
+
+var rawValueGetters: {[sourceFormat: string]: RawValueGetter} = {
+
+    [SOURCE_FORMAT_ARRAY_ROWS]: getRawValueSimply,
+
+    [SOURCE_FORMAT_OBJECT_ROWS]: function (
+        dataItem: Dictionary<OptionDataPrimitive>, dataIndex: number, dimIndex: number, dimName: string
+    ): OptionDataPrimitive | Dictionary<OptionDataPrimitive> {
         return dimIndex != null ? dataItem[dimName] : dataItem;
     },
 
-    keyedColumns: getRawValueSimply,
+    [SOURCE_FORMAT_KEYED_COLUMNS]: getRawValueSimply,
 
-    original: function (dataItem, dataIndex, dimIndex, dimName) {
-        // FIXME
-        // In some case (markpoint in geo (geo-map.html)), dataItem
-        // is {coord: [...]}
+    [SOURCE_FORMAT_ORIGINAL]: function (
+        dataItem: OptionDataItem, dataIndex: number, dimIndex: number, dimName: string
+    ): OptionDataPrimitive | OptionDataItem {
+        // FIXME: In some case (markpoint in geo (geo-map.html)),
+        // dataItem is {coord: [...]}
         var value = getDataItemValue(dataItem);
         return (dimIndex == null || !(value instanceof Array))
             ? value
             : value[dimIndex];
     },
 
-    typedArray: getRawValueSimply
+    [SOURCE_FORMAT_TYPED_ARRAY]: getRawValueSimply
 };
 
-function getRawValueSimply(dataItem, dataIndex, dimIndex, dimName) {
+function getRawValueSimply(
+    dataItem: ArrayLike<OptionDataPrimitive>, dataIndex: number, dimIndex: number, dimName: string
+): OptionDataPrimitive | ArrayLike<OptionDataPrimitive> {
     return dimIndex != null ? dataItem[dimIndex] : dataItem;
 }
 
-
-export var defaultDimValueGetters = {
-
-    arrayRows: getDimValueSimply,
-
-    objectRows: function (dataItem, dimName, dataIndex, dimIndex) {
-        return converDataValue(dataItem[dimName], this._dimensionInfos[dimName]);
-    },
-
-    keyedColumns: getDimValueSimply,
-
-    original: function (dataItem, dimName, dataIndex, dimIndex) {
-        // Performance sensitive, do not use modelUtil.getDataItemValue.
-        // If dataItem is an plain object with no value field, the var `value`
-        // will be assigned with the object, but it will be tread correctly
-        // in the `convertDataValue`.
-        var value = dataItem && (dataItem.value == null ? dataItem : dataItem.value);
-
-        // If any dataItem is like { value: 10 }
-        if (!this._rawData.pure && isDataItemOption(dataItem)) {
-            this.hasItemOption = true;
-        }
-        return converDataValue(
-            (value instanceof Array)
-                ? value[dimIndex]
-                // If value is a single number or something else not array.
-                : value,
-            this._dimensionInfos[dimName]
-        );
-    },
-
-    typedArray: function (dataItem, dimName, dataIndex, dimIndex) {
-        return dataItem[dimIndex];
-    }
-
-};
-
-function getDimValueSimply(dataItem, dimName, dataIndex, dimIndex) {
-    return converDataValue(dataItem[dimIndex], this._dimensionInfos[dimName]);
-}
-
-/**
- * This helper method convert value in data.
- * @param {string|number|Date} value
- * @param {Object|string} [dimInfo] If string (like 'x'), dimType defaults 'number'.
- *        If "dimInfo.ordinalParseAndSave", ordinal value can be parsed.
- */
-function converDataValue(value, dimInfo) {
-    // Performance sensitive.
-    var dimType = dimInfo && dimInfo.type;
-    if (dimType === 'ordinal') {
-        // If given value is a category string
-        var ordinalMeta = dimInfo && dimInfo.ordinalMeta;
-        return ordinalMeta
-            ? ordinalMeta.parseAndCollect(value)
-            : value;
-    }
-
-    if (dimType === 'time'
-        // spead up when using timestamp
-        && typeof value !== 'number'
-        && value != null
-        && value !== '-'
-    ) {
-        value = +parseDate(value);
-    }
-
-    // dimType defaults 'number'.
-    // If dimType is not ordinal and value is null or undefined or NaN or '-',
-    // parse to NaN.
-    return (value == null || value === '')
-        ? NaN
-        // If string (like '-'), using '+' parse to NaN
-        // If object, also parse to NaN
-        : +value;
-}
-
 // ??? FIXME can these logic be more neat: getRawValue, getRawDataItem,
 // Consider persistent.
 // Caution: why use raw value to display on label or tooltip?
@@ -316,13 +313,9 @@ function converDataValue(value, dimInfo) {
 // how to format is expected. More over, if stack is used, calculated
 // value may be 0.91000000001, which have brings trouble to display.
 // TODO: consider how to treat null/undefined/NaN when display?
-/**
- * @param {module:echarts/data/List} data
- * @param {number} dataIndex
- * @param {string|number} [dim] dimName or dimIndex
- * @return {Array.<number>|string|number} can be null/undefined.
- */
-export function retrieveRawValue(data, dataIndex, dim) {
+export function retrieveRawValue(
+    data: List, dataIndex: number, dim?: DimensionName | DimensionIndexLoose
+): any {
     if (!data) {
         return;
     }
@@ -352,14 +345,14 @@ export function retrieveRawValue(data, dataIndex, dim) {
  * data: [{name: 'xx', value: 5, selected: true}, ...]
  * where only sourceFormat is 'original' and 'objectRows' supported.
  *
- * ??? TODO
+ * // TODO
  * Supported detail options in data item when using 'arrayRows'.
  *
- * @param {module:echarts/data/List} data
- * @param {number} dataIndex
- * @param {string} attr like 'selected'
+ * @param data
+ * @param dataIndex
+ * @param attr like 'selected'
  */
-export function retrieveRawAttr(data, dataIndex, attr) {
+export function retrieveRawAttr(data: List, dataIndex: number, attr: string): any {
     if (!data) {
         return;
     }
@@ -377,6 +370,6 @@ export function retrieveRawAttr(data, dataIndex, attr) {
         dataItem = null;
     }
     if (dataItem) {
-        return dataItem[attr];
+        return (dataItem as Dictionary<OptionDataPrimitive>)[attr];
     }
 }
diff --git a/src/data/helper/dimensionHelper.ts b/src/data/helper/dimensionHelper.ts
index b0e616c..1db36ed 100644
--- a/src/data/helper/dimensionHelper.ts
+++ b/src/data/helper/dimensionHelper.ts
@@ -17,16 +17,44 @@
 * under the License.
 */
 
+// @ts-nocheck
+
 import {each, createHashMap, assert} from 'zrender/src/core/util';
 import { __DEV__ } from '../../config';
+import List from '../List';
+import {
+    DimensionName, DimensionIndex, VISUAL_DIMENSIONS, DimensionType
+} from '../../util/types';
+
+export type DimensionSummaryEncode = {
+    defaultedLabel: DimensionName[],
+    defaultedTooltip: DimensionName[],
+    [coordOrVisualDimName: string]:
+        // index: coordDimIndex, value: dataDimName
+        DimensionName[]
+};
+export type DimensionUserOuputEncode = {
+    [coordOrVisualDimName: string]:
+        // index: coordDimIndex, value: dataDimIndex
+        DimensionIndex[]
+};
+export type DimensionUserOuput = {
+    // The same as `data.dimensions`
+    dimensionNames: DimensionName[]
+    encode: DimensionUserOuputEncode
+};
+export type DimensionSummary = {
+    encode: DimensionSummaryEncode,
+    // Those details that can be expose to users are put int `userOutput`.
+    userOutput: DimensionUserOuput,
+    // All of the data dim names that mapped by coordDim.
+    dataDimsOnCoord: DimensionName[],
+    encodeFirstDimNotExtra: {[coordDim: string]: DimensionName},
+}
 
-export var OTHER_DIMENSIONS = createHashMap([
-    'tooltip', 'label', 'itemName', 'itemId', 'seriesName'
-]);
-
-export function summarizeDimensions(data) {
-    var summary = {};
-    var encode = summary.encode = {};
+export function summarizeDimensions(data: List): DimensionSummary {
+    var summary: DimensionSummary = {} as DimensionSummary;
+    var encode = summary.encode = {} as DimensionSummaryEncode;
     var notExtraCoordDimMap = createHashMap();
     var defaultedLabel = [];
     var defaultedTooltip = [];
@@ -43,7 +71,7 @@ export function summarizeDimensions(data) {
         var coordDim = dimItem.coordDim;
         if (coordDim) {
             if (__DEV__) {
-                assert(OTHER_DIMENSIONS.get(coordDim) == null);
+                assert(VISUAL_DIMENSIONS.get(coordDim) == null);
             }
 
             var coordDimIndex = dimItem.coordDimIndex;
@@ -69,7 +97,7 @@ export function summarizeDimensions(data) {
             }
         }
 
-        OTHER_DIMENSIONS.each(function (v, otherDim) {
+        VISUAL_DIMENSIONS.each(function (v, otherDim) {
             var encodeArr = getOrCreateEncodeArr(encode, otherDim);
 
             var dimIndex = dimItem.otherDims[otherDim];
@@ -117,14 +145,17 @@ export function summarizeDimensions(data) {
     return summary;
 }
 
-function getOrCreateEncodeArr(encode, dim) {
+function getOrCreateEncodeArr(
+    encode: DimensionSummaryEncode, dim: DimensionName
+): DimensionName[] {
     if (!encode.hasOwnProperty(dim)) {
         encode[dim] = [];
     }
     return encode[dim];
 }
 
-export function getDimensionTypeByAxis(axisType) {
+// FIXME:TS should be type `AxisType`
+export function getDimensionTypeByAxis(axisType: string) {
     return axisType === 'category'
         ? 'ordinal'
         : axisType === 'time'
@@ -132,7 +163,7 @@ export function getDimensionTypeByAxis(axisType) {
         : 'float';
 }
 
-function mayLabelDimType(dimType) {
+function mayLabelDimType(dimType: DimensionType): boolean {
     // In most cases, ordinal and time do not suitable for label.
     // Ordinal info can be displayed on axis. Time is too long.
     return !(dimType === 'ordinal' || dimType === 'time');
diff --git a/src/data/helper/sourceHelper.ts b/src/data/helper/sourceHelper.ts
index b58b01f..db59b9e 100644
--- a/src/data/helper/sourceHelper.ts
+++ b/src/data/helper/sourceHelper.ts
@@ -17,6 +17,8 @@
 * under the License.
 */
 
+// @ts-nocheck
+
 import {__DEV__} from '../../config';
 import {makeInner, getDataItemValue} from '../../util/model';
 import {
@@ -37,11 +39,11 @@ import {
     SOURCE_FORMAT_ORIGINAL,
     SOURCE_FORMAT_ARRAY_ROWS,
     SOURCE_FORMAT_OBJECT_ROWS,
+    SERIES_LAYOUT_BY_ROW,
     SOURCE_FORMAT_KEYED_COLUMNS,
-    SOURCE_FORMAT_UNKNOWN,
     SOURCE_FORMAT_TYPED_ARRAY,
-    SERIES_LAYOUT_BY_ROW
-} from './sourceType';
+    SOURCE_FORMAT_UNKNOWN
+} from '../../util/types';
 
 // The result of `guessOrdinal`.
 export var BE_ORDINAL = {
diff --git a/src/data/helper/sourceType.ts b/src/data/helper/sourceType.ts
deleted file mode 100644
index 537f671..0000000
--- a/src/data/helper/sourceType.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
-* Licensed to the Apache Software Foundation (ASF) under one
-* or more contributor license agreements.  See the NOTICE file
-* distributed with this work for additional information
-* regarding copyright ownership.  The ASF licenses this file
-* to you under the Apache License, Version 2.0 (the
-* "License"); you may not use this file except in compliance
-* with the License.  You may obtain a copy of the License at
-*
-*   http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing,
-* software distributed under the License is distributed on an
-* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-* KIND, either express or implied.  See the License for the
-* specific language governing permissions and limitations
-* under the License.
-*/
-
-// Avoid typo.
-export var SOURCE_FORMAT_ORIGINAL = 'original';
-export var SOURCE_FORMAT_ARRAY_ROWS = 'arrayRows';
-export var SOURCE_FORMAT_OBJECT_ROWS = 'objectRows';
-export var SOURCE_FORMAT_KEYED_COLUMNS = 'keyedColumns';
-export var SOURCE_FORMAT_UNKNOWN = 'unknown';
-// ??? CHANGE A NAME
-export var SOURCE_FORMAT_TYPED_ARRAY = 'typedArray';
-
-export var SERIES_LAYOUT_BY_COLUMN = 'column';
-export var SERIES_LAYOUT_BY_ROW = 'row';
diff --git a/src/echarts.ts b/src/echarts.ts
index 36a29cf..32fc419 100644
--- a/src/echarts.ts
+++ b/src/echarts.ts
@@ -16,23 +16,27 @@
 * specific language governing permissions and limitations
 * under the License.
 */
+
 import {__DEV__} from './config';
 import * as zrender from 'zrender/src/zrender';
 import * as zrUtil from 'zrender/src/core/util';
 import * as colorTool from 'zrender/src/tool/color';
 import env from 'zrender/src/core/env';
 import timsort from 'zrender/src/core/timsort';
-import Eventful from 'zrender/src/mixin/Eventful';
-import GlobalModel from './model/Global';
+import Eventful from 'zrender/src/core/Eventful';
+import Element, { ElementEvent } from 'zrender/src/Element';
+import CanvasPainter from 'zrender/src/canvas/Painter';
+import SVGPainter from 'zrender/src/svg/Painter';
+import GlobalModel, {QueryConditionKindA} from './model/Global';
 import ExtensionAPI from './ExtensionAPI';
 import CoordinateSystemManager from './CoordinateSystem';
 import OptionManager from './model/OptionManager';
 import backwardCompat from './preprocessor/backwardCompat';
 import dataStack from './processor/dataStack';
-import ComponentModel from './model/Component';
-import SeriesModel from './model/Series';
-import ComponentView from './view/Component';
-import ChartView from './view/Chart';
+import ComponentModel, { ComponentModelConstructor } from './model/Component';
+import SeriesModel, { SeriesModelConstructor } from './model/Series';
+import ComponentView, {ComponentViewConstructor} from './view/Component';
+import ChartView, {ChartViewConstructor} from './view/Chart';
 import * as graphic from './util/graphic';
 import * as modelUtil from './util/model';
 import {throttle} from './util/throttle';
@@ -43,13 +47,33 @@ import Scheduler from './stream/Scheduler';
 import lightTheme from './theme/light';
 import darkTheme from './theme/dark';
 import './component/dataset';
-import mapDataStorage from './coord/geo/mapDataStorage';
+import mapDataStorage, { GeoMapDefinition, GeoMapGeoJSONSource, GeoSpecialAreas } from './coord/geo/mapDataStorage';
+import {CoordinateSystem, CoordinateSystemCreator} from './coord/CoordinateSystem';
+import { parseClassType } from './util/clazz';
+import {ECEventProcessor} from './util/ECEventProcessor';
+import {
+    Payload, PayloadItem, ECElement, RendererType, ECEvent,
+    ActionHandler, ActionInfo, OptionPreprocessor, PostUpdater,
+    LoadingEffect, LoadingEffectCreator, StageHandler,
+    StageHandlerOverallReset, VisualType, StageHandlerInput,
+    ViewRootGroup, DimensionDefinitionLoose, ECEventData, ThemeOption,
+    ECOption,
+    ECUnitOption,
+    ZRColor,
+    ComponentMainType,
+    ComponentSubType
+} from './util/types';
+import Displayable from 'zrender/src/graphic/Displayable';
+import IncrementalDisplayable from 'zrender/src/graphic/IncrementalDisplayable';
+
+
+declare var global: any;
+type ModelFinder = modelUtil.ModelFinder;
 
 var assert = zrUtil.assert;
 var each = zrUtil.each;
 var isFunction = zrUtil.isFunction;
 var isObject = zrUtil.isObject;
-var parseClassType = ComponentModel.parseClassType;
 
 export var version = '4.6.0';
 
@@ -96,1950 +120,1792 @@ export var PRIORITY = {
 // dispatchAction with updateMethod "none" in main process.
 // This flag is used to carry out this rule.
 // All events will be triggered out side main process (i.e. when !this[IN_MAIN_PROCESS]).
-var IN_MAIN_PROCESS = '__flagInMainProcess';
-var OPTION_UPDATED = '__optionUpdated';
+var IN_MAIN_PROCESS = '__flagInMainProcess' as const;
+var OPTION_UPDATED = '__optionUpdated' as const;
 var ACTION_REG = /^[a-zA-Z0-9_]+$/;
 
+var CONNECT_STATUS_KEY = '__connectUpdateStatus' as const;
+var CONNECT_STATUS_PENDING = 0 as const;
+var CONNECT_STATUS_UPDATING = 1 as const;
+var CONNECT_STATUS_UPDATED = 2 as const;
+type ConnectStatus =
+    typeof CONNECT_STATUS_PENDING
+    | typeof CONNECT_STATUS_UPDATING
+    | typeof CONNECT_STATUS_UPDATED;
+
+type SetOptionOpts = {
+    notMerge?: boolean,
+    lazyUpdate?: boolean,
+    silent?: boolean
+};
 
-function createRegisterEventWithLowercaseName(method, ignoreDisposed) {
-    return function (eventName, handler, context) {
-        if (!ignoreDisposed && this._disposed) {
+type EventMethodName = 'on' | 'off';
+function createRegisterEventWithLowercaseECharts(method: EventMethodName) {
+    return function (this: ECharts, ...args: any): ECharts {
+        if (this.isDisposed()) {
             disposedWarning(this.id);
             return;
         }
-
-        // Event name is all lowercase
-        eventName = eventName && eventName.toLowerCase();
-        Eventful.prototype[method].call(this, eventName, handler, context);
+        return toLowercaseNameAndCallEventful<ECharts>(this, method, args);
     };
 }
-
-/**
- * @module echarts~MessageCenter
- */
-function MessageCenter() {
-    Eventful.call(this);
+function createRegisterEventWithLowercaseMessageCenter(method: EventMethodName) {
+    return function (this: MessageCenter, ...args: any): MessageCenter {
+        return toLowercaseNameAndCallEventful<MessageCenter>(this, method, args);
+    };
+}
+function toLowercaseNameAndCallEventful<T>(host: T, method: EventMethodName, args: any): T {
+    // `args[0]` is event name. Event name is all lowercase.
+    args[0] = args[0] && args[0].toLowerCase();
+    return Eventful.prototype[method].apply(host, args) as any;
 }
-MessageCenter.prototype.on = createRegisterEventWithLowercaseName('on', true);
-MessageCenter.prototype.off = createRegisterEventWithLowercaseName('off', true);
-MessageCenter.prototype.one = createRegisterEventWithLowercaseName('one', true);
-zrUtil.mixin(MessageCenter, Eventful);
 
-/**
- * @module echarts~ECharts
- */
-function ECharts(dom, theme, opts) {
-    opts = opts || {};
 
-    // Get theme by name
-    if (typeof theme === 'string') {
-        theme = themeStorage[theme];
-    }
+class MessageCenter extends Eventful<MessageCenter> {}
+var messageCenterProto = MessageCenter.prototype;
+messageCenterProto.on = createRegisterEventWithLowercaseMessageCenter('on');
+messageCenterProto.off = createRegisterEventWithLowercaseMessageCenter('off');
+// messageCenterProto.one = createRegisterEventWithLowercaseMessageCenter('one');
+
+
+class ECharts {
 
     /**
-     * @type {string}
+     * @readonly
      */
-    this.id;
+    id: string;
 
     /**
      * Group id
-     * @type {string}
+     * @readonly
      */
-    this.group;
+    group: string;
 
-    /**
-     * @type {HTMLElement}
-     * @private
-     */
-    this._dom = dom;
+    private _zr: zrender.ZRenderType;
 
-    var defaultRenderer = 'canvas';
-    if (__DEV__) {
-        defaultRenderer = (
-            typeof window === 'undefined' ? global : window
-        ).__ECHARTS__DEFAULT__RENDERER__ || defaultRenderer;
-    }
+    private _dom: HTMLElement;
 
-    /**
-     * @type {module:zrender/ZRender}
-     * @private
-     */
-    var zr = this._zr = zrender.init(dom, {
-        renderer: opts.renderer || defaultRenderer,
-        devicePixelRatio: opts.devicePixelRatio,
-        width: opts.width,
-        height: opts.height
-    });
+    private _model: GlobalModel;
 
-    /**
-     * Expect 60 fps.
-     * @type {Function}
-     * @private
-     */
-    this._throttledZrFlush = throttle(zrUtil.bind(zr.flush, zr), 17);
+    private _throttledZrFlush: zrender.ZRenderType extends {flush: infer R} ? R : never;
 
-    var theme = zrUtil.clone(theme);
-    theme && backwardCompat(theme, true);
-    /**
-     * @type {Object}
-     * @private
-     */
-    this._theme = theme;
+    private _theme: ThemeOption;
 
-    /**
-     * @type {Array.<module:echarts/view/Chart>}
-     * @private
-     */
-    this._chartsViews = [];
+    private _chartsViews: ChartView[] = [];
 
-    /**
-     * @type {Object.<string, module:echarts/view/Chart>}
-     * @private
-     */
-    this._chartsMap = {};
+    private _chartsMap: {[viewId: string]: ChartView} = {};
 
-    /**
-     * @type {Array.<module:echarts/view/Component>}
-     * @private
-     */
-    this._componentsViews = [];
+    private _componentsViews: ComponentView[] = [];
 
-    /**
-     * @type {Object.<string, module:echarts/view/Component>}
-     * @private
-     */
-    this._componentsMap = {};
+    private _componentsMap: {[viewId: string]: ComponentView} = {};
 
-    /**
-     * @type {module:echarts/CoordinateSystem}
-     * @private
-     */
-    this._coordSysMgr = new CoordinateSystemManager();
+    private _coordSysMgr: CoordinateSystemManager;
 
-    /**
-     * @type {module:echarts/ExtensionAPI}
-     * @private
-     */
-    var api = this._api = createExtensionAPI(this);
+    private _api: ExtensionAPI;
 
-    // Sort on demand
-    function prioritySortFunc(a, b) {
-        return a.__prio - b.__prio;
-    }
-    timsort(visualFuncs, prioritySortFunc);
-    timsort(dataProcessorFuncs, prioritySortFunc);
+    private _scheduler: Scheduler;
 
-    /**
-     * @type {module:echarts/stream/Scheduler}
-     */
-    this._scheduler = new Scheduler(this, api, dataProcessorFuncs, visualFuncs);
+    private _messageCenter: MessageCenter;
 
-    Eventful.call(this, this._ecEventProcessor = new EventProcessor());
+    // Can't dispatch action during rendering procedure
+    private _pendingActions: Payload[] = [];
 
-    /**
-     * @type {module:echarts~MessageCenter}
-     * @private
-     */
-    this._messageCenter = new MessageCenter();
+    private _ecEventProcessor: ECEventProcessor;
 
-    // Init mouse events
-    this._initEvents();
+    private _disposed: boolean;
 
-    // In case some people write `window.onresize = chart.resize`
-    this.resize = zrUtil.bind(this.resize, this);
+    private _loadingFX: LoadingEffect;
 
-    // Can't dispatch action during rendering procedure
-    this._pendingActions = [];
+    private [OPTION_UPDATED]: boolean | {silent: boolean};
+    private [IN_MAIN_PROCESS]: boolean;
+    private [CONNECT_STATUS_KEY]: ConnectStatus;
 
-    zr.animation.on('frame', this._onframe, this);
 
-    bindRenderedEvent(zr, this);
+    constructor(
+        dom: HTMLElement,
+        // Theme name or themeOption.
+        theme?: string | ThemeOption,
+        opts?: {
+            renderer?: RendererType,
+            devicePixelRatio?: number,
+            width?: number,
+            height?: number
+        }
+    ) {
+        opts = opts || {};
 
-    // ECharts instance can be used as value.
-    zrUtil.setAsPrimitive(this);
-}
+        // Get theme by name
+        if (typeof theme === 'string') {
+            theme = themeStorage[theme] as object;
+        }
 
-var echartsProto = ECharts.prototype;
+        this._dom = dom;
 
-echartsProto._onframe = function () {
-    if (this._disposed) {
-        return;
-    }
+        var defaultRenderer = 'canvas';
+        if (__DEV__) {
+            defaultRenderer = ((
+                typeof window === 'undefined' ? global : window
+            ) as any).__ECHARTS__DEFAULT__RENDERER__ || defaultRenderer;
+        }
 
-    var scheduler = this._scheduler;
+        var zr = this._zr = zrender.init(dom, {
+            renderer: opts.renderer || defaultRenderer,
+            devicePixelRatio: opts.devicePixelRatio,
+            width: opts.width,
+            height: opts.height
+        });
 
-    // Lazy update
-    if (this[OPTION_UPDATED]) {
-        var silent = this[OPTION_UPDATED].silent;
+        // Expect 60 fps.
+        this._throttledZrFlush = throttle(zrUtil.bind(zr.flush, zr), 17);
 
-        this[IN_MAIN_PROCESS] = true;
+        theme = zrUtil.clone(theme);
+        theme && backwardCompat(theme, true);
 
-        prepare(this);
-        updateMethods.update.call(this);
+        this._theme = theme;
 
-        this[IN_MAIN_PROCESS] = false;
+        this._coordSysMgr = new CoordinateSystemManager();
 
-        this[OPTION_UPDATED] = false;
+        var api = this._api = createExtensionAPI(this);
 
-        flushPendingActions.call(this, silent);
+        // Sort on demand
+        function prioritySortFunc(a: StageHandler, b: StageHandler): number {
+            return a.__prio - b.__prio;
+        }
+        timsort(visualFuncs, prioritySortFunc);
+        timsort(dataProcessorFuncs, prioritySortFunc);
 
-        triggerUpdatedEvent.call(this, silent);
-    }
-    // Avoid do both lazy update and progress in one frame.
-    else if (scheduler.unfinished) {
-        // Stream progress.
-        var remainTime = TEST_FRAME_REMAIN_TIME;
-        var ecModel = this._model;
-        var api = this._api;
-        scheduler.unfinished = false;
-        do {
-            var startTime = +new Date();
+        this._scheduler = new Scheduler(this, api, dataProcessorFuncs, visualFuncs);
 
-            scheduler.performSeriesTasks(ecModel);
+        this._ecEventProcessor = new ECEventProcessor();
+        Eventful.call(this, this._ecEventProcessor);
 
-            // Currently dataProcessorFuncs do not check threshold.
-            scheduler.performDataProcessorTasks(ecModel);
+        this._messageCenter = new MessageCenter();
 
-            updateStreamModes(this, ecModel);
+        // Init mouse events
+        this._initEvents();
 
-            // Do not update coordinate system here. Because that coord system update in
-            // each frame is not a good user experience. So we follow the rule that
-            // the extent of the coordinate system is determin in the first frame (the
-            // frame is executed immedietely after task reset.
-            // this._coordSysMgr.update(ecModel, api);
+        // In case some people write `window.onresize = chart.resize`
+        this.resize = zrUtil.bind(this.resize, this);
 
-            // console.log('--- ec frame visual ---', remainTime);
-            scheduler.performVisualTasks(ecModel);
+        zr.animation.on('frame', this._onframe, this);
 
-            renderSeries(this, this._model, api, 'remain');
+        bindRenderedEvent(zr, this);
 
-            remainTime -= (+new Date() - startTime);
-        }
-        while (remainTime > 0 && scheduler.unfinished);
+        // ECharts instance can be used as value.
+        zrUtil.setAsPrimitive(this);
+    }
 
-        // Call flush explicitly for trigger finished event.
-        if (!scheduler.unfinished) {
-            this._zr.flush();
+    private _onframe(): void {
+        if (this._disposed) {
+            return;
         }
-        // Else, zr flushing be ensue within the same frame,
-        // because zr flushing is after onframe event.
-    }
-};
 
-/**
- * @return {HTMLElement}
- */
-echartsProto.getDom = function () {
-    return this._dom;
-};
+        var scheduler = this._scheduler;
 
-/**
- * @return {module:zrender~ZRender}
- */
-echartsProto.getZr = function () {
-    return this._zr;
-};
+        // Lazy update
+        if (this[OPTION_UPDATED]) {
+            var silent = (this[OPTION_UPDATED] as any).silent;
 
-/**
- * Usage:
- * chart.setOption(option, notMerge, lazyUpdate);
- * chart.setOption(option, {
- *     notMerge: ...,
- *     lazyUpdate: ...,
- *     silent: ...
- * });
- *
- * @param {Object} option
- * @param {Object|boolean} [opts] opts or notMerge.
- * @param {boolean} [opts.notMerge=false]
- * @param {boolean} [opts.lazyUpdate=false] Useful when setOption frequently.
- */
-echartsProto.setOption = function (option, notMerge, lazyUpdate) {
-    if (__DEV__) {
-        assert(!this[IN_MAIN_PROCESS], '`setOption` should not be called during main process.');
-    }
-    if (this._disposed) {
-        disposedWarning(this.id);
-        return;
-    }
+            this[IN_MAIN_PROCESS] = true;
 
-    var silent;
-    if (isObject(notMerge)) {
-        lazyUpdate = notMerge.lazyUpdate;
-        silent = notMerge.silent;
-        notMerge = notMerge.notMerge;
-    }
+            prepare(this);
+            updateMethods.update.call(this);
 
-    this[IN_MAIN_PROCESS] = true;
+            this[IN_MAIN_PROCESS] = false;
 
-    if (!this._model || notMerge) {
-        var optionManager = new OptionManager(this._api);
-        var theme = this._theme;
-        var ecModel = this._model = new GlobalModel();
-        ecModel.scheduler = this._scheduler;
-        ecModel.init(null, null, theme, optionManager);
-    }
+            this[OPTION_UPDATED] = false;
 
-    this._model.setOption(option, optionPreprocessorFuncs);
+            flushPendingActions.call(this, silent);
 
-    if (lazyUpdate) {
-        this[OPTION_UPDATED] = {silent: silent};
-        this[IN_MAIN_PROCESS] = false;
-    }
-    else {
-        prepare(this);
+            triggerUpdatedEvent.call(this, silent);
+        }
+        // Avoid do both lazy update and progress in one frame.
+        else if (scheduler.unfinished) {
+            // Stream progress.
+            var remainTime = TEST_FRAME_REMAIN_TIME;
+            var ecModel = this._model;
+            var api = this._api;
+            scheduler.unfinished = +false;
+            do {
+                var startTime = +new Date();
 
-        updateMethods.update.call(this);
+                scheduler.performSeriesTasks(ecModel);
 
-        // Ensure zr refresh sychronously, and then pixel in canvas can be
-        // fetched after `setOption`.
-        this._zr.flush();
+                // Currently dataProcessorFuncs do not check threshold.
+                scheduler.performDataProcessorTasks(ecModel);
 
-        this[OPTION_UPDATED] = false;
-        this[IN_MAIN_PROCESS] = false;
+                updateStreamModes(this, ecModel);
 
-        flushPendingActions.call(this, silent);
-        triggerUpdatedEvent.call(this, silent);
+                // Do not update coordinate system here. Because that coord system update in
+                // each frame is not a good user experience. So we follow the rule that
+                // the extent of the coordinate system is determin in the first frame (the
+                // frame is executed immedietely after task reset.
+                // this._coordSysMgr.update(ecModel, api);
+
+                // console.log('--- ec frame visual ---', remainTime);
+                scheduler.performVisualTasks(ecModel);
+
+                renderSeries(this, this._model, api, 'remain');
+
+                remainTime -= (+new Date() - startTime);
+            }
+            while (remainTime > 0 && scheduler.unfinished);
+
+            // Call flush explicitly for trigger finished event.
+            if (!scheduler.unfinished) {
+                this._zr.flush();
+            }
+            // Else, zr flushing be ensue within the same frame,
+            // because zr flushing is after onframe event.
+        }
     }
-};
 
-/**
- * @DEPRECATED
- */
-echartsProto.setTheme = function () {
-    console.error('ECharts#setTheme() is DEPRECATED in ECharts 3.0');
-};
+    getDom(): HTMLElement {
+        return this._dom;
+    }
 
-/**
- * @return {module:echarts/model/Global}
- */
-echartsProto.getModel = function () {
-    return this._model;
-};
+    getZr(): zrender.ZRenderType {
+        return this._zr;
+    }
 
-/**
- * @return {Object}
- */
-echartsProto.getOption = function () {
-    return this._model && this._model.getOption();
-};
+    /**
+     * Usage:
+     * chart.setOption(option, notMerge, lazyUpdate);
+     * chart.setOption(option, {
+     *     notMerge: ...,
+     *     lazyUpdate: ...,
+     *     silent: ...
+     * });
+     *
+     * @param opts opts or notMerge.
+     * @param opts.notMerge Default `false`
+     * @param opts.lazyUpdate Default `false`. Useful when setOption frequently.
+     * @param opts.silent Default `false`.
+     */
+    setOption(option: ECOption, notMerge?: boolean, lazyUpdate?: boolean): void;
+    setOption(option: ECOption, opts?: SetOptionOpts): void;
+    setOption(option: ECOption, notMerge?: boolean | SetOptionOpts, lazyUpdate?: boolean): void {
+        if (__DEV__) {
+            assert(!this[IN_MAIN_PROCESS], '`setOption` should not be called during main process.');
+        }
+        if (this._disposed) {
+            disposedWarning(this.id);
+            return;
+        }
 
-/**
- * @return {number}
- */
-echartsProto.getWidth = function () {
-    return this._zr.getWidth();
-};
+        var silent;
+        if (isObject<SetOptionOpts>(notMerge)) {
+            lazyUpdate = notMerge.lazyUpdate;
+            silent = notMerge.silent;
+            notMerge = notMerge.notMerge;
+        }
 
-/**
- * @return {number}
- */
-echartsProto.getHeight = function () {
-    return this._zr.getHeight();
-};
+        this[IN_MAIN_PROCESS] = true;
 
-/**
- * @return {number}
- */
-echartsProto.getDevicePixelRatio = function () {
-    return this._zr.painter.dpr || window.devicePixelRatio || 1;
-};
+        if (!this._model || notMerge) {
+            var optionManager = new OptionManager(this._api);
+            var theme = this._theme;
+            var ecModel = this._model = new GlobalModel();
+            ecModel.scheduler = this._scheduler;
+            ecModel.init(null, null, null, theme, optionManager);
+        }
 
-/**
- * Get canvas which has all thing rendered
- * @param {Object} opts
- * @param {string} [opts.backgroundColor]
- * @return {string}
- */
-echartsProto.getRenderedCanvas = function (opts) {
-    if (!env.canvasSupported) {
-        return;
-    }
-    opts = opts || {};
-    opts.pixelRatio = opts.pixelRatio || 1;
-    opts.backgroundColor = opts.backgroundColor
-        || this._model.get('backgroundColor');
-    var zr = this._zr;
-    // var list = zr.storage.getDisplayList();
-    // Stop animations
-    // Never works before in init animation, so remove it.
-    // zrUtil.each(list, function (el) {
-    //     el.stopAnimation(true);
-    // });
-    return zr.painter.getRenderedCanvas(opts);
-};
+        this._model.setOption(option, optionPreprocessorFuncs);
 
-/**
- * Get svg data url
- * @return {string}
- */
-echartsProto.getSvgDataUrl = function () {
-    if (!env.svgSupported) {
-        return;
-    }
+        if (lazyUpdate) {
+            this[OPTION_UPDATED] = {silent: silent};
+            this[IN_MAIN_PROCESS] = false;
+        }
+        else {
+            prepare(this);
 
-    var zr = this._zr;
-    var list = zr.storage.getDisplayList();
-    // Stop animations
-    zrUtil.each(list, function (el) {
-        el.stopAnimation(true);
-    });
+            updateMethods.update.call(this);
 
-    return zr.painter.pathToDataUrl();
-};
+            // Ensure zr refresh sychronously, and then pixel in canvas can be
+            // fetched after `setOption`.
+            this._zr.flush();
 
-/**
- * @return {string}
- * @param {Object} opts
- * @param {string} [opts.type='png']
- * @param {string} [opts.pixelRatio=1]
- * @param {string} [opts.backgroundColor]
- * @param {string} [opts.excludeComponents]
- */
-echartsProto.getDataURL = function (opts) {
-    if (this._disposed) {
-        disposedWarning(this.id);
-        return;
+            this[OPTION_UPDATED] = false;
+            this[IN_MAIN_PROCESS] = false;
+
+            flushPendingActions.call(this, silent);
+            triggerUpdatedEvent.call(this, silent);
+        }
     }
 
-    opts = opts || {};
-    var excludeComponents = opts.excludeComponents;
-    var ecModel = this._model;
-    var excludesComponentViews = [];
-    var self = this;
-
-    each(excludeComponents, function (componentType) {
-        ecModel.eachComponent({
-            mainType: componentType
-        }, function (component) {
-            var view = self._componentsMap[component.__viewId];
-            if (!view.group.ignore) {
-                excludesComponentViews.push(view);
-                view.group.ignore = true;
-            }
-        });
-    });
+    /**
+     * @DEPRECATED
+     */
+    setTheme(): void {
+        console.error('ECharts#setTheme() is DEPRECATED in ECharts 3.0');
+    }
 
-    var url = this._zr.painter.getType() === 'svg'
-        ? this.getSvgDataUrl()
-        : this.getRenderedCanvas(opts).toDataURL(
-            'image/' + (opts && opts.type || 'png')
-        );
+    getModel(): GlobalModel {
+        return this._model;
+    }
 
-    each(excludesComponentViews, function (view) {
-        view.group.ignore = false;
-    });
+    getOption(): ECUnitOption {
+        return this._model && this._model.getOption();
+    }
 
-    return url;
-};
+    getWidth(): number {
+        return this._zr.getWidth();
+    }
 
+    getHeight(): number {
+        return this._zr.getHeight();
+    }
 
-/**
- * @return {string}
- * @param {Object} opts
- * @param {string} [opts.type='png']
- * @param {string} [opts.pixelRatio=1]
- * @param {string} [opts.backgroundColor]
- */
-echartsProto.getConnectedDataURL = function (opts) {
-    if (this._disposed) {
-        disposedWarning(this.id);
-        return;
+    getDevicePixelRatio(): number {
+        return (this._zr.painter as CanvasPainter).dpr || window.devicePixelRatio || 1;
     }
 
-    if (!env.canvasSupported) {
-        return;
+    /**
+     * Get canvas which has all thing rendered
+     */
+    getRenderedCanvas(opts?: {
+        backgroundColor?: ZRColor
+        pixelRatio?: number
+    }): HTMLCanvasElement {
+        if (!env.canvasSupported) {
+            return;
+        }
+        opts = zrUtil.extend({}, opts || {});
+        opts.pixelRatio = opts.pixelRatio || 1;
+        opts.backgroundColor = opts.backgroundColor
+            || this._model.get('backgroundColor');
+        var zr = this._zr;
+        // var list = zr.storage.getDisplayList();
+        // Stop animations
+        // Never works before in init animation, so remove it.
+        // zrUtil.each(list, function (el) {
+        //     el.stopAnimation(true);
+        // });
+        return (zr.painter as CanvasPainter).getRenderedCanvas(opts);
     }
-    var groupId = this.group;
-    var mathMin = Math.min;
-    var mathMax = Math.max;
-    var MAX_NUMBER = Infinity;
-    if (connectedGroups[groupId]) {
-        var left = MAX_NUMBER;
-        var top = MAX_NUMBER;
-        var right = -MAX_NUMBER;
-        var bottom = -MAX_NUMBER;
-        var canvasList = [];
-        var dpr = (opts && opts.pixelRatio) || 1;
-
-        zrUtil.each(instances, function (chart, id) {
-            if (chart.group === groupId) {
-                var canvas = chart.getRenderedCanvas(
-                    zrUtil.clone(opts)
-                );
-                var boundingRect = chart.getDom().getBoundingClientRect();
-                left = mathMin(boundingRect.left, left);
-                top = mathMin(boundingRect.top, top);
-                right = mathMax(boundingRect.right, right);
-                bottom = mathMax(boundingRect.bottom, bottom);
-                canvasList.push({
-                    dom: canvas,
-                    left: boundingRect.left,
-                    top: boundingRect.top
-                });
-            }
+
+    /**
+     * Get svg data url
+     */
+    getSvgDataUrl(): string {
+        if (!env.svgSupported) {
+            return;
+        }
+
+        var zr = this._zr;
+        var list = zr.storage.getDisplayList();
+        // Stop animations
+        zrUtil.each(list, function (el: Element) {
+            el.stopAnimation(true);
         });
 
-        left *= dpr;
-        top *= dpr;
-        right *= dpr;
-        bottom *= dpr;
-        var width = right - left;
-        var height = bottom - top;
-        var targetCanvas = zrUtil.createCanvas();
-        targetCanvas.width = width;
-        targetCanvas.height = height;
-        var zr = zrender.init(targetCanvas);
-
-        // Background between the charts
-        if (opts.connectedBackgroundColor) {
-            zr.add(new graphic.Rect({
-                shape: {
-                    x: 0,
-                    y: 0,
-                    width: width,
-                    height: height
-                },
-                style: {
-                    fill: opts.connectedBackgroundColor
-                }
-            }));
+        return (zr.painter as SVGPainter).pathToDataUrl();
+    }
+
+    getDataURL(opts?: {
+        // file type 'png' by defualt
+        type?: string,
+        pixelRatio?: number,
+        backgroundColor?: ZRColor,
+        // component type array
+        excludeComponents?: ComponentMainType[]
+    }): string {
+        if (this._disposed) {
+            disposedWarning(this.id);
+            return;
         }
 
-        each(canvasList, function (item) {
-            var img = new graphic.Image({
-                style: {
-                    x: item.left * dpr - left,
-                    y: item.top * dpr - top,
-                    image: item.dom
+        opts = opts || {};
+        var excludeComponents = opts.excludeComponents;
+        var ecModel = this._model;
+        var excludesComponentViews: ComponentView[] = [];
+        var self = this;
+
+        each(excludeComponents, function (componentType) {
+            ecModel.eachComponent({
+                mainType: componentType
+            }, function (component) {
+                var view = self._componentsMap[component.__viewId];
+                if (!view.group.ignore) {
+                    excludesComponentViews.push(view);
+                    view.group.ignore = true;
                 }
             });
-            zr.add(img);
         });
-        zr.refreshImmediately();
-
-        return targetCanvas.toDataURL('image/' + (opts && opts.type || 'png'));
-    }
-    else {
-        return this.getDataURL(opts);
-    }
-};
 
-/**
- * Convert from logical coordinate system to pixel coordinate system.
- * See CoordinateSystem#convertToPixel.
- * @param {string|Object} finder
- *        If string, e.g., 'geo', means {geoIndex: 0}.
- *        If Object, could contain some of these properties below:
- *        {
- *            seriesIndex / seriesId / seriesName,
- *            geoIndex / geoId, geoName,
- *            bmapIndex / bmapId / bmapName,
- *            xAxisIndex / xAxisId / xAxisName,
- *            yAxisIndex / yAxisId / yAxisName,
- *            gridIndex / gridId / gridName,
- *            ... (can be extended)
- *        }
- * @param {Array|number} value
- * @return {Array|number} result
- */
-echartsProto.convertToPixel = zrUtil.curry(doConvertPixel, 'convertToPixel');
+        var url = this._zr.painter.getType() === 'svg'
+            ? this.getSvgDataUrl()
+            : this.getRenderedCanvas(opts).toDataURL(
+                'image/' + (opts && opts.type || 'png')
+            );
 
-/**
- * Convert from pixel coordinate system to logical coordinate system.
- * See CoordinateSystem#convertFromPixel.
- * @param {string|Object} finder
- *        If string, e.g., 'geo', means {geoIndex: 0}.
- *        If Object, could contain some of these properties below:
- *        {
- *            seriesIndex / seriesId / seriesName,
- *            geoIndex / geoId / geoName,
- *            bmapIndex / bmapId / bmapName,
- *            xAxisIndex / xAxisId / xAxisName,
- *            yAxisIndex / yAxisId / yAxisName
- *            gridIndex / gridId / gridName,
- *            ... (can be extended)
- *        }
- * @param {Array|number} value
- * @return {Array|number} result
- */
-echartsProto.convertFromPixel = zrUtil.curry(doConvertPixel, 'convertFromPixel');
+        each(excludesComponentViews, function (view) {
+            view.group.ignore = false;
+        });
 
-function doConvertPixel(methodName, finder, value) {
-    if (this._disposed) {
-        disposedWarning(this.id);
-        return;
+        return url;
     }
 
-    var ecModel = this._model;
-    var coordSysList = this._coordSysMgr.getCoordinateSystems();
-    var result;
+    /**
+     * @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,
+        pixelRatio?: number,
+        backgroundColor?: ZRColor,
+        connectedBackgroundColor?: string
+    }): string {
+        if (this._disposed) {
+            disposedWarning(this.id);
+            return;
+        }
+
+        if (!env.canvasSupported) {
+            return;
+        }
+        var groupId = this.group;
+        var mathMin = Math.min;
+        var mathMax = Math.max;
+        var MAX_NUMBER = Infinity;
+        if (connectedGroups[groupId]) {
+            var left = MAX_NUMBER;
+            var top = MAX_NUMBER;
+            var right = -MAX_NUMBER;
+            var bottom = -MAX_NUMBER;
+            var canvasList: {dom: HTMLCanvasElement, left: number, top: number}[] = [];
+            var dpr = (opts && opts.pixelRatio) || 1;
+
+            zrUtil.each(instances, function (chart, id) {
+                if (chart.group === groupId) {
+                    var canvas = chart.getRenderedCanvas(
+                        zrUtil.clone(opts)
+                    );
+                    var boundingRect = chart.getDom().getBoundingClientRect();
+                    left = mathMin(boundingRect.left, left);
+                    top = mathMin(boundingRect.top, top);
+                    right = mathMax(boundingRect.right, right);
+                    bottom = mathMax(boundingRect.bottom, bottom);
+                    canvasList.push({
+                        dom: canvas,
+                        left: boundingRect.left,
+                        top: boundingRect.top
+                    });
+                }
+            });
+
+            left *= dpr;
+            top *= dpr;
+            right *= dpr;
+            bottom *= dpr;
+            var width = right - left;
+            var height = bottom - top;
+            var targetCanvas = zrUtil.createCanvas();
+            targetCanvas.width = width;
+            targetCanvas.height = height;
+            var zr = zrender.init(targetCanvas);
+
+            // Background between the charts
+            if (opts.connectedBackgroundColor) {
+                zr.add(new graphic.Rect({
+                    shape: {
+                        x: 0,
+                        y: 0,
+                        width: width,
+                        height: height
+                    },
+                    style: {
+                        fill: opts.connectedBackgroundColor
+                    }
+                }));
+            }
 
-    finder = modelUtil.parseFinder(ecModel, finder);
+            each(canvasList, function (item) {
+                var img = new graphic.Image({
+                    style: {
+                        x: item.left * dpr - left,
+                        y: item.top * dpr - top,
+                        image: item.dom
+                    }
+                });
+                zr.add(img);
+            });
+            zr.refreshImmediately();
 
-    for (var i = 0; i < coordSysList.length; i++) {
-        var coordSys = coordSysList[i];
-        if (coordSys[methodName]
-            && (result = coordSys[methodName](ecModel, finder, value)) != null
-        ) {
-            return result;
+            return targetCanvas.toDataURL('image/' + (opts && opts.type || 'png'));
+        }
+        else {
+            return this.getDataURL(opts);
         }
     }
 
-    if (__DEV__) {
-        console.warn(
-            'No coordinate system that supports ' + methodName + ' found by the given finder.'
-        );
+    /**
+     * Convert from logical coordinate system to pixel coordinate system.
+     * See CoordinateSystem#convertToPixel.
+     */
+    convertToPixel(finder: ModelFinder, value: any): number[] {
+        return doConvertPixel(this, 'convertToPixel', finder, value);
     }
-}
 
-/**
- * Is the specified coordinate systems or components contain the given pixel point.
- * @param {string|Object} finder
- *        If string, e.g., 'geo', means {geoIndex: 0}.
- *        If Object, could contain some of these properties below:
- *        {
- *            seriesIndex / seriesId / seriesName,
- *            geoIndex / geoId / geoName,
- *            bmapIndex / bmapId / bmapName,
- *            xAxisIndex / xAxisId / xAxisName,
- *            yAxisIndex / yAxisId / yAxisName,
- *            gridIndex / gridId / gridName,
- *            ... (can be extended)
- *        }
- * @param {Array|number} value
- * @return {boolean} result
- */
-echartsProto.containPixel = function (finder, value) {
-    if (this._disposed) {
-        disposedWarning(this.id);
-        return;
+    /**
+     * Convert from pixel coordinate system to logical coordinate system.
+     * See CoordinateSystem#convertFromPixel.
+     */
+    convertFromPixel(finder: ModelFinder, value: number[]): any {
+        return doConvertPixel(this, 'convertFromPixel', finder, value);
     }
 
-    var ecModel = this._model;
-    var result;
+    /**
+     * Is the specified coordinate systems or components contain the given pixel point.
+     * @param {Array|number} value
+     * @return {boolean} result
+     */
+    containPixel(finder: ModelFinder, value: number[]): boolean {
+        if (this._disposed) {
+            disposedWarning(this.id);
+            return;
+        }
 
-    finder = modelUtil.parseFinder(ecModel, finder);
+        var ecModel = this._model;
+        var result: boolean;
 
-    zrUtil.each(finder, function (models, key) {
-        key.indexOf('Models') >= 0 && zrUtil.each(models, function (model) {
-            var coordSys = model.coordinateSystem;
-            if (coordSys && coordSys.containPoint) {
-                result |= !!coordSys.containPoint(value);
-            }
-            else if (key === 'seriesModels') {
-                var view = this._chartsMap[model.__viewId];
-                if (view && view.containPoint) {
-                    result |= view.containPoint(value, model);
+        var findResult = modelUtil.parseFinder(ecModel, finder);
+
+        zrUtil.each(findResult, function (models, key) {
+            key.indexOf('Models') >= 0 && zrUtil.each(models as ComponentModel, function (model) {
+                var coordSys = model.coordinateSystem;
+                if (coordSys && coordSys.containPoint) {
+                    result = result || !!coordSys.containPoint(value);
+                }
+                else if (key === 'seriesModels') {
+                    var view = this._chartsMap[model.__viewId];
+                    if (view && view.containPoint) {
+                        result = result || view.containPoint(value, model as SeriesModel);
+                    }
+                    else {
+                        if (__DEV__) {
+                            console.warn(key + ': ' + (view
+                                ? 'The found component do not support containPoint.'
+                                : 'No view mapping to the found component.'
+                            ));
+                        }
+                    }
                 }
                 else {
                     if (__DEV__) {
-                        console.warn(key + ': ' + (view
-                            ? 'The found component do not support containPoint.'
-                            : 'No view mapping to the found component.'
-                        ));
+                        console.warn(key + ': containPoint is not supported');
                     }
                 }
-            }
-            else {
-                if (__DEV__) {
-                    console.warn(key + ': containPoint is not supported');
-                }
-            }
+            }, this);
         }, this);
-    }, this);
 
-    return !!result;
-};
+        return !!result;
+    }
 
-/**
- * Get visual from series or data.
- * @param {string|Object} finder
- *        If string, e.g., 'series', means {seriesIndex: 0}.
- *        If Object, could contain some of these properties below:
- *        {
- *            seriesIndex / seriesId / seriesName,
- *            dataIndex / dataIndexInside
- *        }
- *        If dataIndex is not specified, series visual will be fetched,
- *        but not data item visual.
- *        If all of seriesIndex, seriesId, seriesName are not specified,
- *        visual will be fetched from first series.
- * @param {string} visualType 'color', 'symbol', 'symbolSize'
- */
-echartsProto.getVisual = function (finder, visualType) {
-    var ecModel = this._model;
+    /**
+     * Get visual from series or data.
+     * @param finder
+     *        If string, e.g., 'series', means {seriesIndex: 0}.
+     *        If Object, could contain some of these properties below:
+     *        {
+     *            seriesIndex / seriesId / seriesName,
+     *            dataIndex / dataIndexInside
+     *        }
+     *        If dataIndex is not specified, series visual will be fetched,
+     *        but not data item visual.
+     *        If all of seriesIndex, seriesId, seriesName are not specified,
+     *        visual will be fetched from first series.
+     * @param visualType 'color', 'symbol', 'symbolSize'
+     */
+    getVisual(finder: ModelFinder, visualType: string) {
+        var ecModel = this._model;
 
-    finder = modelUtil.parseFinder(ecModel, finder, {defaultMainType: 'series'});
+        finder = modelUtil.parseFinder(ecModel, finder, {defaultMainType: 'series'});
 
-    var seriesModel = finder.seriesModel;
+        var seriesModel = finder.seriesModel;
 
-    if (__DEV__) {
-        if (!seriesModel) {
-            console.warn('There is no specified seires model');
+        if (__DEV__) {
+            if (!seriesModel) {
+                console.warn('There is no specified seires model');
+            }
         }
-    }
 
-    var data = seriesModel.getData();
+        var data = seriesModel.getData();
 
-    var dataIndexInside = finder.hasOwnProperty('dataIndexInside')
-        ? finder.dataIndexInside
-        : finder.hasOwnProperty('dataIndex')
-        ? data.indexOfRawIndex(finder.dataIndex)
-        : null;
+        var dataIndexInside = finder.hasOwnProperty('dataIndexInside')
+            ? finder.dataIndexInside
+            : finder.hasOwnProperty('dataIndex')
+            ? data.indexOfRawIndex(finder.dataIndex)
+            : null;
 
-    return dataIndexInside != null
-        ? data.getItemVisual(dataIndexInside, visualType)
-        : data.getVisual(visualType);
-};
-
-/**
- * Get view of corresponding component model
- * @param  {module:echarts/model/Component} componentModel
- * @return {module:echarts/view/Component}
- */
-echartsProto.getViewOfComponentModel = function (componentModel) {
-    return this._componentsMap[componentModel.__viewId];
-};
+        return dataIndexInside != null
+            ? data.getItemVisual(dataIndexInside, visualType)
+            : data.getVisual(visualType);
+    }
 
-/**
- * Get view of corresponding series model
- * @param  {module:echarts/model/Series} seriesModel
- * @return {module:echarts/view/Chart}
- */
-echartsProto.getViewOfSeriesModel = function (seriesModel) {
-    return this._chartsMap[seriesModel.__viewId];
-};
-
-var updateMethods = {
-
-    prepareAndUpdate: function (payload) {
-        prepare(this);
-        updateMethods.update.call(this, payload);
-    },
+    /**
+     * Get view of corresponding component model
+     */
+    getViewOfComponentModel(componentModel: ComponentModel): ComponentView {
+        return this._componentsMap[componentModel.__viewId];
+    }
 
     /**
-     * @param {Object} payload
-     * @private
+     * Get view of corresponding series model
      */
-    update: function (payload) {
-        // console.profile && console.profile('update');
-
-        var ecModel = this._model;
-        var api = this._api;
-        var zr = this._zr;
-        var coordSysMgr = this._coordSysMgr;
-        var scheduler = this._scheduler;
-
-        // update before setOption
-        if (!ecModel) {
-            return;
-        }
-
-        scheduler.restoreData(ecModel, payload);
-
-        scheduler.performSeriesTasks(ecModel);
-
-        // TODO
-        // Save total ecModel here for undo/redo (after restoring data and before processing data).
-        // Undo (restoration of total ecModel) can be carried out in 'action' or outside API call.
+    getViewOfSeriesModel(seriesModel: SeriesModel): ChartView {
+        return this._chartsMap[seriesModel.__viewId];
+    }
+
+    private _initEvents(): void {
+        each(MOUSE_EVENT_NAMES, function (eveName) {
+            var handler = function (this: ECharts, e: ElementEvent) {
+                var ecModel = this.getModel();
+                var targetEl = e.target;
+                var el = targetEl as ECElement;
+                var params: ECEvent;
+                var isGlobalOut = eveName === 'globalout';
+
+                // no e.target when 'globalout'.
+                if (isGlobalOut) {
+                    params = {} as ECEvent;
+                }
+                else if (el && el.dataIndex != null) {
+                    var dataModel = el.dataModel || ecModel.getSeriesByIndex(el.seriesIndex);
+                    params = (
+                        dataModel && dataModel.getDataParams(el.dataIndex, el.dataType, targetEl) || {}
+                    ) as ECEvent;
+                }
+                // If element has custom eventData of components
+                else if (el && el.eventData) {
+                    params = zrUtil.extend({}, el.eventData) as ECEvent;
+                }
 
-        // Create new coordinate system each update
-        // In LineView may save the old coordinate system and use it to get the orignal point
-        coordSysMgr.create(ecModel, api);
+                // Contract: if params prepared in mouse event,
+                // these properties must be specified:
+                // {
+                //    componentType: string (component main type)
+                //    componentIndex: number
+                // }
+                // Otherwise event query can not work.
+
+                if (params) {
+                    var componentType = params.componentType;
+                    var componentIndex = params.componentIndex;
+                    // Special handling for historic reason: when trigger by
+                    // markLine/markPoint/markArea, the componentType is
+                    // 'markLine'/'markPoint'/'markArea', but we should better
+                    // enable them to be queried by seriesIndex, since their
+                    // option is set in each series.
+                    if (componentType === 'markLine'
+                        || componentType === 'markPoint'
+                        || componentType === 'markArea'
+                    ) {
+                        componentType = 'series';
+                        componentIndex = params.seriesIndex;
+                    }
+                    var model = componentType && componentIndex != null
+                        && ecModel.getComponent(componentType, componentIndex);
+                    var view = model && this[
+                        model.mainType === 'series' ? '_chartsMap' : '_componentsMap'
+                    ][model.__viewId];
 
-        scheduler.performDataProcessorTasks(ecModel, payload);
+                    if (__DEV__) {
+                        // `event.componentType` and `event[componentTpype + 'Index']` must not
+                        // be missed, otherwise there is no way to distinguish source component.
+                        // See `dataFormat.getDataParams`.
+                        if (!isGlobalOut && !(model && view)) {
+                            console.warn('model or view can not be found by params');
+                        }
+                    }
 
-        // Current stream render is not supported in data process. So we can update
-        // stream modes after data processing, where the filtered data is used to
-        // deteming whether use progressive rendering.
-        updateStreamModes(this, ecModel);
+                    params.event = e;
+                    params.type = eveName;
 
-        // We update stream modes before coordinate system updated, then the modes info
-        // can be fetched when coord sys updating (consider the barGrid extent fix). But
-        // the drawback is the full coord info can not be fetched. Fortunately this full
-        // coord is not requied in stream mode updater currently.
-        coordSysMgr.update(ecModel, api);
+                    this._ecEventProcessor.eventInfo = {
+                        targetEl: el,
+                        packedEvent: params,
+                        model: model,
+                        view: view
+                    };
 
-        clearColorPalette(ecModel);
-        scheduler.performVisualTasks(ecModel, payload);
+                    this.trigger(eveName, params);
+                }
+            };
+            // Consider that some component (like tooltip, brush, ...)
+            // register zr event handler, but user event handler might
+            // do anything, such as call `setOption` or `dispatchAction`,
+            // which probably update any of the content and probably
+            // cause problem if it is called previous other inner handlers.
+            (handler as any).zrEventfulCallAtLast = true;
+            this._zr.on(eveName, handler, this);
+        }, this);
 
-        render(this, ecModel, api, payload);
+        each(eventActionMap, function (actionType, eventType) {
+            this._messageCenter.on(eventType, function (event) {
+                this.trigger(eventType, event);
+            }, this);
+        }, this);
+    }
 
-        // Set background
-        var backgroundColor = ecModel.get('backgroundColor') || 'transparent';
+    isDisposed(): boolean {
+        return this._disposed;
+    }
 
-        // In IE8
-        if (!env.canvasSupported) {
-            var colorArr = colorTool.parse(backgroundColor);
-            backgroundColor = colorTool.stringify(colorArr, 'rgb');
-            if (colorArr[3] === 0) {
-                backgroundColor = 'transparent';
-            }
-        }
-        else {
-            zr.setBackgroundColor(backgroundColor);
+    clear(): void {
+        if (this._disposed) {
+            disposedWarning(this.id);
+            return;
         }
+        this.setOption({ series: [] }, true);
+    }
 
-        performPostUpdateFuncs(ecModel, api);
-
-        // console.profile && console.profileEnd('update');
-    },
-
-    /**
-     * @param {Object} payload
-     * @private
-     */
-    updateTransform: function (payload) {
-        var ecModel = this._model;
-        var ecIns = this;
-        var api = this._api;
-
-        // update before setOption
-        if (!ecModel) {
+    dispose(): void {
+        if (this._disposed) {
+            disposedWarning(this.id);
             return;
         }
+        this._disposed = true;
 
-        // ChartView.markUpdateMethod(payload, 'updateTransform');
+        modelUtil.setAttribute(this.getDom(), DOM_ATTRIBUTE_KEY, '');
 
-        var componentDirtyList = [];
-        ecModel.eachComponent(function (componentType, componentModel) {
-            var componentView = ecIns.getViewOfComponentModel(componentModel);
-            if (componentView && componentView.__alive) {
-                if (componentView.updateTransform) {
-                    var result = componentView.updateTransform(componentModel, ecModel, api, payload);
-                    result && result.update && componentDirtyList.push(componentView);
-                }
-                else {
-                    componentDirtyList.push(componentView);
-                }
-            }
-        });
+        var api = this._api;
+        var ecModel = this._model;
 
-        var seriesDirtyMap = zrUtil.createHashMap();
-        ecModel.eachSeries(function (seriesModel) {
-            var chartView = ecIns._chartsMap[seriesModel.__viewId];
-            if (chartView.updateTransform) {
-                var result = chartView.updateTransform(seriesModel, ecModel, api, payload);
-                result && result.update && seriesDirtyMap.set(seriesModel.uid, 1);
-            }
-            else {
-                seriesDirtyMap.set(seriesModel.uid, 1);
-            }
+        each(this._componentsViews, function (component) {
+            component.dispose(ecModel, api);
+        });
+        each(this._chartsViews, function (chart) {
+            chart.dispose(ecModel, api);
         });
 
-        clearColorPalette(ecModel);
-        // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline.
-        // this._scheduler.performVisualTasks(ecModel, payload, 'layout', true);
-        this._scheduler.performVisualTasks(
-            ecModel, payload, {setDirty: true, dirtyMap: seriesDirtyMap}
-        );
-
-        // Currently, not call render of components. Geo render cost a lot.
-        // renderComponents(ecIns, ecModel, api, payload, componentDirtyList);
-        renderSeries(ecIns, ecModel, api, payload, seriesDirtyMap);
+        // Dispose after all views disposed
+        this._zr.dispose();
 
-        performPostUpdateFuncs(ecModel, this._api);
-    },
+        delete instances[this.id];
+    }
 
     /**
-     * @param {Object} payload
-     * @private
+     * Resize the chart
      */
-    updateView: function (payload) {
-        var ecModel = this._model;
-
-        // update before setOption
-        if (!ecModel) {
+    resize(opts?: {
+        width?: number | 'auto', // Can be 'auto' (the same as null/undefined)
+        height?: number | 'auto', // Can be 'auto' (the same as null/undefined)
+        silent?: boolean // by default false.
+    }): void {
+        if (__DEV__) {
+            assert(!this[IN_MAIN_PROCESS], '`resize` should not be called during main process.');
+        }
+        if (this._disposed) {
+            disposedWarning(this.id);
             return;
         }
 
-        ChartView.markUpdateMethod(payload, 'updateView');
-
-        clearColorPalette(ecModel);
+        this._zr.resize(opts);
 
-        // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline.
-        this._scheduler.performVisualTasks(ecModel, payload, {setDirty: true});
-
-        render(this, this._model, this._api, payload);
+        var ecModel = this._model;
 
-        performPostUpdateFuncs(ecModel, this._api);
-    },
+        // Resize loading effect
+        this._loadingFX && this._loadingFX.resize();
 
-    /**
-     * @param {Object} payload
-     * @private
-     */
-    updateVisual: function (payload) {
-        updateMethods.update.call(this, payload);
+        if (!ecModel) {
+            return;
+        }
 
-        // var ecModel = this._model;
+        var optionChanged = ecModel.resetOption('media');
 
-        // // update before setOption
-        // if (!ecModel) {
-        //     return;
-        // }
+        var silent = opts && opts.silent;
 
-        // ChartView.markUpdateMethod(payload, 'updateVisual');
+        this[IN_MAIN_PROCESS] = true;
 
-        // clearColorPalette(ecModel);
+        optionChanged && prepare(this);
+        updateMethods.update.call(this);
 
-        // // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline.
-        // this._scheduler.performVisualTasks(ecModel, payload, {visualType: 'visual', setDirty: true});
+        this[IN_MAIN_PROCESS] = false;
 
-        // render(this, this._model, this._api, payload);
+        flushPendingActions.call(this, silent);
 
-        // performPostUpdateFuncs(ecModel, this._api);
-    },
+        triggerUpdatedEvent.call(this, silent);
+    }
 
     /**
-     * @param {Object} payload
-     * @private
+     * Show loading effect
+     * @param name 'default' by default
+     * @param cfg cfg of registered loading effect
      */
-    updateLayout: function (payload) {
-        updateMethods.update.call(this, payload);
-
-        // var ecModel = this._model;
-
-        // // update before setOption
-        // if (!ecModel) {
-        //     return;
-        // }
-
-        // ChartView.markUpdateMethod(payload, 'updateLayout');
+    showLoading(cfg?: object): void;
+    showLoading(name?: string, cfg?: object): void;
+    showLoading(name?: string | object, cfg?: object): void {
+        if (this._disposed) {
+            disposedWarning(this.id);
+            return;
+        }
 
-        // // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline.
-        // // this._scheduler.performVisualTasks(ecModel, payload, 'layout', true);
-        // this._scheduler.performVisualTasks(ecModel, payload, {setDirty: true});
+        if (isObject(name)) {
+            cfg = name as object;
+            name = '';
+        }
+        name = name || 'default';
 
-        // render(this, this._model, this._api, payload);
+        this.hideLoading();
+        if (!loadingEffects[name]) {
+            if (__DEV__) {
+                console.warn('Loading effects ' + name + ' not exists.');
+            }
+            return;
+        }
+        var el = loadingEffects[name](this._api, cfg);
+        var zr = this._zr;
+        this._loadingFX = el;
 
-        // performPostUpdateFuncs(ecModel, this._api);
+        zr.add(el);
     }
-};
 
-function prepare(ecIns) {
-    var ecModel = ecIns._model;
-    var scheduler = ecIns._scheduler;
-
-    scheduler.restorePipelines(ecModel);
-
-    scheduler.prepareStageTasks();
-
-    prepareView(ecIns, 'component', ecModel, scheduler);
-
-    prepareView(ecIns, 'chart', ecModel, scheduler);
-
-    scheduler.plan();
-}
+    /**
+     * Hide loading effect
+     */
+    hideLoading(): void {
+        if (this._disposed) {
+            disposedWarning(this.id);
+            return;
+        }
 
-/**
- * @private
- */
-function updateDirectly(ecIns, method, payload, mainType, subType) {
-    var ecModel = ecIns._model;
-
-    // broadcast
-    if (!mainType) {
-        // FIXME
-        // Chart will not be update directly here, except set dirty.
-        // But there is no such scenario now.
-        each(ecIns._componentsViews.concat(ecIns._chartsViews), callView);
-        return;
+        this._loadingFX && this._zr.remove(this._loadingFX);
+        this._loadingFX = null;
     }
 
-    var query = {};
-    query[mainType + 'Id'] = payload[mainType + 'Id'];
-    query[mainType + 'Index'] = payload[mainType + 'Index'];
-    query[mainType + 'Name'] = payload[mainType + 'Name'];
+    makeActionFromEvent(eventObj: ECEvent): Payload {
+        var payload = zrUtil.extend({}, eventObj) as Payload;
+        payload.type = eventActionMap[eventObj.type];
+        return payload;
+    }
 
-    var condition = {mainType: mainType, query: query};
-    subType && (condition.subType = subType); // subType may be '' by parseClassType;
+    /**
+     * @param opt If pass boolean, means opt.silent
+     * @param opt.silent Default `false`. Whether trigger events.
+     * @param opt.flush Default `undefined`.
+     *        true: Flush immediately, and then pixel in canvas can be fetched
+     *            immediately. Caution: it might affect performance.
+     *        false: Not flush.
+     *        undefined: Auto decide whether perform flush.
+     */
+    dispatchAction(
+        payload: Payload,
+        opt?: boolean | {
+            silent?: boolean,
+            flush?: boolean | undefined
+        }
+    ): void {
+        if (this._disposed) {
+            disposedWarning(this.id);
+            return;
+        }
 
-    var excludeSeriesId = payload.excludeSeriesId;
-    if (excludeSeriesId != null) {
-        excludeSeriesId = zrUtil.createHashMap(modelUtil.normalizeToArray(excludeSeriesId));
-    }
+        if (!isObject(opt)) {
+            opt = {silent: !!opt};
+        }
 
-    // If dispatchAction before setOption, do nothing.
-    ecModel && ecModel.eachComponent(condition, function (model) {
-        if (!excludeSeriesId || excludeSeriesId.get(model.id) == null) {
-            callView(ecIns[
-                mainType === 'series' ? '_chartsMap' : '_componentsMap'
-            ][model.__viewId]);
+        if (!actions[payload.type]) {
+            return;
         }
-    }, ecIns);
 
-    function callView(view) {
-        view && view.__alive && view[method] && view[method](
-            view.__model, ecModel, ecIns._api, payload
-        );
-    }
-}
+        // Avoid dispatch action before setOption. Especially in `connect`.
+        if (!this._model) {
+            return;
+        }
 
-/**
- * Resize the chart
- * @param {Object} opts
- * @param {number} [opts.width] Can be 'auto' (the same as null/undefined)
- * @param {number} [opts.height] Can be 'auto' (the same as null/undefined)
- * @param {boolean} [opts.silent=false]
- */
-echartsProto.resize = function (opts) {
-    if (__DEV__) {
-        assert(!this[IN_MAIN_PROCESS], '`resize` should not be called during main process.');
-    }
-    if (this._disposed) {
-        disposedWarning(this.id);
-        return;
-    }
+        // May dispatchAction in rendering procedure
+        if (this[IN_MAIN_PROCESS]) {
+            this._pendingActions.push(payload);
+            return;
+        }
 
-    this._zr.resize(opts);
+        var silent = (opt as any).silent;
+        doDispatchAction.call(this, payload, silent);
 
-    var ecModel = this._model;
+        var flush = (opt as any).flush;
+        if (flush) {
+            this._zr.flush();
+        }
+        else if (flush !== false && env.browser.weChat) {
+            // In WeChat embeded browser, `requestAnimationFrame` and `setInterval`
+            // hang when sliding page (on touch event), which cause that zr does not
+            // refresh util user interaction finished, which is not expected.
+            // But `dispatchAction` may be called too frequently when pan on touch
+            // screen, which impacts performance if do not throttle them.
+            this._throttledZrFlush();
+        }
 
-    // Resize loading effect
-    this._loadingFX && this._loadingFX.resize();
+        flushPendingActions.call(this, silent);
 
-    if (!ecModel) {
-        return;
+        triggerUpdatedEvent.call(this, silent);
     }
 
-    var optionChanged = ecModel.resetOption('media');
+    appendData(params: {
+        seriesIndex: number,
+        data: any
+    }): void {
+        if (this._disposed) {
+            disposedWarning(this.id);
+            return;
+        }
 
-    var silent = opts && opts.silent;
+        var seriesIndex = params.seriesIndex;
+        var ecModel = this.getModel();
+        var seriesModel = ecModel.getSeriesByIndex(seriesIndex) as SeriesModel;
 
-    this[IN_MAIN_PROCESS] = true;
+        if (__DEV__) {
+            assert(params.data && seriesModel);
+        }
 
-    optionChanged && prepare(this);
-    updateMethods.update.call(this);
+        seriesModel.appendData(params);
 
-    this[IN_MAIN_PROCESS] = false;
+        // Note: `appendData` does not support that update extent of coordinate
+        // system, util some scenario require that. In the expected usage of
+        // `appendData`, the initial extent of coordinate system should better
+        // be fixed by axis `min`/`max` setting or initial data, otherwise if
+        // the extent changed while `appendData`, the location of the painted
+        // graphic elements have to be changed, which make the usage of
+        // `appendData` meaningless.
 
-    flushPendingActions.call(this, silent);
+        this._scheduler.unfinished = +true;
+    }
 
-    triggerUpdatedEvent.call(this, silent);
-};
 
-function updateStreamModes(ecIns, ecModel) {
-    var chartsMap = ecIns._chartsMap;
-    var scheduler = ecIns._scheduler;
-    ecModel.eachSeries(function (seriesModel) {
-        scheduler.updateStreamModes(seriesModel, chartsMap[seriesModel.__viewId]);
-    });
-}
+    // A work around for no `internal` modifier in ts yet but
+    // need to strictly hide private methods to JS users.
+    static internalField = (function () {
 
-/**
- * Show loading effect
- * @param  {string} [name='default']
- * @param  {Object} [cfg]
- */
-echartsProto.showLoading = function (name, cfg) {
-    if (this._disposed) {
-        disposedWarning(this.id);
-        return;
-    }
+        prepare = function (ecIns: ECharts): void {
+            var scheduler = ecIns._scheduler;
 
-    if (isObject(name)) {
-        cfg = name;
-        name = '';
-    }
-    name = name || 'default';
+            scheduler.restorePipelines(ecIns._model);
+            scheduler.prepareStageTasks();
 
-    this.hideLoading();
-    if (!loadingEffects[name]) {
-        if (__DEV__) {
-            console.warn('Loading effects ' + name + ' not exists.');
-        }
-        return;
-    }
-    var el = loadingEffects[name](this._api, cfg);
-    var zr = this._zr;
-    this._loadingFX = el;
+            prepareView(ecIns, true);
+            prepareView(ecIns, false);
 
-    zr.add(el);
-};
+            scheduler.plan();
+        };
 
-/**
- * Hide loading effect
- */
-echartsProto.hideLoading = function () {
-    if (this._disposed) {
-        disposedWarning(this.id);
-        return;
-    }
+        /**
+         * Prepare view instances of charts and components
+         */
+        prepareView = function (ecIns: ECharts, isComponent: boolean): void {
+            var ecModel = ecIns._model;
+            var scheduler = ecIns._scheduler;
+            var viewList = isComponent ? ecIns._componentsViews : ecIns._chartsViews;
+            var viewMap = isComponent ? ecIns._componentsMap : ecIns._chartsMap;
+            var zr = ecIns._zr;
+            var api = ecIns._api;
+
+            for (var i = 0; i < viewList.length; i++) {
+                viewList[i].__alive = false;
+            }
 
-    this._loadingFX && this._zr.remove(this._loadingFX);
-    this._loadingFX = null;
-};
+            isComponent
+                ? ecModel.eachComponent(function (componentType, model) {
+                    componentType !== 'series' && doPrepare(model);
+                })
+                : ecModel.eachSeries(doPrepare);
+
+            function doPrepare(model: ComponentModel): void {
+                // Consider: id same and type changed.
+                var viewId = '_ec_' + model.id + '_' + model.type;
+                var view = viewMap[viewId];
+                if (!view) {
+                    var classType = parseClassType(model.type);
+                    var Clazz = isComponent
+                        ? (ComponentView as ComponentViewConstructor).getClass(classType.main, classType.sub)
+                        : (ChartView as ChartViewConstructor).getClass(classType.sub);
 
-/**
- * @param {Object} eventObj
- * @return {Object}
- */
-echartsProto.makeActionFromEvent = function (eventObj) {
-    var payload = zrUtil.extend({}, eventObj);
-    payload.type = eventActionMap[eventObj.type];
-    return payload;
-};
+                    if (__DEV__) {
+                        assert(Clazz, classType.sub + ' does not exist.');
+                    }
 
-/**
- * @pubilc
- * @param {Object} payload
- * @param {string} [payload.type] Action type
- * @param {Object|boolean} [opt] If pass boolean, means opt.silent
- * @param {boolean} [opt.silent=false] Whether trigger events.
- * @param {boolean} [opt.flush=undefined]
- *                  true: Flush immediately, and then pixel in canvas can be fetched
- *                      immediately. Caution: it might affect performance.
- *                  false: Not flush.
- *                  undefined: Auto decide whether perform flush.
- */
-echartsProto.dispatchAction = function (payload, opt) {
-    if (this._disposed) {
-        disposedWarning(this.id);
-        return;
-    }
+                    view = new Clazz();
+                    view.init(ecModel, api);
+                    viewMap[viewId] = view;
+                    viewList.push(view as any);
+                    zr.add(view.group);
+                }
 
-    if (!isObject(opt)) {
-        opt = {silent: !!opt};
-    }
+                model.__viewId = view.__id = viewId;
+                view.__alive = true;
+                view.__model = model;
+                view.group.__ecComponentInfo = {
+                    mainType: model.mainType,
+                    index: model.componentIndex
+                };
+                !isComponent && scheduler.prepareView(
+                    view as ChartView, model as SeriesModel, ecModel, api
+                );
+            }
 
-    if (!actions[payload.type]) {
-        return;
-    }
+            for (var i = 0; i < viewList.length;) {
+                var view = viewList[i];
+                if (!view.__alive) {
+                    !isComponent && (view as ChartView).renderTask.dispose();
+                    zr.remove(view.group);
+                    view.dispose(ecModel, api);
+                    viewList.splice(i, 1);
+                    delete viewMap[view.__id];
+                    view.__id = view.group.__ecComponentInfo = null;
+                }
+                else {
+                    i++;
+                }
+            }
+        };
 
-    // Avoid dispatch action before setOption. Especially in `connect`.
-    if (!this._model) {
-        return;
-    }
+        updateDirectly = function (
+            ecIns: ECharts,
+            method: string,
+            payload: Payload,
+            mainType: ComponentMainType,
+            subType?: ComponentSubType
+        ): void {
+            var ecModel = ecIns._model;
+
+            // broadcast
+            if (!mainType) {
+                // FIXME
+                // Chart will not be update directly here, except set dirty.
+                // But there is no such scenario now.
+                each([].concat(ecIns._componentsViews).concat(ecIns._chartsViews), callView);
+                return;
+            }
 
-    // May dispatchAction in rendering procedure
-    if (this[IN_MAIN_PROCESS]) {
-        this._pendingActions.push(payload);
-        return;
-    }
+            var query: QueryConditionKindA['query'] = {};
+            query[mainType + 'Id'] = payload[mainType + 'Id'];
+            query[mainType + 'Index'] = payload[mainType + 'Index'];
+            query[mainType + 'Name'] = payload[mainType + 'Name'];
 
-    doDispatchAction.call(this, payload, opt.silent);
+            var condition = {mainType: mainType, query: query} as QueryConditionKindA;
+            subType && (condition.subType = subType); // subType may be '' by parseClassType;
 
-    if (opt.flush) {
-        this._zr.flush(true);
-    }
-    else if (opt.flush !== false && env.browser.weChat) {
-        // In WeChat embeded browser, `requestAnimationFrame` and `setInterval`
-        // hang when sliding page (on touch event), which cause that zr does not
-        // refresh util user interaction finished, which is not expected.
-        // But `dispatchAction` may be called too frequently when pan on touch
-        // screen, which impacts performance if do not throttle them.
-        this._throttledZrFlush();
-    }
+            var excludeSeriesId = payload.excludeSeriesId;
+            var excludeSeriesIdMap: zrUtil.HashMap<string[]>;
+            if (excludeSeriesId != null) {
+                excludeSeriesIdMap = zrUtil.createHashMap(modelUtil.normalizeToArray(excludeSeriesId));
+            }
 
-    flushPendingActions.call(this, opt.silent);
+            // If dispatchAction before setOption, do nothing.
+            ecModel && ecModel.eachComponent(condition, function (model) {
+                if (!excludeSeriesIdMap || excludeSeriesIdMap.get(model.id) == null) {
+                    callView(ecIns[
+                        mainType === 'series' ? '_chartsMap' : '_componentsMap'
+                    ][model.__viewId]);
+                }
+            }, ecIns);
 
-    triggerUpdatedEvent.call(this, opt.silent);
-};
+            function callView(view: ComponentView | ChartView) {
+                view && view.__alive && (view as any)[method] && (view as any)[method](
+                    view.__model, ecModel, ecIns._api, payload
+                );
+            }
+        };
 
-function doDispatchAction(payload, silent) {
-    var payloadType = payload.type;
-    var escapeConnect = payload.escapeConnect;
-    var actionWrap = actions[payloadType];
-    var actionInfo = actionWrap.actionInfo;
-
-    var cptType = (actionInfo.update || 'update').split(':');
-    var updateMethod = cptType.pop();
-    cptType = cptType[0] != null && parseClassType(cptType[0]);
-
-    this[IN_MAIN_PROCESS] = true;
-
-    var payloads = [payload];
-    var batched = false;
-    // Batch action
-    if (payload.batch) {
-        batched = true;
-        payloads = zrUtil.map(payload.batch, function (item) {
-            item = zrUtil.defaults(zrUtil.extend({}, item), payload);
-            item.batch = null;
-            return item;
-        });
-    }
+        updateMethods = {
 
-    var eventObjBatch = [];
-    var eventObj;
-    var isHighDown = payloadType === 'highlight' || payloadType === 'downplay';
-
-    each(payloads, function (batchItem) {
-        // Action can specify the event by return it.
-        eventObj = actionWrap.action(batchItem, this._model, this._api);
-        // Emit event outside
-        eventObj = eventObj || zrUtil.extend({}, batchItem);
-        // Convert type to eventType
-        eventObj.type = actionInfo.event || eventObj.type;
-        eventObjBatch.push(eventObj);
-
-        // light update does not perform data process, layout and visual.
-        if (isHighDown) {
-            // method, payload, mainType, subType
-            updateDirectly(this, updateMethod, batchItem, 'series');
-        }
-        else if (cptType) {
-            updateDirectly(this, updateMethod, batchItem, cptType.main, cptType.sub);
-        }
-    }, this);
+            prepareAndUpdate: function (this: ECharts, payload: Payload): void {
+                prepare(this);
+                updateMethods.update.call(this, payload);
+            },
 
-    if (updateMethod !== 'none' && !isHighDown && !cptType) {
-        // Still dirty
-        if (this[OPTION_UPDATED]) {
-            // FIXME Pass payload ?
-            prepare(this);
-            updateMethods.update.call(this, payload);
-            this[OPTION_UPDATED] = false;
-        }
-        else {
-            updateMethods[updateMethod].call(this, payload);
-        }
-    }
+            update: function (this: ECharts, payload: Payload): void {
+                // console.profile && console.profile('update');
 
-    // Follow the rule of action batch
-    if (batched) {
-        eventObj = {
-            type: actionInfo.event || payloadType,
-            escapeConnect: escapeConnect,
-            batch: eventObjBatch
-        };
-    }
-    else {
-        eventObj = eventObjBatch[0];
-    }
+                var ecModel = this._model;
+                var api = this._api;
+                var zr = this._zr;
+                var coordSysMgr = this._coordSysMgr;
+                var scheduler = this._scheduler;
 
-    this[IN_MAIN_PROCESS] = false;
+                // update before setOption
+                if (!ecModel) {
+                    return;
+                }
 
-    !silent && this._messageCenter.trigger(eventObj.type, eventObj);
-}
+                scheduler.restoreData(ecModel, payload);
 
-function flushPendingActions(silent) {
-    var pendingActions = this._pendingActions;
-    while (pendingActions.length) {
-        var payload = pendingActions.shift();
-        doDispatchAction.call(this, payload, silent);
-    }
-}
+                scheduler.performSeriesTasks(ecModel);
 
-function triggerUpdatedEvent(silent) {
-    !silent && this.trigger('updated');
-}
+                // TODO
+                // Save total ecModel here for undo/redo (after restoring data and before processing data).
+                // Undo (restoration of total ecModel) can be carried out in 'action' or outside API call.
 
-/**
- * Event `rendered` is triggered when zr
- * rendered. It is useful for realtime
- * snapshot (reflect animation).
- *
- * Event `finished` is triggered when:
- * (1) zrender rendering finished.
- * (2) initial animation finished.
- * (3) progressive rendering finished.
- * (4) no pending action.
- * (5) no delayed setOption needs to be processed.
- */
-function bindRenderedEvent(zr, ecIns) {
-    zr.on('rendered', function () {
-
-        ecIns.trigger('rendered');
-
-        // The `finished` event should not be triggered repeatly,
-        // so it should only be triggered when rendering indeed happend
-        // in zrender. (Consider the case that dipatchAction is keep
-        // triggering when mouse move).
-        if (
-            // Although zr is dirty if initial animation is not finished
-            // and this checking is called on frame, we also check
-            // animation finished for robustness.
-            zr.animation.isFinished()
-            && !ecIns[OPTION_UPDATED]
-            && !ecIns._scheduler.unfinished
-            && !ecIns._pendingActions.length
-        ) {
-            ecIns.trigger('finished');
-        }
-    });
-}
+                // Create new coordinate system each update
+                // In LineView may save the old coordinate system and use it to get the orignal point
+                coordSysMgr.create(ecModel, api);
 
-/**
- * @param {Object} params
- * @param {number} params.seriesIndex
- * @param {Array|TypedArray} params.data
- */
-echartsProto.appendData = function (params) {
-    if (this._disposed) {
-        disposedWarning(this.id);
-        return;
-    }
+                scheduler.performDataProcessorTasks(ecModel, payload);
 
-    var seriesIndex = params.seriesIndex;
-    var ecModel = this.getModel();
-    var seriesModel = ecModel.getSeriesByIndex(seriesIndex);
+                // Current stream render is not supported in data process. So we can update
+                // stream modes after data processing, where the filtered data is used to
+                // deteming whether use progressive rendering.
+                updateStreamModes(this, ecModel);
 
-    if (__DEV__) {
-        assert(params.data && seriesModel);
-    }
+                // We update stream modes before coordinate system updated, then the modes info
+                // can be fetched when coord sys updating (consider the barGrid extent fix). But
+                // the drawback is the full coord info can not be fetched. Fortunately this full
+                // coord is not requied in stream mode updater currently.
+                coordSysMgr.update(ecModel, api);
 
-    seriesModel.appendData(params);
+                clearColorPalette(ecModel);
+                scheduler.performVisualTasks(ecModel, payload);
 
-    // Note: `appendData` does not support that update extent of coordinate
-    // system, util some scenario require that. In the expected usage of
-    // `appendData`, the initial extent of coordinate system should better
-    // be fixed by axis `min`/`max` setting or initial data, otherwise if
-    // the extent changed while `appendData`, the location of the painted
-    // graphic elements have to be changed, which make the usage of
-    // `appendData` meaningless.
+                render(this, ecModel, api, payload);
 
-    this._scheduler.unfinished = true;
-};
+                // Set background
+                var backgroundColor = ecModel.get('backgroundColor') || 'transparent';
 
-/**
- * Register event
- * @method
- */
-echartsProto.on = createRegisterEventWithLowercaseName('on', false);
-echartsProto.off = createRegisterEventWithLowercaseName('off', false);
-echartsProto.one = createRegisterEventWithLowercaseName('one', false);
+                // In IE8
+                if (!env.canvasSupported) {
+                    var colorArr = colorTool.parse(backgroundColor);
+                    backgroundColor = colorTool.stringify(colorArr, 'rgb');
+                    if (colorArr[3] === 0) {
+                        backgroundColor = 'transparent';
+                    }
+                }
+                else {
+                    zr.setBackgroundColor(backgroundColor);
+                }
 
-/**
- * Prepare view instances of charts and components
- * @param  {module:echarts/model/Global} ecModel
- * @private
- */
-function prepareView(ecIns, type, ecModel, scheduler) {
-    var isComponent = type === 'component';
-    var viewList = isComponent ? ecIns._componentsViews : ecIns._chartsViews;
-    var viewMap = isComponent ? ecIns._componentsMap : ecIns._chartsMap;
-    var zr = ecIns._zr;
-    var api = ecIns._api;
-
-    for (var i = 0; i < viewList.length; i++) {
-        viewList[i].__alive = false;
-    }
+                performPostUpdateFuncs(ecModel, api);
 
-    isComponent
-        ? ecModel.eachComponent(function (componentType, model) {
-            componentType !== 'series' && doPrepare(model);
-        })
-        : ecModel.eachSeries(doPrepare);
-
-    function doPrepare(model) {
-        // Consider: id same and type changed.
-        var viewId = '_ec_' + model.id + '_' + model.type;
-        var view = viewMap[viewId];
-        if (!view) {
-            var classType = parseClassType(model.type);
-            var Clazz = isComponent
-                ? ComponentView.getClass(classType.main, classType.sub)
-                : ChartView.getClass(classType.sub);
+                // console.profile && console.profileEnd('update');
+            },
 
-            if (__DEV__) {
-                assert(Clazz, classType.sub + ' does not exist.');
-            }
+            updateTransform: function (this: ECharts, payload: Payload): void {
+                var ecModel = this._model;
+                var ecIns = this;
+                var api = this._api;
 
-            view = new Clazz();
-            view.init(ecModel, api);
-            viewMap[viewId] = view;
-            viewList.push(view);
-            zr.add(view.group);
-        }
+                // update before setOption
+                if (!ecModel) {
+                    return;
+                }
 
-        model.__viewId = view.__id = viewId;
-        view.__alive = true;
-        view.__model = model;
-        view.group.__ecComponentInfo = {
-            mainType: model.mainType,
-            index: model.componentIndex
-        };
-        !isComponent && scheduler.prepareView(view, model, ecModel, api);
-    }
+                // ChartView.markUpdateMethod(payload, 'updateTransform');
 
-    for (var i = 0; i < viewList.length;) {
-        var view = viewList[i];
-        if (!view.__alive) {
-            !isComponent && view.renderTask.dispose();
-            zr.remove(view.group);
-            view.dispose(ecModel, api);
-            viewList.splice(i, 1);
-            delete viewMap[view.__id];
-            view.__id = view.group.__ecComponentInfo = null;
-        }
-        else {
-            i++;
-        }
-    }
-}
+                var componentDirtyList = [];
+                ecModel.eachComponent(function (componentType, componentModel) {
+                    var componentView = ecIns.getViewOfComponentModel(componentModel);
+                    if (componentView && componentView.__alive) {
+                        if (componentView.updateTransform) {
+                            var result = componentView.updateTransform(componentModel, ecModel, api, payload);
+                            result && result.update && componentDirtyList.push(componentView);
+                        }
+                        else {
+                            componentDirtyList.push(componentView);
+                        }
+                    }
+                });
 
-// /**
-//  * Encode visual infomation from data after data processing
-//  *
-//  * @param {module:echarts/model/Global} ecModel
-//  * @param {object} layout
-//  * @param {boolean} [layoutFilter] `true`: only layout,
-//  *                                 `false`: only not layout,
-//  *                                 `null`/`undefined`: all.
-//  * @param {string} taskBaseTag
-//  * @private
-//  */
-// function startVisualEncoding(ecIns, ecModel, api, payload, layoutFilter) {
-//     each(visualFuncs, function (visual, index) {
-//         var isLayout = visual.isLayout;
-//         if (layoutFilter == null
-//             || (layoutFilter === false && !isLayout)
-//             || (layoutFilter === true && isLayout)
-//         ) {
-//             visual.func(ecModel, api, payload);
-//         }
-//     });
-// }
+                var seriesDirtyMap = zrUtil.createHashMap();
+                ecModel.eachSeries(function (seriesModel) {
+                    var chartView = ecIns._chartsMap[seriesModel.__viewId];
+                    if (chartView.updateTransform) {
+                        var result = chartView.updateTransform(seriesModel, ecModel, api, payload);
+                        result && result.update && seriesDirtyMap.set(seriesModel.uid, 1);
+                    }
+                    else {
+                        seriesDirtyMap.set(seriesModel.uid, 1);
+                    }
+                });
 
-function clearColorPalette(ecModel) {
-    ecModel.clearColorPalette();
-    ecModel.eachSeries(function (seriesModel) {
-        seriesModel.clearColorPalette();
-    });
-}
+                clearColorPalette(ecModel);
+                // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline.
+                // this._scheduler.performVisualTasks(ecModel, payload, 'layout', true);
+                this._scheduler.performVisualTasks(
+                    ecModel, payload, {setDirty: true, dirtyMap: seriesDirtyMap}
+                );
 
-function render(ecIns, ecModel, api, payload) {
+                // Currently, not call render of components. Geo render cost a lot.
+                // renderComponents(ecIns, ecModel, api, payload, componentDirtyList);
+                renderSeries(ecIns, ecModel, api, payload, seriesDirtyMap);
 
-    renderComponents(ecIns, ecModel, api, payload);
+                performPostUpdateFuncs(ecModel, this._api);
+            },
 
-    each(ecIns._chartsViews, function (chart) {
-        chart.__alive = false;
-    });
+            updateView: function (this: ECharts, payload: Payload): void {
+                var ecModel = this._model;
 
-    renderSeries(ecIns, ecModel, api, payload);
+                // update before setOption
+                if (!ecModel) {
+                    return;
+                }
 
-    // Remove groups of unrendered charts
-    each(ecIns._chartsViews, function (chart) {
-        if (!chart.__alive) {
-            chart.remove(ecModel, api);
-        }
-    });
-}
+                ChartView.markUpdateMethod(payload, 'updateView');
 
-function renderComponents(ecIns, ecModel, api, payload, dirtyList) {
-    each(dirtyList || ecIns._componentsViews, function (componentView) {
-        var componentModel = componentView.__model;
-        componentView.render(componentModel, ecModel, api, payload);
+                clearColorPalette(ecModel);
 
-        updateZ(componentModel, componentView);
-    });
-}
+                // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline.
+                this._scheduler.performVisualTasks(ecModel, payload, {setDirty: true});
 
-/**
- * Render each chart and component
- * @private
- */
-function renderSeries(ecIns, ecModel, api, payload, dirtyMap) {
-    // Render all charts
-    var scheduler = ecIns._scheduler;
-    var unfinished;
-    ecModel.eachSeries(function (seriesModel) {
-        var chartView = ecIns._chartsMap[seriesModel.__viewId];
-        chartView.__alive = true;
-
-        var renderTask = chartView.renderTask;
-        scheduler.updatePayload(renderTask, payload);
-
-        if (dirtyMap && dirtyMap.get(seriesModel.uid)) {
-            renderTask.dirty();
-        }
+                render(this, this._model, this._api, payload);
 
-        unfinished |= renderTask.perform(scheduler.getPerformArgs(renderTask));
+                performPostUpdateFuncs(ecModel, this._api);
+            },
 
-        chartView.group.silent = !!seriesModel.get('silent');
+            updateVisual: function (this: ECharts, payload: Payload): void {
+                updateMethods.update.call(this, payload);
 
-        updateZ(seriesModel, chartView);
+                // var ecModel = this._model;
 
-        updateBlend(seriesModel, chartView);
-    });
-    scheduler.unfinished |= unfinished;
+                // // update before setOption
+                // if (!ecModel) {
+                //     return;
+                // }
 
-    // If use hover layer
-    updateHoverLayerStatus(ecIns, ecModel);
+                // ChartView.markUpdateMethod(payload, 'updateVisual');
 
-    // Add aria
-    aria(ecIns._zr.dom, ecModel);
-}
+                // clearColorPalette(ecModel);
 
-function performPostUpdateFuncs(ecModel, api) {
-    each(postUpdateFuncs, function (func) {
-        func(ecModel, api);
-    });
-}
+                // // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline.
+                // this._scheduler.performVisualTasks(ecModel, payload, {visualType: 'visual', setDirty: true});
 
+                // render(this, this._model, this._api, payload);
 
-var MOUSE_EVENT_NAMES = [
-    'click', 'dblclick', 'mouseover', 'mouseout', 'mousemove',
-    'mousedown', 'mouseup', 'globalout', 'contextmenu'
-];
+                // performPostUpdateFuncs(ecModel, this._api);
+            },
 
-/**
- * @private
- */
-echartsProto._initEvents = function () {
-    each(MOUSE_EVENT_NAMES, function (eveName) {
-        var handler = function (e) {
-            var ecModel = this.getModel();
-            var el = e.target;
-            var params;
-            var isGlobalOut = eveName === 'globalout';
-
-            // no e.target when 'globalout'.
-            if (isGlobalOut) {
-                params = {};
-            }
-            else if (el && el.dataIndex != null) {
-                var dataModel = el.dataModel || ecModel.getSeriesByIndex(el.seriesIndex);
-                params = dataModel && dataModel.getDataParams(el.dataIndex, el.dataType, el) || {};
+            updateLayout: function (this: ECharts, payload: Payload): void {
+                updateMethods.update.call(this, payload);
+
+                // var ecModel = this._model;
+
+                // // update before setOption
+                // if (!ecModel) {
+                //     return;
+                // }
+
+                // ChartView.markUpdateMethod(payload, 'updateLayout');
+
+                // // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline.
+                // // this._scheduler.performVisualTasks(ecModel, payload, 'layout', true);
+                // this._scheduler.performVisualTasks(ecModel, payload, {setDirty: true});
+
+                // render(this, this._model, this._api, payload);
+
+                // performPostUpdateFuncs(ecModel, this._api);
             }
-            // If element has custom eventData of components
-            else if (el && el.eventData) {
-                params = zrUtil.extend({}, el.eventData);
+        };
+
+        doConvertPixel = function (
+            ecIns: ECharts,
+            methodName: 'convertFromPixel' | 'convertToPixel',
+            finder: ModelFinder,
+            value: any
+        ): any {
+            if (ecIns._disposed) {
+                disposedWarning(ecIns.id);
+                return;
             }
+            var ecModel = ecIns._model;
+            var coordSysList = ecIns._coordSysMgr.getCoordinateSystems();
+            var result;
 
-            // Contract: if params prepared in mouse event,
-            // these properties must be specified:
-            // {
-            //    componentType: string (component main type)
-            //    componentIndex: number
-            // }
-            // Otherwise event query can not work.
-
-            if (params) {
-                var componentType = params.componentType;
-                var componentIndex = params.componentIndex;
-                // Special handling for historic reason: when trigger by
-                // markLine/markPoint/markArea, the componentType is
-                // 'markLine'/'markPoint'/'markArea', but we should better
-                // enable them to be queried by seriesIndex, since their
-                // option is set in each series.
-                if (componentType === 'markLine'
-                    || componentType === 'markPoint'
-                    || componentType === 'markArea'
+            finder = modelUtil.parseFinder(ecModel, finder);
+
+            for (var i = 0; i < coordSysList.length; i++) {
+                var coordSys = coordSysList[i];
+                if (coordSys[methodName]
+                    && (result = coordSys[methodName](ecModel, finder, value)) != null
                 ) {
-                    componentType = 'series';
-                    componentIndex = params.seriesIndex;
-                }
-                var model = componentType && componentIndex != null
-                    && ecModel.getComponent(componentType, componentIndex);
-                var view = model && this[
-                    model.mainType === 'series' ? '_chartsMap' : '_componentsMap'
-                ][model.__viewId];
-
-                if (__DEV__) {
-                    // `event.componentType` and `event[componentTpype + 'Index']` must not
-                    // be missed, otherwise there is no way to distinguish source component.
-                    // See `dataFormat.getDataParams`.
-                    if (!isGlobalOut && !(model && view)) {
-                        console.warn('model or view can not be found by params');
-                    }
+                    return result;
                 }
+            }
+
+            if (__DEV__) {
+                console.warn(
+                    'No coordinate system that supports ' + methodName + ' found by the given finder.'
+                );
+            }
+        };
+
+        updateStreamModes = function (ecIns: ECharts, ecModel: GlobalModel): void {
+            var chartsMap = ecIns._chartsMap;
+            var scheduler = ecIns._scheduler;
+            ecModel.eachSeries(function (seriesModel) {
+                scheduler.updateStreamModes(seriesModel, chartsMap[seriesModel.__viewId]);
+            });
+        };
+
+        doDispatchAction = function (this: ECharts, payload: Payload, silent: boolean): void {
+            var payloadType = payload.type;
+            var escapeConnect = payload.escapeConnect;
+            var actionWrap = actions[payloadType];
+            var actionInfo = actionWrap.actionInfo;
+
+            var cptTypeTmp = (actionInfo.update || 'update').split(':');
+            var updateMethod = cptTypeTmp.pop();
+            var cptType = cptTypeTmp[0] != null && parseClassType(cptTypeTmp[0]);
+
+            this[IN_MAIN_PROCESS] = true;
+
+            var payloads: Payload[] = [payload];
+            var batched = false;
+            // Batch action
+            if (payload.batch) {
+                batched = true;
+                payloads = zrUtil.map<PayloadItem, Payload, unknown>(payload.batch, function (item) {
+                    item = zrUtil.defaults(zrUtil.extend({}, item), payload);
+                    item.batch = null;
+                    return item as Payload;
+                });
+            }
 
-                params.event = e;
-                params.type = eveName;
+            var eventObjBatch: ECEventData[] = [];
+            var eventObj: ECEvent;
+            var isHighDown = payloadType === 'highlight' || payloadType === 'downplay';
+
+            each(payloads, function (batchItem) {
+                // Action can specify the event by return it.
+                eventObj = actionWrap.action(batchItem, this._model, this._api) as ECEvent;
+                // Emit event outside
+                eventObj = eventObj || zrUtil.extend({} as ECEvent, batchItem);
+                // Convert type to eventType
+                eventObj.type = actionInfo.event || eventObj.type;
+                eventObjBatch.push(eventObj);
+
+                // light update does not perform data process, layout and visual.
+                if (isHighDown) {
+                    // method, payload, mainType, subType
+                    updateDirectly(this, updateMethod, batchItem as Payload, 'series');
+                }
+                else if (cptType) {
+                    updateDirectly(this, updateMethod, batchItem as Payload, cptType.main, cptType.sub);
+                }
+            }, this);
+
+            if (updateMethod !== 'none' && !isHighDown && !cptType) {
+                // Still dirty
+                if (this[OPTION_UPDATED]) {
+                    // FIXME Pass payload ?
+                    prepare(this);
+                    updateMethods.update.call(this, payload);
+                    this[OPTION_UPDATED] = false;
+                }
+                else {
+                    updateMethods[updateMethod as keyof typeof updateMethods].call(this, payload);
+                }
+            }
 
-                this._ecEventProcessor.eventInfo = {
-                    targetEl: el,
-                    packedEvent: params,
-                    model: model,
-                    view: view
+            // Follow the rule of action batch
+            if (batched) {
+                eventObj = {
+                    type: actionInfo.event || payloadType,
+                    escapeConnect: escapeConnect,
+                    batch: eventObjBatch
                 };
+            }
+            else {
+                eventObj = eventObjBatch[0] as ECEvent;
+            }
+
+            this[IN_MAIN_PROCESS] = false;
+
+            !silent && this._messageCenter.trigger(eventObj.type, eventObj);
+        };
 
-                this.trigger(eveName, params);
+        flushPendingActions = function (this: ECharts, silent: boolean): void {
+            var pendingActions = this._pendingActions;
+            while (pendingActions.length) {
+                var payload = pendingActions.shift();
+                doDispatchAction.call(this, payload, silent);
             }
         };
-        // Consider that some component (like tooltip, brush, ...)
-        // register zr event handler, but user event handler might
-        // do anything, such as call `setOption` or `dispatchAction`,
-        // which probably update any of the content and probably
-        // cause problem if it is called previous other inner handlers.
-        handler.zrEventfulCallAtLast = true;
-        this._zr.on(eveName, handler, this);
-    }, this);
-
-    each(eventActionMap, function (actionType, eventType) {
-        this._messageCenter.on(eventType, function (event) {
-            this.trigger(eventType, event);
-        }, this);
-    }, this);
-};
 
-/**
- * @return {boolean}
- */
-echartsProto.isDisposed = function () {
-    return this._disposed;
-};
+        triggerUpdatedEvent = function (this: ECharts, silent): void {
+            !silent && this.trigger('updated');
+        };
 
-/**
- * Clear
- */
-echartsProto.clear = function () {
-    if (this._disposed) {
-        disposedWarning(this.id);
-        return;
-    }
-    this.setOption({ series: [] }, true);
-};
+        /**
+         * Event `rendered` is triggered when zr
+         * rendered. It is useful for realtime
+         * snapshot (reflect animation).
+         *
+         * Event `finished` is triggered when:
+         * (1) zrender rendering finished.
+         * (2) initial animation finished.
+         * (3) progressive rendering finished.
+         * (4) no pending action.
+         * (5) no delayed setOption needs to be processed.
+         */
+        bindRenderedEvent = function (zr: zrender.ZRenderType, ecIns: ECharts): void {
+            zr.on('rendered', function () {
+
+                ecIns.trigger('rendered');
+
+                // The `finished` event should not be triggered repeatly,
+                // so it should only be triggered when rendering indeed happend
+                // in zrender. (Consider the case that dipatchAction is keep
+                // triggering when mouse move).
+                if (
+                    // Although zr is dirty if initial animation is not finished
+                    // and this checking is called on frame, we also check
+                    // animation finished for robustness.
+                    zr.animation.isFinished()
+                    && !ecIns[OPTION_UPDATED]
+                    && !ecIns._scheduler.unfinished
+                    && !ecIns._pendingActions.length
+                ) {
+                    ecIns.trigger('finished');
+                }
+            });
+        };
 
-/**
- * Dispose instance
- */
-echartsProto.dispose = function () {
-    if (this._disposed) {
-        disposedWarning(this.id);
-        return;
-    }
-    this._disposed = true;
+        clearColorPalette = function (ecModel: GlobalModel): void {
+            ecModel.clearColorPalette();
+            ecModel.eachSeries(function (seriesModel) {
+                seriesModel.clearColorPalette();
+            });
+        };
 
-    modelUtil.setAttribute(this.getDom(), DOM_ATTRIBUTE_KEY, '');
+        render = function (ecIns: ECharts, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload): void {
 
-    var api = this._api;
-    var ecModel = this._model;
+            renderComponents(ecIns, ecModel, api, payload);
 
-    each(this._componentsViews, function (component) {
-        component.dispose(ecModel, api);
-    });
-    each(this._chartsViews, function (chart) {
-        chart.dispose(ecModel, api);
-    });
+            each(ecIns._chartsViews, function (chart: ChartView) {
+                chart.__alive = false;
+            });
 
-    // Dispose after all views disposed
-    this._zr.dispose();
+            renderSeries(ecIns, ecModel, api, payload);
 
-    delete instances[this.id];
-};
+            // Remove groups of unrendered charts
+            each(ecIns._chartsViews, function (chart: ChartView) {
+                if (!chart.__alive) {
+                    chart.remove(ecModel, api);
+                }
+            });
+        };
 
-zrUtil.mixin(ECharts, Eventful);
+        renderComponents = function (
+            ecIns: ECharts, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload, dirtyList?: ComponentView[]
+        ): void {
+            each(dirtyList || ecIns._componentsViews, function (componentView: ComponentView) {
+                var componentModel = componentView.__model;
+                componentView.render(componentModel, ecModel, api, payload);
 
-function disposedWarning(id) {
-    if (__DEV__) {
-        console.warn('Instance ' + id + ' has been disposed');
-    }
-}
+                updateZ(componentModel, componentView);
+            });
+        };
 
-function updateHoverLayerStatus(ecIns, ecModel) {
-    var zr = ecIns._zr;
-    var storage = zr.storage;
-    var elCount = 0;
+        /**
+         * Render each chart and component
+         */
+        renderSeries = function (
+            ecIns: ECharts,
+            ecModel: GlobalModel,
+            api: ExtensionAPI,
+            payload: Payload | 'remain',
+            dirtyMap?: {[uid: string]: any}
+        ): void {
+            // Render all charts
+            var scheduler = ecIns._scheduler;
+            var unfinished: number;
+            ecModel.eachSeries(function (seriesModel) {
+                var chartView = ecIns._chartsMap[seriesModel.__viewId];
+                chartView.__alive = true;
+
+                var renderTask = chartView.renderTask;
+                scheduler.updatePayload(renderTask, payload);
+
+                if (dirtyMap && dirtyMap.get(seriesModel.uid)) {
+                    renderTask.dirty();
+                }
 
-    storage.traverse(function (el) {
-        elCount++;
-    });
+                unfinished |= +renderTask.perform(scheduler.getPerformArgs(renderTask));
 
-    if (elCount > ecModel.get('hoverLayerThreshold') && !env.node) {
-        ecModel.eachSeries(function (seriesModel) {
-            if (seriesModel.preventUsingHoverLayer) {
-                return;
-            }
-            var chartView = ecIns._chartsMap[seriesModel.__viewId];
-            if (chartView.__alive) {
-                chartView.group.traverse(function (el) {
-                    // Don't switch back.
-                    el.useHoverLayer = true;
-                });
-            }
-        });
-    }
-}
+                chartView.group.silent = !!seriesModel.get('silent');
 
-/**
- * Update chart progressive and blend.
- * @param {module:echarts/model/Series|module:echarts/model/Component} model
- * @param {module:echarts/view/Component|module:echarts/view/Chart} view
- */
-function updateBlend(seriesModel, chartView) {
-    var blendMode = seriesModel.get('blendMode') || null;
-    if (__DEV__) {
-        if (!env.canvasSupported && blendMode && blendMode !== 'source-over') {
-            console.warn('Only canvas support blendMode');
-        }
-    }
-    chartView.group.traverse(function (el) {
-        // FIXME marker and other components
-        if (!el.isGroup) {
-            // Only set if blendMode is changed. In case element is incremental and don't wan't to rerender.
-            if (el.style.blend !== blendMode) {
-                el.setStyle('blend', blendMode);
-            }
-        }
-        if (el.eachPendingDisplayable) {
-            el.eachPendingDisplayable(function (displayable) {
-                displayable.setStyle('blend', blendMode);
+                updateZ(seriesModel, chartView);
+
+                updateBlend(seriesModel, chartView);
             });
-        }
-    });
-}
+            scheduler.unfinished |= unfinished;
 
-/**
- * @param {module:echarts/model/Series|module:echarts/model/Component} model
- * @param {module:echarts/view/Component|module:echarts/view/Chart} view
- */
-function updateZ(model, view) {
-    var z = model.get('z');
-    var zlevel = model.get('zlevel');
-    // Set z and zlevel
-    view.group.traverse(function (el) {
-        if (el.type !== 'group') {
-            z != null && (el.z = z);
-            zlevel != null && (el.zlevel = zlevel);
-        }
-    });
-}
+            // If use hover layer
+            updateHoverLayerStatus(ecIns, ecModel);
 
-function createExtensionAPI(ecInstance) {
-    var coordSysMgr = ecInstance._coordSysMgr;
-    return zrUtil.extend(new ExtensionAPI(ecInstance), {
-        // Inject methods
-        getCoordinateSystems: zrUtil.bind(
-            coordSysMgr.getCoordinateSystems, coordSysMgr
-        ),
-        getComponentByElement: function (el) {
-            while (el) {
-                var modelInfo = el.__ecComponentInfo;
-                if (modelInfo != null) {
-                    return ecInstance._model.getComponent(modelInfo.mainType, modelInfo.index);
-                }
-                el = el.parent;
-            }
-        }
-    });
-}
+            // Add aria
+            aria(ecIns._zr.dom, ecModel);
+        };
 
+        performPostUpdateFuncs = function (ecModel: GlobalModel, api: ExtensionAPI): void {
+            each(postUpdateFuncs, function (func) {
+                func(ecModel, api);
+            });
+        };
 
-/**
- * @class
- * Usage of query:
- * `chart.on('click', query, handler);`
- * The `query` can be:
- * + The component type query string, only `mainType` or `mainType.subType`,
- *   like: 'xAxis', 'series', 'xAxis.category' or 'series.line'.
- * + The component query object, like:
- *   `{seriesIndex: 2}`, `{seriesName: 'xx'}`, `{seriesId: 'some'}`,
- *   `{xAxisIndex: 2}`, `{xAxisName: 'xx'}`, `{xAxisId: 'some'}`.
- * + The data query object, like:
- *   `{dataIndex: 123}`, `{dataType: 'link'}`, `{name: 'some'}`.
- * + The other query object (cmponent customized query), like:
- *   `{element: 'some'}` (only available in custom series).
- *
- * Caveat: If a prop in the `query` object is `null/undefined`, it is the
- * same as there is no such prop in the `query` object.
- */
-function EventProcessor() {
-    // These info required: targetEl, packedEvent, model, view
-    this.eventInfo;
-}
-EventProcessor.prototype = {
-    constructor: EventProcessor,
-
-    normalizeQuery: function (query) {
-        var cptQuery = {};
-        var dataQuery = {};
-        var otherQuery = {};
-
-        // `query` is `mainType` or `mainType.subType` of component.
-        if (zrUtil.isString(query)) {
-            var condCptType = parseClassType(query);
-            // `.main` and `.sub` may be ''.
-            cptQuery.mainType = condCptType.main || null;
-            cptQuery.subType = condCptType.sub || null;
-        }
-        // `query` is an object, convert to {mainType, index, name, id}.
-        else {
-            // `xxxIndex`, `xxxName`, `xxxId`, `name`, `dataIndex`, `dataType` is reserved,
-            // can not be used in `compomentModel.filterForExposedEvent`.
-            var suffixes = ['Index', 'Name', 'Id'];
-            var dataKeys = {name: 1, dataIndex: 1, dataType: 1};
-            zrUtil.each(query, function (val, key) {
-                var reserved = false;
-                for (var i = 0; i < suffixes.length; i++) {
-                    var propSuffix = suffixes[i];
-                    var suffixPos = key.lastIndexOf(propSuffix);
-                    if (suffixPos > 0 && suffixPos === key.length - propSuffix.length) {
-                        var mainType = key.slice(0, suffixPos);
-                        // Consider `dataIndex`.
-                        if (mainType !== 'data') {
-                            cptQuery.mainType = mainType;
-                            cptQuery[propSuffix.toLowerCase()] = val;
-                            reserved = true;
-                        }
+        updateHoverLayerStatus = function (ecIns: ECharts, ecModel: GlobalModel): void {
+            var zr = ecIns._zr;
+            var storage = zr.storage;
+            var elCount = 0;
+
+            storage.traverse(function (el) {
+                elCount++;
+            });
+
+            if (elCount > ecModel.get('hoverLayerThreshold') && !env.node) {
+                ecModel.eachSeries(function (seriesModel) {
+                    if (seriesModel.preventUsingHoverLayer) {
+                        return;
+                    }
+                    var chartView = ecIns._chartsMap[seriesModel.__viewId];
+                    if (chartView.__alive) {
+                        chartView.group.traverse(function (el: ECElement) {
+                            // Don't switch back.
+                            el.useHoverLayer = true;
+                        });
+                    }
+                });
+            }
+        };
+
+        /**
+         * Update chart progressive and blend.
+         */
+        updateBlend = function (seriesModel: SeriesModel, chartView: ChartView): void {
+            var blendMode = seriesModel.get('blendMode') || null;
+            if (__DEV__) {
+                if (!env.canvasSupported && blendMode && blendMode !== 'source-over') {
+                    console.warn('Only canvas support blendMode');
+                }
+            }
+            chartView.group.traverse(function (el: Displayable) {
+                // FIXME marker and other components
+                if (!el.isGroup) {
+                    // Only set if blendMode is changed. In case element is incremental and don't wan't to rerender.
+                    if (el.style.blend !== blendMode) {
+                        el.setStyle('blend', blendMode);
                     }
                 }
-                if (dataKeys.hasOwnProperty(key)) {
-                    dataQuery[key] = val;
-                    reserved = true;
+                if ((el as IncrementalDisplayable).eachPendingDisplayable) {
+                    (el as IncrementalDisplayable).eachPendingDisplayable(function (displayable) {
+                        displayable.setStyle('blend', blendMode);
+                    });
                 }
-                if (!reserved) {
-                    otherQuery[key] = val;
+            });
+        };
+
+        updateZ = function (model: ComponentModel, view: ComponentView | ChartView): void {
+            var z = model.get('z');
+            var zlevel = model.get('zlevel');
+            // Set z and zlevel
+            view.group.traverse(function (el: Displayable) {
+                if (el.type !== 'group') {
+                    z != null && (el.z = z);
+                    zlevel != null && (el.zlevel = zlevel);
                 }
             });
-        }
+        };
 
-        return {
-            cptQuery: cptQuery,
-            dataQuery: dataQuery,
-            otherQuery: otherQuery
+        createExtensionAPI = function (ecIns: ECharts): ExtensionAPI {
+            return new (class extends ExtensionAPI {
+                getCoordinateSystems(): CoordinateSystem[] {
+                    return ecIns._coordSysMgr.getCoordinateSystems();
+                }
+                getComponentByElement(el: Element) {
+                    while (el) {
+                        var modelInfo = (el as ViewRootGroup).__ecComponentInfo;
+                        if (modelInfo != null) {
+                            return ecIns._model.getComponent(modelInfo.mainType, modelInfo.index);
+                        }
+                        el = el.parent;
+                    }
+                }
+            })(ecIns);
         };
-    },
 
-    filter: function (eventType, query, args) {
-        // They should be assigned before each trigger call.
-        var eventInfo = this.eventInfo;
+        enableConnect = function (chart: ECharts): void {
 
-        if (!eventInfo) {
-            return true;
-        }
+            function updateConnectedChartsStatus(charts: ECharts[], status: ConnectStatus) {
+                for (var i = 0; i < charts.length; i++) {
+                    var otherChart = charts[i];
+                    otherChart[CONNECT_STATUS_KEY] = status;
+                }
+            }
 
-        var targetEl = eventInfo.targetEl;
-        var packedEvent = eventInfo.packedEvent;
-        var model = eventInfo.model;
-        var view = eventInfo.view;
+            each(eventActionMap, function (actionType, eventType) {
+                chart._messageCenter.on(eventType, function (event) {
+                    if (connectedGroups[chart.group] && chart[CONNECT_STATUS_KEY] !== CONNECT_STATUS_PENDING) {
+                        if (event && event.escapeConnect) {
+                            return;
+                        }
 
-        // For event like 'globalout'.
-        if (!model || !view) {
-            return true;
-        }
+                        var action = chart.makeActionFromEvent(event);
+                        var otherCharts: ECharts[] = [];
+
+                        each(instances, function (otherChart) {
+                            if (otherChart !== chart && otherChart.group === chart.group) {
+                                otherCharts.push(otherChart);
+                            }
+                        });
+
+                        updateConnectedChartsStatus(otherCharts, CONNECT_STATUS_PENDING);
+                        each(otherCharts, function (otherChart) {
+                            if (otherChart[CONNECT_STATUS_KEY] !== CONNECT_STATUS_UPDATING) {
+                                otherChart.dispatchAction(action);
+                            }
+                        });
+                        updateConnectedChartsStatus(otherCharts, CONNECT_STATUS_UPDATED);
+                    }
+                });
+            });
+        };
 
-        var cptQuery = query.cptQuery;
-        var dataQuery = query.dataQuery;
-
-        return check(cptQuery, model, 'mainType')
-            && check(cptQuery, model, 'subType')
-            && check(cptQuery, model, 'index', 'componentIndex')
-            && check(cptQuery, model, 'name')
-            && check(cptQuery, model, 'id')
-            && check(dataQuery, packedEvent, 'name')
-            && check(dataQuery, packedEvent, 'dataIndex')
-            && check(dataQuery, packedEvent, 'dataType')
-            && (!view.filterForExposedEvent || view.filterForExposedEvent(
-                eventType, query.otherQuery, targetEl, packedEvent
-            ));
-
-        function check(query, host, prop, propOnHost) {
-            return query[prop] == null || host[propOnHost || prop] === query[prop];
-        }
-    },
+    })()
+}
 
-    afterTrigger: function () {
-        // Make sure the eventInfo wont be used in next trigger.
-        this.eventInfo = null;
-    }
+
+// ---------------------------------------
+// Internal method names for class ECharts
+// ---------------------------------------
+var prepare: (ecIns: ECharts) => void;
+var prepareView: (ecIns: ECharts, isComponent: boolean) => void;
+var updateDirectly: (
+    ecIns: ECharts, method: string, payload: Payload, mainType: ComponentMainType, subType?: ComponentSubType
+) => void;
+type UpdateMethod = (this: ECharts, payload?: Payload) => void
+var updateMethods: {
+    prepareAndUpdate: UpdateMethod,
+    update: UpdateMethod,
+    updateTransform: UpdateMethod,
+    updateView: UpdateMethod,
+    updateVisual: UpdateMethod,
+    updateLayout: UpdateMethod
 };
+var doConvertPixel: (ecIns: ECharts, methodName: string, finder: ModelFinder, value: any) => any;
+var updateStreamModes: (ecIns: ECharts, ecModel: GlobalModel) => void;
+var doDispatchAction: (this: ECharts, payload: Payload, silent: boolean) => void;
+var flushPendingActions: (this: ECharts, silent: boolean) => void;
+var triggerUpdatedEvent: (this: ECharts, silent: boolean) => void;
+var bindRenderedEvent: (zr: zrender.ZRenderType, ecIns: ECharts) => void;
+var clearColorPalette: (ecModel: GlobalModel) => void;
+var render: (ecIns: ECharts, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload) => void;
+var renderComponents: (
+    ecIns: ECharts, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload, dirtyList?: ComponentView[]
+) => void;
+var renderSeries: (
+    ecIns: ECharts,
+    ecModel: GlobalModel,
+    api: ExtensionAPI,
+    payload: Payload | 'remain',
+    dirtyMap?: {[uid: string]: any}
+) => void;
+var performPostUpdateFuncs: (ecModel: GlobalModel, api: ExtensionAPI) => void;
+var updateHoverLayerStatus: (ecIns: ECharts, ecModel: GlobalModel) => void;
+var updateBlend: (seriesModel: SeriesModel, chartView: ChartView) => void;
+var updateZ: (model: ComponentModel, view: ComponentView | ChartView) => void;
+var createExtensionAPI: (ecIns: ECharts) => ExtensionAPI;
+var enableConnect: (chart: ECharts) => void;
+
+
+
+interface ECharts extends Eventful {}
+zrUtil.tsMixin(ECharts, Eventful);
 
+var echartsProto = ECharts.prototype;
+echartsProto.on = createRegisterEventWithLowercaseECharts('on');
+echartsProto.off = createRegisterEventWithLowercaseECharts('off');
+// echartsProto.one = createRegisterEventWithLowercaseECharts('one');
 
-/**
- * @type {Object} key: actionType.
- * @inner
- */
-var actions = {};
+// /**
+//  * Encode visual infomation from data after data processing
+//  *
+//  * @param {module:echarts/model/Global} ecModel
+//  * @param {object} layout
+//  * @param {boolean} [layoutFilter] `true`: only layout,
+//  *                                 `false`: only not layout,
+//  *                                 `null`/`undefined`: all.
+//  * @param {string} taskBaseTag
+//  * @private
+//  */
+// function startVisualEncoding(ecIns, ecModel, api, payload, layoutFilter) {
+//     each(visualFuncs, function (visual, index) {
+//         var isLayout = visual.isLayout;
+//         if (layoutFilter == null
+//             || (layoutFilter === false && !isLayout)
+//             || (layoutFilter === true && isLayout)
+//         ) {
+//             visual.func(ecModel, api, payload);
+//         }
+//     });
+// }
 
-/**
- * Map eventType to actionType
- * @type {Object}
- */
-var eventActionMap = {};
 
-/**
- * Data processor functions of each stage
- * @type {Array.<Object.<string, Function>>}
- * @inner
- */
-var dataProcessorFuncs = [];
+var MOUSE_EVENT_NAMES = [
+    'click', 'dblclick', 'mouseover', 'mouseout', 'mousemove',
+    'mousedown', 'mouseup', 'globalout', 'contextmenu'
+];
 
-/**
- * @type {Array.<Function>}
- * @inner
- */
-var optionPreprocessorFuncs = [];
+function disposedWarning(id: string): void {
+    if (__DEV__) {
+        console.warn('Instance ' + id + ' has been disposed');
+    }
+}
 
-/**
- * @type {Array.<Function>}
- * @inner
- */
-var postUpdateFuncs = [];
 
-/**
- * Visual encoding functions of each stage
- * @type {Array.<Object.<string, Function>>}
- */
-var visualFuncs = [];
+var actions: {
+    [actionType: string]: {
+        action: ActionHandler,
+        actionInfo: ActionInfo
+    }
+} = {};
 
 /**
- * Theme storage
- * @type {Object.<key, Object>}
- */
-var themeStorage = {};
-/**
- * Loading effects
+ * Map eventType to actionType
  */
-var loadingEffects = {};
+var eventActionMap: {[eventType: string]: string} = {};
 
-var instances = {};
-var connectedGroups = {};
+var dataProcessorFuncs: StageHandler[] = [];
 
-var idBase = new Date() - 0;
-var groupIdBase = new Date() - 0;
-var DOM_ATTRIBUTE_KEY = '_echarts_instance_';
+var optionPreprocessorFuncs: OptionPreprocessor[] = [];
 
-function enableConnect(chart) {
-    var STATUS_PENDING = 0;
-    var STATUS_UPDATING = 1;
-    var STATUS_UPDATED = 2;
-    var STATUS_KEY = '__connectUpdateStatus';
+var postUpdateFuncs: PostUpdater[] = [];
 
-    function updateConnectedChartsStatus(charts, status) {
-        for (var i = 0; i < charts.length; i++) {
-            var otherChart = charts[i];
-            otherChart[STATUS_KEY] = status;
-        }
-    }
+var visualFuncs: StageHandler[] = [];
 
-    each(eventActionMap, function (actionType, eventType) {
-        chart._messageCenter.on(eventType, function (event) {
-            if (connectedGroups[chart.group] && chart[STATUS_KEY] !== STATUS_PENDING) {
-                if (event && event.escapeConnect) {
-                    return;
-                }
+var themeStorage: {[themeName: string]: ThemeOption} = {};
 
-                var action = chart.makeActionFromEvent(event);
-                var otherCharts = [];
+var loadingEffects: {[effectName: string]: LoadingEffectCreator} = {};
 
-                each(instances, function (otherChart) {
-                    if (otherChart !== chart && otherChart.group === chart.group) {
-                        otherCharts.push(otherChart);
-                    }
-                });
+var instances: {[id: string]: ECharts} = {};
+var connectedGroups: {[groupId: string]: boolean} = {};
+
+var idBase: number = +(new Date()) - 0;
+var groupIdBase: number = +(new Date()) - 0;
+var DOM_ATTRIBUTE_KEY = '_echarts_instance_';
 
-                updateConnectedChartsStatus(otherCharts, STATUS_PENDING);
-                each(otherCharts, function (otherChart) {
-                    if (otherChart[STATUS_KEY] !== STATUS_UPDATING) {
-                        otherChart.dispatchAction(action);
-                    }
-                });
-                updateConnectedChartsStatus(otherCharts, STATUS_UPDATED);
-            }
-        });
-    });
-}
 
 /**
- * @param {HTMLElement} dom
- * @param {Object} [theme]
- * @param {Object} opts
- * @param {number} [opts.devicePixelRatio] Use window.devicePixelRatio by default
- * @param {string} [opts.renderer] Can choose 'canvas' or 'svg' to render the chart.
- * @param {number} [opts.width] Use clientWidth of the input `dom` by default.
- *                              Can be 'auto' (the same as null/undefined)
- * @param {number} [opts.height] Use clientHeight of the input `dom` by default.
- *                               Can be 'auto' (the same as null/undefined)
+ * @param opts.devicePixelRatio Use window.devicePixelRatio by default
+ * @param opts.renderer Can choose 'canvas' or 'svg' to render the chart.
+ * @param opts.width Use clientWidth of the input `dom` by default.
+ *        Can be 'auto' (the same as null/undefined)
+ * @param opts.height Use clientHeight of the input `dom` by default.
+ *        Can be 'auto' (the same as null/undefined)
  */
-export function init(dom, theme, opts) {
+export function init(
+    dom: HTMLElement,
+    theme?: string | object,
+    opts?: {
+        renderer?: RendererType,
+        devicePixelRatio?: number,
+        width?: number,
+        height?: number
+    }
+): ECharts {
     if (__DEV__) {
         // Check version
-        if ((zrender.version.replace('.', '') - 0) < (dependencies.zrender.replace('.', '') - 0)) {
+        if (+zrender.version.replace('.', '') < +dependencies.zrender.replace('.', '')) {
             throw new Error(
                 'zrender/src ' + zrender.version
                 + ' is too old for ECharts ' + version
@@ -2088,9 +1954,23 @@ export function init(dom, theme, opts) {
 }
 
 /**
- * @return {string|Array.<module:echarts~ECharts>} groupId
- */
-export function connect(groupId) {
+ * @usage
+ * (A)
+ * ```js
+ * var chart1 = echarts.init(dom1);
+ * var chart2 = echarts.init(dom2);
+ * chart1.group = 'xxx';
+ * chart2.group = 'xxx';
+ * echarts.connect('xxx');
+ * ```
+ * (B)
+ * ```js
+ * var chart1 = echarts.init(dom1);
+ * var chart2 = echarts.init(dom2);
+ * echarts.connect('xxx', [chart1, chart2]);
+ * ```
+ */
+export function connect(groupId: string | ECharts[]): string {
     // Is array of charts
     if (zrUtil.isArray(groupId)) {
         var charts = groupId;
@@ -2103,31 +1983,29 @@ export function connect(groupId) {
         });
         groupId = groupId || ('g_' + groupIdBase++);
         each(charts, function (chart) {
-            chart.group = groupId;
+            chart.group = groupId as string;
         });
     }
-    connectedGroups[groupId] = true;
-    return groupId;
+    connectedGroups[groupId as string] = true;
+    return groupId as string;
 }
 
 /**
- * @DEPRECATED
- * @return {string} groupId
+ * @deprecated
  */
-export function disConnect(groupId) {
+export function disConnect(groupId: string): void {
     connectedGroups[groupId] = false;
 }
 
 /**
- * @return {string} groupId
+ * Alias and backword compat
  */
 export var disconnect = disConnect;
 
 /**
  * Dispose a chart instance
- * @param  {module:echarts~ECharts|HTMLDomElement|string} chart
  */
-export function dispose(chart) {
+export function dispose(chart: ECharts | HTMLElement | string): void {
     if (typeof chart === 'string') {
         chart = instances[chart];
     }
@@ -2140,42 +2018,32 @@ export function dispose(chart) {
     }
 }
 
-/**
- * @param  {HTMLElement} dom
- * @return {echarts~ECharts}
- */
-export function getInstanceByDom(dom) {
+export function getInstanceByDom(dom: HTMLElement): ECharts {
     return instances[modelUtil.getAttribute(dom, DOM_ATTRIBUTE_KEY)];
 }
 
-/**
- * @param {string} key
- * @return {echarts~ECharts}
- */
-export function getInstanceById(key) {
+export function getInstanceById(key: string): ECharts {
     return instances[key];
 }
 
 /**
  * Register theme
  */
-export function registerTheme(name, theme) {
+export function registerTheme(name: string, theme: ThemeOption): void {
     themeStorage[name] = theme;
 }
 
 /**
  * Register option preprocessor
- * @param {Function} preprocessorFunc
  */
-export function registerPreprocessor(preprocessorFunc) {
+export function registerPreprocessor(preprocessorFunc: OptionPreprocessor): void {
     optionPreprocessorFuncs.push(preprocessorFunc);
 }
 
-/**
- * @param {number} [priority=1000]
- * @param {Object|Function} processor
- */
-export function registerProcessor(priority, processor) {
+export function registerProcessor(
+    priority: number | StageHandlerInput | StageHandlerOverallReset,
+    processor?: StageHandlerInput | StageHandlerOverallReset
+): void {
     normalizeRegister(dataProcessorFuncs, priority, processor, PRIORITY_PROCESSOR_FILTER);
 }
 
@@ -2183,12 +2051,12 @@ export function registerProcessor(priority, processor) {
  * Register postUpdater
  * @param {Function} postUpdateFunc
  */
-export function registerPostUpdate(postUpdateFunc) {
+export function registerPostUpdate(postUpdateFunc: PostUpdater): void {
     postUpdateFuncs.push(postUpdateFunc);
 }
 
 /**
- * Usage:
+ * @usage
  * registerAction('someAction', 'someEvent', function () { ... });
  * registerAction('someAction', function () { ... });
  * registerAction(
@@ -2203,36 +2071,44 @@ export function registerPostUpdate(postUpdateFunc) {
  * @param {string} [eventName]
  * @param {Function} action
  */
-export function registerAction(actionInfo, eventName, action) {
+export function registerAction(type: string, eventName: string, action: ActionHandler): void;
+export function registerAction(type: string, action: ActionHandler): void;
+export function registerAction(actionInfo: ActionInfo, action: ActionHandler): void;
+export function registerAction(
+    actionInfo: string | ActionInfo,
+    eventName: string | ActionHandler,
+    action?: ActionHandler
+): void {
     if (typeof eventName === 'function') {
         action = eventName;
         eventName = '';
     }
     var actionType = isObject(actionInfo)
-        ? actionInfo.type
+        ? (actionInfo as ActionInfo).type
         : ([actionInfo, actionInfo = {
             event: eventName
-        }][0]);
+        } as ActionInfo][0]);
 
     // Event name is all lowercase
-    actionInfo.event = (actionInfo.event || actionType).toLowerCase();
-    eventName = actionInfo.event;
+    (actionInfo as ActionInfo).event = (
+        (actionInfo as ActionInfo).event || actionType as string
+    ).toLowerCase();
+    eventName = (actionInfo as ActionInfo).event;
 
     // Validate action type and event name.
-    assert(ACTION_REG.test(actionType) && ACTION_REG.test(eventName));
+    assert(ACTION_REG.test(actionType as string) && ACTION_REG.test(eventName));
 
-    if (!actions[actionType]) {
-        actions[actionType] = {action: action, actionInfo: actionInfo};
+    if (!actions[actionType as string]) {
+        actions[actionType as string] = {action: action, actionInfo: actionInfo as ActionInfo};
     }
-    eventActionMap[eventName] = actionType;
+    eventActionMap[eventName as string] = actionType as string;
 }
 
-/**
- * @param {string} type
- * @param {*} CoordinateSystem
- */
-export function registerCoordinateSystem(type, CoordinateSystem) {
-    CoordinateSystemManager.register(type, CoordinateSystem);
+export function registerCoordinateSystem(
+    type: string,
+    coordSysCreator: CoordinateSystemCreator
+): void {
+    CoordinateSystemManager.register(type, coordSysCreator);
 }
 
 /**
@@ -2240,12 +2116,12 @@ export function registerCoordinateSystem(type, CoordinateSystem) {
  * @param {string} type
  * @return {Array.<string|Object>}
  */
-export function getCoordinateSystemDimensions(type) {
+export function getCoordinateSystemDimensions(type: string): DimensionDefinitionLoose[] {
     var coordSysCreator = CoordinateSystemManager.get(type);
     if (coordSysCreator) {
         return coordSysCreator.getDimensionsInfo
-                ? coordSysCreator.getDimensionsInfo()
-                : coordSysCreator.dimensions.slice();
+            ? coordSysCreator.getDimensionsInfo()
+            : coordSysCreator.dimensions.slice();
     }
 }
 
@@ -2253,28 +2129,30 @@ export function getCoordinateSystemDimensions(type) {
  * Layout is a special stage of visual encoding
  * Most visual encoding like color are common for different chart
  * But each chart has it's own layout algorithm
- *
- * @param {number} [priority=1000]
- * @param {Function} layoutTask
  */
-export function registerLayout(priority, layoutTask) {
+export function registerLayout(
+    priority: number | StageHandlerInput | StageHandlerOverallReset,
+    layoutTask?: StageHandlerInput | StageHandlerOverallReset
+): void {
     normalizeRegister(visualFuncs, priority, layoutTask, PRIORITY_VISUAL_LAYOUT, 'layout');
 }
 
-/**
- * @param {number} [priority=3000]
- * @param {module:echarts/stream/Task} visualTask
- */
-export function registerVisual(priority, visualTask) {
+export function registerVisual(
+    priority: number | StageHandlerInput | StageHandlerOverallReset,
+    visualTask?: StageHandlerInput | StageHandlerOverallReset
+): void {
     normalizeRegister(visualFuncs, priority, visualTask, PRIORITY_VISUAL_CHART, 'visual');
 }
 
-/**
- * @param {Object|Function} fn: {seriesType, createOnAllSeries, performRawSeries, reset}
- */
-function normalizeRegister(targetList, priority, fn, defaultPriority, visualType) {
+function normalizeRegister(
+    targetList: StageHandlerInput[],
+    priority: number | StageHandlerInput | StageHandlerOverallReset,
+    fn: StageHandlerInput | StageHandlerOverallReset,
+    defaultPriority: number,
+    visualType?: VisualType
+): void {
     if (isFunction(priority) || isObject(priority)) {
-        fn = priority;
+        fn = priority as (StageHandlerInput | StageHandlerOverallReset);
         priority = defaultPriority;
     }
 
@@ -2284,7 +2162,7 @@ function normalizeRegister(targetList, priority, fn, defaultPriority, visualType
         }
         // Check duplicate
         each(targetList, function (wrap) {
-            assert(wrap.__raw !== fn);
+            assert((wrap as StageHandler).__raw !== fn);
         });
     }
 
@@ -2293,69 +2171,51 @@ function normalizeRegister(targetList, priority, fn, defaultPriority, visualType
     stageHandler.__prio = priority;
     stageHandler.__raw = fn;
     targetList.push(stageHandler);
-
-    return stageHandler;
 }
 
-/**
- * @param {string} name
- */
-export function registerLoading(name, loadingFx) {
+export function registerLoading(
+    name: string,
+    loadingFx: LoadingEffectCreator
+): void {
     loadingEffects[name] = loadingFx;
 }
 
-/**
- * @param {Object} opts
- * @param {string} [superClass]
- */
-export function extendComponentModel(opts/*, superClass*/) {
+export function extendComponentModel(proto: object): ComponentModel {
     // var Clazz = ComponentModel;
     // if (superClass) {
     //     var classType = parseClassType(superClass);
     //     Clazz = ComponentModel.getClass(classType.main, classType.sub, true);
     // }
-    return ComponentModel.extend(opts);
+    return (ComponentModel as ComponentModelConstructor).extend(proto) as any;
 }
 
-/**
- * @param {Object} opts
- * @param {string} [superClass]
- */
-export function extendComponentView(opts/*, superClass*/) {
+export function extendComponentView(proto: object): ChartView {
     // var Clazz = ComponentView;
     // if (superClass) {
     //     var classType = parseClassType(superClass);
     //     Clazz = ComponentView.getClass(classType.main, classType.sub, true);
     // }
-    return ComponentView.extend(opts);
+    return (ComponentView as ComponentViewConstructor).extend(proto) as any;
 }
 
-/**
- * @param {Object} opts
- * @param {string} [superClass]
- */
-export function extendSeriesModel(opts/*, superClass*/) {
+export function extendSeriesModel(proto: object): SeriesModel {
     // var Clazz = SeriesModel;
     // if (superClass) {
     //     superClass = 'series.' + superClass.replace('series.', '');
     //     var classType = parseClassType(superClass);
     //     Clazz = ComponentModel.getClass(classType.main, classType.sub, true);
     // }
-    return SeriesModel.extend(opts);
+    return (SeriesModel as SeriesModelConstructor).extend(proto) as any;
 }
 
-/**
- * @param {Object} opts
- * @param {string} [superClass]
- */
-export function extendChartView(opts/*, superClass*/) {
+export function extendChartView(proto: object): ChartView {
     // var Clazz = ChartView;
     // if (superClass) {
     //     superClass = superClass.replace('series.', '');
     //     var classType = parseClassType(superClass);
     //     Clazz = ChartView.getClass(classType.main, true);
     // }
-    return ChartView.extend(opts);
+    return (ChartView as ChartViewConstructor).extend(proto) as any;
 }
 
 /**
@@ -2365,7 +2225,6 @@ export function extendChartView(opts/*, superClass*/) {
  *
  * Be careful of using it in the browser.
  *
- * @param {Function} creator
  * @example
  *     var Canvas = require('canvas');
  *     var echarts = require('echarts');
@@ -2374,47 +2233,26 @@ export function extendChartView(opts/*, superClass*/) {
  *         return new Canvas(32, 32);
  *     });
  */
-export function setCanvasCreator(creator) {
+export function setCanvasCreator(creator: () => HTMLCanvasElement): void {
     zrUtil.$override('createCanvas', creator);
 }
 
 /**
- * @param {string} mapName
- * @param {Array.<Object>|Object|string} geoJson
- * @param {Object} [specialAreas]
- *
- * @example GeoJSON
- *     $.get('USA.json', function (geoJson) {
- *         echarts.registerMap('USA', geoJson);
- *         // Or
- *         echarts.registerMap('USA', {
- *             geoJson: geoJson,
- *             specialAreas: {}
- *         })
- *     });
- *
- *     $.get('airport.svg', function (svg) {
- *         echarts.registerMap('airport', {
- *             svg: svg
- *         }
- *     });
- *
- *     echarts.registerMap('eu', [
- *         {svg: eu-topographic.svg},
- *         {geoJSON: eu.json}
- *     ])
+ * The parameters and usage: see `mapDataStorage.registerMap`.
+ * Compatible with previous `echarts.registerMap`.
  */
-export function registerMap(mapName, geoJson, specialAreas) {
+export function registerMap(
+    mapName: string,
+    geoJson: GeoMapDefinition | GeoMapDefinition[] | GeoMapGeoJSONSource,
+    specialAreas?: GeoSpecialAreas
+): void {
     mapDataStorage.registerMap(mapName, geoJson, specialAreas);
 }
 
-/**
- * @param {string} mapName
- * @return {Object}
- */
-export function getMap(mapName) {
+export function getMap(mapName: string) {
     // For backward compatibility, only return the first one.
     var records = mapDataStorage.retrieveMap(mapName);
+    // FIXME support SVG
     return records && records[0] && {
         geoJson: records[0].geoJSON,
         specialAreas: records[0].specialAreas
@@ -2447,3 +2285,5 @@ registerTheme('dark', darkTheme);
 // For backward compatibility, where the namespace `dataTool` will
 // be mounted on `echarts` is the extension `dataTool` is imported.
 export var dataTool = {};
+
+export interface EChartsType extends ECharts {}
diff --git a/src/export.ts b/src/export.ts
index 6ddd62e..8313fc8 100644
--- a/src/export.ts
+++ b/src/export.ts
@@ -58,7 +58,7 @@ zrUtil.each(
         'extend', 'defaults', 'clone', 'merge'
     ],
     function (name) {
-        ecUtil[name] = zrUtil[name];
+        (ecUtil as any)[name] = (zrUtil as any)[name];
     }
 );
 export {ecUtil as util};
@@ -91,7 +91,7 @@ zrUtil.each(
         'BoundingRect'
     ],
     function (name) {
-        graphic[name] = graphicUtil[name];
+        (graphic as any)[name] = (graphicUtil as any)[name];
     }
 );
 export {graphic};
diff --git a/src/loading/default.ts b/src/loading/default.ts
index 13d90d5..3ace478 100644
--- a/src/loading/default.ts
+++ b/src/loading/default.ts
@@ -19,6 +19,8 @@
 
 import * as zrUtil from 'zrender/src/core/util';
 import * as graphic from '../util/graphic';
+import { LoadingEffect } from '../util/types';
... 6793 lines suppressed ...


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