/** * @class Canvas管理类 * @classdesc * 包含canvas元素、ctx上下文、以及rawSize、size、origin、padding等多种尺寸信息 * 包含canvas元素的创建、清空等方法 * @desc * 其中创建分为两个阶段: * 第一阶段:Canvas构造器中 * 第二阶段:业务方调用setOption方法时,触发postSetup方法 */ import { GridOptions, CanvasOrigin, CanvasSize, CanvasConfig } from '../models'; import { isNumber } from '../utils/type'; export default class Canvas { private static readonly defaultPadding = 40; // FIXME: 若业务方在canvas外侧额外添加边距,则defaultPadding需要动态减小,避免展示过于狭窄 public element: HTMLCanvasElement; public ctx: CanvasRenderingContext2D; public origin: CanvasOrigin = {} as any; public rawSize: CanvasSize = {} as any; public size: CanvasSize = {} as any; public customTooltip: any; public padding: any = { left: Canvas.defaultPadding, right: Canvas.defaultPadding, top: Canvas.defaultPadding, bottom: Canvas.defaultPadding, }; public getBoundingClientRectInMp: any; // mp环境下,获取dom方法 private containerElement: HTMLElement; constructor(el: string, config?: CanvasConfig, cb?: () => CanvasConfig) { this.setup(el, cb); } /** *【延迟初始化】 * 获取options后,调整canvas尺寸/原点等信息 * 在setOption时触发 */ public postSetup(coordinateType: string, grid: GridOptions) { this.parseOptions(grid); this.computeSize(); this.computeOrigin(coordinateType); } // 清空画布 public clearCanvas() { const { ctx } = this; const { width, height } = this.rawSize; ctx.clearRect(0, 0, width, height); } // 初始化 private setup(el: string, cb?: () => CanvasConfig) { const isWeb = !cb; // 引入cb获取mp环境下的异步信息,借此判断环境 (FIXME: 未来可改用更准确的方式) if (isWeb) { this.setupInWeb(el); } else { this.setupInMp(cb()); } } private setupInWeb(el: string) { this.containerElement = document.querySelector(el) as HTMLElement; this.element = document.createElement('canvas'); this.ctx = this.element.getContext('2d') as CanvasRenderingContext2D; const { width, height } = this.containerElement.getBoundingClientRect(); this.element.setAttribute('width', `${width * devicePixelRatio}`); this.element.setAttribute('height', `${height * devicePixelRatio}`); this.element.setAttribute( 'style', `width: ${width}px; height: ${height}px`, ); this.containerElement.appendChild(this.element); this.ctx.scale(devicePixelRatio, devicePixelRatio); this.computeRawSize(); } private setupInMp(config: CanvasConfig) { /** * miniProgram环境下,ctx、dom信息外部获取后传入 */ const { canvasDom, customTooltip, width, height, left, top, right, bottom, ctx, devicePixelRatio, getBoundingClientRectInMp, } = config; this.customTooltip = customTooltip; this.element = canvasDom; this.ctx = ctx; this.getBoundingClientRectInMp = getBoundingClientRectInMp; this.rawSize = this.computeRawSize(width, height, left, top, right, bottom); this.ctx.scale(devicePixelRatio, devicePixelRatio); } private computeRawSize( width?: number, height?: number, left?: number, top?: number, right?: number, bottom?: number, ): CanvasSize { if (width || height || left || top || right || bottom) { return { width, height, left, top, right, bottom, }; } const { left: canvasLeft, top: canvasTop, bottom: canvasBottom, right: canvasRight, width: canvasWidth, height: canvasHeight, } = this.element.getBoundingClientRect(); this.rawSize = { left: canvasLeft, top: canvasTop, bottom: canvasBottom, right: canvasRight, width: canvasWidth, height: canvasHeight, }; } private parseOptions(grid: GridOptions) { const { left, top, right, bottom } = grid || {}; this.padding = { left: isNumber(left) ? left : Canvas.defaultPadding, top: isNumber(top) ? top : Canvas.defaultPadding, right: isNumber(right) ? right : Canvas.defaultPadding, bottom: isNumber(bottom) ? bottom : Canvas.defaultPadding, }; } private computeSize(): void { const { width, height } = this.rawSize; this.size = { width: width - this.padding.left - this.padding.right, height: height - this.padding.top - this.padding.bottom, }; } // eslint-disable-next-line @typescript-eslint/no-unused-vars private computeOrigin(coordinateType: string): void { const { height } = this.rawSize; this.origin = { x: this.padding.left, y: height - this.padding.bottom, }; // 是否根据坐标系类型区分设置origin,待定 // switch (coordinateType) { // case 'cartesian': // this.origin = { // x: padding.left, // y: height - padding.bottom, // }; // break; // case 'polar': // default: // this.origin = { // x: padding.left + (width - padding.left - padding.right) / 2, // y: padding.top - (height - padding.top - padding.bottom) / 2, // }; // break; // } } }