467 lines
14 KiB
JavaScript
467 lines
14 KiB
JavaScript
|
|
/*
|
|
* 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.
|
|
*/
|
|
|
|
var zrUtil = require("zrender/lib/core/util");
|
|
|
|
var RoamController = require("./RoamController");
|
|
|
|
var roamHelper = require("../../component/helper/roamHelper");
|
|
|
|
var _cursorHelper = require("../../component/helper/cursorHelper");
|
|
|
|
var onIrrelevantElement = _cursorHelper.onIrrelevantElement;
|
|
|
|
var graphic = require("../../util/graphic");
|
|
|
|
var geoSourceManager = require("../../coord/geo/geoSourceManager");
|
|
|
|
var _component = require("../../util/component");
|
|
|
|
var getUID = _component.getUID;
|
|
|
|
var Transformable = require("zrender/lib/mixin/Transformable");
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
function updateMapSelectHandler(mapDraw, mapOrGeoModel, regionsGroup, api, fromView) {
|
|
regionsGroup.off('click');
|
|
regionsGroup.off('mousedown');
|
|
|
|
if (mapOrGeoModel.get('selectedMode')) {
|
|
regionsGroup.on('mousedown', function () {
|
|
mapDraw._mouseDownFlag = true;
|
|
});
|
|
regionsGroup.on('click', function (e) {
|
|
if (!mapDraw._mouseDownFlag) {
|
|
return;
|
|
}
|
|
|
|
mapDraw._mouseDownFlag = false;
|
|
var el = e.target;
|
|
|
|
while (!el.__regions) {
|
|
el = el.parent;
|
|
}
|
|
|
|
if (!el) {
|
|
return;
|
|
}
|
|
|
|
var action = {
|
|
type: (mapOrGeoModel.mainType === 'geo' ? 'geo' : 'map') + 'ToggleSelect',
|
|
batch: zrUtil.map(el.__regions, function (region) {
|
|
return {
|
|
name: region.name,
|
|
from: fromView.uid
|
|
};
|
|
})
|
|
};
|
|
action[mapOrGeoModel.mainType + 'Id'] = mapOrGeoModel.id;
|
|
api.dispatchAction(action);
|
|
updateMapSelected(mapOrGeoModel, regionsGroup);
|
|
});
|
|
}
|
|
}
|
|
|
|
function updateMapSelected(mapOrGeoModel, regionsGroup) {
|
|
// FIXME
|
|
regionsGroup.eachChild(function (otherRegionEl) {
|
|
zrUtil.each(otherRegionEl.__regions, function (region) {
|
|
otherRegionEl.trigger(mapOrGeoModel.isSelected(region.name) ? 'emphasis' : 'normal');
|
|
});
|
|
});
|
|
}
|
|
/**
|
|
* @alias module:echarts/component/helper/MapDraw
|
|
* @param {module:echarts/ExtensionAPI} api
|
|
* @param {boolean} updateGroup
|
|
*/
|
|
|
|
|
|
function MapDraw(api, updateGroup) {
|
|
var group = new graphic.Group();
|
|
/**
|
|
* @type {string}
|
|
* @private
|
|
*/
|
|
|
|
this.uid = getUID('ec_map_draw');
|
|
/**
|
|
* @type {module:echarts/component/helper/RoamController}
|
|
* @private
|
|
*/
|
|
|
|
this._controller = new RoamController(api.getZr());
|
|
/**
|
|
* @type {Object} {target, zoom, zoomLimit}
|
|
* @private
|
|
*/
|
|
|
|
this._controllerHost = {
|
|
target: updateGroup ? group : null
|
|
};
|
|
/**
|
|
* @type {module:zrender/container/Group}
|
|
* @readOnly
|
|
*/
|
|
|
|
this.group = group;
|
|
/**
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
|
|
this._updateGroup = updateGroup;
|
|
/**
|
|
* 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.
|
|
* @type {booelan}
|
|
*/
|
|
|
|
this._mouseDownFlag;
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
|
|
this._mapName;
|
|
/**
|
|
* @type {boolean}
|
|
*/
|
|
|
|
this._initialized;
|
|
/**
|
|
* @type {module:zrender/container/Group}
|
|
*/
|
|
|
|
group.add(this._regionsGroup = new graphic.Group());
|
|
/**
|
|
* @type {module:zrender/container/Group}
|
|
*/
|
|
|
|
group.add(this._backgroundGroup = new graphic.Group());
|
|
}
|
|
|
|
MapDraw.prototype = {
|
|
constructor: MapDraw,
|
|
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;
|
|
|
|
this._updateBackground(geo);
|
|
|
|
var regionsGroup = this._regionsGroup;
|
|
var group = this.group;
|
|
var transformInfo = geo.getTransformInfo(); // No animation when first draw or in action
|
|
|
|
var isFirstDraw = !regionsGroup.childAt(0) || payload;
|
|
var targetScale;
|
|
|
|
if (isFirstDraw) {
|
|
group.transform = transformInfo.roamTransform;
|
|
group.decomposeTransform();
|
|
group.dirty();
|
|
} else {
|
|
var target = new Transformable();
|
|
target.transform = transformInfo.roamTransform;
|
|
target.decomposeTransform();
|
|
var props = {
|
|
scale: target.scale,
|
|
position: target.position
|
|
};
|
|
targetScale = target.scale;
|
|
graphic.updateProps(group, props, mapOrGeoModel);
|
|
}
|
|
|
|
var scale = transformInfo.rawScale;
|
|
var position = transformInfo.rawPosition;
|
|
regionsGroup.removeAll();
|
|
var itemStyleAccessPath = ['itemStyle'];
|
|
var hoverItemStyleAccessPath = ['emphasis', 'itemStyle'];
|
|
var labelAccessPath = ['label'];
|
|
var hoverLabelAccessPath = ['emphasis', 'label'];
|
|
var nameMap = zrUtil.createHashMap();
|
|
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.
|
|
var regionGroup = nameMap.get(region.name) || nameMap.set(region.name, new graphic.Group());
|
|
var compoundPath = new graphic.CompoundPath({
|
|
segmentIgnoreThreshold: 1,
|
|
shape: {
|
|
paths: []
|
|
}
|
|
});
|
|
regionGroup.add(compoundPath);
|
|
var regionModel = mapOrGeoModel.getRegionModel(region.name) || mapOrGeoModel;
|
|
var itemStyleModel = regionModel.getModel(itemStyleAccessPath);
|
|
var hoverItemStyleModel = regionModel.getModel(hoverItemStyleAccessPath);
|
|
var itemStyle = getFixedItemStyle(itemStyleModel);
|
|
var hoverItemStyle = getFixedItemStyle(hoverItemStyleModel);
|
|
var labelModel = regionModel.getModel(labelAccessPath);
|
|
var hoverLabelModel = regionModel.getModel(hoverLabelAccessPath);
|
|
var dataIdx; // Use the itemStyle in data if has data
|
|
|
|
if (data) {
|
|
dataIdx = data.indexOfName(region.name); // Only visual color of each item will be used. It can be encoded by dataRange
|
|
// But visual color of series is used in symbol drawing
|
|
//
|
|
// Visual color for each series is for the symbol draw
|
|
|
|
var visualColor = data.getItemVisual(dataIdx, 'color', true);
|
|
|
|
if (visualColor) {
|
|
itemStyle.fill = visualColor;
|
|
}
|
|
}
|
|
|
|
var transformPoint = function (point) {
|
|
return [point[0] * scale[0] + position[0], point[1] * scale[1] + position[1]];
|
|
};
|
|
|
|
zrUtil.each(region.geometries, function (geometry) {
|
|
if (geometry.type !== 'polygon') {
|
|
return;
|
|
}
|
|
|
|
var points = [];
|
|
|
|
for (var i = 0; i < geometry.exterior.length; ++i) {
|
|
points.push(transformPoint(geometry.exterior[i]));
|
|
}
|
|
|
|
compoundPath.shape.paths.push(new graphic.Polygon({
|
|
segmentIgnoreThreshold: 1,
|
|
shape: {
|
|
points: points
|
|
}
|
|
}));
|
|
|
|
for (var i = 0; i < (geometry.interiors ? geometry.interiors.length : 0); ++i) {
|
|
var interior = geometry.interiors[i];
|
|
var points = [];
|
|
|
|
for (var j = 0; j < interior.length; ++j) {
|
|
points.push(transformPoint(interior[j]));
|
|
}
|
|
|
|
compoundPath.shape.paths.push(new graphic.Polygon({
|
|
segmentIgnoreThreshold: 1,
|
|
shape: {
|
|
points: points
|
|
}
|
|
}));
|
|
}
|
|
});
|
|
compoundPath.setStyle(itemStyle);
|
|
compoundPath.style.strokeNoScale = true;
|
|
compoundPath.culling = true; // Label
|
|
|
|
var showLabel = labelModel.get('show');
|
|
var hoverShowLabel = hoverLabelModel.get('show');
|
|
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
|
|
// 4. Region has no series legendSymbol, which will be add a showLabel flag in mapSymbolLayout
|
|
|
|
if (isGeo || isDataNaN && (showLabel || hoverShowLabel) || itemLayout && itemLayout.showLabel) {
|
|
var query = !isGeo ? dataIdx : region.name;
|
|
var labelFetcher; // Consider dataIdx not found.
|
|
|
|
if (!data || dataIdx >= 0) {
|
|
labelFetcher = mapOrGeoModel;
|
|
}
|
|
|
|
var textEl = new graphic.Text({
|
|
position: transformPoint(region.center.slice()),
|
|
// 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?
|
|
scale: [1 / group.scale[0], 1 / group.scale[1]],
|
|
z2: 10,
|
|
silent: true
|
|
});
|
|
graphic.setLabelStyle(textEl.style, textEl.hoverStyle = {}, labelModel, hoverLabelModel, {
|
|
labelFetcher: labelFetcher,
|
|
labelDataIndex: query,
|
|
defaultText: region.name,
|
|
useInsideStyle: false
|
|
}, {
|
|
textAlign: 'center',
|
|
textVerticalAlign: 'middle'
|
|
});
|
|
|
|
if (!isFirstDraw) {
|
|
// Text animation
|
|
var textScale = [1 / targetScale[0], 1 / targetScale[1]];
|
|
graphic.updateProps(textEl, {
|
|
scale: textScale
|
|
}, mapOrGeoModel);
|
|
}
|
|
|
|
regionGroup.add(textEl);
|
|
} // setItemGraphicEl, setHoverStyle after all polygons and labels
|
|
// are added to the rigionGroup
|
|
|
|
|
|
if (data) {
|
|
data.setItemGraphicEl(dataIdx, regionGroup);
|
|
} else {
|
|
var regionModel = mapOrGeoModel.getRegionModel(region.name); // Package custom mouse event for geo component
|
|
|
|
compoundPath.eventData = {
|
|
componentType: 'geo',
|
|
componentIndex: mapOrGeoModel.componentIndex,
|
|
geoIndex: mapOrGeoModel.componentIndex,
|
|
name: region.name,
|
|
region: regionModel && regionModel.option || {}
|
|
};
|
|
}
|
|
|
|
var groupRegions = regionGroup.__regions || (regionGroup.__regions = []);
|
|
groupRegions.push(region);
|
|
regionGroup.highDownSilentOnTouch = !!mapOrGeoModel.get('selectedMode');
|
|
graphic.setHoverStyle(regionGroup, hoverItemStyle);
|
|
regionsGroup.add(regionGroup);
|
|
});
|
|
|
|
this._updateController(mapOrGeoModel, ecModel, api);
|
|
|
|
updateMapSelectHandler(this, mapOrGeoModel, regionsGroup, api, fromView);
|
|
updateMapSelected(mapOrGeoModel, regionsGroup);
|
|
},
|
|
remove: function () {
|
|
this._regionsGroup.removeAll();
|
|
|
|
this._backgroundGroup.removeAll();
|
|
|
|
this._controller.dispose();
|
|
|
|
this._mapName && geoSourceManager.removeGraphic(this._mapName, this.uid);
|
|
this._mapName = null;
|
|
this._controllerHost = {};
|
|
},
|
|
_updateBackground: function (geo) {
|
|
var mapName = geo.map;
|
|
|
|
if (this._mapName !== mapName) {
|
|
zrUtil.each(geoSourceManager.makeGraphic(mapName, this.uid), function (root) {
|
|
this._backgroundGroup.add(root);
|
|
}, this);
|
|
}
|
|
|
|
this._mapName = mapName;
|
|
},
|
|
_updateController: function (mapOrGeoModel, ecModel, api) {
|
|
var geo = mapOrGeoModel.coordinateSystem;
|
|
var controller = this._controller;
|
|
var controllerHost = this._controllerHost;
|
|
controllerHost.zoomLimit = mapOrGeoModel.get('scaleLimit');
|
|
controllerHost.zoom = geo.getZoom(); // roamType is will be set default true if it is null
|
|
|
|
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
|
|
}));
|
|
}, 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(), {
|
|
zoom: e.scale,
|
|
originX: e.originX,
|
|
originY: e.originY
|
|
}));
|
|
|
|
if (this._updateGroup) {
|
|
var scale = this.group.scale;
|
|
|
|
this._regionsGroup.traverse(function (el) {
|
|
if (el.type === 'text') {
|
|
el.attr('scale', [1 / scale[0], 1 / scale[1]]);
|
|
}
|
|
});
|
|
}
|
|
}, this);
|
|
controller.setPointerChecker(function (e, x, y) {
|
|
return geo.getViewRectAfterRoam().contain(x, y) && !onIrrelevantElement(e, api, mapOrGeoModel);
|
|
});
|
|
}
|
|
};
|
|
var _default = MapDraw;
|
|
module.exports = _default; |