MapDraw.ts 17.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/*
* 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.
*/

S
sushuang 已提交
20
import * as zrUtil from 'zrender/src/core/util';
S
sushuang 已提交
21 22 23 24
import RoamController from './RoamController';
import * as roamHelper from '../../component/helper/roamHelper';
import {onIrrelevantElement} from '../../component/helper/cursorHelper';
import * as graphic from '../../util/graphic';
P
pissang 已提交
25
import { enableHoverEmphasis, DISPLAY_STATES } from '../../util/states';
S
sushuang 已提交
26 27
import geoSourceManager from '../../coord/geo/geoSourceManager';
import {getUID} from '../../util/component';
28 29 30 31
import ExtensionAPI from '../../ExtensionAPI';
import GeoModel, { GeoCommonOptionMixin, GeoItemStyleOption } from '../../coord/geo/GeoModel';
import MapSeries from '../../chart/map/MapSeries';
import GlobalModel from '../../model/Global';
P
pissang 已提交
32
import { Payload, ECElement } from '../../util/types';
33 34 35 36 37
import GeoView from '../geo/GeoView';
import MapView from '../../chart/map/MapView';
import Region from '../../coord/geo/Region';
import Geo from '../../coord/geo/Geo';
import Model from '../../model/Model';
38
import Transformable from 'zrender/src/core/Transformable';
P
pissang 已提交
39
import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle';
40 41 42 43 44


interface RegionsGroup extends graphic.Group {
    __regions: Region[];
}
S
sushuang 已提交
45

46
function getFixedItemStyle(model: Model<GeoItemStyleOption>) {
47 48
    const itemStyle = model.getItemStyle();
    const areaColor = model.get('areaColor');
S
sushuang 已提交
49 50 51 52 53

    // If user want the color not to be changed when hover,
    // they should both set areaColor and color to be null.
    if (areaColor != null) {
        itemStyle.fill = areaColor;
L
lang 已提交
54 55
    }

S
sushuang 已提交
56 57
    return itemStyle;
}
58
class MapDraw {
59

60
    private uid: string;
S
sushuang 已提交
61

62 63
    // @ts-ignore FIXME:TS
    private _controller: RoamController;
64

65
    private _controllerHost: {
P
pissang 已提交
66
        target: graphic.Group;
67 68 69
        zoom?: number;
        zoomLimit?: GeoCommonOptionMixin['scaleLimit'];
    };
L
lang 已提交
70

71
    readonly group: graphic.Group;
L
lang 已提交
72 73


S
sushuang 已提交
74 75 76 77 78
    /**
     * This flag is used to make sure that only one among
     * `pan`, `zoom`, `click` can occurs, otherwise 'selected'
     * action may be triggered when `pan`, which is unexpected.
     */
79
    private _mouseDownFlag: boolean;
S
sushuang 已提交
80

81
    private _mapName: string;
S
sushuang 已提交
82

83
    private _initialized: string;
S
sushuang 已提交
84

85 86 87
    private _regionsGroup: RegionsGroup;

    private _backgroundGroup: graphic.Group;
S
sushuang 已提交
88

89

P
pissang 已提交
90
    constructor(api: ExtensionAPI) {
91
        const group = new graphic.Group();
92 93 94
        this.uid = getUID('ec_map_draw');
        // @ts-ignore FIXME:TS
        this._controller = new RoamController(api.getZr());
P
pissang 已提交
95
        this._controllerHost = { target: group };
96
        this.group = group;
L
lang 已提交
97

98 99 100
        group.add(this._regionsGroup = new graphic.Group() as RegionsGroup);
        group.add(this._backgroundGroup = new graphic.Group());
    }
L
lang 已提交
101

102 103 104 105
    draw(
        mapOrGeoModel: GeoModel | MapSeries,
        ecModel: GlobalModel,
        api: ExtensionAPI,
1
100pah 已提交
106 107
        fromView: MapView | GeoView,
        payload: Payload
108
    ): void {
L
lang 已提交
109

110
        const isGeo = mapOrGeoModel.mainType === 'geo';
L
lang 已提交
111

S
sushuang 已提交
112 113
        // Map series has data. GEO model that controlled by map series
        // will be assigned with map data. Other GEO model has no data.
114
        let data = (mapOrGeoModel as MapSeries).getData && (mapOrGeoModel as MapSeries).getData();
115
        isGeo && ecModel.eachComponent({mainType: 'series', subType: 'map'}, function (mapSeries: MapSeries) {
S
sushuang 已提交
116 117
            if (!data && mapSeries.getHostGeoModel() === mapOrGeoModel) {
                data = mapSeries.getData();
L
lang 已提交
118
            }
S
sushuang 已提交
119 120
        });

121
        const geo = mapOrGeoModel.coordinateSystem;
S
sushuang 已提交
122

S
sushuang 已提交
123 124
        this._updateBackground(geo);

125 126
        const regionsGroup = this._regionsGroup;
        const group = this.group;
L
lang 已提交
127

128
        const transformInfo = geo.getTransformInfo();
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152

        // No animation when first draw or in action
        const isFirstDraw = !regionsGroup.childAt(0) || payload;
        let targetScaleX: number;
        let targetScaleY: number;
        if (isFirstDraw) {
            group.transform = transformInfo.roamTransform;
            group.decomposeTransform();
            group.dirty();
        }
        else {
            const target = new Transformable();
            target.transform = transformInfo.roamTransform;
            target.decomposeTransform();
            const props = {
                scaleX: target.scaleX,
                scaleY: target.scaleY,
                x: target.x,
                y: target.y
            };
            targetScaleX = target.scaleX;
            targetScaleY = target.scaleY;
            graphic.updateProps(group, props, mapOrGeoModel);
        }
L
lang 已提交
153

S
sushuang 已提交
154
        regionsGroup.removeAll();
L
lang 已提交
155

156
        const nameMap = zrUtil.createHashMap<RegionsGroup>();
157

158

P
pissang 已提交
159 160 161
        const isVisualEncodedByVisualMap = data
            && data.getVisual('visualMeta')
            && data.getVisual('visualMeta').length > 0;
162

S
sushuang 已提交
163 164 165 166 167 168
        zrUtil.each(geo.regions, function (region) {
            // Consider in GeoJson properties.name may be duplicated, for example,
            // there is multiple region named "United Kindom" or "France" (so many
            // colonies). And it is not appropriate to merge them in geo, which
            // will make them share the same label and bring trouble in label
            // location calculation.
169
            const regionGroup = nameMap.get(region.name)
170
                || nameMap.set(region.name, new graphic.Group() as RegionsGroup);
S
sushuang 已提交
171

172
            const compoundPath = new graphic.CompoundPath({
173
                segmentIgnoreThreshold: 1,
S
sushuang 已提交
174 175 176 177 178 179
                shape: {
                    paths: []
                }
            });
            regionGroup.add(compoundPath);

180
            const regionModel = mapOrGeoModel.getRegionModel(region.name) || mapOrGeoModel;
S
sushuang 已提交
181

182
            // @ts-ignore FIXME:TS fix the "compatible with each other"?
183
            const itemStyleModel = regionModel.getModel('itemStyle');
184
            // @ts-ignore FIXME:TS fix the "compatible with each other"?
P
pissang 已提交
185 186
            const emphasisModel = regionModel.getModel('emphasis');
            const emphasisItemStyleModel = emphasisModel.getModel('itemStyle');
187 188 189 190
            // @ts-ignore FIXME:TS fix the "compatible with each other"?
            const blurItemStyleModel = regionModel.getModel(['blur', 'itemStyle']);
            // @ts-ignore FIXME:TS fix the "compatible with each other"?
            const selectItemStyleModel = regionModel.getModel(['select', 'itemStyle']);
191 192 193

            // NOTE: DONT use 'style' in visual when drawing map.
            // This component is used for drawing underlying map for both geo component and map series.
194
            const itemStyle = getFixedItemStyle(itemStyleModel);
195 196 197
            const emphasisItemStyle = getFixedItemStyle(emphasisItemStyleModel);
            const blurItemStyle = getFixedItemStyle(blurItemStyleModel);
            const selectItemStyle = getFixedItemStyle(selectItemStyleModel);
S
sushuang 已提交
198

199
            let dataIdx;
S
sushuang 已提交
200 201 202
            // Use the itemStyle in data if has data
            if (data) {
                dataIdx = data.indexOfName(region.name);
203
                // Only visual color of each item will be used. It can be encoded by visualMap
S
sushuang 已提交
204 205 206
                // But visual color of series is used in symbol drawing
                //
                // Visual color for each series is for the symbol draw
207
                const style = data.getItemVisual(dataIdx, 'style');
208
                if (isVisualEncodedByVisualMap && style.fill) {
209
                    itemStyle.fill = style.fill;
L
lang 已提交
210
                }
S
sushuang 已提交
211
            }
212

213 214 215 216
            const sx = transformInfo.rawScaleX;
            const sy = transformInfo.rawScaleY;
            const offsetX = transformInfo.rawX;
            const offsetY = transformInfo.rawY;
217
            const transformPoint = function (point: number[]): number[] {
218
                return [
219 220
                    point[0] * sx + offsetX,
                    point[1] * sy + offsetY
221 222 223
                ];
            };

S
sushuang 已提交
224 225 226 227
            zrUtil.each(region.geometries, function (geometry) {
                if (geometry.type !== 'polygon') {
                    return;
                }
228
                const points = [];
1
100pah 已提交
229
                for (let i = 0; i < geometry.exterior.length; ++i) {
230
                    points.push(transformPoint(geometry.exterior[i]));
231
                }
S
sushuang 已提交
232
                compoundPath.shape.paths.push(new graphic.Polygon({
233
                    segmentIgnoreThreshold: 1,
S
sushuang 已提交
234
                    shape: {
235
                        points: points
L
lang 已提交
236
                    }
S
sushuang 已提交
237 238
                }));

1
100pah 已提交
239
                for (let i = 0; i < (geometry.interiors ? geometry.interiors.length : 0); ++i) {
240 241
                    const interior = geometry.interiors[i];
                    const points = [];
242
                    for (let j = 0; j < interior.length; ++j) {
243 244
                        points.push(transformPoint(interior[j]));
                    }
L
lang 已提交
245
                    compoundPath.shape.paths.push(new graphic.Polygon({
246
                        segmentIgnoreThreshold: 1,
L
lang 已提交
247
                        shape: {
248
                            points: points
L
lang 已提交
249
                        }
L
lang 已提交
250
                    }));
S
sushuang 已提交
251 252
                }
            });
L
lang 已提交
253

S
sushuang 已提交
254 255 256
            compoundPath.setStyle(itemStyle);
            compoundPath.style.strokeNoScale = true;
            compoundPath.culling = true;
257

258 259 260
            compoundPath.ensureState('emphasis').style = emphasisItemStyle;
            compoundPath.ensureState('blur').style = blurItemStyle;
            compoundPath.ensureState('select').style = selectItemStyle;
261

P
pissang 已提交
262 263 264 265 266 267 268 269 270 271 272
            let showLabel = false;
            for (let i = 0; i < DISPLAY_STATES.length; i++) {
                const stateName = DISPLAY_STATES[i];
                // @ts-ignore FIXME:TS fix the "compatible with each other"?
                if (regionModel.get(
                    stateName === 'normal' ? ['label', 'show'] : [stateName, 'label', 'show']
                )) {
                    showLabel = true;
                    break;
                }
            }
S
sushuang 已提交
273

274 275
            const isDataNaN = data && isNaN(data.get(data.mapDimension('value'), dataIdx) as number);
            const itemLayout = data && data.getItemLayout(dataIdx);
P
pissang 已提交
276

S
sushuang 已提交
277 278 279 280 281
            // In the following cases label will be drawn
            // 1. In map series and data value is NaN
            // 2. In geo component
            // 4. Region has no series legendSymbol, which will be add a showLabel flag in mapSymbolLayout
            if (
P
pissang 已提交
282
                (isGeo || isDataNaN && (showLabel))
S
sushuang 已提交
283
                || (itemLayout && itemLayout.showLabel)
284
            ) {
285
                const query = !isGeo ? dataIdx : region.name;
286
                let labelFetcher;
S
sushuang 已提交
287 288 289 290 291 292

                // Consider dataIdx not found.
                if (!data || dataIdx >= 0) {
                    labelFetcher = mapOrGeoModel;
                }

293
                const centerPt = transformPoint(region.center);
294
                const textEl = new graphic.Text({
295 296
                    x: centerPt[0],
                    y: centerPt[1],
297 298 299 300
                    // FIXME
                    // label rotation is not support yet in geo or regions of series-map
                    // that has no data. The rotation will be effected by this `scale`.
                    // So needed to change to RectText?
301 302
                    scaleX: 1 / group.scaleX,
                    scaleY: 1 / group.scaleY,
S
sushuang 已提交
303 304
                    z2: 10,
                    silent: true
L
lang 已提交
305 306
                });

307
                setLabelStyle<typeof query>(
P
pissang 已提交
308
                    textEl, getLabelStatesModels(regionModel),
S
sushuang 已提交
309 310 311
                    {
                        labelFetcher: labelFetcher,
                        labelDataIndex: query,
312
                        defaultText: region.name
S
sushuang 已提交
313
                    },
P
pissang 已提交
314
                    { normal: {
P
pissang 已提交
315 316
                        align: 'center',
                        verticalAlign: 'middle'
P
pissang 已提交
317
                    } }
S
sushuang 已提交
318
                );
319

P
pissang 已提交
320
                compoundPath.setTextContent(textEl);
321 322 323
                compoundPath.setTextConfig({
                    local: true
                });
P
pissang 已提交
324

P
pissang 已提交
325 326
                (compoundPath as ECElement).disableLabelAnimation = true;

327 328 329 330 331 332 333
                if (!isFirstDraw) {
                    // Text animation
                    graphic.updateProps(textEl, {
                        scaleX: 1 / targetScaleX,
                        scaleY: 1 / targetScaleY
                    }, mapOrGeoModel);
                }
S
sushuang 已提交
334
            }
P
pah100 已提交
335

S
sushuang 已提交
336 337 338 339 340 341
            // setItemGraphicEl, setHoverStyle after all polygons and labels
            // are added to the rigionGroup
            if (data) {
                data.setItemGraphicEl(dataIdx, regionGroup);
            }
            else {
342
                const regionModel = mapOrGeoModel.getRegionModel(region.name);
S
sushuang 已提交
343
                // Package custom mouse event for geo component
344
                graphic.getECData(compoundPath).eventData = {
S
sushuang 已提交
345
                    componentType: 'geo',
S
sushuang 已提交
346
                    componentIndex: mapOrGeoModel.componentIndex,
S
sushuang 已提交
347 348 349 350 351
                    geoIndex: mapOrGeoModel.componentIndex,
                    name: region.name,
                    region: (regionModel && regionModel.option) || {}
                };
            }
L
lang 已提交
352

353
            const groupRegions = regionGroup.__regions || (regionGroup.__regions = []);
S
sushuang 已提交
354
            groupRegions.push(region);
355

356
            // @ts-ignore FIXME:TS fix the "compatible with each other"?
357
            regionGroup.highDownSilentOnTouch = !!mapOrGeoModel.get('selectedMode');
P
pissang 已提交
358
            enableHoverEmphasis(regionGroup, emphasisModel.get('focus'), emphasisModel.get('blurScope'));
359

S
sushuang 已提交
360
            regionsGroup.add(regionGroup);
S
sushuang 已提交
361
        });
L
lang 已提交
362

S
sushuang 已提交
363
        this._updateController(mapOrGeoModel, ecModel, api);
L
lang 已提交
364

365 366
        this._updateMapSelectHandler(mapOrGeoModel, regionsGroup, api, fromView);
    }
367

368
    remove(): void {
S
sushuang 已提交
369 370
        this._regionsGroup.removeAll();
        this._backgroundGroup.removeAll();
S
sushuang 已提交
371
        this._controller.dispose();
S
sushuang 已提交
372 373
        this._mapName && geoSourceManager.removeGraphic(this._mapName, this.uid);
        this._mapName = null;
P
pissang 已提交
374
        this._controllerHost = null;
375
    }
376

377
    private _updateBackground(geo: Geo): void {
378
        const mapName = geo.map;
S
sushuang 已提交
379 380 381 382 383 384 385 386

        if (this._mapName !== mapName) {
            zrUtil.each(geoSourceManager.makeGraphic(mapName, this.uid), function (root) {
                this._backgroundGroup.add(root);
            }, this);
        }

        this._mapName = mapName;
387
    }
S
sushuang 已提交
388

389
    private _updateController(
1
100pah 已提交
390
        this: MapDraw, mapOrGeoModel: GeoModel | MapSeries, ecModel: GlobalModel, api: ExtensionAPI
391
    ): void {
392 393 394
        const geo = mapOrGeoModel.coordinateSystem;
        const controller = this._controller;
        const controllerHost = this._controllerHost;
395

396
        // @ts-ignore FIXME:TS
S
sushuang 已提交
397 398
        controllerHost.zoomLimit = mapOrGeoModel.get('scaleLimit');
        controllerHost.zoom = geo.getZoom();
P
pah100 已提交
399

S
sushuang 已提交
400
        // roamType is will be set default true if it is null
401
        // @ts-ignore FIXME:TS
S
sushuang 已提交
402
        controller.enable(mapOrGeoModel.get('roam') || false);
403
        const mainType = mapOrGeoModel.mainType;
404

405
        function makeActionBase(): Payload {
406
            const action = {
S
sushuang 已提交
407 408
                type: 'geoRoam',
                componentType: mainType
409
            } as Payload;
S
sushuang 已提交
410 411 412
            action[mainType + 'Id'] = mapOrGeoModel.id;
            return action;
        }
413

1
100pah 已提交
414
        controller.off('pan').on('pan', function (e) {
S
sushuang 已提交
415
            this._mouseDownFlag = false;
P
pah100 已提交
416

417
            roamHelper.updateViewOnPan(controllerHost, e.dx, e.dy);
418

S
sushuang 已提交
419
            api.dispatchAction(zrUtil.extend(makeActionBase(), {
420 421
                dx: e.dx,
                dy: e.dy
S
sushuang 已提交
422 423
            }));
        }, this);
L
lang 已提交
424

1
100pah 已提交
425
        controller.off('zoom').on('zoom', function (e) {
S
sushuang 已提交
426 427
            this._mouseDownFlag = false;

428
            roamHelper.updateViewOnZoom(controllerHost, e.scale, e.originX, e.originY);
S
sushuang 已提交
429 430

            api.dispatchAction(zrUtil.extend(makeActionBase(), {
431 432 433
                zoom: e.scale,
                originX: e.originX,
                originY: e.originY
S
sushuang 已提交
434 435
            }));

P
pissang 已提交
436 437
            const group = this.group;
            this._regionsGroup.traverse(function (el) {
438 439 440 441 442
                const textContent = el.getTextContent();
                if (textContent) {
                    textContent.scaleX = 1 / group.scaleX;
                    textContent.scaleY = 1 / group.scaleY;
                    textContent.markRedraw();
P
pissang 已提交
443 444
                }
            });
S
sushuang 已提交
445 446 447 448
        }, this);

        controller.setPointerChecker(function (e, x, y) {
            return geo.getViewRectAfterRoam().contain(x, y)
S
sushuang 已提交
449
                && !onIrrelevantElement(e, api, mapOrGeoModel);
S
sushuang 已提交
450 451
        });
    }
452 453 454 455 456 457 458

    private _updateMapSelectHandler(
        mapOrGeoModel: GeoModel | MapSeries,
        regionsGroup: RegionsGroup,
        api: ExtensionAPI,
        fromView: MapView | GeoView
    ): void {
459
        const mapDraw = this;
460 461 462 463 464 465 466 467 468 469 470

        regionsGroup.off('mousedown');

        // @ts-ignore FIXME:TS resolve type conflict
        if (mapOrGeoModel.get('selectedMode')) {

            regionsGroup.on('mousedown', function () {
                mapDraw._mouseDownFlag = true;
            });

            regionsGroup.on('click', function (e) {
471
                if (!mapDraw._mouseDownFlag) {
472 473
                    return;
                }
474
                mapDraw._mouseDownFlag = false;
475 476 477 478
            });
        }
    }

S
sushuang 已提交
479
};
L
lang 已提交
480

481
export default MapDraw;