You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by su...@apache.org on 2021/03/31 22:36:46 UTC

[echarts] branch fix/geo-svg updated (9c2cb8e -> 8235095)

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

sushuang pushed a change to branch fix/geo-svg
in repository https://gitbox.apache.org/repos/asf/echarts.git.


    from 9c2cb8e  fix: tweak the last commit.
     new b0d4a35  feature: [geo] (1) Support component focus blur. The option `emphasis.focus` can be specified to enable this feature. `emphasis.focus` only supports the value `'self'`. The option `emphasis.focusScope` is not supported in component. That is, the focus scope can be only a single component itself. All of the elements in this component will be blurred. Added `Component['focusBlurEnabled']` to enable component blur. (2) Support component hover link when highlight with a give [...]
     new 8235095  featrue: [geo] (1) support name on <g>. (2) some refactor.

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


Summary of changes:
 src/chart/graph/GraphView.ts             |   4 +-
 src/chart/helper/SymbolDraw.ts           |   9 +-
 src/chart/sunburst/SunburstPiece.ts      |  10 +-
 src/chart/treemap/TreemapView.ts         |  11 +-
 src/component/geo/GeoView.ts             |   7 +
 src/component/helper/MapDraw.ts          | 618 +++++++++++++++++++------------
 src/component/tooltip/TooltipView.ts     |   7 +-
 src/coord/geo/Geo.ts                     |   2 +-
 src/coord/geo/GeoSVGResource.ts          | 171 +++++----
 src/coord/geo/geoTypes.ts                |  18 -
 src/core/echarts.ts                      |  67 ++--
 src/util/graphic.ts                      |   7 +-
 src/util/innerStore.ts                   |  18 +-
 src/util/states.ts                       | 194 ++++++++--
 src/util/types.ts                        |   2 +-
 src/view/Component.ts                    |  18 +-
 test/{geo-svg.html => geo-svg-demo.html} | 486 +++++++++++-------------
 test/geo-svg.html                        | 249 +++++++------
 18 files changed, 1109 insertions(+), 789 deletions(-)
 copy test/{geo-svg.html => geo-svg-demo.html} (56%)

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


[echarts] 01/02: feature: [geo] (1) Support component focus blur. The option `emphasis.focus` can be specified to enable this feature. `emphasis.focus` only supports the value `'self'`. The option `emphasis.focusScope` is not supported in component. That is, the focus scope can be only a single component itself. All of the elements in this component will be blurred. Added `Component['focusBlurEnabled']` to enable component blur. (2) Support component hover link when highlight with a given name. There proba [...]

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

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

commit b0d4a358f317c0d99ee3e45fce52e1b03b6d18e4
Author: 100pah <su...@gmail.com>
AuthorDate: Mon Mar 29 19:40:35 2021 +0800

    feature: [geo]
    (1) Support component focus blur.
    The option `emphasis.focus` can be specified to enable this feature. `emphasis.focus` only supports the value `'self'`.
    The option `emphasis.focusScope` is not supported in component. That is, the focus scope can be only a single component itself. All of the elements in this component will be blurred.
    Added `Component['focusBlurEnabled']` to enable component blur.
    (2) Support component hover link when highlight with a given name.
    There probably be multiple elements share one region name while those region elements has no common ancestor. hover link enables them to be highlighted together.
    Implementation: if a component implements `Component['findHighDownDispatchers']`,  dispatcher elements by a given name will be found and this feature is enabled.
    (3) Support component highlight/downplay be triggered by `dispatchAction`.
    Implementation: if a component implements `Component['findHighDownDispatchers']`,  dispatcher elements by a given name will be found and this feature is enabled.
    (4) Some refactor.
---
 src/chart/graph/GraphView.ts         |   4 +-
 src/chart/helper/SymbolDraw.ts       |   9 +-
 src/chart/sunburst/SunburstPiece.ts  |  10 +-
 src/chart/treemap/TreemapView.ts     |  11 +-
 src/component/geo/GeoView.ts         |   7 +
 src/component/helper/MapDraw.ts      | 213 +++++++++++++++++++------------
 src/component/tooltip/TooltipView.ts |   7 +-
 src/coord/geo/GeoSVGResource.ts      |  38 +++---
 src/coord/geo/geoTypes.ts            |   4 +-
 src/core/echarts.ts                  |  63 +++++----
 src/util/graphic.ts                  |   7 +-
 src/util/innerStore.ts               |  18 ++-
 src/util/states.ts                   | 194 ++++++++++++++++++++++++----
 src/util/types.ts                    |   2 +-
 src/view/Component.ts                |  18 ++-
 test/geo-svg-demo.html               | 240 +++++++++++++++++++++++++++++++++++
 16 files changed, 665 insertions(+), 180 deletions(-)

diff --git a/src/chart/graph/GraphView.ts b/src/chart/graph/GraphView.ts
index ee068a8..1c8d10f 100644
--- a/src/chart/graph/GraphView.ts
+++ b/src/chart/graph/GraphView.ts
@@ -18,7 +18,7 @@
 */
 
 import * as zrUtil from 'zrender/src/core/util';
-import SymbolDraw from '../helper/SymbolDraw';
+import SymbolDraw, { ListForSymbolDraw } from '../helper/SymbolDraw';
 import LineDraw from '../helper/LineDraw';
 import RoamController, { RoamControllerHost } from '../../component/helper/RoamController';
 import * as roamHelper from '../../component/helper/roamHelper';
@@ -105,7 +105,7 @@ class GraphView extends ChartView {
         adjustEdge(seriesModel.getGraph(), getNodeGlobalScale(seriesModel));
 
         const data = seriesModel.getData();
-        symbolDraw.updateData(data);
+        symbolDraw.updateData(data as ListForSymbolDraw);
 
         const edgeData = seriesModel.getEdgeData();
         // TODO: TYPE
diff --git a/src/chart/helper/SymbolDraw.ts b/src/chart/helper/SymbolDraw.ts
index aace5bd..66edfcc 100644
--- a/src/chart/helper/SymbolDraw.ts
+++ b/src/chart/helper/SymbolDraw.ts
@@ -32,7 +32,8 @@ import {
     ZRStyleProps,
     StatesOptionMixin,
     BlurScope,
-    DisplayState
+    DisplayState,
+    DefaultEmphasisFocus
 } from '../../util/types';
 import { CoordinateSystemClipArea } from '../../coord/CoordinateSystem';
 import Model from '../../model/Model';
@@ -94,7 +95,7 @@ interface SymbolDrawStateOption {
 export interface SymbolDrawItemModelOption extends SymbolOptionMixin<object>,
     StatesOptionMixin<SymbolDrawStateOption, {
         emphasis?: {
-            focus?: string
+            focus?: DefaultEmphasisFocus
             scale?: boolean
         }
     }>,
@@ -111,7 +112,7 @@ export interface SymbolDrawSeriesScope {
     blurItemStyle?: ZRStyleProps
     selectItemStyle?: ZRStyleProps
 
-    focus?: string
+    focus?: DefaultEmphasisFocus
     blurScope?: BlurScope
 
     symbolRotate?: ScatterSeriesOption['symbolRotate']
@@ -148,7 +149,7 @@ function makeSeriesScope(data: List): SymbolDrawSeriesScope {
     };
 }
 
-type ListForSymbolDraw = List<Model<SymbolDrawItemModelOption & AnimationOptionMixin>>;
+export type ListForSymbolDraw = List<Model<SymbolDrawItemModelOption & AnimationOptionMixin>>;
 
 class SymbolDraw {
     group = new graphic.Group();
diff --git a/src/chart/sunburst/SunburstPiece.ts b/src/chart/sunburst/SunburstPiece.ts
index f5a53ca..7f0dbe1 100644
--- a/src/chart/sunburst/SunburstPiece.ts
+++ b/src/chart/sunburst/SunburstPiece.ts
@@ -146,12 +146,12 @@ class SunburstPiece extends graphic.Sector {
 
         const focus = emphasisModel.get('focus');
 
-        const focusDataIndices: number[] = focus === 'ancestor'
-            ? node.getAncestorsIndices()
-            : focus === 'descendant' ? node.getDescendantIndices() : null;
+        const focusOrIndices =
+            focus === 'ancestor' ? node.getAncestorsIndices()
+            : focus === 'descendant' ? node.getDescendantIndices()
+            : focus;
 
-
-        enableHoverEmphasis(this, focusDataIndices || focus, emphasisModel.get('blurScope'));
+        enableHoverEmphasis(this, focusOrIndices, emphasisModel.get('blurScope'));
     }
 
     _updateLabel(
diff --git a/src/chart/treemap/TreemapView.ts b/src/chart/treemap/TreemapView.ts
index d784c81..d53aceb 100644
--- a/src/chart/treemap/TreemapView.ts
+++ b/src/chart/treemap/TreemapView.ts
@@ -803,9 +803,10 @@ function renderNode(
     const focus = nodeModel.get(['emphasis', 'focus']);
     const blurScope = nodeModel.get(['emphasis', 'blurScope']);
 
-    const focusDataIndices: number[] = focus === 'ancestor'
-        ? thisNode.getAncestorsIndices()
-        : focus === 'descendant' ? thisNode.getDescendantIndices() : null;
+    const focusOrIndices =
+        focus === 'ancestor' ? thisNode.getAncestorsIndices()
+        : focus === 'descendant' ? thisNode.getDescendantIndices()
+        : focus;
 
     // No children, render content.
     if (isParent) {
@@ -820,7 +821,7 @@ function renderNode(
             // Only for enabling highlight/downplay.
             data.setItemGraphicEl(thisNode.dataIndex, bg);
 
-            enableHoverFocus(bg, focusDataIndices || focus, blurScope);
+            enableHoverFocus(bg, focusOrIndices, blurScope);
         }
     }
     else {
@@ -834,7 +835,7 @@ function renderNode(
         // Only for enabling highlight/downplay.
         data.setItemGraphicEl(thisNode.dataIndex, group);
 
-        enableHoverFocus(group, focusDataIndices || focus, blurScope);
+        enableHoverFocus(group, focusOrIndices, blurScope);
     }
 
     return group;
diff --git a/src/component/geo/GeoView.ts b/src/component/geo/GeoView.ts
index 961b541..0db6d36 100644
--- a/src/component/geo/GeoView.ts
+++ b/src/component/geo/GeoView.ts
@@ -26,6 +26,7 @@ import GeoModel from '../../coord/geo/GeoModel';
 import { Payload, ZRElementEvent, ECEventData } from '../../util/types';
 import { getECData } from '../../util/innerStore';
 import { findEventDispatcher } from '../../util/event';
+import Element from 'zrender/src/Element';
 
 class GeoView extends ComponentView {
 
@@ -38,6 +39,8 @@ class GeoView extends ComponentView {
 
     private _model: GeoModel;
 
+    focusBlurEnabled = true;
+
     init(ecModel: GlobalModel, api: ExtensionAPI) {
         const mapDraw = new MapDraw(api);
         this._mapDraw = mapDraw;
@@ -94,6 +97,10 @@ class GeoView extends ComponentView {
         });
     }
 
+    findHighDownDispatchers(name: string): Element[] {
+        return this._mapDraw && this._mapDraw.findHighDownDispatchers(name, this._model);
+    }
+
     dispose(): void {
         this._mapDraw && this._mapDraw.remove();
     }
diff --git a/src/component/helper/MapDraw.ts b/src/component/helper/MapDraw.ts
index 738edb5..c589119 100644
--- a/src/component/helper/MapDraw.ts
+++ b/src/component/helper/MapDraw.ts
@@ -22,14 +22,19 @@ 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, DISPLAY_STATES } from '../../util/states';
+import {
+    enableHoverEmphasis,
+    DISPLAY_STATES,
+    enableComponentHighDownFeatures,
+    setDefaultStateProxy
+} from '../../util/states';
 import geoSourceManager from '../../coord/geo/geoSourceManager';
 import {getUID} from '../../util/component';
 import ExtensionAPI from '../../core/ExtensionAPI';
 import GeoModel, { GeoCommonOptionMixin, GeoItemStyleOption } from '../../coord/geo/GeoModel';
 import MapSeries from '../../chart/map/MapSeries';
 import GlobalModel from '../../model/Global';
-import { Payload, ECElement, LineStyleOption } from '../../util/types';
+import { Payload, ECElement, LineStyleOption, InnerFocus } from '../../util/types';
 import GeoView from '../geo/GeoView';
 import MapView from '../../chart/map/MapView';
 import Geo from '../../coord/geo/Geo';
@@ -40,7 +45,7 @@ import { createOrUpdatePatternFromDecal } from '../../util/decal';
 import { ViewCoordSysTransformInfoPart } from '../../coord/View';
 import { GeoSVGGraphicRecord, GeoSVGResource } from '../../coord/geo/GeoSVGResource';
 import Displayable from 'zrender/src/graphic/Displayable';
-import { ElementTextConfig } from 'zrender/src/Element';
+import Element, { ElementTextConfig } from 'zrender/src/Element';
 import List from '../../data/List';
 import { GeoJSONRegion } from '../../coord/geo/Region';
 import { RegionGraphic } from '../../coord/geo/geoTypes';
@@ -102,11 +107,13 @@ class MapDraw {
 
     private _regionsGroup: RegionsGroup;
 
+    private _regionsGroupByName: zrUtil.HashMap<RegionsGroup>;
+
     private _svgMapName: string;
 
     private _svgGroup: graphic.Group;
 
-    private _svgRegionGraphics: GeoSVGGraphicRecord['regionGraphics'];
+    private _svgGraphicRecord: GeoSVGGraphicRecord;
 
 
     constructor(api: ExtensionAPI) {
@@ -189,7 +196,7 @@ class MapDraw {
     }
 
     private _buildGeoJSON(viewBuildCtx: ViewBuildContext): void {
-        const nameMap = zrUtil.createHashMap<RegionsGroup>();
+        const nameMap = this._regionsGroupByName = zrUtil.createHashMap<RegionsGroup>();
         const regionsGroup = this._regionsGroup;
         const transformInfoRaw = viewBuildCtx.transformInfoRaw;
 
@@ -255,7 +262,7 @@ class MapDraw {
             const regionGraphic: RegionGraphic = {
                 name: region.name,
                 el: compoundPath,
-                styleOptionKey: 'itemStyle',
+                optionStyleEnabled: true,
                 stateTrigger: regionGroup,
                 eventTrigger: regionGroup,
                 useLabel: true
@@ -281,19 +288,50 @@ class MapDraw {
             this._useSVG(mapName);
         }
 
-        zrUtil.each(this._svgRegionGraphics, function (regionGraphic) {
+        let focusSelf = false;
+        zrUtil.each(this._svgGraphicRecord.regionGraphics, function (regionGraphic) {
             // Note that we also allow different elements have the same name.
             // For example, a glyph of a city and the label of the city have
             // the same name and their tooltip info can be defined in a single
             // region option.
-            this._resetSingleRegionGraphic(
+            const focus = this._resetSingleRegionGraphic(
                 viewBuildCtx, regionGraphic, [0, 0], 'inside',
                 // We do not know how the SVG like so we'd better not to change z2.
                 // Otherwise it might bring some unexpected result. For example,
                 // an area hovered that make some inner city can not be clicked.
                 true
             );
+            if (focus === 'self') {
+                focusSelf = true;
+            }
         }, this);
+
+        // It's a little complicated to support blurring the entire geoSVG in series-map.
+        // So do not suport it until some requirements come.
+        // At present, in series-map, only regions can be blurred.
+        if (focusSelf && viewBuildCtx.isGeo) {
+            const blurStyle = (viewBuildCtx.mapOrGeoModel as GeoModel).getModel(['blur', 'itemStyle']).getItemStyle();
+            // Only suport `opacity` here. Because not sure that other props are suitable for
+            // all of the elements generated by SVG (especially for Text/TSpan/Image/... ).
+            const opacity = blurStyle.opacity;
+            this._svgGraphicRecord.root.traverse(el => {
+                if (!el.isGroup) {
+                    // PENDING: clear those settings to SVG elements when `_freeSVG`.
+                    // (Currently it happen not to be needed.)
+                    setDefaultStateProxy(el as Displayable);
+                    const style = (el as Displayable).ensureState('blur').style || {};
+                    // Do not overwrite the region style that already set from region option.
+                    if (style.opacity == null && opacity != null) {
+                        style.opacity = opacity;
+                    }
+                    // If opacity not set, but `ensureState('blur').style` set, there will
+                    // be default opacity.
+
+                    // Enable `stateTransition` (animation).
+                    (el as Displayable).ensureState('emphasis');
+                }
+            });
+        }
     }
 
     private _resetSingleRegionGraphic(
@@ -302,48 +340,21 @@ class MapDraw {
         labelXY: number[],
         labelPosition: ElementTextConfig['position'],
         noZ2EmphasisLift: boolean
-    ): void {
+    ): InnerFocus {
 
         const regionName = regionGraphic.name;
         const mapOrGeoModel = viewBuildCtx.mapOrGeoModel;
         const data = viewBuildCtx.data;
-        const isVisualEncodedByVisualMap = viewBuildCtx.isVisualEncodedByVisualMap;
         const isGeo = viewBuildCtx.isGeo;
 
         const dataIdx = data ? data.indexOfName(regionName) : null;
         const regionModel = mapOrGeoModel.getRegionModel(regionName);
-        const styles = makeStyleForRegion(regionGraphic.styleOptionKey, regionModel);
-
-        // Use the itemStyle in data if has data
-        if (styles && styles.styleOptionKey === 'itemStyle' && data) {
-            // Only visual color of each item will be used. It can be encoded by visualMap
-            // But visual color of series is used in symbol drawing
-
-            // Visual color for each series is for the symbol draw
-            const style = data.getItemVisual(dataIdx, 'style');
-            const decal = data.getItemVisual(dataIdx, 'decal');
-            if (isVisualEncodedByVisualMap && style.fill) {
-                styles.normal.fill = style.fill;
-            }
-            if (decal) {
-                styles.normal.decal = createOrUpdatePatternFromDecal(decal, viewBuildCtx.api);
-            }
-        }
 
-        // PENDING: SVG text, tspan and image can be named but not supporeted
-        // to be styled by region option yet.
-        if (styles && regionGraphic.el instanceof graphic.Path) {
-            regionGraphic.el.setStyle(styles.normal);
-            regionGraphic.el.style.strokeNoScale = true;
-            regionGraphic.el.ensureState('emphasis').style = styles.emphasis;
-            regionGraphic.el.ensureState('select').style = styles.select;
-            regionGraphic.el.ensureState('blur').style = styles.blur;
-        }
+        applyOptionStyleForRegion(viewBuildCtx, regionGraphic, dataIdx, regionModel);
 
         if (regionGraphic.el instanceof Displayable) {
             regionGraphic.el.culling = true;
         }
-
         if (noZ2EmphasisLift) {
             (regionGraphic.el as ECElement).z2EmphasisLift = 0;
         }
@@ -455,25 +466,53 @@ class MapDraw {
             });
         }
 
-        if (regionGraphic.stateTrigger) {
+        let focus;
+        const stateTrigger = regionGraphic.stateTrigger;
+        if (stateTrigger) {
             // @ts-ignore FIXME:TS fix the "compatible with each other"?
-            regionGraphic.stateTrigger.highDownSilentOnTouch = !!mapOrGeoModel.get('selectedMode');
+            stateTrigger.highDownSilentOnTouch = !!mapOrGeoModel.get('selectedMode');
             // @ts-ignore FIXME:TS fix the "compatible with each other"?
             const emphasisModel = regionModel.getModel('emphasis');
+            focus = emphasisModel.get('focus');
             enableHoverEmphasis(
-                regionGraphic.stateTrigger, emphasisModel.get('focus'), emphasisModel.get('blurScope')
+                stateTrigger, focus, emphasisModel.get('blurScope')
             );
+            if (isGeo) {
+                enableComponentHighDownFeatures(stateTrigger, mapOrGeoModel as GeoModel, regionName);
+            }
         }
+
+        return focus;
     }
 
     remove(): void {
         this._regionsGroup.removeAll();
+        this._regionsGroupByName = null;
         this._svgGroup.removeAll();
-        this._controller.dispose();
         this._freeSVG();
+        this._controller.dispose();
         this._controllerHost = null;
     }
 
+    findHighDownDispatchers(name: string, geoModel: GeoModel): Element[] {
+        if (name == null) {
+            return [];
+        }
+
+        const geo = geoModel.coordinateSystem;
+
+        if (geo.resourceType === 'geoJSON') {
+            const regionsGroupByName = this._regionsGroupByName;
+            if (regionsGroupByName) {
+                const regionGroup = regionsGroupByName.get(name);
+                return regionGroup ? [regionGroup] : [];
+            }
+        }
+        else if (geo.resourceType === 'geoSVG') {
+            return this._svgGraphicRecord.regionElementMap.get(name) || [];
+        }
+    }
+
     private _svgResourceChanged(mapName: string): boolean {
         return this._svgMapName !== mapName;
     }
@@ -483,7 +522,7 @@ class MapDraw {
         if (resource && resource.type === 'geoSVG') {
             const svgGraphic = (resource as GeoSVGResource).useGraphic(this.uid);
             this._svgGroup.add(svgGraphic.root);
-            this._svgRegionGraphics = svgGraphic.regionGraphics;
+            this._svgGraphicRecord = svgGraphic;
             this._svgMapName = mapName;
         }
     }
@@ -493,11 +532,12 @@ class MapDraw {
         if (mapName == null) {
             return;
         }
+
         const resource = geoSourceManager.getGeoResource(mapName);
         if (resource && resource.type === 'geoSVG') {
             (resource as GeoSVGResource).freeGraphic(this.uid);
         }
-        this._svgRegionGraphics = null;
+        this._svgGraphicRecord = null;
         this._svgGroup.removeAll();
         this._svgMapName = null;
     }
@@ -599,8 +639,10 @@ function labelTextAfterUpdate(this: graphic.Text) {
     m[3] /= scaleY;
 }
 
-function makeStyleForRegion(
-    styleOptionKey: RegionGraphic['styleOptionKey'],
+function applyOptionStyleForRegion(
+    viewBuildCtx: ViewBuildContext,
+    regionGraphic: RegionGraphic,
+    dataIndex: number,
     regionModel: Model<
         GeoStyleableOption & {
             emphasis?: GeoStyleableOption;
@@ -608,48 +650,59 @@ function makeStyleForRegion(
             blur?: GeoStyleableOption;
         }
     >
-): {
-    styleOptionKey: 'itemStyle';
-    normal: ItemStyleProps;
-    emphasis: ItemStyleProps;
-    select: ItemStyleProps;
-    blur: ItemStyleProps;
-} | {
-    styleOptionKey: 'lineStyle';
-    normal: LineStyleProps;
-    emphasis: LineStyleProps;
-    select: LineStyleProps;
-    blur: LineStyleProps;
-} {
-    if (!styleOptionKey) {
+): void {
+
+    if (
+        !regionGraphic.optionStyleEnabled
+        || !(regionGraphic.el instanceof Displayable)
+    ) {
         return;
     }
 
-    const normalStyleModel = regionModel.getModel(styleOptionKey);
-    const emphasisStyleModel = regionModel.getModel(['emphasis', styleOptionKey]);
-    const blurStyleModel = regionModel.getModel(['blur', styleOptionKey]);
-    const selectStyleModel = regionModel.getModel(['select', styleOptionKey]);
+    // All of the path are using `itemStyle`, becuase
+    // (1) Some SVG also use fill on polyline (The different between
+    // polyline and polygon is "open" or "close" but not fill or not).
+    // (2) For the common props like opacity, if some use itemStyle
+    // and some use `lineStyle`, it might confuse users.
+    // (3) Most SVG use <path>, where can not detect wether draw a "line"
+    // or a filled shape, so use `itemStyle` for <path>.
+
+    const normalStyleModel = regionModel.getModel('itemStyle');
+    const emphasisStyleModel = regionModel.getModel(['emphasis', 'itemStyle']);
+    const blurStyleModel = regionModel.getModel(['blur', 'itemStyle']);
+    const selectStyleModel = regionModel.getModel(['select', 'itemStyle']);
 
     // NOTE: DONT use 'style' in visual when drawing map.
     // This component is used for drawing underlying map for both geo component and map series.
-    if (styleOptionKey === 'itemStyle') {
-        return {
-            styleOptionKey,
-            normal: getFixedItemStyle(normalStyleModel),
-            emphasis: getFixedItemStyle(emphasisStyleModel),
-            select: getFixedItemStyle(selectStyleModel),
-            blur: getFixedItemStyle(blurStyleModel)
-        };
-    }
-    else if (styleOptionKey === 'lineStyle') {
-        return {
-            styleOptionKey,
-            normal: normalStyleModel.getLineStyle(),
-            emphasis: emphasisStyleModel.getLineStyle(),
-            select: selectStyleModel.getLineStyle(),
-            blur: blurStyleModel.getLineStyle()
-        };
+    const normalStyle = getFixedItemStyle(normalStyleModel);
+    const emphasisStyle = getFixedItemStyle(emphasisStyleModel);
+    const selectStyle = getFixedItemStyle(selectStyleModel);
+    const blurStyle = getFixedItemStyle(blurStyleModel);
+
+    // Update the itemStyle if has data visual
+    const data = viewBuildCtx.data;
+    if (data) {
+        // Only visual color of each item will be used. It can be encoded by visualMap
+        // But visual color of series is used in symbol drawing
+
+        // Visual color for each series is for the symbol draw
+        const style = data.getItemVisual(dataIndex, 'style');
+        const decal = data.getItemVisual(dataIndex, 'decal');
+        if (viewBuildCtx.isVisualEncodedByVisualMap && style.fill) {
+            normalStyle.fill = style.fill;
+        }
+        if (decal) {
+            normalStyle.decal = createOrUpdatePatternFromDecal(decal, viewBuildCtx.api);
+        }
     }
+
+    // SVG text, tspan and image can be named but not supporeted
+    // to be styled by region option yet.
+    regionGraphic.el.setStyle(normalStyle);
+    regionGraphic.el.style.strokeNoScale = true;
+    regionGraphic.el.ensureState('emphasis').style = emphasisStyle;
+    regionGraphic.el.ensureState('select').style = selectStyle;
+    regionGraphic.el.ensureState('blur').style = blurStyle;
 }
 
 export default MapDraw;
diff --git a/src/component/tooltip/TooltipView.ts b/src/component/tooltip/TooltipView.ts
index c76b489..93949fc 100644
--- a/src/component/tooltip/TooltipView.ts
+++ b/src/component/tooltip/TooltipView.ts
@@ -319,8 +319,6 @@ class TooltipView extends ComponentView {
             el.y = payload.y;
             el.update();
             getECData(el).tooltipConfig = {
-                componentMainType: null,
-                componentIndex: null,
                 name: null,
                 option: payload.tooltip
             };
@@ -707,7 +705,8 @@ class TooltipView extends ComponentView {
         el: ECElement,
         dispatchAction: ExtensionAPI['dispatchAction']
     ) {
-        const tooltipConfig = getECData(el).tooltipConfig;
+        const ecData = getECData(el);
+        const tooltipConfig = ecData.tooltipConfig;
         let tooltipOpt = tooltipConfig.option;
         if (zrUtil.isString(tooltipOpt)) {
             const content = tooltipOpt;
@@ -719,7 +718,7 @@ class TooltipView extends ComponentView {
         }
 
         const tooltipModelCascade = [tooltipOpt] as TooltipModelOptionCascade[];
-        const cmpt = this._ecModel.getComponent(tooltipConfig.componentMainType, tooltipConfig.componentIndex);
+        const cmpt = this._ecModel.getComponent(ecData.componentMainType, ecData.componentIndex);
         if (cmpt) {
             tooltipModelCascade.push(cmpt as Model<TooltipableOption>);
         }
diff --git a/src/coord/geo/GeoSVGResource.ts b/src/coord/geo/GeoSVGResource.ts
index 8162879..79455c3 100644
--- a/src/coord/geo/GeoSVGResource.ts
+++ b/src/coord/geo/GeoSVGResource.ts
@@ -20,33 +20,30 @@
 import { parseSVG, makeViewBoxTransform, SVGNodeTagLower } from 'zrender/src/tool/parseSVG';
 import Group from 'zrender/src/graphic/Group';
 import Rect from 'zrender/src/graphic/shape/Rect';
-import {assert, createHashMap, HashMap, hasOwn} from 'zrender/src/core/util';
+import {assert, createHashMap, HashMap} from 'zrender/src/core/util';
 import BoundingRect from 'zrender/src/core/BoundingRect';
 import { GeoResource, GeoSVGGraphicRoot, GeoSVGSourceInput, RegionGraphic } from './geoTypes';
 import { parseXML } from 'zrender/src/tool/parseXML';
 import { GeoSVGRegion } from './Region';
+import Element from 'zrender/src/Element';
 
+type RegionName = string;
 export interface GeoSVGGraphicRecord {
     root: Group;
     boundingRect: BoundingRect;
     regionGraphics: RegionGraphic[];
+    // A name may correspond to multiple graphics.
+    regionElementMap: HashMap<Element[], RegionName>;
 }
 
 const REGION_AVAILABLE_SVG_TAG_MAP = createHashMap<number, SVGNodeTagLower>([
     'rect', 'circle', 'line', 'ellipse', 'polygon', 'polyline', 'text', 'tspan', 'path'
 ]);
-const STYLE_OPTION_KEY = createHashMap<'itemStyle' | 'lineStyle', SVGNodeTagLower>({
-    'rect': 'itemStyle',
-    'circle': 'itemStyle',
-    'line': 'lineStyle',
-    'ellipse': 'itemStyle',
-    'polygon': 'itemStyle',
-    'polyline': 'lineStyle',
-    // 'image': '?', // TODO
-    // 'text': '?', // TODO
-    // 'tspan': '?', // TODO
-    'path': 'itemStyle'
-});
+
+const OPTION_STYLE_ENABLED_TAG_MAP = createHashMap<number, SVGNodeTagLower>([
+    'rect', 'circle', 'line', 'ellipse', 'polygon', 'polyline', 'path'
+]);
+
 const LABEL_HOST_MAP = createHashMap<number, SVGNodeTagLower>([
     'rect', 'circle', 'line', 'ellipse', 'polygon', 'polyline', 'path'
 ]);
@@ -231,25 +228,30 @@ function buildGraphic(
     (root as GeoSVGGraphicRoot).isGeoSVGGraphicRoot = true;
 
     const regionGraphics = [] as GeoSVGGraphicRecord['regionGraphics'];
+    const regionElementMap = createHashMap<Element[], RegionName>();
     const named = result.named;
     for (let i = 0; i < named.length; i++) {
         const namedItem = named[i];
         const svgNodeTagLower = namedItem.svgNodeTagLower;
 
         if (REGION_AVAILABLE_SVG_TAG_MAP.get(svgNodeTagLower) != null) {
-            const styleOptionKey = STYLE_OPTION_KEY.get(svgNodeTagLower);
+            const optionStyleEnabled = OPTION_STYLE_ENABLED_TAG_MAP.get(svgNodeTagLower);
             const el = namedItem.el;
+            const name = namedItem.name;
 
             regionGraphics.push({
-                name: namedItem.name,
+                name: name,
                 el: el,
-                styleOptionKey: styleOptionKey,
-                stateTrigger: styleOptionKey != null ? el : null,
+                optionStyleEnabled: optionStyleEnabled != null,
+                stateTrigger: optionStyleEnabled != null ? el : null,
                 // text/tspan/image do not suport style but support event.
                 eventTrigger: el,
                 useLabel: LABEL_HOST_MAP.get(svgNodeTagLower) != null
             });
 
+            const els = regionElementMap.get(name) || regionElementMap.set(name, []);
+            els.push(el);
+
             // Only named element has silent: false, other elements should
             // act as background and has no user interaction.
             el.silent = false;
@@ -262,7 +264,7 @@ function buildGraphic(
         }
     }
 
-    return { root, boundingRect, regionGraphics };
+    return { root, boundingRect, regionGraphics, regionElementMap: regionElementMap };
 }
 
 
diff --git a/src/coord/geo/geoTypes.ts b/src/coord/geo/geoTypes.ts
index 773cf1c..8674218 100644
--- a/src/coord/geo/geoTypes.ts
+++ b/src/coord/geo/geoTypes.ts
@@ -155,6 +155,6 @@ export type RegionGraphic = {
     eventTrigger: Element;
     // Whether to set label on `el.textContent`.
     useLabel: boolean;
-    // Use this key to obtain style config in echarts option.
-    styleOptionKey: 'itemStyle' | 'lineStyle';
+    // Whether to be enabled to set style via in echarts option.
+    optionStyleEnabled: boolean;
 };
diff --git a/src/core/echarts.ts b/src/core/echarts.ts
index 799a564..2f652a3 100644
--- a/src/core/echarts.ts
+++ b/src/core/echarts.ts
@@ -41,8 +41,7 @@ import {
     isHighDownDispatcher,
     HOVER_STATE_EMPHASIS,
     HOVER_STATE_BLUR,
-    blurSeries,
-    blurSeriesFromPayload,
+    blurSeriesFromHighlightPayload,
     toggleSelectionFromPayload,
     updateSeriesElementSelection,
     getAllSelectedIndices,
@@ -60,7 +59,11 @@ import {
     enterSelect,
     leaveSelect,
     enterBlur,
-    allLeaveBlur
+    allLeaveBlur,
+    findComponentHighDownDispatchers,
+    blurComponent,
+    handleGlobalMouseOverForHighDown,
+    handleGlboalMouseOutForHighDown
 } from '../util/states';
 import * as modelUtil from '../util/model';
 import {throttle} from '../util/throttle';
@@ -1480,9 +1483,26 @@ class ECharts extends Eventful<ECEventDefinition> {
             // If dispatchAction before setOption, do nothing.
             ecModel && ecModel.eachComponent(condition, function (model) {
                 if (!excludeSeriesIdMap || excludeSeriesIdMap.get(model.id) == null) {
-                    if (isHighDownPayload(payload) && !payload.notBlur) {
-                        if (model instanceof SeriesModel) {
-                            blurSeriesFromPayload(model, payload, ecIns._api);
+                    if (isHighDownPayload(payload)) {
+                        if (payload.type === HIGHLIGHT_ACTION_TYPE) {
+                            if (model instanceof SeriesModel) {
+                                !payload.notBlur && blurSeriesFromHighlightPayload(model, payload, ecIns._api);
+                            }
+                            else {
+                                const { focusSelf, dispatchers } = findComponentHighDownDispatchers(
+                                    model.mainType, model.componentIndex, payload.name, ecIns._api
+                                );
+                                if (focusSelf && !payload.notBlur) {
+                                    blurComponent(model.mainType, model.componentIndex, ecIns._api);
+                                }
+                                // PENDING:
+                                // Whether to put this "enter emphasis" code in `ComponentView`,
+                                // which will be the same as `ChartView` but might be not necessary
+                                // and will be far from this logic.
+                                if (dispatchers) {
+                                    each(dispatchers, dispatcher => enterEmphasis(dispatcher));
+                                }
+                            }
                         }
                     }
                     else if (isSelectChangePayload(payload)) {
@@ -1780,7 +1800,7 @@ class ECharts extends Eventful<ECEventDefinition> {
             let eventObj: ECActionEvent;
 
             const isSelectChange = isSelectChangePayload(payload);
-            const isStatusChange = isHighDownPayload(payload) || isSelectChange;
+            const isHighDown = isHighDownPayload(payload);
 
             each(payloads, (batchItem) => {
                 // Action can specify the event by return it.
@@ -1792,11 +1812,16 @@ class ECharts extends Eventful<ECEventDefinition> {
                 eventObjBatch.push(eventObj);
 
                 // light update does not perform data process, layout and visual.
-                if (isStatusChange) {
-                    // method, payload, mainType, subType
+                if (isHighDown) {
+                    const { queryOptionMap, mainTypeSpecified } = modelUtil.preParseFinder(payload as ModelFinder);
+                    const componentMainType = mainTypeSpecified ? queryOptionMap.keys()[0] : 'series';
+                    updateDirectly(this, updateMethod, batchItem as Payload, componentMainType);
+                    markStatusToUpdate(this);
+                }
+                else if (isSelectChange) {
+                    // At present `dispatchAction({ type: 'select', ... })` is not supported on components.
+                    // geo still use 'geoselect'.
                     updateDirectly(this, updateMethod, batchItem as Payload, 'series');
-
-                    // Mark status to update
                     markStatusToUpdate(this);
                 }
                 else if (cptType) {
@@ -1804,7 +1829,7 @@ class ECharts extends Eventful<ECEventDefinition> {
                 }
             });
 
-            if (updateMethod !== 'none' && !isStatusChange && !cptType) {
+            if (updateMethod !== 'none' && !isHighDown && !isSelectChange && !cptType) {
                 // Still dirty
                 if (this[OPTION_UPDATED_KEY]) {
                     prepare(this);
@@ -1900,24 +1925,14 @@ class ECharts extends Eventful<ECEventDefinition> {
                 const el = e.target;
                 const dispatcher = findEventDispatcher(el, isHighDownDispatcher);
                 if (dispatcher) {
-                    const ecData = getECData(dispatcher);
-                    // Try blur all in the related series. Then emphasis the hoverred.
-                    // TODO. progressive mode.
-                    blurSeries(
-                        ecData.seriesIndex, ecData.focus, ecData.blurScope, ecIns._api
-                    );
-                    enterEmphasisWhenMouseOver(dispatcher, e);
-
+                    handleGlobalMouseOverForHighDown(dispatcher, e, ecIns._api);
                     markStatusToUpdate(ecIns);
                 }
             }).on('mouseout', function (e) {
                 const el = e.target;
                 const dispatcher = findEventDispatcher(el, isHighDownDispatcher);
                 if (dispatcher) {
-                    allLeaveBlur(ecIns._api);
-
-                    leaveEmphasisWhenMouseOut(dispatcher, e);
-
+                    handleGlboalMouseOutForHighDown(dispatcher, e, ecIns._api);
                     markStatusToUpdate(ecIns);
                 }
             }).on('click', function (e) {
diff --git a/src/util/graphic.ts b/src/util/graphic.ts
index 4428ff9..5823c05 100644
--- a/src/util/graphic.ts
+++ b/src/util/graphic.ts
@@ -846,9 +846,10 @@ export function setTooltipConfig(opt: {
         });
     }
 
-    getECData(opt.el).tooltipConfig = {
-        componentMainType: mainType,
-        componentIndex: componentIndex,
+    const ecData = getECData(opt.el);
+    ecData.componentMainType = mainType;
+    ecData.componentIndex = componentIndex;
+    ecData.tooltipConfig = {
         name: itemName,
         option: defaults({
             content: itemName,
diff --git a/src/util/innerStore.ts b/src/util/innerStore.ts
index 65f5aad..9fa4355 100644
--- a/src/util/innerStore.ts
+++ b/src/util/innerStore.ts
@@ -34,14 +34,18 @@ export interface ECData {
     dataType?: SeriesDataType;
     focus?: InnerFocus;
     blurScope?: BlurScope;
+
+    // Required by `tooltipConfig` and `focus`.
+    componentMainType?: ComponentMainType;
+    componentIndex?: number;
+    componentHighDownName?: string;
+
+    // To make a tooltipConfig, seach `setTooltipConfig`.
+    // Used to find component tooltip option, which is used as
+    // the parent of tooltipConfig.option for cascading.
+    // If not provided, do not use component as its parent.
+    // (Set manatary to make developers not to forget them).
     tooltipConfig?: {
-        // To make a tooltipConfig, seach `setTooltipConfig`.
-        // Used to find component tooltip option, which is used as
-        // the parent of tooltipConfig.option for cascading.
-        // If not provided, do not use component as its parent.
-        // (Set manatary to make developers not to forget them).
-        componentMainType: ComponentMainType;
-        componentIndex: number;
         // Target item name to locate tooltip.
         name: string;
         option: ComponentItemTooltipOption<unknown>;
diff --git a/src/util/states.ts b/src/util/states.ts
index b3de1c1..896bc70 100644
--- a/src/util/states.ts
+++ b/src/util/states.ts
@@ -35,9 +35,10 @@ import {
     Payload,
     ZRColor,
     HighlightPayload,
-    DownplayPayload
+    DownplayPayload,
+    ComponentMainType
 } from './types';
-import { extend, indexOf, isArrayLike, isObject, keys, isArray, each } from 'zrender/src/core/util';
+import { extend, indexOf, isArrayLike, isObject, keys, isArray, each, assert } from 'zrender/src/core/util';
 import { getECData } from './innerStore';
 import * as colorTool from 'zrender/src/tool/color';
 import List from '../data/List';
@@ -47,6 +48,8 @@ import { queryDataIndex, makeInner } from './model';
 import Path, { PathStyleProps } from 'zrender/src/graphic/Path';
 import GlobalModel from '../model/Global';
 import ExtensionAPI from '../core/ExtensionAPI';
+import ComponentModel from '../model/Component';
+
 
 // Reserve 0 as default.
 let _highlightNextDigit = 1;
@@ -223,7 +226,7 @@ function createEmphasisDefaultState(
     stateName: 'emphasis',
     targetStates: string[],
     state: Displayable['states'][number]
-) {
+): DisplayableState {
     const hasSelect = targetStates && indexOf(targetStates, 'select') >= 0;
     let cloned = false;
     if (el instanceof Path) {
@@ -270,7 +273,7 @@ function createSelectDefaultState(
     el: Displayable,
     stateName: 'select',
     state: Displayable['states'][number]
-) {
+): DisplayableState {
     // const hasSelect = indexOf(el.currentStates, stateName) >= 0;
     if (state) {
         // TODO Share with textContent?
@@ -287,7 +290,7 @@ function createBlurDefaultState(
     el: Displayable,
     stateName: 'blur',
     state: Displayable['states'][number]
-) {
+): DisplayableState {
     const hasBlur = indexOf(el.currentStates, stateName) >= 0;
     const currentOpacity = el.style.opacity;
 
@@ -482,16 +485,35 @@ export function blurSeries(
     });
 }
 
-export function blurSeriesFromPayload(
-    seriesModel: SeriesModel,
-    payload: Payload,
+export function blurComponent(
+    componentMainType: ComponentMainType,
+    componentIndex: number,
     api: ExtensionAPI
 ) {
-    if (!isHighDownPayload(payload)) {
+    if (componentMainType == null || componentIndex == null) {
+        return;
+    }
+
+    const componentModel = api.getModel().getComponent(componentMainType, componentIndex);
+    if (!componentModel) {
         return;
     }
 
-    const isHighlight = payload.type === HIGHLIGHT_ACTION_TYPE;
+    const view = api.getViewOfComponentModel(componentModel);
+    if (!view || !view.focusBlurEnabled) {
+        return;
+    }
+
+    view.group.traverse(function (child) {
+        singleEnterBlur(child);
+    });
+}
+
+export function blurSeriesFromHighlightPayload(
+    seriesModel: SeriesModel,
+    payload: HighlightPayload,
+    api: ExtensionAPI
+) {
     const seriesIndex = seriesModel.seriesIndex;
     const data = seriesModel.getData(payload.dataType);
     let dataIndex = queryDataIndex(data, payload);
@@ -507,25 +529,133 @@ export function blurSeriesFromPayload(
         }
     }
 
-    if (isHighlight) {
-        if (el) {
-            const ecData = getECData(el);
-            blurSeries(
-                seriesIndex, ecData.focus, ecData.blurScope, api
-            );
+    if (el) {
+        const ecData = getECData(el);
+        blurSeries(
+            seriesIndex, ecData.focus, ecData.blurScope, api
+        );
+    }
+    else {
+        // If there is no element put on the data. Try getting it from raw option
+        // TODO Should put it on seriesModel?
+        const focus = seriesModel.get(['emphasis', 'focus']);
+        const blurScope = seriesModel.get(['emphasis', 'blurScope']);
+        if (focus != null) {
+            blurSeries(seriesIndex, focus, blurScope, api);
         }
-        else {
-            // If there is no element put on the data. Try getting it from raw option
-            // TODO Should put it on seriesModel?
-            const focus = seriesModel.get(['emphasis', 'focus']);
-            const blurScope = seriesModel.get(['emphasis', 'blurScope']);
-            if (focus != null) {
-                blurSeries(seriesIndex, focus, blurScope, api);
-            }
+    }
+}
+
+export function findComponentHighDownDispatchers(
+    componentMainType: ComponentMainType,
+    componentIndex: number,
+    name: string,
+    api: ExtensionAPI
+): {
+    focusSelf: boolean;
+    // If return null/undefined, do not support this feature.
+    dispatchers: Element[];
+} {
+    const ret = {
+        focusSelf: false,
+        dispatchers: null as Element[]
+    };
+    if (componentMainType == null
+        || componentMainType === 'series'
+        || componentIndex == null
+        || name == null
+    ) {
+        return ret;
+    }
+
+    const componentModel = api.getModel().getComponent(componentMainType, componentIndex);
+    if (!componentModel) {
+        return ret;
+    }
+
+    const view = api.getViewOfComponentModel(componentModel);
+    if (!view || !view.findHighDownDispatchers) {
+        return ret;
+    }
+
+    const dispatchers = view.findHighDownDispatchers(name);
+
+    // At presnet, the component (like Geo) only blur inside itself.
+    // So we do not use `blurScope` in component.
+    let focusSelf: boolean;
+    for (let i = 0; i < dispatchers.length; i++) {
+        if (__DEV__) {
+            assert(isHighDownDispatcher(dispatchers[i]));
+        }
+        if (getECData(dispatchers[i]).focus === 'self') {
+            focusSelf = true;
+            break;
         }
     }
+
+    return { focusSelf, dispatchers };
+}
+
+export function handleGlobalMouseOverForHighDown(
+    dispatcher: Element,
+    e: ElementEvent,
+    api: ExtensionAPI
+): void {
+    if (__DEV__) {
+        assert(isHighDownDispatcher(dispatcher));
+    }
+
+    const ecData = getECData(dispatcher);
+
+    const { dispatchers, focusSelf } = findComponentHighDownDispatchers(
+        ecData.componentMainType, ecData.componentIndex, ecData.componentHighDownName, api
+    );
+    // If `findHighDownDispatchers` is supported on the component,
+    // highlight/downplay elements with the same name.
+    if (dispatchers) {
+        if (focusSelf) {
+            blurComponent(ecData.componentMainType, ecData.componentIndex, api);
+        }
+        each(dispatchers, dispatcher => enterEmphasisWhenMouseOver(dispatcher, e));
+    }
+    else {
+        // Try blur all in the related series. Then emphasis the hoverred.
+        // TODO. progressive mode.
+        blurSeries(ecData.seriesIndex, ecData.focus, ecData.blurScope, api);
+        if (ecData.focus === 'self') {
+            blurComponent(ecData.componentMainType, ecData.componentIndex, api);
+        }
+        // Other than series, component that not support `findHighDownDispatcher` will
+        // also use it. But in this case, highlight/downplay are only supported in
+        // mouse hover but not in dispatchAction.
+        enterEmphasisWhenMouseOver(dispatcher, e);
+    }
 }
 
+export function handleGlboalMouseOutForHighDown(
+    dispatcher: Element,
+    e: ElementEvent,
+    api: ExtensionAPI
+): void {
+    if (__DEV__) {
+        assert(isHighDownDispatcher(dispatcher));
+    }
+
+    allLeaveBlur(api);
+
+    const ecData = getECData(dispatcher);
+    const { dispatchers } = findComponentHighDownDispatchers(
+        ecData.componentMainType, ecData.componentIndex, ecData.componentHighDownName, api
+    );
+    if (dispatchers) {
+        each(dispatchers, dispatcher => leaveEmphasisWhenMouseOut(dispatcher, e));
+    }
+    else {
+        leaveEmphasisWhenMouseOut(dispatcher, e);
+    }
+}
+
+
 export function toggleSelectionFromPayload(
     seriesModel: SeriesModel,
     payload: Payload,
@@ -680,6 +810,22 @@ export function isHighDownDispatcher(el: Element): boolean {
 }
 
 /**
+ * Enable component highlight/downplay features:
+ * + hover link (within the same name)
+ * + focus blur in component
+ */
+export function enableComponentHighDownFeatures(
+    el: Element,
+    componentModel: ComponentModel,
+    componentHighDownName: string
+): void {
+    const ecData = getECData(el);
+    ecData.componentMainType = componentModel.mainType;
+    ecData.componentIndex = componentModel.componentIndex;
+    ecData.componentHighDownName = componentHighDownName;
+}
+
+/**
  * Support hightlight/downplay record on each elements.
  * For the case: hover highlight/downplay (legend, visualMap, ...) and
  * user triggerred hightlight/downplay should not conflict.
diff --git a/src/util/types.ts b/src/util/types.ts
index e519ec9..4d0aae2 100644
--- a/src/util/types.ts
+++ b/src/util/types.ts
@@ -1484,7 +1484,7 @@ export type BlurScope = 'coordinateSystem' | 'series' | 'global';
  * can be array of data indices.
  * Or may be an dictionary if have different types of data like in graph.
  */
-export type InnerFocus = string | ArrayLike<number> | Dictionary<ArrayLike<number>>;
+export type InnerFocus = DefaultEmphasisFocus | ArrayLike<number> | Dictionary<ArrayLike<number>>;
 
 export interface DefaultExtraStateOpts {
     emphasis: any
diff --git a/src/view/Component.ts b/src/view/Component.ts
index 8521e84..cfefd04 100644
--- a/src/view/Component.ts
+++ b/src/view/Component.ts
@@ -32,7 +32,7 @@ interface ComponentView {
      * Implement it if needed.
      */
     updateTransform?(
-        seriesModel: ComponentModel, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload
+        model: ComponentModel, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload
     ): void | {update: true};
 
     /**
@@ -42,6 +42,22 @@ interface ComponentView {
     filterForExposedEvent(
         eventType: string, query: EventQueryItem, targetEl: Element, packedEvent: ECActionEvent | ECElementEvent
     ): boolean;
+
+    /**
+     * Find dispatchers for highlight/downplay by name.
+     * If this methods provided, hover link (within the same name) is enabled in component.
+     * That is, in component, a name can correspond to multiple dispatchers.
+     * Those dispatchers can have no common ancestor.
+     * The highlight/downplay state change will be applied on the
+     * dispatchers and their descendents.
+     *
+     * @return Must return an array but not null/undefined.
+     */
+    findHighDownDispatchers?(
+        name: string
+    ): Element[];
+
+    focusBlurEnabled?: boolean;
 }
 
 class ComponentView {
diff --git a/test/geo-svg-demo.html b/test/geo-svg-demo.html
new file mode 100644
index 0000000..ed5afe8
--- /dev/null
+++ b/test/geo-svg-demo.html
@@ -0,0 +1,240 @@
+<!DOCTYPE html>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+
+<html>
+    <head>
+        <meta charset="utf-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1" />
+        <script src="lib/esl.js"></script>
+        <script src="lib/config.js"></script>
+        <script src="lib/jquery.min.js"></script>
+        <script src="lib/facePrint.js"></script>
+        <script src="lib/testHelper.js"></script>
+        <!-- <script src="ut/lib/canteen.js"></script> -->
+        <link rel="stylesheet" href="lib/reset.css" />
+    </head>
+    <body>
+        <style>
+        </style>
+
+
+
+        <div id="main_geo_svg_organ"></div>
+        <!-- <div id="main_geo_svg_organ1"></div> -->
+
+
+
+
+
+        <script>
+            function listenAndPrintEvent(chart) {
+                if (!chart) {
+                    return;
+                }
+                const out = {
+                };
+                chart.on('geoselectchanged', function (params) {
+                    out.geoselectechanged = {
+                        allSelected: params.allSelected
+                    };
+                    console.log('geoselectechanged', params);
+                    chart.__testHelper.updateInfo(out, 'event');
+                });
+                chart.on('selectchanged', function (params) {
+                    out.selectechanged = {
+                        selected: params.selected
+                    };
+                    console.log('selectechanged', params);
+                    chart.__testHelper.updateInfo(out, 'event');
+                });
+                chart.on('click', function (params) {
+                    out.click = {
+                        componentIndex: params.componentIndex,
+                        componentType: params.componentType,
+                        geoIndex: params.geoIndex,
+                        name: params.name
+                    };
+                    console.log('click', params);
+                    chart.__testHelper.updateInfo(out, 'event');
+                });
+            }
+        </script>
+
+
+
+
+
+
+        <script>
+        require(['echarts'/*, 'map/js/china' */], function (echarts) {
+            var option;
+            $.ajax({
+                url: '../../vis-data/map/svg/organ/Veins_Medical_Diagram_clip_art.svg',
+                dataType: 'text'
+            }).done(function (svg) {
+
+                echarts.registerMap('seatmap', {
+                    svg: svg
+                });
+
+                option = {
+                    tooltip: {
+                    },
+                    geo: {
+                        map: 'seatmap',
+                        roam: true,
+                        selectedMode: 'multiple',
+                        // height: 100,
+                        // zoom: 1.5
+                        emphasis: {
+                            focus: 'self',
+                            itemStyle: {
+                                // color: null
+                            },
+                            label: {
+                                show: false,
+                                textBorderColor: '#fff',
+                                textBorderWidth: 2
+                            }
+                        },
+                        blur: {
+                            // itemStyle: {
+                            //     opacity: 0.3
+                            // }
+                        },
+                        select: {
+                            itemStyle: {
+                                color: '#b50205'
+                            },
+                            label: {
+                                show: false,
+                                textBorderColor: '#fff',
+                                textBorderWidth: 2
+                            }
+                        }
+                    }
+                };
+
+                var chart = testHelper.create(echarts, 'main_geo_svg_organ', {
+                    title: [
+                        'pure geo component with svg resource',
+                        'click seat: check **allSelected** correct.'
+                    ],
+                    option: option,
+                    info: {},
+                    infoKey: 'event',
+                    height: 500
+                    // buttons: [{text: 'btn-txt', onclick: function () {}}],
+                    // recordCanvas: true,
+                });
+
+                listenAndPrintEvent(chart);
+
+                if (chart) {
+                    chart.on('highlight', function () {
+                        console.log('agsd');
+                    });
+                }
+
+            });
+
+        });
+        </script>
+
+
+
+
+<!--
+
+
+        <script>
+        require(['echarts'/*, 'map/js/china' */], function (echarts) {
+            var option;
+            $.ajax({
+                url: '../../vis-data/map/svg/organ/Veins_Medical_Diagram_clip_art.svg',
+                dataType: 'text'
+            }).done(function (svg) {
+
+                echarts.registerMap('seatmap', {
+                    svg: svg
+                });
+
+                option = {
+                    tooltip: {
+                    },
+                    series: {
+                        type: 'map',
+                        map: 'seatmap',
+                        roam: true,
+                        selectedMode: 'multiple',
+                        // height: 100,
+                        // zoom: 1.5
+                        emphasis: {
+                            focus: 'self',
+                            label: {
+                                textBorderColor: '#fff',
+                                textBorderWidth: 2
+                            }
+                        },
+                        select: {
+                            itemStyle: {
+                                color: '#b50205'
+                            },
+                            label: {
+                                show: false,
+                                textBorderColor: '#fff',
+                                textBorderWidth: 2
+                            }
+                        }
+                    }
+                };
+
+                var chart = testHelper.create(echarts, 'main_geo_svg_organ1', {
+                    title: [
+                        'pure geo component with svg resource',
+                        'click seat: check **allSelected** correct.'
+                    ],
+                    option: option,
+                    info: {},
+                    infoKey: 'event',
+                    height: 500
+                    // buttons: [{text: 'btn-txt', onclick: function () {}}],
+                    // recordCanvas: true,
+                });
+
+                listenAndPrintEvent(chart);
+
+                if (chart) {
+                    chart.on('highlight', function () {
+                        console.log('agsd');
+                    });
+                }
+
+            });
+
+        });
+        </script> -->
+
+
+
+    </body>
+</html>
+

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


[echarts] 02/02: featrue: [geo] (1) support name on . (2) some refactor.

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

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

commit 8235095d6547ca062086bacccdc3963c9e92ed08
Author: 100pah <su...@gmail.com>
AuthorDate: Thu Apr 1 06:35:56 2021 +0800

    featrue: [geo]
    (1) support name on <g>.
    (2) some refactor.
---
 src/component/helper/MapDraw.ts | 497 ++++++++++++++++++++++++----------------
 src/coord/geo/Geo.ts            |   2 +-
 src/coord/geo/GeoSVGResource.ts | 171 ++++++++------
 src/coord/geo/geoTypes.ts       |  18 --
 src/core/echarts.ts             |  40 ++--
 test/geo-svg-demo.html          | 298 +++++++++++++++++++++---
 test/geo-svg.html               | 249 ++++++++++----------
 7 files changed, 818 insertions(+), 457 deletions(-)

diff --git a/src/component/helper/MapDraw.ts b/src/component/helper/MapDraw.ts
index c589119..22caaeb 100644
--- a/src/component/helper/MapDraw.ts
+++ b/src/component/helper/MapDraw.ts
@@ -26,7 +26,8 @@ import {
     enableHoverEmphasis,
     DISPLAY_STATES,
     enableComponentHighDownFeatures,
-    setDefaultStateProxy
+    setDefaultStateProxy,
+    SPECIAL_STATES
 } from '../../util/states';
 import geoSourceManager from '../../coord/geo/geoSourceManager';
 import {getUID} from '../../util/component';
@@ -39,7 +40,7 @@ import GeoView from '../geo/GeoView';
 import MapView from '../../chart/map/MapView';
 import Geo from '../../coord/geo/Geo';
 import Model from '../../model/Model';
-import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle';
+import { setLabelStyle, getLabelStatesModels, createTextConfig } from '../../label/labelStyle';
 import { getECData } from '../../util/innerStore';
 import { createOrUpdatePatternFromDecal } from '../../util/decal';
 import { ViewCoordSysTransformInfoPart } from '../../coord/View';
@@ -48,14 +49,16 @@ import Displayable from 'zrender/src/graphic/Displayable';
 import Element, { ElementTextConfig } from 'zrender/src/Element';
 import List from '../../data/List';
 import { GeoJSONRegion } from '../../coord/geo/Region';
-import { RegionGraphic } from '../../coord/geo/geoTypes';
-import { ItemStyleProps } from '../../model/mixin/itemStyle';
-import { LineStyleProps } from '../../model/mixin/lineStyle';
+import { SVGNodeTagLower } from 'zrender/src/tool/parseSVG';
 
 
 interface RegionsGroup extends graphic.Group {
 }
 
+type RegionModel = ReturnType<GeoModel['getRegionModel']> | ReturnType<MapSeries['getRegionModel']>;
+
+type MapOrGeoModel = GeoModel | MapSeries;
+
 interface ViewBuildContext {
     api: ExtensionAPI;
     geo: Geo;
@@ -70,6 +73,26 @@ interface GeoStyleableOption {
     itemStyle?: GeoItemStyleOption;
     lineStyle?: LineStyleOption;
 }
+type RegionName = string;
+
+/**
+ * Only these tags enable use `itemStyle` if they are named in SVG.
+ * Other tags like <text> <tspan> <image> might not suitable for `itemStyle`.
+ * They will not be considered to be styled until some requirements come.
+ */
+const OPTION_STYLE_ENABLED_TAGS: SVGNodeTagLower[] = [
+    'rect', 'circle', 'line', 'ellipse', 'polygon', 'polyline', 'path'
+];
+const OPTION_STYLE_ENABLED_TAG_MAP = zrUtil.createHashMap<number, SVGNodeTagLower>(
+    OPTION_STYLE_ENABLED_TAGS
+);
+const STATE_TRIGGER_TAG_MAP = zrUtil.createHashMap<number, SVGNodeTagLower>(
+    OPTION_STYLE_ENABLED_TAGS.concat(['g']) as SVGNodeTagLower[]
+);
+const LABEL_HOST_MAP = zrUtil.createHashMap<number, SVGNodeTagLower>(
+    OPTION_STYLE_ENABLED_TAGS.concat(['g']) as SVGNodeTagLower[]
+);
+
 
 function getFixedItemStyle(model: Model<GeoItemStyleOption>) {
     const itemStyle = model.getItemStyle();
@@ -115,6 +138,10 @@ class MapDraw {
 
     private _svgGraphicRecord: GeoSVGGraphicRecord;
 
+    // A name may correspond to multiple graphics.
+    // Used as event dispatcher.
+    private _svgDispatcherMap: zrUtil.HashMap<Element[], RegionName>;
+
 
     constructor(api: ExtensionAPI) {
         const group = new graphic.Group();
@@ -199,6 +226,8 @@ class MapDraw {
         const nameMap = this._regionsGroupByName = zrUtil.createHashMap<RegionsGroup>();
         const regionsGroup = this._regionsGroup;
         const transformInfoRaw = viewBuildCtx.transformInfoRaw;
+        const mapOrGeoModel = viewBuildCtx.mapOrGeoModel;
+        const data = viewBuildCtx.data;
 
         const transformPoint = function (point: number[]): number[] {
             return [
@@ -211,14 +240,31 @@ class MapDraw {
 
         // Only when the resource is GeoJSON, there is `geo.regions`.
         zrUtil.each(viewBuildCtx.geo.regions, function (region: GeoJSONRegion) {
+            const regionName = region.name;
+            const regionModel = mapOrGeoModel.getRegionModel(regionName);
+            const dataIdx = data ? data.indexOfName(regionName) : null;
 
             // Consider in GeoJson properties.name may be duplicated, for example,
             // there is multiple region named "United Kindom" or "France" (so many
             // colonies). And it is not appropriate to merge them in geo, which
             // will make them share the same label and bring trouble in label
             // location calculation.
-            const regionGroup = nameMap.get(region.name)
-                || nameMap.set(region.name, new graphic.Group() as RegionsGroup);
+            let regionGroup = nameMap.get(regionName);
+
+            if (!regionGroup) {
+                regionGroup = nameMap.set(regionName, new graphic.Group() as RegionsGroup);
+                regionsGroup.add(regionGroup);
+
+                resetEventTriggerForRegion(
+                    viewBuildCtx, regionGroup, regionName, regionModel, mapOrGeoModel, dataIdx
+                );
+                resetTooltipForRegion(
+                    viewBuildCtx, regionGroup, regionName, regionModel, mapOrGeoModel
+                );
+                resetStateTriggerForRegion(
+                    viewBuildCtx, regionGroup, regionName, regionModel, mapOrGeoModel
+                );
+            }
 
             const compoundPath = new graphic.CompoundPath({
                 segmentIgnoreThreshold: 1,
@@ -258,18 +304,18 @@ class MapDraw {
                 }
             });
 
+            applyOptionStyleForRegion(
+                viewBuildCtx, compoundPath, dataIdx, regionModel
+            );
+
+            if (compoundPath instanceof Displayable) {
+                compoundPath.culling = true;
+            }
+
             const centerPt = transformPoint(region.getCenter());
-            const regionGraphic: RegionGraphic = {
-                name: region.name,
-                el: compoundPath,
-                optionStyleEnabled: true,
-                stateTrigger: regionGroup,
-                eventTrigger: regionGroup,
-                useLabel: true
-            };
-            this._resetSingleRegionGraphic(viewBuildCtx, regionGraphic, centerPt, null, false);
-
-            regionsGroup.add(regionGroup);
+            resetLabelForRegion(
+                viewBuildCtx, compoundPath, regionName, regionModel, mapOrGeoModel, dataIdx, centerPt
+            );
 
         }, this);
     }
@@ -288,22 +334,69 @@ class MapDraw {
             this._useSVG(mapName);
         }
 
+        const svgDispatcherMap = this._svgDispatcherMap = zrUtil.createHashMap<Element[], RegionName>();
+
         let focusSelf = false;
-        zrUtil.each(this._svgGraphicRecord.regionGraphics, function (regionGraphic) {
+        zrUtil.each(this._svgGraphicRecord.named, function (namedItem) {
             // Note that we also allow different elements have the same name.
             // For example, a glyph of a city and the label of the city have
             // the same name and their tooltip info can be defined in a single
             // region option.
-            const focus = this._resetSingleRegionGraphic(
-                viewBuildCtx, regionGraphic, [0, 0], 'inside',
-                // We do not know how the SVG like so we'd better not to change z2.
-                // Otherwise it might bring some unexpected result. For example,
-                // an area hovered that make some inner city can not be clicked.
-                true
-            );
-            if (focus === 'self') {
-                focusSelf = true;
+
+            const regionName = namedItem.name;
+            const mapOrGeoModel = viewBuildCtx.mapOrGeoModel;
+            const data = viewBuildCtx.data;
+            const svgNodeTagLower = namedItem.svgNodeTagLower;
+            const el = namedItem.el;
+
+            const dataIdx = data ? data.indexOfName(regionName) : null;
+            const regionModel = mapOrGeoModel.getRegionModel(regionName);
+
+            if (OPTION_STYLE_ENABLED_TAG_MAP.get(svgNodeTagLower) != null
+                && (el instanceof Displayable)
+            ) {
+                applyOptionStyleForRegion(viewBuildCtx, el, dataIdx, regionModel);
+            }
+
+            if (el instanceof Displayable) {
+                el.culling = true;
+            }
+
+            // We do not know how the SVG like so we'd better not to change z2.
+            // Otherwise it might bring some unexpected result. For example,
+            // an area hovered that make some inner city can not be clicked.
+            (el as ECElement).z2EmphasisLift = 0;
+
+            // If self named, that is, if tag is inside a named <g> (where `namedFrom` does not exists):
+            if (!namedItem.namedFrom) {
+                // label should batter to be displayed based on the center of <g>
+                // if it is named rather than displayed on each child.
+                if (LABEL_HOST_MAP.get(svgNodeTagLower) != null) {
+                    resetLabelForRegion(
+                        viewBuildCtx, el, regionName, regionModel, mapOrGeoModel, dataIdx, [0, 0]
+                    );
+                }
+
+                resetEventTriggerForRegion(
+                    viewBuildCtx, el, regionName, regionModel, mapOrGeoModel, dataIdx
+                );
+
+                resetTooltipForRegion(
+                    viewBuildCtx, el, regionName, regionModel, mapOrGeoModel
+                );
+
+                if (STATE_TRIGGER_TAG_MAP.get(svgNodeTagLower) != null) {
+                    const focus = resetStateTriggerForRegion(
+                        viewBuildCtx, el, regionName, regionModel, mapOrGeoModel
+                    );
+                    if (focus === 'self') {
+                        focusSelf = true;
+                    }
+                    const els = svgDispatcherMap.get(regionName) || svgDispatcherMap.set(regionName, []);
+                    els.push(el);
+                }
             }
+
         }, this);
 
         // It's a little complicated to support blurring the entire geoSVG in series-map.
@@ -334,157 +427,6 @@ class MapDraw {
         }
     }
 
-    private _resetSingleRegionGraphic(
-        viewBuildCtx: ViewBuildContext,
-        regionGraphic: RegionGraphic,
-        labelXY: number[],
-        labelPosition: ElementTextConfig['position'],
-        noZ2EmphasisLift: boolean
-    ): InnerFocus {
-
-        const regionName = regionGraphic.name;
-        const mapOrGeoModel = viewBuildCtx.mapOrGeoModel;
-        const data = viewBuildCtx.data;
-        const isGeo = viewBuildCtx.isGeo;
-
-        const dataIdx = data ? data.indexOfName(regionName) : null;
-        const regionModel = mapOrGeoModel.getRegionModel(regionName);
-
-        applyOptionStyleForRegion(viewBuildCtx, regionGraphic, dataIdx, regionModel);
-
-        if (regionGraphic.el instanceof Displayable) {
-            regionGraphic.el.culling = true;
-        }
-        if (noZ2EmphasisLift) {
-            (regionGraphic.el as ECElement).z2EmphasisLift = 0;
-        }
-
-        let showLabel = false;
-        for (let i = 0; i < DISPLAY_STATES.length; i++) {
-            const stateName = DISPLAY_STATES[i];
-            // @ts-ignore FIXME:TS fix the "compatible with each other"?
-            if (regionModel.get(
-                stateName === 'normal' ? ['label', 'show'] : [stateName, 'label', 'show']
-            )) {
-                showLabel = true;
-                break;
-            }
-        }
-
-        const isDataNaN = data && isNaN(data.get(data.mapDimension('value'), dataIdx) as number);
-        const itemLayout = data && data.getItemLayout(dataIdx);
-
-        // In the following cases label will be drawn
-        // 1. In map series and data value is NaN
-        // 2. In geo component
-        // 3. Region has no series legendSymbol, which will be add a showLabel flag in mapSymbolLayout
-        if (
-            regionGraphic.useLabel
-            && (
-                ((isGeo || isDataNaN) && showLabel)
-                || (itemLayout && itemLayout.showLabel)
-            )
-        ) {
-            const query = !isGeo ? dataIdx : regionName;
-            let labelFetcher;
-
-            // Consider dataIdx not found.
-            if (!data || dataIdx >= 0) {
-                labelFetcher = mapOrGeoModel;
-            }
-
-            const textEl = new graphic.Text({
-                x: labelXY[0],
-                y: labelXY[1],
-                z2: 10,
-                silent: true
-            });
-            textEl.afterUpdate = labelTextAfterUpdate;
-
-            setLabelStyle<typeof query>(
-                textEl, getLabelStatesModels(regionModel),
-                {
-                    labelFetcher: labelFetcher,
-                    labelDataIndex: query,
-                    defaultText: regionName
-                },
-                { normal: {
-                    align: 'center',
-                    verticalAlign: 'middle'
-                } }
-            );
-
-            regionGraphic.el.setTextContent(textEl);
-            regionGraphic.el.setTextConfig({
-                local: true,
-                insideFill: textEl.style.fill,
-                position: labelPosition
-            });
-            (regionGraphic.el as ECElement).disableLabelAnimation = true;
-        }
-        else {
-            regionGraphic.el.removeTextContent();
-            regionGraphic.el.removeTextConfig();
-            (regionGraphic.el as ECElement).disableLabelAnimation = null;
-        }
-
-        // setItemGraphicEl, setHoverStyle after all polygons and labels
-        // are added to the rigionGroup
-        if (data) {
-            // FIXME: when series-map use a SVG map, and there are duplicated name specified
-            // on different SVG elements, after `data.setItemGraphicEl(...)`:
-            // (1) all of them will be mounted with `dataIndex`, `seriesIndex`, so that tooltip
-            // can be triggered only mouse hover. That's correct.
-            // (2) only the last element will be kept in `data`, so that if trigger tooltip
-            // by `dispatchAction`, only the last one can be found and triggered. That might be
-            // not correct. We will fix it in future if anyone demanding that.
-            data.setItemGraphicEl(dataIdx, regionGraphic.eventTrigger);
-        }
-        // series-map will not trigger "geoselectchange" no matter it is
-        // based on a declared geo component. Becuause series-map will
-        // trigger "selectchange". If it trigger both the two events,
-        // If users call `chart.dispatchAction({type: 'toggleSelect'})`,
-        // it not easy to also fire event "geoselectchanged".
-        else {
-            // Package custom mouse event for geo component
-            getECData(regionGraphic.eventTrigger).eventData = {
-                componentType: 'geo',
-                componentIndex: mapOrGeoModel.componentIndex,
-                geoIndex: mapOrGeoModel.componentIndex,
-                name: regionName,
-                region: (regionModel && regionModel.option) || {}
-            };
-        }
-
-        if (!data) {
-            graphic.setTooltipConfig({
-                el: regionGraphic.el,
-                componentModel: mapOrGeoModel,
-                itemName: regionName,
-                // @ts-ignore FIXME:TS fix the "compatible with each other"?
-                itemTooltipOption: regionModel.get('tooltip')
-            });
-        }
-
-        let focus;
-        const stateTrigger = regionGraphic.stateTrigger;
-        if (stateTrigger) {
-            // @ts-ignore FIXME:TS fix the "compatible with each other"?
-            stateTrigger.highDownSilentOnTouch = !!mapOrGeoModel.get('selectedMode');
-            // @ts-ignore FIXME:TS fix the "compatible with each other"?
-            const emphasisModel = regionModel.getModel('emphasis');
-            focus = emphasisModel.get('focus');
-            enableHoverEmphasis(
-                stateTrigger, focus, emphasisModel.get('blurScope')
-            );
-            if (isGeo) {
-                enableComponentHighDownFeatures(stateTrigger, mapOrGeoModel as GeoModel, regionName);
-            }
-        }
-
-        return focus;
-    }
-
     remove(): void {
         this._regionsGroup.removeAll();
         this._regionsGroupByName = null;
@@ -509,7 +451,7 @@ class MapDraw {
             }
         }
         else if (geo.resourceType === 'geoSVG') {
-            return this._svgGraphicRecord.regionElementMap.get(name) || [];
+            return this._svgDispatcherMap && this._svgDispatcherMap.get(name) || [];
         }
     }
 
@@ -538,6 +480,7 @@ class MapDraw {
             (resource as GeoSVGResource).freeGraphic(this.uid);
         }
         this._svgGraphicRecord = null;
+        this._svgDispatcherMap = null;
         this._svgGroup.removeAll();
         this._svgMapName = null;
     }
@@ -641,7 +584,7 @@ function labelTextAfterUpdate(this: graphic.Text) {
 
 function applyOptionStyleForRegion(
     viewBuildCtx: ViewBuildContext,
-    regionGraphic: RegionGraphic,
+    el: Displayable,
     dataIndex: number,
     regionModel: Model<
         GeoStyleableOption & {
@@ -651,14 +594,6 @@ function applyOptionStyleForRegion(
         }
     >
 ): void {
-
-    if (
-        !regionGraphic.optionStyleEnabled
-        || !(regionGraphic.el instanceof Displayable)
-    ) {
-        return;
-    }
-
     // All of the path are using `itemStyle`, becuase
     // (1) Some SVG also use fill on polyline (The different between
     // polyline and polygon is "open" or "close" but not fill or not).
@@ -698,11 +633,185 @@ function applyOptionStyleForRegion(
 
     // SVG text, tspan and image can be named but not supporeted
     // to be styled by region option yet.
-    regionGraphic.el.setStyle(normalStyle);
-    regionGraphic.el.style.strokeNoScale = true;
-    regionGraphic.el.ensureState('emphasis').style = emphasisStyle;
-    regionGraphic.el.ensureState('select').style = selectStyle;
-    regionGraphic.el.ensureState('blur').style = blurStyle;
+    el.setStyle(normalStyle);
+    el.style.strokeNoScale = true;
+    el.ensureState('emphasis').style = emphasisStyle;
+    el.ensureState('select').style = selectStyle;
+    el.ensureState('blur').style = blurStyle;
+}
+
+function resetLabelForRegion(
+    viewBuildCtx: ViewBuildContext,
+    el: Element,
+    regionName: string,
+    regionModel: RegionModel,
+    mapOrGeoModel: MapOrGeoModel,
+    // Exist only if `viewBuildCtx.data` exists.
+    dataIdx: number,
+    labelXY: number[]
+): void {
+    const data = viewBuildCtx.data;
+    const isGeo = viewBuildCtx.isGeo;
+
+    let showLabel = false;
+    for (let i = 0; i < DISPLAY_STATES.length; i++) {
+        const stateName = DISPLAY_STATES[i];
+        // @ts-ignore FIXME:TS fix the "compatible with each other"?
+        if (regionModel.get(
+            stateName === 'normal' ? ['label', 'show'] : [stateName, 'label', 'show']
+        )) {
+            showLabel = true;
+            break;
+        }
+    }
+
+    const isDataNaN = data && isNaN(data.get(data.mapDimension('value'), dataIdx) as number);
+    const itemLayout = data && data.getItemLayout(dataIdx);
+
+    // In the following cases label will be drawn
+    // 1. In map series and data value is NaN
+    // 2. In geo component
+    // 3. Region has no series legendSymbol, which will be add a showLabel flag in mapSymbolLayout
+    if (
+        ((isGeo || isDataNaN) && showLabel)
+        || (itemLayout && itemLayout.showLabel)
+    ) {
+        const query = !isGeo ? dataIdx : regionName;
+        let labelFetcher;
+
+        // Consider dataIdx not found.
+        if (!data || dataIdx >= 0) {
+            labelFetcher = mapOrGeoModel;
+        }
+
+        const textEl = new graphic.Text({
+            x: labelXY[0],
+            y: labelXY[1],
+            z2: 10,
+            silent: true
+        });
+        textEl.afterUpdate = labelTextAfterUpdate;
+
+        const labelStateModels = getLabelStatesModels(regionModel);
+        setLabelStyle<typeof query>(
+            textEl,
+            labelStateModels,
+            {
+                labelFetcher: labelFetcher,
+                labelDataIndex: query,
+                defaultText: regionName
+            },
+            { normal: {
+                align: 'center',
+                verticalAlign: 'middle'
+            } }
+        );
+
+        // PENDING: use `setLabelStyle` entirely.
+        el.setTextContent(textEl);
+
+        const textConfig = createTextConfig(labelStateModels.normal, null, false);
+        // Need to apply the `translate`.
+        textConfig.local = true;
+        textConfig.insideFill = textEl.style.fill;
+        el.setTextConfig(textConfig);
+
+        for (let i = 0; i < SPECIAL_STATES.length; i++) {
+            const stateName = SPECIAL_STATES[i];
+            // Hover label only work when `emphasis` state ensured.
+            const state = el.ensureState(stateName);
+
+            const textConfig = state.textConfig = createTextConfig(labelStateModels[stateName], null, true);
+            // Need to apply the `translate`.
+            textConfig.local = true;
+            textConfig.insideFill = textEl.style.fill;
+        }
+
+        (el as ECElement).disableLabelAnimation = true;
+    }
+    else {
+        el.removeTextContent();
+        el.removeTextConfig();
+        (el as ECElement).disableLabelAnimation = null;
+    }
+}
+
+function resetEventTriggerForRegion(
+    viewBuildCtx: ViewBuildContext,
+    eventTrigger: Element,
+    regionName: string,
+    regionModel: RegionModel,
+    mapOrGeoModel: MapOrGeoModel,
+    // Exist only if `viewBuildCtx.data` exists.
+    dataIdx: number
+): void {
+    // setItemGraphicEl, setHoverStyle after all polygons and labels
+    // are added to the rigionGroup
+    if (viewBuildCtx.data) {
+        // FIXME: when series-map use a SVG map, and there are duplicated name specified
+        // on different SVG elements, after `data.setItemGraphicEl(...)`:
+        // (1) all of them will be mounted with `dataIndex`, `seriesIndex`, so that tooltip
+        // can be triggered only mouse hover. That's correct.
+        // (2) only the last element will be kept in `data`, so that if trigger tooltip
+        // by `dispatchAction`, only the last one can be found and triggered. That might be
+        // not correct. We will fix it in future if anyone demanding that.
+        viewBuildCtx.data.setItemGraphicEl(dataIdx, eventTrigger);
+    }
+    // series-map will not trigger "geoselectchange" no matter it is
+    // based on a declared geo component. Becuause series-map will
+    // trigger "selectchange". If it trigger both the two events,
+    // If users call `chart.dispatchAction({type: 'toggleSelect'})`,
+    // it not easy to also fire event "geoselectchanged".
+    else {
+        // Package custom mouse event for geo component
+        getECData(eventTrigger).eventData = {
+            componentType: 'geo',
+            componentIndex: mapOrGeoModel.componentIndex,
+            geoIndex: mapOrGeoModel.componentIndex,
+            name: regionName,
+            region: (regionModel && regionModel.option) || {}
+        };
+    }
+}
+
+function resetTooltipForRegion(
+    viewBuildCtx: ViewBuildContext,
+    el: Element,
+    regionName: string,
+    regionModel: RegionModel,
+    mapOrGeoModel: MapOrGeoModel
+): void {
+    if (!viewBuildCtx.data) {
+        graphic.setTooltipConfig({
+            el: el,
+            componentModel: mapOrGeoModel,
+            itemName: regionName,
+            // @ts-ignore FIXME:TS fix the "compatible with each other"?
+            itemTooltipOption: regionModel.get('tooltip')
+        });
+    }
+}
+
+function resetStateTriggerForRegion(
+    viewBuildCtx: ViewBuildContext,
+    el: Element,
+    regionName: string,
+    regionModel: RegionModel,
+    mapOrGeoModel: MapOrGeoModel
+): InnerFocus {
+    // @ts-ignore FIXME:TS fix the "compatible with each other"?
+    el.highDownSilentOnTouch = !!mapOrGeoModel.get('selectedMode');
+    // @ts-ignore FIXME:TS fix the "compatible with each other"?
+    const emphasisModel = regionModel.getModel('emphasis');
+    const focus = emphasisModel.get('focus');
+    enableHoverEmphasis(
+        el, focus, emphasisModel.get('blurScope')
+    );
+    if (viewBuildCtx.isGeo) {
+        enableComponentHighDownFeatures(el, mapOrGeoModel as GeoModel, regionName);
+    }
+
+    return focus;
 }
 
 export default MapDraw;
diff --git a/src/coord/geo/Geo.ts b/src/coord/geo/Geo.ts
index 430bdf4..a2e96cc 100644
--- a/src/coord/geo/Geo.ts
+++ b/src/coord/geo/Geo.ts
@@ -169,7 +169,7 @@ class Geo extends View {
         return this._nameCoordMap.get(name) || (region && region.getCenter());
     }
 
-    dataToPoint(data: number[], noRoam?: boolean, out?: number[]): number[] {
+    dataToPoint(data: number[] | string, noRoam?: boolean, out?: number[]): number[] {
         if (typeof data === 'string') {
             // Map area name to geoCoord
             data = this.getGeoCoord(data);
diff --git a/src/coord/geo/GeoSVGResource.ts b/src/coord/geo/GeoSVGResource.ts
index 79455c3..93d9c2f 100644
--- a/src/coord/geo/GeoSVGResource.ts
+++ b/src/coord/geo/GeoSVGResource.ts
@@ -17,38 +17,54 @@
 * under the License.
 */
 
-import { parseSVG, makeViewBoxTransform, SVGNodeTagLower } from 'zrender/src/tool/parseSVG';
+import { parseSVG, makeViewBoxTransform, SVGNodeTagLower, SVGParserResultNamedItem } from 'zrender/src/tool/parseSVG';
 import Group from 'zrender/src/graphic/Group';
 import Rect from 'zrender/src/graphic/shape/Rect';
-import {assert, createHashMap, HashMap} from 'zrender/src/core/util';
+import {assert, createHashMap, each, HashMap} from 'zrender/src/core/util';
 import BoundingRect from 'zrender/src/core/BoundingRect';
-import { GeoResource, GeoSVGGraphicRoot, GeoSVGSourceInput, RegionGraphic } from './geoTypes';
+import { GeoResource, GeoSVGGraphicRoot, GeoSVGSourceInput } from './geoTypes';
 import { parseXML } from 'zrender/src/tool/parseXML';
 import { GeoSVGRegion } from './Region';
 import Element from 'zrender/src/Element';
 
-type RegionName = string;
 export interface GeoSVGGraphicRecord {
     root: Group;
     boundingRect: BoundingRect;
-    regionGraphics: RegionGraphic[];
-    // A name may correspond to multiple graphics.
-    regionElementMap: HashMap<Element[], RegionName>;
+    named: SVGParserResultNamedItem[];
 }
 
+/**
+ * "region available" means that: enable users to set attribute `name="xxx"` on those tags
+ * to make it be a region.
+ * 1. region styles and its label styles can be defined in echarts opton:
+ * ```js
+ * geo: {
+ *     regions: [{
+ *         name: 'xxx',
+ *         itemStyle: { ... },
+ *         label: { ... }
+ *     }, {
+ *         ...
+ *     },
+ *     ...]
+ * };
+ * ```
+ * 2. name can be duplicated in different SVG tag. All of the tags with the same name share
+ * a region option. For exampel if there are two <path> representing two lung lobes. They have
+ * no common parents but both of them need to display label "lung" inside.
+ */
 const REGION_AVAILABLE_SVG_TAG_MAP = createHashMap<number, SVGNodeTagLower>([
-    'rect', 'circle', 'line', 'ellipse', 'polygon', 'polyline', 'text', 'tspan', 'path'
+    'rect', 'circle', 'line', 'ellipse', 'polygon', 'polyline', 'path',
+    // <text> <tspan> are also enabled becuase some SVG might paint text itself,
+    // but still need to trigger events or tooltip.
+    'text', 'tspan',
+    // <g> is also enabled because this case: if multiple tags share one name
+    // and need label displayed, every tags will display the name, which is not
+    // expected. So we can put them into a <g name="xxx">. Thereby only one label
+    // displayed and located based on the bounding rect of the <g>.
+    'g'
 ]);
 
-const OPTION_STYLE_ENABLED_TAG_MAP = createHashMap<number, SVGNodeTagLower>([
-    'rect', 'circle', 'line', 'ellipse', 'polygon', 'polyline', 'path'
-]);
-
-const LABEL_HOST_MAP = createHashMap<number, SVGNodeTagLower>([
-    'rect', 'circle', 'line', 'ellipse', 'polygon', 'polyline', 'path'
-]);
-
-
 export class GeoSVGResource implements GeoResource {
 
     readonly type = 'geoSVG';
@@ -57,9 +73,9 @@ export class GeoSVGResource implements GeoResource {
 
     private _firstGraphic: GeoSVGGraphicRecord;
     private _boundingRect: BoundingRect;
-    private _regions: GeoSVGRegion[] = [];
+    private _regions: GeoSVGRegion[];
     // Key: region.name
-    private _regionsMap: HashMap<GeoSVGRegion> = createHashMap<GeoSVGRegion>();
+    private _regionsMap: HashMap<GeoSVGRegion>;
 
     // All used graphics. key: hostKey, value: root
     private _usedGraphicMap: HashMap<GeoSVGGraphicRecord> = createHashMap();
@@ -98,21 +114,14 @@ export class GeoSVGResource implements GeoResource {
 
             this._boundingRect = this._firstGraphic.boundingRect.clone();
 
-            const regionGraphics = firstGraphic.regionGraphics;
             // PENDING: `nameMap` will not be supported until some real requirement come.
             // if (nameMap) {
-            //     regionGraphics = applyNameMap(regionGraphics, nameMap);
+            //     named = applyNameMap(named, nameMap);
             // }
 
-            // Create resions only for the first graphic.
-            for (let i = 0; i < regionGraphics.length; i++) {
-                const regionGraphic = regionGraphics[i];
-                const region = new GeoSVGRegion(regionGraphic.name, regionGraphic.el);
-                // PENDING: if `nameMap` supported, this region can not be mounted on
-                // `this`, but can only be created each time `load()` called.
-                this._regions.push(region);
-                this._regionsMap.set(regionGraphic.name, region);
-            }
+            const { regions, regionsMap } = createRegions(firstGraphic.named);
+            this._regions = regions;
+            this._regionsMap = regionsMap;
         }
 
         return {
@@ -148,10 +157,10 @@ export class GeoSVGResource implements GeoResource {
 
         // PENDING: `nameMap` will not be supported until some real requirement come.
         // `nameMap` can only be obtained from echarts option.
-        // The original `regionGraphics` must not be modified.
+        // The original `named` must not be modified.
         // if (nameMap) {
         //     svgGraphic = extend({}, svgGraphic);
-        //     svgGraphic.regionGraphics = applyNameMap(svgGraphic.regionGraphics, nameMap);
+        //     svgGraphic.named = applyNameMap(svgGraphic.named, nameMap);
         // }
 
         return svgGraphic;
@@ -227,59 +236,75 @@ function buildGraphic(
 
     (root as GeoSVGGraphicRoot).isGeoSVGGraphicRoot = true;
 
-    const regionGraphics = [] as GeoSVGGraphicRecord['regionGraphics'];
-    const regionElementMap = createHashMap<Element[], RegionName>();
-    const named = result.named;
-    for (let i = 0; i < named.length; i++) {
-        const namedItem = named[i];
-        const svgNodeTagLower = namedItem.svgNodeTagLower;
-
-        if (REGION_AVAILABLE_SVG_TAG_MAP.get(svgNodeTagLower) != null) {
-            const optionStyleEnabled = OPTION_STYLE_ENABLED_TAG_MAP.get(svgNodeTagLower);
-            const el = namedItem.el;
-            const name = namedItem.name;
-
-            regionGraphics.push({
-                name: name,
-                el: el,
-                optionStyleEnabled: optionStyleEnabled != null,
-                stateTrigger: optionStyleEnabled != null ? el : null,
-                // text/tspan/image do not suport style but support event.
-                eventTrigger: el,
-                useLabel: LABEL_HOST_MAP.get(svgNodeTagLower) != null
-            });
-
-            const els = regionElementMap.get(name) || regionElementMap.set(name, []);
-            els.push(el);
-
-            // Only named element has silent: false, other elements should
-            // act as background and has no user interaction.
-            el.silent = false;
-            // text|tspan will be converted to group.
-            if (el.isGroup) {
-                el.traverse(child => {
-                    child.silent = false;
-                });
-            }
+    const named = [] as GeoSVGGraphicRecord['named'];
+
+    each(result.named, namedItem => {
+        if (REGION_AVAILABLE_SVG_TAG_MAP.get(namedItem.svgNodeTagLower) != null) {
+            named.push(namedItem);
+            setSilent(namedItem.el);
         }
+    });
+
+    return { root, boundingRect, named };
+}
+
+function setSilent(el: Element): void {
+    // Only named element has silent: false, other elements should
+    // act as background and has no user interaction.
+    el.silent = false;
+    // text|tspan will be converted to group.
+    if (el.isGroup) {
+        el.traverse(child => {
+            child.silent = false;
+        });
     }
+}
+
+function createRegions(
+    named: SVGParserResultNamedItem[]
+): {
+    regions: GeoSVGRegion[];
+    regionsMap: HashMap<GeoSVGRegion>;
+} {
+
+    const regions: GeoSVGRegion[] = [];
+    const regionsMap = createHashMap<GeoSVGRegion>();
+
+    // Create resions only for the first graphic.
+    each(named, namedItem => {
+        // Region has feature to calculate center for tooltip or other features.
+        // If there is a <g name="xxx">, the center should be the center of the
+        // bounding rect of the g.
+        if (namedItem.namedFrom != null) {
+            return;
+        }
 
-    return { root, boundingRect, regionGraphics, regionElementMap: regionElementMap };
+        const region = new GeoSVGRegion(namedItem.name, namedItem.el);
+        // PENDING: if `nameMap` supported, this region can not be mounted on
+        // `this`, but can only be created each time `load()` called.
+        regions.push(region);
+        // PENDING: if multiple tag named with the same name, only one will be
+        // found by `_regionsMap`. `_regionsMap` is used to find a coordinate
+        // by name. We use `region.getCenter()` as the coordinate.
+        regionsMap.set(namedItem.name, region);
+    });
+
+    return { regions, regionsMap };
 }
 
 
 // PENDING: `nameMap` will not be supported until some real requirement come.
 // /**
 //  * Use the alias in geoNameMap.
-//  * The input `regionGraphics` must not be modified.
+//  * The input `named` must not be modified.
 //  */
 // function applyNameMap(
-//     regionGraphics: GeoSVGGraphicRecord['regionGraphics'],
+//     named: GeoSVGGraphicRecord['named'],
 //     nameMap: NameMap
-// ): GeoSVGGraphicRecord['regionGraphics'] {
-//     const result = [] as GeoSVGGraphicRecord['regionGraphics'];
-//     for (let i = 0; i < regionGraphics.length; i++) {
-//         let regionGraphic = regionGraphics[i];
+// ): GeoSVGGraphicRecord['named'] {
+//     const result = [] as GeoSVGGraphicRecord['named'];
+//     for (let i = 0; i < named.length; i++) {
+//         let regionGraphic = named[i];
 //         const name = regionGraphic.name;
 //         if (nameMap && nameMap.hasOwnProperty(name)) {
 //             regionGraphic = extend({}, regionGraphic);
diff --git a/src/coord/geo/geoTypes.ts b/src/coord/geo/geoTypes.ts
index 8674218..e98de49 100644
--- a/src/coord/geo/geoTypes.ts
+++ b/src/coord/geo/geoTypes.ts
@@ -140,21 +140,3 @@ export interface GeoResource {
 export interface GeoSVGGraphicRoot extends Group {
     isGeoSVGGraphicRoot: boolean;
 }
-
-export type RegionGraphic = {
-    // Region name. Can not be null/undefined.
-    name: string;
-    // Main el.
-    el: Element;
-    // If it specified, use it to trigger state
-    // style change (emphasis/select/blur)
-    // Can be null/undefined.
-    stateTrigger: Element;
-    // If it specified, use it to trigger event to users
-    // Can be null/undefined.
-    eventTrigger: Element;
-    // Whether to set label on `el.textContent`.
-    useLabel: boolean;
-    // Whether to be enabled to set style via in echarts option.
-    optionStyleEnabled: boolean;
-};
diff --git a/src/core/echarts.ts b/src/core/echarts.ts
index 2f652a3..10fc41b 100644
--- a/src/core/echarts.ts
+++ b/src/core/echarts.ts
@@ -36,8 +36,6 @@ import ChartView, {ChartViewConstructor} from '../view/Chart';
 import * as graphic from '../util/graphic';
 import {getECData} from '../util/innerStore';
 import {
-    enterEmphasisWhenMouseOver,
-    leaveEmphasisWhenMouseOut,
     isHighDownDispatcher,
     HOVER_STATE_EMPHASIS,
     HOVER_STATE_BLUR,
@@ -1484,24 +1482,28 @@ class ECharts extends Eventful<ECEventDefinition> {
             ecModel && ecModel.eachComponent(condition, function (model) {
                 if (!excludeSeriesIdMap || excludeSeriesIdMap.get(model.id) == null) {
                     if (isHighDownPayload(payload)) {
-                        if (payload.type === HIGHLIGHT_ACTION_TYPE) {
-                            if (model instanceof SeriesModel) {
-                                !payload.notBlur && blurSeriesFromHighlightPayload(model, payload, ecIns._api);
+                        if (model instanceof SeriesModel) {
+                            if (payload.type === HIGHLIGHT_ACTION_TYPE && !payload.notBlur) {
+                                blurSeriesFromHighlightPayload(model, payload, ecIns._api);
+                            }
+                        }
+                        else {
+                            const { focusSelf, dispatchers } = findComponentHighDownDispatchers(
+                                model.mainType, model.componentIndex, payload.name, ecIns._api
+                            );
+                            if (payload.type === HIGHLIGHT_ACTION_TYPE && focusSelf && !payload.notBlur) {
+                                blurComponent(model.mainType, model.componentIndex, ecIns._api);
                             }
-                            else {
-                                const { focusSelf, dispatchers } = findComponentHighDownDispatchers(
-                                    model.mainType, model.componentIndex, payload.name, ecIns._api
-                                );
-                                if (focusSelf && !payload.notBlur) {
-                                    blurComponent(model.mainType, model.componentIndex, ecIns._api);
-                                }
-                                // PENDING:
-                                // Whether to put this "enter emphasis" code in `ComponentView`,
-                                // which will be the same as `ChartView` but might be not necessary
-                                // and will be far from this logic.
-                                if (dispatchers) {
-                                    each(dispatchers, dispatcher => enterEmphasis(dispatcher));
-                                }
+                            // PENDING:
+                            // Whether to put this "enter emphasis" code in `ComponentView`,
+                            // which will be the same as `ChartView` but might be not necessary
+                            // and will be far from this logic.
+                            if (dispatchers) {
+                                each(dispatchers, dispatcher => {
+                                    payload.type === HIGHLIGHT_ACTION_TYPE
+                                        ? enterEmphasis(dispatcher)
+                                        : leaveEmphasis(dispatcher);
+                                });
                             }
                         }
                     }
diff --git a/test/geo-svg-demo.html b/test/geo-svg-demo.html
index ed5afe8..85ad1b0 100644
--- a/test/geo-svg-demo.html
+++ b/test/geo-svg-demo.html
@@ -38,8 +38,8 @@ under the License.
 
 
         <div id="main_geo_svg_organ"></div>
-        <!-- <div id="main_geo_svg_organ1"></div> -->
-
+        <div id="main_geo_svg_regions"></div>
+        <div id="main_geo_svg_line_path"></div>
 
 
 
@@ -99,6 +99,8 @@ under the License.
                     tooltip: {
                     },
                     geo: {
+                        left: 10,
+                        right: '50%',
                         map: 'seatmap',
                         roam: true,
                         selectedMode: 'multiple',
@@ -107,18 +109,16 @@ under the License.
                         emphasis: {
                             focus: 'self',
                             itemStyle: {
-                                // color: null
+                                color: null
                             },
                             label: {
-                                show: false,
+                                position: 'bottom',
+                                distance: 20,
                                 textBorderColor: '#fff',
                                 textBorderWidth: 2
                             }
                         },
                         blur: {
-                            // itemStyle: {
-                            //     opacity: 0.3
-                            // }
                         },
                         select: {
                             itemStyle: {
@@ -130,7 +130,25 @@ under the License.
                                 textBorderWidth: 2
                             }
                         }
-                    }
+                    },
+                    grid: {
+                        left: '60%'
+                    },
+                    xAxis: {
+                        splitLine: {
+                            show: false
+                        }
+                    },
+                    yAxis: {
+                        data: ['heart', 'large-intestine', 'small-intestine', 'spleen', 'kidney', 'lung', 'liver']
+                    },
+                    series: [{
+                        type: 'bar',
+                        emphasis: {
+                            focus: 'self'
+                        },
+                        data: [121, 321, 141, 52, 198, 289, 139]
+                    }]
                 };
 
                 var chart = testHelper.create(echarts, 'main_geo_svg_organ', {
@@ -149,9 +167,34 @@ under the License.
                 listenAndPrintEvent(chart);
 
                 if (chart) {
-                    chart.on('highlight', function () {
-                        console.log('agsd');
+                    chart.on('mouseover', { seriesIndex: 0 }, function (event) {
+                        chart.dispatchAction({
+                            type: 'highlight',
+                            geoIndex: 0,
+                            name: event.name
+                        });
+                    });
+                    chart.on('mouseout', { seriesIndex: 0 }, function (event) {
+                        chart.dispatchAction({
+                            type: 'downplay',
+                            geoIndex: 0,
+                            name: event.name
+                        });
                     });
+                    // chart.on('mouseover', { geoIndex: 0 }, function (event) {
+                    //     chart.dispatchAction({
+                    //         type: 'highlight',
+                    //         seriesIndex: 0,
+                    //         name: event.name
+                    //     });
+                    // });
+                    // chart.on('mouseout', { geoIndex: 0 }, function (event) {
+                    //     chart.dispatchAction({
+                    //         type: 'downplay',
+                    //         seriesIndex: 0,
+                    //         name: event.name
+                    //     });
+                    // });
                 }
 
             });
@@ -162,36 +205,54 @@ under the License.
 
 
 
-<!--
+
 
 
         <script>
         require(['echarts'/*, 'map/js/china' */], function (echarts) {
             var option;
+
             $.ajax({
-                url: '../../vis-data/map/svg/organ/Veins_Medical_Diagram_clip_art.svg',
+                url: '../../vis-data/map/svg/geo-topo/Sicily_prehellenic_topographic_map.svg',
                 dataType: 'text'
             }).done(function (svg) {
 
-                echarts.registerMap('seatmap', {
+                echarts.registerMap('sicily', {
                     svg: svg
                 });
 
                 option = {
                     tooltip: {
                     },
-                    series: {
-                        type: 'map',
-                        map: 'seatmap',
+                    geo: [{
+                        map: 'sicily',
                         roam: true,
                         selectedMode: 'multiple',
-                        // height: 100,
-                        // zoom: 1.5
+                        itemStyle: {
+                            color: null
+                        },
+                        tooltip: {
+                            show: true,
+                            confine: true,
+                            formatter: function (params) {
+                                return [
+                                    'This is the introduction:',
+                                    'xxxxxxxxxxxxxxxxxxxxx',
+                                    'xxxxxxxxxxxxxxxxxxxxx',
+                                    'xxxxxxxxxxxxxxxxxxxxx',
+                                    'xxxxxxxxxxxxxxxxxxxxx',
+                                    'xxxxxxxxxxxxxxxxxxxxx',
+                                    'xxxxxxxxxxxxxxxxxxxxx',
+                                    'xxxxxxxxxxxxxxxxxxxxx',
+                                    'xxxxxxxxxxxxxxxxxxxxx',
+                                    'xxxxxxxxxxxxxxxxxxxxx',
+                                    'xxxxxxxxxxxxxxxxxxxxx'
+                                ].join('<br>');
+                            }
+                        },
                         emphasis: {
-                            focus: 'self',
                             label: {
-                                textBorderColor: '#fff',
-                                textBorderWidth: 2
+                                show: false
                             }
                         },
                         select: {
@@ -199,39 +260,202 @@ under the License.
                                 color: '#b50205'
                             },
                             label: {
-                                show: false,
-                                textBorderColor: '#fff',
-                                textBorderWidth: 2
+                                show: false
                             }
-                        }
-                    }
+                        },
+                        regions: [{
+                            name: 'Sikeloi',
+                            tooltip: {
+                                formatter: 'Sikeloi',
+                                textStyle: { color: '#555' },
+                                backgroundColor: '#ccc'
+                            }
+                        }, {
+                            name: 'Sikanoi',
+                            tooltip: {
+                                formatter: 'Sikanoi',
+                                textStyle: { color: '#555' },
+                                backgroundColor: '#ccc'
+                            }
+                        }, {
+                            name: 'Elymoi',
+                            tooltip: {
+                                formatter: 'Elymoi',
+                                textStyle: { color: '#555' },
+                                backgroundColor: '#ccc'
+                            }
+                        }],
+                        z: 0
+                    }]
                 };
 
-                var chart = testHelper.create(echarts, 'main_geo_svg_organ1', {
+                var chart = testHelper.create(echarts, 'main_geo_svg_regions', {
                     title: [
-                        'pure geo component with svg resource',
-                        'click seat: check **allSelected** correct.'
+                        'symbol and label use the same name in SVG.',
+                        'Hover each symbol and text, tooltip should be displayed.',
+                        'Hover the three area, tooltip should be displayed.',
+                        'Click, check **selected**.'
                     ],
                     option: option,
                     info: {},
                     infoKey: 'event',
                     height: 500
-                    // buttons: [{text: 'btn-txt', onclick: function () {}}],
-                    // recordCanvas: true,
                 });
 
                 listenAndPrintEvent(chart);
 
-                if (chart) {
-                    chart.on('highlight', function () {
-                        console.log('agsd');
-                    });
-                }
+            });
+
+        });
+        </script>
+
+
+
+
+
+
+
+
+
+
+        <script>
+        require(['echarts'/*, 'map/js/china' */], function (echarts) {
+            var option;
+
+            $.ajax({
+                url: '../../vis-data/map/svg/geo-topo/Map_of_Iceland.svg',
+                dataType: 'text'
+            }).done(function (svg) {
+
+                echarts.registerMap('Map_of_Iceland', {
+                    svg: svg
+                });
+
+                option = {
+                    tooltip: {
+                        alwaysShowContent: true,
+                        enterable: true,
+                        extraCssText: 'user-select: text'
+                    },
+                    geo: [{
+                        map: 'Map_of_Iceland',
+                        roam: true,
+                        selectedMode: 'single',
+                        tooltip: {
+                            show: true,
+                            // confine: true
+                        },
+                        label: {
+                            fontSize: 20,
+                            textBorderColor: '#fff',
+                            textBorderWidth: 2
+                        },
+                        emphasis: {
+                            itemStyle: {
+                                color: null,
+                                borderColor: '#b50805',
+                                borderWidth: 5
+                            }
+                        },
+                        select: {
+                            itemStyle: {
+                                color: null,
+                                borderColor: '#b50205',
+                                borderWidth: 5
+                            }
+                        },
+                        regions: [{
+                            name: 'trip1',
+                            label: {
+                                formatter: 'Western Trip'
+                            },
+                            tooltip: {
+                                position: 'right',
+                                formatter: [
+                                    'Western Trip:',
+                                    'xxxxxxxxxxxxxxxxxxxxxxxxxxx',
+                                    'xxxxxxxxxxxxxxxxxxxxxxxxxxx',
+                                    'xxxxxxxxxxxxxxxxxxxxxxxxxxx',
+                                    'xxxxxxxxxxxxxxxxxxxxxxxxxxx',
+                                    'xxxxxxxxxxxxxxxxxxxxxxxxxxx'
+                                ].join('<br>')
+                            }
+                        }, {
+                            name: 'trip2',
+                            label: {
+                                formatter: 'Eastern Trip'
+                            },
+                            tooltip: {
+                                position: 'left',
+                                formatter: [
+                                    'Western Trip:',
+                                    'xxxxxxxxxxxx',
+                                    'xxxxxxxxxxxx',
+                                    'xxxxxxxxxxxx',
+                                    'xxxxxxxxxxxx',
+                                    'xxxxxxxxxxxx',
+                                    'xxxxxxxxxxxx',
+                                    'xxxxxxxxxxxx',
+                                    'xxxxxxxxxxxx',
+                                    'xxxxxxxxxxxx',
+                                    'xxxxxxxxxxxx',
+                                    'xxxxxxxxxxxx'
+                                ].join('<br>')
+                            }
+                        }]
+                    }]
+                };
+
+                var chart = testHelper.create(echarts, 'main_geo_svg_line_path', {
+                    title: [
+                        'Select some route in SVG via API.',
+                        'Some route should be highlighted (check selectedMode **single**).',
+                        'label should be displayed.'
+                    ],
+                    option: option,
+                    info: {},
+                    infoKey: 'event',
+                    height: 500,
+                    buttons: [{
+                        text: 'highlight trip1',
+                        onclick: function () {
+                            chart.dispatchAction({
+                                type: 'geoSelect',
+                                geoIndex: 0,
+                                name: 'trip1'
+                            });
+                            chart.dispatchAction({
+                                type: 'showTip',
+                                geoIndex: 0,
+                                name: 'trip1'
+                            });
+                        }
+                    }, {
+                        text: 'highlight trip2',
+                        onclick: function () {
+                            chart.dispatchAction({
+                                type: 'geoSelect',
+                                geoIndex: 0,
+                                name: 'trip2'
+                            });
+                            chart.dispatchAction({
+                                type: 'showTip',
+                                geoIndex: 0,
+                                name: 'trip2'
+                            });
+                        }
+                    }]
+                });
+
+                listenAndPrintEvent(chart);
 
             });
 
         });
-        </script> -->
+        </script>
+
+
+
 
 
 
diff --git a/test/geo-svg.html b/test/geo-svg.html
index 2d52f33..45ec6e4 100644
--- a/test/geo-svg.html
+++ b/test/geo-svg.html
@@ -39,10 +39,10 @@ under the License.
 
         <div id="main_simple_geo_json"></div>
         <div id="main_simple_geo_svg"></div>
+        <div id="main_geo_svg_emphasis_select"></div>
         <div id="main_pure_geo_svg"></div>
         <div id="main_pure_map_svg"></div>
         <div id="main_map_geo_svg"></div>
-        <div id="main_geo_svg_regions"></div>
 
 
 
@@ -135,7 +135,7 @@ under the License.
                 option: option,
                 info: {},
                 infoKey: 'event',
-                height: 300
+                height: 200
             });
 
             listenAndPrintEvent(chart);
@@ -187,7 +187,7 @@ under the License.
                 option: option,
                 info: {},
                 infoKey: 'event',
-                height: 300
+                height: 200
             });
 
             listenAndPrintEvent(chart);
@@ -199,6 +199,129 @@ under the License.
 
 
 
+        <script>
+        require(['echarts'/*, 'map/js/china' */], function (echarts) {
+            const svg = [
+                '<?xml version="1.0" encoding="utf-8"?>',
+                '<svg xmlns="http://www.w3.org/2000/svg" xmlns:ooo="http://xml.openoffice.org/svg/export" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.2" fill-rule="evenodd" xml:space="preserve">',
+                '<path name="a" d="M 0,0 L 0,100 100,100 100,0 Z" fill="rgb(16,193,138)" stroke="green" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>',
+                '<path name="a" d="M 110,0 L 110,100 210,100 210,0 Z" fill="rgb(16,193,138)" stroke="green" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>',
+                '<g name="b">',
+                    '<path d="M 0,110 L 0,210 100,110 Z" fill="rgb(16,193,138)" stroke="green" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>',
+                    '<path d="M 110,110 L 110,210 210,110 Z" fill="rgb(16,193,138)" stroke="green" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>',
+                '</g>',
+                '<radialGradient id="XMLID_1_" cx="0" cy="220" r="59.4363" gradientUnits="userSpaceOnUse">',
+                    '<stop  offset="0" style="stop-color:#E6E6E6"/>',
+                    '<stop  offset="1" style="stop-color:#4D4D4D"/>',
+                '</radialGradient>',
+                '<path name="c" d="M 0,220 L 0,320 100,220 Z" fill="url(#XMLID_1_)" stroke="green" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>',
+                '<radialGradient id="XMLID_2_" cx="110" cy="220" r="59.4363" gradientUnits="userSpaceOnUse">',
+                    '<stop  offset="0" style="stop-color:#E6E6E6"/>',
+                    '<stop  offset="1" style="stop-color:#4D4D4D"/>',
+                '</radialGradient>',
+                '<path name="c" d="M 110,220 L 110,320 210,220 Z" fill="url(#XMLID_2_)" stroke="green" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>',
+                '</svg>'
+            ].join('')
+
+            echarts.registerMap('testGeoSVG_select_hover', { svg: svg });
+
+            option = {
+                // tooltip: {
+                // },
+                geo: {
+                    map: 'testGeoSVG_select_hover',
+                    roam: true,
+                    selectedMode: 'single',
+                    // height: '100%',
+                    // center
+                    // layoutCenter: ['30%', 40],
+                    // layoutSize: 40,
+                    // boundingCoords
+                    zoom: 1,
+                    aspectScale: 1,
+                    label: {
+                        show: true
+                    },
+                    emphasis: {
+                        label: {
+                            show: true,
+                            fontSize: 100
+                        }
+                    },
+                    select: {
+                        itemStyle: {
+                            color: 'red'
+                        }
+                    }
+                }
+            };
+
+            var chart = testHelper.create(echarts, 'main_geo_svg_emphasis_select', {
+                title: [
+                    'click buttons',
+                    'hover check'
+                ],
+                option: option,
+                info: {},
+                infoKey: 'event',
+                height: 200,
+                button: [{
+                    text: 'highlight a',
+                    onclick: function () {
+                        chart.dispatchAction({ type: 'highlight', geoIndex: 0, name: 'a' });
+                    }
+                }, {
+                    text: 'downplay a',
+                    onclick: function () {
+                        chart.dispatchAction({ type: 'downplay', geoIndex: 0, name: 'a' });
+                    }
+                }, {
+                    text: 'select a',
+                    onclick: function () {
+                        chart.dispatchAction({ type: 'geoSelect', geoIndex: 0, name: 'a' });
+                    }
+                }, {
+                    text: 'unselect a',
+                    onclick: function () {
+                        chart.dispatchAction({ type: 'geoUnSelect', geoIndex: 0, name: 'a' });
+                    }
+                }, {
+                    text: 'highlight b',
+                    onclick: function () {
+                        chart.dispatchAction({ type: 'highlight', geoIndex: 0, name: 'b' });
+                    }
+                }, {
+                    text: 'downplay b',
+                    onclick: function () {
+                        chart.dispatchAction({ type: 'downplay', geoIndex: 0, name: 'b' });
+                    }
+                }, {
+                    text: 'select b',
+                    onclick: function () {
+                        chart.dispatchAction({ type: 'geoSelect', geoIndex: 0, name: 'b' });
+                    }
+                }, {
+                    text: 'unselect b',
+                    onclick: function () {
+                        chart.dispatchAction({ type: 'geoUnSelect', geoIndex: 0, name: 'b' });
+                    }
+                }]
+            });
+
+            listenAndPrintEvent(chart);
+
+            if (chart) {
+                chart.on('highlight', function () {
+                });
+            }
+
+        });
+        </script>
+
+
+
+
+
 
 
         <script>
@@ -220,6 +343,9 @@ under the License.
                         map: 'seatmap',
                         roam: true,
                         selectedMode: 'multiple',
+                        tooltip: {
+                            show: true
+                        },
                         // height: 100,
                         // zoom: 1.5
                         emphasis: {
@@ -251,7 +377,8 @@ under the License.
                 var chart = testHelper.create(echarts, 'main_pure_geo_svg', {
                     title: [
                         'pure geo component with svg resource',
-                        'click seat: check **allSelected** correct.'
+                        'click seat: check **allSelected** correct.',
+                        'hover seat: check **tooltip** and **label** correct.'
                     ],
                     option: option,
                     info: {},
@@ -359,6 +486,9 @@ under the License.
                         selectedMode: 'multiple',
                         // height: 100,
                         // zoom: 1.5
+                        tooltip: {
+                            show: true
+                        },
                         emphasis: {
                             label: {
                                 textBorderColor: '#fff',
@@ -407,118 +537,7 @@ under the License.
 
 
 
-        <script>
-        require(['echarts'/*, 'map/js/china' */], function (echarts) {
-            var option;
 
-            $.ajax({
-                url: '../../vis-data/map/svg/geo-topo/Sicily_prehellenic_topographic_map.svg',
-                dataType: 'text'
-            }).done(function (svg) {
-
-                echarts.registerMap('sicily', {
-                    svg: svg
-                });
-
-                option = {
-                    tooltip: {
-                    },
-                    geo: [{
-                        map: 'sicily',
-                        roam: true,
-                        selectedMode: 'multiple',
-                        itemStyle: {
-                            color: null
-                        },
-                        tooltip: {
-                            show: true,
-                            confine: true,
-                            formatter: function (params) {
-                                return [
-                                    'This is the introduction:',
-                                    'xxxxxxxxxxxxxxxxxxxxx',
-                                    'xxxxxxxxxxxxxxxxxxxxx',
-                                    'xxxxxxxxxxxxxxxxxxxxx',
-                                    'xxxxxxxxxxxxxxxxxxxxx',
-                                    'xxxxxxxxxxxxxxxxxxxxx',
-                                    'xxxxxxxxxxxxxxxxxxxxx',
-                                    'xxxxxxxxxxxxxxxxxxxxx',
-                                    'xxxxxxxxxxxxxxxxxxxxx',
-                                    'xxxxxxxxxxxxxxxxxxxxx',
-                                    'xxxxxxxxxxxxxxxxxxxxx'
-                                ].join('<br>');
-                            }
-                        },
-                        emphasis: {
-                            label: {
-                                show: false
-                            }
-                        },
-                        select: {
-                            itemStyle: {
-                                color: '#b50205'
-                            },
-                            label: {
-                                show: false
-                            }
-                        },
-                        regions: [{
-                            name: 'Sikeloi',
-                            tooltip: {
-                                formatter: 'Sikeloi',
-                                textStyle: { color: '#555' },
-                                backgroundColor: '#ccc'
-                            }
-                        }, {
-                            name: 'Sikanoi',
-                            tooltip: {
-                                formatter: 'Sikanoi',
-                                textStyle: { color: '#555' },
-                                backgroundColor: '#ccc'
-                            }
-                        }, {
-                            name: 'Elymoi',
-                            tooltip: {
-                                formatter: 'Elymoi',
-                                textStyle: { color: '#555' },
-                                backgroundColor: '#ccc'
-                            }
-                        }],
-                        z: 0
-                    }],
-                    // series: {
-                    //     type: 'map',
-                    //     selectedMode: 'multiple',
-                    //     coordinateSystem: 'geo',
-                    //     geoIndex: 0
-                    // }
-                };
-
-                var chart = testHelper.create(echarts, 'main_geo_svg_regions', {
-                    title: [
-                        'symbol and label use the same name in SVG.',
-                        'Hover each symbol and text, tooltip should be displayed.',
-                        'Hover the three area, tooltip should be displayed.',
-                        'Click, check **selected**.'
-                    ],
-                    option: option,
-                    info: {},
-                    infoKey: 'event',
-                    height: 500
-                });
-
-                listenAndPrintEvent(chart);
-
-                if (chart) {
-                    chart.on('georoam', function (params) {
-                        // console.log(params);
-                    });
-                }
-
-            });
-
-        });
-        </script>
 
 
     </body>

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