1
This commit is contained in:
511
ems-frontend/node_modules/zrender/src/canvas/Layer.ts
generated
vendored
Normal file
511
ems-frontend/node_modules/zrender/src/canvas/Layer.ts
generated
vendored
Normal file
@@ -0,0 +1,511 @@
|
||||
import * as util from '../core/util';
|
||||
import {devicePixelRatio} from '../config';
|
||||
import { ImagePatternObject } from '../graphic/Pattern';
|
||||
import CanvasPainter from './Painter';
|
||||
import { GradientObject, InnerGradientObject } from '../graphic/Gradient';
|
||||
import { ZRCanvasRenderingContext } from '../core/types';
|
||||
import Eventful from '../core/Eventful';
|
||||
import { ElementEventCallback } from '../Element';
|
||||
import { getCanvasGradient } from './helper';
|
||||
import { createCanvasPattern } from './graphic';
|
||||
import Displayable from '../graphic/Displayable';
|
||||
import BoundingRect from '../core/BoundingRect';
|
||||
import { REDRAW_BIT } from '../graphic/constants';
|
||||
import { platformApi } from '../core/platform';
|
||||
|
||||
function createDom(id: string, painter: CanvasPainter, dpr: number) {
|
||||
const newDom = platformApi.createCanvas();
|
||||
const width = painter.getWidth();
|
||||
const height = painter.getHeight();
|
||||
|
||||
const newDomStyle = newDom.style;
|
||||
if (newDomStyle) { // In node or some other non-browser environment
|
||||
newDomStyle.position = 'absolute';
|
||||
newDomStyle.left = '0';
|
||||
newDomStyle.top = '0';
|
||||
newDomStyle.width = width + 'px';
|
||||
newDomStyle.height = height + 'px';
|
||||
|
||||
newDom.setAttribute('data-zr-dom-id', id);
|
||||
}
|
||||
|
||||
newDom.width = width * dpr;
|
||||
newDom.height = height * dpr;
|
||||
|
||||
return newDom;
|
||||
}
|
||||
|
||||
export interface LayerConfig {
|
||||
// 每次清空画布的颜色
|
||||
clearColor?: string | GradientObject | ImagePatternObject
|
||||
// 是否开启动态模糊
|
||||
motionBlur?: boolean
|
||||
// 在开启动态模糊的时候使用,与上一帧混合的alpha值,值越大尾迹越明显
|
||||
lastFrameAlpha?: number
|
||||
};
|
||||
|
||||
export default class Layer extends Eventful {
|
||||
|
||||
id: string
|
||||
|
||||
dom: HTMLCanvasElement
|
||||
domBack: HTMLCanvasElement
|
||||
|
||||
ctx: CanvasRenderingContext2D
|
||||
ctxBack: CanvasRenderingContext2D
|
||||
|
||||
painter: CanvasPainter
|
||||
|
||||
// Configs
|
||||
/**
|
||||
* 每次清空画布的颜色
|
||||
*/
|
||||
clearColor: string | GradientObject | ImagePatternObject
|
||||
/**
|
||||
* 是否开启动态模糊
|
||||
*/
|
||||
motionBlur = false
|
||||
/**
|
||||
* 在开启动态模糊的时候使用,与上一帧混合的alpha值,值越大尾迹越明显
|
||||
*/
|
||||
lastFrameAlpha = 0.7
|
||||
/**
|
||||
* Layer dpr
|
||||
*/
|
||||
dpr = 1
|
||||
|
||||
/**
|
||||
* Virtual layer will not be inserted into dom.
|
||||
*/
|
||||
virtual = false
|
||||
|
||||
config = {}
|
||||
|
||||
incremental = false
|
||||
|
||||
zlevel = 0
|
||||
|
||||
maxRepaintRectCount = 5
|
||||
|
||||
private _paintRects: BoundingRect[]
|
||||
|
||||
__dirty = true
|
||||
__firstTimePaint = true
|
||||
|
||||
__used = false
|
||||
|
||||
__drawIndex = 0
|
||||
__startIndex = 0
|
||||
__endIndex = 0
|
||||
|
||||
// indices in the previous frame
|
||||
__prevStartIndex: number = null
|
||||
__prevEndIndex: number = null
|
||||
|
||||
__builtin__: boolean
|
||||
|
||||
constructor(id: string | HTMLCanvasElement, painter: CanvasPainter, dpr?: number) {
|
||||
super();
|
||||
|
||||
let dom;
|
||||
dpr = dpr || devicePixelRatio;
|
||||
if (typeof id === 'string') {
|
||||
dom = createDom(id, painter, dpr);
|
||||
}
|
||||
// Not using isDom because in node it will return false
|
||||
else if (util.isObject(id)) {
|
||||
dom = id;
|
||||
id = dom.id;
|
||||
}
|
||||
this.id = id as string;
|
||||
this.dom = dom;
|
||||
|
||||
const domStyle = dom.style;
|
||||
if (domStyle) { // Not in node
|
||||
util.disableUserSelect(dom);
|
||||
dom.onselectstart = () => false;
|
||||
domStyle.padding = '0';
|
||||
domStyle.margin = '0';
|
||||
domStyle.borderWidth = '0';
|
||||
}
|
||||
|
||||
this.painter = painter;
|
||||
|
||||
this.dpr = dpr;
|
||||
}
|
||||
|
||||
getElementCount() {
|
||||
return this.__endIndex - this.__startIndex;
|
||||
}
|
||||
|
||||
afterBrush() {
|
||||
this.__prevStartIndex = this.__startIndex;
|
||||
this.__prevEndIndex = this.__endIndex;
|
||||
}
|
||||
|
||||
initContext() {
|
||||
this.ctx = this.dom.getContext('2d');
|
||||
(this.ctx as ZRCanvasRenderingContext).dpr = this.dpr;
|
||||
}
|
||||
|
||||
setUnpainted() {
|
||||
this.__firstTimePaint = true;
|
||||
}
|
||||
|
||||
createBackBuffer() {
|
||||
const dpr = this.dpr;
|
||||
|
||||
this.domBack = createDom('back-' + this.id, this.painter, dpr);
|
||||
this.ctxBack = this.domBack.getContext('2d');
|
||||
|
||||
if (dpr !== 1) {
|
||||
this.ctxBack.scale(dpr, dpr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create repaint list when using dirty rect rendering.
|
||||
*
|
||||
* @param displayList current rendering list
|
||||
* @param prevList last frame rendering list
|
||||
* @return repaint rects. null for the first frame, [] for no element dirty
|
||||
*/
|
||||
createRepaintRects(
|
||||
displayList: Displayable[],
|
||||
prevList: Displayable[],
|
||||
viewWidth: number,
|
||||
viewHeight: number
|
||||
) {
|
||||
if (this.__firstTimePaint) {
|
||||
this.__firstTimePaint = false;
|
||||
return null;
|
||||
}
|
||||
|
||||
const mergedRepaintRects: BoundingRect[] = [];
|
||||
const maxRepaintRectCount = this.maxRepaintRectCount;
|
||||
let full = false;
|
||||
const pendingRect = new BoundingRect(0, 0, 0, 0);
|
||||
|
||||
function addRectToMergePool(rect: BoundingRect) {
|
||||
if (!rect.isFinite() || rect.isZero()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mergedRepaintRects.length === 0) {
|
||||
// First rect, create new merged rect
|
||||
const boundingRect = new BoundingRect(0, 0, 0, 0);
|
||||
boundingRect.copy(rect);
|
||||
mergedRepaintRects.push(boundingRect);
|
||||
}
|
||||
else {
|
||||
let isMerged = false;
|
||||
let minDeltaArea = Infinity;
|
||||
let bestRectToMergeIdx = 0;
|
||||
for (let i = 0; i < mergedRepaintRects.length; ++i) {
|
||||
const mergedRect = mergedRepaintRects[i];
|
||||
|
||||
// Merge if has intersection
|
||||
if (mergedRect.intersect(rect)) {
|
||||
const pendingRect = new BoundingRect(0, 0, 0, 0);
|
||||
pendingRect.copy(mergedRect);
|
||||
pendingRect.union(rect);
|
||||
mergedRepaintRects[i] = pendingRect;
|
||||
isMerged = true;
|
||||
break;
|
||||
}
|
||||
else if (full) {
|
||||
// Merged to exists rectangles if full
|
||||
pendingRect.copy(rect);
|
||||
pendingRect.union(mergedRect);
|
||||
const aArea = rect.width * rect.height;
|
||||
const bArea = mergedRect.width * mergedRect.height;
|
||||
const pendingArea = pendingRect.width * pendingRect.height;
|
||||
const deltaArea = pendingArea - aArea - bArea;
|
||||
if (deltaArea < minDeltaArea) {
|
||||
minDeltaArea = deltaArea;
|
||||
bestRectToMergeIdx = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (full) {
|
||||
mergedRepaintRects[bestRectToMergeIdx].union(rect);
|
||||
isMerged = true;
|
||||
}
|
||||
|
||||
if (!isMerged) {
|
||||
// Create new merged rect if cannot merge with current
|
||||
const boundingRect = new BoundingRect(0, 0, 0, 0);
|
||||
boundingRect.copy(rect);
|
||||
mergedRepaintRects.push(boundingRect);
|
||||
}
|
||||
if (!full) {
|
||||
full = mergedRepaintRects.length >= maxRepaintRectCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop the paint list of this frame and get the dirty rects of elements
|
||||
* in this frame.
|
||||
*/
|
||||
for (let i = this.__startIndex; i < this.__endIndex; ++i) {
|
||||
const el = displayList[i];
|
||||
if (el) {
|
||||
/**
|
||||
* `shouldPaint` is true only when the element is not ignored or
|
||||
* invisible and all its ancestors are not ignored.
|
||||
* `shouldPaint` being true means it will be brushed this frame.
|
||||
*
|
||||
* `__isRendered` being true means the element is currently on
|
||||
* the canvas.
|
||||
*
|
||||
* `__dirty` being true means the element should be brushed this
|
||||
* frame.
|
||||
*
|
||||
* We only need to repaint the element's previous painting rect
|
||||
* if it's currently on the canvas and needs repaint this frame
|
||||
* or not painted this frame.
|
||||
*/
|
||||
const shouldPaint = el.shouldBePainted(viewWidth, viewHeight, true, true);
|
||||
const prevRect = el.__isRendered && ((el.__dirty & REDRAW_BIT) || !shouldPaint)
|
||||
? el.getPrevPaintRect()
|
||||
: null;
|
||||
if (prevRect) {
|
||||
addRectToMergePool(prevRect);
|
||||
}
|
||||
|
||||
/**
|
||||
* On the other hand, we only need to paint the current rect
|
||||
* if the element should be brushed this frame and either being
|
||||
* dirty or not rendered before.
|
||||
*/
|
||||
const curRect = shouldPaint && ((el.__dirty & REDRAW_BIT) || !el.__isRendered)
|
||||
? el.getPaintRect()
|
||||
: null;
|
||||
if (curRect) {
|
||||
addRectToMergePool(curRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The above loop calculates the dirty rects of elements that are in the
|
||||
* paint list this frame, which does not include those elements removed
|
||||
* in this frame. So we loop the `prevList` to get the removed elements.
|
||||
*/
|
||||
for (let i = this.__prevStartIndex; i < this.__prevEndIndex; ++i) {
|
||||
const el = prevList[i];
|
||||
/**
|
||||
* Consider the elements whose ancestors are invisible, they should
|
||||
* not be painted and their previous painting rects should be
|
||||
* cleared if they are rendered on the canvas (`__isRendered` being
|
||||
* true). `!shouldPaint` means the element is not brushed in this
|
||||
* frame.
|
||||
*
|
||||
* `!el.__zr` means it's removed from the storage.
|
||||
*
|
||||
* In conclusion, an element needs to repaint the previous painting
|
||||
* rect if and only if it's not painted this frame and was
|
||||
* previously painted on the canvas.
|
||||
*/
|
||||
const shouldPaint = el && el.shouldBePainted(viewWidth, viewHeight, true, true);
|
||||
if (el && (!shouldPaint || !el.__zr) && el.__isRendered) {
|
||||
// el was removed
|
||||
const prevRect = el.getPrevPaintRect();
|
||||
if (prevRect) {
|
||||
addRectToMergePool(prevRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Merge intersected rects in the result
|
||||
let hasIntersections;
|
||||
do {
|
||||
hasIntersections = false;
|
||||
for (let i = 0; i < mergedRepaintRects.length;) {
|
||||
if (mergedRepaintRects[i].isZero()) {
|
||||
mergedRepaintRects.splice(i, 1);
|
||||
continue;
|
||||
}
|
||||
for (let j = i + 1; j < mergedRepaintRects.length;) {
|
||||
if (mergedRepaintRects[i].intersect(mergedRepaintRects[j])) {
|
||||
hasIntersections = true;
|
||||
mergedRepaintRects[i].union(mergedRepaintRects[j]);
|
||||
mergedRepaintRects.splice(j, 1);
|
||||
}
|
||||
else {
|
||||
j++;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
} while (hasIntersections);
|
||||
|
||||
this._paintRects = mergedRepaintRects;
|
||||
|
||||
return mergedRepaintRects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get paint rects for debug usage.
|
||||
*/
|
||||
debugGetPaintRects() {
|
||||
return (this._paintRects || []).slice();
|
||||
}
|
||||
|
||||
resize(width: number, height: number) {
|
||||
const dpr = this.dpr;
|
||||
|
||||
const dom = this.dom;
|
||||
const domStyle = dom.style;
|
||||
const domBack = this.domBack;
|
||||
|
||||
if (domStyle) {
|
||||
domStyle.width = width + 'px';
|
||||
domStyle.height = height + 'px';
|
||||
}
|
||||
|
||||
dom.width = width * dpr;
|
||||
dom.height = height * dpr;
|
||||
|
||||
if (domBack) {
|
||||
domBack.width = width * dpr;
|
||||
domBack.height = height * dpr;
|
||||
|
||||
if (dpr !== 1) {
|
||||
this.ctxBack.scale(dpr, dpr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空该层画布
|
||||
*/
|
||||
clear(
|
||||
clearAll?: boolean,
|
||||
clearColor?: string | GradientObject | ImagePatternObject,
|
||||
repaintRects?: BoundingRect[]
|
||||
) {
|
||||
const dom = this.dom;
|
||||
const ctx = this.ctx;
|
||||
const width = dom.width;
|
||||
const height = dom.height;
|
||||
|
||||
clearColor = clearColor || this.clearColor;
|
||||
const haveMotionBLur = this.motionBlur && !clearAll;
|
||||
const lastFrameAlpha = this.lastFrameAlpha;
|
||||
|
||||
const dpr = this.dpr;
|
||||
const self = this;
|
||||
|
||||
if (haveMotionBLur) {
|
||||
if (!this.domBack) {
|
||||
this.createBackBuffer();
|
||||
}
|
||||
|
||||
this.ctxBack.globalCompositeOperation = 'copy';
|
||||
this.ctxBack.drawImage(
|
||||
dom, 0, 0,
|
||||
width / dpr,
|
||||
height / dpr
|
||||
);
|
||||
}
|
||||
|
||||
const domBack = this.domBack;
|
||||
|
||||
function doClear(x: number, y: number, width: number, height: number) {
|
||||
ctx.clearRect(x, y, width, height);
|
||||
if (clearColor && clearColor !== 'transparent') {
|
||||
let clearColorGradientOrPattern;
|
||||
// Gradient
|
||||
if (util.isGradientObject(clearColor)) {
|
||||
// shouldn't cache when clearColor is not global and size changed
|
||||
const shouldCache = clearColor.global || (
|
||||
(clearColor as InnerGradientObject).__width === width
|
||||
&& (clearColor as InnerGradientObject).__height === height
|
||||
);
|
||||
// Cache canvas gradient
|
||||
clearColorGradientOrPattern = shouldCache
|
||||
&& (clearColor as InnerGradientObject).__canvasGradient
|
||||
|| getCanvasGradient(ctx, clearColor, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: width,
|
||||
height: height
|
||||
});
|
||||
|
||||
(clearColor as InnerGradientObject).__canvasGradient = clearColorGradientOrPattern;
|
||||
(clearColor as InnerGradientObject).__width = width;
|
||||
(clearColor as InnerGradientObject).__height = height;
|
||||
}
|
||||
// Pattern
|
||||
else if (util.isImagePatternObject(clearColor)) {
|
||||
// scale pattern by dpr
|
||||
clearColor.scaleX = clearColor.scaleX || dpr;
|
||||
clearColor.scaleY = clearColor.scaleY || dpr;
|
||||
clearColorGradientOrPattern = createCanvasPattern(
|
||||
ctx, clearColor, {
|
||||
dirty() {
|
||||
self.setUnpainted();
|
||||
self.painter.refresh();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
ctx.save();
|
||||
ctx.fillStyle = clearColorGradientOrPattern || (clearColor as string);
|
||||
ctx.fillRect(x, y, width, height);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
if (haveMotionBLur) {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = lastFrameAlpha;
|
||||
ctx.drawImage(domBack, x, y, width, height);
|
||||
ctx.restore();
|
||||
}
|
||||
};
|
||||
|
||||
if (!repaintRects || haveMotionBLur) {
|
||||
// Clear the full canvas
|
||||
doClear(0, 0, width, height);
|
||||
}
|
||||
else if (repaintRects.length) {
|
||||
// Clear the repaint areas
|
||||
util.each(repaintRects, rect => {
|
||||
doClear(
|
||||
rect.x * dpr,
|
||||
rect.y * dpr,
|
||||
rect.width * dpr,
|
||||
rect.height * dpr
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Interface of refresh
|
||||
refresh: (clearColor?: string | GradientObject | ImagePatternObject) => void
|
||||
|
||||
// Interface of renderToCanvas in getRenderedCanvas
|
||||
renderToCanvas: (ctx: CanvasRenderingContext2D) => void
|
||||
|
||||
// Events
|
||||
onclick: ElementEventCallback<unknown, this>
|
||||
ondblclick: ElementEventCallback<unknown, this>
|
||||
onmouseover: ElementEventCallback<unknown, this>
|
||||
onmouseout: ElementEventCallback<unknown, this>
|
||||
onmousemove: ElementEventCallback<unknown, this>
|
||||
onmousewheel: ElementEventCallback<unknown, this>
|
||||
onmousedown: ElementEventCallback<unknown, this>
|
||||
onmouseup: ElementEventCallback<unknown, this>
|
||||
oncontextmenu: ElementEventCallback<unknown, this>
|
||||
|
||||
ondrag: ElementEventCallback<unknown, this>
|
||||
ondragstart: ElementEventCallback<unknown, this>
|
||||
ondragend: ElementEventCallback<unknown, this>
|
||||
ondragenter: ElementEventCallback<unknown, this>
|
||||
ondragleave: ElementEventCallback<unknown, this>
|
||||
ondragover: ElementEventCallback<unknown, this>
|
||||
ondrop: ElementEventCallback<unknown, this>
|
||||
}
|
||||
977
ems-frontend/node_modules/zrender/src/canvas/Painter.ts
generated
vendored
Normal file
977
ems-frontend/node_modules/zrender/src/canvas/Painter.ts
generated
vendored
Normal file
@@ -0,0 +1,977 @@
|
||||
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;
|
||||
}
|
||||
};
|
||||
4
ems-frontend/node_modules/zrender/src/canvas/canvas.ts
generated
vendored
Normal file
4
ems-frontend/node_modules/zrender/src/canvas/canvas.ts
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
import {registerPainter} from '../zrender';
|
||||
import Painter from './Painter';
|
||||
|
||||
registerPainter('canvas', Painter);
|
||||
32
ems-frontend/node_modules/zrender/src/canvas/dashStyle.ts
generated
vendored
Normal file
32
ems-frontend/node_modules/zrender/src/canvas/dashStyle.ts
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
import { isArray, isNumber, map } from '../core/util';
|
||||
import Path from '../graphic/Path';
|
||||
import TSpan from '../graphic/TSpan';
|
||||
|
||||
export function normalizeLineDash(lineType: any, lineWidth?: number): number[] | false {
|
||||
if (!lineType || lineType === 'solid' || !(lineWidth > 0)) {
|
||||
return null;
|
||||
}
|
||||
return lineType === 'dashed'
|
||||
? [4 * lineWidth, 2 * lineWidth]
|
||||
: lineType === 'dotted'
|
||||
? [lineWidth]
|
||||
: isNumber(lineType)
|
||||
? [lineType] : isArray(lineType) ? lineType : null;
|
||||
}
|
||||
export function getLineDash(el: Path | TSpan): [number[] | false, number] {
|
||||
const style = el.style;
|
||||
|
||||
let lineDash = style.lineDash && style.lineWidth > 0 && normalizeLineDash(style.lineDash, style.lineWidth);
|
||||
let lineDashOffset = style.lineDashOffset;
|
||||
|
||||
if (lineDash) {
|
||||
const lineScale = (style.strokeNoScale && el.getLineScale) ? el.getLineScale() : 1;
|
||||
if (lineScale && lineScale !== 1) {
|
||||
lineDash = map(lineDash, function (rawVal) {
|
||||
return rawVal / lineScale;
|
||||
});
|
||||
lineDashOffset /= lineScale;
|
||||
}
|
||||
}
|
||||
return [lineDash, lineDashOffset];
|
||||
}
|
||||
816
ems-frontend/node_modules/zrender/src/canvas/graphic.ts
generated
vendored
Normal file
816
ems-frontend/node_modules/zrender/src/canvas/graphic.ts
generated
vendored
Normal file
@@ -0,0 +1,816 @@
|
||||
import Displayable, { DEFAULT_COMMON_STYLE } from '../graphic/Displayable';
|
||||
import PathProxy from '../core/PathProxy';
|
||||
import { GradientObject } from '../graphic/Gradient';
|
||||
import { ImagePatternObject, InnerImagePatternObject } from '../graphic/Pattern';
|
||||
import { LinearGradientObject } from '../graphic/LinearGradient';
|
||||
import { RadialGradientObject } from '../graphic/RadialGradient';
|
||||
import { ZRCanvasRenderingContext } from '../core/types';
|
||||
import { createOrUpdateImage, isImageReady } from '../graphic/helper/image';
|
||||
import { getCanvasGradient, isClipPathChanged } from './helper';
|
||||
import Path, { PathStyleProps } from '../graphic/Path';
|
||||
import ZRImage, { ImageStyleProps } from '../graphic/Image';
|
||||
import TSpan, {TSpanStyleProps} from '../graphic/TSpan';
|
||||
import { MatrixArray } from '../core/matrix';
|
||||
import { RADIAN_TO_DEGREE } from '../core/util';
|
||||
import { getLineDash } from './dashStyle';
|
||||
import { REDRAW_BIT, SHAPE_CHANGED_BIT } from '../graphic/constants';
|
||||
import type IncrementalDisplayable from '../graphic/IncrementalDisplayable';
|
||||
import { DEFAULT_FONT } from '../core/platform';
|
||||
|
||||
const pathProxyForDraw = new PathProxy(true);
|
||||
|
||||
// Not use el#hasStroke because style may be different.
|
||||
function styleHasStroke(style: PathStyleProps) {
|
||||
const stroke = style.stroke;
|
||||
return !(stroke == null || stroke === 'none' || !(style.lineWidth > 0));
|
||||
}
|
||||
|
||||
// ignore lineWidth and must be string
|
||||
// Expected color but found '[' when color is gradient
|
||||
function isValidStrokeFillStyle(
|
||||
strokeOrFill: PathStyleProps['stroke'] | PathStyleProps['fill']
|
||||
): strokeOrFill is string {
|
||||
return typeof strokeOrFill === 'string' && strokeOrFill !== 'none';
|
||||
}
|
||||
|
||||
function styleHasFill(style: PathStyleProps) {
|
||||
const fill = style.fill;
|
||||
return fill != null && fill !== 'none';
|
||||
}
|
||||
function doFillPath(ctx: CanvasRenderingContext2D, style: PathStyleProps) {
|
||||
if (style.fillOpacity != null && style.fillOpacity !== 1) {
|
||||
const originalGlobalAlpha = ctx.globalAlpha;
|
||||
ctx.globalAlpha = style.fillOpacity * style.opacity;
|
||||
ctx.fill();
|
||||
// Set back globalAlpha
|
||||
ctx.globalAlpha = originalGlobalAlpha;
|
||||
}
|
||||
else {
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
function doStrokePath(ctx: CanvasRenderingContext2D, style: PathStyleProps) {
|
||||
if (style.strokeOpacity != null && style.strokeOpacity !== 1) {
|
||||
const originalGlobalAlpha = ctx.globalAlpha;
|
||||
ctx.globalAlpha = style.strokeOpacity * style.opacity;
|
||||
ctx.stroke();
|
||||
// Set back globalAlpha
|
||||
ctx.globalAlpha = originalGlobalAlpha;
|
||||
}
|
||||
else {
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
export function createCanvasPattern(
|
||||
this: void,
|
||||
ctx: CanvasRenderingContext2D,
|
||||
pattern: ImagePatternObject,
|
||||
el: {dirty: () => void}
|
||||
): CanvasPattern {
|
||||
const image = createOrUpdateImage(pattern.image, (pattern as InnerImagePatternObject).__image, el);
|
||||
if (isImageReady(image)) {
|
||||
const canvasPattern = ctx.createPattern(image, pattern.repeat || 'repeat');
|
||||
if (
|
||||
typeof DOMMatrix === 'function'
|
||||
&& canvasPattern // image may be not ready
|
||||
&& canvasPattern.setTransform // setTransform may not be supported in some old devices.
|
||||
) {
|
||||
const matrix = new DOMMatrix();
|
||||
matrix.translateSelf((pattern.x || 0), (pattern.y || 0));
|
||||
matrix.rotateSelf(0, 0, (pattern.rotation || 0) * RADIAN_TO_DEGREE);
|
||||
matrix.scaleSelf((pattern.scaleX || 1), (pattern.scaleY || 1));
|
||||
canvasPattern.setTransform(matrix);
|
||||
}
|
||||
return canvasPattern;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw Path Elements
|
||||
function brushPath(ctx: CanvasRenderingContext2D, el: Path, style: PathStyleProps, inBatch: boolean) {
|
||||
let hasStroke = styleHasStroke(style);
|
||||
let hasFill = styleHasFill(style);
|
||||
|
||||
const strokePercent = style.strokePercent;
|
||||
const strokePart = strokePercent < 1;
|
||||
|
||||
// TODO Reduce path memory cost.
|
||||
const firstDraw = !el.path;
|
||||
// Create path for each element when:
|
||||
// 1. Element has interactions.
|
||||
// 2. Element draw part of the line.
|
||||
if ((!el.silent || strokePart) && firstDraw) {
|
||||
el.createPathProxy();
|
||||
}
|
||||
|
||||
const path = el.path || pathProxyForDraw;
|
||||
const dirtyFlag = el.__dirty;
|
||||
|
||||
if (!inBatch) {
|
||||
const fill = style.fill;
|
||||
const stroke = style.stroke;
|
||||
|
||||
const hasFillGradient = hasFill && !!(fill as GradientObject).colorStops;
|
||||
const hasStrokeGradient = hasStroke && !!(stroke as GradientObject).colorStops;
|
||||
const hasFillPattern = hasFill && !!(fill as ImagePatternObject).image;
|
||||
const hasStrokePattern = hasStroke && !!(stroke as ImagePatternObject).image;
|
||||
|
||||
let fillGradient;
|
||||
let strokeGradient;
|
||||
let fillPattern;
|
||||
let strokePattern;
|
||||
let rect;
|
||||
if (hasFillGradient || hasStrokeGradient) {
|
||||
rect = el.getBoundingRect();
|
||||
}
|
||||
|
||||
// Update gradient because bounding rect may changed
|
||||
if (hasFillGradient) {
|
||||
fillGradient = dirtyFlag
|
||||
? getCanvasGradient(ctx, fill as (LinearGradientObject | RadialGradientObject), rect)
|
||||
: el.__canvasFillGradient;
|
||||
// No need to clear cache when fill is not gradient.
|
||||
// It will always been updated when fill changed back to gradient.
|
||||
el.__canvasFillGradient = fillGradient;
|
||||
}
|
||||
if (hasStrokeGradient) {
|
||||
strokeGradient = dirtyFlag
|
||||
? getCanvasGradient(ctx, stroke as (LinearGradientObject | RadialGradientObject), rect)
|
||||
: el.__canvasStrokeGradient;
|
||||
el.__canvasStrokeGradient = strokeGradient;
|
||||
}
|
||||
if (hasFillPattern) {
|
||||
// Pattern might be null if image not ready (even created from dataURI)
|
||||
fillPattern = (dirtyFlag || !el.__canvasFillPattern)
|
||||
? createCanvasPattern(ctx, fill as ImagePatternObject, el)
|
||||
: el.__canvasFillPattern;
|
||||
el.__canvasFillPattern = fillPattern;
|
||||
}
|
||||
if (hasStrokePattern) {
|
||||
// Pattern might be null if image not ready (even created from dataURI)
|
||||
strokePattern = (dirtyFlag || !el.__canvasStrokePattern)
|
||||
? createCanvasPattern(ctx, stroke as ImagePatternObject, el)
|
||||
: el.__canvasStrokePattern;
|
||||
el.__canvasStrokePattern = fillPattern;
|
||||
}
|
||||
// Use the gradient or pattern
|
||||
if (hasFillGradient) {
|
||||
// PENDING If may have affect the state
|
||||
ctx.fillStyle = fillGradient;
|
||||
}
|
||||
else if (hasFillPattern) {
|
||||
if (fillPattern) { // createCanvasPattern may return false if image is not ready.
|
||||
ctx.fillStyle = fillPattern;
|
||||
}
|
||||
else {
|
||||
// Don't fill if image is not ready
|
||||
hasFill = false;
|
||||
}
|
||||
}
|
||||
if (hasStrokeGradient) {
|
||||
ctx.strokeStyle = strokeGradient;
|
||||
}
|
||||
else if (hasStrokePattern) {
|
||||
if (strokePattern) {
|
||||
ctx.strokeStyle = strokePattern;
|
||||
}
|
||||
else {
|
||||
// Don't stroke if image is not ready
|
||||
hasStroke = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update path sx, sy
|
||||
const scale = el.getGlobalScale();
|
||||
path.setScale(scale[0], scale[1], el.segmentIgnoreThreshold);
|
||||
|
||||
let lineDash;
|
||||
let lineDashOffset;
|
||||
if (ctx.setLineDash && style.lineDash) {
|
||||
[lineDash, lineDashOffset] = getLineDash(el);
|
||||
}
|
||||
|
||||
let needsRebuild = true;
|
||||
|
||||
if (firstDraw || (dirtyFlag & SHAPE_CHANGED_BIT)) {
|
||||
path.setDPR((ctx as any).dpr);
|
||||
if (strokePart) {
|
||||
// Use rebuildPath for percent stroke, so no context.
|
||||
path.setContext(null);
|
||||
}
|
||||
else {
|
||||
path.setContext(ctx);
|
||||
needsRebuild = false;
|
||||
}
|
||||
path.reset();
|
||||
|
||||
el.buildPath(path, el.shape, inBatch);
|
||||
path.toStatic();
|
||||
|
||||
// Clear path dirty flag
|
||||
el.pathUpdated();
|
||||
}
|
||||
|
||||
// Not support separate fill and stroke. For the compatibility of SVG
|
||||
if (needsRebuild) {
|
||||
path.rebuildPath(ctx, strokePart ? strokePercent : 1);
|
||||
}
|
||||
|
||||
if (lineDash) {
|
||||
ctx.setLineDash(lineDash);
|
||||
ctx.lineDashOffset = lineDashOffset;
|
||||
}
|
||||
|
||||
if (!inBatch) {
|
||||
if (style.strokeFirst) {
|
||||
if (hasStroke) {
|
||||
doStrokePath(ctx, style);
|
||||
}
|
||||
if (hasFill) {
|
||||
doFillPath(ctx, style);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (hasFill) {
|
||||
doFillPath(ctx, style);
|
||||
}
|
||||
if (hasStroke) {
|
||||
doStrokePath(ctx, style);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lineDash) {
|
||||
// PENDING
|
||||
// Remove lineDash
|
||||
ctx.setLineDash([]);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw Image Elements
|
||||
function brushImage(ctx: CanvasRenderingContext2D, el: ZRImage, style: ImageStyleProps) {
|
||||
const image = el.__image = createOrUpdateImage(
|
||||
style.image,
|
||||
el.__image,
|
||||
el,
|
||||
el.onload
|
||||
);
|
||||
|
||||
if (!image || !isImageReady(image)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const x = style.x || 0;
|
||||
const y = style.y || 0;
|
||||
let width = el.getWidth();
|
||||
let height = el.getHeight();
|
||||
const aspect = image.width / image.height;
|
||||
if (width == null && height != null) {
|
||||
// Keep image/height ratio
|
||||
width = height * aspect;
|
||||
}
|
||||
else if (height == null && width != null) {
|
||||
height = width / aspect;
|
||||
}
|
||||
else if (width == null && height == null) {
|
||||
width = image.width;
|
||||
height = image.height;
|
||||
}
|
||||
|
||||
if (style.sWidth && style.sHeight) {
|
||||
const sx = style.sx || 0;
|
||||
const sy = style.sy || 0;
|
||||
ctx.drawImage(
|
||||
image,
|
||||
sx, sy, style.sWidth, style.sHeight,
|
||||
x, y, width, height
|
||||
);
|
||||
}
|
||||
else if (style.sx && style.sy) {
|
||||
const sx = style.sx;
|
||||
const sy = style.sy;
|
||||
const sWidth = width - sx;
|
||||
const sHeight = height - sy;
|
||||
ctx.drawImage(
|
||||
image,
|
||||
sx, sy, sWidth, sHeight,
|
||||
x, y, width, height
|
||||
);
|
||||
}
|
||||
else {
|
||||
ctx.drawImage(image, x, y, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw Text Elements
|
||||
function brushText(ctx: CanvasRenderingContext2D, el: TSpan, style: TSpanStyleProps) {
|
||||
|
||||
let text = style.text;
|
||||
// Convert to string
|
||||
text != null && (text += '');
|
||||
|
||||
if (text) {
|
||||
ctx.font = style.font || DEFAULT_FONT;
|
||||
ctx.textAlign = style.textAlign;
|
||||
ctx.textBaseline = style.textBaseline;
|
||||
|
||||
let lineDash;
|
||||
let lineDashOffset;
|
||||
if (ctx.setLineDash && style.lineDash) {
|
||||
[lineDash, lineDashOffset] = getLineDash(el);
|
||||
}
|
||||
|
||||
if (lineDash) {
|
||||
ctx.setLineDash(lineDash);
|
||||
ctx.lineDashOffset = lineDashOffset;
|
||||
}
|
||||
|
||||
if (style.strokeFirst) {
|
||||
if (styleHasStroke(style)) {
|
||||
ctx.strokeText(text, style.x, style.y);
|
||||
}
|
||||
if (styleHasFill(style)) {
|
||||
ctx.fillText(text, style.x, style.y);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (styleHasFill(style)) {
|
||||
ctx.fillText(text, style.x, style.y);
|
||||
}
|
||||
if (styleHasStroke(style)) {
|
||||
ctx.strokeText(text, style.x, style.y);
|
||||
}
|
||||
}
|
||||
|
||||
if (lineDash) {
|
||||
// Remove lineDash
|
||||
ctx.setLineDash([]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const SHADOW_NUMBER_PROPS = ['shadowBlur', 'shadowOffsetX', 'shadowOffsetY'] as const;
|
||||
const STROKE_PROPS = [
|
||||
['lineCap', 'butt'], ['lineJoin', 'miter'], ['miterLimit', 10]
|
||||
] as const;
|
||||
|
||||
type AllStyleOption = PathStyleProps | TSpanStyleProps | ImageStyleProps;
|
||||
// type ShadowPropNames = typeof SHADOW_PROPS[number][0];
|
||||
// type StrokePropNames = typeof STROKE_PROPS[number][0];
|
||||
// type DrawPropNames = typeof DRAW_PROPS[number][0];
|
||||
|
||||
function bindCommonProps(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
style: AllStyleOption,
|
||||
prevStyle: AllStyleOption,
|
||||
forceSetAll: boolean,
|
||||
scope: BrushScope
|
||||
): boolean {
|
||||
let styleChanged = false;
|
||||
|
||||
if (!forceSetAll) {
|
||||
prevStyle = prevStyle || {};
|
||||
|
||||
// Shared same style.
|
||||
if (style === prevStyle) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (forceSetAll || style.opacity !== prevStyle.opacity) {
|
||||
flushPathDrawn(ctx, scope);
|
||||
styleChanged = true;
|
||||
// Ensure opacity is between 0 ~ 1. Invalid opacity will lead to a failure set and use the leaked opacity from the previous.
|
||||
const opacity = Math.max(Math.min(style.opacity, 1), 0);
|
||||
ctx.globalAlpha = isNaN(opacity) ? DEFAULT_COMMON_STYLE.opacity : opacity;
|
||||
}
|
||||
|
||||
if (forceSetAll || style.blend !== prevStyle.blend) {
|
||||
if (!styleChanged) {
|
||||
flushPathDrawn(ctx, scope);
|
||||
styleChanged = true;
|
||||
}
|
||||
ctx.globalCompositeOperation = style.blend || DEFAULT_COMMON_STYLE.blend;
|
||||
}
|
||||
for (let i = 0; i < SHADOW_NUMBER_PROPS.length; i++) {
|
||||
const propName = SHADOW_NUMBER_PROPS[i];
|
||||
if (forceSetAll || style[propName] !== prevStyle[propName]) {
|
||||
if (!styleChanged) {
|
||||
flushPathDrawn(ctx, scope);
|
||||
styleChanged = true;
|
||||
}
|
||||
// FIXME Invalid property value will cause style leak from previous element.
|
||||
ctx[propName] = (ctx as ZRCanvasRenderingContext).dpr * (style[propName] || 0);
|
||||
}
|
||||
}
|
||||
if (forceSetAll || style.shadowColor !== prevStyle.shadowColor) {
|
||||
if (!styleChanged) {
|
||||
flushPathDrawn(ctx, scope);
|
||||
styleChanged = true;
|
||||
}
|
||||
ctx.shadowColor = style.shadowColor || DEFAULT_COMMON_STYLE.shadowColor;
|
||||
}
|
||||
return styleChanged;
|
||||
}
|
||||
|
||||
function bindPathAndTextCommonStyle(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
el: TSpan | Path,
|
||||
prevEl: TSpan | Path,
|
||||
forceSetAll: boolean,
|
||||
scope: BrushScope
|
||||
) {
|
||||
const style = getStyle(el, scope.inHover);
|
||||
const prevStyle = forceSetAll
|
||||
? null
|
||||
: (prevEl && getStyle(prevEl, scope.inHover) || {});
|
||||
// Shared same style. prevStyle will be null if forceSetAll.
|
||||
if (style === prevStyle) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let styleChanged = bindCommonProps(ctx, style, prevStyle, forceSetAll, scope);
|
||||
|
||||
if (forceSetAll || style.fill !== prevStyle.fill) {
|
||||
if (!styleChanged) {
|
||||
// Flush before set
|
||||
flushPathDrawn(ctx, scope);
|
||||
styleChanged = true;
|
||||
}
|
||||
isValidStrokeFillStyle(style.fill) && (ctx.fillStyle = style.fill);
|
||||
}
|
||||
if (forceSetAll || style.stroke !== prevStyle.stroke) {
|
||||
if (!styleChanged) {
|
||||
flushPathDrawn(ctx, scope);
|
||||
styleChanged = true;
|
||||
}
|
||||
isValidStrokeFillStyle(style.stroke) && (ctx.strokeStyle = style.stroke);
|
||||
}
|
||||
if (forceSetAll || style.opacity !== prevStyle.opacity) {
|
||||
if (!styleChanged) {
|
||||
flushPathDrawn(ctx, scope);
|
||||
styleChanged = true;
|
||||
}
|
||||
ctx.globalAlpha = style.opacity == null ? 1 : style.opacity;
|
||||
}
|
||||
if (el.hasStroke()) {
|
||||
const lineWidth = style.lineWidth;
|
||||
const newLineWidth = lineWidth / (
|
||||
(style.strokeNoScale && el.getLineScale) ? el.getLineScale() : 1
|
||||
);
|
||||
if (ctx.lineWidth !== newLineWidth) {
|
||||
if (!styleChanged) {
|
||||
flushPathDrawn(ctx, scope);
|
||||
styleChanged = true;
|
||||
}
|
||||
ctx.lineWidth = newLineWidth;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < STROKE_PROPS.length; i++) {
|
||||
const prop = STROKE_PROPS[i];
|
||||
const propName = prop[0];
|
||||
if (forceSetAll || style[propName] !== prevStyle[propName]) {
|
||||
if (!styleChanged) {
|
||||
flushPathDrawn(ctx, scope);
|
||||
styleChanged = true;
|
||||
}
|
||||
// FIXME Invalid property value will cause style leak from previous element.
|
||||
(ctx as any)[propName] = style[propName] || prop[1];
|
||||
}
|
||||
}
|
||||
|
||||
return styleChanged;
|
||||
}
|
||||
|
||||
function bindImageStyle(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
el: ZRImage,
|
||||
prevEl: ZRImage,
|
||||
// forceSetAll must be true if prevEl is null
|
||||
forceSetAll: boolean,
|
||||
scope: BrushScope
|
||||
) {
|
||||
return bindCommonProps(
|
||||
ctx,
|
||||
getStyle(el, scope.inHover),
|
||||
prevEl && getStyle(prevEl, scope.inHover),
|
||||
forceSetAll,
|
||||
scope
|
||||
);
|
||||
}
|
||||
|
||||
function setContextTransform(ctx: CanvasRenderingContext2D, el: Displayable) {
|
||||
const m = el.transform;
|
||||
const dpr = (ctx as ZRCanvasRenderingContext).dpr || 1;
|
||||
if (m) {
|
||||
ctx.setTransform(dpr * m[0], dpr * m[1], dpr * m[2], dpr * m[3], dpr * m[4], dpr * m[5]);
|
||||
}
|
||||
else {
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function updateClipStatus(clipPaths: Path[], ctx: CanvasRenderingContext2D, scope: BrushScope) {
|
||||
let allClipped = false;
|
||||
for (let i = 0; i < clipPaths.length; i++) {
|
||||
const clipPath = clipPaths[i];
|
||||
// Ignore draw following elements if clipPath has zero area.
|
||||
allClipped = allClipped || clipPath.isZeroArea();
|
||||
|
||||
setContextTransform(ctx, clipPath);
|
||||
ctx.beginPath();
|
||||
clipPath.buildPath(ctx, clipPath.shape);
|
||||
ctx.clip();
|
||||
}
|
||||
scope.allClipped = allClipped;
|
||||
}
|
||||
|
||||
function isTransformChanged(m0: MatrixArray, m1: MatrixArray): boolean {
|
||||
if (m0 && m1) {
|
||||
return m0[0] !== m1[0]
|
||||
|| m0[1] !== m1[1]
|
||||
|| m0[2] !== m1[2]
|
||||
|| m0[3] !== m1[3]
|
||||
|| m0[4] !== m1[4]
|
||||
|| m0[5] !== m1[5];
|
||||
}
|
||||
else if (!m0 && !m1) { // All identity matrix.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const DRAW_TYPE_PATH = 1;
|
||||
const DRAW_TYPE_IMAGE = 2;
|
||||
const DRAW_TYPE_TEXT = 3;
|
||||
const DRAW_TYPE_INCREMENTAL = 4;
|
||||
|
||||
export type BrushScope = {
|
||||
inHover: boolean
|
||||
|
||||
// width / height of viewport
|
||||
viewWidth: number
|
||||
viewHeight: number
|
||||
|
||||
// Status for clipping
|
||||
prevElClipPaths?: Path[]
|
||||
prevEl?: Displayable
|
||||
allClipped?: boolean // If the whole element can be clipped
|
||||
|
||||
// Status for batching
|
||||
batchFill?: string
|
||||
batchStroke?: string
|
||||
|
||||
lastDrawType?: number
|
||||
}
|
||||
|
||||
// If path can be batched
|
||||
function canPathBatch(style: PathStyleProps) {
|
||||
|
||||
const hasFill = styleHasFill(style);
|
||||
const hasStroke = styleHasStroke(style);
|
||||
|
||||
return !(
|
||||
// Line dash is dynamically set in brush function.
|
||||
style.lineDash
|
||||
// Can't batch if element is both set fill and stroke. Or both not set
|
||||
|| !(+hasFill ^ +hasStroke)
|
||||
// Can't batch if element is drawn with gradient or pattern.
|
||||
|| (hasFill && typeof style.fill !== 'string')
|
||||
|| (hasStroke && typeof style.stroke !== 'string')
|
||||
// Can't batch if element only stroke part of line.
|
||||
|| style.strokePercent < 1
|
||||
// Has stroke or fill opacity
|
||||
|| style.strokeOpacity < 1
|
||||
|| style.fillOpacity < 1
|
||||
);
|
||||
}
|
||||
|
||||
function flushPathDrawn(ctx: CanvasRenderingContext2D, scope: BrushScope) {
|
||||
// Force flush all after drawn last element
|
||||
scope.batchFill && ctx.fill();
|
||||
scope.batchStroke && ctx.stroke();
|
||||
scope.batchFill = '';
|
||||
scope.batchStroke = '';
|
||||
}
|
||||
|
||||
function getStyle(el: Displayable, inHover?: boolean) {
|
||||
return inHover ? (el.__hoverStyle || el.style) : el.style;
|
||||
}
|
||||
|
||||
export function brushSingle(ctx: CanvasRenderingContext2D, el: Displayable) {
|
||||
brush(ctx, el, { inHover: false, viewWidth: 0, viewHeight: 0 }, true);
|
||||
}
|
||||
|
||||
// Brush different type of elements.
|
||||
export function brush(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
el: Displayable,
|
||||
scope: BrushScope,
|
||||
isLast: boolean
|
||||
) {
|
||||
const m = el.transform;
|
||||
|
||||
if (!el.shouldBePainted(scope.viewWidth, scope.viewHeight, false, false)) {
|
||||
// Needs to mark el rendered.
|
||||
// Or this element will always been rendered in progressive rendering.
|
||||
// But other dirty bit should not be cleared, otherwise it cause the shape
|
||||
// can not be updated in this case.
|
||||
el.__dirty &= ~REDRAW_BIT;
|
||||
el.__isRendered = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// HANDLE CLIPPING
|
||||
const clipPaths = el.__clipPaths;
|
||||
const prevElClipPaths = scope.prevElClipPaths;
|
||||
|
||||
let forceSetTransform = false;
|
||||
let forceSetStyle = false;
|
||||
// Optimize when clipping on group with several elements
|
||||
if (!prevElClipPaths || isClipPathChanged(clipPaths, prevElClipPaths)) {
|
||||
// If has previous clipping state, restore from it
|
||||
if (prevElClipPaths && prevElClipPaths.length) {
|
||||
// Flush restore
|
||||
flushPathDrawn(ctx, scope);
|
||||
|
||||
ctx.restore();
|
||||
// Must set all style and transform because context changed by restore
|
||||
forceSetStyle = forceSetTransform = true;
|
||||
|
||||
scope.prevElClipPaths = null;
|
||||
scope.allClipped = false;
|
||||
// Reset prevEl since context has been restored
|
||||
scope.prevEl = null;
|
||||
}
|
||||
// New clipping state
|
||||
if (clipPaths && clipPaths.length) {
|
||||
// Flush before clip
|
||||
flushPathDrawn(ctx, scope);
|
||||
|
||||
ctx.save();
|
||||
updateClipStatus(clipPaths, ctx, scope);
|
||||
// Must set transform because it's changed when clip.
|
||||
forceSetTransform = true;
|
||||
}
|
||||
scope.prevElClipPaths = clipPaths;
|
||||
}
|
||||
|
||||
// Not rendering elements if it's clipped by a zero area path.
|
||||
// Or it may cause bug on some version of IE11 (like 11.0.9600.178**),
|
||||
// where exception "unexpected call to method or property access"
|
||||
// might be thrown when calling ctx.fill or ctx.stroke after a path
|
||||
// whose area size is zero is drawn and ctx.clip() is called and
|
||||
// shadowBlur is set. See #4572, #3112, #5777.
|
||||
// (e.g.,
|
||||
// ctx.moveTo(10, 10);
|
||||
// ctx.lineTo(20, 10);
|
||||
// ctx.closePath();
|
||||
// ctx.clip();
|
||||
// ctx.shadowBlur = 10;
|
||||
// ...
|
||||
// ctx.fill();
|
||||
// )
|
||||
if (scope.allClipped) {
|
||||
el.__isRendered = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// START BRUSH
|
||||
el.beforeBrush && el.beforeBrush();
|
||||
el.innerBeforeBrush();
|
||||
|
||||
const prevEl = scope.prevEl;
|
||||
// TODO el type changed.
|
||||
if (!prevEl) {
|
||||
forceSetStyle = forceSetTransform = true;
|
||||
}
|
||||
|
||||
let canBatchPath = el instanceof Path // Only path supports batch
|
||||
&& el.autoBatch
|
||||
&& canPathBatch(el.style);
|
||||
|
||||
if (forceSetTransform || isTransformChanged(m, prevEl.transform)) {
|
||||
// Flush
|
||||
flushPathDrawn(ctx, scope);
|
||||
setContextTransform(ctx, el);
|
||||
}
|
||||
else if (!canBatchPath) {
|
||||
// Flush
|
||||
flushPathDrawn(ctx, scope);
|
||||
}
|
||||
|
||||
const style = getStyle(el, scope.inHover);
|
||||
if (el instanceof Path) {
|
||||
// PENDING do we need to rebind all style if displayable type changed?
|
||||
if (scope.lastDrawType !== DRAW_TYPE_PATH) {
|
||||
forceSetStyle = true;
|
||||
scope.lastDrawType = DRAW_TYPE_PATH;
|
||||
}
|
||||
|
||||
bindPathAndTextCommonStyle(ctx, el as Path, prevEl as Path, forceSetStyle, scope);
|
||||
// Begin path at start
|
||||
if (!canBatchPath || (!scope.batchFill && !scope.batchStroke)) {
|
||||
ctx.beginPath();
|
||||
}
|
||||
brushPath(ctx, el as Path, style, canBatchPath);
|
||||
|
||||
if (canBatchPath) {
|
||||
scope.batchFill = style.fill as string || '';
|
||||
scope.batchStroke = style.stroke as string || '';
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (el instanceof TSpan) {
|
||||
if (scope.lastDrawType !== DRAW_TYPE_TEXT) {
|
||||
forceSetStyle = true;
|
||||
scope.lastDrawType = DRAW_TYPE_TEXT;
|
||||
}
|
||||
|
||||
bindPathAndTextCommonStyle(ctx, el as TSpan, prevEl as TSpan, forceSetStyle, scope);
|
||||
brushText(ctx, el as TSpan, style);
|
||||
}
|
||||
else if (el instanceof ZRImage) {
|
||||
if (scope.lastDrawType !== DRAW_TYPE_IMAGE) {
|
||||
forceSetStyle = true;
|
||||
scope.lastDrawType = DRAW_TYPE_IMAGE;
|
||||
}
|
||||
|
||||
bindImageStyle(ctx, el as ZRImage, prevEl as ZRImage, forceSetStyle, scope);
|
||||
brushImage(ctx, el as ZRImage, style);
|
||||
}
|
||||
// Assume it's a IncrementalDisplayable
|
||||
else if ((el as IncrementalDisplayable).getTemporalDisplayables) {
|
||||
if (scope.lastDrawType !== DRAW_TYPE_INCREMENTAL) {
|
||||
forceSetStyle = true;
|
||||
scope.lastDrawType = DRAW_TYPE_INCREMENTAL;
|
||||
}
|
||||
|
||||
brushIncremental(ctx, el as IncrementalDisplayable, scope);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (canBatchPath && isLast) {
|
||||
flushPathDrawn(ctx, scope);
|
||||
}
|
||||
|
||||
el.innerAfterBrush();
|
||||
el.afterBrush && el.afterBrush();
|
||||
|
||||
scope.prevEl = el;
|
||||
|
||||
// Mark as painted.
|
||||
el.__dirty = 0;
|
||||
el.__isRendered = true;
|
||||
}
|
||||
|
||||
function brushIncremental(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
el: IncrementalDisplayable,
|
||||
scope: BrushScope
|
||||
) {
|
||||
let displayables = el.getDisplayables();
|
||||
let temporalDisplayables = el.getTemporalDisplayables();
|
||||
// Provide an inner scope.
|
||||
// Save current context and restore after brushed.
|
||||
ctx.save();
|
||||
let innerScope: BrushScope = {
|
||||
prevElClipPaths: null,
|
||||
prevEl: null,
|
||||
allClipped: false,
|
||||
viewWidth: scope.viewWidth,
|
||||
viewHeight: scope.viewHeight,
|
||||
inHover: scope.inHover
|
||||
};
|
||||
let i;
|
||||
let len;
|
||||
// Render persistant displayables.
|
||||
for (i = el.getCursor(), len = displayables.length; i < len; i++) {
|
||||
const displayable = displayables[i];
|
||||
displayable.beforeBrush && displayable.beforeBrush();
|
||||
displayable.innerBeforeBrush();
|
||||
brush(ctx, displayable, innerScope, i === len - 1);
|
||||
displayable.innerAfterBrush();
|
||||
displayable.afterBrush && displayable.afterBrush();
|
||||
innerScope.prevEl = displayable;
|
||||
}
|
||||
// Render temporary displayables.
|
||||
for (let i = 0, len = temporalDisplayables.length; i < len; i++) {
|
||||
const displayable = temporalDisplayables[i];
|
||||
displayable.beforeBrush && displayable.beforeBrush();
|
||||
displayable.innerBeforeBrush();
|
||||
brush(ctx, displayable, innerScope, i === len - 1);
|
||||
displayable.innerAfterBrush();
|
||||
displayable.afterBrush && displayable.afterBrush();
|
||||
innerScope.prevEl = displayable;
|
||||
}
|
||||
el.clearTemporalDisplayables();
|
||||
el.notClear = true;
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
127
ems-frontend/node_modules/zrender/src/canvas/helper.ts
generated
vendored
Normal file
127
ems-frontend/node_modules/zrender/src/canvas/helper.ts
generated
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
import { LinearGradientObject } from '../graphic/LinearGradient';
|
||||
import { RadialGradientObject } from '../graphic/RadialGradient';
|
||||
import { GradientObject } from '../graphic/Gradient';
|
||||
import { RectLike } from '../core/BoundingRect';
|
||||
import Path from '../graphic/Path';
|
||||
|
||||
function isSafeNum(num: number) {
|
||||
// NaN、Infinity、undefined、'xx'
|
||||
return isFinite(num);
|
||||
}
|
||||
|
||||
export function createLinearGradient(
|
||||
this: void,
|
||||
ctx: CanvasRenderingContext2D,
|
||||
obj: LinearGradientObject,
|
||||
rect: RectLike
|
||||
) {
|
||||
let x = obj.x == null ? 0 : obj.x;
|
||||
let x2 = obj.x2 == null ? 1 : obj.x2;
|
||||
let y = obj.y == null ? 0 : obj.y;
|
||||
let y2 = obj.y2 == null ? 0 : obj.y2;
|
||||
|
||||
if (!obj.global) {
|
||||
x = x * rect.width + rect.x;
|
||||
x2 = x2 * rect.width + rect.x;
|
||||
y = y * rect.height + rect.y;
|
||||
y2 = y2 * rect.height + rect.y;
|
||||
}
|
||||
|
||||
// Fix NaN when rect is Infinity
|
||||
x = isSafeNum(x) ? x : 0;
|
||||
x2 = isSafeNum(x2) ? x2 : 1;
|
||||
y = isSafeNum(y) ? y : 0;
|
||||
y2 = isSafeNum(y2) ? y2 : 0;
|
||||
|
||||
const canvasGradient = ctx.createLinearGradient(x, y, x2, y2);
|
||||
|
||||
return canvasGradient;
|
||||
}
|
||||
|
||||
export function createRadialGradient(
|
||||
this: void,
|
||||
ctx: CanvasRenderingContext2D,
|
||||
obj: RadialGradientObject,
|
||||
rect: RectLike
|
||||
) {
|
||||
const width = rect.width;
|
||||
const height = rect.height;
|
||||
const min = Math.min(width, height);
|
||||
|
||||
let x = obj.x == null ? 0.5 : obj.x;
|
||||
let y = obj.y == null ? 0.5 : obj.y;
|
||||
let r = obj.r == null ? 0.5 : obj.r;
|
||||
|
||||
if (!obj.global) {
|
||||
x = x * width + rect.x;
|
||||
y = y * height + rect.y;
|
||||
r = r * min;
|
||||
}
|
||||
|
||||
x = isSafeNum(x) ? x : 0.5;
|
||||
y = isSafeNum(y) ? y : 0.5;
|
||||
r = r >= 0 && isSafeNum(r) ? r : 0.5;
|
||||
|
||||
const canvasGradient = ctx.createRadialGradient(x, y, 0, x, y, r);
|
||||
|
||||
return canvasGradient;
|
||||
}
|
||||
|
||||
export function getCanvasGradient(this: void, ctx: CanvasRenderingContext2D, obj: GradientObject, rect: RectLike) {
|
||||
// TODO Cache?
|
||||
const canvasGradient = obj.type === 'radial'
|
||||
? createRadialGradient(ctx, obj as RadialGradientObject, rect)
|
||||
: createLinearGradient(ctx, obj as LinearGradientObject, rect);
|
||||
|
||||
const colorStops = obj.colorStops;
|
||||
for (let i = 0; i < colorStops.length; i++) {
|
||||
canvasGradient.addColorStop(
|
||||
colorStops[i].offset, colorStops[i].color
|
||||
);
|
||||
}
|
||||
return canvasGradient;
|
||||
}
|
||||
|
||||
export function isClipPathChanged(clipPaths: Path[], prevClipPaths: Path[]): boolean {
|
||||
// displayable.__clipPaths can only be `null`/`undefined` or an non-empty array.
|
||||
if (clipPaths === prevClipPaths || (!clipPaths && !prevClipPaths)) {
|
||||
return false;
|
||||
}
|
||||
if (!clipPaths || !prevClipPaths || (clipPaths.length !== prevClipPaths.length)) {
|
||||
return true;
|
||||
}
|
||||
for (let i = 0; i < clipPaths.length; i++) {
|
||||
if (clipPaths[i] !== prevClipPaths[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function parseInt10(val: string) {
|
||||
return parseInt(val, 10);
|
||||
}
|
||||
export function getSize(
|
||||
root: HTMLElement,
|
||||
whIdx: number,
|
||||
opts: { width?: number | string, height?: number | string}
|
||||
) {
|
||||
|
||||
const wh = ['width', 'height'][whIdx] as 'width' | 'height';
|
||||
const cwh = ['clientWidth', 'clientHeight'][whIdx] as 'clientWidth' | 'clientHeight';
|
||||
const plt = ['paddingLeft', 'paddingTop'][whIdx] as 'paddingLeft' | 'paddingTop';
|
||||
const prb = ['paddingRight', 'paddingBottom'][whIdx] as 'paddingRight' | 'paddingBottom';
|
||||
|
||||
if (opts[wh] != null && opts[wh] !== 'auto') {
|
||||
return parseFloat(opts[wh] as string);
|
||||
}
|
||||
|
||||
// IE8 does not support getComputedStyle, but it use VML.
|
||||
const stl = document.defaultView.getComputedStyle(root);
|
||||
|
||||
return (
|
||||
(root[cwh] || parseInt10(stl[wh]) || parseInt10(root.style[wh]))
|
||||
- (parseInt10(stl[plt]) || 0)
|
||||
- (parseInt10(stl[prb]) || 0)
|
||||
) | 0;
|
||||
}
|
||||
Reference in New Issue
Block a user