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 graphic from '../../util/graphic'; | |
| import MapDraw from '../../component/helper/MapDraw'; | |
| import ChartView from '../../view/Chart'; | |
| import MapSeries, { MapDataItemOption } from './MapSeries'; | |
| import GlobalModel from '../../model/Global'; | |
| import ExtensionAPI from '../../core/ExtensionAPI'; | |
| import { Payload, DisplayState, ECElement } from '../../util/types'; | |
| import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle'; | |
| import { setStatesFlag, Z2_EMPHASIS_LIFT } from '../../util/states'; | |
| class MapView extends ChartView { | |
| static type = 'map' as const; | |
| readonly type = MapView.type; | |
| private _mapDraw: MapDraw; | |
| render( | |
| mapModel: MapSeries, | |
| ecModel: GlobalModel, | |
| api: ExtensionAPI, | |
| payload: Payload | |
| ): void { | |
| // Not render if it is an toggleSelect action from self | |
| if (payload && payload.type === 'mapToggleSelect' | |
| && payload.from === this.uid | |
| ) { | |
| return; | |
| } | |
| const group = this.group; | |
| group.removeAll(); | |
| if (mapModel.getHostGeoModel()) { | |
| return; | |
| } | |
| if (this._mapDraw && payload && payload.type === 'geoRoam') { | |
| this._mapDraw.resetForLabelLayout(); | |
| } | |
| // Not update map if it is an roam action from self | |
| if (!(payload && payload.type === 'geoRoam' | |
| && payload.componentType === 'series' | |
| && payload.seriesId === mapModel.id | |
| ) | |
| ) { | |
| if (mapModel.needsDrawMap) { | |
| const mapDraw = this._mapDraw || new MapDraw(api); | |
| group.add(mapDraw.group); | |
| mapDraw.draw(mapModel, ecModel, api, this, payload); | |
| this._mapDraw = mapDraw; | |
| } | |
| else { | |
| // Remove drawn map | |
| this._mapDraw && this._mapDraw.remove(); | |
| this._mapDraw = null; | |
| } | |
| } | |
| else { | |
| const mapDraw = this._mapDraw; | |
| mapDraw && group.add(mapDraw.group); | |
| } | |
| mapModel.get('showLegendSymbol') && ecModel.getComponent('legend') | |
| && this._renderSymbols(mapModel, ecModel, api); | |
| } | |
| remove(): void { | |
| this._mapDraw && this._mapDraw.remove(); | |
| this._mapDraw = null; | |
| this.group.removeAll(); | |
| } | |
| dispose(): void { | |
| this._mapDraw && this._mapDraw.remove(); | |
| this._mapDraw = null; | |
| } | |
| private _renderSymbols(mapModel: MapSeries, ecModel: GlobalModel, api: ExtensionAPI): void { | |
| const originalData = mapModel.originalData; | |
| const group = this.group; | |
| originalData.each(originalData.mapDimension('value'), function (value, originalDataIndex) { | |
| if (isNaN(value as number)) { | |
| return; | |
| } | |
| const layout = originalData.getItemLayout(originalDataIndex); | |
| if (!layout || !layout.point) { | |
| // Not exists in map | |
| return; | |
| } | |
| const point = layout.point; | |
| const offset = layout.offset; | |
| const circle = new graphic.Circle({ | |
| style: { | |
| // Because the special of map draw. | |
| // Which needs statistic of multiple series and draw on one map. | |
| // And each series also need a symbol with legend color | |
| // | |
| // Layout and visual are put one the different data | |
| // TODO | |
| fill: mapModel.getData().getVisual('style').fill | |
| }, | |
| shape: { | |
| cx: point[0] + offset * 9, | |
| cy: point[1], | |
| r: 3 | |
| }, | |
| silent: true, | |
| // Do not overlap the first series, on which labels are displayed. | |
| z2: 8 + (!offset ? Z2_EMPHASIS_LIFT + 1 : 0) | |
| }); | |
| // Only the series that has the first value on the same region is in charge of rendering the label. | |
| // But consider the case: | |
| // series: [ | |
| // {id: 'X', type: 'map', map: 'm', {data: [{name: 'A', value: 11}, {name: 'B', {value: 22}]}, | |
| // {id: 'Y', type: 'map', map: 'm', {data: [{name: 'A', value: 21}, {name: 'C', {value: 33}]} | |
| // ] | |
| // The offset `0` of item `A` is at series `X`, but of item `C` is at series `Y`. | |
| // For backward compatibility, we follow the rule that render label `A` by the | |
| // settings on series `X` but render label `C` by the settings on series `Y`. | |
| if (!offset) { | |
| const fullData = mapModel.mainSeries.getData(); | |
| const name = originalData.getName(originalDataIndex); | |
| const fullIndex = fullData.indexOfName(name); | |
| const itemModel = originalData.getItemModel<MapDataItemOption>(originalDataIndex); | |
| const labelModel = itemModel.getModel('label'); | |
| const regionGroup = fullData.getItemGraphicEl(fullIndex); | |
| // `getFormattedLabel` needs to use `getData` inside. Here | |
| // `mapModel.getData()` is shallow cloned from `mainSeries.getData()`. | |
| // FIXME | |
| // If this is not the `mainSeries`, the item model (like label formatter) | |
| // set on original data item will never get. But it has been working | |
| // like that from the beginning, and this scenario is rarely encountered. | |
| // So it won't be fixed until we have to. | |
| setLabelStyle(circle, getLabelStatesModels(itemModel), { | |
| labelFetcher: { | |
| getFormattedLabel(idx: number, state: DisplayState) { | |
| return mapModel.getFormattedLabel(fullIndex, state); | |
| } | |
| }, | |
| defaultText: name | |
| }); | |
| (circle as ECElement).disableLabelAnimation = true; | |
| if (!labelModel.get('position')) { | |
| circle.setTextConfig({ | |
| position: 'bottom' | |
| }); | |
| } | |
| (regionGroup as ECElement).onHoverStateChange = function (toState) { | |
| setStatesFlag(circle, toState); | |
| }; | |
| } | |
| group.add(circle); | |
| }); | |
| } | |
| } | |
| export default MapView; | |