zrender
Version:
A lightweight graphic library providing 2d draw for Apache ECharts
243 lines (201 loc) • 6.87 kB
text/typescript
import * as util from './core/util';
import Group, { GroupLike } from './graphic/Group';
import Element from './Element';
// Use timsort because in most case elements are partially sorted
// https://jsfiddle.net/pissang/jr4x7mdm/8/
import timsort from './core/timsort';
import Displayable from './graphic/Displayable';
import Path from './graphic/Path';
import { REDRAW_BIT } from './graphic/constants';
let invalidZErrorLogged = false;
function logInvalidZError() {
if (invalidZErrorLogged) {
return;
}
invalidZErrorLogged = true;
console.warn('z / z2 / zlevel of displayable is invalid, which may cause unexpected errors');
}
function shapeCompareFunc(a: Displayable, b: Displayable) {
if (a.zlevel === b.zlevel) {
if (a.z === b.z) {
return a.z2 - b.z2;
}
return a.z - b.z;
}
return a.zlevel - b.zlevel;
}
export default class Storage {
private _roots: Element[] = []
private _displayList: Displayable[] = []
private _displayListLen = 0
traverse<T>(
cb: (this: T, el: Element) => void,
context?: T
) {
for (let i = 0; i < this._roots.length; i++) {
this._roots[i].traverse(cb, context);
}
}
/**
* get a list of elements to be rendered
*
* @param {boolean} update whether to update elements before return
* @param {DisplayParams} params options
* @return {Displayable[]} a list of elements
*/
getDisplayList(update?: boolean, includeIgnore?: boolean): Displayable[] {
includeIgnore = includeIgnore || false;
const displayList = this._displayList;
// If displaylist is not created yet. Update force
if (update || !displayList.length) {
this.updateDisplayList(includeIgnore);
}
return displayList;
}
/**
* 更新图形的绘制队列。
* 每次绘制前都会调用,该方法会先深度优先遍历整个树,更新所有Group和Shape的变换并且把所有可见的Shape保存到数组中,
* 最后根据绘制的优先级(zlevel > z > 插入顺序)排序得到绘制队列
*/
updateDisplayList(includeIgnore?: boolean) {
this._displayListLen = 0;
const roots = this._roots;
const displayList = this._displayList;
for (let i = 0, len = roots.length; i < len; i++) {
this._updateAndAddDisplayable(roots[i], null, includeIgnore);
}
displayList.length = this._displayListLen;
timsort(displayList, shapeCompareFunc);
}
private _updateAndAddDisplayable(
el: Element,
clipPaths: Path[],
includeIgnore?: boolean
) {
if (el.ignore && !includeIgnore) {
return;
}
el.beforeUpdate();
el.update();
el.afterUpdate();
const userSetClipPath = el.getClipPath();
if (el.ignoreClip) {
clipPaths = null;
}
else if (userSetClipPath) {
// FIXME 效率影响
if (clipPaths) {
clipPaths = clipPaths.slice();
}
else {
clipPaths = [];
}
let currentClipPath = userSetClipPath;
let parentClipPath = el;
// Recursively add clip path
while (currentClipPath) {
// clipPath 的变换是基于使用这个 clipPath 的元素
// TODO: parent should be group type.
currentClipPath.parent = parentClipPath as Group;
currentClipPath.updateTransform();
clipPaths.push(currentClipPath);
parentClipPath = currentClipPath;
currentClipPath = currentClipPath.getClipPath();
}
}
// ZRText and Group and combining morphing Path may use children
if ((el as GroupLike).childrenRef) {
const children = (el as GroupLike).childrenRef();
for (let i = 0; i < children.length; i++) {
const child = children[i];
// Force to mark as dirty if group is dirty
if (el.__dirty) {
child.__dirty |= REDRAW_BIT;
}
this._updateAndAddDisplayable(child, clipPaths, includeIgnore);
}
// Mark group clean here
el.__dirty = 0;
}
else {
const disp = el as Displayable;
// Element is displayable
if (clipPaths && clipPaths.length) {
disp.__clipPaths = clipPaths;
}
else if (disp.__clipPaths && disp.__clipPaths.length > 0) {
disp.__clipPaths = [];
}
// Avoid invalid z, z2, zlevel cause sorting error.
if (isNaN(disp.z)) {
logInvalidZError();
disp.z = 0;
}
if (isNaN(disp.z2)) {
logInvalidZError();
disp.z2 = 0;
}
if (isNaN(disp.zlevel)) {
logInvalidZError();
disp.zlevel = 0;
}
this._displayList[this._displayListLen++] = disp;
}
// Add decal
const decalEl = (el as Path).getDecalElement && (el as Path).getDecalElement();
if (decalEl) {
this._updateAndAddDisplayable(decalEl, clipPaths, includeIgnore);
}
// Add attached text element and guide line.
const textGuide = el.getTextGuideLine();
if (textGuide) {
this._updateAndAddDisplayable(textGuide, clipPaths, includeIgnore);
}
const textEl = el.getTextContent();
if (textEl) {
this._updateAndAddDisplayable(textEl, clipPaths, includeIgnore);
}
}
/**
* 添加图形(Displayable)或者组(Group)到根节点
*/
addRoot(el: Element) {
if (el.__zr && el.__zr.storage === this) {
return;
}
this._roots.push(el);
}
/**
* 删除指定的图形(Displayable)或者组(Group)
* @param el
*/
delRoot(el: Element | Element[]) {
if (el instanceof Array) {
for (let i = 0, l = el.length; i < l; i++) {
this.delRoot(el[i]);
}
return;
}
const idx = util.indexOf(this._roots, el);
if (idx >= 0) {
this._roots.splice(idx, 1);
}
}
delAllRoots() {
this._roots = [];
this._displayList = [];
this._displayListLen = 0;
return;
}
getRoots() {
return this._roots;
}
/**
* 清空并且释放Storage
*/
dispose() {
this._displayList = null;
this._roots = null;
}
displayableSortFunc = shapeCompareFunc
}