| /* | 
 | * Licensed to the Apache Software Foundation (ASF) under one | 
 | * or more contributor license agreements.  See the NOTICE file | 
 | * distributed with this work for additional information | 
 | * regarding copyright ownership.  The ASF licenses this file | 
 | * to you under the Apache License, Version 2.0 (the | 
 | * "License"); you may not use this file except in compliance | 
 | * with the License.  You may obtain a copy of the License at | 
 | * | 
 | *   http://www.apache.org/licenses/LICENSE-2.0 | 
 | * | 
 | * Unless required by applicable law or agreed to in writing, | 
 | * software distributed under the License is distributed on an | 
 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | 
 | * KIND, either express or implied.  See the License for the | 
 | * specific language governing permissions and limitations | 
 | * under the License. | 
 | */ | 
 |  | 
 | import * as zrUtil from 'zrender/src/core/util'; | 
 | import RoamController from './RoamController'; | 
 | import * as roamHelper from '../../component/helper/roamHelper'; | 
 | import {onIrrelevantElement} from '../../component/helper/cursorHelper'; | 
 | import * as graphic from '../../util/graphic'; | 
 | import { | 
 |     toggleHoverEmphasis, | 
 |     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, RegoinOption } from '../../coord/geo/GeoModel'; | 
 | import MapSeries, { MapDataItemOption } from '../../chart/map/MapSeries'; | 
 | import GlobalModel from '../../model/Global'; | 
 | import { Payload, ECElement, LineStyleOption, InnerFocus, DisplayState } from '../../util/types'; | 
 | import GeoView from '../geo/GeoView'; | 
 | import MapView from '../../chart/map/MapView'; | 
 | import Geo from '../../coord/geo/Geo'; | 
 | import Model from '../../model/Model'; | 
 | import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle'; | 
 | import { getECData } from '../../util/innerStore'; | 
 | import { createOrUpdatePatternFromDecal } from '../../util/decal'; | 
 | import ZRText, {TextStyleProps} from 'zrender/src/graphic/Text'; | 
 | import { ViewCoordSysTransformInfoPart } from '../../coord/View'; | 
 | import { GeoSVGGraphicRecord, GeoSVGResource } from '../../coord/geo/GeoSVGResource'; | 
 | import Displayable from 'zrender/src/graphic/Displayable'; | 
 | import Element from 'zrender/src/Element'; | 
 | import SeriesData from '../../data/SeriesData'; | 
 | import { GeoJSONRegion } from '../../coord/geo/Region'; | 
 | import { SVGNodeTagLower } from 'zrender/src/tool/parseSVG'; | 
 | import { makeInner } from '../../util/model'; | 
 | import { GeoProjection, ProjectionStream } from '../../coord/geo/geoTypes'; | 
 |  | 
 | interface RegionsGroup extends graphic.Group { | 
 | } | 
 |  | 
 | type RegionModel = ReturnType<GeoModel['getRegionModel']> | ReturnType<MapSeries['getRegionModel']>; | 
 |  | 
 | type MapOrGeoModel = GeoModel | MapSeries; | 
 |  | 
 | interface ViewBuildContext { | 
 |     api: ExtensionAPI; | 
 |     geo: Geo; | 
 |     mapOrGeoModel: GeoModel | MapSeries; | 
 |     data: SeriesData; | 
 |     isVisualEncodedByVisualMap: boolean; | 
 |     isGeo: boolean; | 
 |     transformInfoRaw: ViewCoordSysTransformInfoPart; | 
 | } | 
 |  | 
 | interface GeoStyleableOption { | 
 |     itemStyle?: GeoItemStyleOption; | 
 |     lineStyle?: LineStyleOption; | 
 | } | 
 | type RegionName = string; | 
 |  | 
 | /** | 
 |  * Only these tags enable use `itemStyle` if they are named in SVG. | 
 |  * Other tags like <text> <tspan> <image> might not suitable for `itemStyle`. | 
 |  * They will not be considered to be styled until some requirements come. | 
 |  */ | 
 | const OPTION_STYLE_ENABLED_TAGS: SVGNodeTagLower[] = [ | 
 |     'rect', 'circle', 'line', 'ellipse', 'polygon', 'polyline', 'path' | 
 | ]; | 
 | const OPTION_STYLE_ENABLED_TAG_MAP = zrUtil.createHashMap<number, SVGNodeTagLower>( | 
 |     OPTION_STYLE_ENABLED_TAGS | 
 | ); | 
 | const STATE_TRIGGER_TAG_MAP = zrUtil.createHashMap<number, SVGNodeTagLower>( | 
 |     OPTION_STYLE_ENABLED_TAGS.concat(['g']) as SVGNodeTagLower[] | 
 | ); | 
 | const LABEL_HOST_MAP = zrUtil.createHashMap<number, SVGNodeTagLower>( | 
 |     OPTION_STYLE_ENABLED_TAGS.concat(['g']) as SVGNodeTagLower[] | 
 | ); | 
 | const mapLabelRaw = makeInner<{ | 
 |     ignore: boolean | 
 | }, ZRText>(); | 
 |  | 
 |  | 
 | function getFixedItemStyle(model: Model<GeoItemStyleOption>) { | 
 |     const itemStyle = model.getItemStyle(); | 
 |     const areaColor = model.get('areaColor'); | 
 |  | 
 |     // If user want the color not to be changed when hover, | 
 |     // they should both set areaColor and color to be null. | 
 |     if (areaColor != null) { | 
 |         itemStyle.fill = areaColor; | 
 |     } | 
 |  | 
 |     return itemStyle; | 
 | } | 
 | // Only stroke can be used for line. | 
 | // Using fill in style if stroke not exits. | 
 | // TODO Not sure yet. Perhaps a separate `lineStyle`? | 
 | function fixLineStyle(styleHost: { style: graphic.Path['style'] }) { | 
 |     const style = styleHost.style; | 
 |     if (style) { | 
 |         style.stroke = (style.stroke || style.fill); | 
 |         style.fill = null; | 
 |     } | 
 | } | 
 |  | 
 | class MapDraw { | 
 |  | 
 |     private uid: string; | 
 |  | 
 |     private _controller: RoamController; | 
 |  | 
 |     private _controllerHost: { | 
 |         target: graphic.Group; | 
 |         zoom?: number; | 
 |         zoomLimit?: GeoCommonOptionMixin['scaleLimit']; | 
 |     }; | 
 |  | 
 |     readonly group: graphic.Group; | 
 |  | 
 |  | 
 |     /** | 
 |      * This flag is used to make sure that only one among | 
 |      * `pan`, `zoom`, `click` can occurs, otherwise 'selected' | 
 |      * action may be triggered when `pan`, which is unexpected. | 
 |      */ | 
 |     private _mouseDownFlag: boolean; | 
 |  | 
 |     private _regionsGroup: RegionsGroup; | 
 |  | 
 |     private _regionsGroupByName: zrUtil.HashMap<RegionsGroup>; | 
 |  | 
 |     private _svgMapName: string; | 
 |  | 
 |     private _svgGroup: graphic.Group; | 
 |  | 
 |     private _svgGraphicRecord: GeoSVGGraphicRecord; | 
 |  | 
 |     // A name may correspond to multiple graphics. | 
 |     // Used as event dispatcher. | 
 |     private _svgDispatcherMap: zrUtil.HashMap<Element[], RegionName>; | 
 |  | 
 |  | 
 |     constructor(api: ExtensionAPI) { | 
 |         const group = new graphic.Group(); | 
 |         this.uid = getUID('ec_map_draw'); | 
 |         this._controller = new RoamController(api.getZr()); | 
 |         this._controllerHost = { target: group }; | 
 |         this.group = group; | 
 |  | 
 |         group.add(this._regionsGroup = new graphic.Group() as RegionsGroup); | 
 |         group.add(this._svgGroup = new graphic.Group()); | 
 |     } | 
 |  | 
 |     draw( | 
 |         mapOrGeoModel: GeoModel | MapSeries, | 
 |         ecModel: GlobalModel, | 
 |         api: ExtensionAPI, | 
 |         fromView: MapView | GeoView, | 
 |         payload: Payload | 
 |     ): void { | 
 |  | 
 |         const isGeo = mapOrGeoModel.mainType === 'geo'; | 
 |  | 
 |         // Map series has data. GEO model that controlled by map series | 
 |         // will be assigned with map data. Other GEO model has no data. | 
 |         let data = (mapOrGeoModel as MapSeries).getData && (mapOrGeoModel as MapSeries).getData(); | 
 |         isGeo && ecModel.eachComponent({mainType: 'series', subType: 'map'}, function (mapSeries: MapSeries) { | 
 |             if (!data && mapSeries.getHostGeoModel() === mapOrGeoModel) { | 
 |                 data = mapSeries.getData(); | 
 |             } | 
 |         }); | 
 |  | 
 |         const geo = mapOrGeoModel.coordinateSystem; | 
 |  | 
 |         const regionsGroup = this._regionsGroup; | 
 |         const group = this.group; | 
 |  | 
 |         const transformInfo = geo.getTransformInfo(); | 
 |         const transformInfoRaw = transformInfo.raw; | 
 |         const transformInfoRoam = transformInfo.roam; | 
 |  | 
 |         // No animation when first draw or in action | 
 |         const isFirstDraw = !regionsGroup.childAt(0) || payload; | 
 |  | 
 |         if (isFirstDraw) { | 
 |             group.x = transformInfoRoam.x; | 
 |             group.y = transformInfoRoam.y; | 
 |             group.scaleX = transformInfoRoam.scaleX; | 
 |             group.scaleY = transformInfoRoam.scaleY; | 
 |             group.dirty(); | 
 |         } | 
 |         else { | 
 |             graphic.updateProps(group, transformInfoRoam, mapOrGeoModel); | 
 |         } | 
 |  | 
 |         const isVisualEncodedByVisualMap = data | 
 |             && data.getVisual('visualMeta') | 
 |             && data.getVisual('visualMeta').length > 0; | 
 |  | 
 |         const viewBuildCtx = { | 
 |             api, | 
 |             geo, | 
 |             mapOrGeoModel, | 
 |             data, | 
 |             isVisualEncodedByVisualMap, | 
 |             isGeo, | 
 |             transformInfoRaw | 
 |         }; | 
 |  | 
 |         if (geo.resourceType === 'geoJSON') { | 
 |             this._buildGeoJSON(viewBuildCtx); | 
 |         } | 
 |         else if (geo.resourceType === 'geoSVG') { | 
 |             this._buildSVG(viewBuildCtx); | 
 |         } | 
 |  | 
 |         this._updateController(mapOrGeoModel, ecModel, api); | 
 |  | 
 |         this._updateMapSelectHandler(mapOrGeoModel, regionsGroup, api, fromView); | 
 |     } | 
 |  | 
 |     private _buildGeoJSON(viewBuildCtx: ViewBuildContext): void { | 
 |         const regionsGroupByName = this._regionsGroupByName = zrUtil.createHashMap<RegionsGroup, string>(); | 
 |         const regionsInfoByName = zrUtil.createHashMap<{ | 
 |             dataIdx: number; | 
 |             regionModel: Model<RegoinOption> | Model<MapDataItemOption>; | 
 |         }, string>(); | 
 |         const regionsGroup = this._regionsGroup; | 
 |         const transformInfoRaw = viewBuildCtx.transformInfoRaw; | 
 |         const mapOrGeoModel = viewBuildCtx.mapOrGeoModel; | 
 |         const data = viewBuildCtx.data; | 
 |         const projection = viewBuildCtx.geo.projection; | 
 |         const projectionStream = projection && projection.stream; | 
 |  | 
 |         function transformPoint(point: number[], project: GeoProjection['project']): number[] { | 
 |             if (project) { | 
 |                 // projection may return null point. | 
 |                 point = project(point); | 
 |             } | 
 |             return point && [ | 
 |                 point[0] * transformInfoRaw.scaleX + transformInfoRaw.x, | 
 |                 point[1] * transformInfoRaw.scaleY + transformInfoRaw.y | 
 |             ]; | 
 |         }; | 
 |  | 
 |         function transformPolygonPoints(inPoints: number[][]): number[][] { | 
 |             const outPoints = []; | 
 |             // If projectionStream is provided. Use it instead of single point project. | 
 |             const project = !projectionStream && projection && projection.project; | 
 |             for (let i = 0; i < inPoints.length; ++i) { | 
 |                 const newPt = transformPoint(inPoints[i], project); | 
 |                 newPt && outPoints.push(newPt); | 
 |             } | 
 |             return outPoints; | 
 |         } | 
 |  | 
 |         function getPolyShape(points: number[][]) { | 
 |             return { | 
 |                 shape: { | 
 |                     points: transformPolygonPoints(points) | 
 |                 } | 
 |             }; | 
 |         } | 
 |  | 
 |         regionsGroup.removeAll(); | 
 |  | 
 |         // Only when the resource is GeoJSON, there is `geo.regions`. | 
 |         zrUtil.each(viewBuildCtx.geo.regions, function (region: GeoJSONRegion) { | 
 |             const regionName = region.name; | 
 |  | 
 |             // Consider in GeoJson properties.name may be duplicated, for example, | 
 |             // there is multiple region named "United Kindom" or "France" (so many | 
 |             // colonies). And it is not appropriate to merge them in geo, which | 
 |             // will make them share the same label and bring trouble in label | 
 |             // location calculation. | 
 |             let regionGroup = regionsGroupByName.get(regionName); | 
 |             let { dataIdx, regionModel } = regionsInfoByName.get(regionName) || {}; | 
 |  | 
 |             if (!regionGroup) { | 
 |                 regionGroup = regionsGroupByName.set(regionName, new graphic.Group() as RegionsGroup); | 
 |                 regionsGroup.add(regionGroup); | 
 |  | 
 |                 dataIdx = data ? data.indexOfName(regionName) : null; | 
 |                 regionModel = viewBuildCtx.isGeo | 
 |                     ? mapOrGeoModel.getRegionModel(regionName) | 
 |                     : (data ? data.getItemModel(dataIdx) as Model<MapDataItemOption> : null); | 
 |  | 
 |                 regionsInfoByName.set(regionName, { dataIdx, regionModel }); | 
 |             } | 
 |  | 
 |             const polygonSubpaths: graphic.Polygon[] = []; | 
 |             const polylineSubpaths: graphic.Polyline[] = []; | 
 |  | 
 |             zrUtil.each(region.geometries, function (geometry) { | 
 |                 // Polygon and MultiPolygon | 
 |                 if (geometry.type === 'polygon') { | 
 |                     let polys = [geometry.exterior].concat(geometry.interiors || []); | 
 |                     if (projectionStream) { | 
 |                         polys = projectPolys(polys, projectionStream); | 
 |                     } | 
 |                     zrUtil.each(polys, (poly) => { | 
 |                         polygonSubpaths.push(new graphic.Polygon(getPolyShape(poly))); | 
 |                     }); | 
 |                 } | 
 |                 // LineString and MultiLineString | 
 |                 else { | 
 |                     let points = geometry.points; | 
 |                     if (projectionStream) { | 
 |                         points = projectPolys(points, projectionStream, true); | 
 |                     } | 
 |                     zrUtil.each(points, points => { | 
 |                         polylineSubpaths.push(new graphic.Polyline(getPolyShape(points))); | 
 |                     }); | 
 |                 } | 
 |             }); | 
 |  | 
 |             const centerPt = transformPoint(region.getCenter(), projection && projection.project); | 
 |  | 
 |             function createCompoundPath(subpaths: graphic.Path[], isLine?: boolean) { | 
 |                 if (!subpaths.length) { | 
 |                     return; | 
 |                 } | 
 |                 const compoundPath = new graphic.CompoundPath({ | 
 |                     culling: true, | 
 |                     segmentIgnoreThreshold: 1, | 
 |                     shape: { | 
 |                         paths: subpaths | 
 |                     } | 
 |                 }); | 
 |                 regionGroup.add(compoundPath); | 
 |                 applyOptionStyleForRegion( | 
 |                     viewBuildCtx, compoundPath, dataIdx, regionModel | 
 |                 ); | 
 |                 resetLabelForRegion( | 
 |                     viewBuildCtx, compoundPath, regionName, regionModel, mapOrGeoModel, dataIdx, centerPt | 
 |                 ); | 
 |  | 
 |                 if (isLine) { | 
 |                     fixLineStyle(compoundPath); | 
 |                     zrUtil.each(compoundPath.states, fixLineStyle); | 
 |                 } | 
 |             } | 
 |  | 
 |             createCompoundPath(polygonSubpaths); | 
 |             createCompoundPath(polylineSubpaths, true); | 
 |         }); | 
 |  | 
 |         // Ensure children have been added to `regionGroup` before calling them. | 
 |         regionsGroupByName.each(function (regionGroup, regionName) { | 
 |             const { dataIdx, regionModel } = regionsInfoByName.get(regionName); | 
 |  | 
 |             resetEventTriggerForRegion( | 
 |                 viewBuildCtx, regionGroup, regionName, regionModel, mapOrGeoModel, dataIdx | 
 |             ); | 
 |             resetTooltipForRegion( | 
 |                 viewBuildCtx, regionGroup, regionName, regionModel, mapOrGeoModel | 
 |             ); | 
 |             resetStateTriggerForRegion( | 
 |                 viewBuildCtx, regionGroup, regionName, regionModel, mapOrGeoModel | 
 |             ); | 
 |  | 
 |         }, this); | 
 |     } | 
 |  | 
 |     private _buildSVG(viewBuildCtx: ViewBuildContext): void { | 
 |         const mapName = viewBuildCtx.geo.map; | 
 |         const transformInfoRaw = viewBuildCtx.transformInfoRaw; | 
 |  | 
 |         this._svgGroup.x = transformInfoRaw.x; | 
 |         this._svgGroup.y = transformInfoRaw.y; | 
 |         this._svgGroup.scaleX = transformInfoRaw.scaleX; | 
 |         this._svgGroup.scaleY = transformInfoRaw.scaleY; | 
 |  | 
 |         if (this._svgResourceChanged(mapName)) { | 
 |             this._freeSVG(); | 
 |             this._useSVG(mapName); | 
 |         } | 
 |  | 
 |         const svgDispatcherMap = this._svgDispatcherMap = zrUtil.createHashMap<Element[], RegionName>(); | 
 |  | 
 |         let focusSelf = false; | 
 |         zrUtil.each(this._svgGraphicRecord.named, function (namedItem) { | 
 |             // Note that we also allow different elements have the same name. | 
 |             // For example, a glyph of a city and the label of the city have | 
 |             // the same name and their tooltip info can be defined in a single | 
 |             // region option. | 
 |  | 
 |             const regionName = namedItem.name; | 
 |             const mapOrGeoModel = viewBuildCtx.mapOrGeoModel; | 
 |             const data = viewBuildCtx.data; | 
 |             const svgNodeTagLower = namedItem.svgNodeTagLower; | 
 |             const el = namedItem.el; | 
 |  | 
 |             const dataIdx = data ? data.indexOfName(regionName) : null; | 
 |             const regionModel = mapOrGeoModel.getRegionModel(regionName); | 
 |  | 
 |             if (OPTION_STYLE_ENABLED_TAG_MAP.get(svgNodeTagLower) != null | 
 |                 && (el instanceof Displayable) | 
 |             ) { | 
 |                 applyOptionStyleForRegion(viewBuildCtx, el, dataIdx, regionModel); | 
 |             } | 
 |  | 
 |             if (el instanceof Displayable) { | 
 |                 el.culling = true; | 
 |             } | 
 |  | 
 |             // We do not know how the SVG like so we'd better not to change z2. | 
 |             // Otherwise it might bring some unexpected result. For example, | 
 |             // an area hovered that make some inner city can not be clicked. | 
 |             (el as ECElement).z2EmphasisLift = 0; | 
 |  | 
 |             // If self named: | 
 |             if (!namedItem.namedFrom) { | 
 |                 // label should batter to be displayed based on the center of <g> | 
 |                 // if it is named rather than displayed on each child. | 
 |                 if (LABEL_HOST_MAP.get(svgNodeTagLower) != null) { | 
 |                     resetLabelForRegion( | 
 |                         viewBuildCtx, el, regionName, regionModel, mapOrGeoModel, dataIdx, null | 
 |                     ); | 
 |                 } | 
 |  | 
 |                 resetEventTriggerForRegion( | 
 |                     viewBuildCtx, el, regionName, regionModel, mapOrGeoModel, dataIdx | 
 |                 ); | 
 |  | 
 |                 resetTooltipForRegion( | 
 |                     viewBuildCtx, el, regionName, regionModel, mapOrGeoModel | 
 |                 ); | 
 |  | 
 |                 if (STATE_TRIGGER_TAG_MAP.get(svgNodeTagLower) != null) { | 
 |                     const focus = resetStateTriggerForRegion( | 
 |                         viewBuildCtx, el, regionName, regionModel, mapOrGeoModel | 
 |                     ); | 
 |                     if (focus === 'self') { | 
 |                         focusSelf = true; | 
 |                     } | 
 |                     const els = svgDispatcherMap.get(regionName) || svgDispatcherMap.set(regionName, []); | 
 |                     els.push(el); | 
 |                 } | 
 |             } | 
 |  | 
 |         }, this); | 
 |  | 
 |         this._enableBlurEntireSVG(focusSelf, viewBuildCtx); | 
 |     } | 
 |  | 
 |     private _enableBlurEntireSVG( | 
 |         focusSelf: boolean, | 
 |         viewBuildCtx: ViewBuildContext | 
 |     ): void { | 
 |         // 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 `ensureState('blur').style = {}`, there will be default opacity. | 
 |  | 
 |                     // Enable `stateTransition` (animation). | 
 |                     (el as Displayable).ensureState('emphasis'); | 
 |                 } | 
 |             }); | 
 |         } | 
 |     } | 
 |  | 
 |     remove(): void { | 
 |         this._regionsGroup.removeAll(); | 
 |         this._regionsGroupByName = null; | 
 |         this._svgGroup.removeAll(); | 
 |         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._svgDispatcherMap && this._svgDispatcherMap.get(name) || []; | 
 |         } | 
 |     } | 
 |  | 
 |     private _svgResourceChanged(mapName: string): boolean { | 
 |         return this._svgMapName !== mapName; | 
 |     } | 
 |  | 
 |     private _useSVG(mapName: string): void { | 
 |         const resource = geoSourceManager.getGeoResource(mapName); | 
 |         if (resource && resource.type === 'geoSVG') { | 
 |             const svgGraphic = (resource as GeoSVGResource).useGraphic(this.uid); | 
 |             this._svgGroup.add(svgGraphic.root); | 
 |             this._svgGraphicRecord = svgGraphic; | 
 |             this._svgMapName = mapName; | 
 |         } | 
 |     } | 
 |  | 
 |     private _freeSVG(): void { | 
 |         const mapName = this._svgMapName; | 
 |         if (mapName == null) { | 
 |             return; | 
 |         } | 
 |  | 
 |         const resource = geoSourceManager.getGeoResource(mapName); | 
 |         if (resource && resource.type === 'geoSVG') { | 
 |             (resource as GeoSVGResource).freeGraphic(this.uid); | 
 |         } | 
 |         this._svgGraphicRecord = null; | 
 |         this._svgDispatcherMap = null; | 
 |         this._svgGroup.removeAll(); | 
 |         this._svgMapName = null; | 
 |     } | 
 |  | 
 |     private _updateController( | 
 |         this: MapDraw, mapOrGeoModel: GeoModel | MapSeries, ecModel: GlobalModel, api: ExtensionAPI | 
 |     ): void { | 
 |         const geo = mapOrGeoModel.coordinateSystem; | 
 |         const controller = this._controller; | 
 |         const controllerHost = this._controllerHost; | 
 |  | 
 |         // @ts-ignore FIXME:TS | 
 |         controllerHost.zoomLimit = mapOrGeoModel.get('scaleLimit'); | 
 |         controllerHost.zoom = geo.getZoom(); | 
 |  | 
 |         // roamType is will be set default true if it is null | 
 |         // @ts-ignore FIXME:TS | 
 |         controller.enable(mapOrGeoModel.get('roam') || false); | 
 |         const mainType = mapOrGeoModel.mainType; | 
 |  | 
 |         function makeActionBase(): Payload { | 
 |             const action = { | 
 |                 type: 'geoRoam', | 
 |                 componentType: mainType | 
 |             } as Payload; | 
 |             action[mainType + 'Id'] = mapOrGeoModel.id; | 
 |             return action; | 
 |         } | 
 |  | 
 |         controller.off('pan').on('pan', function (e) { | 
 |             this._mouseDownFlag = false; | 
 |  | 
 |             roamHelper.updateViewOnPan(controllerHost, e.dx, e.dy); | 
 |  | 
 |             api.dispatchAction(zrUtil.extend(makeActionBase(), { | 
 |                 dx: e.dx, | 
 |                 dy: e.dy, | 
 |                 animation: { | 
 |                     duration: 0 | 
 |                 } | 
 |             })); | 
 |         }, this); | 
 |  | 
 |         controller.off('zoom').on('zoom', function (e) { | 
 |             this._mouseDownFlag = false; | 
 |  | 
 |             roamHelper.updateViewOnZoom(controllerHost, e.scale, e.originX, e.originY); | 
 |  | 
 |             api.dispatchAction(zrUtil.extend(makeActionBase(), { | 
 |                 zoom: e.scale, | 
 |                 originX: e.originX, | 
 |                 originY: e.originY, | 
 |                 animation: { | 
 |                     duration: 0 | 
 |                 } | 
 |             })); | 
 |  | 
 |         }, this); | 
 |  | 
 |         controller.setPointerChecker(function (e, x, y) { | 
 |             return geo.containPoint([x, y]) | 
 |                 && !onIrrelevantElement(e, api, mapOrGeoModel); | 
 |         }); | 
 |     } | 
 |  | 
 |     /** | 
 |      * FIXME: this is a temporarily workaround. | 
 |      * When `geoRoam` the elements need to be reset in `MapView['render']`, because the props like | 
 |      * `ignore` might have been modified by `LabelManager`, and `LabelManager#addLabelsOfSeries` | 
 |      * will subsequently cache `defaultAttr` like `ignore`. If do not do this reset, the modified | 
 |      * props will have no chance to be restored. | 
 |      * Note: This reset should be after `clearStates` in `renderSeries` because `useStates` in | 
 |      * `renderSeries` will cache the modified `ignore` to `el._normalState`. | 
 |      * TODO: | 
 |      * Use clone/immutable in `LabelManager`? | 
 |      */ | 
 |     resetForLabelLayout() { | 
 |         this.group.traverse(el => { | 
 |             const label = el.getTextContent(); | 
 |             if (label) { | 
 |                 label.ignore = mapLabelRaw(label).ignore; | 
 |             } | 
 |         }); | 
 |     } | 
 |  | 
 |     private _updateMapSelectHandler( | 
 |         mapOrGeoModel: GeoModel | MapSeries, | 
 |         regionsGroup: RegionsGroup, | 
 |         api: ExtensionAPI, | 
 |         fromView: MapView | GeoView | 
 |     ): void { | 
 |         const mapDraw = this; | 
 |  | 
 |         regionsGroup.off('mousedown'); | 
 |         regionsGroup.off('click'); | 
 |  | 
 |         // @ts-ignore FIXME:TS resolve type conflict | 
 |         if (mapOrGeoModel.get('selectedMode')) { | 
 |  | 
 |             regionsGroup.on('mousedown', function () { | 
 |                 mapDraw._mouseDownFlag = true; | 
 |             }); | 
 |  | 
 |             regionsGroup.on('click', function (e) { | 
 |                 if (!mapDraw._mouseDownFlag) { | 
 |                     return; | 
 |                 } | 
 |                 mapDraw._mouseDownFlag = false; | 
 |             }); | 
 |         } | 
 |     } | 
 |  | 
 | }; | 
 |  | 
 | function applyOptionStyleForRegion( | 
 |     viewBuildCtx: ViewBuildContext, | 
 |     el: Displayable, | 
 |     dataIndex: number, | 
 |     regionModel: Model< | 
 |         GeoStyleableOption & { | 
 |             emphasis?: GeoStyleableOption; | 
 |             select?: GeoStyleableOption; | 
 |             blur?: GeoStyleableOption; | 
 |         } | 
 |     > | 
 | ): void { | 
 |     // All of the path are using `itemStyle`, because | 
 |     // (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. | 
 |     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. | 
 |     el.setStyle(normalStyle); | 
 |     el.style.strokeNoScale = true; | 
 |     el.ensureState('emphasis').style = emphasisStyle; | 
 |     el.ensureState('select').style = selectStyle; | 
 |     el.ensureState('blur').style = blurStyle; | 
 |  | 
 |     // Enable blur | 
 |     setDefaultStateProxy(el); | 
 | } | 
 |  | 
 | function resetLabelForRegion( | 
 |     viewBuildCtx: ViewBuildContext, | 
 |     el: Element, | 
 |     regionName: string, | 
 |     regionModel: RegionModel, | 
 |     mapOrGeoModel: MapOrGeoModel, | 
 |     // Exist only if `viewBuildCtx.data` exists. | 
 |     dataIdx: number, | 
 |     // If labelXY not provided, use `textConfig.position: 'inside'` | 
 |     labelXY: number[] | 
 | ): void { | 
 |     const data = viewBuildCtx.data; | 
 |     const isGeo = viewBuildCtx.isGeo; | 
 |  | 
 |     const isDataNaN = data && isNaN(data.get(data.mapDimension('value'), dataIdx) as number); | 
 |     const itemLayout = data && data.getItemLayout(dataIdx); | 
 |  | 
 |     // In the following cases label will be drawn | 
 |     // 1. In map series and data value is NaN | 
 |     // 2. In geo component | 
 |     // 3. Region has no series legendIcon, which will be add a showLabel flag in mapSymbolLayout | 
 |     if ( | 
 |         ((isGeo || isDataNaN)) | 
 |         || (itemLayout && itemLayout.showLabel) | 
 |     ) { | 
 |  | 
 |         const query = !isGeo ? dataIdx : regionName; | 
 |         let labelFetcher; | 
 |  | 
 |         // Consider dataIdx not found. | 
 |         if (!data || dataIdx >= 0) { | 
 |             labelFetcher = mapOrGeoModel; | 
 |         } | 
 |  | 
 |         const specifiedTextOpt: Partial<Record<DisplayState, TextStyleProps>> = labelXY ? { | 
 |             normal: { | 
 |                 align: 'center', | 
 |                 verticalAlign: 'middle' | 
 |             } | 
 |         } : null; | 
 |  | 
 |         // Caveat: must be called after `setDefaultStateProxy(el);` called. | 
 |         // because textContent will be assign with `el.stateProxy` inside. | 
 |         setLabelStyle<typeof query>( | 
 |             el, | 
 |             getLabelStatesModels(regionModel), | 
 |             { | 
 |                 labelFetcher, | 
 |                 labelDataIndex: query, | 
 |                 defaultText: regionName | 
 |             }, | 
 |             specifiedTextOpt | 
 |         ); | 
 |  | 
 |         const textEl = el.getTextContent(); | 
 |         if (textEl) { | 
 |             mapLabelRaw(textEl).ignore = textEl.ignore; | 
 |  | 
 |             if (el.textConfig && labelXY) { | 
 |                 // Compute a relative offset based on the el bounding rect. | 
 |                 const rect = el.getBoundingRect().clone(); | 
 |                 // Need to make sure the percent position base on the same rect in normal and | 
 |                 // emphasis state. Otherwise if using boundingRect of el, but the emphasis state | 
 |                 // has borderWidth (even 0.5px), the text position will be changed obviously | 
 |                 // if the position is very big like ['1234%', '1345%']. | 
 |                 el.textConfig.layoutRect = rect; | 
 |                 el.textConfig.position = [ | 
 |                     ((labelXY[0] - rect.x) / rect.width * 100) + '%', | 
 |                     ((labelXY[1] - rect.y) / rect.height * 100) + '%' | 
 |                 ]; | 
 |             } | 
 |         } | 
 |  | 
 |         // PENDING: | 
 |         // If labelLayout is enabled (test/label-layout.html), el.dataIndex should be specified. | 
 |         // But el.dataIndex is also used to determine whether user event should be triggered, | 
 |         // where el.seriesIndex or el.dataModel must be specified. At present for a single el | 
 |         // there is not case that "only label layout enabled but user event disabled", so here | 
 |         // we depends `resetEventTriggerForRegion` to do the job of setting `el.dataIndex`. | 
 |  | 
 |         (el as ECElement).disableLabelAnimation = true; | 
 |     } | 
 |     else { | 
 |         el.removeTextContent(); | 
 |         el.removeTextConfig(); | 
 |         (el as ECElement).disableLabelAnimation = null; | 
 |     } | 
 | } | 
 |  | 
 | function resetEventTriggerForRegion( | 
 |     viewBuildCtx: ViewBuildContext, | 
 |     eventTrigger: Element, | 
 |     regionName: string, | 
 |     regionModel: RegionModel, | 
 |     mapOrGeoModel: MapOrGeoModel, | 
 |     // Exist only if `viewBuildCtx.data` exists. | 
 |     dataIdx: number | 
 | ): void { | 
 |     // setItemGraphicEl, setHoverStyle after all polygons and labels | 
 |     // are added to the regionGroup | 
 |     if (viewBuildCtx.data) { | 
 |         // FIXME: when series-map use a SVG map, and there are duplicated name specified | 
 |         // on different SVG elements, after `data.setItemGraphicEl(...)`: | 
 |         // (1) all of them will be mounted with `dataIndex`, `seriesIndex`, so that tooltip | 
 |         // can be triggered only mouse hover. That's correct. | 
 |         // (2) only the last element will be kept in `data`, so that if trigger tooltip | 
 |         // by `dispatchAction`, only the last one can be found and triggered. That might be | 
 |         // not correct. We will fix it in future if anyone demanding that. | 
 |         viewBuildCtx.data.setItemGraphicEl(dataIdx, eventTrigger); | 
 |     } | 
 |     // series-map will not trigger "geoselectchange" no matter it is | 
 |     // based on a declared geo component. Because series-map will | 
 |     // trigger "selectchange". If it trigger both the two events, | 
 |     // If users call `chart.dispatchAction({type: 'toggleSelect'})`, | 
 |     // it not easy to also fire event "geoselectchanged". | 
 |     else { | 
 |         // Package custom mouse event for geo component | 
 |         getECData(eventTrigger).eventData = { | 
 |             componentType: 'geo', | 
 |             componentIndex: mapOrGeoModel.componentIndex, | 
 |             geoIndex: mapOrGeoModel.componentIndex, | 
 |             name: regionName, | 
 |             region: (regionModel && regionModel.option) || {} | 
 |         }; | 
 |     } | 
 | } | 
 |  | 
 | function resetTooltipForRegion( | 
 |     viewBuildCtx: ViewBuildContext, | 
 |     el: Element, | 
 |     regionName: string, | 
 |     regionModel: RegionModel, | 
 |     mapOrGeoModel: MapOrGeoModel | 
 | ): void { | 
 |     if (!viewBuildCtx.data) { | 
 |         graphic.setTooltipConfig({ | 
 |             el: el, | 
 |             componentModel: mapOrGeoModel, | 
 |             itemName: regionName, | 
 |             // @ts-ignore FIXME:TS fix the "compatible with each other"? | 
 |             itemTooltipOption: regionModel.get('tooltip') | 
 |         }); | 
 |     } | 
 | } | 
 |  | 
 | function resetStateTriggerForRegion( | 
 |     viewBuildCtx: ViewBuildContext, | 
 |     el: Element, | 
 |     regionName: string, | 
 |     regionModel: RegionModel, | 
 |     mapOrGeoModel: MapOrGeoModel | 
 | ): InnerFocus { | 
 |     // @ts-ignore FIXME:TS fix the "compatible with each other"? | 
 |     el.highDownSilentOnTouch = !!mapOrGeoModel.get('selectedMode'); | 
 |     // @ts-ignore FIXME:TS fix the "compatible with each other"? | 
 |     const emphasisModel = regionModel.getModel('emphasis'); | 
 |     const focus = emphasisModel.get('focus'); | 
 |     toggleHoverEmphasis(el, focus, emphasisModel.get('blurScope'), emphasisModel.get('disabled')); | 
 |     if (viewBuildCtx.isGeo) { | 
 |         enableComponentHighDownFeatures(el, mapOrGeoModel as GeoModel, regionName); | 
 |     } | 
 |  | 
 |     return focus; | 
 | } | 
 |  | 
 | function projectPolys( | 
 |     rings: number[][][], // Polygons include exterior and interiors. Or polylines. | 
 |     createStream: (outStream: ProjectionStream) => ProjectionStream, | 
 |     isLine?: boolean | 
 | ) { | 
 |     const polygons: number[][][] = []; | 
 |     let curPoly: number[][]; | 
 |  | 
 |     function startPolygon() { | 
 |         curPoly = []; | 
 |     } | 
 |     function endPolygon() { | 
 |         if (curPoly.length) { | 
 |             polygons.push(curPoly); | 
 |             curPoly = []; | 
 |         } | 
 |     } | 
 |     const stream = createStream({ | 
 |         polygonStart: startPolygon, | 
 |         polygonEnd: endPolygon, | 
 |         lineStart: startPolygon, | 
 |         lineEnd: endPolygon, | 
 |         point(x, y) { | 
 |             // May have NaN values from stream. | 
 |             if (isFinite(x) && isFinite(y)) { | 
 |                 curPoly.push([x, y]); | 
 |             } | 
 |         }, | 
 |         sphere() {} | 
 |     }); | 
 |     !isLine && stream.polygonStart(); | 
 |     zrUtil.each(rings, ring => { | 
 |         stream.lineStart(); | 
 |         for (let i = 0; i < ring.length; i++) { | 
 |             stream.point(ring[i][0], ring[i][1]); | 
 |         } | 
 |         stream.lineEnd(); | 
 |     }); | 
 |     !isLine && stream.polygonEnd(); | 
 |     return polygons; | 
 | } | 
 |  | 
 | export default MapDraw; | 
 |  | 
 |  | 
 | // @ts-ignore FIXME:TS fix the "compatible with each other"? |