| /** | 
|  * @file Manages SVG clipPath elements. | 
|  * @author Zhang Wenli | 
|  */ | 
|   | 
| import Definable from './Definable'; | 
| import * as zrUtil from '../../core/util'; | 
| import Displayable from '../../graphic/Displayable'; | 
| import Path from '../../graphic/Path'; | 
| import {path} from '../graphic'; | 
| import { Dictionary } from '../../core/types'; | 
| import { isClipPathChanged } from '../../canvas/helper'; | 
| import { getClipPathsKey, getIdURL } from '../../svg/helper'; | 
| import { createElement } from '../../svg/core'; | 
|   | 
| type PathExtended = Path & { | 
|     _dom: SVGElement | 
| } | 
|   | 
| export function hasClipPath(displayable: Displayable) { | 
|     const clipPaths = displayable.__clipPaths; | 
|     return clipPaths && clipPaths.length > 0; | 
| } | 
| /** | 
|  * Manages SVG clipPath elements. | 
|  */ | 
| export default class ClippathManager extends Definable { | 
|   | 
|     private _refGroups: Dictionary<SVGElement> = {}; | 
|     private _keyDuplicateCount: Dictionary<number> = {}; | 
|   | 
|     constructor(zrId: number, svgRoot: SVGElement) { | 
|         super(zrId, svgRoot, 'clipPath', '__clippath_in_use__'); | 
|     } | 
|   | 
|     markAllUnused() { | 
|         super.markAllUnused(); | 
|         const refGroups = this._refGroups; | 
|         for (let key in refGroups) { | 
|             if (refGroups.hasOwnProperty(key)) { | 
|                 this.markDomUnused(refGroups[key]); | 
|             } | 
|         } | 
|         this._keyDuplicateCount = {}; | 
|     } | 
|   | 
|   | 
|     private _getClipPathGroup(displayable: Displayable, prevDisplayable: Displayable) { | 
|         if (!hasClipPath(displayable)) { | 
|             return; | 
|         } | 
|         const clipPaths = displayable.__clipPaths; | 
|   | 
|         const keyDuplicateCount = this._keyDuplicateCount; | 
|         let clipPathKey = getClipPathsKey(clipPaths); | 
|         if (isClipPathChanged(clipPaths, prevDisplayable && prevDisplayable.__clipPaths)) { | 
|             keyDuplicateCount[clipPathKey] = keyDuplicateCount[clipPathKey] || 0; | 
|             keyDuplicateCount[clipPathKey] && (clipPathKey += '-' + keyDuplicateCount[clipPathKey]); | 
|             keyDuplicateCount[clipPathKey]++; | 
|         } | 
|   | 
|         return this._refGroups[clipPathKey] | 
|             || (this._refGroups[clipPathKey] = createElement('g')); | 
|     } | 
|   | 
|     /** | 
|      * Update clipPath. | 
|      * | 
|      * @param displayable displayable element | 
|      */ | 
|     update(displayable: Displayable, prevDisplayable: Displayable) { | 
|         const clipGroup = this._getClipPathGroup(displayable, prevDisplayable); | 
|         if (clipGroup) { | 
|             this.markDomUsed(clipGroup); | 
|             this.updateDom(clipGroup, displayable.__clipPaths); | 
|         } | 
|         return clipGroup; | 
|     }; | 
|   | 
|   | 
|     /** | 
|      * Create an SVGElement of displayable and create a <clipPath> of its | 
|      * clipPath | 
|      */ | 
|     updateDom(parentEl: SVGElement, clipPaths: Path[]) { | 
|         if (clipPaths && clipPaths.length > 0) { | 
|             // Has clipPath, create <clipPath> with the first clipPath | 
|             const defs = this.getDefs(true); | 
|             const clipPath = clipPaths[0] as PathExtended; | 
|             let clipPathEl; | 
|             let id; | 
|   | 
|             if (clipPath._dom) { | 
|                 // Use a dom that is already in <defs> | 
|                 id = clipPath._dom.getAttribute('id'); | 
|                 clipPathEl = clipPath._dom; | 
|   | 
|                 // Use a dom that is already in <defs> | 
|                 if (!defs.contains(clipPathEl)) { | 
|                     // This happens when set old clipPath that has | 
|                     // been previously removed | 
|                     defs.appendChild(clipPathEl); | 
|                 } | 
|             } | 
|             else { | 
|                 // New <clipPath> | 
|                 id = 'zr' + this._zrId + '-clip-' + this.nextId; | 
|                 ++this.nextId; | 
|                 clipPathEl = createElement('clipPath'); | 
|                 clipPathEl.setAttribute('id', id); | 
|                 defs.appendChild(clipPathEl); | 
|   | 
|                 clipPath._dom = clipPathEl; | 
|             } | 
|   | 
|             // Build path and add to <clipPath> | 
|             path.brush(clipPath); | 
|   | 
|             const pathEl = this.getSvgElement(clipPath); | 
|   | 
|             clipPathEl.innerHTML = ''; | 
|             clipPathEl.appendChild(pathEl); | 
|   | 
|             parentEl.setAttribute('clip-path', getIdURL(id)); | 
|   | 
|             if (clipPaths.length > 1) { | 
|                 // Make the other clipPaths recursively | 
|                 this.updateDom(clipPathEl, clipPaths.slice(1)); | 
|             } | 
|         } | 
|         else { | 
|             // No clipPath | 
|             if (parentEl) { | 
|                 parentEl.setAttribute('clip-path', 'none'); | 
|             } | 
|         } | 
|     }; | 
|   | 
|     /** | 
|      * Mark a single clipPath to be used | 
|      * | 
|      * @param displayable displayable element | 
|      */ | 
|     markUsed(displayable: Displayable) { | 
|         // displayable.__clipPaths can only be `null`/`undefined` or an non-empty array. | 
|         if (displayable.__clipPaths) { | 
|             zrUtil.each(displayable.__clipPaths, (clipPath: PathExtended) => { | 
|                 if (clipPath._dom) { | 
|                     super.markDomUsed(clipPath._dom); | 
|                 } | 
|             }); | 
|         } | 
|     }; | 
|   | 
|     removeUnused() { | 
|         super.removeUnused(); | 
|   | 
|         const newRefGroupsMap: Dictionary<SVGElement> = {}; | 
|         const refGroups = this._refGroups; | 
|         for (let key in refGroups) { | 
|             if (refGroups.hasOwnProperty(key)) { | 
|                 const group = refGroups[key]; | 
|                 if (!this.isDomUnused(group)) { | 
|                     newRefGroupsMap[key] = group; | 
|                 } | 
|                 else if (group.parentNode) { | 
|                     group.parentNode.removeChild(group); | 
|                 } | 
|             } | 
|         } | 
|         this._refGroups = newRefGroupsMap; | 
|     } | 
| } |