MapDraw.ts 17.7 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
import { getECData } from '../../util/innerStore';
41 42 43 44 45


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

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

    // 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 已提交
55 56
    }

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

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

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

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

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


S
sushuang 已提交
75 76 77 78 79
    /**
     * 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.
     */
80
    private _mouseDownFlag: boolean;
S
sushuang 已提交
81

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

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

86 87 88
    private _regionsGroup: RegionsGroup;

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

90

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

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

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

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

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

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

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

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

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

        // 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 已提交
154

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

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

159

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

S
sushuang 已提交
164 165 166 167 168 169
        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.
170
            const regionGroup = nameMap.get(region.name)
171
                || nameMap.set(region.name, new graphic.Group() as RegionsGroup);
S
sushuang 已提交
172

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

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

183
            // @ts-ignore FIXME:TS fix the "compatible with each other"?
184
            const itemStyleModel = regionModel.getModel('itemStyle');
185
            // @ts-ignore FIXME:TS fix the "compatible with each other"?
P
pissang 已提交
186 187
            const emphasisModel = regionModel.getModel('emphasis');
            const emphasisItemStyleModel = emphasisModel.getModel('itemStyle');
188 189 190 191
            // @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']);
192 193 194

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

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

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

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

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

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

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

P
pissang 已提交
263 264 265 266 267 268 269 270 271 272 273
            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 已提交
274

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

S
sushuang 已提交
278 279 280 281 282
            // 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 已提交
283
                (isGeo || isDataNaN && (showLabel))
S
sushuang 已提交
284
                || (itemLayout && itemLayout.showLabel)
285
            ) {
286
                const query = !isGeo ? dataIdx : region.name;
287
                let labelFetcher;
S
sushuang 已提交
288 289 290 291 292 293

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

294
                const centerPt = transformPoint(region.center);
295
                const textEl = new graphic.Text({
296 297
                    x: centerPt[0],
                    y: centerPt[1],
298 299 300 301
                    // 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?
302 303
                    scaleX: 1 / group.scaleX,
                    scaleY: 1 / group.scaleY,
S
sushuang 已提交
304 305
                    z2: 10,
                    silent: true
L
lang 已提交
306 307
                });

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        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) {
472
                if (!mapDraw._mouseDownFlag) {
473 474
                    return;
                }
475
                mapDraw._mouseDownFlag = false;
476 477 478 479
            });
        }
    }

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

482
export default MapDraw;