UNPKG

zrender

Version:

A lightweight graphic library providing 2d draw for Apache ECharts

235 lines (205 loc) 6.4 kB
/** * @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; } }