UNPKG

@devexperts/dxcharts-lite

Version:
250 lines (249 loc) 13.1 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 { CanvasElement } from '../../canvas/canvas-bounds-container'; import { getTextLines, drawText, getTextLineHeight, prepareTextForFill, } from '../../utils/canvas/canvas-text-functions.utils'; import { getFormattedTimeLabel } from './navigation-map.model'; import { flat } from '../../utils/array.utils'; const BTN_ARROW_WIDTH = 4; export class NavigationMapDrawer { constructor(config, chartModel, canvasModel, canvasBoundsContainer, formatterFactory, visualCandlesProvider) { this.config = config; this.chartModel = chartModel; this.canvasModel = canvasModel; this.canvasBoundsContainer = canvasBoundsContainer; this.formatterFactory = formatterFactory; this.visualCandlesProvider = visualCandlesProvider; } /** * Draws the navigation map on the canvas if it is visible in the configuration. * It first gets the bounds of all the necessary elements on the canvas. * Then it fills the background of the navigation map with the configured color. * Draws a line at the top of the navigation map with the configured color. * Draws the left and right arrow buttons if their width is not zero. * Draws the chart with the configured colors and fills it with a gradient if the gradient colors are configured. * Draws the slider with the left and right knots and the slider window. * Draws the time labels. * @returns {void} */ draw() { if (this.config.components.navigationMap.visible) { const candles = this.visualCandlesProvider(); if (candles.length) { const nMap = this.canvasBoundsContainer.getBounds(CanvasElement.N_MAP); const btnL = this.canvasBoundsContainer.getBounds(CanvasElement.N_MAP_BTN_L); const btnR = this.canvasBoundsContainer.getBounds(CanvasElement.N_MAP_BTN_R); const knotL = this.canvasBoundsContainer.getBounds(CanvasElement.N_MAP_KNOT_L); const knotR = this.canvasBoundsContainer.getBounds(CanvasElement.N_MAP_KNOT_R); const slider = this.canvasBoundsContainer.getBounds(CanvasElement.N_MAP_SLIDER_WINDOW); const chart = this.canvasBoundsContainer.getBounds(CanvasElement.N_MAP_CHART); const ctx = this.canvasModel.ctx; ctx.fillStyle = this.config.colors.navigationMap.backgroundColor; ctx.fillRect(nMap.x, nMap.y, nMap.width, nMap.height); // bar resizer at the very top ctx.strokeStyle = this.config.colors.paneResizer.lineColor; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(nMap.x, nMap.y); ctx.lineTo(nMap.x + nMap.width, nMap.y); ctx.closePath(); ctx.stroke(); // btn left and right if (btnL.width !== 0) { this.drawLeftArrowButton(ctx, btnL, this.config); } if (btnR.width !== 0) { this.drawRightArrowButton(ctx, btnR, this.config); } // chart ctx.fillStyle = this.config.colors.navigationMap.mapFillColor; ctx.beginPath(); ctx.moveTo(chart.x, chart.y + chart.height); for (const candle of candles) { ctx.lineTo(candle[0], candle[1]); } ctx.lineTo(chart.x + chart.width, chart.y + chart.height); ctx.lineTo(chart.x, chart.y + chart.height); ctx.closePath(); if (this.config.colors.navigationMap.mapGradientTopColor && this.config.colors.navigationMap.mapGradientBottomColor) { const grd = ctx.createLinearGradient(chart.x, chart.y, chart.x, chart.y + chart.height); grd.addColorStop(0, this.config.colors.navigationMap.mapGradientTopColor); grd.addColorStop(1, this.config.colors.navigationMap.mapGradientBottomColor); ctx.fillStyle = grd; } ctx.fill(); if (this.config.colors.navigationMap.mapColor) { ctx.strokeStyle = this.config.colors.navigationMap.mapColor; ctx.stroke(); } // slider this.drawSlider(ctx, knotL, knotR, btnL, slider); // time labels this.drawTimeLabels(ctx); } } } /** * Moves the left knot and the slider to the right of the left arrow button if the left knot is to the left of the button. * @param {Bounds} knotL - The bounds of the left knot. * @param {Bounds} knotR - The bounds of the right knot. * @param {Bounds} btnL - The bounds of the left arrow button. * @param {Bounds} slider - The bounds of the slider. */ blockDrawSliderOnLeftArrow(knotL, knotR, btnL, slider) { if (knotL.x <= btnL.x + btnL.width) { knotL.x = btnL.x + btnL.width; slider.x = knotL.x + knotL.width; knotR.x = slider.x + slider.width; } } /** * Draws a slider on a canvas context with given bounds for knots, button and slider. * @param {CanvasRenderingContext2D} ctx - The canvas context to draw on. * @param {Bounds} knotL - The bounds for the left knot. * @param {Bounds} knotR - The bounds for the right knot. * @param {Bounds} btnL - The bounds for the left button. * @param {Bounds} slider - The bounds for the slider. * @returns {void} */ drawSlider(ctx, knotL, knotR, btnL, slider) { //Slider can't move above the left arrow this.blockDrawSliderOnLeftArrow(knotL, knotR, btnL, slider); // knots this.drawKnotButton(ctx, knotL, this.config, true); this.drawKnotButton(ctx, knotR, this.config, false); // slider ctx.fillStyle = this.config.colors.navigationMap.sliderColor; ctx.fillRect(slider.x, slider.y, slider.width, slider.height); } /** * Draws a left arrow button on a canvas context with the provided bounds and configuration * @param {CanvasRenderingContext2D} ctx - The canvas context to draw on * @param {Bounds} bounds - The bounds of the button to be drawn * @param {FullChartConfig} config - The configuration object containing the colors to be used for the button and arrow * @returns {void} */ drawLeftArrowButton(ctx, bounds, config) { ctx.fillStyle = config.colors.navigationMap.buttonColor; ctx.fillRect(bounds.x, bounds.y, bounds.width, bounds.height); ctx.beginPath(); const leftX = Math.round(bounds.x + bounds.width / 2 - BTN_ARROW_WIDTH / 2); const midY = Math.round(bounds.y + bounds.height / 2); ctx.moveTo(leftX, midY); ctx.lineTo(leftX + BTN_ARROW_WIDTH, midY + BTN_ARROW_WIDTH); ctx.lineTo(leftX + BTN_ARROW_WIDTH, midY - BTN_ARROW_WIDTH); ctx.fillStyle = config.colors.navigationMap.buttonArrowColor; ctx.fill(); } /** * Draws a right arrow button on a canvas context with the provided bounds and configuration * @param {CanvasRenderingContext2D} ctx - The canvas context to draw on * @param {Bounds} bounds - The bounds of the button to be drawn * @param {FullChartConfig} config - The configuration object containing the colors to be used for the button and arrow * @returns {void} */ drawRightArrowButton(ctx, bounds, config) { ctx.fillStyle = config.colors.navigationMap.buttonColor; ctx.fillRect(bounds.x, bounds.y, bounds.width, bounds.height); ctx.beginPath(); const leftX = Math.round(bounds.x + bounds.width / 2 - BTN_ARROW_WIDTH / 2); const midY = Math.round(bounds.y + bounds.height / 2); ctx.moveTo(leftX, midY - BTN_ARROW_WIDTH); ctx.lineTo(leftX, midY + BTN_ARROW_WIDTH); ctx.lineTo(leftX + BTN_ARROW_WIDTH, midY); ctx.fillStyle = config.colors.navigationMap.buttonArrowColor; ctx.fill(); } /** * Draws a knot button on a canvas context with the given bounds and configuration. * @param {CanvasRenderingContext2D} ctx - The canvas context to draw on. * @param {Bounds} bounds - The bounds of the knot button. * @param {FullChartConfig} config - The configuration for the chart. * @param {boolean} isLeft - Whether the knot button is on the left side of the chart. * @returns {void} */ drawKnotButton(ctx, bounds, config, isLeft) { const borderWidth = config.components.navigationMap.knots.border; ctx.fillStyle = config.colors.navigationMap.knotColor; const xWithBorder = isLeft ? bounds.x - borderWidth : bounds.x + borderWidth; ctx.fillRect(xWithBorder, bounds.y, bounds.width, bounds.height); ctx.lineWidth = config.components.navigationMap.knots.lineWidth; if (borderWidth > 0) { ctx.beginPath(); ctx.moveTo(xWithBorder, bounds.y); ctx.lineTo(xWithBorder, bounds.y + bounds.height); ctx.lineTo(xWithBorder + bounds.width, bounds.y + bounds.height); ctx.lineTo(xWithBorder + bounds.width, bounds.y); ctx.lineTo(xWithBorder, bounds.y); ctx.lineWidth = borderWidth; ctx.strokeStyle = config.colors.navigationMap.knotBorderColor; ctx.stroke(); } ctx.beginPath(); ctx.moveTo(xWithBorder + bounds.width / 2, bounds.y + bounds.height / 4); ctx.lineTo(xWithBorder + bounds.width / 2, bounds.y + (3 * bounds.height) / 4); ctx.strokeStyle = config.colors.navigationMap.knotLineColor; ctx.stroke(); } /** * Draws time labels on the navigation map canvas. * @param {CanvasRenderingContext2D} ctx - The 2D rendering context for the canvas. * @returns {void} */ drawTimeLabels(ctx) { const candleSource = flat(this.chartModel.mainCandleSeries.getSeriesInViewport()); const timeLabelsConfig = this.config.components.navigationMap.timeLabels; const timeLabelsVisible = timeLabelsConfig.visible; if (candleSource.length && timeLabelsVisible) { const startViewportTimestamp = candleSource[0].candle.timestamp; const endViewportTimestamp = candleSource[candleSource.length - 1].candle.timestamp; const textLabelL = this.canvasBoundsContainer.getBounds(CanvasElement.N_MAP_LABEL_L); const textLabelR = this.canvasBoundsContainer.getBounds(CanvasElement.N_MAP_LABEL_R); const timeLabelsColor = this.config.colors.navigationMap.timeLabelsTextColor; const textProperties = { textFill: timeLabelsColor, textFontFamily: timeLabelsConfig.fontFamily, textSize: `${timeLabelsConfig.fontSize}px`, }; const leftLabelProperties = { dateFormat: timeLabelsConfig.dateFormat, padding: timeLabelsConfig.padding, textProperties: Object.assign(Object.assign({}, textProperties), { textAlign: 'left' }), }; const rightLabelProperties = { dateFormat: timeLabelsConfig.dateFormat, padding: timeLabelsConfig.padding, textProperties: Object.assign(Object.assign({}, textProperties), { textAlign: 'right' }), }; this.drawTimeLabel(ctx, textLabelL, startViewportTimestamp, leftLabelProperties); this.drawTimeLabel(ctx, textLabelR, endViewportTimestamp, rightLabelProperties); } } /** * Draws a time label on a canvas context. * @private * @param {CanvasRenderingContext2D} ctx - The canvas context to draw on. * @param {Bounds} bounds - The bounds of the label. * @param {number} timestamp - The timestamp to display. * @param {NMapTimeLabelProperties} config - The configuration for the label. * @returns {void} */ drawTimeLabel(ctx, bounds, timestamp, config) { const padding = config.padding; const text = getFormattedTimeLabel(timestamp, config.dateFormat, this.formatterFactory); prepareTextForFill(ctx, config.textProperties); const lineHeight = getTextLineHeight(ctx); const lines = getTextLines(text); drawText(ctx, lines, bounds.x + padding.x, bounds.y + lineHeight + padding.y, config.textProperties); } /** * Returns an array of string containing the canvas ID. * @returns {Array<string>} An array of string containing the canvas ID. */ getCanvasIds() { return [this.canvasModel.canvasId]; } }