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 <[email protected]> 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: [email protected] For additional commands, e-mail: [email protected]
