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