| /** | 
|  * @file Manages elements that can be defined in <defs> in SVG, | 
|  *       e.g., gradients, clip path, etc. | 
|  * @author Zhang Wenli | 
|  */ | 
|   | 
| import {createElement} from '../../svg/core'; | 
| import * as zrUtil from '../../core/util'; | 
| import Displayable from '../../graphic/Displayable'; | 
|   | 
|   | 
| const MARK_UNUSED = '0'; | 
| const MARK_USED = '1'; | 
|   | 
| /** | 
|  * Manages elements that can be defined in <defs> in SVG, | 
|  * e.g., gradients, clip path, etc. | 
|  */ | 
| export default class Definable { | 
|   | 
|     nextId = 0 | 
|   | 
|     protected _zrId: number | 
|     protected _svgRoot: SVGElement | 
|     protected _tagNames: string[] | 
|     protected _markLabel: string | 
|     protected _domName: string = '_dom' | 
|   | 
|     constructor( | 
|         zrId: number,   // zrender instance id | 
|         svgRoot: SVGElement,        // root of SVG document | 
|         tagNames: string | string[],    // possible tag names | 
|         markLabel: string,  // label name to make if the element | 
|         domName?: string | 
|     ) { | 
|         this._zrId = zrId; | 
|         this._svgRoot = svgRoot; | 
|         this._tagNames = typeof tagNames === 'string' ? [tagNames] : tagNames; | 
|         this._markLabel = markLabel; | 
|   | 
|         if (domName) { | 
|             this._domName = domName; | 
|         } | 
|     } | 
|   | 
|     /** | 
|      * Get the <defs> tag for svgRoot; optionally creates one if not exists. | 
|      * | 
|      * @param isForceCreating if need to create when not exists | 
|      * @return SVG <defs> element, null if it doesn't | 
|      * exist and isForceCreating is false | 
|      */ | 
|     getDefs(isForceCreating?: boolean): SVGDefsElement { | 
|         let svgRoot = this._svgRoot; | 
|         let defs = this._svgRoot.getElementsByTagName('defs'); | 
|         if (defs.length === 0) { | 
|             // Not exist | 
|             if (isForceCreating) { | 
|                 let defs = svgRoot.insertBefore( | 
|                     createElement('defs'), // Create new tag | 
|                     svgRoot.firstChild // Insert in the front of svg | 
|                 ) as SVGDefsElement; | 
|                 if (!defs.contains) { | 
|                     // IE doesn't support contains method | 
|                     defs.contains = function (el) { | 
|                         const children = defs.children; | 
|                         if (!children) { | 
|                             return false; | 
|                         } | 
|                         for (let i = children.length - 1; i >= 0; --i) { | 
|                             if (children[i] === el) { | 
|                                 return true; | 
|                             } | 
|                         } | 
|                         return false; | 
|                     }; | 
|                 } | 
|                 return defs; | 
|             } | 
|             else { | 
|                 return null; | 
|             } | 
|         } | 
|         else { | 
|             return defs[0]; | 
|         } | 
|     } | 
|   | 
|   | 
|     /** | 
|      * Update DOM element if necessary. | 
|      * | 
|      * @param element style element. e.g., for gradient, | 
|      *                                it may be '#ccc' or {type: 'linear', ...} | 
|      * @param onUpdate update callback | 
|      */ | 
|     doUpdate<T>(target: T, onUpdate?: (target: T) => void) { | 
|         if (!target) { | 
|             return; | 
|         } | 
|   | 
|         const defs = this.getDefs(false); | 
|         if ((target as any)[this._domName] && defs.contains((target as any)[this._domName])) { | 
|             // Update DOM | 
|             if (typeof onUpdate === 'function') { | 
|                 onUpdate(target); | 
|             } | 
|         } | 
|         else { | 
|             // No previous dom, create new | 
|             const dom = this.add(target); | 
|             if (dom) { | 
|                 (target as any)[this._domName] = dom; | 
|             } | 
|         } | 
|     } | 
|   | 
|     add(target: any): SVGElement { | 
|         return null; | 
|     } | 
|   | 
|     /** | 
|      * Add gradient dom to defs | 
|      * | 
|      * @param dom DOM to be added to <defs> | 
|      */ | 
|     addDom(dom: SVGElement) { | 
|         const defs = this.getDefs(true); | 
|         if (dom.parentNode !== defs) { | 
|             defs.appendChild(dom); | 
|         } | 
|     } | 
|   | 
|   | 
|     /** | 
|      * Remove DOM of a given element. | 
|      * | 
|      * @param target Target where to attach the dom | 
|      */ | 
|     removeDom<T>(target: T) { | 
|         const defs = this.getDefs(false); | 
|         if (defs && (target as any)[this._domName]) { | 
|             defs.removeChild((target as any)[this._domName]); | 
|             (target as any)[this._domName] = null; | 
|         } | 
|     } | 
|   | 
|   | 
|     /** | 
|      * Get DOMs of this element. | 
|      * | 
|      * @return doms of this defineable elements in <defs> | 
|      */ | 
|     getDoms() { | 
|         const defs = this.getDefs(false); | 
|         if (!defs) { | 
|             // No dom when defs is not defined | 
|             return []; | 
|         } | 
|   | 
|         let doms: SVGElement[] = []; | 
|         zrUtil.each(this._tagNames, function (tagName) { | 
|             const tags = defs.getElementsByTagName(tagName) as HTMLCollectionOf<SVGElement>; | 
|             // Note that tags is HTMLCollection, which is array-like | 
|             // rather than real array. | 
|             // So `doms.concat(tags)` add tags as one object. | 
|             for (let i = 0; i < tags.length; i++) { | 
|                 doms.push(tags[i]); | 
|             } | 
|         }); | 
|   | 
|         return doms; | 
|     } | 
|   | 
|   | 
|     /** | 
|      * Mark DOMs to be unused before painting, and clear unused ones at the end | 
|      * of the painting. | 
|      */ | 
|     markAllUnused() { | 
|         const doms = this.getDoms(); | 
|         const that = this; | 
|         zrUtil.each(doms, function (dom) { | 
|             (dom as any)[that._markLabel] = MARK_UNUSED; | 
|         }); | 
|     } | 
|   | 
|   | 
|     /** | 
|      * Mark a single DOM to be used. | 
|      * | 
|      * @param dom DOM to mark | 
|      */ | 
|     markDomUsed(dom: SVGElement) { | 
|         dom && ((dom as any)[this._markLabel] = MARK_USED); | 
|     }; | 
|   | 
|     markDomUnused(dom: SVGElement) { | 
|         dom && ((dom as any)[this._markLabel] = MARK_UNUSED); | 
|     }; | 
|   | 
|     isDomUnused(dom: SVGElement) { | 
|         return dom && (dom as any)[this._markLabel] !== MARK_USED; | 
|     } | 
|   | 
|     /** | 
|      * Remove unused DOMs defined in <defs> | 
|      */ | 
|     removeUnused() { | 
|         const defs = this.getDefs(false); | 
|         if (!defs) { | 
|             // Nothing to remove | 
|             return; | 
|         } | 
|   | 
|         const doms = this.getDoms(); | 
|         zrUtil.each(doms, (dom) => { | 
|             if (this.isDomUnused(dom)) { | 
|                 // Remove gradient | 
|                 defs.removeChild(dom); | 
|             } | 
|         }); | 
|     } | 
|   | 
|     /** | 
|      * Get SVG element. | 
|      * | 
|      * @param displayable displayable element | 
|      * @return SVG element | 
|      */ | 
|     getSvgElement(displayable: Displayable): SVGElement { | 
|         return displayable.__svgEl; | 
|     } | 
|   | 
| } |