This is an automated email from the ASF dual-hosted git repository. sushuang pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git
commit 68893b09910ed7b58d518e2b4513c2972aa869be Author: sushuang <[email protected]> AuthorDate: Mon May 28 21:10:56 2018 +0800 support svg geo (part I) --- src/component/helper/MapDraw.js | 85 ++++++++++++++++------ src/coord/geo/Geo.js | 126 +++++++++++---------------------- src/coord/geo/GeoModel.js | 5 +- src/coord/geo/Region.js | 10 ++- src/coord/geo/fix/diaoyuIsland.js | 16 ++--- src/coord/geo/fix/geoCoord.js | 8 +-- src/coord/geo/fix/nanhai.js | 6 +- src/coord/geo/fix/textCoord.js | 8 +-- src/coord/geo/geoCreator.js | 80 +++++++-------------- src/coord/geo/geoJSONLoader.js | 94 +++++++++++++++++++++++++ src/coord/geo/geoSVGLoader.js | 143 ++++++++++++++++++++++++++++++++++++++ src/coord/geo/geoSourceManager.js | 126 +++++++++++++++++++++++++++++++++ src/coord/geo/mapDataStorage.js | 104 +++++++++++++++++++++++++++ src/echarts.js | 38 +++++----- 14 files changed, 647 insertions(+), 202 deletions(-) diff --git a/src/component/helper/MapDraw.js b/src/component/helper/MapDraw.js index 0d00e2d..9ea90d9 100644 --- a/src/component/helper/MapDraw.js +++ b/src/component/helper/MapDraw.js @@ -22,6 +22,8 @@ import RoamController from './RoamController'; import * as roamHelper from '../../component/helper/roamHelper'; import {onIrrelevantElement} from '../../component/helper/cursorHelper'; import * as graphic from '../../util/graphic'; +import geoSourceManager from '../../coord/geo/geoSourceManager'; +import {getUID} from '../../util/component'; function getFixedItemStyle(model, scale) { var itemStyle = model.getItemStyle(); @@ -36,17 +38,17 @@ function getFixedItemStyle(model, scale) { return itemStyle; } -function updateMapSelectHandler(mapDraw, mapOrGeoModel, group, api, fromView) { - group.off('click'); - group.off('mousedown'); +function updateMapSelectHandler(mapDraw, mapOrGeoModel, regionsGroup, api, fromView) { + regionsGroup.off('click'); + regionsGroup.off('mousedown'); if (mapOrGeoModel.get('selectedMode')) { - group.on('mousedown', function () { + regionsGroup.on('mousedown', function () { mapDraw._mouseDownFlag = true; }); - group.on('click', function (e) { + regionsGroup.on('click', function (e) { if (!mapDraw._mouseDownFlag) { return; } @@ -73,14 +75,14 @@ function updateMapSelectHandler(mapDraw, mapOrGeoModel, group, api, fromView) { api.dispatchAction(action); - updateMapSelected(mapOrGeoModel, group); + updateMapSelected(mapOrGeoModel, regionsGroup); }); } } -function updateMapSelected(mapOrGeoModel, group) { +function updateMapSelected(mapOrGeoModel, regionsGroup) { // FIXME - group.eachChild(function (otherRegionEl) { + regionsGroup.eachChild(function (otherRegionEl) { zrUtil.each(otherRegionEl.__regions, function (region) { otherRegionEl.trigger(mapOrGeoModel.isSelected(region.name) ? 'emphasis' : 'normal'); }); @@ -97,6 +99,12 @@ function MapDraw(api, updateGroup) { var group = new graphic.Group(); /** + * @type {string} + * @private + */ + this.uid = getUID('ec_map_draw'); + + /** * @type {module:echarts/component/helper/RoamController} * @private */ @@ -127,6 +135,26 @@ function MapDraw(api, updateGroup) { * @type {booelan} */ this._mouseDownFlag; + + /** + * @type {string} + */ + this._mapName; + + /** + * @type {boolean} + */ + this._initialized; + + /** + * @type {module:zrender/container/Group} + */ + group.add(this._regionsGroup = new graphic.Group()); + + /** + * @type {module:zrender/container/Group} + */ + group.add(this._backgroundGroup = new graphic.Group()); } MapDraw.prototype = { @@ -148,23 +176,26 @@ MapDraw.prototype = { var geo = mapOrGeoModel.coordinateSystem; + this._updateBackground(geo); + + var regionsGroup = this._regionsGroup; var group = this.group; var scale = geo.scale; - var groupNewProp = { + var transform = { position: geo.position, scale: scale }; // No animation when first draw or in action - if (!group.childAt(0) || payload) { - group.attr(groupNewProp); + if (!regionsGroup.childAt(0) || payload) { + group.attr(transform); } else { - graphic.updateProps(group, groupNewProp, mapOrGeoModel); + graphic.updateProps(group, transform, mapOrGeoModel); } - group.removeAll(); + regionsGroup.removeAll(); var itemStyleAccessPath = ['itemStyle']; var hoverItemStyleAccessPath = ['emphasis', 'itemStyle']; @@ -306,22 +337,37 @@ MapDraw.prototype = { {hoverSilentOnTouch: !!mapOrGeoModel.get('selectedMode')} ); - group.add(regionGroup); + regionsGroup.add(regionGroup); }); this._updateController(mapOrGeoModel, ecModel, api); - updateMapSelectHandler(this, mapOrGeoModel, group, api, fromView); + updateMapSelectHandler(this, mapOrGeoModel, regionsGroup, api, fromView); - updateMapSelected(mapOrGeoModel, group); + updateMapSelected(mapOrGeoModel, regionsGroup); }, remove: function () { - this.group.removeAll(); + this._regionsGroup.removeAll(); + this._backgroundGroup.removeAll(); this._controller.dispose(); + this._mapName && geoSourceManager.removeGraphic(this._mapName, this.uid); + this._mapName = null; this._controllerHost = {}; }, + _updateBackground: function (geo) { + var mapName = geo.map; + + if (this._mapName !== mapName) { + zrUtil.each(geoSourceManager.makeGraphic(mapName, this.uid), function (root) { + this._backgroundGroup.add(root); + }, this); + } + + this._mapName = mapName; + }, + _updateController: function (mapOrGeoModel, ecModel, api) { var geo = mapOrGeoModel.coordinateSystem; var controller = this._controller; @@ -366,9 +412,8 @@ MapDraw.prototype = { })); if (this._updateGroup) { - var group = this.group; - var scale = group.scale; - group.traverse(function (el) { + var scale = this.group.scale; + this._regionsGroup.traverse(function (el) { if (el.type === 'text') { el.attr('scale', [1 / scale[0], 1 / scale[1]]); } diff --git a/src/coord/geo/Geo.js b/src/coord/geo/Geo.js index 83428af..8c6491c 100644 --- a/src/coord/geo/Geo.js +++ b/src/coord/geo/Geo.js @@ -19,33 +19,22 @@ import * as zrUtil from 'zrender/src/core/util'; import BoundingRect from 'zrender/src/core/BoundingRect'; -import parseGeoJson from './parseGeoJson'; import View from '../View'; +import geoSourceManager from './geoSourceManager'; -import fixNanhai from './fix/nanhai'; -import fixTextCoord from './fix/textCoord'; -import fixGeoCoord from './fix/geoCoord'; -import fixDiaoyuIsland from './fix/diaoyuIsland'; - -// Geo fix functions -var geoFixFuncs = [ - fixNanhai, - fixTextCoord, - fixGeoCoord, - fixDiaoyuIsland -]; /** * [Geo description] - * @param {string} name Geo name + * For backward compatibility, the orginal interface: + * `name, map, geoJson, specialAreas, nameMap` is kept. + * + * @param {string|Object} name * @param {string} map Map type - * @param {Object} geoJson - * @param {Object} [specialAreas] * Specify the positioned areas by left, top, width, height * @param {Object.<string, string>} [nameMap] * Specify name alias */ -function Geo(name, map, geoJson, specialAreas, nameMap) { +function Geo(name, map, nameMap) { View.call(this, name); @@ -55,9 +44,20 @@ function Geo(name, map, geoJson, specialAreas, nameMap) { */ this.map = map; - this._nameCoordMap = zrUtil.createHashMap(); + var source = geoSourceManager.load(map, nameMap); + + this._nameCoordMap = source.nameCoordMap; + this._regionsMap = source.nameCoordMap; + + /** + * @readOnly + */ + this.regions = source.regions; - this.loadGeoJson(geoJson, specialAreas, nameMap); + /** + * @type {module:zrender/src/core/BoundingRect} + */ + this._rect = source.boundingRect; } Geo.prototype = { @@ -86,61 +86,23 @@ Geo.prototype = { } return false; }, + /** - * @param {Object} geoJson - * @param {Object} [specialAreas] - * Specify the positioned areas by left, top, width, height - * @param {Object.<string, string>} [nameMap] - * Specify name alias + * @override */ - loadGeoJson: function (geoJson, specialAreas, nameMap) { - // https://jsperf.com/try-catch-performance-overhead - try { - this.regions = geoJson ? parseGeoJson(geoJson) : []; - } - catch (e) { - throw 'Invalid geoJson format\n' + e.message; - } - specialAreas = specialAreas || {}; - nameMap = nameMap || {}; - var regions = this.regions; - var regionsMap = zrUtil.createHashMap(); - for (var i = 0; i < regions.length; i++) { - var regionName = regions[i].name; - // Try use the alias in nameMap - regionName = nameMap.hasOwnProperty(regionName) ? nameMap[regionName] : regionName; - regions[i].name = regionName; - - regionsMap.set(regionName, regions[i]); - // Add geoJson - this.addGeoCoord(regionName, regions[i].center); - - // Some area like Alaska in USA map needs to be tansformed - // to look better - var specialArea = specialAreas[regionName]; - if (specialArea) { - regions[i].transformTo( - specialArea.left, specialArea.top, specialArea.width, specialArea.height - ); - } - } - - this._regionsMap = regionsMap; - - this._rect = null; - - zrUtil.each(geoFixFuncs, function (fixFunc) { - fixFunc(this); - }, this); - }, - - // Overwrite transformTo: function (x, y, width, height) { var rect = this.getBoundingRect(); + // FIXME + // Should not name it as invertLng. + var invertLng = this.invertLng; + rect = rect.clone(); - // Longitute is inverted - rect.y = -rect.y - rect.height; + + if (invertLng) { + // Longitute is inverted + rect.y = -rect.y - rect.height; + } var rawTransformable = this._rawTransformable; @@ -150,8 +112,10 @@ Geo.prototype = { rawTransformable.decomposeTransform(); - var scale = rawTransformable.scale; - scale[1] = -scale[1]; + if (invertLng) { + var scale = rawTransformable.scale; + scale[1] = -scale[1]; + } rawTransformable.updateTransform(); @@ -193,21 +157,11 @@ Geo.prototype = { return this._nameCoordMap.get(name); }, - // Overwrite + /** + * @override + */ getBoundingRect: function () { - if (this._rect) { - return this._rect; - } - var rect; - - var regions = this.regions; - for (var i = 0; i < regions.length; i++) { - var regionRect = regions[i].getBoundingRect(); - rect = rect || regionRect.clone(); - rect.union(regionRect); - } - // FIXME Always return new ? - return (this._rect = rect || new BoundingRect(0, 0, 0, 0)); + return this._rect; }, /** @@ -227,12 +181,12 @@ Geo.prototype = { }, /** - * @inheritDoc + * @override */ convertToPixel: zrUtil.curry(doConvert, 'dataToPoint'), /** - * @inheritDoc + * @override */ convertFromPixel: zrUtil.curry(doConvert, 'pointToData') diff --git a/src/coord/geo/GeoModel.js b/src/coord/geo/GeoModel.js index 63f9257..fad9977 100644 --- a/src/coord/geo/GeoModel.js +++ b/src/coord/geo/GeoModel.js @@ -78,7 +78,9 @@ var GeoModel = ComponentModel.extend({ // Aspect is width / height. Inited to be geoJson bbox aspect // This parameter is used for scale this aspect - aspectScale: 0.75, + // If svg used, aspectScale is 1 by default. + // aspectScale: 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 @@ -86,7 +88,6 @@ var GeoModel = ComponentModel.extend({ // layoutCenter: [50%, 50%] // layoutSize: 100 - silent: false, // Map type diff --git a/src/coord/geo/Region.js b/src/coord/geo/Region.js index 43d9cd6..4de33b1 100644 --- a/src/coord/geo/Region.js +++ b/src/coord/geo/Region.js @@ -27,7 +27,7 @@ import * as vec2 from 'zrender/src/core/vector'; import * as polygonContain from 'zrender/src/contain/polygon'; /** - * @param {string} name + * @param {string|Region} name * @param {Array} geometries * @param {Array.<number>} cp */ @@ -168,6 +168,14 @@ Region.prototype = { rect.x + rect.width / 2, rect.y + rect.height / 2 ]; + }, + + cloneShallow: function (name) { + name == null && (name = this.name); + var newRegion = new Region(name, this.geometries, this.center); + newRegion._rect = this._rect; + newRegion.transformTo = null; // Simply avoid to be called. + return newRegion; } }; diff --git a/src/coord/geo/fix/diaoyuIsland.js b/src/coord/geo/fix/diaoyuIsland.js index 22fcd77..87b3f57 100644 --- a/src/coord/geo/fix/diaoyuIsland.js +++ b/src/coord/geo/fix/diaoyuIsland.js @@ -34,15 +34,11 @@ var points = [ ] ]; -export default function (geo) { - if (geo.map === 'china') { - for (var i = 0, len = geo.regions.length; i < len; ++i) { - if (geo.regions[i].name === '台湾') { - geo.regions[i].geometries.push({ - type: 'polygon', - exterior: points[0] - }); - } - } +export default function (mapType, region) { + if (mapType === 'china' && region.name === '台湾') { + region.geometries.push({ + type: 'polygon', + exterior: points[0] + }); } } \ No newline at end of file diff --git a/src/coord/geo/fix/geoCoord.js b/src/coord/geo/fix/geoCoord.js index 69cc11a..b6e7080 100644 --- a/src/coord/geo/fix/geoCoord.js +++ b/src/coord/geo/fix/geoCoord.js @@ -17,21 +17,19 @@ * under the License. */ -import * as zrUtil from 'zrender/src/core/util'; - var geoCoordMap = { 'Russia': [100, 60], 'United States': [-99, 38], 'United States of America': [-99, 38] }; -export default function (geo) { - zrUtil.each(geo.regions, function (region) { +export default function (mapType, region) { + if (mapType === 'world') { var geoCoord = geoCoordMap[region.name]; if (geoCoord) { var cp = region.center; cp[0] = geoCoord[0]; cp[1] = geoCoord[1]; } - }); + } } \ No newline at end of file diff --git a/src/coord/geo/fix/nanhai.js b/src/coord/geo/fix/nanhai.js index f59c529..4eabeaf 100644 --- a/src/coord/geo/fix/nanhai.js +++ b/src/coord/geo/fix/nanhai.js @@ -51,9 +51,9 @@ for (var i = 0; i < points.length; i++) { } } -export default function (geo) { - if (geo.map === 'china') { - geo.regions.push(new Region( +export default function (mapType, regions) { + if (mapType === 'china') { + regions.push(new Region( '南海诸岛', zrUtil.map(points, function (exterior) { return { diff --git a/src/coord/geo/fix/textCoord.js b/src/coord/geo/fix/textCoord.js index c2b858a..3ec837c 100644 --- a/src/coord/geo/fix/textCoord.js +++ b/src/coord/geo/fix/textCoord.js @@ -17,8 +17,6 @@ * under the License. */ -import * as zrUtil from 'zrender/src/core/util'; - var coordsOffsetMap = { '南海诸岛' : [32, 80], // 全国 @@ -29,13 +27,13 @@ var coordsOffsetMap = { '天津': [5, 5] }; -export default function (geo) { - zrUtil.each(geo.regions, function (region) { +export default function (mapType, region) { + if (mapType === 'china') { var coordFix = coordsOffsetMap[region.name]; if (coordFix) { var cp = region.center; cp[0] += coordFix[0] / 10.5; cp[1] += -coordFix[1] / (10.5 / 0.75); } - }); + } } \ No newline at end of file diff --git a/src/coord/geo/geoCreator.js b/src/coord/geo/geoCreator.js index da94330..e740b65 100644 --- a/src/coord/geo/geoCreator.js +++ b/src/coord/geo/geoCreator.js @@ -23,6 +23,8 @@ import * as zrUtil from 'zrender/src/core/util'; import Geo from './Geo'; import * as layout from '../../util/layout'; import * as numberUtil from '../../util/number'; +import geoSourceManager from './geoSourceManager'; +import mapDataStorage from './mapDataStorage'; /** * Resize method bound to the geo @@ -55,8 +57,7 @@ function resizeGeo(geoModel, api) { var viewWidth = api.getWidth(); var viewHeight = api.getHeight(); - var aspectScale = geoModel.get('aspectScale') || 0.75; - var aspect = rect.width / rect.height * aspectScale; + var aspect = rect.width / rect.height * this.aspectScale; var useCenterAndSize = false; @@ -122,12 +123,6 @@ function setGeoCoords(geo, model) { }); } -if (__DEV__) { - var mapNotExistsError = function (name) { - console.error('Map ' + name + ' not exists. You can download map file on http://echarts.baidu.com/download-map.html'); - }; -} - var geoCreator = { // For deciding which dimensions to use when creating list data @@ -139,17 +134,8 @@ var geoCreator = { // FIXME Create each time may be slow ecModel.eachComponent('geo', function (geoModel, idx) { var name = geoModel.get('map'); - var mapData = echarts.getMap(name); - if (__DEV__) { - if (!mapData) { - mapNotExistsError(name); - } - } - var geo = new Geo( - name + idx, name, - mapData && mapData.geoJson, mapData && mapData.specialAreas, - geoModel.get('nameMap') - ); + var geo = new Geo(name + idx, name, geoModel.get('nameMap')); + geo.zoomLimit = geoModel.get('scaleLimit'); geoList.push(geo); @@ -158,6 +144,20 @@ var geoCreator = { geoModel.coordinateSystem = geo; geo.model = geoModel; + // FIXME ??? + var aspectScale = geoModel.get('aspectScale'); + var invertLng = true; + var mapRecords = mapDataStorage.retrieveMap(name); + if (mapRecords && mapRecords[0] && mapRecords[0].type === 'svg') { + aspectScale == null && (aspectScale = 1); + invertLng = false; + } + else { + aspectScale == null && (aspectScale = 0.75); + } + geo.aspectScale = aspectScale; + geo.invertLng = invertLng; + // Inject resize method geo.resize = resizeGeo; @@ -184,21 +184,11 @@ var geoCreator = { }); zrUtil.each(mapModelGroupBySeries, function (mapSeries, mapType) { - var mapData = echarts.getMap(mapType); - if (__DEV__) { - if (!mapData) { - mapNotExistsError(mapSeries[0].get('map')); - } - } - var nameMapList = zrUtil.map(mapSeries, function (singleMapSeries) { return singleMapSeries.get('nameMap'); }); - var geo = new Geo( - mapType, mapType, - mapData && mapData.geoJson, mapData && mapData.specialAreas, - zrUtil.mergeAll(nameMapList) - ); + var geo = new Geo(mapType, mapType, zrUtil.mergeAll(nameMapList)); + geo.zoomLimit = zrUtil.retrieve.apply(null, zrUtil.map(mapSeries, function (singleMapSeries) { return singleMapSeries.get('scaleLimit'); })); @@ -229,34 +219,18 @@ var geoCreator = { getFilledRegions: function (originRegionArr, mapName, nameMap) { // Not use the original var regionsArr = (originRegionArr || []).slice(); - nameMap = nameMap || {}; - - var map = echarts.getMap(mapName); - var geoJson = map && map.geoJson; - if (!geoJson) { - if (__DEV__) { - mapNotExistsError(mapName); - } - return originRegionArr; - } var dataNameMap = zrUtil.createHashMap(); - var features = geoJson.features; for (var i = 0; i < regionsArr.length; i++) { dataNameMap.set(regionsArr[i].name, regionsArr[i]); } - for (var i = 0; i < features.length; i++) { - var name = features[i].properties.name; - if (!dataNameMap.get(name)) { - if (nameMap.hasOwnProperty(name)) { - name = nameMap[name]; - } - regionsArr.push({ - name: name - }); - } - } + var source = geoSourceManager.load(mapName, nameMap); + zrUtil.each(source.regions, function (region) { + var name = region.name; + !dataNameMap.get(name) && regionsArr.push({name: name}); + }); + return regionsArr; } }; diff --git a/src/coord/geo/geoJSONLoader.js b/src/coord/geo/geoJSONLoader.js new file mode 100644 index 0000000..5f0c05a --- /dev/null +++ b/src/coord/geo/geoJSONLoader.js @@ -0,0 +1,94 @@ +/* +* 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 {each} from 'zrender/src/core/util'; +import parseGeoJson from './parseGeoJson'; +import {makeInner} from '../../util/model'; + +// Built-in GEO fixer. +import fixNanhai from './fix/nanhai'; +import fixTextCoord from './fix/textCoord'; +import fixGeoCoord from './fix/geoCoord'; +import fixDiaoyuIsland from './fix/diaoyuIsland'; + +var inner = makeInner(); + +export default { + + /** + * @param {string} mapName + * @param {Object} mapRecord {specialAreas, geoJSON} + * @return {Object} {regions, boundingRect} + */ + load: function (mapName, mapRecord) { + + var parsed = inner(mapRecord).parsed; + + if (parsed) { + return parsed; + } + + var specialAreas = mapRecord.specialAreas || {}; + var geoJSON = mapRecord.geoJSON; + var regions; + + // https://jsperf.com/try-catch-performance-overhead + try { + regions = geoJSON ? parseGeoJson(geoJSON) : []; + } + catch (e) { + throw new Error('Invalid geoJson format\n' + e.message); + } + + each(regions, function (region) { + var regionName = region.name; + + fixTextCoord(mapName, region); + fixGeoCoord(mapName, region); + fixDiaoyuIsland(mapName, region); + + // Some area like Alaska in USA map needs to be tansformed + // to look better + var specialArea = specialAreas[regionName]; + if (specialArea) { + region.transformTo( + specialArea.left, specialArea.top, specialArea.width, specialArea.height + ); + } + }); + + fixNanhai(mapName, regions); + + return (inner(mapRecord).parsed = { + regions: regions, + boundingRect: getBoundingRect(regions) + }); + } +}; + +function getBoundingRect(regions) { + var rect; + for (var i = 0; i < regions.length; i++) { + var regionRect = regions[i].getBoundingRect(); + rect = rect || regionRect.clone(); + rect.union(regionRect); + } + return rect; +} + diff --git a/src/coord/geo/geoSVGLoader.js b/src/coord/geo/geoSVGLoader.js new file mode 100644 index 0000000..98eab03 --- /dev/null +++ b/src/coord/geo/geoSVGLoader.js @@ -0,0 +1,143 @@ +/* +* 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 {parseSVG, makeViewBoxTransform} from 'zrender/src/tool/parseSVG'; +import Group from 'zrender/src/container/Group'; +import Rect from 'zrender/src/graphic/shape/Rect'; +import {assert, createHashMap} from 'zrender/src/core/util'; +import BoundingRect from 'zrender/src/core/BoundingRect'; +import {makeInner} from '../../util/model'; + +var inner = makeInner(); + +export default { + + /** + * @param {string} mapName + * @param {Object} mapRecord {specialAreas, geoJSON} + * @return {Object} {root, boundingRect} + */ + load: function (mapName, mapRecord) { + var originRoot = inner(mapRecord).originRoot; + if (originRoot) { + return { + root: originRoot, + boundingRect: inner(mapRecord).boundingRect + }; + } + + var graphic = buildGraphic(mapRecord); + + inner(mapRecord).originRoot = graphic.root; + inner(mapRecord).boundingRect = graphic.boundingRect; + + return graphic; + }, + + makeGraphic: function (mapName, mapRecord, hostKey) { + // For performance consideration (in large SVG), graphic only maked + // when necessary and reuse them according to hostKey. + var field = inner(mapRecord); + var rootMap = field.rootMap || (field.rootMap = createHashMap()); + + var root = rootMap.get(hostKey); + if (root) { + return root; + } + + var originRoot = field.originRoot; + var boundingRect = field.boundingRect; + + // For performance, if originRoot is not used by a view, + // assign it to a view, but not reproduce graphic elements. + if (!field.originRootHostKey) { + field.originRootHostKey = hostKey; + root = originRoot; + } + else { + root = buildGraphic(mapRecord, boundingRect).root; + } + + return rootMap.set(hostKey, root); + }, + + removeGraphic: function (mapName, mapRecord, hostKey) { + var field = inner(mapRecord); + var rootMap = field.rootMap; + rootMap && rootMap.removeKey(hostKey); + if (hostKey === field.originRootHostKey) { + field.originRootHostKey = null; + } + } +}; + +function buildGraphic(mapRecord, boundingRect) { + var svgXML = mapRecord.svgXML; + var result; + var root; + + try { + result = svgXML && parseSVG(svgXML, { + ignoreViewBox: true, + ignoreRootClip: true + }) || {}; + root = result.root; + assert(root != null); + } + catch (e) { + throw new Error('Invalid svg format\n' + e.message); + } + + var svgWidth = result.width; + var svgHeight = result.height; + var viewBoxRect = result.viewBoxRect; + + if (!boundingRect) { + boundingRect = (svgWidth == null || svgHeight == null) + // If svg width / height not specified, calculate + // bounding rect as the width / height + ? root.getBoundingRect() + : new BoundingRect(0, 0, 0, 0); + + if (svgWidth != null) { + boundingRect.width = svgWidth; + } + if (svgHeight != null) { + boundingRect.height = svgHeight; + } + } + + if (viewBoxRect) { + var viewBoxTransform = makeViewBoxTransform(viewBoxRect, boundingRect.width, boundingRect.height); + var elRoot = root; + root = new Group(); + root.add(elRoot); + elRoot.scale = viewBoxTransform.scale; + elRoot.position = viewBoxTransform.position; + } + + root.setClipPath(new Rect({ + shape: boundingRect.plain() + })); + + return { + root: root, + boundingRect: boundingRect + }; +} diff --git a/src/coord/geo/geoSourceManager.js b/src/coord/geo/geoSourceManager.js new file mode 100644 index 0000000..53ce1f8 --- /dev/null +++ b/src/coord/geo/geoSourceManager.js @@ -0,0 +1,126 @@ +/* +* 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 {__DEV__} from '../../config'; +import {each, createHashMap} from 'zrender/src/core/util'; +import mapDataStorage from './mapDataStorage'; +import geoJSONLoader from './geoJSONLoader'; +import geoSVGLoader from './geoSVGLoader'; +import BoundingRect from 'zrender/src/core/BoundingRect'; + +var loaders = { + geoJSON: geoJSONLoader, + svg: geoSVGLoader +}; + +export default { + + /** + * @param {string} mapName + * @param {Object} nameMap + * @return {Object} source {regions, regionsMap, nameCoordMap, boundingRect} + */ + load: function (mapName, nameMap) { + var regions = []; + var regionsMap = createHashMap(); + var nameCoordMap = createHashMap(); + var boundingRect; + var mapRecords = retrieveMap(mapName); + + each(mapRecords, function (record) { + var singleSource = loaders[record.type].load(mapName, record); + + each(singleSource.regions, function (region) { + var regionName = region.name; + + // Try use the alias in geoNameMap + if (nameMap && nameMap.hasOwnProperty(regionName)) { + region = region.cloneShallow(regionName = nameMap[regionName]); + } + + regions.push(region); + regionsMap.set(regionName, region); + nameCoordMap.set(regionName, region.center); + }); + + var rect = singleSource.boundingRect; + if (rect) { + boundingRect + ? boundingRect.union(rect) + : (boundingRect = rect.clone()); + } + }); + + return { + regions: regions, + regionsMap: regionsMap, + nameCoordMap: nameCoordMap, + // FIXME Always return new ? + boundingRect: boundingRect || new BoundingRect(0, 0, 0, 0) + }; + }, + + /** + * @param {string} mapName + * @param {string} hostKey For cache. + * @return {Array.<module:zrender/Element>} Roots. + */ + makeGraphic: makeInvoker('makeGraphic'), + + /** + * @param {string} mapName + * @param {string} hostKey For cache. + */ + removeGraphic: makeInvoker('removeGraphic') +}; + +function makeInvoker(methodName) { + return function (mapName, hostKey) { + var mapRecords = retrieveMap(mapName); + var results = []; + + each(mapRecords, function (record) { + var method = loaders[record.type][methodName]; + method && results.push(method(mapName, record, hostKey)); + }); + + return results; + }; +} + +function mapNotExistsError(mapName) { + if (__DEV__) { + console.error( + 'Map ' + mapName + ' not exists. You can download map file on http://echarts.baidu.com/download-map.html' + ); + } +} + +function retrieveMap(mapName) { + var mapRecords = mapDataStorage.retrieveMap(mapName) || []; + + if (__DEV__) { + if (!mapRecords.length) { + mapNotExistsError(mapName); + } + } + + return mapRecords; +} + diff --git a/src/coord/geo/mapDataStorage.js b/src/coord/geo/mapDataStorage.js new file mode 100644 index 0000000..8f6de57 --- /dev/null +++ b/src/coord/geo/mapDataStorage.js @@ -0,0 +1,104 @@ +/* +* 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 {__DEV__} from '../../config'; +import {createHashMap, isString, isArray, each, assert} from 'zrender/src/core/util'; +import {parseXML} from 'zrender/src/tool/parseSVG'; + + +var storage = createHashMap(); + +// For minimize the code size of common echarts package, +// do not put too much logic in this module. + +export default { + + // The format of record: see `echarts.registerMap`. + // Compatible with previous `echarts.registerMap`. + registerMap: function (mapName, rawGeoJson, rawSpecialAreas) { + + var records; + + if (isArray(rawGeoJson)) { + records = rawGeoJson; + } + else if (rawGeoJson.svg) { + records = [{ + type: 'svg', + source: rawGeoJson.svg, + specialAreas: rawGeoJson.specialAreas + }]; + } + else { + // Backward compatibility. + if (rawGeoJson.geoJson && !rawGeoJson.features) { + rawSpecialAreas = rawGeoJson.specialAreas; + rawGeoJson = rawGeoJson.geoJson; + } + records = [{ + type: 'geoJSON', + source: rawGeoJson, + specialAreas: rawSpecialAreas + }]; + } + + each(records, function (record) { + var type = record.type; + type === 'geoJson' && (type = record.type = 'geoJSON'); + + var parse = parsers[type]; + + if (__DEV__) { + assert(parse, 'Illegal map type: ' + type); + } + + parse(record); + }); + + return storage.set(mapName, records); + }, + + retrieveMap: function (mapName) { + return storage.get(mapName); + } + +}; + +var parsers = { + + geoJSON: function (record) { + var source = record.source; + record.geoJSON = !isString(source) + ? source + : (typeof JSON !== 'undefined' && JSON.parse) + ? JSON.parse(source) + : (new Function('return (' + source + ');'))(); + }, + + // Only perform parse to XML object here, which might be time + // consiming for large SVG. + // Although convert XML to zrender element is also time consiming, + // if we do it here, the clone of zrender elements has to be + // required. So we do it once for each geo instance, util real + // performance issues call for optimizing it. + svg: function (record) { + record.svgXML = parseXML(record.source); + } + +}; diff --git a/src/echarts.js b/src/echarts.js index 019ef6b..dec7148 100644 --- a/src/echarts.js +++ b/src/echarts.js @@ -43,6 +43,7 @@ import Scheduler from './stream/Scheduler'; import lightTheme from './theme/light'; import darkTheme from './theme/dark'; import './component/dataset'; +import mapDataStorage from './coord/geo/mapDataStorage'; var assert = zrUtil.assert; var each = zrUtil.each; @@ -1720,8 +1721,6 @@ var idBase = new Date() - 0; var groupIdBase = new Date() - 0; var DOM_ATTRIBUTE_KEY = '_echarts_instance_'; -var mapDataStores = {}; - function enableConnect(chart) { var STATUS_PENDING = 0; var STATUS_UPDATING = 1; @@ -2115,10 +2114,10 @@ export function setCanvasCreator(creator) { /** * @param {string} mapName - * @param {Object|string} geoJson + * @param {Array.<Object>|Object|string} geoJson * @param {Object} [specialAreas] * - * @example + * @example GeoJSON * $.get('USA.json', function (geoJson) { * echarts.registerMap('USA', geoJson); * // Or @@ -2127,20 +2126,20 @@ export function setCanvasCreator(creator) { * specialAreas: {} * }) * }); + * + * $.get('airport.svg', function (svg) { + * echarts.registerMap('airport', { + * svg: svg + * } + * }); + * + * echarts.registerMap('eu', [ + * {svg: eu-topographic.svg}, + * {geoJSON: eu.json} + * ]) */ export function registerMap(mapName, geoJson, specialAreas) { - if (geoJson.geoJson && !geoJson.features) { - specialAreas = geoJson.specialAreas; - geoJson = geoJson.geoJson; - } - if (typeof geoJson === 'string') { - geoJson = (typeof JSON !== 'undefined' && JSON.parse) - ? JSON.parse(geoJson) : (new Function('return (' + geoJson + ');'))(); - } - mapDataStores[mapName] = { - geoJson: geoJson, - specialAreas: specialAreas - }; + mapDataStorage.registerMap(mapName, geoJson, specialAreas); } /** @@ -2148,7 +2147,12 @@ export function registerMap(mapName, geoJson, specialAreas) { * @return {Object} */ export function getMap(mapName) { - return mapDataStores[mapName]; + // For backward compatibility, only return the first one. + var records = mapDataStorage.retrieveMap(mapName); + return records && records[0] && { + geoJson: records[0].geoJSON, + specialAreas: records[0].specialAreas + }; } registerVisual(PRIORITY_VISUAL_GLOBAL, seriesColor); -- To stop receiving notification emails like this one, please contact [email protected]. --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
