e929ff1b9a4b8ddd3500e4beea0d73ed7078c47a407b9b18afd8657512c50517b9b1d4a66a1095f93c78cf9d81e14fb03b58890ff335e87fb780497dd6eb04 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. /**
  2. * @file Manages SVG pattern elements.
  3. * @author Zhang Wenli
  4. */
  5. import Definable from './Definable';
  6. import * as zrUtil from '../../core/util';
  7. import Displayable from '../../graphic/Displayable';
  8. import {PatternObject} from '../../graphic/Pattern';
  9. import {createOrUpdateImage} from '../../graphic/helper/image';
  10. import WeakMap from '../../core/WeakMap';
  11. import { getIdURL, isPattern, isSVGPattern } from '../../svg/helper';
  12. import { createElement } from '../../svg/core';
  13. const patternDomMap = new WeakMap<PatternObject, SVGElement>();
  14. /**
  15. * Manages SVG pattern elements.
  16. *
  17. * @param zrId zrender instance id
  18. * @param svgRoot root of SVG document
  19. */
  20. export default class PatternManager extends Definable {
  21. constructor(zrId: number, svgRoot: SVGElement) {
  22. super(zrId, svgRoot, ['pattern'], '__pattern_in_use__');
  23. }
  24. /**
  25. * Create new pattern DOM for fill or stroke if not exist,
  26. * but will not update pattern if exists.
  27. *
  28. * @param svgElement SVG element to paint
  29. * @param displayable zrender displayable element
  30. */
  31. addWithoutUpdate(
  32. svgElement: SVGElement,
  33. displayable: Displayable
  34. ) {
  35. if (displayable && displayable.style) {
  36. const that = this;
  37. zrUtil.each(['fill', 'stroke'], function (fillOrStroke: 'fill' | 'stroke') {
  38. const pattern = displayable.style[fillOrStroke] as PatternObject;
  39. if (isPattern(pattern)) {
  40. const defs = that.getDefs(true);
  41. // Create dom in <defs> if not exists
  42. let dom = patternDomMap.get(pattern);
  43. if (dom) {
  44. // Pattern exists
  45. if (!defs.contains(dom)) {
  46. // __dom is no longer in defs, recreate
  47. that.addDom(dom);
  48. }
  49. }
  50. else {
  51. // New dom
  52. dom = that.add(pattern);
  53. }
  54. that.markUsed(displayable);
  55. svgElement.setAttribute(fillOrStroke, getIdURL(dom.getAttribute('id')));
  56. }
  57. });
  58. }
  59. }
  60. /**
  61. * Add a new pattern tag in <defs>
  62. *
  63. * @param pattern zr pattern instance
  64. */
  65. add(pattern: PatternObject): SVGElement {
  66. if (!isPattern(pattern)) {
  67. return;
  68. }
  69. let dom = createElement('pattern');
  70. pattern.id = pattern.id == null ? this.nextId++ : pattern.id;
  71. dom.setAttribute('id', 'zr' + this._zrId
  72. + '-pattern-' + pattern.id);
  73. dom.setAttribute('patternUnits', 'userSpaceOnUse');
  74. this.updateDom(pattern, dom);
  75. this.addDom(dom);
  76. return dom;
  77. }
  78. /**
  79. * Update pattern.
  80. *
  81. * @param pattern zr pattern instance or color string
  82. */
  83. update(pattern: PatternObject | string) {
  84. if (!isPattern(pattern)) {
  85. return;
  86. }
  87. const that = this;
  88. this.doUpdate(pattern, function () {
  89. const dom = patternDomMap.get(pattern);
  90. that.updateDom(pattern, dom);
  91. });
  92. }
  93. /**
  94. * Update pattern dom
  95. *
  96. * @param pattern zr pattern instance
  97. * @param patternDom DOM to update
  98. */
  99. updateDom(pattern: PatternObject, patternDom: SVGElement) {
  100. if (isSVGPattern(pattern)) {
  101. // New SVGPattern will not been supported in the legacy SVG renderer.
  102. // svg-legacy will been removed soon.
  103. // const svgElement = pattern.svgElement;
  104. // const isStringSVG = typeof svgElement === 'string';
  105. // if (isStringSVG || svgElement.parentNode !== patternDom) {
  106. // if (isStringSVG) {
  107. // patternDom.innerHTML = svgElement;
  108. // }
  109. // else {
  110. // patternDom.innerHTML = '';
  111. // patternDom.appendChild(svgElement);
  112. // }
  113. // patternDom.setAttribute('width', pattern.svgWidth as any);
  114. // patternDom.setAttribute('height', pattern.svgHeight as any);
  115. // }
  116. }
  117. else {
  118. let img: SVGElement;
  119. const prevImage = patternDom.getElementsByTagName('image');
  120. if (prevImage.length) {
  121. if (pattern.image) {
  122. // Update
  123. img = prevImage[0];
  124. }
  125. else {
  126. // Remove
  127. patternDom.removeChild(prevImage[0]);
  128. return;
  129. }
  130. }
  131. else if (pattern.image) {
  132. // Create
  133. img = createElement('image');
  134. }
  135. if (img) {
  136. let imageSrc;
  137. const patternImage = pattern.image;
  138. if (typeof patternImage === 'string') {
  139. imageSrc = patternImage;
  140. }
  141. else if (patternImage instanceof HTMLImageElement) {
  142. imageSrc = patternImage.src;
  143. }
  144. else if (patternImage instanceof HTMLCanvasElement) {
  145. imageSrc = patternImage.toDataURL();
  146. }
  147. if (imageSrc) {
  148. img.setAttribute('href', imageSrc);
  149. // No need to re-render so dirty is empty
  150. const hostEl = {
  151. dirty: () => {}
  152. };
  153. const updateSize = (img: HTMLImageElement) => {
  154. patternDom.setAttribute('width', img.width as any);
  155. patternDom.setAttribute('height', img.height as any);
  156. };
  157. const createdImage = createOrUpdateImage(imageSrc, img as any, hostEl, updateSize);
  158. if (createdImage && createdImage.width && createdImage.height) {
  159. // Loaded before
  160. updateSize(createdImage as HTMLImageElement);
  161. }
  162. patternDom.appendChild(img);
  163. }
  164. }
  165. }
  166. const x = pattern.x || 0;
  167. const y = pattern.y || 0;
  168. const rotation = (pattern.rotation || 0) / Math.PI * 180;
  169. const scaleX = pattern.scaleX || 1;
  170. const scaleY = pattern.scaleY || 1;
  171. const transform = `translate(${x}, ${y}) rotate(${rotation}) scale(${scaleX}, ${scaleY})`;
  172. patternDom.setAttribute('patternTransform', transform);
  173. patternDomMap.set(pattern, patternDom);
  174. }
  175. /**
  176. * Mark a single pattern to be used
  177. *
  178. * @param displayable displayable element
  179. */
  180. markUsed(displayable: Displayable) {
  181. if (displayable.style) {
  182. if (isPattern(displayable.style.fill)) {
  183. super.markDomUsed(patternDomMap.get(displayable.style.fill));
  184. }
  185. if (isPattern(displayable.style.stroke)) {
  186. super.markDomUsed(patternDomMap.get(displayable.style.stroke));
  187. }
  188. }
  189. }
  190. }