UNPKG

di-echarts

Version:

Apache ECharts is a powerful, interactive charting and data visualization library for browser

284 lines (235 loc) 10 kB
/* * 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 Geo, { geo2DDimensions } from './Geo'; import * as layout from '../../util/layout'; import * as numberUtil from '../../util/number'; import geoSourceManager from './geoSourceManager'; import GeoModel, { GeoCommonOptionMixin, GeoOption, RegoinOption } from './GeoModel'; import MapSeries, { MapSeriesOption } from '../../chart/map/MapSeries'; import ExtensionAPI from '../../core/ExtensionAPI'; import { CoordinateSystemCreator } from '../CoordinateSystem'; import { NameMap } from './geoTypes'; import { SeriesOption, SeriesOnGeoOptionMixin } from '../../util/types'; import { Dictionary } from 'zrender/src/core/types'; import type Model from '../../model/Model'; import type GlobalModel from '../../model/Global'; import type SeriesModel from '../../model/Series'; import type ComponentModel from '../../model/Component'; import * as vector from 'zrender/src/core/vector'; export type resizeGeoType = typeof resizeGeo; /** * Resize method bound to the geo */ function resizeGeo(this: Geo, geoModel: ComponentModel<GeoOption | MapSeriesOption>, api: ExtensionAPI): void { const boundingCoords = geoModel.get('boundingCoords'); if (boundingCoords != null) { let leftTop = boundingCoords[0]; let rightBottom = boundingCoords[1]; if (!( isFinite(leftTop[0]) && isFinite(leftTop[1]) && isFinite(rightBottom[0]) && isFinite(rightBottom[1]) )) { if (__DEV__) { console.error('Invalid boundingCoords'); } } else { // Sample around the lng/lat rect and use projection to calculate actual bounding rect. const projection = this.projection; if (projection) { const xMin = leftTop[0]; const yMin = leftTop[1]; const xMax = rightBottom[0]; const yMax = rightBottom[1]; leftTop = [Infinity, Infinity]; rightBottom = [-Infinity, -Infinity]; // TODO better way? const sampleLine = (x0: number, y0: number, x1: number, y1: number) => { const dx = x1 - x0; const dy = y1 - y0; for (let i = 0; i <= 100; i++) { const p = i / 100; const pt = projection.project([x0 + dx * p, y0 + dy * p]); vector.min(leftTop, leftTop, pt); vector.max(rightBottom, rightBottom, pt); } }; // Top sampleLine(xMin, yMin, xMax, yMin); // Right sampleLine(xMax, yMin, xMax, yMax); // Bottom sampleLine(xMax, yMax, xMin, yMax); // Left sampleLine(xMin, yMax, xMax, yMin); } this.setBoundingRect(leftTop[0], leftTop[1], rightBottom[0] - leftTop[0], rightBottom[1] - leftTop[1]); } } const rect = this.getBoundingRect(); const centerOption = geoModel.get('layoutCenter'); const sizeOption = geoModel.get('layoutSize'); const viewWidth = api.getWidth(); const viewHeight = api.getHeight(); const aspect = rect.width / rect.height * this.aspectScale; let useCenterAndSize = false; let center: number[]; let size: number; if (centerOption && sizeOption) { center = [ numberUtil.parsePercent(centerOption[0], viewWidth), numberUtil.parsePercent(centerOption[1], viewHeight) ]; size = numberUtil.parsePercent(sizeOption, Math.min(viewWidth, viewHeight)); if (!isNaN(center[0]) && !isNaN(center[1]) && !isNaN(size)) { useCenterAndSize = true; } else { if (__DEV__) { console.warn('Given layoutCenter or layoutSize data are invalid. Use left/top/width/height instead.'); } } } let viewRect: layout.LayoutRect; if (useCenterAndSize) { viewRect = {} as layout.LayoutRect; if (aspect > 1) { // Width is same with size viewRect.width = size; viewRect.height = size / aspect; } else { viewRect.height = size; viewRect.width = size * aspect; } viewRect.y = center[1] - viewRect.height / 2; viewRect.x = center[0] - viewRect.width / 2; } else { // Use left/top/width/height const boxLayoutOption = geoModel.getBoxLayoutParams() as Parameters<typeof layout.getLayoutRect>[0]; boxLayoutOption.aspect = aspect; viewRect = layout.getLayoutRect(boxLayoutOption, { width: viewWidth, height: viewHeight }); } this.setViewRect(viewRect.x, viewRect.y, viewRect.width, viewRect.height); this.setCenter(geoModel.get('center'), api); this.setZoom(geoModel.get('zoom')); } // Back compat for ECharts2, where the coord map is set on map series: // {type: 'map', geoCoord: {'cityA': [116.46,39.92], 'cityA': [119.12,24.61]}}, function setGeoCoords(geo: Geo, model: MapSeries) { zrUtil.each(model.get('geoCoord'), function (geoCoord, name) { geo.addGeoCoord(name, geoCoord); }); } class GeoCreator implements CoordinateSystemCreator { // For deciding which dimensions to use when creating list data dimensions = geo2DDimensions; create(ecModel: GlobalModel, api: ExtensionAPI): Geo[] { const geoList = [] as Geo[]; function getCommonGeoProperties(model: Model<GeoCommonOptionMixin>) { return { nameProperty: model.get('nameProperty'), aspectScale: model.get('aspectScale'), projection: model.get('projection') }; } // FIXME Create each time may be slow ecModel.eachComponent('geo', function (geoModel: GeoModel, idx) { const mapName = geoModel.get('map'); const geo = new Geo(mapName + idx, mapName, zrUtil.extend({ nameMap: geoModel.get('nameMap') }, getCommonGeoProperties(geoModel))); geo.zoomLimit = geoModel.get('scaleLimit'); geoList.push(geo); // setGeoCoords(geo, geoModel); geoModel.coordinateSystem = geo; geo.model = geoModel; // Inject resize method geo.resize = resizeGeo; geo.resize(geoModel, api); }); ecModel.eachSeries(function (seriesModel) { const coordSys = seriesModel.get('coordinateSystem'); if (coordSys === 'geo') { const geoIndex = ( seriesModel as SeriesModel<SeriesOption & SeriesOnGeoOptionMixin> ).get('geoIndex') || 0; seriesModel.coordinateSystem = geoList[geoIndex]; } }); // If has map series const mapModelGroupBySeries = {} as Dictionary<MapSeries[]>; ecModel.eachSeriesByType('map', function (seriesModel: MapSeries) { if (!seriesModel.getHostGeoModel()) { const mapType = seriesModel.getMapType(); mapModelGroupBySeries[mapType] = mapModelGroupBySeries[mapType] || []; mapModelGroupBySeries[mapType].push(seriesModel); } }); zrUtil.each(mapModelGroupBySeries, function (mapSeries, mapType) { const nameMapList = zrUtil.map(mapSeries, function (singleMapSeries) { return singleMapSeries.get('nameMap'); }); const geo = new Geo(mapType, mapType, zrUtil.extend({ nameMap: zrUtil.mergeAll(nameMapList) }, getCommonGeoProperties(mapSeries[0]))); geo.zoomLimit = zrUtil.retrieve.apply(null, zrUtil.map(mapSeries, function (singleMapSeries) { return singleMapSeries.get('scaleLimit'); })); geoList.push(geo); // Inject resize method geo.resize = resizeGeo; geo.resize(mapSeries[0], api); zrUtil.each(mapSeries, function (singleMapSeries) { singleMapSeries.coordinateSystem = geo; setGeoCoords(geo, singleMapSeries); }); }); return geoList; } /** * Fill given regions array */ getFilledRegions( originRegionArr: RegoinOption[], mapName: string, nameMap: NameMap, nameProperty: string ): RegoinOption[] { // Not use the original const regionsArr = (originRegionArr || []).slice(); const dataNameMap = zrUtil.createHashMap(); for (let i = 0; i < regionsArr.length; i++) { dataNameMap.set(regionsArr[i].name, regionsArr[i]); } const source = geoSourceManager.load(mapName, nameMap, nameProperty); zrUtil.each(source.regions, function (region) { const name = region.name; !dataNameMap.get(name) && regionsArr.push({name: name}); }); return regionsArr; } } const geoCreator = new GeoCreator(); export default geoCreator;