243 lines
6.7 KiB
TypeScript
243 lines
6.7 KiB
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
|
||
} |