graphic.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077
  1. var zrUtil = require("zrender/lib/core/util");
  2. var pathTool = require("zrender/lib/tool/path");
  3. var colorTool = require("zrender/lib/tool/color");
  4. var matrix = require("zrender/lib/core/matrix");
  5. var vector = require("zrender/lib/core/vector");
  6. var Path = require("zrender/lib/graphic/Path");
  7. var Transformable = require("zrender/lib/mixin/Transformable");
  8. var Image = require("zrender/lib/graphic/Image");
  9. exports.Image = Image;
  10. var Group = require("zrender/lib/container/Group");
  11. exports.Group = Group;
  12. var Text = require("zrender/lib/graphic/Text");
  13. exports.Text = Text;
  14. var Circle = require("zrender/lib/graphic/shape/Circle");
  15. exports.Circle = Circle;
  16. var Sector = require("zrender/lib/graphic/shape/Sector");
  17. exports.Sector = Sector;
  18. var Ring = require("zrender/lib/graphic/shape/Ring");
  19. exports.Ring = Ring;
  20. var Polygon = require("zrender/lib/graphic/shape/Polygon");
  21. exports.Polygon = Polygon;
  22. var Polyline = require("zrender/lib/graphic/shape/Polyline");
  23. exports.Polyline = Polyline;
  24. var Rect = require("zrender/lib/graphic/shape/Rect");
  25. exports.Rect = Rect;
  26. var Line = require("zrender/lib/graphic/shape/Line");
  27. exports.Line = Line;
  28. var BezierCurve = require("zrender/lib/graphic/shape/BezierCurve");
  29. exports.BezierCurve = BezierCurve;
  30. var Arc = require("zrender/lib/graphic/shape/Arc");
  31. exports.Arc = Arc;
  32. var CompoundPath = require("zrender/lib/graphic/CompoundPath");
  33. exports.CompoundPath = CompoundPath;
  34. var LinearGradient = require("zrender/lib/graphic/LinearGradient");
  35. exports.LinearGradient = LinearGradient;
  36. var RadialGradient = require("zrender/lib/graphic/RadialGradient");
  37. exports.RadialGradient = RadialGradient;
  38. var BoundingRect = require("zrender/lib/core/BoundingRect");
  39. exports.BoundingRect = BoundingRect;
  40. var round = Math.round;
  41. var mathMax = Math.max;
  42. var mathMin = Math.min;
  43. var EMPTY_OBJ = {};
  44. /**
  45. * Extend shape with parameters
  46. */
  47. function extendShape(opts) {
  48. return Path.extend(opts);
  49. }
  50. /**
  51. * Extend path
  52. */
  53. function extendPath(pathData, opts) {
  54. return pathTool.extendFromString(pathData, opts);
  55. }
  56. /**
  57. * Create a path element from path data string
  58. * @param {string} pathData
  59. * @param {Object} opts
  60. * @param {module:zrender/core/BoundingRect} rect
  61. * @param {string} [layout=cover] 'center' or 'cover'
  62. */
  63. function makePath(pathData, opts, rect, layout) {
  64. var path = pathTool.createFromString(pathData, opts);
  65. var boundingRect = path.getBoundingRect();
  66. if (rect) {
  67. if (layout === 'center') {
  68. rect = centerGraphic(rect, boundingRect);
  69. }
  70. resizePath(path, rect);
  71. }
  72. return path;
  73. }
  74. /**
  75. * Create a image element from image url
  76. * @param {string} imageUrl image url
  77. * @param {Object} opts options
  78. * @param {module:zrender/core/BoundingRect} rect constrain rect
  79. * @param {string} [layout=cover] 'center' or 'cover'
  80. */
  81. function makeImage(imageUrl, rect, layout) {
  82. var path = new Image({
  83. style: {
  84. image: imageUrl,
  85. x: rect.x,
  86. y: rect.y,
  87. width: rect.width,
  88. height: rect.height
  89. },
  90. onload: function (img) {
  91. if (layout === 'center') {
  92. var boundingRect = {
  93. width: img.width,
  94. height: img.height
  95. };
  96. path.setStyle(centerGraphic(rect, boundingRect));
  97. }
  98. }
  99. });
  100. return path;
  101. }
  102. /**
  103. * Get position of centered element in bounding box.
  104. *
  105. * @param {Object} rect element local bounding box
  106. * @param {Object} boundingRect constraint bounding box
  107. * @return {Object} element position containing x, y, width, and height
  108. */
  109. function centerGraphic(rect, boundingRect) {
  110. // Set rect to center, keep width / height ratio.
  111. var aspect = boundingRect.width / boundingRect.height;
  112. var width = rect.height * aspect;
  113. var height;
  114. if (width <= rect.width) {
  115. height = rect.height;
  116. } else {
  117. width = rect.width;
  118. height = width / aspect;
  119. }
  120. var cx = rect.x + rect.width / 2;
  121. var cy = rect.y + rect.height / 2;
  122. return {
  123. x: cx - width / 2,
  124. y: cy - height / 2,
  125. width: width,
  126. height: height
  127. };
  128. }
  129. var mergePath = pathTool.mergePath;
  130. /**
  131. * Resize a path to fit the rect
  132. * @param {module:zrender/graphic/Path} path
  133. * @param {Object} rect
  134. */
  135. function resizePath(path, rect) {
  136. if (!path.applyTransform) {
  137. return;
  138. }
  139. var pathRect = path.getBoundingRect();
  140. var m = pathRect.calculateTransform(rect);
  141. path.applyTransform(m);
  142. }
  143. /**
  144. * Sub pixel optimize line for canvas
  145. *
  146. * @param {Object} param
  147. * @param {Object} [param.shape]
  148. * @param {number} [param.shape.x1]
  149. * @param {number} [param.shape.y1]
  150. * @param {number} [param.shape.x2]
  151. * @param {number} [param.shape.y2]
  152. * @param {Object} [param.style]
  153. * @param {number} [param.style.lineWidth]
  154. * @return {Object} Modified param
  155. */
  156. function subPixelOptimizeLine(param) {
  157. var shape = param.shape;
  158. var lineWidth = param.style.lineWidth;
  159. if (round(shape.x1 * 2) === round(shape.x2 * 2)) {
  160. shape.x1 = shape.x2 = subPixelOptimize(shape.x1, lineWidth, true);
  161. }
  162. if (round(shape.y1 * 2) === round(shape.y2 * 2)) {
  163. shape.y1 = shape.y2 = subPixelOptimize(shape.y1, lineWidth, true);
  164. }
  165. return param;
  166. }
  167. /**
  168. * Sub pixel optimize rect for canvas
  169. *
  170. * @param {Object} param
  171. * @param {Object} [param.shape]
  172. * @param {number} [param.shape.x]
  173. * @param {number} [param.shape.y]
  174. * @param {number} [param.shape.width]
  175. * @param {number} [param.shape.height]
  176. * @param {Object} [param.style]
  177. * @param {number} [param.style.lineWidth]
  178. * @return {Object} Modified param
  179. */
  180. function subPixelOptimizeRect(param) {
  181. var shape = param.shape;
  182. var lineWidth = param.style.lineWidth;
  183. var originX = shape.x;
  184. var originY = shape.y;
  185. var originWidth = shape.width;
  186. var originHeight = shape.height;
  187. shape.x = subPixelOptimize(shape.x, lineWidth, true);
  188. shape.y = subPixelOptimize(shape.y, lineWidth, true);
  189. shape.width = Math.max(subPixelOptimize(originX + originWidth, lineWidth, false) - shape.x, originWidth === 0 ? 0 : 1);
  190. shape.height = Math.max(subPixelOptimize(originY + originHeight, lineWidth, false) - shape.y, originHeight === 0 ? 0 : 1);
  191. return param;
  192. }
  193. /**
  194. * Sub pixel optimize for canvas
  195. *
  196. * @param {number} position Coordinate, such as x, y
  197. * @param {number} lineWidth Should be nonnegative integer.
  198. * @param {boolean=} positiveOrNegative Default false (negative).
  199. * @return {number} Optimized position.
  200. */
  201. function subPixelOptimize(position, lineWidth, positiveOrNegative) {
  202. // Assure that (position + lineWidth / 2) is near integer edge,
  203. // otherwise line will be fuzzy in canvas.
  204. var doubledPosition = round(position * 2);
  205. return (doubledPosition + round(lineWidth)) % 2 === 0 ? doubledPosition / 2 : (doubledPosition + (positiveOrNegative ? 1 : -1)) / 2;
  206. }
  207. function hasFillOrStroke(fillOrStroke) {
  208. return fillOrStroke != null && fillOrStroke != 'none';
  209. }
  210. function liftColor(color) {
  211. return typeof color === 'string' ? colorTool.lift(color, -0.1) : color;
  212. }
  213. /**
  214. * @private
  215. */
  216. function cacheElementStl(el) {
  217. if (el.__hoverStlDirty) {
  218. var stroke = el.style.stroke;
  219. var fill = el.style.fill; // Create hoverStyle on mouseover
  220. var hoverStyle = el.__hoverStl;
  221. hoverStyle.fill = hoverStyle.fill || (hasFillOrStroke(fill) ? liftColor(fill) : null);
  222. hoverStyle.stroke = hoverStyle.stroke || (hasFillOrStroke(stroke) ? liftColor(stroke) : null);
  223. var normalStyle = {};
  224. for (var name in hoverStyle) {
  225. // See comment in `doSingleEnterHover`.
  226. if (hoverStyle[name] != null) {
  227. normalStyle[name] = el.style[name];
  228. }
  229. }
  230. el.__normalStl = normalStyle;
  231. el.__hoverStlDirty = false;
  232. }
  233. }
  234. /**
  235. * @private
  236. */
  237. function doSingleEnterHover(el) {
  238. if (el.__isHover) {
  239. return;
  240. }
  241. cacheElementStl(el);
  242. if (el.useHoverLayer) {
  243. el.__zr && el.__zr.addHover(el, el.__hoverStl);
  244. } else {
  245. var style = el.style;
  246. var insideRollbackOpt = style.insideRollbackOpt; // Consider case: only `position: 'top'` is set on emphasis, then text
  247. // color should be returned to `autoColor`, rather than remain '#fff'.
  248. // So we should rollback then apply again after style merging.
  249. insideRollbackOpt && rollbackInsideStyle(style); // styles can be:
  250. // {
  251. // label: {
  252. // normal: {
  253. // show: false,
  254. // position: 'outside',
  255. // fontSize: 18
  256. // },
  257. // emphasis: {
  258. // show: true
  259. // }
  260. // }
  261. // },
  262. // where properties of `emphasis` may not appear in `normal`. We previously use
  263. // module:echarts/util/model#defaultEmphasis to merge `normal` to `emphasis`.
  264. // But consider rich text and setOption in merge mode, it is impossible to cover
  265. // all properties in merge. So we use merge mode when setting style here, where
  266. // only properties that is not `null/undefined` can be set. The disadventage:
  267. // null/undefined can not be used to remove style any more in `emphasis`.
  268. style.extendFrom(el.__hoverStl); // Do not save `insideRollback`.
  269. if (insideRollbackOpt) {
  270. applyInsideStyle(style, style.insideOriginalTextPosition, insideRollbackOpt); // textFill may be rollbacked to null.
  271. if (style.textFill == null) {
  272. style.textFill = insideRollbackOpt.autoColor;
  273. }
  274. }
  275. el.dirty(false);
  276. el.z2 += 1;
  277. }
  278. el.__isHover = true;
  279. }
  280. /**
  281. * @inner
  282. */
  283. function doSingleLeaveHover(el) {
  284. if (!el.__isHover) {
  285. return;
  286. }
  287. var normalStl = el.__normalStl;
  288. if (el.useHoverLayer) {
  289. el.__zr && el.__zr.removeHover(el);
  290. } else {
  291. // Consider null/undefined value, should use
  292. // `setStyle` but not `extendFrom(stl, true)`.
  293. normalStl && el.setStyle(normalStl);
  294. el.z2 -= 1;
  295. }
  296. el.__isHover = false;
  297. }
  298. /**
  299. * @inner
  300. */
  301. function doEnterHover(el) {
  302. el.type === 'group' ? el.traverse(function (child) {
  303. if (child.type !== 'group') {
  304. doSingleEnterHover(child);
  305. }
  306. }) : doSingleEnterHover(el);
  307. }
  308. function doLeaveHover(el) {
  309. el.type === 'group' ? el.traverse(function (child) {
  310. if (child.type !== 'group') {
  311. doSingleLeaveHover(child);
  312. }
  313. }) : doSingleLeaveHover(el);
  314. }
  315. /**
  316. * @inner
  317. */
  318. function setElementHoverStl(el, hoverStl) {
  319. // If element has sepcified hoverStyle, then use it instead of given hoverStyle
  320. // Often used when item group has a label element and it's hoverStyle is different
  321. el.__hoverStl = el.hoverStyle || hoverStl || {};
  322. el.__hoverStlDirty = true;
  323. if (el.__isHover) {
  324. cacheElementStl(el);
  325. }
  326. }
  327. /**
  328. * @inner
  329. */
  330. function onElementMouseOver(e) {
  331. if (this.__hoverSilentOnTouch && e.zrByTouch) {
  332. return;
  333. } // Only if element is not in emphasis status
  334. !this.__isEmphasis && doEnterHover(this);
  335. }
  336. /**
  337. * @inner
  338. */
  339. function onElementMouseOut(e) {
  340. if (this.__hoverSilentOnTouch && e.zrByTouch) {
  341. return;
  342. } // Only if element is not in emphasis status
  343. !this.__isEmphasis && doLeaveHover(this);
  344. }
  345. /**
  346. * @inner
  347. */
  348. function enterEmphasis() {
  349. this.__isEmphasis = true;
  350. doEnterHover(this);
  351. }
  352. /**
  353. * @inner
  354. */
  355. function leaveEmphasis() {
  356. this.__isEmphasis = false;
  357. doLeaveHover(this);
  358. }
  359. /**
  360. * Set hover style of element.
  361. * This method can be called repeatly without side-effects.
  362. * @param {module:zrender/Element} el
  363. * @param {Object} [hoverStyle]
  364. * @param {Object} [opt]
  365. * @param {boolean} [opt.hoverSilentOnTouch=false]
  366. * In touch device, mouseover event will be trigger on touchstart event
  367. * (see module:zrender/dom/HandlerProxy). By this mechanism, we can
  368. * conviniently use hoverStyle when tap on touch screen without additional
  369. * code for compatibility.
  370. * But if the chart/component has select feature, which usually also use
  371. * hoverStyle, there might be conflict between 'select-highlight' and
  372. * 'hover-highlight' especially when roam is enabled (see geo for example).
  373. * In this case, hoverSilentOnTouch should be used to disable hover-highlight
  374. * on touch device.
  375. */
  376. function setHoverStyle(el, hoverStyle, opt) {
  377. el.__hoverSilentOnTouch = opt && opt.hoverSilentOnTouch;
  378. el.type === 'group' ? el.traverse(function (child) {
  379. if (child.type !== 'group') {
  380. setElementHoverStl(child, hoverStyle);
  381. }
  382. }) : setElementHoverStl(el, hoverStyle); // Duplicated function will be auto-ignored, see Eventful.js.
  383. el.on('mouseover', onElementMouseOver).on('mouseout', onElementMouseOut); // Emphasis, normal can be triggered manually
  384. el.on('emphasis', enterEmphasis).on('normal', leaveEmphasis);
  385. }
  386. /**
  387. * @param {Object|module:zrender/graphic/Style} normalStyle
  388. * @param {Object} emphasisStyle
  389. * @param {module:echarts/model/Model} normalModel
  390. * @param {module:echarts/model/Model} emphasisModel
  391. * @param {Object} opt Check `opt` of `setTextStyleCommon` to find other props.
  392. * @param {Object} [opt.defaultText]
  393. * @param {module:echarts/model/Model} [opt.labelFetcher] Fetch text by
  394. * `opt.labelFetcher.getFormattedLabel(opt.labelDataIndex, 'normal'/'emphasis', null, opt.labelDimIndex)`
  395. * @param {module:echarts/model/Model} [opt.labelDataIndex] Fetch text by
  396. * `opt.textFetcher.getFormattedLabel(opt.labelDataIndex, 'normal'/'emphasis', null, opt.labelDimIndex)`
  397. * @param {module:echarts/model/Model} [opt.labelDimIndex] Fetch text by
  398. * `opt.textFetcher.getFormattedLabel(opt.labelDataIndex, 'normal'/'emphasis', null, opt.labelDimIndex)`
  399. * @param {Object} [normalSpecified]
  400. * @param {Object} [emphasisSpecified]
  401. */
  402. function setLabelStyle(normalStyle, emphasisStyle, normalModel, emphasisModel, opt, normalSpecified, emphasisSpecified) {
  403. opt = opt || EMPTY_OBJ;
  404. var labelFetcher = opt.labelFetcher;
  405. var labelDataIndex = opt.labelDataIndex;
  406. var labelDimIndex = opt.labelDimIndex; // This scenario, `label.normal.show = true; label.emphasis.show = false`,
  407. // is not supported util someone requests.
  408. var showNormal = normalModel.getShallow('show');
  409. var showEmphasis = emphasisModel.getShallow('show'); // Consider performance, only fetch label when necessary.
  410. // If `normal.show` is `false` and `emphasis.show` is `true` and `emphasis.formatter` is not set,
  411. // label should be displayed, where text is fetched by `normal.formatter` or `opt.defaultText`.
  412. var baseText = showNormal || showEmphasis ? zrUtil.retrieve2(labelFetcher ? labelFetcher.getFormattedLabel(labelDataIndex, 'normal', null, labelDimIndex) : null, opt.defaultText) : null;
  413. var normalStyleText = showNormal ? baseText : null;
  414. var emphasisStyleText = showEmphasis ? zrUtil.retrieve2(labelFetcher ? labelFetcher.getFormattedLabel(labelDataIndex, 'emphasis', null, labelDimIndex) : null, baseText) : null; // Optimize: If style.text is null, text will not be drawn.
  415. if (normalStyleText != null || emphasisStyleText != null) {
  416. // Always set `textStyle` even if `normalStyle.text` is null, because default
  417. // values have to be set on `normalStyle`.
  418. // If we set default values on `emphasisStyle`, consider case:
  419. // Firstly, `setOption(... label: {normal: {text: null}, emphasis: {show: true}} ...);`
  420. // Secondly, `setOption(... label: {noraml: {show: true, text: 'abc', color: 'red'} ...);`
  421. // Then the 'red' will not work on emphasis.
  422. setTextStyle(normalStyle, normalModel, normalSpecified, opt);
  423. setTextStyle(emphasisStyle, emphasisModel, emphasisSpecified, opt, true);
  424. }
  425. normalStyle.text = normalStyleText;
  426. emphasisStyle.text = emphasisStyleText;
  427. }
  428. /**
  429. * Set basic textStyle properties.
  430. * @param {Object|module:zrender/graphic/Style} textStyle
  431. * @param {module:echarts/model/Model} model
  432. * @param {Object} [specifiedTextStyle] Can be overrided by settings in model.
  433. * @param {Object} [opt] See `opt` of `setTextStyleCommon`.
  434. * @param {boolean} [isEmphasis]
  435. */
  436. function setTextStyle(textStyle, textStyleModel, specifiedTextStyle, opt, isEmphasis) {
  437. setTextStyleCommon(textStyle, textStyleModel, opt, isEmphasis);
  438. specifiedTextStyle && zrUtil.extend(textStyle, specifiedTextStyle);
  439. textStyle.host && textStyle.host.dirty && textStyle.host.dirty(false);
  440. return textStyle;
  441. }
  442. /**
  443. * Set text option in the style.
  444. * @deprecated
  445. * @param {Object} textStyle
  446. * @param {module:echarts/model/Model} labelModel
  447. * @param {string|boolean} defaultColor Default text color.
  448. * If set as false, it will be processed as a emphasis style.
  449. */
  450. function setText(textStyle, labelModel, defaultColor) {
  451. var opt = {
  452. isRectText: true
  453. };
  454. var isEmphasis;
  455. if (defaultColor === false) {
  456. isEmphasis = true;
  457. } else {
  458. // Support setting color as 'auto' to get visual color.
  459. opt.autoColor = defaultColor;
  460. }
  461. setTextStyleCommon(textStyle, labelModel, opt, isEmphasis);
  462. textStyle.host && textStyle.host.dirty && textStyle.host.dirty(false);
  463. }
  464. /**
  465. * {
  466. * disableBox: boolean, Whether diable drawing box of block (outer most).
  467. * isRectText: boolean,
  468. * autoColor: string, specify a color when color is 'auto',
  469. * for textFill, textStroke, textBackgroundColor, and textBorderColor.
  470. * If autoColor specified, it is used as default textFill.
  471. * useInsideStyle:
  472. * `true`: Use inside style (textFill, textStroke, textStrokeWidth)
  473. * if `textFill` is not specified.
  474. * `false`: Do not use inside style.
  475. * `null/undefined`: use inside style if `isRectText` is true and
  476. * `textFill` is not specified and textPosition contains `'inside'`.
  477. * forceRich: boolean
  478. * }
  479. */
  480. function setTextStyleCommon(textStyle, textStyleModel, opt, isEmphasis) {
  481. // Consider there will be abnormal when merge hover style to normal style if given default value.
  482. opt = opt || EMPTY_OBJ;
  483. if (opt.isRectText) {
  484. var textPosition = textStyleModel.getShallow('position') || (isEmphasis ? null : 'inside'); // 'outside' is not a valid zr textPostion value, but used
  485. // in bar series, and magric type should be considered.
  486. textPosition === 'outside' && (textPosition = 'top');
  487. textStyle.textPosition = textPosition;
  488. textStyle.textOffset = textStyleModel.getShallow('offset');
  489. var labelRotate = textStyleModel.getShallow('rotate');
  490. labelRotate != null && (labelRotate *= Math.PI / 180);
  491. textStyle.textRotation = labelRotate;
  492. textStyle.textDistance = zrUtil.retrieve2(textStyleModel.getShallow('distance'), isEmphasis ? null : 5);
  493. }
  494. var ecModel = textStyleModel.ecModel;
  495. var globalTextStyle = ecModel && ecModel.option.textStyle; // Consider case:
  496. // {
  497. // data: [{
  498. // value: 12,
  499. // label: {
  500. // normal: {
  501. // rich: {
  502. // // no 'a' here but using parent 'a'.
  503. // }
  504. // }
  505. // }
  506. // }],
  507. // rich: {
  508. // a: { ... }
  509. // }
  510. // }
  511. var richItemNames = getRichItemNames(textStyleModel);
  512. var richResult;
  513. if (richItemNames) {
  514. richResult = {};
  515. for (var name in richItemNames) {
  516. if (richItemNames.hasOwnProperty(name)) {
  517. // Cascade is supported in rich.
  518. var richTextStyle = textStyleModel.getModel(['rich', name]); // In rich, never `disableBox`.
  519. setTokenTextStyle(richResult[name] = {}, richTextStyle, globalTextStyle, opt, isEmphasis);
  520. }
  521. }
  522. }
  523. textStyle.rich = richResult;
  524. setTokenTextStyle(textStyle, textStyleModel, globalTextStyle, opt, isEmphasis, true);
  525. if (opt.forceRich && !opt.textStyle) {
  526. opt.textStyle = {};
  527. }
  528. return textStyle;
  529. } // Consider case:
  530. // {
  531. // data: [{
  532. // value: 12,
  533. // label: {
  534. // normal: {
  535. // rich: {
  536. // // no 'a' here but using parent 'a'.
  537. // }
  538. // }
  539. // }
  540. // }],
  541. // rich: {
  542. // a: { ... }
  543. // }
  544. // }
  545. function getRichItemNames(textStyleModel) {
  546. // Use object to remove duplicated names.
  547. var richItemNameMap;
  548. while (textStyleModel && textStyleModel !== textStyleModel.ecModel) {
  549. var rich = (textStyleModel.option || EMPTY_OBJ).rich;
  550. if (rich) {
  551. richItemNameMap = richItemNameMap || {};
  552. for (var name in rich) {
  553. if (rich.hasOwnProperty(name)) {
  554. richItemNameMap[name] = 1;
  555. }
  556. }
  557. }
  558. textStyleModel = textStyleModel.parentModel;
  559. }
  560. return richItemNameMap;
  561. }
  562. function setTokenTextStyle(textStyle, textStyleModel, globalTextStyle, opt, isEmphasis, isBlock) {
  563. // In merge mode, default value should not be given.
  564. globalTextStyle = !isEmphasis && globalTextStyle || EMPTY_OBJ;
  565. textStyle.textFill = getAutoColor(textStyleModel.getShallow('color'), opt) || globalTextStyle.color;
  566. textStyle.textStroke = getAutoColor(textStyleModel.getShallow('textBorderColor'), opt) || globalTextStyle.textBorderColor;
  567. textStyle.textStrokeWidth = zrUtil.retrieve2(textStyleModel.getShallow('textBorderWidth'), globalTextStyle.textBorderWidth);
  568. if (!isEmphasis) {
  569. if (isBlock) {
  570. // Always set `insideRollback`, for clearing previous.
  571. var originalTextPosition = textStyle.textPosition;
  572. textStyle.insideRollback = applyInsideStyle(textStyle, originalTextPosition, opt); // Save original textPosition, because style.textPosition will be repalced by
  573. // real location (like [10, 30]) in zrender.
  574. textStyle.insideOriginalTextPosition = originalTextPosition;
  575. textStyle.insideRollbackOpt = opt;
  576. } // Set default finally.
  577. if (textStyle.textFill == null) {
  578. textStyle.textFill = opt.autoColor;
  579. }
  580. } // Do not use `getFont` here, because merge should be supported, where
  581. // part of these properties may be changed in emphasis style, and the
  582. // others should remain their original value got from normal style.
  583. textStyle.fontStyle = textStyleModel.getShallow('fontStyle') || globalTextStyle.fontStyle;
  584. textStyle.fontWeight = textStyleModel.getShallow('fontWeight') || globalTextStyle.fontWeight;
  585. textStyle.fontSize = textStyleModel.getShallow('fontSize') || globalTextStyle.fontSize;
  586. textStyle.fontFamily = textStyleModel.getShallow('fontFamily') || globalTextStyle.fontFamily;
  587. textStyle.textAlign = textStyleModel.getShallow('align');
  588. textStyle.textVerticalAlign = textStyleModel.getShallow('verticalAlign') || textStyleModel.getShallow('baseline');
  589. textStyle.textLineHeight = textStyleModel.getShallow('lineHeight');
  590. textStyle.textWidth = textStyleModel.getShallow('width');
  591. textStyle.textHeight = textStyleModel.getShallow('height');
  592. textStyle.textTag = textStyleModel.getShallow('tag');
  593. if (!isBlock || !opt.disableBox) {
  594. textStyle.textBackgroundColor = getAutoColor(textStyleModel.getShallow('backgroundColor'), opt);
  595. textStyle.textPadding = textStyleModel.getShallow('padding');
  596. textStyle.textBorderColor = getAutoColor(textStyleModel.getShallow('borderColor'), opt);
  597. textStyle.textBorderWidth = textStyleModel.getShallow('borderWidth');
  598. textStyle.textBorderRadius = textStyleModel.getShallow('borderRadius');
  599. textStyle.textBoxShadowColor = textStyleModel.getShallow('shadowColor');
  600. textStyle.textBoxShadowBlur = textStyleModel.getShallow('shadowBlur');
  601. textStyle.textBoxShadowOffsetX = textStyleModel.getShallow('shadowOffsetX');
  602. textStyle.textBoxShadowOffsetY = textStyleModel.getShallow('shadowOffsetY');
  603. }
  604. textStyle.textShadowColor = textStyleModel.getShallow('textShadowColor') || globalTextStyle.textShadowColor;
  605. textStyle.textShadowBlur = textStyleModel.getShallow('textShadowBlur') || globalTextStyle.textShadowBlur;
  606. textStyle.textShadowOffsetX = textStyleModel.getShallow('textShadowOffsetX') || globalTextStyle.textShadowOffsetX;
  607. textStyle.textShadowOffsetY = textStyleModel.getShallow('textShadowOffsetY') || globalTextStyle.textShadowOffsetY;
  608. }
  609. function getAutoColor(color, opt) {
  610. return color !== 'auto' ? color : opt && opt.autoColor ? opt.autoColor : null;
  611. }
  612. function applyInsideStyle(textStyle, textPosition, opt) {
  613. var useInsideStyle = opt.useInsideStyle;
  614. var insideRollback;
  615. if (textStyle.textFill == null && useInsideStyle !== false && (useInsideStyle === true || opt.isRectText && textPosition // textPosition can be [10, 30]
  616. && typeof textPosition === 'string' && textPosition.indexOf('inside') >= 0)) {
  617. insideRollback = {
  618. textFill: null,
  619. textStroke: textStyle.textStroke,
  620. textStrokeWidth: textStyle.textStrokeWidth
  621. };
  622. textStyle.textFill = '#fff'; // Consider text with #fff overflow its container.
  623. if (textStyle.textStroke == null) {
  624. textStyle.textStroke = opt.autoColor;
  625. textStyle.textStrokeWidth == null && (textStyle.textStrokeWidth = 2);
  626. }
  627. }
  628. return insideRollback;
  629. }
  630. function rollbackInsideStyle(style) {
  631. var insideRollback = style.insideRollback;
  632. if (insideRollback) {
  633. style.textFill = insideRollback.textFill;
  634. style.textStroke = insideRollback.textStroke;
  635. style.textStrokeWidth = insideRollback.textStrokeWidth;
  636. }
  637. }
  638. function getFont(opt, ecModel) {
  639. // ecModel or default text style model.
  640. var gTextStyleModel = ecModel || ecModel.getModel('textStyle');
  641. return [// FIXME in node-canvas fontWeight is before fontStyle
  642. 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(' ');
  643. }
  644. function animateOrSetProps(isUpdate, el, props, animatableModel, dataIndex, cb) {
  645. if (typeof dataIndex === 'function') {
  646. cb = dataIndex;
  647. dataIndex = null;
  648. } // Do not check 'animation' property directly here. Consider this case:
  649. // animation model is an `itemModel`, whose does not have `isAnimationEnabled`
  650. // but its parent model (`seriesModel`) does.
  651. var animationEnabled = animatableModel && animatableModel.isAnimationEnabled();
  652. if (animationEnabled) {
  653. var postfix = isUpdate ? 'Update' : '';
  654. var duration = animatableModel.getShallow('animationDuration' + postfix);
  655. var animationEasing = animatableModel.getShallow('animationEasing' + postfix);
  656. var animationDelay = animatableModel.getShallow('animationDelay' + postfix);
  657. if (typeof animationDelay === 'function') {
  658. animationDelay = animationDelay(dataIndex, animatableModel.getAnimationDelayParams ? animatableModel.getAnimationDelayParams(el, dataIndex) : null);
  659. }
  660. if (typeof duration === 'function') {
  661. duration = duration(dataIndex);
  662. }
  663. duration > 0 ? el.animateTo(props, duration, animationDelay || 0, animationEasing, cb, !!cb) : (el.stopAnimation(), el.attr(props), cb && cb());
  664. } else {
  665. el.stopAnimation();
  666. el.attr(props);
  667. cb && cb();
  668. }
  669. }
  670. /**
  671. * Update graphic element properties with or without animation according to the
  672. * configuration in series.
  673. *
  674. * Caution: this method will stop previous animation.
  675. * So if do not use this method to one element twice before
  676. * animation starts, unless you know what you are doing.
  677. *
  678. * @param {module:zrender/Element} el
  679. * @param {Object} props
  680. * @param {module:echarts/model/Model} [animatableModel]
  681. * @param {number} [dataIndex]
  682. * @param {Function} [cb]
  683. * @example
  684. * graphic.updateProps(el, {
  685. * position: [100, 100]
  686. * }, seriesModel, dataIndex, function () { console.log('Animation done!'); });
  687. * // Or
  688. * graphic.updateProps(el, {
  689. * position: [100, 100]
  690. * }, seriesModel, function () { console.log('Animation done!'); });
  691. */
  692. function updateProps(el, props, animatableModel, dataIndex, cb) {
  693. animateOrSetProps(true, el, props, animatableModel, dataIndex, cb);
  694. }
  695. /**
  696. * Init graphic element properties with or without animation according to the
  697. * configuration in series.
  698. *
  699. * Caution: this method will stop previous animation.
  700. * So if do not use this method to one element twice before
  701. * animation starts, unless you know what you are doing.
  702. *
  703. * @param {module:zrender/Element} el
  704. * @param {Object} props
  705. * @param {module:echarts/model/Model} [animatableModel]
  706. * @param {number} [dataIndex]
  707. * @param {Function} cb
  708. */
  709. function initProps(el, props, animatableModel, dataIndex, cb) {
  710. animateOrSetProps(false, el, props, animatableModel, dataIndex, cb);
  711. }
  712. /**
  713. * Get transform matrix of target (param target),
  714. * in coordinate of its ancestor (param ancestor)
  715. *
  716. * @param {module:zrender/mixin/Transformable} target
  717. * @param {module:zrender/mixin/Transformable} [ancestor]
  718. */
  719. function getTransform(target, ancestor) {
  720. var mat = matrix.identity([]);
  721. while (target && target !== ancestor) {
  722. matrix.mul(mat, target.getLocalTransform(), mat);
  723. target = target.parent;
  724. }
  725. return mat;
  726. }
  727. /**
  728. * Apply transform to an vertex.
  729. * @param {Array.<number>} target [x, y]
  730. * @param {Array.<number>|TypedArray.<number>|Object} transform Can be:
  731. * + Transform matrix: like [1, 0, 0, 1, 0, 0]
  732. * + {position, rotation, scale}, the same as `zrender/Transformable`.
  733. * @param {boolean=} invert Whether use invert matrix.
  734. * @return {Array.<number>} [x, y]
  735. */
  736. function applyTransform(target, transform, invert) {
  737. if (transform && !zrUtil.isArrayLike(transform)) {
  738. transform = Transformable.getLocalTransform(transform);
  739. }
  740. if (invert) {
  741. transform = matrix.invert([], transform);
  742. }
  743. return vector.applyTransform([], target, transform);
  744. }
  745. /**
  746. * @param {string} direction 'left' 'right' 'top' 'bottom'
  747. * @param {Array.<number>} transform Transform matrix: like [1, 0, 0, 1, 0, 0]
  748. * @param {boolean=} invert Whether use invert matrix.
  749. * @return {string} Transformed direction. 'left' 'right' 'top' 'bottom'
  750. */
  751. function transformDirection(direction, transform, invert) {
  752. // Pick a base, ensure that transform result will not be (0, 0).
  753. var hBase = transform[4] === 0 || transform[5] === 0 || transform[0] === 0 ? 1 : Math.abs(2 * transform[4] / transform[0]);
  754. var vBase = transform[4] === 0 || transform[5] === 0 || transform[2] === 0 ? 1 : Math.abs(2 * transform[4] / transform[2]);
  755. var vertex = [direction === 'left' ? -hBase : direction === 'right' ? hBase : 0, direction === 'top' ? -vBase : direction === 'bottom' ? vBase : 0];
  756. vertex = applyTransform(vertex, transform, invert);
  757. return Math.abs(vertex[0]) > Math.abs(vertex[1]) ? vertex[0] > 0 ? 'right' : 'left' : vertex[1] > 0 ? 'bottom' : 'top';
  758. }
  759. /**
  760. * Apply group transition animation from g1 to g2.
  761. * If no animatableModel, no animation.
  762. */
  763. function groupTransition(g1, g2, animatableModel, cb) {
  764. if (!g1 || !g2) {
  765. return;
  766. }
  767. function getElMap(g) {
  768. var elMap = {};
  769. g.traverse(function (el) {
  770. if (!el.isGroup && el.anid) {
  771. elMap[el.anid] = el;
  772. }
  773. });
  774. return elMap;
  775. }
  776. function getAnimatableProps(el) {
  777. var obj = {
  778. position: vector.clone(el.position),
  779. rotation: el.rotation
  780. };
  781. if (el.shape) {
  782. obj.shape = zrUtil.extend({}, el.shape);
  783. }
  784. return obj;
  785. }
  786. var elMap1 = getElMap(g1);
  787. g2.traverse(function (el) {
  788. if (!el.isGroup && el.anid) {
  789. var oldEl = elMap1[el.anid];
  790. if (oldEl) {
  791. var newProp = getAnimatableProps(el);
  792. el.attr(getAnimatableProps(oldEl));
  793. updateProps(el, newProp, animatableModel, el.dataIndex);
  794. } // else {
  795. // if (el.previousProps) {
  796. // graphic.updateProps
  797. // }
  798. // }
  799. }
  800. });
  801. }
  802. /**
  803. * @param {Array.<Array.<number>>} points Like: [[23, 44], [53, 66], ...]
  804. * @param {Object} rect {x, y, width, height}
  805. * @return {Array.<Array.<number>>} A new clipped points.
  806. */
  807. function clipPointsByRect(points, rect) {
  808. return zrUtil.map(points, function (point) {
  809. var x = point[0];
  810. x = mathMax(x, rect.x);
  811. x = mathMin(x, rect.x + rect.width);
  812. var y = point[1];
  813. y = mathMax(y, rect.y);
  814. y = mathMin(y, rect.y + rect.height);
  815. return [x, y];
  816. });
  817. }
  818. /**
  819. * @param {Object} targetRect {x, y, width, height}
  820. * @param {Object} rect {x, y, width, height}
  821. * @return {Object} A new clipped rect. If rect size are negative, return undefined.
  822. */
  823. function clipRectByRect(targetRect, rect) {
  824. var x = mathMax(targetRect.x, rect.x);
  825. var x2 = mathMin(targetRect.x + targetRect.width, rect.x + rect.width);
  826. var y = mathMax(targetRect.y, rect.y);
  827. var y2 = mathMin(targetRect.y + targetRect.height, rect.y + rect.height);
  828. if (x2 >= x && y2 >= y) {
  829. return {
  830. x: x,
  831. y: y,
  832. width: x2 - x,
  833. height: y2 - y
  834. };
  835. }
  836. }
  837. /**
  838. * @param {string} iconStr Support 'image://' or 'path://' or direct svg path.
  839. * @param {Object} [opt] Properties of `module:zrender/Element`, except `style`.
  840. * @param {Object} [rect] {x, y, width, height}
  841. * @return {module:zrender/Element} Icon path or image element.
  842. */
  843. function createIcon(iconStr, opt, rect) {
  844. opt = zrUtil.extend({
  845. rectHover: true
  846. }, opt);
  847. var style = opt.style = {
  848. strokeNoScale: true
  849. };
  850. rect = rect || {
  851. x: -1,
  852. y: -1,
  853. width: 2,
  854. height: 2
  855. };
  856. if (iconStr) {
  857. return iconStr.indexOf('image://') === 0 ? (style.image = iconStr.slice(8), zrUtil.defaults(style, rect), new Image(opt)) : makePath(iconStr.replace('path://', ''), opt, rect, 'center');
  858. }
  859. }
  860. exports.extendShape = extendShape;
  861. exports.extendPath = extendPath;
  862. exports.makePath = makePath;
  863. exports.makeImage = makeImage;
  864. exports.mergePath = mergePath;
  865. exports.resizePath = resizePath;
  866. exports.subPixelOptimizeLine = subPixelOptimizeLine;
  867. exports.subPixelOptimizeRect = subPixelOptimizeRect;
  868. exports.subPixelOptimize = subPixelOptimize;
  869. exports.setHoverStyle = setHoverStyle;
  870. exports.setLabelStyle = setLabelStyle;
  871. exports.setTextStyle = setTextStyle;
  872. exports.setText = setText;
  873. exports.getFont = getFont;
  874. exports.updateProps = updateProps;
  875. exports.initProps = initProps;
  876. exports.getTransform = getTransform;
  877. exports.applyTransform = applyTransform;
  878. exports.transformDirection = transformDirection;
  879. exports.groupTransition = groupTransition;
  880. exports.clipPointsByRect = clipPointsByRect;
  881. exports.clipRectByRect = clipRectByRect;
  882. exports.createIcon = createIcon;