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 
 | 
} 
 |