UNPKG

@devexperts/dxcharts-lite

Version:
274 lines (273 loc) 12 kB
/* * 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; } }