zrender
Version:
A lightweight graphic library providing 2d draw for Apache ECharts
235 lines (205 loc) • 6.4 kB
text/typescript
/**
* @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;
}
}