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/09 17:58:24 UTC

[echarts] 06/06: feature: [geo] (1) "geoselectchanged" event: add param: allSelected: { geoIndex: number, name: string[] }[] (2) geoSVG resource support: + label + emphasis/select/blur state + "geoselectchanged" event (on geo component) + "selectchanged" event (on map series) (3) some refactor to make those happen: + The original `Region` is migrated to `GeoJSONRegion`. And make `Region` as the base class of `GeoJSONRegion` and `GeoSVGRegion`. + Modify the constructor of `Geo`.

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 30c861cc88d0b9831256d44f4a0404c05e825710
Author: 100pah <su...@gmail.com>
AuthorDate: Wed Mar 10 01:57:25 2021 +0800

    feature: [geo]
    (1) "geoselectchanged" event: add param: allSelected: { geoIndex: number, name: string[] }[]
    (2) geoSVG resource support:
    + label
    + emphasis/select/blur state
    + "geoselectchanged" event (on geo component)
    + "selectchanged" event (on map series)
    (3) some refactor to make those happen:
    + The original `Region` is migrated to `GeoJSONRegion`. And make `Region` as the base class of `GeoJSONRegion` and `GeoSVGRegion`.
    + Modify the constructor of `Geo`.
---
 src/chart/map/MapSeries.ts        |   7 ++-
 src/chart/map/mapSymbolLayout.ts  |   4 +-
 src/component/geo/install.ts      |  16 ++++-
 src/component/helper/MapDraw.ts   |  32 ++++++----
 src/coord/geo/Geo.ts              |  55 +++++++++++-----
 src/coord/geo/GeoJSONResource.ts  |  16 ++---
 src/coord/geo/GeoModel.ts         |   4 +-
 src/coord/geo/GeoSVGResource.ts   |  96 +++++++++++++++++++---------
 src/coord/geo/Region.ts           | 112 +++++++++++++++++++++++++++++----
 src/coord/geo/fix/diaoyuIsland.ts |   6 +-
 src/coord/geo/fix/geoCoord.ts     |  16 ++---
 src/coord/geo/fix/nanhai.ts       |   6 +-
 src/coord/geo/fix/textCoord.ts    |  10 +--
 src/coord/geo/geoCreator.ts       |  27 ++++----
 src/coord/geo/geoSourceManager.ts |   2 +-
 src/coord/geo/geoTypes.ts         |  19 +++---
 src/coord/geo/parseGeoJson.ts     |   8 +--
 test/geo-svg.html                 | 128 +++++++++++++++++++++++++++++++++-----
 18 files changed, 417 insertions(+), 147 deletions(-)

diff --git a/src/chart/map/MapSeries.ts b/src/chart/map/MapSeries.ts
index 2f0f3d8..1532dca 100644
--- a/src/chart/map/MapSeries.ts
+++ b/src/chart/map/MapSeries.ts
@@ -212,7 +212,7 @@ class MapSeries extends SeriesModel<MapSeriesOption> {
             const geo = this.coordinateSystem;
             const region = geo.getRegion(name);
 
-            return region && geo.dataToPoint(region.center);
+            return region && geo.dataToPoint(region.getCenter());
         }
     };
 
@@ -251,7 +251,10 @@ class MapSeries extends SeriesModel<MapSeriesOption> {
 
         // Aspect is width / height. Inited to be geoJson bbox aspect
         // This parameter is used for scale this aspect
-        aspectScale: 0.75,
+        // Default value:
+        // for geoSVG source: 1,
+        // for geoJSON source: 0.75.
+        aspectScale: null,
 
         ///// Layout with center and size
         // If you wan't to put map in a fixed size box with right aspect ratio
diff --git a/src/chart/map/mapSymbolLayout.ts b/src/chart/map/mapSymbolLayout.ts
index 65e8d31..498788f 100644
--- a/src/chart/map/mapSymbolLayout.ts
+++ b/src/chart/map/mapSymbolLayout.ts
@@ -22,6 +22,7 @@ import * as zrUtil from 'zrender/src/core/util';
 import GlobalModel from '../../model/Global';
 import MapSeries from './MapSeries';
 import { Dictionary } from '../../util/types';
+import { GeoJSONRegion } from '../../coord/geo/Region';
 
 export default function mapSymbolLayout(ecModel: GlobalModel) {
 
@@ -38,6 +39,7 @@ export default function mapSymbolLayout(ecModel: GlobalModel) {
         zrUtil.each(mapSeries.seriesGroup, function (subMapSeries) {
             const geo = subMapSeries.coordinateSystem;
             const data = subMapSeries.originalData;
+
             if (subMapSeries.get('showLegendSymbol') && ecModel.getComponent('legend')) {
                 data.each(data.mapDimension('value'), function (value, idx) {
                     const name = data.getName(idx);
@@ -52,7 +54,7 @@ export default function mapSymbolLayout(ecModel: GlobalModel) {
 
                     const offset = mapSymbolOffsets[name] || 0;
 
-                    const point = geo.dataToPoint(region.center);
+                    const point = geo.dataToPoint(region.getCenter());
 
                     mapSymbolOffsets[name] = offset + 1;
 
diff --git a/src/component/geo/install.ts b/src/component/geo/install.ts
index cb0de29..0db971a 100644
--- a/src/component/geo/install.ts
+++ b/src/component/geo/install.ts
@@ -42,20 +42,34 @@ export function install(registers: EChartsExtensionInstallRegisters) {
         actionInfo.update = 'geo:updateSelectStatus';
         registers.registerAction(actionInfo, function (payload, ecModel) {
             const selected = {} as {[regionName: string]: boolean};
+            const allSelected = [] as ({ name: string[], geoIndex: number })[];
 
             ecModel.eachComponent(
                 { mainType: 'geo', query: payload},
                 function (geoModel: GeoModel) {
                     geoModel[method](payload.name);
                     const geo = geoModel.coordinateSystem;
+
                     each(geo.regions, function (region) {
                         selected[region.name] = geoModel.isSelected(region.name) || false;
                     });
+
+                    // Notice: there might be duplicated name in different regions.
+                    const names = [] as string[];
+                    each(selected, function (v, name) {
+                        selected[name] && names.push(name);
+                    });
+                    allSelected.push({
+                        geoIndex: geoModel.componentIndex,
+                        // Use singular, the same naming convention as the event `selectchanged`.
+                        name: names
+                    });
                 }
             );
 
             return {
                 selected: selected,
+                allSelected: allSelected,
                 name: payload.name
             };
         });
@@ -118,5 +132,5 @@ export function install(registers: EChartsExtensionInstallRegisters) {
                 }
             }
         );
-    })
+    });
 }
\ No newline at end of file
diff --git a/src/component/helper/MapDraw.ts b/src/component/helper/MapDraw.ts
index a7176bf..15634f2 100644
--- a/src/component/helper/MapDraw.ts
+++ b/src/component/helper/MapDraw.ts
@@ -42,6 +42,7 @@ import { GeoSVGResource } from '../../coord/geo/GeoSVGResource';
 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';
 
 
 interface RegionsGroup extends graphic.Group {
@@ -91,13 +92,13 @@ class MapDraw {
      */
     private _mouseDownFlag: boolean;
 
-    private _svgMapName: string;
-
     private _regionsGroup: RegionsGroup;
 
+    private _svgMapName: string;
+
     private _svgGroup: graphic.Group;
 
-    private _svgNamedElements: Displayable[];
+    private _svgRegionElements: Displayable[];
 
 
     constructor(api: ExtensionAPI) {
@@ -167,8 +168,12 @@ class MapDraw {
             transformInfoRaw
         };
 
-        this._buildGeoJSON(viewBuildCtx);
-        this._buildSVG(viewBuildCtx);
+        if (geo.resourceType === 'geoJSON') {
+            this._buildGeoJSON(viewBuildCtx);
+        }
+        else if (geo.resourceType === 'geoSVG') {
+            this._buildSVG(viewBuildCtx);
+        }
 
         this._updateController(mapOrGeoModel, ecModel, api);
 
@@ -190,7 +195,7 @@ class MapDraw {
         regionsGroup.removeAll();
 
         // Only when the resource is GeoJSON, there is `geo.regions`.
-        zrUtil.each(viewBuildCtx.geo.regions, function (region) {
+        zrUtil.each(viewBuildCtx.geo.regions, function (region: GeoJSONRegion) {
 
             // Consider in GeoJson properties.name may be duplicated, for example,
             // there is multiple region named "United Kindom" or "France" (so many
@@ -238,7 +243,7 @@ class MapDraw {
                 }
             });
 
-            const centerPt = transformPoint(region.center);
+            const centerPt = transformPoint(region.getCenter());
 
             this._resetSingleRegionGraphic(
                 viewBuildCtx, compoundPath, regionGroup, region.name, centerPt, null
@@ -263,9 +268,9 @@ class MapDraw {
             this._useSVG(mapName);
         }
 
-        zrUtil.each(this._svgNamedElements, function (namedElement) {
+        zrUtil.each(this._svgRegionElements, function (el: Displayable) {
             this._resetSingleRegionGraphic(
-                viewBuildCtx, namedElement, namedElement, namedElement.name, [0, 0], 'inside'
+                viewBuildCtx, el, el, el.name, [0, 0], 'inside'
             );
         }, this);
     }
@@ -430,10 +435,10 @@ class MapDraw {
 
     private _useSVG(mapName: string): void {
         const resource = geoSourceManager.getGeoResource(mapName);
-        if (resource && resource.type === 'svg') {
+        if (resource && resource.type === 'geoSVG') {
             const svgGraphic = (resource as GeoSVGResource).useGraphic(this.uid);
             this._svgGroup.add(svgGraphic.root);
-            this._svgNamedElements = svgGraphic.namedElements;
+            this._svgRegionElements = svgGraphic.regionElements;
             this._svgMapName = mapName;
         }
     }
@@ -444,11 +449,11 @@ class MapDraw {
             return;
         }
         const resource = geoSourceManager.getGeoResource(mapName);
-        if (resource && resource.type === 'svg') {
+        if (resource && resource.type === 'geoSVG') {
             (resource as GeoSVGResource).freeGraphic(this.uid);
         }
+        this._svgRegionElements = null;
         this._svgGroup.removeAll();
-        this._svgNamedElements = null;
         this._svgMapName = null;
     }
 
@@ -516,6 +521,7 @@ class MapDraw {
         const mapDraw = this;
 
         regionsGroup.off('mousedown');
+        regionsGroup.off('click');
 
         // @ts-ignore FIXME:TS resolve type conflict
         if (mapOrGeoModel.get('selectedMode')) {
diff --git a/src/coord/geo/Geo.ts b/src/coord/geo/Geo.ts
index 516a971..1f29cc5 100644
--- a/src/coord/geo/Geo.ts
+++ b/src/coord/geo/Geo.ts
@@ -21,13 +21,29 @@ import * as zrUtil from 'zrender/src/core/util';
 import BoundingRect from 'zrender/src/core/BoundingRect';
 import View from '../View';
 import geoSourceManager from './geoSourceManager';
-import Region from './Region';
-import { NameMap } from './geoTypes';
+import { GeoJSONRegion, Region } from './Region';
+import { GeoResource, NameMap } from './geoTypes';
 import GlobalModel from '../../model/Global';
 import { ParsedModelFinder, ParsedModelFinderKnown, SINGLE_REFERRING } from '../../util/model';
 import GeoModel from './GeoModel';
 import { resizeGeoType } from './geoCreator';
 
+const GEO_DEFAULT_PARAMS: {
+    [type in GeoResource['type']]: {
+        aspectScale: number;
+        invertLongitute: boolean;
+    }
+} = {
+    'geoJSON': {
+        aspectScale: 0.75,
+        invertLongitute: true
+    },
+    'geoSVG': {
+        aspectScale: 1,
+        invertLongitute: false
+    }
+} as const;
+
 
 class Geo extends View {
 
@@ -37,35 +53,42 @@ class Geo extends View {
 
     // map type
     readonly map: string;
+    readonly resourceType: GeoResource['type'];
 
     private _nameCoordMap: zrUtil.HashMap<number[]>;
     private _regionsMap: zrUtil.HashMap<Region>;
     private _invertLongitute: boolean;
     readonly regions: Region[];
+    readonly aspectScale: number;
 
     // Injected outside
-    aspectScale: number;
     model: GeoModel;
     resize: resizeGeoType;
 
-    /**
-     * For backward compatibility, the orginal interface:
-     * `name, map, geoJson, specialAreas, nameMap` is kept.
-     *
-     * @param map Map type Specify the positioned areas by left, top, width, height.
-     * @param [nameMap] Specify name alias
-     */
-    constructor(name: string, map: string, nameMap?: NameMap, invertLongitute?: boolean) {
+    constructor(
+        name: string,
+        map: string,
+        opt: {
+            // Specify name alias
+            nameMap?: NameMap;
+            aspectScale?: number;
+        }
+    ) {
         super(name);
 
         this.map = map;
 
-        const source = geoSourceManager.load(map, nameMap);
+        const source = geoSourceManager.load(map, opt.nameMap);
+        const resource = geoSourceManager.getGeoResource(map);
+        this.resourceType = resource ? resource.type : null;
+
+        const defaultParmas = GEO_DEFAULT_PARAMS[resource.type];
 
         this._nameCoordMap = source.nameCoordMap;
         this._regionsMap = source.regionsMap;
-        this._invertLongitute = invertLongitute == null ? true : invertLongitute;
+        this._invertLongitute = defaultParmas.invertLongitute;
         this.regions = source.regions;
+        this.aspectScale = zrUtil.retrieve2(opt.aspectScale, defaultParmas.aspectScale);
 
         const boundingRect = source.boundingRect;
         this.setBoundingRect(boundingRect.x, boundingRect.y, boundingRect.width, boundingRect.height);
@@ -77,7 +100,8 @@ class Geo extends View {
     containCoord(coord: number[]) {
         const regions = this.regions;
         for (let i = 0; i < regions.length; i++) {
-            if (regions[i].contain(coord)) {
+            const region = regions[i];
+            if (region.type === 'geoJSON' && (region as GeoJSONRegion).contain(coord)) {
                 return true;
             }
         }
@@ -120,7 +144,8 @@ class Geo extends View {
     getRegionByCoord(coord: number[]): Region {
         const regions = this.regions;
         for (let i = 0; i < regions.length; i++) {
-            if (regions[i].contain(coord)) {
+            const region = regions[i];
+            if (region.type === 'geoJSON' && (region as GeoJSONRegion).contain(coord)) {
                 return regions[i];
             }
         }
diff --git a/src/coord/geo/GeoJSONResource.ts b/src/coord/geo/GeoJSONResource.ts
index 6189322..e5aea61 100644
--- a/src/coord/geo/GeoJSONResource.ts
+++ b/src/coord/geo/GeoJSONResource.ts
@@ -26,7 +26,7 @@ import fixTextCoord from './fix/textCoord';
 import fixGeoCoord from './fix/geoCoord';
 import fixDiaoyuIsland from './fix/diaoyuIsland';
 import BoundingRect from 'zrender/src/core/BoundingRect';
-import Region from './Region';
+import { GeoJSONRegion } from './Region';
 import { GeoJSON, GeoJSONCompressed, GeoJSONSourceInput, GeoResource, GeoSpecialAreas, NameMap } from './geoTypes';
 
 
@@ -38,7 +38,7 @@ export class GeoJSONResource implements GeoResource {
     private _mapName: string;
 
     private _parsed: {
-        regions: Region[];
+        regions: GeoJSONRegion[];
         boundingRect: BoundingRect;
     };
 
@@ -65,10 +65,10 @@ export class GeoJSONResource implements GeoResource {
             };
         }
 
-        const regionsMap = createHashMap<Region>();
-        const nameCoordMap = createHashMap<Region['center']>();
+        const regionsMap = createHashMap<GeoJSONRegion>();
+        const nameCoordMap = createHashMap<ReturnType<GeoJSONRegion['getCenter']>>();
 
-        const finalRegions: Region[] = [];
+        const finalRegions: GeoJSONRegion[] = [];
         each(parsed.regions, function (region) {
             let regionName = region.name;
 
@@ -79,7 +79,7 @@ export class GeoJSONResource implements GeoResource {
 
             finalRegions.push(region);
             regionsMap.set(regionName, region);
-            nameCoordMap.set(regionName, region.center);
+            nameCoordMap.set(regionName, region.getCenter());
         });
 
         return {
@@ -90,7 +90,7 @@ export class GeoJSONResource implements GeoResource {
         };
     }
 
-    private _parseToRegions(nameProperty: string): Region[] {
+    private _parseToRegions(nameProperty: string): GeoJSONRegion[] {
         const mapName = this._mapName;
         const geoJSON = this._geoJSON;
         let rawRegions;
@@ -147,7 +147,7 @@ export class GeoJSONResource implements GeoResource {
 
 }
 
-function calculateBoundingRect(regions: Region[]): BoundingRect {
+function calculateBoundingRect(regions: GeoJSONRegion[]): BoundingRect {
     let rect;
     for (let i = 0; i < regions.length; i++) {
         const regionRect = regions[i].getBoundingRect();
diff --git a/src/coord/geo/GeoModel.ts b/src/coord/geo/GeoModel.ts
index 78054c6..7095c6f 100644
--- a/src/coord/geo/GeoModel.ts
+++ b/src/coord/geo/GeoModel.ts
@@ -129,7 +129,7 @@ class GeoModel extends ComponentModel<GeoOption> {
         top: 'center',
 
         // Default value:
-        // for SVG source: 1,
+        // for geoSVG source: 1,
         // for geoJSON source: 0.75.
         aspectScale: null,
 
@@ -287,8 +287,6 @@ class GeoModel extends ComponentModel<GeoOption> {
         return !!(selectedMap && selectedMap[name]);
     }
 
-    private _initSelectedMapFromData() {
-    }
 }
 
 export default GeoModel;
diff --git a/src/coord/geo/GeoSVGResource.ts b/src/coord/geo/GeoSVGResource.ts
index 43c18fa..0099161 100644
--- a/src/coord/geo/GeoSVGResource.ts
+++ b/src/coord/geo/GeoSVGResource.ts
@@ -22,25 +22,35 @@ 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 BoundingRect from 'zrender/src/core/BoundingRect';
-import { GeoResource, GeoSVGSourceInput } from './geoTypes';
+import { GeoResource, GeoSVGGraphicRoot, GeoSVGSourceInput, NameMap } from './geoTypes';
 import { parseXML } from 'zrender/src/tool/parseXML';
 import Displayable from 'zrender/src/graphic/Displayable';
+import { GeoSVGRegion } from './Region';
 
-export interface GeoSVGGraphic {
+interface GeoSVGGraphicRecord {
     root: Group;
-    namedElements: Displayable[];
+    boundingRect: BoundingRect;
+    regionElements: Displayable[];
 }
 
 export class GeoSVGResource implements GeoResource {
 
-    readonly type = 'svg';
+    readonly type = 'geoSVG';
     private _mapName: string;
     private _parsedXML: SVGElement;
-    private _rootForRect: GeoSVGGraphic;
+
+    private _firstGraphic: GeoSVGGraphicRecord;
     private _boundingRect: BoundingRect;
-    // key: hostKey, value: root
-    private _usedRootMap: HashMap<GeoSVGGraphic> = createHashMap();
-    private _freedRoots: GeoSVGGraphic[] = [];
+    private _regions: GeoSVGRegion[] = [];
+    // Key: region.name
+    private _regionsMap: HashMap<GeoSVGRegion> = createHashMap<GeoSVGRegion>();
+    // Key: region.name
+    private _nameCoordMap: HashMap<number[]> = createHashMap<number[]>();
+
+    // All used graphics. key: hostKey, value: root
+    private _usedGraphicMap: HashMap<GeoSVGGraphicRecord> = createHashMap();
+    // All unused graphics.
+    private _freedGraphics: GeoSVGGraphicRecord[] = [];
 
     constructor(
         mapName: string,
@@ -57,22 +67,47 @@ export class GeoSVGResource implements GeoResource {
         this._parsedXML = parseXML(svg);
     }
 
-    load(): { boundingRect: BoundingRect } {
+    load(nameMap: NameMap, nameProperty: string) {
         // In the "load" stage, graphic need to be built to
         // get boundingRect for geo coordinate system.
-        const rootForRect = this._rootForRect;
-        if (rootForRect) {
-            return { boundingRect: this._boundingRect };
-        }
+        let firstGraphic = this._firstGraphic;
+
+        // Create the return data structure only when first graphic created.
+        // Because they will be used in geo coordinate system update stage,
+        // and `regions` will be mounted at `geo` coordinate system,
+        // in which there is no "view" info, so that it should better not to
+        // make references to graphic elements.
+        if (!firstGraphic) {
+            firstGraphic = this._firstGraphic = buildGraphic(this._parsedXML);
+
+            this._freedGraphics.push(firstGraphic);
 
-        const graphic = buildGraphic(this._parsedXML);
+            this._boundingRect = this._firstGraphic.boundingRect.clone();
 
-        this._rootForRect = graphic;
-        this._boundingRect = graphic.boundingRect;
+            // Create resions only for the first graphic, see coments below.
+            const regionElements = firstGraphic.regionElements;
+            for (let i = 0; i < regionElements.length; i++) {
+                const el = regionElements[i];
 
-        this._freedRoots.push(graphic);
+                // Try use the alias in geoNameMap
+                let regionName = el.name;
+                if (nameMap && nameMap.hasOwnProperty(regionName)) {
+                    regionName = nameMap[regionName];
+                }
 
-        return { boundingRect: graphic.boundingRect };
+                const region = new GeoSVGRegion(regionName, el);
+
+                this._regions.push(region);
+                this._regionsMap.set(regionName, region);
+            }
+        }
+
+        return {
+            boundingRect: this._boundingRect,
+            regions: this._regions,
+            regionsMap: this._regionsMap,
+            nameCoordMap: this._nameCoordMap
+        };
     }
 
     // Consider:
@@ -83,26 +118,28 @@ export class GeoSVGResource implements GeoResource {
     //     and it is called without view info.
     // So we maintain graphic elements in this module, and enables `view` to use/return these
     // graphics from/to the pool with it's uid.
-    useGraphic(hostKey: string): GeoSVGGraphic {
-        const usedRootMap = this._usedRootMap;
+    useGraphic(hostKey: string): GeoSVGGraphicRecord {
+        const usedRootMap = this._usedGraphicMap;
 
         let svgGraphic = usedRootMap.get(hostKey);
         if (svgGraphic) {
             return svgGraphic;
         }
 
-        svgGraphic = this._freedRoots.pop() || buildGraphic(this._parsedXML, this._boundingRect);
+        svgGraphic = this._freedGraphics.pop()
+            // use the first boundingRect to avoid duplicated boundingRect calculation.
+            || buildGraphic(this._parsedXML, this._boundingRect);
 
         return usedRootMap.set(hostKey, svgGraphic);
     }
 
     freeGraphic(hostKey: string): void {
-        const usedRootMap = this._usedRootMap;
+        const usedRootMap = this._usedGraphicMap;
 
         const svgGraphic = usedRootMap.get(hostKey);
         if (svgGraphic) {
             usedRootMap.removeKey(hostKey);
-            this._freedRoots.push(svgGraphic);
+            this._freedGraphics.push(svgGraphic);
         }
     }
 
@@ -111,12 +148,10 @@ export class GeoSVGResource implements GeoResource {
 
 function buildGraphic(
     svgXML: SVGElement,
+    // If input boundingRect, avoid boundingRect calculation,
+    // which might be time-consuming.
     boundingRect?: BoundingRect
-): {
-    root: Group;
-    boundingRect: BoundingRect;
-    namedElements: Displayable[]
-} {
+): GeoSVGGraphicRecord {
     let result;
     let root;
 
@@ -151,6 +186,7 @@ function buildGraphic(
         }
     }
 
+    // Note: we keep the covenant that the root has no transform.
     if (viewBoxRect) {
         const viewBoxTransform = makeViewBoxTransform(viewBoxRect, boundingRect.width, boundingRect.height);
         const elRoot = root;
@@ -165,9 +201,11 @@ function buildGraphic(
         shape: boundingRect.plain()
     }));
 
+    (root as GeoSVGGraphicRoot).isGeoSVGGraphicRoot = true;
+
     return {
         root: root,
         boundingRect: boundingRect,
-        namedElements: result.namedElements
+        regionElements: result.namedElements
     };
 }
diff --git a/src/coord/geo/Region.ts b/src/coord/geo/Region.ts
index 36a07c7..df482e3 100644
--- a/src/coord/geo/Region.ts
+++ b/src/coord/geo/Region.ts
@@ -22,10 +22,38 @@ import BoundingRect from 'zrender/src/core/BoundingRect';
 import * as bbox from 'zrender/src/core/bbox';
 import * as vec2 from 'zrender/src/core/vector';
 import * as polygonContain from 'zrender/src/contain/polygon';
-import { GeoJSON } from './geoTypes';
+import { GeoJSON, GeoSVGGraphicRoot } from './geoTypes';
+import * as matrix from 'zrender/src/core/matrix';
+import Element from 'zrender/src/Element';
 
+const TMP_TRANSFORM = [] as number[];
 
-class Region {
+export class Region {
+
+    readonly name: string;
+    readonly type: 'geoJSON' | 'geoSVG';
+
+    constructor(
+        name: string
+    ) {
+        this.name = name;
+    }
+
+    /**
+     * Get center point in data unit. That is,
+     * for GeoJSONRegion, the unit is lat/lng,
+     * for GeoSVGRegion, the unit is pixel but based on root.
+     */
+    getCenter(): number[] {
+        return;
+    }
+
+}
+
+
+export class GeoJSONRegion extends Region {
+
+    readonly type = 'geoJSON';
 
     readonly geometries: {
         type: 'polygon'; // FIXME:TS Is there other types?
@@ -33,9 +61,7 @@ class Region {
         interiors?: number[][][];
     }[];
 
-    readonly name: string;
-
-    center: number[];
+    private _center: number[];
 
     // Injected outside.
     properties: GeoJSON['features'][0]['properties'];
@@ -45,10 +71,11 @@ class Region {
 
     constructor(
         name: string,
-        geometries: Region['geometries'],
+        geometries: GeoJSONRegion['geometries'],
         cp: GeoJSON['features'][0]['properties']['cp']
     ) {
-        this.name = name;
+        super(name);
+
         this.geometries = geometries;
 
         if (!cp) {
@@ -61,7 +88,7 @@ class Region {
         else {
             cp = [cp[0], cp[1]];
         }
-        this.center = cp;
+        this._center = cp;
     }
 
     getBoundingRect(): BoundingRect {
@@ -155,20 +182,81 @@ class Region {
         rect = this._rect;
         rect.copy(target);
         // Update center
-        this.center = [
+        this._center = [
             rect.x + rect.width / 2,
             rect.y + rect.height / 2
         ];
     }
 
-    cloneShallow(name: string): Region {
+    cloneShallow(name: string): GeoJSONRegion {
         name == null && (name = this.name);
-        const newRegion = new Region(name, this.geometries, this.center);
+        const newRegion = new GeoJSONRegion(name, this.geometries, this._center);
         newRegion._rect = this._rect;
         newRegion.transformTo = null; // Simply avoid to be called.
         return newRegion;
     }
 
+    getCenter() {
+        return this._center;
+    }
+
+    setCenter(center: number[]) {
+        this._center = center;
+    }
+
+}
+
+export class GeoSVGRegion extends Region {
+
+    readonly type = 'geoSVG';
+
+    private _center: number[];
+
+    // Can only be used to calculate, but not be modified.
+    // Becuase this el may not belongs to this view,
+    // but been displaying on some other view.
+    private _elOnlyForCalculate: Element;
+
+    constructor(
+        name: string,
+        elOnlyForCalculate: Element
+    ) {
+        super(name);
+        this._elOnlyForCalculate = elOnlyForCalculate;
+    }
+
+    getCenter() {
+        let center = this._center;
+        if (!center) {
+            // In most cases there are no need to calculate this center.
+            // So calculate only when called.
+            center = this._center = this._calculateCenter();
+        }
+        return center;
+    }
+
+    private _calculateCenter(): number[] {
+        const el = this._elOnlyForCalculate;
+        const rect = el.getBoundingRect();
+        const center = [
+            rect.x + rect.width,
+            rect.y + rect.height
+        ];
+
+        const mat = matrix.identity(TMP_TRANSFORM);
+
+        let target = el;
+        while (target && !(target as GeoSVGGraphicRoot).isGeoSVGGraphicRoot) {
+            matrix.mul(mat, target.getLocalTransform(), mat);
+            target = target.parent;
+        }
+
+        matrix.invert(mat, mat);
+
+        vec2.applyTransform(center, center, mat);
+
+        return center;
+    }
+
 }
 
-export default Region;
diff --git a/src/coord/geo/fix/diaoyuIsland.ts b/src/coord/geo/fix/diaoyuIsland.ts
index 06042cc..b845900 100644
--- a/src/coord/geo/fix/diaoyuIsland.ts
+++ b/src/coord/geo/fix/diaoyuIsland.ts
@@ -1,5 +1,3 @@
-import Region from '../Region';
-
 /*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
@@ -19,6 +17,8 @@ import Region from '../Region';
 * under the License.
 */
 
+import { GeoJSONRegion } from '../Region';
+
 // Fix for 钓鱼岛
 
 // let Region = require('../Region');
@@ -36,7 +36,7 @@ const points = [
     ]
 ];
 
-export default function fixDiaoyuIsland(mapType: string, region: Region) {
+export default function fixDiaoyuIsland(mapType: string, region: GeoJSONRegion) {
     if (mapType === 'china' && region.name === '台湾') {
         region.geometries.push({
             type: 'polygon',
diff --git a/src/coord/geo/fix/geoCoord.ts b/src/coord/geo/fix/geoCoord.ts
index bfef6e9..ea03c64 100644
--- a/src/coord/geo/fix/geoCoord.ts
+++ b/src/coord/geo/fix/geoCoord.ts
@@ -1,6 +1,3 @@
-import Region from '../Region';
-import { Dictionary } from 'zrender/src/core/types';
-
 /*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
@@ -20,19 +17,24 @@ import { Dictionary } from 'zrender/src/core/types';
 * under the License.
 */
 
+import { Dictionary } from 'zrender/src/core/types';
+import { GeoJSONRegion } from '../Region';
+
 const geoCoordMap = {
     'Russia': [100, 60],
     'United States': [-99, 38],
     'United States of America': [-99, 38]
 } as Dictionary<number[]>;
 
-export default function fixGeoCoords(mapType: string, region: Region) {
+export default function fixGeoCoords(mapType: string, region: GeoJSONRegion) {
     if (mapType === 'world') {
         const geoCoord = geoCoordMap[region.name];
         if (geoCoord) {
-            const cp = region.center;
-            cp[0] = geoCoord[0];
-            cp[1] = geoCoord[1];
+            const cp = [
+                geoCoord[0],
+                geoCoord[1]
+            ];
+            region.setCenter(cp);
         }
     }
 }
\ No newline at end of file
diff --git a/src/coord/geo/fix/nanhai.ts b/src/coord/geo/fix/nanhai.ts
index 2bbc3d0..e1dd612 100644
--- a/src/coord/geo/fix/nanhai.ts
+++ b/src/coord/geo/fix/nanhai.ts
@@ -20,7 +20,7 @@
 // Fix for 南海诸岛
 
 import * as zrUtil from 'zrender/src/core/util';
-import Region from '../Region';
+import { GeoJSONRegion } from '../Region';
 
 const geoCoord = [126, 25];
 
@@ -51,9 +51,9 @@ for (let i = 0; i < points.length; i++) {
     }
 }
 
-export default function fixNanhai(mapType: string, regions: Region[]) {
+export default function fixNanhai(mapType: string, regions: GeoJSONRegion[]) {
     if (mapType === 'china') {
-        regions.push(new Region(
+        regions.push(new GeoJSONRegion(
             '南海诸岛',
             zrUtil.map(points, function (exterior) {
                 return {
diff --git a/src/coord/geo/fix/textCoord.ts b/src/coord/geo/fix/textCoord.ts
index 477d644..813e26a 100644
--- a/src/coord/geo/fix/textCoord.ts
+++ b/src/coord/geo/fix/textCoord.ts
@@ -1,6 +1,3 @@
-import Region from '../Region';
-import { Dictionary } from 'zrender/src/core/types';
-
 /*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
@@ -20,6 +17,8 @@ import { Dictionary } from 'zrender/src/core/types';
 * under the License.
 */
 
+import { GeoJSONRegion } from '../Region';
+import { Dictionary } from 'zrender/src/core/types';
 
 const coordsOffsetMap = {
     '南海诸岛': [32, 80],
@@ -31,13 +30,14 @@ const coordsOffsetMap = {
     '天津': [5, 5]
 } as Dictionary<number[]>;
 
-export default function fixTextCoords(mapType: string, region: Region) {
+export default function fixTextCoords(mapType: string, region: GeoJSONRegion) {
     if (mapType === 'china') {
         const coordFix = coordsOffsetMap[region.name];
         if (coordFix) {
-            const cp = region.center;
+            const cp = region.getCenter();
             cp[0] += coordFix[0] / 10.5;
             cp[1] += -coordFix[1] / (10.5 / 0.75);
+            region.setCenter(cp);
         }
     }
 }
\ No newline at end of file
diff --git a/src/coord/geo/geoCreator.ts b/src/coord/geo/geoCreator.ts
index e01f0d8..49ae8d5 100644
--- a/src/coord/geo/geoCreator.ts
+++ b/src/coord/geo/geoCreator.ts
@@ -35,6 +35,7 @@ import ComponentModel from '../../model/Component';
 
 
 export type resizeGeoType = typeof resizeGeo;
+
 /**
  * Resize method bound to the geo
  */
@@ -138,21 +139,11 @@ class GeoCreator implements CoordinateSystemCreator {
         ecModel.eachComponent('geo', function (geoModel: GeoModel, idx) {
             const name = geoModel.get('map');
 
-            let aspectScale = geoModel.get('aspectScale');
-            let invertLongitute = true;
-
-            const geoResource = geoSourceManager.getGeoResource(name);
-            if (geoResource.type === 'svg') {
-                aspectScale == null && (aspectScale = 1);
-                invertLongitute = false;
-            }
-            else {
-                aspectScale == null && (aspectScale = 0.75);
-            }
-
-            const geo = new Geo(name + idx, name, geoModel.get('nameMap'), invertLongitute);
+            const geo = new Geo(name + idx, name, {
+                nameMap: geoModel.get('nameMap'),
+                aspectScale: geoModel.get('aspectScale')
+            });
 
-            geo.aspectScale = aspectScale;
             geo.zoomLimit = geoModel.get('scaleLimit');
             geoList.push(geo);
 
@@ -192,7 +183,11 @@ class GeoCreator implements CoordinateSystemCreator {
             const nameMapList = zrUtil.map(mapSeries, function (singleMapSeries) {
                 return singleMapSeries.get('nameMap');
             });
-            const geo = new Geo(mapType, mapType, zrUtil.mergeAll(nameMapList));
+
+            const geo = new Geo(mapType, mapType, {
+                nameMap: zrUtil.mergeAll(nameMapList),
+                aspectScale: mapSeries[0].get('aspectScale')
+            });
 
             geo.zoomLimit = zrUtil.retrieve.apply(null, zrUtil.map(mapSeries, function (singleMapSeries) {
                 return singleMapSeries.get('scaleLimit');
@@ -201,7 +196,6 @@ class GeoCreator implements CoordinateSystemCreator {
 
             // Inject resize method
             geo.resize = resizeGeo;
-            geo.aspectScale = mapSeries[0].get('aspectScale');
 
             geo.resize(mapSeries[0], api);
 
@@ -239,6 +233,7 @@ class GeoCreator implements CoordinateSystemCreator {
     }
 }
 
+
 const geoCreator = new GeoCreator();
 
 export default geoCreator;
diff --git a/src/coord/geo/geoSourceManager.ts b/src/coord/geo/geoSourceManager.ts
index 66cdf1e..f13d9ec 100644
--- a/src/coord/geo/geoSourceManager.ts
+++ b/src/coord/geo/geoSourceManager.ts
@@ -131,7 +131,7 @@ export default {
             && (resource as GeoJSONResource).getMapForUser();
     },
 
-    load: function (mapName: string, nameMap: NameMap, nameProperty?: string) {
+    load: function (mapName: string, nameMap: NameMap, nameProperty?: string): ReturnType<GeoResource['load']> {
         const resource = storage.get(mapName);
 
         if (!resource) {
diff --git a/src/coord/geo/geoTypes.ts b/src/coord/geo/geoTypes.ts
index 52b8eb2..303db25 100644
--- a/src/coord/geo/geoTypes.ts
+++ b/src/coord/geo/geoTypes.ts
@@ -19,7 +19,8 @@
 
 import BoundingRect from 'zrender/src/core/BoundingRect';
 import { HashMap } from 'zrender/src/core/util';
-import Region from './Region';
+import { Group } from '../../util/graphic';
+import { Region } from './Region';
 
 
 export type GeoSVGSourceInput = 'string' | Document | SVGElement;
@@ -123,13 +124,17 @@ interface GeoJSONGeometryMultiPolygonCompressed {
 // };
 
 export interface GeoResource {
-    readonly type: 'geoJSON' | 'svg';
+    readonly type: 'geoJSON' | 'geoSVG';
     load(nameMap: NameMap, nameProperty: string): {
         boundingRect: BoundingRect;
-        regions?: Region[];
-        // Key: mapName
-        regionsMap?: HashMap<Region>;
-        // Key: mapName
-        nameCoordMap?: HashMap<number[]>;
+        regions: Region[];
+        // Key: region.name
+        regionsMap: HashMap<Region>;
+        // Key: region.name
+        nameCoordMap: HashMap<number[]>;
     };
 }
+
+export interface GeoSVGGraphicRoot extends Group {
+    isGeoSVGGraphicRoot: boolean;
+}
diff --git a/src/coord/geo/parseGeoJson.ts b/src/coord/geo/parseGeoJson.ts
index e37260e..7b2b920 100644
--- a/src/coord/geo/parseGeoJson.ts
+++ b/src/coord/geo/parseGeoJson.ts
@@ -22,7 +22,7 @@
  */
 
 import * as zrUtil from 'zrender/src/core/util';
-import Region from './Region';
+import { GeoJSONRegion } from './Region';
 import { GeoJSONCompressed, GeoJSON } from './geoTypes';
 
 
@@ -100,7 +100,7 @@ function decodePolygon(
     return result;
 }
 
-export default function parseGeoJSON(geoJson: GeoJSON | GeoJSONCompressed, nameProperty: string): Region[] {
+export default function parseGeoJSON(geoJson: GeoJSON | GeoJSONCompressed, nameProperty: string): GeoJSONRegion[] {
 
     geoJson = decode(geoJson);
 
@@ -113,7 +113,7 @@ export default function parseGeoJSON(geoJson: GeoJSON | GeoJSONCompressed, nameP
         const properties = featureObj.properties;
         const geo = featureObj.geometry;
 
-        const geometries = [] as Region['geometries'];
+        const geometries = [] as GeoJSONRegion['geometries'];
         if (geo.type === 'Polygon') {
             const coordinates = geo.coordinates;
             geometries.push({
@@ -137,7 +137,7 @@ export default function parseGeoJSON(geoJson: GeoJSON | GeoJSONCompressed, nameP
             });
         }
 
-        const region = new Region(
+        const region = new GeoJSONRegion(
             properties[nameProperty || 'name'],
             geometries,
             properties.cp
diff --git a/test/geo-svg.html b/test/geo-svg.html
index d7568d5..70b6139 100644
--- a/test/geo-svg.html
+++ b/test/geo-svg.html
@@ -39,6 +39,7 @@ under the License.
 
         <div id="main0"></div>
         <div id="main1"></div>
+        <div id="main2"></div>
 
 
 
@@ -72,6 +73,7 @@ under the License.
                 geo: {
                     map: 'testGeoJson1',
                     roam: true,
+                    selectedMode: 'single',
                     // height: '100%',
                     // center
                     // layoutCenter: ['30%', 40],
@@ -89,9 +91,22 @@ under the License.
                     'At the center of the canvas.'
                 ],
                 option: option,
+                info: [],
+                infoKey: 'allSelected',
                 height: 300
             });
 
+            if (chart) {
+                if (chart) {
+                    chart.on('geoselectchanged', function (params) {
+                        chart.__testHelper.updateInfo(
+                            params.allSelected,
+                            'allSelected'
+                        );
+                    });
+                }
+            }
+
 
         });
         </script>
@@ -102,11 +117,7 @@ under the License.
         require(['echarts'/*, 'map/js/china' */], function (echarts) {
             var option;
             $.ajax({
-                url: '../../vis-data/map/svg/seats/seatmap-example.svg', // 剧场例子
-                // url: '../../vis-data/map/svg/seats/Ethiopian_Airlines_Flight_961_seating_plan.svg', // 飞机例子
-                // url: '../../vis-data/map/svg/seats/oracle-seating-map-2017-2.svg', // 渲染错误
-                // url: '../../vis-data/map/svg/seats/DC-10-30_seat_configuration_chart.svg', // 渲染错误
-                // url: '../../vis-data/map/svg/seats/Airbus_A300B4-622R_seat_configuration_chart.svg', // 渲染错误
+                url: '../../vis-data/map/svg/seats/seatmap-example.svg',
                 dataType: 'text'
             }).done(function (svg) {
 
@@ -118,24 +129,25 @@ under the License.
                     geo: {
                         map: 'seatmap',
                         roam: true,
+                        selectedMode: 'multiple',
                         // height: 100,
                         // zoom: 1.5
                         emphasis: {
-                            // itemStyle: {
-                            //     color: 'red'
-                            // },
                             label: {
-                                // color: '#fff',
                                 textBorderColor: '#fff',
                                 textBorderWidth: 2
                             }
                         },
-                        // itemStyle: {
-                        //     color: 'red'
-                        // },
-                        // label: {
-                        //     color: '#fff'
-                        // }
+                        select: {
+                            itemStyle: {
+                                color: '#b50205'
+                            },
+                            label: {
+                                show: false,
+                                textBorderColor: '#fff',
+                                textBorderWidth: 2
+                            }
+                        }
                     },
                     // series: {
                     //     type: 'scatter',
@@ -148,15 +160,26 @@ under the License.
 
                 var chart = testHelper.create(echarts, 'main1', {
                     title: [
-                        'Test Case Description of main0',
-                        '(Muliple lines and **emphasis** are supported in description)'
+                        'geo component with svg resource',
+                        'click seat: check **allSelected** correct.'
                     ],
                     option: option,
+                    info: [],
+                    infoKey: 'allSelected',
                     height: 300
                     // buttons: [{text: 'btn-txt', onclick: function () {}}],
                     // recordCanvas: true,
                 });
 
+                if (chart) {
+                    chart.on('geoselectchanged', function (params) {
+                        chart.__testHelper.updateInfo(
+                            params.allSelected,
+                            'allSelected'
+                        );
+                    });
+                }
+
             });
 
         });
@@ -165,6 +188,77 @@ under the License.
 
 
 
+
+
+        <script>
+        require(['echarts'/*, 'map/js/china' */], function (echarts) {
+            var option;
+            $.ajax({
+                url: '../../vis-data/map/svg/seats/seatmap-example.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: {
+                            label: {
+                                textBorderColor: '#fff',
+                                textBorderWidth: 2
+                            }
+                        },
+                        select: {
+                            itemStyle: {
+                                color: '#b50205'
+                            },
+                            label: {
+                                show: false,
+                                textBorderColor: '#fff',
+                                textBorderWidth: 2
+                            }
+                        }
+                    }
+                };
+
+                var chart = testHelper.create(echarts, 'main2', {
+                    title: [
+                        'map series with svg resource',
+                        'Hover seat: check **tooltip** correct.'
+                    ],
+                    option: option,
+                    info: [],
+                    infoKey: 'allSelected',
+                    height: 300
+                    // buttons: [{text: 'btn-txt', onclick: function () {}}],
+                    // recordCanvas: true,
+                });
+
+                if (chart) {
+                    chart.on('selectchanged', function (params) {
+                        chart.__testHelper.updateInfo(
+                            params.selected,
+                            'selected'
+                        );
+                    });
+                }
+
+            });
+
+        });
+        </script>
+
+
     </body>
 </html>
 


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