/*! 
 | 
* ZRender, a high performance 2d drawing library. 
 | 
* 
 | 
* Copyright (c) 2013, Baidu Inc. 
 | 
* All rights reserved. 
 | 
* 
 | 
* LICENSE 
 | 
* https://github.com/ecomfe/zrender/blob/master/LICENSE.txt 
 | 
*/ 
 | 
  
 | 
import env from './core/env'; 
 | 
import * as zrUtil from './core/util'; 
 | 
import Handler from './Handler'; 
 | 
import Storage from './Storage'; 
 | 
import {PainterBase} from './PainterBase'; 
 | 
import Animation, {getTime} from './animation/Animation'; 
 | 
import HandlerProxy from './dom/HandlerProxy'; 
 | 
import Element, { ElementEventCallback } from './Element'; 
 | 
import { Dictionary, ElementEventName, RenderedEvent, WithThisType } from './core/types'; 
 | 
import { LayerConfig } from './canvas/Layer'; 
 | 
import { GradientObject } from './graphic/Gradient'; 
 | 
import { PatternObject } from './graphic/Pattern'; 
 | 
import { EventCallback } from './core/Eventful'; 
 | 
import Displayable from './graphic/Displayable'; 
 | 
import { lum } from './tool/color'; 
 | 
import { DARK_MODE_THRESHOLD } from './config'; 
 | 
import Group from './graphic/Group'; 
 | 
  
 | 
  
 | 
type PainterBaseCtor = { 
 | 
    new(dom: HTMLElement, storage: Storage, ...args: any[]): PainterBase 
 | 
} 
 | 
  
 | 
const painterCtors: Dictionary<PainterBaseCtor> = {}; 
 | 
  
 | 
let instances: { [key: number]: ZRender } = {}; 
 | 
  
 | 
function delInstance(id: number) { 
 | 
    delete instances[id]; 
 | 
} 
 | 
  
 | 
function isDarkMode(backgroundColor: string | GradientObject | PatternObject): boolean { 
 | 
    if (!backgroundColor) { 
 | 
        return false; 
 | 
    } 
 | 
    if (typeof backgroundColor === 'string') { 
 | 
        return lum(backgroundColor, 1) < DARK_MODE_THRESHOLD; 
 | 
    } 
 | 
    else if ((backgroundColor as GradientObject).colorStops) { 
 | 
        const colorStops = (backgroundColor as GradientObject).colorStops; 
 | 
        let totalLum = 0; 
 | 
        const len = colorStops.length; 
 | 
        // Simply do the math of average the color. Not consider the offset 
 | 
        for (let i = 0; i < len; i++) { 
 | 
            totalLum += lum(colorStops[i].color, 1); 
 | 
        } 
 | 
        totalLum /= len; 
 | 
  
 | 
        return totalLum < DARK_MODE_THRESHOLD; 
 | 
    } 
 | 
    // Can't determine 
 | 
    return false; 
 | 
} 
 | 
  
 | 
class ZRender { 
 | 
    /** 
 | 
     * Not necessary if using SSR painter like svg-ssr 
 | 
     */ 
 | 
    dom?: HTMLElement 
 | 
  
 | 
    id: number 
 | 
  
 | 
    storage: Storage 
 | 
    painter: PainterBase 
 | 
    handler: Handler 
 | 
    animation: Animation 
 | 
  
 | 
    private _sleepAfterStill = 10; 
 | 
  
 | 
    private _stillFrameAccum = 0; 
 | 
  
 | 
    private _needsRefresh = true 
 | 
    private _needsRefreshHover = true 
 | 
    private _disposed: boolean; 
 | 
    /** 
 | 
     * If theme is dark mode. It will determine the color strategy for labels. 
 | 
     */ 
 | 
    private _darkMode = false; 
 | 
  
 | 
    private _backgroundColor: string | GradientObject | PatternObject; 
 | 
  
 | 
    constructor(id: number, dom?: HTMLElement, opts?: ZRenderInitOpt) { 
 | 
        opts = opts || {}; 
 | 
  
 | 
        /** 
 | 
         * @type {HTMLDomElement} 
 | 
         */ 
 | 
        this.dom = dom; 
 | 
  
 | 
        this.id = id; 
 | 
  
 | 
        const storage = new Storage(); 
 | 
  
 | 
        let rendererType = opts.renderer || 'canvas'; 
 | 
  
 | 
        if (!painterCtors[rendererType]) { 
 | 
            // Use the first registered renderer. 
 | 
            rendererType = zrUtil.keys(painterCtors)[0]; 
 | 
        } 
 | 
        if (process.env.NODE_ENV !== 'production') { 
 | 
            if (!painterCtors[rendererType]) { 
 | 
                throw new Error(`Renderer '${rendererType}' is not imported. Please import it first.`); 
 | 
            } 
 | 
        } 
 | 
  
 | 
        opts.useDirtyRect = opts.useDirtyRect == null 
 | 
            ? false 
 | 
            : opts.useDirtyRect; 
 | 
  
 | 
        const painter = new painterCtors[rendererType](dom, storage, opts, id); 
 | 
        const ssrMode = opts.ssr || painter.ssrOnly; 
 | 
  
 | 
        this.storage = storage; 
 | 
        this.painter = painter; 
 | 
  
 | 
        const handlerProxy = (!env.node && !env.worker && !ssrMode) 
 | 
            ? new HandlerProxy(painter.getViewportRoot(), painter.root) 
 | 
            : null; 
 | 
  
 | 
        const useCoarsePointer = opts.useCoarsePointer; 
 | 
        const usePointerSize = (useCoarsePointer == null || useCoarsePointer === 'auto') 
 | 
            ? env.touchEventsSupported 
 | 
            : !!useCoarsePointer; 
 | 
        const defaultPointerSize = 44; 
 | 
        let pointerSize; 
 | 
        if (usePointerSize) { 
 | 
            pointerSize = zrUtil.retrieve2(opts.pointerSize, defaultPointerSize); 
 | 
        } 
 | 
  
 | 
        this.handler = new Handler(storage, painter, handlerProxy, painter.root, pointerSize); 
 | 
  
 | 
        this.animation = new Animation({ 
 | 
            stage: { 
 | 
                update: ssrMode ? null : () => this._flush(true) 
 | 
            } 
 | 
        }); 
 | 
  
 | 
        if (!ssrMode) { 
 | 
            this.animation.start(); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * 添加元素 
 | 
     */ 
 | 
    add(el: Element) { 
 | 
        if (this._disposed || !el) { 
 | 
            return; 
 | 
        } 
 | 
        this.storage.addRoot(el); 
 | 
        el.addSelfToZr(this); 
 | 
        this.refresh(); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * 删除元素 
 | 
     */ 
 | 
    remove(el: Element) { 
 | 
        if (this._disposed || !el) { 
 | 
            return; 
 | 
        } 
 | 
        this.storage.delRoot(el); 
 | 
        el.removeSelfFromZr(this); 
 | 
        this.refresh(); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Change configuration of layer 
 | 
    */ 
 | 
    configLayer(zLevel: number, config: LayerConfig) { 
 | 
        if (this._disposed) { 
 | 
            return; 
 | 
        } 
 | 
        if (this.painter.configLayer) { 
 | 
            this.painter.configLayer(zLevel, config); 
 | 
        } 
 | 
        this.refresh(); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Set background color 
 | 
     */ 
 | 
    setBackgroundColor(backgroundColor: string | GradientObject | PatternObject) { 
 | 
        if (this._disposed) { 
 | 
            return; 
 | 
        } 
 | 
        if (this.painter.setBackgroundColor) { 
 | 
            this.painter.setBackgroundColor(backgroundColor); 
 | 
        } 
 | 
        this.refresh(); 
 | 
        this._backgroundColor = backgroundColor; 
 | 
        this._darkMode = isDarkMode(backgroundColor); 
 | 
    } 
 | 
  
 | 
    getBackgroundColor() { 
 | 
        return this._backgroundColor; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Force to set dark mode 
 | 
     */ 
 | 
    setDarkMode(darkMode: boolean) { 
 | 
        this._darkMode = darkMode; 
 | 
    } 
 | 
  
 | 
    isDarkMode() { 
 | 
        return this._darkMode; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Repaint the canvas immediately 
 | 
     */ 
 | 
    refreshImmediately(fromInside?: boolean) { 
 | 
        if (this._disposed) { 
 | 
            return; 
 | 
        } 
 | 
        // const start = new Date(); 
 | 
        if (!fromInside) { 
 | 
            // Update animation if refreshImmediately is invoked from outside. 
 | 
            // Not trigger stage update to call flush again. Which may refresh twice 
 | 
            this.animation.update(true); 
 | 
        } 
 | 
  
 | 
        // Clear needsRefresh ahead to avoid something wrong happens in refresh 
 | 
        // Or it will cause zrender refreshes again and again. 
 | 
        this._needsRefresh = false; 
 | 
        this.painter.refresh(); 
 | 
        // Avoid trigger zr.refresh in Element#beforeUpdate hook 
 | 
        this._needsRefresh = false; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Mark and repaint the canvas in the next frame of browser 
 | 
     */ 
 | 
    refresh() { 
 | 
        if (this._disposed) { 
 | 
            return; 
 | 
        } 
 | 
        this._needsRefresh = true; 
 | 
        // Active the animation again. 
 | 
        this.animation.start(); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Perform all refresh 
 | 
     */ 
 | 
    flush() { 
 | 
        if (this._disposed) { 
 | 
            return; 
 | 
        } 
 | 
        this._flush(false); 
 | 
    } 
 | 
  
 | 
    private _flush(fromInside?: boolean) { 
 | 
        let triggerRendered; 
 | 
  
 | 
        const start = getTime(); 
 | 
        if (this._needsRefresh) { 
 | 
            triggerRendered = true; 
 | 
            this.refreshImmediately(fromInside); 
 | 
        } 
 | 
  
 | 
        if (this._needsRefreshHover) { 
 | 
            triggerRendered = true; 
 | 
            this.refreshHoverImmediately(); 
 | 
        } 
 | 
        const end = getTime(); 
 | 
  
 | 
        if (triggerRendered) { 
 | 
            this._stillFrameAccum = 0; 
 | 
            this.trigger('rendered', { 
 | 
                elapsedTime: end - start 
 | 
            } as RenderedEvent); 
 | 
        } 
 | 
        else if (this._sleepAfterStill > 0) { 
 | 
            this._stillFrameAccum++; 
 | 
            // Stop the animation after still for 10 frames. 
 | 
            if (this._stillFrameAccum > this._sleepAfterStill) { 
 | 
                this.animation.stop(); 
 | 
            } 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Set sleep after still for frames. 
 | 
     * Disable auto sleep when it's 0. 
 | 
     */ 
 | 
    setSleepAfterStill(stillFramesCount: number) { 
 | 
        this._sleepAfterStill = stillFramesCount; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Wake up animation loop. But not render. 
 | 
     */ 
 | 
    wakeUp() { 
 | 
        if (this._disposed) { 
 | 
            return; 
 | 
        } 
 | 
        this.animation.start(); 
 | 
        // Reset the frame count. 
 | 
        this._stillFrameAccum = 0; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Refresh hover in next frame 
 | 
     */ 
 | 
    refreshHover() { 
 | 
        this._needsRefreshHover = true; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Refresh hover immediately 
 | 
     */ 
 | 
    refreshHoverImmediately() { 
 | 
        if (this._disposed) { 
 | 
            return; 
 | 
        } 
 | 
        this._needsRefreshHover = false; 
 | 
        if (this.painter.refreshHover && this.painter.getType() === 'canvas') { 
 | 
            this.painter.refreshHover(); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Resize the canvas. 
 | 
     * Should be invoked when container size is changed 
 | 
     */ 
 | 
    resize(opts?: { 
 | 
        width?: number| string 
 | 
        height?: number | string 
 | 
    }) { 
 | 
        if (this._disposed) { 
 | 
            return; 
 | 
        } 
 | 
        opts = opts || {}; 
 | 
        this.painter.resize(opts.width, opts.height); 
 | 
        this.handler.resize(); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Stop and clear all animation immediately 
 | 
     */ 
 | 
    clearAnimation() { 
 | 
        if (this._disposed) { 
 | 
            return; 
 | 
        } 
 | 
        this.animation.clear(); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Get container width 
 | 
     */ 
 | 
    getWidth(): number | undefined { 
 | 
        if (this._disposed) { 
 | 
            return; 
 | 
        } 
 | 
        return this.painter.getWidth(); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Get container height 
 | 
     */ 
 | 
    getHeight(): number | undefined { 
 | 
        if (this._disposed) { 
 | 
            return; 
 | 
        } 
 | 
        return this.painter.getHeight(); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Set default cursor 
 | 
     * @param cursorStyle='default' 例如 crosshair 
 | 
     */ 
 | 
    setCursorStyle(cursorStyle: string) { 
 | 
        if (this._disposed) { 
 | 
            return; 
 | 
        } 
 | 
        this.handler.setCursorStyle(cursorStyle); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Find hovered element 
 | 
     * @param x 
 | 
     * @param y 
 | 
     * @return {target, topTarget} 
 | 
     */ 
 | 
    findHover(x: number, y: number): { 
 | 
        target: Displayable 
 | 
        topTarget: Displayable 
 | 
    } | undefined { 
 | 
        if (this._disposed) { 
 | 
            return; 
 | 
        } 
 | 
        return this.handler.findHover(x, y); 
 | 
    } 
 | 
  
 | 
    on<Ctx>(eventName: ElementEventName, eventHandler: ElementEventCallback<Ctx, ZRenderType>, context?: Ctx): this 
 | 
    // eslint-disable-next-line max-len 
 | 
    on<Ctx>(eventName: string, eventHandler: WithThisType<EventCallback<any[]>, unknown extends Ctx ? ZRenderType : Ctx>, context?: Ctx): this 
 | 
    // eslint-disable-next-line max-len 
 | 
    on<Ctx>(eventName: string, eventHandler: (...args: any) => any, context?: Ctx): this { 
 | 
        if (!this._disposed) { 
 | 
            this.handler.on(eventName, eventHandler, context); 
 | 
        } 
 | 
        return this; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Unbind event 
 | 
     * @param eventName Event name 
 | 
     * @param eventHandler Handler function 
 | 
     */ 
 | 
    // eslint-disable-next-line max-len 
 | 
    off(eventName?: string, eventHandler?: EventCallback) { 
 | 
        if (this._disposed) { 
 | 
            return; 
 | 
        } 
 | 
        this.handler.off(eventName, eventHandler); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Trigger event manually 
 | 
     * 
 | 
     * @param eventName Event name 
 | 
     * @param event Event object 
 | 
     */ 
 | 
    trigger(eventName: string, event?: unknown) { 
 | 
        if (this._disposed) { 
 | 
            return; 
 | 
        } 
 | 
        this.handler.trigger(eventName, event); 
 | 
    } 
 | 
  
 | 
  
 | 
    /** 
 | 
     * Clear all objects and the canvas. 
 | 
     */ 
 | 
    clear() { 
 | 
        if (this._disposed) { 
 | 
            return; 
 | 
        } 
 | 
        const roots = this.storage.getRoots(); 
 | 
        for (let i = 0; i < roots.length; i++) { 
 | 
            if (roots[i] instanceof Group) { 
 | 
                roots[i].removeSelfFromZr(this); 
 | 
            } 
 | 
        } 
 | 
        this.storage.delAllRoots(); 
 | 
        this.painter.clear(); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Dispose self. 
 | 
     */ 
 | 
    dispose() { 
 | 
        if (this._disposed) { 
 | 
            return; 
 | 
        } 
 | 
  
 | 
        this.animation.stop(); 
 | 
  
 | 
        this.clear(); 
 | 
        this.storage.dispose(); 
 | 
        this.painter.dispose(); 
 | 
        this.handler.dispose(); 
 | 
  
 | 
        this.animation = 
 | 
        this.storage = 
 | 
        this.painter = 
 | 
        this.handler = null; 
 | 
  
 | 
        this._disposed = true; 
 | 
  
 | 
        delInstance(this.id); 
 | 
    } 
 | 
} 
 | 
  
 | 
  
 | 
export interface ZRenderInitOpt { 
 | 
    renderer?: string   // 'canvas' or 'svg 
 | 
    devicePixelRatio?: number 
 | 
    width?: number | string // 10, 10px, 'auto' 
 | 
    height?: number | string 
 | 
    useDirtyRect?: boolean 
 | 
    useCoarsePointer?: 'auto' | boolean 
 | 
    pointerSize?: number 
 | 
    ssr?: boolean   // If enable ssr mode. 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Initializing a zrender instance 
 | 
 * 
 | 
 * @param dom Not necessary if using SSR painter like svg-ssr 
 | 
 */ 
 | 
export function init(dom?: HTMLElement | null, opts?: ZRenderInitOpt) { 
 | 
    const zr = new ZRender(zrUtil.guid(), dom, opts); 
 | 
    instances[zr.id] = zr; 
 | 
    return zr; 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Dispose zrender instance 
 | 
 */ 
 | 
export function dispose(zr: ZRender) { 
 | 
    zr.dispose(); 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Dispose all zrender instances 
 | 
 */ 
 | 
export function disposeAll() { 
 | 
    for (let key in instances) { 
 | 
        if (instances.hasOwnProperty(key)) { 
 | 
            instances[key].dispose(); 
 | 
        } 
 | 
    } 
 | 
    instances = {}; 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Get zrender instance by id 
 | 
 */ 
 | 
export function getInstance(id: number): ZRender { 
 | 
    return instances[id]; 
 | 
} 
 | 
  
 | 
export function registerPainter(name: string, Ctor: PainterBaseCtor) { 
 | 
    painterCtors[name] = Ctor; 
 | 
} 
 | 
  
 | 
export type ElementSSRData = zrUtil.HashMap<unknown>; 
 | 
export type ElementSSRDataGetter<T> = (el: Element) => zrUtil.HashMap<T>; 
 | 
  
 | 
let ssrDataGetter: ElementSSRDataGetter<unknown>; 
 | 
  
 | 
export function getElementSSRData(el: Element): ElementSSRData { 
 | 
    if (typeof ssrDataGetter === 'function') { 
 | 
        return ssrDataGetter(el); 
 | 
    } 
 | 
} 
 | 
  
 | 
export function registerSSRDataGetter<T>(getter: ElementSSRDataGetter<T>) { 
 | 
    ssrDataGetter = getter; 
 | 
} 
 | 
  
 | 
/** 
 | 
 * @type {string} 
 | 
 */ 
 | 
export const version = '5.6.1'; 
 | 
  
 | 
  
 | 
export interface ZRenderType extends ZRender {}; 
 |