const cacheChart = {} const fontSizeReg = /([\d\.]+)px/ class EventEmit { constructor() { this.__events = {} } on(type, listener) { if (!type || !listener) { return } const events = this.__events[type] || [] events.push(listener) this.__events[type] = events } emit(type, e) { if (type.constructor === Object) { e = type type = e && e.type } if (!type) { return } const events = this.__events[type] if (!events || !events.length) { return } events.forEach(listener => { listener.call(this, e) }) } off(type, listener) { const __events = this.__events const events = __events[type] if (!events || !events.length) { return } if (!listener) { delete __events[type] return } for (let i = 0, len = events.length; i < len; i++) { if (events[i] === listener) { events.splice(i, 1) i-- } } } } class Image { constructor() { this.currentSrc = null this.naturalHeight = 0 this.naturalWidth = 0 this.width = 0 this.height = 0 this.tagName = 'IMG' } set src(src) { this.currentSrc = src uni.getImageInfo({ src, success: res => { this.naturalWidth = this.width = res.width this.naturalHeight = this.height = res.height this.onload() }, fail: () => { this.onerror() }, }) } get src() { return this.currentSrc } } class OffscreenCanvas { constructor(ctx, com, canvasId) { this.tagName = 'canvas' this.com = com this.canvasId = canvasId this.ctx = ctx } set width(w) { this.com.offscreenWidth = w } set height(h) { this.com.offscreenHeight = h } get width() { return this.com.offscreenWidth || 0 } get height() { return this.com.offscreenHeight || 0 } getContext(type) { return this.ctx } getImageData() { return new Promise((resolve, reject) => { this.com.$nextTick(() => { uni.canvasGetImageData( { x: 0, y: 0, width: this.com.offscreenWidth, height: this.com.offscreenHeight, canvasId: this.canvasId, success: res => { resolve(res) }, fail: err => { reject(err) }, }, this.com ) }) }) } } export class Canvas { constructor(ctx, com, isNew, canvasNode = {}) { cacheChart[com.canvasId] = { ctx } this.canvasId = com.canvasId this.chart = null this.isNew = isNew this.tagName = 'canvas' this.canvasNode = canvasNode this.com = com if (!isNew) { this._initStyle(ctx) } this._initEvent() this._ee = new EventEmit() } getContext(type) { if (type === '2d') { return this.ctx } } setAttribute(key, value) { if (key === 'aria-label') { this.com['ariaLabel'] = value } } setChart(chart) { this.chart = chart } createOffscreenCanvas(param) { if (!this.children) { this.com.isOffscreenCanvas = true this.com.offscreenWidth = param.width || 300 this.com.offscreenHeight = param.height || 300 const com = this.com const canvasId = this.com.offscreenCanvasId const context = uni.createCanvasContext(canvasId, this.com) this._initStyle(context) this.children = new OffscreenCanvas(context, com, canvasId) } return this.children } appendChild(child) { console.log('child', child) } dispatchEvent(type, e) { if (typeof type == 'object') { this._ee.emit(type.type, type) } else { this._ee.emit(type, e) } return true } attachEvent() {} detachEvent() {} addEventListener(type, listener) { this._ee.on(type, listener) } removeEventListener(type, listener) { this._ee.off(type, listener) } _initCanvas(zrender, ctx) { // zrender.util.getContext = function() { // return ctx; // }; // zrender.util.$override('measureText', function(text, font) { // ctx.font = font || '12px sans-serif'; // return ctx.measureText(text, font); // }); } _initStyle(ctx, child) { const styles = [ 'fillStyle', 'strokeStyle', 'fontSize', 'globalAlpha', 'opacity', 'textAlign', 'textBaseline', 'shadow', 'lineWidth', 'lineCap', 'lineJoin', 'lineDash', 'miterLimit', // 'font' ] const colorReg = /#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])\b/g styles.forEach(style => { Object.defineProperty(ctx, style, { set: value => { // if (style === 'font' && fontSizeReg.test(value)) { // const match = fontSizeReg.exec(value); // ctx.setFontSize(match[1]); // return; // } if (style === 'opacity') { ctx.setGlobalAlpha(value) return } if ( (style !== 'fillStyle' && style !== 'strokeStyle') || (value !== 'none' && value !== null) ) { // #ifdef H5 || APP-PLUS || MP-BAIDU if (typeof value == 'object') { if (value.hasOwnProperty('colorStop') || value.hasOwnProperty('colors')) { ctx['set' + style.charAt(0).toUpperCase() + style.slice(1)](value) } return } // #endif // #ifdef MP-TOUTIAO if (colorReg.test(value)) { value = value.replace(colorReg, '#$1$1$2$2$3$3') } // #endif ctx['set' + style.charAt(0).toUpperCase() + style.slice(1)](value) } }, }) }) if (!this.isNew && !child) { ctx.uniDrawImage = ctx.drawImage ctx.drawImage = (...a) => { a[0] = a[0].src ctx.uniDrawImage(...a) } } if (!ctx.createRadialGradient) { ctx.createRadialGradient = function () { return ctx.createCircularGradient(...[...arguments].slice(-3)) } } // 字节不支持 if (!ctx.strokeText) { ctx.strokeText = (...a) => { ctx.fillText(...a) } } // 钉钉不支持 if (!ctx.measureText) { const strLen = str => { let len = 0 for (let i = 0; i < str.length; i++) { if (str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) { len++ } else { len += 2 } } return len } ctx.measureText = (text, font) => { let fontSize = ctx?.state?.fontSize || 12 if (font) { fontSize = parseInt(font.match(/([\d\.]+)px/)[1]) } fontSize /= 2 let isBold = fontSize >= 16 const widthFactor = isBold ? 1.3 : 1 return { width: strLen(text) * fontSize * widthFactor, } } } } _initEvent(e) { this.event = {} const eventNames = [ { wxName: 'touchStart', ecName: 'mousedown', }, { wxName: 'touchMove', ecName: 'mousemove', }, { wxName: 'touchEnd', ecName: 'mouseup', }, { wxName: 'touchEnd', ecName: 'click', }, ] eventNames.forEach(name => { this.event[name.wxName] = e => { const touch = e.touches[0] this.chart.getZr().handler.dispatch(name.ecName, { zrX: name.wxName === 'tap' ? touch.clientX : touch.x, zrY: name.wxName === 'tap' ? touch.clientY : touch.y, }) } }) } set width(w) { this.canvasNode.width = w } set height(h) { this.canvasNode.height = h } get width() { return this.canvasNode.width || 0 } get height() { return this.canvasNode.height || 0 } get ctx() { return cacheChart[this.canvasId]['ctx'] || null } set chart(chart) { cacheChart[this.canvasId]['chart'] = chart } get chart() { return cacheChart[this.canvasId]['chart'] || null } } export function dispatch(name, { x, y, wheelDelta }) { this.dispatch(name, { zrX: x, zrY: y, zrDelta: wheelDelta, preventDefault: () => {}, stopPropagation: () => {}, }) } export function setCanvasCreator(echarts, { canvas, node }) { // echarts.setCanvasCreator(() => canvas); if (echarts && !echarts.registerPreprocessor) { return console.warn('echarts 版本不对或未传入echarts,vue3请使用esm格式') } echarts.registerPreprocessor(option => { if (option && option.series) { if (option.series.length > 0) { option.series.forEach(series => { series.progressive = 0 }) } else if (typeof option.series === 'object') { option.series.progressive = 0 } } }) function loadImage(src, onload, onerror) { let img = null if (node && node.createImage) { img = node.createImage() img.onload = onload.bind(img) img.onerror = onerror.bind(img) img.src = src return img } else { img = new Image() img.onload = onload.bind(img) img.onerror = onerror.bind(img) img.src = src return img } } if (echarts.setPlatformAPI) { echarts.setPlatformAPI({ loadImage: canvas.setChart ? loadImage : null, createCanvas() { const key = 'createOffscreenCanvas' return uni.canIUse(key) && uni[key] ? uni[key]({ type: '2d' }) : canvas }, }) } }