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/07/07 08:11:38 UTC

[incubator-echarts] branch state updated: refact: move states related code from graphic to states

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

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


The following commit(s) were added to refs/heads/state by this push:
     new ceed556  refact: move states related code from graphic to states
ceed556 is described below

commit ceed556861675d3a002cddb2e5fa83d277b8d87b
Author: pissang <bm...@gmail.com>
AuthorDate: Tue Jul 7 16:11:19 2020 +0800

    refact: move states related code from graphic to states
---
 src/chart/bar/BarView.ts                     |   6 +-
 src/chart/bar/PictorialBarView.ts            |  25 ++-
 src/chart/boxplot/BoxplotView.ts             |   3 +-
 src/chart/candlestick/CandlestickView.ts     |   3 +-
 src/chart/custom.ts                          |  17 +-
 src/chart/funnel/FunnelView.ts               |   5 +-
 src/chart/gauge/GaugeView.ts                 |   5 +-
 src/chart/heatmap/HeatmapView.ts             |   3 +-
 src/chart/helper/EffectSymbol.ts             |   3 +-
 src/chart/helper/Line.ts                     |   7 +-
 src/chart/helper/Polyline.ts                 |   3 +-
 src/chart/helper/Symbol.ts                   |   7 +-
 src/chart/parallel/ParallelView.ts           |   3 +-
 src/chart/pie/PieView.ts                     |   5 +-
 src/chart/radar/RadarView.ts                 |   7 +-
 src/chart/sankey/SankeyView.ts               |  13 +-
 src/chart/sunburst/SunburstPiece.ts          |   3 +-
 src/chart/themeRiver/ThemeRiverView.ts       |   5 +-
 src/chart/treemap/TreemapView.ts             |  17 +-
 src/component/helper/MapDraw.ts              |   3 +-
 src/component/legend/LegendView.ts           |   5 +-
 src/component/marker/MarkAreaView.ts         |   3 +-
 src/component/timeline/SliderTimelineView.ts |   7 +-
 src/component/toolbox/ToolboxView.ts         |  13 +-
 src/echarts.ts                               |  21 +-
 src/util/graphic.ts                          | 323 ++-------------------------
 src/util/states.ts                           | 316 ++++++++++++++++++++++++++
 src/util/types.ts                            |  18 +-
 src/view/Chart.ts                            |   7 +-
 29 files changed, 454 insertions(+), 402 deletions(-)

diff --git a/src/chart/bar/BarView.ts b/src/chart/bar/BarView.ts
index 34ccafc..74470ae 100644
--- a/src/chart/bar/BarView.ts
+++ b/src/chart/bar/BarView.ts
@@ -24,12 +24,10 @@ import {
     getECData,
     updateProps,
     initProps,
-    enableHoverEmphasis,
     updateLabel,
     initLabel,
-    removeElement,
-    setStatesStylesFromModel
-} from '../../util/graphic';
+    removeElement} from '../../util/graphic';
+import { enableHoverEmphasis, setStatesStylesFromModel } from '../../util/states';
 import { setLabelStyle } from '../../label/labelStyle';
 import Path, { PathProps } from 'zrender/src/graphic/Path';
 import Group from 'zrender/src/graphic/Group';
diff --git a/src/chart/bar/PictorialBarView.ts b/src/chart/bar/PictorialBarView.ts
index b118edc..716cdc8 100644
--- a/src/chart/bar/PictorialBarView.ts
+++ b/src/chart/bar/PictorialBarView.ts
@@ -19,6 +19,13 @@
 
 import * as zrUtil from 'zrender/src/core/util';
 import * as graphic from '../../util/graphic';
+import {
+    enterEmphasisWhenMouseOver,
+    leaveEmphasisWhenMouseOut,
+    enterEmphasis,
+    leaveEmphasis,
+    enableHoverEmphasis
+} from '../../util/states';
 import {createSymbol} from '../../util/symbol';
 import {parsePercent, isNumeric} from '../../util/number';
 import ChartView from '../../view/Chart';
@@ -27,7 +34,7 @@ import ExtensionAPI from '../../ExtensionAPI';
 import List from '../../data/List';
 import GlobalModel from '../../model/Global';
 import Model from '../../model/Model';
-import { ColorString, AnimationOptionMixin } from '../../util/types';
+import { ColorString, AnimationOptionMixin, ZRElementEvent } from '../../util/types';
 import type Cartesian2D from '../../coord/cartesian/Cartesian2D';
 import type Displayable from 'zrender/src/graphic/Displayable';
 import type Axis2D from '../../coord/cartesian/Axis2D';
@@ -623,15 +630,15 @@ function createOrUpdateRepeatSymbols(
         };
     }
 
-    function onMouseOver() {
+    function onMouseOver(e: ZRElementEvent) {
         eachPath(bar, function (path) {
-            graphic.enterEmphasis(path);
+            enterEmphasisWhenMouseOver(path, e);
         });
     }
 
-    function onMouseOut() {
+    function onMouseOut(e: ZRElementEvent) {
         eachPath(bar, function (path) {
-            graphic.leaveEmphasis(path);
+            leaveEmphasisWhenMouseOut(path, e);
         });
     }
 }
@@ -689,11 +696,11 @@ function createOrUpdateSingleSymbol(
     updateHoverAnimation(mainPath, symbolMeta);
 
     function onMouseOver(this: typeof mainPath) {
-        graphic.enterEmphasis(this);
+        enterEmphasis(this);
     }
 
     function onMouseOut(this: typeof mainPath) {
-        graphic.leaveEmphasis(this);
+        leaveEmphasis(this);
     }
 }
 
@@ -936,7 +943,7 @@ function updateCommon(
 
     eachPath(bar, function (path) {
         path.useStyle(symbolMeta.style);
-        graphic.enableHoverEmphasis(path);
+        enableHoverEmphasis(path);
 
         path.ensureState('emphasis').style = emphasisStyle;
         path.ensureState('blur').style = blurStyle;
@@ -963,7 +970,7 @@ function updateCommon(
         }
     );
 
-    graphic.enableHoverEmphasis(barRect);
+    enableHoverEmphasis(barRect);
 }
 
 function toIntTimes(times: number) {
diff --git a/src/chart/boxplot/BoxplotView.ts b/src/chart/boxplot/BoxplotView.ts
index 0ce6c02..89a2b6f 100644
--- a/src/chart/boxplot/BoxplotView.ts
+++ b/src/chart/boxplot/BoxplotView.ts
@@ -20,6 +20,7 @@
 import * as zrUtil from 'zrender/src/core/util';
 import ChartView from '../../view/Chart';
 import * as graphic from '../../util/graphic';
+import { setStatesStylesFromModel } from '../../util/states';
 import Path, { PathProps } from 'zrender/src/graphic/Path';
 import BoxplotSeriesModel, { BoxplotDataItemOption } from './BoxplotSeries';
 import GlobalModel from '../../model/Global';
@@ -180,7 +181,7 @@ function updateNormalBoxData(
 
     el.z2 = 100;
 
-    graphic.setStatesStylesFromModel(el, data.getItemModel<BoxplotDataItemOption>(dataIndex));
+    setStatesStylesFromModel(el, data.getItemModel<BoxplotDataItemOption>(dataIndex));
 }
 
 function transInit(points: number[][], dim: number, itemLayout: BoxplotItemLayout) {
diff --git a/src/chart/candlestick/CandlestickView.ts b/src/chart/candlestick/CandlestickView.ts
index c61ba6b..595d1ee 100644
--- a/src/chart/candlestick/CandlestickView.ts
+++ b/src/chart/candlestick/CandlestickView.ts
@@ -20,6 +20,7 @@
 import * as zrUtil from 'zrender/src/core/util';
 import ChartView from '../../view/Chart';
 import * as graphic from '../../util/graphic';
+import { setStatesStylesFromModel } from '../../util/states';
 import Path, { PathProps } from 'zrender/src/graphic/Path';
 import {createClipPath} from '../helper/createClipPathFromCoordSys';
 import CandlestickSeriesModel, { CandlestickDataItemOption } from './CandlestickSeries';
@@ -279,7 +280,7 @@ function setBoxCommon(el: NormalBoxPath, data: List, dataIndex: number, isSimple
 
     el.__simpleBox = isSimpleBox;
 
-    graphic.setStatesStylesFromModel(el, itemModel);
+    setStatesStylesFromModel(el, itemModel);
 }
 
 function transInit(points: number[][], itemLayout: CandlestickItemLayout) {
diff --git a/src/chart/custom.ts b/src/chart/custom.ts
index 5330c07..d9fa1bd 100644
--- a/src/chart/custom.ts
+++ b/src/chart/custom.ts
@@ -23,7 +23,8 @@ import {
     hasOwn, assert, isString, retrieve2, retrieve3, defaults, each, keys, isArrayLike, bind
 } from 'zrender/src/core/util';
 import * as graphicUtil from '../util/graphic';
-import * as SetLabelStyleOpt from '../label/labelStyle';
+import { enableElementHoverEmphasis, setAsHighDownDispatcher } from '../util/states';
+import * as labelStyleHelper from '../label/labelStyle';
 import {getDefaultLabel} from './helper/labelHelper';
 import createListFromArray from './helper/createListFromArray';
 import {getLayoutOnAxis} from '../layout/barGrid';
@@ -1113,11 +1114,11 @@ function updateElOnState(
             stateObj.textConfig = txCfgOpt;
         }
 
-        graphicUtil.enableElementHoverEmphasis(elDisplayable);
+        enableElementHoverEmphasis(elDisplayable);
     }
 
     if (isRoot) {
-        graphicUtil.setAsHighDownDispatcher(el, styleOpt !== false);
+        setAsHighDownDispatcher(el, styleOpt !== false);
     }
 }
 
@@ -1383,14 +1384,14 @@ function makeRenderItem(
         // Now that the feture of "auto adjust text fill/stroke" has been migrated to zrender
         // since ec5, we should set `isAttached` as `false` here and make compat in
         // `convertToEC4StyleForCustomSerise`.
-        const textStyle = SetLabelStyleOpt.createTextStyle(labelModel, null, opt, false, true);
+        const textStyle = labelStyleHelper.createTextStyle(labelModel, null, opt, false, true);
         textStyle.text = labelModel.getShallow('show')
             ? retrieve2(
                 customSeries.getFormattedLabel(dataIndexInside, NORMAL),
                 getDefaultLabel(data, dataIndexInside)
             )
             : null;
-        const textConfig = SetLabelStyleOpt.createTextConfig(textStyle, labelModel, opt, false);
+        const textConfig = labelStyleHelper.createTextConfig(textStyle, labelModel, opt, false);
 
         preFetchFromExtra(userProps, itemStyle);
         itemStyle = convertToEC4StyleForCustomSerise(itemStyle, textStyle, textConfig);
@@ -1415,7 +1416,7 @@ function makeRenderItem(
 
         let itemStyle = getItemStyleModel(dataIndexInside, EMPHASIS).getItemStyle();
         const labelModel = getLabelModel(dataIndexInside, EMPHASIS);
-        const textStyle = SetLabelStyleOpt.createTextStyle(labelModel, null, null, true, true);
+        const textStyle = labelStyleHelper.createTextStyle(labelModel, null, null, true, true);
         textStyle.text = labelModel.getShallow('show')
             ? retrieve3(
                 customSeries.getFormattedLabel(dataIndexInside, EMPHASIS),
@@ -1423,7 +1424,7 @@ function makeRenderItem(
                 getDefaultLabel(data, dataIndexInside)
             )
             : null;
-        const textConfig = SetLabelStyleOpt.createTextConfig(textStyle, labelModel, null, true);
+        const textConfig = labelStyleHelper.createTextConfig(textStyle, labelModel, null, true);
 
         preFetchFromExtra(userProps, itemStyle);
         itemStyle = convertToEC4StyleForCustomSerise(itemStyle, textStyle, textConfig);
@@ -1502,7 +1503,7 @@ function makeRenderItem(
     function font(
         opt: Parameters<typeof graphicUtil.getFont>[0]
     ): ReturnType<typeof graphicUtil.getFont> {
-        return SetLabelStyleOpt.getFont(opt, ecModel);
+        return labelStyleHelper.getFont(opt, ecModel);
     }
 }
 
diff --git a/src/chart/funnel/FunnelView.ts b/src/chart/funnel/FunnelView.ts
index 25fa13c..a4d0822 100644
--- a/src/chart/funnel/FunnelView.ts
+++ b/src/chart/funnel/FunnelView.ts
@@ -18,6 +18,7 @@
 */
 
 import * as graphic from '../../util/graphic';
+import { setStatesStylesFromModel, enableHoverEmphasis } from '../../util/states';
 import ChartView from '../../view/Chart';
 import FunnelSeriesModel, {FunnelDataItemOption} from './FunnelSeries';
 import GlobalModel from '../../model/Global';
@@ -84,11 +85,11 @@ class FunnelPiece extends graphic.Polygon {
             }, seriesModel, idx);
         }
 
-        graphic.setStatesStylesFromModel(polygon, itemModel);
+        setStatesStylesFromModel(polygon, itemModel);
 
         this._updateLabel(data, idx);
 
-        graphic.enableHoverEmphasis(this);
+        enableHoverEmphasis(this);
     }
 
     _updateLabel(data: List, idx: number) {
diff --git a/src/chart/gauge/GaugeView.ts b/src/chart/gauge/GaugeView.ts
index eb09903..5cb812f 100644
--- a/src/chart/gauge/GaugeView.ts
+++ b/src/chart/gauge/GaugeView.ts
@@ -19,6 +19,7 @@
 
 import PointerPath from './PointerPath';
 import * as graphic from '../../util/graphic';
+import { setStatesStylesFromModel, enableHoverEmphasis } from '../../util/states';
 import {createTextStyle} from '../../label/labelStyle';
 import ChartView from '../../view/Chart';
 import {parsePercent, round, linearMap} from '../../util/number';
@@ -385,8 +386,8 @@ class GaugeView extends ChartView {
                 ));
             }
 
-            graphic.setStatesStylesFromModel(pointer, itemModel);
-            graphic.enableHoverEmphasis(pointer);
+            setStatesStylesFromModel(pointer, itemModel);
+            enableHoverEmphasis(pointer);
         });
 
         this._data = data;
diff --git a/src/chart/heatmap/HeatmapView.ts b/src/chart/heatmap/HeatmapView.ts
index 36b1382..795f000 100644
--- a/src/chart/heatmap/HeatmapView.ts
+++ b/src/chart/heatmap/HeatmapView.ts
@@ -19,6 +19,7 @@
 
 import {__DEV__} from '../../config';
 import * as graphic from '../../util/graphic';
+import { enableHoverEmphasis } from '../../util/states';
 import HeatmapLayer from './HeatmapLayer';
 import * as zrUtil from 'zrender/src/core/util';
 import ChartView from '../../view/Chart';
@@ -270,7 +271,7 @@ class HeatmapView extends ChartView {
             rect.ensureState('blur').style = blurStyle;
             rect.ensureState('select').style = selectStyle;
 
-            graphic.enableHoverEmphasis(rect);
+            enableHoverEmphasis(rect);
 
             rect.incremental = incremental;
             // PENDING
diff --git a/src/chart/helper/EffectSymbol.ts b/src/chart/helper/EffectSymbol.ts
index 57f6618..0615d88 100644
--- a/src/chart/helper/EffectSymbol.ts
+++ b/src/chart/helper/EffectSymbol.ts
@@ -19,7 +19,8 @@
 
 import * as zrUtil from 'zrender/src/core/util';
 import {createSymbol} from '../../util/symbol';
-import {Group, Path, enterEmphasis, leaveEmphasis, enableHoverEmphasis} from '../../util/graphic';
+import {Group, Path} from '../../util/graphic';
+import { enterEmphasis, leaveEmphasis, enableHoverEmphasis } from '../../util/states';
 import {parsePercent} from '../../util/number';
 import SymbolClz from './Symbol';
 import List from '../../data/List';
diff --git a/src/chart/helper/Line.ts b/src/chart/helper/Line.ts
index b13d510..6e9318b 100644
--- a/src/chart/helper/Line.ts
+++ b/src/chart/helper/Line.ts
@@ -22,6 +22,7 @@ import * as vector from 'zrender/src/core/vector';
 import * as symbolUtil from '../../util/symbol';
 import ECLinePath from './LinePath';
 import * as graphic from '../../util/graphic';
+import { enableHoverEmphasis, enterEmphasis, leaveEmphasis } from '../../util/states';
 import {createTextStyle} from '../../label/labelStyle';
 import {round} from '../../util/number';
 import List from '../../data/List';
@@ -307,15 +308,15 @@ class Line extends graphic.Group {
 
         label.ignore = !showLabel && !hoverShowLabel;
 
-        graphic.enableHoverEmphasis(this);
+        enableHoverEmphasis(this);
     }
 
     highlight() {
-        graphic.enterEmphasis(this);
+        enterEmphasis(this);
     }
 
     downplay() {
-        graphic.leaveEmphasis(this);
+        leaveEmphasis(this);
     }
 
     updateLayout(lineData: List, idx: number) {
diff --git a/src/chart/helper/Polyline.ts b/src/chart/helper/Polyline.ts
index 8527dd6..4b3d011 100644
--- a/src/chart/helper/Polyline.ts
+++ b/src/chart/helper/Polyline.ts
@@ -18,6 +18,7 @@
 */
 
 import * as graphic from '../../util/graphic';
+import { enableHoverEmphasis } from '../../util/states';
 import type { LineDrawSeriesScope, LineDrawModelOption } from './LineDraw';
 import type List from '../../data/List';
 
@@ -72,7 +73,7 @@ class Polyline extends graphic.Group {
         const lineEmphasisState = line.ensureState('emphasis');
         lineEmphasisState.style = hoverLineStyle;
 
-        graphic.enableHoverEmphasis(this);
+        enableHoverEmphasis(this);
     };
 
     updateLayout(lineData: List, idx: number) {
diff --git a/src/chart/helper/Symbol.ts b/src/chart/helper/Symbol.ts
index 41963be..0e33484 100644
--- a/src/chart/helper/Symbol.ts
+++ b/src/chart/helper/Symbol.ts
@@ -19,6 +19,7 @@
 
 import {createSymbol} from '../../util/symbol';
 import * as graphic from '../../util/graphic';
+import { enterEmphasis, leaveEmphasis, enableHoverEmphasis } from '../../util/states';
 import {parsePercent} from '../../util/number';
 import {getDefaultLabel} from './labelHelper';
 import List from '../../data/List';
@@ -109,14 +110,14 @@ class Symbol extends graphic.Group {
      * Highlight symbol
      */
     highlight() {
-        graphic.enterEmphasis(this.childAt(0));
+        enterEmphasis(this.childAt(0));
     }
 
     /**
      * Downplay symbol
      */
     downplay() {
-        graphic.leaveEmphasis(this.childAt(0));
+        leaveEmphasis(this.childAt(0));
     }
 
     /**
@@ -294,7 +295,7 @@ class Symbol extends graphic.Group {
             this.states.emphasis = null;
         }
 
-        graphic.enableHoverEmphasis(this);
+        enableHoverEmphasis(this);
     }
 
     setSymbolScale(scale: number) {
diff --git a/src/chart/parallel/ParallelView.ts b/src/chart/parallel/ParallelView.ts
index 659b671..b7ba339 100644
--- a/src/chart/parallel/ParallelView.ts
+++ b/src/chart/parallel/ParallelView.ts
@@ -18,6 +18,7 @@
 */
 
 import * as graphic from '../../util/graphic';
+import { setStatesStylesFromModel } from '../../util/states';
 import ChartView from '../../view/Chart';
 import List from '../../data/List';
 import ParallelSeriesModel, { ParallelSeriesDataItemOption } from './ParallelSeries';
@@ -203,7 +204,7 @@ function updateElCommon(
     seriesScope.smooth && (el.shape.smooth = seriesScope.smooth);
 
     const itemModel = data.getItemModel<ParallelSeriesDataItemOption>(dataIndex);
-    graphic.setStatesStylesFromModel(el, itemModel, 'lineStyle', 'getLineStyle');
+    setStatesStylesFromModel(el, itemModel, 'lineStyle', 'getLineStyle');
 }
 
 // function simpleDiff(oldData, newData, dimensions) {
diff --git a/src/chart/pie/PieView.ts b/src/chart/pie/PieView.ts
index 7d038e2..0284831 100644
--- a/src/chart/pie/PieView.ts
+++ b/src/chart/pie/PieView.ts
@@ -21,6 +21,7 @@
 
 import { extend, curry } from 'zrender/src/core/util';
 import * as graphic from '../../util/graphic';
+import { setStatesStylesFromModel, enableHoverEmphasis } from '../../util/states';
 import ChartView from '../../view/Chart';
 import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../ExtensionAPI';
@@ -121,7 +122,7 @@ class PiePiece extends graphic.Sector {
         }
 
         sector.useStyle(data.getItemVisual(idx, 'style'));
-        graphic.setStatesStylesFromModel(sector, itemModel);
+        setStatesStylesFromModel(sector, itemModel);
 
         const midAngle = (layout.startAngle + layout.endAngle) / 2;
         const offset = seriesModel.get('selectedOffset');
@@ -156,7 +157,7 @@ class PiePiece extends graphic.Sector {
             y: dy
         });
 
-        graphic.enableHoverEmphasis(this);
+        enableHoverEmphasis(this);
 
         // State will be set after all rendered in the pipeline.
         (sector as ECElement).selected = seriesModel.isSelected(data.getName(idx));
diff --git a/src/chart/radar/RadarView.ts b/src/chart/radar/RadarView.ts
index 39ff302..967beec 100644
--- a/src/chart/radar/RadarView.ts
+++ b/src/chart/radar/RadarView.ts
@@ -18,6 +18,7 @@
 */
 
 import * as graphic from '../../util/graphic';
+import { setStatesStylesFromModel, enableHoverEmphasis } from '../../util/states';
 import * as zrUtil from 'zrender/src/core/util';
 import * as symbolUtil from '../../util/symbol';
 import ChartView from '../../view/Chart';
@@ -197,8 +198,8 @@ class RadarView extends ChartView {
                 )
             );
 
-            graphic.setStatesStylesFromModel(polyline, itemModel, 'lineStyle');
-            graphic.setStatesStylesFromModel(polygon, itemModel, 'areaStyle');
+            setStatesStylesFromModel(polyline, itemModel, 'lineStyle');
+            setStatesStylesFromModel(polygon, itemModel, 'areaStyle');
 
             const areaStyleModel = itemModel.getModel('areaStyle');
             const polygonIgnore = areaStyleModel.isEmpty() && areaStyleModel.parentModel.isEmpty();
@@ -243,7 +244,7 @@ class RadarView extends ChartView {
                 );
             });
 
-            graphic.enableHoverEmphasis(itemGroup);
+            enableHoverEmphasis(itemGroup);
         });
 
         this._data = data;
diff --git a/src/chart/sankey/SankeyView.ts b/src/chart/sankey/SankeyView.ts
index 9c6d82a..7e3dd6f 100644
--- a/src/chart/sankey/SankeyView.ts
+++ b/src/chart/sankey/SankeyView.ts
@@ -18,6 +18,7 @@
 */
 
 import * as graphic from '../../util/graphic';
+import { enterEmphasis, leaveEmphasis, enableHoverEmphasis } from '../../util/states';
 import * as zrUtil from 'zrender/src/core/util';
 import { LayoutOrient, Payload } from '../../util/types';
 import { PathProps } from 'zrender/src/graphic/Path';
@@ -117,11 +118,11 @@ class SankeyPath extends graphic.Path<SankeyPathProps> {
     }
 
     highlight() {
-        graphic.enterEmphasis(this);
+        enterEmphasis(this);
     }
 
     downplay() {
-        graphic.leaveEmphasis(this);
+        leaveEmphasis(this);
     }
 }
 
@@ -230,7 +231,7 @@ class SankeyView extends ChartView {
                     break;
             }
 
-            graphic.enableHoverEmphasis(
+            enableHoverEmphasis(
                 curve,
                 edgeModel.getModel(['emphasis', 'lineStyle']).getItemStyle()
             );
@@ -272,7 +273,7 @@ class SankeyView extends ChartView {
 
             rect.setStyle('fill', node.getVisual('color'));
 
-            graphic.enableHoverEmphasis(rect, hoverStyle);
+            enableHoverEmphasis(rect, hoverStyle);
 
             group.add(rect);
 
@@ -305,11 +306,11 @@ class SankeyView extends ChartView {
             }
 
             el.highlight = function () {
-                graphic.enterEmphasis(this);
+                enterEmphasis(this);
             };
 
             el.downplay = function () {
-                graphic.leaveEmphasis(this);
+                leaveEmphasis(this);
             };
 
             el.focusNodeAdjHandler && el.off('mouseover', el.focusNodeAdjHandler);
diff --git a/src/chart/sunburst/SunburstPiece.ts b/src/chart/sunburst/SunburstPiece.ts
index 70aaaa8..0ad0e63 100644
--- a/src/chart/sunburst/SunburstPiece.ts
+++ b/src/chart/sunburst/SunburstPiece.ts
@@ -19,6 +19,7 @@
 
 import * as zrUtil from 'zrender/src/core/util';
 import * as graphic from '../../util/graphic';
+import { enableHoverEmphasis } from '../../util/states';
 import {createTextStyle} from '../../label/labelStyle';
 import { TreeNode } from '../../data/Tree';
 import SunburstSeriesModel, { SunburstSeriesNodeItemOption, SunburstSeriesOption } from './SunburstSeries';
@@ -154,7 +155,7 @@ class SunburstPiece extends graphic.Sector {
         this._seriesModel = seriesModel || this._seriesModel;
         this._ecModel = ecModel || this._ecModel;
 
-        graphic.enableHoverEmphasis(this);
+        enableHoverEmphasis(this);
     }
 
     onEmphasis(highlightPolicy: AllPropTypes<typeof NodeHighlightPolicy>) {
diff --git a/src/chart/themeRiver/ThemeRiverView.ts b/src/chart/themeRiver/ThemeRiverView.ts
index 0617eab..a8af6eb 100644
--- a/src/chart/themeRiver/ThemeRiverView.ts
+++ b/src/chart/themeRiver/ThemeRiverView.ts
@@ -19,6 +19,7 @@
 
 import {ECPolygon} from '../line/poly';
 import * as graphic from '../../util/graphic';
+import { setStatesStylesFromModel, enableHoverEmphasis } from '../../util/states';
 import {createTextStyle} from '../../label/labelStyle';
 import {bind, extend} from 'zrender/src/core/util';
 import DataDiffer from '../../data/DataDiffer';
@@ -158,9 +159,9 @@ class ThemeRiverView extends ChartView {
 
             polygon.useStyle(style);
 
-            graphic.setStatesStylesFromModel(polygon, seriesModel);
+            setStatesStylesFromModel(polygon, seriesModel);
 
-            graphic.enableHoverEmphasis(polygon);
+            enableHoverEmphasis(polygon);
         }
 
         this._layersSeries = layersSeries;
diff --git a/src/chart/treemap/TreemapView.ts b/src/chart/treemap/TreemapView.ts
index 0712c7f..f6ce7c8 100644
--- a/src/chart/treemap/TreemapView.ts
+++ b/src/chart/treemap/TreemapView.ts
@@ -19,6 +19,7 @@
 
 import {bind, each, indexOf, curry, extend, retrieve} from 'zrender/src/core/util';
 import * as graphic from '../../util/graphic';
+import { isHighDownDispatcher, setAsHighDownDispatcher, enableElementHoverEmphasis } from '../../util/states';
 import DataDiffer from '../../data/DataDiffer';
 import * as helper from '../helper/treeHelper';
 import Breadcrumb from './Breadcrumb';
@@ -794,11 +795,11 @@ function renderNode(
         // Because of the implementation about "traverse" in graphic hover style, we
         // can not set hover listener on the "group" of non-leaf node. Otherwise the
         // hover event from the descendents will be listenered.
-        if (graphic.isHighDownDispatcher(group)) {
-            graphic.setAsHighDownDispatcher(group, false);
+        if (isHighDownDispatcher(group)) {
+            setAsHighDownDispatcher(group, false);
         }
         if (bg) {
-            graphic.setAsHighDownDispatcher(bg, true);
+            setAsHighDownDispatcher(bg, true);
             // Only for enabling highlight/downplay.
             data.setItemGraphicEl(thisNode.dataIndex, bg);
         }
@@ -807,10 +808,10 @@ function renderNode(
         const content = giveGraphic('content', Rect, depth, Z_CONTENT);
         content && renderContent(group, content);
 
-        if (bg && graphic.isHighDownDispatcher(bg)) {
-            graphic.setAsHighDownDispatcher(bg, false);
+        if (bg && isHighDownDispatcher(bg)) {
+            setAsHighDownDispatcher(bg, false);
         }
-        graphic.setAsHighDownDispatcher(group, true);
+        setAsHighDownDispatcher(group, true);
         // Only for enabling highlight/downplay.
         data.setItemGraphicEl(thisNode.dataIndex, group);
     }
@@ -865,7 +866,7 @@ function renderNode(
             bg.ensureState('emphasis').style = emphasisStyle;
             bg.ensureState('blur').style = blurStyle;
             bg.ensureState('select').style = selectStyle;
-            graphic.enableElementHoverEmphasis(bg);
+            enableElementHoverEmphasis(bg);
         }
 
         group.add(bg);
@@ -910,7 +911,7 @@ function renderNode(
             content.ensureState('emphasis').style = emphasisStyle;
             content.ensureState('blur').style = blurStyle;
             content.ensureState('select').style = selectStyle;
-            graphic.enableElementHoverEmphasis(content);
+            enableElementHoverEmphasis(content);
         }
 
         group.add(content);
diff --git a/src/component/helper/MapDraw.ts b/src/component/helper/MapDraw.ts
index 77c4251..d518d64 100644
--- a/src/component/helper/MapDraw.ts
+++ b/src/component/helper/MapDraw.ts
@@ -22,6 +22,7 @@ import RoamController from './RoamController';
 import * as roamHelper from '../../component/helper/roamHelper';
 import {onIrrelevantElement} from '../../component/helper/cursorHelper';
 import * as graphic from '../../util/graphic';
+import { enableHoverEmphasis } from '../../util/states';
 import geoSourceManager from '../../coord/geo/geoSourceManager';
 import {getUID} from '../../util/component';
 import ExtensionAPI from '../../ExtensionAPI';
@@ -358,7 +359,7 @@ class MapDraw {
 
             // @ts-ignore FIXME:TS fix the "compatible with each other"?
             regionGroup.highDownSilentOnTouch = !!mapOrGeoModel.get('selectedMode');
-            graphic.enableHoverEmphasis(regionGroup);
+            enableHoverEmphasis(regionGroup);
 
             regionsGroup.add(regionGroup);
         });
diff --git a/src/component/legend/LegendView.ts b/src/component/legend/LegendView.ts
index 6805e00..3edf9b2 100644
--- a/src/component/legend/LegendView.ts
+++ b/src/component/legend/LegendView.ts
@@ -21,6 +21,7 @@ import {__DEV__} from '../../config';
 import * as zrUtil from 'zrender/src/core/util';
 import {createSymbol} from '../../util/symbol';
 import * as graphic from '../../util/graphic';
+import { enableHoverEmphasis } from '../../util/states';
 import {setLabelStyle, createTextStyle} from '../../label/labelStyle';
 import {makeBackground} from '../helper/listComponent';
 import * as layoutUtil from '../../util/layout';
@@ -319,7 +320,7 @@ class LegendView extends ComponentView {
                     defaultText: selectorItem.title
                 }
             );
-            graphic.enableHoverEmphasis(labelText);
+            enableHoverEmphasis(labelText);
         });
     }
 
@@ -455,7 +456,7 @@ class LegendView extends ComponentView {
 
         this.getContentGroup().add(itemGroup);
 
-        graphic.enableHoverEmphasis(itemGroup);
+        enableHoverEmphasis(itemGroup);
 
         // @ts-ignore
         itemGroup.__legendDataIndex = dataIndex;
diff --git a/src/component/marker/MarkAreaView.ts b/src/component/marker/MarkAreaView.ts
index 383b31f..b9f021c 100644
--- a/src/component/marker/MarkAreaView.ts
+++ b/src/component/marker/MarkAreaView.ts
@@ -23,6 +23,7 @@ import * as colorUtil from 'zrender/src/tool/color';
 import List from '../../data/List';
 import * as numberUtil from '../../util/number';
 import * as graphic from '../../util/graphic';
+import { enableHoverEmphasis } from '../../util/states';
 import * as markerHelper from './markerHelper';
 import MarkerView from './MarkerView';
 import { retrieve, mergeAll, map, defaults, curry, filter, HashMap } from 'zrender/src/core/util';
@@ -306,7 +307,7 @@ class MarkAreaView extends MarkerView {
                 }
             );
 
-            graphic.enableHoverEmphasis(polygon, itemModel.getModel(['emphasis', 'itemStyle']).getItemStyle());
+            enableHoverEmphasis(polygon, itemModel.getModel(['emphasis', 'itemStyle']).getItemStyle());
 
             graphic.getECData(polygon).dataModel = maModel;
         });
diff --git a/src/component/timeline/SliderTimelineView.ts b/src/component/timeline/SliderTimelineView.ts
index 792654f..8caf8f9 100644
--- a/src/component/timeline/SliderTimelineView.ts
+++ b/src/component/timeline/SliderTimelineView.ts
@@ -20,6 +20,7 @@
 import BoundingRect, { RectLike } from 'zrender/src/core/BoundingRect';
 import * as matrix from 'zrender/src/core/matrix';
 import * as graphic from '../../util/graphic';
+import { enableHoverEmphasis } from '../../util/states';
 import { createTextStyle } from '../../label/labelStyle';
 import * as layout from '../../util/layout';
 import TimelineView from './TimelineView';
@@ -399,7 +400,7 @@ class SliderTimelineView extends TimelineView {
                 onclick: bind(this._changeTimeline, this, value)
             };
             const el = giveSymbol(itemModel, itemStyleModel, group, symbolOpt);
-            graphic.enableHoverEmphasis(el, hoverStyleModel.getItemStyle());
+            enableHoverEmphasis(el, hoverStyleModel.getItemStyle());
 
             const ecData = graphic.getECData(el);
             if (itemModel.get('tooltip')) {
@@ -453,7 +454,7 @@ class SliderTimelineView extends TimelineView {
             });
 
             group.add(textEl);
-            graphic.enableHoverEmphasis(
+            enableHoverEmphasis(
                 textEl, createTextStyle(hoverLabelModel)
             );
 
@@ -514,7 +515,7 @@ class SliderTimelineView extends TimelineView {
             };
             const btn = makeControlIcon(timelineModel, iconPath, rect, opt);
             group.add(btn);
-            graphic.enableHoverEmphasis(btn, hoverStyle);
+            enableHoverEmphasis(btn, hoverStyle);
         }
     }
 
diff --git a/src/component/toolbox/ToolboxView.ts b/src/component/toolbox/ToolboxView.ts
index 27e6829..40f2060 100644
--- a/src/component/toolbox/ToolboxView.ts
+++ b/src/component/toolbox/ToolboxView.ts
@@ -20,6 +20,7 @@
 import * as zrUtil from 'zrender/src/core/util';
 import * as textContain from 'zrender/src/contain/text';
 import * as graphic from '../../util/graphic';
+import { enterEmphasis, leaveEmphasis } from '../../util/states';
 import Model from '../../model/Model';
 import DataDiffer from '../../data/DataDiffer';
 import * as listComponentHelper from '../helper/listComponent';
@@ -144,7 +145,7 @@ class ToolboxView extends ComponentView {
                 option.iconStatus = option.iconStatus || {};
                 option.iconStatus[iconName] = status;
                 if (iconPaths[iconName]) {
-                    graphic[status === 'emphasis' ? 'enterEmphasis' : 'leaveEmphasis'](iconPaths[iconName]);
+                    (status === 'emphasis' ? enterEmphasis : leaveEmphasis)(iconPaths[iconName]);
                 }
             };
 
@@ -262,19 +263,15 @@ class ToolboxView extends ComponentView {
 
                     // Use enterEmphasis and leaveEmphasis provide by ec.
                     // There are flags managed by the echarts.
-                    graphic.enterEmphasis(this);
+                    enterEmphasis(this);
                 })
                 .on('mouseout', function () {
                     if (featureModel.get(['iconStatus', iconName]) !== 'emphasis') {
-                        graphic.leaveEmphasis(this);
+                        leaveEmphasis(this);
                     }
                     textContent.hide();
                 });
-
-                graphic[
-                    featureModel.get(['iconStatus', iconName]) === 'emphasis'
-                    ? 'enterEmphasis' : 'leaveEmphasis'
-                ](path);
+                (featureModel.get(['iconStatus', iconName]) === 'emphasis' ? enterEmphasis : leaveEmphasis)(path);
 
                 group.add(path);
                 (path as graphic.Path).on('click', zrUtil.bind(
diff --git a/src/echarts.ts b/src/echarts.ts
index 79649f3..74103ef 100644
--- a/src/echarts.ts
+++ b/src/echarts.ts
@@ -38,6 +38,7 @@ import SeriesModel, { SeriesModelConstructor } from './model/Series';
 import ComponentView, {ComponentViewConstructor} from './view/Component';
 import ChartView, {ChartViewConstructor} from './view/Chart';
 import * as graphic from './util/graphic';
+import { enterEmphasisWhenMouseOver, leaveEmphasisWhenMouseOut, isHighDownDispatcher } from './util/states';
 import * as modelUtil from './util/model';
 import {throttle} from './util/throttle';
 import {seriesStyleTask, dataStyleTask, dataColorPaletteTask} from './visual/style';
@@ -1672,7 +1673,7 @@ class ECharts extends Eventful {
 
         bindMouseEvent = function (zr: zrender.ZRenderType, ecIns: ECharts): void {
             function getHighDownDispatcher(target: Element) {
-                while (target && !graphic.isHighDownDispatcher(target)) {
+                while (target && !isHighDownDispatcher(target)) {
                     target = target.parent;
                 }
                 return target;
@@ -1681,13 +1682,13 @@ class ECharts extends Eventful {
                 const dispatcher = getHighDownDispatcher(e.target);
                 if (dispatcher) {
                     markStatusToUpdate(ecIns);
-                    graphic.enterEmphasisWhenMouseOver(dispatcher, e);
+                    enterEmphasisWhenMouseOver(dispatcher, e);
                 }
             }).on('mouseout', function (e) {
                 const dispatcher = getHighDownDispatcher(e.target);
                 if (dispatcher) {
                     markStatusToUpdate(ecIns);
-                    graphic.leaveEmphasisWhenMouseOut(dispatcher, e);
+                    leaveEmphasisWhenMouseOut(dispatcher, e);
                 }
             });
         };
@@ -1831,14 +1832,12 @@ class ECharts extends Eventful {
                     }
                 }
 
-                if (el.selected || el.highlighted) {
-                    // Only use states when it's exists.
-                    if (el.selected && el.states.select) {
-                        newStates.push('select');
-                    }
-                    if (el.highlighted && el.states.emphasis) {
-                        newStates.push('emphasis');
-                    }
+                // Only use states when it's exists.
+                if (el.selected && el.states.select) {
+                    newStates.push('select');
+                }
+                if (el.highlighted && el.states.emphasis) {
+                    newStates.push('emphasis');
                 }
                 else if (el.blurred && el.states.blur) {
                     newStates.push('blur');
diff --git a/src/util/graphic.ts b/src/util/graphic.ts
index d5e1500..bec255b 100644
--- a/src/util/graphic.ts
+++ b/src/util/graphic.ts
@@ -18,7 +18,6 @@
 */
 
 import * as pathTool from 'zrender/src/tool/path';
-import * as colorTool from 'zrender/src/tool/color';
 import * as matrix from 'zrender/src/core/matrix';
 import * as vector from 'zrender/src/core/vector';
 import Path, { PathProps } from 'zrender/src/graphic/Path';
@@ -45,31 +44,27 @@ import Point from 'zrender/src/core/Point';
 import IncrementalDisplayable from 'zrender/src/graphic/IncrementalDisplayable';
 import * as subPixelOptimizeUtil from 'zrender/src/graphic/helper/subPixelOptimize';
 import { Dictionary } from 'zrender/src/core/types';
-import LRU from 'zrender/src/core/LRU';
-import Displayable, { DisplayableProps, DisplayableState } from 'zrender/src/graphic/Displayable';
-import { PatternObject } from 'zrender/src/graphic/Pattern';
-import { GradientObject } from 'zrender/src/graphic/Gradient';
-import Element, { ElementEvent, ElementProps } from 'zrender/src/Element';
+import Displayable, { DisplayableProps } from 'zrender/src/graphic/Displayable';
+import Element, { ElementProps } from 'zrender/src/Element';
 import Model from '../model/Model';
 import {
     AnimationOptionMixin,
     LabelOption,
     AnimationDelayCallbackParam,
     DisplayState,
-    ECElement,
     ZRRectLike,
     ColorString,
     DataModel,
     ECEventData,
     ZRStyleProps,
-    ParsedValue} from './types';
+    ParsedValue,
+    BlurScope} from './types';
 import { makeInner } from './model';
 import {
     extend,
     isArrayLike,
     map,
     defaults,
-    indexOf,
     isObject
 } from 'zrender/src/core/util';
 import * as numberUtil from './number';
@@ -86,12 +81,10 @@ export const EMPTY_OBJ = {};
 
 export const Z2_EMPHASIS_LIFT = 10;
 
-const EMPHASIS = 'emphasis';
-const NORMAL = 'normal';
+export const EMPHASIS = 'emphasis';
+export const NORMAL = 'normal';
 
-// Reserve 0 as default.
-let _highlightNextDigit = 1;
-const _highlightKeyMap: Dictionary<number> = {};
+export const _highlightKeyMap: Dictionary<number> = {};
 
 const _customShapeMap: Dictionary<{ new(): Path }> = {};
 
@@ -107,8 +100,8 @@ type ExtendedProps = {
 
     __highDownDispatcher: boolean
 };
-type ExtendedElement = Element & ExtendedProps;
-type ExtendedDisplayable = Displayable & ExtendedProps;
+export type ExtendedElement = Element & ExtendedProps;
+export type ExtendedDisplayable = Displayable & ExtendedProps;
 
 export type TextCommonParams = {
     /**
@@ -341,300 +334,6 @@ export function subPixelOptimizeRect(param: {
 export const subPixelOptimize = subPixelOptimizeUtil.subPixelOptimize;
 
 
-function hasFillOrStroke(fillOrStroke: string | PatternObject | GradientObject) {
-    return fillOrStroke != null && fillOrStroke !== 'none';
-}
-
-// Most lifted color are duplicated.
-const liftedColorCache = new LRU<string>(100);
-
-function liftColor(color: string): string {
-    if (typeof color !== 'string') {
-        return color;
-    }
-    let liftedColor = liftedColorCache.get(color);
-    if (!liftedColor) {
-        liftedColor = colorTool.lift(color, -0.1);
-        liftedColorCache.put(color, liftedColor);
-    }
-    return liftedColor;
-}
-
-function singleEnterEmphasis(el: Element) {
-    // Only mark the flag.
-    // States will be applied in the echarts.ts in next frame.
-    (el as ECElement).highlighted = true;
-}
-
-
-function singleLeaveEmphasis(el: Element) {
-    // Only mark the flag.
-    // States will be applied in the echarts.ts in next frame.
-    (el as ECElement).highlighted = false;
-}
-
-function updateElementState<T>(
-    el: ExtendedElement,
-    updater: (this: void, el: Element, commonParam?: T) => void,
-    commonParam?: T
-) {
-    // If root is group, also enter updater for `onStateChange`.
-    let fromState: DisplayState = NORMAL;
-    let toState: DisplayState = NORMAL;
-    let trigger;
-    // See the rule of `onStateChange` on `graphic.setAsHighDownDispatcher`.
-    (el as ECElement).highlighted && (fromState = EMPHASIS, trigger = true);
-    updater(el, commonParam);
-    (el as ECElement).highlighted && (toState = EMPHASIS, trigger = true);
-    trigger && el.__onStateChange && el.__onStateChange(fromState, toState);
-}
-
-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);
-    });
-}
-
-/**
- * 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();
-    }
-}
-
-function elementStateProxy(this: Displayable, stateName: string): DisplayableState {
-    let state = this.states[stateName];
-    if (stateName === 'emphasis' && this.style) {
-        const hasEmphasis = indexOf(this.currentStates, stateName) >= 0;
-        if (!(this instanceof ZRText)) {
-            const currentFill = this.style.fill;
-            const currentStroke = this.style.stroke;
-            if (currentFill || currentStroke) {
-                let fromState: {fill: ColorString, stroke: ColorString};
-                if (!hasEmphasis) {
-                    fromState = {fill: currentFill, stroke: currentStroke};
-                    for (let i = 0; i < this.animators.length; i++) {
-                        const animator = this.animators[i];
-                        if (animator.__fromStateTransition
-                            // Dont consider the animation to emphasis state.
-                            && animator.__fromStateTransition.indexOf('emphasis') < 0
-                            && animator.targetName === 'style'
-                        ) {
-                            animator.saveFinalToTarget(fromState, ['fill', 'stroke']);
-                        }
-                    }
-                }
-
-                state = state || {};
-                // Apply default color lift
-                let emphasisStyle = state.style || {};
-                let cloned = false;
-                if (!hasFillOrStroke(emphasisStyle.fill)) {
-                    cloned = true;
-                    // Not modify the original value.
-                    state = extend({}, state);
-                    emphasisStyle = extend({}, emphasisStyle);
-                    // Already being applied 'emphasis'. DON'T lift color multiple times.
-                    emphasisStyle.fill = hasEmphasis ? currentFill : liftColor(fromState.fill);
-                }
-                if (!hasFillOrStroke(emphasisStyle.stroke)) {
-                    if (!cloned) {
-                        state = extend({}, state);
-                        emphasisStyle = extend({}, emphasisStyle);
-                    }
-                    emphasisStyle.stroke = hasEmphasis ? currentStroke : liftColor(fromState.stroke);
-                }
-
-                state.style = emphasisStyle;
-            }
-        }
-        if (state) {
-            const z2EmphasisLift = (this as ECElement).z2EmphasisLift;
-            // TODO Share with textContent?
-            state.z2 = this.z2 + (z2EmphasisLift != null ? z2EmphasisLift : Z2_EMPHASIS_LIFT);
-        }
-    }
-
-    return state;
-}
-
-/**FI
- * Set hover style (namely "emphasis style") of element.
- * @param el Should not be `zrender/graphic/Group`.
- */
-export function enableElementHoverEmphasis(el: Displayable) {
-    el.stateProxy = elementStateProxy;
-    const textContent = el.getTextContent();
-    const textGuide = el.getTextGuideLine();
-    if (textContent) {
-        textContent.stateProxy = elementStateProxy;
-    }
-    if (textGuide) {
-        textGuide.stateProxy = elementStateProxy;
-    }
-}
-
-export function enterEmphasisWhenMouseOver(el: Element, e: ElementEvent) {
-    !shouldSilent(el, e)
-        // "emphasis" event highlight has higher priority than mouse highlight.
-        && !(el as ExtendedElement).__highByOuter
-        && 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
-        && traverseUpdateState((el as ExtendedElement), singleLeaveEmphasis);
-}
-
-export function enterEmphasis(el: Element, highlightDigit?: number) {
-    (el as ExtendedElement).__highByOuter |= 1 << (highlightDigit || 0);
-    traverseUpdateState((el as ExtendedElement), singleEnterEmphasis);
-}
-
-export function leaveEmphasis(el: Element, highlightDigit?: number) {
-    !((el as ExtendedElement).__highByOuter &= ~(1 << (highlightDigit || 0)))
-        && traverseUpdateState((el as ExtendedElement), singleLeaveEmphasis);
-}
-
-function shouldSilent(el: Element, e: ElementEvent) {
-    return (el as ExtendedElement).__highDownSilentOnTouch && e.zrByTouch;
-}
-
-/**
- * Enable the function that mouseover will trigger the emphasis state.
- *
- * NOTICE:
- * Call the method for a "root" element once. Do not call it for each descendants.
- * If the descendants elemenets of a group has itself hover style different from the
- * root group, we can simply mount the style on `el.states.emphasis` for them, but should
- * not call this method for them.
- */
-export function enableHoverEmphasis(el: Element) {
-    setAsHighDownDispatcher(el, true);
-    traverseUpdateState(el as ExtendedElement, enableElementHoverEmphasis);
-}
-
-const OTHER_STATES = ['emphasis', 'blur', 'select'] as const;
-
-const styleGetterMap: Dictionary<'getItemStyle' | 'getLineStyle' | 'getAreaStyle'> = {
-    itemStyle: 'getItemStyle',
-    lineStyle: 'getLineStyle',
-    areaStyle: 'getAreaStyle'
-};
-/**
- * Set emphasis/blur/selected states of element.
- */
-export function setStatesStylesFromModel(
-    el: Displayable,
-    itemModel: Model<Partial<Record<'emphasis' | 'blur' | 'select', any>>>,
-    styleType?: string,   // default itemStyle
-    getterType?: 'getItemStyle' | 'getLineStyle' | 'getAreaStyle'
-) {
-    styleType = styleType || 'itemStyle';
-    for (let i = 0; i < OTHER_STATES.length; i++) {
-        const stateName = OTHER_STATES[i];
-        const model = itemModel.getModel([stateName, styleType]);
-        const state = el.ensureState(stateName);
-        // Let it throw error if getterType is not found.
-        state.style = model[getterType || styleGetterMap[styleType]]();
-    }
-}
-
-/**
- * @param {module:zrender/Element} el
- * @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, `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.
- *        @this {string} This dispatcher `el`.
- *        @param {string} fromState Can be "normal" or "emphasis".
- *               `fromState` might equal to `toState`,
- *               for example, when this method is called when `el` is
- *               on "emphasis" state.
- *        @param {string} toState Can be "normal" or "emphasis".
- *
- *        FIXME
- *        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
- *        mantained on elements.
- *
- * @param {boolean} [el.highDownSilentOnTouch=false]
- *        In touch device, mouseover event will be trigger on touchstart event
- *        (see module:zrender/dom/HandlerProxy). By this mechanism, we can
- *        conveniently use hoverStyle when tap on touch screen without additional
- *        code for compatibility.
- *        But if the chart/component has select feature, which usually also use
- *        hoverStyle, there might be conflict between 'select-highlight' and
- *        'hover-highlight' especially when roam is enabled (see geo for example).
- *        In this case, `highDownSilentOnTouch` should be used to disable
- *        hover-highlight on touch device.
- * @param {boolean} [asDispatcher=true] If `false`, do not set as "highDownDispatcher".
- */
-export function setAsHighDownDispatcher(el: Element, asDispatcher: boolean) {
-    const disable = asDispatcher === false;
-    const extendedEl = el as ExtendedElement;
-    // 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).onStateChange) {
-        extendedEl.__onStateChange = (el as ECElement).onStateChange;
-    }
-
-    // Simple optimize, since this method might be
-    // called for each elements of a group in some cases.
-    if (!disable || extendedEl.__highDownDispatcher) {
-
-        // Emphasis, normal can be triggered manually by API or other components like hover link.
-        // el[method]('emphasis', onElementEmphasisEvent)[method]('normal', onElementNormalEvent);
-        // Also keep previous record.
-        extendedEl.__highByOuter = extendedEl.__highByOuter || 0;
-
-        extendedEl.__highDownDispatcher = !disable;
-    }
-}
-
-export function isHighDownDispatcher(el: Element): boolean {
-    return !!(el && (el as ExtendedDisplayable).__highDownDispatcher);
-}
-
-/**
- * Support hightlight/downplay record on each elements.
- * For the case: hover highlight/downplay (legend, visualMap, ...) and
- * user triggerred hightlight/downplay should not conflict.
- * Only all of the highlightDigit cleared, return to normal.
- * @param {string} highlightKey
- * @return {number} highlightDigit
- */
-export function getHighlightDigit(highlightKey: number) {
-    let highlightDigit = _highlightKeyMap[highlightKey];
-    if (highlightDigit == null && _highlightNextDigit <= 32) {
-        highlightDigit = _highlightKeyMap[highlightKey] = _highlightNextDigit++;
-    }
-    return highlightDigit;
-}
-
 type AnimateOrSetPropsOption = {
     dataIndex?: number;
     cb?: () => void;
@@ -1158,6 +857,10 @@ export interface ECData {
     eventData?: ECEventData;
     seriesIndex?: number;
     dataType?: string;
+
+    // self | series
+    focus?: string
+    blurScope?: BlurScope
 }
 
 export const getECData = makeInner<ECData, Element>();
diff --git a/src/util/states.ts b/src/util/states.ts
new file mode 100644
index 0000000..32baa56
--- /dev/null
+++ b/src/util/states.ts
@@ -0,0 +1,316 @@
+import ZRText from 'zrender/src/graphic/Text';
+import { Dictionary } from 'zrender/src/core/types';
+import LRU from 'zrender/src/core/LRU';
+import Displayable, { DisplayableState } from 'zrender/src/graphic/Displayable';
+import { PatternObject } from 'zrender/src/graphic/Pattern';
+import { GradientObject } from 'zrender/src/graphic/Gradient';
+import Element, { ElementEvent } from 'zrender/src/Element';
+import Model from '../model/Model';
+import { DisplayState, ECElement, ColorString, BlurScope } from './types';
+import { extend, indexOf } from 'zrender/src/core/util';
+import { __DEV__ } from '../config';
+import {
+    ExtendedElement,
+    NORMAL,
+    EMPHASIS,
+    Z2_EMPHASIS_LIFT,
+    getECData,
+    ExtendedDisplayable,
+    _highlightKeyMap
+} from './graphic';
+import * as colorTool from 'zrender/src/tool/color';
+
+// Reserve 0 as default.
+export let _highlightNextDigit = 1;
+
+function hasFillOrStroke(fillOrStroke: string | PatternObject | GradientObject) {
+    return fillOrStroke != null && fillOrStroke !== 'none';
+}
+// Most lifted color are duplicated.
+const liftedColorCache = new LRU<string>(100);
+function liftColor(color: string): string {
+    if (typeof color !== 'string') {
+        return color;
+    }
+    let liftedColor = liftedColorCache.get(color);
+    if (!liftedColor) {
+        liftedColor = colorTool.lift(color, -0.1);
+        liftedColorCache.put(color, liftedColor);
+    }
+    return liftedColor;
+}
+
+function singleEnterEmphasis(el: Element) {
+    // Only mark the flag.
+    // States will be applied in the echarts.ts in next frame.
+    (el as ECElement).highlighted = true;
+}
+
+function singleLeaveEmphasis(el: Element) {
+    // Only mark the flag.
+    // States will be applied in the echarts.ts in next frame.
+    (el as ECElement).highlighted = false;
+}
+
+function updateElementState<T>(
+    el: ExtendedElement,
+    updater: (this: void, el: Element, commonParam?: T) => void,
+    commonParam?: T
+) {
+    // If root is group, also enter updater for `onStateChange`.
+    let fromState: DisplayState = NORMAL;
+    let toState: DisplayState = NORMAL;
+    let trigger;
+    // See the rule of `onStateChange` on `graphic.setAsHighDownDispatcher`.
+    (el as ECElement).highlighted && (fromState = EMPHASIS, trigger = true);
+    updater(el, commonParam);
+    (el as ECElement).highlighted && (toState = EMPHASIS, trigger = true);
+    trigger && el.__onStateChange && el.__onStateChange(fromState, toState);
+}
+
+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);
+    });
+}
+/**
+ * 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();
+    }
+}
+function elementStateProxy(this: Displayable, stateName: string): DisplayableState {
+    let state = this.states[stateName];
+    if (stateName === 'emphasis' && this.style) {
+        const hasEmphasis = indexOf(this.currentStates, stateName) >= 0;
+        if (!(this instanceof ZRText)) {
+            const currentFill = this.style.fill;
+            const currentStroke = this.style.stroke;
+            if (currentFill || currentStroke) {
+                let fromState: {
+                    fill: ColorString;
+                    stroke: ColorString;
+                };
+                if (!hasEmphasis) {
+                    fromState = { fill: currentFill, stroke: currentStroke };
+                    for (let i = 0; i < this.animators.length; i++) {
+                        const animator = this.animators[i];
+                        if (animator.__fromStateTransition
+                            // Dont consider the animation to emphasis state.
+                            && animator.__fromStateTransition.indexOf('emphasis') < 0
+                            && animator.targetName === 'style') {
+                            animator.saveFinalToTarget(fromState, ['fill', 'stroke']);
+                        }
+                    }
+                }
+                state = state || {};
+                // Apply default color lift
+                let emphasisStyle = state.style || {};
+                let cloned = false;
+                if (!hasFillOrStroke(emphasisStyle.fill)) {
+                    cloned = true;
+                    // Not modify the original value.
+                    state = extend({}, state);
+                    emphasisStyle = extend({}, emphasisStyle);
+                    // Already being applied 'emphasis'. DON'T lift color multiple times.
+                    emphasisStyle.fill = hasEmphasis ? currentFill : liftColor(fromState.fill);
+                }
+                if (!hasFillOrStroke(emphasisStyle.stroke)) {
+                    if (!cloned) {
+                        state = extend({}, state);
+                        emphasisStyle = extend({}, emphasisStyle);
+                    }
+                    emphasisStyle.stroke = hasEmphasis ? currentStroke : liftColor(fromState.stroke);
+                }
+                state.style = emphasisStyle;
+            }
+        }
+        if (state) {
+            const z2EmphasisLift = (this as ECElement).z2EmphasisLift;
+            // TODO Share with textContent?
+            state.z2 = this.z2 + (z2EmphasisLift != null ? z2EmphasisLift : Z2_EMPHASIS_LIFT);
+        }
+    }
+    return state;
+}
+/**FI
+ * Set hover style (namely "emphasis style") of element.
+ * @param el Should not be `zrender/graphic/Group`.
+ * @param focus 'self' | 'selfInSeries' | 'series'
+ */
+export function enableElementHoverEmphasis(el: Displayable) {
+    el.stateProxy = elementStateProxy;
+    const textContent = el.getTextContent();
+    const textGuide = el.getTextGuideLine();
+    if (textContent) {
+        textContent.stateProxy = elementStateProxy;
+    }
+    if (textGuide) {
+        textGuide.stateProxy = elementStateProxy;
+    }
+}
+
+export function enterEmphasisWhenMouseOver(el: Element, e: ElementEvent) {
+    !shouldSilent(el, e)
+        // "emphasis" event highlight has higher priority than mouse highlight.
+        && !(el as ExtendedElement).__highByOuter
+        && 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
+        && traverseUpdateState((el as ExtendedElement), singleLeaveEmphasis);
+}
+
+export function enterEmphasis(el: Element, highlightDigit?: number) {
+    (el as ExtendedElement).__highByOuter |= 1 << (highlightDigit || 0);
+    traverseUpdateState((el as ExtendedElement), singleEnterEmphasis);
+}
+
+export function leaveEmphasis(el: Element, highlightDigit?: number) {
+    !((el as ExtendedElement).__highByOuter &= ~(1 << (highlightDigit || 0)))
+        && traverseUpdateState((el as ExtendedElement), singleLeaveEmphasis);
+}
+
+function shouldSilent(el: Element, e: ElementEvent) {
+    return (el as ExtendedElement).__highDownSilentOnTouch && e.zrByTouch;
+}
+
+/**
+ * Enable the function that mouseover will trigger the emphasis state.
+ *
+ * NOTICE:
+ * Call the method for a "root" element once. Do not call it for each descendants.
+ * If the descendants elemenets of a group has itself hover style different from the
+ * root group, we can simply mount the style on `el.states.emphasis` for them, but should
+ * not call this method for them.
+ */
+export function enableHoverEmphasis(el: Element, focus?: string, blurScope?: BlurScope) {
+    setAsHighDownDispatcher(el, true);
+    traverseUpdateState(el as ExtendedElement, enableElementHoverEmphasis);
+    const ecData = getECData(el);
+    if (__DEV__) {
+        if (ecData.dataIndex == null && focus != null) {
+            console.warn('focus can only been set on element with dataIndex');
+        }
+    }
+    ecData.focus = focus;
+    ecData.blurScope = blurScope;
+}
+
+const OTHER_STATES = ['emphasis', 'blur', 'select'] as const;
+const styleGetterMap: Dictionary<'getItemStyle' | 'getLineStyle' | 'getAreaStyle'> = {
+    itemStyle: 'getItemStyle',
+    lineStyle: 'getLineStyle',
+    areaStyle: 'getAreaStyle'
+};
+/**
+ * Set emphasis/blur/selected states of element.
+ */
+export function setStatesStylesFromModel(
+    el: Displayable,
+    itemModel: Model<Partial<Record<'emphasis' | 'blur' | 'select', any>>>,
+    styleType?: string, // default itemStyle
+    getterType?: 'getItemStyle' | 'getLineStyle' | 'getAreaStyle'
+) {
+    styleType = styleType || 'itemStyle';
+    for (let i = 0; i < OTHER_STATES.length; i++) {
+        const stateName = OTHER_STATES[i];
+        const model = itemModel.getModel([stateName, styleType]);
+        const state = el.ensureState(stateName);
+        // Let it throw error if getterType is not found.
+        state.style = model[getterType || styleGetterMap[styleType]]();
+    }
+}
+
+/**
+ * @param {module:zrender/Element} el
+ * @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, `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.
+ *        @this {string} This dispatcher `el`.
+ *        @param {string} fromState Can be "normal" or "emphasis".
+ *               `fromState` might equal to `toState`,
+ *               for example, when this method is called when `el` is
+ *               on "emphasis" state.
+ *        @param {string} toState Can be "normal" or "emphasis".
+ *
+ *        FIXME
+ *        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
+ *        mantained on elements.
+ *
+ * @param {boolean} [el.highDownSilentOnTouch=false]
+ *        In touch device, mouseover event will be trigger on touchstart event
+ *        (see module:zrender/dom/HandlerProxy). By this mechanism, we can
+ *        conveniently use hoverStyle when tap on touch screen without additional
+ *        code for compatibility.
+ *        But if the chart/component has select feature, which usually also use
+ *        hoverStyle, there might be conflict between 'select-highlight' and
+ *        'hover-highlight' especially when roam is enabled (see geo for example).
+ *        In this case, `highDownSilentOnTouch` should be used to disable
+ *        hover-highlight on touch device.
+ * @param {boolean} [asDispatcher=true] If `false`, do not set as "highDownDispatcher".
+ */
+export function setAsHighDownDispatcher(el: Element, asDispatcher: boolean) {
+    const disable = asDispatcher === false;
+    const extendedEl = el as ExtendedElement;
+    // 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).onStateChange) {
+        extendedEl.__onStateChange = (el as ECElement).onStateChange;
+    }
+    // Simple optimize, since this method might be
+    // called for each elements of a group in some cases.
+    if (!disable || extendedEl.__highDownDispatcher) {
+        // Emphasis, normal can be triggered manually by API or other components like hover link.
+        // el[method]('emphasis', onElementEmphasisEvent)[method]('normal', onElementNormalEvent);
+        // Also keep previous record.
+        extendedEl.__highByOuter = extendedEl.__highByOuter || 0;
+        extendedEl.__highDownDispatcher = !disable;
+    }
+}
+
+export function isHighDownDispatcher(el: Element): boolean {
+    return !!(el && (el as ExtendedDisplayable).__highDownDispatcher);
+}
+
+/**
+ * Support hightlight/downplay record on each elements.
+ * For the case: hover highlight/downplay (legend, visualMap, ...) and
+ * user triggerred hightlight/downplay should not conflict.
+ * Only all of the highlightDigit cleared, return to normal.
+ * @param {string} highlightKey
+ * @return {number} highlightDigit
+ */
+export function getHighlightDigit(highlightKey: number) {
+    let highlightDigit = _highlightKeyMap[highlightKey];
+    if (highlightDigit == null && _highlightNextDigit <= 32) {
+        highlightDigit = _highlightKeyMap[highlightKey] = _highlightNextDigit++;
+    }
+    return highlightDigit;
+}
diff --git a/src/util/types.ts b/src/util/types.ts
index f6b9358..c4fc3bf 100644
--- a/src/util/types.ts
+++ b/src/util/types.ts
@@ -1124,17 +1124,29 @@ export interface ComponentOption {
     // FIXME:TS more
 }
 
-export interface StatesOptionMixin<StateOption> {
+export type BlurScope = 'coordinateSystem' | 'series' | 'global';
+
+export interface StatesOptionMixin<StateOption, ExtraFocusOptions = never> {
     /**
      * Emphasis states
      */
     emphasis?: StateOption & {
         /**
          * self: Focus self and blur all others.
-         * selfInSeries: Focus self and blur others in the same series.
          * series: Focus series and blur all other series.
          */
-        focus?: 'self' | 'selfInSeries' | 'series'
+        focus?: 'self' | 'series' | ExtraFocusOptions
+
+        /**
+         * Scope of blurred element when focus.
+         *
+         * coordinateSystem: blur others in the same coordinateSystem
+         * series: blur others in the same series
+         * global: blur all others
+         *
+         * Default to be coordinate system.
+         */
+        blurScope?: BlurScope
     }
     /**
      * Select states
diff --git a/src/view/Chart.ts b/src/view/Chart.ts
index 529ed94..c5b7292 100644
--- a/src/view/Chart.ts
+++ b/src/view/Chart.ts
@@ -22,7 +22,7 @@ import Group from 'zrender/src/graphic/Group';
 import * as componentUtil from '../util/component';
 import * as clazzUtil from '../util/clazz';
 import * as modelUtil from '../util/model';
-import * as graphicUtil from '../util/graphic';
+import { enterEmphasis, leaveEmphasis, getHighlightDigit } from '../util/states';
 import {createTask, TaskResetCallbackReturn} from '../stream/task';
 import createRenderPlanner from '../chart/helper/createRenderPlanner';
 import SeriesModel from '../model/Series';
@@ -202,8 +202,7 @@ class ChartView {
  */
 function elSetState(el: Element, state: DisplayState, highlightDigit: number) {
     if (el) {
-        state === 'emphasis' ? graphicUtil.enterEmphasis(el, highlightDigit)
-            : graphicUtil.leaveEmphasis(el, highlightDigit);
+        (state === 'emphasis' ? enterEmphasis : leaveEmphasis)(el, highlightDigit);
     }
 }
 
@@ -211,7 +210,7 @@ function toggleHighlight(data: List, payload: Payload, state: DisplayState) {
     const dataIndex = modelUtil.queryDataIndex(data, payload);
 
     const highlightDigit = (payload && payload.highlightKey != null)
-        ? graphicUtil.getHighlightDigit(payload.highlightKey)
+        ? getHighlightDigit(payload.highlightKey)
         : null;
 
     if (dataIndex != null) {


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