UNPKG

@devexperts/dxcharts-lite

Version:
146 lines (145 loc) 6.88 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 { clipToBounds } from '../../utils/canvas/canvas-drawing-functions.utils'; import { unitToPixels } from '../../model/scaling/viewport.model'; import { flat } from '../../utils/array.utils'; import { ceilToDPR, floorToDPR } from '../../utils/device/device-pixel-ratio.utils'; import { resolveColorUsingConfig } from './volume-color-resolvers.functions'; // ratio of CHART bounds height which is for overlay volumes const OVERLAY_VOLUME_TOTAL_HEIGHT_DIVISOR = 3; export class VolumesDrawer { constructor(config, volumesModel, chartModel, getViewportModel, volumesColorByChartTypeMap, drawPredicate) { this.config = config; this.volumesModel = volumesModel; this.chartModel = chartModel; this.getViewportModel = getViewportModel; this.volumesColorByChartTypeMap = volumesColorByChartTypeMap; this.drawPredicate = drawPredicate; this.volumeBarColors = { down: '#FF00FF', up: '#FF00FF', none: '#FF00FF', }; } /** * This function is used to calculate the colors for each volume bar. It is an optimization to avoid calculating the color for each volume, which can be expensive. * @param {BarType} barType - The type of bar to calculate the color for. * @returns {void} */ calculateColors(barType) { var _a; const getVolumeBarColor = (_a = this.volumesColorByChartTypeMap[barType]) !== null && _a !== void 0 ? _a : resolveColorUsingConfig; this.volumeBarColors['down'] = getVolumeBarColor('down', this.config.colors); this.volumeBarColors['up'] = getVolumeBarColor('up', this.config.colors); this.volumeBarColors['none'] = getVolumeBarColor('none', this.config.colors); } /** * Draws the volumes on the chart canvas if the volumes are visible and the drawPredicate is true. * Calculates the colors based on the chart type and saves the canvas context. * Clips the canvas to the viewport bounds and draws the volumes. * Restores the canvas context. * @returns {void} */ draw(canvasModel) { if (this.config.components.volumes.visible && this.drawPredicate()) { this.calculateColors(this.config.components.chart.type); const ctx = canvasModel.ctx; ctx.save(); const bounds = this.getViewportModel().getBounds(); clipToBounds(ctx, bounds); this.drawVolumes(canvasModel); ctx.restore(); } } /** * Draws the last bar of the chart if the volumes component is visible. * TODO: Rework the function. * * @returns {void} */ drawLastBar() { if (this.config.components.volumes.visible) { // TODO rework // this.calculateColors(this.config.components.chart.type); // const ctx = this.canvasModel.ctx; // const lastVolume = lastOf(this.volumes.data); // if (lastVolume) { // ctx.fillStyle = this.volumeBarColors[lastVolume.direction]; // const { width, height, x, y } = lastVolume; // ctx.fillRect(x, y, width, height); // } } } /** * Draws volumes on the chart based on the data provided by the volumesModel and chartModel. * If the volumeMax is 0, the function will return without drawing anything. * The function loops through all the candles in the viewport and calculates the x, y, width and height of each volume bar. * If the config.components.volumes.showSeparately is true, the function will draw each volume bar separately. * Otherwise, it will calculate the zoomY based on the volumeMax and the fullVHeight, and draw all the volume bars together. * @private */ drawVolumes(canvasModel) { const volumeMax = this.volumesModel.volumeMax.getValue(); if (volumeMax === 0) { return; } const candles = flat(this.chartModel.mainCandleSeries // TODO volumes drawer should be a part of data series drawer .getSeriesInViewport(this.chartModel.scale.xStart - 1, this.chartModel.scale.xEnd + 1)); const viewportModel = this.getViewportModel(); candles.forEach((vCandle, idx) => { if (vCandle.candle.volume) { const bounds = viewportModel.getBounds(); const fullVHeight = bounds.height; const nextX = candles[idx + 1] !== undefined ? floorToDPR(viewportModel.toX(candles[idx + 1].startUnit)) : undefined; const x = floorToDPR(viewportModel.toX(vCandle.startUnit)); const width = nextX !== undefined ? nextX - x : floorToDPR(unitToPixels(vCandle.width, viewportModel.zoomX)); if (this.config.components.volumes.showSeparately) { const y = floorToDPR(viewportModel.toY(vCandle.candle.volume)); const height = ceilToDPR(viewportModel.toY(0)) - y; this.drawVolume(canvasModel, vCandle, x, y, width, height); } else { const zoomY = volumeMax / (fullVHeight / OVERLAY_VOLUME_TOTAL_HEIGHT_DIVISOR); const height = Math.max(ceilToDPR(unitToPixels(vCandle.candle.volume, zoomY)), 2); const y = floorToDPR(bounds.y + fullVHeight - height); this.drawVolume(canvasModel, vCandle, x, y, width, height); } } }); } /** * Draws a volume bar on the canvas. * * @private * @param {VisualCandle} vCandle - The visual candle object. * @param {Pixel} x - The x coordinate of the top-left corner of the volume bar. * @param {Pixel} y - The y coordinate of the top-left corner of the volume bar. * @param {Pixel} width - The width of the volume bar. * @param {Pixel} height - The height of the volume bar. * @returns {void} */ drawVolume(canvasModel, vCandle, x, y, width, height) { const ctx = canvasModel.ctx; const yStart = y; const yEnd = y + height; const direction = vCandle.name; ctx.fillStyle = this.volumeBarColors[direction]; ctx.strokeStyle = this.volumeBarColors[direction]; if (width === 0) { // just draw a vertical line ctx.beginPath(); ctx.moveTo(x, yStart); ctx.lineTo(x, ceilToDPR(yEnd)); ctx.stroke(); } else { ctx.fillRect(x, y, width, height); } } }