@devexperts/dxcharts-lite
Version:
182 lines (181 loc) • 6.9 kB
JavaScript
/*
* 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}`;
}
}