UNPKG

@devexperts/dxcharts-lite

Version:
163 lines (162 loc) 8.78 kB
/* * Copyright (C) 2019 - 2026 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 { calculateTextWidth } from '../../utils/canvas/canvas-font-measure-tool.utils'; import { HIGHLIGHTS_TYPES } from './highlights.model'; import { CanvasElement } from '../../canvas/canvas-bounds-container'; import { unitToPixels } from '../../model/scaling/viewport.model'; import { clipToBounds } from '../../utils/canvas/canvas-drawing-functions.utils'; const LABEL_PADDINGS = [20, 10]; export class HighlightsDrawer { constructor(highlightsModel, chartModel, canvasModel, canvasBoundsContainer, config) { this.highlightsModel = highlightsModel; this.chartModel = chartModel; this.canvasModel = canvasModel; this.canvasBoundsContainer = canvasBoundsContainer; this.config = config; } /** * Draws highlights on the chart canvas if they are visible. * @function * @name draw * @memberof ChartComponent.prototype * * @returns {void} * * @example * chartComponent.draw(); */ draw() { var _a, _b, _c, _d; if (this.config.components.highlights.visible) { const candles = this.chartModel.getCandles(); const ctx = this.canvasModel.ctx; const highlights = this.highlightsModel.getVisualHighlights(); const highlightsExist = this.highlightsModel.getHighlights().length; if (highlightsExist && candles.length !== 0 && this.chartModel.scale.isScaleReady()) { const chartBounds = this.canvasBoundsContainer.getBounds(CanvasElement.ALL_PANES); ctx.save(); //clip rect to throw away everything that doesn't fit chart bounds clipToBounds(ctx, chartBounds); const borderWidth = (_a = this.config.components.highlights.border.width) !== null && _a !== void 0 ? _a : 1; const borderDash = (_b = this.config.components.highlights.border.dash) !== null && _b !== void 0 ? _b : [0, 0]; const fontSize = (_c = this.config.components.highlights.fontSize) !== null && _c !== void 0 ? _c : 11; const fontFamily = (_d = this.config.components.highlights.fontFamily) !== null && _d !== void 0 ? _d : 'monospace'; const font = `${fontSize}px ${fontFamily}, monospace`; ctx.font = font; ctx.lineWidth = borderWidth; ctx.setLineDash(borderDash); HIGHLIGHTS_TYPES.forEach(highlightType => { var _a, _b; const items = highlights[highlightType]; if (items) { const itemColors = this.config.colors.highlights[highlightType]; const strokeStyle = (_a = itemColors === null || itemColors === void 0 ? void 0 : itemColors.border) !== null && _a !== void 0 ? _a : '#ffffff'; const fillStyle = (_b = itemColors === null || itemColors === void 0 ? void 0 : itemColors.background) !== null && _b !== void 0 ? _b : '#ffffff'; ctx.save(); // start line path to draw highlights' borders // it is done once for all highlights because it is more perfomant ctx.beginPath(); ctx.fillStyle = fillStyle; ctx.strokeStyle = strokeStyle; items.forEach(item => { var _a, _b, _c; const fromXCandle = this.chartModel.candleFromTimestamp(item.from); const fromXCandleWidth = unitToPixels(fromXCandle.width, this.chartModel.scale.zoomX); const fromX = fromXCandle.xStart(this.chartModel.scale); // currently endTime timestamp for PRE_MARKET type includes same timestamp for REGULAR type startTime // so we have to take previous timestamp based on current period to exclude PRE_MARKET highlighting const xCandleTimestamp = item.to - this.chartModel.chartBaseModel.period; const toXCandle = this.chartModel.candleFromTimestamp(xCandleTimestamp); const toXCandleWidth = unitToPixels(toXCandle.width, this.chartModel.scale.zoomX); const toX = toXCandle.xStart(this.chartModel.scale) + toXCandleWidth; // draw highlight' borders if (item.border) { this.drawBorders(item.border, ctx, fromX + fromXCandleWidth, toX - toXCandleWidth, chartBounds); } // draw highlight' background ctx.fillRect(fromX, chartBounds.y, toX - fromX, chartBounds.y + chartBounds.height); // draw highlight' label if (item.label) { const label = (_a = item.label.text) !== null && _a !== void 0 ? _a : ''; const itemColors = this.config.colors.highlights[item.type]; ctx.save(); ctx.fillStyle = (_b = itemColors === null || itemColors === void 0 ? void 0 : itemColors.label) !== null && _b !== void 0 ? _b : '#ffffff'; const labelWidth = calculateTextWidth(label, ctx, font); const [labelX, labelY] = this.resolveHighlightLabelPosition((_c = item.label.placement) !== null && _c !== void 0 ? _c : 'left-left', chartBounds, [fromX, toX], labelWidth); ctx.fillText(label, labelX, labelY); ctx.restore(); } }); ctx.closePath(); ctx.restore(); } }); ctx.restore(); } } } /** * Calculates the position of the highlight label based on the given parameters. * @param {HighlightTextPlacement} placement - The placement of the highlight text. * @param {Bounds} bounds - The bounds of the highlight. * @param {[number, number]} highlightFromTo - The start and end position of the highlight. * @param {number} labelWidth - The width of the label. * @returns {[number, number]} - The x and y position of the highlight label. */ resolveHighlightLabelPosition(placement, bounds, highlightFromTo, labelWidth) { const [fromX, toX] = highlightFromTo; switch (placement) { case 'right-left': { return [toX - LABEL_PADDINGS[1] - labelWidth, bounds.y + LABEL_PADDINGS[0]]; } case 'left-left': { return [fromX - LABEL_PADDINGS[1] - labelWidth, bounds.y + LABEL_PADDINGS[0]]; } case 'right-right': { return [toX + LABEL_PADDINGS[1], bounds.y + LABEL_PADDINGS[0]]; } case 'left-right': default: { return [fromX + LABEL_PADDINGS[1], bounds.y + LABEL_PADDINGS[0]]; } } } /** * Draws borders on a canvas context for a given chart. * @param {HighlightBorder} border - The border to draw. * @param {CanvasRenderingContext2D} ctx - The canvas context to draw on. * @param {number} fromX - The starting x-coordinate of the border. * @param {number} toX - The ending x-coordinate of the border. * @param {Bounds} chartBounds - The bounds of the chart to draw the border on. * @returns {void} */ drawBorders(border, ctx, fromX, toX, chartBounds) { const leftBorder = border.left; const rightBorder = border.right; if (leftBorder) { ctx.beginPath(); ctx.moveTo(fromX, chartBounds.y); ctx.lineTo(fromX, chartBounds.y + chartBounds.height); ctx.stroke(); ctx.closePath(); } if (rightBorder) { ctx.beginPath(); ctx.moveTo(toX, chartBounds.y); ctx.lineTo(toX, chartBounds.y + chartBounds.height); ctx.stroke(); ctx.closePath(); } } /** * Returns an array of canvas IDs. * * @returns {Array<string>} An array containing the canvas ID. */ getCanvasIds() { return [this.canvasModel.canvasId]; } }