graphic.js 17 KB

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