| import { devicePixelRatio } from './config'; |
| import * as util from './core/util'; |
| import log from './core/log'; |
| import BoundingRect from './core/BoundingRect'; |
| import timsort from './core/timsort'; |
| import Layer from './Layer'; |
| import requestAnimationFrame from './animation/requestAnimationFrame'; |
| import Image from './graphic/Image'; |
| import env from './core/env'; |
| var HOVER_LAYER_ZLEVEL = 1e5; |
| var CANVAS_ZLEVEL = 314159; |
| var EL_AFTER_INCREMENTAL_INC = 0.01; |
| var INCREMENTAL_INC = 0.001; |
| |
| function parseInt10(val) { |
| return parseInt(val, 10); |
| } |
| |
| function isLayerValid(layer) { |
| if (!layer) { |
| return false; |
| } |
| |
| if (layer.__builtin__) { |
| return true; |
| } |
| |
| if (typeof layer.resize !== 'function' || typeof layer.refresh !== 'function') { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| var tmpRect = new BoundingRect(0, 0, 0, 0); |
| var viewRect = new BoundingRect(0, 0, 0, 0); |
| |
| function isDisplayableCulled(el, width, height) { |
| tmpRect.copy(el.getBoundingRect()); |
| |
| if (el.transform) { |
| tmpRect.applyTransform(el.transform); |
| } |
| |
| viewRect.width = width; |
| viewRect.height = height; |
| return !tmpRect.intersect(viewRect); |
| } |
| |
| function isClipPathChanged(clipPaths, prevClipPaths) { |
| // displayable.__clipPaths can only be `null`/`undefined` or an non-empty array. |
| if (clipPaths === prevClipPaths) { |
| return false; |
| } |
| |
| if (!clipPaths || !prevClipPaths || clipPaths.length !== prevClipPaths.length) { |
| return true; |
| } |
| |
| for (var i = 0; i < clipPaths.length; i++) { |
| if (clipPaths[i] !== prevClipPaths[i]) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| function doClip(clipPaths, ctx) { |
| for (var i = 0; i < clipPaths.length; i++) { |
| var clipPath = clipPaths[i]; |
| clipPath.setTransform(ctx); |
| ctx.beginPath(); |
| clipPath.buildPath(ctx, clipPath.shape); |
| ctx.clip(); // Transform back |
| |
| clipPath.restoreTransform(ctx); |
| } |
| } |
| |
| function createRoot(width, height) { |
| var domRoot = document.createElement('div'); // domRoot.onselectstart = returnFalse; // 避免页面选中的尴尬 |
| |
| domRoot.style.cssText = ['position:relative', 'overflow:hidden', 'width:' + width + 'px', 'height:' + height + 'px', 'padding:0', 'margin:0', 'border-width:0'].join(';') + ';'; |
| return domRoot; |
| } |
| /** |
| * @alias module:zrender/Painter |
| * @constructor |
| * @param {HTMLElement} root 绘图容器 |
| * @param {module:zrender/Storage} storage |
| * @param {Object} opts |
| */ |
| |
| |
| var Painter = function (root, storage, opts) { |
| this.type = 'canvas'; // In node environment using node-canvas |
| |
| var singleCanvas = !root.nodeName // In node ? |
| || root.nodeName.toUpperCase() === 'CANVAS'; |
| this._opts = opts = util.extend({}, opts || {}); |
| /** |
| * @type {number} |
| */ |
| |
| this.dpr = opts.devicePixelRatio || devicePixelRatio; |
| /** |
| * @type {boolean} |
| * @private |
| */ |
| |
| this._singleCanvas = singleCanvas; |
| /** |
| * 绘图容器 |
| * @type {HTMLElement} |
| */ |
| |
| this.root = root; |
| var rootStyle = root.style; |
| |
| if (rootStyle) { |
| rootStyle['-webkit-tap-highlight-color'] = 'transparent'; |
| rootStyle['-webkit-user-select'] = rootStyle['user-select'] = rootStyle['-webkit-touch-callout'] = 'none'; |
| root.innerHTML = ''; |
| } |
| /** |
| * @type {module:zrender/Storage} |
| */ |
| |
| |
| this.storage = storage; |
| /** |
| * @type {Array.<number>} |
| * @private |
| */ |
| |
| var zlevelList = this._zlevelList = []; |
| /** |
| * @type {Object.<string, module:zrender/Layer>} |
| * @private |
| */ |
| |
| var layers = this._layers = {}; |
| /** |
| * @type {Object.<string, Object>} |
| * @private |
| */ |
| |
| this._layerConfig = {}; |
| /** |
| * zrender will do compositing when root is a canvas and have multiple zlevels. |
| */ |
| |
| this._needsManuallyCompositing = false; |
| |
| if (!singleCanvas) { |
| this._width = this._getSize(0); |
| this._height = this._getSize(1); |
| var domRoot = this._domRoot = createRoot(this._width, this._height); |
| root.appendChild(domRoot); |
| } else { |
| var width = root.width; |
| var height = root.height; |
| |
| if (opts.width != null) { |
| width = opts.width; |
| } |
| |
| if (opts.height != null) { |
| height = opts.height; |
| } |
| |
| this.dpr = opts.devicePixelRatio || 1; // Use canvas width and height directly |
| |
| root.width = width * this.dpr; |
| root.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. |
| |
| var mainLayer = new Layer(root, 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; |
| } |
| /** |
| * @type {module:zrender/Layer} |
| * @private |
| */ |
| |
| |
| this._hoverlayer = null; |
| this._hoverElements = []; |
| }; |
| |
| Painter.prototype = { |
| constructor: Painter, |
| getType: function () { |
| return 'canvas'; |
| }, |
| |
| /** |
| * If painter use a single canvas |
| * @return {boolean} |
| */ |
| isSingleCanvas: function () { |
| return this._singleCanvas; |
| }, |
| |
| /** |
| * @return {HTMLDivElement} |
| */ |
| getViewportRoot: function () { |
| return this._domRoot; |
| }, |
| getViewportRootOffset: function () { |
| var viewportRoot = this.getViewportRoot(); |
| |
| if (viewportRoot) { |
| return { |
| offsetLeft: viewportRoot.offsetLeft || 0, |
| offsetTop: viewportRoot.offsetTop || 0 |
| }; |
| } |
| }, |
| |
| /** |
| * 刷新 |
| * @param {boolean} [paintAll=false] 强制绘制所有displayable |
| */ |
| refresh: function (paintAll) { |
| var list = this.storage.getDisplayList(true); |
| var zlevelList = this._zlevelList; |
| this._redrawId = Math.random(); |
| |
| this._paintList(list, paintAll, this._redrawId); // Paint custum layers |
| |
| |
| for (var i = 0; i < zlevelList.length; i++) { |
| var z = zlevelList[i]; |
| var layer = this._layers[z]; |
| |
| if (!layer.__builtin__ && layer.refresh) { |
| var clearColor = i === 0 ? this._backgroundColor : null; |
| layer.refresh(clearColor); |
| } |
| } |
| |
| this.refreshHover(); |
| return this; |
| }, |
| addHover: function (el, hoverStyle) { |
| if (el.__hoverMir) { |
| return; |
| } |
| |
| var elMirror = new el.constructor({ |
| style: el.style, |
| shape: el.shape, |
| z: el.z, |
| z2: el.z2, |
| silent: el.silent |
| }); |
| elMirror.__from = el; |
| el.__hoverMir = elMirror; |
| hoverStyle && elMirror.setStyle(hoverStyle); |
| |
| this._hoverElements.push(elMirror); |
| |
| return elMirror; |
| }, |
| removeHover: function (el) { |
| var elMirror = el.__hoverMir; |
| var hoverElements = this._hoverElements; |
| var idx = util.indexOf(hoverElements, elMirror); |
| |
| if (idx >= 0) { |
| hoverElements.splice(idx, 1); |
| } |
| |
| el.__hoverMir = null; |
| }, |
| clearHover: function (el) { |
| var hoverElements = this._hoverElements; |
| |
| for (var i = 0; i < hoverElements.length; i++) { |
| var from = hoverElements[i].__from; |
| |
| if (from) { |
| from.__hoverMir = null; |
| } |
| } |
| |
| hoverElements.length = 0; |
| }, |
| refreshHover: function () { |
| var hoverElements = this._hoverElements; |
| var len = hoverElements.length; |
| var hoverLayer = this._hoverlayer; |
| hoverLayer && hoverLayer.clear(); |
| |
| if (!len) { |
| return; |
| } |
| |
| timsort(hoverElements, this.storage.displayableSortFunc); // Use a extream large zlevel |
| // FIXME? |
| |
| if (!hoverLayer) { |
| hoverLayer = this._hoverlayer = this.getLayer(HOVER_LAYER_ZLEVEL); |
| } |
| |
| var scope = {}; |
| hoverLayer.ctx.save(); |
| |
| for (var i = 0; i < len;) { |
| var el = hoverElements[i]; |
| var originalEl = el.__from; // Original el is removed |
| // PENDING |
| |
| if (!(originalEl && originalEl.__zr)) { |
| hoverElements.splice(i, 1); |
| originalEl.__hoverMir = null; |
| len--; |
| continue; |
| } |
| |
| i++; // Use transform |
| // FIXME style and shape ? |
| |
| if (!originalEl.invisible) { |
| el.transform = originalEl.transform; |
| el.invTransform = originalEl.invTransform; |
| el.__clipPaths = originalEl.__clipPaths; // el. |
| |
| this._doPaintEl(el, hoverLayer, true, scope); |
| } |
| } |
| |
| hoverLayer.ctx.restore(); |
| }, |
| getHoverLayer: function () { |
| return this.getLayer(HOVER_LAYER_ZLEVEL); |
| }, |
| _paintList: function (list, paintAll, redrawId) { |
| if (this._redrawId !== redrawId) { |
| return; |
| } |
| |
| paintAll = paintAll || false; |
| |
| this._updateLayerStatus(list); |
| |
| var finished = this._doPaintList(list, paintAll); |
| |
| if (this._needsManuallyCompositing) { |
| this._compositeManually(); |
| } |
| |
| if (!finished) { |
| var self = this; |
| requestAnimationFrame(function () { |
| self._paintList(list, paintAll, redrawId); |
| }); |
| } |
| }, |
| _compositeManually: function () { |
| var ctx = this.getLayer(CANVAS_ZLEVEL).ctx; |
| var width = this._domRoot.width; |
| var height = this._domRoot.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); |
| } |
| }); |
| }, |
| _doPaintList: function (list, paintAll) { |
| var layerList = []; |
| |
| for (var zi = 0; zi < this._zlevelList.length; zi++) { |
| var zlevel = this._zlevelList[zi]; |
| var layer = this._layers[zlevel]; |
| |
| if (layer.__builtin__ && layer !== this._hoverlayer && (layer.__dirty || paintAll)) { |
| layerList.push(layer); |
| } |
| } |
| |
| var finished = true; |
| |
| for (var k = 0; k < layerList.length; k++) { |
| var layer = layerList[k]; |
| var ctx = layer.ctx; |
| var scope = {}; |
| ctx.save(); |
| var start = paintAll ? layer.__startIndex : layer.__drawIndex; |
| var useTimer = !paintAll && layer.incremental && Date.now; |
| var startTime = useTimer && Date.now(); |
| var clearColor = layer.zlevel === this._zlevelList[0] ? this._backgroundColor : null; // All elements in this layer are cleared. |
| |
| if (layer.__startIndex === layer.__endIndex) { |
| layer.clear(false, clearColor); |
| } else if (start === layer.__startIndex) { |
| var firstEl = list[start]; |
| |
| if (!firstEl.incremental || !firstEl.notClear || paintAll) { |
| layer.clear(false, clearColor); |
| } |
| } |
| |
| if (start === -1) { |
| console.error('For some unknown reason. drawIndex is -1'); |
| start = layer.__startIndex; |
| } |
| |
| for (var i = start; i < layer.__endIndex; i++) { |
| var el = list[i]; |
| |
| this._doPaintEl(el, layer, paintAll, scope); |
| |
| el.__dirty = el.__dirtyText = false; |
| |
| if (useTimer) { |
| // Date.now can be executed in 13,025,305 ops/second. |
| var dTime = Date.now() - startTime; // Give 15 millisecond to draw. |
| // The rest elements will be drawn in the next frame. |
| |
| if (dTime > 15) { |
| break; |
| } |
| } |
| } |
| |
| layer.__drawIndex = i; |
| |
| if (layer.__drawIndex < layer.__endIndex) { |
| finished = false; |
| } |
| |
| if (scope.prevElClipPaths) { |
| // Needs restore the state. If last drawn element is in the clipping area. |
| ctx.restore(); |
| } |
| |
| ctx.restore(); |
| } |
| |
| if (env.wxa) { |
| // Flush for weixin application |
| util.each(this._layers, function (layer) { |
| if (layer && layer.ctx && layer.ctx.draw) { |
| layer.ctx.draw(); |
| } |
| }); |
| } |
| |
| return finished; |
| }, |
| _doPaintEl: function (el, currentLayer, forcePaint, scope) { |
| var ctx = currentLayer.ctx; |
| var m = el.transform; |
| |
| if ((currentLayer.__dirty || forcePaint) && // Ignore invisible element |
| !el.invisible // Ignore transparent element |
| && el.style.opacity !== 0 // Ignore scale 0 element, in some environment like node-canvas |
| // Draw a scale 0 element can cause all following draw wrong |
| // And setTransform with scale 0 will cause set back transform failed. |
| && !(m && !m[0] && !m[3]) // Ignore culled element |
| && !(el.culling && isDisplayableCulled(el, this._width, this._height))) { |
| var clipPaths = el.__clipPaths; |
| var prevElClipPaths = scope.prevElClipPaths; // Optimize when clipping on group with several elements |
| |
| if (!prevElClipPaths || isClipPathChanged(clipPaths, prevElClipPaths)) { |
| // If has previous clipping state, restore from it |
| if (prevElClipPaths) { |
| ctx.restore(); |
| scope.prevElClipPaths = null; // Reset prevEl since context has been restored |
| |
| scope.prevEl = null; |
| } // New clipping state |
| |
| |
| if (clipPaths) { |
| ctx.save(); |
| doClip(clipPaths, ctx); |
| scope.prevElClipPaths = clipPaths; |
| } |
| } |
| |
| el.beforeBrush && el.beforeBrush(ctx); |
| el.brush(ctx, scope.prevEl || null); |
| scope.prevEl = el; |
| el.afterBrush && el.afterBrush(ctx); |
| } |
| }, |
| |
| /** |
| * 获取 zlevel 所在层,如果不存在则会创建一个新的层 |
| * @param {number} zlevel |
| * @param {boolean} virtual Virtual layer will not be inserted into dom. |
| * @return {module:zrender/Layer} |
| */ |
| getLayer: function (zlevel, virtual) { |
| if (this._singleCanvas && !this._needsManuallyCompositing) { |
| zlevel = CANVAS_ZLEVEL; |
| } |
| |
| var 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); |
| } |
| |
| 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: function (zlevel, layer) { |
| var layersMap = this._layers; |
| var zlevelList = this._zlevelList; |
| var len = zlevelList.length; |
| var prevLayer = null; |
| var i = -1; |
| var domRoot = this._domRoot; |
| |
| if (layersMap[zlevel]) { |
| log('ZLevel ' + zlevel + ' has been used already'); |
| return; |
| } // Check if is a valid layer |
| |
| |
| if (!isLayerValid(layer)) { |
| log('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; // Vitual layer will not directly show on the screen. |
| // (It can be a WebGL layer and assigned to a ZImage element) |
| // But it still under management of zrender. |
| |
| if (!layer.virtual) { |
| if (prevLayer) { |
| var 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); |
| } |
| } |
| } |
| }, |
| // Iterate each layer |
| eachLayer: function (cb, context) { |
| var zlevelList = this._zlevelList; |
| var z; |
| var i; |
| |
| for (i = 0; i < zlevelList.length; i++) { |
| z = zlevelList[i]; |
| cb.call(context, this._layers[z], z); |
| } |
| }, |
| // Iterate each buildin layer |
| eachBuiltinLayer: function (cb, context) { |
| var zlevelList = this._zlevelList; |
| var layer; |
| var z; |
| var i; |
| |
| for (i = 0; i < zlevelList.length; i++) { |
| z = zlevelList[i]; |
| layer = this._layers[z]; |
| |
| if (layer.__builtin__) { |
| cb.call(context, layer, z); |
| } |
| } |
| }, |
| // Iterate each other layer except buildin layer |
| eachOtherLayer: function (cb, context) { |
| var zlevelList = this._zlevelList; |
| var layer; |
| var z; |
| var i; |
| |
| for (i = 0; i < zlevelList.length; i++) { |
| z = zlevelList[i]; |
| layer = this._layers[z]; |
| |
| if (!layer.__builtin__) { |
| cb.call(context, layer, z); |
| } |
| } |
| }, |
| |
| /** |
| * 获取所有已创建的层 |
| * @param {Array.<module:zrender/Layer>} [prevLayer] |
| */ |
| getLayers: function () { |
| return this._layers; |
| }, |
| _updateLayerStatus: function (list) { |
| this.eachBuiltinLayer(function (layer, z) { |
| layer.__dirty = layer.__used = false; |
| }); |
| |
| function updatePrevLayer(idx) { |
| if (prevLayer) { |
| if (prevLayer.__endIndex !== idx) { |
| prevLayer.__dirty = true; |
| } |
| |
| prevLayer.__endIndex = idx; |
| } |
| } |
| |
| if (this._singleCanvas) { |
| for (var i = 1; i < list.length; i++) { |
| var el = list[i]; |
| |
| if (el.zlevel !== list[i - 1].zlevel || el.incremental) { |
| this._needsManuallyCompositing = true; |
| break; |
| } |
| } |
| } |
| |
| var prevLayer = null; |
| var incrementalLayerCount = 0; |
| |
| for (var i = 0; i < list.length; i++) { |
| var el = list[i]; |
| var zlevel = el.zlevel; |
| var layer; // PENDING If change one incremental element style ? |
| // TODO Where there are non-incremental elements between incremental elements. |
| |
| 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__) { |
| log('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) { |
| 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: function () { |
| this.eachBuiltinLayer(this._clearLayer); |
| return this; |
| }, |
| _clearLayer: function (layer) { |
| layer.clear(); |
| }, |
| setBackgroundColor: function (backgroundColor) { |
| this._backgroundColor = backgroundColor; |
| }, |
| |
| /** |
| * 修改指定zlevel的绘制参数 |
| * |
| * @param {string} zlevel |
| * @param {Object} config 配置对象 |
| * @param {string} [config.clearColor=0] 每次清空画布的颜色 |
| * @param {string} [config.motionBlur=false] 是否开启动态模糊 |
| * @param {number} [config.lastFrameAlpha=0.7] |
| * 在开启动态模糊的时候使用,与上一帧混合的alpha值,值越大尾迹越明显 |
| */ |
| configLayer: function (zlevel, config) { |
| if (config) { |
| var layerConfig = this._layerConfig; |
| |
| if (!layerConfig[zlevel]) { |
| layerConfig[zlevel] = config; |
| } else { |
| util.merge(layerConfig[zlevel], config, true); |
| } |
| |
| for (var i = 0; i < this._zlevelList.length; i++) { |
| var _zlevel = this._zlevelList[i]; |
| |
| if (_zlevel === zlevel || _zlevel === zlevel + EL_AFTER_INCREMENTAL_INC) { |
| var layer = this._layers[_zlevel]; |
| util.merge(layer, layerConfig[zlevel], true); |
| } |
| } |
| } |
| }, |
| |
| /** |
| * 删除指定层 |
| * @param {number} zlevel 层所在的zlevel |
| */ |
| delLayer: function (zlevel) { |
| var layers = this._layers; |
| var zlevelList = this._zlevelList; |
| var layer = layers[zlevel]; |
| |
| if (!layer) { |
| return; |
| } |
| |
| layer.dom.parentNode.removeChild(layer.dom); |
| delete layers[zlevel]; |
| zlevelList.splice(util.indexOf(zlevelList, zlevel), 1); |
| }, |
| |
| /** |
| * 区域大小变化后重绘 |
| */ |
| resize: function (width, height) { |
| if (!this._domRoot.style) { |
| // Maybe in node or worker |
| if (width == null || height == null) { |
| return; |
| } |
| |
| this._width = width; |
| this._height = height; |
| this.getLayer(CANVAS_ZLEVEL).resize(width, height); |
| } else { |
| var domRoot = this._domRoot; // FIXME Why ? |
| |
| domRoot.style.display = 'none'; // Save input w/h |
| |
| var opts = this._opts; |
| width != null && (opts.width = width); |
| height != null && (opts.height = height); |
| width = this._getSize(0); |
| height = this._getSize(1); |
| domRoot.style.display = ''; // 优化没有实际改变的resize |
| |
| if (this._width !== width || height !== this._height) { |
| domRoot.style.width = width + 'px'; |
| domRoot.style.height = height + 'px'; |
| |
| for (var id in this._layers) { |
| if (this._layers.hasOwnProperty(id)) { |
| this._layers[id].resize(width, height); |
| } |
| } |
| |
| util.each(this._progressiveLayers, function (layer) { |
| layer.resize(width, height); |
| }); |
| this.refresh(true); |
| } |
| |
| this._width = width; |
| this._height = height; |
| } |
| |
| return this; |
| }, |
| |
| /** |
| * 清除单独的一个层 |
| * @param {number} zlevel |
| */ |
| clearLayer: function (zlevel) { |
| var layer = this._layers[zlevel]; |
| |
| if (layer) { |
| layer.clear(); |
| } |
| }, |
| |
| /** |
| * 释放 |
| */ |
| dispose: function () { |
| this.root.innerHTML = ''; |
| this.root = this.storage = this._domRoot = this._layers = null; |
| }, |
| |
| /** |
| * Get canvas which has all thing rendered |
| * @param {Object} opts |
| * @param {string} [opts.backgroundColor] |
| * @param {number} [opts.pixelRatio] |
| */ |
| getRenderedCanvas: function (opts) { |
| opts = opts || {}; |
| |
| if (this._singleCanvas && !this._compositeManually) { |
| return this._layers[CANVAS_ZLEVEL].dom; |
| } |
| |
| var imageLayer = new Layer('image', this, opts.pixelRatio || this.dpr); |
| imageLayer.initContext(); |
| imageLayer.clear(false, opts.backgroundColor || this._backgroundColor); |
| |
| if (opts.pixelRatio <= this.dpr) { |
| this.refresh(); |
| var width = imageLayer.dom.width; |
| var height = imageLayer.dom.height; |
| var ctx = imageLayer.ctx; |
| this.eachLayer(function (layer) { |
| if (layer.__builtin__) { |
| ctx.drawImage(layer.dom, 0, 0, width, height); |
| } else if (layer.renderToCanvas) { |
| imageLayer.ctx.save(); |
| layer.renderToCanvas(imageLayer.ctx); |
| imageLayer.ctx.restore(); |
| } |
| }); |
| } else { |
| // PENDING, echarts-gl and incremental rendering. |
| var scope = {}; |
| var displayList = this.storage.getDisplayList(true); |
| |
| for (var i = 0; i < displayList.length; i++) { |
| var el = displayList[i]; |
| |
| this._doPaintEl(el, imageLayer, true, scope); |
| } |
| } |
| |
| return imageLayer.dom; |
| }, |
| |
| /** |
| * 获取绘图区域宽度 |
| */ |
| getWidth: function () { |
| return this._width; |
| }, |
| |
| /** |
| * 获取绘图区域高度 |
| */ |
| getHeight: function () { |
| return this._height; |
| }, |
| _getSize: function (whIdx) { |
| var opts = this._opts; |
| var wh = ['width', 'height'][whIdx]; |
| var cwh = ['clientWidth', 'clientHeight'][whIdx]; |
| var plt = ['paddingLeft', 'paddingTop'][whIdx]; |
| var prb = ['paddingRight', 'paddingBottom'][whIdx]; |
| |
| if (opts[wh] != null && opts[wh] !== 'auto') { |
| return parseFloat(opts[wh]); |
| } |
| |
| var root = this.root; // IE8 does not support getComputedStyle, but it use VML. |
| |
| var stl = document.defaultView.getComputedStyle(root); |
| return (root[cwh] || parseInt10(stl[wh]) || parseInt10(root.style[wh])) - (parseInt10(stl[plt]) || 0) - (parseInt10(stl[prb]) || 0) | 0; |
| }, |
| pathToImage: function (path, dpr) { |
| dpr = dpr || this.dpr; |
| var canvas = document.createElement('canvas'); |
| var ctx = canvas.getContext('2d'); |
| var rect = path.getBoundingRect(); |
| var style = path.style; |
| var shadowBlurSize = style.shadowBlur * dpr; |
| var shadowOffsetX = style.shadowOffsetX * dpr; |
| var shadowOffsetY = style.shadowOffsetY * dpr; |
| var lineWidth = style.hasStroke() ? style.lineWidth : 0; |
| var leftMargin = Math.max(lineWidth / 2, -shadowOffsetX + shadowBlurSize); |
| var rightMargin = Math.max(lineWidth / 2, shadowOffsetX + shadowBlurSize); |
| var topMargin = Math.max(lineWidth / 2, -shadowOffsetY + shadowBlurSize); |
| var bottomMargin = Math.max(lineWidth / 2, shadowOffsetY + shadowBlurSize); |
| var width = rect.width + leftMargin + rightMargin; |
| var height = rect.height + topMargin + bottomMargin; |
| canvas.width = width * dpr; |
| canvas.height = height * dpr; |
| ctx.scale(dpr, dpr); |
| ctx.clearRect(0, 0, width, height); |
| ctx.dpr = dpr; |
| var pathTransform = { |
| position: path.position, |
| rotation: path.rotation, |
| scale: path.scale |
| }; |
| path.position = [leftMargin - rect.x, topMargin - rect.y]; |
| path.rotation = 0; |
| path.scale = [1, 1]; |
| path.updateTransform(); |
| |
| if (path) { |
| path.brush(ctx); |
| } |
| |
| var ImageShape = Image; |
| var imgShape = new ImageShape({ |
| style: { |
| x: 0, |
| y: 0, |
| image: canvas |
| } |
| }); |
| |
| if (pathTransform.position != null) { |
| imgShape.position = path.position = pathTransform.position; |
| } |
| |
| if (pathTransform.rotation != null) { |
| imgShape.rotation = path.rotation = pathTransform.rotation; |
| } |
| |
| if (pathTransform.scale != null) { |
| imgShape.scale = path.scale = pathTransform.scale; |
| } |
| |
| return imgShape; |
| } |
| }; |
| export default Painter; |