graphic.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. import { adjustTextY, getIdURL, getMatrixStr, getPathPrecision, getShadowKey, getSRTTransformString, hasShadow, isAroundZero, isGradient, isImagePattern, isLinearGradient, isPattern, isRadialGradient, normalizeColor, round4, TEXT_ALIGN_TO_ANCHOR } from './helper.js';
  2. import Path from '../graphic/Path.js';
  3. import ZRImage from '../graphic/Image.js';
  4. import { getLineHeight } from '../contain/text.js';
  5. import TSpan from '../graphic/TSpan.js';
  6. import SVGPathRebuilder from './SVGPathRebuilder.js';
  7. import mapStyleToAttrs from './mapStyleToAttrs.js';
  8. import { createVNode, vNodeToString } from './core.js';
  9. import { assert, clone, isFunction, isString, logError, map, retrieve2 } from '../core/util.js';
  10. import { createOrUpdateImage } from '../graphic/helper/image.js';
  11. import { createCSSAnimation } from './cssAnimation.js';
  12. import { hasSeparateFont, parseFontSize } from '../graphic/Text.js';
  13. import { DEFAULT_FONT, DEFAULT_FONT_FAMILY } from '../core/platform.js';
  14. var round = Math.round;
  15. function isImageLike(val) {
  16. return val && isString(val.src);
  17. }
  18. function isCanvasLike(val) {
  19. return val && isFunction(val.toDataURL);
  20. }
  21. function setStyleAttrs(attrs, style, el, scope) {
  22. mapStyleToAttrs(function (key, val) {
  23. var isFillStroke = key === 'fill' || key === 'stroke';
  24. if (isFillStroke && isGradient(val)) {
  25. setGradient(style, attrs, key, scope);
  26. }
  27. else if (isFillStroke && isPattern(val)) {
  28. setPattern(el, attrs, key, scope);
  29. }
  30. else {
  31. attrs[key] = val;
  32. }
  33. }, style, el, false);
  34. setShadow(el, attrs, scope);
  35. }
  36. function noRotateScale(m) {
  37. return isAroundZero(m[0] - 1)
  38. && isAroundZero(m[1])
  39. && isAroundZero(m[2])
  40. && isAroundZero(m[3] - 1);
  41. }
  42. function noTranslate(m) {
  43. return isAroundZero(m[4]) && isAroundZero(m[5]);
  44. }
  45. function setTransform(attrs, m, compress) {
  46. if (m && !(noTranslate(m) && noRotateScale(m))) {
  47. var mul = compress ? 10 : 1e4;
  48. attrs.transform = noRotateScale(m)
  49. ? "translate(" + round(m[4] * mul) / mul + " " + round(m[5] * mul) / mul + ")" : getMatrixStr(m);
  50. }
  51. }
  52. function convertPolyShape(shape, attrs, mul) {
  53. var points = shape.points;
  54. var strArr = [];
  55. for (var i = 0; i < points.length; i++) {
  56. strArr.push(round(points[i][0] * mul) / mul);
  57. strArr.push(round(points[i][1] * mul) / mul);
  58. }
  59. attrs.points = strArr.join(' ');
  60. }
  61. function validatePolyShape(shape) {
  62. return !shape.smooth;
  63. }
  64. function createAttrsConvert(desc) {
  65. var normalizedDesc = map(desc, function (item) {
  66. return (typeof item === 'string' ? [item, item] : item);
  67. });
  68. return function (shape, attrs, mul) {
  69. for (var i = 0; i < normalizedDesc.length; i++) {
  70. var item = normalizedDesc[i];
  71. var val = shape[item[0]];
  72. if (val != null) {
  73. attrs[item[1]] = round(val * mul) / mul;
  74. }
  75. }
  76. };
  77. }
  78. var buitinShapesDef = {
  79. circle: [createAttrsConvert(['cx', 'cy', 'r'])],
  80. polyline: [convertPolyShape, validatePolyShape],
  81. polygon: [convertPolyShape, validatePolyShape]
  82. };
  83. function hasShapeAnimation(el) {
  84. var animators = el.animators;
  85. for (var i = 0; i < animators.length; i++) {
  86. if (animators[i].targetName === 'shape') {
  87. return true;
  88. }
  89. }
  90. return false;
  91. }
  92. export function brushSVGPath(el, scope) {
  93. var style = el.style;
  94. var shape = el.shape;
  95. var builtinShpDef = buitinShapesDef[el.type];
  96. var attrs = {};
  97. var needsAnimate = scope.animation;
  98. var svgElType = 'path';
  99. var strokePercent = el.style.strokePercent;
  100. var precision = (scope.compress && getPathPrecision(el)) || 4;
  101. if (builtinShpDef
  102. && !scope.willUpdate
  103. && !(builtinShpDef[1] && !builtinShpDef[1](shape))
  104. && !(needsAnimate && hasShapeAnimation(el))
  105. && !(strokePercent < 1)) {
  106. svgElType = el.type;
  107. var mul = Math.pow(10, precision);
  108. builtinShpDef[0](shape, attrs, mul);
  109. }
  110. else {
  111. if (!el.path) {
  112. el.createPathProxy();
  113. }
  114. var path = el.path;
  115. if (el.shapeChanged()) {
  116. path.beginPath();
  117. el.buildPath(path, el.shape);
  118. el.pathUpdated();
  119. }
  120. var pathVersion = path.getVersion();
  121. var elExt = el;
  122. var svgPathBuilder = elExt.__svgPathBuilder;
  123. if (elExt.__svgPathVersion !== pathVersion
  124. || !svgPathBuilder
  125. || strokePercent !== elExt.__svgPathStrokePercent) {
  126. if (!svgPathBuilder) {
  127. svgPathBuilder = elExt.__svgPathBuilder = new SVGPathRebuilder();
  128. }
  129. svgPathBuilder.reset(precision);
  130. path.rebuildPath(svgPathBuilder, strokePercent);
  131. svgPathBuilder.generateStr();
  132. elExt.__svgPathVersion = pathVersion;
  133. elExt.__svgPathStrokePercent = strokePercent;
  134. }
  135. attrs.d = svgPathBuilder.getStr();
  136. }
  137. setTransform(attrs, el.transform);
  138. setStyleAttrs(attrs, style, el, scope);
  139. scope.animation && createCSSAnimation(el, attrs, scope);
  140. return createVNode(svgElType, el.id + '', attrs);
  141. }
  142. export function brushSVGImage(el, scope) {
  143. var style = el.style;
  144. var image = style.image;
  145. if (image && !isString(image)) {
  146. if (isImageLike(image)) {
  147. image = image.src;
  148. }
  149. else if (isCanvasLike(image)) {
  150. image = image.toDataURL();
  151. }
  152. }
  153. if (!image) {
  154. return;
  155. }
  156. var x = style.x || 0;
  157. var y = style.y || 0;
  158. var dw = style.width;
  159. var dh = style.height;
  160. var attrs = {
  161. href: image,
  162. width: dw,
  163. height: dh
  164. };
  165. if (x) {
  166. attrs.x = x;
  167. }
  168. if (y) {
  169. attrs.y = y;
  170. }
  171. setTransform(attrs, el.transform);
  172. setStyleAttrs(attrs, style, el, scope);
  173. scope.animation && createCSSAnimation(el, attrs, scope);
  174. return createVNode('image', el.id + '', attrs);
  175. }
  176. ;
  177. export function brushSVGTSpan(el, scope) {
  178. var style = el.style;
  179. var text = style.text;
  180. text != null && (text += '');
  181. if (!text || isNaN(style.x) || isNaN(style.y)) {
  182. return;
  183. }
  184. var font = style.font || DEFAULT_FONT;
  185. var x = style.x || 0;
  186. var y = adjustTextY(style.y || 0, getLineHeight(font), style.textBaseline);
  187. var textAlign = TEXT_ALIGN_TO_ANCHOR[style.textAlign]
  188. || style.textAlign;
  189. var attrs = {
  190. 'dominant-baseline': 'central',
  191. 'text-anchor': textAlign
  192. };
  193. if (hasSeparateFont(style)) {
  194. var separatedFontStr = '';
  195. var fontStyle = style.fontStyle;
  196. var fontSize = parseFontSize(style.fontSize);
  197. if (!parseFloat(fontSize)) {
  198. return;
  199. }
  200. var fontFamily = style.fontFamily || DEFAULT_FONT_FAMILY;
  201. var fontWeight = style.fontWeight;
  202. separatedFontStr += "font-size:" + fontSize + ";font-family:" + fontFamily + ";";
  203. if (fontStyle && fontStyle !== 'normal') {
  204. separatedFontStr += "font-style:" + fontStyle + ";";
  205. }
  206. if (fontWeight && fontWeight !== 'normal') {
  207. separatedFontStr += "font-weight:" + fontWeight + ";";
  208. }
  209. attrs.style = separatedFontStr;
  210. }
  211. else {
  212. attrs.style = "font: " + font;
  213. }
  214. if (text.match(/\s/)) {
  215. attrs['xml:space'] = 'preserve';
  216. }
  217. if (x) {
  218. attrs.x = x;
  219. }
  220. if (y) {
  221. attrs.y = y;
  222. }
  223. setTransform(attrs, el.transform);
  224. setStyleAttrs(attrs, style, el, scope);
  225. scope.animation && createCSSAnimation(el, attrs, scope);
  226. return createVNode('text', el.id + '', attrs, undefined, text);
  227. }
  228. export function brush(el, scope) {
  229. if (el instanceof Path) {
  230. return brushSVGPath(el, scope);
  231. }
  232. else if (el instanceof ZRImage) {
  233. return brushSVGImage(el, scope);
  234. }
  235. else if (el instanceof TSpan) {
  236. return brushSVGTSpan(el, scope);
  237. }
  238. }
  239. function setShadow(el, attrs, scope) {
  240. var style = el.style;
  241. if (hasShadow(style)) {
  242. var shadowKey = getShadowKey(el);
  243. var shadowCache = scope.shadowCache;
  244. var shadowId = shadowCache[shadowKey];
  245. if (!shadowId) {
  246. var globalScale = el.getGlobalScale();
  247. var scaleX = globalScale[0];
  248. var scaleY = globalScale[1];
  249. if (!scaleX || !scaleY) {
  250. return;
  251. }
  252. var offsetX = style.shadowOffsetX || 0;
  253. var offsetY = style.shadowOffsetY || 0;
  254. var blur_1 = style.shadowBlur;
  255. var _a = normalizeColor(style.shadowColor), opacity = _a.opacity, color = _a.color;
  256. var stdDx = blur_1 / 2 / scaleX;
  257. var stdDy = blur_1 / 2 / scaleY;
  258. var stdDeviation = stdDx + ' ' + stdDy;
  259. shadowId = scope.zrId + '-s' + scope.shadowIdx++;
  260. scope.defs[shadowId] = createVNode('filter', shadowId, {
  261. 'id': shadowId,
  262. 'x': '-100%',
  263. 'y': '-100%',
  264. 'width': '300%',
  265. 'height': '300%'
  266. }, [
  267. createVNode('feDropShadow', '', {
  268. 'dx': offsetX / scaleX,
  269. 'dy': offsetY / scaleY,
  270. 'stdDeviation': stdDeviation,
  271. 'flood-color': color,
  272. 'flood-opacity': opacity
  273. })
  274. ]);
  275. shadowCache[shadowKey] = shadowId;
  276. }
  277. attrs.filter = getIdURL(shadowId);
  278. }
  279. }
  280. function setGradient(style, attrs, target, scope) {
  281. var val = style[target];
  282. var gradientTag;
  283. var gradientAttrs = {
  284. 'gradientUnits': val.global
  285. ? 'userSpaceOnUse'
  286. : 'objectBoundingBox'
  287. };
  288. if (isLinearGradient(val)) {
  289. gradientTag = 'linearGradient';
  290. gradientAttrs.x1 = val.x;
  291. gradientAttrs.y1 = val.y;
  292. gradientAttrs.x2 = val.x2;
  293. gradientAttrs.y2 = val.y2;
  294. }
  295. else if (isRadialGradient(val)) {
  296. gradientTag = 'radialGradient';
  297. gradientAttrs.cx = retrieve2(val.x, 0.5);
  298. gradientAttrs.cy = retrieve2(val.y, 0.5);
  299. gradientAttrs.r = retrieve2(val.r, 0.5);
  300. }
  301. else {
  302. if (process.env.NODE_ENV !== 'production') {
  303. logError('Illegal gradient type.');
  304. }
  305. return;
  306. }
  307. var colors = val.colorStops;
  308. var colorStops = [];
  309. for (var i = 0, len = colors.length; i < len; ++i) {
  310. var offset = round4(colors[i].offset) * 100 + '%';
  311. var stopColor = colors[i].color;
  312. var _a = normalizeColor(stopColor), color = _a.color, opacity = _a.opacity;
  313. var stopsAttrs = {
  314. 'offset': offset
  315. };
  316. stopsAttrs['stop-color'] = color;
  317. if (opacity < 1) {
  318. stopsAttrs['stop-opacity'] = opacity;
  319. }
  320. colorStops.push(createVNode('stop', i + '', stopsAttrs));
  321. }
  322. var gradientVNode = createVNode(gradientTag, '', gradientAttrs, colorStops);
  323. var gradientKey = vNodeToString(gradientVNode);
  324. var gradientCache = scope.gradientCache;
  325. var gradientId = gradientCache[gradientKey];
  326. if (!gradientId) {
  327. gradientId = scope.zrId + '-g' + scope.gradientIdx++;
  328. gradientCache[gradientKey] = gradientId;
  329. gradientAttrs.id = gradientId;
  330. scope.defs[gradientId] = createVNode(gradientTag, gradientId, gradientAttrs, colorStops);
  331. }
  332. attrs[target] = getIdURL(gradientId);
  333. }
  334. function setPattern(el, attrs, target, scope) {
  335. var val = el.style[target];
  336. var patternAttrs = {
  337. 'patternUnits': 'userSpaceOnUse'
  338. };
  339. var child;
  340. if (isImagePattern(val)) {
  341. var imageWidth_1 = val.imageWidth;
  342. var imageHeight_1 = val.imageHeight;
  343. var imageSrc = void 0;
  344. var patternImage = val.image;
  345. if (isString(patternImage)) {
  346. imageSrc = patternImage;
  347. }
  348. else if (isImageLike(patternImage)) {
  349. imageSrc = patternImage.src;
  350. }
  351. else if (isCanvasLike(patternImage)) {
  352. imageSrc = patternImage.toDataURL();
  353. }
  354. if (typeof Image === 'undefined') {
  355. var errMsg = 'Image width/height must been given explictly in svg-ssr renderer.';
  356. assert(imageWidth_1, errMsg);
  357. assert(imageHeight_1, errMsg);
  358. }
  359. else if (imageWidth_1 == null || imageHeight_1 == null) {
  360. var setSizeToVNode_1 = function (vNode, img) {
  361. if (vNode) {
  362. var svgEl = vNode.elm;
  363. var width = (vNode.attrs.width = imageWidth_1 || img.width);
  364. var height = (vNode.attrs.height = imageHeight_1 || img.height);
  365. if (svgEl) {
  366. svgEl.setAttribute('width', width);
  367. svgEl.setAttribute('height', height);
  368. }
  369. }
  370. };
  371. var createdImage = createOrUpdateImage(imageSrc, null, el, function (img) {
  372. setSizeToVNode_1(patternVNode, img);
  373. setSizeToVNode_1(child, img);
  374. });
  375. if (createdImage && createdImage.width && createdImage.height) {
  376. imageWidth_1 = imageWidth_1 || createdImage.width;
  377. imageHeight_1 = imageHeight_1 || createdImage.height;
  378. }
  379. }
  380. child = createVNode('image', 'img', {
  381. href: imageSrc,
  382. width: imageWidth_1,
  383. height: imageHeight_1
  384. });
  385. patternAttrs.width = imageWidth_1;
  386. patternAttrs.height = imageHeight_1;
  387. }
  388. else if (val.svgElement) {
  389. child = clone(val.svgElement);
  390. patternAttrs.width = val.svgWidth;
  391. patternAttrs.height = val.svgHeight;
  392. }
  393. if (!child) {
  394. return;
  395. }
  396. patternAttrs.patternTransform = getSRTTransformString(val);
  397. var patternVNode = createVNode('pattern', '', patternAttrs, [child]);
  398. var patternKey = vNodeToString(patternVNode);
  399. var patternCache = scope.patternCache;
  400. var patternId = patternCache[patternKey];
  401. if (!patternId) {
  402. patternId = scope.zrId + '-p' + scope.patternIdx++;
  403. patternCache[patternKey] = patternId;
  404. patternAttrs.id = patternId;
  405. patternVNode = scope.defs[patternId] = createVNode('pattern', patternId, patternAttrs, [child]);
  406. }
  407. attrs[target] = getIdURL(patternId);
  408. }
  409. export function setClipPath(clipPath, attrs, scope) {
  410. var clipPathCache = scope.clipPathCache, defs = scope.defs;
  411. var clipPathId = clipPathCache[clipPath.id];
  412. if (!clipPathId) {
  413. clipPathId = scope.zrId + '-c' + scope.clipPathIdx++;
  414. var clipPathAttrs = {
  415. id: clipPathId
  416. };
  417. clipPathCache[clipPath.id] = clipPathId;
  418. defs[clipPathId] = createVNode('clipPath', clipPathId, clipPathAttrs, [brushSVGPath(clipPath, scope)]);
  419. }
  420. attrs['clip-path'] = getIdURL(clipPathId);
  421. }