Style.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. import fixShadow from './helper/fixShadow';
  2. import {ContextCachedBy} from './constant';
  3. var STYLE_COMMON_PROPS = [
  4. ['shadowBlur', 0], ['shadowOffsetX', 0], ['shadowOffsetY', 0], ['shadowColor', '#000'],
  5. ['lineCap', 'butt'], ['lineJoin', 'miter'], ['miterLimit', 10]
  6. ];
  7. // var SHADOW_PROPS = STYLE_COMMON_PROPS.slice(0, 4);
  8. // var LINE_PROPS = STYLE_COMMON_PROPS.slice(4);
  9. var Style = function (opts) {
  10. this.extendFrom(opts, false);
  11. };
  12. function createLinearGradient(ctx, obj, rect) {
  13. var x = obj.x == null ? 0 : obj.x;
  14. var x2 = obj.x2 == null ? 1 : obj.x2;
  15. var y = obj.y == null ? 0 : obj.y;
  16. var y2 = obj.y2 == null ? 0 : obj.y2;
  17. if (!obj.global) {
  18. x = x * rect.width + rect.x;
  19. x2 = x2 * rect.width + rect.x;
  20. y = y * rect.height + rect.y;
  21. y2 = y2 * rect.height + rect.y;
  22. }
  23. // Fix NaN when rect is Infinity
  24. x = isNaN(x) ? 0 : x;
  25. x2 = isNaN(x2) ? 1 : x2;
  26. y = isNaN(y) ? 0 : y;
  27. y2 = isNaN(y2) ? 0 : y2;
  28. var canvasGradient = ctx.createLinearGradient(x, y, x2, y2);
  29. return canvasGradient;
  30. }
  31. function createRadialGradient(ctx, obj, rect) {
  32. var width = rect.width;
  33. var height = rect.height;
  34. var min = Math.min(width, height);
  35. var x = obj.x == null ? 0.5 : obj.x;
  36. var y = obj.y == null ? 0.5 : obj.y;
  37. var r = obj.r == null ? 0.5 : obj.r;
  38. if (!obj.global) {
  39. x = x * width + rect.x;
  40. y = y * height + rect.y;
  41. r = r * min;
  42. }
  43. var canvasGradient = ctx.createRadialGradient(x, y, 0, x, y, r);
  44. return canvasGradient;
  45. }
  46. Style.prototype = {
  47. constructor: Style,
  48. /**
  49. * @type {string}
  50. */
  51. fill: '#000',
  52. /**
  53. * @type {string}
  54. */
  55. stroke: null,
  56. /**
  57. * @type {number}
  58. */
  59. opacity: 1,
  60. /**
  61. * @type {number}
  62. */
  63. fillOpacity: null,
  64. /**
  65. * @type {number}
  66. */
  67. strokeOpacity: null,
  68. /**
  69. * `true` is not supported.
  70. * `false`/`null`/`undefined` are the same.
  71. * `false` is used to remove lineDash in some
  72. * case that `null`/`undefined` can not be set.
  73. * (e.g., emphasis.lineStyle in echarts)
  74. * @type {Array.<number>|boolean}
  75. */
  76. lineDash: null,
  77. /**
  78. * @type {number}
  79. */
  80. lineDashOffset: 0,
  81. /**
  82. * @type {number}
  83. */
  84. shadowBlur: 0,
  85. /**
  86. * @type {number}
  87. */
  88. shadowOffsetX: 0,
  89. /**
  90. * @type {number}
  91. */
  92. shadowOffsetY: 0,
  93. /**
  94. * @type {number}
  95. */
  96. lineWidth: 1,
  97. /**
  98. * If stroke ignore scale
  99. * @type {Boolean}
  100. */
  101. strokeNoScale: false,
  102. // Bounding rect text configuration
  103. // Not affected by element transform
  104. /**
  105. * @type {string}
  106. */
  107. text: null,
  108. /**
  109. * If `fontSize` or `fontFamily` exists, `font` will be reset by
  110. * `fontSize`, `fontStyle`, `fontWeight`, `fontFamily`.
  111. * So do not visit it directly in upper application (like echarts),
  112. * but use `contain/text#makeFont` instead.
  113. * @type {string}
  114. */
  115. font: null,
  116. /**
  117. * The same as font. Use font please.
  118. * @deprecated
  119. * @type {string}
  120. */
  121. textFont: null,
  122. /**
  123. * It helps merging respectively, rather than parsing an entire font string.
  124. * @type {string}
  125. */
  126. fontStyle: null,
  127. /**
  128. * It helps merging respectively, rather than parsing an entire font string.
  129. * @type {string}
  130. */
  131. fontWeight: null,
  132. /**
  133. * It helps merging respectively, rather than parsing an entire font string.
  134. * Should be 12 but not '12px'.
  135. * @type {number}
  136. */
  137. fontSize: null,
  138. /**
  139. * It helps merging respectively, rather than parsing an entire font string.
  140. * @type {string}
  141. */
  142. fontFamily: null,
  143. /**
  144. * Reserved for special functinality, like 'hr'.
  145. * @type {string}
  146. */
  147. textTag: null,
  148. /**
  149. * @type {string}
  150. */
  151. textFill: '#000',
  152. /**
  153. * @type {string}
  154. */
  155. textStroke: null,
  156. /**
  157. * @type {number}
  158. */
  159. textWidth: null,
  160. /**
  161. * Only for textBackground.
  162. * @type {number}
  163. */
  164. textHeight: null,
  165. /**
  166. * textStroke may be set as some color as a default
  167. * value in upper applicaion, where the default value
  168. * of textStrokeWidth should be 0 to make sure that
  169. * user can choose to do not use text stroke.
  170. * @type {number}
  171. */
  172. textStrokeWidth: 0,
  173. /**
  174. * @type {number}
  175. */
  176. textLineHeight: null,
  177. /**
  178. * 'inside', 'left', 'right', 'top', 'bottom'
  179. * [x, y]
  180. * Based on x, y of rect.
  181. * @type {string|Array.<number>}
  182. * @default 'inside'
  183. */
  184. textPosition: 'inside',
  185. /**
  186. * If not specified, use the boundingRect of a `displayable`.
  187. * @type {Object}
  188. */
  189. textRect: null,
  190. /**
  191. * [x, y]
  192. * @type {Array.<number>}
  193. */
  194. textOffset: null,
  195. /**
  196. * @type {string}
  197. */
  198. textAlign: null,
  199. /**
  200. * @type {string}
  201. */
  202. textVerticalAlign: null,
  203. /**
  204. * @type {number}
  205. */
  206. textDistance: 5,
  207. /**
  208. * @type {string}
  209. */
  210. textShadowColor: 'transparent',
  211. /**
  212. * @type {number}
  213. */
  214. textShadowBlur: 0,
  215. /**
  216. * @type {number}
  217. */
  218. textShadowOffsetX: 0,
  219. /**
  220. * @type {number}
  221. */
  222. textShadowOffsetY: 0,
  223. /**
  224. * @type {string}
  225. */
  226. textBoxShadowColor: 'transparent',
  227. /**
  228. * @type {number}
  229. */
  230. textBoxShadowBlur: 0,
  231. /**
  232. * @type {number}
  233. */
  234. textBoxShadowOffsetX: 0,
  235. /**
  236. * @type {number}
  237. */
  238. textBoxShadowOffsetY: 0,
  239. /**
  240. * Whether transform text.
  241. * Only available in Path and Image element,
  242. * where the text is called as `RectText`.
  243. * @type {boolean}
  244. */
  245. transformText: false,
  246. /**
  247. * Text rotate around position of Path or Image.
  248. * The origin of the rotation can be specified by `textOrigin`.
  249. * Only available in Path and Image element,
  250. * where the text is called as `RectText`.
  251. */
  252. textRotation: 0,
  253. /**
  254. * Text origin of text rotation.
  255. * Useful in the case like label rotation of circular symbol.
  256. * Only available in Path and Image element, where the text is called
  257. * as `RectText` and the element is called as "host element".
  258. * The value can be:
  259. * + If specified as a coordinate like `[10, 40]`, it is the `[x, y]`
  260. * base on the left-top corner of the rect of its host element.
  261. * + If specified as a string `center`, it is the center of the rect of
  262. * its host element.
  263. * + By default, this origin is the `textPosition`.
  264. * @type {string|Array.<number>}
  265. */
  266. textOrigin: null,
  267. /**
  268. * @type {string}
  269. */
  270. textBackgroundColor: null,
  271. /**
  272. * @type {string}
  273. */
  274. textBorderColor: null,
  275. /**
  276. * @type {number}
  277. */
  278. textBorderWidth: 0,
  279. /**
  280. * @type {number}
  281. */
  282. textBorderRadius: 0,
  283. /**
  284. * Can be `2` or `[2, 4]` or `[2, 3, 4, 5]`
  285. * @type {number|Array.<number>}
  286. */
  287. textPadding: null,
  288. /**
  289. * Text styles for rich text.
  290. * @type {Object}
  291. */
  292. rich: null,
  293. /**
  294. * {outerWidth, outerHeight, ellipsis, placeholder}
  295. * @type {Object}
  296. */
  297. truncate: null,
  298. /**
  299. * https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
  300. * @type {string}
  301. */
  302. blend: null,
  303. /**
  304. * @param {CanvasRenderingContext2D} ctx
  305. */
  306. bind: function (ctx, el, prevEl) {
  307. var style = this;
  308. var prevStyle = prevEl && prevEl.style;
  309. // If no prevStyle, it means first draw.
  310. // Only apply cache if the last time cachced by this function.
  311. var notCheckCache = !prevStyle || ctx.__attrCachedBy !== ContextCachedBy.STYLE_BIND;
  312. ctx.__attrCachedBy = ContextCachedBy.STYLE_BIND;
  313. for (var i = 0; i < STYLE_COMMON_PROPS.length; i++) {
  314. var prop = STYLE_COMMON_PROPS[i];
  315. var styleName = prop[0];
  316. if (notCheckCache || style[styleName] !== prevStyle[styleName]) {
  317. // FIXME Invalid property value will cause style leak from previous element.
  318. ctx[styleName] =
  319. fixShadow(ctx, styleName, style[styleName] || prop[1]);
  320. }
  321. }
  322. if ((notCheckCache || style.fill !== prevStyle.fill)) {
  323. ctx.fillStyle = style.fill;
  324. }
  325. if ((notCheckCache || style.stroke !== prevStyle.stroke)) {
  326. ctx.strokeStyle = style.stroke;
  327. }
  328. if ((notCheckCache || style.opacity !== prevStyle.opacity)) {
  329. ctx.globalAlpha = style.opacity == null ? 1 : style.opacity;
  330. }
  331. if ((notCheckCache || style.blend !== prevStyle.blend)) {
  332. ctx.globalCompositeOperation = style.blend || 'source-over';
  333. }
  334. if (this.hasStroke()) {
  335. var lineWidth = style.lineWidth;
  336. ctx.lineWidth = lineWidth / (
  337. (this.strokeNoScale && el && el.getLineScale) ? el.getLineScale() : 1
  338. );
  339. }
  340. },
  341. hasFill: function () {
  342. var fill = this.fill;
  343. return fill != null && fill !== 'none';
  344. },
  345. hasStroke: function () {
  346. var stroke = this.stroke;
  347. return stroke != null && stroke !== 'none' && this.lineWidth > 0;
  348. },
  349. /**
  350. * Extend from other style
  351. * @param {zrender/graphic/Style} otherStyle
  352. * @param {boolean} overwrite true: overwrirte any way.
  353. * false: overwrite only when !target.hasOwnProperty
  354. * others: overwrite when property is not null/undefined.
  355. */
  356. extendFrom: function (otherStyle, overwrite) {
  357. if (otherStyle) {
  358. for (var name in otherStyle) {
  359. if (otherStyle.hasOwnProperty(name)
  360. && (overwrite === true
  361. || (
  362. overwrite === false
  363. ? !this.hasOwnProperty(name)
  364. : otherStyle[name] != null
  365. )
  366. )
  367. ) {
  368. this[name] = otherStyle[name];
  369. }
  370. }
  371. }
  372. },
  373. /**
  374. * Batch setting style with a given object
  375. * @param {Object|string} obj
  376. * @param {*} [obj]
  377. */
  378. set: function (obj, value) {
  379. if (typeof obj === 'string') {
  380. this[obj] = value;
  381. }
  382. else {
  383. this.extendFrom(obj, true);
  384. }
  385. },
  386. /**
  387. * Clone
  388. * @return {zrender/graphic/Style} [description]
  389. */
  390. clone: function () {
  391. var newStyle = new this.constructor();
  392. newStyle.extendFrom(this, true);
  393. return newStyle;
  394. },
  395. getGradient: function (ctx, obj, rect) {
  396. var method = obj.type === 'radial' ? createRadialGradient : createLinearGradient;
  397. var canvasGradient = method(ctx, obj, rect);
  398. var colorStops = obj.colorStops;
  399. for (var i = 0; i < colorStops.length; i++) {
  400. canvasGradient.addColorStop(
  401. colorStops[i].offset, colorStops[i].color
  402. );
  403. }
  404. return canvasGradient;
  405. }
  406. };
  407. var styleProto = Style.prototype;
  408. for (var i = 0; i < STYLE_COMMON_PROPS.length; i++) {
  409. var prop = STYLE_COMMON_PROPS[i];
  410. if (!(prop[0] in styleProto)) {
  411. styleProto[prop[0]] = prop[1];
  412. }
  413. }
  414. // Provide for others
  415. Style.getGradient = styleProto.getGradient;
  416. export default Style;