You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
674 lines
27 KiB
674 lines
27 KiB
1 month ago
|
|
||
|
/*
|
||
|
* 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.
|
||
|
*/
|
||
|
|
||
|
|
||
|
/**
|
||
|
* AUTO-GENERATED FILE. DO NOT MODIFY.
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* 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/lib/core/util.js';
|
||
|
import RoamController from './RoamController.js';
|
||
|
import * as roamHelper from '../../component/helper/roamHelper.js';
|
||
|
import { onIrrelevantElement } from '../../component/helper/cursorHelper.js';
|
||
|
import * as graphic from '../../util/graphic.js';
|
||
|
import { toggleHoverEmphasis, enableComponentHighDownFeatures, setDefaultStateProxy } from '../../util/states.js';
|
||
|
import geoSourceManager from '../../coord/geo/geoSourceManager.js';
|
||
|
import { getUID } from '../../util/component.js';
|
||
|
import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle.js';
|
||
|
import { getECData } from '../../util/innerStore.js';
|
||
|
import { createOrUpdatePatternFromDecal } from '../../util/decal.js';
|
||
|
import Displayable from 'zrender/lib/graphic/Displayable.js';
|
||
|
import { makeInner } from '../../util/model.js';
|
||
|
/**
|
||
|
* Only these tags enable use `itemStyle` if they are named in SVG.
|
||
|
* Other tags like <text> <tspan> <image> might not suitable for `itemStyle`.
|
||
|
* They will not be considered to be styled until some requirements come.
|
||
|
*/
|
||
|
var OPTION_STYLE_ENABLED_TAGS = ['rect', 'circle', 'line', 'ellipse', 'polygon', 'polyline', 'path'];
|
||
|
var OPTION_STYLE_ENABLED_TAG_MAP = zrUtil.createHashMap(OPTION_STYLE_ENABLED_TAGS);
|
||
|
var STATE_TRIGGER_TAG_MAP = zrUtil.createHashMap(OPTION_STYLE_ENABLED_TAGS.concat(['g']));
|
||
|
var LABEL_HOST_MAP = zrUtil.createHashMap(OPTION_STYLE_ENABLED_TAGS.concat(['g']));
|
||
|
var mapLabelRaw = makeInner();
|
||
|
function getFixedItemStyle(model) {
|
||
|
var itemStyle = model.getItemStyle();
|
||
|
var areaColor = model.get('areaColor');
|
||
|
// 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;
|
||
|
}
|
||
|
return itemStyle;
|
||
|
}
|
||
|
// Only stroke can be used for line.
|
||
|
// Using fill in style if stroke not exits.
|
||
|
// TODO Not sure yet. Perhaps a separate `lineStyle`?
|
||
|
function fixLineStyle(styleHost) {
|
||
|
var style = styleHost.style;
|
||
|
if (style) {
|
||
|
style.stroke = style.stroke || style.fill;
|
||
|
style.fill = null;
|
||
|
}
|
||
|
}
|
||
|
var MapDraw = /** @class */function () {
|
||
|
function MapDraw(api) {
|
||
|
var group = new graphic.Group();
|
||
|
this.uid = getUID('ec_map_draw');
|
||
|
this._controller = new RoamController(api.getZr());
|
||
|
this._controllerHost = {
|
||
|
target: group
|
||
|
};
|
||
|
this.group = group;
|
||
|
group.add(this._regionsGroup = new graphic.Group());
|
||
|
group.add(this._svgGroup = new graphic.Group());
|
||
|
}
|
||
|
MapDraw.prototype.draw = function (mapOrGeoModel, ecModel, api, fromView, payload) {
|
||
|
var isGeo = mapOrGeoModel.mainType === 'geo';
|
||
|
// Map series has data. GEO model that controlled by map series
|
||
|
// will be assigned with map data. Other GEO model has no data.
|
||
|
var data = mapOrGeoModel.getData && mapOrGeoModel.getData();
|
||
|
isGeo && ecModel.eachComponent({
|
||
|
mainType: 'series',
|
||
|
subType: 'map'
|
||
|
}, function (mapSeries) {
|
||
|
if (!data && mapSeries.getHostGeoModel() === mapOrGeoModel) {
|
||
|
data = mapSeries.getData();
|
||
|
}
|
||
|
});
|
||
|
var geo = mapOrGeoModel.coordinateSystem;
|
||
|
var regionsGroup = this._regionsGroup;
|
||
|
var group = this.group;
|
||
|
var transformInfo = geo.getTransformInfo();
|
||
|
var transformInfoRaw = transformInfo.raw;
|
||
|
var transformInfoRoam = transformInfo.roam;
|
||
|
// No animation when first draw or in action
|
||
|
var isFirstDraw = !regionsGroup.childAt(0) || payload;
|
||
|
if (isFirstDraw) {
|
||
|
group.x = transformInfoRoam.x;
|
||
|
group.y = transformInfoRoam.y;
|
||
|
group.scaleX = transformInfoRoam.scaleX;
|
||
|
group.scaleY = transformInfoRoam.scaleY;
|
||
|
group.dirty();
|
||
|
} else {
|
||
|
graphic.updateProps(group, transformInfoRoam, mapOrGeoModel);
|
||
|
}
|
||
|
var isVisualEncodedByVisualMap = data && data.getVisual('visualMeta') && data.getVisual('visualMeta').length > 0;
|
||
|
var viewBuildCtx = {
|
||
|
api: api,
|
||
|
geo: geo,
|
||
|
mapOrGeoModel: mapOrGeoModel,
|
||
|
data: data,
|
||
|
isVisualEncodedByVisualMap: isVisualEncodedByVisualMap,
|
||
|
isGeo: isGeo,
|
||
|
transformInfoRaw: transformInfoRaw
|
||
|
};
|
||
|
if (geo.resourceType === 'geoJSON') {
|
||
|
this._buildGeoJSON(viewBuildCtx);
|
||
|
} else if (geo.resourceType === 'geoSVG') {
|
||
|
this._buildSVG(viewBuildCtx);
|
||
|
}
|
||
|
this._updateController(mapOrGeoModel, ecModel, api);
|
||
|
this._updateMapSelectHandler(mapOrGeoModel, regionsGroup, api, fromView);
|
||
|
};
|
||
|
MapDraw.prototype._buildGeoJSON = function (viewBuildCtx) {
|
||
|
var regionsGroupByName = this._regionsGroupByName = zrUtil.createHashMap();
|
||
|
var regionsInfoByName = zrUtil.createHashMap();
|
||
|
var regionsGroup = this._regionsGroup;
|
||
|
var transformInfoRaw = viewBuildCtx.transformInfoRaw;
|
||
|
var mapOrGeoModel = viewBuildCtx.mapOrGeoModel;
|
||
|
var data = viewBuildCtx.data;
|
||
|
var projection = viewBuildCtx.geo.projection;
|
||
|
var projectionStream = projection && projection.stream;
|
||
|
function transformPoint(point, project) {
|
||
|
if (project) {
|
||
|
// projection may return null point.
|
||
|
point = project(point);
|
||
|
}
|
||
|
return point && [point[0] * transformInfoRaw.scaleX + transformInfoRaw.x, point[1] * transformInfoRaw.scaleY + transformInfoRaw.y];
|
||
|
}
|
||
|
;
|
||
|
function transformPolygonPoints(inPoints) {
|
||
|
var outPoints = [];
|
||
|
// If projectionStream is provided. Use it instead of single point project.
|
||
|
var project = !projectionStream && projection && projection.project;
|
||
|
for (var i = 0; i < inPoints.length; ++i) {
|
||
|
var newPt = transformPoint(inPoints[i], project);
|
||
|
newPt && outPoints.push(newPt);
|
||
|
}
|
||
|
return outPoints;
|
||
|
}
|
||
|
function getPolyShape(points) {
|
||
|
return {
|
||
|
shape: {
|
||
|
points: transformPolygonPoints(points)
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
regionsGroup.removeAll();
|
||
|
// Only when the resource is GeoJSON, there is `geo.regions`.
|
||
|
zrUtil.each(viewBuildCtx.geo.regions, function (region) {
|
||
|
var regionName = region.name;
|
||
|
// 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.
|
||
|
var regionGroup = regionsGroupByName.get(regionName);
|
||
|
var _a = regionsInfoByName.get(regionName) || {},
|
||
|
dataIdx = _a.dataIdx,
|
||
|
regionModel = _a.regionModel;
|
||
|
if (!regionGroup) {
|
||
|
regionGroup = regionsGroupByName.set(regionName, new graphic.Group());
|
||
|
regionsGroup.add(regionGroup);
|
||
|
dataIdx = data ? data.indexOfName(regionName) : null;
|
||
|
regionModel = viewBuildCtx.isGeo ? mapOrGeoModel.getRegionModel(regionName) : data ? data.getItemModel(dataIdx) : null;
|
||
|
regionsInfoByName.set(regionName, {
|
||
|
dataIdx: dataIdx,
|
||
|
regionModel: regionModel
|
||
|
});
|
||
|
}
|
||
|
var polygonSubpaths = [];
|
||
|
var polylineSubpaths = [];
|
||
|
zrUtil.each(region.geometries, function (geometry) {
|
||
|
// Polygon and MultiPolygon
|
||
|
if (geometry.type === 'polygon') {
|
||
|
var polys = [geometry.exterior].concat(geometry.interiors || []);
|
||
|
if (projectionStream) {
|
||
|
polys = projectPolys(polys, projectionStream);
|
||
|
}
|
||
|
zrUtil.each(polys, function (poly) {
|
||
|
polygonSubpaths.push(new graphic.Polygon(getPolyShape(poly)));
|
||
|
});
|
||
|
}
|
||
|
// LineString and MultiLineString
|
||
|
else {
|
||
|
var points = geometry.points;
|
||
|
if (projectionStream) {
|
||
|
points = projectPolys(points, projectionStream, true);
|
||
|
}
|
||
|
zrUtil.each(points, function (points) {
|
||
|
polylineSubpaths.push(new graphic.Polyline(getPolyShape(points)));
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
var centerPt = transformPoint(region.getCenter(), projection && projection.project);
|
||
|
function createCompoundPath(subpaths, isLine) {
|
||
|
if (!subpaths.length) {
|
||
|
return;
|
||
|
}
|
||
|
var compoundPath = new graphic.CompoundPath({
|
||
|
culling: true,
|
||
|
segmentIgnoreThreshold: 1,
|
||
|
shape: {
|
||
|
paths: subpaths
|
||
|
}
|
||
|
});
|
||
|
regionGroup.add(compoundPath);
|
||
|
applyOptionStyleForRegion(viewBuildCtx, compoundPath, dataIdx, regionModel);
|
||
|
resetLabelForRegion(viewBuildCtx, compoundPath, regionName, regionModel, mapOrGeoModel, dataIdx, centerPt);
|
||
|
if (isLine) {
|
||
|
fixLineStyle(compoundPath);
|
||
|
zrUtil.each(compoundPath.states, fixLineStyle);
|
||
|
}
|
||
|
}
|
||
|
createCompoundPath(polygonSubpaths);
|
||
|
createCompoundPath(polylineSubpaths, true);
|
||
|
});
|
||
|
// Ensure children have been added to `regionGroup` before calling them.
|
||
|
regionsGroupByName.each(function (regionGroup, regionName) {
|
||
|
var _a = regionsInfoByName.get(regionName),
|
||
|
dataIdx = _a.dataIdx,
|
||
|
regionModel = _a.regionModel;
|
||
|
resetEventTriggerForRegion(viewBuildCtx, regionGroup, regionName, regionModel, mapOrGeoModel, dataIdx);
|
||
|
resetTooltipForRegion(viewBuildCtx, regionGroup, regionName, regionModel, mapOrGeoModel);
|
||
|
resetStateTriggerForRegion(viewBuildCtx, regionGroup, regionName, regionModel, mapOrGeoModel);
|
||
|
}, this);
|
||
|
};
|
||
|
MapDraw.prototype._buildSVG = function (viewBuildCtx) {
|
||
|
var mapName = viewBuildCtx.geo.map;
|
||
|
var transformInfoRaw = viewBuildCtx.transformInfoRaw;
|
||
|
this._svgGroup.x = transformInfoRaw.x;
|
||
|
this._svgGroup.y = transformInfoRaw.y;
|
||
|
this._svgGroup.scaleX = transformInfoRaw.scaleX;
|
||
|
this._svgGroup.scaleY = transformInfoRaw.scaleY;
|
||
|
if (this._svgResourceChanged(mapName)) {
|
||
|
this._freeSVG();
|
||
|
this._useSVG(mapName);
|
||
|
}
|
||
|
var svgDispatcherMap = this._svgDispatcherMap = zrUtil.createHashMap();
|
||
|
var focusSelf = false;
|
||
|
zrUtil.each(this._svgGraphicRecord.named, function (namedItem) {
|
||
|
// Note that we also allow different elements have the same name.
|
||
|
// For example, a glyph of a city and the label of the city have
|
||
|
// the same name and their tooltip info can be defined in a single
|
||
|
// region option.
|
||
|
var regionName = namedItem.name;
|
||
|
var mapOrGeoModel = viewBuildCtx.mapOrGeoModel;
|
||
|
var data = viewBuildCtx.data;
|
||
|
var svgNodeTagLower = namedItem.svgNodeTagLower;
|
||
|
var el = namedItem.el;
|
||
|
var dataIdx = data ? data.indexOfName(regionName) : null;
|
||
|
var regionModel = mapOrGeoModel.getRegionModel(regionName);
|
||
|
if (OPTION_STYLE_ENABLED_TAG_MAP.get(svgNodeTagLower) != null && el instanceof Displayable) {
|
||
|
applyOptionStyleForRegion(viewBuildCtx, el, dataIdx, regionModel);
|
||
|
}
|
||
|
if (el instanceof Displayable) {
|
||
|
el.culling = true;
|
||
|
}
|
||
|
// We do not know how the SVG like so we'd better not to change z2.
|
||
|
// Otherwise it might bring some unexpected result. For example,
|
||
|
// an area hovered that make some inner city can not be clicked.
|
||
|
el.z2EmphasisLift = 0;
|
||
|
// If self named:
|
||
|
if (!namedItem.namedFrom) {
|
||
|
// label should batter to be displayed based on the center of <g>
|
||
|
// if it is named rather than displayed on each child.
|
||
|
if (LABEL_HOST_MAP.get(svgNodeTagLower) != null) {
|
||
|
resetLabelForRegion(viewBuildCtx, el, regionName, regionModel, mapOrGeoModel, dataIdx, null);
|
||
|
}
|
||
|
resetEventTriggerForRegion(viewBuildCtx, el, regionName, regionModel, mapOrGeoModel, dataIdx);
|
||
|
resetTooltipForRegion(viewBuildCtx, el, regionName, regionModel, mapOrGeoModel);
|
||
|
if (STATE_TRIGGER_TAG_MAP.get(svgNodeTagLower) != null) {
|
||
|
var focus_1 = resetStateTriggerForRegion(viewBuildCtx, el, regionName, regionModel, mapOrGeoModel);
|
||
|
if (focus_1 === 'self') {
|
||
|
focusSelf = true;
|
||
|
}
|
||
|
var els = svgDispatcherMap.get(regionName) || svgDispatcherMap.set(regionName, []);
|
||
|
els.push(el);
|
||
|
}
|
||
|
}
|
||
|
}, this);
|
||
|
this._enableBlurEntireSVG(focusSelf, viewBuildCtx);
|
||
|
};
|
||
|
MapDraw.prototype._enableBlurEntireSVG = function (focusSelf, viewBuildCtx) {
|
||
|
// It's a little complicated to support blurring the entire geoSVG in series-map.
|
||
|
// So do not support it until some requirements come.
|
||
|
// At present, in series-map, only regions can be blurred.
|
||
|
if (focusSelf && viewBuildCtx.isGeo) {
|
||
|
var blurStyle = viewBuildCtx.mapOrGeoModel.getModel(['blur', 'itemStyle']).getItemStyle();
|
||
|
// Only support `opacity` here. Because not sure that other props are suitable for
|
||
|
// all of the elements generated by SVG (especially for Text/TSpan/Image/... ).
|
||
|
var opacity_1 = blurStyle.opacity;
|
||
|
this._svgGraphicRecord.root.traverse(function (el) {
|
||
|
if (!el.isGroup) {
|
||
|
// PENDING: clear those settings to SVG elements when `_freeSVG`.
|
||
|
// (Currently it happen not to be needed.)
|
||
|
setDefaultStateProxy(el);
|
||
|
var style = el.ensureState('blur').style || {};
|
||
|
// Do not overwrite the region style that already set from region option.
|
||
|
if (style.opacity == null && opacity_1 != null) {
|
||
|
style.opacity = opacity_1;
|
||
|
}
|
||
|
// If `ensureState('blur').style = {}`, there will be default opacity.
|
||
|
// Enable `stateTransition` (animation).
|
||
|
el.ensureState('emphasis');
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
MapDraw.prototype.remove = function () {
|
||
|
this._regionsGroup.removeAll();
|
||
|
this._regionsGroupByName = null;
|
||
|
this._svgGroup.removeAll();
|
||
|
this._freeSVG();
|
||
|
this._controller.dispose();
|
||
|
this._controllerHost = null;
|
||
|
};
|
||
|
MapDraw.prototype.findHighDownDispatchers = function (name, geoModel) {
|
||
|
if (name == null) {
|
||
|
return [];
|
||
|
}
|
||
|
var geo = geoModel.coordinateSystem;
|
||
|
if (geo.resourceType === 'geoJSON') {
|
||
|
var regionsGroupByName = this._regionsGroupByName;
|
||
|
if (regionsGroupByName) {
|
||
|
var regionGroup = regionsGroupByName.get(name);
|
||
|
return regionGroup ? [regionGroup] : [];
|
||
|
}
|
||
|
} else if (geo.resourceType === 'geoSVG') {
|
||
|
return this._svgDispatcherMap && this._svgDispatcherMap.get(name) || [];
|
||
|
}
|
||
|
};
|
||
|
MapDraw.prototype._svgResourceChanged = function (mapName) {
|
||
|
return this._svgMapName !== mapName;
|
||
|
};
|
||
|
MapDraw.prototype._useSVG = function (mapName) {
|
||
|
var resource = geoSourceManager.getGeoResource(mapName);
|
||
|
if (resource && resource.type === 'geoSVG') {
|
||
|
var svgGraphic = resource.useGraphic(this.uid);
|
||
|
this._svgGroup.add(svgGraphic.root);
|
||
|
this._svgGraphicRecord = svgGraphic;
|
||
|
this._svgMapName = mapName;
|
||
|
}
|
||
|
};
|
||
|
MapDraw.prototype._freeSVG = function () {
|
||
|
var mapName = this._svgMapName;
|
||
|
if (mapName == null) {
|
||
|
return;
|
||
|
}
|
||
|
var resource = geoSourceManager.getGeoResource(mapName);
|
||
|
if (resource && resource.type === 'geoSVG') {
|
||
|
resource.freeGraphic(this.uid);
|
||
|
}
|
||
|
this._svgGraphicRecord = null;
|
||
|
this._svgDispatcherMap = null;
|
||
|
this._svgGroup.removeAll();
|
||
|
this._svgMapName = null;
|
||
|
};
|
||
|
MapDraw.prototype._updateController = function (mapOrGeoModel, ecModel, api) {
|
||
|
var geo = mapOrGeoModel.coordinateSystem;
|
||
|
var controller = this._controller;
|
||
|
var controllerHost = this._controllerHost;
|
||
|
// @ts-ignore FIXME:TS
|
||
|
controllerHost.zoomLimit = mapOrGeoModel.get('scaleLimit');
|
||
|
controllerHost.zoom = geo.getZoom();
|
||
|
// roamType is will be set default true if it is null
|
||
|
// @ts-ignore FIXME:TS
|
||
|
controller.enable(mapOrGeoModel.get('roam') || false);
|
||
|
var mainType = mapOrGeoModel.mainType;
|
||
|
function makeActionBase() {
|
||
|
var action = {
|
||
|
type: 'geoRoam',
|
||
|
componentType: mainType
|
||
|
};
|
||
|
action[mainType + 'Id'] = mapOrGeoModel.id;
|
||
|
return action;
|
||
|
}
|
||
|
controller.off('pan').on('pan', function (e) {
|
||
|
this._mouseDownFlag = false;
|
||
|
roamHelper.updateViewOnPan(controllerHost, e.dx, e.dy);
|
||
|
api.dispatchAction(zrUtil.extend(makeActionBase(), {
|
||
|
dx: e.dx,
|
||
|
dy: e.dy,
|
||
|
animation: {
|
||
|
duration: 0
|
||
|
}
|
||
|
}));
|
||
|
}, this);
|
||
|
controller.off('zoom').on('zoom', function (e) {
|
||
|
this._mouseDownFlag = false;
|
||
|
roamHelper.updateViewOnZoom(controllerHost, e.scale, e.originX, e.originY);
|
||
|
api.dispatchAction(zrUtil.extend(makeActionBase(), {
|
||
|
totalZoom: controllerHost.zoom,
|
||
|
zoom: e.scale,
|
||
|
originX: e.originX,
|
||
|
originY: e.originY,
|
||
|
animation: {
|
||
|
duration: 0
|
||
|
}
|
||
|
}));
|
||
|
}, this);
|
||
|
controller.setPointerChecker(function (e, x, y) {
|
||
|
return geo.containPoint([x, y]) && !onIrrelevantElement(e, api, mapOrGeoModel);
|
||
|
});
|
||
|
};
|
||
|
/**
|
||
|
* FIXME: this is a temporarily workaround.
|
||
|
* When `geoRoam` the elements need to be reset in `MapView['render']`, because the props like
|
||
|
* `ignore` might have been modified by `LabelManager`, and `LabelManager#addLabelsOfSeries`
|
||
|
* will subsequently cache `defaultAttr` like `ignore`. If do not do this reset, the modified
|
||
|
* props will have no chance to be restored.
|
||
|
* Note: This reset should be after `clearStates` in `renderSeries` because `useStates` in
|
||
|
* `renderSeries` will cache the modified `ignore` to `el._normalState`.
|
||
|
* TODO:
|
||
|
* Use clone/immutable in `LabelManager`?
|
||
|
*/
|
||
|
MapDraw.prototype.resetForLabelLayout = function () {
|
||
|
this.group.traverse(function (el) {
|
||
|
var label = el.getTextContent();
|
||
|
if (label) {
|
||
|
label.ignore = mapLabelRaw(label).ignore;
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
MapDraw.prototype._updateMapSelectHandler = function (mapOrGeoModel, regionsGroup, api, fromView) {
|
||
|
var mapDraw = this;
|
||
|
regionsGroup.off('mousedown');
|
||
|
regionsGroup.off('click');
|
||
|
// @ts-ignore FIXME:TS resolve type conflict
|
||
|
if (mapOrGeoModel.get('selectedMode')) {
|
||
|
regionsGroup.on('mousedown', function () {
|
||
|
mapDraw._mouseDownFlag = true;
|
||
|
});
|
||
|
regionsGroup.on('click', function (e) {
|
||
|
if (!mapDraw._mouseDownFlag) {
|
||
|
return;
|
||
|
}
|
||
|
mapDraw._mouseDownFlag = false;
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
return MapDraw;
|
||
|
}();
|
||
|
;
|
||
|
function applyOptionStyleForRegion(viewBuildCtx, el, dataIndex, regionModel) {
|
||
|
// All of the path are using `itemStyle`, because
|
||
|
// (1) Some SVG also use fill on polyline (The different between
|
||
|
// polyline and polygon is "open" or "close" but not fill or not).
|
||
|
// (2) For the common props like opacity, if some use itemStyle
|
||
|
// and some use `lineStyle`, it might confuse users.
|
||
|
// (3) Most SVG use <path>, where can not detect whether to draw a "line"
|
||
|
// or a filled shape, so use `itemStyle` for <path>.
|
||
|
var normalStyleModel = regionModel.getModel('itemStyle');
|
||
|
var emphasisStyleModel = regionModel.getModel(['emphasis', 'itemStyle']);
|
||
|
var blurStyleModel = regionModel.getModel(['blur', 'itemStyle']);
|
||
|
var selectStyleModel = regionModel.getModel(['select', 'itemStyle']);
|
||
|
// NOTE: DON'T use 'style' in visual when drawing map.
|
||
|
// This component is used for drawing underlying map for both geo component and map series.
|
||
|
var normalStyle = getFixedItemStyle(normalStyleModel);
|
||
|
var emphasisStyle = getFixedItemStyle(emphasisStyleModel);
|
||
|
var selectStyle = getFixedItemStyle(selectStyleModel);
|
||
|
var blurStyle = getFixedItemStyle(blurStyleModel);
|
||
|
// Update the itemStyle if has data visual
|
||
|
var data = viewBuildCtx.data;
|
||
|
if (data) {
|
||
|
// Only visual color of each item will be used. It can be encoded by visualMap
|
||
|
// But visual color of series is used in symbol drawing
|
||
|
// Visual color for each series is for the symbol draw
|
||
|
var style = data.getItemVisual(dataIndex, 'style');
|
||
|
var decal = data.getItemVisual(dataIndex, 'decal');
|
||
|
if (viewBuildCtx.isVisualEncodedByVisualMap && style.fill) {
|
||
|
normalStyle.fill = style.fill;
|
||
|
}
|
||
|
if (decal) {
|
||
|
normalStyle.decal = createOrUpdatePatternFromDecal(decal, viewBuildCtx.api);
|
||
|
}
|
||
|
}
|
||
|
// SVG text, tspan and image can be named but not supporeted
|
||
|
// to be styled by region option yet.
|
||
|
el.setStyle(normalStyle);
|
||
|
el.style.strokeNoScale = true;
|
||
|
el.ensureState('emphasis').style = emphasisStyle;
|
||
|
el.ensureState('select').style = selectStyle;
|
||
|
el.ensureState('blur').style = blurStyle;
|
||
|
// Enable blur
|
||
|
setDefaultStateProxy(el);
|
||
|
}
|
||
|
function resetLabelForRegion(viewBuildCtx, el, regionName, regionModel, mapOrGeoModel,
|
||
|
// Exist only if `viewBuildCtx.data` exists.
|
||
|
dataIdx,
|
||
|
// If labelXY not provided, use `textConfig.position: 'inside'`
|
||
|
labelXY) {
|
||
|
var data = viewBuildCtx.data;
|
||
|
var isGeo = viewBuildCtx.isGeo;
|
||
|
var isDataNaN = data && isNaN(data.get(data.mapDimension('value'), dataIdx));
|
||
|
var itemLayout = data && data.getItemLayout(dataIdx);
|
||
|
// In the following cases label will be drawn
|
||
|
// 1. In map series and data value is NaN
|
||
|
// 2. In geo component
|
||
|
// 3. Region has no series legendIcon, which will be add a showLabel flag in mapSymbolLayout
|
||
|
if (isGeo || isDataNaN || itemLayout && itemLayout.showLabel) {
|
||
|
var query = !isGeo ? dataIdx : regionName;
|
||
|
var labelFetcher = void 0;
|
||
|
// Consider dataIdx not found.
|
||
|
if (!data || dataIdx >= 0) {
|
||
|
labelFetcher = mapOrGeoModel;
|
||
|
}
|
||
|
var specifiedTextOpt = labelXY ? {
|
||
|
normal: {
|
||
|
align: 'center',
|
||
|
verticalAlign: 'middle'
|
||
|
}
|
||
|
} : null;
|
||
|
// Caveat: must be called after `setDefaultStateProxy(el);` called.
|
||
|
// because textContent will be assign with `el.stateProxy` inside.
|
||
|
setLabelStyle(el, getLabelStatesModels(regionModel), {
|
||
|
labelFetcher: labelFetcher,
|
||
|
labelDataIndex: query,
|
||
|
defaultText: regionName
|
||
|
}, specifiedTextOpt);
|
||
|
var textEl = el.getTextContent();
|
||
|
if (textEl) {
|
||
|
mapLabelRaw(textEl).ignore = textEl.ignore;
|
||
|
if (el.textConfig && labelXY) {
|
||
|
// Compute a relative offset based on the el bounding rect.
|
||
|
var rect = el.getBoundingRect().clone();
|
||
|
// Need to make sure the percent position base on the same rect in normal and
|
||
|
// emphasis state. Otherwise if using boundingRect of el, but the emphasis state
|
||
|
// has borderWidth (even 0.5px), the text position will be changed obviously
|
||
|
// if the position is very big like ['1234%', '1345%'].
|
||
|
el.textConfig.layoutRect = rect;
|
||
|
el.textConfig.position = [(labelXY[0] - rect.x) / rect.width * 100 + '%', (labelXY[1] - rect.y) / rect.height * 100 + '%'];
|
||
|
}
|
||
|
}
|
||
|
// PENDING:
|
||
|
// If labelLayout is enabled (test/label-layout.html), el.dataIndex should be specified.
|
||
|
// But el.dataIndex is also used to determine whether user event should be triggered,
|
||
|
// where el.seriesIndex or el.dataModel must be specified. At present for a single el
|
||
|
// there is not case that "only label layout enabled but user event disabled", so here
|
||
|
// we depends `resetEventTriggerForRegion` to do the job of setting `el.dataIndex`.
|
||
|
el.disableLabelAnimation = true;
|
||
|
} else {
|
||
|
el.removeTextContent();
|
||
|
el.removeTextConfig();
|
||
|
el.disableLabelAnimation = null;
|
||
|
}
|
||
|
}
|
||
|
function resetEventTriggerForRegion(viewBuildCtx, eventTrigger, regionName, regionModel, mapOrGeoModel,
|
||
|
// Exist only if `viewBuildCtx.data` exists.
|
||
|
dataIdx) {
|
||
|
// setItemGraphicEl, setHoverStyle after all polygons and labels
|
||
|
// are added to the regionGroup
|
||
|
if (viewBuildCtx.data) {
|
||
|
// FIXME: when series-map use a SVG map, and there are duplicated name specified
|
||
|
// on different SVG elements, after `data.setItemGraphicEl(...)`:
|
||
|
// (1) all of them will be mounted with `dataIndex`, `seriesIndex`, so that tooltip
|
||
|
// can be triggered only mouse hover. That's correct.
|
||
|
// (2) only the last element will be kept in `data`, so that if trigger tooltip
|
||
|
// by `dispatchAction`, only the last one can be found and triggered. That might be
|
||
|
// not correct. We will fix it in future if anyone demanding that.
|
||
|
viewBuildCtx.data.setItemGraphicEl(dataIdx, eventTrigger);
|
||
|
}
|
||
|
// series-map will not trigger "geoselectchange" no matter it is
|
||
|
// based on a declared geo component. Because series-map will
|
||
|
// trigger "selectchange". If it trigger both the two events,
|
||
|
// If users call `chart.dispatchAction({type: 'toggleSelect'})`,
|
||
|
// it not easy to also fire event "geoselectchanged".
|
||
|
else {
|
||
|
// Package custom mouse event for geo component
|
||
|
getECData(eventTrigger).eventData = {
|
||
|
componentType: 'geo',
|
||
|
componentIndex: mapOrGeoModel.componentIndex,
|
||
|
geoIndex: mapOrGeoModel.componentIndex,
|
||
|
name: regionName,
|
||
|
region: regionModel && regionModel.option || {}
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
function resetTooltipForRegion(viewBuildCtx, el, regionName, regionModel, mapOrGeoModel) {
|
||
|
if (!viewBuildCtx.data) {
|
||
|
graphic.setTooltipConfig({
|
||
|
el: el,
|
||
|
componentModel: mapOrGeoModel,
|
||
|
itemName: regionName,
|
||
|
// @ts-ignore FIXME:TS fix the "compatible with each other"?
|
||
|
itemTooltipOption: regionModel.get('tooltip')
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
function resetStateTriggerForRegion(viewBuildCtx, el, regionName, regionModel, mapOrGeoModel) {
|
||
|
// @ts-ignore FIXME:TS fix the "compatible with each other"?
|
||
|
el.highDownSilentOnTouch = !!mapOrGeoModel.get('selectedMode');
|
||
|
// @ts-ignore FIXME:TS fix the "compatible with each other"?
|
||
|
var emphasisModel = regionModel.getModel('emphasis');
|
||
|
var focus = emphasisModel.get('focus');
|
||
|
toggleHoverEmphasis(el, focus, emphasisModel.get('blurScope'), emphasisModel.get('disabled'));
|
||
|
if (viewBuildCtx.isGeo) {
|
||
|
enableComponentHighDownFeatures(el, mapOrGeoModel, regionName);
|
||
|
}
|
||
|
return focus;
|
||
|
}
|
||
|
function projectPolys(rings,
|
||
|
// Polygons include exterior and interiors. Or polylines.
|
||
|
createStream, isLine) {
|
||
|
var polygons = [];
|
||
|
var curPoly;
|
||
|
function startPolygon() {
|
||
|
curPoly = [];
|
||
|
}
|
||
|
function endPolygon() {
|
||
|
if (curPoly.length) {
|
||
|
polygons.push(curPoly);
|
||
|
curPoly = [];
|
||
|
}
|
||
|
}
|
||
|
var stream = createStream({
|
||
|
polygonStart: startPolygon,
|
||
|
polygonEnd: endPolygon,
|
||
|
lineStart: startPolygon,
|
||
|
lineEnd: endPolygon,
|
||
|
point: function (x, y) {
|
||
|
// May have NaN values from stream.
|
||
|
if (isFinite(x) && isFinite(y)) {
|
||
|
curPoly.push([x, y]);
|
||
|
}
|
||
|
},
|
||
|
sphere: function () {}
|
||
|
});
|
||
|
!isLine && stream.polygonStart();
|
||
|
zrUtil.each(rings, function (ring) {
|
||
|
stream.lineStart();
|
||
|
for (var i = 0; i < ring.length; i++) {
|
||
|
stream.point(ring[i][0], ring[i][1]);
|
||
|
}
|
||
|
stream.lineEnd();
|
||
|
});
|
||
|
!isLine && stream.polygonEnd();
|
||
|
return polygons;
|
||
|
}
|
||
|
export default MapDraw;
|
||
|
// @ts-ignore FIXME:TS fix the "compatible with each other"?
|