@devexperts/dxcharts-lite
Version:
274 lines (273 loc) • 12 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/.
*/
/*
* Copyright (C) 2019 - 2024 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 { Subject } from 'rxjs';
import { getDefaultConfig } from '../chart.config';
import { defaultCandleTransformer } from '../components/chart/candle-transformer.functions';
import { calculateCandleWidth } from '../components/chart/candle-width-calculator.functions';
import { binarySearch, lastOf } from '../utils/array.utils';
import { merge } from '../utils/merge.utils';
import { PriceIncrementsUtils } from '../utils/price-increments.utils';
import { calculateCandlesHighLow, createCandleSeriesHighLowProvider } from './candle-series-high-low.provider';
import { BASIC_CANDLE_WIDTH, nameDirection } from './candle.model';
import { DataSeriesModel } from './data-series.model';
import { getDefaultHighLowWithIndex } from './scale.model';
const DEFAULT_CANDLE_SERIES_CONFIG = getDefaultConfig().colors;
export class CandleSeriesModel extends DataSeriesModel {
get dataPoints() {
return super.dataPoints;
}
set dataPoints(points) {
super.dataPoints = points;
this.applyPriceMovement();
}
get instrument() {
return this._instrument;
}
set instrument(instrument) {
var _a;
this._instrument = instrument;
this.pricePrecisions = PriceIncrementsUtils.computePrecisions((_a = instrument.priceIncrements) !== null && _a !== void 0 ? _a : [0.01]);
}
constructor(extentComponent, id, htId, eventBus, scale, instrument, candlesTransformersByChartType, candleWidthByChartType, colors = DEFAULT_CANDLE_SERIES_CONFIG) {
super(extentComponent, id, htId);
this.eventBus = eventBus;
this.candlesTransformersByChartType = candlesTransformersByChartType;
this.candleWidthByChartType = candleWidthByChartType;
this.colors = colors;
// high low in absolute price values
this.zippedHighLow = getDefaultHighLowWithIndex();
this.lastPriceMovement = 'none';
this.lastVisualCandleChangedSubject = new Subject();
// start/end candle index in viewport
this.meanCandleWidth = BASIC_CANDLE_WIDTH;
this._instrument = instrument;
this.instrument = instrument;
this.highLowProvider = createCandleSeriesHighLowProvider(this);
this.scale = scale;
this.name = instrument.symbol;
}
/**
* Recalculates data viewport indexes based on xStart and xEnd parameters or values from scaleModel.
* Calls superclass method for calculation, recalculates zipped high/low data points, and fires draw event.
*
* @param {number} [xStart=this.scale.xStart] - Start index of visible data range.
* @param {number} [xEnd=this.scale.xEnd] - End index of visible data range.
* @returns {void}
*/
recalculateDataViewportIndexes(xStart = this.scale.xStart, xEnd = this.scale.xEnd) {
const { dataIdxStart, dataIdxEnd } = this.calculateDataViewportIndexes(xStart, xEnd);
this.dataIdxStart = dataIdxStart;
this.dataIdxEnd = dataIdxEnd;
this.recalculateZippedHighLow();
this.eventBus.fireDraw();
}
/**
* Calculates and returns the indexes of the start and end points of the data viewport,
* based on the given start and end units on the x-axis.
*
* @param {Unit} xStart - The start value of the viewport on the x-axis.
* @param {Unit} xEnd - The end value of the viewport on the x-axis.
* @returns {DataSeriesViewportIndexes} An object containing the calculated start and end indexes of the data viewport.
*/
calculateDataViewportIndexes(xStart, xEnd) {
const dataIdxStart = binarySearch(this.visualPoints, xStart, (it) => it.startUnit).index;
const dataIdxEnd = binarySearch(this.visualPoints, xEnd, (it) => it.startUnit).index;
return {
dataIdxStart,
dataIdxEnd,
};
}
/**
* Calculates the price movement of the last candle by comparing the open and close prices.
* Sets the lastPriceMovement property of the instance with the name of the direction of the price movement.
*
* @returns {void}
*/
applyPriceMovement() {
const lastCandle = lastOf(this.dataPoints);
if (lastCandle) {
this.lastPriceMovement = nameDirection(lastCandle.open, lastCandle.close);
}
}
/**
* Should be called 1 time when candles are set / updated to chart.
*/
recalculateVisualPoints() {
super.recalculateVisualPoints();
this.recalculateMeanCandleWidth(this.visualPoints);
}
/**
* Used for optimization when we have to update only the last candle
* @doc-tags tricky
*/
// recalculateOnlyLastVisualCandle() {
// const prev = this.visualPointsFlat[this.visualPointsFlat.length - 2];
// const last = this.visualPointsFlat[this.visualPointsFlat.length - 1];
// if (last && prev) {
// const startX = this.visualPointsFlat[prev.idx ?? 0].startUnit;
// const updatedCandle = lastOf(this.toVisualCandles([prev, last], startX));
// if (updatedCandle) {
// this.visualCandleSource[this.visualCandleSource.length - 1] = updatedCandle;
// this.lastVisualCandleChangedSubject.next();
// this.eventBus.fire(EVENT_DRAW_LAST_CANDLE);
// }
// }
// }
/**
* Recalculates and returns the zipped high-low values for the visible data range.
* Uses the visualPoints, dataIdxStart, and dataIdxEnd properties of the instance to calculate high-low values.
*
* @returns {HighLowWithIndex} - An object containing the high-low values along with their corresponding indexes.
*/
recalculateZippedHighLow() {
return (this.zippedHighLow = calculateCandlesHighLow(this.visualPoints.slice(this.dataIdxStart, this.dataIdxEnd + 1)));
}
/**
* Updates the current price and the last price movement.
* Compares the current price with the previous price, and sets the lastPriceMovement property accordingly.
*
* @param {number} currentPrice - The current price of the asset.
* @returns {void}
*/
updateCurrentPrice(currentPrice) {
this.previousPrice = this.currentPrice || currentPrice;
this.currentPrice = currentPrice;
if (this.currentPrice !== this.previousPrice) {
this.lastPriceMovement = this.currentPrice > this.previousPrice ? 'up' : 'down';
}
}
/**
* Updates the colors used to render the candlestick series and recalculates the visual points.
* Merges the newConfig object with the existing colors object of the instance.
* Calls the recalculateVisualPoints method of the instance to update the visual points using the new color configuration.
*
* @param {PartialCandleSeriesColors} newConfig - The new color configuration to be merged with the existing colors object.
* @returns {void}
*/
updateCandleSeriesColors(newConfig) {
this.colors = merge(newConfig, this.colors);
this.recalculateVisualPoints();
}
/**
* Returns an observable that emits an event when the last visual candle changes.
* Returns the observable associated with the lastVisualCandleChangedSubject of the instance.
*
* @returns {Observable<void>} - An observable that emits an event when the last visual candle changes.
*/
observeLastVisualCandleChanged() {
return this.lastVisualCandleChangedSubject.asObservable();
}
doDeactivate() {
super.doDeactivate();
}
/**
* Transforms candles list into visual candles.
* NOTE: should be used only on FULL candles array, no single transformations, since candle coordinates depend on each other.
* OR provide startX :)
* @param candles
* @param startX
*/
toVisualPoints(candles, startX = 0) {
var _a, _b;
if (candles.length === 0) {
return [];
}
const type = this.config.type;
const visualCandles = [];
let accumulatedWidth = startX;
const calculator = (_a = this.candleWidthByChartType[type]) !== null && _a !== void 0 ? _a : calculateCandleWidth;
for (let i = 0; i < candles.length; i++) {
const candle = candles[i];
const prevCandle = candles[i - 1];
const width = calculator(candle);
// x - middle of candle
const x = accumulatedWidth + width / 2;
const transformer = (_b = this.candlesTransformersByChartType[type]) !== null && _b !== void 0 ? _b : defaultCandleTransformer;
visualCandles.push(transformer(candle, { x, width, prevCandle, activeCandle: this.activeCandle }, visualCandles[i - 1]));
accumulatedWidth += width;
}
return visualCandles;
}
/**
* Recalculates the mean candle width using the given array of visual candles.
* Calculates the sum of widths of all visual candles and divides it by the total number of candles to get the mean candle width.
* Sets the meanCandleWidth property of the instance with the calculated value.
*
* @param {VisualCandle[]} visualCandles - An array of visual candles to be used for mean candle width calculation.
* @returns {void}
*/
recalculateMeanCandleWidth(visualCandles) {
if (visualCandles.length !== 0) {
this.meanCandleWidth = visualCandles.reduce((acc, cur) => acc + cur.width, 0) / visualCandles.length;
}
else {
this.meanCandleWidth = BASIC_CANDLE_WIDTH;
}
}
/**
* Sets the active candle for the chart and recalculates the visual points.
* Sets the activeCandle property of the instance with the provided candle parameter.
* Calls the recalculateVisualPoints method of the instance to recalculate the visual points using the new active candle.
*
* @param {Candle} candle - The candle to be set as active candle.
* @returns {void}
*/
setActiveCandle(candle) {
this.activeCandle = candle;
this.recalculateVisualPoints();
}
/**
* Clears all the data and visual elements from the chart.
* Sets the dataPoints property of the instance to an empty array.
* Calls the clearVisualCandles and clearPrices methods of the instance to clear the visual elements from the chart.
*
* @returns {void}
*/
clearData() {
this.dataPoints = [];
this.clearVisualCandles();
this.clearPrices();
}
/**
* Clears the price-related properties of the instance.
* Sets the previousPrice, currentPrice and lastPriceMovement properties of the instance to undefined, undefined, and 'none' respectively.
*
* @returns {void}
*/
clearPrices() {
this.previousPrice = undefined;
this.currentPrice = undefined;
this.lastPriceMovement = 'none';
}
/**
* Clears the visualPoints property of the instance.
* Sets the visualPoints property of the instance to an empty array.
*
* @returns {void}
*/
clearVisualCandles() {
this.visualPoints = [];
}
}
/**
* Checks if the provided chart type is linked.
* @param {BarType} chartType - The type of chart to check.
* @returns {boolean} - Returns true if the chart type is linked, false otherwise.
*/
export function isLinked(chartType) {
switch (chartType) {
case 'line':
case 'area':
return true;
default:
return false;
}
}