UNPKG

@devexperts/dxcharts-lite

Version:
128 lines (127 loc) 5.56 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 { Subject, animationFrameScheduler } from 'rxjs'; import { distinctUntilChanged, throttleTime } from 'rxjs/operators'; import { ChartBaseElement } from '../model/chart-base-element'; import { CanvasBoundsContainer } from './canvas-bounds-container'; /** * This class is responsible for changing cursor for different entities which are drawn on the canvas. * @doc-tags chart-core,cursor */ export class CursorHandler extends ChartBaseElement { constructor(element, canvasInputListener, canvasBoundsContainer, hitTestCanvasModel) { super(); this.element = element; this.canvasInputListener = canvasInputListener; this.canvasBoundsContainer = canvasBoundsContainer; this.hitTestCanvasModel = hitTestCanvasModel; this.normalLayer = new Map(); this.extensionLayer = new Map(); this.cursorChangedSubject = new Subject(); } /** * This method is called when the user activates the canvas. It subscribes to the mouse move event and checks if the mouse pointer is over any of the elements in the normalLayer or extensionLayer. If the mouse pointer is over any of the elements, it updates the cursor to the corresponding cursor of the element. */ doActivate() { super.doActivate(); this.canvasInputListener .observeMouseMoveNoDrag() .pipe(throttleTime(100, animationFrameScheduler, { trailing: true })) .subscribe(point => { const cursorFromHT = this.hitTestCanvasModel.resolveCursor(point); if (cursorFromHT !== undefined) { this.updateCursor(cursorFromHT); return; } if (point) { this.normalLayer.forEach(val => { if (val.hitTest(point.x, point.y)) { this.updateCursor(val.cursor); } }); this.extensionLayer.forEach(val => { if (val.hitTest(point.x, point.y)) { this.updateCursor(val.cursor); } }); } }); } /*** * @param elId * @param bounds * @param cursorType * @param extension is an extra area beyond the borders of the bounds */ setCursorForBounds(elId, bounds, cursorType, extension = 0) { const hitTest = CanvasBoundsContainer.hitTestOf(bounds, { extensionY: extension }); if (extension) { // extension layer has a higher priority, so if its hitTest intersects with other hitTests // the cursor will be of type specified in extension layer this.extensionLayer.set(elId, { cursor: cursorType, hitTest }); } else { this.normalLayer.set(elId, { cursor: cursorType, hitTest }); } } /** * Sets a cursor for a canvas element * @param {string} canvasEl - The canvas element to add the cursor to * @param {CursorType} cursor - The type of cursor to add * @param {number} [extensionRadius] - The extension radius of the cursor */ setCursorForCanvasEl(canvasEl, cursor, extensionRadius) { this.observeCursorType(canvasEl, cursor, extensionRadius); } /** * Removes the cursor for a given canvas element. * * @param {string} canvasEl - The canvas element to remove the cursor from. * @returns {void} */ removeCursorForCanvasEl(canvasEl) { this.normalLayer.delete(canvasEl); this.extensionLayer.delete(canvasEl); } /** * Returns an Observable that emits the latest CursorType value whenever the cursorChangedSubject emits a new value. * The emitted value is guaranteed to be distinct from the previous one. * @returns {Observable<CursorType>} An Observable that emits the latest CursorType value. */ observeCursorChanged() { return this.cursorChangedSubject.pipe(distinctUntilChanged()); } /** * Sets the cursor type for a given canvas element and optionally extends the hit test area. * @param {string} canvasElement - The ID of the canvas element to observe. * @param {CursorType} cursorType - The type of cursor to set. * @param {number} [extensionY] - Optional extension of the hit test area in the Y axis. * @returns {void} */ observeCursorType(canvasElement, cursorType, extensionY) { const hitTest = extensionY ? this.canvasBoundsContainer.getBoundsHitTest(canvasElement, { extensionY }) : this.canvasBoundsContainer.getBoundsHitTest(canvasElement); if (extensionY) { this.extensionLayer.set(canvasElement, { cursor: cursorType, hitTest }); } else { this.normalLayer.set(canvasElement, { cursor: cursorType, hitTest }); } } /** * Updates the cursor type of an element based on the mouse enter or leave event. * @param {CursorType} cursorType - The type of cursor to be set on the element. * @returns {void} */ updateCursor(cursorType) { if (this.element.style.cursor === cursorType) { return; } this.element.style.cursor = cursorType; this.cursorChangedSubject.next(cursorType); } }