graphic.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. import { DEFAULT_COMMON_STYLE } from '../graphic/Displayable.js';
  2. import PathProxy from '../core/PathProxy.js';
  3. import { createOrUpdateImage, isImageReady } from '../graphic/helper/image.js';
  4. import { getCanvasGradient, isClipPathChanged } from './helper.js';
  5. import Path from '../graphic/Path.js';
  6. import ZRImage from '../graphic/Image.js';
  7. import TSpan from '../graphic/TSpan.js';
  8. import { RADIAN_TO_DEGREE } from '../core/util.js';
  9. import { getLineDash } from './dashStyle.js';
  10. import { REDRAW_BIT, SHAPE_CHANGED_BIT } from '../graphic/constants.js';
  11. import { DEFAULT_FONT } from '../core/platform.js';
  12. var pathProxyForDraw = new PathProxy(true);
  13. function styleHasStroke(style) {
  14. var stroke = style.stroke;
  15. return !(stroke == null || stroke === 'none' || !(style.lineWidth > 0));
  16. }
  17. function isValidStrokeFillStyle(strokeOrFill) {
  18. return typeof strokeOrFill === 'string' && strokeOrFill !== 'none';
  19. }
  20. function styleHasFill(style) {
  21. var fill = style.fill;
  22. return fill != null && fill !== 'none';
  23. }
  24. function doFillPath(ctx, style) {
  25. if (style.fillOpacity != null && style.fillOpacity !== 1) {
  26. var originalGlobalAlpha = ctx.globalAlpha;
  27. ctx.globalAlpha = style.fillOpacity * style.opacity;
  28. ctx.fill();
  29. ctx.globalAlpha = originalGlobalAlpha;
  30. }
  31. else {
  32. ctx.fill();
  33. }
  34. }
  35. function doStrokePath(ctx, style) {
  36. if (style.strokeOpacity != null && style.strokeOpacity !== 1) {
  37. var originalGlobalAlpha = ctx.globalAlpha;
  38. ctx.globalAlpha = style.strokeOpacity * style.opacity;
  39. ctx.stroke();
  40. ctx.globalAlpha = originalGlobalAlpha;
  41. }
  42. else {
  43. ctx.stroke();
  44. }
  45. }
  46. export function createCanvasPattern(ctx, pattern, el) {
  47. var image = createOrUpdateImage(pattern.image, pattern.__image, el);
  48. if (isImageReady(image)) {
  49. var canvasPattern = ctx.createPattern(image, pattern.repeat || 'repeat');
  50. if (typeof DOMMatrix === 'function'
  51. && canvasPattern
  52. && canvasPattern.setTransform) {
  53. var matrix = new DOMMatrix();
  54. matrix.translateSelf((pattern.x || 0), (pattern.y || 0));
  55. matrix.rotateSelf(0, 0, (pattern.rotation || 0) * RADIAN_TO_DEGREE);
  56. matrix.scaleSelf((pattern.scaleX || 1), (pattern.scaleY || 1));
  57. canvasPattern.setTransform(matrix);
  58. }
  59. return canvasPattern;
  60. }
  61. }
  62. function brushPath(ctx, el, style, inBatch) {
  63. var _a;
  64. var hasStroke = styleHasStroke(style);
  65. var hasFill = styleHasFill(style);
  66. var strokePercent = style.strokePercent;
  67. var strokePart = strokePercent < 1;
  68. var firstDraw = !el.path;
  69. if ((!el.silent || strokePart) && firstDraw) {
  70. el.createPathProxy();
  71. }
  72. var path = el.path || pathProxyForDraw;
  73. var dirtyFlag = el.__dirty;
  74. if (!inBatch) {
  75. var fill = style.fill;
  76. var stroke = style.stroke;
  77. var hasFillGradient = hasFill && !!fill.colorStops;
  78. var hasStrokeGradient = hasStroke && !!stroke.colorStops;
  79. var hasFillPattern = hasFill && !!fill.image;
  80. var hasStrokePattern = hasStroke && !!stroke.image;
  81. var fillGradient = void 0;
  82. var strokeGradient = void 0;
  83. var fillPattern = void 0;
  84. var strokePattern = void 0;
  85. var rect = void 0;
  86. if (hasFillGradient || hasStrokeGradient) {
  87. rect = el.getBoundingRect();
  88. }
  89. if (hasFillGradient) {
  90. fillGradient = dirtyFlag
  91. ? getCanvasGradient(ctx, fill, rect)
  92. : el.__canvasFillGradient;
  93. el.__canvasFillGradient = fillGradient;
  94. }
  95. if (hasStrokeGradient) {
  96. strokeGradient = dirtyFlag
  97. ? getCanvasGradient(ctx, stroke, rect)
  98. : el.__canvasStrokeGradient;
  99. el.__canvasStrokeGradient = strokeGradient;
  100. }
  101. if (hasFillPattern) {
  102. fillPattern = (dirtyFlag || !el.__canvasFillPattern)
  103. ? createCanvasPattern(ctx, fill, el)
  104. : el.__canvasFillPattern;
  105. el.__canvasFillPattern = fillPattern;
  106. }
  107. if (hasStrokePattern) {
  108. strokePattern = (dirtyFlag || !el.__canvasStrokePattern)
  109. ? createCanvasPattern(ctx, stroke, el)
  110. : el.__canvasStrokePattern;
  111. el.__canvasStrokePattern = fillPattern;
  112. }
  113. if (hasFillGradient) {
  114. ctx.fillStyle = fillGradient;
  115. }
  116. else if (hasFillPattern) {
  117. if (fillPattern) {
  118. ctx.fillStyle = fillPattern;
  119. }
  120. else {
  121. hasFill = false;
  122. }
  123. }
  124. if (hasStrokeGradient) {
  125. ctx.strokeStyle = strokeGradient;
  126. }
  127. else if (hasStrokePattern) {
  128. if (strokePattern) {
  129. ctx.strokeStyle = strokePattern;
  130. }
  131. else {
  132. hasStroke = false;
  133. }
  134. }
  135. }
  136. var scale = el.getGlobalScale();
  137. path.setScale(scale[0], scale[1], el.segmentIgnoreThreshold);
  138. var lineDash;
  139. var lineDashOffset;
  140. if (ctx.setLineDash && style.lineDash) {
  141. _a = getLineDash(el), lineDash = _a[0], lineDashOffset = _a[1];
  142. }
  143. var needsRebuild = true;
  144. if (firstDraw || (dirtyFlag & SHAPE_CHANGED_BIT)) {
  145. path.setDPR(ctx.dpr);
  146. if (strokePart) {
  147. path.setContext(null);
  148. }
  149. else {
  150. path.setContext(ctx);
  151. needsRebuild = false;
  152. }
  153. path.reset();
  154. el.buildPath(path, el.shape, inBatch);
  155. path.toStatic();
  156. el.pathUpdated();
  157. }
  158. if (needsRebuild) {
  159. path.rebuildPath(ctx, strokePart ? strokePercent : 1);
  160. }
  161. if (lineDash) {
  162. ctx.setLineDash(lineDash);
  163. ctx.lineDashOffset = lineDashOffset;
  164. }
  165. if (!inBatch) {
  166. if (style.strokeFirst) {
  167. if (hasStroke) {
  168. doStrokePath(ctx, style);
  169. }
  170. if (hasFill) {
  171. doFillPath(ctx, style);
  172. }
  173. }
  174. else {
  175. if (hasFill) {
  176. doFillPath(ctx, style);
  177. }
  178. if (hasStroke) {
  179. doStrokePath(ctx, style);
  180. }
  181. }
  182. }
  183. if (lineDash) {
  184. ctx.setLineDash([]);
  185. }
  186. }
  187. function brushImage(ctx, el, style) {
  188. var image = el.__image = createOrUpdateImage(style.image, el.__image, el, el.onload);
  189. if (!image || !isImageReady(image)) {
  190. return;
  191. }
  192. var x = style.x || 0;
  193. var y = style.y || 0;
  194. var width = el.getWidth();
  195. var height = el.getHeight();
  196. var aspect = image.width / image.height;
  197. if (width == null && height != null) {
  198. width = height * aspect;
  199. }
  200. else if (height == null && width != null) {
  201. height = width / aspect;
  202. }
  203. else if (width == null && height == null) {
  204. width = image.width;
  205. height = image.height;
  206. }
  207. if (style.sWidth && style.sHeight) {
  208. var sx = style.sx || 0;
  209. var sy = style.sy || 0;
  210. ctx.drawImage(image, sx, sy, style.sWidth, style.sHeight, x, y, width, height);
  211. }
  212. else if (style.sx && style.sy) {
  213. var sx = style.sx;
  214. var sy = style.sy;
  215. var sWidth = width - sx;
  216. var sHeight = height - sy;
  217. ctx.drawImage(image, sx, sy, sWidth, sHeight, x, y, width, height);
  218. }
  219. else {
  220. ctx.drawImage(image, x, y, width, height);
  221. }
  222. }
  223. function brushText(ctx, el, style) {
  224. var _a;
  225. var text = style.text;
  226. text != null && (text += '');
  227. if (text) {
  228. ctx.font = style.font || DEFAULT_FONT;
  229. ctx.textAlign = style.textAlign;
  230. ctx.textBaseline = style.textBaseline;
  231. var lineDash = void 0;
  232. var lineDashOffset = void 0;
  233. if (ctx.setLineDash && style.lineDash) {
  234. _a = getLineDash(el), lineDash = _a[0], lineDashOffset = _a[1];
  235. }
  236. if (lineDash) {
  237. ctx.setLineDash(lineDash);
  238. ctx.lineDashOffset = lineDashOffset;
  239. }
  240. if (style.strokeFirst) {
  241. if (styleHasStroke(style)) {
  242. ctx.strokeText(text, style.x, style.y);
  243. }
  244. if (styleHasFill(style)) {
  245. ctx.fillText(text, style.x, style.y);
  246. }
  247. }
  248. else {
  249. if (styleHasFill(style)) {
  250. ctx.fillText(text, style.x, style.y);
  251. }
  252. if (styleHasStroke(style)) {
  253. ctx.strokeText(text, style.x, style.y);
  254. }
  255. }
  256. if (lineDash) {
  257. ctx.setLineDash([]);
  258. }
  259. }
  260. }
  261. var SHADOW_NUMBER_PROPS = ['shadowBlur', 'shadowOffsetX', 'shadowOffsetY'];
  262. var STROKE_PROPS = [
  263. ['lineCap', 'butt'], ['lineJoin', 'miter'], ['miterLimit', 10]
  264. ];
  265. function bindCommonProps(ctx, style, prevStyle, forceSetAll, scope) {
  266. var styleChanged = false;
  267. if (!forceSetAll) {
  268. prevStyle = prevStyle || {};
  269. if (style === prevStyle) {
  270. return false;
  271. }
  272. }
  273. if (forceSetAll || style.opacity !== prevStyle.opacity) {
  274. flushPathDrawn(ctx, scope);
  275. styleChanged = true;
  276. var opacity = Math.max(Math.min(style.opacity, 1), 0);
  277. ctx.globalAlpha = isNaN(opacity) ? DEFAULT_COMMON_STYLE.opacity : opacity;
  278. }
  279. if (forceSetAll || style.blend !== prevStyle.blend) {
  280. if (!styleChanged) {
  281. flushPathDrawn(ctx, scope);
  282. styleChanged = true;
  283. }
  284. ctx.globalCompositeOperation = style.blend || DEFAULT_COMMON_STYLE.blend;
  285. }
  286. for (var i = 0; i < SHADOW_NUMBER_PROPS.length; i++) {
  287. var propName = SHADOW_NUMBER_PROPS[i];
  288. if (forceSetAll || style[propName] !== prevStyle[propName]) {
  289. if (!styleChanged) {
  290. flushPathDrawn(ctx, scope);
  291. styleChanged = true;
  292. }
  293. ctx[propName] = ctx.dpr * (style[propName] || 0);
  294. }
  295. }
  296. if (forceSetAll || style.shadowColor !== prevStyle.shadowColor) {
  297. if (!styleChanged) {
  298. flushPathDrawn(ctx, scope);
  299. styleChanged = true;
  300. }
  301. ctx.shadowColor = style.shadowColor || DEFAULT_COMMON_STYLE.shadowColor;
  302. }
  303. return styleChanged;
  304. }
  305. function bindPathAndTextCommonStyle(ctx, el, prevEl, forceSetAll, scope) {
  306. var style = getStyle(el, scope.inHover);
  307. var prevStyle = forceSetAll
  308. ? null
  309. : (prevEl && getStyle(prevEl, scope.inHover) || {});
  310. if (style === prevStyle) {
  311. return false;
  312. }
  313. var styleChanged = bindCommonProps(ctx, style, prevStyle, forceSetAll, scope);
  314. if (forceSetAll || style.fill !== prevStyle.fill) {
  315. if (!styleChanged) {
  316. flushPathDrawn(ctx, scope);
  317. styleChanged = true;
  318. }
  319. isValidStrokeFillStyle(style.fill) && (ctx.fillStyle = style.fill);
  320. }
  321. if (forceSetAll || style.stroke !== prevStyle.stroke) {
  322. if (!styleChanged) {
  323. flushPathDrawn(ctx, scope);
  324. styleChanged = true;
  325. }
  326. isValidStrokeFillStyle(style.stroke) && (ctx.strokeStyle = style.stroke);
  327. }
  328. if (forceSetAll || style.opacity !== prevStyle.opacity) {
  329. if (!styleChanged) {
  330. flushPathDrawn(ctx, scope);
  331. styleChanged = true;
  332. }
  333. ctx.globalAlpha = style.opacity == null ? 1 : style.opacity;
  334. }
  335. if (el.hasStroke()) {
  336. var lineWidth = style.lineWidth;
  337. var newLineWidth = lineWidth / ((style.strokeNoScale && el.getLineScale) ? el.getLineScale() : 1);
  338. if (ctx.lineWidth !== newLineWidth) {
  339. if (!styleChanged) {
  340. flushPathDrawn(ctx, scope);
  341. styleChanged = true;
  342. }
  343. ctx.lineWidth = newLineWidth;
  344. }
  345. }
  346. for (var i = 0; i < STROKE_PROPS.length; i++) {
  347. var prop = STROKE_PROPS[i];
  348. var propName = prop[0];
  349. if (forceSetAll || style[propName] !== prevStyle[propName]) {
  350. if (!styleChanged) {
  351. flushPathDrawn(ctx, scope);
  352. styleChanged = true;
  353. }
  354. ctx[propName] = style[propName] || prop[1];
  355. }
  356. }
  357. return styleChanged;
  358. }
  359. function bindImageStyle(ctx, el, prevEl, forceSetAll, scope) {
  360. return bindCommonProps(ctx, getStyle(el, scope.inHover), prevEl && getStyle(prevEl, scope.inHover), forceSetAll, scope);
  361. }
  362. function setContextTransform(ctx, el) {
  363. var m = el.transform;
  364. var dpr = ctx.dpr || 1;
  365. if (m) {
  366. ctx.setTransform(dpr * m[0], dpr * m[1], dpr * m[2], dpr * m[3], dpr * m[4], dpr * m[5]);
  367. }
  368. else {
  369. ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  370. }
  371. }
  372. function updateClipStatus(clipPaths, ctx, scope) {
  373. var allClipped = false;
  374. for (var i = 0; i < clipPaths.length; i++) {
  375. var clipPath = clipPaths[i];
  376. allClipped = allClipped || clipPath.isZeroArea();
  377. setContextTransform(ctx, clipPath);
  378. ctx.beginPath();
  379. clipPath.buildPath(ctx, clipPath.shape);
  380. ctx.clip();
  381. }
  382. scope.allClipped = allClipped;
  383. }
  384. function isTransformChanged(m0, m1) {
  385. if (m0 && m1) {
  386. return m0[0] !== m1[0]
  387. || m0[1] !== m1[1]
  388. || m0[2] !== m1[2]
  389. || m0[3] !== m1[3]
  390. || m0[4] !== m1[4]
  391. || m0[5] !== m1[5];
  392. }
  393. else if (!m0 && !m1) {
  394. return false;
  395. }
  396. return true;
  397. }
  398. var DRAW_TYPE_PATH = 1;
  399. var DRAW_TYPE_IMAGE = 2;
  400. var DRAW_TYPE_TEXT = 3;
  401. var DRAW_TYPE_INCREMENTAL = 4;
  402. function canPathBatch(style) {
  403. var hasFill = styleHasFill(style);
  404. var hasStroke = styleHasStroke(style);
  405. return !(style.lineDash
  406. || !(+hasFill ^ +hasStroke)
  407. || (hasFill && typeof style.fill !== 'string')
  408. || (hasStroke && typeof style.stroke !== 'string')
  409. || style.strokePercent < 1
  410. || style.strokeOpacity < 1
  411. || style.fillOpacity < 1);
  412. }
  413. function flushPathDrawn(ctx, scope) {
  414. scope.batchFill && ctx.fill();
  415. scope.batchStroke && ctx.stroke();
  416. scope.batchFill = '';
  417. scope.batchStroke = '';
  418. }
  419. function getStyle(el, inHover) {
  420. return inHover ? (el.__hoverStyle || el.style) : el.style;
  421. }
  422. export function brushSingle(ctx, el) {
  423. brush(ctx, el, { inHover: false, viewWidth: 0, viewHeight: 0 }, true);
  424. }
  425. export function brush(ctx, el, scope, isLast) {
  426. var m = el.transform;
  427. if (!el.shouldBePainted(scope.viewWidth, scope.viewHeight, false, false)) {
  428. el.__dirty &= ~REDRAW_BIT;
  429. el.__isRendered = false;
  430. return;
  431. }
  432. var clipPaths = el.__clipPaths;
  433. var prevElClipPaths = scope.prevElClipPaths;
  434. var forceSetTransform = false;
  435. var forceSetStyle = false;
  436. if (!prevElClipPaths || isClipPathChanged(clipPaths, prevElClipPaths)) {
  437. if (prevElClipPaths && prevElClipPaths.length) {
  438. flushPathDrawn(ctx, scope);
  439. ctx.restore();
  440. forceSetStyle = forceSetTransform = true;
  441. scope.prevElClipPaths = null;
  442. scope.allClipped = false;
  443. scope.prevEl = null;
  444. }
  445. if (clipPaths && clipPaths.length) {
  446. flushPathDrawn(ctx, scope);
  447. ctx.save();
  448. updateClipStatus(clipPaths, ctx, scope);
  449. forceSetTransform = true;
  450. }
  451. scope.prevElClipPaths = clipPaths;
  452. }
  453. if (scope.allClipped) {
  454. el.__isRendered = false;
  455. return;
  456. }
  457. el.beforeBrush && el.beforeBrush();
  458. el.innerBeforeBrush();
  459. var prevEl = scope.prevEl;
  460. if (!prevEl) {
  461. forceSetStyle = forceSetTransform = true;
  462. }
  463. var canBatchPath = el instanceof Path
  464. && el.autoBatch
  465. && canPathBatch(el.style);
  466. if (forceSetTransform || isTransformChanged(m, prevEl.transform)) {
  467. flushPathDrawn(ctx, scope);
  468. setContextTransform(ctx, el);
  469. }
  470. else if (!canBatchPath) {
  471. flushPathDrawn(ctx, scope);
  472. }
  473. var style = getStyle(el, scope.inHover);
  474. if (el instanceof Path) {
  475. if (scope.lastDrawType !== DRAW_TYPE_PATH) {
  476. forceSetStyle = true;
  477. scope.lastDrawType = DRAW_TYPE_PATH;
  478. }
  479. bindPathAndTextCommonStyle(ctx, el, prevEl, forceSetStyle, scope);
  480. if (!canBatchPath || (!scope.batchFill && !scope.batchStroke)) {
  481. ctx.beginPath();
  482. }
  483. brushPath(ctx, el, style, canBatchPath);
  484. if (canBatchPath) {
  485. scope.batchFill = style.fill || '';
  486. scope.batchStroke = style.stroke || '';
  487. }
  488. }
  489. else {
  490. if (el instanceof TSpan) {
  491. if (scope.lastDrawType !== DRAW_TYPE_TEXT) {
  492. forceSetStyle = true;
  493. scope.lastDrawType = DRAW_TYPE_TEXT;
  494. }
  495. bindPathAndTextCommonStyle(ctx, el, prevEl, forceSetStyle, scope);
  496. brushText(ctx, el, style);
  497. }
  498. else if (el instanceof ZRImage) {
  499. if (scope.lastDrawType !== DRAW_TYPE_IMAGE) {
  500. forceSetStyle = true;
  501. scope.lastDrawType = DRAW_TYPE_IMAGE;
  502. }
  503. bindImageStyle(ctx, el, prevEl, forceSetStyle, scope);
  504. brushImage(ctx, el, style);
  505. }
  506. else if (el.getTemporalDisplayables) {
  507. if (scope.lastDrawType !== DRAW_TYPE_INCREMENTAL) {
  508. forceSetStyle = true;
  509. scope.lastDrawType = DRAW_TYPE_INCREMENTAL;
  510. }
  511. brushIncremental(ctx, el, scope);
  512. }
  513. }
  514. if (canBatchPath && isLast) {
  515. flushPathDrawn(ctx, scope);
  516. }
  517. el.innerAfterBrush();
  518. el.afterBrush && el.afterBrush();
  519. scope.prevEl = el;
  520. el.__dirty = 0;
  521. el.__isRendered = true;
  522. }
  523. function brushIncremental(ctx, el, scope) {
  524. var displayables = el.getDisplayables();
  525. var temporalDisplayables = el.getTemporalDisplayables();
  526. ctx.save();
  527. var innerScope = {
  528. prevElClipPaths: null,
  529. prevEl: null,
  530. allClipped: false,
  531. viewWidth: scope.viewWidth,
  532. viewHeight: scope.viewHeight,
  533. inHover: scope.inHover
  534. };
  535. var i;
  536. var len;
  537. for (i = el.getCursor(), len = displayables.length; i < len; i++) {
  538. var displayable = displayables[i];
  539. displayable.beforeBrush && displayable.beforeBrush();
  540. displayable.innerBeforeBrush();
  541. brush(ctx, displayable, innerScope, i === len - 1);
  542. displayable.innerAfterBrush();
  543. displayable.afterBrush && displayable.afterBrush();
  544. innerScope.prevEl = displayable;
  545. }
  546. for (var i_1 = 0, len_1 = temporalDisplayables.length; i_1 < len_1; i_1++) {
  547. var displayable = temporalDisplayables[i_1];
  548. displayable.beforeBrush && displayable.beforeBrush();
  549. displayable.innerBeforeBrush();
  550. brush(ctx, displayable, innerScope, i_1 === len_1 - 1);
  551. displayable.innerAfterBrush();
  552. displayable.afterBrush && displayable.afterBrush();
  553. innerScope.prevEl = displayable;
  554. }
  555. el.clearTemporalDisplayables();
  556. el.notClear = true;
  557. ctx.restore();
  558. }