Spaces:
Running
Running
/* | |
* 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 createSeriesDataSimply from '../helper/createSeriesDataSimply'; | |
import SeriesModel from '../../model/Series'; | |
import geoSourceManager from '../../coord/geo/geoSourceManager'; | |
import {makeSeriesEncodeForNameBased} from '../../data/helper/sourceHelper'; | |
import { | |
SeriesOption, | |
BoxLayoutOptionMixin, | |
SeriesEncodeOptionMixin, | |
OptionDataItemObject, | |
OptionDataValueNumeric, | |
ParsedValue, | |
SeriesOnGeoOptionMixin, | |
StatesOptionMixin, | |
SeriesLabelOption, | |
StatesMixinBase, | |
CallbackDataParams | |
} from '../../util/types'; | |
import { Dictionary } from 'zrender/src/core/types'; | |
import GeoModel, { GeoCommonOptionMixin, GeoItemStyleOption } from '../../coord/geo/GeoModel'; | |
import SeriesData from '../../data/SeriesData'; | |
import Model from '../../model/Model'; | |
import Geo from '../../coord/geo/Geo'; | |
import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup'; | |
import {createSymbol, ECSymbol} from '../../util/symbol'; | |
import {LegendIconParams} from '../../component/legend/LegendModel'; | |
import {Group} from '../../util/graphic'; | |
export interface MapStateOption<TCbParams = never> { | |
itemStyle?: GeoItemStyleOption<TCbParams> | |
label?: SeriesLabelOption | |
} | |
export interface MapDataItemOption extends MapStateOption, | |
StatesOptionMixin<MapStateOption, StatesMixinBase>, | |
OptionDataItemObject<OptionDataValueNumeric> { | |
cursor?: string | |
} | |
export type MapValueCalculationType = 'sum' | 'average' | 'min' | 'max'; | |
export interface MapSeriesOption extends | |
SeriesOption<MapStateOption<CallbackDataParams>, StatesMixinBase>, | |
MapStateOption<CallbackDataParams>, | |
GeoCommonOptionMixin, | |
// If `geoIndex` is not specified, a exclusive geo will be | |
// created. Otherwise use the specified geo component, and | |
// `map` and `mapType` are ignored. | |
SeriesOnGeoOptionMixin, | |
BoxLayoutOptionMixin, | |
SeriesEncodeOptionMixin { | |
type?: 'map' | |
coordinateSystem?: string; | |
silent?: boolean; | |
// FIXME:TS add marker types | |
markLine?: any; | |
markPoint?: any; | |
markArea?: any; | |
mapValueCalculation?: MapValueCalculationType; | |
showLegendSymbol?: boolean; | |
// @deprecated. Only for echarts2 backward compat. | |
geoCoord?: Dictionary<number[]>; | |
data?: (OptionDataValueNumeric | OptionDataValueNumeric[] | MapDataItemOption)[] | |
nameProperty?: string; | |
} | |
class MapSeries extends SeriesModel<MapSeriesOption> { | |
static type = 'series.map' as const; | |
type = MapSeries.type; | |
static dependencies = ['geo']; | |
static layoutMode = 'box' as const; | |
coordinateSystem: Geo; | |
// ----------------- | |
// Injected outside | |
originalData: SeriesData; | |
mainSeries: MapSeries; | |
// Only first map series of same mapType will drawMap. | |
needsDrawMap: boolean = false; | |
// Group of all map series with same mapType | |
seriesGroup: MapSeries[] = []; | |
getInitialData(this: MapSeries, option: MapSeriesOption): SeriesData { | |
const data = createSeriesDataSimply(this, { | |
coordDimensions: ['value'], | |
encodeDefaulter: zrUtil.curry(makeSeriesEncodeForNameBased, this) | |
}); | |
const dataNameMap = zrUtil.createHashMap(); | |
const toAppendNames = [] as string[]; | |
for (let i = 0, len = data.count(); i < len; i++) { | |
const name = data.getName(i); | |
dataNameMap.set(name, true); | |
} | |
const geoSource = geoSourceManager.load(this.getMapType(), this.option.nameMap, this.option.nameProperty); | |
zrUtil.each(geoSource.regions, function (region) { | |
const name = region.name; | |
if (!dataNameMap.get(name)) { | |
toAppendNames.push(name); | |
} | |
}); | |
// Complete data with missing regions. The consequent processes (like visual | |
// map and render) can not be performed without a "full data". For example, | |
// find `dataIndex` by name. | |
data.appendValues([], toAppendNames); | |
return data; | |
} | |
/** | |
* If no host geo model, return null, which means using a | |
* inner exclusive geo model. | |
*/ | |
getHostGeoModel(): GeoModel { | |
const geoIndex = this.option.geoIndex; | |
return geoIndex != null | |
? this.ecModel.getComponent('geo', geoIndex) as GeoModel | |
: null; | |
} | |
getMapType(): string { | |
return (this.getHostGeoModel() || this).option.map; | |
} | |
// _fillOption(option, mapName) { | |
// Shallow clone | |
// option = zrUtil.extend({}, option); | |
// option.data = geoCreator.getFilledRegions(option.data, mapName, option.nameMap); | |
// return option; | |
// } | |
getRawValue(dataIndex: number): ParsedValue { | |
// Use value stored in data instead because it is calculated from multiple series | |
// FIXME Provide all value of multiple series ? | |
const data = this.getData(); | |
return data.get(data.mapDimension('value'), dataIndex); | |
} | |
/** | |
* Get model of region | |
*/ | |
getRegionModel(regionName: string): Model<MapDataItemOption> { | |
const data = this.getData(); | |
return data.getItemModel(data.indexOfName(regionName)); | |
} | |
/** | |
* Map tooltip formatter | |
*/ | |
formatTooltip( | |
dataIndex: number, | |
multipleSeries: boolean, | |
dataType: string | |
) { | |
// FIXME orignalData and data is a bit confusing | |
const data = this.getData(); | |
const value = this.getRawValue(dataIndex); | |
const name = data.getName(dataIndex); | |
const seriesGroup = this.seriesGroup; | |
const seriesNames = []; | |
for (let i = 0; i < seriesGroup.length; i++) { | |
const otherIndex = seriesGroup[i].originalData.indexOfName(name); | |
const valueDim = data.mapDimension('value'); | |
if (!isNaN(seriesGroup[i].originalData.get(valueDim, otherIndex) as number)) { | |
seriesNames.push(seriesGroup[i].name); | |
} | |
} | |
return createTooltipMarkup('section', { | |
header: seriesNames.join(', '), | |
noHeader: !seriesNames.length, | |
blocks: [createTooltipMarkup('nameValue', { | |
name: name, value: value | |
})] | |
}); | |
} | |
getTooltipPosition = function (this: MapSeries, dataIndex: number): number[] { | |
if (dataIndex != null) { | |
const name = this.getData().getName(dataIndex); | |
const geo = this.coordinateSystem; | |
const region = geo.getRegion(name); | |
return region && geo.dataToPoint(region.getCenter()); | |
} | |
}; | |
setZoom(zoom: number): void { | |
this.option.zoom = zoom; | |
} | |
setCenter(center: number[]): void { | |
this.option.center = center; | |
} | |
getLegendIcon(opt: LegendIconParams): ECSymbol | Group { | |
const iconType = opt.icon || 'roundRect'; | |
const icon = createSymbol( | |
iconType, | |
0, | |
0, | |
opt.itemWidth, | |
opt.itemHeight, | |
opt.itemStyle.fill | |
); | |
icon.setStyle(opt.itemStyle); | |
// Map do not use itemStyle.borderWidth as border width | |
icon.style.stroke = 'none'; | |
// No rotation because no series visual symbol for map | |
if (iconType.indexOf('empty') > -1) { | |
icon.style.stroke = icon.style.fill; | |
icon.style.fill = '#fff'; | |
icon.style.lineWidth = 2; | |
} | |
return icon; | |
} | |
static defaultOption: MapSeriesOption = { | |
// 一级层叠 | |
// zlevel: 0, | |
// 二级层叠 | |
z: 2, | |
coordinateSystem: 'geo', | |
// map should be explicitly specified since ec3. | |
map: '', | |
// If `geoIndex` is not specified, a exclusive geo will be | |
// created. Otherwise use the specified geo component, and | |
// `map` and `mapType` are ignored. | |
// geoIndex: 0, | |
// 'center' | 'left' | 'right' | 'x%' | {number} | |
left: 'center', | |
// 'center' | 'top' | 'bottom' | 'x%' | {number} | |
top: 'center', | |
// right | |
// bottom | |
// width: | |
// height | |
// Aspect is width / height. Inited to be geoJson bbox aspect | |
// This parameter is used for scale this aspect | |
// Default value: | |
// for geoSVG source: 1, | |
// for geoJSON source: 0.75. | |
aspectScale: null, | |
// Layout with center and size | |
// If you want to put map in a fixed size box with right aspect ratio | |
// This two properties may be more convenient. | |
// layoutCenter: [50%, 50%] | |
// layoutSize: 100 | |
showLegendSymbol: true, | |
// Define left-top, right-bottom coords to control view | |
// For example, [ [180, 90], [-180, -90] ], | |
// higher priority than center and zoom | |
boundingCoords: null, | |
// Default on center of map | |
center: null, | |
zoom: 1, | |
scaleLimit: null, | |
selectedMode: true, | |
label: { | |
show: false, | |
color: '#000' | |
}, | |
// scaleLimit: null, | |
itemStyle: { | |
borderWidth: 0.5, | |
borderColor: '#444', | |
areaColor: '#eee' | |
}, | |
emphasis: { | |
label: { | |
show: true, | |
color: 'rgb(100,0,0)' | |
}, | |
itemStyle: { | |
areaColor: 'rgba(255,215,0,0.8)' | |
} | |
}, | |
select: { | |
label: { | |
show: true, | |
color: 'rgb(100,0,0)' | |
}, | |
itemStyle: { | |
color: 'rgba(255,215,0,0.8)' | |
} | |
}, | |
nameProperty: 'name' | |
}; | |
} | |
export default MapSeries; | |