graphic.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  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, canBatch, scope) {
  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 (!canBatch) {
  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 = strokePattern;
  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, canBatch);
  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 (!canBatch) {
  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. else {
  184. scope.batchFill = hasFill;
  185. scope.batchStroke = hasStroke;
  186. }
  187. if (lineDash) {
  188. ctx.setLineDash([]);
  189. }
  190. }
  191. function brushImage(ctx, el, style) {
  192. var image = el.__image = createOrUpdateImage(style.image, el.__image, el, el.onload);
  193. if (!image || !isImageReady(image)) {
  194. return;
  195. }
  196. var x = style.x || 0;
  197. var y = style.y || 0;
  198. var width = el.getWidth();
  199. var height = el.getHeight();
  200. var aspect = image.width / image.height;
  201. if (width == null && height != null) {
  202. width = height * aspect;
  203. }
  204. else if (height == null && width != null) {
  205. height = width / aspect;
  206. }
  207. else if (width == null && height == null) {
  208. width = image.width;
  209. height = image.height;
  210. }
  211. if (style.sWidth && style.sHeight) {
  212. var sx = style.sx || 0;
  213. var sy = style.sy || 0;
  214. ctx.drawImage(image, sx, sy, style.sWidth, style.sHeight, x, y, width, height);
  215. }
  216. else if (style.sx && style.sy) {
  217. var sx = style.sx;
  218. var sy = style.sy;
  219. var sWidth = width - sx;
  220. var sHeight = height - sy;
  221. ctx.drawImage(image, sx, sy, sWidth, sHeight, x, y, width, height);
  222. }
  223. else {
  224. ctx.drawImage(image, x, y, width, height);
  225. }
  226. }
  227. function brushText(ctx, el, style) {
  228. var _a;
  229. var text = style.text;
  230. text != null && (text += '');
  231. if (text) {
  232. ctx.font = style.font || DEFAULT_FONT;
  233. ctx.textAlign = style.textAlign;
  234. ctx.textBaseline = style.textBaseline;
  235. var lineDash = void 0;
  236. var lineDashOffset = void 0;
  237. if (ctx.setLineDash && style.lineDash) {
  238. _a = getLineDash(el), lineDash = _a[0], lineDashOffset = _a[1];
  239. }
  240. if (lineDash) {
  241. ctx.setLineDash(lineDash);
  242. ctx.lineDashOffset = lineDashOffset;
  243. }
  244. if (style.strokeFirst) {
  245. if (styleHasStroke(style)) {
  246. ctx.strokeText(text, style.x, style.y);
  247. }
  248. if (styleHasFill(style)) {
  249. ctx.fillText(text, style.x, style.y);
  250. }
  251. }
  252. else {
  253. if (styleHasFill(style)) {
  254. ctx.fillText(text, style.x, style.y);
  255. }
  256. if (styleHasStroke(style)) {
  257. ctx.strokeText(text, style.x, style.y);
  258. }
  259. }
  260. if (lineDash) {
  261. ctx.setLineDash([]);
  262. }
  263. }
  264. }
  265. var SHADOW_NUMBER_PROPS = ['shadowBlur', 'shadowOffsetX', 'shadowOffsetY'];
  266. var STROKE_PROPS = [
  267. ['lineCap', 'butt'], ['lineJoin', 'miter'], ['miterLimit', 10]
  268. ];
  269. function bindCommonProps(ctx, style, prevStyle, forceSetAll, scope) {
  270. var styleChanged = false;
  271. if (!forceSetAll) {
  272. prevStyle = prevStyle || {};
  273. if (style === prevStyle) {
  274. return false;
  275. }
  276. }
  277. if (forceSetAll || style.opacity !== prevStyle.opacity) {
  278. flushPathDrawn(ctx, scope);
  279. styleChanged = true;
  280. var opacity = Math.max(Math.min(style.opacity, 1), 0);
  281. ctx.globalAlpha = isNaN(opacity) ? DEFAULT_COMMON_STYLE.opacity : opacity;
  282. }
  283. if (forceSetAll || style.blend !== prevStyle.blend) {
  284. if (!styleChanged) {
  285. flushPathDrawn(ctx, scope);
  286. styleChanged = true;
  287. }
  288. ctx.globalCompositeOperation = style.blend || DEFAULT_COMMON_STYLE.blend;
  289. }
  290. for (var i = 0; i < SHADOW_NUMBER_PROPS.length; i++) {
  291. var propName = SHADOW_NUMBER_PROPS[i];
  292. if (forceSetAll || style[propName] !== prevStyle[propName]) {
  293. if (!styleChanged) {
  294. flushPathDrawn(ctx, scope);
  295. styleChanged = true;
  296. }
  297. ctx[propName] = ctx.dpr * (style[propName] || 0);
  298. }
  299. }
  300. if (forceSetAll || style.shadowColor !== prevStyle.shadowColor) {
  301. if (!styleChanged) {
  302. flushPathDrawn(ctx, scope);
  303. styleChanged = true;
  304. }
  305. ctx.shadowColor = style.shadowColor || DEFAULT_COMMON_STYLE.shadowColor;
  306. }
  307. return styleChanged;
  308. }
  309. function bindPathAndTextCommonStyle(ctx, el, prevEl, forceSetAll, scope) {
  310. var style = el.style;
  311. var prevStyle = forceSetAll
  312. ? null
  313. : (prevEl && prevEl.style || {});
  314. if (style === prevStyle) {
  315. return false;
  316. }
  317. var styleChanged = bindCommonProps(ctx, style, prevStyle, forceSetAll, scope);
  318. if (forceSetAll || style.fill !== prevStyle.fill) {
  319. if (!styleChanged) {
  320. flushPathDrawn(ctx, scope);
  321. styleChanged = true;
  322. }
  323. isValidStrokeFillStyle(style.fill) && (ctx.fillStyle = style.fill);
  324. }
  325. if (forceSetAll || style.stroke !== prevStyle.stroke) {
  326. if (!styleChanged) {
  327. flushPathDrawn(ctx, scope);
  328. styleChanged = true;
  329. }
  330. isValidStrokeFillStyle(style.stroke) && (ctx.strokeStyle = style.stroke);
  331. }
  332. if (forceSetAll || style.opacity !== prevStyle.opacity) {
  333. if (!styleChanged) {
  334. flushPathDrawn(ctx, scope);
  335. styleChanged = true;
  336. }
  337. ctx.globalAlpha = style.opacity == null ? 1 : style.opacity;
  338. }
  339. if (el.hasStroke()) {
  340. var lineWidth = style.lineWidth;
  341. var newLineWidth = lineWidth / ((style.strokeNoScale && el.getLineScale) ? el.getLineScale() : 1);
  342. if (ctx.lineWidth !== newLineWidth) {
  343. if (!styleChanged) {
  344. flushPathDrawn(ctx, scope);
  345. styleChanged = true;
  346. }
  347. ctx.lineWidth = newLineWidth;
  348. }
  349. }
  350. for (var i = 0; i < STROKE_PROPS.length; i++) {
  351. var prop = STROKE_PROPS[i];
  352. var propName = prop[0];
  353. if (forceSetAll || style[propName] !== prevStyle[propName]) {
  354. if (!styleChanged) {
  355. flushPathDrawn(ctx, scope);
  356. styleChanged = true;
  357. }
  358. ctx[propName] = style[propName] || prop[1];
  359. }
  360. }
  361. return styleChanged;
  362. }
  363. function bindImageStyle(ctx, el, prevEl, forceSetAll, scope) {
  364. return bindCommonProps(ctx, el.style, prevEl && prevEl.style, forceSetAll, scope);
  365. }
  366. function setContextTransform(ctx, el) {
  367. var m = el.transform;
  368. var dpr = ctx.dpr || 1;
  369. if (m) {
  370. ctx.setTransform(dpr * m[0], dpr * m[1], dpr * m[2], dpr * m[3], dpr * m[4], dpr * m[5]);
  371. }
  372. else {
  373. ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  374. }
  375. }
  376. function updateClipStatus(clipPaths, ctx, scope) {
  377. var allClipped = false;
  378. for (var i = 0; i < clipPaths.length; i++) {
  379. var clipPath = clipPaths[i];
  380. allClipped = allClipped || clipPath.isZeroArea();
  381. setContextTransform(ctx, clipPath);
  382. ctx.beginPath();
  383. clipPath.buildPath(ctx, clipPath.shape);
  384. ctx.clip();
  385. }
  386. scope.allClipped = allClipped;
  387. }
  388. function isTransformChanged(m0, m1) {
  389. if (m0 && m1) {
  390. return m0[0] !== m1[0]
  391. || m0[1] !== m1[1]
  392. || m0[2] !== m1[2]
  393. || m0[3] !== m1[3]
  394. || m0[4] !== m1[4]
  395. || m0[5] !== m1[5];
  396. }
  397. else if (!m0 && !m1) {
  398. return false;
  399. }
  400. return true;
  401. }
  402. var DRAW_TYPE_PATH = 1;
  403. var DRAW_TYPE_IMAGE = 2;
  404. var DRAW_TYPE_TEXT = 3;
  405. var DRAW_TYPE_INCREMENTAL = 4;
  406. function canPathBatch(style) {
  407. var hasFill = styleHasFill(style);
  408. var hasStroke = styleHasStroke(style);
  409. return !(style.lineDash
  410. || !(+hasFill ^ +hasStroke)
  411. || (hasFill && typeof style.fill !== 'string')
  412. || (hasStroke && typeof style.stroke !== 'string')
  413. || style.strokePercent < 1
  414. || style.strokeOpacity < 1
  415. || style.fillOpacity < 1);
  416. }
  417. function flushPathDrawn(ctx, scope) {
  418. if (scope.batchFill) {
  419. scope.batchFill = false;
  420. ctx.fill();
  421. }
  422. if (scope.batchStroke) {
  423. scope.batchStroke = false;
  424. ctx.stroke();
  425. }
  426. }
  427. export function brushSingle(ctx, el) {
  428. var scope = { inHover: false, viewWidth: 0, viewHeight: 0, beforeBrushParam: {} };
  429. brush(ctx, el, scope);
  430. brushLoopFinalize(ctx, scope);
  431. }
  432. export function brush(ctx, el, scope) {
  433. var m = el.transform;
  434. if (!el.shouldBePainted(scope.viewWidth, scope.viewHeight, false, false)) {
  435. el.__dirty &= ~REDRAW_BIT;
  436. el.__isRendered = false;
  437. return;
  438. }
  439. var clipPaths = el.__clipPaths;
  440. var prevElClipPaths = scope.prevElClipPaths;
  441. var style = el.style;
  442. var forceSetTransform = false;
  443. var forceSetStyle = false;
  444. if (!prevElClipPaths || isClipPathChanged(clipPaths, prevElClipPaths)) {
  445. if (prevElClipPaths) {
  446. flushPathDrawn(ctx, scope);
  447. ctx.restore();
  448. forceSetStyle = forceSetTransform = true;
  449. scope.prevElClipPaths = null;
  450. scope.allClipped = false;
  451. scope.prevEl = null;
  452. }
  453. if (clipPaths && clipPaths.length) {
  454. flushPathDrawn(ctx, scope);
  455. ctx.save();
  456. updateClipStatus(clipPaths, ctx, scope);
  457. forceSetTransform = true;
  458. scope.prevElClipPaths = clipPaths;
  459. }
  460. }
  461. if (scope.allClipped) {
  462. el.__dirty &= ~REDRAW_BIT;
  463. el.__isRendered = false;
  464. return;
  465. }
  466. el.beforeBrush && el.beforeBrush(scope.beforeBrushParam);
  467. el.innerBeforeBrush();
  468. var prevEl = scope.prevEl;
  469. if (!prevEl) {
  470. forceSetStyle = forceSetTransform = true;
  471. }
  472. var canBatchPath = el instanceof Path
  473. && el.autoBatch
  474. && canPathBatch(style);
  475. if (forceSetTransform || isTransformChanged(m, prevEl.transform)) {
  476. flushPathDrawn(ctx, scope);
  477. setContextTransform(ctx, el);
  478. }
  479. else if (!canBatchPath) {
  480. flushPathDrawn(ctx, scope);
  481. }
  482. if (el instanceof Path) {
  483. if (scope.lastDrawType !== DRAW_TYPE_PATH) {
  484. forceSetStyle = true;
  485. scope.lastDrawType = DRAW_TYPE_PATH;
  486. }
  487. bindPathAndTextCommonStyle(ctx, el, prevEl, forceSetStyle, scope);
  488. if (!canBatchPath || (!scope.batchFill && !scope.batchStroke)) {
  489. ctx.beginPath();
  490. }
  491. brushPath(ctx, el, style, canBatchPath, scope);
  492. }
  493. else {
  494. if (el instanceof TSpan) {
  495. if (scope.lastDrawType !== DRAW_TYPE_TEXT) {
  496. forceSetStyle = true;
  497. scope.lastDrawType = DRAW_TYPE_TEXT;
  498. }
  499. bindPathAndTextCommonStyle(ctx, el, prevEl, forceSetStyle, scope);
  500. brushText(ctx, el, style);
  501. }
  502. else if (el instanceof ZRImage) {
  503. if (scope.lastDrawType !== DRAW_TYPE_IMAGE) {
  504. forceSetStyle = true;
  505. scope.lastDrawType = DRAW_TYPE_IMAGE;
  506. }
  507. bindImageStyle(ctx, el, prevEl, forceSetStyle, scope);
  508. brushImage(ctx, el, style);
  509. }
  510. else if (el.getTemporalDisplayables) {
  511. if (scope.lastDrawType !== DRAW_TYPE_INCREMENTAL) {
  512. forceSetStyle = true;
  513. scope.lastDrawType = DRAW_TYPE_INCREMENTAL;
  514. }
  515. brushIncremental(ctx, el, scope);
  516. }
  517. }
  518. el.innerAfterBrush();
  519. if (el.afterBrush) {
  520. if (canBatchPath) {
  521. flushPathDrawn(ctx, scope);
  522. }
  523. el.afterBrush();
  524. }
  525. scope.prevEl = el;
  526. el.__dirty = 0;
  527. el.__isRendered = true;
  528. }
  529. export function brushLoopFinalize(ctx, scope) {
  530. flushPathDrawn(ctx, scope);
  531. if (scope.prevElClipPaths) {
  532. ctx.restore();
  533. }
  534. }
  535. function brushIncremental(ctx, el, scope) {
  536. var displayables = el.getDisplayables();
  537. var temporalDisplayables = el.getTemporalDisplayables();
  538. ctx.save();
  539. var innerScope = {
  540. prevElClipPaths: null,
  541. prevEl: null,
  542. allClipped: false,
  543. viewWidth: scope.viewWidth,
  544. viewHeight: scope.viewHeight,
  545. inHover: scope.inHover,
  546. beforeBrushParam: {}
  547. };
  548. var i;
  549. var len;
  550. for (i = el.getCursor(), len = displayables.length; i < len; i++) {
  551. var displayable = displayables[i];
  552. displayable.beforeBrush && displayable.beforeBrush(scope.beforeBrushParam);
  553. displayable.innerBeforeBrush();
  554. brush(ctx, displayable, innerScope);
  555. displayable.innerAfterBrush();
  556. displayable.afterBrush && displayable.afterBrush();
  557. innerScope.prevEl = displayable;
  558. }
  559. brushLoopFinalize(ctx, innerScope);
  560. for (var i_1 = 0, len_1 = temporalDisplayables.length; i_1 < len_1; i_1++) {
  561. var displayable = temporalDisplayables[i_1];
  562. displayable.beforeBrush && displayable.beforeBrush(scope.beforeBrushParam);
  563. displayable.innerBeforeBrush();
  564. brush(ctx, displayable, innerScope);
  565. displayable.innerAfterBrush();
  566. displayable.afterBrush && displayable.afterBrush();
  567. innerScope.prevEl = displayable;
  568. }
  569. brushLoopFinalize(ctx, innerScope);
  570. el.clearTemporalDisplayables();
  571. el.notClear = true;
  572. ctx.restore();
  573. }