@devexperts/dxcharts-lite
Version:
160 lines (159 loc) • 7.42 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 { 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 viewportModel = this.getViewportModel();
if (!viewportModel.isViewportValid()) {
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));
candles.forEach((vCandle, idx) => {
if (vCandle.candle.volume) {
const bounds = viewportModel.getBounds();
const fullVHeight = bounds.height;
if (fullVHeight <= 0) {
return;
}
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 volumeHeightDivisor = fullVHeight / OVERLAY_VOLUME_TOTAL_HEIGHT_DIVISOR;
if (volumeHeightDivisor <= 0) {
return;
}
const zoomY = volumeMax / volumeHeightDivisor;
if (!isFinite(zoomY) || zoomY <= 0) {
return;
}
const calculatedHeight = Math.max(ceilToDPR(unitToPixels(vCandle.candle.volume, zoomY)), 2);
const height = Math.min(calculatedHeight, fullVHeight);
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);
}
}
}