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/07/31 10:15:49 UTC

[incubator-echarts] branch dataset-trans created (now 246667e)

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

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


      at 246667e  fix: prevent potential issue after the implementation of isString change.

This branch includes the following new commits:

     new 9d9ffce  ts: add types to some @ts-nocheck and remove some 'any'.
     new 53d0200  Merge branch 'next' into dataset-trans
     new e9a2b0f  feature: data transform
     new f8a59df  fix: fix dimension inherit rule.
     new b6124d5  Merge branch 'next' into dataset-trans
     new c6465c9  ts: fix type.
     new 4243980  ts: fix type.
     new 9881368  feature: add boxplot transform.
     new 2ce65aa  test: add case for ecStat
     new 246667e  fix: prevent potential issue after the implementation of isString change.

The 10 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



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


[incubator-echarts] 02/10: Merge branch 'next' into dataset-trans

Posted by su...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 53d0200400bf0ca3907d8192d5f5afd6328d75d7
Merge: 9d9ffce d9d27e2
Author: 100pah <su...@gmail.com>
AuthorDate: Thu Jul 30 13:28:03 2020 +0800

    Merge branch 'next' into dataset-trans
    
    # Conflicts:
    #	src/chart/graph/GraphSeries.ts
    #	src/chart/sankey/SankeySeries.ts
    #	src/data/helper/dimensionHelper.ts
    #	src/model/mixin/dataFormat.ts
    #	src/util/graphic.ts
    #	src/util/states.ts

 .eslintignore                                      |   14 +-
 .gitignore                                         |    4 +-
 README.md                                          |    2 +-
 build/build.js                                     |  229 +-
 build/config.js                                    |   73 +-
 build/dev-fast.js                                  |   25 +-
 build/ec-lang-rollup-plugin.js                     |    2 +-
 build/pre-publish.js                               |  122 +-
 build/progress.js                                  |    6 +-
 build/release.js                                   |   81 +
 build/{remove-dev.js => transform-dev.js}          |   39 +-
 echarts.common.ts                                  |    2 +-
 {extension => extension-src}/bmap/README.md        |    0
 extension-src/dataTool/prepareBoxplotData.ts       |   23 +-
 extension/bmap/BMapCoordSys.js                     |  245 --
 extension/bmap/BMapModel.js                        |   68 -
 extension/bmap/BMapView.js                         |  143 -
 extension/bmap/bmap.js                             |   65 -
 extension/dataTool/gexf.js                         |  230 --
 extension/dataTool/index.js                        |   61 -
 extension/dataTool/prepareBoxplotData.js           |  107 -
 extension/webpack.config.js                        |   36 -
 package-lock.json                                  |  165 +-
 package.json                                       |   14 +-
 src/.eslintrc.yaml                                 |    1 +
 src/ExtensionAPI.ts                                |    8 +-
 src/chart/bar/BarView.ts                           |    5 +-
 src/chart/bar/PictorialBarView.ts                  |   17 +-
 src/chart/custom.ts                                |    3 +-
 src/chart/gauge/GaugeView.ts                       |    4 +-
 src/chart/graph/GraphSeries.ts                     |   10 +-
 src/chart/graph/GraphView.ts                       |    5 +-
 src/chart/heatmap/HeatmapView.ts                   |    3 +-
 src/chart/helper/LargeLineDraw.ts                  |    3 +-
 src/chart/helper/LargeSymbolDraw.ts                |    3 +-
 src/chart/helper/LineDraw.ts                       |    3 +-
 src/chart/helper/Polyline.ts                       |    1 +
 src/chart/helper/Symbol.ts                         |   34 +-
 src/chart/helper/SymbolDraw.ts                     |    3 +-
 src/chart/line/LineSeries.ts                       |   12 +-
 src/chart/line/LineView.ts                         |   19 +-
 src/chart/lines/LinesSeries.ts                     |   11 +-
 src/chart/lines/LinesView.ts                       |    1 -
 src/chart/map/MapSeries.ts                         |   21 +-
 src/chart/pie/labelLayout.ts                       |    5 +
 src/chart/radar/RadarSeries.ts                     |   58 +-
 src/chart/radar/RadarView.ts                       |   18 +-
 src/chart/sankey/SankeySeries.ts                   |   32 +-
 src/chart/sankey/SankeyView.ts                     |    7 +-
 src/chart/sankey/sankeyVisual.ts                   |   11 +-
 src/chart/sunburst/SunburstPiece.ts                |    5 +-
 src/chart/sunburst/sunburstAction.ts               |    1 -
 src/chart/themeRiver/ThemeRiverSeries.ts           |   20 +-
 src/chart/tree/TreeSeries.ts                       |   23 +-
 src/chart/tree/TreeView.ts                         |    5 +-
 src/chart/treemap/Breadcrumb.ts                    |    6 +-
 src/chart/treemap/TreemapSeries.ts                 |   28 +-
 src/chart/treemap/TreemapView.ts                   |   31 +-
 src/component/axis/AngleAxisView.ts                |    3 +-
 src/component/axis/AxisBuilder.ts                  |   30 +-
 src/component/axis/AxisView.ts                     |    1 -
 src/component/axis/CartesianAxisView.ts            |   17 +-
 src/component/axisPointer/AxisPointerModel.ts      |   13 +-
 src/component/axisPointer/axisTrigger.ts           |   18 +-
 src/component/axisPointer/findPointFromSeries.ts   |   29 +-
 src/component/axisPointer/viewHelper.ts            |   11 +-
 src/component/brush/BrushModel.ts                  |    1 -
 src/component/dataZoom/DataZoomModel.ts            |    1 -
 src/component/dataZoom/SliderZoomModel.ts          |   96 +-
 src/component/dataZoom/SliderZoomView.ts           |  418 ++-
 src/component/dataZoom/helper.ts                   |    1 -
 src/component/geo/GeoView.ts                       |    2 +-
 src/component/graphic.ts                           |    1 -
 src/component/helper/BrushController.ts            |    3 +-
 src/component/helper/BrushTargetManager.ts         |    1 -
 src/component/helper/MapDraw.ts                    |    3 +-
 src/component/legend/LegendView.ts                 |   22 +-
 src/component/marker/MarkAreaView.ts               |    3 +-
 src/component/marker/MarkLineView.ts               |    3 +-
 src/component/marker/MarkPointView.ts              |    2 +-
 src/component/marker/MarkerModel.ts                |   31 +-
 src/component/radar/RadarView.ts                   |    1 -
 src/component/timeline/SliderTimelineModel.ts      |   61 +-
 src/component/timeline/SliderTimelineView.ts       |  192 +-
 src/component/timeline/TimelineModel.ts            |   21 +
 src/component/title.ts                             |   10 +-
 src/component/toolbox/feature/DataZoom.ts          |   16 +-
 src/component/tooltip/TooltipHTMLContent.ts        |  103 +-
 src/component/tooltip/TooltipModel.ts              |   50 +-
 src/component/tooltip/TooltipRichContent.ts        |   21 +-
 src/component/tooltip/TooltipView.ts               |  199 +-
 src/component/visualMap/ContinuousModel.ts         |   48 +-
 src/component/visualMap/ContinuousView.ts          |  250 +-
 src/component/visualMap/PiecewiseModel.ts          |    1 -
 src/config.ts                                      |   41 -
 src/coord/Axis.ts                                  |   10 +-
 src/coord/axisCommonTypes.ts                       |   24 +-
 src/coord/axisDefault.ts                           |   32 +-
 src/coord/axisHelper.ts                            |   51 +-
 src/coord/axisTickLabelBuilder.ts                  |  147 +-
 src/coord/calendar/Calendar.ts                     |    1 -
 src/coord/cartesian/Grid.ts                        |    1 -
 src/coord/cartesian/GridModel.ts                   |    2 +-
 src/coord/cartesian/cartesianAxisHelper.ts         |    1 -
 src/coord/geo/geoCreator.ts                        |    1 -
 src/coord/geo/geoSourceManager.ts                  |    1 -
 src/coord/geo/mapDataStorage.ts                    |    1 -
 src/coord/polar/polarCreator.ts                    |    1 -
 src/coord/radar/IndicatorAxis.ts                   |    2 +
 src/coord/scaleRawExtentInfo.ts                    |    1 -
 src/data/Graph.ts                                  |    1 -
 src/data/List.ts                                   |    3 +-
 src/data/helper/dataProvider.ts                    |    1 -
 src/data/helper/dimensionHelper.ts                 |    1 -
 src/data/helper/sourceHelper.ts                    |    1 -
 src/echarts.ts                                     |  114 +-
 src/export.ts                                      |    2 +
 extension/echarts.js => src/global.d.ts            |    2 +-
 src/label/LabelManager.ts                          |    4 +-
 src/label/labelStyle.ts                            |   45 +-
 src/lang.ts                                        |   16 +
 src/langEN.ts                                      |   20 +-
 src/layout/barGrid.ts                              |   14 +-
 src/legacy/dataSelectAction.ts                     |    1 -
 .../legacy/getTextRect.ts                          |   52 +-
 src/model/Global.ts                                |    1 -
 src/model/Series.ts                                |   71 +-
 src/model/internalComponentCreator.ts              |    1 -
 src/model/mixin/dataFormat.ts                      |    6 +-
 src/model/referHelper.ts                           |    1 -
 src/preprocessor/backwardCompat.ts                 |    1 -
 src/preprocessor/helper/compatStyle.ts             |    1 -
 src/scale/Interval.ts                              |   39 +-
 src/scale/Log.ts                                   |   11 +-
 src/scale/Ordinal.ts                               |   12 +-
 src/scale/Scale.ts                                 |    6 +-
 src/scale/Time.ts                                  |  467 ++-
 src/stream/task.ts                                 |    1 -
 src/util/animation.ts                              |    1 +
 src/util/clazz.ts                                  |    1 -
 .../util/ecData.ts                                 |   31 +-
 src/util/format.ts                                 |   48 +-
 src/util/graphic.ts                                |   31 +-
 src/util/log.ts                                    |    1 -
 src/util/model.ts                                  |    1 -
 src/util/number.ts                                 |    2 +-
 src/util/states.ts                                 |   17 +-
 src/util/styleCompat.ts                            |    1 -
 src/util/symbol.ts                                 |    2 +-
 src/util/time.ts                                   |  276 ++
 src/util/types.ts                                  |   36 +-
 src/visual/helper.ts                               |    1 -
 test/boxplot.html                                  |    2 +-
 test/dataZoom-rainfall-inside.html                 |    7 +-
 test/line-boldWhenHover.html                       |   92 +
 test/new-tooltip.html                              | 3280 ++++++++++++++++++++
 test/timeScale-formatter.html                      |  364 +++
 test/timeScale.html                                |    9 +-
 test/timeZone.html                                 |   17 +-
 test/timeline-finance.html                         |    6 +-
 test/visualMap-scatter-symbolSize.html             |    1 +
 tsconfig.json                                      |    2 +-
 162 files changed, 6921 insertions(+), 2376 deletions(-)

diff --cc src/chart/graph/GraphSeries.ts
index a72622f,489a389..77f4887
--- a/src/chart/graph/GraphSeries.ts
+++ b/src/chart/graph/GraphSeries.ts
@@@ -42,8 -42,7 +42,9 @@@ import 
      Dictionary,
      LineLabelOption,
      StatesOptionMixin,
 +    GraphEdgeItemObject,
-     OptionDataValueNumeric
++    OptionDataValueNumeric,
+     TooltipRenderMode
  } from '../../util/types';
  import SeriesModel from '../../model/Series';
  import Graph from '../../data/Graph';
diff --cc src/chart/sankey/SankeySeries.ts
index 19c64f6,4b4a7f1..5fe0f3e
--- a/src/chart/sankey/SankeySeries.ts
+++ b/src/chart/sankey/SankeySeries.ts
@@@ -33,8 -32,7 +32,9 @@@ import 
      ColorString,
      StatesOptionMixin,
      OptionDataItemObject,
 +    GraphEdgeItemObject,
-     OptionDataValueNumeric
++    OptionDataValueNumeric,
+     TooltipRenderMode
  } from '../../util/types';
  import GlobalModel from '../../model/Global';
  import List from '../../data/List';
diff --cc src/data/helper/dimensionHelper.ts
index 955a3d0,433fd79..98805ac
--- a/src/data/helper/dimensionHelper.ts
+++ b/src/data/helper/dimensionHelper.ts
@@@ -17,12 -17,12 +17,11 @@@
  * under the License.
  */
  
 -// @ts-nocheck
  
  import {each, createHashMap, assert} from 'zrender/src/core/util';
- import { __DEV__ } from '../../config';
 -import List from '../List';
 +import List, { ListDimensionType } from '../List';
  import {
 -    DimensionName, VISUAL_DIMENSIONS, DimensionType, DimensionUserOuput
 +    DimensionName, VISUAL_DIMENSIONS, DimensionType, DimensionUserOuput, DimensionUserOuputEncode, DimensionIndex
  } from '../../util/types';
  
  export type DimensionSummaryEncode = {
diff --cc src/data/helper/sourceHelper.ts
index 45d1c10,19bc6ee..8f9d7cb
--- a/src/data/helper/sourceHelper.ts
+++ b/src/data/helper/sourceHelper.ts
@@@ -17,8 -17,8 +17,7 @@@
  * under the License.
  */
  
 -// @ts-nocheck
  
- import {__DEV__} from '../../config';
  import {makeInner, getDataItemValue} from '../../util/model';
  import {
      createHashMap,
diff --cc src/model/mixin/dataFormat.ts
index a10f4a7,c1e714a..f1aa9b7
--- a/src/model/mixin/dataFormat.ts
+++ b/src/model/mixin/dataFormat.ts
@@@ -28,7 -28,7 +28,8 @@@ import 
      ColorString,
      ZRColor,
      OptionDataValue,
-     SeriesDataType
++    SeriesDataType,
+     TooltipOrderMode
  } from '../../util/types';
  import GlobalModel from '../Global';
  
diff --cc src/util/states.ts
index 15f53f8,9cf8e03..fbeb25f
--- a/src/util/states.ts
+++ b/src/util/states.ts
@@@ -5,9 -5,19 +5,20 @@@ import { PatternObject } from 'zrender/
  import { GradientObject } from 'zrender/src/graphic/Gradient';
  import Element, { ElementEvent } from 'zrender/src/Element';
  import Model from '../model/Model';
- import { DisplayState, ECElement, ColorString, BlurScope, InnerFocus, Payload, ZRColor, SeriesDataType } from './types';
+ import {
++    SeriesDataType,
+     DisplayState,
+     ECElement,
+     ColorString,
+     BlurScope,
+     InnerFocus,
+     Payload,
+     ZRColor,
+     HighlightPayload,
+     DownplayPayload
+ } from './types';
  import { extend, indexOf, isArrayLike, isObject, keys, isArray, each } from 'zrender/src/core/util';
- import { getECData } from './graphic';
+ import { getECData } from './ecData';
  import * as colorTool from 'zrender/src/tool/color';
  import { EChartsType } from '../echarts';
  import List from '../data/List';


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


[incubator-echarts] 05/10: Merge branch 'next' into dataset-trans

Posted by su...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit b6124d53b8f1cd91c4cb7a841837aad0d76498b6
Merge: f8a59df d17f801
Author: 100pah <su...@gmail.com>
AuthorDate: Fri Jul 31 16:37:15 2020 +0800

    Merge branch 'next' into dataset-trans
    
    # Conflicts:
    #	src/echarts.ts

 .eslintignore                                    |   3 +-
 build/build-i18n.js                              |  91 ++++++
 build/build.js                                   |  33 +-
 build/config.js                                  |   7 +-
 build/ec-lang-rollup-plugin.js                   |  80 -----
 build/release.js                                 |  43 ++-
 src/langEN.ts => i18n/langEN-obj.js              |  32 +-
 src/langEN.ts => i18n/langEN.js                  |  28 +-
 i18n/langES-obj.js                               | 111 +++++++
 i18n/langES.js                                   | 107 +++++++
 i18n/langFI-obj.js                               | 111 +++++++
 i18n/langFI.js                                   | 107 +++++++
 i18n/langFR-obj.js                               | 172 +++++++++++
 i18n/langFR.js                                   | 168 +++++++++++
 i18n/langTH-obj.js                               | 111 +++++++
 i18n/langTH.js                                   | 107 +++++++
 src/lang.ts => i18n/langZH-obj.js                |  30 +-
 src/lang.ts => i18n/langZH.js                    |  26 +-
 package.json                                     |   3 +-
 src/action/changeAxisOrder.ts                    |   3 +
 src/chart/bar/BarView.ts                         | 365 +++++++++++++----------
 src/component/axis/AxisBuilder.ts                |   5 +-
 src/component/legend/LegendModel.ts              |  32 +-
 src/component/timeline/SliderTimelineView.ts     |   1 +
 src/component/toolbox/ToolboxModel.ts            |  10 +-
 src/component/toolbox/feature/Brush.ts           |  41 +--
 src/component/toolbox/feature/DataView.ts        |  43 +--
 src/component/toolbox/feature/DataZoom.ts        |  28 +-
 src/component/toolbox/feature/MagicType.ts       |  44 +--
 src/component/toolbox/feature/Restore.ts         |  21 +-
 src/component/toolbox/feature/SaveAsImage.ts     |  34 ++-
 src/component/toolbox/featureManager.ts          |   3 +-
 src/coord/Axis.ts                                |  10 +-
 src/coord/axisHelper.ts                          |   1 +
 src/coord/cartesian/defaultAxisExtentFromData.ts |   8 +-
 src/echarts.ts                                   |  13 +-
 src/{ => i18n}/langEN.ts                         |   2 +-
 src/i18n/langES.ts                               |  80 +++++
 src/i18n/langFI.ts                               |  80 +++++
 src/i18n/langFR.ts                               | 142 +++++++++
 src/i18n/langTH.ts                               |  80 +++++
 src/{lang.ts => i18n/langZH.ts}                  |  38 ++-
 src/langES.ts                                    |  68 -----
 src/langFI.ts                                    |  68 -----
 src/langTH.ts                                    |  68 -----
 src/locale.ts                                    |  79 +++++
 src/model/Global.ts                              |  14 +
 src/scale/Ordinal.ts                             |  33 +-
 src/scale/Scale.ts                               |   7 +
 src/scale/Time.ts                                | 101 +++++--
 src/util/format.ts                               |  21 +-
 src/util/graphic.ts                              |  59 +++-
 src/util/log.ts                                  |  22 +-
 src/util/time.ts                                 |  70 +++--
 src/visual/aria.ts                               |   9 +-
 test/bar-race.html                               |  75 +++--
 test/lang.html                                   | 188 ++++++++++++
 test/timeZone.html                               |  11 +-
 58 files changed, 2568 insertions(+), 779 deletions(-)

diff --cc src/echarts.ts
index fa991fd,b69a478..a16d89f
--- a/src/echarts.ts
+++ b/src/echarts.ts
@@@ -100,7 -100,7 +100,8 @@@ import { handleLegacySelectEvents } fro
  
  // At least canvas renderer.
  import 'zrender/src/canvas/canvas';
 +import { registerExternalTransform } from './data/helper/transform';
+ import { createLocaleObject, SYSTEM_LANG, LocaleOption } from './locale';
  
  declare let global: any;
  type ModelFinder = modelUtil.ModelFinder;
diff --cc src/util/log.ts
index 3aaab2d,8bb7d69..e50fb6d
--- a/src/util/log.ts
+++ b/src/util/log.ts
@@@ -18,10 -18,27 +18,28 @@@
  */
  
  import { Dictionary } from './types';
 +import { map, isString, isFunction, eqNaN, isRegExp } from 'zrender/src/core/util';
  
+ const ECHARTS_PREFIX = '[ECharts] ';
  const storedLogs: Dictionary<boolean> = {};
  
+ const hasConsole = typeof console !== 'undefined'
+     // eslint-disable-next-line
+     && console.warn && console.log;
+ 
+ export function log(str: string) {
+     if (hasConsole) {
+         // eslint-disable-next-line
+         console.log(ECHARTS_PREFIX + str);
+     }
+ }
+ 
+ export function warn(str: string) {
+     if (hasConsole) {
+         console.warn(ECHARTS_PREFIX + str);
+     }
+ }
+ 
  export function deprecateLog(str: string) {
      if (__DEV__) {
          if (storedLogs[str]) {  // Not display duplicate message.


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


[incubator-echarts] 06/10: ts: fix type.

Posted by su...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit c6465c965fb7f36a11c3c598aee586cb2d45c30a
Author: 100pah <su...@gmail.com>
AuthorDate: Fri Jul 31 16:41:26 2020 +0800

    ts: fix type.
---
 src/data/helper/sourceManager.ts | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/data/helper/sourceManager.ts b/src/data/helper/sourceManager.ts
index 5a3f9ea..6a45f7f 100644
--- a/src/data/helper/sourceManager.ts
+++ b/src/data/helper/sourceManager.ts
@@ -19,8 +19,8 @@
 
 import { DatasetModel } from '../../component/dataset';
 import SeriesModel from '../../model/Series';
-import { setAsPrimitive, map, isTypedArray, defaults, assert, each } from 'zrender/src/core/util';
-import Source from '../Source';
+import { setAsPrimitive, map, isTypedArray, assert, each } from 'zrender/src/core/util';
+import Source, { SourceMetaRawOption } from '../Source';
 import {
     SeriesEncodableModel, OptionSourceData,
     SOURCE_FORMAT_TYPED_ARRAY, SOURCE_FORMAT_ORIGINAL,
@@ -28,7 +28,7 @@ import {
 } from '../../util/types';
 import {
     querySeriesUpstreamDatasetModel, queryDatasetUpstreamDatasetModels,
-    createSource, SourceMetaRawOption, cloneSourceShallow, inheritSourceMetaRawOption
+    createSource, cloneSourceShallow, inheritSourceMetaRawOption
 } from './sourceHelper';
 import { applyDataTransform } from './transform';
 


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


[incubator-echarts] 08/10: feature: add boxplot transform.

Posted by su...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 9881368b1668ca7f91d85d9f0d3504c974358533
Author: 100pah <su...@gmail.com>
AuthorDate: Fri Jul 31 17:32:38 2020 +0800

    feature: add boxplot transform.
---
 extension-src/dataTool/boxplotTransform.ts |  70 +++++++++++++++++
 extension-src/dataTool/index.ts            |   3 +
 test/data-transform-external.html          | 116 +++++++++++++++++++++++++++++
 3 files changed, 189 insertions(+)

diff --git a/extension-src/dataTool/boxplotTransform.ts b/extension-src/dataTool/boxplotTransform.ts
new file mode 100644
index 0000000..4a51370
--- /dev/null
+++ b/extension-src/dataTool/boxplotTransform.ts
@@ -0,0 +1,70 @@
+/*
+* 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.
+*/
+
+import { DataTransformOption, ExternalDataTransform } from '../../src/data/helper/transform';
+import prepareBoxplotData from './prepareBoxplotData';
+import { isArray, each } from 'zrender/src/core/util';
+// import { throwError, makePrintable } from '../../src/util/log';
+
+
+export interface BoxplotTransformOption extends DataTransformOption {
+    type: 'echarts-extension:boxplot';
+    config: {
+        boundIQR?: number | 'none',
+        layout?: 'horizontal' | 'vertical'
+    }
+}
+
+export const boxplotTransform: ExternalDataTransform<BoxplotTransformOption> = {
+
+    type: 'echarts-extension:boxplot',
+
+    // PEDING: enhance to filter by index rather than create new data
+    transform: function transform(params) {
+        const source = params.source;
+        const config = params.config || {};
+
+        const sourceData = source.data;
+        if (
+            !isArray(sourceData)
+            || (sourceData[0] && !isArray(sourceData[0]))
+        ) {
+            throw new Error(
+                'source data is not applicable for this boxplot transform. Expect number[][].'
+            );
+        }
+
+        const result = prepareBoxplotData(
+            source.data as number[][],
+            config
+        );
+
+        const boxData = result.boxData as (number | string)[][];
+        each(boxData, function (item, dataIdx) {
+            item.unshift(result.axisData[dataIdx]);
+        });
+
+        return [{
+            data: boxData
+        }, {
+            data: result.outliers
+        }];
+    }
+};
+
diff --git a/extension-src/dataTool/index.ts b/extension-src/dataTool/index.ts
index 6724942..c4cc447 100644
--- a/extension-src/dataTool/index.ts
+++ b/extension-src/dataTool/index.ts
@@ -21,12 +21,14 @@
 import * as echarts from 'echarts';
 import * as gexf from './gexf';
 import prepareBoxplotData from './prepareBoxplotData';
+import { boxplotTransform } from './boxplotTransform';
 
 export const version = '1.0.0';
 
 export {gexf};
 
 export {prepareBoxplotData};
+export {boxplotTransform};
 
 // For backward compatibility, where the namespace `dataTool` will
 // be mounted on `echarts` is the extension `dataTool` is imported.
@@ -36,4 +38,5 @@ if (echarts.dataTool) {
     echarts.dataTool.version = version;
     echarts.dataTool.gexf = gexf;
     echarts.dataTool.prepareBoxplotData = prepareBoxplotData;
+    echarts.dataTool.boxplotTransform = boxplotTransform;
 }
diff --git a/test/data-transform-external.html b/test/data-transform-external.html
new file mode 100644
index 0000000..f204874
--- /dev/null
+++ b/test/data-transform-external.html
@@ -0,0 +1,116 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+
+
+<html>
+    <head>
+        <meta charset="utf-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1" />
+        <script src="lib/esl.js"></script>
+        <script src="lib/config.js"></script>
+        <script src="lib/jquery.min.js"></script>
+        <script src="lib/facePrint.js"></script>
+        <script src="lib/testHelper.js"></script>
+        <!-- <script src="ut/lib/canteen.js"></script> -->
+        <link rel="stylesheet" href="lib/reset.css" />
+    </head>
+    <body>
+        <style>
+        </style>
+
+
+
+        <div id="main_boxplot"></div>
+
+
+
+
+        <script>
+        require(['echarts', 'data/Michelson-Morley.json', 'extension/dataTool'], function (echarts, rawData, dataTool) {
+            var boxplotTransform = dataTool.boxplotTransform;
+
+            echarts.registerTransform(boxplotTransform);
+
+            var option = {
+                dataset: [{
+                    source: rawData
+                }, {
+                    transform: {
+                        type: 'echarts-extension:boxplot'
+                    }
+                }, {
+                    fromDatasetIndex: 1,
+                    fromTransformResult: 1
+                }],
+                title: [
+                    {
+                        text: 'Michelson-Morley Experiment',
+                        left: 'center'
+                    },
+                    {
+                        text: 'upper: Q3 + 1.5 * IRQ \nlower: Q1 - 1.5 * IRQ',
+                        borderColor: '#999',
+                        borderWidth: 1,
+                        textStyle: {
+                            fontSize: 14
+                        },
+                        left: '10%',
+                        top: '90%'
+                    }
+                ],
+                legend: {
+                    bottom: 20
+                },
+                tooltip: {
+                },
+                grid: {
+                    bottom: 80
+                },
+                xAxis: {
+                    type: 'category',
+                },
+                yAxis: {
+                    type: 'value',
+                    name: 'km/s minus 299,000',
+                },
+                series: [{
+                    name: 'boxplot',
+                    type: 'boxplot',
+                    datasetIndex: 1
+                }, {
+                    name: 'outlier',
+                    type: 'scatter',
+                    datasetIndex: 2
+                }]
+            };
+
+            var chart = testHelper.create(echarts, 'main_boxplot', {
+                title: [
+                    'Boxplot',
+                ],
+                option: option
+            });
+        });
+        </script>
+
+
+    </body>
+</html>
+


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


[incubator-echarts] 07/10: ts: fix type.

Posted by su...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 4243980aa4881555339e9151d7e6d6e312798dde
Author: 100pah <su...@gmail.com>
AuthorDate: Fri Jul 31 16:56:05 2020 +0800

    ts: fix type.
---
 extension-src/dataTool/prepareBoxplotData.ts | 19 ++++++++++++++-----
 1 file changed, 14 insertions(+), 5 deletions(-)

diff --git a/extension-src/dataTool/prepareBoxplotData.ts b/extension-src/dataTool/prepareBoxplotData.ts
index b57575a..1bb733e 100644
--- a/extension-src/dataTool/prepareBoxplotData.ts
+++ b/extension-src/dataTool/prepareBoxplotData.ts
@@ -17,7 +17,6 @@
 * under the License.
 */
 
-// @ts-nocheck
 
 function asc<T extends number[]>(arr: T): T {
     arr.sort(function (a, b) {
@@ -59,11 +58,21 @@ function quantile(ascArr: number[], p: number): number {
  *      axisData: Array.<string>
  * }
  */
-export default function (rawData, opt) {
-    opt = opt || [];
+export default function (
+    rawData: number[][],
+    opt: {
+        boundIQR?: number | 'none',
+        layout?: 'horizontal' | 'vertical'
+    }
+): {
+    boxData: number[][]
+    outliers: number[][]
+    axisData: string[]
+} {
+    opt = opt || {};
     const boxData = [];
     const outliers = [];
-    const axisData = [];
+    const axisData: string[] = [];
     const boundIQR = opt.boundIQR;
     const useExtreme = boundIQR === 'none' || boundIQR === 0;
 
@@ -77,7 +86,7 @@ export default function (rawData, opt) {
         const min = ascList[0];
         const max = ascList[ascList.length - 1];
 
-        const bound = (boundIQR == null ? 1.5 : boundIQR) * (Q3 - Q1);
+        const bound = (boundIQR == null ? 1.5 : boundIQR as number) * (Q3 - Q1);
 
         const low = useExtreme
             ? min


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


[incubator-echarts] 01/10: ts: add types to some @ts-nocheck and remove some 'any'.

Posted by su...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 9d9ffce7efb4576cb65325f24fa650ae636c67b0
Author: 100pah <su...@gmail.com>
AuthorDate: Sun Jul 26 19:32:17 2020 +0800

    ts: add types to some @ts-nocheck and remove some 'any'.
---
 src/chart/bar/BarView.ts                    |   2 +-
 src/chart/custom.ts                         |   5 +-
 src/chart/funnel/FunnelSeries.ts            |   2 +-
 src/chart/graph/GraphSeries.ts              |  20 +--
 src/chart/helper/createGraphFromNodeEdge.ts |  21 ++-
 src/chart/helper/createListFromArray.ts     |   6 +-
 src/chart/helper/whiskerBoxCommon.ts        |   7 +-
 src/chart/map/MapSeries.ts                  |   2 +-
 src/chart/pie/PieSeries.ts                  |   2 +-
 src/chart/sankey/SankeySeries.ts            |  19 +--
 src/chart/sunburst/SunburstSeries.ts        |   2 +-
 src/component/toolbox/feature/DataView.ts   |   2 +-
 src/component/toolbox/feature/MagicType.ts  |  17 +-
 src/coord/calendar/Calendar.ts              |   2 +-
 src/data/DataDimensionInfo.ts               |   2 +-
 src/data/Graph.ts                           |   2 +-
 src/data/List.ts                            |  23 ++-
 src/data/Source.ts                          |   8 +-
 src/data/helper/completeDimensions.ts       | 100 +++++++----
 src/data/helper/createDimensions.ts         |  22 ++-
 src/data/helper/dimensionHelper.ts          |  23 ++-
 src/data/helper/linkList.ts                 | 119 ++++++++------
 src/data/helper/sourceHelper.ts             | 246 ++++++++++++++++++----------
 src/label/LabelManager.ts                   |   7 +-
 src/model/Series.ts                         |  20 +--
 src/model/mixin/dataFormat.ts               |   9 +-
 src/stream/task.ts                          |   5 +-
 src/util/graphic.ts                         |   5 +-
 src/util/model.ts                           |   2 +-
 src/util/states.ts                          |   8 +-
 src/util/types.ts                           |  72 ++++++--
 31 files changed, 484 insertions(+), 298 deletions(-)

diff --git a/src/chart/bar/BarView.ts b/src/chart/bar/BarView.ts
index eecb05d..f98de86 100644
--- a/src/chart/bar/BarView.ts
+++ b/src/chart/bar/BarView.ts
@@ -787,7 +787,7 @@ function updateStyle(
 
 // In case width or height are too small.
 function getLineWidth(
-    itemModel: Model<BarSeriesOption>,
+    itemModel: Model<BarDataItemOption>,
     rawLayout: RectLayout
 ) {
     const lineWidth = itemModel.get(BAR_BORDER_WIDTH_QUERY) || 0;
diff --git a/src/chart/custom.ts b/src/chart/custom.ts
index e1775f5..b31a4ff 100644
--- a/src/chart/custom.ts
+++ b/src/chart/custom.ts
@@ -50,7 +50,8 @@ import {
     DisplayState,
     ECElement,
     DisplayStateNonNormal,
-    BlurScope
+    BlurScope,
+    SeriesDataType
 } from '../util/types';
 import Element, { ElementProps, ElementTextConfig } from 'zrender/src/Element';
 import prepareCartesian2d from '../coord/cartesian/prepareCustom';
@@ -411,7 +412,7 @@ class CustomSeriesModel extends SeriesModel<CustomSeriesOption> {
         return createListFromArray(this.getSource(), this);
     }
 
-    getDataParams(dataIndex: number, dataType?: string, el?: Element): CallbackDataParams & {
+    getDataParams(dataIndex: number, dataType?: SeriesDataType, el?: Element): CallbackDataParams & {
         info: CustomExtraElementInfo
     } {
         const params = super.getDataParams(dataIndex, dataType) as ReturnType<CustomSeriesModel['getDataParams']>;
diff --git a/src/chart/funnel/FunnelSeries.ts b/src/chart/funnel/FunnelSeries.ts
index 15d35c5..bb358b2 100644
--- a/src/chart/funnel/FunnelSeries.ts
+++ b/src/chart/funnel/FunnelSeries.ts
@@ -99,7 +99,7 @@ class FunnelSeriesModel extends SeriesModel<FunnelSeriesOption> {
         this._defaultLabelLine(option);
     }
 
-    getInitialData(option: FunnelSeriesOption, ecModel: GlobalModel): List {
+    getInitialData(this: FunnelSeriesModel, option: FunnelSeriesOption, ecModel: GlobalModel): List {
         return createListSimply(this, {
             coordDimensions: ['value'],
             encodeDefaulter: zrUtil.curry(makeSeriesEncodeForNameBased, this)
diff --git a/src/chart/graph/GraphSeries.ts b/src/chart/graph/GraphSeries.ts
index 5a7f29c..a72622f 100644
--- a/src/chart/graph/GraphSeries.ts
+++ b/src/chart/graph/GraphSeries.ts
@@ -41,7 +41,9 @@ import {
     LabelFormatterCallback,
     Dictionary,
     LineLabelOption,
-    StatesOptionMixin
+    StatesOptionMixin,
+    GraphEdgeItemObject,
+    OptionDataValueNumeric
 } from '../../util/types';
 import SeriesModel from '../../model/Series';
 import Graph from '../../data/Graph';
@@ -107,16 +109,10 @@ export interface GraphEdgeStateOption {
     lineStyle?: GraphEdgeLineStyleOption
     label?: LineLabelOption
 }
-export interface GraphEdgeItemOption
-    extends GraphEdgeStateOption, StatesOptionMixin<GraphEdgeStateOption, ExtraEdgeStateOption> {
-    /**
-     * Name or index of source node.
-     */
-    source?: string | number
-    /**
-     * Name or index of target node.
-     */
-    target?: string | number
+export interface GraphEdgeItemOption extends
+        GraphEdgeStateOption,
+        StatesOptionMixin<GraphEdgeStateOption, ExtraEdgeStateOption>,
+        GraphEdgeItemObject<OptionDataValueNumeric> {
 
     value?: number
 
@@ -272,7 +268,7 @@ class GraphSeriesModel extends SeriesModel<GraphSeriesOption> {
         defaultEmphasis(option, 'edgeLabel', ['show']);
     }
 
-    getInitialData(option: GraphSeriesOption, ecModel: GlobalModel) {
+    getInitialData(option: GraphSeriesOption, ecModel: GlobalModel): List {
         const edges = option.edges || option.links || [];
         const nodes = option.data || option.nodes || [];
         const self = this;
diff --git a/src/chart/helper/createGraphFromNodeEdge.ts b/src/chart/helper/createGraphFromNodeEdge.ts
index dac9dee..16206de 100644
--- a/src/chart/helper/createGraphFromNodeEdge.ts
+++ b/src/chart/helper/createGraphFromNodeEdge.ts
@@ -17,7 +17,6 @@
 * under the License.
 */
 
-// @ts-nocheck
 
 import * as zrUtil from 'zrender/src/core/util';
 import List from '../../data/List';
@@ -26,8 +25,19 @@ import linkList from '../../data/helper/linkList';
 import createDimensions from '../../data/helper/createDimensions';
 import CoordinateSystem from '../../CoordinateSystem';
 import createListFromArray from './createListFromArray';
+import {
+    OptionSourceDataOriginal, GraphEdgeItemObject, OptionDataValue,
+    OptionDataItemObject
+} from '../../util/types';
+import SeriesModel from '../../model/Series';
 
-export default function (nodes, edges, seriesModel, directed, beforeLink) {
+export default function (
+    nodes: OptionSourceDataOriginal<OptionDataValue, OptionDataItemObject<OptionDataValue>>,
+    edges: OptionSourceDataOriginal<OptionDataValue, GraphEdgeItemObject<OptionDataValue>>,
+    seriesModel: SeriesModel,
+    directed: boolean,
+    beforeLink: (nodeData: List, edgeData: List) => void
+): Graph {
     // ??? TODO
     // support dataset?
     const graph = new Graph(directed);
@@ -48,7 +58,10 @@ export default function (nodes, edges, seriesModel, directed, beforeLink) {
         // addEdge may fail when source or target not exists
         if (graph.addEdge(source, target, linkCount)) {
             validEdges.push(link);
-            linkNameList.push(zrUtil.retrieve(link.id, source + ' > ' + target));
+            linkNameList.push(zrUtil.retrieve(
+                link.id != null ? link.id + '' : null,
+                source + ' > ' + target
+            ));
             linkCount++;
         }
     }
@@ -60,7 +73,7 @@ export default function (nodes, edges, seriesModel, directed, beforeLink) {
     }
     else {
         const coordSysCtor = CoordinateSystem.get(coordSys);
-        const coordDimensions = (coordSysCtor && coordSysCtor.type !== 'view')
+        const coordDimensions = coordSysCtor
             ? (coordSysCtor.dimensions || []) : [];
         // FIXME: Some geo do not need `value` dimenson, whereas `calendar` needs
         // `value` dimension, but graph need `value` dimension. It's better to
diff --git a/src/chart/helper/createListFromArray.ts b/src/chart/helper/createListFromArray.ts
index 253240f..e451cd3 100644
--- a/src/chart/helper/createListFromArray.ts
+++ b/src/chart/helper/createListFromArray.ts
@@ -27,10 +27,12 @@ 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, DimensionDefinitionLoose, DimensionDefinition } from '../../util/types';
+import {
+    SOURCE_FORMAT_ORIGINAL, DimensionDefinitionLoose, DimensionDefinition, OptionSourceData
+} from '../../util/types';
 import SeriesModel from '../../model/Series';
 
-function createListFromArray(source: Source | any[], seriesModel: SeriesModel, opt?: {
+function createListFromArray(source: Source | OptionSourceData, seriesModel: SeriesModel, opt?: {
     generateCoord?: string
     useEncodeDefaulter?: boolean
 }): List {
diff --git a/src/chart/helper/whiskerBoxCommon.ts b/src/chart/helper/whiskerBoxCommon.ts
index 31dcec9..d1f6976 100644
--- a/src/chart/helper/whiskerBoxCommon.ts
+++ b/src/chart/helper/whiskerBoxCommon.ts
@@ -28,6 +28,7 @@ import type CartesianAxisModel from '../../coord/cartesian/AxisModel';
 import type DataDimensionInfo from '../../data/DataDimensionInfo';
 import type List from '../../data/List';
 import type Axis2D from '../../coord/cartesian/Axis2D';
+import { CoordDimensionDefinition } from '../../data/helper/createDimensions';
 
 interface CommonOption extends SeriesOption, SeriesOnCartesianOptionMixin {
     layout?: LayoutOrient
@@ -50,7 +51,7 @@ class WhiskerBoxCommonMixin<Opts extends CommonOption> {
      */
     _baseAxisDim: string;
 
-    defaultValueDimensions: Partial<DataDimensionInfo>[];
+    defaultValueDimensions: CoordDimensionDefinition['dimsDef'];
 
     /**
      * @override
@@ -117,7 +118,7 @@ class WhiskerBoxCommonMixin<Opts extends CommonOption> {
         }
 
         const defaultValueDimensions = this.defaultValueDimensions;
-        const coordDimensions = [{
+        const coordDimensions: CoordDimensionDefinition[] = [{
             name: baseAxisDim,
             type: getDimensionTypeByAxis(baseAxisType),
             ordinalMeta: ordinalMeta,
@@ -138,7 +139,7 @@ class WhiskerBoxCommonMixin<Opts extends CommonOption> {
                 coordDimensions: coordDimensions,
                 dimensionsCount: defaultValueDimensions.length + 1,
                 encodeDefaulter: zrUtil.curry(
-                    makeSeriesEncodeForAxisCoordSys, coordDimensions, this
+                    makeSeriesEncodeForAxisCoordSys, coordDimensions, this as any
                 )
             }
         );
diff --git a/src/chart/map/MapSeries.ts b/src/chart/map/MapSeries.ts
index 7aae79d..581b2cd 100644
--- a/src/chart/map/MapSeries.ts
+++ b/src/chart/map/MapSeries.ts
@@ -110,7 +110,7 @@ class MapSeries extends SeriesModel<MapSeriesOption> {
     seriesGroup: MapSeries[] = [];
 
 
-    getInitialData(option: MapSeriesOption): List {
+    getInitialData(this: MapSeries, option: MapSeriesOption): List {
         const data = createListSimply(this, {
             coordDimensions: ['value'],
             encodeDefaulter: zrUtil.curry(makeSeriesEncodeForNameBased, this)
diff --git a/src/chart/pie/PieSeries.ts b/src/chart/pie/PieSeries.ts
index de0b77e..f74b045 100644
--- a/src/chart/pie/PieSeries.ts
+++ b/src/chart/pie/PieSeries.ts
@@ -135,7 +135,7 @@ class PieSeriesModel extends SeriesModel<PieSeriesOption> {
     /**
      * @overwrite
      */
-    getInitialData(): List {
+    getInitialData(this: PieSeriesModel): List {
         return createListSimply(this, {
             coordDimensions: ['value'],
             encodeDefaulter: zrUtil.curry(makeSeriesEncodeForNameBased, this)
diff --git a/src/chart/sankey/SankeySeries.ts b/src/chart/sankey/SankeySeries.ts
index 5cd34ea..19c64f6 100644
--- a/src/chart/sankey/SankeySeries.ts
+++ b/src/chart/sankey/SankeySeries.ts
@@ -32,7 +32,9 @@ import {
     LayoutOrient,
     ColorString,
     StatesOptionMixin,
-    OptionDataItemObject
+    OptionDataItemObject,
+    GraphEdgeItemObject,
+    OptionDataValueNumeric
 } from '../../util/types';
 import GlobalModel from '../../model/Global';
 import List from '../../data/List';
@@ -77,17 +79,10 @@ export interface SankeyNodeItemOption extends SankeyNodeStateOption,
     focusNodeAdjacency?: FocusNodeAdjacency
 }
 
-export interface SankeyEdgeItemOption
-    extends SankeyEdgeStateOption, StatesOptionMixin<SankeyEdgeStateOption, ExtraStateOption> {
-    /**
-     * Name or index of source node.
-     */
-    source?: string | number
-    /**
-     * Name or index of target node.
-     */
-    target?: string | number
-
+export interface SankeyEdgeItemOption extends
+        SankeyEdgeStateOption,
+        StatesOptionMixin<SankeyEdgeStateOption, ExtraStateOption>,
+        GraphEdgeItemObject<OptionDataValueNumeric> {
     focusNodeAdjacency?: FocusNodeAdjacency
 }
 
diff --git a/src/chart/sunburst/SunburstSeries.ts b/src/chart/sunburst/SunburstSeries.ts
index dfb922c..fff56bf 100644
--- a/src/chart/sunburst/SunburstSeries.ts
+++ b/src/chart/sunburst/SunburstSeries.ts
@@ -128,7 +128,7 @@ class SunburstSeriesModel extends SeriesModel<SunburstSeriesOption> {
 
     getInitialData(option: SunburstSeriesOption, ecModel: GlobalModel) {
         // Create a virtual root.
-        const root = { name: option.name, children: option.data };
+        const root = { name: option.name, children: option.data } as SunburstSeriesNodeItemOption;
 
         completeTreeValue(root);
 
diff --git a/src/component/toolbox/feature/DataView.ts b/src/component/toolbox/feature/DataView.ts
index 690f7d4..44ca225 100644
--- a/src/component/toolbox/feature/DataView.ts
+++ b/src/component/toolbox/feature/DataView.ts
@@ -485,7 +485,7 @@ echarts.registerAction({
             const originalData = seriesModel.get('data');
             newSeriesOptList.push({
                 name: seriesOpt.name,
-                data: tryMergeDataOption(seriesOpt.data, originalData)
+                data: tryMergeDataOption(seriesOpt.data as DataList, originalData as DataList)
             });
         }
     });
diff --git a/src/component/toolbox/feature/MagicType.ts b/src/component/toolbox/feature/MagicType.ts
index b5b3e0f..cc8a82f 100644
--- a/src/component/toolbox/feature/MagicType.ts
+++ b/src/component/toolbox/feature/MagicType.ts
@@ -100,7 +100,7 @@ class MagicType extends ToolboxFeature<ToolboxMagicTypeFeatureOption> {
         const newOption: ECUnitOption = {
             series: []
         };
-        const generateNewSeriesTypes = function (seriesModel: SeriesModel) {
+        const generateNewSeriesTypes = function (seriesModel: SeriesModel<MegicTypeSeriesOption>) {
             const seriesType = seriesModel.subType;
             const seriesId = seriesModel.id;
             const newSeriesOpt = seriesOptGenreator[type](
@@ -170,17 +170,18 @@ class MagicType extends ToolboxFeature<ToolboxMagicTypeFeatureOption> {
     }
 }
 
+type MegicTypeSeriesOption = SeriesOption & {
+    // TODO: TYPE More specified series option
+    stack?: boolean | string
+    data?: unknown[]
+    markPoint?: unknown
+    markLine?: unknown
+};
 
 type SeriesOptGenreator = (
     seriesType: string,
     seriesId: string,
-    seriesModel: SeriesModel<SeriesOption & {
-        // TODO: TYPE More specified series option
-        stack?: boolean | string
-        data?: any[]
-        markPoint?: any
-        markLine?: any
-    }>,
+    seriesModel: SeriesModel<MegicTypeSeriesOption>,
     model: ToolboxFeatureModel<ToolboxMagicTypeFeatureOption>
 ) => SeriesOption;
 
diff --git a/src/coord/calendar/Calendar.ts b/src/coord/calendar/Calendar.ts
index ae23233..f75a40b 100644
--- a/src/coord/calendar/Calendar.ts
+++ b/src/coord/calendar/Calendar.ts
@@ -98,7 +98,7 @@ class Calendar implements CoordinateSystem, CoordinateSystemMaster {
     static readonly dimensions = ['time', 'value'];
     static getDimensionsInfo() {
         return [{
-            name: 'time', type: 'time'
+            name: 'time', type: 'time' as const
         }, 'value'];
     }
 
diff --git a/src/data/DataDimensionInfo.ts b/src/data/DataDimensionInfo.ts
index bfe27dc..e2f3e02 100644
--- a/src/data/DataDimensionInfo.ts
+++ b/src/data/DataDimensionInfo.ts
@@ -126,7 +126,7 @@ class DataDimensionInfo {
     /**
      * @param opt All of the fields will be shallow copied.
      */
-    constructor(opt: object | DataDimensionInfo) {
+    constructor(opt?: object | DataDimensionInfo) {
         if (opt != null) {
             zrUtil.extend(this, opt);
         }
diff --git a/src/data/Graph.ts b/src/data/Graph.ts
index 164fe27..684927f 100644
--- a/src/data/Graph.ts
+++ b/src/data/Graph.ts
@@ -68,7 +68,7 @@ class Graph {
     /**
      * Add a new node
      */
-    addNode(id: string, dataIndex?: number): GraphNode {
+    addNode(id: string | number, dataIndex?: number): GraphNode {
         id = id == null ? ('' + dataIndex) : ('' + id);
 
         const nodesMap = this._nodesMap;
diff --git a/src/data/List.ts b/src/data/List.ts
index a1ad1db..daa14cb 100644
--- a/src/data/List.ts
+++ b/src/data/List.ts
@@ -35,7 +35,7 @@ import {ArrayLike, Dictionary, FunctionPropertyNames} from 'zrender/src/core/typ
 import Element from 'zrender/src/Element';
 import {
     DimensionIndex, DimensionName, DimensionLoose, OptionDataItem,
-    ParsedValue, ParsedValueNumeric, OrdinalNumber, DimensionUserOuput, ModelOption
+    ParsedValue, ParsedValueNumeric, OrdinalNumber, DimensionUserOuput, ModelOption, SeriesDataType
 } from '../util/types';
 import {parseDate} from '../util/number';
 import {isDataItemOption} from '../util/model';
@@ -180,16 +180,22 @@ class List<
 
     readonly hostModel: HostModel;
 
-    readonly dataType: string;
+    /**
+     * @readonly
+     */
+    dataType: SeriesDataType;
 
     /**
+     * @readonly
      * Host graph if List is used to store graph nodes / edges.
      */
-    readonly graph?: Graph;
+    graph?: Graph;
+
     /**
+     * @readonly
      * Host tree if List is used to store tree ndoes.
      */
-    readonly tree?: Tree;
+    tree?: Tree;
 
     // Indices stores the indices of data subset after filtered.
     // This data subset will be used in chart.
@@ -267,9 +273,9 @@ class List<
 
     // 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'];
+    TRANSFERABLE_METHODS = ['cloneShallow', 'downSample', 'map'] as const;
     // Methods that change indices of this list should be listed here.
-    CHANGABLE_METHODS = ['filterSelf', 'selectRange'];
+    CHANGABLE_METHODS = ['filterSelf', 'selectRange'] as const;
 
 
     /**
@@ -2190,4 +2196,9 @@ class List<
 
 }
 
+interface List {
+    getLinkedData(dataType?: SeriesDataType): List;
+    getLinkedDataAll(): { data: List, type?: SeriesDataType }[];
+}
+
 export default List;
diff --git a/src/data/Source.ts b/src/data/Source.ts
index 0bc561c..10d62af 100644
--- a/src/data/Source.ts
+++ b/src/data/Source.ts
@@ -25,7 +25,8 @@ import {
     SERIES_LAYOUT_BY_COLUMN,
     SOURCE_FORMAT_UNKNOWN,
     SOURCE_FORMAT_KEYED_COLUMNS,
-    SOURCE_FORMAT_TYPED_ARRAY
+    SOURCE_FORMAT_TYPED_ARRAY,
+    DimensionName
 } from '../util/types';
 
 /**
@@ -97,7 +98,7 @@ class Source {
      * can be null/undefined.
      * Might be specified outside.
      */
-    encodeDefine: HashMap<OptionEncodeValue>;
+    encodeDefine: HashMap<OptionEncodeValue, DimensionName>;
 
     /**
      * Not null/undefined, uint.
@@ -128,7 +129,8 @@ class Source {
         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.encodeDefine = fields.encodeDefine
+            && createHashMap<OptionEncodeValue, DimensionName>(fields.encodeDefine);
         this.startIndex = fields.startIndex || 0;
         this.dimensionsDetectCount = fields.dimensionsDetectCount;
     }
diff --git a/src/data/helper/completeDimensions.ts b/src/data/helper/completeDimensions.ts
index 904c7a7..25e6568 100644
--- a/src/data/helper/completeDimensions.ts
+++ b/src/data/helper/completeDimensions.ts
@@ -17,18 +17,22 @@
 * under the License.
 */
 
-// @ts-nocheck
 /**
  * @deprecated
  * Use `echarts/data/helper/createDimensions` instead.
  */
 
-import {createHashMap, each, isString, defaults, extend, isObject, clone} from 'zrender/src/core/util';
+import {createHashMap, each, isString, defaults, extend, isObject, clone, HashMap} from 'zrender/src/core/util';
 import {normalizeToArray} from '../../util/model';
 import {guessOrdinal, BE_ORDINAL} from './sourceHelper';
 import Source from '../Source';
-import {VISUAL_DIMENSIONS} from '../../util/types';
+import {
+    VISUAL_DIMENSIONS, DimensionDefinitionLoose, OptionSourceData,
+    EncodeDefaulter, OptionEncodeValue, OptionEncode, DimensionName, DimensionIndex, DataVisualDimensions
+} from '../../util/types';
 import DataDimensionInfo from '../DataDimensionInfo';
+import List from '../List';
+import { CoordDimensionDefinition, CoordDimensionDefinitionLoose } from './createDimensions';
 
 /**
  * @see {module:echarts/test/ut/spec/data/completeDimensions}
@@ -69,25 +73,37 @@ import DataDimensionInfo from '../DataDimensionInfo';
  * @param {number} [opt.dimCount] If not specified, guess by the first data item.
  * @return {Array.<module:data/DataDimensionInfo>}
  */
-function completeDimensions(sysDims, source, opt) {
+function completeDimensions(
+    sysDims: CoordDimensionDefinitionLoose[],
+    source: Source | List | OptionSourceData,
+    opt: {
+        dimsDef?: DimensionDefinitionLoose[];
+        encodeDef?: HashMap<OptionEncodeValue, DimensionName> | OptionEncode;
+        dimCount?: number;
+        encodeDefaulter?: EncodeDefaulter;
+        generateCoord?: string;
+        generateCoordCount?: number;
+    }
+): DataDimensionInfo[] {
     if (!(source instanceof Source)) {
-        source = Source.seriesDataToSource(source);
+        source = Source.seriesDataToSource(source as OptionSourceData);
     }
 
     opt = opt || {};
     sysDims = (sysDims || []).slice();
     const dimsDef = (opt.dimsDef || []).slice();
-    const dataDimNameMap = createHashMap();
-    const coordDimNameMap = createHashMap();
+    const dataDimNameMap = createHashMap<DimensionIndex, DimensionName>();
+    const coordDimNameMap = createHashMap<true, DimensionName>();
     // let valueCandidate;
-    const result = [];
+    const result: DataDimensionInfo[] = [];
 
     const dimCount = getDimCount(source, sysDims, dimsDef, opt.dimCount);
 
     // Apply user defined dims (`name` and `type`) and init result.
     for (let i = 0; i < dimCount; i++) {
+        const dimDefItemRaw = dimsDef[i];
         const dimDefItem = dimsDef[i] = extend(
-            {}, isObject(dimsDef[i]) ? dimsDef[i] : {name: dimsDef[i]}
+            {}, isObject(dimDefItemRaw) ? dimDefItemRaw : { name: dimDefItemRaw }
         );
         const userDimName = dimDefItem.name;
         const resultItem = result[i] = new DataDimensionInfo();
@@ -107,24 +123,26 @@ function completeDimensions(sysDims, source, opt) {
     if (!encodeDef && opt.encodeDefaulter) {
         encodeDef = opt.encodeDefaulter(source, dimCount);
     }
-    encodeDef = createHashMap(encodeDef);
+    const encodeDefMap = createHashMap<DimensionIndex[] | false, DimensionName>(encodeDef as any);
 
-    // Set `coordDim` and `coordDimIndex` by `encodeDef` and normalize `encodeDef`.
-    encodeDef.each(function (dataDims, coordDim) {
-        dataDims = normalizeToArray(dataDims).slice();
+    // Set `coordDim` and `coordDimIndex` by `encodeDefMap` and normalize `encodeDefMap`.
+    encodeDefMap.each(function (dataDimsRaw, coordDim) {
+        const dataDims = normalizeToArray(dataDimsRaw as []).slice();
 
         // Note: It is allowed that `dataDims.length` is `0`, e.g., options is
         // `{encode: {x: -1, y: 1}}`. Should not filter anything in
         // this case.
         if (dataDims.length === 1 && !isString(dataDims[0]) && dataDims[0] < 0) {
-            encodeDef.set(coordDim, false);
+            encodeDefMap.set(coordDim, false);
             return;
         }
 
-        const validDataDims = encodeDef.set(coordDim, []);
-        each(dataDims, function (resultDimIdx, idx) {
+        const validDataDims = encodeDefMap.set(coordDim, []) as DimensionIndex[];
+        each(dataDims, function (resultDimIdxOrName, idx) {
             // The input resultDimIdx can be dim name or index.
-            isString(resultDimIdx) && (resultDimIdx = dataDimNameMap.get(resultDimIdx));
+            const resultDimIdx = isString(resultDimIdxOrName)
+                ? dataDimNameMap.get(resultDimIdxOrName)
+                : resultDimIdxOrName;
             if (resultDimIdx != null && resultDimIdx < dimCount) {
                 validDataDims[idx] = resultDimIdx;
                 applyDim(result[resultDimIdx], coordDim, idx);
@@ -134,15 +152,17 @@ function completeDimensions(sysDims, source, opt) {
 
     // Apply templetes and default order from `sysDims`.
     let availDimIdx = 0;
-    each(sysDims, function (sysDimItem, sysDimIndex) {
-        let coordDim;
-        let sysDimItemDimsDef;
-        let sysDimItemOtherDims;
-        if (isString(sysDimItem)) {
-            coordDim = sysDimItem;
-            sysDimItem = {};
+    each(sysDims, function (sysDimItemRaw) {
+        let coordDim: DimensionName;
+        let sysDimItemDimsDef: CoordDimensionDefinition['dimsDef'];
+        let sysDimItemOtherDims: CoordDimensionDefinition['otherDims'];
+        let sysDimItem: CoordDimensionDefinition;
+        if (isString(sysDimItemRaw)) {
+            coordDim = sysDimItemRaw;
+            sysDimItem = {} as CoordDimensionDefinition;
         }
         else {
+            sysDimItem = sysDimItemRaw;
             coordDim = sysDimItem.name;
             const ordinalMeta = sysDimItem.ordinalMeta;
             sysDimItem.ordinalMeta = null;
@@ -155,7 +175,7 @@ function completeDimensions(sysDims, source, opt) {
                 sysDimItem.dimsDef = sysDimItem.otherDims = null;
         }
 
-        let dataDims = encodeDef.get(coordDim);
+        let dataDims = encodeDefMap.get(coordDim);
 
         // negative resultDimIdx means no need to mapping.
         if (dataDims === false) {
@@ -189,9 +209,9 @@ function completeDimensions(sysDims, source, opt) {
         });
     });
 
-    function applyDim(resultItem, coordDim, coordDimIndex) {
-        if (VISUAL_DIMENSIONS.get(coordDim) != null) {
-            resultItem.otherDims[coordDim] = coordDimIndex;
+    function applyDim(resultItem: DataDimensionInfo, coordDim: DimensionName, coordDimIndex: DimensionIndex) {
+        if (VISUAL_DIMENSIONS.get(coordDim as keyof DataVisualDimensions) != null) {
+            resultItem.otherDims[coordDim as keyof DataVisualDimensions] = coordDimIndex;
         }
         else {
             resultItem.coordDim = coordDim;
@@ -224,13 +244,12 @@ function completeDimensions(sysDims, source, opt) {
         }
 
         resultItem.name == null && (resultItem.name = genName(
-            resultItem.coordDim,
-            dataDimNameMap
+            resultItem.coordDim, dataDimNameMap, false
         ));
 
         if (resultItem.type == null
             && (
-                guessOrdinal(source, resultDimIdx, resultItem.name) === BE_ORDINAL.Must
+                guessOrdinal(source, resultDimIdx) === BE_ORDINAL.Must
                 // Consider the case:
                 // {
                 //    dataset: {source: [
@@ -267,7 +286,12 @@ function completeDimensions(sysDims, source, opt) {
 // (2) sometimes user need to calcualte bubble size or use visualMap
 // on other dimensions besides coordSys needed.
 // So, dims that is not used by system, should be shared in storage?
-function getDimCount(source, sysDims, dimsDef, optDimCount) {
+function getDimCount(
+    source: Source,
+    sysDims: CoordDimensionDefinitionLoose[],
+    dimsDef: DimensionDefinitionLoose[],
+    optDimCount: number
+): number {
     // Note that the result dimCount should not small than columns count
     // of data, otherwise `dataDimNameMap` checking will be incorrect.
     let dimCount = Math.max(
@@ -277,13 +301,19 @@ function getDimCount(source, sysDims, dimsDef, optDimCount) {
         optDimCount || 0
     );
     each(sysDims, function (sysDimItem) {
-        const sysDimItemDimsDef = sysDimItem.dimsDef;
-        sysDimItemDimsDef && (dimCount = Math.max(dimCount, sysDimItemDimsDef.length));
+        let sysDimItemDimsDef;
+        if (isObject(sysDimItem) && (sysDimItemDimsDef = sysDimItem.dimsDef)) {
+            dimCount = Math.max(dimCount, sysDimItemDimsDef.length);
+        }
     });
     return dimCount;
 }
 
-function genName(name, map, fromZero) {
+function genName(
+    name: DimensionName,
+    map: HashMap<unknown, DimensionName>,
+    fromZero: boolean
+): DimensionName {
     if (fromZero || map.get(name) != null) {
         let i = 0;
         while (map.get(name + i) != null) {
diff --git a/src/data/helper/createDimensions.ts b/src/data/helper/createDimensions.ts
index 770bfd6..f3cd936 100644
--- a/src/data/helper/createDimensions.ts
+++ b/src/data/helper/createDimensions.ts
@@ -22,16 +22,30 @@
  * `completeDimensions` is to be deprecated.
  */
 import completeDimensions from './completeDimensions';
-import { DimensionDefinitionLoose, OptionEncode, OptionEncodeValue, EncodeDefaulter } from '../../util/types';
+import {
+    DimensionDefinitionLoose, OptionEncode, OptionEncodeValue,
+    EncodeDefaulter, OptionSourceData, DimensionName, DimensionDefinition, DataVisualDimensions, DimensionIndex
+} from '../../util/types';
 import Source from '../Source';
 import List from '../List';
 import DataDimensionInfo from '../DataDimensionInfo';
 import { HashMap } from 'zrender/src/core/util';
+import OrdinalMeta from '../OrdinalMeta';
+
+
+export interface CoordDimensionDefinition extends DimensionDefinition {
+    dimsDef?: (DimensionName | { name: DimensionName, defaultTooltip?: boolean })[];
+    otherDims?: DataVisualDimensions;
+    ordinalMeta?: OrdinalMeta;
+    coordDim?: DimensionName;
+    coordDimIndex?: DimensionIndex;
+}
+export type CoordDimensionDefinitionLoose = CoordDimensionDefinition['name'] | CoordDimensionDefinition;
 
 export type CreateDimensionsParams = {
-    coordDimensions?: DimensionDefinitionLoose[],
+    coordDimensions?: CoordDimensionDefinitionLoose[],
     dimensionsDefine?: DimensionDefinitionLoose[],
-    encodeDefine?: HashMap<OptionEncodeValue> | OptionEncode,
+    encodeDefine?: HashMap<OptionEncodeValue, DimensionName> | OptionEncode,
     dimensionsCount?: number,
     encodeDefaulter?: EncodeDefaulter,
     generateCoord?: string,
@@ -46,7 +60,7 @@ export type CreateDimensionsParams = {
  */
 export default function (
     // TODO: TYPE completeDimensions type
-    source: Source | List | any[],
+    source: Source | List | OptionSourceData,
     opt?: CreateDimensionsParams
 ): DataDimensionInfo[] {
     opt = opt || {};
diff --git a/src/data/helper/dimensionHelper.ts b/src/data/helper/dimensionHelper.ts
index 245f545..955a3d0 100644
--- a/src/data/helper/dimensionHelper.ts
+++ b/src/data/helper/dimensionHelper.ts
@@ -17,13 +17,12 @@
 * under the License.
 */
 
-// @ts-nocheck
 
 import {each, createHashMap, assert} from 'zrender/src/core/util';
 import { __DEV__ } from '../../config';
-import List from '../List';
+import List, { ListDimensionType } from '../List';
 import {
-    DimensionName, VISUAL_DIMENSIONS, DimensionType, DimensionUserOuput
+    DimensionName, VISUAL_DIMENSIONS, DimensionType, DimensionUserOuput, DimensionUserOuputEncode, DimensionIndex
 } from '../../util/types';
 
 export type DimensionSummaryEncode = {
@@ -45,9 +44,9 @@ export type DimensionSummary = {
 export function summarizeDimensions(data: List): DimensionSummary {
     const summary: DimensionSummary = {} as DimensionSummary;
     const encode = summary.encode = {} as DimensionSummaryEncode;
-    const notExtraCoordDimMap = createHashMap();
-    let defaultedLabel = [];
-    let defaultedTooltip = [];
+    const notExtraCoordDimMap = createHashMap<1, DimensionName>();
+    let defaultedLabel = [] as DimensionName[];
+    let defaultedTooltip = [] as DimensionName[];
 
     // See the comment of `List.js#userOutput`.
     const userOutput = summary.userOutput = {
@@ -61,7 +60,7 @@ export function summarizeDimensions(data: List): DimensionSummary {
         const coordDim = dimItem.coordDim;
         if (coordDim) {
             if (__DEV__) {
-                assert(VISUAL_DIMENSIONS.get(coordDim) == null);
+                assert(VISUAL_DIMENSIONS.get(coordDim as any) == null);
             }
 
             const coordDimIndex = dimItem.coordDimIndex;
@@ -97,8 +96,8 @@ export function summarizeDimensions(data: List): DimensionSummary {
         });
     });
 
-    let dataDimsOnCoord = [];
-    const encodeFirstDimNotExtra = {};
+    let dataDimsOnCoord = [] as DimensionName[];
+    const encodeFirstDimNotExtra = {} as {[coordDim: string]: DimensionName};
 
     notExtraCoordDimMap.each(function (v, coordDim) {
         const dimArr = encode[coordDim];
@@ -136,8 +135,8 @@ export function summarizeDimensions(data: List): DimensionSummary {
 }
 
 function getOrCreateEncodeArr(
-    encode: DimensionSummaryEncode, dim: DimensionName
-): DimensionName[] {
+    encode: DimensionSummaryEncode | DimensionUserOuputEncode, dim: DimensionName
+): (DimensionIndex | DimensionName)[] {
     if (!encode.hasOwnProperty(dim)) {
         encode[dim] = [];
     }
@@ -145,7 +144,7 @@ function getOrCreateEncodeArr(
 }
 
 // FIXME:TS should be type `AxisType`
-export function getDimensionTypeByAxis(axisType: string) {
+export function getDimensionTypeByAxis(axisType: string): ListDimensionType {
     return axisType === 'category'
         ? 'ordinal'
         : axisType === 'time'
diff --git a/src/data/helper/linkList.ts b/src/data/helper/linkList.ts
index c2ced47..a7cecc4 100644
--- a/src/data/helper/linkList.ts
+++ b/src/data/helper/linkList.ts
@@ -17,18 +17,28 @@
 * under the License.
 */
 
-// @ts-nocheck
 
 /**
  * Link lists and struct (graph or tree)
  */
 
-import * as zrUtil from 'zrender/src/core/util';
+import { curry, each, assert, extend, map, keys } from 'zrender/src/core/util';
+import List from '../List';
+import { makeInner } from '../../util/model';
+import { SeriesDataType } from '../../util/types';
 
-const each = zrUtil.each;
+// That is: { dataType: data },
+// like: { node: nodeList, edge: edgeList }.
+// Should contain mainData.
+type Datas = { [key in SeriesDataType]?: List };
+type StructReferDataAttr = 'data' | 'edgeData';
+type StructAttr = 'tree' | 'graph';
+
+const inner = makeInner<{
+    datas: Datas;
+    mainData: List;
+}, List>();
 
-const DATAS = '\0__link_datas';
-const MAIN_DATA = '\0__link_mainData';
 
 // Caution:
 // In most case, either list or its shallow clones (see list.cloneShallow)
@@ -37,75 +47,79 @@ const MAIN_DATA = '\0__link_mainData';
 // But in some rare case, we have to keep old list (like do animation in chart). So
 // please take care that both the old list and the new list share the same tree/graph.
 
-/**
- * @param {Object} opt
- * @param {module:echarts/data/List} opt.mainData
- * @param {Object} [opt.struct] For example, instance of Graph or Tree.
- * @param {string} [opt.structAttr] designation: list[structAttr] = struct;
- * @param {Object} [opt.datas] {dataType: data},
- *                 like: {node: nodeList, edge: edgeList}.
- *                 Should contain mainData.
- * @param {Object} [opt.datasAttr] {dataType: attr},
- *                 designation: struct[datasAttr[dataType]] = list;
- */
-function linkList(opt) {
+type LinkListOpt = {
+    mainData: List;
+    // For example, instance of Graph or Tree.
+    struct: {
+        update: () => void;
+    } & {
+        [key in StructReferDataAttr]?: List
+    };
+    // Will designate: `mainData[structAttr] = struct;`
+    structAttr: StructAttr;
+    datas?: Datas;
+    // { dataType: attr },
+    // Will designate: `struct[datasAttr[dataType]] = list;`
+    datasAttr?: { [key in SeriesDataType]?: StructReferDataAttr };
+};
+
+function linkList(opt: LinkListOpt): void {
     const mainData = opt.mainData;
     let datas = opt.datas;
 
     if (!datas) {
-        datas = {main: mainData};
-        opt.datasAttr = {main: 'data'};
+        datas = { main: mainData };
+        opt.datasAttr = { main: 'data' };
     }
     opt.datas = opt.mainData = null;
 
     linkAll(mainData, datas, opt);
 
     // Porxy data original methods.
-    each(datas, function (data) {
+    each(datas, function (data: List) {
         each(mainData.TRANSFERABLE_METHODS, function (methodName) {
-            data.wrapMethod(methodName, zrUtil.curry(transferInjection, opt));
+            data.wrapMethod(methodName, curry(transferInjection, opt));
         });
-
     });
 
     // Beyond transfer, additional features should be added to `cloneShallow`.
-    mainData.wrapMethod('cloneShallow', zrUtil.curry(cloneShallowInjection, opt));
+    mainData.wrapMethod('cloneShallow', curry(cloneShallowInjection, opt));
 
     // Only mainData trigger change, because struct.update may trigger
     // another changable methods, which may bring about dead lock.
     each(mainData.CHANGABLE_METHODS, function (methodName) {
-        mainData.wrapMethod(methodName, zrUtil.curry(changeInjection, opt));
+        mainData.wrapMethod(methodName, curry(changeInjection, opt));
     });
 
     // Make sure datas contains mainData.
-    zrUtil.assert(datas[mainData.dataType] === mainData);
+    assert(datas[mainData.dataType] === mainData);
 }
 
-function transferInjection(opt, res) {
+function transferInjection(this: List, opt: LinkListOpt, res: List): unknown {
     if (isMainData(this)) {
         // Transfer datas to new main data.
-        const datas = zrUtil.extend({}, this[DATAS]);
+        const datas = extend({}, inner(this).datas);
         datas[this.dataType] = res;
         linkAll(res, datas, opt);
     }
     else {
         // Modify the reference in main data to point newData.
-        linkSingle(res, this.dataType, this[MAIN_DATA], opt);
+        linkSingle(res, this.dataType, inner(this).mainData, opt);
     }
     return res;
 }
 
-function changeInjection(opt, res) {
-    opt.struct && opt.struct.update(this);
+function changeInjection(opt: LinkListOpt, res: unknown): unknown {
+    opt.struct && opt.struct.update();
     return res;
 }
 
-function cloneShallowInjection(opt, res) {
+function cloneShallowInjection(opt: LinkListOpt, res: List): List {
     // cloneShallow, which brings about some fragilities, may be inappropriate
     // to be exposed as an API. So for implementation simplicity we can make
     // the restriction that cloneShallow of not-mainData should not be invoked
     // outside, but only be invoked here.
-    each(res[DATAS], function (data, dataType) {
+    each(inner(res).datas, function (data: List, dataType) {
         data !== res && linkSingle(data.cloneShallow(), dataType, res, opt);
     });
     return res;
@@ -115,49 +129,52 @@ function cloneShallowInjection(opt, res) {
  * Supplement method to List.
  *
  * @public
- * @param {string} [dataType] If not specified, return mainData.
- * @return {module:echarts/data/List}
+ * @param [dataType] If not specified, return mainData.
  */
-function getLinkedData(dataType) {
-    const mainData = this[MAIN_DATA];
+function getLinkedData(this: List, dataType?: SeriesDataType): List {
+    const mainData = inner(this).mainData;
     return (dataType == null || mainData == null)
         ? mainData
-        : mainData[DATAS][dataType];
+        : inner(mainData).datas[dataType];
 }
 
 /**
  * Get list of all linked data
  */
-function getLinkedDataAll() {
-    const mainData = this[MAIN_DATA];
+function getLinkedDataAll(this: List): {
+    data: List,
+    type?: SeriesDataType
+}[] {
+    const mainData = inner(this).mainData;
     return (mainData == null)
         ? [{ data: mainData }]
-        : zrUtil.map(zrUtil.keys(mainData[DATAS]), function (type) {
+        : map(keys(inner(mainData).datas), function (type) {
             return {
                 type,
-                data: mainData[DATAS][type]
+                data: inner(mainData).datas[type]
             };
         });
 }
 
-function isMainData(data) {
-    return data[MAIN_DATA] === data;
+function isMainData(data: List): boolean {
+    return inner(data).mainData === data;
 }
 
-function linkAll(mainData, datas, opt) {
-    mainData[DATAS] = {};
-    each(datas, function (data, dataType) {
+function linkAll(mainData: List, datas: Datas, opt: LinkListOpt): void {
+    inner(mainData).datas = {};
+    each(datas, function (data: List, dataType) {
         linkSingle(data, dataType, mainData, opt);
     });
 }
 
-function linkSingle(data, dataType, mainData, opt) {
-    mainData[DATAS][dataType] = data;
-    data[MAIN_DATA] = mainData;
+function linkSingle(data: List, dataType: SeriesDataType, mainData: List, opt: LinkListOpt): void {
+    inner(mainData).datas[dataType] = data;
+    inner(data).mainData = mainData;
+
     data.dataType = dataType;
 
     if (opt.struct) {
-        data[opt.structAttr] = opt.struct;
+        data[opt.structAttr] = opt.struct as any;
         opt.struct[opt.datasAttr[dataType]] = data;
     }
 
@@ -166,4 +183,4 @@ function linkSingle(data, dataType, mainData, opt) {
     data.getLinkedDataAll = getLinkedDataAll;
 }
 
-export default linkList;
\ No newline at end of file
+export default linkList;
diff --git a/src/data/helper/sourceHelper.ts b/src/data/helper/sourceHelper.ts
index fc760d8..45d1c10 100644
--- a/src/data/helper/sourceHelper.ts
+++ b/src/data/helper/sourceHelper.ts
@@ -17,7 +17,6 @@
 * under the License.
 */
 
-// @ts-nocheck
 
 import {__DEV__} from '../../config';
 import {makeInner, getDataItemValue} from '../../util/model';
@@ -31,7 +30,9 @@ import {
     isTypedArray,
     isArrayLike,
     extend,
-    assert
+    assert,
+    hasOwn,
+    HashMap
 } from 'zrender/src/core/util';
 import Source from '../Source';
 
@@ -42,8 +43,29 @@ import {
     SERIES_LAYOUT_BY_ROW,
     SOURCE_FORMAT_KEYED_COLUMNS,
     SOURCE_FORMAT_TYPED_ARRAY,
-    SOURCE_FORMAT_UNKNOWN
+    SOURCE_FORMAT_UNKNOWN,
+    SourceFormat,
+    Dictionary,
+    SeriesEncodeOptionMixin,
+    SeriesOption,
+    OptionSourceData,
+    SeriesLayoutBy,
+    OptionSourceHeader,
+    DimensionName,
+    DimensionDefinition,
+    DimensionDefinitionLoose,
+    OptionSourceDataArrayRows,
+    OptionDataValue,
+    OptionSourceDataKeyedColumns,
+    OptionSourceDataOriginal,
+    OptionSourceDataObjectRows,
+    OptionEncode,
+    DimensionIndex
 } from '../../util/types';
+import { DatasetModel } from '../../component/dataset';
+import SeriesModel from '../../model/Series';
+import GlobalModel from '../../model/Global';
+import { CoordDimensionDefinition } from './createDimensions';
 
 // The result of `guessOrdinal`.
 export const BE_ORDINAL = {
@@ -51,17 +73,33 @@ export const BE_ORDINAL = {
     Might: 2, // Encounter string but number-like.
     Not: 3 // Other cases
 };
+type BeOrdinalValue = (typeof BE_ORDINAL)[keyof typeof BE_ORDINAL];
+
+const innerDatasetModel = makeInner<{
+    sourceFormat: SourceFormat;
+}, DatasetModel>();
+const innerSeriesModel = makeInner<{
+    source: Source;
+}, SeriesModel>();
+const innerGlobalModel = makeInner<{
+    datasetMap: HashMap<DatasetRecord, string>
+}, GlobalModel>();
+
+interface DatasetRecord {
+    categoryWayDim: number;
+    valueWayDim: number;
+}
 
-const inner = makeInner();
+type SeriesEncodeInternal = {
+    [key in keyof OptionEncode]: DimensionIndex[];
+};
 
-/**
- * @see {module:echarts/data/Source}
- * @param {module:echarts/component/dataset/DatasetModel} datasetModel
- * @return {string} sourceFormat
- */
-export function detectSourceFormat(datasetModel) {
+type SeriesEncodableModel = SeriesModel<SeriesOption & SeriesEncodeOptionMixin>;
+
+
+export function detectSourceFormat(datasetModel: DatasetModel): void {
     const data = datasetModel.option.source;
-    let sourceFormat = SOURCE_FORMAT_UNKNOWN;
+    let sourceFormat: SourceFormat = SOURCE_FORMAT_UNKNOWN;
 
     if (isTypedArray(data)) {
         sourceFormat = SOURCE_FORMAT_TYPED_ARRAY;
@@ -90,7 +128,7 @@ export function detectSourceFormat(datasetModel) {
     }
     else if (isObject(data)) {
         for (const key in data) {
-            if (data.hasOwnProperty(key) && isArrayLike(data[key])) {
+            if (hasOwn(data, key) && isArrayLike((data as Dictionary<unknown>)[key])) {
                 sourceFormat = SOURCE_FORMAT_KEYED_COLUMNS;
                 break;
             }
@@ -100,7 +138,7 @@ export function detectSourceFormat(datasetModel) {
         throw new Error('Invalid data');
     }
 
-    inner(datasetModel).sourceFormat = sourceFormat;
+    innerDatasetModel(datasetModel).sourceFormat = sourceFormat;
 }
 
 /**
@@ -124,19 +162,17 @@ export function detectSourceFormat(datasetModel) {
  *     }]
  *
  * Get data from series itself or datset.
- * @return {module:echarts/data/Source} source
  */
-export function getSource(seriesModel) {
-    return inner(seriesModel).source;
+export function getSource(seriesModel: SeriesModel): Source {
+    return innerSeriesModel(seriesModel).source;
 }
 
 /**
  * MUST be called before mergeOption of all series.
- * @param {module:echarts/model/Global} ecModel
  */
-export function resetSourceDefaulter(ecModel) {
+export function resetSourceDefaulter(ecModel: GlobalModel): void {
     // `datasetMap` is used to make default encode.
-    inner(ecModel).datasetMap = createHashMap();
+    innerGlobalModel(ecModel).datasetMap = createHashMap();
 }
 
 /**
@@ -152,14 +188,12 @@ export function resetSourceDefaulter(ecModel) {
  * Simplify the typing of encode in option, avoiding the case like that:
  * series: [{encode: {x: 0, y: 1}}, {encode: {x: 0, y: 2}}, {encode: {x: 0, y: 3}}],
  * where the "y" have to be manually typed as "1, 2, 3, ...".
- *
- * @param {module:echarts/model/Series} seriesModel
  */
-export function prepareSource(seriesModel) {
+export function prepareSource(seriesModel: SeriesEncodableModel): void {
     const seriesOption = seriesModel.option;
 
-    let data = seriesOption.data;
-    let sourceFormat = isTypedArray(data)
+    let data = seriesOption.data as OptionSourceData;
+    let sourceFormat: SourceFormat = isTypedArray(data)
         ? SOURCE_FORMAT_TYPED_ARRAY : SOURCE_FORMAT_ORIGINAL;
     let fromDataset = false;
 
@@ -172,7 +206,7 @@ export function prepareSource(seriesModel) {
         const datasetOption = datasetModel.option;
 
         data = datasetOption.source;
-        sourceFormat = inner(datasetModel).sourceFormat;
+        sourceFormat = innerDatasetModel(datasetModel).sourceFormat;
         fromDataset = true;
 
         // These settings from series has higher priority.
@@ -185,7 +219,7 @@ export function prepareSource(seriesModel) {
         data, sourceFormat, seriesLayoutBy, sourceHeader, dimensionsDefine
     );
 
-    inner(seriesModel).source = new Source({
+    innerSeriesModel(seriesModel).source = new Source({
         data: data,
         fromDataset: fromDataset,
         seriesLayoutBy: seriesLayoutBy,
@@ -199,15 +233,30 @@ export function prepareSource(seriesModel) {
 }
 
 // return {startIndex, dimensionsDefine, dimensionsCount}
-function completeBySourceData(data, sourceFormat, seriesLayoutBy, sourceHeader, dimensionsDefine) {
+function completeBySourceData(
+    data: OptionSourceData,
+    sourceFormat: SourceFormat,
+    seriesLayoutBy: SeriesLayoutBy,
+    sourceHeader: OptionSourceHeader,
+    dimensionsDefine: DimensionDefinitionLoose[]
+): {
+    dimensionsDefine: DimensionDefinition[];
+    startIndex: number;
+    dimensionsDetectCount: number;
+} {
+    let dimensionsDetectCount;
+    let startIndex: number;
+
     if (!data) {
-        return {dimensionsDefine: normalizeDimensionsDefine(dimensionsDefine)};
+        return {
+            dimensionsDefine: normalizeDimensionsDefine(dimensionsDefine),
+            startIndex,
+            dimensionsDetectCount
+        };
     }
 
-    let dimensionsDetectCount;
-    let startIndex;
-
     if (sourceFormat === SOURCE_FORMAT_ARRAY_ROWS) {
+        const dataArrayRows = data as OptionSourceDataArrayRows;
         // Rule: Most of the first line are string: it is header.
         // Caution: consider a line with 5 string and 1 number,
         // it still can not be sure it is a head, because the
@@ -224,7 +273,7 @@ function completeBySourceData(data, sourceFormat, seriesLayoutBy, sourceHeader,
                     }
                 }
             // 10 is an experience number, avoid long loop.
-            }, seriesLayoutBy, data, 10);
+            }, seriesLayoutBy, dataArrayRows, 10);
         }
         else {
             startIndex = sourceHeader ? 1 : 0;
@@ -233,33 +282,33 @@ function completeBySourceData(data, sourceFormat, seriesLayoutBy, sourceHeader,
         if (!dimensionsDefine && startIndex === 1) {
             dimensionsDefine = [];
             arrayRowsTravelFirst(function (val, index) {
-                dimensionsDefine[index] = val != null ? val : '';
-            }, seriesLayoutBy, data);
+                dimensionsDefine[index] = (val != null ? val + '' : '') as DimensionName;
+            }, seriesLayoutBy, dataArrayRows, Infinity);
         }
 
         dimensionsDetectCount = dimensionsDefine
             ? dimensionsDefine.length
             : seriesLayoutBy === SERIES_LAYOUT_BY_ROW
-            ? data.length
-            : data[0]
-            ? data[0].length
+            ? dataArrayRows.length
+            : dataArrayRows[0]
+            ? dataArrayRows[0].length
             : null;
     }
     else if (sourceFormat === SOURCE_FORMAT_OBJECT_ROWS) {
         if (!dimensionsDefine) {
-            dimensionsDefine = objectRowsCollectDimensions(data);
+            dimensionsDefine = objectRowsCollectDimensions(data as OptionSourceDataObjectRows);
         }
     }
     else if (sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS) {
         if (!dimensionsDefine) {
             dimensionsDefine = [];
-            each(data, function (colArr, key) {
+            each(data as OptionSourceDataKeyedColumns, function (colArr, key) {
                 dimensionsDefine.push(key);
             });
         }
     }
     else if (sourceFormat === SOURCE_FORMAT_ORIGINAL) {
-        const value0 = getDataItemValue(data[0]);
+        const value0 = getDataItemValue((data as OptionSourceDataOriginal)[0]);
         dimensionsDetectCount = isArray(value0) && value0.length || 1;
     }
     else if (sourceFormat === SOURCE_FORMAT_TYPED_ARRAY) {
@@ -278,12 +327,12 @@ function completeBySourceData(data, sourceFormat, seriesLayoutBy, sourceHeader,
 // Consider dimensions defined like ['A', 'price', 'B', 'price', 'C', 'price'],
 // which is reasonable. But dimension name is duplicated.
 // Returns undefined or an array contains only object without null/undefiend or string.
-function normalizeDimensionsDefine(dimensionsDefine) {
+function normalizeDimensionsDefine(dimensionsDefine: DimensionDefinitionLoose[]): DimensionDefinition[] {
     if (!dimensionsDefine) {
         // The meaning of null/undefined is different from empty array.
         return;
     }
-    const nameMap = createHashMap();
+    const nameMap = createHashMap<{ count: number }, string>();
     return map(dimensionsDefine, function (item, index) {
         item = extend({}, isObject(item) ? item : {name: item});
 
@@ -317,8 +366,12 @@ function normalizeDimensionsDefine(dimensionsDefine) {
     });
 }
 
-function arrayRowsTravelFirst(cb, seriesLayoutBy, data, maxLoop) {
-    maxLoop == null && (maxLoop = Infinity);
+function arrayRowsTravelFirst(
+    cb: (val: OptionDataValue, idx: number) => void,
+    seriesLayoutBy: SeriesLayoutBy,
+    data: OptionSourceDataArrayRows,
+    maxLoop: number
+): void {
     if (seriesLayoutBy === SERIES_LAYOUT_BY_ROW) {
         for (let i = 0; i < data.length && i < maxLoop; i++) {
             cb(data[i] ? data[i][0] : null, i);
@@ -332,12 +385,12 @@ function arrayRowsTravelFirst(cb, seriesLayoutBy, data, maxLoop) {
     }
 }
 
-function objectRowsCollectDimensions(data) {
+function objectRowsCollectDimensions(data: OptionSourceDataObjectRows): DimensionDefinitionLoose[] {
     let firstIndex = 0;
     let obj;
     while (firstIndex < data.length && !(obj = data[firstIndex++])) {} // jshint ignore: line
     if (obj) {
-        const dimensions = [];
+        const dimensions: DimensionDefinitionLoose[] = [];
         each(obj, function (value, key) {
             dimensions.push(key);
         });
@@ -358,13 +411,14 @@ function objectRowsCollectDimensions(data) {
  *     The result of data arrengment of data dimensions like:
  *     | ser_shared_x | ser0_y | ser1_y | ser2_y |
  *
- * @param {Array.<Object|string>} coordDimensions [{name: <string>, type: <string>, dimsDef: <Array>}, ...]
- * @param {module:model/Series} seriesModel
- * @param {module:data/Source} source
- * @return {Object} encode Never be `null/undefined`.
+ * @return encode Never be `null/undefined`.
  */
-export function makeSeriesEncodeForAxisCoordSys(coordDimensions, seriesModel, source) {
-    const encode = {};
+export function makeSeriesEncodeForAxisCoordSys(
+    coordDimensions: (DimensionName | CoordDimensionDefinition)[],
+    seriesModel: SeriesModel,
+    source: Source
+): SeriesEncodeInternal {
+    const encode: SeriesEncodeInternal = {};
 
     const datasetModel = getDatasetModel(seriesModel);
     // Currently only make default when using dataset, util more reqirements occur.
@@ -372,21 +426,23 @@ export function makeSeriesEncodeForAxisCoordSys(coordDimensions, seriesModel, so
         return encode;
     }
 
-    const encodeItemName = [];
-    const encodeSeriesName = [];
+    const encodeItemName: DimensionIndex[] = [];
+    const encodeSeriesName: DimensionIndex[] = [];
 
     const ecModel = seriesModel.ecModel;
-    const datasetMap = inner(ecModel).datasetMap;
+    const datasetMap = innerGlobalModel(ecModel).datasetMap;
     const key = datasetModel.uid + '_' + source.seriesLayoutBy;
 
-    let baseCategoryDimIndex;
+    let baseCategoryDimIndex: number;
     let categoryWayValueDimStart;
     coordDimensions = coordDimensions.slice();
-    each(coordDimensions, function (coordDimInfo, coordDimIdx) {
-        !isObject(coordDimInfo) && (coordDimensions[coordDimIdx] = {name: coordDimInfo});
+    each(coordDimensions, function (coordDimInfoLoose, coordDimIdx) {
+        const coordDimInfo: CoordDimensionDefinition = isObject(coordDimInfoLoose)
+            ? coordDimInfoLoose
+            : (coordDimensions[coordDimIdx] = { name: coordDimInfoLoose as DimensionName });
         if (coordDimInfo.type === 'ordinal' && baseCategoryDimIndex == null) {
             baseCategoryDimIndex = coordDimIdx;
-            categoryWayValueDimStart = getDataDimCountOnCoordDim(coordDimensions[coordDimIdx]);
+            categoryWayValueDimStart = getDataDimCountOnCoordDim(coordDimInfo);
         }
         encode[coordDimInfo.name] = [];
     });
@@ -396,7 +452,7 @@ export function makeSeriesEncodeForAxisCoordSys(coordDimensions, seriesModel, so
 
     // TODO
     // Auto detect first time axis and do arrangement.
-    each(coordDimensions, function (coordDimInfo, coordDimIdx) {
+    each(coordDimensions, function (coordDimInfo: CoordDimensionDefinition, coordDimIdx) {
         const coordDimName = coordDimInfo.name;
         const count = getDataDimCountOnCoordDim(coordDimInfo);
 
@@ -428,13 +484,13 @@ export function makeSeriesEncodeForAxisCoordSys(coordDimensions, seriesModel, so
         }
     });
 
-    function pushDim(dimIdxArr, idxFrom, idxCount) {
+    function pushDim(dimIdxArr: DimensionIndex[], idxFrom: number, idxCount: number) {
         for (let i = 0; i < idxCount; i++) {
             dimIdxArr.push(idxFrom + i);
         }
     }
 
-    function getDataDimCountOnCoordDim(coordDimInfo) {
+    function getDataDimCountOnCoordDim(coordDimInfo: CoordDimensionDefinition) {
         const dimsDef = coordDimInfo.dimsDef;
         return dimsDef ? dimsDef.length : 1;
     }
@@ -448,12 +504,14 @@ export function makeSeriesEncodeForAxisCoordSys(coordDimensions, seriesModel, so
 /**
  * Work for data like [{name: ..., value: ...}, ...].
  *
- * @param {module:model/Series} seriesModel
- * @param {module:data/Source} source
- * @return {Object} encode Never be `null/undefined`.
+ * @return encode Never be `null/undefined`.
  */
-export function makeSeriesEncodeForNameBased(seriesModel, source, dimCount) {
-    const encode = {};
+export function makeSeriesEncodeForNameBased(
+    seriesModel: SeriesModel,
+    source: Source,
+    dimCount: number
+): SeriesEncodeInternal {
+    const encode: SeriesEncodeInternal = {};
 
     const datasetModel = getDatasetModel(seriesModel);
     // Currently only make default when using dataset, util more reqirements occur.
@@ -473,11 +531,12 @@ export function makeSeriesEncodeForNameBased(seriesModel, source, dimCount) {
         });
     }
 
-    // idxResult: {v, n}.
+    type IdxResult = { v: number, n: number };
+
     const idxResult = (function () {
 
-        const idxRes0 = {};
-        const idxRes1 = {};
+        const idxRes0 = {} as IdxResult;
+        const idxRes1 = {} as IdxResult;
         const guessRecords = [];
 
         // 5 is an experience value.
@@ -521,7 +580,7 @@ export function makeSeriesEncodeForNameBased(seriesModel, source, dimCount) {
             }
         }
 
-        function fulfilled(idxResult) {
+        function fulfilled(idxResult: IdxResult) {
             return idxResult.v != null && idxResult.n != null;
         }
 
@@ -529,7 +588,7 @@ export function makeSeriesEncodeForNameBased(seriesModel, source, dimCount) {
     })();
 
     if (idxResult) {
-        encode.value = idxResult.v;
+        encode.value = [idxResult.v];
         // `potentialNameDimIndex` has highest priority.
         const nameDimIndex = potentialNameDimIndex != null ? potentialNameDimIndex : idxResult.n;
         // By default, label use itemName in charts.
@@ -544,7 +603,7 @@ export function makeSeriesEncodeForNameBased(seriesModel, source, dimCount) {
 /**
  * If return null/undefined, indicate that should not use datasetModel.
  */
-function getDatasetModel(seriesModel) {
+function getDatasetModel(seriesModel: SeriesEncodableModel): DatasetModel {
     const option = seriesModel.option;
     // Caution: consider the scenario:
     // A dataset is declared and a series is not expected to use the dataset,
@@ -553,7 +612,7 @@ function getDatasetModel(seriesModel) {
     // the user should set an empty array to avoid that dataset is used by default.
     const thisData = option.data;
     if (!thisData) {
-        return seriesModel.ecModel.getComponent('dataset', option.datasetIndex || 0);
+        return seriesModel.ecModel.getComponent('dataset', option.datasetIndex || 0) as DatasetModel;
     }
 }
 
@@ -561,12 +620,8 @@ function getDatasetModel(seriesModel) {
  * The rule should not be complex, otherwise user might not
  * be able to known where the data is wrong.
  * The code is ugly, but how to make it neat?
- *
- * @param {module:echars/data/Source} source
- * @param {number} dimIndex
- * @return {BE_ORDINAL} guess result.
  */
-export function guessOrdinal(source, dimIndex) {
+export function guessOrdinal(source: Source, dimIndex: DimensionIndex): BeOrdinalValue {
     return doGuessOrdinal(
         source.data,
         source.sourceFormat,
@@ -580,8 +635,13 @@ export function guessOrdinal(source, dimIndex) {
 // dimIndex may be overflow source data.
 // return {BE_ORDINAL}
 function doGuessOrdinal(
-    data, sourceFormat, seriesLayoutBy, dimensionsDefine, startIndex, dimIndex
-) {
+    data: Source['data'],
+    sourceFormat: Source['sourceFormat'],
+    seriesLayoutBy: Source['seriesLayoutBy'],
+    dimensionsDefine: Source['dimensionsDefine'],
+    startIndex: Source['startIndex'],
+    dimIndex: DimensionIndex
+): BeOrdinalValue {
     let result;
     // Experience value.
     const maxLoop = 5;
@@ -610,8 +670,9 @@ function doGuessOrdinal(
     }
 
     if (sourceFormat === SOURCE_FORMAT_ARRAY_ROWS) {
+        const dataArrayRows = data as OptionSourceDataArrayRows;
         if (seriesLayoutBy === SERIES_LAYOUT_BY_ROW) {
-            const sample = data[dimIndex];
+            const sample = dataArrayRows[dimIndex];
             for (let i = 0; i < (sample || []).length && i < maxLoop; i++) {
                 if ((result = detectValue(sample[startIndex + i])) != null) {
                     return result;
@@ -619,8 +680,8 @@ function doGuessOrdinal(
             }
         }
         else {
-            for (let i = 0; i < data.length && i < maxLoop; i++) {
-                const row = data[startIndex + i];
+            for (let i = 0; i < dataArrayRows.length && i < maxLoop; i++) {
+                const row = dataArrayRows[startIndex + i];
                 if (row && (result = detectValue(row[dimIndex])) != null) {
                     return result;
                 }
@@ -628,21 +689,23 @@ function doGuessOrdinal(
         }
     }
     else if (sourceFormat === SOURCE_FORMAT_OBJECT_ROWS) {
+        const dataObjectRows = data as OptionSourceDataObjectRows;
         if (!dimName) {
             return BE_ORDINAL.Not;
         }
-        for (let i = 0; i < data.length && i < maxLoop; i++) {
-            const item = data[i];
+        for (let i = 0; i < dataObjectRows.length && i < maxLoop; i++) {
+            const item = dataObjectRows[i];
             if (item && (result = detectValue(item[dimName])) != null) {
                 return result;
             }
         }
     }
     else if (sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS) {
+        const dataKeyedColumns = data as OptionSourceDataKeyedColumns;
         if (!dimName) {
             return BE_ORDINAL.Not;
         }
-        const sample = data[dimName];
+        const sample = dataKeyedColumns[dimName];
         if (!sample || isTypedArray(sample)) {
             return BE_ORDINAL.Not;
         }
@@ -653,8 +716,9 @@ function doGuessOrdinal(
         }
     }
     else if (sourceFormat === SOURCE_FORMAT_ORIGINAL) {
-        for (let i = 0; i < data.length && i < maxLoop; i++) {
-            const item = data[i];
+        const dataOriginal = data as OptionSourceDataOriginal;
+        for (let i = 0; i < dataOriginal.length && i < maxLoop; i++) {
+            const item = dataOriginal[i];
             const val = getDataItemValue(item);
             if (!isArray(val)) {
                 return BE_ORDINAL.Not;
@@ -665,11 +729,11 @@ function doGuessOrdinal(
         }
     }
 
-    function detectValue(val) {
+    function detectValue(val: OptionDataValue): BeOrdinalValue {
         const beStr = isString(val);
         // Consider usage convenience, '1', '2' will be treated as "number".
         // `isFinit('')` get `true`.
-        if (val != null && isFinite(val) && val !== '') {
+        if (val != null && isFinite(val as number) && val !== '') {
             return beStr ? BE_ORDINAL.Might : BE_ORDINAL.Not;
         }
         else if (beStr && val !== '-') {
diff --git a/src/label/LabelManager.ts b/src/label/LabelManager.ts
index 12ddbc7..646ded9 100644
--- a/src/label/LabelManager.ts
+++ b/src/label/LabelManager.ts
@@ -36,7 +36,8 @@ import {
     LabelLayoutOptionCallbackParams,
     LabelLineOption,
     Dictionary,
-    ECElement
+    ECElement,
+    SeriesDataType
 } from '../util/types';
 import { parsePercent } from '../util/number';
 import ChartView from '../view/Chart';
@@ -57,7 +58,7 @@ interface LabelDesc {
 
     seriesModel: SeriesModel
     dataIndex: number
-    dataType: string
+    dataType: SeriesDataType
 
     layoutOption: LabelLayoutOptionCallback | LabelLayoutOption
     computedLayoutOption: LabelLayoutOption
@@ -188,7 +189,7 @@ class LabelManager {
      */
     private _addLabel(
         dataIndex: number,
-        dataType: string,
+        dataType: SeriesDataType,
         seriesModel: SeriesModel,
         label: ZRText,
         layoutOption: LabelDesc['layoutOption']
diff --git a/src/model/Series.ts b/src/model/Series.ts
index a580c9c..64c24e0 100644
--- a/src/model/Series.ts
+++ b/src/model/Series.ts
@@ -30,7 +30,7 @@ import * as modelUtil from '../util/model';
 import {
     DataHost, DimensionName, StageHandlerProgressParams,
     SeriesOption, TooltipRenderMode, ZRColor, BoxLayoutOptionMixin,
-    ScaleDataValue, Dictionary, ColorString, OptionDataItemObject
+    ScaleDataValue, Dictionary, ColorString, OptionDataItemObject, SeriesDataType
 } from '../util/types';
 import ComponentModel, { ComponentModelConstructor } from './Component';
 import {ColorPaletteMixin} from './mixin/colorPalette';
@@ -327,11 +327,11 @@ class SeriesModel<Opt extends SeriesOption = SeriesOption> extends ComponentMode
      * data in the stream procedure. So we fetch data from upstream
      * each time `task.perform` called.
      */
-    getData(dataType?: string): List<this> {
+    getData(dataType?: SeriesDataType): List<this> {
         const task = getCurrentTask(this);
         if (task) {
             const data = task.context.data;
-            return dataType == null ? data : data.getLinkedData(dataType);
+            return (dataType == null ? data : data.getLinkedData(dataType)) as List<this>;
         }
         else {
             // When series is not alive (that may happen when click toolbox
@@ -344,12 +344,10 @@ class SeriesModel<Opt extends SeriesOption = SeriesOption> extends ComponentMode
 
     getAllData(): ({
         data: List,
-        type?: string
+        type?: SeriesDataType
     })[] {
         const mainData = this.getData();
-        // @ts-ignore
         return (mainData && mainData.getLinkedDataAll)
-            // @ts-ignore
             ? mainData.getLinkedDataAll()
             : [{ data: mainData }];
     }
@@ -419,7 +417,7 @@ class SeriesModel<Opt extends SeriesOption = SeriesOption> extends ComponentMode
     formatTooltip(
         dataIndex: number,
         multipleSeries?: boolean,
-        dataType?: string,
+        dataType?: SeriesDataType,
         renderMode?: TooltipRenderMode
     ): {
         html: string,
@@ -613,11 +611,11 @@ class SeriesModel<Opt extends SeriesOption = SeriesOption> extends ComponentMode
     }
 
     // PENGING If selectedMode is null ?
-    select(innerDataIndices: number[], dataType?: string): void {
+    select(innerDataIndices: number[], dataType?: SeriesDataType): void {
         this._innerSelect(this.getData(dataType), innerDataIndices);
     }
 
-    unselect(innerDataIndices: number[], dataType?: string): void {
+    unselect(innerDataIndices: number[], dataType?: SeriesDataType): void {
         const selectedMap = this.option.selectedMap;
         if (!selectedMap) {
             return;
@@ -631,7 +629,7 @@ class SeriesModel<Opt extends SeriesOption = SeriesOption> extends ComponentMode
         }
     }
 
-    toggleSelect(innerDataIndices: number[], dataType?: string): void {
+    toggleSelect(innerDataIndices: number[], dataType?: SeriesDataType): void {
         const tmpArr: number[] = [];
         for (let i = 0; i < innerDataIndices.length; i++) {
             tmpArr[0] = innerDataIndices[i];
@@ -654,7 +652,7 @@ class SeriesModel<Opt extends SeriesOption = SeriesOption> extends ComponentMode
         return dataIndices;
     }
 
-    isSelected(dataIndex: number, dataType?: string): boolean {
+    isSelected(dataIndex: number, dataType?: SeriesDataType): boolean {
         const selectedMap = this.option.selectedMap;
         if (!selectedMap) {
             return false;
diff --git a/src/model/mixin/dataFormat.ts b/src/model/mixin/dataFormat.ts
index eacee23..a10f4a7 100644
--- a/src/model/mixin/dataFormat.ts
+++ b/src/model/mixin/dataFormat.ts
@@ -27,7 +27,8 @@ import {
     CallbackDataParams,
     ColorString,
     ZRColor,
-    OptionDataValue
+    OptionDataValue,
+    SeriesDataType
 } from '../../util/types';
 import GlobalModel from '../Global';
 
@@ -51,7 +52,7 @@ class DataFormatMixin {
      */
     getDataParams(
         dataIndex: number,
-        dataType?: string
+        dataType?: SeriesDataType
     ): CallbackDataParams {
 
         const data = this.getData(dataType);
@@ -102,7 +103,7 @@ class DataFormatMixin {
     getFormattedLabel(
         dataIndex: number,
         status?: DisplayState,
-        dataType?: string,
+        dataType?: SeriesDataType,
         labelDimIndex?: number,
         formatter?: string | ((params: object) => string),
         extendParams?: Partial<CallbackDataParams>
@@ -154,7 +155,7 @@ class DataFormatMixin {
      */
     getRawValue(
         idx: number,
-        dataType?: string
+        dataType?: SeriesDataType
     ): unknown {
         return retrieveRawValue(this.getData(dataType), idx);
     }
diff --git a/src/stream/task.ts b/src/stream/task.ts
index a25b14e..5f4a65f 100644
--- a/src/stream/task.ts
+++ b/src/stream/task.ts
@@ -22,10 +22,11 @@ import { __DEV__ } from '../config';
 import SeriesModel from '../model/Series';
 import { Pipeline } from './Scheduler';
 import { Payload } from '../util/types';
+import List from '../data/List';
 
 export interface TaskContext {
-    outputData?: any;
-    data?: any;
+    outputData?: List;
+    data?: List;
     payload?: Payload;
     model?: SeriesModel;
 };
diff --git a/src/util/graphic.ts b/src/util/graphic.ts
index 124e8bf..3ea91f7 100644
--- a/src/util/graphic.ts
+++ b/src/util/graphic.ts
@@ -58,7 +58,8 @@ import {
     ParsedValue,
     BlurScope,
     InnerFocus,
-    PayloadAnimationPart
+    PayloadAnimationPart,
+    SeriesDataType
 } from './types';
 import { makeInner } from './model';
 import {
@@ -897,7 +898,7 @@ export interface ECData {
     dataModel?: DataModel;
     eventData?: ECEventData;
     seriesIndex?: number;
-    dataType?: string;
+    dataType?: SeriesDataType;
 
     focus?: InnerFocus
     blurScope?: BlurScope
diff --git a/src/util/model.ts b/src/util/model.ts
index 5cf3568..ed30d25 100644
--- a/src/util/model.ts
+++ b/src/util/model.ts
@@ -29,7 +29,7 @@ import {
     indexOf
 } from 'zrender/src/core/util';
 import env from 'zrender/src/core/env';
-import GlobalModel, { QueryConditionKindB } from '../model/Global';
+import GlobalModel from '../model/Global';
 import ComponentModel, {ComponentModelConstructor} from '../model/Component';
 import List from '../data/List';
 import {
diff --git a/src/util/states.ts b/src/util/states.ts
index 2f3c474..15f53f8 100644
--- a/src/util/states.ts
+++ b/src/util/states.ts
@@ -5,7 +5,7 @@ import { PatternObject } from 'zrender/src/graphic/Pattern';
 import { GradientObject } from 'zrender/src/graphic/Gradient';
 import Element, { ElementEvent } from 'zrender/src/Element';
 import Model from '../model/Model';
-import { DisplayState, ECElement, ColorString, BlurScope, InnerFocus, Payload, ZRColor } from './types';
+import { DisplayState, ECElement, ColorString, BlurScope, InnerFocus, Payload, ZRColor, SeriesDataType } from './types';
 import { extend, indexOf, isArrayLike, isObject, keys, isArray, each } from 'zrender/src/core/util';
 import { getECData } from './graphic';
 import * as colorTool from 'zrender/src/tool/color';
@@ -188,7 +188,7 @@ function getFromStateStyle(
 function createEmphasisDefaultState(
     el: Displayable,
     stateName: 'emphasis',
-    targetStates?: string[],
+    targetStates: string[],
     state: Displayable['states'][number]
 ) {
     const hasSelect = targetStates && indexOf(targetStates, 'select') >= 0;
@@ -436,7 +436,7 @@ export function toggleSeriesBlurState(
             else if (isObject(focus)) {
                 const dataTypes = keys(focus);
                 for (let d = 0; d < dataTypes.length; d++) {
-                    leaveBlurOfIndices(seriesModel.getData(dataTypes[d]), focus[dataTypes[d]]);
+                    leaveBlurOfIndices(seriesModel.getData(dataTypes[d] as SeriesDataType), focus[dataTypes[d]]);
                 }
             }
 
@@ -530,7 +530,7 @@ export function updateSeriesElementSelection(seriesModel: SeriesModel) {
 export function getAllSelectedIndices(ecModel: GlobalModel) {
     const ret: {
         seriesIndex: number
-        dataType?: string
+        dataType?: SeriesDataType
         dataIndex: number[]
     }[] = [];
     ecModel.eachSeries(function (seriesModel) {
diff --git a/src/util/types.ts b/src/util/types.ts
index 0c7cf84..5ad303f 100644
--- a/src/util/types.ts
+++ b/src/util/types.ts
@@ -126,7 +126,7 @@ export interface ECElement extends Element {
 }
 
 export interface DataHost {
-    getData(dataType?: string): List;
+    getData(dataType?: SeriesDataType): List;
 }
 
 export interface DataModel extends DataHost, DataFormatMixin {}
@@ -161,7 +161,7 @@ export interface SelectChangedPayload extends Payload {
     fromActionPayload: Payload
     selected: {
         seriesIndex: number
-        dataType?: string
+        dataType?: SeriesDataType
         dataIndex: number[]
     }[]
 }
@@ -302,7 +302,7 @@ export type DimensionName = string;
 export type DimensionLoose = DimensionName | DimensionIndexLoose;
 export type DimensionType = ListDimensionType;
 
-export const VISUAL_DIMENSIONS = createHashMap([
+export const VISUAL_DIMENSIONS = createHashMap<number, keyof DataVisualDimensions>([
     'tooltip', 'label', 'itemName', 'itemId', 'seriesName'
 ]);
 // The key is VISUAL_DIMENSIONS
@@ -318,11 +318,11 @@ export interface DataVisualDimensions {
 }
 
 export type DimensionDefinition = {
-    type?: string,
-    name: string,
+    type?: ListDimensionType,
+    name: DimensionName,
     displayName?: string
 };
-export type DimensionDefinitionLoose = DimensionDefinition['type'] | DimensionDefinition;
+export type DimensionDefinitionLoose = DimensionDefinition['name'] | DimensionDefinition;
 
 export const SOURCE_FORMAT_ORIGINAL = 'original' as const;
 export const SOURCE_FORMAT_ARRAY_ROWS = 'arrayRows' as const;
@@ -343,7 +343,10 @@ export const SERIES_LAYOUT_BY_COLUMN = 'column' as const;
 export const SERIES_LAYOUT_BY_ROW = 'row' as const;
 
 export type SeriesLayoutBy = typeof SERIES_LAYOUT_BY_COLUMN | typeof SERIES_LAYOUT_BY_ROW;
+// null/undefined/'auto': auto detect header, see "src/data/helper/sourceHelper".
+export type OptionSourceHeader = boolean | 'auto';
 
+export type SeriesDataType = 'main' | 'node' | 'edge';
 
 
 // --------------------------------------------
@@ -445,22 +448,56 @@ export type ECOption = ECUnitOption | {
 };
 
 // series.data or dataset.source
-export type OptionSourceData =
-    ArrayLike<OptionDataItem>
-    | Dictionary<ArrayLike<OptionDataItem>>; // Only for `SOURCE_FORMAT_KEYED_COLUMNS`.
+export type OptionSourceData<
+    VAL extends OptionDataValue = OptionDataValue,
+    ORIITEM extends OptionDataItemOriginal<VAL> = OptionDataItemOriginal<VAL>
+> =
+    OptionSourceDataOriginal<VAL, ORIITEM>
+    | OptionSourceDataObjectRows<VAL>
+    | OptionSourceDataArrayRows<VAL>
+    | OptionSourceDataKeyedColumns<VAL>
+    | OptionSourceDataTypedArray;
+export type OptionDataItemOriginal<
+    VAL extends OptionDataValue = OptionDataValue
+> = VAL | VAL[] | OptionDataItemObject<VAL>;
+export type OptionSourceDataOriginal<
+    VAL extends OptionDataValue = OptionDataValue,
+    ORIITEM extends OptionDataItemOriginal<VAL> = OptionDataItemOriginal<VAL>
+> = ArrayLike<ORIITEM>;
+export type OptionSourceDataObjectRows<VAL extends OptionDataValue = OptionDataValue> =
+    ArrayLike<Dictionary<VAL>>;
+export type OptionSourceDataArrayRows<VAL extends OptionDataValue = OptionDataValue> =
+    ArrayLike<ArrayLike<VAL>>;
+export type OptionSourceDataKeyedColumns<VAL extends OptionDataValue = OptionDataValue> =
+    Dictionary<ArrayLike<VAL>>;
+export type OptionSourceDataTypedArray = ArrayLike<number>;
+
 // See also `model.js#getDataItemValue`.
 export type OptionDataItem =
     OptionDataValue
     | Dictionary<OptionDataValue>
-    | ArrayLike<OptionDataValue>
+    | OptionDataValue[]
     // FIXME: In some case (markpoint in geo (geo-map.html)), dataItem is {coord: [...]}
     | OptionDataItemObject<OptionDataValue>;
 // Only for `SOURCE_FORMAT_KEYED_ORIGINAL`
 export type OptionDataItemObject<T> = {
-    name?: string
-    value?: T[] | T
+    id?: string | number;
+    name?: string;
+    value?: T[] | T;
     selected?: boolean;
 };
+export interface GraphEdgeItemObject<
+    VAL extends OptionDataValue
+> extends OptionDataItemObject<VAL> {
+    /**
+     * Name or index of source node.
+     */
+    source?: string | number
+    /**
+     * Name or index of target node.
+     */
+    target?: string | number
+}
 export type OptionDataValue = string | number | Date;
 
 export type OptionDataValueNumeric = number | '-';
@@ -490,7 +527,7 @@ export interface OptionEncodeVisualDimensions {
 export interface OptionEncode extends OptionEncodeVisualDimensions {
     [coordDim: string]: OptionEncodeValue
 }
-export type OptionEncodeValue = DimensionIndex[] | DimensionIndex | DimensionName[] | DimensionName;
+export type OptionEncodeValue = DimensionLoose | DimensionLoose[];
 export type EncodeDefaulter = (source: Source, dimCount: number) => OptionEncode;
 
 // TODO: TYPE Different callback param for different series
@@ -509,7 +546,7 @@ export interface CallbackDataParams {
     name: string;
     dataIndex: number;
     data: any;
-    dataType?: string;
+    dataType?: SeriesDataType;
     value: any;
     color?: ZRColor;
     borderColor?: string;
@@ -869,7 +906,7 @@ export interface LabelLineOption {
 
 export interface LabelLayoutOptionCallbackParams {
     dataIndex: number,
-    dataType: string,
+    dataType: SeriesDataType,
     seriesIndex: number,
     text: string
     align: ZRTextAlign
@@ -1234,7 +1271,7 @@ export interface SeriesOption<StateOption=any, ExtraStateOpts extends {
     cursor?: string
 
     // Needs to be override
-    data?: any
+    data?: unknown
 
     legendHoverLink?: boolean
 
@@ -1327,6 +1364,7 @@ export interface SeriesSamplingOptionMixin {
 export interface SeriesEncodeOptionMixin {
     datasetIndex?: number;
     seriesLayoutBy?: SeriesLayoutBy;
-    dimensions?: DimensionName[];
+    sourceHeader?: OptionSourceHeader;
+    dimensions?: DimensionDefinitionLoose[];
     encode?: OptionEncode
 }


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


[incubator-echarts] 03/10: feature: data transform

Posted by su...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit e9a2b0f5aee017be1bf41726e6531c38530ce28e
Author: 100pah <su...@gmail.com>
AuthorDate: Fri Jul 31 13:02:59 2020 +0800

    feature: data transform
---
 src/chart/helper/createListFromArray.ts        |   9 +-
 src/chart/parallel/ParallelSeries.ts           |  30 +-
 src/component/dataset.ts                       |  46 +-
 src/{util/ecData.ts => component/transform.ts} |  22 +-
 src/component/transform/filterTransform.ts     | 101 ++++
 src/component/transform/sortTransform.ts       | 223 +++++++++
 src/component/visualMap/PiecewiseModel.ts      |   1 +
 src/data/List.ts                               |  45 +-
 src/data/Source.ts                             |  32 +-
 src/data/helper/dataProvider.ts                | 267 ++++++----
 src/data/helper/parseDataValue.ts              |  73 +++
 src/data/helper/sourceHelper.ts                | 219 ++++----
 src/data/helper/sourceManager.ts               | 368 ++++++++++++++
 src/data/helper/transform.ts                   | 317 ++++++++++++
 src/echarts.ts                                 |   3 +
 src/model/Component.ts                         |   4 +-
 src/model/OptionManager.ts                     |   4 +
 src/model/Series.ts                            |  16 +-
 src/util/conditionalExpression.ts              | 536 ++++++++++++++++++++
 src/util/ecData.ts                             |   4 +-
 src/util/log.ts                                |  67 ++-
 src/util/model.ts                              |   8 +-
 src/util/number.ts                             |   9 +-
 src/util/types.ts                              |   7 +-
 test/data-transform.html                       | 661 +++++++++++++++++++++++++
 25 files changed, 2760 insertions(+), 312 deletions(-)

diff --git a/src/chart/helper/createListFromArray.ts b/src/chart/helper/createListFromArray.ts
index e451cd3..c12ac24 100644
--- a/src/chart/helper/createListFromArray.ts
+++ b/src/chart/helper/createListFromArray.ts
@@ -28,13 +28,13 @@ import Source from '../../data/Source';
 import {enableDataStack} from '../../data/helper/dataStackHelper';
 import {makeSeriesEncodeForAxisCoordSys} from '../../data/helper/sourceHelper';
 import {
-    SOURCE_FORMAT_ORIGINAL, DimensionDefinitionLoose, DimensionDefinition, OptionSourceData
+    SOURCE_FORMAT_ORIGINAL, DimensionDefinitionLoose, DimensionDefinition, OptionSourceData, EncodeDefaulter
 } from '../../util/types';
 import SeriesModel from '../../model/Series';
 
 function createListFromArray(source: Source | OptionSourceData, seriesModel: SeriesModel, opt?: {
     generateCoord?: string
-    useEncodeDefaulter?: boolean
+    useEncodeDefaulter?: boolean | EncodeDefaulter
 }): List {
     opt = opt || {};
 
@@ -73,10 +73,13 @@ function createListFromArray(source: Source | OptionSourceData, seriesModel: Ser
         )) || ['x', 'y'];
     }
 
+    const useEncodeDefaulter = opt.useEncodeDefaulter;
     const dimInfoList = createDimensions(source, {
         coordDimensions: coordSysDimDefs,
         generateCoord: opt.generateCoord,
-        encodeDefaulter: opt.useEncodeDefaulter
+        encodeDefaulter: zrUtil.isFunction(useEncodeDefaulter)
+            ? useEncodeDefaulter
+            : useEncodeDefaulter
             ? zrUtil.curry(makeSeriesEncodeForAxisCoordSys, coordSysDimDefs, seriesModel)
             : null
     });
diff --git a/src/chart/parallel/ParallelSeries.ts b/src/chart/parallel/ParallelSeries.ts
index d0b5a38..287bc2f 100644
--- a/src/chart/parallel/ParallelSeries.ts
+++ b/src/chart/parallel/ParallelSeries.ts
@@ -18,7 +18,7 @@
 */
 
 
-import {each, createHashMap} from 'zrender/src/core/util';
+import {each, bind} from 'zrender/src/core/util';
 import SeriesModel from '../../model/Series';
 import createListFromArray from '../helper/createListFromArray';
 import {
@@ -29,13 +29,15 @@ import {
     SeriesTooltipOption,
     DimensionName,
     OptionDataValue,
-    StatesOptionMixin
+    StatesOptionMixin,
+    OptionEncodeValue,
+    Dictionary,
+    OptionEncode
  } from '../../util/types';
 import GlobalModel from '../../model/Global';
 import List from '../../data/List';
 import { ParallelActiveState, ParallelAxisOption } from '../../coord/parallel/AxisModel';
 import Parallel from '../../coord/parallel/Parallel';
-import Source from '../../data/Source';
 import ParallelModel from '../../coord/parallel/ParallelModel';
 
 type ParallelSeriesDataValue = OptionDataValue[];
@@ -89,12 +91,10 @@ class ParallelSeriesModel extends SeriesModel<ParallelSeriesOption> {
     coordinateSystem: Parallel;
 
 
-    getInitialData(option: ParallelSeriesOption, ecModel: GlobalModel): List {
-        const source = this.getSource();
-
-        setEncodeAndDimensions(source, this);
-
-        return createListFromArray(source, this);
+    getInitialData(this: ParallelSeriesModel, option: ParallelSeriesOption, ecModel: GlobalModel): List {
+        return createListFromArray(this.getSource(), this, {
+            useEncodeDefaulter: bind(makeDefaultEncode, null, this)
+        });
     }
 
     /**
@@ -151,7 +151,7 @@ class ParallelSeriesModel extends SeriesModel<ParallelSeriesOption> {
 
 SeriesModel.registerClass(ParallelSeriesModel);
 
-function setEncodeAndDimensions(source: Source, seriesModel: ParallelSeriesModel): void {
+function makeDefaultEncode(seriesModel: ParallelSeriesModel): OptionEncode {
     // The mapping of parallelAxis dimension to data dimension can
     // be specified in parallelAxis.option.dim. For example, if
     // parallelAxis.option.dim is 'dim3', it mapping to the third
@@ -159,10 +159,6 @@ function setEncodeAndDimensions(source: Source, seriesModel: ParallelSeriesModel
     // Moreover, parallelModel.dimension should not be regarded as data
     // dimensions. Consider dimensions = ['dim4', 'dim2', 'dim6'];
 
-    if (source.encodeDefine) {
-        return;
-    }
-
     const parallelModel = seriesModel.ecModel.getComponent(
         'parallel', seriesModel.get('parallelIndex')
     ) as ParallelModel;
@@ -170,11 +166,13 @@ function setEncodeAndDimensions(source: Source, seriesModel: ParallelSeriesModel
         return;
     }
 
-    const encodeDefine = source.encodeDefine = createHashMap();
+    const encodeDefine: Dictionary<OptionEncodeValue> = {};
     each(parallelModel.dimensions, function (axisDim) {
         const dataDimIndex = convertDimNameToNumber(axisDim);
-        encodeDefine.set(axisDim, dataDimIndex);
+        encodeDefine[axisDim] = dataDimIndex;
     });
+
+    return encodeDefine;
 }
 
 function convertDimNameToNumber(dimName: DimensionName): number {
diff --git a/src/component/dataset.ts b/src/component/dataset.ts
index 8e16f47..26b77ec 100644
--- a/src/component/dataset.ts
+++ b/src/component/dataset.ts
@@ -28,22 +28,33 @@
 
 import ComponentModel from '../model/Component';
 import ComponentView from '../view/Component';
-import {detectSourceFormat} from '../data/helper/sourceHelper';
 import {
-    SERIES_LAYOUT_BY_COLUMN, ComponentOption, SeriesEncodeOptionMixin, OptionSourceData, SeriesLayoutBy
+    SERIES_LAYOUT_BY_COLUMN, ComponentOption, SeriesEncodeOptionMixin,
+    OptionSourceData, SeriesLayoutBy, OptionSourceHeader
 } from '../util/types';
+import { DataTransformOption, PipedDataTransformOption } from '../data/helper/transform';
+import GlobalModel from '../model/Global';
+import Model from '../model/Model';
+import { disableTransformOptionMerge, SourceManager } from '../data/helper/sourceManager';
 
 
-interface DatasetOption extends
+export interface DatasetOption extends
         Pick<ComponentOption, 'type' | 'id' | 'name'>,
         Pick<SeriesEncodeOptionMixin, 'dimensions'> {
     seriesLayoutBy?: SeriesLayoutBy;
-    // null/undefined/'auto': auto detect header, see "src/data/helper/sourceHelper".
-    sourceHeader?: boolean | 'auto';
-    data?: OptionSourceData;
+    sourceHeader?: OptionSourceHeader;
+    source?: OptionSourceData;
+
+    fromDatasetIndex?: number;
+    fromDatasetId?: string;
+    transform?: DataTransformOption | PipedDataTransformOption;
+    // When a transform result more than on results, the results can be referenced only by:
+    // Using `fromDatasetIndex`/`fromDatasetId` and `transfromResultIndex` to retrieve
+    // the results from other dataset.
+    fromTransformResult?: number;
 }
 
-class DatasetModel extends ComponentModel {
+export class DatasetModel<Opts extends DatasetOption = DatasetOption> extends ComponentModel<Opts> {
 
     type = 'dataset';
     static type = 'dataset';
@@ -52,18 +63,33 @@ class DatasetModel extends ComponentModel {
         seriesLayoutBy: SERIES_LAYOUT_BY_COLUMN
     };
 
+    private _sourceManager: SourceManager;
+
+    init(option: Opts, parentModel: Model, ecModel: GlobalModel): void {
+        super.init(option, parentModel, ecModel);
+        this._sourceManager = new SourceManager(this);
+        disableTransformOptionMerge(this);
+    }
+
+    mergeOption(newOption: Opts, ecModel: GlobalModel): void {
+        super.mergeOption(newOption, ecModel);
+        disableTransformOptionMerge(this);
+    }
+
     optionUpdated() {
-        detectSourceFormat(this);
+        this._sourceManager.dirty();
+    }
+
+    getSourceManager() {
+        return this._sourceManager;
     }
 }
 
 ComponentModel.registerClass(DatasetModel);
 
-
 class DatasetView extends ComponentView {
     static type = 'dataset';
     type = 'dataset';
 }
 
 ComponentView.registerClass(DatasetView);
-
diff --git a/src/util/ecData.ts b/src/component/transform.ts
similarity index 63%
copy from src/util/ecData.ts
copy to src/component/transform.ts
index 2dc31fc..d679f8c 100644
--- a/src/util/ecData.ts
+++ b/src/component/transform.ts
@@ -17,19 +17,9 @@
 * under the License.
 */
 
-import Element from 'zrender/src/Element';
-import { DataModel, ECEventData, BlurScope, InnerFocus } from './types';
-import { makeInner } from './model';
-/**
- * ECData stored on graphic element
- */
-export interface ECData {
-    dataIndex?: number;
-    dataModel?: DataModel;
-    eventData?: ECEventData;
-    seriesIndex?: number;
-    dataType?: string;
-    focus?: InnerFocus;
-    blurScope?: BlurScope;
-}
-export const getECData = makeInner<ECData, Element>();
+import * as echarts from '../echarts';
+import {filterTransform} from './transform/filterTransform';
+import {sortTransform} from './transform/sortTransform';
+
+echarts.registerTransform(filterTransform);
+echarts.registerTransform(sortTransform);
diff --git a/src/component/transform/filterTransform.ts b/src/component/transform/filterTransform.ts
new file mode 100644
index 0000000..4b13008
--- /dev/null
+++ b/src/component/transform/filterTransform.ts
@@ -0,0 +1,101 @@
+/*
+* 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.
+*/
+
+import { DataTransformOption, ExternalDataTransform } from '../../data/helper/transform';
+import { DimensionIndex, OptionDataItem } from '../../util/types';
+import { parseConditionalExpression, ConditionalExpressionOption } from '../../util/conditionalExpression';
+import { hasOwn, createHashMap } from 'zrender/src/core/util';
+import { makePrintable, throwError } from '../../util/log';
+
+
+export interface FilterTransformOption extends DataTransformOption {
+    type: 'filter';
+    config: ConditionalExpressionOption;
+}
+
+export const filterTransform: ExternalDataTransform<FilterTransformOption> = {
+
+    type: 'echarts:filter',
+
+    // PEDING: enhance to filter by index rather than create new data
+    transform: function transform(params) {
+        // [Caveat] Fail-Fast:
+        // Do not return the whole dataset unless user config indicate it explicitly.
+        // For example, if no condition specified by mistake, return an empty result
+        // is better than return the entire raw soruce for user to find the mistake.
+
+        const source = params.source;
+        let rawItem: OptionDataItem;
+
+        const condition = parseConditionalExpression<{ dimIdx: DimensionIndex }>(params.config, {
+
+            valueGetterAttrMap: createHashMap<boolean, string>({ dimension: true }),
+
+            prepareGetValue: function (exprOption) {
+                let errMsg = '';
+                const dimLoose = exprOption.dimension;
+                if (!hasOwn(exprOption, 'dimension')) {
+                    if (__DEV__) {
+                        errMsg = makePrintable(
+                            'Relation condition must has prop "dimension" specified.',
+                            'Illegal condition:', exprOption
+                        );
+                    }
+                    throwError(errMsg);
+                }
+
+                const dimInfo = source.getDimensionInfo(dimLoose);
+                if (!dimInfo) {
+                    if (__DEV__) {
+                        errMsg = makePrintable(
+                            'Can not find dimension info via: "' + dimLoose + '".\n',
+                            'Existing dimensions: ', source.dimensions, '.\n',
+                            'Illegal condition:', exprOption, '.\n'
+                        );
+                    }
+                    throwError(errMsg);
+                }
+
+                return { dimIdx: dimInfo.index };
+            },
+
+            getValue: function (param) {
+                return source.retrieveItemValue(rawItem, param.dimIdx);
+            }
+        });
+
+        const sourceHeaderCount = source.sourceHeaderCount;
+        const resultData = [];
+        for (let i = 0; i < sourceHeaderCount; i++) {
+            resultData.push(source.getRawHeaderItem(i));
+        }
+        for (let i = 0, len = source.count(); i < len; i++) {
+            rawItem = source.getRawDataItem(i);
+            if (condition.evaluate()) {
+                resultData.push(rawItem);
+            }
+        }
+
+        return {
+            data: resultData,
+            dimensions: source.dimensions,
+            sourceHeader: sourceHeaderCount
+        };
+    }
+};
diff --git a/src/component/transform/sortTransform.ts b/src/component/transform/sortTransform.ts
new file mode 100644
index 0000000..9dfce97
--- /dev/null
+++ b/src/component/transform/sortTransform.ts
@@ -0,0 +1,223 @@
+/*
+* 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.
+*/
+
+import { DataTransformOption, ExternalDataTransform } from '../../data/helper/transform';
+import {
+    DimensionLoose, SOURCE_FORMAT_KEYED_COLUMNS, DimensionIndex, OptionDataValue
+} from '../../util/types';
+import { makePrintable, throwError } from '../../util/log';
+import { isArray, each, hasOwn } from 'zrender/src/core/util';
+import { normalizeToArray } from '../../util/model';
+import { parseDate } from '../../util/number';
+
+/**
+ * @usage
+ *
+ * ```js
+ * transform: {
+ *     type: 'sort',
+ *     config: { dimension: 'score', order: 'asc' }
+ * }
+ * transform: {
+ *     type: 'sort',
+ *     config: [
+ *         { dimension: 1, order: 'asc' },
+ *         { dimension: 'age', order: 'desc' }
+ *     ]
+ * }
+ * ```
+ */
+
+export interface SortTransformOption extends DataTransformOption {
+    type: 'sort';
+    config: OrderExpression | OrderExpression[];
+}
+
+// PENDING: whether support { dimension: 'score', order: 'asc' } ?
+type OrderExpression = {
+    dimension: DimensionLoose;
+    order: SortOrder;
+    parse?: 'time'
+};
+
+type SortOrder = 'asc' | 'desc';
+const SortOrderValidMap = { asc: true, desc: true } as const;
+
+let sampleLog = '';
+if (__DEV__) {
+    sampleLog = [
+        'Valid config is like:',
+        '{ dimension: "age", order: "asc" }',
+        'or [{ dimension: "age", order: "asc"], { dimension: "date", order: "desc" }]'
+    ].join('');
+}
+
+const timeParser = function (val: OptionDataValue): number {
+    return +parseDate(val);
+};
+
+
+export const sortTransform: ExternalDataTransform<SortTransformOption> = {
+
+    type: 'echarts:sort',
+
+    transform: function transform(params) {
+        const source = params.source;
+        const config = params.config;
+        let errMsg = '';
+
+        // Normalize
+        // const orderExprList: OrderExpression[] = isArray(config[0])
+        //     ? config as OrderExpression[]
+        //     : [config as OrderExpression];
+        const orderExprList: OrderExpression[] = normalizeToArray(config);
+
+        if (!orderExprList.length) {
+            if (__DEV__) {
+                errMsg = 'Empty `config` in sort transform.';
+            }
+            throwError(errMsg);
+        }
+
+        const orderDefList: {
+            dimIdx: DimensionIndex;
+            orderReturn: -1 | 1;
+            parser: (val: OptionDataValue) => number;
+        }[] = [];
+        each(orderExprList, function (orderExpr) {
+            const dimLoose = orderExpr.dimension;
+            const order = orderExpr.order;
+            const parserName = orderExpr.parse;
+
+            if (dimLoose == null) {
+                if (__DEV__) {
+                    errMsg = 'Sort transform config must has "dimension" specified.' + sampleLog;
+                }
+                throwError(errMsg);
+            }
+
+            if (!hasOwn(SortOrderValidMap, order)) {
+                if (__DEV__) {
+                    errMsg = 'Sort transform config must has "order" specified.' + sampleLog;
+                }
+                throwError(errMsg);
+            }
+
+            const dimInfo = source.getDimensionInfo(dimLoose);
+            if (!dimInfo) {
+                if (__DEV__) {
+                    errMsg = makePrintable(
+                        'Can not find dimension info via: "' + dimLoose + '".\n',
+                        'Existing dimensions: ', source.dimensions, '.\n',
+                        'Illegal config:', orderExpr, '.\n'
+                    );
+                }
+                throwError(errMsg);
+            }
+
+            let parser;
+            if (parserName) {
+                if (parserName !== 'time') {
+                    if (__DEV__) {
+                        errMsg = makePrintable(
+                            'Invalid parser name' + parserName + '.\n',
+                            'Illegal config:', orderExpr, '.\n'
+                        );
+                    }
+                    throwError(errMsg);
+                }
+                parser = timeParser;
+            }
+
+            orderDefList.push({
+                dimIdx: dimInfo.index,
+                orderReturn: order === 'asc' ? -1 : 1,
+                parser: parser
+            });
+        });
+
+        // TODO: support it?
+        if (!isArray(source.data)) {
+            if (__DEV__) {
+                errMsg = source.sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS
+                    ? 'sourceFormat ' + SOURCE_FORMAT_KEYED_COLUMNS + ' is not supported yet'
+                    : source.data == null
+                    ? 'Upstream source data is null/undefined'
+                    : 'Unsupported source format.';
+            }
+            throwError(errMsg);
+        }
+
+        // Other source format are all array.
+        const sourceHeaderCount = source.sourceHeaderCount;
+        const resultData = [];
+        const headerPlaceholder = {};
+        for (let i = 0; i < sourceHeaderCount; i++) {
+            resultData.push(headerPlaceholder);
+        }
+        for (let i = 0, len = source.count(); i < len; i++) {
+            resultData.push(source.getRawDataItem(i));
+        }
+
+        resultData.sort(function (item0, item1) {
+            if (item0 === headerPlaceholder) {
+                return -1;
+            }
+            if (item1 === headerPlaceholder) {
+                return 1;
+            }
+            // FIXME: check other empty?
+            // Always put empty item last?
+            if (item0 == null) {
+                return 1;
+            }
+            if (item1 == null) {
+                return -1;
+            }
+            // TODO Optimize a little: manually loop unrolling?
+            for (let i = 0; i < orderDefList.length; i++) {
+                const orderDef = orderDefList[i];
+                let val0 = source.retrieveItemValue(item0, orderDef.dimIdx);
+                let val1 = source.retrieveItemValue(item1, orderDef.dimIdx);
+                if (orderDef.parser) {
+                    val0 = orderDef.parser(val0);
+                    val1 = orderDef.parser(val1);
+                }
+                if (val0 < val1) {
+                    return orderDef.orderReturn;
+                }
+                else if (val0 > val1) {
+                    return -orderDef.orderReturn;
+                }
+            }
+            return 0;
+        });
+
+        for (let i = 0; i < sourceHeaderCount; i++) {
+            resultData[i] = source.getRawHeaderItem(i);
+        }
+
+        return {
+            data: resultData,
+            dimensions: source.dimensions,
+            sourceHeader: sourceHeaderCount
+        };
+    }
+};
+
diff --git a/src/component/visualMap/PiecewiseModel.ts b/src/component/visualMap/PiecewiseModel.ts
index 64e5f5e..d8d0785 100644
--- a/src/component/visualMap/PiecewiseModel.ts
+++ b/src/component/visualMap/PiecewiseModel.ts
@@ -28,6 +28,7 @@ import ComponentModel from '../../model/Component';
 import { inheritDefaultOption } from '../../util/component';
 
 
+// TODO: use `relationExpression.ts` instead
 interface VisualPiece extends VisualOptionPiecewise {
     min?: number
     max?: number
diff --git a/src/data/List.ts b/src/data/List.ts
index b40492c..871f5fd 100644
--- a/src/data/List.ts
+++ b/src/data/List.ts
@@ -36,13 +36,13 @@ import {
     DimensionIndex, DimensionName, DimensionLoose, OptionDataItem,
     ParsedValue, ParsedValueNumeric, OrdinalNumber, DimensionUserOuput, ModelOption, SeriesDataType
 } from '../util/types';
-import {parseDate} from '../util/number';
 import {isDataItemOption} from '../util/model';
 import { getECData } from '../util/ecData';
 import { PathStyleProps } from 'zrender/src/graphic/Path';
 import type Graph from './Graph';
 import type Tree from './Tree';
 import type { VisualMeta } from '../component/visualMap/VisualMapModel';
+import { parseDataValue } from './helper/parseDataValue';
 
 
 const isObject = zrUtil.isObject;
@@ -1916,7 +1916,7 @@ class List<
             objectRows: function (
                 this: List, dataItem: Dictionary<any>, dimName: string, dataIndex: number, dimIndex: number
             ): ParsedValue {
-                return convertDataValue(dataItem[dimName], this._dimensionInfos[dimName]);
+                return parseDataValue(dataItem[dimName], this._dimensionInfos[dimName]);
             },
 
             keyedColumns: getDimValueSimply,
@@ -1934,7 +1934,7 @@ class List<
                 if (!this._rawData.pure && isDataItemOption(dataItem)) {
                     this.hasItemOption = true;
                 }
-                return convertDataValue(
+                return parseDataValue(
                     (value instanceof Array)
                         ? value[dimIndex]
                         // If value is a single number or something else not array.
@@ -1954,43 +1954,8 @@ class List<
         function getDimValueSimply(
             this: List, dataItem: any, dimName: string, dataIndex: number, dimIndex: number
         ): ParsedValue {
-            return convertDataValue(dataItem[dimIndex], this._dimensionInfos[dimName]);
-        }
-
-        /**
-         * 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): ParsedValue {
-            // Performance sensitive.
-            const dimType = dimInfo && dimInfo.type;
-            if (dimType === 'ordinal') {
-                // If given value is a category string
-                const 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;
-        };
+            return parseDataValue(dataItem[dimIndex], this._dimensionInfos[dimName]);
+        }
 
         prepareInvertedIndex = function (list: List): void {
             const invertedIndicesMap = list._invertedIndicesMap;
diff --git a/src/data/Source.ts b/src/data/Source.ts
index 10d62af..bed37d6 100644
--- a/src/data/Source.ts
+++ b/src/data/Source.ts
@@ -67,11 +67,8 @@ import {
 
 class Source {
 
-    readonly fromDataset: boolean;
-
     /**
      * Not null/undefined.
-     * @type {Array|Object}
      */
     readonly data: OptionSourceData;
 
@@ -98,7 +95,7 @@ class Source {
      * can be null/undefined.
      * Might be specified outside.
      */
-    encodeDefine: HashMap<OptionEncodeValue, DimensionName>;
+    readonly encodeDefine: HashMap<OptionEncodeValue, DimensionName>;
 
     /**
      * Not null/undefined, uint.
@@ -112,27 +109,33 @@ class Source {
 
 
     constructor(fields: {
-        fromDataset: boolean,
-        data?: OptionSourceData,
-        sourceFormat?: SourceFormat, // default: SOURCE_FORMAT_UNKNOWN
+        data: OptionSourceData,
+        sourceFormat: SourceFormat, // default: SOURCE_FORMAT_UNKNOWN
+
+        // Visit config are optional:
         seriesLayoutBy?: SeriesLayoutBy, // default: 'column'
         dimensionsDefine?: DimensionDefinition[],
-        encodeDefine?: OptionEncode,
         startIndex?: number, // default: 0
-        dimensionsDetectCount?: number
+        dimensionsDetectCount?: number,
+
+        // [Caveat]
+        // This is the raw user defined `encode` in `series`.
+        // If user not defined, DO NOT make a empty object or hashMap here.
+        // An empty object or hashMap will prevent from auto generating encode.
+        encodeDefine?: HashMap<OptionEncodeValue, DimensionName>
     }) {
 
-        this.fromDataset = fields.fromDataset;
         this.data = fields.data || (
             fields.sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS ? {} : []
         );
         this.sourceFormat = fields.sourceFormat || SOURCE_FORMAT_UNKNOWN;
+
+        // Visit config
         this.seriesLayoutBy = fields.seriesLayoutBy || SERIES_LAYOUT_BY_COLUMN;
-        this.dimensionsDefine = fields.dimensionsDefine;
-        this.encodeDefine = fields.encodeDefine
-            && createHashMap<OptionEncodeValue, DimensionName>(fields.encodeDefine);
         this.startIndex = fields.startIndex || 0;
+        this.dimensionsDefine = fields.dimensionsDefine;
         this.dimensionsDetectCount = fields.dimensionsDetectCount;
+        this.encodeDefine = fields.encodeDefine;
     }
 
     /**
@@ -143,8 +146,7 @@ class Source {
             data: data,
             sourceFormat: isTypedArray(data)
                 ? SOURCE_FORMAT_TYPED_ARRAY
-                : SOURCE_FORMAT_ORIGINAL,
-            fromDataset: false
+                : SOURCE_FORMAT_ORIGINAL
         });
     };
 }
diff --git a/src/data/helper/dataProvider.ts b/src/data/helper/dataProvider.ts
index 3363597..788610a 100644
--- a/src/data/helper/dataProvider.ts
+++ b/src/data/helper/dataProvider.ts
@@ -21,7 +21,7 @@
 // ??? refactor? check the outer usage of data provider.
 // merge with defaultDimValueGetter?
 
-import {isTypedArray, extend, assert, each, isObject} from 'zrender/src/core/util';
+import {isTypedArray, extend, assert, each, isObject, bind} from 'zrender/src/core/util';
 import {getDataItemValue} from '../../util/model';
 import Source from '../Source';
 import {ArrayLike, Dictionary} from 'zrender/src/core/types';
@@ -34,7 +34,7 @@ import {
     SERIES_LAYOUT_BY_COLUMN,
     SERIES_LAYOUT_BY_ROW,
     DimensionName, DimensionIndex, OptionSourceData,
-    DimensionIndexLoose, OptionDataItem, OptionDataValue
+    DimensionIndexLoose, OptionDataItem, OptionDataValue, DimensionDefinition, SourceFormat, SeriesLayoutBy
 } from '../../util/types';
 import List from '../List';
 
@@ -54,6 +54,7 @@ export interface DataProvider {
 
 
 let providerMethods: Dictionary<any>;
+let mountMethods: (provider: DefaultDataProvider, data: OptionSourceData, source: Source) => void;
 
 /**
  * If normal array used, mutable chunk size is supported.
@@ -90,12 +91,10 @@ export class DefaultDataProvider implements DataProvider {
 
         // declare source is Source;
         this._source = source;
-
         const data = this._data = source.data;
-        const sourceFormat = source.sourceFormat;
 
         // Typed array. TODO IE10+?
-        if (sourceFormat === SOURCE_FORMAT_TYPED_ARRAY) {
+        if (source.sourceFormat === SOURCE_FORMAT_TYPED_ARRAY) {
             if (__DEV__) {
                 if (dimSize == null) {
                     throw new Error('Typed array data must specify dimension size');
@@ -106,17 +105,7 @@ export class DefaultDataProvider implements DataProvider {
             this._data = data;
         }
 
-        const methods = providerMethods[
-            sourceFormat === SOURCE_FORMAT_ARRAY_ROWS
-            ? sourceFormat + '_' + source.seriesLayoutBy
-            : sourceFormat
-        ];
-
-        if (__DEV__) {
-            assert(methods, 'Invalide sourceFormat: ' + sourceFormat);
-        }
-
-        extend(this, methods);
+        mountMethods(this, data, source);
     }
 
     getSource(): Source {
@@ -127,7 +116,7 @@ export class DefaultDataProvider implements DataProvider {
         return 0;
     }
 
-    getItem(idx: number): OptionDataItem {
+    getItem(idx: number, out?: ArrayLike<number>): OptionDataItem {
         return;
     }
 
@@ -139,35 +128,58 @@ export class DefaultDataProvider implements DataProvider {
 
     private static internalField = (function () {
 
+        mountMethods = function (provider, data, source) {
+            const sourceFormat = source.sourceFormat;
+            const seriesLayoutBy = source.seriesLayoutBy;
+            const startIndex = source.startIndex;
+            const dimsDef = source.dimensionsDefine;
+
+            const methods = providerMethods[getMethodMapKey(sourceFormat, seriesLayoutBy)];
+            if (__DEV__) {
+                assert(methods, 'Invalide sourceFormat: ' + sourceFormat);
+            }
+
+            extend(provider, methods);
+
+            if (sourceFormat === SOURCE_FORMAT_TYPED_ARRAY) {
+                provider.getItem = getItemForTypedArray;
+                provider.count = countForTypedArray;
+            }
+            else {
+                const rawItemGetter = getRawSourceItemGetter(sourceFormat, seriesLayoutBy);
+                provider.getItem = bind(rawItemGetter, null, data, startIndex, dimsDef);
+                const rawCounter = getRawSourceDataCounter(sourceFormat, seriesLayoutBy);
+                provider.count = bind(rawCounter, null, data, startIndex, dimsDef);
+            }
+        };
+
+        const getItemForTypedArray: DefaultDataProvider['getItem'] = function (
+            this: DefaultDataProvider, idx: number, out: ArrayLike<number>
+        ): ArrayLike<number> {
+            idx = idx - this._offset;
+            out = out || [];
+            const offset = this._dimSize * idx;
+            for (let i = 0; i < this._dimSize; i++) {
+                out[i] = (this._data as ArrayLike<number>)[offset + i];
+            }
+            return out;
+        };
+
+        const countForTypedArray: DefaultDataProvider['count'] = function (
+            this: DefaultDataProvider
+        ) {
+            return this._data ? ((this._data as ArrayLike<number>).length / this._dimSize) : 0;
+        };
+
         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): OptionDataValue[] {
-                    return (this._data as OptionDataValue[][])[idx + this._source.startIndex];
-                },
                 appendData: appendDataSimply
             },
 
             [SOURCE_FORMAT_ARRAY_ROWS + '_' + SERIES_LAYOUT_BY_ROW]: {
                 pure: true,
-                count: function (this: DefaultDataProvider): number {
-                    const row = (this._data as OptionDataValue[][])[0];
-                    return row ? Math.max(0, row.length - this._source.startIndex) : 0;
-                },
-                getItem: function (this: DefaultDataProvider, idx: number): OptionDataValue[] {
-                    idx += this._source.startIndex;
-                    const item = [];
-                    const data = this._data as OptionDataValue[][];
-                    for (let i = 0; i < data.length; i++) {
-                        const row = data[i];
-                        item.push(row ? row[idx] : null);
-                    }
-                    return item;
-                },
                 appendData: function () {
                     throw new Error('Do not support appendData when set seriesLayoutBy: "row".');
                 }
@@ -175,27 +187,11 @@ export class DefaultDataProvider implements DataProvider {
 
             [SOURCE_FORMAT_OBJECT_ROWS]: {
                 pure: true,
-                count: countSimply,
-                getItem: getItemSimply,
                 appendData: appendDataSimply
             },
 
             [SOURCE_FORMAT_KEYED_COLUMNS]: {
                 pure: true,
-                count: function (this: DefaultDataProvider): number {
-                    const dimName = this._source.dimensionsDefine[0].name;
-                    const col = (this._data as Dictionary<OptionDataValue[]>)[dimName];
-                    return col ? col.length : 0;
-                },
-                getItem: function (this: DefaultDataProvider, idx: number): OptionDataValue[] {
-                    const item = [];
-                    const dims = this._source.dimensionsDefine;
-                    for (let i = 0; i < dims.length; i++) {
-                        const col = (this._data as Dictionary<OptionDataValue[]>)[dims[i].name];
-                        item.push(col ? col[idx] : null);
-                    }
-                    return item;
-                },
                 appendData: function (this: DefaultDataProvider, newData: Dictionary<OptionDataValue[]>) {
                     const data = this._data as Dictionary<OptionDataValue[]>;
                     each(newData, function (newCol, key) {
@@ -208,26 +204,12 @@ export class DefaultDataProvider implements DataProvider {
             },
 
             [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 || [];
-                    const offset = this._dimSize * idx;
-                    for (let 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(
@@ -235,7 +217,6 @@ export class DefaultDataProvider implements DataProvider {
                             'Added data must be TypedArray if data in initialization is TypedArray'
                         );
                     }
-
                     this._data = newData;
                 },
 
@@ -248,12 +229,6 @@ export class DefaultDataProvider implements DataProvider {
             }
         };
 
-        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 (let i = 0; i < newData.length; i++) {
                 (this._data as any[]).push(newData[i]);
@@ -262,23 +237,136 @@ export class DefaultDataProvider implements DataProvider {
 
     })();
 }
+
+
+
+type RawSourceItemGetter = (
+    rawData: OptionSourceData,
+    startIndex: number,
+    dimsDef: DimensionDefinition[],
+    idx: number
+) => OptionDataItem;
+
+const getItemSimply: RawSourceItemGetter = function (
+    rawData, startIndex, dimsDef, idx
+): OptionDataItem {
+    return (rawData as [])[idx];
+};
+
+const rawSourceItemGetterMap: Dictionary<RawSourceItemGetter> = {
+    [SOURCE_FORMAT_ARRAY_ROWS + '_' + SERIES_LAYOUT_BY_COLUMN]: function (
+        rawData, startIndex, dimsDef, idx
+    ): OptionDataValue[] {
+        return (rawData as OptionDataValue[][])[idx + startIndex];
+    },
+    [SOURCE_FORMAT_ARRAY_ROWS + '_' + SERIES_LAYOUT_BY_ROW]: function (
+        rawData, startIndex, dimsDef, idx
+    ): OptionDataValue[] {
+        idx += startIndex;
+        const item = [];
+        const data = rawData as OptionDataValue[][];
+        for (let i = 0; i < data.length; i++) {
+            const row = data[i];
+            item.push(row ? row[idx] : null);
+        }
+        return item;
+    },
+    [SOURCE_FORMAT_OBJECT_ROWS]: getItemSimply,
+    [SOURCE_FORMAT_KEYED_COLUMNS]: function (
+        rawData, startIndex, dimsDef, idx
+    ): OptionDataValue[] {
+        const item = [];
+        for (let i = 0; i < dimsDef.length; i++) {
+            const col = (rawData as Dictionary<OptionDataValue[]>)[dimsDef[i].name];
+            item.push(col ? col[idx] : null);
+        }
+        return item;
+    },
+    [SOURCE_FORMAT_ORIGINAL]: getItemSimply
+};
+
+export function getRawSourceItemGetter(
+    sourceFormat: SourceFormat, seriesLayoutBy: SeriesLayoutBy
+): RawSourceItemGetter {
+    const method = rawSourceItemGetterMap[getMethodMapKey(sourceFormat, seriesLayoutBy)];
+    if (__DEV__) {
+        assert(method, 'Do not suppport get item on "' + sourceFormat + '", "' + seriesLayoutBy + '".');
+    }
+    return method;
+}
+
+
+
+
+type RawSourceDataCounter = (
+    rawData: OptionSourceData,
+    startIndex: number,
+    dimsDef: DimensionDefinition[]
+) => number;
+
+const countSimply: RawSourceDataCounter = function (
+    rawData, startIndex, dimsDef
+) {
+    return (rawData as []).length;
+};
+
+const rawSourceDataCounterMap: Dictionary<RawSourceDataCounter> = {
+    [SOURCE_FORMAT_ARRAY_ROWS + '_' + SERIES_LAYOUT_BY_COLUMN]: function (
+        rawData, startIndex, dimsDef
+    ) {
+        return Math.max(0, (rawData as OptionDataItem[][]).length - startIndex);
+    },
+    [SOURCE_FORMAT_ARRAY_ROWS + '_' + SERIES_LAYOUT_BY_ROW]: function (
+        rawData, startIndex, dimsDef
+    ) {
+        const row = (rawData as OptionDataValue[][])[0];
+        return row ? Math.max(0, row.length - startIndex) : 0;
+    },
+    [SOURCE_FORMAT_OBJECT_ROWS]: countSimply,
+    [SOURCE_FORMAT_KEYED_COLUMNS]: function (
+        rawData, startIndex, dimsDef
+    ) {
+        const dimName = dimsDef[0].name;
+        const col = (rawData as Dictionary<OptionDataValue[]>)[dimName];
+        return col ? col.length : 0;
+    },
+    [SOURCE_FORMAT_ORIGINAL]: countSimply
+};
+
+export function getRawSourceDataCounter(
+    sourceFormat: SourceFormat, seriesLayoutBy: SeriesLayoutBy
+): RawSourceDataCounter {
+    const method = rawSourceDataCounterMap[getMethodMapKey(sourceFormat, seriesLayoutBy)];
+    if (__DEV__) {
+        assert(method, 'Do not suppport count on "' + sourceFormat + '", "' + seriesLayoutBy + '".');
+    }
+    return method;
+}
+
+
+
 // TODO
 // merge it to dataProvider?
-type RawValueGetter = (
+type RawSourceValueGetter = (
     dataItem: OptionDataItem,
-    dataIndex: number,
     dimIndex: DimensionIndex,
     dimName: DimensionName
     // If dimIndex not provided, return OptionDataItem.
     // If dimIndex provided, return OptionDataPrimitive.
 ) => OptionDataValue | OptionDataItem;
 
-const rawValueGetters: {[sourceFormat: string]: RawValueGetter} = {
+const getRawValueSimply = function (
+    dataItem: ArrayLike<OptionDataValue>, dimIndex: number, dimName: string
+): OptionDataValue | ArrayLike<OptionDataValue> {
+    return dimIndex != null ? dataItem[dimIndex] : dataItem;
+};
+
+const rawSourceValueGetterMap: {[sourceFormat: string]: RawSourceValueGetter} = {
 
     [SOURCE_FORMAT_ARRAY_ROWS]: getRawValueSimply,
 
     [SOURCE_FORMAT_OBJECT_ROWS]: function (
-        dataItem: Dictionary<OptionDataValue>, dataIndex: number, dimIndex: number, dimName: string
+        dataItem: Dictionary<OptionDataValue>, dimIndex: number, dimName: string
     ): OptionDataValue | Dictionary<OptionDataValue> {
         return dimIndex != null ? dataItem[dimName] : dataItem;
     },
@@ -286,7 +374,7 @@ const rawValueGetters: {[sourceFormat: string]: RawValueGetter} = {
     [SOURCE_FORMAT_KEYED_COLUMNS]: getRawValueSimply,
 
     [SOURCE_FORMAT_ORIGINAL]: function (
-        dataItem: OptionDataItem, dataIndex: number, dimIndex: number, dimName: string
+        dataItem: OptionDataItem, dimIndex: number, dimName: string
     ): OptionDataValue | OptionDataItem {
         // FIXME: In some case (markpoint in geo (geo-map.html)),
         // dataItem is {coord: [...]}
@@ -299,12 +387,22 @@ const rawValueGetters: {[sourceFormat: string]: RawValueGetter} = {
     [SOURCE_FORMAT_TYPED_ARRAY]: getRawValueSimply
 };
 
-function getRawValueSimply(
-    dataItem: ArrayLike<OptionDataValue>, dataIndex: number, dimIndex: number, dimName: string
-): OptionDataValue | ArrayLike<OptionDataValue> {
-    return dimIndex != null ? dataItem[dimIndex] : dataItem;
+export function getRawSourceValueGetter(sourceFormat: SourceFormat): RawSourceValueGetter {
+    const method = rawSourceValueGetterMap[sourceFormat];
+    if (__DEV__) {
+        assert(method, 'Do not suppport get value on "' + sourceFormat + '".');
+    }
+    return method;
 }
 
+
+function getMethodMapKey(sourceFormat: SourceFormat, seriesLayoutBy: SeriesLayoutBy): string {
+    return sourceFormat === SOURCE_FORMAT_ARRAY_ROWS
+        ? sourceFormat + '_' + seriesLayoutBy
+        : sourceFormat;
+}
+
+
 // ??? FIXME can these logic be more neat: getRawValue, getRawDataItem,
 // Consider persistent.
 // Caution: why use raw value to display on label or tooltip?
@@ -336,9 +434,10 @@ export function retrieveRawValue(
         dimIndex = dimInfo.index;
     }
 
-    return rawValueGetters[sourceFormat](dataItem, dataIndex, dimIndex, dimName);
+    return getRawSourceValueGetter(sourceFormat)(dataItem, dimIndex, dimName);
 }
 
+
 /**
  * Compatible with some cases (in pie, map) like:
  * data: [{name: 'xx', value: 5, selected: true}, ...]
diff --git a/src/data/helper/parseDataValue.ts b/src/data/helper/parseDataValue.ts
new file mode 100644
index 0000000..6994378
--- /dev/null
+++ b/src/data/helper/parseDataValue.ts
@@ -0,0 +1,73 @@
+/*
+* 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.
+*/
+
+import { ParsedValue, DimensionType } from '../../util/types';
+import OrdinalMeta from '../OrdinalMeta';
+import { parseDate } from '../../util/number';
+
+
+/**
+ * Convert raw the value in to inner value in List.
+ *
+ * [Performance sensitive]
+ *
+ * [Caution]: this is the key logic of user value parser.
+ * For backward compatibiliy, do not modify it until have to !
+ */
+export function parseDataValue(
+    value: any,
+    // For high performance, do not omit the second param.
+    opt: {
+        // Default type: 'number'. There is no 'unknown' type. That is, a string
+        // will be parsed to NaN if do not set `type` as 'ordinal'. It has been
+        // the logic in `List.ts` for long time. Follow the same way if you need
+        // to get same result as List did from a raw value.
+        type?: DimensionType,
+        ordinalMeta?: OrdinalMeta
+    }
+): ParsedValue {
+    // Performance sensitive.
+    const dimType = opt && opt.type;
+    if (dimType === 'ordinal') {
+        // If given value is a category string
+        const ordinalMeta = opt && opt.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;
+};
+
diff --git a/src/data/helper/sourceHelper.ts b/src/data/helper/sourceHelper.ts
index 8f9d7cb..9ae0151 100644
--- a/src/data/helper/sourceHelper.ts
+++ b/src/data/helper/sourceHelper.ts
@@ -18,7 +18,7 @@
 */
 
 
-import {makeInner, getDataItemValue} from '../../util/model';
+import {makeInner, getDataItemValue, queryReferringComponents, SINGLE_REFERRING} from '../../util/model';
 import {
     createHashMap,
     each,
@@ -31,7 +31,9 @@ import {
     extend,
     assert,
     hasOwn,
-    HashMap
+    HashMap,
+    isNumber,
+    clone
 } from 'zrender/src/core/util';
 import Source from '../Source';
 
@@ -45,8 +47,6 @@ import {
     SOURCE_FORMAT_UNKNOWN,
     SourceFormat,
     Dictionary,
-    SeriesEncodeOptionMixin,
-    SeriesOption,
     OptionSourceData,
     SeriesLayoutBy,
     OptionSourceHeader,
@@ -59,9 +59,11 @@ import {
     OptionSourceDataOriginal,
     OptionSourceDataObjectRows,
     OptionEncode,
-    DimensionIndex
+    DimensionIndex,
+    SeriesEncodableModel,
+    OptionEncodeValue
 } from '../../util/types';
-import { DatasetModel } from '../../component/dataset';
+import { DatasetModel, DatasetOption } from '../../component/dataset';
 import SeriesModel from '../../model/Series';
 import GlobalModel from '../../model/Global';
 import { CoordDimensionDefinition } from './createDimensions';
@@ -74,16 +76,11 @@ export const BE_ORDINAL = {
 };
 type BeOrdinalValue = (typeof BE_ORDINAL)[keyof typeof BE_ORDINAL];
 
-const innerDatasetModel = makeInner<{
-    sourceFormat: SourceFormat;
-}, DatasetModel>();
-const innerSeriesModel = makeInner<{
-    source: Source;
-}, SeriesModel>();
 const innerGlobalModel = makeInner<{
     datasetMap: HashMap<DatasetRecord, string>
 }, GlobalModel>();
 
+
 interface DatasetRecord {
     categoryWayDim: number;
     valueWayDim: number;
@@ -93,11 +90,13 @@ type SeriesEncodeInternal = {
     [key in keyof OptionEncode]: DimensionIndex[];
 };
 
-type SeriesEncodableModel = SeriesModel<SeriesOption & SeriesEncodeOptionMixin>;
-
+export interface SourceMetaRawOption {
+    seriesLayoutBy: SeriesLayoutBy;
+    sourceHeader: OptionSourceHeader;
+    dimensions: DimensionDefinitionLoose[];
+}
 
-export function detectSourceFormat(datasetModel: DatasetModel): void {
-    const data = datasetModel.option.source;
+export function detectSourceFormat(data: DatasetOption['source']): SourceFormat {
     let sourceFormat: SourceFormat = SOURCE_FORMAT_UNKNOWN;
 
     if (isTypedArray(data)) {
@@ -137,33 +136,7 @@ export function detectSourceFormat(datasetModel: DatasetModel): void {
         throw new Error('Invalid data');
     }
 
-    innerDatasetModel(datasetModel).sourceFormat = sourceFormat;
-}
-
-/**
- * [Scenarios]:
- * (1) Provide source data directly:
- *     series: {
- *         encode: {...},
- *         dimensions: [...]
- *         seriesLayoutBy: 'row',
- *         data: [[...]]
- *     }
- * (2) Refer to datasetModel.
- *     series: [{
- *         encode: {...}
- *         // Ignore datasetIndex means `datasetIndex: 0`
- *         // and the dimensions defination in dataset is used
- *     }, {
- *         encode: {...},
- *         seriesLayoutBy: 'column',
- *         datasetIndex: 1
- *     }]
- *
- * Get data from series itself or datset.
- */
-export function getSource(seriesModel: SeriesModel): Source {
-    return innerSeriesModel(seriesModel).source;
+    return sourceFormat;
 }
 
 /**
@@ -174,65 +147,63 @@ export function resetSourceDefaulter(ecModel: GlobalModel): void {
     innerGlobalModel(ecModel).datasetMap = createHashMap();
 }
 
-/**
- * [Caution]:
- * MUST be called after series option merged and
- * before "series.getInitailData()" called.
- *
- * [The rule of making default encode]:
- * Category axis (if exists) alway map to the first dimension.
- * Each other axis occupies a subsequent dimension.
- *
- * [Why make default encode]:
- * Simplify the typing of encode in option, avoiding the case like that:
- * series: [{encode: {x: 0, y: 1}}, {encode: {x: 0, y: 2}}, {encode: {x: 0, y: 3}}],
- * where the "y" have to be manually typed as "1, 2, 3, ...".
- */
-export function prepareSource(seriesModel: SeriesEncodableModel): void {
-    const seriesOption = seriesModel.option;
-
-    let data = seriesOption.data as OptionSourceData;
-    let sourceFormat: SourceFormat = isTypedArray(data)
-        ? SOURCE_FORMAT_TYPED_ARRAY : SOURCE_FORMAT_ORIGINAL;
-    let fromDataset = false;
-
-    let seriesLayoutBy = seriesOption.seriesLayoutBy;
-    let sourceHeader = seriesOption.sourceHeader;
-    let dimensionsDefine = seriesOption.dimensions;
-
-    const datasetModel = getDatasetModel(seriesModel);
-    if (datasetModel) {
-        const datasetOption = datasetModel.option;
-
-        data = datasetOption.source;
-        sourceFormat = innerDatasetModel(datasetModel).sourceFormat;
-        fromDataset = true;
-
-        // These settings from series has higher priority.
-        seriesLayoutBy = seriesLayoutBy || datasetOption.seriesLayoutBy;
-        sourceHeader == null && (sourceHeader = datasetOption.sourceHeader);
-        dimensionsDefine = dimensionsDefine || datasetOption.dimensions;
-    }
-
-    const completeResult = completeBySourceData(
-        data, sourceFormat, seriesLayoutBy, sourceHeader, dimensionsDefine
+export function createSource(
+    sourceData: OptionSourceData,
+    thisMetaRawOption: SourceMetaRawOption,
+    // can be null. If not provided, auto detect it from `sourceData`.
+    sourceFormat: SourceFormat,
+    encodeDefine: OptionEncode  // can be null
+): Source {
+    sourceFormat = sourceFormat || detectSourceFormat(sourceData);
+    const dimInfo = determineSourceDimensions(
+        sourceData,
+        sourceFormat,
+        thisMetaRawOption.seriesLayoutBy,
+        thisMetaRawOption.sourceHeader,
+        thisMetaRawOption.dimensions
     );
-
-    innerSeriesModel(seriesModel).source = new Source({
-        data: data,
-        fromDataset: fromDataset,
-        seriesLayoutBy: seriesLayoutBy,
+    const source = new Source({
+        data: sourceData,
         sourceFormat: sourceFormat,
-        dimensionsDefine: completeResult.dimensionsDefine,
-        startIndex: completeResult.startIndex,
-        dimensionsDetectCount: completeResult.dimensionsDetectCount,
-        // Note: dataset option does not have `encode`.
-        encodeDefine: seriesOption.encode
+
+        seriesLayoutBy: thisMetaRawOption.seriesLayoutBy,
+        dimensionsDefine: dimInfo.dimensionsDefine,
+        startIndex: dimInfo.startIndex,
+        dimensionsDetectCount: dimInfo.dimensionsDetectCount,
+        encodeDefine: makeEncodeDefine(encodeDefine)
+    });
+
+    return source;
+}
+
+/**
+ * Clone except source data.
+ */
+export function cloneSourceShallow(source: Source) {
+    return new Source({
+        data: source.data,
+        sourceFormat: source.sourceFormat,
+
+        seriesLayoutBy: source.seriesLayoutBy,
+        dimensionsDefine: clone(source.dimensionsDefine),
+        startIndex: source.startIndex,
+        dimensionsDetectCount: source.dimensionsDetectCount,
+        encodeDefine: makeEncodeDefine(source.encodeDefine)
     });
 }
 
+function makeEncodeDefine(
+    encodeDefine: OptionEncode | HashMap<OptionEncodeValue, DimensionName>
+): HashMap<OptionEncodeValue, DimensionName> {
+    // null means user not specify `series.encode`.
+    return encodeDefine
+        ? createHashMap<OptionEncodeValue, DimensionName>(encodeDefine)
+        : null;
+}
+
+
 // return {startIndex, dimensionsDefine, dimensionsCount}
-function completeBySourceData(
+export function determineSourceDimensions(
     data: OptionSourceData,
     sourceFormat: SourceFormat,
     seriesLayoutBy: SeriesLayoutBy,
@@ -248,7 +219,7 @@ function completeBySourceData(
 
     if (!data) {
         return {
-            dimensionsDefine: normalizeDimensionsDefine(dimensionsDefine),
+            dimensionsDefine: normalizeDimensionsOption(dimensionsDefine),
             startIndex,
             dimensionsDetectCount
         };
@@ -275,7 +246,7 @@ function completeBySourceData(
             }, seriesLayoutBy, dataArrayRows, 10);
         }
         else {
-            startIndex = sourceHeader ? 1 : 0;
+            startIndex = isNumber(sourceHeader) ? sourceHeader : sourceHeader ? 1 : 0;
         }
 
         if (!dimensionsDefine && startIndex === 1) {
@@ -318,7 +289,7 @@ function completeBySourceData(
 
     return {
         startIndex: startIndex,
-        dimensionsDefine: normalizeDimensionsDefine(dimensionsDefine),
+        dimensionsDefine: normalizeDimensionsOption(dimensionsDefine),
         dimensionsDetectCount: dimensionsDetectCount
     };
 }
@@ -326,7 +297,7 @@ function completeBySourceData(
 // Consider dimensions defined like ['A', 'price', 'B', 'price', 'C', 'price'],
 // which is reasonable. But dimension name is duplicated.
 // Returns undefined or an array contains only object without null/undefiend or string.
-function normalizeDimensionsDefine(dimensionsDefine: DimensionDefinitionLoose[]): DimensionDefinition[] {
+function normalizeDimensionsOption(dimensionsDefine: DimensionDefinitionLoose[]): DimensionDefinition[] {
     if (!dimensionsDefine) {
         // The meaning of null/undefined is different from empty array.
         return;
@@ -419,7 +390,7 @@ export function makeSeriesEncodeForAxisCoordSys(
 ): SeriesEncodeInternal {
     const encode: SeriesEncodeInternal = {};
 
-    const datasetModel = getDatasetModel(seriesModel);
+    const datasetModel = querySeriesUpstreamDatasetModel(seriesModel);
     // Currently only make default when using dataset, util more reqirements occur.
     if (!datasetModel || !coordDimensions) {
         return encode;
@@ -512,7 +483,7 @@ export function makeSeriesEncodeForNameBased(
 ): SeriesEncodeInternal {
     const encode: SeriesEncodeInternal = {};
 
-    const datasetModel = getDatasetModel(seriesModel);
+    const datasetModel = querySeriesUpstreamDatasetModel(seriesModel);
     // Currently only make default when using dataset, util more reqirements occur.
     if (!datasetModel) {
         return encode;
@@ -600,19 +571,53 @@ export function makeSeriesEncodeForNameBased(
 }
 
 /**
- * If return null/undefined, indicate that should not use datasetModel.
+ * @return If return null/undefined, indicate that should not use datasetModel.
  */
-function getDatasetModel(seriesModel: SeriesEncodableModel): DatasetModel {
-    const option = seriesModel.option;
+export function querySeriesUpstreamDatasetModel(
+    seriesModel: SeriesEncodableModel
+): DatasetModel {
     // Caution: consider the scenario:
     // A dataset is declared and a series is not expected to use the dataset,
     // and at the beginning `setOption({series: { noData })` (just prepare other
     // option but no data), then `setOption({series: {data: [...]}); In this case,
     // the user should set an empty array to avoid that dataset is used by default.
-    const thisData = option.data;
+    const thisData = seriesModel.get('data', true);
     if (!thisData) {
-        return seriesModel.ecModel.getComponent('dataset', option.datasetIndex || 0) as DatasetModel;
+        return queryReferringComponents(
+            seriesModel.ecModel,
+            'dataset',
+            {
+                index: seriesModel.get('datasetIndex', true),
+                id: seriesModel.get('datasetId', true)
+            },
+            SINGLE_REFERRING
+        ).models[0] as DatasetModel;
+    }
+}
+
+/**
+ * @return Always return an array event empty.
+ */
+export function queryDatasetUpstreamDatasetModels(
+    datasetModel: DatasetModel
+): DatasetModel[] {
+    // Only these attributes declared, we by defualt reference to `datasetIndex: 0`.
+    // Otherwise, no reference.
+    if (!datasetModel.get('transform', true)
+        && !datasetModel.get('fromTransformResult', true)
+    ) {
+        return [];
     }
+
+    return queryReferringComponents(
+        datasetModel.ecModel,
+        'dataset',
+        {
+            index: datasetModel.get('fromDatasetIndex', true),
+            id: datasetModel.get('fromDatasetId', true)
+        },
+        SINGLE_REFERRING
+    ).models as DatasetModel[];
 }
 
 /**
diff --git a/src/data/helper/sourceManager.ts b/src/data/helper/sourceManager.ts
new file mode 100644
index 0000000..74ed264
--- /dev/null
+++ b/src/data/helper/sourceManager.ts
@@ -0,0 +1,368 @@
+/*
+* 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.
+*/
+
+import { DatasetModel } from '../../component/dataset';
+import SeriesModel from '../../model/Series';
+import { setAsPrimitive, map, isTypedArray, defaults, assert, each } from 'zrender/src/core/util';
+import Source from '../Source';
+import {
+    SeriesEncodableModel, OptionSourceData,
+    SOURCE_FORMAT_TYPED_ARRAY, SOURCE_FORMAT_ORIGINAL,
+    SourceFormat, SeriesLayoutBy, OptionSourceHeader, DimensionDefinitionLoose
+} from '../../util/types';
+import {
+    querySeriesUpstreamDatasetModel, queryDatasetUpstreamDatasetModels,
+    createSource, SourceMetaRawOption, cloneSourceShallow
+} from './sourceHelper';
+import { applyDataTransform } from './transform';
+
+
+/**
+ * [REQUIREMENT_MEMO]:
+ * (0) `metaRawOption` means `dimensions`/`sourceHeader`/`seriesLayoutBy` in raw option.
+ * (1) Keep support the feature: `metaRawOption` can be specified both on `series` and
+ * `root-dataset`. Them on `series` has higher priority.
+ * (2) Do not support to set `metaRawOption` on a `non-root-dataset`, because it might
+ * confuse users: whether those props indicate how to visit the upstream source or visit
+ * the transform result source, and some transforms has nothing to do with these props,
+ * and some transforms might have multiple upstream.
+ * (3) Transforms should specify `metaRawOption` in each output, just like they can be
+ * declared in `root-dataset`.
+ * (4) At present only support visit source in `SERIES_LAYOUT_BY_COLUMN` in transforms.
+ * That is for reducing complexity in transfroms.
+ * PENDING: Whether to provide transposition transform?
+ *
+ * [IMPLEMENTAION_MEMO]:
+ * "sourceVisitConfig" are calculated from `metaRawOption` and `data`.
+ * They will not be calculated until `source` is about to be visited (to prevent from
+ * duplicate calcuation). `source` is visited only in series and input to transforms.
+ *
+ * [SCENARIO]:
+ * (1) Provide source data directly:
+ * ```js
+ * series: {
+ *     encode: {...},
+ *     dimensions: [...]
+ *     seriesLayoutBy: 'row',
+ *     data: [[...]]
+ * }
+ * ```
+ * (2) Series refer to dataset.
+ * ```js
+ * series: [{
+ *     encode: {...}
+ *     // Ignore datasetIndex means `datasetIndex: 0`
+ *     // and the dimensions defination in dataset is used
+ * }, {
+ *     encode: {...},
+ *     seriesLayoutBy: 'column',
+ *     datasetIndex: 1
+ * }]
+ * ```
+ * (3) dataset transform
+ * ```js
+ * dataset: [{
+ *     source: [...]
+ * }, {
+ *     source: [...]
+ * }, {
+ *     // By default from 0.
+ *     transform: { type: 'filter', config: {...} }
+ * }, {
+ *     // Piped.
+ *     transform: [
+ *         { type: 'filter', config: {...} },
+ *         { type: 'sort', config: {...} }
+ *     ]
+ * }, {
+ *     id: 'regressionData',
+ *     fromDatasetIndex: 1,
+ *     // Third-party transform
+ *     transform: { type: 'ecStat:regression', config: {...} }
+ * }, {
+ *     // retrieve the extra result.
+ *     id: 'regressionFormula',
+ *     fromDatasetId: 'regressionData',
+ *     fromTransformResult: 1
+ * }]
+ * ```
+ */
+
+export class SourceManager {
+
+    // Currently only datasetModel can host `transform`
+    private _sourceHost: DatasetModel | SeriesModel;
+
+    // Cached source. Do not repeat calculating if not dirty.
+    private _sourceList: Source[] = [];
+
+    // version sign of each upstream source manager.
+    private _upstreamSignList: string[] = [];
+
+    private _versionSignBase = 0;
+
+    constructor(sourceHost: DatasetModel | SeriesModel) {
+        this._sourceHost = sourceHost;
+    }
+
+    /**
+     * Mark dirty.
+     */
+    dirty() {
+        this._setLocalSource([], []);
+    }
+
+    private _setLocalSource(
+        sourceList: Source[],
+        upstreamSignList: string[]
+    ): void {
+        this._sourceList = sourceList;
+        this._upstreamSignList = upstreamSignList;
+        this._versionSignBase++;
+        if (this._versionSignBase > 9e10) {
+            this._versionSignBase = 0;
+        }
+    }
+
+    /**
+     * For detecting whether the upstream source is dirty, so that
+     * the local cached source (in `_sourceList`) should be discarded.
+     */
+    private _getVersionSign(): string {
+        return this._sourceHost.uid + '_' + this._versionSignBase;
+    }
+
+    /**
+     * Always return a source instance. Otherwise throw error.
+     */
+    prepareSource(): void {
+        // For the case that call `setOption` multiple time but no data changed,
+        // cache the result source to prevent from repeating transform.
+        if (this._isDirty()) {
+            this._createSource();
+        }
+    }
+
+    private _createSource(): void {
+        this._setLocalSource([], []);
+        const sourceHost = this._sourceHost;
+
+        const upSourceMgrList = this._getUpstreamSourceManagers();
+        const hasUpstream = !!upSourceMgrList.length;
+        let resultSourceList: Source[];
+        let upstreamSignList: string[];
+
+        if (isSeries(sourceHost)) {
+            const seriesModel = sourceHost as SeriesEncodableModel;
+            let data;
+            let sourceFormat: SourceFormat;
+            let upMetaRawOption;
+
+            // Has upstream dataset
+            if (hasUpstream) {
+                const upSourceMgr = upSourceMgrList[0];
+                upSourceMgr.prepareSource();
+                const upSource = upSourceMgr.getSource();
+                data = upSource.data;
+                sourceFormat = upSource.sourceFormat;
+                upMetaRawOption = upSourceMgr._getSourceMetaRawOption();
+                upstreamSignList = [upSourceMgr._getVersionSign()];
+            }
+            // Series data is from own.
+            else {
+                data = seriesModel.get('data', true) as OptionSourceData;
+                sourceFormat = isTypedArray(data)
+                    ? SOURCE_FORMAT_TYPED_ARRAY : SOURCE_FORMAT_ORIGINAL;
+                upstreamSignList = [];
+            }
+
+            const thisMetaRawOption = defaults(
+                this._getSourceMetaRawOption(),
+                // See [REQUIREMENT MEMO], merge settings on series and parent dataset if it is root.
+                upMetaRawOption
+            );
+            resultSourceList = [createSource(
+                data,
+                thisMetaRawOption,
+                sourceFormat,
+                seriesModel.get('encode', true)
+            )];
+        }
+        else {
+            const datasetModel = sourceHost as DatasetModel;
+
+            // Has upstream dataset.
+            if (hasUpstream) {
+                const result = this._applyTransform(upSourceMgrList);
+                resultSourceList = result.sourceList;
+                upstreamSignList = result.upstreamSignList;
+            }
+            // Is root dataset.
+            else {
+                const sourceData = datasetModel.get('source', true);
+                resultSourceList = [createSource(
+                    sourceData,
+                    this._getSourceMetaRawOption(),
+                    null,
+                    // Note: dataset option does not have `encode`.
+                    null
+                )];
+                upstreamSignList = [];
+            }
+        }
+
+        if (__DEV__) {
+            assert(resultSourceList && upstreamSignList);
+        }
+
+        this._setLocalSource(resultSourceList, upstreamSignList);
+    }
+
+    private _applyTransform(
+        upMgrList: SourceManager[]
+    ): {
+        sourceList: Source[],
+        upstreamSignList: string[]
+    } {
+        const datasetModel = this._sourceHost as DatasetModel;
+        const transformOption = datasetModel.get('transform', true);
+        const fromTransformResult = datasetModel.get('fromTransformResult', true);
+        let sourceList: Source[];
+        let upstreamSignList: string[];
+
+        if (transformOption) {
+            const upSourceList: Source[] = [];
+            upstreamSignList = [];
+            each(upMgrList, upMgr => {
+                upMgr.prepareSource();
+                upSourceList.push(upMgr.getSource());
+                upstreamSignList.push(upMgr._getVersionSign());
+            });
+            sourceList = applyDataTransform(
+                transformOption,
+                upSourceList,
+                { datasetIndex: datasetModel.componentIndex }
+            );
+        }
+        else if (fromTransformResult != null) {
+            if (upMgrList.length !== 1) {
+                let errMsg = '';
+                if (__DEV__) {
+                    errMsg = 'When using `fromTransformResult`, there should be only one upstream dataset';
+                }
+                doThrow(errMsg);
+            }
+            const upMgr = upMgrList[0];
+            upMgr.prepareSource();
+            const upSource = upMgr.getSource(fromTransformResult);
+            upstreamSignList = [upMgr._getVersionSign()];
+            sourceList = [cloneSourceShallow(upSource)];
+        }
+
+        return { sourceList, upstreamSignList };
+    }
+
+    private _isDirty(): boolean {
+        const sourceList = this._sourceList;
+        if (!sourceList.length) {
+            return true;
+        }
+
+        // All sourceList is from the some upsteam.
+        const upSourceMgrList = this._getUpstreamSourceManagers();
+        for (let i = 0; i < upSourceMgrList.length; i++) {
+            const upSrcMgr = upSourceMgrList[i];
+            if (
+                // Consider the case that there is ancestor diry, call it recursively.
+                // The performance is probably not an issue because usually the chain is not long.
+                upSrcMgr._isDirty()
+                || this._upstreamSignList[i] !== upSrcMgr._getVersionSign()
+            ) {
+                return true;
+            }
+        }
+    }
+
+    /**
+     * @param sourceIndex By defualt 0, means "main source".
+     *                    Most cases there is only one source.
+     */
+    getSource(sourceIndex?: number) {
+        return this._sourceList[sourceIndex || 0];
+    }
+
+    /**
+     * PEDING: Is it fast enough?
+     * If no upstream, return empty array.
+     */
+    private _getUpstreamSourceManagers(): SourceManager[] {
+        // Always get the relationship from the raw option.
+        // Do not cache the link of the dependency graph, so that
+        // no need to update them when change happen.
+        const sourceHost = this._sourceHost;
+
+        if (isSeries(sourceHost)) {
+            const datasetModel = querySeriesUpstreamDatasetModel(sourceHost);
+            return !datasetModel ? [] : [datasetModel.getSourceManager()];
+        }
+        else {
+            return map(
+                queryDatasetUpstreamDatasetModels(sourceHost as DatasetModel),
+                datasetModel => datasetModel.getSourceManager()
+            );
+        }
+    }
+
+    private _getSourceMetaRawOption(): SourceMetaRawOption {
+        const sourceHost = this._sourceHost;
+        let seriesLayoutBy: SeriesLayoutBy;
+        let sourceHeader: OptionSourceHeader;
+        let dimensions: DimensionDefinitionLoose[];
+        if (isSeries(sourceHost)) {
+            seriesLayoutBy = sourceHost.get('seriesLayoutBy', true);
+            sourceHeader = sourceHost.get('sourceHeader', true);
+            dimensions = sourceHost.get('dimensions', true);
+        }
+        // See [REQUIREMENT MEMO], `non-root-dataset` do not support them.
+        else if (!this._getUpstreamSourceManagers().length) {
+            const model = sourceHost as DatasetModel;
+            seriesLayoutBy = model.get('seriesLayoutBy', true);
+            sourceHeader = model.get('sourceHeader', true);
+            dimensions = model.get('dimensions', true);
+        }
+        return { seriesLayoutBy, sourceHeader, dimensions };
+    }
+
+}
+
+// Call this method after `super.init` and `super.mergeOption` to
+// disable the transform merge, but do not disable transfrom clone from rawOption.
+export function disableTransformOptionMerge(datasetModel: DatasetModel): void {
+    const transformOption = datasetModel.option.transform;
+    transformOption && setAsPrimitive(datasetModel.option.transform);
+}
+
+function isSeries(sourceHost: SourceManager['_sourceHost']): sourceHost is SeriesEncodableModel {
+    // Avoid circular dependency with Series.ts
+    return (sourceHost as SeriesModel).mainType === 'series';
+}
+
+function doThrow(errMsg: string): void {
+    throw new Error(errMsg);
+}
diff --git a/src/data/helper/transform.ts b/src/data/helper/transform.ts
new file mode 100644
index 0000000..e6150b1
--- /dev/null
+++ b/src/data/helper/transform.ts
@@ -0,0 +1,317 @@
+/*
+* 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.
+*/
+
+import {
+    Dictionary, OptionSourceData, DimensionDefinitionLoose, OptionSourceHeader,
+    SourceFormat, DimensionDefinition, OptionDataItem, DimensionIndex,
+    OptionDataValue, DimensionLoose, DimensionName, ParsedValue, SERIES_LAYOUT_BY_COLUMN
+} from '../../util/types';
+import Source from '../Source';
+import { normalizeToArray } from '../../util/model';
+import {
+    assert, createHashMap, bind, each, hasOwn, map, clone, isObject,
+    isArrayLike
+} from 'zrender/src/core/util';
+import {
+    getRawSourceItemGetter, getRawSourceDataCounter, getRawSourceValueGetter
+} from './dataProvider';
+import { parseDataValue } from './parseDataValue';
+import { createSource } from './sourceHelper';
+import { consoleLog, makePrintable } from '../../util/log';
+
+
+export type PipedDataTransformOption = DataTransformOption[];
+export type DataTransformType = string;
+export type DataTransformConfig = unknown;
+
+export interface DataTransformOption {
+    type: DataTransformType;
+    config: DataTransformConfig;
+    // Print the result via `console.log` when transform performed. Only work in dev mode for debug.
+    print?: boolean;
+}
+
+export interface DataTransformResult {
+    source: Source;
+}
+
+export interface DataTransform {
+    (sourceList: Source[], config: DataTransformConfig): {
+    }
+}
+
+export interface ExternalDataTransform<TO extends DataTransformOption = DataTransformOption> {
+    // Must include namespace like: 'ecStat:regression'
+    type: string,
+    transform?: (
+        param: ExternalDataTransformParam<TO>
+    ) => ExternalDataTransformResultItem | ExternalDataTransformResultItem[]
+}
+
+interface ExternalDataTransformParam<TO extends DataTransformOption = DataTransformOption> {
+    // This is the first source in sourceList. In most cases,
+    // there is only one upstream source.
+    source: ExternalSource;
+    sourceList: ExternalSource[];
+    config: TO['config'];
+}
+export interface ExternalDataTransformResultItem {
+    data: OptionSourceData;
+    dimensions?: DimensionDefinitionLoose[];
+    sourceHeader?: OptionSourceHeader;
+}
+export interface ExternalDimensionDefinition extends DimensionDefinition {
+    // Mandatory
+    index: DimensionIndex;
+}
+
+/**
+ * TODO: disable writable.
+ * This structure will be exposed to users.
+ */
+class ExternalSource {
+    /**
+     * [Caveat]
+     * This instance is to be exposed to users.
+     * DO NOT mount private members on this instance directly.
+     * If we have to use private members, we can make them in closure or use `makeInner`.
+     */
+
+    data: OptionSourceData;
+    sourceFormat: SourceFormat;
+    dimensions: ExternalDimensionDefinition[];
+    sourceHeaderCount: number;
+
+    getDimensionInfo(dim: DimensionLoose): ExternalDimensionDefinition {
+        return;
+    }
+
+    getRawDataItem(dataIndex: number): OptionDataItem {
+        return;
+    }
+
+    getRawHeaderItem(dataIndex: number): OptionDataItem {
+        return;
+    }
+
+    count(): number {
+        return;
+    }
+
+    /**
+     * Only support by dimension index.
+     * No need to support by dimension name in transform function,
+     * becuase transform function is not case-specific, no need to use name literally.
+     */
+    retrieveItemValue(rawItem: OptionDataItem, dimIndex: DimensionIndex): OptionDataValue {
+        return;
+    }
+
+    convertDataValue(rawVal: unknown, dimInfo: ExternalDimensionDefinition): ParsedValue {
+        return parseDataValue(rawVal, dimInfo);
+    }
+}
+
+function createExternalSource(
+    data: OptionSourceData,
+    sourceFormat: SourceFormat,
+    dimsDef: DimensionDefinition[],
+    sourceHeaderCount: number
+): ExternalSource {
+    const extSource = new ExternalSource();
+
+    extSource.data = data;
+    extSource.sourceFormat = sourceFormat;
+    extSource.sourceHeaderCount = sourceHeaderCount;
+
+    // Create a new dimensions structure for exposing.
+    const dimensions = extSource.dimensions = [] as ExternalDimensionDefinition[];
+    const dimsByName = {} as Dictionary<ExternalDimensionDefinition>;
+    each(dimsDef, function (dimDef, idx) {
+        const name = dimDef.name;
+        const dimDefExt = {
+            index: idx,
+            name: name,
+            displayName: dimDef.displayName
+        };
+        dimensions.push(dimDefExt);
+        // Users probably not sepcify dimension name. For simplicity, data transform
+        // do not generate dimension name.
+        if (name != null) {
+            // Dimension name should not be duplicated.
+            // For simplicity, data transform forbid name duplication, do not generate
+            // new name like module `completeDimensions.ts` did, but just tell users.
+            assert(!hasOwn(dimsByName, name), 'dimension name "' + name + '" duplicated.');
+            dimsByName[name] = dimDefExt;
+        }
+    });
+
+    // Implement public methods:
+    const rawItemGetter = getRawSourceItemGetter(sourceFormat, SERIES_LAYOUT_BY_COLUMN);
+    extSource.getRawDataItem = bind(rawItemGetter, null, data, sourceHeaderCount, dimensions);
+    extSource.getRawHeaderItem = function (dataIndex: number) {
+        if (dataIndex < sourceHeaderCount) {
+            return rawItemGetter(data, 0, dimensions, dataIndex);
+        }
+    };
+
+    const rawCounter = getRawSourceDataCounter(sourceFormat, SERIES_LAYOUT_BY_COLUMN);
+    extSource.count = bind(rawCounter, null, data, sourceHeaderCount, dimensions);
+
+    const rawValueGetter = getRawSourceValueGetter(sourceFormat);
+    extSource.retrieveItemValue = function (rawItem, dimIndex) {
+        if (rawItem == null) {
+            return;
+        }
+        const dimDef = extSource.dimensions[dimIndex];
+        // When `dimIndex` is `null`, `rawValueGetter` return the whole item.
+        if (dimDef) {
+            return rawValueGetter(rawItem, dimIndex, dimDef.name) as OptionDataValue;
+        }
+    };
+
+    extSource.getDimensionInfo = bind(getDimensionInfo, null, dimensions, dimsByName);
+
+    return extSource;
+}
+
+
+function getDimensionInfo(
+    dimensions: ExternalDimensionDefinition[],
+    dimsByName: Dictionary<ExternalDimensionDefinition>,
+    dim: DimensionLoose
+): ExternalDimensionDefinition {
+    if (dim == null) {
+        return;
+    }
+    // Keep the same logic as `List::getDimension` did.
+    if (typeof dim === 'number'
+        // If being a number-like string but not being defined a dimension name.
+        || (!isNaN(dim as any) && !hasOwn(dimsByName, dim))
+    ) {
+        return dimensions[dim as DimensionIndex];
+    }
+    else if (hasOwn(dimsByName, dim)) {
+        return dimsByName[dim as DimensionName];
+    }
+}
+
+
+
+const externalTransformMap = createHashMap<ExternalDataTransform, string>();
+
+export function registerExternalTransform(
+    externalTransform: ExternalDataTransform
+): void {
+    externalTransform = clone(externalTransform);
+    let type = externalTransform.type;
+    assert(type, 'Must have a `type` when `registerTransform`.');
+    const typeParsed = type.split(':');
+    assert(typeParsed.length === 2, 'Name must include namespace like "ns:regression".');
+    // Namespace 'echarts:xxx' is official namespace, where the transforms should
+    // be called directly via 'xxx' rather than 'echarts:xxx'.
+    if (typeParsed[0] === 'echarts') {
+        type = typeParsed[1];
+    }
+    externalTransformMap.set(type, externalTransform);
+}
+
+export function applyDataTransform(
+    rawTransOption: DataTransformOption | PipedDataTransformOption,
+    sourceList: Source[],
+    infoForPrint: { datasetIndex: number }
+): Source[] {
+    const pipedTransOption: PipedDataTransformOption = normalizeToArray(rawTransOption);
+
+    for (let i = 0, len = pipedTransOption.length; i < len; i++) {
+        const transOption = pipedTransOption[i];
+        sourceList = applySingleDataTransform(transOption, sourceList);
+        // piped transform only support single input, except the fist one.
+        // piped transform only support single output, except the last one.
+        if (i < len - 1) {
+            sourceList.length = Math.max(sourceList.length, 1);
+        }
+
+        if (__DEV__) {
+            if (transOption.print) {
+                const printStrArr = map(sourceList, source => {
+                    return '--- datasetIndex: ' + infoForPrint.datasetIndex + ', transform result: ---\n'
+                        + makePrintable(source.data);
+                }).join('\n');
+                consoleLog(printStrArr);
+            }
+        }
+    }
+
+    return sourceList;
+}
+
+function applySingleDataTransform(
+    rawTransOption: DataTransformOption,
+    upSourceList: Source[]
+): Source[] {
+    assert(upSourceList.length, 'Must have at least one upstream dataset.');
+
+    const transOption = rawTransOption;
+    const transType = transOption.type;
+    const externalTransform = externalTransformMap.get(transType);
+
+    assert(externalTransform, 'Can not find transform on type "' + transType + '".');
+
+    // Prepare source
+    const sourceList = map(upSourceList, function (source) {
+        return createExternalSource(
+            source.data,
+            source.sourceFormat,
+            source.dimensionsDefine,
+            source.startIndex
+        );
+    });
+
+    const resultList = normalizeToArray(
+        externalTransform.transform({
+            source: sourceList[0],
+            sourceList: sourceList,
+            config: clone(transOption.config)
+        })
+    );
+
+    return map(resultList, function (result) {
+        assert(
+            isObject(result),
+            'A transform should not return some empty results.'
+        );
+        assert(
+            isObject(result.data) || isArrayLike(result.data),
+            'Result data should be object or array in data transform.'
+        );
+
+        return createSource(
+            result.data,
+            {
+                seriesLayoutBy: SERIES_LAYOUT_BY_COLUMN,
+                sourceHeader: result.sourceHeader,
+                dimensions: result.dimensions
+            },
+            null,
+            null
+        );
+    });
+}
+
diff --git a/src/echarts.ts b/src/echarts.ts
index 76334a7..fa991fd 100644
--- a/src/echarts.ts
+++ b/src/echarts.ts
@@ -100,6 +100,7 @@ import { handleLegacySelectEvents } from './legacy/dataSelectAction';
 
 // At least canvas renderer.
 import 'zrender/src/canvas/canvas';
+import { registerExternalTransform } from './data/helper/transform';
 
 declare let global: any;
 type ModelFinder = modelUtil.ModelFinder;
@@ -2716,6 +2717,8 @@ export function getMap(mapName: string) {
     };
 }
 
+export const registerTransform = registerExternalTransform;
+
 /**
  * Globa dispatchAction to a specified chart instance.
  */
diff --git a/src/model/Component.ts b/src/model/Component.ts
index 4fc656a..373ea68 100644
--- a/src/model/Component.ts
+++ b/src/model/Component.ts
@@ -29,7 +29,9 @@ import {
     ClassManager,
     mountExtend
 } from '../util/clazz';
-import {makeInner, ModelFinderIndexQuery, queryReferringComponents, ModelFinderIdQuery, QueryReferringOpt} from '../util/model';
+import {
+    makeInner, ModelFinderIndexQuery, queryReferringComponents, ModelFinderIdQuery, QueryReferringOpt
+} from '../util/model';
 import * as layout from '../util/layout';
 import GlobalModel from './Global';
 import {
diff --git a/src/model/OptionManager.ts b/src/model/OptionManager.ts
index 33f0742..b92d5b4 100644
--- a/src/model/OptionManager.ts
+++ b/src/model/OptionManager.ts
@@ -36,6 +36,7 @@ import {
     each, clone, map, isTypedArray, setAsPrimitive
     // , HashMap , createHashMap, extend, merge,
 } from 'zrender/src/core/util';
+import { DatasetOption } from '../component/dataset';
 
 const QUERY_REG = /^(min|max)?(.+)$/;
 
@@ -98,6 +99,9 @@ class OptionManager {
             each(normalizeToArray((rawOption as ECUnitOption).series), function (series: SeriesOption) {
                 series && series.data && isTypedArray(series.data) && setAsPrimitive(series.data);
             });
+            each(normalizeToArray((rawOption as ECUnitOption).dataset), function (dataset: DatasetOption) {
+                dataset && dataset.source && isTypedArray(dataset.source) && setAsPrimitive(dataset.source);
+            });
         }
 
         // Caution: some series modify option data, if do not clone,
diff --git a/src/model/Series.ts b/src/model/Series.ts
index 6fdd691..b697c56 100644
--- a/src/model/Series.ts
+++ b/src/model/Series.ts
@@ -41,10 +41,6 @@ import {
     fetchLayoutMode
 } from '../util/layout';
 import {createTask} from '../stream/task';
-import {
-    prepareSource,
-    getSource
-} from '../data/helper/sourceHelper';
 import {retrieveRawValue} from '../data/helper/dataProvider';
 import GlobalModel from './Global';
 import { CoordinateSystem } from '../coord/CoordinateSystem';
@@ -57,10 +53,12 @@ import Axis from '../coord/Axis';
 import { GradientObject } from 'zrender/src/graphic/Gradient';
 import type { BrushCommonSelectorsForSeries, BrushSelectableArea } from '../component/brush/selector';
 import makeStyleMapper from './mixin/makeStyleMapper';
+import { SourceManager } from '../data/helper/sourceManager';
 
 const inner = modelUtil.makeInner<{
     data: List
     dataBeforeProcessed: List
+    sourceManager: SourceManager
 }, SeriesModel>();
 
 function getSelectionKey(data: List, dataIndex: number): string {
@@ -139,7 +137,6 @@ class SeriesModel<Opt extends SeriesOption = SeriesOption> extends ComponentMode
     // Injected outside
     pipelineContext: PipelineContext;
 
-
     // ---------------------------------------
     // Props to tell visual/style.ts about how to do visual encoding.
     // ---------------------------------------
@@ -197,7 +194,8 @@ class SeriesModel<Opt extends SeriesOption = SeriesOption> extends ComponentMode
 
         this.mergeDefaultAndTheme(option, ecModel);
 
-        prepareSource(this);
+        const sourceManager = inner(this).sourceManager = new SourceManager(this);
+        sourceManager.prepareSource();
 
         const data = this.getInitialData(option, ecModel);
         wrapData(data, this);
@@ -273,7 +271,9 @@ class SeriesModel<Opt extends SeriesOption = SeriesOption> extends ComponentMode
             );
         }
 
-        prepareSource(this);
+        const sourceManager = inner(this).sourceManager;
+        sourceManager.dirty();
+        sourceManager.prepareSource();
 
         const data = this.getInitialData(newSeriesOption, ecModel);
         wrapData(data, this);
@@ -377,7 +377,7 @@ class SeriesModel<Opt extends SeriesOption = SeriesOption> extends ComponentMode
     }
 
     getSource(): Source {
-        return getSource(this);
+        return inner(this).sourceManager.getSource();
     }
 
     /**
diff --git a/src/util/conditionalExpression.ts b/src/util/conditionalExpression.ts
new file mode 100644
index 0000000..b48a896
--- /dev/null
+++ b/src/util/conditionalExpression.ts
@@ -0,0 +1,536 @@
+/*
+* 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.
+*/
+
+import { OptionDataValue, DimensionLoose, Dictionary } from './types';
+import {
+    createHashMap, keys, isArray, map, isObject, isString, trim, HashMap, isRegExp, isArrayLike
+} from 'zrender/src/core/util';
+import { throwError, makePrintable } from './log';
+import { parseDate } from './number';
+
+
+// PENDING:
+// (1) Support more parser like: `parse: 'trim'`, `parse: 'lowerCase'`, `parse: 'year'`, `parse: 'dayOfWeek'`?
+// (2) Support piped parser ?
+// (3) Support callback parser or callback condition?
+// (4) At present do not support string expression yet but only stuctured expression.
+
+
+/**
+ * The structured expression considered:
+ * (1) Literal simplicity
+ * (2) Sementic displayed clearly
+ *
+ * Sementic supports:
+ * (1) relational expression
+ * (2) logical expression
+ *
+ * For example:
+ * ```js
+ * {
+ *     and: [{
+ *         or: [{
+ *             dimension: 'Year', gt: 2012, lt: 2019
+ *         }, {
+ *             dimension: 'Year', '>': 2002, '<=': 2009
+ *         }]
+ *     }, {
+ *         dimension: 'Product', eq: 'Tofu'
+ *     }]
+ * }
+ *
+ * { dimension: 'Product', eq: 'Tofu' }
+ *
+ * {
+ *     or: [
+ *         { dimension: 'Product', value: 'Tofu' },
+ *         { dimension: 'Product', value: 'Biscuit' }
+ *     ]
+ * }
+ *
+ * {
+ *     and: [true]
+ * }
+ * ```
+ *
+ * [PARSER]
+ * In an relation expression object, we can specify some built-in parsers:
+ * ```js
+ * // Trim if string
+ * {
+ *     parse: 'trim',
+ *     eq: 'Flowers'
+ * }
+ * // Parse as time and enable arithmetic relation comparison.
+ * {
+ *     parse: 'time',
+ *     lt: '2012-12-12'
+ * }
+ * // RegExp, include the feature in SQL: `like '%xxx%'`.
+ * {
+ *     reg: /^asdf$/
+ * }
+ * {
+ *     reg: '^asdf$' // Serializable reg exp, will be `new RegExp(...)`
+ * }
+ * ```
+ *
+ *
+ * [EMPTY_RULE]
+ * (1) If a relational expression set value as `null`/`undefined` like:
+ * `{ dimension: 'Product', lt: undefined }`,
+ * The result will be `false` rather than `true`.
+ * Consider the case like "filter condition", return all result when null/undefined
+ * is probably not expected and even dangours.
+ * (2) If a relational expression has no operator like:
+ * `{ dimension: 'Product' }`,
+ * An error will be thrown. Because it is probably a mistake.
+ * (3) If a logical expression has no children like
+ * `{ and: undefined }` or `{ and: [] }`,
+ * An error will be thrown. Because it is probably an mistake.
+ * (4) If intending have a condition that always `true` or always `false`,
+ * Use `true` or `flase`.
+ * The entire condition can be `true`/`false`,
+ * or also can be `{ and: [true] }`, `{ or: [false] }`
+ */
+
+
+// --------------------------------------------------
+// --- Relational Expression --------------------------
+// --------------------------------------------------
+
+/**
+ * Date string and ordinal string can be accepted.
+ */
+interface RelationalExpressionOptionByOp {
+    lt?: OptionDataValue; // less than
+    lte?: OptionDataValue; // less than or equal
+    gt?: OptionDataValue; // greater than
+    gte?: OptionDataValue; // greater than or equal
+    eq?: OptionDataValue; // equal
+    ne?: OptionDataValue; // not equal
+    reg?: RegExp | string; // RegExp
+};
+interface RelationalExpressionOptionByOpAlias {
+    value?: RelationalExpressionOptionByOp['eq'];
+
+    '<'?: OptionDataValue; // lt
+    '<='?: OptionDataValue; // lte
+    '>'?: OptionDataValue; // gt
+    '>='?: OptionDataValue; // gte
+    '='?: OptionDataValue; // eq
+    '!='?: OptionDataValue; // ne
+    '<>'?: OptionDataValue; // ne (SQL style)
+
+    // '=='?: OptionDataValue; // eq
+    // '==='?: OptionDataValue; // eq
+    // '!=='?: OptionDataValue; // eq
+
+    // ge: RelationalExpressionOptionByOp['gte'];
+    // le: RelationalExpressionOptionByOp['lte'];
+    // neq: RelationalExpressionOptionByOp['ne'];
+};
+const aliasToOpMap = createHashMap<RelationalExpressionOp, RelationalExpressionOpAlias>({
+    value: 'eq',
+
+    // PENDING: not good for literal semantic?
+    '<': 'lt',
+    '<=': 'lte',
+    '>': 'gt',
+    '>=': 'gte',
+    '=': 'eq',
+    '!=': 'ne',
+    '<>': 'ne'
+
+    // Might mileading for sake of the different between '==' and '===',
+    // So dont support them.
+    // '==': 'eq',
+    // '===': 'seq',
+    // '!==': 'sne'
+
+    // PENDING: Whether support some common alias "ge", "le", "neq"?
+    // ge: 'gte',
+    // le: 'lte',
+    // neq: 'ne',
+});
+
+type RelationalExpressionOp = keyof RelationalExpressionOptionByOp;
+type RelationalExpressionOpAlias = keyof RelationalExpressionOptionByOpAlias;
+
+interface RelationalExpressionOption extends
+        RelationalExpressionOptionByOp, RelationalExpressionOptionByOpAlias {
+    dimension?: DimensionLoose;
+    parse?: RelationalExpressionValueParserType;
+}
+
+type RelationalExpressionOpEvaluate = (tarVal: unknown, condVal: unknown) => boolean;
+
+const relationalOpEvaluateMap = createHashMap<RelationalExpressionOpEvaluate, RelationalExpressionOp>({
+    // PENDING: should keep supporting string compare?
+    lt: function (tarVal, condVal) {
+        return tarVal < condVal;
+    },
+    lte: function (tarVal, condVal) {
+        return tarVal <= condVal;
+    },
+    gt: function (tarVal, condVal) {
+        return tarVal > condVal;
+    },
+    gte: function (tarVal, condVal) {
+        return tarVal >= condVal;
+    },
+    eq: function (tarVal, condVal) {
+        // eq is probably most used, DO NOT use JS ==,
+        // the rule is too complicated.
+        return tarVal === condVal;
+    },
+    ne: function (tarVal, condVal) {
+        return tarVal !== condVal;
+    },
+    reg: function (tarVal, condVal: RegExp) {
+        const type = typeof tarVal;
+        return type === 'string' ? condVal.test(tarVal as string)
+            : type === 'number' ? condVal.test(tarVal + '')
+            : false;
+    }
+});
+
+function parseRegCond(condVal: unknown): RegExp {
+    // Support condVal: RegExp | string
+    return isString(condVal) ? new RegExp(condVal)
+        : isRegExp(condVal) ? condVal as RegExp
+        : null;
+}
+
+type RelationalExpressionValueParserType = 'time' | 'trim';
+type RelationalExpressionValueParser = (val: unknown) => unknown;
+const valueParserMap = createHashMap<RelationalExpressionValueParser, RelationalExpressionValueParserType>({
+    time: function (val): number {
+        // return timestamp.
+        return +parseDate(val);
+    },
+    trim: function (val) {
+        return typeof val === 'string' ? trim(val) : val;
+    }
+});
+
+
+// --------------------------------------------------
+// --- Logical Expression ---------------------------
+// --------------------------------------------------
+
+
+interface LogicalExpressionOption {
+    and?: LogicalExpressionSubOption[];
+    or?: LogicalExpressionSubOption[];
+    not?: LogicalExpressionSubOption;
+}
+type LogicalExpressionSubOption =
+    LogicalExpressionOption | RelationalExpressionOption | TrueFalseExpressionOption;
+
+
+
+// -----------------------------------------------------
+// --- Conditional Expression --------------------------
+// -----------------------------------------------------
+
+
+export type TrueExpressionOption = true;
+export type FalseExpressionOption = false;
+export type TrueFalseExpressionOption = TrueExpressionOption | FalseExpressionOption;
+
+export type ConditionalExpressionOption =
+    LogicalExpressionOption
+    | RelationalExpressionOption
+    | TrueFalseExpressionOption;
+
+type ValueGetterParam = Dictionary<unknown>;
+export interface ConditionalExpressionValueGetterParamGetter<VGP extends ValueGetterParam = ValueGetterParam> {
+    (relExpOption: RelationalExpressionOption): VGP
+}
+export interface ConditionalExpressionValueGetter<VGP extends ValueGetterParam = ValueGetterParam> {
+    (param: VGP): OptionDataValue
+}
+
+interface ParsedConditionInternal {
+    evaluate(): boolean;
+}
+class ConstConditionInternal implements ParsedConditionInternal {
+    value: boolean;
+    evaluate(): boolean {
+        return this.value;
+    }
+}
+class AndConditionInternal implements ParsedConditionInternal {
+    children: ParsedConditionInternal[];
+    evaluate() {
+        const children = this.children;
+        for (let i = 0; i < children.length; i++) {
+            if (!children[i].evaluate()) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
+class OrConditionInternal implements ParsedConditionInternal {
+    children: ParsedConditionInternal[];
+    evaluate() {
+        const children = this.children;
+        for (let i = 0; i < children.length; i++) {
+            if (children[i].evaluate()) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
+class NotConditionInternal implements ParsedConditionInternal {
+    child: ParsedConditionInternal;
+    evaluate() {
+        return !this.child.evaluate();
+    }
+}
+class RelationalConditionInternal implements ParsedConditionInternal {
+    valueGetterParam: ValueGetterParam;
+    valueParser: RelationalExpressionValueParser;
+    // If no parser, be null/undefined.
+    getValue: ConditionalExpressionValueGetter;
+    subCondList: {
+        condValue: unknown;
+        evaluate: RelationalExpressionOpEvaluate;
+    }[];
+
+    evaluate() {
+        const getValue = this.getValue;
+        const needParse = !!this.valueParser;
+        // Call getValue with no `this`.
+        const tarValRaw = getValue(this.valueGetterParam);
+        const tarValParsed = needParse ? this.valueParser(tarValRaw) : null;
+
+        // Relational cond follow "and" logic internally.
+        for (let i = 0; i < this.subCondList.length; i++) {
+            const subCond = this.subCondList[i];
+            if (
+                !subCond.evaluate(
+                    needParse ? tarValParsed : tarValRaw,
+                    subCond.condValue
+                )
+            ) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
+
+function parseOption(
+    exprOption: ConditionalExpressionOption,
+    getters: ConditionalGetters
+): ParsedConditionInternal {
+    if (exprOption === true || exprOption === false) {
+        const cond = new ConstConditionInternal();
+        cond.value = exprOption as boolean;
+        return cond;
+    }
+
+    let errMsg = '';
+    if (!isObjectNotArray(exprOption)) {
+        if (__DEV__) {
+            errMsg = makePrintable(
+                'Illegal config. Expect a plain object but actually', exprOption
+            );
+        }
+        throwError(errMsg);
+    }
+
+    if ((exprOption as LogicalExpressionOption).and) {
+        return parseAndOrOption('and', exprOption as LogicalExpressionOption, getters);
+    }
+    else if ((exprOption as LogicalExpressionOption).or) {
+        return parseAndOrOption('or', exprOption as LogicalExpressionOption, getters);
+    }
+    else if ((exprOption as LogicalExpressionOption).not) {
+        return parseNotOption(exprOption as LogicalExpressionOption, getters);
+    }
+
+    return parseRelationalOption(exprOption as RelationalExpressionOption, getters);
+}
+
+function parseAndOrOption(
+    op: 'and' | 'or',
+    exprOption: LogicalExpressionOption,
+    getters: ConditionalGetters
+): ParsedConditionInternal {
+    const subOptionArr = exprOption[op] as ConditionalExpressionOption[];
+    let errMsg = '';
+    if (__DEV__) {
+        errMsg = makePrintable(
+            '"and"/"or" condition should only be `' + op + ': [...]` and must not be empty array.',
+            'Illegal condition:', exprOption
+        );
+    }
+    if (!isArray(subOptionArr)) {
+        throwError(errMsg);
+    }
+    if (!(subOptionArr as []).length) {
+        throwError(errMsg);
+    }
+    const cond = op === 'and' ? new AndConditionInternal() : new OrConditionInternal();
+    cond.children = map(subOptionArr, subOption => parseOption(subOption, getters));
+    if (!cond.children.length) {
+        throwError(errMsg);
+    }
+    return cond;
+}
+
+function parseNotOption(
+    exprOption: LogicalExpressionOption,
+    getters: ConditionalGetters
+): ParsedConditionInternal {
+    const subOption = exprOption.not as ConditionalExpressionOption;
+    let errMsg = '';
+    if (__DEV__) {
+        errMsg = makePrintable(
+            '"not" condition should only be `not: {}`.',
+            'Illegal condition:', exprOption
+        );
+    }
+    if (!isObjectNotArray(subOption)) {
+        throwError(errMsg);
+    }
+    const cond = new NotConditionInternal();
+    cond.child = parseOption(subOption, getters);
+    if (!cond.child) {
+        throwError(errMsg);
+    }
+    return cond;
+}
+
+function parseRelationalOption(
+    exprOption: RelationalExpressionOption,
+    getters: ConditionalGetters
+): ParsedConditionInternal {
+    let errMsg = '';
+
+    const valueGetterParam = getters.prepareGetValue(exprOption);
+
+    const subCondList = [] as RelationalConditionInternal['subCondList'];
+    const exprKeys = keys(exprOption);
+
+    const parserName = exprOption.parse;
+    const valueParser = parserName ? valueParserMap.get(parserName) : null;
+
+    for (let i = 0; i < exprKeys.length; i++) {
+        const keyRaw = exprKeys[i];
+        if (keyRaw === 'parse' || getters.valueGetterAttrMap.get(keyRaw)) {
+            continue;
+        }
+
+        const op: RelationalExpressionOp = aliasToOpMap.get(keyRaw as RelationalExpressionOpAlias)
+            || (keyRaw as RelationalExpressionOp);
+        const evaluateHandler = relationalOpEvaluateMap.get(op);
+
+        if (!evaluateHandler) {
+            if (__DEV__) {
+                errMsg = makePrintable(
+                    'Illegal relational operation: "' + keyRaw + '" in condition:', exprOption
+                );
+            }
+            throwError(errMsg);
+        }
+
+        const condValueRaw = exprOption[keyRaw];
+        let condValue;
+        if (keyRaw === 'reg') {
+            condValue = parseRegCond(condValueRaw);
+            if (condValue == null) {
+                let errMsg = '';
+                if (__DEV__) {
+                    errMsg = makePrintable('Illegal regexp', condValueRaw, 'in', exprOption);
+                }
+                throwError(errMsg);
+            }
+        }
+        else {
+            // At present, all other operators are applicable `RelationalExpressionValueParserType`.
+            // But if adding new parser, we should check it again.
+            condValue = valueParser ? valueParser(condValueRaw) : condValueRaw;
+        }
+
+        subCondList.push({
+            condValue: condValue,
+            evaluate: evaluateHandler
+        });
+    }
+
+    if (!subCondList.length) {
+        if (__DEV__) {
+            errMsg = makePrintable(
+                'Relational condition must have at least one operator.',
+                'Illegal condition:', exprOption
+            );
+        }
+        // No relational operator always disabled in case of dangers result.
+        throwError(errMsg);
+    }
+
+    const cond = new RelationalConditionInternal();
+    cond.valueGetterParam = valueGetterParam;
+    cond.valueParser = valueParser;
+    cond.getValue = getters.getValue;
+    cond.subCondList = subCondList;
+
+    return cond;
+}
+
+function isObjectNotArray(val: unknown): boolean {
+    return isObject(val) && !isArrayLike(val);
+}
+
+
+class ConditionalExpressionParsed {
+
+    private _cond: ParsedConditionInternal;
+
+    constructor(
+        exprOption: ConditionalExpressionOption,
+        getters: ConditionalGetters
+    ) {
+        this._cond = parseOption(exprOption, getters);
+    }
+
+    evaluate(): boolean {
+        return this._cond.evaluate();
+    }
+};
+
+interface ConditionalGetters<VGP extends ValueGetterParam = ValueGetterParam> {
+    prepareGetValue: ConditionalExpressionValueGetterParamGetter<VGP>;
+    getValue: ConditionalExpressionValueGetter<VGP>;
+    valueGetterAttrMap: HashMap<boolean, string>;
+}
+
+export function parseConditionalExpression<VGP extends ValueGetterParam = ValueGetterParam>(
+    exprOption: ConditionalExpressionOption,
+    getters: ConditionalGetters<VGP>
+): ConditionalExpressionParsed {
+    return new ConditionalExpressionParsed(exprOption, getters);
+}
+
diff --git a/src/util/ecData.ts b/src/util/ecData.ts
index 2dc31fc..fcd176d 100644
--- a/src/util/ecData.ts
+++ b/src/util/ecData.ts
@@ -18,7 +18,7 @@
 */
 
 import Element from 'zrender/src/Element';
-import { DataModel, ECEventData, BlurScope, InnerFocus } from './types';
+import { DataModel, ECEventData, BlurScope, InnerFocus, SeriesDataType } from './types';
 import { makeInner } from './model';
 /**
  * ECData stored on graphic element
@@ -28,7 +28,7 @@ export interface ECData {
     dataModel?: DataModel;
     eventData?: ECEventData;
     seriesIndex?: number;
-    dataType?: string;
+    dataType?: SeriesDataType;
     focus?: InnerFocus;
     blurScope?: BlurScope;
 }
diff --git a/src/util/log.ts b/src/util/log.ts
index c841bac..3aaab2d 100644
--- a/src/util/log.ts
+++ b/src/util/log.ts
@@ -18,6 +18,7 @@
 */
 
 import { Dictionary } from './types';
+import { map, isString, isFunction, eqNaN, isRegExp } from 'zrender/src/core/util';
 
 const storedLogs: Dictionary<boolean> = {};
 
@@ -37,4 +38,68 @@ export function deprecateReplaceLog(oldOpt: string, newOpt: string, scope?: stri
     if (__DEV__) {
         deprecateLog((scope ? `[${scope}]` : '') + `${oldOpt} is deprecated, use ${newOpt} instead.`);
     }
-}
\ No newline at end of file
+}
+
+export function consoleLog(...args: unknown[]) {
+    if (__DEV__) {
+        /* eslint-disable no-console */
+        if (typeof console !== 'undefined' && console.log) {
+            console.log.apply(console, args);
+        }
+        /* eslint-enable no-console */
+    }
+}
+
+/**
+ * If in __DEV__ environment, get console printable message for users hint.
+ * Parameters are separated by ' '.
+ * @usuage
+ * makePrintable('This is an error on', someVar, someObj);
+ *
+ * @param hintInfo anything about the current execution context to hint users.
+ * @throws Error
+ */
+export function makePrintable(...hintInfo: unknown[]) {
+    let msg = '';
+
+    if (__DEV__) {
+        // Fuzzy stringify for print.
+        // This code only exist in dev environment.
+        msg = map(hintInfo, arg => {
+            if (isString(arg)) {
+                // Print without quotation mark for some statement.
+                return arg;
+            }
+            else if (typeof JSON !== 'undefined' && JSON.stringify) {
+                try {
+                    return JSON.stringify(arg, function (n, val) {
+                        return val === void 0 ? 'undefined'
+                            : val === Infinity ? 'Infinity'
+                            : val === -Infinity ? '-Infinity'
+                            : eqNaN(val) ? 'NaN'
+                            : val instanceof Date ? 'Date(' + val.toISOString() + ')'
+                            : isFunction(val) ? 'function () { ... }'
+                            : isRegExp(val) ? val + ''
+                            : val;
+                    });
+                    // In most cases the info object is small, so do not line break.
+                }
+                catch (err) {
+                    return '?';
+                }
+            }
+            else {
+                return '?';
+            }
+        }).join(' ');
+    }
+
+    return msg;
+}
+
+/**
+ * @throws Error
+ */
+export function throwError(msg?: string) {
+    throw new Error(msg);
+}
diff --git a/src/util/model.ts b/src/util/model.ts
index ee975c5..699b297 100644
--- a/src/util/model.ts
+++ b/src/util/model.ts
@@ -791,7 +791,7 @@ export function parseFinder(
     }
 
     const defaultMainType = opt ? opt.defaultMainType : null;
-    const queryOptionMap = createHashMap<QueryReferringOption, ComponentMainType>();
+    const queryOptionMap = createHashMap<QueryReferringUserOption, ComponentMainType>();
     const result = {} as ParsedModelFinder;
 
     each(finder, function (value, key) {
@@ -803,7 +803,7 @@ export function parseFinder(
 
         const parsedKey = key.match(/^(\w+)(Index|Id|Name)$/) || [];
         const mainType = parsedKey[1];
-        const queryType = (parsedKey[2] || '').toLowerCase() as keyof QueryReferringOption;
+        const queryType = (parsedKey[2] || '').toLowerCase() as keyof QueryReferringUserOption;
 
         if (
             !mainType
@@ -836,7 +836,7 @@ export function parseFinder(
     return result;
 }
 
-type QueryReferringOption = {
+export type QueryReferringUserOption = {
     index?: ModelFinderIndexQuery,
     id?: ModelFinderIdQuery,
     name?: ModelFinderNameQuery,
@@ -857,7 +857,7 @@ export type QueryReferringOpt = {
 export function queryReferringComponents(
     ecModel: GlobalModel,
     mainType: ComponentMainType,
-    userOption: QueryReferringOption,
+    userOption: QueryReferringUserOption,
     opt: QueryReferringOpt
 ): {
     // Always be array rather than null/undefined, which is convenient to use.
diff --git a/src/util/number.ts b/src/util/number.ts
index d303534..68cad8b 100644
--- a/src/util/number.ts
+++ b/src/util/number.ts
@@ -286,7 +286,8 @@ export function isRadianAroundZero(val: number): boolean {
 const TIME_REG = /^(?:(\d{4})(?:[-\/](\d{1,2})(?:[-\/](\d{1,2})(?:[T ](\d{1,2})(?::(\d{1,2})(?::(\d{1,2})(?:[.,](\d+))?)?)?(Z|[\+\-]\d\d:?\d\d)?)?)?)?)?$/; // jshint ignore:line
 
 /**
- * @param value These values can be accepted:
+ * @param value valid type: number | string | Date, otherwise return `new Date(NaN)`
+ *   These values can be accepted:
  *   + An instance of Date, represent a time in its own time zone.
  *   + Or string in a subset of ISO 8601, only including:
  *     + only year, month, date: '2012-03', '2012-03-01', '2012-03-01 05', '2012-03-01 05:06',
@@ -298,9 +299,9 @@ const TIME_REG = /^(?:(\d{4})(?:[-\/](\d{1,2})(?:[-\/](\d{1,2})(?:[T ](\d{1,2})(
  *     '2012', '2012-3-1', '2012/3/1', '2012/03/01',
  *     '2009/6/12 2:00', '2009/6/12 2:05:08', '2009/6/12 2:05:08.123'
  *   + a timestamp, which represent a time in UTC.
- * @return date
+ * @return date Never be null/undefined. If invalid, return `new Date(NaN)`.
  */
-export function parseDate(value: number | string | Date): Date {
+export function parseDate(value: unknown): Date {
     if (value instanceof Date) {
         return value;
     }
@@ -358,7 +359,7 @@ export function parseDate(value: number | string | Date): Date {
         return new Date(NaN);
     }
 
-    return new Date(Math.round(value));
+    return new Date(Math.round(value as number));
 }
 
 /**
diff --git a/src/util/types.ts b/src/util/types.ts
index c40b281..f475a7c 100644
--- a/src/util/types.ts
+++ b/src/util/types.ts
@@ -372,7 +372,9 @@ export const SERIES_LAYOUT_BY_ROW = 'row' as const;
 
 export type SeriesLayoutBy = typeof SERIES_LAYOUT_BY_COLUMN | typeof SERIES_LAYOUT_BY_ROW;
 // null/undefined/'auto': auto detect header, see "src/data/helper/sourceHelper".
-export type OptionSourceHeader = boolean | 'auto';
+// If number, means header lines count, or say, `startIndex`.
+// Like `sourceHeader: 2`, means line 0 and line 1 are header, data start from line 2.
+export type OptionSourceHeader = boolean | 'auto' | number;
 
 export type SeriesDataType = 'main' | 'node' | 'edge';
 
@@ -1395,8 +1397,11 @@ export interface SeriesSamplingOptionMixin {
 
 export interface SeriesEncodeOptionMixin {
     datasetIndex?: number;
+    datasetId?: string | number;
     seriesLayoutBy?: SeriesLayoutBy;
     sourceHeader?: OptionSourceHeader;
     dimensions?: DimensionDefinitionLoose[];
     encode?: OptionEncode
 }
+
+export type SeriesEncodableModel = SeriesModel<SeriesOption & SeriesEncodeOptionMixin>;
diff --git a/test/data-transform.html b/test/data-transform.html
new file mode 100644
index 0000000..05ada21
--- /dev/null
+++ b/test/data-transform.html
@@ -0,0 +1,661 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+
+
+<html>
+    <head>
+        <meta charset="utf-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1" />
+        <script src="lib/esl.js"></script>
+        <script src="lib/config.js"></script>
+        <script src="lib/jquery.min.js"></script>
+        <script src="lib/facePrint.js"></script>
+        <script src="lib/testHelper.js"></script>
+        <!-- <script src="ut/lib/canteen.js"></script> -->
+        <link rel="stylesheet" href="lib/reset.css" />
+    </head>
+    <body>
+        <style>
+        </style>
+
+
+
+        <!-- <div id="main_simplest_pies"></div>
+        <div id="main_pies_encode_price"></div>
+        <div id="main_cartesian_parse_trim_time_reg"></div> -->
+        <div id="main_cartesian_sort"></div>
+
+
+
+        <script>
+            var FOOD_SALES_PRICE_HEADER =
+                ['Product', 'Sales', 'Price', 'Year'];
+            var FOOD_SALES_PRICE_NO_HEADER = [
+                ['Cake', 123, 32.12, 2011],
+                ['Cereal', 231, 14.41, 2011],
+                ['Tofu', 235, 5.14, 2011],
+                ['Dumpling', 341, 25.53, 2011],
+                ['Biscuit', 122, 29.36, 2011],
+                ['Cake', 143, 30.21, 2012],
+                ['Cereal', 201, 19.85, 2012],
+                ['Tofu', 255, 7.61, 2012],
+                ['Dumpling', 241, 27.89, 2012],
+                ['Biscuit', 102, 34.53, 2012],
+                ['Cake', 153, 28.82, 2013],
+                ['Cereal', 181, 21.16, 2013],
+                ['Tofu', 295, 4.24, 2013],
+                ['Dumpling', 281, 31.66, 2013],
+                ['Biscuit', 92, 39.82, 2013],
+                ['Cake', 223, 29.22, 2014],
+                ['Cereal', 211, 17.88, 2014],
+                ['Tofu', 345, 3.09, 2014],
+                ['Dumpling', 211, 35.05, 2014],
+                ['Biscuit', 72, 24.19, 2014]
+            ];
+            var FOOD_SALES_PRICE_WITH_HEADER =
+                [FOOD_SALES_PRICE_HEADER]
+                .concat(FOOD_SALES_PRICE_NO_HEADER);
+
+            var NAME_SCORE_DIM = {
+                Name: 0,
+                Age: 1,
+                Sex: 2,
+                Score: 3,
+                Date: 4
+            };
+            var NAME_SCORE_DIRTY_DATA_HEADER =
+                ['Name', 'Age', 'Sex', 'Score', 'Date'];
+            var NAME_SCORE_DIRTY_DATA_NO_HEADER = [
+                // This is for trim testing.
+                [' Jobs Mat ', 41, 'male', 314, '2011-02-12'],
+                // This is for edge testing (03-01, 20)
+                ['Hottlyuipe Xu ', 20, 'female', 351, '2011-03-01'],
+                [' Jone Mat ', 52, 'male', 287, '2011-02-14'],
+                ['Uty Xu', 19, 'male', 219, '2011-02-18'],
+                ['Tatum von Godden', 25, 'female', 301, '2011-04-02'],
+                ['Must Godden', 31, 'female', 235, '2011-03-19'],
+                ['Caoas Xu', 71, 'male', 318, '2011-02-24'],
+                ['Malise Mat', 67, 'female', 366, '2011-03-12'],
+            ];
+            var NAME_SCORE_DIRTY_DATA_WITH_HEADER =
+                [NAME_SCORE_DIRTY_DATA_HEADER]
+                .concat(NAME_SCORE_DIRTY_DATA_NO_HEADER);
+
+        </script>
+
+
+
+
+        <!-- ------------------------------- -->
+        <!-- ------------------------------- -->
+        <!-- ------------------------------- -->
+        <!-- ------------------------------- -->
+
+
+
+
+        <script>
+        require(['echarts'], function (echarts) {
+            var option = {
+                dataset: [{
+                    source: FOOD_SALES_PRICE_WITH_HEADER
+                }, {
+                    transform: {
+                        type: 'filter',
+                        config: { dimension: 'Year', value: 2011 }
+                    }
+                }, {
+                    transform: {
+                        type: 'filter',
+                        config: { dimension: 'Year', value: 2012 }
+                    }
+                }, {
+                    transform: {
+                        type: 'filter',
+                        config: { dimension: 'Year', value: 2013 }
+                    }
+                }],
+                tooltip: {},
+                series: [{
+                    type: 'pie',
+                    datasetIndex: 1,
+                    radius: 50,
+                    center: ['25%', '50%']
+                }, {
+                    type: 'pie',
+                    datasetIndex: 2,
+                    radius: 50,
+                    center: ['50%', '50%']
+                }, {
+                    type: 'pie',
+                    datasetIndex: 3,
+                    radius: 50,
+                    center: ['75%', '50%']
+                }]
+            };
+
+            var chart = testHelper.create(echarts, 'main_simplest_pies', {
+                title: [
+                    '**3 pies** should shoud **Sales data** (interger about hundreds)',
+                    'Pie by "Year", Sector by "Product"'
+                ],
+                height: 300,
+                option: option
+            });
+        });
+        </script>
+
+
+
+
+
+
+        <script>
+        require(['echarts'], function (echarts) {
+            var option = {
+                dataset: [{
+                    source: FOOD_SALES_PRICE_WITH_HEADER
+                }, {
+                    transform: {
+                        type: 'filter',
+                        config: { dimension: 'Product', value: 'Tofu' }
+                    }
+                }, {
+                    transform: {
+                        type: 'filter',
+                        config: { dimension: 'Product', value: 'Biscuit' }
+                    }
+                }, {
+                    transform: {
+                        type: 'filter',
+                        config: { dimension: 'Product', value: 'Dumpling' }
+                    }
+                }],
+                series: [{
+                    type: 'pie',
+                    datasetIndex: 1,
+                    center: ['25%', '50%'],
+                    radius: 50,
+                    encode: { itemName: 'Year', value: 'Price' },
+                }, {
+                    type: 'pie',
+                    datasetIndex: 2,
+                    center: ['50%', '50%'],
+                    radius: 50,
+                    encode: { itemName: 'Year', value: 'Price' }
+                }, {
+                    type: 'pie',
+                    datasetIndex: 3,
+                    center: ['75%', '50%'],
+                    radius: 50,
+                    encode: { itemName: 'Year', value: 'Price' }
+                }]
+            };
+
+            var chart = testHelper.create(echarts, 'main_pies_encode_price', {
+                title: [
+                    '**3 pies** should shoud **Prices data** (float like xx.xx)',
+                    'Pie by "Product", Sector by "Year"'
+                ],
+                height: 300,
+                option: option
+            });
+        });
+        </script>
+
+
+
+
+
+
+        <script>
+        require(['echarts'], function (echarts) {
+            var option = {
+                dataset: [{ source: NAME_SCORE_DIRTY_DATA_WITH_HEADER }],
+                tooltip: {},
+                grid: [],
+                xAxis: [],
+                yAxis: [],
+                series: []
+            };
+
+            var leftStart = 50;
+            var leftBase = leftStart;
+            var topBase = 30;
+            var gridWidth = 100;
+            var gridHeight = 100;
+            var gapWidth = 70;
+            var gapHeight = 80;
+            var chartWidth = 800;
+
+            function addCartesian(opt) {
+                option.grid.push({
+                    left: leftBase,
+                    top: topBase,
+                    width: gridWidth,
+                    height: gridHeight
+                });
+
+                leftBase += gridWidth + gapWidth;
+                if (leftBase + gridWidth > chartWidth) {
+                    leftBase = leftStart;
+                    topBase += gridHeight + gapHeight;
+                }
+
+                option.xAxis.push({
+                    name: opt.xAxis.name,
+                    type: 'category',
+                    nameLocation: 'middle',
+                    nameGap: 30,
+                    gridIndex: option.grid.length - 1
+                });
+                option.yAxis.push({
+                    gridIndex: option.grid.length - 1
+                });
+
+                var series = opt.series;
+
+                series.type = 'scatter';
+                series.xAxisIndex = option.xAxis.length - 1;
+                series.yAxisIndex = option.yAxis.length - 1;
+                series.label = { show: true, position: 'bottom' };
+                series.encode = {
+                    x: NAME_SCORE_DIM.Date,
+                    y: NAME_SCORE_DIM.Score,
+                    label: series.encode && series.encode.label || [NAME_SCORE_DIM.Name],
+                    tooltip: [
+                        NAME_SCORE_DIM.Name,
+                        NAME_SCORE_DIM.Date,
+                        NAME_SCORE_DIM.Score,
+                        NAME_SCORE_DIM.Sex,
+                        NAME_SCORE_DIM.Age
+                    ]
+                };
+                option.series.push(series);
+            }
+
+
+            option.dataset.push({
+                id: 'a',
+                transform: {
+                    type: 'filter',
+                    // print: true,
+                    config: { dimension: NAME_SCORE_DIM.Name, eq: 'Jobs Mat', parse: 'trim' }
+                }
+            });
+            addCartesian({
+                series: {
+                    datasetId: 'a'
+                },
+                xAxis: { name: 'Only show "Jobs Mat"' }
+            });
+
+            option.dataset.push({
+                id: 'b',
+                transform: {
+                    type: 'filter',
+                    // print: true,
+                    config: { dimension: NAME_SCORE_DIM.Date, lt: '2011-03', gte: '2011-02', parse: 'time' }
+                }
+            });
+            addCartesian({
+                series: {
+                    datasetId: 'b'
+                },
+                xAxis: { name: 'Show four points\nDate in 2011-02' }
+            });
+
+            option.dataset.push({
+                id: 'c',
+                transform: {
+                    type: 'filter',
+                    // print: true,
+                    config: { dimension: NAME_SCORE_DIM.Date, lte: '2011-03', gte: '2011-02-29', parse: 'time' }
+                }
+            });
+            addCartesian({
+                series: {
+                    datasetId: 'c'
+                },
+                xAxis: { name: 'Show "Hottlyuipe Xu"' }
+            });
+
+            option.dataset.push({
+                id: 'd',
+                transform: {
+                    type: 'filter',
+                    // print: true,
+                    config: { dimension: NAME_SCORE_DIM.Name, reg: /\sXu$/, parse: 'trim' }
+                }
+            });
+            addCartesian({
+                series: {
+                    datasetId: 'd'
+                },
+                xAxis: { name: 'Show three points\nname reg /sXu$/' }
+            });
+
+            option.dataset.push({
+                id: 'e',
+                transform: {
+                    type: 'filter',
+                    // print: true,
+                    config: { dimension: NAME_SCORE_DIM.Sex, ne: 'male', parse: 'trim' }
+                }
+            });
+            addCartesian({
+                series: {
+                    datasetId: 'e',
+                    encode: { label: [NAME_SCORE_DIM.Sex] }
+                },
+                xAxis: { name: 'Show four points\n!male' }
+            });
+
+            option.dataset.push({
+                id: 'f',
+                transform: {
+                    type: 'filter',
+                    // print: true,
+                    config: {
+                        and: [
+                            { dimension: NAME_SCORE_DIM.Sex, eq: 'male', parse: 'trim' },
+                            { dimension: NAME_SCORE_DIM.Score, '>': 300 }
+                        ]
+                    }
+                }
+            });
+            addCartesian({
+                series: {
+                    datasetId: 'f',
+                    encode: { label: [NAME_SCORE_DIM.Sex] }
+                },
+                xAxis: { name: 'Show two points\nmale > 300' }
+            });
+
+
+            option.dataset.push({
+                id: 'g',
+                transform: {
+                    type: 'filter',
+                    // print: true,
+                    config: {
+                        and: [
+                            { dimension: NAME_SCORE_DIM.Sex, eq: 'female' },
+                            {
+                                or: [
+                                    { dimension: NAME_SCORE_DIM.Age, '>=': 20, '<=': 30 },
+                                    { dimension: NAME_SCORE_DIM.Age, '>=': 60 }
+                                ]
+                            }
+                        ]
+                    }
+                }
+            });
+            addCartesian({
+                series: {
+                    datasetId: 'g',
+                    encode: { label: [NAME_SCORE_DIM.Sex] }
+                },
+                xAxis: { name: 'Show three points\nfemale && (20-30 || 60)' }
+            });
+
+            option.dataset.push({
+                id: 'h',
+                transform: {
+                    type: 'filter',
+                    // print: true,
+                    config: {
+                        not: {
+                            and: [
+                                { dimension: NAME_SCORE_DIM.Sex, eq: 'female' },
+                                {
+                                    or: [
+                                        { dimension: NAME_SCORE_DIM.Age, '>=': 20, '<=': 30 },
+                                        { dimension: NAME_SCORE_DIM.Age, '>=': 60 }
+                                    ]
+                                }
+                            ]
+                        }
+                    }
+                }
+            });
+            addCartesian({
+                series: {
+                    datasetId: 'h',
+                    encode: { label: [NAME_SCORE_DIM.Sex] }
+                },
+                xAxis: { name: 'Show five points\n!(female && (20-30 || 60))' }
+            });
+
+
+            option.dataset.push({
+                id: 'i',
+                transform: {
+                    type: 'filter',
+                    // print: true,
+                    config: true
+                }
+            });
+            addCartesian({
+                series: {
+                    datasetId: 'i',
+                    encode: { label: [NAME_SCORE_DIM.Sex] }
+                },
+                xAxis: { name: 'Show all eight points\nconfig: true' }
+            });
+
+
+
+
+            var chart = testHelper.create(echarts, 'main_cartesian_parse_trim_time_reg', {
+                title: [
+                    'Check each cartesians.',
+                    'The expectationa are below each cartesian.'
+                ],
+                width: chartWidth,
+                height: 600,
+                option: option
+            });
+        });
+        </script>
+
+
+
+
+
+
+
+
+
+
+        <script>
+        require(['echarts'], function (echarts) {
+            var option = {
+                dataset: [{ source: NAME_SCORE_DIRTY_DATA_WITH_HEADER }],
+                tooltip: {},
+                grid: [],
+                xAxis: [],
+                yAxis: [],
+                series: []
+            };
+
+            var leftStart = 50;
+            var leftBase = leftStart;
+            var topBase = 30;
+            var gridWidth = 100;
+            var gridHeight = 100;
+            var gapWidth = 70;
+            var gapHeight = 80;
+            var chartWidth = 800;
+
+            function addCartesian(opt) {
+                option.grid.push({
+                    left: leftBase,
+                    top: topBase,
+                    width: gridWidth,
+                    height: gridHeight
+                });
+
+                leftBase += gridWidth + gapWidth;
+                if (leftBase + gridWidth > chartWidth) {
+                    leftBase = leftStart;
+                    topBase += gridHeight + gapHeight;
+                }
+
+                option.xAxis.push({
+                    name: opt.xAxis.name,
+                    type: 'category',
+                    nameLocation: 'middle',
+                    nameGap: 30,
+                    gridIndex: option.grid.length - 1
+                });
+                option.yAxis.push({
+                    gridIndex: option.grid.length - 1
+                });
+
+                var series = opt.series;
+
+                series.type = 'bar';
+                series.xAxisIndex = option.xAxis.length - 1;
+                series.yAxisIndex = option.yAxis.length - 1;
+                series.label = { show: true, position: 'top' };
+                series.encode = {
+                    x: NAME_SCORE_DIM.Date,
+                    y: NAME_SCORE_DIM.Score,
+                    label: series.encode && series.encode.label || [NAME_SCORE_DIM.Name],
+                    tooltip: [
+                        NAME_SCORE_DIM.Name,
+                        NAME_SCORE_DIM.Date,
+                        NAME_SCORE_DIM.Score,
+                        NAME_SCORE_DIM.Sex,
+                        NAME_SCORE_DIM.Age
+                    ]
+                };
+                option.series.push(series);
+            }
+
+
+            option.dataset.push({
+                id: 'a',
+                transform: {
+                    type: 'sort',
+                    // print: true,
+                    config: { dimension: NAME_SCORE_DIM.Score, order: 'asc' }
+                }
+            });
+            addCartesian({
+                series: {
+                    datasetId: 'a'
+                },
+                xAxis: { name: 'Show all eight\norder by Score asc' }
+            });
+
+            option.dataset.push({
+                id: 'b',
+                transform: {
+                    type: 'sort',
+                    // print: true,
+                    config: { dimension: NAME_SCORE_DIM.Age, order: 'desc' }
+                }
+            });
+            addCartesian({
+                series: {
+                    datasetId: 'b',
+                    encode: { label: NAME_SCORE_DIM.Age }
+                },
+                xAxis: { name: 'Show all eight\norder by Age desc' }
+            });
+
+            option.dataset.push({
+                id: 'c',
+                transform: {
+                    type: 'sort',
+                    // print: true,
+                    config: [
+                        { dimension: NAME_SCORE_DIM.Sex, order: 'asc' },
+                        { dimension: NAME_SCORE_DIM.Score, order: 'desc' }
+                    ]
+                }
+            });
+            addCartesian({
+                series: {
+                    datasetId: 'c',
+                    encode: { label: NAME_SCORE_DIM.Sex }
+                },
+                xAxis: { name: 'Show all eight\nSex asc, Score desc' }
+            });
+
+            option.dataset.push({
+                id: 'd',
+                transform: {
+                    type: 'sort',
+                    // print: true,
+                    config: [
+                        { dimension: NAME_SCORE_DIM.Date, order: 'asc', parse: 'time' }
+                    ]
+                }
+            });
+            addCartesian({
+                series: {
+                    datasetId: 'd'
+                },
+                xAxis: { name: 'Show all eight\nDate asc' }
+            });
+
+
+            option.dataset.push({
+                id: 'e',
+                transform: [{
+                    type: 'filter',
+                    // print: true,
+                    config: { dimension: NAME_SCORE_DIM.Age, lte: 40, gte: 20 }
+                }, {
+                    type: 'sort',
+                    // print: true,
+                    config: { dimension: NAME_SCORE_DIM.Score, order: 'asc' }
+                }]
+            });
+            addCartesian({
+                series: {
+                    datasetId: 'e'
+                },
+                xAxis: { name: 'Show three ponits\nFilter by Age 20-40\nOrder by Score' }
+            });
+
+
+            var chart = testHelper.create(echarts, 'main_cartesian_sort', {
+                title: [
+                    'Check each cartesians.',
+                    'The expectationa are below each cartesian.'
+                ],
+                width: chartWidth,
+                height: 600,
+                option: option
+            });
+        });
+        </script>
+
+
+
+
+
+    </body>
+</html>
+


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


[incubator-echarts] 04/10: fix: fix dimension inherit rule.

Posted by su...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit f8a59dfa6b2c9f651582c053e851d5286afc7a63
Author: 100pah <su...@gmail.com>
AuthorDate: Fri Jul 31 16:20:01 2020 +0800

    fix: fix dimension inherit rule.
---
 src/component/transform/filterTransform.ts |   6 +-
 src/component/transform/sortTransform.ts   |   6 +-
 src/data/Source.ts                         |  22 ++++-
 src/data/helper/sourceHelper.ts            |  22 +++--
 src/data/helper/sourceManager.ts           |  45 ++++++---
 src/data/helper/transform.ts               |  74 ++++++++++-----
 test/data-transform.html                   | 145 ++++++++++++++++++++++++++++-
 7 files changed, 264 insertions(+), 56 deletions(-)

diff --git a/src/component/transform/filterTransform.ts b/src/component/transform/filterTransform.ts
index 4b13008..4126f98 100644
--- a/src/component/transform/filterTransform.ts
+++ b/src/component/transform/filterTransform.ts
@@ -65,7 +65,7 @@ export const filterTransform: ExternalDataTransform<FilterTransformOption> = {
                     if (__DEV__) {
                         errMsg = makePrintable(
                             'Can not find dimension info via: "' + dimLoose + '".\n',
-                            'Existing dimensions: ', source.dimensions, '.\n',
+                            'Existing dimensions: ', source.getDimensionInfoAll(), '.\n',
                             'Illegal condition:', exprOption, '.\n'
                         );
                     }
@@ -93,9 +93,7 @@ export const filterTransform: ExternalDataTransform<FilterTransformOption> = {
         }
 
         return {
-            data: resultData,
-            dimensions: source.dimensions,
-            sourceHeader: sourceHeaderCount
+            data: resultData
         };
     }
 };
diff --git a/src/component/transform/sortTransform.ts b/src/component/transform/sortTransform.ts
index 9dfce97..e4f75e5 100644
--- a/src/component/transform/sortTransform.ts
+++ b/src/component/transform/sortTransform.ts
@@ -124,7 +124,7 @@ export const sortTransform: ExternalDataTransform<SortTransformOption> = {
                 if (__DEV__) {
                     errMsg = makePrintable(
                         'Can not find dimension info via: "' + dimLoose + '".\n',
-                        'Existing dimensions: ', source.dimensions, '.\n',
+                        'Existing dimensions: ', source.getDimensionInfoAll(), '.\n',
                         'Illegal config:', orderExpr, '.\n'
                     );
                 }
@@ -214,9 +214,7 @@ export const sortTransform: ExternalDataTransform<SortTransformOption> = {
         }
 
         return {
-            data: resultData,
-            dimensions: source.dimensions,
-            sourceHeader: sourceHeaderCount
+            data: resultData
         };
     }
 };
diff --git a/src/data/Source.ts b/src/data/Source.ts
index bed37d6..06d5a27 100644
--- a/src/data/Source.ts
+++ b/src/data/Source.ts
@@ -17,16 +17,18 @@
 * under the License.
 */
 
-import {createHashMap, isTypedArray, HashMap} from 'zrender/src/core/util';
+import {isTypedArray, HashMap} from 'zrender/src/core/util';
 import {
     SourceFormat, SeriesLayoutBy, DimensionDefinition,
-    OptionEncodeValue, OptionSourceData, OptionEncode,
+    OptionEncodeValue, OptionSourceData,
     SOURCE_FORMAT_ORIGINAL,
     SERIES_LAYOUT_BY_COLUMN,
     SOURCE_FORMAT_UNKNOWN,
     SOURCE_FORMAT_KEYED_COLUMNS,
     SOURCE_FORMAT_TYPED_ARRAY,
-    DimensionName
+    DimensionName,
+    OptionSourceHeader,
+    DimensionDefinitionLoose
 } from '../util/types';
 
 /**
@@ -65,6 +67,12 @@ import {
  * + "unknown"
  */
 
+export interface SourceMetaRawOption {
+    seriesLayoutBy: SeriesLayoutBy;
+    sourceHeader: OptionSourceHeader;
+    dimensions: DimensionDefinitionLoose[];
+}
+
 class Source {
 
     /**
@@ -107,6 +115,11 @@ class Source {
      */
     readonly dimensionsDetectCount: number;
 
+    /**
+     * Raw props from user option.
+     */
+    readonly metaRawOption: SourceMetaRawOption;
+
 
     constructor(fields: {
         data: OptionSourceData,
@@ -118,6 +131,8 @@ class Source {
         startIndex?: number, // default: 0
         dimensionsDetectCount?: number,
 
+        metaRawOption?: SourceMetaRawOption,
+
         // [Caveat]
         // This is the raw user defined `encode` in `series`.
         // If user not defined, DO NOT make a empty object or hashMap here.
@@ -136,6 +151,7 @@ class Source {
         this.dimensionsDefine = fields.dimensionsDefine;
         this.dimensionsDetectCount = fields.dimensionsDetectCount;
         this.encodeDefine = fields.encodeDefine;
+        this.metaRawOption = fields.metaRawOption;
     }
 
     /**
diff --git a/src/data/helper/sourceHelper.ts b/src/data/helper/sourceHelper.ts
index 9ae0151..0905908 100644
--- a/src/data/helper/sourceHelper.ts
+++ b/src/data/helper/sourceHelper.ts
@@ -33,9 +33,10 @@ import {
     hasOwn,
     HashMap,
     isNumber,
-    clone
+    clone,
+    defaults
 } from 'zrender/src/core/util';
-import Source from '../Source';
+import Source, { SourceMetaRawOption } from '../Source';
 
 import {
     SOURCE_FORMAT_ORIGINAL,
@@ -90,12 +91,6 @@ type SeriesEncodeInternal = {
     [key in keyof OptionEncode]: DimensionIndex[];
 };
 
-export interface SourceMetaRawOption {
-    seriesLayoutBy: SeriesLayoutBy;
-    sourceHeader: OptionSourceHeader;
-    dimensions: DimensionDefinitionLoose[];
-}
-
 export function detectSourceFormat(data: DatasetOption['source']): SourceFormat {
     let sourceFormat: SourceFormat = SOURCE_FORMAT_UNKNOWN;
 
@@ -170,12 +165,21 @@ export function createSource(
         dimensionsDefine: dimInfo.dimensionsDefine,
         startIndex: dimInfo.startIndex,
         dimensionsDetectCount: dimInfo.dimensionsDetectCount,
-        encodeDefine: makeEncodeDefine(encodeDefine)
+        encodeDefine: makeEncodeDefine(encodeDefine),
+        metaRawOption: clone(thisMetaRawOption)
     });
 
     return source;
 }
 
+// See [DIMENSION_INHERIT_RULE] in `sourceManager.ts`.
+export function inheritSourceMetaRawOption(opt: {
+    parent: SourceMetaRawOption, // Can be null/undefined
+    thisNew: SourceMetaRawOption // Must be object
+}) {
+    return defaults(opt.thisNew, opt.parent);
+}
+
 /**
  * Clone except source data.
  */
diff --git a/src/data/helper/sourceManager.ts b/src/data/helper/sourceManager.ts
index 74ed264..5a3f9ea 100644
--- a/src/data/helper/sourceManager.ts
+++ b/src/data/helper/sourceManager.ts
@@ -28,7 +28,7 @@ import {
 } from '../../util/types';
 import {
     querySeriesUpstreamDatasetModel, queryDatasetUpstreamDatasetModels,
-    createSource, SourceMetaRawOption, cloneSourceShallow
+    createSource, SourceMetaRawOption, cloneSourceShallow, inheritSourceMetaRawOption
 } from './sourceHelper';
 import { applyDataTransform } from './transform';
 
@@ -53,6 +53,27 @@ import { applyDataTransform } from './transform';
  * They will not be calculated until `source` is about to be visited (to prevent from
  * duplicate calcuation). `source` is visited only in series and input to transforms.
  *
+ * [DIMENSION_INHERIT_RULE]:
+ * By default the dimensions are inherited from ancestors, unless a transform return
+ * a new dimensions definition.
+ * Consider the case:
+ * ```js
+ * dataset: [{
+ *     source: [ ['Product', 'Sales', 'Prise'], ['Cookies', 321, 44.21], ...]
+ * }, {
+ *     transform: { type: 'filter', ... }
+ * }]
+ *
+ * dataset: [{
+ *     dimension: ['Product', 'Sales', 'Prise'],
+ *     source: [ ['Cookies', 321, 44.21], ...]
+ * }, {
+ *     transform: { type: 'filter', ... }
+ * }]
+ * ```
+ * The two types of option should have the same behavior after transform.
+ *
+ *
  * [SCENARIO]:
  * (1) Provide source data directly:
  * ```js
@@ -172,16 +193,15 @@ export class SourceManager {
             const seriesModel = sourceHost as SeriesEncodableModel;
             let data;
             let sourceFormat: SourceFormat;
-            let upMetaRawOption;
+            let upSource;
 
             // Has upstream dataset
             if (hasUpstream) {
                 const upSourceMgr = upSourceMgrList[0];
                 upSourceMgr.prepareSource();
-                const upSource = upSourceMgr.getSource();
+                upSource = upSourceMgr.getSource();
                 data = upSource.data;
                 sourceFormat = upSource.sourceFormat;
-                upMetaRawOption = upSourceMgr._getSourceMetaRawOption();
                 upstreamSignList = [upSourceMgr._getVersionSign()];
             }
             // Series data is from own.
@@ -192,11 +212,12 @@ export class SourceManager {
                 upstreamSignList = [];
             }
 
-            const thisMetaRawOption = defaults(
-                this._getSourceMetaRawOption(),
-                // See [REQUIREMENT MEMO], merge settings on series and parent dataset if it is root.
-                upMetaRawOption
-            );
+            // See [REQUIREMENT_MEMO], merge settings on series and parent dataset if it is root.
+            const thisMetaRawOption = inheritSourceMetaRawOption({
+                parent: upSource ? upSource.metaRawOption : null,
+                thisNew: this._createSourceMetaRawOption()
+            });
+
             resultSourceList = [createSource(
                 data,
                 thisMetaRawOption,
@@ -218,7 +239,7 @@ export class SourceManager {
                 const sourceData = datasetModel.get('source', true);
                 resultSourceList = [createSource(
                     sourceData,
-                    this._getSourceMetaRawOption(),
+                    this._createSourceMetaRawOption(),
                     null,
                     // Note: dataset option does not have `encode`.
                     null
@@ -329,7 +350,7 @@ export class SourceManager {
         }
     }
 
-    private _getSourceMetaRawOption(): SourceMetaRawOption {
+    private _createSourceMetaRawOption(): SourceMetaRawOption {
         const sourceHost = this._sourceHost;
         let seriesLayoutBy: SeriesLayoutBy;
         let sourceHeader: OptionSourceHeader;
@@ -339,7 +360,7 @@ export class SourceManager {
             sourceHeader = sourceHost.get('sourceHeader', true);
             dimensions = sourceHost.get('dimensions', true);
         }
-        // See [REQUIREMENT MEMO], `non-root-dataset` do not support them.
+        // See [REQUIREMENT_MEMO], `non-root-dataset` do not support them.
         else if (!this._getUpstreamSourceManagers().length) {
             const model = sourceHost as DatasetModel;
             seriesLayoutBy = model.get('seriesLayoutBy', true);
diff --git a/src/data/helper/transform.ts b/src/data/helper/transform.ts
index e6150b1..d657d52 100644
--- a/src/data/helper/transform.ts
+++ b/src/data/helper/transform.ts
@@ -32,7 +32,7 @@ import {
     getRawSourceItemGetter, getRawSourceDataCounter, getRawSourceValueGetter
 } from './dataProvider';
 import { parseDataValue } from './parseDataValue';
-import { createSource } from './sourceHelper';
+import { createSource, inheritSourceMetaRawOption } from './sourceHelper';
 import { consoleLog, makePrintable } from '../../util/log';
 
 
@@ -76,7 +76,7 @@ export interface ExternalDataTransformResultItem {
     dimensions?: DimensionDefinitionLoose[];
     sourceHeader?: OptionSourceHeader;
 }
-export interface ExternalDimensionDefinition extends DimensionDefinition {
+interface ExternalDimensionDefinition extends DimensionDefinition {
     // Mandatory
     index: DimensionIndex;
 }
@@ -95,13 +95,16 @@ class ExternalSource {
 
     data: OptionSourceData;
     sourceFormat: SourceFormat;
-    dimensions: ExternalDimensionDefinition[];
     sourceHeaderCount: number;
 
     getDimensionInfo(dim: DimensionLoose): ExternalDimensionDefinition {
         return;
     }
 
+    getDimensionInfoAll(): ExternalDimensionDefinition[] {
+        return;
+    }
+
     getRawDataItem(dataIndex: number): OptionDataItem {
         return;
     }
@@ -140,8 +143,13 @@ function createExternalSource(
     extSource.sourceFormat = sourceFormat;
     extSource.sourceHeaderCount = sourceHeaderCount;
 
+    // [MEMO]
     // Create a new dimensions structure for exposing.
-    const dimensions = extSource.dimensions = [] as ExternalDimensionDefinition[];
+    // Do not expose all dimension info to users directly.
+    // Becuase the dimension is probably auto detected from data and not might reliable.
+    // Should not lead the transformers to think that is relialbe and return it.
+    // See [DIMENSION_INHERIT_RULE] in `sourceManager.ts`.
+    const dimensions = [] as ExternalDimensionDefinition[];
     const dimsByName = {} as Dictionary<ExternalDimensionDefinition>;
     each(dimsDef, function (dimDef, idx) {
         const name = dimDef.name;
@@ -179,7 +187,7 @@ function createExternalSource(
         if (rawItem == null) {
             return;
         }
-        const dimDef = extSource.dimensions[dimIndex];
+        const dimDef = dimensions[dimIndex];
         // When `dimIndex` is `null`, `rawValueGetter` return the whole item.
         if (dimDef) {
             return rawValueGetter(rawItem, dimIndex, dimDef.name) as OptionDataValue;
@@ -187,6 +195,7 @@ function createExternalSource(
     };
 
     extSource.getDimensionInfo = bind(getDimensionInfo, null, dimensions, dimsByName);
+    extSource.getDimensionInfoAll = bind(getDimensionInfoAll, null, dimensions);
 
     return extSource;
 }
@@ -212,6 +221,12 @@ function getDimensionInfo(
     }
 }
 
+function getDimensionInfoAll(
+    dimensions: ExternalDimensionDefinition[]
+): ExternalDimensionDefinition[] {
+    return dimensions;
+}
+
 
 
 const externalTransformMap = createHashMap<ExternalDataTransform, string>();
@@ -241,22 +256,13 @@ export function applyDataTransform(
 
     for (let i = 0, len = pipedTransOption.length; i < len; i++) {
         const transOption = pipedTransOption[i];
-        sourceList = applySingleDataTransform(transOption, sourceList);
+        const isFinal = i === len - 1;
+        sourceList = applySingleDataTransform(transOption, sourceList, infoForPrint, isFinal);
         // piped transform only support single input, except the fist one.
         // piped transform only support single output, except the last one.
-        if (i < len - 1) {
+        if (!isFinal) {
             sourceList.length = Math.max(sourceList.length, 1);
         }
-
-        if (__DEV__) {
-            if (transOption.print) {
-                const printStrArr = map(sourceList, source => {
-                    return '--- datasetIndex: ' + infoForPrint.datasetIndex + ', transform result: ---\n'
-                        + makePrintable(source.data);
-                }).join('\n');
-                consoleLog(printStrArr);
-            }
-        }
     }
 
     return sourceList;
@@ -264,7 +270,9 @@ export function applyDataTransform(
 
 function applySingleDataTransform(
     rawTransOption: DataTransformOption,
-    upSourceList: Source[]
+    upSourceList: Source[],
+    infoForPrint: { datasetIndex: number },
+    isFinal: boolean
 ): Source[] {
     assert(upSourceList.length, 'Must have at least one upstream dataset.');
 
@@ -292,6 +300,23 @@ function applySingleDataTransform(
         })
     );
 
+    if (__DEV__) {
+        if (isFinal && transOption.print) {
+            const printStrArr = map(resultList, extSource => {
+                return [
+                    '--- datasetIndex: ' + infoForPrint.datasetIndex + '---',
+                    '- transform result data:',
+                    makePrintable(extSource.data),
+                    '- transform result dimensions:',
+                    makePrintable(extSource.dimensions),
+                    '- transform result sourceHeader: ' + extSource.sourceHeader,
+                    '------'
+                ].join('\n');
+            }).join('\n');
+            consoleLog(printStrArr);
+        }
+    }
+
     return map(resultList, function (result) {
         assert(
             isObject(result),
@@ -302,13 +327,18 @@ function applySingleDataTransform(
             'Result data should be object or array in data transform.'
         );
 
-        return createSource(
-            result.data,
-            {
+        const resultMetaRawOption = inheritSourceMetaRawOption({
+            parent: upSourceList[0].metaRawOption,
+            thisNew: {
                 seriesLayoutBy: SERIES_LAYOUT_BY_COLUMN,
                 sourceHeader: result.sourceHeader,
                 dimensions: result.dimensions
-            },
+            }
+        });
+
+        return createSource(
+            result.data,
+            resultMetaRawOption,
             null,
             null
         );
diff --git a/test/data-transform.html b/test/data-transform.html
index 05ada21..9a11dc7 100644
--- a/test/data-transform.html
+++ b/test/data-transform.html
@@ -37,10 +37,12 @@ under the License.
 
 
 
-        <!-- <div id="main_simplest_pies"></div>
+        <div id="main_simplest_pies"></div>
         <div id="main_pies_encode_price"></div>
-        <div id="main_cartesian_parse_trim_time_reg"></div> -->
+        <div id="main_cartesian_parse_trim_time_reg"></div>
         <div id="main_cartesian_sort"></div>
+        <div id="main_update_condition"></div>
+        <div id="main_update_source_no_dim_inside_data"></div>
 
 
 
@@ -656,6 +658,145 @@ under the License.
 
 
 
+
+
+        <script>
+        require(['echarts'], function (echarts) {
+            var currentYear = 2011
+            var option = {
+                title: {
+                    text: currentYear,
+                    left: 'center'
+                },
+                dataset: [{
+                    source: FOOD_SALES_PRICE_WITH_HEADER
+                }, {
+                    id: 'one_year',
+                    transform: {
+                        type: 'filter',
+                        config: { dimension: 'Year', value: currentYear }
+                    }
+                }],
+                tooltip: {},
+                xAxis: { type: 'category' },
+                yAxis: {},
+                series: [{
+                    name: 'one year',
+                    type: 'bar',
+                    datasetIndex: 1,
+                }]
+            };
+
+            var chart = testHelper.create(echarts, 'main_update_condition', {
+                title: [
+                    'click "next year", check the bar change.'
+                ],
+                height: 300,
+                option: option,
+                buttons: [{
+                    text: 'next year',
+                    onclick: function () {
+                        currentYear++;
+                        if (currentYear >= 2014) {
+                            currentYear = 2011
+                        }
+                        chart.setOption({
+                            title: {
+                                text: currentYear
+                            },
+                            dataset: {
+                                id: 'one_year',
+                                transform: {
+                                    type: 'filter',
+                                    config: { dimension: 'Year', value: currentYear }
+                                }
+                            }
+                        });
+                    }
+                }]
+            });
+        });
+        </script>
+
+
+
+
+
+        <script>
+        require(['echarts'], function (echarts) {
+            var currData = FOOD_SALES_PRICE_NO_HEADER;
+
+            var option = {
+                dataset: [{
+                    id: 'all_data',
+                    dimensions: FOOD_SALES_PRICE_HEADER,
+                    source: currData
+                }, {
+                    transform: {
+                        type: 'filter',
+                        print: true,
+                        config: { dimension: 'Price', '<': 40 }
+                    }
+                }],
+                tooltip: {},
+                legend: {},
+                xAxis: { type: 'category' },
+                yAxis: { scale: true },
+                series: [{
+                    name: 'all data',
+                    type: 'scatter',
+                    symbolSize: 15,
+                    encode: {
+                        itemId: 'Product',
+                        y: 'Price',
+                        label: [0, 1, 2, 3]
+                    },
+                    itemStyle: {
+                        color: '#999'
+                    }
+                }, {
+                    name: 'Price < 40',
+                    type: 'scatter',
+                    encode: {
+                        itemId: 'Product',
+                        x: 0,
+                        y: 'Price',
+                        label: [0, 1, 2, 3]
+                    },
+                    datasetIndex: 1
+                }]
+            };
+
+            var chart = testHelper.create(echarts, 'main_update_source_no_dim_inside_data', {
+                title: [
+                    'Init: all items that Price < 40',
+                    'click "add price 10", check the bar change.'
+                ],
+                height: 300,
+                option: option,
+                buttons: [{
+                    text: 'add price 10',
+                    onclick: function () {
+                        currData = echarts.util.clone(currData);
+                        echarts.util.each(currData, function (line) {
+                            line[2] += 10;
+                        });
+
+                        chart.setOption({
+                            dataset: {
+                                id: 'all_data',
+                                source: currData
+                            }
+                        });
+                    }
+                }]
+            });
+        });
+        </script>
+
+
+
+
     </body>
 </html>
 


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


[incubator-echarts] 09/10: test: add case for ecStat

Posted by su...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 2ce65aa05ba26adb0e66a0e9016a809aff69014b
Author: 100pah <su...@gmail.com>
AuthorDate: Fri Jul 31 18:05:53 2020 +0800

    test: add case for ecStat
---
 test/data-transform-ecStat.html | 120 ++++++++++++++++++++++++++++++++++++++++
 test/lib/ecStat-1.1.1.min.js    |   1 +
 test/lib/ecStatTransform.js     |  48 ++++++++++++++++
 3 files changed, 169 insertions(+)

diff --git a/test/data-transform-ecStat.html b/test/data-transform-ecStat.html
new file mode 100644
index 0000000..e05ddbc
--- /dev/null
+++ b/test/data-transform-ecStat.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+
+
+<html>
+    <head>
+        <meta charset="utf-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1" />
+        <script src="lib/jquery.min.js"></script>
+        <script src="lib/facePrint.js"></script>
+        <script src="lib/testHelper.js"></script>
+        <script src="../dist/echarts.js"></script>
+        <script src="lib/ecStat-1.1.1.min.js"></script>
+        <script src="lib/ecStatTransform.js"></script>
+        <!-- <script src="ut/lib/canteen.js"></script> -->
+        <link rel="stylesheet" href="lib/reset.css" />
+    </head>
+    <body>
+        <style>
+        </style>
+
+
+
+        <div id="main_regression"></div>
+
+
+
+
+        <script>
+
+            echarts.registerTransform(ecStatTransform(ecStat).regression);
+
+            var rawData = [
+                [1, 4862.4],
+                [2, 5294.7],
+                [3, 5934.5],
+                [4, 7171.0],
+                [5, 8964.4],
+                [6, 10202.2],
+                [7, 11962.5],
+                [8, 14928.3],
+                [9, 16909.2],
+                [10, 18547.9],
+                [11, 21617.8],
+                [12, 26638.1],
+                [13, 34634.4],
+                [14, 46759.4],
+                [15, 58478.1],
+                [16, 67884.6],
+                [17, 74462.6],
+                [18, 79395.7]
+            ];
+
+            var option = {
+                dataset: [{
+                    source: rawData
+                }, {
+                    transform: {
+                        type: 'ecStat:regression',
+                        config: {
+                            method: 'exponential'
+                        }
+                    }
+                }, {
+                    fromDatasetIndex: 1,
+                    fromTransformResult: 1
+                }],
+                legend: {
+                    bottom: 20
+                },
+                tooltip: {
+                },
+                xAxis: {
+                    type: 'category',
+                },
+                yAxis: {
+                },
+                series: [{
+                    name: 'scatter',
+                    type: 'scatter',
+                    datasetIndex: 0
+                }, {
+                    name: 'regression',
+                    type: 'line',
+                    symbol: 'none',
+                    datasetIndex: 1
+                }]
+            };
+
+            var chart = testHelper.create(echarts, 'main_regression', {
+                title: [
+                    'Regression',
+                ],
+                option: option
+            });
+
+
+        </script>
+
+
+    </body>
+</html>
+
diff --git a/test/lib/ecStat-1.1.1.min.js b/test/lib/ecStat-1.1.1.min.js
new file mode 100644
index 0000000..6aa4af5
--- /dev/null
+++ b/test/lib/ecStat-1.1.1.min.js
@@ -0,0 +1 @@
+!function(r,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.ecStat=t():r.ecStat=t()}(this,function(){return function(r){function t(e){if(n[e])return n[e].exports;var o=n[e]={exports:{},id:e,loaded:!1};return r[e].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=r,t.c=n,t.p="",t(0)}([function(r,t,n){var e;e=function(r){return{clustering:n(11),regression:n(13), [...]
\ No newline at end of file
diff --git a/test/lib/ecStatTransform.js b/test/lib/ecStatTransform.js
new file mode 100644
index 0000000..e411eed
--- /dev/null
+++ b/test/lib/ecStatTransform.js
@@ -0,0 +1,48 @@
+/*
+* 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.
+*/
+
+(function (root) {
+
+    root.ecStatTransform = function (ecStat) {
+
+        var regression = {
+
+            type: 'ecStat:regression',
+
+            transform: function transform(params) {
+                var source = params.source;
+                var config = params.config || {};
+                var method = config.method || 'linear';
+                var result = ecStat.regression(method, source.data);
+
+                return [{
+                    data: result.points
+                }, {
+                    data: [[result.expression]]
+                }];
+            }
+        };
+
+
+        return {
+            regression: regression
+        }
+    };
+
+})(window);


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


[incubator-echarts] 10/10: fix: prevent potential issue after the implementation of isString change.

Posted by su...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 246667e768cc418668d89900750a37f6feef8893
Author: 100pah <su...@gmail.com>
AuthorDate: Fri Jul 31 18:14:03 2020 +0800

    fix: prevent potential issue after the implementation of isString change.
---
 src/util/model.ts | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/util/model.ts b/src/util/model.ts
index 699b297..83a615f 100644
--- a/src/util/model.ts
+++ b/src/util/model.ts
@@ -26,7 +26,8 @@ import {
     map,
     assert,
     isString,
-    indexOf
+    indexOf,
+    isStringSafe
 } from 'zrender/src/core/util';
 import env from 'zrender/src/core/env';
 import GlobalModel from '../model/Global';
@@ -527,7 +528,7 @@ export function validateIdOrName(idOrName: unknown) {
 }
 
 function isValidIdOrName(idOrName: unknown): boolean {
-    return isString(idOrName) || isNumeric(idOrName);
+    return isStringSafe(idOrName) || isNumeric(idOrName);
 }
 
 export function isNameSpecified(componentModel: ComponentModel): boolean {


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