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

[incubator-echarts] branch next updated: refact: use 'style' in visual instead of separate color, opacity. bug fixes on state management.

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

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


The following commit(s) were added to refs/heads/next by this push:
     new 51c675d  refact: use 'style' in visual instead of separate color, opacity.  bug fixes on state management.
51c675d is described below

commit 51c675d11a8ba94d3f47ed170ed5e3b8c1ee5d2e
Author: pissang <bm...@gmail.com>
AuthorDate: Wed Apr 1 14:28:01 2020 +0800

    refact: use 'style' in visual instead of separate color, opacity.  bug fixes on state management.
---
 src/chart/bar/BarView.ts                           |  41 ++---
 src/chart/bar/PictorialBarSeries.ts                |   4 +
 src/chart/bar/PictorialBarView.ts                  |  50 +++---
 src/chart/bar/barItemStyle.ts                      |  60 -------
 src/chart/boxplot/BoxplotSeries.ts                 |   2 +
 src/chart/boxplot/BoxplotView.ts                   |  13 +-
 src/chart/boxplot/boxplotVisual.ts                 |  29 +---
 src/chart/candlestick/CandlestickView.ts           |  20 +--
 src/chart/candlestick/candlestickVisual.ts         |  18 +-
 src/chart/effectScatter.ts                         |   2 -
 src/chart/effectScatter/EffectScatterSeries.ts     |   1 +
 src/chart/funnel.ts                                |   2 -
 src/chart/funnel/FunnelSeries.ts                   |   2 +
 src/chart/funnel/FunnelView.ts                     |  23 +--
 src/chart/gauge/GaugeView.ts                       |   1 +
 src/chart/graph.ts                                 |   2 -
 src/chart/graph/GraphSeries.ts                     |   7 +-
 src/chart/graph/GraphView.ts                       |   4 +-
 src/chart/graph/categoryVisual.ts                  |  37 ++--
 src/chart/graph/edgeVisual.ts                      |  43 +++--
 src/chart/heatmap/HeatmapView.ts                   |  13 +-
 src/chart/helper/EffectLine.ts                     |   7 +-
 src/chart/helper/EffectSymbol.ts                   |   4 +-
 src/chart/helper/LargeLineDraw.ts                  |   6 +-
 src/chart/helper/LargeSymbolDraw.ts                |  16 +-
 src/chart/helper/Line.ts                           |  66 ++++---
 src/chart/helper/LineDraw.ts                       |   2 +-
 src/chart/helper/Polyline.ts                       |  14 +-
 src/chart/helper/Symbol.ts                         |  74 +++-----
 src/chart/helper/SymbolDraw.ts                     |   5 +-
 src/chart/line.ts                                  |   3 +-
 src/chart/line/LineSeries.ts                       |   3 +
 src/chart/line/LineView.ts                         |   6 +-
 src/chart/lines/LinesSeries.ts                     |   3 +-
 src/chart/lines/linesVisual.ts                     |  14 +-
 src/chart/map.ts                                   |   2 -
 src/chart/map/MapView.ts                           |   3 +-
 src/chart/parallel/ParallelSeries.ts               |   3 +-
 src/chart/parallel/ParallelView.ts                 |  25 +--
 src/chart/parallel/parallelVisual.ts               |  23 +--
 src/chart/pictorialBar.ts                          |   2 -
 src/chart/pie.ts                                   |   2 -
 src/chart/pie/PieSeries.ts                         |   2 +
 src/chart/pie/PieView.ts                           |  27 ++-
 src/chart/radar.ts                                 |   4 -
 src/chart/radar/RadarSeries.ts                     |   4 +
 src/chart/radar/RadarView.ts                       |  21 ++-
 src/chart/scatter.ts                               |   3 -
 src/chart/scatter/ScatterSeries.ts                 |   2 +
 src/chart/sunburst.ts                              |   2 -
 src/chart/sunburst/SunburstPiece.ts                | 148 ++++++++--------
 src/chart/sunburst/SunburstSeries.ts               |  12 +-
 src/chart/sunburst/SunburstView.ts                 |   6 +-
 src/chart/sunburst/sunburstLayout.ts               |   4 +-
 .../sunburstVisual.ts}                             |  35 ++--
 src/chart/themeRiver.ts                            |   2 -
 src/chart/themeRiver/ThemeRiverSeries.ts           |   2 +
 src/chart/themeRiver/ThemeRiverView.ts             |  11 +-
 src/chart/tree.ts                                  |   4 +-
 src/chart/tree/TreeSeries.ts                       |   5 +
 src/chart/tree/TreeView.ts                         |   3 +
 src/chart/{map/mapVisual.ts => tree/treeVisual.ts} |  28 ++-
 src/chart/treemap/TreemapView.ts                   |   5 +-
 src/chart/treemap/treemapVisual.ts                 |   6 +-
 src/component/axis/AngleAxisView.ts                |   1 -
 src/component/axis/AxisBuilder.ts                  |   1 +
 src/component/axis/CartesianAxisView.ts            |   2 +
 src/component/axis/axisSplitHelper.ts              |   1 +
 src/component/brush/brushAction.ts                 |   2 +-
 src/component/helper/MapDraw.ts                    |  14 +-
 src/component/legend/LegendView.ts                 |  40 ++---
 src/component/marker/MarkAreaView.ts               |  34 ++--
 src/component/marker/MarkLineView.ts               |  35 ++--
 src/component/marker/MarkPointView.ts              |  11 +-
 src/component/toolbox/ToolboxView.ts               |   2 +-
 src/component/visualMap/visualEncoding.ts          |   3 +-
 src/data/Graph.ts                                  |  20 +--
 src/data/List.ts                                   | 189 ++++++++++++---------
 src/data/Tree.ts                                   |   7 +-
 src/echarts.ts                                     |  70 +++++---
 src/model/Model.ts                                 |   4 +-
 src/model/Series.ts                                |  40 ++++-
 src/model/mixin/areaStyle.ts                       |   5 +-
 src/model/mixin/dataFormat.ts                      |   7 +-
 src/model/mixin/itemStyle.ts                       |   6 +-
 src/model/mixin/lineStyle.ts                       |   6 +-
 src/model/mixin/makeStyleMapper.ts                 |  11 +-
 src/util/graphic.ts                                | 121 ++++++++-----
 src/util/symbol.ts                                 |  39 +++--
 src/util/types.ts                                  |   2 +-
 src/view/Chart.ts                                  |  10 --
 src/visual/LegendVisualProvider.ts                 |   2 +-
 src/{chart/tree.ts => visual/commonVisualTypes.ts} |  16 +-
 src/visual/dataColor.ts                            |  95 -----------
 src/visual/helper.ts                               |  87 ++++++++++
 src/visual/seriesColor.ts                          |  75 --------
 src/visual/style.ts                                | 165 ++++++++++++++++++
 src/visual/symbol.ts                               | 178 +++++++++++--------
 src/visual/visualSolution.ts                       |   9 +-
 99 files changed, 1185 insertions(+), 1118 deletions(-)

diff --git a/src/chart/bar/BarView.ts b/src/chart/bar/BarView.ts
index 53c9817..f7e4719 100644
--- a/src/chart/bar/BarView.ts
+++ b/src/chart/bar/BarView.ts
@@ -19,8 +19,7 @@
 
 import {__DEV__} from '../../config';
 import * as zrUtil from 'zrender/src/core/util';
-import {Rect, Sector, getECData, updateProps, initProps, enableHoverEmphasis, setLabelStyle} from '../../util/graphic';
-import {getBarItemStyle} from './barItemStyle';
+import {Rect, Sector, getECData, updateProps, initProps, enableHoverEmphasis, setLabelStyle, clearStates} from '../../util/graphic';
 import Path, { PathProps } from 'zrender/src/graphic/Path';
 import Group from 'zrender/src/graphic/Group';
 import {throttle} from '../../util/throttle';
@@ -30,7 +29,7 @@ import ChartView from '../../view/Chart';
 import List from '../../data/List';
 import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../ExtensionAPI';
-import { StageHandlerProgressParams, ZRElementEvent } from '../../util/types';
+import { StageHandlerProgressParams, ZRElementEvent, ColorString } from '../../util/types';
 import BarSeriesModel, { BarSeriesOption, BarDataItemOption } from './BarSeries';
 import type Axis2D from '../../coord/cartesian/Axis2D';
 import type Cartesian2D from '../../coord/cartesian/Cartesian2D';
@@ -171,7 +170,7 @@ class BarView extends ChartView {
                     const bgEl = createBackgroundEl(
                         coord, isHorizontalOrRadial, layout
                     );
-                    bgEl.useStyle(getBarItemStyle(backgroundModel));
+                    bgEl.useStyle(backgroundModel.getItemStyle());
                     bgEls[dataIndex] = bgEl;
                 }
 
@@ -207,7 +206,7 @@ class BarView extends ChartView {
 
                 if (drawBackground) {
                     const bgEl = oldBgEls[oldIndex];
-                    bgEl.useStyle(getBarItemStyle(backgroundModel));
+                    bgEl.useStyle(backgroundModel.getItemStyle());
                     bgEls[newIndex] = bgEl;
 
                     const shape = createBackgroundShape(isHorizontalOrRadial, layout, coord);
@@ -231,6 +230,7 @@ class BarView extends ChartView {
                 }
 
                 if (el) {
+                    clearStates(el);
                     updateProps(el as Path, {
                         shape: layout
                     }, animationModel, newIndex);
@@ -394,6 +394,7 @@ const elementCreator: {
             shape: zrUtil.extend({}, layout),
             z2: 1
         });
+        // rect.autoBatch = true;
 
         rect.name = 'item';
 
@@ -528,24 +529,16 @@ function updateStyle(
     isHorizontal: boolean,
     isPolar: boolean
 ) {
-    const color = data.getItemVisual(dataIndex, 'color');
-    const opacity = data.getItemVisual(dataIndex, 'opacity');
-    const stroke = data.getVisual('borderColor');
-    const itemStyleModel = itemModel.getModel('itemStyle');
-    const hoverStyle = getBarItemStyle(itemModel.getModel(['emphasis', 'itemStyle']));
+    const style = data.getItemVisual(dataIndex, 'style');
+    const hoverStyle = itemModel.getModel(['emphasis', 'itemStyle']).getItemStyle();
 
     if (!isPolar) {
-        (el as Rect).setShape('r', itemStyleModel.get('barBorderRadius') || 0);
+        (el as Rect).setShape('r', itemModel.get(['itemStyle', 'barBorderRadius']) || 0);
     }
 
-    el.useStyle(zrUtil.defaults(
-        {
-            stroke: isZeroOnPolar(layout as SectorLayout) ? 'none' : stroke,
-            fill: isZeroOnPolar(layout as SectorLayout) ? 'none' : color,
-            opacity: opacity
-        },
-        getBarItemStyle(itemStyleModel)
-    ));
+    el.useStyle(style);
+
+    el.ignore = isZeroOnPolar(layout as SectorLayout);
 
     const cursorStyle = itemModel.getShallow('cursor');
     cursorStyle && (el as Path).attr('cursor', cursorStyle);
@@ -563,7 +556,7 @@ function updateStyle(
                 labelFetcher: seriesModel,
                 labelDataIndex: dataIndex,
                 defaultText: getDefaultLabel(seriesModel.getData(), dataIndex),
-                autoColor: color,
+                autoColor: style.fill as ColorString,
                 defaultOutsidePosition: labelPositionOutside
             }
         );
@@ -727,12 +720,12 @@ function setLargeStyle(
     seriesModel: BarSeriesModel,
     data: List
 ) {
-    const borderColor = data.getVisual('borderColor') || data.getVisual('color');
-    const itemStyle = seriesModel.getModel('itemStyle').getItemStyle(['color', 'borderColor']);
+    const globalStyle = data.getVisual('style');
 
-    el.useStyle(itemStyle);
+    el.useStyle(zrUtil.extend({}, globalStyle));
+    // Use stroke instead of fill.
     el.style.fill = null;
-    el.style.stroke = borderColor;
+    el.style.stroke = globalStyle.fill;
     el.style.lineWidth = data.getLayout('barWidth');
 }
 
diff --git a/src/chart/bar/PictorialBarSeries.ts b/src/chart/bar/PictorialBarSeries.ts
index d6ff9e0..07abcc7 100644
--- a/src/chart/bar/PictorialBarSeries.ts
+++ b/src/chart/bar/PictorialBarSeries.ts
@@ -124,6 +124,10 @@ class PictorialBarSeriesModel extends BaseBarSeriesModel<PictorialBarSeriesOptio
 
     coordinateSystem: Cartesian2D;
 
+
+    hasSymbolVisual = true;
+    defaultSymbol = 'roundRect';
+
     static defaultOption: PictorialBarSeriesOption = inheritDefaultOption(BaseBarSeriesModel.defaultOption, {
 
         symbol: 'circle',     // Customized bar shape
diff --git a/src/chart/bar/PictorialBarView.ts b/src/chart/bar/PictorialBarView.ts
index 743ab47..518630c 100644
--- a/src/chart/bar/PictorialBarView.ts
+++ b/src/chart/bar/PictorialBarView.ts
@@ -33,7 +33,7 @@ import type Displayable from 'zrender/src/graphic/Displayable';
 import type Axis2D from '../../coord/cartesian/Axis2D';
 import type Element from 'zrender/src/Element';
 import { getDefaultLabel } from '../helper/labelHelper';
-import { PathProps } from 'zrender/src/graphic/Path';
+import { PathProps, PathStyleProps } from 'zrender/src/graphic/Path';
 
 
 const BAR_BORDER_WIDTH_QUERY = ['itemStyle', 'borderWidth'] as const;
@@ -90,7 +90,7 @@ interface SymbolMeta {
     valueLineWidth: number
 
     opacity: number
-    color: ColorString
+    style: PathStyleProps
     z2: number
 
     itemModel: ItemModel
@@ -190,6 +190,7 @@ class PictorialBarView extends ChartView {
                 }
 
                 if (bar) {
+                    bar.clearStates();
                     updateBar(bar, opt, symbolMeta);
                 }
                 else {
@@ -253,7 +254,7 @@ function getSymbolMeta(
         layout: layout,
         itemModel: itemModel,
         symbolType: data.getItemVisual(dataIndex, 'symbol') || 'circle',
-        color: data.getItemVisual(dataIndex, 'color'),
+        style: data.getItemVisual(dataIndex, 'style'),
         symbolClip: symbolClip,
         symbolRepeat: symbolRepeat,
         symbolRepeatDirection: itemModel.get('symbolRepeatDirection'),
@@ -354,15 +355,19 @@ function prepareSymbolSize(
     const categoryDim = opt.categoryDim;
     const categorySize = Math.abs(layout[categoryDim.wh]);
 
-    let symbolSize = data.getItemVisual(dataIndex, 'symbolSize');
+    const symbolSize = data.getItemVisual(dataIndex, 'symbolSize');
+    let parsedSymbolSize: number[];
     if (zrUtil.isArray(symbolSize)) {
-        symbolSize = symbolSize.slice();
+        parsedSymbolSize = symbolSize.slice();
     }
     else {
         if (symbolSize == null) {
-            symbolSize = '100%';
+            // will parse to number below
+            parsedSymbolSize = ['100%', '100%'] as unknown as number[];
+        }
+        else {
+            parsedSymbolSize = [symbolSize, symbolSize];
         }
-        symbolSize = [symbolSize, symbolSize];
     }
 
     // Note: percentage symbolSize (like '100%') do not consider lineWidth, because it is
@@ -370,21 +375,21 @@ function prepareSymbolSize(
     // So the actual size will bigger than layout size if lineWidth is bigger than zero,
     // which can be tolerated in pictorial chart.
 
-    symbolSize[categoryDim.index] = parsePercent(
-        symbolSize[categoryDim.index],
+    parsedSymbolSize[categoryDim.index] = parsePercent(
+        parsedSymbolSize[categoryDim.index],
         categorySize
     );
-    symbolSize[valueDim.index] = parsePercent(
-        symbolSize[valueDim.index],
+    parsedSymbolSize[valueDim.index] = parsePercent(
+        parsedSymbolSize[valueDim.index],
         symbolRepeat ? categorySize : Math.abs(boundingLength)
     );
 
-    outputSymbolMeta.symbolSize = symbolSize;
+    outputSymbolMeta.symbolSize = parsedSymbolSize;
 
     // If x or y is less than zero, show reversed shape.
     const symbolScale = outputSymbolMeta.symbolScale = [
-        symbolSize[0] / symbolPatternSize,
-        symbolSize[1] / symbolPatternSize
+        parsedSymbolSize[0] / symbolPatternSize,
+        parsedSymbolSize[1] / symbolPatternSize
     ];
     // Follow convention, 'right' and 'top' is the normal scale.
     symbolScale[valueDim.index] *= (opt.isHorizontal ? -1 : 1) * pxSign;
@@ -523,8 +528,7 @@ function createPath(symbolMeta: SymbolMeta) {
         -symbolPatternSize / 2,
         -symbolPatternSize / 2,
         symbolPatternSize,
-        symbolPatternSize,
-        symbolMeta.color
+        symbolPatternSize
     );
     (path as Displayable).attr({
         culling: true
@@ -921,25 +925,15 @@ function updateCommon(
     opt: CreateOpts,
     symbolMeta: SymbolMeta
 ) {
-    const color = symbolMeta.color;
     const dataIndex = symbolMeta.dataIndex;
     const itemModel = symbolMeta.itemModel;
     // Color must be excluded.
     // Because symbol provide setColor individually to set fill and stroke
-    const normalStyle = itemModel.getModel('itemStyle').getItemStyle(['color']);
     const hoverStyle = itemModel.getModel(['emphasis', 'itemStyle']).getItemStyle();
     const cursorStyle = itemModel.getShallow('cursor');
 
     eachPath(bar, function (path) {
-        // PENDING setColor should be before setStyle!!!
-        path.setColor(color);
-        path.setStyle(zrUtil.defaults(
-            {
-                fill: color,
-                opacity: symbolMeta.opacity
-            },
-            normalStyle
-        ));
+        path.useStyle(symbolMeta.style);
         graphic.enableHoverEmphasis(path, hoverStyle);
 
         cursorStyle && (path.cursor = cursorStyle);
@@ -959,7 +953,7 @@ function updateCommon(
             labelFetcher: opt.seriesModel,
             labelDataIndex: dataIndex,
             defaultText: getDefaultLabel(opt.seriesModel.getData(), dataIndex),
-            autoColor: color,
+            autoColor: symbolMeta.style.fill as ColorString,
             defaultOutsidePosition: barPositionOutside
         }
     );
diff --git a/src/chart/bar/barItemStyle.ts b/src/chart/bar/barItemStyle.ts
deleted file mode 100644
index bf00770..0000000
--- a/src/chart/bar/barItemStyle.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
-* Licensed to the Apache Software Foundation (ASF) under one
-* or more contributor license agreements.  See the NOTICE file
-* distributed with this work for additional information
-* regarding copyright ownership.  The ASF licenses this file
-* to you under the Apache License, Version 2.0 (the
-* "License"); you may not use this file except in compliance
-* with the License.  You may obtain a copy of the License at
-*
-*   http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing,
-* software distributed under the License is distributed on an
-* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-* KIND, either express or implied.  See the License for the
-* specific language governing permissions and limitations
-* under the License.
-*/
-
-import makeStyleMapper from '../../model/mixin/makeStyleMapper';
-import Model from '../../model/Model';
-import { PathStyleProps } from 'zrender/src/graphic/Path';
-
-const mapStyle = makeStyleMapper(
-    [
-        ['fill', 'color'],
-        ['stroke', 'borderColor'],
-        ['lineWidth', 'borderWidth'],
-        // Compatitable with 2
-        ['stroke', 'barBorderColor'],
-        ['lineWidth', 'barBorderWidth'],
-        ['opacity'],
-        ['shadowBlur'],
-        ['shadowOffsetX'],
-        ['shadowOffsetY'],
-        ['shadowColor']
-    ]
-);
-
-type BarItemStyleKeys = 'fill'
-    | 'stroke'
-    | 'lineWidth'
-    | 'stroke'
-    | 'lineWidth'
-    | 'opacity'
-    | 'shadowBlur'
-    | 'shadowOffsetX'
-    | 'shadowOffsetY'
-    | 'shadowColor';
-type ItemStyleProps = Pick<PathStyleProps, BarItemStyleKeys>;
-
-export function getBarItemStyle(model: Model, excludes?: BarItemStyleKeys[]): ItemStyleProps {
-    const style = mapStyle(model, excludes);
-    if (model.getBorderLineDash) {
-        const lineDash = model.getBorderLineDash();
-        lineDash && (style.lineDash = lineDash);
-    }
-    return style;
-}
-
diff --git a/src/chart/boxplot/BoxplotSeries.ts b/src/chart/boxplot/BoxplotSeries.ts
index 7de4b72..d65e505 100644
--- a/src/chart/boxplot/BoxplotSeries.ts
+++ b/src/chart/boxplot/BoxplotSeries.ts
@@ -97,6 +97,8 @@ class BoxplotSeriesModel extends SeriesModel<BoxplotSeriesOption> {
 
     dimensions: string[];
 
+    visualColorBrushType = 'stroke' as const;
+
     static defaultOption: BoxplotSeriesOption = {
         zlevel: 0,
         z: 2,
diff --git a/src/chart/boxplot/BoxplotView.ts b/src/chart/boxplot/BoxplotView.ts
index e4b4473..21df40f 100644
--- a/src/chart/boxplot/BoxplotView.ts
+++ b/src/chart/boxplot/BoxplotView.ts
@@ -20,7 +20,7 @@
 import * as zrUtil from 'zrender/src/core/util';
 import ChartView from '../../view/Chart';
 import * as graphic from '../../util/graphic';
-import Path, { PathProps, PathStyleProps } from 'zrender/src/graphic/Path';
+import Path, { PathProps } from 'zrender/src/graphic/Path';
 import BoxplotSeriesModel, { BoxplotDataItemOption } from './BoxplotSeries';
 import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../ExtensionAPI';
@@ -179,14 +179,9 @@ function updateNormalBoxData(
     );
 
     const itemModel = data.getItemModel<BoxplotDataItemOption>(dataIndex);
-    const normalItemStyleModel = itemModel.getModel('itemStyle');
-    const borderColor = data.getItemVisual(dataIndex, 'color');
-
-    // Exclude borderColor.
-    const itemStyle = normalItemStyleModel.getItemStyle(['borderColor']) as PathStyleProps;
-    itemStyle.stroke = borderColor;
-    itemStyle.strokeNoScale = true;
-    el.useStyle(itemStyle);
+
+    el.useStyle(data.getItemVisual(dataIndex, 'style'));
+    el.style.strokeNoScale = true;
 
     el.z2 = 100;
 
diff --git a/src/chart/boxplot/boxplotVisual.ts b/src/chart/boxplot/boxplotVisual.ts
index c2ebc87..4deee98 100644
--- a/src/chart/boxplot/boxplotVisual.ts
+++ b/src/chart/boxplot/boxplotVisual.ts
@@ -19,36 +19,11 @@
 
 import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../ExtensionAPI';
-import BoxplotSeriesModel, { BoxplotDataItemOption } from './BoxplotSeries';
-
-const borderColorQuery = ['itemStyle', 'borderColor'] as const;
+import BoxplotSeriesModel from './BoxplotSeries';
 
 export default function (ecModel: GlobalModel, api: ExtensionAPI) {
-
-    const globalColors = ecModel.get('color');
-
     ecModel.eachRawSeriesByType('boxplot', function (seriesModel: BoxplotSeriesModel) {
-
-        const defaulColor = globalColors[seriesModel.seriesIndex % globalColors.length];
-        const data = seriesModel.getData();
-
-        data.setVisual({
-            legendSymbol: 'roundRect',
-            // Use name 'color' but not 'borderColor' for legend usage and
-            // visual coding from other component like dataRange.
-            color: seriesModel.get(borderColorQuery) || defaulColor
-        });
-
-        // Only visible series has each data be visual encoded
-        if (!ecModel.isSeriesFiltered(seriesModel)) {
-            data.each(function (idx) {
-                const itemModel = data.getItemModel<BoxplotDataItemOption>(idx);
-                data.setItemVisual(
-                    idx,
-                    {color: itemModel.get(borderColorQuery, true)}
-                );
-            });
-        }
+        seriesModel.getData().setVisual('legendSymbol', 'roundRect');
     });
 
 }
\ No newline at end of file
diff --git a/src/chart/candlestick/CandlestickView.ts b/src/chart/candlestick/CandlestickView.ts
index 3130a34..6b9909d 100644
--- a/src/chart/candlestick/CandlestickView.ts
+++ b/src/chart/candlestick/CandlestickView.ts
@@ -132,6 +132,7 @@ class CandlestickView extends ChartView {
                     el = createNormalBox(itemLayout, newIdx);
                 }
                 else {
+                    graphic.clearStates(el);
                     graphic.updateProps(el, {
                         shape: {
                             points: itemLayout.ends
@@ -274,18 +275,9 @@ function isNormalBoxClipped(clipArea: CoordinateSystemClipArea, itemLayout: Cand
 
 function setBoxCommon(el: NormalBoxPath, data: List, dataIndex: number, isSimpleBox?: boolean) {
     const itemModel = data.getItemModel(dataIndex) as Model<CandlestickDataItemOption>;
-    const normalItemStyleModel = itemModel.getModel('itemStyle');
-    const color = data.getItemVisual(dataIndex, 'color');
-    const borderColor = data.getItemVisual(dataIndex, 'borderColor') || color;
 
-    // Color must be excluded.
-    // Because symbol provide setColor individually to set fill and stroke
-    const itemStyle = normalItemStyleModel.getItemStyle(SKIP_PROPS);
-
-    el.useStyle(itemStyle);
+    el.useStyle(data.getItemVisual(dataIndex, 'style'));
     el.style.strokeNoScale = true;
-    el.style.fill = color;
-    el.style.stroke = borderColor;
 
     el.__simpleBox = isSimpleBox;
 
@@ -369,9 +361,9 @@ function createLarge(seriesModel: CandlestickSeriesModel, group: graphic.Group,
 }
 
 function setLargeStyle(sign: number, el: LargeBoxPath, seriesModel: CandlestickSeriesModel, data: List) {
-    const suffix = sign > 0 ? 'P' : 'N';
-    const borderColor = data.getVisual('borderColor' + suffix)
-        || data.getVisual('color' + suffix);
+    // TODO put in visual?
+    const borderColor = seriesModel.get(['itemStyle', sign > 0 ? 'borderColor' : 'borderColor0'])
+        || seriesModel.get(['itemStyle', sign > 0 ? 'color' : 'color0']);
 
     // Color must be excluded.
     // Because symbol provide setColor individually to set fill and stroke
@@ -380,8 +372,6 @@ function setLargeStyle(sign: number, el: LargeBoxPath, seriesModel: CandlestickS
     el.useStyle(itemStyle);
     el.style.fill = null;
     el.style.stroke = borderColor;
-    // No different
-    // el.style.lineWidth = .5;
 }
 
 
diff --git a/src/chart/candlestick/candlestickVisual.ts b/src/chart/candlestick/candlestickVisual.ts
index d55d2e6..7e7ed5c 100644
--- a/src/chart/candlestick/candlestickVisual.ts
+++ b/src/chart/candlestick/candlestickVisual.ts
@@ -52,13 +52,7 @@ const candlestickVisual: StageHandler = {
 
         const data = seriesModel.getData();
 
-        data.setVisual({
-            legendSymbol: 'roundRect',
-            colorP: getColor(1, seriesModel),
-            colorN: getColor(-1, seriesModel),
-            borderColorP: getBorderColor(1, seriesModel),
-            borderColorN: getBorderColor(-1, seriesModel)
-        });
+        data.setVisual('legendSymbol', 'roundRect');
 
         // Only visible series has each data be visual encoded
         if (ecModel.isSeriesFiltered(seriesModel)) {
@@ -73,13 +67,9 @@ const candlestickVisual: StageHandler = {
                     const itemModel = data.getItemModel(dataIndex);
                     const sign = data.getItemLayout(dataIndex).sign;
 
-                    data.setItemVisual(
-                        dataIndex,
-                        {
-                            color: getColor(sign, itemModel),
-                            borderColor: getBorderColor(sign, itemModel)
-                        }
-                    );
+                    const style = itemModel.getItemStyle(['color', 'borderColor']);
+                    style.fill = getColor(sign, itemModel);
+                    style.stroke = getBorderColor(sign, itemModel) || style.fill;
                 }
             }
         };
diff --git a/src/chart/effectScatter.ts b/src/chart/effectScatter.ts
index 31391f1..06e7dda 100644
--- a/src/chart/effectScatter.ts
+++ b/src/chart/effectScatter.ts
@@ -22,8 +22,6 @@ import * as echarts from '../echarts';
 import './effectScatter/EffectScatterSeries';
 import './effectScatter/EffectScatterView';
 
-import visualSymbol from '../visual/symbol';
 import layoutPoints from '../layout/points';
 
-echarts.registerVisual(visualSymbol('effectScatter', 'circle'));
 echarts.registerLayout(layoutPoints('effectScatter'));
\ No newline at end of file
diff --git a/src/chart/effectScatter/EffectScatterSeries.ts b/src/chart/effectScatter/EffectScatterSeries.ts
index 99feb09..e4887c7 100644
--- a/src/chart/effectScatter/EffectScatterSeries.ts
+++ b/src/chart/effectScatter/EffectScatterSeries.ts
@@ -81,6 +81,7 @@ class EffectScatterSeriesModel extends SeriesModel<EffectScatterSeriesOption> {
 
     static readonly dependencies = ['grid', 'polar'];
 
+    hasSymbolVisual = true;
 
     getInitialData(option: EffectScatterSeriesOption, ecModel: GlobalModel): List {
         return createListFromArray(this.getSource(), this, {useEncodeDefaulter: true});
diff --git a/src/chart/funnel.ts b/src/chart/funnel.ts
index 154b40b..98c5594 100644
--- a/src/chart/funnel.ts
+++ b/src/chart/funnel.ts
@@ -22,10 +22,8 @@ import * as echarts from '../echarts';
 import './funnel/FunnelSeries';
 import './funnel/FunnelView';
 
-import dataColor from '../visual/dataColor';
 import funnelLayout from './funnel/funnelLayout';
 import dataFilter from '../processor/dataFilter';
 
-echarts.registerVisual(dataColor('funnel'));
 echarts.registerLayout(funnelLayout);
 echarts.registerProcessor(dataFilter('funnel'));
\ No newline at end of file
diff --git a/src/chart/funnel/FunnelSeries.ts b/src/chart/funnel/FunnelSeries.ts
index a00c36e..1140507 100644
--- a/src/chart/funnel/FunnelSeries.ts
+++ b/src/chart/funnel/FunnelSeries.ts
@@ -96,6 +96,8 @@ class FunnelSeriesModel extends SeriesModel<FunnelSeriesOption> {
     static type = 'series.funnel' as const;
     type = FunnelSeriesModel.type;
 
+    useColorPaletteOnData: true;
+
     init(option: FunnelSeriesOption) {
         super.init.apply(this, arguments as any);
 
diff --git a/src/chart/funnel/FunnelView.ts b/src/chart/funnel/FunnelView.ts
index 123b190..b5ae8d6 100644
--- a/src/chart/funnel/FunnelView.ts
+++ b/src/chart/funnel/FunnelView.ts
@@ -24,7 +24,7 @@ import FunnelSeriesModel, {FunnelDataItemOption} from './FunnelSeries';
 import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../ExtensionAPI';
 import List from '../../data/List';
-import { DisplayState } from '../../util/types';
+import { DisplayState, ColorString } from '../../util/types';
 import Displayable from 'zrender/src/graphic/Displayable';
 
 const opacityAccessPath = ['itemStyle', 'opacity'] as const;
@@ -51,7 +51,7 @@ class FunnelPiece extends graphic.Group {
         this.updateData(data, idx, true);
     }
 
-    highDownOnUpdate(fromState: DisplayState, toState: DisplayState) {
+    onStateChange(fromState: DisplayState, toState: DisplayState) {
 
         const labelLine = this.childAt(1) as graphic.Polyline;
         const text = this.childAt(2) as graphic.Text;
@@ -102,18 +102,9 @@ class FunnelPiece extends graphic.Group {
         }
 
         // Update common style
-        const itemStyleModel = itemModel.getModel('itemStyle');
-        const visualColor = data.getItemVisual(idx, 'color');
-
-        polygon.setStyle(
-            zrUtil.defaults(
-                {
-                    lineJoin: 'round',
-                    fill: visualColor
-                },
-                itemStyleModel.getItemStyle(['opacity'])
-            )
-        );
+        polygon.useStyle(data.getItemVisual(idx, 'style'));
+        polygon.style.lineJoin = 'round';
+
         const polygonEmphasisState = polygon.ensureState('emphasis');
         polygonEmphasisState.style = itemModel.getModel(['emphasis', 'itemStyle']).getItemStyle();
 
@@ -137,7 +128,8 @@ class FunnelPiece extends graphic.Group {
         const labelHoverModel = itemModel.getModel(['emphasis', 'label']);
         const labelLineModel = itemModel.getModel('labelLine');
         const labelLineHoverModel = itemModel.getModel(['emphasis', 'labelLine']);
-        const visualColor = data.getItemVisual(idx, 'color');
+
+        const visualColor = data.getItemVisual(idx, 'style').stroke as ColorString;
 
         graphic.setLabelStyle(
             labelText, labelModel, labelHoverModel,
@@ -221,6 +213,7 @@ class FunnelView extends ChartView {
             })
             .update(function (newIdx, oldIdx) {
                 const piece = oldData.getItemGraphicEl(oldIdx) as FunnelPiece;
+                graphic.clearStates(piece);
 
                 piece.updateData(data, newIdx);
 
diff --git a/src/chart/gauge/GaugeView.ts b/src/chart/gauge/GaugeView.ts
index fe590bd..77e3c8c 100644
--- a/src/chart/gauge/GaugeView.ts
+++ b/src/chart/gauge/GaugeView.ts
@@ -347,6 +347,7 @@ class GaugeView extends ChartView {
             })
             .update(function (newIdx, oldIdx) {
                 const pointer = oldData.getItemGraphicEl(oldIdx) as PointerPath;
+                graphic.clearStates(pointer);
 
                 graphic.updateProps(pointer, {
                     shape: {
diff --git a/src/chart/graph.ts b/src/chart/graph.ts
index 6dcb736..9ed32c8 100644
--- a/src/chart/graph.ts
+++ b/src/chart/graph.ts
@@ -24,7 +24,6 @@ import './graph/GraphView';
 import './graph/graphAction';
 
 import categoryFilter from './graph/categoryFilter';
-import visualSymbol from '../visual/symbol';
 import categoryVisual from './graph/categoryVisual';
 import edgeVisual from './graph/edgeVisual';
 import simpleLayout from './graph/simpleLayout';
@@ -35,7 +34,6 @@ import View from '../coord/View';
 
 echarts.registerProcessor(categoryFilter);
 
-echarts.registerVisual(visualSymbol('graph', 'circle', null));
 echarts.registerVisual(categoryVisual);
 echarts.registerVisual(edgeVisual);
 
diff --git a/src/chart/graph/GraphSeries.ts b/src/chart/graph/GraphSeries.ts
index bac92d4..10fd53a 100644
--- a/src/chart/graph/GraphSeries.ts
+++ b/src/chart/graph/GraphSeries.ts
@@ -47,6 +47,7 @@ import Graph from '../../data/Graph';
 import GlobalModel from '../../model/Global';
 import { VectorArray } from 'zrender/src/core/vector';
 import { ForceLayoutInstance } from './forceLayout';
+import { LineDataVisual } from '../../visual/commonVisualTypes';
 
 type GraphDataValue = OptionDataValue | OptionDataValue[];
 
@@ -224,6 +225,8 @@ class GraphSeriesModel extends SeriesModel<GraphSeriesOption> {
 
     forceLayout?: ForceLayoutInstance;
 
+    hasSymbolVisual = true;
+
     init(option: GraphSeriesOption) {
         super.init.apply(this, arguments as any);
 
@@ -310,8 +313,8 @@ class GraphSeriesModel extends SeriesModel<GraphSeriesOption> {
         return this.getData().graph;
     }
 
-    getEdgeData(): List {
-        return this.getGraph().edgeData;
+    getEdgeData() {
+        return this.getGraph().edgeData as List<GraphSeriesModel, LineDataVisual>;
     }
 
     getCategoriesData(): List {
diff --git a/src/chart/graph/GraphView.ts b/src/chart/graph/GraphView.ts
index c73f0b1..7d604a3 100644
--- a/src/chart/graph/GraphView.ts
+++ b/src/chart/graph/GraphView.ts
@@ -38,6 +38,7 @@ import Symbol from '../helper/Symbol';
 import Model from '../../model/Model';
 import { Payload } from '../../util/types';
 import { LineLabel } from '../helper/Line';
+import List from '../../data/List';
 
 const FOCUS_ADJACENCY = '__focusNodeAdjacency';
 const UNFOCUS_ADJACENCY = '__unfocusNodeAdjacency';
@@ -172,7 +173,8 @@ class GraphView extends ChartView {
         symbolDraw.updateData(data);
 
         const edgeData = seriesModel.getEdgeData();
-        lineDraw.updateData(edgeData);
+        // TODO: TYPE
+        lineDraw.updateData(edgeData as List);
 
         this._updateNodeAndLinkScale();
 
diff --git a/src/chart/graph/categoryVisual.ts b/src/chart/graph/categoryVisual.ts
index 1775d44..593d493 100644
--- a/src/chart/graph/categoryVisual.ts
+++ b/src/chart/graph/categoryVisual.ts
@@ -20,6 +20,7 @@
 import GlobalModel from '../../model/Global';
 import GraphSeriesModel, { GraphNodeItemOption } from './GraphSeries';
 import { Dictionary, ColorString } from '../../util/types';
+import { extend } from 'zrender/src/core/util';
 
 export default function (ecModel: GlobalModel) {
 
@@ -36,14 +37,12 @@ export default function (ecModel: GlobalModel) {
             categoryNameIdxMap['ec-' + name] = idx;
             const itemModel = categoriesData.getItemModel<GraphNodeItemOption>(idx);
 
-            const color = itemModel.get(['itemStyle', 'color'])
-                || seriesModel.getColorFromPalette(name, paletteScope);
-            categoriesData.setItemVisual(idx, 'color', color);
-
-            const opacity = itemModel.get(['itemStyle', 'opacity']);
-            if (opacity != null) {
-                categoriesData.setItemVisual(idx, 'opacity', opacity);
+            const style = itemModel.getItemStyle();
+            if (!style.fill) {
+                // Get color from palette.
+                style.fill = seriesModel.getColorFromPalette(name, paletteScope);
             }
+            categoriesData.setItemVisual(idx, 'style', style);
 
             const symbolVisualList = ['symbol', 'symbolSize', 'symbolKeepAspect'] as const;
 
@@ -59,21 +58,23 @@ export default function (ecModel: GlobalModel) {
         if (categoriesData.count()) {
             data.each(function (idx) {
                 const model = data.getItemModel<GraphNodeItemOption>(idx);
-                let category = model.getShallow('category');
-                if (category != null) {
-                    if (typeof category === 'string') {
-                        category = categoryNameIdxMap['ec-' + category];
+                let categoryIdx = model.getShallow('category');
+                if (categoryIdx != null) {
+                    if (typeof categoryIdx === 'string') {
+                        categoryIdx = categoryNameIdxMap['ec-' + categoryIdx];
                     }
 
-                    const visualList = ['color', 'opacity', 'symbol', 'symbolSize', 'symbolKeepAspect'] as const;
+                    const categoryStyle = categoriesData.getItemVisual(categoryIdx, 'style');
+                    const style = data.ensureUniqueItemVisual(idx, 'style');
+                    extend(style, categoryStyle);
+
+                    const visualList = ['symbol', 'symbolSize', 'symbolKeepAspect'] as const;
 
                     for (let i = 0; i < visualList.length; i++) {
-                        if (data.getItemVisual(idx, visualList[i], true) == null) {
-                            data.setItemVisual(
-                                idx, visualList[i],
-                                categoriesData.getItemVisual(category, visualList[i])
-                            );
-                        }
+                        data.setItemVisual(
+                            idx, visualList[i],
+                            categoriesData.getItemVisual(categoryIdx, visualList[i])
+                        );
                     }
                 }
             });
diff --git a/src/chart/graph/edgeVisual.ts b/src/chart/graph/edgeVisual.ts
index 85b88ba..7c74786 100644
--- a/src/chart/graph/edgeVisual.ts
+++ b/src/chart/graph/edgeVisual.ts
@@ -19,7 +19,11 @@
 
 import GlobalModel from '../../model/Global';
 import GraphSeriesModel, { GraphEdgeItemOption } from './GraphSeries';
+import { DefaultDataVisual } from '../../data/List';
+import { extend } from 'zrender/src/core/util';
 
+function normalize(a: string | string[]): string[];
+function normalize(a: number | number[]): number[];
 function normalize(a: string | number | (string | number)[]): (string | number)[] {
     if (!(a instanceof Array)) {
         a = [a, a];
@@ -27,6 +31,13 @@ function normalize(a: string | number | (string | number)[]): (string | number)[
     return a;
 }
 
+interface EdgeLineDataVisual extends DefaultDataVisual {
+    fromSymbol: string
+    toSymbol: string
+    fromSymbolSize: number
+    toSymbolSize: number
+}
+
 export default function (ecModel: GlobalModel) {
     ecModel.eachSeriesByType('graph', function (seriesModel: GraphSeriesModel) {
         const graph = seriesModel.getGraph();
@@ -34,15 +45,15 @@ export default function (ecModel: GlobalModel) {
         const symbolType = normalize(seriesModel.get('edgeSymbol'));
         const symbolSize = normalize(seriesModel.get('edgeSymbolSize'));
 
-        const colorQuery = ['lineStyle', 'color'] as const;
-        const opacityQuery = ['lineStyle', 'opacity'] as const;
+        // const colorQuery = ['lineStyle', 'color'] as const;
+        // const opacityQuery = ['lineStyle', 'opacity'] as const;
 
         edgeData.setVisual('fromSymbol', symbolType && symbolType[0]);
         edgeData.setVisual('toSymbol', symbolType && symbolType[1]);
         edgeData.setVisual('fromSymbolSize', symbolSize && symbolSize[0]);
         edgeData.setVisual('toSymbolSize', symbolSize && symbolSize[1]);
-        edgeData.setVisual('color', seriesModel.get(colorQuery));
-        edgeData.setVisual('opacity', seriesModel.get(opacityQuery));
+
+        edgeData.setVisual('style', seriesModel.getModel('itemStyle').getItemStyle());
 
         edgeData.each(function (idx) {
             const itemModel = edgeData.getItemModel<GraphEdgeItemOption>(idx);
@@ -50,24 +61,28 @@ export default function (ecModel: GlobalModel) {
             const symbolType = normalize(itemModel.getShallow('symbol', true));
             const symbolSize = normalize(itemModel.getShallow('symbolSize', true));
             // Edge visual must after node visual
-            let color = itemModel.get(colorQuery);
-            const opacity = itemModel.get(opacityQuery);
-            switch (color) {
-                case 'source':
-                    color = edge.node1.getVisual('color');
+            const style = itemModel.getModel('lineStyle').getLineStyle();
+
+            const existsStyle = edgeData.ensureUniqueItemVisual(idx, 'style');
+            extend(existsStyle, style);
+
+            switch (existsStyle.stroke) {
+                case 'source': {
+                    const nodeStyle = edge.node1.getVisual('style');
+                    existsStyle.stroke = nodeStyle && nodeStyle.fill;
                     break;
-                case 'target':
-                    color = edge.node2.getVisual('color');
+                }
+                case 'target': {
+                    const nodeStyle = edge.node2.getVisual('style');
+                    existsStyle.stroke = nodeStyle && nodeStyle.fill;
                     break;
+                }
             }
 
             symbolType[0] && edge.setVisual('fromSymbol', symbolType[0]);
             symbolType[1] && edge.setVisual('toSymbol', symbolType[1]);
             symbolSize[0] && edge.setVisual('fromSymbolSize', symbolSize[0]);
             symbolSize[1] && edge.setVisual('toSymbolSize', symbolSize[1]);
-
-            edge.setVisual('color', color);
-            edge.setVisual('opacity', opacity);
         });
     });
 }
\ No newline at end of file
diff --git a/src/chart/heatmap/HeatmapView.ts b/src/chart/heatmap/HeatmapView.ts
index ed96906..d4585d3 100644
--- a/src/chart/heatmap/HeatmapView.ts
+++ b/src/chart/heatmap/HeatmapView.ts
@@ -184,7 +184,6 @@ class HeatmapView extends ChartView {
         const group = this.group;
         const data = seriesModel.getData();
 
-        let style = seriesModel.getModel('itemStyle').getItemStyle(['color']);
         let hoverStl = seriesModel.getModel(['emphasis', 'itemStyle']).getItemStyle();
         let labelModel = seriesModel.getModel('label');
         let hoverLabelModel = seriesModel.getModel(['emphasis', 'label']);
@@ -221,10 +220,7 @@ class HeatmapView extends ChartView {
                         width: Math.ceil(width),
                         height: Math.ceil(height)
                     },
-                    style: {
-                        fill: data.getItemVisual(idx, 'color'),
-                        opacity: data.getItemVisual(idx, 'opacity')
-                    }
+                    style: data.getItemVisual(idx, 'style')
                 });
             }
             else {
@@ -236,10 +232,7 @@ class HeatmapView extends ChartView {
                 rect = new graphic.Rect({
                     z2: 1,
                     shape: coordSys.dataToRect([data.get(dataDims[0], idx)]).contentShape,
-                    style: {
-                        fill: data.getItemVisual(idx, 'color'),
-                        opacity: data.getItemVisual(idx, 'opacity')
-                    }
+                    style: data.getItemVisual(idx, 'style')
                 });
             }
 
@@ -247,7 +240,6 @@ class HeatmapView extends ChartView {
 
             // Optimization for large datset
             if (data.hasItemOption) {
-                style = itemModel.getModel('itemStyle').getItemStyle(['color']);
                 hoverStl = itemModel.getModel(['emphasis', 'itemStyle']).getItemStyle();
                 labelModel = itemModel.getModel('label');
                 hoverLabelModel = itemModel.getModel(['emphasis', 'label']);
@@ -268,7 +260,6 @@ class HeatmapView extends ChartView {
                 }
             );
 
-            rect.setStyle(style);
             graphic.enableHoverEmphasis(rect, data.hasItemOption ? hoverStl : zrUtil.extend({}, hoverStl));
 
             rect.incremental = incremental;
diff --git a/src/chart/helper/EffectLine.ts b/src/chart/helper/EffectLine.ts
index bf20ab9..66503f0 100644
--- a/src/chart/helper/EffectLine.ts
+++ b/src/chart/helper/EffectLine.ts
@@ -30,6 +30,7 @@ import * as curveUtil from 'zrender/src/core/curve';
 import type List from '../../data/List';
 import { LineDrawSeriesScope, LineDrawModelOption } from './LineDraw';
 import Model from '../../model/Model';
+import { ColorString } from '../../util/types';
 
 export type ECSymbolOnEffectLine = ReturnType<typeof createSymbol> & {
     __t: number
@@ -67,7 +68,9 @@ class EffectLine extends graphic.Group {
         if (!zrUtil.isArray(size)) {
             size = [size, size];
         }
-        const color = effectModel.get('color') || lineData.getItemVisual(idx, 'color');
+
+        const lineStyle = lineData.getItemVisual(idx, 'style');
+        const color = effectModel.get('color') || (lineStyle && lineStyle.stroke);
         let symbol = this.childAt(1) as ECSymbolOnEffectLine;
 
         if (this._symbolType !== symbolType) {
@@ -89,7 +92,7 @@ class EffectLine extends graphic.Group {
         }
 
         // Shadow color is same with color in default
-        symbol.setStyle('shadowColor', color);
+        symbol.setStyle('shadowColor', color as ColorString);
         symbol.setStyle(effectModel.getItemStyle(['color']));
 
         symbol.scaleX = size[0];
diff --git a/src/chart/helper/EffectSymbol.ts b/src/chart/helper/EffectSymbol.ts
index f6fb57c..c43a928 100644
--- a/src/chart/helper/EffectSymbol.ts
+++ b/src/chart/helper/EffectSymbol.ts
@@ -173,7 +173,9 @@ class EffectSymbol extends Group {
         const itemModel = data.getItemModel<SymbolDrawItemModelOption>(idx);
         const symbolType = data.getItemVisual(idx, 'symbol');
         const symbolSize = normalizeSymbolSize(data.getItemVisual(idx, 'symbolSize'));
-        const color = data.getItemVisual(idx, 'color');
+
+        const symbolStyle = data.getItemVisual(idx, 'style');
+        const color = symbolStyle && symbolStyle.fill;
 
         rippleGroup.setScale(symbolSize);
 
diff --git a/src/chart/helper/LargeLineDraw.ts b/src/chart/helper/LargeLineDraw.ts
index 761a21f..6aabbb1 100644
--- a/src/chart/helper/LargeLineDraw.ts
+++ b/src/chart/helper/LargeLineDraw.ts
@@ -261,9 +261,9 @@ class LargeLineDraw {
         );
         lineEl.style.strokeNoScale = true;
 
-        const visualColor = data.getVisual('color');
-        if (visualColor) {
-            lineEl.setStyle('stroke', visualColor);
+        const style = data.getVisual('style');
+        if (style && style.stroke) {
+            lineEl.setStyle('stroke', style.stroke);
         }
         lineEl.setStyle('fill', null);
 
diff --git a/src/chart/helper/LargeSymbolDraw.ts b/src/chart/helper/LargeSymbolDraw.ts
index ef30a1f..458788f 100644
--- a/src/chart/helper/LargeSymbolDraw.ts
+++ b/src/chart/helper/LargeSymbolDraw.ts
@@ -264,20 +264,9 @@ class LargeSymbolDraw {
         const hostModel = data.hostModel;
 
         opt = opt || {};
-        // TODO
-        // if (data.hasItemVisual.symbolSize) {
-        //     // TODO typed array?
-        //     symbolEl.setShape('sizes', data.mapArray(
-        //         function (idx) {
-        //             let size = data.getItemVisual(idx, 'symbolSize');
-        //             return (size instanceof Array) ? size : [size, size];
-        //         }
-        //     ));
-        // }
-        // else {
+
         const size = data.getVisual('symbolSize');
         symbolEl.setShape('size', (size instanceof Array) ? size : [size, size]);
-        // }
 
         symbolEl.softClipShape = opt.clipShape || null;
         // Create symbolProxy to build path for each data
@@ -295,7 +284,8 @@ class LargeSymbolDraw {
             )
         );
 
-        const visualColor = data.getVisual('color');
+        const globalStyle = data.getVisual('style');
+        const visualColor = globalStyle && globalStyle.fill;
         if (visualColor) {
             symbolEl.setColor(visualColor);
         }
diff --git a/src/chart/helper/Line.ts b/src/chart/helper/Line.ts
index 1835716..47823aa 100644
--- a/src/chart/helper/Line.ts
+++ b/src/chart/helper/Line.ts
@@ -24,15 +24,19 @@ import ECLinePath from './LinePath';
 import * as graphic from '../../util/graphic';
 import {round} from '../../util/number';
 import List from '../../data/List';
-import { ZRTextAlign, ZRTextVerticalAlign, LineLabelOption } from '../../util/types';
+import { ZRTextAlign, ZRTextVerticalAlign, LineLabelOption, ColorString } from '../../util/types';
 import SeriesModel from '../../model/Series';
 import type { LineDrawSeriesScope, LineDrawModelOption } from './LineDraw';
+
 import { TextStyleProps } from 'zrender/src/graphic/Text';
+import { LineDataVisual } from '../../visual/commonVisualTypes';
 
 const SYMBOL_CATEGORIES = ['fromSymbol', 'toSymbol'] as const;
 
 type ECSymbol = ReturnType<typeof createSymbol>;
 
+type LineList = List<SeriesModel, LineDataVisual>;
+
 export interface LineLabel extends graphic.Text {
     lineLabelOriginalOpacity: number
 }
@@ -44,28 +48,29 @@ interface InnerLineLabel extends LineLabel {
     __labelDistance: number[]
 }
 
-function makeSymbolTypeKey(symbolCategory: string) {
+function makeSymbolTypeKey(symbolCategory: 'fromSymbol' | 'toSymbol') {
     return '_' + symbolCategory + 'Type' as '_fromSymbolType' | '_toSymbolType';
 }
 
 /**
  * @inner
  */
-function createSymbol(name: string, lineData: List, idx: number) {
-    const color = lineData.getItemVisual(idx, 'color');
+function createSymbol(name: 'fromSymbol' | 'toSymbol', lineData: LineList, idx: number) {
     const symbolType = lineData.getItemVisual(idx, name);
-    let symbolSize = lineData.getItemVisual(idx, name + 'Size');
+    const symbolSize = lineData.getItemVisual(
+        idx,
+        name + 'Size' as 'fromSymbolSize' | 'toSymbolSize'
+    );
 
     if (!symbolType || symbolType === 'none') {
         return;
     }
 
-    if (!zrUtil.isArray(symbolSize)) {
-        symbolSize = [symbolSize, symbolSize];
-    }
+    const symbolSizeArr = zrUtil.isArray(symbolSize)
+        ? symbolSize : [symbolSize, symbolSize];
     const symbolPath = symbolUtil.createSymbol(
-        symbolType, -symbolSize[0] / 2, -symbolSize[1] / 2,
-        symbolSize[0], symbolSize[1], color
+        symbolType, -symbolSizeArr[0] / 2, -symbolSizeArr[1] / 2,
+        symbolSizeArr[0], symbolSizeArr[1]
     );
 
     symbolPath.name = name;
@@ -105,7 +110,6 @@ function setLinePoints(targetShape: ECLinePath['shape'], points: number[][]) {
     }
 }
 
-
 class Line extends graphic.Group {
 
     private _fromSymbolType: string;
@@ -113,10 +117,10 @@ class Line extends graphic.Group {
 
     constructor(lineData: List, idx: number, seriesScope?: LineDrawSeriesScope) {
         super();
-        this._createLine(lineData, idx, seriesScope);
+        this._createLine(lineData as LineList, idx, seriesScope);
     }
 
-    _createLine(lineData: List, idx: number, seriesScope?: LineDrawSeriesScope) {
+    _createLine(lineData: LineList, idx: number, seriesScope?: LineDrawSeriesScope) {
         const seriesModel = lineData.hostModel;
         const linePoints = lineData.getItemLayout(idx);
         const line = createLine(linePoints);
@@ -150,6 +154,7 @@ class Line extends graphic.Group {
         this._updateCommonStl(lineData, idx, seriesScope);
     }
 
+    // TODO More strict on the List type in parameters?
     updateData(lineData: List, idx: number, seriesScope: LineDrawSeriesScope) {
         const seriesModel = lineData.hostModel;
 
@@ -163,12 +168,12 @@ class Line extends graphic.Group {
         graphic.updateProps(line, target, seriesModel, idx);
 
         zrUtil.each(SYMBOL_CATEGORIES, function (symbolCategory) {
-            const symbolType = lineData.getItemVisual(idx, symbolCategory);
+            const symbolType = (lineData as LineList).getItemVisual(idx, symbolCategory);
             const key = makeSymbolTypeKey(symbolCategory);
             // Symbol changed
             if (this[key] !== symbolType) {
                 this.remove(this.childOfName(symbolCategory));
-                const symbol = createSymbol(symbolCategory, lineData, idx);
+                const symbol = createSymbol(symbolCategory, lineData as LineList, idx);
                 this.add(symbol);
             }
             this[key] = symbolType;
@@ -182,7 +187,6 @@ class Line extends graphic.Group {
 
         const line = this.childOfName('line') as ECLinePath;
 
-        let lineStyle = seriesScope && seriesScope.lineStyle;
         let hoverLineStyle = seriesScope && seriesScope.hoverLineStyle;
         let labelModel = seriesScope && seriesScope.labelModel;
         let hoverLabelModel = seriesScope && seriesScope.hoverLabelModel;
@@ -191,29 +195,18 @@ class Line extends graphic.Group {
         if (!seriesScope || lineData.hasItemOption) {
             const itemModel = lineData.getItemModel<LineDrawModelOption>(idx);
 
-            lineStyle = itemModel.getModel('lineStyle').getLineStyle();
             hoverLineStyle = itemModel.getModel(['emphasis', 'lineStyle']).getLineStyle();
 
             labelModel = itemModel.getModel('label');
             hoverLabelModel = itemModel.getModel(['emphasis', 'label']);
         }
 
-        const visualColor = lineData.getItemVisual(idx, 'color');
-        const visualOpacity = zrUtil.retrieve3(
-            lineData.getItemVisual(idx, 'opacity'),
-            lineStyle.opacity,
-            1
-        );
-
-        line.useStyle(zrUtil.defaults(
-            {
-                strokeNoScale: true,
-                fill: 'none',
-                stroke: visualColor,
-                opacity: visualOpacity
-            },
-            lineStyle
-        ));
+        const lineStyle = lineData.getItemVisual(idx, 'style');
+        const visualColor = lineStyle.stroke;
+
+        line.useStyle(lineStyle);
+        line.style.strokeNoScale = true;
+
         const lineEmphasisState = line.ensureState('emphasis');
         lineEmphasisState.style = hoverLineStyle;
 
@@ -222,9 +215,8 @@ class Line extends graphic.Group {
             const symbol = this.childOfName(symbolCategory) as ECSymbol;
             if (symbol) {
                 symbol.setColor(visualColor);
-                symbol.setStyle({
-                    opacity: visualOpacity
-                });
+                symbol.style.opacity = lineStyle.opacity;
+                symbol.markRedraw();
             }
         }, this);
 
@@ -237,7 +229,7 @@ class Line extends graphic.Group {
 
         // FIXME: the logic below probably should be merged to `graphic.setLabelStyle`.
         if (showLabel || hoverShowLabel) {
-            defaultLabelColor = visualColor || '#000';
+            defaultLabelColor = visualColor as ColorString || '#000';
 
             baseText = seriesModel.getFormattedLabel(idx, 'normal', lineData.dataType);
             if (baseText == null) {
diff --git a/src/chart/helper/LineDraw.ts b/src/chart/helper/LineDraw.ts
index 18e6857..0acf339 100644
--- a/src/chart/helper/LineDraw.ts
+++ b/src/chart/helper/LineDraw.ts
@@ -77,7 +77,6 @@ export interface LineDrawModelOption {
 type ListForLineDraw = List<Model<LineDrawModelOption & AnimationOptionMixin>>;
 
 export interface LineDrawSeriesScope {
-    lineStyle?: ZRStyleProps
     hoverLineStyle?: ZRStyleProps
 
     labelModel?: Model<LineLabelOption>
@@ -205,6 +204,7 @@ class LineDraw {
             itemEl = new this._LineCtor(newLineData, newIdx, seriesScope);
         }
         else {
+            graphic.clearStates(itemEl);
             itemEl.updateData(newLineData, newIdx, seriesScope);
         }
 
diff --git a/src/chart/helper/Polyline.ts b/src/chart/helper/Polyline.ts
index 49df307..0e8df66 100644
--- a/src/chart/helper/Polyline.ts
+++ b/src/chart/helper/Polyline.ts
@@ -18,7 +18,6 @@
 */
 
 import * as graphic from '../../util/graphic';
-import * as zrUtil from 'zrender/src/core/util';
 import type { LineDrawSeriesScope, LineDrawModelOption } from './LineDraw';
 import type List from '../../data/List';
 
@@ -61,23 +60,14 @@ class Polyline extends graphic.Group {
         const line = this.childAt(0) as graphic.Polyline;
         const itemModel = lineData.getItemModel<LineDrawModelOption>(idx);
 
-        const visualColor = lineData.getItemVisual(idx, 'color');
 
-        let lineStyle = seriesScope && seriesScope.lineStyle;
         let hoverLineStyle = seriesScope && seriesScope.hoverLineStyle;
 
         if (!seriesScope || lineData.hasItemOption) {
-            lineStyle = itemModel.getModel('lineStyle').getLineStyle();
             hoverLineStyle = itemModel.getModel(['emphasis', 'lineStyle']).getLineStyle();
         }
-        line.useStyle(zrUtil.defaults(
-            {
-                strokeNoScale: true,
-                fill: 'none',
-                stroke: visualColor
-            },
-            lineStyle
-        ));
+        line.useStyle(lineData.getItemVisual(idx, 'style'));
+        line.style.strokeNoScale = true;
 
         const lineEmphasisState = line.ensureState('emphasis');
         lineEmphasisState.style = hoverLineStyle;
diff --git a/src/chart/helper/Symbol.ts b/src/chart/helper/Symbol.ts
index a4c46df..33eab5d 100644
--- a/src/chart/helper/Symbol.ts
+++ b/src/chart/helper/Symbol.ts
@@ -17,25 +17,24 @@
 * under the License.
 */
 
-import * as zrUtil from 'zrender/src/core/util';
 import {createSymbol} from '../../util/symbol';
 import * as graphic from '../../util/graphic';
 import {parsePercent} from '../../util/number';
 import {getDefaultLabel} from './labelHelper';
 import List from '../../data/List';
-import { DisplayState } from '../../util/types';
+import { DisplayState, ColorString } from '../../util/types';
 import SeriesModel from '../../model/Series';
 import { PathProps } from 'zrender/src/graphic/Path';
 import { SymbolDrawSeriesScope, SymbolDrawItemModelOption } from './SymbolDraw';
+import { extend } from 'zrender/src/core/util';
 
 // Update common properties
-const normalStyleAccessPath = ['itemStyle'] as const;
 const emphasisStyleAccessPath = ['emphasis', 'itemStyle'] as const;
 const normalLabelAccessPath = ['label'] as const;
 const emphasisLabelAccessPath = ['emphasis', 'label'] as const;
 
 type ECSymbol = ReturnType<typeof createSymbol> & {
-    highDownOnUpdate(fromState: DisplayState, toState: DisplayState): void
+    onStateChange(fromState: DisplayState, toState: DisplayState): void
 };
 
 class Symbol extends graphic.Group {
@@ -67,8 +66,6 @@ class Symbol extends graphic.Group {
         // Remove paths created before
         this.removeAll();
 
-        const color = data.getItemVisual(idx, 'color');
-
         // let symbolPath = createSymbol(
         //     symbolType, -0.5, -0.5, 1, 1, color
         // );
@@ -76,7 +73,7 @@ class Symbol extends graphic.Group {
         // and macOS Sierra, a circle stroke become a rect, no matter what
         // the scale is set. So we set width/height as 2. See #4150.
         const symbolPath = createSymbol(
-            symbolType, -1, -1, 2, 2, color, keepAspect
+            symbolType, -1, -1, 2, 2, null, keepAspect
         );
 
         symbolPath.attr({
@@ -170,7 +167,7 @@ class Symbol extends graphic.Group {
 
         if (isInit) {
             const keepAspect = data.getItemVisual(idx, 'symbolKeepAspect');
-            this._createSymbol(symbolType, data, idx, symbolSize, keepAspect);
+            this._createSymbol(symbolType as string, data, idx, symbolSize, keepAspect);
         }
         else {
             const symbolPath = this.childAt(0) as ECSymbol;
@@ -185,11 +182,13 @@ class Symbol extends graphic.Group {
 
         if (isInit) {
             const symbolPath = this.childAt(0) as ECSymbol;
-            const fadeIn = seriesScope && seriesScope.fadeIn;
+            // Always fadeIn. Because it has fadeOut animation when symbol is removed..
+            // const fadeIn = seriesScope && seriesScope.fadeIn;
+            const fadeIn = true;
 
             const target: PathProps = {
-                scaleX: symbolPath.scaleX,
-                scaleY: symbolPath.scaleY
+                scaleX: this._scaleX,
+                scaleY: this._scaleY
             };
             fadeIn && (target.style = {
                 opacity: symbolPath.style.opacity
@@ -212,25 +211,7 @@ class Symbol extends graphic.Group {
     ) {
         const symbolPath = this.childAt(0) as ECSymbol;
         const seriesModel = data.hostModel as SeriesModel;
-        const color = data.getItemVisual(idx, 'color');
-
-        // Reset style
-        if (symbolPath.type !== 'image') {
-            symbolPath.useStyle({
-                strokeNoScale: true
-            });
-        }
-        else {
-            symbolPath.setStyle({
-                opacity: null,
-                shadowBlur: null,
-                shadowOffsetX: null,
-                shadowOffsetY: null,
-                shadowColor: null
-            });
-        }
 
-        let itemStyle = seriesScope && seriesScope.itemStyle;
         let hoverItemStyle = seriesScope && seriesScope.hoverItemStyle;
         let symbolRotate = seriesScope && seriesScope.symbolRotate;
         let symbolOffset = seriesScope && seriesScope.symbolOffset;
@@ -243,9 +224,6 @@ class Symbol extends graphic.Group {
             const itemModel = (seriesScope && seriesScope.itemModel)
                 ? seriesScope.itemModel : data.getItemModel<SymbolDrawItemModelOption>(idx);
 
-            // Color must be excluded.
-            // Because symbol provide setColor individually to set fill and stroke
-            itemStyle = itemModel.getModel(normalStyleAccessPath).getItemStyle(['color']);
             hoverItemStyle = itemModel.getModel(emphasisStyleAccessPath).getItemStyle();
 
             symbolRotate = itemModel.getShallow('symbolRotate');
@@ -256,11 +234,6 @@ class Symbol extends graphic.Group {
             hoverAnimation = itemModel.getShallow('hoverAnimation');
             cursorStyle = itemModel.getShallow('cursor');
         }
-        else {
-            hoverItemStyle = zrUtil.extend({}, hoverItemStyle);
-        }
-
-        const elStyle = symbolPath.style;
 
         symbolPath.attr('rotation', (symbolRotate || 0) * Math.PI / 180 || 0);
 
@@ -272,14 +245,19 @@ class Symbol extends graphic.Group {
         cursorStyle && symbolPath.attr('cursor', cursorStyle);
 
         // PENDING setColor before setStyle!!!
-        symbolPath.setColor(color, seriesScope && seriesScope.symbolInnerColor);
-
-        symbolPath.setStyle(itemStyle);
-
-        const opacity = data.getItemVisual(idx, 'opacity');
-        if (opacity != null) {
-            elStyle.opacity = opacity;
+        const symbolStyle = data.getItemVisual(idx, 'style');
+        const visualColor = symbolStyle.fill;
+        if (symbolPath.__isEmptyBrush) {
+            // fill and stroke will be swapped if it's empty.
+            // So we cloned a new style to avoid it affecting the original style in visual storage.
+            // TODO Better implementation. No empty logic!
+            symbolPath.useStyle(extend({}, symbolStyle));
+        }
+        else {
+            symbolPath.useStyle(symbolStyle);
         }
+        symbolPath.setColor(visualColor, seriesScope && seriesScope.symbolInnerColor);
+        symbolPath.style.strokeNoScale = true;
 
         const liftZ = data.getItemVisual(idx, 'liftZ');
         const z2Origin = this._z2;
@@ -302,7 +280,7 @@ class Symbol extends graphic.Group {
                 labelFetcher: seriesModel,
                 labelDataIndex: idx,
                 defaultText: getLabelDefaultText,
-                autoColor: color
+                autoColor: visualColor as ColorString
             }
         );
 
@@ -313,9 +291,9 @@ class Symbol extends graphic.Group {
 
         this._scaleX = symbolSize[0] / 2;
         this._scaleY = symbolSize[1] / 2;
-        symbolPath.highDownOnUpdate = (
+        symbolPath.onStateChange = (
             hoverAnimation && seriesModel.isAnimationEnabled()
-        ) ? highDownOnUpdate : null;
+        ) ? onStateChange : null;
 
         graphic.enableHoverEmphasis(symbolPath, hoverItemStyle);
     }
@@ -352,7 +330,7 @@ class Symbol extends graphic.Group {
     }
 }
 
-function highDownOnUpdate(this: ECSymbol, fromState: DisplayState, toState: DisplayState) {
+function onStateChange(this: ECSymbol, fromState: DisplayState, toState: DisplayState) {
     // Do not support this hover animation util some scenario required.
     // Animation can only be supported in hover layer when using `el.incremetal`.
     if (this.incremental || this.useHoverLayer) {
diff --git a/src/chart/helper/SymbolDraw.ts b/src/chart/helper/SymbolDraw.ts
index 7eea47e..938a4d8 100644
--- a/src/chart/helper/SymbolDraw.ts
+++ b/src/chart/helper/SymbolDraw.ts
@@ -94,7 +94,6 @@ export interface SymbolDrawItemModelOption extends SymbolOptionMixin<object> {
 }
 
 export interface SymbolDrawSeriesScope {
-    itemStyle?: ZRStyleProps
     hoverItemStyle?: ZRStyleProps
     symbolRotate?: number
     symbolOffset?: number[]
@@ -111,7 +110,6 @@ export interface SymbolDrawSeriesScope {
 function makeSeriesScope(data: List): SymbolDrawSeriesScope {
     const seriesModel = data.hostModel;
     return {
-        itemStyle: seriesModel.getModel('itemStyle').getItemStyle(['color']),
         hoverItemStyle: seriesModel.getModel(['emphasis', 'itemStyle']).getItemStyle(),
         symbolRotate: seriesModel.get('symbolRotate'),
         symbolOffset: seriesModel.get('symbolOffset'),
@@ -168,6 +166,7 @@ class SymbolDraw {
             })
             .update(function (newIdx, oldIdx) {
                 let symbolEl = oldData.getItemGraphicEl(oldIdx) as SymbolLike;
+
                 const point = data.getItemLayout(newIdx) as number[];
                 if (!symbolNeedsDraw(data, point, newIdx, opt)) {
                     group.remove(symbolEl);
@@ -178,6 +177,8 @@ class SymbolDraw {
                     symbolEl.setPosition(point);
                 }
                 else {
+                    graphic.clearStates(symbolEl);
+
                     symbolEl.updateData(data, newIdx, seriesScope);
                     graphic.updateProps(symbolEl, {
                         x: point[0],
diff --git a/src/chart/line.ts b/src/chart/line.ts
index c0c1e6d..541b481 100644
--- a/src/chart/line.ts
+++ b/src/chart/line.ts
@@ -21,14 +21,13 @@ import * as echarts from '../echarts';
 
 import './line/LineSeries';
 import './line/LineView';
-import visualSymbol from '../visual/symbol';
+
 import layoutPoints from '../layout/points';
 import dataSample from '../processor/dataSample';
 
 // In case developer forget to include grid component
 import '../component/gridSimple';
 
-echarts.registerVisual(visualSymbol('line', 'circle', 'line'));
 echarts.registerLayout(layoutPoints('line'));
 
 // Down sample after filter
diff --git a/src/chart/line/LineSeries.ts b/src/chart/line/LineSeries.ts
index c53aa8c..1fcb748 100644
--- a/src/chart/line/LineSeries.ts
+++ b/src/chart/line/LineSeries.ts
@@ -103,6 +103,9 @@ class LineSeriesModel extends SeriesModel<LineSeriesOption> {
 
     coordinateSystem: Cartesian2D | Polar;
 
+    hasSymbolVisual = true;
+    legendSymbol = 'line';
+
     getInitialData(option: LineSeriesOption): List {
         if (__DEV__) {
             const coordSys = option.coordinateSystem;
diff --git a/src/chart/line/LineView.ts b/src/chart/line/LineView.ts
index c829293..80a2f3f 100644
--- a/src/chart/line/LineView.ts
+++ b/src/chart/line/LineView.ts
@@ -37,7 +37,6 @@ import type ExtensionAPI from '../../ExtensionAPI';
 import Cartesian2D from '../../coord/cartesian/Cartesian2D';
 import Polar from '../../coord/polar/Polar';
 import type List from '../../data/List';
-import type { VisualMeta } from '../../component/visualMap/VisualMapModel';
 import type { Payload, Dictionary, ColorString } from '../../util/types';
 import type OrdinalScale from '../../scale/Ordinal';
 import type Axis2D from '../../coord/cartesian/Axis2D';
@@ -135,7 +134,7 @@ function getVisualGradient(
     data: List,
     coordSys: Cartesian2D | Polar
 ) {
-    const visualMetaList = data.getVisual('visualMeta') as VisualMeta[];
+    const visualMetaList = data.getVisual('visualMeta');
     if (!visualMetaList || !visualMetaList.length || !data.count()) {
         // When data.count() is 0, gradient range can not be calculated.
         return;
@@ -513,7 +512,8 @@ class LineView extends ChartView {
             }
         }
 
-        const visualColor = getVisualGradient(data, coordSys) || data.getVisual('color');
+        const visualColor = getVisualGradient(data, coordSys)
+            || data.getVisual('style')[data.getVisual('brushType')];
 
         polyline.useStyle(zrUtil.defaults(
             // Use color in lineStyle first
diff --git a/src/chart/lines/LinesSeries.ts b/src/chart/lines/LinesSeries.ts
index 7618169..41d514a 100644
--- a/src/chart/lines/LinesSeries.ts
+++ b/src/chart/lines/LinesSeries.ts
@@ -149,7 +149,8 @@ class LinesSeriesModel extends SeriesModel<LinesSeriesOption> {
 
     static readonly dependencies = ['grid', 'polar', 'geo', 'calendar'];
 
-    visualColorAccessPath = ['lineStyle', 'color'];
+    visualStyleAccessPath = 'lineStyle';
+    visualColorBrushType = 'stroke' as const;
 
     private _flatCoords: ArrayLike<number>;
     private _flatCoordsOffset: ArrayLike<number>;
diff --git a/src/chart/lines/linesVisual.ts b/src/chart/lines/linesVisual.ts
index 0f37f93..1e4c931 100644
--- a/src/chart/lines/linesVisual.ts
+++ b/src/chart/lines/linesVisual.ts
@@ -21,6 +21,7 @@ import { StageHandler } from '../../util/types';
 import List from '../../data/List';
 import LinesSeriesModel, { LinesDataItemOption } from './LinesSeries';
 import Model from '../../model/Model';
+import { LineDataVisual } from '../../visual/commonVisualTypes';
 
 function normalize(a: string | string[]): string[];
 function normalize(a: number | number[]): number[];
@@ -31,33 +32,30 @@ function normalize(a: string | number | (string | number)[]): (string | number)[
     return a;
 }
 
-const opacityQuery = ['lineStyle', 'opacity'] as const;
-
 const linesVisual: StageHandler = {
     seriesType: 'lines',
     reset(seriesModel: LinesSeriesModel) {
         const symbolType = normalize(seriesModel.get('symbol'));
         const symbolSize = normalize(seriesModel.get('symbolSize'));
-        const data = seriesModel.getData();
+        const data = seriesModel.getData() as List<LinesSeriesModel, LineDataVisual>;
 
         data.setVisual('fromSymbol', symbolType && symbolType[0]);
         data.setVisual('toSymbol', symbolType && symbolType[1]);
         data.setVisual('fromSymbolSize', symbolSize && symbolSize[0]);
         data.setVisual('toSymbolSize', symbolSize && symbolSize[1]);
-        data.setVisual('opacity', seriesModel.get(opacityQuery));
 
-        function dataEach(data: List<LinesSeriesModel>, idx: number): void {
+        function dataEach(
+            data: List<LinesSeriesModel, LineDataVisual>,
+            idx: number
+        ): void {
             const itemModel = data.getItemModel(idx) as Model<LinesDataItemOption>;
             const symbolType = normalize(itemModel.getShallow('symbol', true));
             const symbolSize = normalize(itemModel.getShallow('symbolSize', true));
-            const opacity = itemModel.get(opacityQuery);
 
             symbolType[0] && data.setItemVisual(idx, 'fromSymbol', symbolType[0]);
             symbolType[1] && data.setItemVisual(idx, 'toSymbol', symbolType[1]);
             symbolSize[0] && data.setItemVisual(idx, 'fromSymbolSize', symbolSize[0]);
             symbolSize[1] && data.setItemVisual(idx, 'toSymbolSize', symbolSize[1]);
-
-            data.setItemVisual(idx, 'opacity', opacity);
         }
 
         return {
diff --git a/src/chart/map.ts b/src/chart/map.ts
index 6e22e8b..b53e5d5 100644
--- a/src/chart/map.ts
+++ b/src/chart/map.ts
@@ -25,13 +25,11 @@ import '../action/geoRoam';
 import '../coord/geo/geoCreator';
 
 import mapSymbolLayout from './map/mapSymbolLayout';
-import mapVisual from './map/mapVisual';
 import mapDataStatistic from './map/mapDataStatistic';
 import backwardCompat from './map/backwardCompat';
 import createDataSelectAction from '../action/createDataSelectAction';
 
 echarts.registerLayout(mapSymbolLayout);
-echarts.registerVisual(mapVisual);
 echarts.registerProcessor(echarts.PRIORITY.PROCESSOR.STATISTIC, mapDataStatistic);
 echarts.registerPreprocessor(backwardCompat);
 
diff --git a/src/chart/map/MapView.ts b/src/chart/map/MapView.ts
index 87ded60..6e0bd06 100644
--- a/src/chart/map/MapView.ts
+++ b/src/chart/map/MapView.ts
@@ -142,7 +142,8 @@ class MapView extends ChartView {
                     // And each series also need a symbol with legend color
                     //
                     // Layout and visual are put one the different data
-                    fill: mapModel.getData().getVisual('color')
+                    // TODO
+                    fill: mapModel.getData().getVisual('style').fill
                 },
                 shape: {
                     cx: point[0] + offset * 9,
diff --git a/src/chart/parallel/ParallelSeries.ts b/src/chart/parallel/ParallelSeries.ts
index 879d22f..6b9dbfc 100644
--- a/src/chart/parallel/ParallelSeries.ts
+++ b/src/chart/parallel/ParallelSeries.ts
@@ -85,7 +85,8 @@ class ParallelSeriesModel extends SeriesModel<ParallelSeriesOption> {
 
     static dependencies = ['parallel'];
 
-    visualColorAccessPath = ['lineStyle', 'color'];
+    visualStyleAccessPath = 'lineStyle';
+    visualColorBrushType = 'stroke' as const;
 
     coordinateSystem: Parallel;
 
diff --git a/src/chart/parallel/ParallelView.ts b/src/chart/parallel/ParallelView.ts
index d5e2873..9569dd3 100644
--- a/src/chart/parallel/ParallelView.ts
+++ b/src/chart/parallel/ParallelView.ts
@@ -20,19 +20,17 @@
 import * as graphic from '../../util/graphic';
 import ChartView from '../../view/Chart';
 import List from '../../data/List';
-import ParallelSeriesModel, { ParallelSeriesDataItemOption } from './ParallelSeries';
+import ParallelSeriesModel from './ParallelSeries';
 import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../ExtensionAPI';
 import { StageHandlerProgressParams, ParsedValue, Payload } from '../../util/types';
 import Parallel from '../../coord/parallel/Parallel';
 import { OptionAxisType } from '../../coord/axisCommonTypes';
-import { PathStyleProps } from 'zrender/src/graphic/Path';
 
 const DEFAULT_SMOOTH = 0.3;
 
 interface ParallelDrawSeriesScope {
     smooth: number
-    lineStyle: PathStyleProps
 }
 class ParallelView extends ChartView {
     static type = 'parallel';
@@ -79,6 +77,8 @@ class ParallelView extends ChartView {
 
         function update(newDataIndex: number, oldDataIndex: number) {
             const line = oldData.getItemGraphicEl(oldDataIndex) as graphic.Polyline;
+            graphic.clearStates(line);
+
             const points = createLinePoints(data, newDataIndex, dimensions, coordSys);
             data.setItemGraphicEl(newDataIndex, line);
             const animationModel = (payload && payload.animation === false) ? null : seriesModel;
@@ -189,7 +189,6 @@ function makeSeriesScope(seriesModel: ParallelSeriesModel): ParallelDrawSeriesSc
     smooth === true && (smooth = DEFAULT_SMOOTH);
 
     return {
-        lineStyle: seriesModel.getModel('lineStyle').getLineStyle(),
         smooth: smooth != null ? +smooth : DEFAULT_SMOOTH
     };
 }
@@ -200,23 +199,7 @@ function updateElCommon(
     dataIndex: number,
     seriesScope: ParallelDrawSeriesScope
 ) {
-    let lineStyle = seriesScope.lineStyle;
-
-    if (data.hasItemOption) {
-        const lineStyleModel = data.getItemModel<ParallelSeriesDataItemOption>(dataIndex)
-            .getModel('lineStyle');
-        lineStyle = lineStyleModel.getLineStyle();
-    }
-
-    el.useStyle(lineStyle);
-
-    const elStyle = el.style;
-    elStyle.fill = null;
-    // lineStyle.color have been set to itemVisual in module:echarts/visual/seriesColor.
-    elStyle.stroke = data.getItemVisual(dataIndex, 'color');
-    // lineStyle.opacity have been set to itemVisual in parallelVisual.
-    elStyle.opacity = data.getItemVisual(dataIndex, 'opacity');
-
+    el.useStyle(data.getItemVisual(dataIndex, 'style'));
     seriesScope.smooth && (el.shape.smooth = seriesScope.smooth);
 }
 
diff --git a/src/chart/parallel/parallelVisual.ts b/src/chart/parallel/parallelVisual.ts
index 4aa88a6..e50b82e 100644
--- a/src/chart/parallel/parallelVisual.ts
+++ b/src/chart/parallel/parallelVisual.ts
@@ -30,28 +30,14 @@ const parallelVisual: StageHandler = {
 
     reset: function (seriesModel: ParallelSeriesModel, ecModel) {
 
-        // let itemStyleModel = seriesModel.getModel('itemStyle');
-        const lineStyleModel = seriesModel.getModel('lineStyle');
-        const globalColors = ecModel.get('color');
-
-        const color = lineStyleModel.get('color')
-            // || itemStyleModel.get('color')
-            || globalColors[seriesModel.seriesIndex % globalColors.length];
-        const inactiveOpacity = seriesModel.get('inactiveOpacity');
-        const activeOpacity = seriesModel.get('activeOpacity');
-        const lineStyle = seriesModel.getModel('lineStyle').getLineStyle();
-
         const coordSys = seriesModel.coordinateSystem;
-        const data = seriesModel.getData();
 
         const opacityMap = {
-            normal: lineStyle.opacity,
-            active: activeOpacity,
-            inactive: inactiveOpacity
+            normal: seriesModel.get(['lineStyle', 'opacity']),
+            active: seriesModel.get('activeOpacity'),
+            inactive: seriesModel.get('inactiveOpacity')
         };
 
-        data.setVisual('color', color);
-
         return {
             progress(params, data) {
                 coordSys.eachActiveState(data, function (activeState, dataIndex) {
@@ -62,7 +48,8 @@ const parallelVisual: StageHandler = {
                         );
                         itemOpacity != null && (opacity = itemOpacity);
                     }
-                    data.setItemVisual(dataIndex, 'opacity', opacity);
+                    const existsStyle = data.ensureUniqueItemVisual(dataIndex, 'style');
+                    existsStyle.opacity = opacity;
                 }, params.start, params.end);
             }
         };
diff --git a/src/chart/pictorialBar.ts b/src/chart/pictorialBar.ts
index 975e4d9..65e494c 100644
--- a/src/chart/pictorialBar.ts
+++ b/src/chart/pictorialBar.ts
@@ -25,7 +25,6 @@ import './bar/PictorialBarSeries';
 import './bar/PictorialBarView';
 
 import { layout } from '../layout/barGrid';
-import visualSymbol from '../visual/symbol';
 
 // In case developer forget to include grid component
 import '../component/gridSimple';
@@ -33,4 +32,3 @@ import '../component/gridSimple';
 echarts.registerLayout(zrUtil.curry(
     layout, 'pictorialBar'
 ));
-echarts.registerVisual(visualSymbol('pictorialBar', 'roundRect'));
diff --git a/src/chart/pie.ts b/src/chart/pie.ts
index 4f8630b..b568a99 100644
--- a/src/chart/pie.ts
+++ b/src/chart/pie.ts
@@ -24,7 +24,6 @@ import './pie/PieSeries';
 import './pie/PieView';
 
 import createDataSelectAction from '../action/createDataSelectAction';
-import dataColor from '../visual/dataColor';
 import pieLayout from './pie/pieLayout';
 import dataFilter from '../processor/dataFilter';
 
@@ -44,6 +43,5 @@ createDataSelectAction('pie', [{
 
 // type PieLayoutParameters = Parameters<typeof pieLayout>;
 
-echarts.registerVisual(dataColor('pie'));
 echarts.registerLayout(zrUtil.curry(pieLayout, 'pie'));
 echarts.registerProcessor(dataFilter('pie'));
\ No newline at end of file
diff --git a/src/chart/pie/PieSeries.ts b/src/chart/pie/PieSeries.ts
index bae1cdd..f1bc70d 100644
--- a/src/chart/pie/PieSeries.ts
+++ b/src/chart/pie/PieSeries.ts
@@ -112,6 +112,8 @@ class PieSeriesModel extends SeriesModel<PieSeriesOption> {
 
     static type = 'series.pie' as const;
 
+    useColorPaletteOnData = true;
+
     /**
      * @overwrite
      */
diff --git a/src/chart/pie/PieView.ts b/src/chart/pie/PieView.ts
index 3e02c4f..020e624 100644
--- a/src/chart/pie/PieView.ts
+++ b/src/chart/pie/PieView.ts
@@ -24,7 +24,7 @@ import * as graphic from '../../util/graphic';
 import ChartView from '../../view/Chart';
 import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../ExtensionAPI';
-import { Payload, DisplayState, ECElement } from '../../util/types';
+import { Payload, DisplayState, ECElement, ColorString } from '../../util/types';
 import List from '../../data/List';
 import PieSeriesModel, {PieDataItemOption} from './PieSeries';
 import { Dictionary } from 'zrender/src/core/types';
@@ -157,18 +157,7 @@ class PiePiece extends graphic.Group {
             }
         }
 
-        // Update common style
-        const visualColor = data.getItemVisual(idx, 'color');
-
-        sector.useStyle(
-            zrUtil.defaults(
-                {
-                    lineJoin: 'bevel',
-                    fill: visualColor
-                },
-                itemModel.getModel('itemStyle').getItemStyle()
-            )
-        );
+        sector.useStyle(data.getItemVisual(idx, 'style'));
         const sectorEmphasisState = sector.ensureState('emphasis');
         sectorEmphasisState.style = itemModel.getModel(['emphasis', 'itemStyle']).getItemStyle();
 
@@ -188,7 +177,7 @@ class PiePiece extends graphic.Group {
         const withAnimation = !firstCreate && animationTypeUpdate === 'transition';
         this._updateLabel(data, idx, withAnimation);
 
-        (this as ECElement).highDownOnUpdate = (itemModel.get('hoverAnimation') && seriesModel.isAnimationEnabled())
+        (this as ECElement).onStateChange = (itemModel.get('hoverAnimation') && seriesModel.isAnimationEnabled())
             ? function (fromState: DisplayState, toState: DisplayState): void {
                 if (toState === 'emphasis') {
 
@@ -246,7 +235,9 @@ class PiePiece extends graphic.Group {
         const labelHoverModel = itemModel.getModel(['emphasis', 'label']);
         const labelLineModel = itemModel.getModel('labelLine');
         const labelLineHoverModel = itemModel.getModel(['emphasis', 'labelLine']);
-        const visualColor = data.getItemVisual(idx, 'color');
+
+        const style = data.getItemVisual(idx, 'style');
+        const visualColor = style && style.fill as ColorString;
 
         graphic.setLabelStyle(
             labelText,
@@ -260,7 +251,7 @@ class PiePiece extends graphic.Group {
             {
                 align: labelLayout.textAlign,
                 verticalAlign: labelLayout.verticalAlign,
-                opacity: data.getItemVisual(idx, 'opacity')
+                opacity: style && style.opacity
             }
         );
 
@@ -313,7 +304,7 @@ class PiePiece extends graphic.Group {
         // Default use item visual color
         labelLine.setStyle({
             stroke: visualColor,
-            opacity: data.getItemVisual(idx, 'opacity')
+            opacity: style && style.opacity
         });
         labelLine.setStyle(labelLineModel.getModel('lineStyle').getLineStyle());
 
@@ -382,6 +373,8 @@ class PieView extends ChartView {
             .update(function (newIdx, oldIdx) {
                 const piePiece = oldData.getItemGraphicEl(oldIdx) as PiePiece;
 
+                graphic.clearStates(piePiece);
+
                 if (!isFirstRender && animationTypeUpdate !== 'transition') {
                     piePiece.eachChild(function (child) {
                         child.stopAnimation(true);
diff --git a/src/chart/radar.ts b/src/chart/radar.ts
index 0ed87f7..282537f 100644
--- a/src/chart/radar.ts
+++ b/src/chart/radar.ts
@@ -24,14 +24,10 @@ import '../component/radar';
 import './radar/RadarSeries';
 import './radar/RadarView';
 
-import dataColor from '../visual/dataColor';
-import visualSymbol from '../visual/symbol';
 import radarLayout from './radar/radarLayout';
 import dataFilter from '../processor/dataFilter';
 import backwardCompat from './radar/backwardCompat';
 
-echarts.registerVisual(dataColor('radar'));
-echarts.registerVisual(visualSymbol('radar', 'circle'));
 echarts.registerLayout(radarLayout);
 echarts.registerProcessor(dataFilter('radar'));
 echarts.registerPreprocessor(backwardCompat);
\ No newline at end of file
diff --git a/src/chart/radar/RadarSeries.ts b/src/chart/radar/RadarSeries.ts
index 0403b8d..05c8dd1 100644
--- a/src/chart/radar/RadarSeries.ts
+++ b/src/chart/radar/RadarSeries.ts
@@ -82,6 +82,10 @@ class RadarSeriesModel extends SeriesModel<RadarSeriesOption> {
 
     coordinateSystem: Radar;
 
+    useColorPaletteOnData = true;
+
+    hasSymbolVisual = true;
+
     // Overwrite
     init(option: RadarSeriesOption) {
         super.init.apply(this, arguments as any);
diff --git a/src/chart/radar/RadarView.ts b/src/chart/radar/RadarView.ts
index b96deb0..98698b6 100644
--- a/src/chart/radar/RadarView.ts
+++ b/src/chart/radar/RadarView.ts
@@ -24,7 +24,7 @@ import ChartView from '../../view/Chart';
 import RadarSeriesModel, { RadarSeriesDataItemOption } from './RadarSeries';
 import ExtensionAPI from '../../ExtensionAPI';
 import List from '../../data/List';
-import { ZRColor, DisplayState, ECElement } from '../../util/types';
+import { DisplayState, ECElement, ColorString } from '../../util/types';
 import GlobalModel from '../../model/Global';
 import { VectorArray } from 'zrender/src/core/vector';
 
@@ -54,7 +54,6 @@ class RadarView extends ChartView {
 
         function createSymbol(data: List<RadarSeriesModel>, idx: number) {
             const symbolType = data.getItemVisual(idx, 'symbol') as string || 'circle';
-            const color = data.getItemVisual(idx, 'color') as ZRColor;
             if (symbolType === 'none') {
                 return;
             }
@@ -62,7 +61,7 @@ class RadarView extends ChartView {
                 data.getItemVisual(idx, 'symbolSize')
             );
             const symbolPath = symbolUtil.createSymbol(
-                symbolType, -1, -1, 2, 2, color
+                symbolType, -1, -1, 2, 2
             );
             symbolPath.attr({
                 style: {
@@ -144,6 +143,9 @@ class RadarView extends ChartView {
             })
             .update(function (newIdx, oldIdx) {
                 const itemGroup = oldData.getItemGraphicEl(oldIdx) as graphic.Group;
+
+                graphic.clearStates(itemGroup);
+
                 const polyline = itemGroup.childAt(0) as graphic.Polyline;
                 const polygon = itemGroup.childAt(1) as graphic.Polygon;
                 const symbolGroup = itemGroup.childAt(2) as graphic.Group;
@@ -180,7 +182,9 @@ class RadarView extends ChartView {
             const polyline = itemGroup.childAt(0) as graphic.Polyline;
             const polygon = itemGroup.childAt(1) as graphic.Polygon;
             const symbolGroup = itemGroup.childAt(2) as graphic.Group;
-            const color = data.getItemVisual(idx, 'color');
+            // Radar uses the visual encoded from itemStyle.
+            const itemStyle = data.getItemVisual(idx, 'style');
+            const color = itemStyle.fill;
 
             group.add(itemGroup);
 
@@ -216,12 +220,13 @@ class RadarView extends ChartView {
             const polygonEmphasisState = polygon.ensureState('emphasis');
             polygonEmphasisState.style = hoverAreaStyleModel.getAreaStyle();
 
-            const itemStyle = itemModel.getModel('itemStyle').getItemStyle(['color']);
             const itemHoverStyle = itemModel.getModel(['emphasis', 'itemStyle']).getItemStyle();
             const labelModel = itemModel.getModel('label');
             const labelHoverModel = itemModel.getModel(['emphasis', 'label']);
             symbolGroup.eachChild(function (symbolPath: RadarSymbol) {
-                symbolPath.setStyle(itemStyle);
+                symbolPath.useStyle(itemStyle);
+                symbolPath.setColor(color);
+
                 const pathEmphasisState = symbolPath.ensureState('emphasis');
                 pathEmphasisState.style = zrUtil.clone(itemHoverStyle);
                 let defaultText = data.get(data.dimensions[symbolPath.__dimIdx], idx);
@@ -234,12 +239,12 @@ class RadarView extends ChartView {
                         labelDataIndex: idx,
                         labelDimIndex: symbolPath.__dimIdx,
                         defaultText: defaultText as string,
-                        autoColor: color
+                        autoColor: color as ColorString
                     }
                 );
             });
 
-            (itemGroup as ECElement).highDownOnUpdate = function (fromState: DisplayState, toState: DisplayState) {
+            (itemGroup as ECElement).onStateChange = function (fromState: DisplayState, toState: DisplayState) {
                 polygon.attr('ignore', toState === 'emphasis' ? hoverPolygonIgnore : polygonIgnore);
             };
             graphic.enableHoverEmphasis(itemGroup);
diff --git a/src/chart/scatter.ts b/src/chart/scatter.ts
index 896a43a..91d5721 100644
--- a/src/chart/scatter.ts
+++ b/src/chart/scatter.ts
@@ -18,16 +18,13 @@
 */
 
 import * as echarts from '../echarts';
-// import * as zrUtil from 'zrender/src/core/util';
 
 import './scatter/ScatterSeries';
 import './scatter/ScatterView';
 
-import visualSymbol from '../visual/symbol';
 import layoutPoints from '../layout/points';
 
 // In case developer forget to include grid component
 import '../component/gridSimple';
 
-echarts.registerVisual(visualSymbol('scatter', 'circle'));
 echarts.registerLayout(layoutPoints('scatter'));
diff --git a/src/chart/scatter/ScatterSeries.ts b/src/chart/scatter/ScatterSeries.ts
index d1c58b2..e6b721b 100644
--- a/src/chart/scatter/ScatterSeries.ts
+++ b/src/chart/scatter/ScatterSeries.ts
@@ -86,6 +86,8 @@ class ScatterSeriesModel extends SeriesModel<ScatterSeriesOption> {
 
     static readonly dependencies = ['grid', 'polar', 'geo', 'singleAxis', 'calendar'];
 
+    hasSymbolVisual = true;
+
     getInitialData(option: ScatterSeriesOption, ecModel: GlobalModel): List {
         return createListFromArray(this.getSource(), this, {
             useEncodeDefaulter: true
diff --git a/src/chart/sunburst.ts b/src/chart/sunburst.ts
index 2f7edd8..8922feb 100644
--- a/src/chart/sunburst.ts
+++ b/src/chart/sunburst.ts
@@ -24,10 +24,8 @@ import './sunburst/SunburstSeries';
 import './sunburst/SunburstView';
 import './sunburst/sunburstAction';
 
-import dataColor from '../visual/dataColor';
 import sunburstLayout from './sunburst/sunburstLayout';
 import dataFilter from '../processor/dataFilter';
 
-echarts.registerVisual(zrUtil.curry(dataColor, 'sunburst'));
 echarts.registerLayout(zrUtil.curry(sunburstLayout, 'sunburst'));
 echarts.registerProcessor(zrUtil.curry(dataFilter, 'sunburst'));
diff --git a/src/chart/sunburst/SunburstPiece.ts b/src/chart/sunburst/SunburstPiece.ts
index 4b526ea..1095a6e 100644
--- a/src/chart/sunburst/SunburstPiece.ts
+++ b/src/chart/sunburst/SunburstPiece.ts
@@ -19,9 +19,9 @@
 
 import * as zrUtil from 'zrender/src/core/util';
 import * as graphic from '../../util/graphic';
-import { ZRColor, ColorString } from '../../util/types';
+import { ColorString } from '../../util/types';
 import { TreeNode } from '../../data/Tree';
-import SunburstSeriesModel, { SunburstSeriesNodeOption, SunburstSeriesOption } from './SunburstSeries';
+import SunburstSeriesModel, { SunburstSeriesNodeItemOption, SunburstSeriesOption } from './SunburstSeries';
 import GlobalModel from '../../model/Global';
 import { AllPropTypes } from 'zrender/src/core/types';
 
@@ -62,7 +62,7 @@ class SunburstPiece extends graphic.Group {
 
         const text = new graphic.Text({
             z2: DEFAULT_TEXT_Z,
-            silent: node.getModel<SunburstSeriesNodeOption>().get(['label', 'silent'])
+            silent: node.getModel<SunburstSeriesNodeItemOption>().get(['label', 'silent'])
         });
         sector.setTextContent(text);
 
@@ -98,7 +98,7 @@ class SunburstPiece extends graphic.Group {
         const sector = this.childAt(0) as graphic.Sector;
         graphic.getECData(sector).dataIndex = node.dataIndex;
 
-        const itemModel = node.getModel<SunburstSeriesNodeOption>();
+        const itemModel = node.getModel<SunburstSeriesNodeItemOption>();
         const layout = node.getLayout();
         // if (!layout) {
         //     console.log(node.getLayout());
@@ -106,11 +106,9 @@ class SunburstPiece extends graphic.Group {
         const sectorShape = zrUtil.extend({}, layout);
         sectorShape.label = null;
 
-        const visualColor = getNodeColor(node, seriesModel, ecModel);
-
-        fillDefaultColor(node, seriesModel, visualColor);
-
-        const normalStyle = itemModel.getModel('itemStyle').getItemStyle();
+        // const visualColor = getNodeColor(node, seriesModel, ecModel);
+        // fillDefaultColor(node, seriesModel, visualColor);
+        const normalStyle = node.getVisual('style');
         let style;
         if (state === 'normal') {
             style = normalStyle;
@@ -120,13 +118,13 @@ class SunburstPiece extends graphic.Group {
                 .getItemStyle();
             style = zrUtil.merge(stateStyle, normalStyle);
         }
-        style = zrUtil.defaults(
-            {
-                lineJoin: 'bevel',
-                fill: style.fill || visualColor
-            },
-            style
-        );
+        // style = zrUtil.defaults(
+        //     {
+        //         lineJoin: 'bevel',
+        //         fill: style.fill || visualColor
+        //     },
+        //     style
+        // );
 
         if (firstCreate) {
             sector.setShape(sectorShape);
@@ -160,7 +158,7 @@ class SunburstPiece extends graphic.Group {
             }, seriesModel);
         }
 
-        this._updateLabel(seriesModel, visualColor, state);
+        this._updateLabel(seriesModel, style.fill, state);
 
         const cursorStyle = itemModel.getShallow('cursor');
         cursorStyle && sector.attr('cursor', cursorStyle);
@@ -212,7 +210,7 @@ class SunburstPiece extends graphic.Group {
         visualColor: ColorString,
         state: 'emphasis' | 'normal' | 'highlight' | 'downplay'
     ) {
-        const itemModel = this.node.getModel<SunburstSeriesNodeOption>();
+        const itemModel = this.node.getModel<SunburstSeriesNodeItemOption>();
         const normalModel = itemModel.getModel('label');
         const labelModel = state === 'normal' || state === 'emphasis'
             ? normalModel
@@ -321,7 +319,7 @@ class SunburstPiece extends graphic.Group {
         }
         label.attr('rotation', rotate);
 
-        type LabelOption = SunburstSeriesNodeOption['label'];
+        type LabelOption = SunburstSeriesNodeItemOption['label'];
         function getLabelAttr<T extends keyof LabelOption>(name: T): LabelOption[T] {
             const stateAttr = labelModel.get(name);
             if (stateAttr == null) {
@@ -372,58 +370,58 @@ class SunburstPiece extends graphic.Group {
 export default SunburstPiece;
 
 
-/**
- * Get node color
- */
-function getNodeColor(
-    node: TreeNode,
-    seriesModel: SunburstSeriesModel,
-    ecModel: GlobalModel
-) {
-    // Color from visualMap
-    let visualColor = node.getVisual('color');
-    const visualMetaList = node.getVisual('visualMeta');
-    if (!visualMetaList || visualMetaList.length === 0) {
-        // Use first-generation color if has no visualMap
-        visualColor = null;
-    }
-
-    // Self color or level color
-    let color = node.getModel<SunburstSeriesNodeOption>().get(['itemStyle', 'color']);
-    if (color) {
-        return color;
-    }
-    else if (visualColor) {
-        // Color mapping
-        return visualColor;
-    }
-    else if (node.depth === 0) {
-        // Virtual root node
-        return ecModel.option.color[0];
-    }
-    else {
-        // First-generation color
-        const length = ecModel.option.color.length;
-        color = ecModel.option.color[getRootId(node) % length];
-    }
-    return color;
-}
-
-/**
- * Get index of root in sorted order
- *
- * @param {TreeNode} node current node
- * @return {number} index in root
- */
-function getRootId(node: TreeNode) {
-    let ancestor = node;
-    while (ancestor.depth > 1) {
-        ancestor = ancestor.parentNode;
-    }
-
-    const virtualRoot = node.getAncestors()[0];
-    return zrUtil.indexOf(virtualRoot.children, ancestor);
-}
+// /**
+//  * Get node color
+//  */
+// function getNodeColor(
+//     node: TreeNode,
+//     seriesModel: SunburstSeriesModel,
+//     ecModel: GlobalModel
+// ) {
+//     // Color from visualMap
+//     let visualColor = node.getVisual('color');
+//     const visualMetaList = node.getVisual('visualMeta');
+//     if (!visualMetaList || visualMetaList.length === 0) {
+//         // Use first-generation color if has no visualMap
+//         visualColor = null;
+//     }
+
+//     // Self color or level color
+//     let color = node.getModel<SunburstSeriesNodeItemOption>().get(['itemStyle', 'color']);
+//     if (color) {
+//         return color;
+//     }
+//     else if (visualColor) {
+//         // Color mapping
+//         return visualColor;
+//     }
+//     else if (node.depth === 0) {
+//         // Virtual root node
+//         return ecModel.option.color[0];
+//     }
+//     else {
+//         // First-generation color
+//         const length = ecModel.option.color.length;
+//         color = ecModel.option.color[getRootId(node) % length];
+//     }
+//     return color;
+// }
+
+// /**
+//  * Get index of root in sorted order
+//  *
+//  * @param {TreeNode} node current node
+//  * @return {number} index in root
+//  */
+// function getRootId(node: TreeNode) {
+//     let ancestor = node;
+//     while (ancestor.depth > 1) {
+//         ancestor = ancestor.parentNode;
+//     }
+
+//     const virtualRoot = node.getAncestors()[0];
+//     return zrUtil.indexOf(virtualRoot.children, ancestor);
+// }
 
 function isNodeHighlighted(
     node: TreeNode,
@@ -445,7 +443,7 @@ function isNodeHighlighted(
 }
 
 // Fix tooltip callback function params.color incorrect when pick a default color
-function fillDefaultColor(node: TreeNode, seriesModel: SunburstSeriesModel, color: ZRColor) {
-    const data = seriesModel.getData();
-    data.setItemVisual(node.dataIndex, 'color', color);
-}
+// function fillDefaultColor(node: TreeNode, seriesModel: SunburstSeriesModel, color: ZRColor) {
+//     const data = seriesModel.getData();
+//     data.setItemVisual(node.dataIndex, 'color', color);
+// }
diff --git a/src/chart/sunburst/SunburstSeries.ts b/src/chart/sunburst/SunburstSeries.ts
index 6211e01..be7b077 100644
--- a/src/chart/sunburst/SunburstSeries.ts
+++ b/src/chart/sunburst/SunburstSeries.ts
@@ -42,11 +42,11 @@ interface SunburstDataParams extends CallbackDataParams {
     treePathInfo: {
         name: string,
         dataIndex: number
-        value: SunburstSeriesNodeOption['value']
+        value: SunburstSeriesNodeItemOption['value']
     }[]
 }
 
-export interface SunburstSeriesNodeOption {
+export interface SunburstSeriesNodeItemOption {
     name?: string
 
     nodeClick?: 'rootToNode' | 'link'
@@ -71,7 +71,7 @@ export interface SunburstSeriesNodeOption {
 
     value?: OptionDataValue | OptionDataValue[]
 
-    children?: SunburstSeriesNodeOption[]
+    children?: SunburstSeriesNodeItemOption[]
 
     collapsed?: boolean
 
@@ -152,6 +152,8 @@ class SunburstSeriesModel extends SeriesModel<SunburstSeriesOption> {
 
     private _viewRoot: TreeNode;
 
+    useColorPaletteOnData = true;
+
     getInitialData(option: SunburstSeriesOption, ecModel: GlobalModel) {
         // Create a virtual root.
         const root = { name: option.name, children: option.data };
@@ -183,7 +185,7 @@ class SunburstSeriesModel extends SeriesModel<SunburstSeriesOption> {
         const params = super.getDataParams.apply(this, arguments as any) as SunburstDataParams;
 
         const node = this.getData().tree.getNodeByDataIndex(dataIndex);
-        params.treePathInfo = wrapTreePathInfo<SunburstSeriesNodeOption['value']>(node, this);
+        params.treePathInfo = wrapTreePathInfo<SunburstSeriesNodeItemOption['value']>(node, this);
 
         return params;
     }
@@ -292,7 +294,7 @@ class SunburstSeriesModel extends SeriesModel<SunburstSeriesOption> {
 
 
 
-function completeTreeValue(dataNode: SunburstSeriesNodeOption) {
+function completeTreeValue(dataNode: SunburstSeriesNodeItemOption) {
     // Postorder travel tree.
     // If value of none-leaf node is not set,
     // calculate it by suming up the value of all children.
diff --git a/src/chart/sunburst/SunburstView.ts b/src/chart/sunburst/SunburstView.ts
index b5ce867..bef20f9 100644
--- a/src/chart/sunburst/SunburstView.ts
+++ b/src/chart/sunburst/SunburstView.ts
@@ -21,7 +21,7 @@ import * as zrUtil from 'zrender/src/core/util';
 import ChartView from '../../view/Chart';
 import SunburstPiece from './SunburstPiece';
 import DataDiffer from '../../data/DataDiffer';
-import SunburstSeriesModel, { SunburstSeriesNodeOption } from './SunburstSeries';
+import SunburstSeriesModel, { SunburstSeriesNodeItemOption } from './SunburstSeries';
 import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../ExtensionAPI';
 import { TreeNode } from '../../data/Tree';
@@ -210,12 +210,12 @@ class SunburstView extends ChartView {
                 if (!targetFound
                     && node.piece && node.piece.childAt(0) === e.target
                 ) {
-                    const nodeClick = node.getModel<SunburstSeriesNodeOption>().get('nodeClick');
+                    const nodeClick = node.getModel<SunburstSeriesNodeItemOption>().get('nodeClick');
                     if (nodeClick === 'rootToNode') {
                         this._rootToNode(node);
                     }
                     else if (nodeClick === 'link') {
-                        const itemModel = node.getModel<SunburstSeriesNodeOption>();
+                        const itemModel = node.getModel<SunburstSeriesNodeItemOption>();
                         const link = itemModel.get('link');
                         if (link) {
                             const linkTarget = itemModel.get('target', true)
diff --git a/src/chart/sunburst/sunburstLayout.ts b/src/chart/sunburst/sunburstLayout.ts
index 195ec83..f1da06c 100644
--- a/src/chart/sunburst/sunburstLayout.ts
+++ b/src/chart/sunburst/sunburstLayout.ts
@@ -21,7 +21,7 @@ import { parsePercent } from '../../util/number';
 import * as zrUtil from 'zrender/src/core/util';
 import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../ExtensionAPI';
-import SunburstSeriesModel, { SunburstSeriesNodeOption, SunburstSeriesOption } from './SunburstSeries';
+import SunburstSeriesModel, { SunburstSeriesNodeItemOption, SunburstSeriesOption } from './SunburstSeries';
 import { TreeNode } from '../../data/Tree';
 
 // let PI2 = Math.PI * 2;
@@ -119,7 +119,7 @@ export default function (
                 let rStart = r0 + rPerLevel * depth;
                 let rEnd = r0 + rPerLevel * (depth + 1);
 
-                const itemModel = node.getModel<SunburstSeriesNodeOption>();
+                const itemModel = node.getModel<SunburstSeriesNodeItemOption>();
                 // @ts-ignore. TODO this is not provided to developer yet. Rename it.
                 if (itemModel.get('r0') != null) {
                     // @ts-ignore
diff --git a/src/chart/themeRiver/themeRiverVisual.ts b/src/chart/sunburst/sunburstVisual.ts
similarity index 52%
rename from src/chart/themeRiver/themeRiverVisual.ts
rename to src/chart/sunburst/sunburstVisual.ts
index 7140528..97eb785 100644
--- a/src/chart/themeRiver/themeRiverVisual.ts
+++ b/src/chart/sunburst/sunburstVisual.ts
@@ -17,32 +17,21 @@
 * under the License.
 */
 
-import {createHashMap} from 'zrender/src/core/util';
 import GlobalModel from '../../model/Global';
-import ThemeRiverSeriesModel from './ThemeRiverSeries';
+import SunburstSeriesModel, { SunburstSeriesNodeItemOption } from './SunburstSeries';
+import { extend } from 'zrender/src/core/util';
 
 export default function (ecModel: GlobalModel) {
-    ecModel.eachSeriesByType('themeRiver', function (seriesModel: ThemeRiverSeriesModel) {
-        const data = seriesModel.getData();
-        const rawData = seriesModel.getRawData();
-        const colorList = seriesModel.get('color');
-        const idxMap = createHashMap<number>();
-
-        data.each(function (idx) {
-            idxMap.set(data.getRawIndex(idx), idx);
-        });
-
-        rawData.each(function (rawIndex) {
-            const name = rawData.getName(rawIndex);
-            const color = colorList[(seriesModel.nameMap.get(name) - 1) % colorList.length];
 
-            rawData.setItemVisual(rawIndex, 'color', color);
-
-            const idx = idxMap.get(rawIndex);
-
-            if (idx != null) {
-                data.setItemVisual(idx, 'color', color);
-            }
+    ecModel.eachSeriesByType('graph', function (seriesModel: SunburstSeriesModel) {
+        const data = seriesModel.getData();
+        const tree = data.tree;
+        tree.eachNode(function (node) {
+            const model = node.getModel<SunburstSeriesNodeItemOption>();
+            // TODO Optimize
+            const style = model.getModel('itemStyle').getItemStyle();
+            const existsStyle = data.ensureUniqueItemVisual(node.dataIndex, 'style');
+            extend(existsStyle, style);
         });
     });
-}
\ No newline at end of file
+}
diff --git a/src/chart/themeRiver.ts b/src/chart/themeRiver.ts
index 26f136c..582003b 100644
--- a/src/chart/themeRiver.ts
+++ b/src/chart/themeRiver.ts
@@ -24,9 +24,7 @@ import './themeRiver/ThemeRiverSeries';
 import './themeRiver/ThemeRiverView';
 
 import themeRiverLayout from './themeRiver/themeRiverLayout';
-import themeRiverVisual from './themeRiver/themeRiverVisual';
 import dataFilter from '../processor/dataFilter';
 
 echarts.registerLayout(themeRiverLayout);
-echarts.registerVisual(themeRiverVisual);
 echarts.registerProcessor(dataFilter('themeRiver'));
\ No newline at end of file
diff --git a/src/chart/themeRiver/ThemeRiverSeries.ts b/src/chart/themeRiver/ThemeRiverSeries.ts
index cee81ee..5e28bf2 100644
--- a/src/chart/themeRiver/ThemeRiverSeries.ts
+++ b/src/chart/themeRiver/ThemeRiverSeries.ts
@@ -87,6 +87,8 @@ class ThemeRiverSeriesModel extends SeriesModel<ThemeRiverSeriesOption> {
 
     coordinateSystem: Single;
 
+    useColorPaletteOnData = true;
+
     /**
      * @override
      */
diff --git a/src/chart/themeRiver/ThemeRiverView.ts b/src/chart/themeRiver/ThemeRiverView.ts
index 3fa1a8d..f42e220 100644
--- a/src/chart/themeRiver/ThemeRiverView.ts
+++ b/src/chart/themeRiver/ThemeRiverView.ts
@@ -76,7 +76,7 @@ class ThemeRiverView extends ChartView {
             }
             const points0 = [];
             const points1 = [];
-            let color;
+            let style;
             const indices = layersSeries[idx].indices;
             let j = 0;
             for (; j < indices.length; j++) {
@@ -88,7 +88,7 @@ class ThemeRiverView extends ChartView {
                 points0.push([x, y0]);
                 points1.push([x, y0 + y]);
 
-                color = data.getItemVisual(indices[j], 'color');
+                style = data.getItemVisual(indices[j], 'style');
             }
 
             let polygon: ECPolygon;
@@ -154,12 +154,7 @@ class ThemeRiverView extends ChartView {
             }
 
             const hoverItemStyleModel = seriesModel.getModel(['emphasis', 'itemStyle']);
-            const itemStyleModel = seriesModel.getModel('itemStyle');
-
-
-            polygon.setStyle(extend({
-                fill: color
-            }, itemStyleModel.getItemStyle(['color'])));
+            polygon.setStyle(style);
 
             graphic.enableHoverEmphasis(polygon, hoverItemStyleModel.getItemStyle());
         }
diff --git a/src/chart/tree.ts b/src/chart/tree.ts
index e498d25..52b14c5 100644
--- a/src/chart/tree.ts
+++ b/src/chart/tree.ts
@@ -23,8 +23,8 @@ import './tree/TreeSeries';
 import './tree/TreeView';
 import './tree/treeAction';
 
-import visualSymbol from '../visual/symbol';
 import treeLayout from './tree/treeLayout';
+import treeVisual from './tree/treeVisual';
 
-echarts.registerVisual(visualSymbol('tree', 'circle'));
 echarts.registerLayout(treeLayout);
+echarts.registerLayout(treeVisual);
diff --git a/src/chart/tree/TreeSeries.ts b/src/chart/tree/TreeSeries.ts
index 2149e5b..4f24538 100644
--- a/src/chart/tree/TreeSeries.ts
+++ b/src/chart/tree/TreeSeries.ts
@@ -137,6 +137,11 @@ class TreeSeriesModel extends SeriesModel<TreeSeriesOption> {
 
     layoutInfo: LayoutRect;
 
+    hasSymbolVisual = true;
+
+    // Do it self.
+    ignoreStyleOnData = true;
+
     /**
      * Init a tree data structure from data in option series
      * @param  option  the object used to config echarts view
diff --git a/src/chart/tree/TreeView.ts b/src/chart/tree/TreeView.ts
index 580510a..2d16448 100644
--- a/src/chart/tree/TreeView.ts
+++ b/src/chart/tree/TreeView.ts
@@ -224,6 +224,9 @@ class TreeView extends ChartView {
                     symbolEl && removeNode(oldData, oldIdx, symbolEl, group, seriesModel, seriesScope);
                     return;
                 }
+                if (symbolEl) {
+                    graphic.clearStates(symbolEl);
+                }
                 // Update node and edge
                 updateNode(data, newIdx, symbolEl, group, seriesModel, seriesScope);
             })
diff --git a/src/chart/map/mapVisual.ts b/src/chart/tree/treeVisual.ts
similarity index 59%
rename from src/chart/map/mapVisual.ts
rename to src/chart/tree/treeVisual.ts
index 031d40c..b4f9332 100644
--- a/src/chart/map/mapVisual.ts
+++ b/src/chart/tree/treeVisual.ts
@@ -1,7 +1,3 @@
-import GlobalModel from '../../model/Global';
-import MapSeries from './MapSeries';
-import { ZRColor } from '../../util/types';
-
 /*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
@@ -21,19 +17,21 @@ import { ZRColor } from '../../util/types';
 * under the License.
 */
 
+import GlobalModel from '../../model/Global';
+import TreeSeriesModel, { TreeSeriesNodeItemOption } from './TreeSeries';
+import { extend } from 'zrender/src/core/util';
 
 export default function (ecModel: GlobalModel) {
-    ecModel.eachSeriesByType('map', function (seriesModel: MapSeries) {
-        const colorList = seriesModel.get('color') as ZRColor[];
-        const itemStyleModel = seriesModel.getModel('itemStyle');
-
-        const areaColor = itemStyleModel.get('areaColor');
-        const color = itemStyleModel.get('color')
-            || colorList[seriesModel.seriesIndex % colorList.length];
 
-        seriesModel.getData().setVisual({
-            'areaColor': areaColor,
-            'color': color
+    ecModel.eachSeriesByType('tree', function (seriesModel: TreeSeriesModel) {
+        const data = seriesModel.getData();
+        const tree = data.tree;
+        tree.eachNode(function (node) {
+            const model = node.getModel<TreeSeriesNodeItemOption>();
+            // TODO Optimize
+            const style = model.getModel('itemStyle').getItemStyle();
+            const existsStyle = data.ensureUniqueItemVisual(node.dataIndex, 'style');
+            extend(existsStyle, style);
         });
     });
-}
\ No newline at end of file
+}
diff --git a/src/chart/treemap/TreemapView.ts b/src/chart/treemap/TreemapView.ts
index 5ecb9a8..8111b91 100644
--- a/src/chart/treemap/TreemapView.ts
+++ b/src/chart/treemap/TreemapView.ts
@@ -864,7 +864,7 @@ function renderNode(
         }
         else {
             bg.invisible = false;
-            const visualBorderColor = thisNode.getVisual('borderColor', true);
+            const visualBorderColor = thisNode.getVisual('style').stroke;
             const emphasisBorderColor = itemStyleEmphasisModel.get('borderColor');
             const normalStyle = getItemStyleNormal(itemStyleNormalModel);
             normalStyle.fill = visualBorderColor;
@@ -916,7 +916,8 @@ function renderNode(
         }
         else {
             content.invisible = false;
-            const visualColor = thisNode.getVisual('color', true);
+            // TODO. Optimize.
+            const visualColor = thisNode.getVisual('style').fill;
             const normalStyle = getItemStyleNormal(itemStyleNormalModel);
             normalStyle.fill = visualColor;
             const emphasisStyle = getItemStyleEmphasis(itemStyleEmphasisModel);
diff --git a/src/chart/treemap/treemapVisual.ts b/src/chart/treemap/treemapVisual.ts
index f26909d..0ff4b8e 100644
--- a/src/chart/treemap/treemapVisual.ts
+++ b/src/chart/treemap/treemapVisual.ts
@@ -79,6 +79,7 @@ function travelTree(
 ) {
     const nodeModel = node.getModel<TreemapSeriesNodeItemOption>();
     const nodeLayout = node.getLayout();
+    const data = node.hostTree.data;
 
     // Optimize
     if (!nodeLayout || nodeLayout.invisible || !nodeLayout.isInView) {
@@ -91,6 +92,7 @@ function travelTree(
         nodeItemStyleModel, designatedVisual, levelItemStyle, seriesItemStyleModel
     );
 
+    const existsStyle = data.ensureUniqueItemVisual(node.dataIndex, 'style');
     // calculate border color
     let borderColor = nodeItemStyleModel.get('borderColor');
     const borderColorSaturation = nodeItemStyleModel.get('borderColorSaturation');
@@ -100,13 +102,13 @@ function travelTree(
         thisNodeColor = calculateColor(visuals);
         borderColor = calculateBorderColor(borderColorSaturation, thisNodeColor);
     }
-    node.setVisual('borderColor', borderColor);
+    existsStyle.stroke = borderColor;
 
     const viewChildren = node.viewChildren;
     if (!viewChildren || !viewChildren.length) {
         thisNodeColor = calculateColor(visuals);
         // Apply visual to this node.
-        node.setVisual('color', thisNodeColor);
+        existsStyle.fill = thisNodeColor;
     }
     else {
         const mapping = buildVisualMapping(
diff --git a/src/component/axis/AngleAxisView.ts b/src/component/axis/AngleAxisView.ts
index 3a95368..48f1eea 100644
--- a/src/component/axis/AngleAxisView.ts
+++ b/src/component/axis/AngleAxisView.ts
@@ -163,7 +163,6 @@ const angelAxisElementsBuilders: Record<typeof elementList[number], AngleAxisEle
                 silent: true
             });
         }
-        shape.style.fill = null;
         group.add(shape);
     },
 
diff --git a/src/component/axis/AxisBuilder.ts b/src/component/axis/AxisBuilder.ts
index 940b6ac..ed95b41 100644
--- a/src/component/axis/AxisBuilder.ts
+++ b/src/component/axis/AxisBuilder.ts
@@ -635,6 +635,7 @@ function createTicks(
             },
             style: tickLineStyle,
             z2: 2,
+            autoBatch: true,
             silent: true
         });
         tickEl.anid = anidPrefix + '_' + ticksCoords[i].tickValue;
diff --git a/src/component/axis/CartesianAxisView.ts b/src/component/axis/CartesianAxisView.ts
index 5c677fa..711b487 100644
--- a/src/component/axis/CartesianAxisView.ts
+++ b/src/component/axis/CartesianAxisView.ts
@@ -141,6 +141,7 @@ const axisElementBuilders: Record<typeof selfBuilderAttrs[number], AxisElementBu
             axisGroup.add(new graphic.Line({
                 anid: tickValue != null ? 'line_' + ticksCoords[i].tickValue : null,
                 subPixelOptimize: true,
+                autoBatch: true,
                 shape: {
                     x1: p1[0],
                     y1: p1[1],
@@ -194,6 +195,7 @@ const axisElementBuilders: Record<typeof selfBuilderAttrs[number], AxisElementBu
                 axisGroup.add(new graphic.Line({
                     anid: 'minor_line_' + minorTicksCoords[i][k].tickValue,
                     subPixelOptimize: true,
+                    autoBatch: true,
                     shape: {
                         x1: p1[0],
                         y1: p1[1],
diff --git a/src/component/axis/axisSplitHelper.ts b/src/component/axis/axisSplitHelper.ts
index 6d92f92..83af2d3 100644
--- a/src/component/axis/axisSplitHelper.ts
+++ b/src/component/axis/axisSplitHelper.ts
@@ -117,6 +117,7 @@ export function rectCoordAxisBuildSplitArea(
             style: zrUtil.defaults({
                 fill: areaColors[colorIndex]
             }, areaStyle),
+            autoBatch: true,
             silent: true
         }));
 
diff --git a/src/component/brush/brushAction.ts b/src/component/brush/brushAction.ts
index bb407f0..86c09d0 100644
--- a/src/component/brush/brushAction.ts
+++ b/src/component/brush/brushAction.ts
@@ -28,7 +28,7 @@ interface BrushPayload extends Payload {
 }
 
 echarts.registerAction(
-    {type: 'brush', event: 'brush' /*, update: 'updateView' */},
+    {type: 'brush', event: 'brush', update: 'updateVisual' },
     function (payload: BrushPayload, ecModel: GlobalModel) {
         ecModel.eachComponent(
             {mainType: 'brush', query: payload},
diff --git a/src/component/helper/MapDraw.ts b/src/component/helper/MapDraw.ts
index 21ce559..11d1a1d 100644
--- a/src/component/helper/MapDraw.ts
+++ b/src/component/helper/MapDraw.ts
@@ -141,9 +141,9 @@ class MapDraw {
 
         regionsGroup.removeAll();
 
-        const itemStyleAccessPath = ['itemStyle'] as const;
+        const itemStyleAccessPath = 'itemStyle';
         const hoverItemStyleAccessPath = ['emphasis', 'itemStyle'] as const;
-        const labelAccessPath = ['label'] as const;
+        const labelAccessPath = 'label';
         const hoverLabelAccessPath = ['emphasis', 'label'] as const;
         const nameMap = zrUtil.createHashMap<RegionsGroup>();
 
@@ -170,6 +170,9 @@ class MapDraw {
             const itemStyleModel = regionModel.getModel(itemStyleAccessPath);
             // @ts-ignore FIXME:TS fix the "compatible with each other"?
             const hoverItemStyleModel = regionModel.getModel(hoverItemStyleAccessPath);
+
+            // NOTE: DONT use 'style' in visual when drawing map.
+            // This component is used for drawing underlying map for both geo component and map series.
             const itemStyle = getFixedItemStyle(itemStyleModel);
             const hoverItemStyle = getFixedItemStyle(hoverItemStyleModel);
 
@@ -186,9 +189,10 @@ class MapDraw {
                 // But visual color of series is used in symbol drawing
                 //
                 // Visual color for each series is for the symbol draw
-                const visualColor = data.getItemVisual(dataIdx, 'color', true);
-                if (visualColor) {
-                    itemStyle.fill = visualColor;
+                const style = data.getItemVisual(dataIdx, 'style');
+                const globalStyle = data.getVisual('style');
+                if (style !== globalStyle) {
+                    itemStyle.fill = style.fill;
                 }
             }
 
diff --git a/src/component/legend/LegendView.ts b/src/component/legend/LegendView.ts
index 6783978..ecff71e 100644
--- a/src/component/legend/LegendView.ts
+++ b/src/component/legend/LegendView.ts
@@ -28,16 +28,18 @@ import LegendModel, { LegendOption, LegendSelectorButtonOption, LegendTooltipFor
 import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../ExtensionAPI';
 import {
-    ColorString,
     ZRTextAlign,
     ZRColor,
     ItemStyleOption,
     ZRRectLike,
     ECElement,
-    CommonTooltipOption
+    CommonTooltipOption,
+    ColorString
 } from '../../util/types';
 import Model from '../../model/Model';
 import Displayable from 'zrender/src/graphic/Displayable';
+import { PathStyleProps } from 'zrender/src/graphic/Path';
+import { parse, stringify } from 'zrender/src/tool/color';
 
 const curry = zrUtil.curry;
 const each = zrUtil.each;
@@ -192,20 +194,9 @@ class LegendView extends ComponentView {
             // Legend to control series.
             if (seriesModel) {
                 const data = seriesModel.getData();
-                let color = data.getVisual('color');
-                let borderColor = data.getVisual('borderColor');
-
-                // If color is a callback function
-                if (typeof color === 'function') {
-                    // Use the first data
-                    color = color(seriesModel.getDataParams(0));
-                }
-
-                 // If borderColor is a callback function
-                if (typeof borderColor === 'function') {
-                    // Use the first data
-                    borderColor = borderColor(seriesModel.getDataParams(0));
-                }
+                const style = data.getVisual('style');
+                const color = style.fill;
+                const borderColor = style.stroke;
 
                 // Using rect symbol defaultly
                 const legendSymbolType = data.getVisual('legendSymbol') || 'roundRect';
@@ -241,8 +232,17 @@ class LegendView extends ComponentView {
 
                         const idx = provider.indexOfName(name);
 
-                        const color = provider.getItemVisual(idx, 'color');
-                        const borderColor = provider.getItemVisual(idx, 'borderColor');
+                        const style = provider.getItemVisual(idx, 'style') as PathStyleProps;
+                        const borderColor = style.stroke;
+                        let color = style.fill;
+                        const colorArr = parse(style.fill as ColorString);
+                        // Color may be set to transparent in visualMap when data is out of range.
+                        // Do not show nothing.
+                        if (colorArr && colorArr[3] === 0) {
+                            colorArr[3] = 0.2;
+                            // TODO color is set to 0, 0, 0, 0. Should show correct RGBA
+                            color = stringify(colorArr, 'rgba');
+                        }
 
                         const legendSymbolType = 'roundRect';
 
@@ -329,8 +329,8 @@ class LegendView extends ComponentView {
         legendSymbolType: string,
         symbolType: string,
         itemAlign: LegendOption['align'],
-        color: ColorString,
-        borderColor: ColorString,
+        color: ZRColor,
+        borderColor: ZRColor,
         selectMode: LegendOption['selectedMode']
     ) {
         const itemWidth = legendModel.get('itemWidth');
diff --git a/src/component/marker/MarkAreaView.ts b/src/component/marker/MarkAreaView.ts
index da9c27e..3be84b9 100644
--- a/src/component/marker/MarkAreaView.ts
+++ b/src/component/marker/MarkAreaView.ts
@@ -26,7 +26,7 @@ import * as graphic from '../../util/graphic';
 import * as markerHelper from './markerHelper';
 import MarkerView from './MarkerView';
 import { retrieve, mergeAll, map, defaults, curry, filter, HashMap } from 'zrender/src/core/util';
-import { ScaleDataValue, ParsedValue } from '../../util/types';
+import { ScaleDataValue, ParsedValue, ZRColor } from '../../util/types';
 import { CoordinateSystem, isCoordinateSystemType } from '../../coord/CoordinateSystem';
 import MarkAreaModel, { MarkArea2DDataItemOption } from './MarkAreaModel';
 import SeriesModel from '../../model/Series';
@@ -37,6 +37,7 @@ import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../ExtensionAPI';
 import MarkerModel from './MarkerModel';
 import { makeInner } from '../../util/model';
+import { getVisualFromData } from '../../visual/helper';
 
 interface MarkAreaDrawGroup {
     group: graphic.Group
@@ -244,10 +245,19 @@ class MarkAreaView extends MarkerView {
                 return getSingleMarkerEndPoint(areaData, idx, dim, seriesModel, api);
             }));
 
+            const style = areaData.getItemModel<MarkAreaMergedItemOption>(idx).getModel('itemStyle').getItemStyle();
+            const color = getVisualFromData(seriesData, 'color') as ZRColor;
+            if (!style.fill) {
+                style.fill = color;
+                if (typeof style.fill === 'string') {
+                    style.fill = colorUtil.modifyAlpha(style.fill, 0.4);
+                }
+            }
+            if (!style.stroke) {
+                style.stroke = color;
+            }
             // Visual
-            areaData.setItemVisual(idx, {
-                color: seriesData.getVisual('color')
-            });
+            areaData.setItemVisual(idx, 'style', style);
         });
 
 
@@ -263,6 +273,7 @@ class MarkAreaView extends MarkerView {
             })
             .update(function (newIdx, oldIdx) {
                 const polygon = inner(polygonGroup).data.getItemGraphicEl(oldIdx) as graphic.Polygon;
+                graphic.clearStates(polygon);
                 graphic.updateProps(polygon, {
                     shape: {
                         points: areaData.getItemLayout(newIdx)
@@ -281,16 +292,8 @@ class MarkAreaView extends MarkerView {
             const itemModel = areaData.getItemModel<MarkAreaMergedItemOption>(idx);
             const labelModel = itemModel.getModel('label');
             const labelHoverModel = itemModel.getModel(['emphasis', 'label']);
-            const color = areaData.getItemVisual(idx, 'color');
-            polygon.useStyle(
-                defaults(
-                    itemModel.getModel('itemStyle').getItemStyle(),
-                    {
-                        fill: colorUtil.modifyAlpha(color, 0.4),
-                        stroke: color
-                    }
-                )
-            );
+            const style = areaData.getItemVisual(idx, 'style');
+            polygon.useStyle(areaData.getItemVisual(idx, 'style'));
 
             graphic.setLabelStyle(
                 polygon, labelModel, labelHoverModel,
@@ -298,7 +301,8 @@ class MarkAreaView extends MarkerView {
                     labelFetcher: maModel,
                     labelDataIndex: idx,
                     defaultText: areaData.getName(idx) || '',
-                    autoColor: color
+                    autoColor: typeof style.fill === 'string'
+                        ? colorUtil.modifyAlpha(style.fill, 1) : '#000'
                 }
             );
 
diff --git a/src/component/marker/MarkLineView.ts b/src/component/marker/MarkLineView.ts
index 0254bab..cf0b933 100644
--- a/src/component/marker/MarkLineView.ts
+++ b/src/component/marker/MarkLineView.ts
@@ -25,7 +25,7 @@ import MarkerView from './MarkerView';
 import {getStackedDimension} from '../../data/helper/dataStackHelper';
 import { CoordinateSystem, isCoordinateSystemType } from '../../coord/CoordinateSystem';
 import MarkLineModel, { MarkLine2DDataItemOption, MarkLineOption } from './MarkLineModel';
-import { ScaleDataValue } from '../../util/types';
+import { ScaleDataValue, ColorString } from '../../util/types';
 import SeriesModel from '../../model/Series';
 import { __DEV__ } from '../../config';
 import { getECData } from '../../util/graphic';
@@ -48,6 +48,8 @@ import {
 } from 'zrender/src/core/util';
 import ComponentView from '../../view/Component';
 import { makeInner } from '../../util/model';
+import { LineDataVisual } from '../../visual/commonVisualTypes';
+import { getItemVisualFromData, getVisualFromData } from '../../visual/helper';
 
 // Item option for configuring line and each end of symbol.
 // Line option. be merged from configuration of two ends.
@@ -308,7 +310,7 @@ class MarkLineView extends MarkerView {
 
         const fromData = mlData.from;
         const toData = mlData.to;
-        const lineData = mlData.line;
+        const lineData = mlData.line as List<MarkLineModel, LineDataVisual>;
 
         inner(mlModel).from = fromData;
         inner(mlModel).to = toData;
@@ -332,20 +334,26 @@ class MarkLineView extends MarkerView {
 
         // Update visual and layout of line
         lineData.each(function (idx) {
-            const lineColor = lineData.getItemModel<MarkLineMergedItemOption>(idx).get(['lineStyle', 'color']);
-            lineData.setItemVisual(idx, {
-                color: lineColor || fromData.getItemVisual(idx, 'color')
-            });
+            const lineStyle = lineData.getItemModel<MarkLineMergedItemOption>(idx)
+                .getModel('lineStyle').getLineStyle();
+            // lineData.setItemVisual(idx, {
+            //     color: lineColor || fromData.getItemVisual(idx, 'color')
+            // });
             lineData.setItemLayout(idx, [
                 fromData.getItemLayout(idx),
                 toData.getItemLayout(idx)
             ]);
 
+            if (lineStyle.stroke == null) {
+                lineStyle.stroke = getItemVisualFromData(fromData, idx, 'color') as ColorString;
+            }
+
             lineData.setItemVisual(idx, {
-                'fromSymbolSize': fromData.getItemVisual(idx, 'symbolSize'),
-                'fromSymbol': fromData.getItemVisual(idx, 'symbol'),
-                'toSymbolSize': toData.getItemVisual(idx, 'symbolSize'),
-                'toSymbol': toData.getItemVisual(idx, 'symbol')
+                fromSymbolSize: fromData.getItemVisual(idx, 'symbolSize') as number,
+                fromSymbol: fromData.getItemVisual(idx, 'symbol'),
+                toSymbolSize: toData.getItemVisual(idx, 'symbolSize') as number,
+                toSymbol: toData.getItemVisual(idx, 'symbol'),
+                style: lineStyle
             });
         });
 
@@ -370,10 +378,15 @@ class MarkLineView extends MarkerView {
                 data, idx, isFrom, seriesModel, api
             );
 
+            const style = itemModel.getModel('itemStyle').getItemStyle();
+            if (style.fill == null) {
+                style.fill = getVisualFromData(seriesData, 'color') as ColorString;
+            }
+
             data.setItemVisual(idx, {
                 symbolSize: itemModel.get('symbolSize') || (symbolSize as number[])[isFrom ? 0 : 1],
                 symbol: itemModel.get('symbol', true) || (symbolType as string[])[isFrom ? 0 : 1],
-                color: itemModel.get(['itemStyle', 'color']) || seriesData.getVisual('color')
+                style
             });
         }
 
diff --git a/src/component/marker/MarkPointView.ts b/src/component/marker/MarkPointView.ts
index af65086..370d05f 100644
--- a/src/component/marker/MarkPointView.ts
+++ b/src/component/marker/MarkPointView.ts
@@ -32,6 +32,8 @@ import MarkerModel from './MarkerModel';
 import ExtensionAPI from '../../ExtensionAPI';
 import { HashMap, isFunction, map, defaults, filter, curry } from 'zrender/src/core/util';
 import { getECData } from '../../util/graphic';
+import { getVisualFromData } from '../../visual/helper';
+import { ZRColor } from '../../util/types';
 
 function updateMarkerLayout(
     mpData: List<MarkPointModel>,
@@ -131,11 +133,16 @@ class MarkPointView extends MarkerView {
                 }
             }
 
+            const style = itemModel.getModel('itemStyle').getItemStyle();
+            const color = getVisualFromData(seriesData, 'color') as ZRColor;
+            if (!style.fill) {
+                style.fill = color;
+            }
+
             mpData.setItemVisual(idx, {
                 symbol: symbol,
                 symbolSize: symbolSize,
-                color: itemModel.get(['itemStyle', 'color'])
-                    || seriesData.getVisual('color')
+                style
             });
         });
 
diff --git a/src/component/toolbox/ToolboxView.ts b/src/component/toolbox/ToolboxView.ts
index daf8328..4ecee54 100644
--- a/src/component/toolbox/ToolboxView.ts
+++ b/src/component/toolbox/ToolboxView.ts
@@ -291,7 +291,7 @@ class ToolboxView extends ComponentView {
             const textContent = icon.getTextContent();
             const emphasisTextState = textContent && textContent.states.emphasis;
             // May be background element
-            if (emphasisTextState && titleText) {
+            if (emphasisTextState && !zrUtil.isFunction(emphasisTextState) && titleText) {
                 const emphasisTextStyle = emphasisTextState.style || (emphasisTextState.style = {});
                 const rect = textContain.getBoundingRect(
                     titleText, ZRText.makeFont(emphasisTextStyle)
diff --git a/src/component/visualMap/visualEncoding.ts b/src/component/visualMap/visualEncoding.ts
index 90f9d13..e21ff38 100644
--- a/src/component/visualMap/visualEncoding.ts
+++ b/src/component/visualMap/visualEncoding.ts
@@ -24,6 +24,7 @@ import VisualMapping from '../../visual/VisualMapping';
 import VisualMapModel, { VisualMeta } from './VisualMapModel';
 import { StageHandlerProgressExecutor, BuiltinVisualProperty, ParsedValue } from '../../util/types';
 import SeriesModel from '../../model/Series';
+import { getVisualFromData } from '../../visual/helper';
 
 const VISUAL_PRIORITY = echarts.PRIORITY.VISUAL.COMPONENT;
 
@@ -94,7 +95,7 @@ function getColorVisual(
     const mappings = visualMapModel.targetVisuals[valueState];
     const visualTypes = VisualMapping.prepareVisualTypes(mappings);
     const resultVisual: Partial<Record<BuiltinVisualProperty, any>> = {
-        color: seriesModel.getData().getVisual('color') // default color.
+        color: getVisualFromData(seriesModel.getData(), 'color') // default color.
     };
 
     for (let i = 0, len = visualTypes.length; i < len; i++) {
diff --git a/src/data/Graph.ts b/src/data/Graph.ts
index 98633d0..6453c21 100644
--- a/src/data/Graph.ts
+++ b/src/data/Graph.ts
@@ -422,34 +422,34 @@ function createGraphDataProxyMixin<Host extends GraphEdge | GraphNode>(
         /**
          * @param Default 'value'. can be 'a', 'b', 'c', 'd', 'e'.
          */
-        getValue: function (this: Host, dimension?: DimensionLoose): ParsedValue {
+        getValue(this: Host, dimension?: DimensionLoose): ParsedValue {
             const data = this[hostName][dataName];
             return data.get(data.getDimension(dimension || 'value'), this.dataIndex);
         },
-
-        setVisual: function (this: Host, key: string | Dictionary<any>, value?: any) {
+        // TODO: TYPE stricter type.
+        setVisual(this: Host, key: string | Dictionary<any>, value?: any) {
             this.dataIndex >= 0
-                && this[hostName][dataName].setItemVisual(this.dataIndex, key as string, value);
+                && this[hostName][dataName].setItemVisual(this.dataIndex, key as any, value);
         },
 
-        getVisual: function (this: Host, key: string, ignoreParent?: boolean) {
-            return this[hostName][dataName].getItemVisual(this.dataIndex, key, ignoreParent);
+        getVisual(this: Host, key: string) {
+            return this[hostName][dataName].getItemVisual(this.dataIndex, key as any);
         },
 
-        setLayout: function (this: Host, layout: any, merge?: boolean) {
+        setLayout(this: Host, layout: any, merge?: boolean) {
             this.dataIndex >= 0
                 && this[hostName][dataName].setItemLayout(this.dataIndex, layout, merge);
         },
 
-        getLayout: function (this: Host) {
+        getLayout(this: Host) {
             return this[hostName][dataName].getItemLayout(this.dataIndex);
         },
 
-        getGraphicEl: function (this: Host): Element {
+        getGraphicEl(this: Host): Element {
             return this[hostName][dataName].getItemGraphicEl(this.dataIndex);
         },
 
-        getRawIndex: function (this: Host) {
+        getRawIndex(this: Host) {
             return this[hostName][dataName].getRawIndex(this.dataIndex);
         }
     };
diff --git a/src/data/List.ts b/src/data/List.ts
index 9297b5e..16777a6 100644
--- a/src/data/List.ts
+++ b/src/data/List.ts
@@ -39,9 +39,11 @@ import {
 } from '../util/types';
 import {parseDate} from '../util/number';
 import {isDataItemOption} from '../util/model';
+import { getECData } from '../util/graphic';
+import { PathStyleProps } from 'zrender/src/graphic/Path';
 import type Graph from './Graph';
 import type Tree from './Tree';
-import { getECData } from '../util/graphic';
+import type { VisualMeta } from '../component/visualMap/VisualMapModel';
 
 
 const isObject = zrUtil.isObject;
@@ -77,7 +79,6 @@ type DataTypedArrayConstructor = typeof Uint32Array | typeof Int32Array | typeof
 type DataArrayLikeConstructor = typeof Array | DataTypedArrayConstructor;
 
 
-
 type DimValueGetter = (
     this: List,
     dataItem: any,
@@ -112,7 +113,6 @@ type MapCb2<Ctx> = (this: CtxOrList<Ctx>, x: ParsedValue, y: ParsedValue, idx: n
 type MapCb<Ctx> = (this: CtxOrList<Ctx>, ...args: any) => ParsedValue | ParsedValue[];
 
 
-
 const TRANSFERABLE_PROPERTIES = [
     'hasItemOption', '_nameList', '_idList', '_invertedIndicesMap',
     '_rawData', '_chunkSize', '_chunkCount', '_dimValueGetter',
@@ -122,8 +122,29 @@ const CLONE_PROPERTIES = [
     '_extent', '_approximateExtent', '_rawExtent'
 ];
 
+export interface DefaultDataVisual {
+    style: PathStyleProps
+    // Brush type determined which prop should be set with encoded color.
+    // It's only available on the global visual. Use getVisual('brushType') to access it.
+    // It will be set in visual/style.ts module in the first priority.
+    brushType: 'fill' | 'stroke'
+
+    symbol?: string
+    symbolSize?: number | number[]
+    symbolKeepAspect?: boolean
+
+    liftZ?: number
+    // For legend.
+    legendSymbol?: string
+
+    // visualMap will inject visualMeta data
+    visualMeta?: VisualMeta[]
+}
 
-class List<HostModel extends Model = Model> {
+class List<
+    HostModel extends Model = Model,
+    Visual extends DefaultDataVisual = DefaultDataVisual
+> {
 
     readonly type = 'list';
 
@@ -168,10 +189,6 @@ class List<HostModel extends Model = Model> {
     // Item visual properties after visual coding
     private _itemVisuals: Dictionary<any>[] = [];
 
-    // Key: visual type, Value: boolean
-    // @readonly
-    hasItemVisual: Dictionary<boolean> = {};
-
     // Item layout properties after layout
     private _itemLayouts: any[] = [];
 
@@ -1596,8 +1613,8 @@ class List<HostModel extends Model = Model> {
     /**
      * Get visual property.
      */
-    getVisual(key: string): any {
-        const visual = this._visual;
+    getVisual<K extends keyof Visual>(key: K): Visual[K] {
+        const visual = this._visual as Visual;
         return visual && visual[key];
     }
 
@@ -1610,19 +1627,94 @@ class List<HostModel extends Model = Model> {
      *      'color': color
      *  });
      */
-    setVisual(key: string, val: any): void;
-    setVisual(kvObj: Dictionary<any>): void;
-    setVisual(key: string | Dictionary<any>, val?: any): void {
-        if (isObject(key)) {
-            for (const name in key) {
-                if (key.hasOwnProperty(name)) {
-                    this.setVisual(name, key[name]);
-                }
+    setVisual<K extends keyof Visual>(key: K, val: Visual[K]): void;
+    setVisual(kvObj: Partial<Visual>): void;
+    setVisual(kvObj: string | Partial<Visual>, val?: any): void {
+        this._visual = this._visual || {};
+        if (isObject(kvObj)) {
+            zrUtil.extend(this._visual, kvObj);
+        }
+        else {
+            this._visual[kvObj as string] = val;
+        }
+    }
+
+    /**
+     * Get visual property of single data item
+     */
+    // eslint-disable-next-line
+    getItemVisual<K extends keyof Visual>(idx: number, key: K): Visual[K] {
+        const itemVisual = this._itemVisuals[idx] as Visual;
+        const val = itemVisual && itemVisual[key];
+        if (val == null) {
+            // Use global visual property
+            return this.getVisual(key);
+        }
+        return val;
+    }
+
+    /**
+     * Make sure itemVisual property is unique
+     */
+    // TODO: use key to save visual to reduce memory.
+    // eslint-disable-next-line
+    ensureUniqueItemVisual<K extends keyof Visual>(idx: number, key: K): Visual[K] {
+        const itemVisuals = this._itemVisuals;
+        let itemVisual = itemVisuals[idx] as Visual;
+        if (!itemVisual) {
+            itemVisual = itemVisuals[idx] = {} as Visual;
+        }
+        let val = itemVisual[key];
+        if (!val) {
+            val = this.getVisual(key);
+
+            // TODO Performance?
+            if (zrUtil.isArray(val)) {
+                val = val.slice() as unknown as Visual[K];
             }
-            return;
+            else if (isObject(val)) {
+                val = zrUtil.extend({}, val);
+            }
+
+            itemVisual[key] = val;
         }
-        this._visual = this._visual || {};
-        this._visual[key] = val;
+        return val;
+    }
+    /**
+     * Set visual property of single data item
+     *
+     * @param {number} idx
+     * @param {string|Object} key
+     * @param {*} [value]
+     *
+     * @example
+     *  setItemVisual(0, 'color', color);
+     *  setItemVisual(0, {
+     *      'color': color
+     *  });
+     */
+    // eslint-disable-next-line
+    setItemVisual<K extends keyof Visual>(idx: number, key: K, value: Visual[K]): void;
+    setItemVisual(idx: number, kvObject: Partial<Visual>): void;
+    // eslint-disable-next-line
+    setItemVisual<K extends keyof Visual>(idx: number, key: K | Partial<Visual>, value?: Visual[K]): void {
+        const itemVisual = this._itemVisuals[idx] || {};
+        this._itemVisuals[idx] = itemVisual;
+
+        if (isObject(key)) {
+            zrUtil.extend(itemVisual, key);
+        }
+        else {
+            itemVisual[key as string] = value;
+        }
+    }
+
+    /**
+     * Clear itemVisuals and list visual.
+     */
+    clearAllVisual(): void {
+        this._visual = {};
+        this._itemVisuals = [];
     }
 
     /**
@@ -1677,61 +1769,6 @@ class List<HostModel extends Model = Model> {
     }
 
     /**
-     * Get visual property of single data item
-     */
-    getItemVisual(idx: number, key: string, ignoreParent?: boolean): any {
-        const itemVisual = this._itemVisuals[idx];
-        const val = itemVisual && itemVisual[key];
-        if (val == null && !ignoreParent) {
-            // Use global visual property
-            return this.getVisual(key);
-        }
-        return val;
-    }
-
-    /**
-     * Set visual property of single data item
-     *
-     * @param {number} idx
-     * @param {string|Object} key
-     * @param {*} [value]
-     *
-     * @example
-     *  setItemVisual(0, 'color', color);
-     *  setItemVisual(0, {
-     *      'color': color
-     *  });
-     */
-    setItemVisual(idx: number, key: string, value: any): void;
-    setItemVisual(idx: number, kvObject: Dictionary<any>): void;
-    setItemVisual(idx: number, key: string | Dictionary<any>, value?: any): void {
-        const itemVisual = this._itemVisuals[idx] || {};
-        const hasItemVisual = this.hasItemVisual;
-        this._itemVisuals[idx] = itemVisual;
-
-        if (isObject(key)) {
-            for (const name in key) {
-                if (key.hasOwnProperty(name)) {
-                    itemVisual[name] = key[name];
-                    hasItemVisual[name] = true;
-                }
-            }
-            return;
-        }
-        itemVisual[key] = value;
-        hasItemVisual[key] = true;
-    }
-
-    /**
-     * Clear itemVisuals and list visual.
-     */
-    clearAllVisual(): void {
-        this._visual = {};
-        this._itemVisuals = [];
-        this.hasItemVisual = {};
-    }
-
-    /**
      * Set graphic element relative to data. It can be set as null
      */
     setItemGraphicEl(idx: number, el: Element): void {
diff --git a/src/data/Tree.ts b/src/data/Tree.ts
index 255d757..8152ff9 100644
--- a/src/data/Tree.ts
+++ b/src/data/Tree.ts
@@ -239,18 +239,19 @@ export class TreeNode {
      *      'color': color
      *  });
      */
+    // TODO: TYPE
     setVisual(key: string, value: any): void
     setVisual(obj: Dictionary<any>): void
     setVisual(key: string | Dictionary<any>, value?: any) {
         this.dataIndex >= 0
-            && this.hostTree.data.setItemVisual(this.dataIndex, key as string, value);
+            && this.hostTree.data.setItemVisual(this.dataIndex, key as any, value);
     }
 
     /**
      * Get item visual
      */
-    getVisual(key: string, ignoreParent?: boolean): any {
-        return this.hostTree.data.getItemVisual(this.dataIndex, key, ignoreParent);
+    getVisual(key: string): any {
+        return this.hostTree.data.getItemVisual(this.dataIndex, key as any);
     }
 
     getRawIndex(): number {
diff --git a/src/echarts.ts b/src/echarts.ts
index ff4709e..6a4c646 100644
--- a/src/echarts.ts
+++ b/src/echarts.ts
@@ -40,7 +40,7 @@ import ChartView, {ChartViewConstructor} from './view/Chart';
 import * as graphic from './util/graphic';
 import * as modelUtil from './util/model';
 import {throttle} from './util/throttle';
-import seriesColor from './visual/seriesColor';
+import {seriesStyleTask, dataStyleTask} from './visual/style';
 import aria from './visual/aria';
 import loadingDefault from './loading/default';
 import Scheduler from './stream/Scheduler';
@@ -69,6 +69,8 @@ import IncrementalDisplayable from 'zrender/src/graphic/IncrementalDisplayable';
 
 // At least canvas renderer.
 import 'zrender/src/canvas/canvas';
+import { seriesSymbolTask, dataSymbolTask } from './visual/symbol';
+import { getVisualFromData, getItemVisualFromData } from './visual/helper';
 
 declare let global: any;
 type ModelFinder = modelUtil.ModelFinder;
@@ -97,6 +99,7 @@ const PRIORITY_VISUAL_GLOBAL = 2000;
 const PRIORITY_VISUAL_CHART = 3000;
 const PRIORITY_VISUAL_POST_CHART_LAYOUT = 3500;
 const PRIORITY_VISUAL_COMPONENT = 4000;
+const PRIORITY_VISUAL_CHART_DATA_CUSTOM = 4500;    // visual property in data
 // FIXME
 // necessary?
 const PRIORITY_VISUAL_BRUSH = 5000;
@@ -114,7 +117,8 @@ export const PRIORITY = {
         CHART: PRIORITY_VISUAL_CHART,
         POST_CHART_LAYOUT: PRIORITY_VISUAL_POST_CHART_LAYOUT,
         COMPONENT: PRIORITY_VISUAL_COMPONENT,
-        BRUSH: PRIORITY_VISUAL_BRUSH
+        BRUSH: PRIORITY_VISUAL_BRUSH,
+        CHART_ITEM: PRIORITY_VISUAL_CHART_DATA_CUSTOM
     }
 };
 
@@ -745,8 +749,8 @@ class ECharts extends Eventful {
             : null;
 
         return dataIndexInside != null
-            ? data.getItemVisual(dataIndexInside, visualType)
-            : data.getVisual(visualType);
+            ? getItemVisualFromData(data, dataIndexInside, visualType)
+            : getVisualFromData(data, visualType);
     }
 
     /**
@@ -1292,7 +1296,6 @@ class ECharts extends Eventful {
 
             updateTransform: function (this: ECharts, payload: Payload): void {
                 const ecModel = this._model;
-                const ecIns = this;
                 const api = this._api;
 
                 // update before setOption
@@ -1303,8 +1306,8 @@ class ECharts extends Eventful {
                 // ChartView.markUpdateMethod(payload, 'updateTransform');
 
                 const componentDirtyList = [];
-                ecModel.eachComponent(function (componentType, componentModel) {
-                    const componentView = ecIns.getViewOfComponentModel(componentModel);
+                ecModel.eachComponent((componentType, componentModel) => {
+                    const componentView = this.getViewOfComponentModel(componentModel);
                     if (componentView && componentView.__alive) {
                         if (componentView.updateTransform) {
                             const result = componentView.updateTransform(componentModel, ecModel, api, payload);
@@ -1317,8 +1320,8 @@ class ECharts extends Eventful {
                 });
 
                 const seriesDirtyMap = zrUtil.createHashMap();
-                ecModel.eachSeries(function (seriesModel) {
-                    const chartView = ecIns._chartsMap[seriesModel.__viewId];
+                ecModel.eachSeries((seriesModel) => {
+                    const chartView = this._chartsMap[seriesModel.__viewId];
                     if (chartView.updateTransform) {
                         const result = chartView.updateTransform(seriesModel, ecModel, api, payload);
                         result && result.update && seriesDirtyMap.set(seriesModel.uid, 1);
@@ -1337,7 +1340,7 @@ class ECharts extends Eventful {
 
                 // Currently, not call render of components. Geo render cost a lot.
                 // renderComponents(ecIns, ecModel, api, payload, componentDirtyList);
-                renderSeries(ecIns, ecModel, api, payload, seriesDirtyMap);
+                renderSeries(this, ecModel, api, payload, seriesDirtyMap);
 
                 performPostUpdateFuncs(ecModel, this._api);
             },
@@ -1363,25 +1366,40 @@ class ECharts extends Eventful {
             },
 
             updateVisual: function (this: ECharts, payload: Payload): void {
-                updateMethods.update.call(this, payload);
+                // updateMethods.update.call(this, payload);
 
-                // let ecModel = this._model;
+                const ecModel = this._model;
 
-                // // update before setOption
-                // if (!ecModel) {
-                //     return;
-                // }
+                // update before setOption
+                if (!ecModel) {
+                    return;
+                }
 
-                // ChartView.markUpdateMethod(payload, 'updateVisual');
+                // clear all visual
+                ecModel.eachSeries(function (seriesModel) {
+                    seriesModel.getData().clearAllVisual();
+                });
 
-                // clearColorPalette(ecModel);
+                // Perform visual
+                ChartView.markUpdateMethod(payload, 'updateVisual');
 
-                // // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline.
-                // this._scheduler.performVisualTasks(ecModel, payload, {visualType: 'visual', setDirty: true});
+                clearColorPalette(ecModel);
 
-                // render(this, this._model, this._api, payload);
+                // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline.
+                this._scheduler.performVisualTasks(ecModel, payload, {visualType: 'visual', setDirty: true});
 
-                // performPostUpdateFuncs(ecModel, this._api);
+                ecModel.eachComponent((componentType, componentModel) => {  // TODO componentType may be series.
+                    const componentView = this.getViewOfComponentModel(componentModel);
+                    componentView && componentView.__alive
+                        && componentView.updateVisual(componentModel, ecModel, this._api, payload);
+                });
+
+                ecModel.eachSeries((seriesModel) => {
+                    const chartView = this._chartsMap[seriesModel.__viewId];
+                    chartView.updateVisual(seriesModel, ecModel, this._api, payload);
+                });
+
+                performPostUpdateFuncs(ecModel, this._api);
             },
 
             updateLayout: function (this: ECharts, payload: Payload): void {
@@ -2323,7 +2341,13 @@ export function getMap(mapName: string) {
 
 
 
-registerVisual(PRIORITY_VISUAL_GLOBAL, seriesColor);
+// Buitlin global visual
+registerVisual(PRIORITY_VISUAL_GLOBAL, seriesStyleTask);
+registerVisual(PRIORITY_VISUAL_CHART_DATA_CUSTOM, dataStyleTask);
+
+registerVisual(PRIORITY_VISUAL_GLOBAL, seriesSymbolTask);
+registerVisual(PRIORITY_VISUAL_CHART_DATA_CUSTOM, dataSymbolTask);
+
 registerPreprocessor(backwardCompat);
 registerProcessor(PRIORITY_PROCESSOR_DATASTACK, dataStack);
 registerLoading('default', loadingDefault);
diff --git a/src/model/Model.ts b/src/model/Model.ts
index 7dd5e93..5f7d787 100644
--- a/src/model/Model.ts
+++ b/src/model/Model.ts
@@ -132,8 +132,8 @@ class Model<Opt extends ModelOption = ModelOption> {    // TODO: TYPE use unkown
         const option = this.option;
 
         let val = option == null ? option : option[key];
-        if (val == null) {
-            const parentModel = !ignoreParent && getParent(this, key as string);
+        if (val == null && !ignoreParent) {
+            const parentModel = getParent(this, key as string);
             if (parentModel) {
                 // FIXME:TS do not know how to make it works
                 val = parentModel.getShallow(key);
diff --git a/src/model/Series.ts b/src/model/Series.ts
index 9c898df..e370663 100644
--- a/src/model/Series.ts
+++ b/src/model/Series.ts
@@ -57,6 +57,7 @@ import Source from '../data/Source';
 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';
 
 const inner = modelUtil.makeInner<{
     data: List
@@ -131,14 +132,30 @@ class SeriesModel<Opt extends SeriesOption = SeriesOption> extends ComponentMode
     // Injected outside
     pipelineContext: PipelineContext;
 
+
+    // ---------------------------------------
+    // Props to tell echarts about how to do visual encoding.
+    // ---------------------------------------
     // legend visual provider to the legend component
     legendVisualProvider: LegendVisualProvider;
 
-    // Access path of color for visual
-    visualColorAccessPath: string[];
-
-    // Access path of borderColor for visual
-    visualBorderColorAccessPath: string[];
+    // Access path of style for visual
+    visualStyleAccessPath: string;
+    // Which property is treated as main color. Which can get from the palette.
+    visualColorBrushType: 'fill' | 'stroke';
+    // Style mapping rules.
+    visualStyleMapper: ReturnType<typeof makeStyleMapper>;
+    // If ignore style on data. It's only for global visual/style.ts
+    // Perhaps series it self will handle it.
+    ignoreStyleOnData: boolean;
+    // If use palette on each data.
+    useColorPaletteOnData: boolean;
+    // If do symbol visual encoding
+    hasSymbolVisual: boolean;
+    // Default symbol type.
+    defaultSymbol: string;
+    // Symbol provide to legend.
+    legendSymbol: string;
 
     readonly preventUsingHoverLayer: boolean;
 
@@ -146,8 +163,13 @@ class SeriesModel<Opt extends SeriesOption = SeriesOption> extends ComponentMode
         const proto = SeriesModel.prototype;
         proto.type = 'series.__base__';
         proto.seriesIndex = 0;
-        proto.visualColorAccessPath = ['itemStyle', 'color'];
-        proto.visualBorderColorAccessPath = ['itemStyle', 'borderColor'];
+        proto.useColorPaletteOnData = false;
+        proto.ignoreStyleOnData = false;
+        proto.hasSymbolVisual = false;
+        proto.defaultSymbol = 'circle';
+        // Make sure the values can be accessed!
+        proto.visualStyleAccessPath = 'itemStyle';
+        proto.visualColorBrushType = 'fill';
     })();
 
 
@@ -458,7 +480,8 @@ class SeriesModel<Opt extends SeriesOption = SeriesOption> extends ComponentMode
         const value = this.getRawValue(dataIndex) as any;
         const isValueArr = zrUtil.isArray(value);
 
-        const color = data.getItemVisual(dataIndex, 'color') as ZRColor;
+        const style = data.getItemVisual(dataIndex, 'style');
+        const color = style[this.visualColorBrushType];
         let colorStr: ColorString;
         if (zrUtil.isString(color)) {
             colorStr = color;
@@ -563,7 +586,6 @@ class SeriesModel<Opt extends SeriesOption = SeriesOption> extends ComponentMode
         return this.get('progressiveThreshold');
     }
 
-
     // /**
     //  * @see {module:echarts/stream/Scheduler}
     //  */
diff --git a/src/model/mixin/areaStyle.ts b/src/model/mixin/areaStyle.ts
index 309593a..2227dac 100644
--- a/src/model/mixin/areaStyle.ts
+++ b/src/model/mixin/areaStyle.ts
@@ -22,14 +22,15 @@ import Model from '../Model';
 import { AreaStyleOption } from '../../util/types';
 import { PathStyleProps } from 'zrender/src/graphic/Path';
 
-const getAreaStyle = makeStyleMapper([
+export const AREA_STYLE_KEY_MAP = [
     ['fill', 'color'],
     ['shadowBlur'],
     ['shadowOffsetX'],
     ['shadowOffsetY'],
     ['opacity'],
     ['shadowColor']
-]);
+];
+const getAreaStyle = makeStyleMapper(AREA_STYLE_KEY_MAP);
 
 type AreaStyleProps = Pick<PathStyleProps,
     'fill'
diff --git a/src/model/mixin/dataFormat.ts b/src/model/mixin/dataFormat.ts
index a0f2c72..475a419 100644
--- a/src/model/mixin/dataFormat.ts
+++ b/src/model/mixin/dataFormat.ts
@@ -20,7 +20,7 @@
 import {retrieveRawValue} from '../../data/helper/dataProvider';
 import {getTooltipMarker, formatTpl} from '../../util/format';
 import { getTooltipRenderMode } from '../../util/model';
-import { DataHost, DisplayState, TooltipRenderMode, CallbackDataParams } from '../../util/types';
+import { DataHost, DisplayState, TooltipRenderMode, CallbackDataParams, ColorString } from '../../util/types';
 import GlobalModel from '../Global';
 import Element from 'zrender/src/Element';
 
@@ -52,8 +52,9 @@ class DataFormatMixin {
         const rawDataIndex = data.getRawIndex(dataIndex);
         const name = data.getName(dataIndex);
         const itemOpt = data.getRawDataItem(dataIndex);
-        const color = data.getItemVisual(dataIndex, 'color');
-        const borderColor = data.getItemVisual(dataIndex, 'borderColor');
+        const style = data.getItemVisual(dataIndex, 'style');
+        const color = style && style.fill as ColorString;
+        const borderColor = style && style.stroke as ColorString;
         const tooltipModel = this.ecModel.getComponent('tooltip');
         // @ts-ignore FIXME:TooltipModel
         const renderModeOption = tooltipModel && tooltipModel.get('renderMode');
diff --git a/src/model/mixin/itemStyle.ts b/src/model/mixin/itemStyle.ts
index 15a7e80..c0c88e0 100644
--- a/src/model/mixin/itemStyle.ts
+++ b/src/model/mixin/itemStyle.ts
@@ -22,7 +22,7 @@ import Model from '../Model';
 import { ItemStyleOption } from '../../util/types';
 import { PathStyleProps } from 'zrender/src/graphic/Path';
 
-const getItemStyle = makeStyleMapper([
+export const ITEM_STYLE_KEY_MAP = [
     ['fill', 'color'],
     ['stroke', 'borderColor'],
     ['lineWidth', 'borderWidth'],
@@ -31,7 +31,9 @@ const getItemStyle = makeStyleMapper([
     ['shadowOffsetX'],
     ['shadowOffsetY'],
     ['shadowColor']
-]);
+];
+
+const getItemStyle = makeStyleMapper(ITEM_STYLE_KEY_MAP);
 
 type ItemStyleKeys = 'fill'
     | 'stroke'
diff --git a/src/model/mixin/lineStyle.ts b/src/model/mixin/lineStyle.ts
index 075c1a6..88512de 100644
--- a/src/model/mixin/lineStyle.ts
+++ b/src/model/mixin/lineStyle.ts
@@ -22,7 +22,7 @@ import Model from '../Model';
 import { LineStyleOption } from '../../util/types';
 import { PathStyleProps } from 'zrender/src/graphic/Path';
 
-const getLineStyle = makeStyleMapper([
+export const LINE_STYLE_KEY_MAP = [
     ['lineWidth', 'width'],
     ['stroke', 'color'],
     ['opacity'],
@@ -30,7 +30,9 @@ const getLineStyle = makeStyleMapper([
     ['shadowOffsetX'],
     ['shadowOffsetY'],
     ['shadowColor']
-]);
+];
+
+const getLineStyle = makeStyleMapper(LINE_STYLE_KEY_MAP);
 
 type LineStyleKeys = 'lineWidth'
     | 'stroke'
diff --git a/src/model/mixin/makeStyleMapper.ts b/src/model/mixin/makeStyleMapper.ts
index 0fced21..e08e42a 100644
--- a/src/model/mixin/makeStyleMapper.ts
+++ b/src/model/mixin/makeStyleMapper.ts
@@ -22,14 +22,18 @@
 import * as zrUtil from 'zrender/src/core/util';
 import Model from '../Model';
 import { Dictionary } from 'zrender/src/core/types';
+import { PathStyleProps } from 'zrender/src/graphic/Path';
 
-export default function (properties: readonly string[][]) {
+export default function (properties: readonly string[][], ignoreParent?: boolean) {
     // Normalize
     for (let i = 0; i < properties.length; i++) {
         if (!properties[i][1]) {
             properties[i][1] = properties[i][0];
         }
     }
+
+    ignoreParent = ignoreParent || false;
+
     return function (model: Model, excludes?: readonly string[], includes?: readonly string[]) {
         const style: Dictionary<any> = {};
         for (let i = 0; i < properties.length; i++) {
@@ -39,11 +43,12 @@ export default function (properties: readonly string[][]) {
             ) {
                 continue;
             }
-            const val = model.getShallow(propName);
+            const val = model.getShallow(propName, ignoreParent);
             if (val != null) {
                 style[properties[i][0]] = val;
             }
         }
-        return style;
+        // TODO Text or image?
+        return style as PathStyleProps;
     };
 }
\ No newline at end of file
diff --git a/src/util/graphic.ts b/src/util/graphic.ts
index 311f883..1b9540e 100644
--- a/src/util/graphic.ts
+++ b/src/util/graphic.ts
@@ -62,7 +62,16 @@ import {
 } from './types';
 import GlobalModel from '../model/Global';
 import { makeInner } from './model';
-import { isFunction, retrieve2, extend, keys, trim, isArrayLike, map, defaults } from 'zrender/src/core/util';
+import {
+    isFunction,
+    retrieve2,
+    extend,
+    keys,
+    trim,
+    isArrayLike,
+    map,
+    defaults
+} from 'zrender/src/core/util';
 
 
 const mathMax = Math.max;
@@ -97,7 +106,7 @@ type ExtendedProps = {
     __highByOuter: number
 
     __highDownSilentOnTouch: boolean
-    __highDownOnUpdate: (fromState: DisplayState, toState: DisplayState) => void
+    __onStateChange: (fromState: DisplayState, toState: DisplayState) => void
 
     __highDownDispatcher: boolean
 };
@@ -367,17 +376,24 @@ function singleEnterEmphasis(el: Element) {
         return;
     }
     const disp = el as Displayable;
-    const emphasisStyle = disp.states.emphasis.style;
-    const currentFill = disp.style && disp.style.fill;
-    const currentStroke = disp.style && disp.style.stroke;
+
+    let emphasisStyle;
+    let currentFill;
+    let currentStroke;
+    // state may be a function
+    if (!(typeof disp.states.emphasis === 'function')) {
+        emphasisStyle = disp.states.emphasis.style;
+        currentFill = disp.style && disp.style.fill;
+        currentStroke = disp.style && disp.style.stroke;
+    }
 
     el.useState('emphasis');
 
-    if (disp.style) { // Is not group
-        if (emphasisStyle && !hasFillOrStroke(emphasisStyle.fill)) {
+    if (emphasisStyle && (currentFill || currentStroke)) {
+        if (!hasFillOrStroke(emphasisStyle.fill)) {
             disp.style.fill = liftColor(currentFill);
         }
-        if (emphasisStyle && !hasFillOrStroke(emphasisStyle.stroke)) {
+        if (!hasFillOrStroke(emphasisStyle.stroke)) {
             disp.style.stroke = liftColor(currentStroke);
         }
         disp.z2 += Z2_EMPHASIS_LIFT;
@@ -393,29 +409,51 @@ function singleEnterEmphasis(el: Element) {
 
 function singleEnterNormal(el: Element) {
     el.clearStates();
-
     (el as ExtendedElement).__highlighted = false;
 }
 
-function traverseUpdate<T>(
+function updateElementState<T>(
     el: ExtendedElement,
     updater: (this: void, el: Element, commonParam?: T) => void,
     commonParam?: T
 ) {
-    // If root is group, also enter updater for `highDownOnUpdate`.
+    // If root is group, also enter updater for `onStateChange`.
     let fromState: DisplayState = NORMAL;
     let toState: DisplayState = NORMAL;
     let trigger;
-    // See the rule of `highDownOnUpdate` on `graphic.setAsHighDownDispatcher`.
+    // See the rule of `onStateChange` on `graphic.setAsHighDownDispatcher`.
     el.__highlighted && (fromState = EMPHASIS, trigger = true);
     updater(el, commonParam);
     el.__highlighted && (toState = EMPHASIS, trigger = true);
+    trigger && el.__onStateChange && el.__onStateChange(fromState, toState);
+}
 
-    el.isGroup && el.traverse(function (child) {
-        !child.isGroup && updater(child, commonParam);
+function traverseUpdateState<T>(
+    el: ExtendedElement,
+    updater: (this: void, el: Element, commonParam?: T) => void,
+    commonParam?: T
+) {
+    updateElementState(el, updater, commonParam);
+    el.isGroup && el.traverse(function (child: ExtendedElement) {
+        updateElementState(child, updater, commonParam);
     });
 
-    trigger && el.__highDownOnUpdate && el.__highDownOnUpdate(fromState, toState);
+}
+
+/**
+ * If we reuse elements when rerender.
+ * DONT forget to clearStates before we update the style and shape.
+ * Or we may update on the wrong state instead of normal state.
+ */
+export function clearStates(el: Element) {
+    if (el.isGroup) {
+        el.traverse(function (child) {
+            child.clearStates();
+        });
+    }
+    else {
+        el.clearStates();
+    }
 }
 
 /**
@@ -444,24 +482,24 @@ export function enterEmphasisWhenMouseOver(el: Element, e: ElementEvent) {
     !shouldSilent(el, e)
         // "emphasis" event highlight has higher priority than mouse highlight.
         && !(el as ExtendedElement).__highByOuter
-        && traverseUpdate((el as ExtendedElement), singleEnterEmphasis);
+        && traverseUpdateState((el as ExtendedElement), singleEnterEmphasis);
 }
 
 export function leaveEmphasisWhenMouseOut(el: Element, e: ElementEvent) {
     !shouldSilent(el, e)
         // "emphasis" event highlight has higher priority than mouse highlight.
         && !(el as ExtendedElement).__highByOuter
-        && traverseUpdate((el as ExtendedElement), singleEnterNormal);
+        && traverseUpdateState((el as ExtendedElement), singleEnterNormal);
 }
 
 export function enterEmphasis(el: Element, highlightDigit?: number) {
     (el as ExtendedElement).__highByOuter |= 1 << (highlightDigit || 0);
-    traverseUpdate((el as ExtendedElement), singleEnterEmphasis);
+    traverseUpdateState((el as ExtendedElement), singleEnterEmphasis);
 }
 
 export function leaveEmphasis(el: Element, highlightDigit?: number) {
     !((el as ExtendedElement).__highByOuter &= ~(1 << (highlightDigit || 0)))
-        && traverseUpdate((el as ExtendedElement), singleEnterNormal);
+        && traverseUpdateState((el as ExtendedElement), singleEnterNormal);
 }
 
 function shouldSilent(el: Element, e: ElementEvent) {
@@ -485,14 +523,14 @@ function shouldSilent(el: Element, e: ElementEvent) {
  */
 export function enableHoverEmphasis(el: Element, hoverStyle?: ZRStyleProps) {
     setAsHighDownDispatcher(el, true);
-    traverseUpdate(el as ExtendedElement, enableElementHoverEmphasis, hoverStyle);
+    traverseUpdateState(el as ExtendedElement, enableElementHoverEmphasis, hoverStyle);
 }
 
 /**
  * @param {module:zrender/Element} el
- * @param {Function} [el.highDownOnUpdate] Called when state updated.
+ * @param {Function} [el.onStateChange] Called when state updated.
  *        Since `setHoverStyle` has the constraint that it must be called after
- *        all of the normal style updated, `highDownOnUpdate` is not needed to
+ *        all of the normal style updated, `onStateChange` is not needed to
  *        trigger if both `fromState` and `toState` is 'normal', and needed to
  *        trigger if both `fromState` and `toState` is 'emphasis', which enables
  *        to sync outside style settings to "emphasis" state.
@@ -504,7 +542,7 @@ export function enableHoverEmphasis(el: Element, hoverStyle?: ZRStyleProps) {
  *        @param {string} toState Can be "normal" or "emphasis".
  *
  *        FIXME
- *        CAUTION: Do not expose `highDownOnUpdate` outside echarts.
+ *        CAUTION: Do not expose `onStateChange` outside echarts.
  *        Because it is not a complete solution. The update
  *        listener should not have been mount in element,
  *        and the normal/emphasis state should not have
@@ -525,13 +563,13 @@ export function enableHoverEmphasis(el: Element, hoverStyle?: ZRStyleProps) {
 export function setAsHighDownDispatcher(el: Element, asDispatcher: boolean) {
     const disable = asDispatcher === false;
     const extendedEl = el as ExtendedElement;
-    // Make `highDownSilentOnTouch` and `highDownOnUpdate` only work after
+    // Make `highDownSilentOnTouch` and `onStateChange` only work after
     // `setAsHighDownDispatcher` called. Avoid it is modified by user unexpectedly.
     if ((el as ECElement).highDownSilentOnTouch) {
         extendedEl.__highDownSilentOnTouch = (el as ECElement).highDownSilentOnTouch;
     }
-    if ((el as ECElement).highDownOnUpdate) {
-        extendedEl.__highDownOnUpdate = (el as ECElement).highDownOnUpdate;
+    if ((el as ECElement).onStateChange) {
+        extendedEl.__onStateChange = (el as ECElement).onStateChange;
     }
 
     // Simple optimize, since this method might be
@@ -606,10 +644,6 @@ export function setLabelStyle<LDI>(
 ) {
     opt = opt || EMPTY_OBJ;
     const isSetOnText = targetEl instanceof ZRText;
-    const labelFetcher = opt.labelFetcher;
-    const labelDataIndex = opt.labelDataIndex;
-    const labelDimIndex = opt.labelDimIndex;
-
     // This scenario, `label.normal.show = true; label.emphasis.show = false`,
     // is not supported util someone requests.
 
@@ -619,26 +653,27 @@ export function setLabelStyle<LDI>(
     // Consider performance, only fetch label when necessary.
     // If `normal.show` is `false` and `emphasis.show` is `true` and `emphasis.formatter` is not set,
     // label should be displayed, where text is fetched by `normal.formatter` or `opt.defaultText`.
-    let baseText;
+    let richText = isSetOnText ? targetEl as ZRText : null;
     if (showNormal || showEmphasis) {
+        const labelFetcher = opt.labelFetcher;
+        const labelDataIndex = opt.labelDataIndex;
+        const labelDimIndex = opt.labelDimIndex;
+
+        let baseText;
         if (labelFetcher) {
             baseText = labelFetcher.getFormattedLabel(labelDataIndex, 'normal', null, labelDimIndex);
         }
         if (baseText == null) {
             baseText = isFunction(opt.defaultText) ? opt.defaultText(labelDataIndex, opt) : opt.defaultText;
         }
-    }
-    const normalStyleText = baseText;
-    const emphasisStyleText = retrieve2(
-        labelFetcher
-            ? labelFetcher.getFormattedLabel(labelDataIndex, 'emphasis', null, labelDimIndex)
-            : null,
-        baseText
-    );
+        const normalStyleText = baseText;
+        const emphasisStyleText = retrieve2(
+            labelFetcher
+                ? labelFetcher.getFormattedLabel(labelDataIndex, 'emphasis', null, labelDimIndex)
+                : null,
+            baseText
+        );
 
-    let richText = isSetOnText ? targetEl as ZRText : null;
-    // Optimize: If style.text is null, text will not be drawn.
-    if (showNormal || showEmphasis) {
         if (!isSetOnText) {
             // Reuse the previous
             richText = targetEl.getTextContent();
@@ -1414,4 +1449,4 @@ export {
     RadialGradient,
     BoundingRect,
     Path
-};
+};
\ No newline at end of file
diff --git a/src/util/symbol.ts b/src/util/symbol.ts
index 011ec0e..a0bedde 100644
--- a/src/util/symbol.ts
+++ b/src/util/symbol.ts
@@ -29,6 +29,7 @@ import { ZRColor } from './types';
 type ECSymbol = graphic.Path & {
     __isEmptyBrush?: boolean
     setColor: (color: ZRColor, innerColor?: string) => void
+    getColor: () => ZRColor
 };
 type SymbolCtor = { new(): ECSymbol };
 type SymbolShapeMaker = (x: number, y: number, w: number, h: number, shape: Dictionary<any>) => void;
@@ -171,10 +172,11 @@ const Arrow = graphic.Path.extend({
 /**
  * Map of path contructors
  */
+// TODO Use function to build symbol path.
 const symbolCtors: Dictionary<SymbolCtor> = {
-
-    // TODO
-    line: graphic.Line as unknown as SymbolCtor,
+    // Use small height rect to simulate line.
+    // Avoid using stroke.
+    line: graphic.Rect as unknown as SymbolCtor,
 
     rect: graphic.Rect as unknown as SymbolCtor,
 
@@ -194,14 +196,16 @@ const symbolCtors: Dictionary<SymbolCtor> = {
 };
 
 
+// NOTICE Only use fill. No line!
 const symbolShapeMakers: Dictionary<SymbolShapeMaker> = {
 
-    line: function (x, y, w, h, shape: graphic.Line['shape']) {
-        // FIXME
-        shape.x1 = x;
-        shape.y1 = y + h / 2;
-        shape.x2 = x + w;
-        shape.y2 = y + h / 2;
+    line: function (x, y, w, h, shape: graphic.Rect['shape']) {
+        const thickness = 2;
+        // A thin line
+        shape.x = x;
+        shape.y = y + h / 2 - thickness / 2;
+        shape.width = w;
+        shape.height = thickness;
     },
 
     rect: function (x, y, w, h, shape: graphic.Rect['shape']) {
@@ -310,18 +314,14 @@ const SymbolClz = graphic.Path.extend({
 function symbolPathSetColor(this: ECSymbol, color: ZRColor, innerColor?: string) {
     if (this.type !== 'image') {
         const symbolStyle = this.style;
-        const symbolShape = this.shape;
-        if (symbolShape && symbolShape.symbolType === 'line') {
-            symbolStyle.stroke = color;
-        }
-        else if (this.__isEmptyBrush) {
+        if (this.__isEmptyBrush) {
             symbolStyle.stroke = color;
             symbolStyle.fill = innerColor || '#fff';
+            // TODO Same width with lineStyle in LineView.
+            symbolStyle.lineWidth = 2;
         }
         else {
-            // FIXME 判断图形默认是填充还是描边,使用 onlyStroke ?
-            symbolStyle.fill && (symbolStyle.fill = color);
-            symbolStyle.stroke && (symbolStyle.stroke = color);
+            symbolStyle.fill = color;
         }
         this.markRedraw();
     }
@@ -377,9 +377,12 @@ export function createSymbol(
 
     (symbolPath as ECSymbol).__isEmptyBrush = isEmpty;
 
+    // TODO Should deprecate setColor
     (symbolPath as ECSymbol).setColor = symbolPathSetColor;
 
-    (symbolPath as ECSymbol).setColor(color);
+    if (color) {
+        (symbolPath as ECSymbol).setColor(color);
+    }
 
     return symbolPath as ECSymbol;
 }
diff --git a/src/util/types.ts b/src/util/types.ts
index 4e4ea3c..0503ec8 100644
--- a/src/util/types.ts
+++ b/src/util/types.ts
@@ -105,7 +105,7 @@ export interface ECElement extends Element {
         formatterParams?: unknown;
     };
     highDownSilentOnTouch?: boolean;
-    highDownOnUpdate?: (fromState: 'normal' | 'emphasis', toState: 'normal' | 'emphasis') => void;
+    onStateChange?: (fromState: 'normal' | 'emphasis', toState: 'normal' | 'emphasis') => void;
 }
 
 export interface DataHost {
diff --git a/src/view/Chart.ts b/src/view/Chart.ts
index 4813eda..7c00696 100644
--- a/src/view/Chart.ts
+++ b/src/view/Chart.ts
@@ -35,7 +35,6 @@ import {
 } from '../util/types';
 import { SeriesTaskContext, SeriesTask } from '../stream/Scheduler';
 import List from '../data/List';
-import { graphic } from '../export';
 
 const inner = modelUtil.makeInner<{
     updateMethod: keyof ChartView
@@ -195,15 +194,6 @@ function elSetState(el: Element, state: DisplayState, highlightDigit: number) {
     if (el) {
         state === 'emphasis' ? graphicUtil.enterEmphasis(el, highlightDigit)
             : graphicUtil.leaveEmphasis(el, highlightDigit);
-
-        if (el.isGroup
-            // Simple optimize.
-            && !graphicUtil.isHighDownDispatcher(el)
-        ) {
-            for (let i = 0, len = (el as Group).childCount(); i < len; i++) {
-                elSetState((el as Group).childAt(i), state, highlightDigit);
-            }
-        }
     }
 }
 
diff --git a/src/visual/LegendVisualProvider.ts b/src/visual/LegendVisualProvider.ts
index ebea252..c62297e 100644
--- a/src/visual/LegendVisualProvider.ts
+++ b/src/visual/LegendVisualProvider.ts
@@ -62,7 +62,7 @@ class LegendVisualProvider {
     getItemVisual(dataIndex: number, key: string): any {
         // Get encoded visual properties from final filtered data.
         const dataWithEncodedVisual = this._getDataWithEncodedVisual();
-        return dataWithEncodedVisual.getItemVisual(dataIndex, key);
+        return dataWithEncodedVisual.getItemVisual(dataIndex, key as any);
     }
 }
 
diff --git a/src/chart/tree.ts b/src/visual/commonVisualTypes.ts
similarity index 72%
copy from src/chart/tree.ts
copy to src/visual/commonVisualTypes.ts
index e498d25..53647e7 100644
--- a/src/chart/tree.ts
+++ b/src/visual/commonVisualTypes.ts
@@ -17,14 +17,12 @@
 * under the License.
 */
 
-import * as echarts from '../echarts';
+import { DefaultDataVisual } from '../data/List';
 
-import './tree/TreeSeries';
-import './tree/TreeView';
-import './tree/treeAction';
+export interface LineDataVisual extends DefaultDataVisual {
+    fromSymbol: string
+    toSymbol: string
+    fromSymbolSize: number
+    toSymbolSize: number
+}
 
-import visualSymbol from '../visual/symbol';
-import treeLayout from './tree/treeLayout';
-
-echarts.registerVisual(visualSymbol('tree', 'circle'));
-echarts.registerLayout(treeLayout);
diff --git a/src/visual/dataColor.ts b/src/visual/dataColor.ts
deleted file mode 100644
index ff3d9e8..0000000
--- a/src/visual/dataColor.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
-* Licensed to the Apache Software Foundation (ASF) under one
-* or more contributor license agreements.  See the NOTICE file
-* distributed with this work for additional information
-* regarding copyright ownership.  The ASF licenses this file
-* to you under the Apache License, Version 2.0 (the
-* "License"); you may not use this file except in compliance
-* with the License.  You may obtain a copy of the License at
-*
-*   http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing,
-* software distributed under the License is distributed on an
-* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-* KIND, either express or implied.  See the License for the
-* specific language governing permissions and limitations
-* under the License.
-*/
-
-// Pick color from palette for each data item.
-// Applicable for charts that require applying color palette
-// in data level (like pie, funnel, chord).
-import {createHashMap} from 'zrender/src/core/util';
-import { StageHandler, ItemStyleOption } from '../util/types';
-import SeriesModel from '../model/Series';
-
-interface SeriesModelWithPaletteScope extends SeriesModel {
-    __paletteScope: any
-}
-
-export default function (seriesType: string): StageHandler {
-    return {
-        getTargetSeries: function (ecModel) {
-            // Pie and funnel may use diferrent scope
-            const paletteScope = {};
-            const seiresModelMap = createHashMap<SeriesModel>();
-
-            ecModel.eachSeriesByType(seriesType, function (seriesModel) {
-                (seriesModel as SeriesModelWithPaletteScope).__paletteScope = paletteScope;
-                seiresModelMap.set(seriesModel.uid, seriesModel);
-            });
-
-            return seiresModelMap;
-        },
-        reset: function (seriesModel) {
-            const dataAll = seriesModel.getRawData();
-            const idxMap: {[key: number]: number} = {};
-            const data = seriesModel.getData();
-
-            data.each(function (idx) {
-                const rawIdx = data.getRawIndex(idx);
-                idxMap[rawIdx] = idx;
-            });
-
-            dataAll.each(function (rawIdx) {
-                const filteredIdx = idxMap[rawIdx];
-
-                // If series.itemStyle.normal.color is a function. itemVisual may be encoded
-                const singleDataColor = filteredIdx != null
-                    && data.getItemVisual(filteredIdx, 'color', true);
-
-                const singleDataBorderColor = filteredIdx != null
-                    && data.getItemVisual(filteredIdx, 'borderColor', true);
-
-                let itemModel;
-                if (!singleDataColor || !singleDataBorderColor) {
-                    // FIXME Performance
-                    itemModel = dataAll.getItemModel<{itemStyle: ItemStyleOption}>(rawIdx);
-                }
-
-                if (!singleDataColor) {
-                    const color = itemModel.get(['itemStyle', 'color'])
-                        || seriesModel.getColorFromPalette(
-                            dataAll.getName(rawIdx) || (rawIdx + ''),
-                            (seriesModel as SeriesModelWithPaletteScope).__paletteScope,
-                            dataAll.count()
-                        );
-                    // Data is not filtered
-                    if (filteredIdx != null) {
-                        data.setItemVisual(filteredIdx, 'color', color);
-                    }
-                }
-
-                if (!singleDataBorderColor) {
-                    const borderColor = itemModel.get(['itemStyle', 'borderColor']);
-
-                    // Data is not filtered
-                    if (filteredIdx != null) {
-                        data.setItemVisual(filteredIdx, 'borderColor', borderColor);
-                    }
-                }
-            });
-        }
-    };
-}
\ No newline at end of file
diff --git a/src/visual/helper.ts b/src/visual/helper.ts
new file mode 100644
index 0000000..affe031
--- /dev/null
+++ b/src/visual/helper.ts
@@ -0,0 +1,87 @@
+/*
+* 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.
+*/
+
+/**
+ * A mapping of visual provided to deverloper and visual stored in the List module.
+ * To developer:
+ *  'color', 'opacity', 'symbol', 'symbolSize'...
+ * In the List module storage:
+ *  'style', 'symbol', 'symbolSize'...
+ */
+import List from '../data/List';
+import { __DEV__ } from '../config';
+
+
+export function getItemVisualFromData(data: List, dataIndex: number, key: string) {
+    switch (key) {
+        case 'color':
+            const style = data.getItemVisual(dataIndex, 'style');
+            return style[data.getVisual('brushType')];
+        case 'opacity':
+            return data.getItemVisual(dataIndex, 'style').opacity;
+        case 'symbol':
+        case 'symbolSize':
+        case 'liftZ':
+            return data.getItemVisual(dataIndex, key);
+        default:
+            if (__DEV__) {
+                console.warn(`Unknown visual type ${key}`);
+            }
+    }
+}
+
+export function getVisualFromData(data: List, key: string) {
+    switch (key) {
+        case 'color':
+            const style = data.getVisual('style');
+            return style[data.getVisual('brushType')];
+        case 'opacity':
+            return data.getVisual('style').opacity;
+        case 'symbol':
+        case 'symbolSize':
+        case 'liftZ':
+            return data.getVisual(key);
+        default:
+            if (__DEV__) {
+                console.warn(`Unknown visual type ${key}`);
+            }
+    }
+}
+
+export function setItemVisualFromData(data: List, dataIndex: number, key: string, value: any) {
+    switch (key) {
+        case 'color':
+            // Make sure not sharing style object.
+            const style = data.ensureUniqueItemVisual(dataIndex, 'style');
+            style[data.getVisual('brushType')] = value;
+            break;
+        case 'opacity':
+            data.ensureUniqueItemVisual(dataIndex, 'style').opacity = value;
+            break;
+        case 'symbol':
+        case 'symbolSize':
+        case 'liftZ':
+            data.setItemVisual(dataIndex, key, value);
+            break;
+        default:
+            if (__DEV__) {
+                console.warn(`Unknown visual type ${key}`);
+            }
+    }
+}
\ No newline at end of file
diff --git a/src/visual/seriesColor.ts b/src/visual/seriesColor.ts
deleted file mode 100644
index 0f47e8d..0000000
--- a/src/visual/seriesColor.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
-* Licensed to the Apache Software Foundation (ASF) under one
-* or more contributor license agreements.  See the NOTICE file
-* distributed with this work for additional information
-* regarding copyright ownership.  The ASF licenses this file
-* to you under the Apache License, Version 2.0 (the
-* "License"); you may not use this file except in compliance
-* with the License.  You may obtain a copy of the License at
-*
-*   http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing,
-* software distributed under the License is distributed on an
-* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-* KIND, either express or implied.  See the License for the
-* specific language governing permissions and limitations
-* under the License.
-*/
-
-import Gradient from 'zrender/src/graphic/Gradient';
-import {isFunction} from 'zrender/src/core/util';
-import { StageHandler } from '../util/types';
-
-const seriesColorTask: StageHandler = {
-    createOnAllSeries: true,
-    performRawSeries: true,
-    reset: function (seriesModel, ecModel) {
-        const data = seriesModel.getData();
-        const colorAccessPath = seriesModel.visualColorAccessPath
-            || ['itemStyle', 'color'];
-        // Set in itemStyle
-        let color = seriesModel.get(colorAccessPath as any);
-        const colorCallback = (isFunction(color) && !(color instanceof Gradient))
-            ? color : null;
-        // Default color
-        if (!color || colorCallback) {
-            color = seriesModel.getColorFromPalette(
-                // TODO series count changed.
-                seriesModel.name, null, ecModel.getSeriesCount()
-            );
-        }
-
-        data.setVisual('color', color);
-
-        const borderColorAccessPath = seriesModel.visualBorderColorAccessPath || ['itemStyle', 'borderColor'];
-        const borderColor = seriesModel.get(borderColorAccessPath as any);
-        data.setVisual('borderColor', borderColor);
-
-        // Only visible series has each data be visual encoded
-        if (!ecModel.isSeriesFiltered(seriesModel)) {
-            if (colorCallback) {
-                data.each(function (idx) {
-                    data.setItemVisual(
-                        idx, 'color', colorCallback(seriesModel.getDataParams(idx))
-                    );
-                });
-            }
-
-            return {
-                dataEach: data.hasItemOption ? function (data, idx) {
-                    const itemModel = data.getItemModel(idx);
-                    const color = itemModel.get(colorAccessPath as any, true);
-                    const borderColor = itemModel.get(borderColorAccessPath as any, true);
-                    if (color != null) {
-                        data.setItemVisual(idx, 'color', color);
-                    }
-                    if (borderColor != null) {
-                        data.setItemVisual(idx, 'borderColor', borderColor);
-                    }
-                } : null
-            };
-        }
-    }
-};
-export default seriesColorTask;
\ No newline at end of file
diff --git a/src/visual/style.ts b/src/visual/style.ts
new file mode 100644
index 0000000..3d66948
--- /dev/null
+++ b/src/visual/style.ts
@@ -0,0 +1,165 @@
+/*
+* 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 { isFunction, extend } from 'zrender/src/core/util';
+import { StageHandler, CallbackDataParams, ZRColor } from '../util/types';
+import makeStyleMapper from '../model/mixin/makeStyleMapper';
+import { ITEM_STYLE_KEY_MAP } from '../model/mixin/itemStyle';
+import { LINE_STYLE_KEY_MAP } from '../model/mixin/lineStyle';
+import SeriesModel from '../model/Series';
+import Model from '../model/Model';
+
+interface SeriesModelWithPaletteScope extends SeriesModel {
+    __paletteScope: any
+}
+
+const defaultStyleMappers = {
+    itemStyle: makeStyleMapper(ITEM_STYLE_KEY_MAP, true),
+    lineStyle: makeStyleMapper(LINE_STYLE_KEY_MAP, true)
+};
+
+const defaultColorKey = {
+    lineStyle: 'stroke',
+    itemStyle: 'fill'
+} as const;
+
+function getStyleMapper(seriesModel: SeriesModel, stylePath: string) {
+    const styleMapper = seriesModel.visualStyleMapper
+        || defaultStyleMappers[stylePath as 'itemStyle' | 'lineStyle'];
+    if (!styleMapper) {
+        console.warn(`Unkown style type '${stylePath}'.`);
+        return defaultStyleMappers.itemStyle;
+    }
+    return styleMapper;
+}
+
+function getDefaultColorKey(seriesModel: SeriesModel, stylePath: string): 'stroke' | 'fill' {
+    // return defaultColorKey[stylePath] ||
+    const colorKey = seriesModel.visualColorBrushType
+        || defaultColorKey[stylePath as 'itemStyle' | 'lineStyle'];
+
+    if (!colorKey) {
+        console.warn(`Unkown style type '${stylePath}'.`);
+        return 'fill';
+    }
+
+    return colorKey;
+}
+
+type ColorCallback = (params: CallbackDataParams) => ZRColor;
+
+const seriesStyleTask: StageHandler = {
+    createOnAllSeries: true,
+    performRawSeries: true,
+    reset(seriesModel, ecModel) {
+        const data = seriesModel.getData();
+        const stylePath = seriesModel.visualStyleAccessPath
+            || 'itemStyle';
+        // Set in itemStyle
+        const styleModel = seriesModel.getModel(stylePath as any);
+        const getStyle = getStyleMapper(seriesModel, stylePath);
+
+        const globalStyle = getStyle(styleModel);
+
+        // TODO
+        const colorKey = getDefaultColorKey(seriesModel, stylePath);
+        const color = globalStyle[colorKey];
+
+        // TODO style callback
+        const colorCallback = isFunction(color) ? color as unknown as ColorCallback : null;
+        // Default
+        if (!globalStyle[colorKey] || colorCallback) {  // TODO Better handling on callback
+            globalStyle[colorKey] = seriesModel.getColorFromPalette(
+                // TODO series count changed.
+                seriesModel.name, null, ecModel.getSeriesCount()
+            );
+        }
+
+        data.setVisual('style', globalStyle);
+        data.setVisual('brushType', colorKey);
+
+        // Only visible series has each data be visual encoded
+        if (!ecModel.isSeriesFiltered(seriesModel)) {
+            if (colorCallback) {
+                return {
+                    dataEach(data, idx) {
+                        data.each(function (idx) {
+                            const dataParams = seriesModel.getDataParams(idx);
+                            const itemStyle = extend({}, globalStyle);
+                            itemStyle[colorKey] = colorCallback(dataParams);
+                        });
+                    }
+                };
+            }
+        }
+    }
+};
+
+const sharedModel = new Model();
+const dataStyleTask: StageHandler = {
+    createOnAllSeries: true,
+    performRawSeries: true,
+    reset(seriesModel, ecModel) {
+        if (seriesModel.ignoreStyleOnData) {
+            return;
+        }
+
+        const data = seriesModel.getData();
+        const stylePath = seriesModel.visualStyleAccessPath
+            || 'itemStyle';
+        // Set in itemStyle
+        const getStyle = getStyleMapper(seriesModel, stylePath);
+        const colorKey = getDefaultColorKey(seriesModel, stylePath);
+
+        const idxMap: {[key: number]: number} = {};
+        data.each(function (idx) {
+            const rawIdx = data.getRawIndex(idx);
+            idxMap[idx] = rawIdx;
+        });
+
+        return {
+            dataEach: data.hasItemOption ? function (data, idx) {
+                // Not use getItemModel for performance considuration
+                const rawItem = data.getRawDataItem(idx) as any;
+                if (rawItem && rawItem[stylePath]) {
+                    sharedModel.option = rawItem[stylePath];
+                    const style = getStyle(sharedModel);
+
+                    const existsStyle = data.ensureUniqueItemVisual(idx, 'style');
+                    extend(existsStyle, style);
+                }
+
+                if (seriesModel.useColorPaletteOnData) {
+                    const dataAll = seriesModel.getRawData();
+                    const existsStyle = data.ensureUniqueItemVisual(idx, 'style');
+                    const rawIdx = idxMap[idx];
+                    if (!existsStyle[colorKey]) {
+                        // Get color from palette.
+                        existsStyle[colorKey] = seriesModel.getColorFromPalette(
+                            dataAll.getName(rawIdx) || (rawIdx + ''),
+                            (seriesModel as SeriesModelWithPaletteScope).__paletteScope,
+                            dataAll.count()
+                        );
+                    }
+                }
+            } : null
+        };
+    }
+};
+export {seriesStyleTask, dataStyleTask};
\ No newline at end of file
diff --git a/src/visual/symbol.ts b/src/visual/symbol.ts
index 5d3cfe5..444f448 100644
--- a/src/visual/symbol.ts
+++ b/src/visual/symbol.ts
@@ -30,79 +30,111 @@ import List from '../data/List';
 import SeriesModel from '../model/Series';
 import GlobalModel from '../model/Global';
 
-export default function (seriesType: string, defaultSymbolType: string, legendSymbol?: string): StageHandler {
-    // Encoding visual for all series include which is filtered for legend drawing
-    return {
-        seriesType: seriesType,
-
-        // For legend.
-        performRawSeries: true,
-
-        reset: function (
-            seriesModel: SeriesModel<SeriesOption & SymbolOptionMixin<CallbackDataParams>>,
-            ecModel: GlobalModel
-        ) {
-            const data = seriesModel.getData();
-
-            const symbolType = seriesModel.get('symbol');
-            const symbolSize = seriesModel.get('symbolSize');
-            const keepAspect = seriesModel.get('symbolKeepAspect');
-
-            const hasSymbolTypeCallback = isFunction(symbolType);
-            const hasSymbolSizeCallback = isFunction(symbolSize);
-            const hasCallback = hasSymbolTypeCallback || hasSymbolSizeCallback;
-            const seriesSymbol = (!hasSymbolTypeCallback && symbolType) ? symbolType : defaultSymbolType;
-            const seriesSymbolSize = !hasSymbolSizeCallback ? symbolSize : null;
-
-            data.setVisual({
-                legendSymbol: legendSymbol || seriesSymbol,
-                // If seting callback functions on `symbol` or `symbolSize`, for simplicity and avoiding
-                // to bring trouble, we do not pick a reuslt from one of its calling on data item here,
-                // but just use the default value. Callback on `symbol` or `symbolSize` is convenient in
-                // some cases but generally it is not recommanded.
-                symbol: seriesSymbol,
-                symbolSize: seriesSymbolSize,
-                symbolKeepAspect: keepAspect
-            });
-
-            // Only visible series has each data be visual encoded
-            if (ecModel.isSeriesFiltered(seriesModel)) {
-                return;
-            }
+// Encoding visual for all series include which is filtered for legend drawing
+const seriesSymbolTask: StageHandler = {
 
-            function dataEach(data: List, idx: number) {
-                if (hasCallback) {
-                    const rawValue = seriesModel.getRawValue(idx);
-                    const params = seriesModel.getDataParams(idx);
-                    hasSymbolTypeCallback && data.setItemVisual(
-                        idx, 'symbol', (symbolType as SymbolCallback<CallbackDataParams>)(rawValue, params)
-                    );
-                    hasSymbolSizeCallback && data.setItemVisual(
-                        idx, 'symbolSize', (symbolSize as SymbolSizeCallback<CallbackDataParams>)(rawValue, params)
-                    );
-                }
-
-                if (data.hasItemOption) {
-                    const itemModel = data.getItemModel<SymbolOptionMixin>(idx);
-                    const itemSymbolType = itemModel.getShallow('symbol', true);
-                    const itemSymbolSize = itemModel.getShallow('symbolSize', true);
-                    const itemSymbolKeepAspect = itemModel.getShallow('symbolKeepAspect', true);
-
-                    // If has item symbol
-                    if (itemSymbolType != null) {
-                        data.setItemVisual(idx, 'symbol', itemSymbolType);
-                    }
-                    if (itemSymbolSize != null) {
-                        // PENDING Transform symbolSize ?
-                        data.setItemVisual(idx, 'symbolSize', itemSymbolSize);
-                    }
-                    if (itemSymbolKeepAspect != null) {
-                        data.setItemVisual(idx, 'symbolKeepAspect', itemSymbolKeepAspect);
-                    }
-                }
-            }
+    createOnAllSeries: true,
+
+    // For legend.
+    performRawSeries: true,
+
+    reset: function (
+        seriesModel: SeriesModel<SeriesOption & SymbolOptionMixin<CallbackDataParams>>,
+        ecModel: GlobalModel
+    ) {
+        const data = seriesModel.getData();
 
-            return { dataEach: (data.hasItemOption || hasCallback) ? dataEach : null };
+        if (seriesModel.legendSymbol) {
+            data.setVisual('legendSymbol', seriesModel.legendSymbol);
         }
-    };
-}
+
+        if (!seriesModel.hasSymbolVisual) {
+            return;
+        }
+
+        const symbolType = seriesModel.get('symbol');
+        const symbolSize = seriesModel.get('symbolSize');
+        const keepAspect = seriesModel.get('symbolKeepAspect');
+
+        const hasSymbolTypeCallback = isFunction(symbolType);
+        const hasSymbolSizeCallback = isFunction(symbolSize);
+        const hasCallback = hasSymbolTypeCallback || hasSymbolSizeCallback;
+        const seriesSymbol = (!hasSymbolTypeCallback && symbolType) ? symbolType : seriesModel.defaultSymbol;
+        const seriesSymbolSize = !hasSymbolSizeCallback ? symbolSize : null;
+
+        data.setVisual({
+            legendSymbol: seriesModel.legendSymbol || seriesSymbol as string,
+            // If seting callback functions on `symbol` or `symbolSize`, for simplicity and avoiding
+            // to bring trouble, we do not pick a reuslt from one of its calling on data item here,
+            // but just use the default value. Callback on `symbol` or `symbolSize` is convenient in
+            // some cases but generally it is not recommanded.
+            symbol: seriesSymbol as string,
+            symbolSize: seriesSymbolSize as number | number[],
+            symbolKeepAspect: keepAspect
+        });
+
+        // Only visible series has each data be visual encoded
+        if (ecModel.isSeriesFiltered(seriesModel)) {
+            return;
+        }
+
+        function dataEach(data: List, idx: number) {
+            const rawValue = seriesModel.getRawValue(idx);
+            const params = seriesModel.getDataParams(idx);
+            hasSymbolTypeCallback && data.setItemVisual(
+                idx, 'symbol', (symbolType as SymbolCallback<CallbackDataParams>)(rawValue, params)
+            );
+            hasSymbolSizeCallback && data.setItemVisual(
+                idx, 'symbolSize', (symbolSize as SymbolSizeCallback<CallbackDataParams>)(rawValue, params)
+            );
+        }
+
+        return { dataEach: hasCallback ? dataEach : null };
+    }
+};
+
+const dataSymbolTask: StageHandler = {
+
+    createOnAllSeries: true,
+
+    // For legend.
+    performRawSeries: true,
+
+    reset: function (
+        seriesModel: SeriesModel<SeriesOption & SymbolOptionMixin<CallbackDataParams>>,
+        ecModel: GlobalModel
+    ) {
+        if (!seriesModel.hasSymbolVisual) {
+            return;
+        }
+        // Only visible series has each data be visual encoded
+        if (ecModel.isSeriesFiltered(seriesModel)) {
+            return;
+        }
+
+        const data = seriesModel.getData();
+
+        function dataEach(data: List, idx: number) {
+            const itemModel = data.getItemModel<SymbolOptionMixin>(idx);
+            const itemSymbolType = itemModel.getShallow('symbol', true);
+            const itemSymbolSize = itemModel.getShallow('symbolSize', true);
+            const itemSymbolKeepAspect = itemModel.getShallow('symbolKeepAspect', true);
+
+            // If has item symbol
+            if (itemSymbolType != null) {
+                data.setItemVisual(idx, 'symbol', itemSymbolType);
+            }
+            if (itemSymbolSize != null) {
+                // PENDING Transform symbolSize ?
+                data.setItemVisual(idx, 'symbolSize', itemSymbolSize);
+            }
+            if (itemSymbolKeepAspect != null) {
+                data.setItemVisual(idx, 'symbolKeepAspect', itemSymbolKeepAspect);
+            }
+        }
+
+        return { dataEach: data.hasItemOption ? dataEach : null };
+    }
+};
+
+export {seriesSymbolTask, dataSymbolTask};
\ No newline at end of file
diff --git a/src/visual/visualSolution.ts b/src/visual/visualSolution.ts
index 5ae42fa..accd72d 100644
--- a/src/visual/visualSolution.ts
+++ b/src/visual/visualSolution.ts
@@ -31,6 +31,7 @@ import {
     StageHandlerProgressExecutor
 } from '../util/types';
 import List from '../data/List';
+import { getItemVisualFromData, setItemVisualFromData } from './helper';
 
 const each = zrUtil.each;
 
@@ -148,11 +149,11 @@ export function applyVisual<VisualState extends string, Scope>(
     let dataIndex: number;
 
     function getVisual(key: string) {
-        return data.getItemVisual(dataIndex, key);
+        return getItemVisualFromData(data, dataIndex, key) as string | number;
     }
 
     function setVisual(key: string, value: any) {
-        data.setItemVisual(dataIndex, key, value);
+        setItemVisualFromData(data, dataIndex, key, value);
     }
 
     if (dimension == null) {
@@ -214,11 +215,11 @@ export function incrementalApplyVisual<VisualState extends string>(
             }
 
             function getVisual(key: string) {
-                return data.getItemVisual(dataIndex, key);
+                return getItemVisualFromData(data, dataIndex, key) as string | number;
             }
 
             function setVisual(key: string, value: any) {
-                data.setItemVisual(dataIndex, key, value);
+                setItemVisualFromData(data, dataIndex, key, value);
             }
 
             let dataIndex: number;


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