UNPKG

@devexperts/dxcharts-lite

Version:
182 lines (181 loc) 6.9 kB
/* * Copyright (C) 2019 - 2025 Devexperts Solutions IE Limited * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. * If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { EVENT_DRAW } from '../events/events'; import { MIN_SUPPORTED_CANVAS_SIZE } from '../model/canvas.model'; import { arrayIntersect, reorderArray } from '../utils/array.utils'; import { animationFrameThrottled } from '../utils/performance/request-animation-frame-throttle.utils'; import { uuid } from '../utils/uuid.utils'; export const HIT_TEST_PREFIX = 'HIT_TEST_'; const drawerTypes = [ 'MAIN_BACKGROUND', 'MAIN_CLEAR', 'HIT_TEST_CLEAR', 'YAXIS_CLEAR', 'SERIES_CLEAR', 'OVER_SERIES_CLEAR', 'HIT_TEST_DRAWINGS', 'GRID', 'X_AXIS', 'Y_AXIS', 'HIGH_LOW', 'DYNAMIC_OBJECTS', 'N_MAP_CHART', 'PL_CHART', 'WATERMARK', 'EMPTY_CHART', 'OFFLINE_CHART', 'LABELS', 'EVENTS', 'HIT_TEST_EVENTS', 'ZERO_LINE', 'PL_ZERO_LINE_BACKGROUND', 'CROSS_TOOL', ]; /** * Manages the drawing process. * Can re-order drawers to make one be on top of the other. */ export class DrawingManager { constructor(eventBus, chartResizeHandler) { this.chartResizeHandler = chartResizeHandler; this.drawingOrder = []; this.drawersMap = {}; this.canvasIdsList = []; this.animFrameId = `draw_${uuid()}`; // eventBus.on(EVENT_DRAW_LAST_CANDLE, () => animationFrameThrottled(this.animFrameId + 'last', () => this.drawLastBar())); this.drawHitTestCanvas = () => { this.drawingOrder.forEach(drawer => { if (drawer.indexOf(HIT_TEST_PREFIX) !== -1) { this.drawersMap[drawer].draw(); } }); }; eventBus.on(EVENT_DRAW, (canvasIds) => { if (chartResizeHandler.wasResized()) { // if we fire bus.fireDraw() without arguments(undefined) we need to keep canvasIdsList empty if (this.canvasIdsList) { if (canvasIds && canvasIds.length !== 0) { this.canvasIdsList = this.canvasIdsList.concat(canvasIds); } else { // make this undefined until the end of frame - this will redraw everything this.canvasIdsList = undefined; } } animationFrameThrottled(this.animFrameId, () => { this.forceDraw(this.canvasIdsList); this.canvasIdsList = []; this.drawHitTestCanvas(); }); } }); } /** * Updates canvases' sizes and executes redraw without animation frame. * This is required for multi-chart canvas update synchronization. * If all canvases update in separate animation frames - we see visual lag. Instead we should do all updates and then redraw. * @doc-tags tricky,canvas,resize */ redrawCanvasesImmediate() { this.chartResizeHandler.fireUpdates(); this.forceDraw(); } drawLastBar() { this.drawingOrder.forEach(drawerName => { if (drawerName.indexOf(HIT_TEST_PREFIX) === -1) { const drawer = this.drawersMap[drawerName]; drawer.drawLastBar && drawer.drawLastBar(); } }); } forceDraw(canvasIds) { if (!this.isDrawable()) { return; } this.drawingOrder.forEach(drawerName => { if (drawerName.indexOf(HIT_TEST_PREFIX) === -1) { const drawer = this.drawersMap[drawerName]; // draw if not canvas specified if (!canvasIds || canvasIds.length === 0) { drawer.draw(); return; } // draw if at least 1 canvas is touched (intersection) if (arrayIntersect(canvasIds, drawer.getCanvasIds()).length) { drawer.draw(); } } }); } /** * Indicates whether it is possible to draw chart or not. * @returns {boolean} true if chart is drawable */ isDrawable() { var _a, _b, _c, _d; return (((_b = (_a = this.chartResizeHandler.previousBCR) === null || _a === void 0 ? void 0 : _a.height) !== null && _b !== void 0 ? _b : 0) > MIN_SUPPORTED_CANVAS_SIZE.width && ((_d = (_c = this.chartResizeHandler.previousBCR) === null || _c === void 0 ? void 0 : _c.width) !== null && _d !== void 0 ? _d : 0) > MIN_SUPPORTED_CANVAS_SIZE.height); } drawHitTestOnly() { this.drawHitTestCanvas(); } addDrawer(drawer, name = uuid()) { this.drawingOrder.indexOf(name) === -1 && this.drawingOrder.push(name); this.drawersMap[name] = drawer; } addDrawerAfter(drawer, newDrawerName, drawerToPutAfterName) { this.addDrawer(drawer, newDrawerName); const newDrawerIdx = this.drawingOrder.indexOf(newDrawerName); this.drawingOrder.splice(newDrawerIdx, 1); const idx = this.drawingOrder.indexOf(drawerToPutAfterName); this.drawingOrder.splice(idx + 1, 0, newDrawerName); this.reorderDrawers(this.drawingOrder); } addDrawerBefore(drawer, newDrawerName, drawerToPutBeforeName) { this.addDrawer(drawer, newDrawerName); const idx = this.drawingOrder.indexOf(drawerToPutBeforeName); if (idx !== -1) { const newDrawerIdx = this.drawingOrder.indexOf(newDrawerName); this.drawingOrder.splice(newDrawerIdx, 1); this.drawingOrder.splice(idx, 0, newDrawerName); this.reorderDrawers(this.drawingOrder); return true; } return false; } getDrawerByName(name) { return this.drawersMap[name]; } getNameByDrawer(drawer) { for (const drawerName in this.drawersMap) { if (this.drawersMap[drawerName] === drawer) { return drawerName; } } } removeDrawerByName(name) { const drawer = this.drawersMap[name]; this.removeDrawer(drawer); } removeDrawer(drawer) { Object.keys(this.drawersMap).forEach(name => { if (this.drawersMap[name] === drawer) { delete this.drawersMap[name]; const drawerIdx = this.drawingOrder.indexOf(name); drawerIdx !== -1 && this.drawingOrder.splice(drawerIdx, 1); } }); } reorderDrawers(newOrder) { this.drawingOrder = reorderArray(this.drawingOrder, newOrder); } } // use this to create dynamic names export class DynamicDrawerType { static paneResizer(id) { return `PANE_RESIZER_${id}`; } }