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:47 UTC

[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 [...]

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