73f8d378b55e563f7edc6f88fca448c15880be123b531479cd9423462cb7944ebe54642cc83082728b69449f09b0532f19ec352e281e8dae2abb2d956a2fa2 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. /**
  2. * @file Manages elements that can be defined in <defs> in SVG,
  3. * e.g., gradients, clip path, etc.
  4. * @author Zhang Wenli
  5. */
  6. import {createElement} from '../../svg/core';
  7. import * as zrUtil from '../../core/util';
  8. import Displayable from '../../graphic/Displayable';
  9. const MARK_UNUSED = '0';
  10. const MARK_USED = '1';
  11. /**
  12. * Manages elements that can be defined in <defs> in SVG,
  13. * e.g., gradients, clip path, etc.
  14. */
  15. export default class Definable {
  16. nextId = 0
  17. protected _zrId: number
  18. protected _svgRoot: SVGElement
  19. protected _tagNames: string[]
  20. protected _markLabel: string
  21. protected _domName: string = '_dom'
  22. constructor(
  23. zrId: number, // zrender instance id
  24. svgRoot: SVGElement, // root of SVG document
  25. tagNames: string | string[], // possible tag names
  26. markLabel: string, // label name to make if the element
  27. domName?: string
  28. ) {
  29. this._zrId = zrId;
  30. this._svgRoot = svgRoot;
  31. this._tagNames = typeof tagNames === 'string' ? [tagNames] : tagNames;
  32. this._markLabel = markLabel;
  33. if (domName) {
  34. this._domName = domName;
  35. }
  36. }
  37. /**
  38. * Get the <defs> tag for svgRoot; optionally creates one if not exists.
  39. *
  40. * @param isForceCreating if need to create when not exists
  41. * @return SVG <defs> element, null if it doesn't
  42. * exist and isForceCreating is false
  43. */
  44. getDefs(isForceCreating?: boolean): SVGDefsElement {
  45. let svgRoot = this._svgRoot;
  46. let defs = this._svgRoot.getElementsByTagName('defs');
  47. if (defs.length === 0) {
  48. // Not exist
  49. if (isForceCreating) {
  50. let defs = svgRoot.insertBefore(
  51. createElement('defs'), // Create new tag
  52. svgRoot.firstChild // Insert in the front of svg
  53. ) as SVGDefsElement;
  54. if (!defs.contains) {
  55. // IE doesn't support contains method
  56. defs.contains = function (el) {
  57. const children = defs.children;
  58. if (!children) {
  59. return false;
  60. }
  61. for (let i = children.length - 1; i >= 0; --i) {
  62. if (children[i] === el) {
  63. return true;
  64. }
  65. }
  66. return false;
  67. };
  68. }
  69. return defs;
  70. }
  71. else {
  72. return null;
  73. }
  74. }
  75. else {
  76. return defs[0];
  77. }
  78. }
  79. /**
  80. * Update DOM element if necessary.
  81. *
  82. * @param element style element. e.g., for gradient,
  83. * it may be '#ccc' or {type: 'linear', ...}
  84. * @param onUpdate update callback
  85. */
  86. doUpdate<T>(target: T, onUpdate?: (target: T) => void) {
  87. if (!target) {
  88. return;
  89. }
  90. const defs = this.getDefs(false);
  91. if ((target as any)[this._domName] && defs.contains((target as any)[this._domName])) {
  92. // Update DOM
  93. if (typeof onUpdate === 'function') {
  94. onUpdate(target);
  95. }
  96. }
  97. else {
  98. // No previous dom, create new
  99. const dom = this.add(target);
  100. if (dom) {
  101. (target as any)[this._domName] = dom;
  102. }
  103. }
  104. }
  105. add(target: any): SVGElement {
  106. return null;
  107. }
  108. /**
  109. * Add gradient dom to defs
  110. *
  111. * @param dom DOM to be added to <defs>
  112. */
  113. addDom(dom: SVGElement) {
  114. const defs = this.getDefs(true);
  115. if (dom.parentNode !== defs) {
  116. defs.appendChild(dom);
  117. }
  118. }
  119. /**
  120. * Remove DOM of a given element.
  121. *
  122. * @param target Target where to attach the dom
  123. */
  124. removeDom<T>(target: T) {
  125. const defs = this.getDefs(false);
  126. if (defs && (target as any)[this._domName]) {
  127. defs.removeChild((target as any)[this._domName]);
  128. (target as any)[this._domName] = null;
  129. }
  130. }
  131. /**
  132. * Get DOMs of this element.
  133. *
  134. * @return doms of this defineable elements in <defs>
  135. */
  136. getDoms() {
  137. const defs = this.getDefs(false);
  138. if (!defs) {
  139. // No dom when defs is not defined
  140. return [];
  141. }
  142. let doms: SVGElement[] = [];
  143. zrUtil.each(this._tagNames, function (tagName) {
  144. const tags = defs.getElementsByTagName(tagName) as HTMLCollectionOf<SVGElement>;
  145. // Note that tags is HTMLCollection, which is array-like
  146. // rather than real array.
  147. // So `doms.concat(tags)` add tags as one object.
  148. for (let i = 0; i < tags.length; i++) {
  149. doms.push(tags[i]);
  150. }
  151. });
  152. return doms;
  153. }
  154. /**
  155. * Mark DOMs to be unused before painting, and clear unused ones at the end
  156. * of the painting.
  157. */
  158. markAllUnused() {
  159. const doms = this.getDoms();
  160. const that = this;
  161. zrUtil.each(doms, function (dom) {
  162. (dom as any)[that._markLabel] = MARK_UNUSED;
  163. });
  164. }
  165. /**
  166. * Mark a single DOM to be used.
  167. *
  168. * @param dom DOM to mark
  169. */
  170. markDomUsed(dom: SVGElement) {
  171. dom && ((dom as any)[this._markLabel] = MARK_USED);
  172. };
  173. markDomUnused(dom: SVGElement) {
  174. dom && ((dom as any)[this._markLabel] = MARK_UNUSED);
  175. };
  176. isDomUnused(dom: SVGElement) {
  177. return dom && (dom as any)[this._markLabel] !== MARK_USED;
  178. }
  179. /**
  180. * Remove unused DOMs defined in <defs>
  181. */
  182. removeUnused() {
  183. const defs = this.getDefs(false);
  184. if (!defs) {
  185. // Nothing to remove
  186. return;
  187. }
  188. const doms = this.getDoms();
  189. zrUtil.each(doms, (dom) => {
  190. if (this.isDomUnused(dom)) {
  191. // Remove gradient
  192. defs.removeChild(dom);
  193. }
  194. });
  195. }
  196. /**
  197. * Get SVG element.
  198. *
  199. * @param displayable displayable element
  200. * @return SVG element
  201. */
  202. getSvgElement(displayable: Displayable): SVGElement {
  203. return displayable.__svgEl;
  204. }
  205. }