| import {devicePixelRatio} from '../config'; | 
| import * as util from '../core/util'; | 
| import Layer, { LayerConfig } from './Layer'; | 
| import requestAnimationFrame from '../animation/requestAnimationFrame'; | 
| import env from '../core/env'; | 
| import Displayable from '../graphic/Displayable'; | 
| import { WXCanvasRenderingContext } from '../core/types'; | 
| import { GradientObject } from '../graphic/Gradient'; | 
| import { ImagePatternObject } from '../graphic/Pattern'; | 
| import Storage from '../Storage'; | 
| import { brush, BrushScope, brushSingle } from './graphic'; | 
| import { PainterBase } from '../PainterBase'; | 
| import BoundingRect from '../core/BoundingRect'; | 
| import { REDRAW_BIT } from '../graphic/constants'; | 
| import { getSize } from './helper'; | 
| import type IncrementalDisplayable from '../graphic/IncrementalDisplayable'; | 
|   | 
| const HOVER_LAYER_ZLEVEL = 1e5; | 
| const CANVAS_ZLEVEL = 314159; | 
|   | 
| const EL_AFTER_INCREMENTAL_INC = 0.01; | 
| const INCREMENTAL_INC = 0.001; | 
|   | 
|   | 
| function isLayerValid(layer: Layer) { | 
|     if (!layer) { | 
|         return false; | 
|     } | 
|   | 
|     if (layer.__builtin__) { | 
|         return true; | 
|     } | 
|   | 
|     if (typeof (layer.resize) !== 'function' | 
|         || typeof (layer.refresh) !== 'function' | 
|     ) { | 
|         return false; | 
|     } | 
|   | 
|     return true; | 
| } | 
|   | 
| function createRoot(width: number, height: number) { | 
|     const domRoot = document.createElement('div'); | 
|   | 
|     // domRoot.onselectstart = returnFalse; // Avoid page selected | 
|     domRoot.style.cssText = [ | 
|         'position:relative', | 
|         // IOS13 safari probably has a compositing bug (z order of the canvas and the consequent | 
|         // dom does not act as expected) when some of the parent dom has | 
|         // `-webkit-overflow-scrolling: touch;` and the webpage is longer than one screen and | 
|         // the canvas is not at the top part of the page. | 
|         // Check `https://bugs.webkit.org/show_bug.cgi?id=203681` for more details. We remove | 
|         // this `overflow:hidden` to avoid the bug. | 
|         // 'overflow:hidden', | 
|         'width:' + width + 'px', | 
|         'height:' + height + 'px', | 
|         'padding:0', | 
|         'margin:0', | 
|         'border-width:0' | 
|     ].join(';') + ';'; | 
|   | 
|     return domRoot; | 
| } | 
|   | 
| interface CanvasPainterOption { | 
|     devicePixelRatio?: number | 
|     width?: number | string  // Can be 10 / 10px / auto | 
|     height?: number | string, | 
|     useDirtyRect?: boolean | 
| } | 
|   | 
| export default class CanvasPainter implements PainterBase { | 
|   | 
|     type = 'canvas' | 
|   | 
|     root: HTMLElement | 
|   | 
|     dpr: number | 
|   | 
|     storage: Storage | 
|   | 
|     private _singleCanvas: boolean | 
|   | 
|     private _opts: CanvasPainterOption | 
|   | 
|     private _zlevelList: number[] = [] | 
|   | 
|     private _prevDisplayList: Displayable[] = [] | 
|   | 
|     private _layers: {[key: number]: Layer} = {} // key is zlevel | 
|   | 
|     private _layerConfig: {[key: number]: LayerConfig} = {} // key is zlevel | 
|   | 
|     /** | 
|      * zrender will do compositing when root is a canvas and have multiple zlevels. | 
|      */ | 
|     private _needsManuallyCompositing = false | 
|   | 
|     private _width: number | 
|     private _height: number | 
|   | 
|     private _domRoot: HTMLElement | 
|   | 
|     private _hoverlayer: Layer | 
|   | 
|     private _redrawId: number | 
|   | 
|     private _backgroundColor: string | GradientObject | ImagePatternObject | 
|   | 
|   | 
|     constructor(root: HTMLElement, storage: Storage, opts: CanvasPainterOption, id: number) { | 
|   | 
|         this.type = 'canvas'; | 
|   | 
|         // In node environment using node-canvas | 
|         const singleCanvas = !root.nodeName // In node ? | 
|             || root.nodeName.toUpperCase() === 'CANVAS'; | 
|   | 
|         this._opts = opts = util.extend({}, opts || {}) as CanvasPainterOption; | 
|   | 
|         /** | 
|          * @type {number} | 
|          */ | 
|         this.dpr = opts.devicePixelRatio || devicePixelRatio; | 
|         /** | 
|          * @type {boolean} | 
|          * @private | 
|          */ | 
|         this._singleCanvas = singleCanvas; | 
|         /** | 
|          * 绘图容器 | 
|          * @type {HTMLElement} | 
|          */ | 
|         this.root = root; | 
|   | 
|         const rootStyle = root.style; | 
|   | 
|         if (rootStyle) { | 
|             // @ts-ignore | 
|             util.disableUserSelect(root); | 
|             root.innerHTML = ''; | 
|         } | 
|   | 
|         /** | 
|          * @type {module:zrender/Storage} | 
|          */ | 
|         this.storage = storage; | 
|   | 
|         const zlevelList: number[] = this._zlevelList; | 
|   | 
|         this._prevDisplayList = []; | 
|   | 
|         const layers = this._layers; | 
|   | 
|         if (!singleCanvas) { | 
|             this._width = getSize(root, 0, opts); | 
|             this._height = getSize(root, 1, opts); | 
|   | 
|             const domRoot = this._domRoot = createRoot( | 
|                 this._width, this._height | 
|             ); | 
|             root.appendChild(domRoot); | 
|         } | 
|         else { | 
|             const rootCanvas = root as HTMLCanvasElement; | 
|             let width = rootCanvas.width; | 
|             let height = rootCanvas.height; | 
|   | 
|             if (opts.width != null) { | 
|                 // TODO sting? | 
|                 width = opts.width as number; | 
|             } | 
|             if (opts.height != null) { | 
|                 // TODO sting? | 
|                 height = opts.height as number; | 
|             } | 
|             this.dpr = opts.devicePixelRatio || 1; | 
|   | 
|             // Use canvas width and height directly | 
|             rootCanvas.width = width * this.dpr; | 
|             rootCanvas.height = height * this.dpr; | 
|   | 
|             this._width = width; | 
|             this._height = height; | 
|   | 
|             // Create layer if only one given canvas | 
|             // Device can be specified to create a high dpi image. | 
|             const mainLayer = new Layer(rootCanvas, this, this.dpr); | 
|             mainLayer.__builtin__ = true; | 
|             mainLayer.initContext(); | 
|             // FIXME Use canvas width and height | 
|             // mainLayer.resize(width, height); | 
|             layers[CANVAS_ZLEVEL] = mainLayer; | 
|             mainLayer.zlevel = CANVAS_ZLEVEL; | 
|             // Not use common zlevel. | 
|             zlevelList.push(CANVAS_ZLEVEL); | 
|   | 
|             this._domRoot = root; | 
|         } | 
|     } | 
|   | 
|   | 
|     getType() { | 
|         return 'canvas'; | 
|     } | 
|   | 
|     /** | 
|      * If painter use a single canvas | 
|      */ | 
|     isSingleCanvas() { | 
|         return this._singleCanvas; | 
|     } | 
|   | 
|     getViewportRoot() { | 
|         return this._domRoot; | 
|     } | 
|   | 
|     getViewportRootOffset() { | 
|         const viewportRoot = this.getViewportRoot(); | 
|         if (viewportRoot) { | 
|             return { | 
|                 offsetLeft: viewportRoot.offsetLeft || 0, | 
|                 offsetTop: viewportRoot.offsetTop || 0 | 
|             }; | 
|         } | 
|     } | 
|   | 
|     /** | 
|      * 刷新 | 
|      * @param paintAll 强制绘制所有displayable | 
|      */ | 
|     refresh(paintAll?: boolean) { | 
|         const list = this.storage.getDisplayList(true); | 
|         const prevList = this._prevDisplayList; | 
|   | 
|         const zlevelList = this._zlevelList; | 
|   | 
|         this._redrawId = Math.random(); | 
|   | 
|         this._paintList(list, prevList, paintAll, this._redrawId); | 
|   | 
|         // Paint custum layers | 
|         for (let i = 0; i < zlevelList.length; i++) { | 
|             const z = zlevelList[i]; | 
|             const layer = this._layers[z]; | 
|             if (!layer.__builtin__ && layer.refresh) { | 
|                 const clearColor = i === 0 ? this._backgroundColor : null; | 
|                 layer.refresh(clearColor); | 
|             } | 
|         } | 
|   | 
|         if (this._opts.useDirtyRect) { | 
|             this._prevDisplayList = list.slice(); | 
|         } | 
|   | 
|         return this; | 
|     } | 
|   | 
|   | 
|     refreshHover() { | 
|         this._paintHoverList(this.storage.getDisplayList(false)); | 
|     } | 
|   | 
|     private _paintHoverList(list: Displayable[]) { | 
|         let len = list.length; | 
|         let hoverLayer = this._hoverlayer; | 
|         hoverLayer && hoverLayer.clear(); | 
|   | 
|         if (!len) { | 
|             return; | 
|         } | 
|   | 
|         const scope: BrushScope = { | 
|             inHover: true, | 
|             viewWidth: this._width, | 
|             viewHeight: this._height | 
|         }; | 
|   | 
|         let ctx; | 
|         for (let i = 0; i < len; i++) { | 
|             const el = list[i]; | 
|             if (el.__inHover) { | 
|                 // Use a extream large zlevel | 
|                 // FIXME? | 
|                 if (!hoverLayer) { | 
|                     hoverLayer = this._hoverlayer = this.getLayer(HOVER_LAYER_ZLEVEL); | 
|                 } | 
|   | 
|                 if (!ctx) { | 
|                     ctx = hoverLayer.ctx; | 
|                     ctx.save(); | 
|                 } | 
|   | 
|                 brush(ctx, el, scope, i === len - 1); | 
|             } | 
|         } | 
|         if (ctx) { | 
|             ctx.restore(); | 
|         } | 
|     } | 
|   | 
|     getHoverLayer() { | 
|         return this.getLayer(HOVER_LAYER_ZLEVEL); | 
|     } | 
|   | 
|     paintOne(ctx: CanvasRenderingContext2D, el: Displayable) { | 
|         brushSingle(ctx, el); | 
|     } | 
|   | 
|     private _paintList(list: Displayable[], prevList: Displayable[], paintAll: boolean, redrawId?: number) { | 
|         if (this._redrawId !== redrawId) { | 
|             return; | 
|         } | 
|   | 
|         paintAll = paintAll || false; | 
|   | 
|         this._updateLayerStatus(list); | 
|   | 
|         const {finished, needsRefreshHover} = this._doPaintList(list, prevList, paintAll); | 
|   | 
|         if (this._needsManuallyCompositing) { | 
|             this._compositeManually(); | 
|         } | 
|   | 
|         if (needsRefreshHover) { | 
|             this._paintHoverList(list); | 
|         } | 
|   | 
|         if (!finished) { | 
|             const self = this; | 
|             requestAnimationFrame(function () { | 
|                 self._paintList(list, prevList, paintAll, redrawId); | 
|             }); | 
|         } | 
|         else { | 
|             this.eachLayer(layer => { | 
|                 layer.afterBrush && layer.afterBrush(); | 
|             }); | 
|         } | 
|     } | 
|   | 
|     private _compositeManually() { | 
|         const ctx = this.getLayer(CANVAS_ZLEVEL).ctx; | 
|         const width = (this._domRoot as HTMLCanvasElement).width; | 
|         const height = (this._domRoot as HTMLCanvasElement).height; | 
|         ctx.clearRect(0, 0, width, height); | 
|         // PENDING, If only builtin layer? | 
|         this.eachBuiltinLayer(function (layer) { | 
|             if (layer.virtual) { | 
|                 ctx.drawImage(layer.dom, 0, 0, width, height); | 
|             } | 
|         }); | 
|     } | 
|   | 
|     private _doPaintList( | 
|         list: Displayable[], | 
|         prevList: Displayable[], | 
|         paintAll?: boolean | 
|     ): { | 
|         finished: boolean | 
|         needsRefreshHover: boolean | 
|     } { | 
|         const layerList = []; | 
|         const useDirtyRect = this._opts.useDirtyRect; | 
|         for (let zi = 0; zi < this._zlevelList.length; zi++) { | 
|             const zlevel = this._zlevelList[zi]; | 
|             const layer = this._layers[zlevel]; | 
|             if (layer.__builtin__ | 
|                 && layer !== this._hoverlayer | 
|                 && (layer.__dirty || paintAll) | 
|                 // Layer with hover elements can't be redrawn. | 
|                 // && !layer.__hasHoverLayerELement | 
|             ) { | 
|                 layerList.push(layer); | 
|             } | 
|         } | 
|   | 
|         let finished = true; | 
|         let needsRefreshHover = false; | 
|   | 
|         for (let k = 0; k < layerList.length; k++) { | 
|             const layer = layerList[k]; | 
|             const ctx = layer.ctx; | 
|   | 
|             const repaintRects = useDirtyRect | 
|                 && layer.createRepaintRects(list, prevList, this._width, this._height); | 
|   | 
|             let start = paintAll ? layer.__startIndex : layer.__drawIndex; | 
|   | 
|             const useTimer = !paintAll && layer.incremental && Date.now; | 
|             const startTime = useTimer && Date.now(); | 
|   | 
|             const clearColor = layer.zlevel === this._zlevelList[0] | 
|                 ? this._backgroundColor : null; | 
|   | 
|             // All elements in this layer are removed. | 
|             if (layer.__startIndex === layer.__endIndex) { | 
|                 layer.clear(false, clearColor, repaintRects); | 
|             } | 
|             else if (start === layer.__startIndex) { | 
|                 const firstEl = list[start]; | 
|                 if (!firstEl.incremental || !(firstEl as IncrementalDisplayable).notClear || paintAll) { | 
|                     layer.clear(false, clearColor, repaintRects); | 
|                 } | 
|             } | 
|             if (start === -1) { | 
|                 console.error('For some unknown reason. drawIndex is -1'); | 
|                 start = layer.__startIndex; | 
|             } | 
|             let i: number; | 
|             /* eslint-disable-next-line */ | 
|             const repaint = (repaintRect?: BoundingRect) => { | 
|                 const scope: BrushScope = { | 
|                     inHover: false, | 
|                     allClipped: false, | 
|                     prevEl: null, | 
|                     viewWidth: this._width, | 
|                     viewHeight: this._height | 
|                 }; | 
|   | 
|                 for (i = start; i < layer.__endIndex; i++) { | 
|                     const el = list[i]; | 
|   | 
|                     if (el.__inHover) { | 
|                         needsRefreshHover = true; | 
|                     } | 
|   | 
|                     this._doPaintEl(el, layer, useDirtyRect, repaintRect, scope, i === layer.__endIndex - 1); | 
|   | 
|                     if (useTimer) { | 
|                         // Date.now can be executed in 13,025,305 ops/second. | 
|                         const dTime = Date.now() - startTime; | 
|                         // Give 15 millisecond to draw. | 
|                         // The rest elements will be drawn in the next frame. | 
|                         if (dTime > 15) { | 
|                             break; | 
|                         } | 
|                     } | 
|                 } | 
|   | 
|                 if (scope.prevElClipPaths) { | 
|                     // Needs restore the state. If last drawn element is in the clipping area. | 
|                     ctx.restore(); | 
|                 } | 
|             }; | 
|   | 
|             if (repaintRects) { | 
|                 if (repaintRects.length === 0) { | 
|                     // Nothing to repaint, mark as finished | 
|                     i = layer.__endIndex; | 
|                 } | 
|                 else { | 
|                     const dpr = this.dpr; | 
|                     // Set repaintRect as clipPath | 
|                     for (var r = 0; r < repaintRects.length; ++r) { | 
|                         const rect = repaintRects[r]; | 
|   | 
|                         ctx.save(); | 
|                         ctx.beginPath(); | 
|                         ctx.rect( | 
|                             rect.x * dpr, | 
|                             rect.y * dpr, | 
|                             rect.width * dpr, | 
|                             rect.height * dpr | 
|                         ); | 
|                         ctx.clip(); | 
|   | 
|                         repaint(rect); | 
|                         ctx.restore(); | 
|                     } | 
|                 } | 
|             } | 
|             else { | 
|                 // Paint all once | 
|                 ctx.save(); | 
|                 repaint(); | 
|                 ctx.restore(); | 
|             } | 
|   | 
|             layer.__drawIndex = i; | 
|   | 
|             if (layer.__drawIndex < layer.__endIndex) { | 
|                 finished = false; | 
|             } | 
|         } | 
|   | 
|         if (env.wxa) { | 
|             // Flush for weixin application | 
|             util.each(this._layers, function (layer) { | 
|                 if (layer && layer.ctx && (layer.ctx as WXCanvasRenderingContext).draw) { | 
|                     (layer.ctx as WXCanvasRenderingContext).draw(); | 
|                 } | 
|             }); | 
|         } | 
|   | 
|         return { | 
|             finished, | 
|             needsRefreshHover | 
|         }; | 
|     } | 
|   | 
|     private _doPaintEl( | 
|         el: Displayable, | 
|         currentLayer: Layer, | 
|         useDirtyRect: boolean, | 
|         repaintRect: BoundingRect, | 
|         scope: BrushScope, | 
|         isLast: boolean | 
|     ) { | 
|         const ctx = currentLayer.ctx; | 
|         if (useDirtyRect) { | 
|             const paintRect = el.getPaintRect(); | 
|             if (!repaintRect || paintRect && paintRect.intersect(repaintRect)) { | 
|                 brush(ctx, el, scope, isLast); | 
|                 el.setPrevPaintRect(paintRect); | 
|             } | 
|         } | 
|         else { | 
|             brush(ctx, el, scope, isLast); | 
|         } | 
|     } | 
|   | 
|     /** | 
|      * 获取 zlevel 所在层,如果不存在则会创建一个新的层 | 
|      * @param zlevel | 
|      * @param virtual Virtual layer will not be inserted into dom. | 
|      */ | 
|     getLayer(zlevel: number, virtual?: boolean) { | 
|         if (this._singleCanvas && !this._needsManuallyCompositing) { | 
|             zlevel = CANVAS_ZLEVEL; | 
|         } | 
|         let layer = this._layers[zlevel]; | 
|         if (!layer) { | 
|             // Create a new layer | 
|             layer = new Layer('zr_' + zlevel, this, this.dpr); | 
|             layer.zlevel = zlevel; | 
|             layer.__builtin__ = true; | 
|   | 
|             if (this._layerConfig[zlevel]) { | 
|                 util.merge(layer, this._layerConfig[zlevel], true); | 
|             } | 
|             // TODO Remove EL_AFTER_INCREMENTAL_INC magic number | 
|             else if (this._layerConfig[zlevel - EL_AFTER_INCREMENTAL_INC]) { | 
|                 util.merge(layer, this._layerConfig[zlevel - EL_AFTER_INCREMENTAL_INC], true); | 
|             } | 
|   | 
|             if (virtual) { | 
|                 layer.virtual = virtual; | 
|             } | 
|   | 
|             this.insertLayer(zlevel, layer); | 
|   | 
|             // Context is created after dom inserted to document | 
|             // Or excanvas will get 0px clientWidth and clientHeight | 
|             layer.initContext(); | 
|         } | 
|   | 
|         return layer; | 
|     } | 
|   | 
|     insertLayer(zlevel: number, layer: Layer) { | 
|   | 
|         const layersMap = this._layers; | 
|         const zlevelList = this._zlevelList; | 
|         const len = zlevelList.length; | 
|         const domRoot = this._domRoot; | 
|         let prevLayer = null; | 
|         let i = -1; | 
|   | 
|         if (layersMap[zlevel]) { | 
|             if (process.env.NODE_ENV !== 'production') { | 
|                 util.logError('ZLevel ' + zlevel + ' has been used already'); | 
|             } | 
|             return; | 
|         } | 
|         // Check if is a valid layer | 
|         if (!isLayerValid(layer)) { | 
|             if (process.env.NODE_ENV !== 'production') { | 
|                 util.logError('Layer of zlevel ' + zlevel + ' is not valid'); | 
|             } | 
|             return; | 
|         } | 
|   | 
|         if (len > 0 && zlevel > zlevelList[0]) { | 
|             for (i = 0; i < len - 1; i++) { | 
|                 if ( | 
|                     zlevelList[i] < zlevel | 
|                     && zlevelList[i + 1] > zlevel | 
|                 ) { | 
|                     break; | 
|                 } | 
|             } | 
|             prevLayer = layersMap[zlevelList[i]]; | 
|         } | 
|         zlevelList.splice(i + 1, 0, zlevel); | 
|   | 
|         layersMap[zlevel] = layer; | 
|   | 
|         // Virtual layer will not directly show on the screen. | 
|         // (It can be a WebGL layer and assigned to a ZRImage element) | 
|         // But it still under management of zrender. | 
|         if (!layer.virtual) { | 
|             if (prevLayer) { | 
|                 const prevDom = prevLayer.dom; | 
|                 if (prevDom.nextSibling) { | 
|                     domRoot.insertBefore( | 
|                         layer.dom, | 
|                         prevDom.nextSibling | 
|                     ); | 
|                 } | 
|                 else { | 
|                     domRoot.appendChild(layer.dom); | 
|                 } | 
|             } | 
|             else { | 
|                 if (domRoot.firstChild) { | 
|                     domRoot.insertBefore(layer.dom, domRoot.firstChild); | 
|                 } | 
|                 else { | 
|                     domRoot.appendChild(layer.dom); | 
|                 } | 
|             } | 
|         } | 
|   | 
|         layer.painter || (layer.painter = this); | 
|     } | 
|   | 
|     // Iterate each layer | 
|     eachLayer<T>(cb: (this: T, layer: Layer, z: number) => void, context?: T) { | 
|         const zlevelList = this._zlevelList; | 
|         for (let i = 0; i < zlevelList.length; i++) { | 
|             const z = zlevelList[i]; | 
|             cb.call(context, this._layers[z], z); | 
|         } | 
|     } | 
|   | 
|     // Iterate each buildin layer | 
|     eachBuiltinLayer<T>(cb: (this: T, layer: Layer, z: number) => void, context?: T) { | 
|         const zlevelList = this._zlevelList; | 
|         for (let i = 0; i < zlevelList.length; i++) { | 
|             const z = zlevelList[i]; | 
|             const layer = this._layers[z]; | 
|             if (layer.__builtin__) { | 
|                 cb.call(context, layer, z); | 
|             } | 
|         } | 
|     } | 
|   | 
|     // Iterate each other layer except buildin layer | 
|     eachOtherLayer<T>(cb: (this: T, layer: Layer, z: number) => void, context?: T) { | 
|         const zlevelList = this._zlevelList; | 
|         for (let i = 0; i < zlevelList.length; i++) { | 
|             const z = zlevelList[i]; | 
|             const layer = this._layers[z]; | 
|             if (!layer.__builtin__) { | 
|                 cb.call(context, layer, z); | 
|             } | 
|         } | 
|     } | 
|   | 
|     /** | 
|      * 获取所有已创建的层 | 
|      * @param prevLayer | 
|      */ | 
|     getLayers() { | 
|         return this._layers; | 
|     } | 
|   | 
|     _updateLayerStatus(list: Displayable[]) { | 
|   | 
|         this.eachBuiltinLayer(function (layer, z) { | 
|             layer.__dirty = layer.__used = false; | 
|         }); | 
|   | 
|         function updatePrevLayer(idx: number) { | 
|             if (prevLayer) { | 
|                 if (prevLayer.__endIndex !== idx) { | 
|                     prevLayer.__dirty = true; | 
|                 } | 
|                 prevLayer.__endIndex = idx; | 
|             } | 
|         } | 
|   | 
|         if (this._singleCanvas) { | 
|             for (let i = 1; i < list.length; i++) { | 
|                 const el = list[i]; | 
|                 if (el.zlevel !== list[i - 1].zlevel || el.incremental) { | 
|                     this._needsManuallyCompositing = true; | 
|                     break; | 
|                 } | 
|             } | 
|         } | 
|   | 
|         let prevLayer: Layer = null; | 
|         let incrementalLayerCount = 0; | 
|         let prevZlevel; | 
|         let i; | 
|   | 
|         for (i = 0; i < list.length; i++) { | 
|             const el = list[i]; | 
|             const zlevel = el.zlevel; | 
|             let layer; | 
|   | 
|             if (prevZlevel !== zlevel) { | 
|                 prevZlevel = zlevel; | 
|                 incrementalLayerCount = 0; | 
|             } | 
|   | 
|             // TODO Not use magic number on zlevel. | 
|   | 
|             // Each layer with increment element can be separated to 3 layers. | 
|             //          (Other Element drawn after incremental element) | 
|             // -----------------zlevel + EL_AFTER_INCREMENTAL_INC-------------------- | 
|             //                      (Incremental element) | 
|             // ----------------------zlevel + INCREMENTAL_INC------------------------ | 
|             //              (Element drawn before incremental element) | 
|             // --------------------------------zlevel-------------------------------- | 
|             if (el.incremental) { | 
|                 layer = this.getLayer(zlevel + INCREMENTAL_INC, this._needsManuallyCompositing); | 
|                 layer.incremental = true; | 
|                 incrementalLayerCount = 1; | 
|             } | 
|             else { | 
|                 layer = this.getLayer( | 
|                     zlevel + (incrementalLayerCount > 0 ? EL_AFTER_INCREMENTAL_INC : 0), | 
|                     this._needsManuallyCompositing | 
|                 ); | 
|             } | 
|   | 
|             if (!layer.__builtin__) { | 
|                 util.logError('ZLevel ' + zlevel + ' has been used by unkown layer ' + layer.id); | 
|             } | 
|   | 
|             if (layer !== prevLayer) { | 
|                 layer.__used = true; | 
|                 if (layer.__startIndex !== i) { | 
|                     layer.__dirty = true; | 
|                 } | 
|                 layer.__startIndex = i; | 
|                 if (!layer.incremental) { | 
|                     layer.__drawIndex = i; | 
|                 } | 
|                 else { | 
|                     // Mark layer draw index needs to update. | 
|                     layer.__drawIndex = -1; | 
|                 } | 
|                 updatePrevLayer(i); | 
|                 prevLayer = layer; | 
|             } | 
|             if ((el.__dirty & REDRAW_BIT) && !el.__inHover) {  // Ignore dirty elements in hover layer. | 
|                 layer.__dirty = true; | 
|                 if (layer.incremental && layer.__drawIndex < 0) { | 
|                     // Start draw from the first dirty element. | 
|                     layer.__drawIndex = i; | 
|                 } | 
|             } | 
|         } | 
|   | 
|         updatePrevLayer(i); | 
|   | 
|         this.eachBuiltinLayer(function (layer, z) { | 
|             // Used in last frame but not in this frame. Needs clear | 
|             if (!layer.__used && layer.getElementCount() > 0) { | 
|                 layer.__dirty = true; | 
|                 layer.__startIndex = layer.__endIndex = layer.__drawIndex = 0; | 
|             } | 
|             // For incremental layer. In case start index changed and no elements are dirty. | 
|             if (layer.__dirty && layer.__drawIndex < 0) { | 
|                 layer.__drawIndex = layer.__startIndex; | 
|             } | 
|         }); | 
|     } | 
|   | 
|     /** | 
|      * 清除hover层外所有内容 | 
|      */ | 
|     clear() { | 
|         this.eachBuiltinLayer(this._clearLayer); | 
|         return this; | 
|     } | 
|   | 
|     _clearLayer(layer: Layer) { | 
|         layer.clear(); | 
|     } | 
|   | 
|     setBackgroundColor(backgroundColor: string | GradientObject | ImagePatternObject) { | 
|         this._backgroundColor = backgroundColor; | 
|   | 
|         util.each(this._layers, layer => { | 
|             layer.setUnpainted(); | 
|         }); | 
|     } | 
|   | 
|     /** | 
|      * 修改指定zlevel的绘制参数 | 
|      */ | 
|     configLayer(zlevel: number, config: LayerConfig) { | 
|         if (config) { | 
|             const layerConfig = this._layerConfig; | 
|             if (!layerConfig[zlevel]) { | 
|                 layerConfig[zlevel] = config; | 
|             } | 
|             else { | 
|                 util.merge(layerConfig[zlevel], config, true); | 
|             } | 
|   | 
|             for (let i = 0; i < this._zlevelList.length; i++) { | 
|                 const _zlevel = this._zlevelList[i]; | 
|                 // TODO Remove EL_AFTER_INCREMENTAL_INC magic number | 
|                 if (_zlevel === zlevel || _zlevel === zlevel + EL_AFTER_INCREMENTAL_INC) { | 
|                     const layer = this._layers[_zlevel]; | 
|                     util.merge(layer, layerConfig[zlevel], true); | 
|                 } | 
|             } | 
|         } | 
|     } | 
|   | 
|     /** | 
|      * 删除指定层 | 
|      * @param zlevel 层所在的zlevel | 
|      */ | 
|     delLayer(zlevel: number) { | 
|         const layers = this._layers; | 
|         const zlevelList = this._zlevelList; | 
|         const layer = layers[zlevel]; | 
|         if (!layer) { | 
|             return; | 
|         } | 
|         layer.dom.parentNode.removeChild(layer.dom); | 
|         delete layers[zlevel]; | 
|   | 
|         zlevelList.splice(util.indexOf(zlevelList, zlevel), 1); | 
|     } | 
|   | 
|     /** | 
|      * 区域大小变化后重绘 | 
|      */ | 
|     resize( | 
|         width?: number | string, | 
|         height?: number | string | 
|     ) { | 
|         if (!this._domRoot.style) { // Maybe in node or worker | 
|             if (width == null || height == null) { | 
|                 return; | 
|             } | 
|             // TODO width / height may be string | 
|             this._width = width as number; | 
|             this._height = height as number; | 
|   | 
|             this.getLayer(CANVAS_ZLEVEL).resize(width as number, height as number); | 
|         } | 
|         else { | 
|             const domRoot = this._domRoot; | 
|             // FIXME Why ? | 
|             domRoot.style.display = 'none'; | 
|   | 
|             // Save input w/h | 
|             const opts = this._opts; | 
|             const root = this.root; | 
|             width != null && (opts.width = width); | 
|             height != null && (opts.height = height); | 
|   | 
|             width = getSize(root, 0, opts); | 
|             height = getSize(root, 1, opts); | 
|   | 
|             domRoot.style.display = ''; | 
|   | 
|             // 优化没有实际改变的resize | 
|             if (this._width !== width || height !== this._height) { | 
|                 domRoot.style.width = width + 'px'; | 
|                 domRoot.style.height = height + 'px'; | 
|   | 
|                 for (let id in this._layers) { | 
|                     if (this._layers.hasOwnProperty(id)) { | 
|                         this._layers[id].resize(width, height); | 
|                     } | 
|                 } | 
|   | 
|                 this.refresh(true); | 
|             } | 
|   | 
|             this._width = width; | 
|             this._height = height; | 
|   | 
|         } | 
|         return this; | 
|     } | 
|   | 
|     /** | 
|      * 清除单独的一个层 | 
|      * @param {number} zlevel | 
|      */ | 
|     clearLayer(zlevel: number) { | 
|         const layer = this._layers[zlevel]; | 
|         if (layer) { | 
|             layer.clear(); | 
|         } | 
|     } | 
|   | 
|     /** | 
|      * 释放 | 
|      */ | 
|     dispose() { | 
|         this.root.innerHTML = ''; | 
|   | 
|         this.root = | 
|         this.storage = | 
|   | 
|         this._domRoot = | 
|         this._layers = null; | 
|     } | 
|   | 
|     /** | 
|      * Get canvas which has all thing rendered | 
|      */ | 
|     getRenderedCanvas(opts?: { | 
|         backgroundColor?: string | GradientObject | ImagePatternObject | 
|         pixelRatio?: number | 
|     }) { | 
|         opts = opts || {}; | 
|         if (this._singleCanvas && !this._compositeManually) { | 
|             return this._layers[CANVAS_ZLEVEL].dom; | 
|         } | 
|   | 
|         const imageLayer = new Layer('image', this, opts.pixelRatio || this.dpr); | 
|         imageLayer.initContext(); | 
|         imageLayer.clear(false, opts.backgroundColor || this._backgroundColor); | 
|   | 
|         const ctx = imageLayer.ctx; | 
|   | 
|         if (opts.pixelRatio <= this.dpr) { | 
|             this.refresh(); | 
|   | 
|             const width = imageLayer.dom.width; | 
|             const height = imageLayer.dom.height; | 
|             this.eachLayer(function (layer) { | 
|                 if (layer.__builtin__) { | 
|                     ctx.drawImage(layer.dom, 0, 0, width, height); | 
|                 } | 
|                 else if (layer.renderToCanvas) { | 
|                     ctx.save(); | 
|                     layer.renderToCanvas(ctx); | 
|                     ctx.restore(); | 
|                 } | 
|             }); | 
|         } | 
|         else { | 
|             // PENDING, echarts-gl and incremental rendering. | 
|             const scope = { | 
|                 inHover: false, | 
|                 viewWidth: this._width, | 
|                 viewHeight: this._height | 
|             }; | 
|             const displayList = this.storage.getDisplayList(true); | 
|             for (let i = 0, len = displayList.length; i < len; i++) { | 
|                 const el = displayList[i]; | 
|                 brush(ctx, el, scope, i === len - 1); | 
|             } | 
|         } | 
|   | 
|         return imageLayer.dom; | 
|     } | 
|     /** | 
|      * 获取绘图区域宽度 | 
|      */ | 
|     getWidth() { | 
|         return this._width; | 
|     } | 
|   | 
|     /** | 
|      * 获取绘图区域高度 | 
|      */ | 
|     getHeight() { | 
|         return this._height; | 
|     } | 
| }; |