import Canvas from '../renderer/canvas'; import { Options, LegendOptions, TooltipOptions, AxisPointerOptions, AxisPointerCustomOptions, CanvasConfig, SeriesOptions, DataItem, } from '../models'; import { Renderer } from '../atomComponent'; import Graph from '../graphs/graph'; import OptionParser from './optionParser'; import { Coordinate, getCoordnateType } from '../coordinate'; import { pluginManager, Plugin } from '../pluginManager'; import { StyleManager } from '../theme'; import { EventBus, getEnv, nextTick } from '../utils'; import { EventName } from '../constants'; import { Interaction } from '../interaction'; import Painter from '../painter'; // let asyncUpdateTimer = null; export default class Chart extends EventBus { public static use(plugins: Plugin[]) { plugins.forEach(plugin => plugin.install(pluginManager)); } public id: any; public style: any; public coordinate: Coordinate; public painter: Painter; public canvas: Canvas; public graphs: Array = []; public dataset: Array; public series: Array; private taskQueue: Renderer[] = []; private globalData: Object = {}; private parsedOptions: any; constructor(el: string, config?: CanvasConfig, cb?: () => CanvasConfig) { super(); this.id = el; this.canvas = new Canvas(el, config, cb); this.initRePaintListener(); } public getInteration() { return Interaction.getInstance(this.id, this.canvas); } public setOption(options: Options) { const parsedOptions = new OptionParser(options).parse(); const { ctx } = this.canvas; const { grid, axis, legend, dataset, series, colors, tooltip, axisPointer } = parsedOptions; this.dataset = dataset; this.series = series; this.parsedOptions = parsedOptions; const coordinateType = getCoordnateType(series); // 画布初始化(延迟初始化) this.canvas.postSetup(coordinateType, grid); // 坐标系&画笔 this.initPainter(ctx, this.initCoordinate()); // 坐标轴 if (coordinateType === 'cartesian') { this.axis(axis); } // 图 this.graph(series); // 样式 this.initGlobalStyle(colors); // 图例 if (legend) { this.legend(legend); } if (coordinateType === 'cartesian') { this.axisPointer(axisPointer); } if (tooltip?.show) { this.tooltip(tooltip); } // 渲染 this.render(); } public getParsedOptions() { return this.parsedOptions; }; public getOption() { return (this.taskQueue as any).reduce((memo, current) => { const name = current?.name?.toLowerCase(); switch (name) { case 'axis': // TODO: 非grahp组件,是否仍需使用array类型管理? !memo.xAxis && (memo.xAxis = []); !memo.yAxis && (memo.yAxis = []); memo.xAxis.push(current.xAxisOptions); memo.yAxis.push(current.yAxisOptions); break; case 'graph': !memo.series && (memo.series = []); memo.series.push(...current.series.map((item) => { const result = { ...item, type: current.type, ...item.options }; delete result.options; return result; })); break; case 'legend': !memo.legend && (memo.legend = []); memo.legend.push(current.options); break; case 'axispointer': !memo.axisPointer && (memo.axisPointer = []); memo.axisPointer.push(current.options); break; case 'canvastooltip': !memo.tooltip && (memo.tooltip = []); memo.tooltip.push(current.options); } return memo; }, {} as any); } public setGlobalData(key: string, value: any) { if (!this.globalData[key]) { this.globalData[key] = value; } else { if (value instanceof Array) { this.globalData[key] = value; } else if (value instanceof Object) { this.globalData[key] = { ...this.globalData[key], ...value, }; } else { this.globalData[key] = value; } } } public getGlobalData(key: string) { return this.globalData[key]; } /** * 设置全局样式 */ private initGlobalStyle(colors: string[]) { this.style = new StyleManager(this).setup(colors); } /** * 设置坐标系映射器 */ private initCoordinate() { const { rawSize, // 画布尺寸 size, // 图表尺寸 origin, // 图表原点 } = this.canvas; const source = { x: 0, y: 0, width: rawSize.width, height: rawSize.height, }; const target = { x: origin.x, y: origin.y, width: size.width, height: size.height, }; this.coordinate = new Coordinate(source, target); return this.coordinate; } private initPainter(ctx: CanvasRenderingContext2D, coordinate: Coordinate) { this.painter = new Painter(ctx, coordinate); } private axis(axisOptions: any) { const Axis = pluginManager.getComponent('axis'); // 画坐标系:x轴和y轴 const axis = new Axis(this, axisOptions); this.taskQueue.push(axis); } private legend(options: LegendOptions = {}) { const Legend = pluginManager.getComponent('legend'); const legend = new Legend(this, options); this.taskQueue.push(legend); } private graph(series: Array) { const groupGraphMap = series.reduce((memo, current) => { const { type } = current; if (!memo[type]) { // eslint-disable-next-line no-param-reassign memo[type] = [current]; } else { memo[type].push(current); } return memo; }, {}); Object.entries(groupGraphMap).forEach(([type, seriesOptions]) => { const graph = new Graph(this, type as any, seriesOptions as Array); this.taskQueue.push(graph); this.graphs.push(graph); }); } private tooltip(options?: TooltipOptions) { const { renderMode, width } = options; if (renderMode === 'canvas' && !width) throw new Error('当renderMode被指定为canvas时,必须同时配置width'); if (renderMode === 'html' && getEnv() === 'mp') throw new Error('小程序环境不支持html模式的renderMode,请使用richText或canvas'); let tooltipRenderer = getEnv() === 'mp' ? 'canvas' : 'html'; tooltipRenderer = renderMode ?? tooltipRenderer; let tooltip = null; if (tooltipRenderer === 'html') { const Tooltip = pluginManager.getComponent('htmlTooltip'); tooltip = new Tooltip(this, options); } if (tooltipRenderer === 'richText') { const Tooltip = pluginManager.getComponent('richTextTooltip'); tooltip = new Tooltip(this, options); } if (tooltipRenderer === 'canvas') { const Tooltip = pluginManager.getComponent('canvasTooltip'); tooltip = new Tooltip(this, options); } this.taskQueue.push(tooltip); } private axisPointer(options?: AxisPointerOptions & AxisPointerCustomOptions) { const AxisPointer = pluginManager.getComponent('axisPointer'); const axisPointer = new AxisPointer(this, options); this.taskQueue.push(axisPointer); } // 渲染 private render() { this.taskQueue.forEach(task => task.render()); } // 重绘监听 private initRePaintListener() { this.on(EventName.Repaint, ({ dirtyComps }) => { this.update(dirtyComps); }); } /** * 重绘 * @param dirtyComps 需要重绘的组件 */ private update(dirtyComps) { // asyncUpdateTimer && clearTimeout(asyncUpdateTimer); const dirtyCompIds = dirtyComps.map(comp => comp.id); this.taskQueue = this.taskQueue.map((comp) => { if (dirtyCompIds.includes(comp.id)) { return dirtyComps.find(dirtyComp => dirtyComp.id === comp.id); } return comp; }); // asyncUpdateTimer = setTimeout(() => { // this.canvas.clearCanvas(); // this.render(); // }, 0); nextTick(() => { this.canvas.clearCanvas(); this.render(); }); } }