@devexperts/dxcharts-lite
Version:
163 lines (162 loc) • 8.78 kB
JavaScript
/*
* 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];
}
}