graphic.js 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527
  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 pathTool = require("zrender/lib/tool/path");
  21. var colorTool = require("zrender/lib/tool/color");
  22. var matrix = require("zrender/lib/core/matrix");
  23. var vector = require("zrender/lib/core/vector");
  24. var Path = require("zrender/lib/graphic/Path");
  25. var Transformable = require("zrender/lib/mixin/Transformable");
  26. var ZImage = require("zrender/lib/graphic/Image");
  27. exports.Image = ZImage;
  28. var Group = require("zrender/lib/container/Group");
  29. exports.Group = Group;
  30. var Text = require("zrender/lib/graphic/Text");
  31. exports.Text = Text;
  32. var Circle = require("zrender/lib/graphic/shape/Circle");
  33. exports.Circle = Circle;
  34. var Sector = require("zrender/lib/graphic/shape/Sector");
  35. exports.Sector = Sector;
  36. var Ring = require("zrender/lib/graphic/shape/Ring");
  37. exports.Ring = Ring;
  38. var Polygon = require("zrender/lib/graphic/shape/Polygon");
  39. exports.Polygon = Polygon;
  40. var Polyline = require("zrender/lib/graphic/shape/Polyline");
  41. exports.Polyline = Polyline;
  42. var Rect = require("zrender/lib/graphic/shape/Rect");
  43. exports.Rect = Rect;
  44. var Line = require("zrender/lib/graphic/shape/Line");
  45. exports.Line = Line;
  46. var BezierCurve = require("zrender/lib/graphic/shape/BezierCurve");
  47. exports.BezierCurve = BezierCurve;
  48. var Arc = require("zrender/lib/graphic/shape/Arc");
  49. exports.Arc = Arc;
  50. var CompoundPath = require("zrender/lib/graphic/CompoundPath");
  51. exports.CompoundPath = CompoundPath;
  52. var LinearGradient = require("zrender/lib/graphic/LinearGradient");
  53. exports.LinearGradient = LinearGradient;
  54. var RadialGradient = require("zrender/lib/graphic/RadialGradient");
  55. exports.RadialGradient = RadialGradient;
  56. var BoundingRect = require("zrender/lib/core/BoundingRect");
  57. exports.BoundingRect = BoundingRect;
  58. var IncrementalDisplayable = require("zrender/lib/graphic/IncrementalDisplayable");
  59. exports.IncrementalDisplayable = IncrementalDisplayable;
  60. var subPixelOptimizeUtil = require("zrender/lib/graphic/helper/subPixelOptimize");
  61. /*
  62. * Licensed to the Apache Software Foundation (ASF) under one
  63. * or more contributor license agreements. See the NOTICE file
  64. * distributed with this work for additional information
  65. * regarding copyright ownership. The ASF licenses this file
  66. * to you under the Apache License, Version 2.0 (the
  67. * "License"); you may not use this file except in compliance
  68. * with the License. You may obtain a copy of the License at
  69. *
  70. * http://www.apache.org/licenses/LICENSE-2.0
  71. *
  72. * Unless required by applicable law or agreed to in writing,
  73. * software distributed under the License is distributed on an
  74. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  75. * KIND, either express or implied. See the License for the
  76. * specific language governing permissions and limitations
  77. * under the License.
  78. */
  79. var mathMax = Math.max;
  80. var mathMin = Math.min;
  81. var EMPTY_OBJ = {};
  82. var Z2_EMPHASIS_LIFT = 1; // key: label model property nane, value: style property name.
  83. var CACHED_LABEL_STYLE_PROPERTIES = {
  84. color: 'textFill',
  85. textBorderColor: 'textStroke',
  86. textBorderWidth: 'textStrokeWidth'
  87. };
  88. var EMPHASIS = 'emphasis';
  89. var NORMAL = 'normal'; // Reserve 0 as default.
  90. var _highlightNextDigit = 1;
  91. var _highlightKeyMap = {};
  92. var _customShapeMap = {};
  93. /**
  94. * Extend shape with parameters
  95. */
  96. function extendShape(opts) {
  97. return Path.extend(opts);
  98. }
  99. /**
  100. * Extend path
  101. */
  102. function extendPath(pathData, opts) {
  103. return pathTool.extendFromString(pathData, opts);
  104. }
  105. /**
  106. * Register a user defined shape.
  107. * The shape class can be fetched by `getShapeClass`
  108. * This method will overwrite the registered shapes, including
  109. * the registered built-in shapes, if using the same `name`.
  110. * The shape can be used in `custom series` and
  111. * `graphic component` by declaring `{type: name}`.
  112. *
  113. * @param {string} name
  114. * @param {Object} ShapeClass Can be generated by `extendShape`.
  115. */
  116. function registerShape(name, ShapeClass) {
  117. _customShapeMap[name] = ShapeClass;
  118. }
  119. /**
  120. * Find shape class registered by `registerShape`. Usually used in
  121. * fetching user defined shape.
  122. *
  123. * [Caution]:
  124. * (1) This method **MUST NOT be used inside echarts !!!**, unless it is prepared
  125. * to use user registered shapes.
  126. * Because the built-in shape (see `getBuiltInShape`) will be registered by
  127. * `registerShape` by default. That enables users to get both built-in
  128. * shapes as well as the shapes belonging to themsleves. But users can overwrite
  129. * the built-in shapes by using names like 'circle', 'rect' via calling
  130. * `registerShape`. So the echarts inner featrues should not fetch shapes from here
  131. * in case that it is overwritten by users, except that some features, like
  132. * `custom series`, `graphic component`, do it deliberately.
  133. *
  134. * (2) In the features like `custom series`, `graphic component`, the user input
  135. * `{tpye: 'xxx'}` does not only specify shapes but also specify other graphic
  136. * elements like `'group'`, `'text'`, `'image'` or event `'path'`. Those names
  137. * are reserved names, that is, if some user register a shape named `'image'`,
  138. * the shape will not be used. If we intending to add some more reserved names
  139. * in feature, that might bring break changes (disable some existing user shape
  140. * names). But that case probably rearly happen. So we dont make more mechanism
  141. * to resolve this issue here.
  142. *
  143. * @param {string} name
  144. * @return {Object} The shape class. If not found, return nothing.
  145. */
  146. function getShapeClass(name) {
  147. if (_customShapeMap.hasOwnProperty(name)) {
  148. return _customShapeMap[name];
  149. }
  150. }
  151. /**
  152. * Create a path element from path data string
  153. * @param {string} pathData
  154. * @param {Object} opts
  155. * @param {module:zrender/core/BoundingRect} rect
  156. * @param {string} [layout=cover] 'center' or 'cover'
  157. */
  158. function makePath(pathData, opts, rect, layout) {
  159. var path = pathTool.createFromString(pathData, opts);
  160. if (rect) {
  161. if (layout === 'center') {
  162. rect = centerGraphic(rect, path.getBoundingRect());
  163. }
  164. resizePath(path, rect);
  165. }
  166. return path;
  167. }
  168. /**
  169. * Create a image element from image url
  170. * @param {string} imageUrl image url
  171. * @param {Object} opts options
  172. * @param {module:zrender/core/BoundingRect} rect constrain rect
  173. * @param {string} [layout=cover] 'center' or 'cover'
  174. */
  175. function makeImage(imageUrl, rect, layout) {
  176. var path = new ZImage({
  177. style: {
  178. image: imageUrl,
  179. x: rect.x,
  180. y: rect.y,
  181. width: rect.width,
  182. height: rect.height
  183. },
  184. onload: function (img) {
  185. if (layout === 'center') {
  186. var boundingRect = {
  187. width: img.width,
  188. height: img.height
  189. };
  190. path.setStyle(centerGraphic(rect, boundingRect));
  191. }
  192. }
  193. });
  194. return path;
  195. }
  196. /**
  197. * Get position of centered element in bounding box.
  198. *
  199. * @param {Object} rect element local bounding box
  200. * @param {Object} boundingRect constraint bounding box
  201. * @return {Object} element position containing x, y, width, and height
  202. */
  203. function centerGraphic(rect, boundingRect) {
  204. // Set rect to center, keep width / height ratio.
  205. var aspect = boundingRect.width / boundingRect.height;
  206. var width = rect.height * aspect;
  207. var height;
  208. if (width <= rect.width) {
  209. height = rect.height;
  210. } else {
  211. width = rect.width;
  212. height = width / aspect;
  213. }
  214. var cx = rect.x + rect.width / 2;
  215. var cy = rect.y + rect.height / 2;
  216. return {
  217. x: cx - width / 2,
  218. y: cy - height / 2,
  219. width: width,
  220. height: height
  221. };
  222. }
  223. var mergePath = pathTool.mergePath;
  224. /**
  225. * Resize a path to fit the rect
  226. * @param {module:zrender/graphic/Path} path
  227. * @param {Object} rect
  228. */
  229. function resizePath(path, rect) {
  230. if (!path.applyTransform) {
  231. return;
  232. }
  233. var pathRect = path.getBoundingRect();
  234. var m = pathRect.calculateTransform(rect);
  235. path.applyTransform(m);
  236. }
  237. /**
  238. * Sub pixel optimize line for canvas
  239. *
  240. * @param {Object} param
  241. * @param {Object} [param.shape]
  242. * @param {number} [param.shape.x1]
  243. * @param {number} [param.shape.y1]
  244. * @param {number} [param.shape.x2]
  245. * @param {number} [param.shape.y2]
  246. * @param {Object} [param.style]
  247. * @param {number} [param.style.lineWidth]
  248. * @return {Object} Modified param
  249. */
  250. function subPixelOptimizeLine(param) {
  251. subPixelOptimizeUtil.subPixelOptimizeLine(param.shape, param.shape, param.style);
  252. return param;
  253. }
  254. /**
  255. * Sub pixel optimize rect for canvas
  256. *
  257. * @param {Object} param
  258. * @param {Object} [param.shape]
  259. * @param {number} [param.shape.x]
  260. * @param {number} [param.shape.y]
  261. * @param {number} [param.shape.width]
  262. * @param {number} [param.shape.height]
  263. * @param {Object} [param.style]
  264. * @param {number} [param.style.lineWidth]
  265. * @return {Object} Modified param
  266. */
  267. function subPixelOptimizeRect(param) {
  268. subPixelOptimizeUtil.subPixelOptimizeRect(param.shape, param.shape, param.style);
  269. return param;
  270. }
  271. /**
  272. * Sub pixel optimize for canvas
  273. *
  274. * @param {number} position Coordinate, such as x, y
  275. * @param {number} lineWidth Should be nonnegative integer.
  276. * @param {boolean=} positiveOrNegative Default false (negative).
  277. * @return {number} Optimized position.
  278. */
  279. var subPixelOptimize = subPixelOptimizeUtil.subPixelOptimize;
  280. function hasFillOrStroke(fillOrStroke) {
  281. return fillOrStroke != null && fillOrStroke !== 'none';
  282. } // Most lifted color are duplicated.
  283. var liftedColorMap = zrUtil.createHashMap();
  284. var liftedColorCount = 0;
  285. function liftColor(color) {
  286. if (typeof color !== 'string') {
  287. return color;
  288. }
  289. var liftedColor = liftedColorMap.get(color);
  290. if (!liftedColor) {
  291. liftedColor = colorTool.lift(color, -0.1);
  292. if (liftedColorCount < 10000) {
  293. liftedColorMap.set(color, liftedColor);
  294. liftedColorCount++;
  295. }
  296. }
  297. return liftedColor;
  298. }
  299. function cacheElementStl(el) {
  300. if (!el.__hoverStlDirty) {
  301. return;
  302. }
  303. el.__hoverStlDirty = false;
  304. var hoverStyle = el.__hoverStl;
  305. if (!hoverStyle) {
  306. el.__cachedNormalStl = el.__cachedNormalZ2 = null;
  307. return;
  308. }
  309. var normalStyle = el.__cachedNormalStl = {};
  310. el.__cachedNormalZ2 = el.z2;
  311. var elStyle = el.style;
  312. for (var name in hoverStyle) {
  313. // See comment in `singleEnterEmphasis`.
  314. if (hoverStyle[name] != null) {
  315. normalStyle[name] = elStyle[name];
  316. }
  317. } // Always cache fill and stroke to normalStyle for lifting color.
  318. normalStyle.fill = elStyle.fill;
  319. normalStyle.stroke = elStyle.stroke;
  320. }
  321. function singleEnterEmphasis(el) {
  322. var hoverStl = el.__hoverStl;
  323. if (!hoverStl || el.__highlighted) {
  324. return;
  325. }
  326. var zr = el.__zr;
  327. var useHoverLayer = el.useHoverLayer && zr && zr.painter.type === 'canvas';
  328. el.__highlighted = useHoverLayer ? 'layer' : 'plain';
  329. if (el.isGroup || !zr && el.useHoverLayer) {
  330. return;
  331. }
  332. var elTarget = el;
  333. var targetStyle = el.style;
  334. if (useHoverLayer) {
  335. elTarget = zr.addHover(el);
  336. targetStyle = elTarget.style;
  337. }
  338. rollbackDefaultTextStyle(targetStyle);
  339. if (!useHoverLayer) {
  340. cacheElementStl(elTarget);
  341. } // styles can be:
  342. // {
  343. // label: {
  344. // show: false,
  345. // position: 'outside',
  346. // fontSize: 18
  347. // },
  348. // emphasis: {
  349. // label: {
  350. // show: true
  351. // }
  352. // }
  353. // },
  354. // where properties of `emphasis` may not appear in `normal`. We previously use
  355. // module:echarts/util/model#defaultEmphasis to merge `normal` to `emphasis`.
  356. // But consider rich text and setOption in merge mode, it is impossible to cover
  357. // all properties in merge. So we use merge mode when setting style here.
  358. // But we choose the merge strategy that only properties that is not `null/undefined`.
  359. // Because when making a textStyle (espacially rich text), it is not easy to distinguish
  360. // `hasOwnProperty` and `null/undefined` in code, so we trade them as the same for simplicity.
  361. // But this strategy brings a trouble that `null/undefined` can not be used to remove
  362. // style any more in `emphasis`. Users can both set properties directly on normal and
  363. // emphasis to avoid this issue, or we might support `'none'` for this case if required.
  364. targetStyle.extendFrom(hoverStl);
  365. setDefaultHoverFillStroke(targetStyle, hoverStl, 'fill');
  366. setDefaultHoverFillStroke(targetStyle, hoverStl, 'stroke');
  367. applyDefaultTextStyle(targetStyle);
  368. if (!useHoverLayer) {
  369. el.dirty(false);
  370. el.z2 += Z2_EMPHASIS_LIFT;
  371. }
  372. }
  373. function setDefaultHoverFillStroke(targetStyle, hoverStyle, prop) {
  374. if (!hasFillOrStroke(hoverStyle[prop]) && hasFillOrStroke(targetStyle[prop])) {
  375. targetStyle[prop] = liftColor(targetStyle[prop]);
  376. }
  377. }
  378. function singleEnterNormal(el) {
  379. var highlighted = el.__highlighted;
  380. if (!highlighted) {
  381. return;
  382. }
  383. el.__highlighted = false;
  384. if (el.isGroup) {
  385. return;
  386. }
  387. if (highlighted === 'layer') {
  388. el.__zr && el.__zr.removeHover(el);
  389. } else {
  390. var style = el.style;
  391. var normalStl = el.__cachedNormalStl;
  392. if (normalStl) {
  393. rollbackDefaultTextStyle(style);
  394. el.setStyle(normalStl);
  395. applyDefaultTextStyle(style);
  396. } // `__cachedNormalZ2` will not be reset if calling `setElementHoverStyle`
  397. // when `el` is on emphasis state. So here by comparing with 1, we try
  398. // hard to make the bug case rare.
  399. var normalZ2 = el.__cachedNormalZ2;
  400. if (normalZ2 != null && el.z2 - normalZ2 === Z2_EMPHASIS_LIFT) {
  401. el.z2 = normalZ2;
  402. }
  403. }
  404. }
  405. function traverseUpdate(el, updater, commonParam) {
  406. // If root is group, also enter updater for `highDownOnUpdate`.
  407. var fromState = NORMAL;
  408. var toState = NORMAL;
  409. var trigger; // See the rule of `highDownOnUpdate` on `graphic.setAsHighDownDispatcher`.
  410. el.__highlighted && (fromState = EMPHASIS, trigger = true);
  411. updater(el, commonParam);
  412. el.__highlighted && (toState = EMPHASIS, trigger = true);
  413. el.isGroup && el.traverse(function (child) {
  414. !child.isGroup && updater(child, commonParam);
  415. });
  416. trigger && el.__highDownOnUpdate && el.__highDownOnUpdate(fromState, toState);
  417. }
  418. /**
  419. * Set hover style (namely "emphasis style") of element, based on the current
  420. * style of the given `el`.
  421. * This method should be called after all of the normal styles have been adopted
  422. * to the `el`. See the reason on `setHoverStyle`.
  423. *
  424. * @param {module:zrender/Element} el Should not be `zrender/container/Group`.
  425. * @param {Object} [el.hoverStyle] Can be set on el or its descendants,
  426. * e.g., `el.hoverStyle = ...; graphic.setHoverStyle(el); `.
  427. * Often used when item group has a label element and it's hoverStyle is different.
  428. * @param {Object|boolean} [hoverStl] The specified hover style.
  429. * If set as `false`, disable the hover style.
  430. * Similarly, The `el.hoverStyle` can alse be set
  431. * as `false` to disable the hover style.
  432. * Otherwise, use the default hover style if not provided.
  433. */
  434. function setElementHoverStyle(el, hoverStl) {
  435. // For performance consideration, it might be better to make the "hover style" only the
  436. // difference properties from the "normal style", but not a entire copy of all styles.
  437. hoverStl = el.__hoverStl = hoverStl !== false && (el.hoverStyle || hoverStl || {});
  438. el.__hoverStlDirty = true; // FIXME
  439. // It is not completely right to save "normal"/"emphasis" flag on elements.
  440. // It probably should be saved on `data` of series. Consider the cases:
  441. // (1) A highlighted elements are moved out of the view port and re-enter
  442. // again by dataZoom.
  443. // (2) call `setOption` and replace elements totally when they are highlighted.
  444. if (el.__highlighted) {
  445. // Consider the case:
  446. // The styles of a highlighted `el` is being updated. The new "emphasis style"
  447. // should be adapted to the `el`. Notice here new "normal styles" should have
  448. // been set outside and the cached "normal style" is out of date.
  449. el.__cachedNormalStl = null; // Do not clear `__cachedNormalZ2` here, because setting `z2` is not a constraint
  450. // of this method. In most cases, `z2` is not set and hover style should be able
  451. // to rollback. Of course, that would bring bug, but only in a rare case, see
  452. // `doSingleLeaveHover` for details.
  453. singleEnterNormal(el);
  454. singleEnterEmphasis(el);
  455. }
  456. }
  457. function onElementMouseOver(e) {
  458. !shouldSilent(this, e) // "emphasis" event highlight has higher priority than mouse highlight.
  459. && !this.__highByOuter && traverseUpdate(this, singleEnterEmphasis);
  460. }
  461. function onElementMouseOut(e) {
  462. !shouldSilent(this, e) // "emphasis" event highlight has higher priority than mouse highlight.
  463. && !this.__highByOuter && traverseUpdate(this, singleEnterNormal);
  464. }
  465. function onElementEmphasisEvent(highlightDigit) {
  466. this.__highByOuter |= 1 << (highlightDigit || 0);
  467. traverseUpdate(this, singleEnterEmphasis);
  468. }
  469. function onElementNormalEvent(highlightDigit) {
  470. !(this.__highByOuter &= ~(1 << (highlightDigit || 0))) && traverseUpdate(this, singleEnterNormal);
  471. }
  472. function shouldSilent(el, e) {
  473. return el.__highDownSilentOnTouch && e.zrByTouch;
  474. }
  475. /**
  476. * Set hover style (namely "emphasis style") of element,
  477. * based on the current style of the given `el`.
  478. *
  479. * (1)
  480. * **CONSTRAINTS** for this method:
  481. * <A> This method MUST be called after all of the normal styles having been adopted
  482. * to the `el`.
  483. * <B> The input `hoverStyle` (that is, "emphasis style") MUST be the subset of the
  484. * "normal style" having been set to the el.
  485. * <C> `color` MUST be one of the "normal styles" (because color might be lifted as
  486. * a default hover style).
  487. *
  488. * The reason: this method treat the current style of the `el` as the "normal style"
  489. * and cache them when enter/update the "emphasis style". Consider the case: the `el`
  490. * is in "emphasis" state and `setOption`/`dispatchAction` trigger the style updating
  491. * logic, where the el should shift from the original emphasis style to the new
  492. * "emphasis style" and should be able to "downplay" back to the new "normal style".
  493. *
  494. * Indeed, it is error-prone to make a interface has so many constraints, but I have
  495. * not found a better solution yet to fit the backward compatibility, performance and
  496. * the current programming style.
  497. *
  498. * (2)
  499. * Call the method for a "root" element once. Do not call it for each descendants.
  500. * If the descendants elemenets of a group has itself hover style different from the
  501. * root group, we can simply mount the style on `el.hoverStyle` for them, but should
  502. * not call this method for them.
  503. *
  504. * (3) These input parameters can be set directly on `el`:
  505. *
  506. * @param {module:zrender/Element} el
  507. * @param {Object} [el.hoverStyle] See `graphic.setElementHoverStyle`.
  508. * @param {boolean} [el.highDownSilentOnTouch=false] See `graphic.setAsHighDownDispatcher`.
  509. * @param {Function} [el.highDownOnUpdate] See `graphic.setAsHighDownDispatcher`.
  510. * @param {Object|boolean} [hoverStyle] See `graphic.setElementHoverStyle`.
  511. */
  512. function setHoverStyle(el, hoverStyle) {
  513. setAsHighDownDispatcher(el, true);
  514. traverseUpdate(el, setElementHoverStyle, hoverStyle);
  515. }
  516. /**
  517. * @param {module:zrender/Element} el
  518. * @param {Function} [el.highDownOnUpdate] Called when state updated.
  519. * Since `setHoverStyle` has the constraint that it must be called after
  520. * all of the normal style updated, `highDownOnUpdate` is not needed to
  521. * trigger if both `fromState` and `toState` is 'normal', and needed to
  522. * trigger if both `fromState` and `toState` is 'emphasis', which enables
  523. * to sync outside style settings to "emphasis" state.
  524. * @this {string} This dispatcher `el`.
  525. * @param {string} fromState Can be "normal" or "emphasis".
  526. * `fromState` might equal to `toState`,
  527. * for example, when this method is called when `el` is
  528. * on "emphasis" state.
  529. * @param {string} toState Can be "normal" or "emphasis".
  530. *
  531. * FIXME
  532. * CAUTION: Do not expose `highDownOnUpdate` outside echarts.
  533. * Because it is not a complete solution. The update
  534. * listener should not have been mount in element,
  535. * and the normal/emphasis state should not have
  536. * mantained on elements.
  537. *
  538. * @param {boolean} [el.highDownSilentOnTouch=false]
  539. * In touch device, mouseover event will be trigger on touchstart event
  540. * (see module:zrender/dom/HandlerProxy). By this mechanism, we can
  541. * conveniently use hoverStyle when tap on touch screen without additional
  542. * code for compatibility.
  543. * But if the chart/component has select feature, which usually also use
  544. * hoverStyle, there might be conflict between 'select-highlight' and
  545. * 'hover-highlight' especially when roam is enabled (see geo for example).
  546. * In this case, `highDownSilentOnTouch` should be used to disable
  547. * hover-highlight on touch device.
  548. * @param {boolean} [asDispatcher=true] If `false`, do not set as "highDownDispatcher".
  549. */
  550. function setAsHighDownDispatcher(el, asDispatcher) {
  551. var disable = asDispatcher === false; // Make `highDownSilentOnTouch` and `highDownOnUpdate` only work after
  552. // `setAsHighDownDispatcher` called. Avoid it is modified by user unexpectedly.
  553. el.__highDownSilentOnTouch = el.highDownSilentOnTouch;
  554. el.__highDownOnUpdate = el.highDownOnUpdate; // Simple optimize, since this method might be
  555. // called for each elements of a group in some cases.
  556. if (!disable || el.__highDownDispatcher) {
  557. var method = disable ? 'off' : 'on'; // Duplicated function will be auto-ignored, see Eventful.js.
  558. el[method]('mouseover', onElementMouseOver)[method]('mouseout', onElementMouseOut); // Emphasis, normal can be triggered manually by API or other components like hover link.
  559. el[method]('emphasis', onElementEmphasisEvent)[method]('normal', onElementNormalEvent); // Also keep previous record.
  560. el.__highByOuter = el.__highByOuter || 0;
  561. el.__highDownDispatcher = !disable;
  562. }
  563. }
  564. /**
  565. * @param {module:zrender/src/Element} el
  566. * @return {boolean}
  567. */
  568. function isHighDownDispatcher(el) {
  569. return !!(el && el.__highDownDispatcher);
  570. }
  571. /**
  572. * Support hightlight/downplay record on each elements.
  573. * For the case: hover highlight/downplay (legend, visualMap, ...) and
  574. * user triggerred hightlight/downplay should not conflict.
  575. * Only all of the highlightDigit cleared, return to normal.
  576. * @param {string} highlightKey
  577. * @return {number} highlightDigit
  578. */
  579. function getHighlightDigit(highlightKey) {
  580. var highlightDigit = _highlightKeyMap[highlightKey];
  581. if (highlightDigit == null && _highlightNextDigit <= 32) {
  582. highlightDigit = _highlightKeyMap[highlightKey] = _highlightNextDigit++;
  583. }
  584. return highlightDigit;
  585. }
  586. /**
  587. * See more info in `setTextStyleCommon`.
  588. * @param {Object|module:zrender/graphic/Style} normalStyle
  589. * @param {Object} emphasisStyle
  590. * @param {module:echarts/model/Model} normalModel
  591. * @param {module:echarts/model/Model} emphasisModel
  592. * @param {Object} opt Check `opt` of `setTextStyleCommon` to find other props.
  593. * @param {string|Function} [opt.defaultText]
  594. * @param {module:echarts/model/Model} [opt.labelFetcher] Fetch text by
  595. * `opt.labelFetcher.getFormattedLabel(opt.labelDataIndex, 'normal'/'emphasis', null, opt.labelDimIndex, opt.labelProp)`
  596. * @param {number} [opt.labelDataIndex] Fetch text by
  597. * `opt.textFetcher.getFormattedLabel(opt.labelDataIndex, 'normal'/'emphasis', null, opt.labelDimIndex, opt.labelProp)`
  598. * @param {number} [opt.labelDimIndex] Fetch text by
  599. * `opt.textFetcher.getFormattedLabel(opt.labelDataIndex, 'normal'/'emphasis', null, opt.labelDimIndex, opt.labelProp)`
  600. * @param {string} [opt.labelProp] Fetch text by
  601. * `opt.textFetcher.getFormattedLabel(opt.labelDataIndex, 'normal'/'emphasis', null, opt.labelDimIndex, opt.labelProp)`
  602. * @param {Object} [normalSpecified]
  603. * @param {Object} [emphasisSpecified]
  604. */
  605. function setLabelStyle(normalStyle, emphasisStyle, normalModel, emphasisModel, opt, normalSpecified, emphasisSpecified) {
  606. opt = opt || EMPTY_OBJ;
  607. var labelFetcher = opt.labelFetcher;
  608. var labelDataIndex = opt.labelDataIndex;
  609. var labelDimIndex = opt.labelDimIndex;
  610. var labelProp = opt.labelProp; // This scenario, `label.normal.show = true; label.emphasis.show = false`,
  611. // is not supported util someone requests.
  612. var showNormal = normalModel.getShallow('show');
  613. var showEmphasis = emphasisModel.getShallow('show'); // Consider performance, only fetch label when necessary.
  614. // If `normal.show` is `false` and `emphasis.show` is `true` and `emphasis.formatter` is not set,
  615. // label should be displayed, where text is fetched by `normal.formatter` or `opt.defaultText`.
  616. var baseText;
  617. if (showNormal || showEmphasis) {
  618. if (labelFetcher) {
  619. baseText = labelFetcher.getFormattedLabel(labelDataIndex, 'normal', null, labelDimIndex, labelProp);
  620. }
  621. if (baseText == null) {
  622. baseText = zrUtil.isFunction(opt.defaultText) ? opt.defaultText(labelDataIndex, opt) : opt.defaultText;
  623. }
  624. }
  625. var normalStyleText = showNormal ? baseText : null;
  626. var emphasisStyleText = showEmphasis ? zrUtil.retrieve2(labelFetcher ? labelFetcher.getFormattedLabel(labelDataIndex, 'emphasis', null, labelDimIndex, labelProp) : null, baseText) : null; // Optimize: If style.text is null, text will not be drawn.
  627. if (normalStyleText != null || emphasisStyleText != null) {
  628. // Always set `textStyle` even if `normalStyle.text` is null, because default
  629. // values have to be set on `normalStyle`.
  630. // If we set default values on `emphasisStyle`, consider case:
  631. // Firstly, `setOption(... label: {normal: {text: null}, emphasis: {show: true}} ...);`
  632. // Secondly, `setOption(... label: {noraml: {show: true, text: 'abc', color: 'red'} ...);`
  633. // Then the 'red' will not work on emphasis.
  634. setTextStyle(normalStyle, normalModel, normalSpecified, opt);
  635. setTextStyle(emphasisStyle, emphasisModel, emphasisSpecified, opt, true);
  636. }
  637. normalStyle.text = normalStyleText;
  638. emphasisStyle.text = emphasisStyleText;
  639. }
  640. /**
  641. * Modify label style manually.
  642. * Only works after `setLabelStyle` and `setElementHoverStyle` called.
  643. *
  644. * @param {module:zrender/src/Element} el
  645. * @param {Object} [normalStyleProps] optional
  646. * @param {Object} [emphasisStyleProps] optional
  647. */
  648. function modifyLabelStyle(el, normalStyleProps, emphasisStyleProps) {
  649. var elStyle = el.style;
  650. if (normalStyleProps) {
  651. rollbackDefaultTextStyle(elStyle);
  652. el.setStyle(normalStyleProps);
  653. applyDefaultTextStyle(elStyle);
  654. }
  655. elStyle = el.__hoverStl;
  656. if (emphasisStyleProps && elStyle) {
  657. rollbackDefaultTextStyle(elStyle);
  658. zrUtil.extend(elStyle, emphasisStyleProps);
  659. applyDefaultTextStyle(elStyle);
  660. }
  661. }
  662. /**
  663. * Set basic textStyle properties.
  664. * See more info in `setTextStyleCommon`.
  665. * @param {Object|module:zrender/graphic/Style} textStyle
  666. * @param {module:echarts/model/Model} model
  667. * @param {Object} [specifiedTextStyle] Can be overrided by settings in model.
  668. * @param {Object} [opt] See `opt` of `setTextStyleCommon`.
  669. * @param {boolean} [isEmphasis]
  670. */
  671. function setTextStyle(textStyle, textStyleModel, specifiedTextStyle, opt, isEmphasis) {
  672. setTextStyleCommon(textStyle, textStyleModel, opt, isEmphasis);
  673. specifiedTextStyle && zrUtil.extend(textStyle, specifiedTextStyle); // textStyle.host && textStyle.host.dirty && textStyle.host.dirty(false);
  674. return textStyle;
  675. }
  676. /**
  677. * Set text option in the style.
  678. * See more info in `setTextStyleCommon`.
  679. * @deprecated
  680. * @param {Object} textStyle
  681. * @param {module:echarts/model/Model} labelModel
  682. * @param {string|boolean} defaultColor Default text color.
  683. * If set as false, it will be processed as a emphasis style.
  684. */
  685. function setText(textStyle, labelModel, defaultColor) {
  686. var opt = {
  687. isRectText: true
  688. };
  689. var isEmphasis;
  690. if (defaultColor === false) {
  691. isEmphasis = true;
  692. } else {
  693. // Support setting color as 'auto' to get visual color.
  694. opt.autoColor = defaultColor;
  695. }
  696. setTextStyleCommon(textStyle, labelModel, opt, isEmphasis); // textStyle.host && textStyle.host.dirty && textStyle.host.dirty(false);
  697. }
  698. /**
  699. * The uniform entry of set text style, that is, retrieve style definitions
  700. * from `model` and set to `textStyle` object.
  701. *
  702. * Never in merge mode, but in overwrite mode, that is, all of the text style
  703. * properties will be set. (Consider the states of normal and emphasis and
  704. * default value can be adopted, merge would make the logic too complicated
  705. * to manage.)
  706. *
  707. * The `textStyle` object can either be a plain object or an instance of
  708. * `zrender/src/graphic/Style`, and either be the style of normal or emphasis.
  709. * After this mothod called, the `textStyle` object can then be used in
  710. * `el.setStyle(textStyle)` or `el.hoverStyle = textStyle`.
  711. *
  712. * Default value will be adopted and `insideRollbackOpt` will be created.
  713. * See `applyDefaultTextStyle` `rollbackDefaultTextStyle` for more details.
  714. *
  715. * opt: {
  716. * disableBox: boolean, Whether diable drawing box of block (outer most).
  717. * isRectText: boolean,
  718. * autoColor: string, specify a color when color is 'auto',
  719. * for textFill, textStroke, textBackgroundColor, and textBorderColor.
  720. * If autoColor specified, it is used as default textFill.
  721. * useInsideStyle:
  722. * `true`: Use inside style (textFill, textStroke, textStrokeWidth)
  723. * if `textFill` is not specified.
  724. * `false`: Do not use inside style.
  725. * `null/undefined`: use inside style if `isRectText` is true and
  726. * `textFill` is not specified and textPosition contains `'inside'`.
  727. * forceRich: boolean
  728. * }
  729. */
  730. function setTextStyleCommon(textStyle, textStyleModel, opt, isEmphasis) {
  731. // Consider there will be abnormal when merge hover style to normal style if given default value.
  732. opt = opt || EMPTY_OBJ;
  733. if (opt.isRectText) {
  734. var textPosition;
  735. if (opt.getTextPosition) {
  736. textPosition = opt.getTextPosition(textStyleModel, isEmphasis);
  737. } else {
  738. textPosition = textStyleModel.getShallow('position') || (isEmphasis ? null : 'inside'); // 'outside' is not a valid zr textPostion value, but used
  739. // in bar series, and magric type should be considered.
  740. textPosition === 'outside' && (textPosition = 'top');
  741. }
  742. textStyle.textPosition = textPosition;
  743. textStyle.textOffset = textStyleModel.getShallow('offset');
  744. var labelRotate = textStyleModel.getShallow('rotate');
  745. labelRotate != null && (labelRotate *= Math.PI / 180);
  746. textStyle.textRotation = labelRotate;
  747. textStyle.textDistance = zrUtil.retrieve2(textStyleModel.getShallow('distance'), isEmphasis ? null : 5);
  748. }
  749. var ecModel = textStyleModel.ecModel;
  750. var globalTextStyle = ecModel && ecModel.option.textStyle; // Consider case:
  751. // {
  752. // data: [{
  753. // value: 12,
  754. // label: {
  755. // rich: {
  756. // // no 'a' here but using parent 'a'.
  757. // }
  758. // }
  759. // }],
  760. // rich: {
  761. // a: { ... }
  762. // }
  763. // }
  764. var richItemNames = getRichItemNames(textStyleModel);
  765. var richResult;
  766. if (richItemNames) {
  767. richResult = {};
  768. for (var name in richItemNames) {
  769. if (richItemNames.hasOwnProperty(name)) {
  770. // Cascade is supported in rich.
  771. var richTextStyle = textStyleModel.getModel(['rich', name]); // In rich, never `disableBox`.
  772. // FIXME: consider `label: {formatter: '{a|xx}', color: 'blue', rich: {a: {}}}`,
  773. // the default color `'blue'` will not be adopted if no color declared in `rich`.
  774. // That might confuses users. So probably we should put `textStyleModel` as the
  775. // root ancestor of the `richTextStyle`. But that would be a break change.
  776. setTokenTextStyle(richResult[name] = {}, richTextStyle, globalTextStyle, opt, isEmphasis);
  777. }
  778. }
  779. }
  780. textStyle.rich = richResult;
  781. setTokenTextStyle(textStyle, textStyleModel, globalTextStyle, opt, isEmphasis, true);
  782. if (opt.forceRich && !opt.textStyle) {
  783. opt.textStyle = {};
  784. }
  785. return textStyle;
  786. } // Consider case:
  787. // {
  788. // data: [{
  789. // value: 12,
  790. // label: {
  791. // rich: {
  792. // // no 'a' here but using parent 'a'.
  793. // }
  794. // }
  795. // }],
  796. // rich: {
  797. // a: { ... }
  798. // }
  799. // }
  800. function getRichItemNames(textStyleModel) {
  801. // Use object to remove duplicated names.
  802. var richItemNameMap;
  803. while (textStyleModel && textStyleModel !== textStyleModel.ecModel) {
  804. var rich = (textStyleModel.option || EMPTY_OBJ).rich;
  805. if (rich) {
  806. richItemNameMap = richItemNameMap || {};
  807. for (var name in rich) {
  808. if (rich.hasOwnProperty(name)) {
  809. richItemNameMap[name] = 1;
  810. }
  811. }
  812. }
  813. textStyleModel = textStyleModel.parentModel;
  814. }
  815. return richItemNameMap;
  816. }
  817. function setTokenTextStyle(textStyle, textStyleModel, globalTextStyle, opt, isEmphasis, isBlock) {
  818. // In merge mode, default value should not be given.
  819. globalTextStyle = !isEmphasis && globalTextStyle || EMPTY_OBJ;
  820. textStyle.textFill = getAutoColor(textStyleModel.getShallow('color'), opt) || globalTextStyle.color;
  821. textStyle.textStroke = getAutoColor(textStyleModel.getShallow('textBorderColor'), opt) || globalTextStyle.textBorderColor;
  822. textStyle.textStrokeWidth = zrUtil.retrieve2(textStyleModel.getShallow('textBorderWidth'), globalTextStyle.textBorderWidth);
  823. if (!isEmphasis) {
  824. if (isBlock) {
  825. textStyle.insideRollbackOpt = opt;
  826. applyDefaultTextStyle(textStyle);
  827. } // Set default finally.
  828. if (textStyle.textFill == null) {
  829. textStyle.textFill = opt.autoColor;
  830. }
  831. } // Do not use `getFont` here, because merge should be supported, where
  832. // part of these properties may be changed in emphasis style, and the
  833. // others should remain their original value got from normal style.
  834. textStyle.fontStyle = textStyleModel.getShallow('fontStyle') || globalTextStyle.fontStyle;
  835. textStyle.fontWeight = textStyleModel.getShallow('fontWeight') || globalTextStyle.fontWeight;
  836. textStyle.fontSize = textStyleModel.getShallow('fontSize') || globalTextStyle.fontSize;
  837. textStyle.fontFamily = textStyleModel.getShallow('fontFamily') || globalTextStyle.fontFamily;
  838. textStyle.textAlign = textStyleModel.getShallow('align');
  839. textStyle.textVerticalAlign = textStyleModel.getShallow('verticalAlign') || textStyleModel.getShallow('baseline');
  840. textStyle.textLineHeight = textStyleModel.getShallow('lineHeight');
  841. textStyle.textWidth = textStyleModel.getShallow('width');
  842. textStyle.textHeight = textStyleModel.getShallow('height');
  843. textStyle.textTag = textStyleModel.getShallow('tag');
  844. if (!isBlock || !opt.disableBox) {
  845. textStyle.textBackgroundColor = getAutoColor(textStyleModel.getShallow('backgroundColor'), opt);
  846. textStyle.textPadding = textStyleModel.getShallow('padding');
  847. textStyle.textBorderColor = getAutoColor(textStyleModel.getShallow('borderColor'), opt);
  848. textStyle.textBorderWidth = textStyleModel.getShallow('borderWidth');
  849. textStyle.textBorderRadius = textStyleModel.getShallow('borderRadius');
  850. textStyle.textBoxShadowColor = textStyleModel.getShallow('shadowColor');
  851. textStyle.textBoxShadowBlur = textStyleModel.getShallow('shadowBlur');
  852. textStyle.textBoxShadowOffsetX = textStyleModel.getShallow('shadowOffsetX');
  853. textStyle.textBoxShadowOffsetY = textStyleModel.getShallow('shadowOffsetY');
  854. }
  855. textStyle.textShadowColor = textStyleModel.getShallow('textShadowColor') || globalTextStyle.textShadowColor;
  856. textStyle.textShadowBlur = textStyleModel.getShallow('textShadowBlur') || globalTextStyle.textShadowBlur;
  857. textStyle.textShadowOffsetX = textStyleModel.getShallow('textShadowOffsetX') || globalTextStyle.textShadowOffsetX;
  858. textStyle.textShadowOffsetY = textStyleModel.getShallow('textShadowOffsetY') || globalTextStyle.textShadowOffsetY;
  859. }
  860. function getAutoColor(color, opt) {
  861. return color !== 'auto' ? color : opt && opt.autoColor ? opt.autoColor : null;
  862. }
  863. /**
  864. * Give some default value to the input `textStyle` object, based on the current settings
  865. * in this `textStyle` object.
  866. *
  867. * The Scenario:
  868. * when text position is `inside` and `textFill` is not specified, we show
  869. * text border by default for better view. But it should be considered that text position
  870. * might be changed when hovering or being emphasis, where the `insideRollback` is used to
  871. * restore the style.
  872. *
  873. * Usage (& NOTICE):
  874. * When a style object (eithor plain object or instance of `zrender/src/graphic/Style`) is
  875. * about to be modified on its text related properties, `rollbackDefaultTextStyle` should
  876. * be called before the modification and `applyDefaultTextStyle` should be called after that.
  877. * (For the case that all of the text related properties is reset, like `setTextStyleCommon`
  878. * does, `rollbackDefaultTextStyle` is not needed to be called).
  879. */
  880. function applyDefaultTextStyle(textStyle) {
  881. var textPosition = textStyle.textPosition;
  882. var opt = textStyle.insideRollbackOpt;
  883. var insideRollback;
  884. if (opt && textStyle.textFill == null) {
  885. var autoColor = opt.autoColor;
  886. var isRectText = opt.isRectText;
  887. var useInsideStyle = opt.useInsideStyle;
  888. var useInsideStyleCache = useInsideStyle !== false && (useInsideStyle === true || isRectText && textPosition // textPosition can be [10, 30]
  889. && typeof textPosition === 'string' && textPosition.indexOf('inside') >= 0);
  890. var useAutoColorCache = !useInsideStyleCache && autoColor != null; // All of the props declared in `CACHED_LABEL_STYLE_PROPERTIES` are to be cached.
  891. if (useInsideStyleCache || useAutoColorCache) {
  892. insideRollback = {
  893. textFill: textStyle.textFill,
  894. textStroke: textStyle.textStroke,
  895. textStrokeWidth: textStyle.textStrokeWidth
  896. };
  897. }
  898. if (useInsideStyleCache) {
  899. textStyle.textFill = '#fff'; // Consider text with #fff overflow its container.
  900. if (textStyle.textStroke == null) {
  901. textStyle.textStroke = autoColor;
  902. textStyle.textStrokeWidth == null && (textStyle.textStrokeWidth = 2);
  903. }
  904. }
  905. if (useAutoColorCache) {
  906. textStyle.textFill = autoColor;
  907. }
  908. } // Always set `insideRollback`, so that the previous one can be cleared.
  909. textStyle.insideRollback = insideRollback;
  910. }
  911. /**
  912. * Consider the case: in a scatter,
  913. * label: {
  914. * normal: {position: 'inside'},
  915. * emphasis: {position: 'top'}
  916. * }
  917. * In the normal state, the `textFill` will be set as '#fff' for pretty view (see
  918. * `applyDefaultTextStyle`), but when switching to emphasis state, the `textFill`
  919. * should be retured to 'autoColor', but not keep '#fff'.
  920. */
  921. function rollbackDefaultTextStyle(style) {
  922. var insideRollback = style.insideRollback;
  923. if (insideRollback) {
  924. // Reset all of the props in `CACHED_LABEL_STYLE_PROPERTIES`.
  925. style.textFill = insideRollback.textFill;
  926. style.textStroke = insideRollback.textStroke;
  927. style.textStrokeWidth = insideRollback.textStrokeWidth;
  928. style.insideRollback = null;
  929. }
  930. }
  931. function getFont(opt, ecModel) {
  932. var gTextStyleModel = ecModel && ecModel.getModel('textStyle');
  933. return zrUtil.trim([// FIXME in node-canvas fontWeight is before fontStyle
  934. opt.fontStyle || gTextStyleModel && gTextStyleModel.getShallow('fontStyle') || '', opt.fontWeight || gTextStyleModel && gTextStyleModel.getShallow('fontWeight') || '', (opt.fontSize || gTextStyleModel && gTextStyleModel.getShallow('fontSize') || 12) + 'px', opt.fontFamily || gTextStyleModel && gTextStyleModel.getShallow('fontFamily') || 'sans-serif'].join(' '));
  935. }
  936. function animateOrSetProps(isUpdate, el, props, animatableModel, dataIndex, cb) {
  937. if (typeof dataIndex === 'function') {
  938. cb = dataIndex;
  939. dataIndex = null;
  940. } // Do not check 'animation' property directly here. Consider this case:
  941. // animation model is an `itemModel`, whose does not have `isAnimationEnabled`
  942. // but its parent model (`seriesModel`) does.
  943. var animationEnabled = animatableModel && animatableModel.isAnimationEnabled();
  944. if (animationEnabled) {
  945. var postfix = isUpdate ? 'Update' : '';
  946. var duration = animatableModel.getShallow('animationDuration' + postfix);
  947. var animationEasing = animatableModel.getShallow('animationEasing' + postfix);
  948. var animationDelay = animatableModel.getShallow('animationDelay' + postfix);
  949. if (typeof animationDelay === 'function') {
  950. animationDelay = animationDelay(dataIndex, animatableModel.getAnimationDelayParams ? animatableModel.getAnimationDelayParams(el, dataIndex) : null);
  951. }
  952. if (typeof duration === 'function') {
  953. duration = duration(dataIndex);
  954. }
  955. duration > 0 ? el.animateTo(props, duration, animationDelay || 0, animationEasing, cb, !!cb) : (el.stopAnimation(), el.attr(props), cb && cb());
  956. } else {
  957. el.stopAnimation();
  958. el.attr(props);
  959. cb && cb();
  960. }
  961. }
  962. /**
  963. * Update graphic element properties with or without animation according to the
  964. * configuration in series.
  965. *
  966. * Caution: this method will stop previous animation.
  967. * So do not use this method to one element twice before
  968. * animation starts, unless you know what you are doing.
  969. *
  970. * @param {module:zrender/Element} el
  971. * @param {Object} props
  972. * @param {module:echarts/model/Model} [animatableModel]
  973. * @param {number} [dataIndex]
  974. * @param {Function} [cb]
  975. * @example
  976. * graphic.updateProps(el, {
  977. * position: [100, 100]
  978. * }, seriesModel, dataIndex, function () { console.log('Animation done!'); });
  979. * // Or
  980. * graphic.updateProps(el, {
  981. * position: [100, 100]
  982. * }, seriesModel, function () { console.log('Animation done!'); });
  983. */
  984. function updateProps(el, props, animatableModel, dataIndex, cb) {
  985. animateOrSetProps(true, el, props, animatableModel, dataIndex, cb);
  986. }
  987. /**
  988. * Init graphic element properties with or without animation according to the
  989. * configuration in series.
  990. *
  991. * Caution: this method will stop previous animation.
  992. * So do not use this method to one element twice before
  993. * animation starts, unless you know what you are doing.
  994. *
  995. * @param {module:zrender/Element} el
  996. * @param {Object} props
  997. * @param {module:echarts/model/Model} [animatableModel]
  998. * @param {number} [dataIndex]
  999. * @param {Function} cb
  1000. */
  1001. function initProps(el, props, animatableModel, dataIndex, cb) {
  1002. animateOrSetProps(false, el, props, animatableModel, dataIndex, cb);
  1003. }
  1004. /**
  1005. * Get transform matrix of target (param target),
  1006. * in coordinate of its ancestor (param ancestor)
  1007. *
  1008. * @param {module:zrender/mixin/Transformable} target
  1009. * @param {module:zrender/mixin/Transformable} [ancestor]
  1010. */
  1011. function getTransform(target, ancestor) {
  1012. var mat = matrix.identity([]);
  1013. while (target && target !== ancestor) {
  1014. matrix.mul(mat, target.getLocalTransform(), mat);
  1015. target = target.parent;
  1016. }
  1017. return mat;
  1018. }
  1019. /**
  1020. * Apply transform to an vertex.
  1021. * @param {Array.<number>} target [x, y]
  1022. * @param {Array.<number>|TypedArray.<number>|Object} transform Can be:
  1023. * + Transform matrix: like [1, 0, 0, 1, 0, 0]
  1024. * + {position, rotation, scale}, the same as `zrender/Transformable`.
  1025. * @param {boolean=} invert Whether use invert matrix.
  1026. * @return {Array.<number>} [x, y]
  1027. */
  1028. function applyTransform(target, transform, invert) {
  1029. if (transform && !zrUtil.isArrayLike(transform)) {
  1030. transform = Transformable.getLocalTransform(transform);
  1031. }
  1032. if (invert) {
  1033. transform = matrix.invert([], transform);
  1034. }
  1035. return vector.applyTransform([], target, transform);
  1036. }
  1037. /**
  1038. * @param {string} direction 'left' 'right' 'top' 'bottom'
  1039. * @param {Array.<number>} transform Transform matrix: like [1, 0, 0, 1, 0, 0]
  1040. * @param {boolean=} invert Whether use invert matrix.
  1041. * @return {string} Transformed direction. 'left' 'right' 'top' 'bottom'
  1042. */
  1043. function transformDirection(direction, transform, invert) {
  1044. // Pick a base, ensure that transform result will not be (0, 0).
  1045. var hBase = transform[4] === 0 || transform[5] === 0 || transform[0] === 0 ? 1 : Math.abs(2 * transform[4] / transform[0]);
  1046. var vBase = transform[4] === 0 || transform[5] === 0 || transform[2] === 0 ? 1 : Math.abs(2 * transform[4] / transform[2]);
  1047. var vertex = [direction === 'left' ? -hBase : direction === 'right' ? hBase : 0, direction === 'top' ? -vBase : direction === 'bottom' ? vBase : 0];
  1048. vertex = applyTransform(vertex, transform, invert);
  1049. return Math.abs(vertex[0]) > Math.abs(vertex[1]) ? vertex[0] > 0 ? 'right' : 'left' : vertex[1] > 0 ? 'bottom' : 'top';
  1050. }
  1051. /**
  1052. * Apply group transition animation from g1 to g2.
  1053. * If no animatableModel, no animation.
  1054. */
  1055. function groupTransition(g1, g2, animatableModel, cb) {
  1056. if (!g1 || !g2) {
  1057. return;
  1058. }
  1059. function getElMap(g) {
  1060. var elMap = {};
  1061. g.traverse(function (el) {
  1062. if (!el.isGroup && el.anid) {
  1063. elMap[el.anid] = el;
  1064. }
  1065. });
  1066. return elMap;
  1067. }
  1068. function getAnimatableProps(el) {
  1069. var obj = {
  1070. position: vector.clone(el.position),
  1071. rotation: el.rotation
  1072. };
  1073. if (el.shape) {
  1074. obj.shape = zrUtil.extend({}, el.shape);
  1075. }
  1076. return obj;
  1077. }
  1078. var elMap1 = getElMap(g1);
  1079. g2.traverse(function (el) {
  1080. if (!el.isGroup && el.anid) {
  1081. var oldEl = elMap1[el.anid];
  1082. if (oldEl) {
  1083. var newProp = getAnimatableProps(el);
  1084. el.attr(getAnimatableProps(oldEl));
  1085. updateProps(el, newProp, animatableModel, el.dataIndex);
  1086. } // else {
  1087. // if (el.previousProps) {
  1088. // graphic.updateProps
  1089. // }
  1090. // }
  1091. }
  1092. });
  1093. }
  1094. /**
  1095. * @param {Array.<Array.<number>>} points Like: [[23, 44], [53, 66], ...]
  1096. * @param {Object} rect {x, y, width, height}
  1097. * @return {Array.<Array.<number>>} A new clipped points.
  1098. */
  1099. function clipPointsByRect(points, rect) {
  1100. // FIXME: this way migth be incorrect when grpahic clipped by a corner.
  1101. // and when element have border.
  1102. return zrUtil.map(points, function (point) {
  1103. var x = point[0];
  1104. x = mathMax(x, rect.x);
  1105. x = mathMin(x, rect.x + rect.width);
  1106. var y = point[1];
  1107. y = mathMax(y, rect.y);
  1108. y = mathMin(y, rect.y + rect.height);
  1109. return [x, y];
  1110. });
  1111. }
  1112. /**
  1113. * @param {Object} targetRect {x, y, width, height}
  1114. * @param {Object} rect {x, y, width, height}
  1115. * @return {Object} A new clipped rect. If rect size are negative, return undefined.
  1116. */
  1117. function clipRectByRect(targetRect, rect) {
  1118. var x = mathMax(targetRect.x, rect.x);
  1119. var x2 = mathMin(targetRect.x + targetRect.width, rect.x + rect.width);
  1120. var y = mathMax(targetRect.y, rect.y);
  1121. var y2 = mathMin(targetRect.y + targetRect.height, rect.y + rect.height); // If the total rect is cliped, nothing, including the border,
  1122. // should be painted. So return undefined.
  1123. if (x2 >= x && y2 >= y) {
  1124. return {
  1125. x: x,
  1126. y: y,
  1127. width: x2 - x,
  1128. height: y2 - y
  1129. };
  1130. }
  1131. }
  1132. /**
  1133. * @param {string} iconStr Support 'image://' or 'path://' or direct svg path.
  1134. * @param {Object} [opt] Properties of `module:zrender/Element`, except `style`.
  1135. * @param {Object} [rect] {x, y, width, height}
  1136. * @return {module:zrender/Element} Icon path or image element.
  1137. */
  1138. function createIcon(iconStr, opt, rect) {
  1139. opt = zrUtil.extend({
  1140. rectHover: true
  1141. }, opt);
  1142. var style = opt.style = {
  1143. strokeNoScale: true
  1144. };
  1145. rect = rect || {
  1146. x: -1,
  1147. y: -1,
  1148. width: 2,
  1149. height: 2
  1150. };
  1151. if (iconStr) {
  1152. return iconStr.indexOf('image://') === 0 ? (style.image = iconStr.slice(8), zrUtil.defaults(style, rect), new ZImage(opt)) : makePath(iconStr.replace('path://', ''), opt, rect, 'center');
  1153. }
  1154. }
  1155. /**
  1156. * Return `true` if the given line (line `a`) and the given polygon
  1157. * are intersect.
  1158. * Note that we do not count colinear as intersect here because no
  1159. * requirement for that. We could do that if required in future.
  1160. *
  1161. * @param {number} a1x
  1162. * @param {number} a1y
  1163. * @param {number} a2x
  1164. * @param {number} a2y
  1165. * @param {Array.<Array.<number>>} points Points of the polygon.
  1166. * @return {boolean}
  1167. */
  1168. function linePolygonIntersect(a1x, a1y, a2x, a2y, points) {
  1169. for (var i = 0, p2 = points[points.length - 1]; i < points.length; i++) {
  1170. var p = points[i];
  1171. if (lineLineIntersect(a1x, a1y, a2x, a2y, p[0], p[1], p2[0], p2[1])) {
  1172. return true;
  1173. }
  1174. p2 = p;
  1175. }
  1176. }
  1177. /**
  1178. * Return `true` if the given two lines (line `a` and line `b`)
  1179. * are intersect.
  1180. * Note that we do not count colinear as intersect here because no
  1181. * requirement for that. We could do that if required in future.
  1182. *
  1183. * @param {number} a1x
  1184. * @param {number} a1y
  1185. * @param {number} a2x
  1186. * @param {number} a2y
  1187. * @param {number} b1x
  1188. * @param {number} b1y
  1189. * @param {number} b2x
  1190. * @param {number} b2y
  1191. * @return {boolean}
  1192. */
  1193. function lineLineIntersect(a1x, a1y, a2x, a2y, b1x, b1y, b2x, b2y) {
  1194. // let `vec_m` to be `vec_a2 - vec_a1` and `vec_n` to be `vec_b2 - vec_b1`.
  1195. var mx = a2x - a1x;
  1196. var my = a2y - a1y;
  1197. var nx = b2x - b1x;
  1198. var ny = b2y - b1y; // `vec_m` and `vec_n` are parallel iff
  1199. // exising `k` such that `vec_m = k · vec_n`, equivalent to `vec_m X vec_n = 0`.
  1200. var nmCrossProduct = crossProduct2d(nx, ny, mx, my);
  1201. if (nearZero(nmCrossProduct)) {
  1202. return false;
  1203. } // `vec_m` and `vec_n` are intersect iff
  1204. // existing `p` and `q` in [0, 1] such that `vec_a1 + p * vec_m = vec_b1 + q * vec_n`,
  1205. // such that `q = ((vec_a1 - vec_b1) X vec_m) / (vec_n X vec_m)`
  1206. // and `p = ((vec_a1 - vec_b1) X vec_n) / (vec_n X vec_m)`.
  1207. var b1a1x = a1x - b1x;
  1208. var b1a1y = a1y - b1y;
  1209. var q = crossProduct2d(b1a1x, b1a1y, mx, my) / nmCrossProduct;
  1210. if (q < 0 || q > 1) {
  1211. return false;
  1212. }
  1213. var p = crossProduct2d(b1a1x, b1a1y, nx, ny) / nmCrossProduct;
  1214. if (p < 0 || p > 1) {
  1215. return false;
  1216. }
  1217. return true;
  1218. }
  1219. /**
  1220. * Cross product of 2-dimension vector.
  1221. */
  1222. function crossProduct2d(x1, y1, x2, y2) {
  1223. return x1 * y2 - x2 * y1;
  1224. }
  1225. function nearZero(val) {
  1226. return val <= 1e-6 && val >= -1e-6;
  1227. } // Register built-in shapes. These shapes might be overwirtten
  1228. // by users, although we do not recommend that.
  1229. registerShape('circle', Circle);
  1230. registerShape('sector', Sector);
  1231. registerShape('ring', Ring);
  1232. registerShape('polygon', Polygon);
  1233. registerShape('polyline', Polyline);
  1234. registerShape('rect', Rect);
  1235. registerShape('line', Line);
  1236. registerShape('bezierCurve', BezierCurve);
  1237. registerShape('arc', Arc);
  1238. exports.Z2_EMPHASIS_LIFT = Z2_EMPHASIS_LIFT;
  1239. exports.CACHED_LABEL_STYLE_PROPERTIES = CACHED_LABEL_STYLE_PROPERTIES;
  1240. exports.extendShape = extendShape;
  1241. exports.extendPath = extendPath;
  1242. exports.registerShape = registerShape;
  1243. exports.getShapeClass = getShapeClass;
  1244. exports.makePath = makePath;
  1245. exports.makeImage = makeImage;
  1246. exports.mergePath = mergePath;
  1247. exports.resizePath = resizePath;
  1248. exports.subPixelOptimizeLine = subPixelOptimizeLine;
  1249. exports.subPixelOptimizeRect = subPixelOptimizeRect;
  1250. exports.subPixelOptimize = subPixelOptimize;
  1251. exports.setElementHoverStyle = setElementHoverStyle;
  1252. exports.setHoverStyle = setHoverStyle;
  1253. exports.setAsHighDownDispatcher = setAsHighDownDispatcher;
  1254. exports.isHighDownDispatcher = isHighDownDispatcher;
  1255. exports.getHighlightDigit = getHighlightDigit;
  1256. exports.setLabelStyle = setLabelStyle;
  1257. exports.modifyLabelStyle = modifyLabelStyle;
  1258. exports.setTextStyle = setTextStyle;
  1259. exports.setText = setText;
  1260. exports.getFont = getFont;
  1261. exports.updateProps = updateProps;
  1262. exports.initProps = initProps;
  1263. exports.getTransform = getTransform;
  1264. exports.applyTransform = applyTransform;
  1265. exports.transformDirection = transformDirection;
  1266. exports.groupTransition = groupTransition;
  1267. exports.clipPointsByRect = clipPointsByRect;
  1268. exports.clipRectByRect = clipRectByRect;
  1269. exports.createIcon = createIcon;
  1270. exports.linePolygonIntersect = linePolygonIntersect;
  1271. exports.lineLineIntersect = lineLineIntersect;