graphic.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. import { createElement, normalizeColor } from './core';
  2. import ZRImage from '../graphic/Image';
  3. import { DEFAULT_FONT, getLineHeight } from '../contain/text';
  4. import { map } from '../core/util';
  5. import { normalizeLineDash } from '../graphic/helper/dashStyle';
  6. var NONE = 'none';
  7. var mathRound = Math.round;
  8. var mathSin = Math.sin;
  9. var mathCos = Math.cos;
  10. var PI = Math.PI;
  11. var PI2 = Math.PI * 2;
  12. var degree = 180 / PI;
  13. var EPSILON = 1e-4;
  14. function round3(val) {
  15. return mathRound(val * 1e3) / 1e3;
  16. }
  17. function round4(val) {
  18. return mathRound(val * 1e4) / 1e4;
  19. }
  20. function isAroundZero(val) {
  21. return val < EPSILON && val > -EPSILON;
  22. }
  23. function pathHasFill(style) {
  24. var fill = style.fill;
  25. return fill != null && fill !== NONE;
  26. }
  27. function pathHasStroke(style) {
  28. var stroke = style.stroke;
  29. return stroke != null && stroke !== NONE;
  30. }
  31. function setTransform(svgEl, m) {
  32. if (m) {
  33. attr(svgEl, 'transform', 'matrix('
  34. + round3(m[0]) + ','
  35. + round3(m[1]) + ','
  36. + round3(m[2]) + ','
  37. + round3(m[3]) + ','
  38. + round4(m[4]) + ','
  39. + round4(m[5])
  40. + ')');
  41. }
  42. }
  43. function attr(el, key, val) {
  44. if (!val || val.type !== 'linear' && val.type !== 'radial') {
  45. el.setAttribute(key, val);
  46. }
  47. }
  48. function attrXLink(el, key, val) {
  49. el.setAttributeNS('http://www.w3.org/1999/xlink', key, val);
  50. }
  51. function attrXML(el, key, val) {
  52. el.setAttributeNS('http://www.w3.org/XML/1998/namespace', key, val);
  53. }
  54. function bindStyle(svgEl, style, el) {
  55. var opacity = style.opacity == null ? 1 : style.opacity;
  56. if (el instanceof ZRImage) {
  57. attr(svgEl, 'opacity', opacity + '');
  58. return;
  59. }
  60. if (pathHasFill(style)) {
  61. var fill = normalizeColor(style.fill);
  62. attr(svgEl, 'fill', fill.color);
  63. attr(svgEl, 'fill-opacity', (style.fillOpacity != null
  64. ? style.fillOpacity * fill.opacity * opacity
  65. : fill.opacity * opacity) + '');
  66. }
  67. else {
  68. attr(svgEl, 'fill', NONE);
  69. }
  70. if (pathHasStroke(style)) {
  71. var stroke = normalizeColor(style.stroke);
  72. attr(svgEl, 'stroke', stroke.color);
  73. var strokeWidth = style.lineWidth;
  74. var strokeScale_1 = style.strokeNoScale
  75. ? el.getLineScale()
  76. : 1;
  77. attr(svgEl, 'stroke-width', (strokeScale_1 ? strokeWidth / strokeScale_1 : 0) + '');
  78. attr(svgEl, 'paint-order', style.strokeFirst ? 'stroke' : 'fill');
  79. attr(svgEl, 'stroke-opacity', (style.strokeOpacity != null
  80. ? style.strokeOpacity * stroke.opacity * opacity
  81. : stroke.opacity * opacity) + '');
  82. var lineDash = style.lineDash && strokeWidth > 0 && normalizeLineDash(style.lineDash, strokeWidth);
  83. if (lineDash) {
  84. var lineDashOffset = style.lineDashOffset;
  85. if (strokeScale_1 && strokeScale_1 !== 1) {
  86. lineDash = map(lineDash, function (rawVal) {
  87. return rawVal / strokeScale_1;
  88. });
  89. if (lineDashOffset) {
  90. lineDashOffset /= strokeScale_1;
  91. lineDashOffset = mathRound(lineDashOffset);
  92. }
  93. }
  94. attr(svgEl, 'stroke-dasharray', lineDash.join(','));
  95. attr(svgEl, 'stroke-dashoffset', (lineDashOffset || 0) + '');
  96. }
  97. else {
  98. attr(svgEl, 'stroke-dasharray', NONE);
  99. }
  100. style.lineCap && attr(svgEl, 'stroke-linecap', style.lineCap);
  101. style.lineJoin && attr(svgEl, 'stroke-linejoin', style.lineJoin);
  102. style.miterLimit && attr(svgEl, 'stroke-miterlimit', style.miterLimit + '');
  103. }
  104. else {
  105. attr(svgEl, 'stroke', NONE);
  106. }
  107. }
  108. var SVGPathRebuilder = (function () {
  109. function SVGPathRebuilder() {
  110. }
  111. SVGPathRebuilder.prototype.reset = function () {
  112. this._d = [];
  113. this._str = '';
  114. };
  115. SVGPathRebuilder.prototype.moveTo = function (x, y) {
  116. this._add('M', x, y);
  117. };
  118. SVGPathRebuilder.prototype.lineTo = function (x, y) {
  119. this._add('L', x, y);
  120. };
  121. SVGPathRebuilder.prototype.bezierCurveTo = function (x, y, x2, y2, x3, y3) {
  122. this._add('C', x, y, x2, y2, x3, y3);
  123. };
  124. SVGPathRebuilder.prototype.quadraticCurveTo = function (x, y, x2, y2) {
  125. this._add('Q', x, y, x2, y2);
  126. };
  127. SVGPathRebuilder.prototype.arc = function (cx, cy, r, startAngle, endAngle, anticlockwise) {
  128. this.ellipse(cx, cy, r, r, 0, startAngle, endAngle, anticlockwise);
  129. };
  130. SVGPathRebuilder.prototype.ellipse = function (cx, cy, rx, ry, psi, startAngle, endAngle, anticlockwise) {
  131. var firstCmd = this._d.length === 0;
  132. var dTheta = endAngle - startAngle;
  133. var clockwise = !anticlockwise;
  134. var dThetaPositive = Math.abs(dTheta);
  135. var isCircle = isAroundZero(dThetaPositive - PI2)
  136. || (clockwise ? dTheta >= PI2 : -dTheta >= PI2);
  137. var unifiedTheta = dTheta > 0 ? dTheta % PI2 : (dTheta % PI2 + PI2);
  138. var large = false;
  139. if (isCircle) {
  140. large = true;
  141. }
  142. else if (isAroundZero(dThetaPositive)) {
  143. large = false;
  144. }
  145. else {
  146. large = (unifiedTheta >= PI) === !!clockwise;
  147. }
  148. var x0 = round4(cx + rx * mathCos(startAngle));
  149. var y0 = round4(cy + ry * mathSin(startAngle));
  150. if (isCircle) {
  151. if (clockwise) {
  152. dTheta = PI2 - 1e-4;
  153. }
  154. else {
  155. dTheta = -PI2 + 1e-4;
  156. }
  157. large = true;
  158. if (firstCmd) {
  159. this._d.push('M', x0, y0);
  160. }
  161. }
  162. var x = round4(cx + rx * mathCos(startAngle + dTheta));
  163. var y = round4(cy + ry * mathSin(startAngle + dTheta));
  164. if (isNaN(x0) || isNaN(y0) || isNaN(rx) || isNaN(ry) || isNaN(psi) || isNaN(degree) || isNaN(x) || isNaN(y)) {
  165. return '';
  166. }
  167. this._d.push('A', round4(rx), round4(ry), mathRound(psi * degree), +large, +clockwise, x, y);
  168. };
  169. SVGPathRebuilder.prototype.rect = function (x, y, w, h) {
  170. this._add('M', x, y);
  171. this._add('L', x + w, y);
  172. this._add('L', x + w, y + h);
  173. this._add('L', x, y + h);
  174. this._add('L', x, y);
  175. this._add('Z');
  176. };
  177. SVGPathRebuilder.prototype.closePath = function () {
  178. if (this._d.length > 0) {
  179. this._add('Z');
  180. }
  181. };
  182. SVGPathRebuilder.prototype._add = function (cmd, a, b, c, d, e, f, g, h) {
  183. this._d.push(cmd);
  184. for (var i = 1; i < arguments.length; i++) {
  185. var val = arguments[i];
  186. if (isNaN(val)) {
  187. this._invalid = true;
  188. return;
  189. }
  190. this._d.push(round4(val));
  191. }
  192. };
  193. SVGPathRebuilder.prototype.generateStr = function () {
  194. this._str = this._invalid ? '' : this._d.join(' ');
  195. this._d = [];
  196. };
  197. SVGPathRebuilder.prototype.getStr = function () {
  198. return this._str;
  199. };
  200. return SVGPathRebuilder;
  201. }());
  202. var svgPath = {
  203. brush: function (el) {
  204. var style = el.style;
  205. var svgEl = el.__svgEl;
  206. if (!svgEl) {
  207. svgEl = createElement('path');
  208. el.__svgEl = svgEl;
  209. }
  210. if (!el.path) {
  211. el.createPathProxy();
  212. }
  213. var path = el.path;
  214. if (el.shapeChanged()) {
  215. path.beginPath();
  216. el.buildPath(path, el.shape);
  217. el.pathUpdated();
  218. }
  219. var pathVersion = path.getVersion();
  220. var elExt = el;
  221. var svgPathBuilder = elExt.__svgPathBuilder;
  222. if (elExt.__svgPathVersion !== pathVersion || !svgPathBuilder || el.style.strokePercent < 1) {
  223. if (!svgPathBuilder) {
  224. svgPathBuilder = elExt.__svgPathBuilder = new SVGPathRebuilder();
  225. }
  226. svgPathBuilder.reset();
  227. path.rebuildPath(svgPathBuilder, el.style.strokePercent);
  228. svgPathBuilder.generateStr();
  229. elExt.__svgPathVersion = pathVersion;
  230. }
  231. attr(svgEl, 'd', svgPathBuilder.getStr());
  232. bindStyle(svgEl, style, el);
  233. setTransform(svgEl, el.transform);
  234. }
  235. };
  236. export { svgPath as path };
  237. var svgImage = {
  238. brush: function (el) {
  239. var style = el.style;
  240. var image = style.image;
  241. if (image instanceof HTMLImageElement) {
  242. image = image.src;
  243. }
  244. else if (image instanceof HTMLCanvasElement) {
  245. image = image.toDataURL();
  246. }
  247. if (!image) {
  248. return;
  249. }
  250. var x = style.x || 0;
  251. var y = style.y || 0;
  252. var dw = style.width;
  253. var dh = style.height;
  254. var svgEl = el.__svgEl;
  255. if (!svgEl) {
  256. svgEl = createElement('image');
  257. el.__svgEl = svgEl;
  258. }
  259. if (image !== el.__imageSrc) {
  260. attrXLink(svgEl, 'href', image);
  261. el.__imageSrc = image;
  262. }
  263. attr(svgEl, 'width', dw + '');
  264. attr(svgEl, 'height', dh + '');
  265. attr(svgEl, 'x', x + '');
  266. attr(svgEl, 'y', y + '');
  267. bindStyle(svgEl, style, el);
  268. setTransform(svgEl, el.transform);
  269. }
  270. };
  271. export { svgImage as image };
  272. var TEXT_ALIGN_TO_ANCHOR = {
  273. left: 'start',
  274. right: 'end',
  275. center: 'middle',
  276. middle: 'middle'
  277. };
  278. function adjustTextY(y, lineHeight, textBaseline) {
  279. if (textBaseline === 'top') {
  280. y += lineHeight / 2;
  281. }
  282. else if (textBaseline === 'bottom') {
  283. y -= lineHeight / 2;
  284. }
  285. return y;
  286. }
  287. var svgText = {
  288. brush: function (el) {
  289. var style = el.style;
  290. var text = style.text;
  291. text != null && (text += '');
  292. if (!text || isNaN(style.x) || isNaN(style.y)) {
  293. return;
  294. }
  295. var textSvgEl = el.__svgEl;
  296. if (!textSvgEl) {
  297. textSvgEl = createElement('text');
  298. attrXML(textSvgEl, 'xml:space', 'preserve');
  299. el.__svgEl = textSvgEl;
  300. }
  301. var font = style.font || DEFAULT_FONT;
  302. var textSvgElStyle = textSvgEl.style;
  303. textSvgElStyle.font = font;
  304. textSvgEl.textContent = text;
  305. bindStyle(textSvgEl, style, el);
  306. setTransform(textSvgEl, el.transform);
  307. var x = style.x || 0;
  308. var y = adjustTextY(style.y || 0, getLineHeight(font), style.textBaseline);
  309. var textAlign = TEXT_ALIGN_TO_ANCHOR[style.textAlign]
  310. || style.textAlign;
  311. attr(textSvgEl, 'dominant-baseline', 'central');
  312. attr(textSvgEl, 'text-anchor', textAlign);
  313. attr(textSvgEl, 'x', x + '');
  314. attr(textSvgEl, 'y', y + '');
  315. }
  316. };
  317. export { svgText as text };