GeoSVGResource.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  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. /**
  20. * AUTO-GENERATED FILE. DO NOT MODIFY.
  21. */
  22. /*
  23. * Licensed to the Apache Software Foundation (ASF) under one
  24. * or more contributor license agreements. See the NOTICE file
  25. * distributed with this work for additional information
  26. * regarding copyright ownership. The ASF licenses this file
  27. * to you under the Apache License, Version 2.0 (the
  28. * "License"); you may not use this file except in compliance
  29. * with the License. You may obtain a copy of the License at
  30. *
  31. * http://www.apache.org/licenses/LICENSE-2.0
  32. *
  33. * Unless required by applicable law or agreed to in writing,
  34. * software distributed under the License is distributed on an
  35. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  36. * KIND, either express or implied. See the License for the
  37. * specific language governing permissions and limitations
  38. * under the License.
  39. */
  40. import { parseSVG, makeViewBoxTransform } from 'zrender/lib/tool/parseSVG.js';
  41. import Group from 'zrender/lib/graphic/Group.js';
  42. import Rect from 'zrender/lib/graphic/shape/Rect.js';
  43. import { assert, createHashMap, each } from 'zrender/lib/core/util.js';
  44. import BoundingRect from 'zrender/lib/core/BoundingRect.js';
  45. import { parseXML } from 'zrender/lib/tool/parseXML.js';
  46. import { GeoSVGRegion } from './Region.js';
  47. /**
  48. * "region available" means that: enable users to set attribute `name="xxx"` on those tags
  49. * to make it be a region.
  50. * 1. region styles and its label styles can be defined in echarts opton:
  51. * ```js
  52. * geo: {
  53. * regions: [{
  54. * name: 'xxx',
  55. * itemStyle: { ... },
  56. * label: { ... }
  57. * }, {
  58. * ...
  59. * },
  60. * ...]
  61. * };
  62. * ```
  63. * 2. name can be duplicated in different SVG tag. All of the tags with the same name share
  64. * a region option. For exampel if there are two <path> representing two lung lobes. They have
  65. * no common parents but both of them need to display label "lung" inside.
  66. */
  67. var REGION_AVAILABLE_SVG_TAG_MAP = createHashMap(['rect', 'circle', 'line', 'ellipse', 'polygon', 'polyline', 'path', // <text> <tspan> are also enabled because some SVG might paint text itself,
  68. // but still need to trigger events or tooltip.
  69. 'text', 'tspan', // <g> is also enabled because this case: if multiple tags share one name
  70. // and need label displayed, every tags will display the name, which is not
  71. // expected. So we can put them into a <g name="xxx">. Thereby only one label
  72. // displayed and located based on the bounding rect of the <g>.
  73. 'g']);
  74. var GeoSVGResource =
  75. /** @class */
  76. function () {
  77. function GeoSVGResource(mapName, svg) {
  78. this.type = 'geoSVG'; // All used graphics. key: hostKey, value: root
  79. this._usedGraphicMap = createHashMap(); // All unused graphics.
  80. this._freedGraphics = [];
  81. this._mapName = mapName; // Only perform parse to XML object here, which might be time
  82. // consiming for large SVG.
  83. // Although convert XML to zrender element is also time consiming,
  84. // if we do it here, the clone of zrender elements has to be
  85. // required. So we do it once for each geo instance, util real
  86. // performance issues call for optimizing it.
  87. this._parsedXML = parseXML(svg);
  88. }
  89. GeoSVGResource.prototype.load = function ()
  90. /* nameMap: NameMap */
  91. {
  92. // In the "load" stage, graphic need to be built to
  93. // get boundingRect for geo coordinate system.
  94. var firstGraphic = this._firstGraphic; // Create the return data structure only when first graphic created.
  95. // Because they will be used in geo coordinate system update stage,
  96. // and `regions` will be mounted at `geo` coordinate system,
  97. // in which there is no "view" info, so that it should better not to
  98. // make references to graphic elements.
  99. if (!firstGraphic) {
  100. firstGraphic = this._firstGraphic = this._buildGraphic(this._parsedXML);
  101. this._freedGraphics.push(firstGraphic);
  102. this._boundingRect = this._firstGraphic.boundingRect.clone(); // PENDING: `nameMap` will not be supported until some real requirement come.
  103. // if (nameMap) {
  104. // named = applyNameMap(named, nameMap);
  105. // }
  106. var _a = createRegions(firstGraphic.named),
  107. regions = _a.regions,
  108. regionsMap = _a.regionsMap;
  109. this._regions = regions;
  110. this._regionsMap = regionsMap;
  111. }
  112. return {
  113. boundingRect: this._boundingRect,
  114. regions: this._regions,
  115. regionsMap: this._regionsMap
  116. };
  117. };
  118. GeoSVGResource.prototype._buildGraphic = function (svgXML) {
  119. var result;
  120. var rootFromParse;
  121. try {
  122. result = svgXML && parseSVG(svgXML, {
  123. ignoreViewBox: true,
  124. ignoreRootClip: true
  125. }) || {};
  126. rootFromParse = result.root;
  127. assert(rootFromParse != null);
  128. } catch (e) {
  129. throw new Error('Invalid svg format\n' + e.message);
  130. } // Note: we keep the covenant that the root has no transform. So always add an extra root.
  131. var root = new Group();
  132. root.add(rootFromParse);
  133. root.isGeoSVGGraphicRoot = true; // [THE_RULE_OF_VIEWPORT_AND_VIEWBOX]
  134. //
  135. // Consider: `<svg width="..." height="..." viewBox="...">`
  136. // - the `width/height` we call it `svgWidth/svgHeight` for short.
  137. // - `(0, 0, svgWidth, svgHeight)` defines the viewport of the SVG, or say,
  138. // "viewport boundingRect", or `boundingRect` for short.
  139. // - `viewBox` defines the transform from the real content ot the viewport.
  140. // `viewBox` has the same unit as the content of SVG.
  141. // If `viewBox` exists, a transform is defined, so the unit of `svgWidth/svgHeight` become
  142. // different from the content of SVG. Otherwise, they are the same.
  143. //
  144. // If both `svgWidth/svgHeight/viewBox` are specified in a SVG file, the transform rule will be:
  145. // 0. `boundingRect` is `(0, 0, svgWidth, svgHeight)`. Set it to Geo['_rect'] (View['_rect']).
  146. // 1. Make a transform from `viewBox` to `boundingRect`.
  147. // Note: only support `preserveAspectRatio 'xMidYMid'` here. That is, this transform will preserve
  148. // the aspect ratio.
  149. // 2. Make a transform from boundingRect to Geo['_viewRect'] (View['_viewRect'])
  150. // (`Geo`/`View` will do this job).
  151. // Note: this transform might not preserve aspect radio, which depending on how users specify
  152. // viewRect in echarts option (e.g., `geo.left/top/width/height` will not preserve aspect ratio,
  153. // but `geo.layoutCenter/layoutSize` will preserve aspect ratio).
  154. //
  155. // If `svgWidth/svgHeight` not specified, we use `viewBox` as the `boundingRect` to make the SVG
  156. // layout look good.
  157. //
  158. // If neither `svgWidth/svgHeight` nor `viewBox` are not specified, we calculate the boundingRect
  159. // of the SVG content and use them to make SVG layout look good.
  160. var svgWidth = result.width;
  161. var svgHeight = result.height;
  162. var viewBoxRect = result.viewBoxRect;
  163. var boundingRect = this._boundingRect;
  164. if (!boundingRect) {
  165. var bRectX = void 0;
  166. var bRectY = void 0;
  167. var bRectWidth = void 0;
  168. var bRectHeight = void 0;
  169. if (svgWidth != null) {
  170. bRectX = 0;
  171. bRectWidth = svgWidth;
  172. } else if (viewBoxRect) {
  173. bRectX = viewBoxRect.x;
  174. bRectWidth = viewBoxRect.width;
  175. }
  176. if (svgHeight != null) {
  177. bRectY = 0;
  178. bRectHeight = svgHeight;
  179. } else if (viewBoxRect) {
  180. bRectY = viewBoxRect.y;
  181. bRectHeight = viewBoxRect.height;
  182. } // If both viewBox and svgWidth/svgHeight not specified,
  183. // we have to determine how to layout those element to make them look good.
  184. if (bRectX == null || bRectY == null) {
  185. var calculatedBoundingRect = rootFromParse.getBoundingRect();
  186. if (bRectX == null) {
  187. bRectX = calculatedBoundingRect.x;
  188. bRectWidth = calculatedBoundingRect.width;
  189. }
  190. if (bRectY == null) {
  191. bRectY = calculatedBoundingRect.y;
  192. bRectHeight = calculatedBoundingRect.height;
  193. }
  194. }
  195. boundingRect = this._boundingRect = new BoundingRect(bRectX, bRectY, bRectWidth, bRectHeight);
  196. }
  197. if (viewBoxRect) {
  198. var viewBoxTransform = makeViewBoxTransform(viewBoxRect, boundingRect); // Only support `preserveAspectRatio 'xMidYMid'`
  199. rootFromParse.scaleX = rootFromParse.scaleY = viewBoxTransform.scale;
  200. rootFromParse.x = viewBoxTransform.x;
  201. rootFromParse.y = viewBoxTransform.y;
  202. } // SVG needs to clip based on `viewBox`. And some SVG files really rely on this feature.
  203. // They do not strictly confine all of the content inside a display rect, but deliberately
  204. // use a `viewBox` to define a displayable rect.
  205. // PENDING:
  206. // The drawback of the `setClipPath` here is: the region label (genereted by echarts) near the
  207. // edge might also be clipped, because region labels are put as `textContent` of the SVG path.
  208. root.setClipPath(new Rect({
  209. shape: boundingRect.plain()
  210. }));
  211. var named = [];
  212. each(result.named, function (namedItem) {
  213. if (REGION_AVAILABLE_SVG_TAG_MAP.get(namedItem.svgNodeTagLower) != null) {
  214. named.push(namedItem);
  215. setSilent(namedItem.el);
  216. }
  217. });
  218. return {
  219. root: root,
  220. boundingRect: boundingRect,
  221. named: named
  222. };
  223. };
  224. /**
  225. * Consider:
  226. * (1) One graphic element can not be shared by different `geoView` running simultaneously.
  227. * Notice, also need to consider multiple echarts instances share a `mapRecord`.
  228. * (2) Converting SVG to graphic elements is time consuming.
  229. * (3) In the current architecture, `load` should be called frequently to get boundingRect,
  230. * and it is called without view info.
  231. * So we maintain graphic elements in this module, and enables `view` to use/return these
  232. * graphics from/to the pool with it's uid.
  233. */
  234. GeoSVGResource.prototype.useGraphic = function (hostKey
  235. /* , nameMap: NameMap */
  236. ) {
  237. var usedRootMap = this._usedGraphicMap;
  238. var svgGraphic = usedRootMap.get(hostKey);
  239. if (svgGraphic) {
  240. return svgGraphic;
  241. }
  242. svgGraphic = this._freedGraphics.pop() // use the first boundingRect to avoid duplicated boundingRect calculation.
  243. || this._buildGraphic(this._parsedXML);
  244. usedRootMap.set(hostKey, svgGraphic); // PENDING: `nameMap` will not be supported until some real requirement come.
  245. // `nameMap` can only be obtained from echarts option.
  246. // The original `named` must not be modified.
  247. // if (nameMap) {
  248. // svgGraphic = extend({}, svgGraphic);
  249. // svgGraphic.named = applyNameMap(svgGraphic.named, nameMap);
  250. // }
  251. return svgGraphic;
  252. };
  253. GeoSVGResource.prototype.freeGraphic = function (hostKey) {
  254. var usedRootMap = this._usedGraphicMap;
  255. var svgGraphic = usedRootMap.get(hostKey);
  256. if (svgGraphic) {
  257. usedRootMap.removeKey(hostKey);
  258. this._freedGraphics.push(svgGraphic);
  259. }
  260. };
  261. return GeoSVGResource;
  262. }();
  263. export { GeoSVGResource };
  264. function setSilent(el) {
  265. // Only named element has silent: false, other elements should
  266. // act as background and has no user interaction.
  267. el.silent = false; // text|tspan will be converted to group.
  268. if (el.isGroup) {
  269. el.traverse(function (child) {
  270. child.silent = false;
  271. });
  272. }
  273. }
  274. function createRegions(named) {
  275. var regions = [];
  276. var regionsMap = createHashMap(); // Create resions only for the first graphic.
  277. each(named, function (namedItem) {
  278. // Region has feature to calculate center for tooltip or other features.
  279. // If there is a <g name="xxx">, the center should be the center of the
  280. // bounding rect of the g.
  281. if (namedItem.namedFrom != null) {
  282. return;
  283. }
  284. var region = new GeoSVGRegion(namedItem.name, namedItem.el); // PENDING: if `nameMap` supported, this region can not be mounted on
  285. // `this`, but can only be created each time `load()` called.
  286. regions.push(region); // PENDING: if multiple tag named with the same name, only one will be
  287. // found by `_regionsMap`. `_regionsMap` is used to find a coordinate
  288. // by name. We use `region.getCenter()` as the coordinate.
  289. regionsMap.set(namedItem.name, region);
  290. });
  291. return {
  292. regions: regions,
  293. regionsMap: regionsMap
  294. };
  295. } // PENDING: `nameMap` will not be supported until some real requirement come.
  296. // /**
  297. // * Use the alias in geoNameMap.
  298. // * The input `named` must not be modified.
  299. // */
  300. // function applyNameMap(
  301. // named: GeoSVGGraphicRecord['named'],
  302. // nameMap: NameMap
  303. // ): GeoSVGGraphicRecord['named'] {
  304. // const result = [] as GeoSVGGraphicRecord['named'];
  305. // for (let i = 0; i < named.length; i++) {
  306. // let regionGraphic = named[i];
  307. // const name = regionGraphic.name;
  308. // if (nameMap && nameMap.hasOwnProperty(name)) {
  309. // regionGraphic = extend({}, regionGraphic);
  310. // regionGraphic.name = name;
  311. // }
  312. // result.push(regionGraphic);
  313. // }
  314. // return result;
  315. // }