MapDraw.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. var zrUtil = require("zrender/lib/core/util");
  20. var RoamController = require("./RoamController");
  21. var roamHelper = require("../../component/helper/roamHelper");
  22. var _cursorHelper = require("../../component/helper/cursorHelper");
  23. var onIrrelevantElement = _cursorHelper.onIrrelevantElement;
  24. var graphic = require("../../util/graphic");
  25. var geoSourceManager = require("../../coord/geo/geoSourceManager");
  26. var _component = require("../../util/component");
  27. var getUID = _component.getUID;
  28. var Transformable = require("zrender/lib/mixin/Transformable");
  29. /*
  30. * Licensed to the Apache Software Foundation (ASF) under one
  31. * or more contributor license agreements. See the NOTICE file
  32. * distributed with this work for additional information
  33. * regarding copyright ownership. The ASF licenses this file
  34. * to you under the Apache License, Version 2.0 (the
  35. * "License"); you may not use this file except in compliance
  36. * with the License. You may obtain a copy of the License at
  37. *
  38. * http://www.apache.org/licenses/LICENSE-2.0
  39. *
  40. * Unless required by applicable law or agreed to in writing,
  41. * software distributed under the License is distributed on an
  42. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  43. * KIND, either express or implied. See the License for the
  44. * specific language governing permissions and limitations
  45. * under the License.
  46. */
  47. function getFixedItemStyle(model) {
  48. var itemStyle = model.getItemStyle();
  49. var areaColor = model.get('areaColor'); // If user want the color not to be changed when hover,
  50. // they should both set areaColor and color to be null.
  51. if (areaColor != null) {
  52. itemStyle.fill = areaColor;
  53. }
  54. return itemStyle;
  55. }
  56. function updateMapSelectHandler(mapDraw, mapOrGeoModel, regionsGroup, api, fromView) {
  57. regionsGroup.off('click');
  58. regionsGroup.off('mousedown');
  59. if (mapOrGeoModel.get('selectedMode')) {
  60. regionsGroup.on('mousedown', function () {
  61. mapDraw._mouseDownFlag = true;
  62. });
  63. regionsGroup.on('click', function (e) {
  64. if (!mapDraw._mouseDownFlag) {
  65. return;
  66. }
  67. mapDraw._mouseDownFlag = false;
  68. var el = e.target;
  69. while (!el.__regions) {
  70. el = el.parent;
  71. }
  72. if (!el) {
  73. return;
  74. }
  75. var action = {
  76. type: (mapOrGeoModel.mainType === 'geo' ? 'geo' : 'map') + 'ToggleSelect',
  77. batch: zrUtil.map(el.__regions, function (region) {
  78. return {
  79. name: region.name,
  80. from: fromView.uid
  81. };
  82. })
  83. };
  84. action[mapOrGeoModel.mainType + 'Id'] = mapOrGeoModel.id;
  85. api.dispatchAction(action);
  86. updateMapSelected(mapOrGeoModel, regionsGroup);
  87. });
  88. }
  89. }
  90. function updateMapSelected(mapOrGeoModel, regionsGroup) {
  91. // FIXME
  92. regionsGroup.eachChild(function (otherRegionEl) {
  93. zrUtil.each(otherRegionEl.__regions, function (region) {
  94. otherRegionEl.trigger(mapOrGeoModel.isSelected(region.name) ? 'emphasis' : 'normal');
  95. });
  96. });
  97. }
  98. /**
  99. * @alias module:echarts/component/helper/MapDraw
  100. * @param {module:echarts/ExtensionAPI} api
  101. * @param {boolean} updateGroup
  102. */
  103. function MapDraw(api, updateGroup) {
  104. var group = new graphic.Group();
  105. /**
  106. * @type {string}
  107. * @private
  108. */
  109. this.uid = getUID('ec_map_draw');
  110. /**
  111. * @type {module:echarts/component/helper/RoamController}
  112. * @private
  113. */
  114. this._controller = new RoamController(api.getZr());
  115. /**
  116. * @type {Object} {target, zoom, zoomLimit}
  117. * @private
  118. */
  119. this._controllerHost = {
  120. target: updateGroup ? group : null
  121. };
  122. /**
  123. * @type {module:zrender/container/Group}
  124. * @readOnly
  125. */
  126. this.group = group;
  127. /**
  128. * @type {boolean}
  129. * @private
  130. */
  131. this._updateGroup = updateGroup;
  132. /**
  133. * This flag is used to make sure that only one among
  134. * `pan`, `zoom`, `click` can occurs, otherwise 'selected'
  135. * action may be triggered when `pan`, which is unexpected.
  136. * @type {booelan}
  137. */
  138. this._mouseDownFlag;
  139. /**
  140. * @type {string}
  141. */
  142. this._mapName;
  143. /**
  144. * @type {boolean}
  145. */
  146. this._initialized;
  147. /**
  148. * @type {module:zrender/container/Group}
  149. */
  150. group.add(this._regionsGroup = new graphic.Group());
  151. /**
  152. * @type {module:zrender/container/Group}
  153. */
  154. group.add(this._backgroundGroup = new graphic.Group());
  155. }
  156. MapDraw.prototype = {
  157. constructor: MapDraw,
  158. draw: function (mapOrGeoModel, ecModel, api, fromView, payload) {
  159. var isGeo = mapOrGeoModel.mainType === 'geo'; // Map series has data. GEO model that controlled by map series
  160. // will be assigned with map data. Other GEO model has no data.
  161. var data = mapOrGeoModel.getData && mapOrGeoModel.getData();
  162. isGeo && ecModel.eachComponent({
  163. mainType: 'series',
  164. subType: 'map'
  165. }, function (mapSeries) {
  166. if (!data && mapSeries.getHostGeoModel() === mapOrGeoModel) {
  167. data = mapSeries.getData();
  168. }
  169. });
  170. var geo = mapOrGeoModel.coordinateSystem;
  171. this._updateBackground(geo);
  172. var regionsGroup = this._regionsGroup;
  173. var group = this.group;
  174. var transformInfo = geo.getTransformInfo(); // No animation when first draw or in action
  175. var isFirstDraw = !regionsGroup.childAt(0) || payload;
  176. var targetScale;
  177. if (isFirstDraw) {
  178. group.transform = transformInfo.roamTransform;
  179. group.decomposeTransform();
  180. group.dirty();
  181. } else {
  182. var target = new Transformable();
  183. target.transform = transformInfo.roamTransform;
  184. target.decomposeTransform();
  185. var props = {
  186. scale: target.scale,
  187. position: target.position
  188. };
  189. targetScale = target.scale;
  190. graphic.updateProps(group, props, mapOrGeoModel);
  191. }
  192. var scale = transformInfo.rawScale;
  193. var position = transformInfo.rawPosition;
  194. regionsGroup.removeAll();
  195. var itemStyleAccessPath = ['itemStyle'];
  196. var hoverItemStyleAccessPath = ['emphasis', 'itemStyle'];
  197. var labelAccessPath = ['label'];
  198. var hoverLabelAccessPath = ['emphasis', 'label'];
  199. var nameMap = zrUtil.createHashMap();
  200. zrUtil.each(geo.regions, function (region) {
  201. // Consider in GeoJson properties.name may be duplicated, for example,
  202. // there is multiple region named "United Kindom" or "France" (so many
  203. // colonies). And it is not appropriate to merge them in geo, which
  204. // will make them share the same label and bring trouble in label
  205. // location calculation.
  206. var regionGroup = nameMap.get(region.name) || nameMap.set(region.name, new graphic.Group());
  207. var compoundPath = new graphic.CompoundPath({
  208. segmentIgnoreThreshold: 1,
  209. shape: {
  210. paths: []
  211. }
  212. });
  213. regionGroup.add(compoundPath);
  214. var regionModel = mapOrGeoModel.getRegionModel(region.name) || mapOrGeoModel;
  215. var itemStyleModel = regionModel.getModel(itemStyleAccessPath);
  216. var hoverItemStyleModel = regionModel.getModel(hoverItemStyleAccessPath);
  217. var itemStyle = getFixedItemStyle(itemStyleModel);
  218. var hoverItemStyle = getFixedItemStyle(hoverItemStyleModel);
  219. var labelModel = regionModel.getModel(labelAccessPath);
  220. var hoverLabelModel = regionModel.getModel(hoverLabelAccessPath);
  221. var dataIdx; // Use the itemStyle in data if has data
  222. if (data) {
  223. dataIdx = data.indexOfName(region.name); // Only visual color of each item will be used. It can be encoded by dataRange
  224. // But visual color of series is used in symbol drawing
  225. //
  226. // Visual color for each series is for the symbol draw
  227. var visualColor = data.getItemVisual(dataIdx, 'color', true);
  228. if (visualColor) {
  229. itemStyle.fill = visualColor;
  230. }
  231. }
  232. var transformPoint = function (point) {
  233. return [point[0] * scale[0] + position[0], point[1] * scale[1] + position[1]];
  234. };
  235. zrUtil.each(region.geometries, function (geometry) {
  236. if (geometry.type !== 'polygon') {
  237. return;
  238. }
  239. var points = [];
  240. for (var i = 0; i < geometry.exterior.length; ++i) {
  241. points.push(transformPoint(geometry.exterior[i]));
  242. }
  243. compoundPath.shape.paths.push(new graphic.Polygon({
  244. segmentIgnoreThreshold: 1,
  245. shape: {
  246. points: points
  247. }
  248. }));
  249. for (var i = 0; i < (geometry.interiors ? geometry.interiors.length : 0); ++i) {
  250. var interior = geometry.interiors[i];
  251. var points = [];
  252. for (var j = 0; j < interior.length; ++j) {
  253. points.push(transformPoint(interior[j]));
  254. }
  255. compoundPath.shape.paths.push(new graphic.Polygon({
  256. segmentIgnoreThreshold: 1,
  257. shape: {
  258. points: points
  259. }
  260. }));
  261. }
  262. });
  263. compoundPath.setStyle(itemStyle);
  264. compoundPath.style.strokeNoScale = true;
  265. compoundPath.culling = true; // Label
  266. var showLabel = labelModel.get('show');
  267. var hoverShowLabel = hoverLabelModel.get('show');
  268. var isDataNaN = data && isNaN(data.get(data.mapDimension('value'), dataIdx));
  269. var itemLayout = data && data.getItemLayout(dataIdx); // In the following cases label will be drawn
  270. // 1. In map series and data value is NaN
  271. // 2. In geo component
  272. // 4. Region has no series legendSymbol, which will be add a showLabel flag in mapSymbolLayout
  273. if (isGeo || isDataNaN && (showLabel || hoverShowLabel) || itemLayout && itemLayout.showLabel) {
  274. var query = !isGeo ? dataIdx : region.name;
  275. var labelFetcher; // Consider dataIdx not found.
  276. if (!data || dataIdx >= 0) {
  277. labelFetcher = mapOrGeoModel;
  278. }
  279. var textEl = new graphic.Text({
  280. position: transformPoint(region.center.slice()),
  281. // FIXME
  282. // label rotation is not support yet in geo or regions of series-map
  283. // that has no data. The rotation will be effected by this `scale`.
  284. // So needed to change to RectText?
  285. scale: [1 / group.scale[0], 1 / group.scale[1]],
  286. z2: 10,
  287. silent: true
  288. });
  289. graphic.setLabelStyle(textEl.style, textEl.hoverStyle = {}, labelModel, hoverLabelModel, {
  290. labelFetcher: labelFetcher,
  291. labelDataIndex: query,
  292. defaultText: region.name,
  293. useInsideStyle: false
  294. }, {
  295. textAlign: 'center',
  296. textVerticalAlign: 'middle'
  297. });
  298. if (!isFirstDraw) {
  299. // Text animation
  300. var textScale = [1 / targetScale[0], 1 / targetScale[1]];
  301. graphic.updateProps(textEl, {
  302. scale: textScale
  303. }, mapOrGeoModel);
  304. }
  305. regionGroup.add(textEl);
  306. } // setItemGraphicEl, setHoverStyle after all polygons and labels
  307. // are added to the rigionGroup
  308. if (data) {
  309. data.setItemGraphicEl(dataIdx, regionGroup);
  310. } else {
  311. var regionModel = mapOrGeoModel.getRegionModel(region.name); // Package custom mouse event for geo component
  312. compoundPath.eventData = {
  313. componentType: 'geo',
  314. componentIndex: mapOrGeoModel.componentIndex,
  315. geoIndex: mapOrGeoModel.componentIndex,
  316. name: region.name,
  317. region: regionModel && regionModel.option || {}
  318. };
  319. }
  320. var groupRegions = regionGroup.__regions || (regionGroup.__regions = []);
  321. groupRegions.push(region);
  322. regionGroup.highDownSilentOnTouch = !!mapOrGeoModel.get('selectedMode');
  323. graphic.setHoverStyle(regionGroup, hoverItemStyle);
  324. regionsGroup.add(regionGroup);
  325. });
  326. this._updateController(mapOrGeoModel, ecModel, api);
  327. updateMapSelectHandler(this, mapOrGeoModel, regionsGroup, api, fromView);
  328. updateMapSelected(mapOrGeoModel, regionsGroup);
  329. },
  330. remove: function () {
  331. this._regionsGroup.removeAll();
  332. this._backgroundGroup.removeAll();
  333. this._controller.dispose();
  334. this._mapName && geoSourceManager.removeGraphic(this._mapName, this.uid);
  335. this._mapName = null;
  336. this._controllerHost = {};
  337. },
  338. _updateBackground: function (geo) {
  339. var mapName = geo.map;
  340. if (this._mapName !== mapName) {
  341. zrUtil.each(geoSourceManager.makeGraphic(mapName, this.uid), function (root) {
  342. this._backgroundGroup.add(root);
  343. }, this);
  344. }
  345. this._mapName = mapName;
  346. },
  347. _updateController: function (mapOrGeoModel, ecModel, api) {
  348. var geo = mapOrGeoModel.coordinateSystem;
  349. var controller = this._controller;
  350. var controllerHost = this._controllerHost;
  351. controllerHost.zoomLimit = mapOrGeoModel.get('scaleLimit');
  352. controllerHost.zoom = geo.getZoom(); // roamType is will be set default true if it is null
  353. controller.enable(mapOrGeoModel.get('roam') || false);
  354. var mainType = mapOrGeoModel.mainType;
  355. function makeActionBase() {
  356. var action = {
  357. type: 'geoRoam',
  358. componentType: mainType
  359. };
  360. action[mainType + 'Id'] = mapOrGeoModel.id;
  361. return action;
  362. }
  363. controller.off('pan').on('pan', function (e) {
  364. this._mouseDownFlag = false;
  365. roamHelper.updateViewOnPan(controllerHost, e.dx, e.dy);
  366. api.dispatchAction(zrUtil.extend(makeActionBase(), {
  367. dx: e.dx,
  368. dy: e.dy
  369. }));
  370. }, this);
  371. controller.off('zoom').on('zoom', function (e) {
  372. this._mouseDownFlag = false;
  373. roamHelper.updateViewOnZoom(controllerHost, e.scale, e.originX, e.originY);
  374. api.dispatchAction(zrUtil.extend(makeActionBase(), {
  375. zoom: e.scale,
  376. originX: e.originX,
  377. originY: e.originY
  378. }));
  379. if (this._updateGroup) {
  380. var scale = this.group.scale;
  381. this._regionsGroup.traverse(function (el) {
  382. if (el.type === 'text') {
  383. el.attr('scale', [1 / scale[0], 1 / scale[1]]);
  384. }
  385. });
  386. }
  387. }, this);
  388. controller.setPointerChecker(function (e, x, y) {
  389. return geo.getViewRectAfterRoam().contain(x, y) && !onIrrelevantElement(e, api, mapOrGeoModel);
  390. });
  391. }
  392. };
  393. var _default = MapDraw;
  394. module.exports = _default;