graphic.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  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 builtinShapesDef = {
  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 = builtinShapesDef[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. var needBuildPath = !el.path || el.shapeChanged();
  112. if (!el.path) {
  113. el.createPathProxy();
  114. }
  115. var path = el.path;
  116. if (needBuildPath) {
  117. path.beginPath();
  118. el.buildPath(path, el.shape);
  119. el.pathUpdated();
  120. }
  121. var pathVersion = path.getVersion();
  122. var elExt = el;
  123. var svgPathBuilder = elExt.__svgPathBuilder;
  124. if (elExt.__svgPathVersion !== pathVersion
  125. || !svgPathBuilder
  126. || strokePercent !== elExt.__svgPathStrokePercent) {
  127. if (!svgPathBuilder) {
  128. svgPathBuilder = elExt.__svgPathBuilder = new SVGPathRebuilder();
  129. }
  130. svgPathBuilder.reset(precision);
  131. path.rebuildPath(svgPathBuilder, strokePercent);
  132. svgPathBuilder.generateStr();
  133. elExt.__svgPathVersion = pathVersion;
  134. elExt.__svgPathStrokePercent = strokePercent;
  135. }
  136. attrs.d = svgPathBuilder.getStr();
  137. }
  138. setTransform(attrs, el.transform);
  139. setStyleAttrs(attrs, style, el, scope);
  140. scope.animation && createCSSAnimation(el, attrs, scope);
  141. return createVNode(svgElType, el.id + '', attrs);
  142. }
  143. export function brushSVGImage(el, scope) {
  144. var style = el.style;
  145. var image = style.image;
  146. if (image && !isString(image)) {
  147. if (isImageLike(image)) {
  148. image = image.src;
  149. }
  150. else if (isCanvasLike(image)) {
  151. image = image.toDataURL();
  152. }
  153. }
  154. if (!image) {
  155. return;
  156. }
  157. var x = style.x || 0;
  158. var y = style.y || 0;
  159. var dw = style.width;
  160. var dh = style.height;
  161. var attrs = {
  162. href: image,
  163. width: dw,
  164. height: dh
  165. };
  166. if (x) {
  167. attrs.x = x;
  168. }
  169. if (y) {
  170. attrs.y = y;
  171. }
  172. setTransform(attrs, el.transform);
  173. setStyleAttrs(attrs, style, el, scope);
  174. scope.animation && createCSSAnimation(el, attrs, scope);
  175. return createVNode('image', el.id + '', attrs);
  176. }
  177. ;
  178. export function brushSVGTSpan(el, scope) {
  179. var style = el.style;
  180. var text = style.text;
  181. text != null && (text += '');
  182. if (!text || isNaN(style.x) || isNaN(style.y)) {
  183. return;
  184. }
  185. var font = style.font || DEFAULT_FONT;
  186. var x = style.x || 0;
  187. var y = adjustTextY(style.y || 0, getLineHeight(font), style.textBaseline);
  188. var textAlign = TEXT_ALIGN_TO_ANCHOR[style.textAlign]
  189. || style.textAlign;
  190. var attrs = {
  191. 'dominant-baseline': 'central',
  192. 'text-anchor': textAlign
  193. };
  194. if (hasSeparateFont(style)) {
  195. var separatedFontStr = '';
  196. var fontStyle = style.fontStyle;
  197. var fontSize = parseFontSize(style.fontSize);
  198. if (!parseFloat(fontSize)) {
  199. return;
  200. }
  201. var fontFamily = style.fontFamily || DEFAULT_FONT_FAMILY;
  202. var fontWeight = style.fontWeight;
  203. separatedFontStr += "font-size:" + fontSize + ";font-family:" + fontFamily + ";";
  204. if (fontStyle && fontStyle !== 'normal') {
  205. separatedFontStr += "font-style:" + fontStyle + ";";
  206. }
  207. if (fontWeight && fontWeight !== 'normal') {
  208. separatedFontStr += "font-weight:" + fontWeight + ";";
  209. }
  210. attrs.style = separatedFontStr;
  211. }
  212. else {
  213. attrs.style = "font: " + font;
  214. }
  215. if (text.match(/\s/)) {
  216. attrs['xml:space'] = 'preserve';
  217. }
  218. if (x) {
  219. attrs.x = x;
  220. }
  221. if (y) {
  222. attrs.y = y;
  223. }
  224. setTransform(attrs, el.transform);
  225. setStyleAttrs(attrs, style, el, scope);
  226. scope.animation && createCSSAnimation(el, attrs, scope);
  227. return createVNode('text', el.id + '', attrs, undefined, text);
  228. }
  229. export function brush(el, scope) {
  230. if (el instanceof Path) {
  231. return brushSVGPath(el, scope);
  232. }
  233. else if (el instanceof ZRImage) {
  234. return brushSVGImage(el, scope);
  235. }
  236. else if (el instanceof TSpan) {
  237. return brushSVGTSpan(el, scope);
  238. }
  239. }
  240. function setShadow(el, attrs, scope) {
  241. var style = el.style;
  242. if (hasShadow(style)) {
  243. var shadowKey = getShadowKey(el);
  244. var shadowCache = scope.shadowCache;
  245. var shadowId = shadowCache[shadowKey];
  246. if (!shadowId) {
  247. var globalScale = el.getGlobalScale();
  248. var scaleX = globalScale[0];
  249. var scaleY = globalScale[1];
  250. if (!scaleX || !scaleY) {
  251. return;
  252. }
  253. var offsetX = style.shadowOffsetX || 0;
  254. var offsetY = style.shadowOffsetY || 0;
  255. var blur_1 = style.shadowBlur;
  256. var _a = normalizeColor(style.shadowColor), opacity = _a.opacity, color = _a.color;
  257. var stdDx = blur_1 / 2 / scaleX;
  258. var stdDy = blur_1 / 2 / scaleY;
  259. var stdDeviation = stdDx + ' ' + stdDy;
  260. shadowId = scope.zrId + '-s' + scope.shadowIdx++;
  261. scope.defs[shadowId] = createVNode('filter', shadowId, {
  262. 'id': shadowId,
  263. 'x': '-100%',
  264. 'y': '-100%',
  265. 'width': '300%',
  266. 'height': '300%'
  267. }, [
  268. createVNode('feDropShadow', '', {
  269. 'dx': offsetX / scaleX,
  270. 'dy': offsetY / scaleY,
  271. 'stdDeviation': stdDeviation,
  272. 'flood-color': color,
  273. 'flood-opacity': opacity
  274. })
  275. ]);
  276. shadowCache[shadowKey] = shadowId;
  277. }
  278. attrs.filter = getIdURL(shadowId);
  279. }
  280. }
  281. export function setGradient(style, attrs, target, scope) {
  282. var val = style[target];
  283. var gradientTag;
  284. var gradientAttrs = {
  285. 'gradientUnits': val.global
  286. ? 'userSpaceOnUse'
  287. : 'objectBoundingBox'
  288. };
  289. if (isLinearGradient(val)) {
  290. gradientTag = 'linearGradient';
  291. gradientAttrs.x1 = val.x;
  292. gradientAttrs.y1 = val.y;
  293. gradientAttrs.x2 = val.x2;
  294. gradientAttrs.y2 = val.y2;
  295. }
  296. else if (isRadialGradient(val)) {
  297. gradientTag = 'radialGradient';
  298. gradientAttrs.cx = retrieve2(val.x, 0.5);
  299. gradientAttrs.cy = retrieve2(val.y, 0.5);
  300. gradientAttrs.r = retrieve2(val.r, 0.5);
  301. }
  302. else {
  303. if (process.env.NODE_ENV !== 'production') {
  304. logError('Illegal gradient type.');
  305. }
  306. return;
  307. }
  308. var colors = val.colorStops;
  309. var colorStops = [];
  310. for (var i = 0, len = colors.length; i < len; ++i) {
  311. var offset = round4(colors[i].offset) * 100 + '%';
  312. var stopColor = colors[i].color;
  313. var _a = normalizeColor(stopColor), color = _a.color, opacity = _a.opacity;
  314. var stopsAttrs = {
  315. 'offset': offset
  316. };
  317. stopsAttrs['stop-color'] = color;
  318. if (opacity < 1) {
  319. stopsAttrs['stop-opacity'] = opacity;
  320. }
  321. colorStops.push(createVNode('stop', i + '', stopsAttrs));
  322. }
  323. var gradientVNode = createVNode(gradientTag, '', gradientAttrs, colorStops);
  324. var gradientKey = vNodeToString(gradientVNode);
  325. var gradientCache = scope.gradientCache;
  326. var gradientId = gradientCache[gradientKey];
  327. if (!gradientId) {
  328. gradientId = scope.zrId + '-g' + scope.gradientIdx++;
  329. gradientCache[gradientKey] = gradientId;
  330. gradientAttrs.id = gradientId;
  331. scope.defs[gradientId] = createVNode(gradientTag, gradientId, gradientAttrs, colorStops);
  332. }
  333. attrs[target] = getIdURL(gradientId);
  334. }
  335. export function setPattern(el, attrs, target, scope) {
  336. var val = el.style[target];
  337. var boundingRect = el.getBoundingRect();
  338. var patternAttrs = {};
  339. var repeat = val.repeat;
  340. var noRepeat = repeat === 'no-repeat';
  341. var repeatX = repeat === 'repeat-x';
  342. var repeatY = repeat === 'repeat-y';
  343. var child;
  344. if (isImagePattern(val)) {
  345. var imageWidth_1 = val.imageWidth;
  346. var imageHeight_1 = val.imageHeight;
  347. var imageSrc = void 0;
  348. var patternImage = val.image;
  349. if (isString(patternImage)) {
  350. imageSrc = patternImage;
  351. }
  352. else if (isImageLike(patternImage)) {
  353. imageSrc = patternImage.src;
  354. }
  355. else if (isCanvasLike(patternImage)) {
  356. imageSrc = patternImage.toDataURL();
  357. }
  358. if (typeof Image === 'undefined') {
  359. var errMsg = 'Image width/height must been given explictly in svg-ssr renderer.';
  360. assert(imageWidth_1, errMsg);
  361. assert(imageHeight_1, errMsg);
  362. }
  363. else if (imageWidth_1 == null || imageHeight_1 == null) {
  364. var setSizeToVNode_1 = function (vNode, img) {
  365. if (vNode) {
  366. var svgEl = vNode.elm;
  367. var width = imageWidth_1 || img.width;
  368. var height = imageHeight_1 || img.height;
  369. if (vNode.tag === 'pattern') {
  370. if (repeatX) {
  371. height = 1;
  372. width /= boundingRect.width;
  373. }
  374. else if (repeatY) {
  375. width = 1;
  376. height /= boundingRect.height;
  377. }
  378. }
  379. vNode.attrs.width = width;
  380. vNode.attrs.height = height;
  381. if (svgEl) {
  382. svgEl.setAttribute('width', width);
  383. svgEl.setAttribute('height', height);
  384. }
  385. }
  386. };
  387. var createdImage = createOrUpdateImage(imageSrc, null, el, function (img) {
  388. noRepeat || setSizeToVNode_1(patternVNode, img);
  389. setSizeToVNode_1(child, img);
  390. });
  391. if (createdImage && createdImage.width && createdImage.height) {
  392. imageWidth_1 = imageWidth_1 || createdImage.width;
  393. imageHeight_1 = imageHeight_1 || createdImage.height;
  394. }
  395. }
  396. child = createVNode('image', 'img', {
  397. href: imageSrc,
  398. width: imageWidth_1,
  399. height: imageHeight_1
  400. });
  401. patternAttrs.width = imageWidth_1;
  402. patternAttrs.height = imageHeight_1;
  403. }
  404. else if (val.svgElement) {
  405. child = clone(val.svgElement);
  406. patternAttrs.width = val.svgWidth;
  407. patternAttrs.height = val.svgHeight;
  408. }
  409. if (!child) {
  410. return;
  411. }
  412. var patternWidth;
  413. var patternHeight;
  414. if (noRepeat) {
  415. patternWidth = patternHeight = 1;
  416. }
  417. else if (repeatX) {
  418. patternHeight = 1;
  419. patternWidth = patternAttrs.width / boundingRect.width;
  420. }
  421. else if (repeatY) {
  422. patternWidth = 1;
  423. patternHeight = patternAttrs.height / boundingRect.height;
  424. }
  425. else {
  426. patternAttrs.patternUnits = 'userSpaceOnUse';
  427. }
  428. if (patternWidth != null && !isNaN(patternWidth)) {
  429. patternAttrs.width = patternWidth;
  430. }
  431. if (patternHeight != null && !isNaN(patternHeight)) {
  432. patternAttrs.height = patternHeight;
  433. }
  434. var patternTransform = getSRTTransformString(val);
  435. patternTransform && (patternAttrs.patternTransform = patternTransform);
  436. var patternVNode = createVNode('pattern', '', patternAttrs, [child]);
  437. var patternKey = vNodeToString(patternVNode);
  438. var patternCache = scope.patternCache;
  439. var patternId = patternCache[patternKey];
  440. if (!patternId) {
  441. patternId = scope.zrId + '-p' + scope.patternIdx++;
  442. patternCache[patternKey] = patternId;
  443. patternAttrs.id = patternId;
  444. patternVNode = scope.defs[patternId] = createVNode('pattern', patternId, patternAttrs, [child]);
  445. }
  446. attrs[target] = getIdURL(patternId);
  447. }
  448. export function setClipPath(clipPath, attrs, scope) {
  449. var clipPathCache = scope.clipPathCache, defs = scope.defs;
  450. var clipPathId = clipPathCache[clipPath.id];
  451. if (!clipPathId) {
  452. clipPathId = scope.zrId + '-c' + scope.clipPathIdx++;
  453. var clipPathAttrs = {
  454. id: clipPathId
  455. };
  456. clipPathCache[clipPath.id] = clipPathId;
  457. defs[clipPathId] = createVNode('clipPath', clipPathId, clipPathAttrs, [brushSVGPath(clipPath, scope)]);
  458. }
  459. attrs['clip-path'] = getIdURL(clipPathId);
  460. }