UNPKG

@devexperts/dxcharts-lite

Version:
686 lines (685 loc) 25.7 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 { DEFAULT_MERGE_OPTIONS, merge } from './utils/merge.utils'; import { defaultSortCandles } from './model/candle.model'; export const MAIN_FONT = 'Open Sans Semibold, sans-serif'; export const LastBarRedrawableBarTypes = ['candle', 'bar', 'scatterPlot', 'trend', 'hollow', 'histogram']; export const availableBarTypes = [ 'candle', 'line', 'area', 'bar', 'scatterPlot', 'trend', 'hollow', 'histogram', 'baseline', ]; export const isLastBarRedrawAvailable = (type) => LastBarRedrawableBarTypes.find(t => t === type) !== undefined; /** * Full chart-core default config. * @doc-tags chart-core,default-config * @doc-tags-name getDefaultConfig=xl */ export const getDefaultConfig = () => ({ devexpertsPromoLink: true, useUTCTimeOverride: false, shortDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], rtl: false, intlFormatter: { decimalSeparator: '.', thousandsSeparator: '', }, scale: { keepZoomXOnYAxisChange: true, auto: true, zoomToCursor: false, lockPriceToBarRatio: false, autoScaleOnCandles: true, autoScaleDisableOnDrag: { enabled: true, edgeAngle: Math.PI / 15, yDiff: 80, }, inverse: false, zoomSensitivity: { wheel: 0.25, }, defaultViewportItems: 100, disableAnimations: false, }, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, // local timezone components: { chart: { type: 'candle', showCandlesBorder: true, showActiveCandlesBorder: true, showWicks: true, candleLineWidth: 1, lineWidth: 1, areaLineWidth: 1, barLineWidth: 1, minWidth: 0.5, // minimum candle width in canvas units - not real pixels! for mac with DPR=2 it will be equal 1 pixel minCandles: 10, candlePaddingPercent: 0.25, highlightActiveCandle: true, cursor: 'default', selectedWidth: 3, minCandlesOffset: 2, defaultZoomCandleWidth: 7, zoomStep: 0, histogram: { barCapSize: 1, }, maxYAxisScalesAmount: 10, applyBackgroundToAxes: { x: true, y: true, }, sortCandles: defaultSortCandles, }, yAxis: { type: 'regular', visible: true, labelHeight: 23, zeroPercentLine: true, customScale: true, customScaleDblClick: true, align: 'right', fontSize: 12, fontFamily: MAIN_FONT, cursor: 'ns-resize', resizeDisabledCursor: 'default', labelBoxMargin: { top: 4, bottom: 4, end: 8, start: 10, }, typeConfig: { badge: { rounded: true, paddings: { top: 4, bottom: 4, end: 4, start: 4, }, }, plain: {}, rectangle: { rounded: false, paddings: { top: 4, bottom: 4, end: 4, start: 4, }, }, }, labels: { descriptions: false, settings: { lastPrice: { mode: 'label', type: 'badge', }, countdownToBarClose: { mode: 'none', type: 'rectangle', }, }, }, treasuryFormat: { enabled: false, }, }, xAxis: { visible: true, formatsForLabelsConfig: { lessThanSecond: 'HH:mm:ss', second_1: 'HH:mm:ss', minute_1: 'HH:mm', minute_5: 'HH:mm', minute_30: 'HH:mm', hour_1: 'HH:mm', day_1: 'dd.MM', month_1: 'MMM', year_1: 'YYYY', }, fontSize: 12, fontFamily: MAIN_FONT, cursor: 'ew-resize', padding: { top: 8, bottom: 16, }, fontStyle: '', }, events: { visible: false, eventsVisibility: { 'conference-calls': true, dividends: true, splits: true, earnings: true, }, height: 20, cursor: 'default', xAxisLabelFormat: [ { format: 'd MMM', }, ], icons: { earnings: { normal: '<svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1.06066 6.5L6.5 1.06066L11.9393 6.5L6.5 11.9393L1.06066 6.5Z" stroke="#D92C40" stroke-width="1.5"/></svg>', hover: '<svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1.06066 6.5L6.5 1.06066L11.9393 6.5L6.5 11.9393L1.06066 6.5Z" fill="#D92C40" stroke="#D92C40" stroke-width="1.5"/></svg>', }, }, }, offsets: { visible: true, right: 10, top: 10, bottom: 20, left: 0, }, waterMark: { visible: false, fontFamily: 'Open Sans, sans-serif', firstRowFontSize: 80, firstRowBottomPadding: 10, secondRowFontSize: 40, secondRowBottomPadding: 25, thirdRowFontSize: 40, thirdRowBottomPadding: 15, position: 'center', offsetX: 20, offsetY: 20, logoWidth: 20, logoHeight: 20, }, highLow: { visible: false, font: '12px sans-serif', prefix: { high: 'H: ', low: 'L: ' }, lineDash: [2, 4] }, highlights: { visible: false, fontFamily: 'Open Sans', fontSize: 13, border: { width: 1, dash: [8, 4], }, }, crossTool: { type: 'cross-and-labels', discrete: true, magnetTarget: 'none', lineDash: [4, 6], xAxisLabelFormat: [ { format: 'dd.MM.YYYY', showWhen: { periodMoreThen: 24 * 60 * 60 * 1000, }, }, { format: 'dd.MM.YYYY HH:mm', showWhen: { periodLessThen: 24 * 60 * 60 * 1000, periodMoreThen: 6 * 1000, }, }, { format: 'dd.MM.YYYY HH:mm:ss', showWhen: { periodLessThen: 6 * 1000, }, }, ], xLabel: { padding: { top: 4, bottom: 4, right: 8, left: 8, }, margin: { top: 4, }, }, yLabel: { padding: { top: 4, bottom: 4, end: 4, start: 4, }, type: 'badge', }, }, grid: { visible: true, horizontal: false, vertical: true, width: 1, dash: [0, 0], color: '#FFFFFF', }, volumes: { visible: true, showSeparately: false, valueLines: 15, barCapSize: 1, volumeBarSpace: 0, volumeFillColor: '#FFFFFF', }, navigationMap: { visible: false, allCandlesHistory: true, timeLabels: { visible: false, dateFormat: 'dd.MM.YYYY HH:mm', fontFamily: 'Open Sans', fontSize: 13, padding: { x: 10, y: 1, }, }, minSliderWindowWidth: 10, cursors: { chart: 'default', buttonLeft: 'pointer', buttonRight: 'pointer', leftResizer: 'ew-resize', rightResizer: 'ew-resize', slider: 'grab', }, knots: { height: 35, width: 7, border: 0, lineWidth: 1, }, }, baseline: { cursor: 'ns-resize', dragZone: 3, height: 1, }, paneResizer: { cursor: 'ns-resize', height: 1, visible: true, fixedMode: false, dragZone: 3, }, }, colors: { candleTheme: { upColor: 'rgba(77,153,83,1)', downColor: 'rgba(217,44,64,1)', noneColor: 'rgba(255,255,255,1)', upWickColor: 'rgba(77,153,83,1)', downWickColor: 'rgba(217,44,64,1)', noneWickColor: 'rgba(255,255,255,1)', borderOpacity: 1, }, barTheme: { upColor: 'rgba(77,153,83,1)', downColor: 'rgba(217,44,64,1)', noneColor: 'rgba(255,255,255,1)' }, lineTheme: { upColor: 'rgba(77,153,83,1)', downColor: 'rgba(217,44,64,1)', noneColor: 'rgba(255,255,255,1)' }, chartAreaTheme: { backgroundMode: 'regular', backgroundColor: 'rgba(20,20,19,1)', backgroundGradientTopColor: 'red', backgroundGradientBottomColor: 'blue', gridColor: 'rgba(37,37,36,1)', }, scatterPlot: { mainColor: 'rgba(255,255,255,1)' }, areaTheme: { lineColor: 'rgba(127,120,214,1)', startColor: 'rgba(169,38,251,1)', stopColor: 'rgba(169,38,251,0.8)', }, baseLineTheme: { lowerSectionStrokeColor: 'rgba(217,44,64,1)', upperSectionStrokeColor: 'rgba(77,153,83,1)', lowerSectionFillColor: 'rgba(217, 44, 64, 0.07)', upperSectionFillColor: 'rgba(77, 153, 83, 0.07)', baselineColor: 'rgba(55,55,54,1)', }, histogram: { upCap: 'rgba(77,153,83,1)', upBottom: 'rgba(77,153,83,0.1)', upBright: 'rgba(77,153,83,0.4)', downCap: 'rgba(217,44,64,1)', downBottom: 'rgba(217,44,64,0.1)', downBright: 'rgba(217,44,64,0.4)', noneCap: 'rgba(255,255,255,1)', noneBottom: 'rgba(255,255,255,0.1)', noneBright: 'rgba(255,255,255,0.4)', }, crossTool: { lineColor: 'rgba(107,96,86,1)', labelBoxColor: 'rgba(107,96,86,1)', labelTextColor: 'rgba(255,255,255,1)', }, waterMarkTheme: { firstRowColor: 'rgba(255,255,255,0.2)', secondRowColor: 'rgba(255,255,255,0.2)', thirdRowColor: 'rgba(255,255,255,0.2)', }, highlights: { NO_TRADING: { border: 'rgba(107,96,86,1)', background: 'transparent', label: 'transparent' }, AFTER_MARKET: { border: 'rgba(107,96,86,1)', background: 'rgba(38, 251, 149, 0.05)', label: 'transparent' }, PRE_MARKET: { border: 'rgba(107,96,86,1)', background: 'rgba(255, 170, 0, 0.05)', label: 'transparent' }, REGULAR: { border: 'rgba(107,96,86,1)', background: 'transparent', label: 'transparent' }, }, activeCandleTheme: { upColor: 'rgba(98,201,93,1)', downColor: 'rgba(255,47,47,1)', noneColor: 'rgba(255,255,255,1)', upWickColor: 'rgba(98,201,93,1)', downWickColor: 'rgba(255,47,47,1)', noneWickColor: 'rgba(255,255,255,1)', borderOpacity: 0.5, }, volume: { downBarColor: 'rgba(99,30,37,1)', upBarColor: 'rgba(42,72,44,1)', noneBarColor: 'rgba(255,255,255,0.4)', upCapColor: 'rgba(42,72,44,1)', downCapColor: 'rgba(99,30,37,1)', noneCapColor: 'rgba(255,255,255,0.4)', }, highLowTheme: { highColor: 'rgba(223,222,223,1)', lowColor: 'rgba(223,222,223,1)' }, instrumentInfo: { textColor: '#aeb1b3' }, paneResizer: { lineColor: 'rgba(61,61,61,1)', bgColor: 'rgba(20,20,19,1)', bgHoverColor: 'rgba(55,55,54,0.6)', }, events: { earnings: { color: 'rgba(217,44,64,1)', normal: 'rgba(217,44,64,1)', hover: 'rgba(217,44,64,1)', line: 'rgba(217,44,64,1)', }, dividends: { color: 'rgba(169,38,251,1)', normal: 'rgba(169,38,251,1)', hover: 'rgba(169,38,251,1)', line: 'rgba(169,38,251,1)', }, splits: { color: 'rgba(244,187,63,1)', normal: 'rgba(244,187,63,1)', hover: 'rgba(244,187,63,1)', line: 'rgba(244,187,63,1)', }, 'conference-calls': { color: 'rgba(48,194,97,1)', normal: 'rgba(48,194,97,1)', hover: 'rgba(48,194,97,1)', line: 'rgba(48,194,97,1)', }, }, secondaryChartTheme: [ { lineTheme: { upColor: 'rgba(226,61,25,1)', downColor: 'rgba(226,61,25,1)', noneColor: 'rgba(226,61,25,1)', }, areaTheme: { lineColor: 'rgba(226,61,25,1)', startColor: 'rgba(226,61,25,0.8)', stopColor: 'rgba(226,61,25,0)', }, }, { lineTheme: { upColor: 'rgba(250,191,64,1)', downColor: 'rgba(250,191,64,1)', noneColor: 'rgba(250,191,64,1)', }, areaTheme: { lineColor: 'rgba(250,191,64,1)', startColor: 'rgba(250,191,64,0.8)', stopColor: 'rgba(250,191,64,0)', }, }, { lineTheme: { upColor: 'rgba(169,38,251,1)', downColor: 'rgba(169,38,251,1)', noneColor: 'rgba(169,38,251,1)', }, areaTheme: { lineColor: 'rgba(169,38,251,1)', startColor: 'rgba(169,38,251,0.8)', stopColor: 'rgba(169,38,251,0)', }, }, { lineTheme: { upColor: 'rgba(77,211,240,1)', downColor: 'rgba(77,211,240,1)', noneColor: 'rgba(77,211,240,1)', }, areaTheme: { lineColor: 'rgba(77,211,240,1)', startColor: 'rgba(77,211,240,0.8)', stopColor: 'rgba(77,211,240,0)', }, }, { lineTheme: { upColor: 'rgba(59,203,91,1)', downColor: 'rgba(59,203,91,1)', noneColor: 'rgba(59,203,91,1)', }, areaTheme: { lineColor: 'rgba(59,203,91,1)', startColor: 'rgba(59,203,91,0.8)', stopColor: 'rgba(59,203,91,0)', }, }, ], yAxis: { backgroundColor: 'rgba(20,20,19,1)', labelBoxColor: 'rgba(20,20,19,1)', labelTextColor: 'rgba(128,128,128,1)', labelInvertedTextColor: 'rgba(20,20,19,1)', rectLabelTextColor: 'rgba(255,255,255,1)', rectLabelInvertedTextColor: 'rgba(20,20,19,1)', zeroPercentLine: 'rgba(55,55,54,1)', }, labels: { lastPrice: { textNegative: 'rgba(255,255,255,1)', textPositive: 'rgba(255,255,255,1)', textSelected: 'rgba(0,0,0,1)', boxNegative: 'rgba(217,44,64,1)', boxPositive: 'rgba(77,153,83,1)', boxSelected: 'rgba(255,255,255,1)', }, countdownToBarClose: { textNegative: 'rgba(255,255,255,1)', textPositive: 'rgba(255,255,255,1)', textSelected: 'rgba(255,255,255,1)', boxNegative: 'rgba(217,44,64,1)', boxPositive: 'rgba(77,153,83,1)', boxSelected: 'rgba(255,255,255,1)', }, highLow: { high: { boxColor: 'rgba(107,96,86,1)', textColor: 'rgba(255,255,255,1)', descriptionText: 'High' }, low: { boxColor: 'rgba(107,96,86,1)', textColor: 'rgba(255,255,255,1)', descriptionText: 'Low' }, }, bidAsk: { bid: { boxColor: 'rgba(77,153,83,1)', textColor: 'rgba(255,255,255,1)', descriptionText: 'Bid' }, ask: { boxColor: 'rgba(217,44,64,1)', textColor: 'rgba(255,255,255,1)', descriptionText: 'Ask' }, }, prePostMarket: { post: { boxColor: 'rgba(38,251,149,1)', textColor: 'rgba(20,20,19,1)', descriptionText: 'Post' }, pre: { boxColor: 'rgba(255,170,0,1)', textColor: 'rgba(20,20,19,1)', descriptionText: 'Pre' }, }, prevDayClose: { boxColor: 'rgba(107,96,86,1)', textColor: 'rgba(255,255,255,1)' }, }, xAxis: { backgroundColor: 'rgba(20,20,19,1)', labelTextColor: 'rgba(128,128,128,1)', }, navigationMap: { backgroundColor: 'transparent', buttonColor: 'rgba(255,255,255,0.1)', buttonArrowColor: 'rgba(212,212,211,1)', knotColor: 'rgba(255,255,255,0.1)', knotLineColor: 'rgba(212,212,211,1)', sliderColor: 'rgba(255,255,255,0.08)', knotBorderColor: '#0b0d1a', timeLabelsTextColor: 'rgba(128,128,128,1)', mapColor: 'rgba(255,255,255,0.1)', mapFillColor: 'rgba(255,255,255,0.1)', mapGradientTopColor: 'rgba(255,255,255,0.1)', mapGradientBottomColor: 'rgba(255,255,255,0.1)', }, }, animation: { moveDuration: 1000, candleDuration: 200, paneResizer: { bgMode: true, enabled: true, duration: 40, }, yAxis: { background: { enabled: false, duration: 40, }, }, }, drawingOrder: [ 'OVER_SERIES_CLEAR', 'MAIN_CLEAR', 'SERIES_CLEAR', 'GRID', 'X_AXIS', 'Y_AXIS', 'DYNAMIC_OBJECTS', 'WATERMARK', 'N_MAP_CHART', 'EVENTS', ], }); /** * Merges a partial chart configuration object with a default chart configuration object. * @param {PartialChartConfig} config - The partial chart configuration object to merge. * @param {FullChartConfig} defaultConfig - The default chart configuration object to merge with. * @returns {FullChartConfig} - The merged chart configuration object. */ export function mergeWithDefaultConfig(config, defaultConfig = getDefaultConfig()) { merge(config, defaultConfig, DEFAULT_MERGE_OPTIONS); // eslint-disable-next-line no-restricted-syntax return config; } /** * Merges a partial chart configuration object with the default configuration object and returns a new object with the merged values. * @param {PartialChartConfig} config - The partial chart configuration object to merge with the default configuration object. * @param {FullChartConfig} defaultConfig - The default chart configuration object to merge with the partial configuration object. If not provided, the default configuration object will be retrieved using getDefaultConfig() function. * @returns {FullChartConfig} - A new object with the merged values of the partial and default configuration objects. * @todo Implement deep copy of the partial configuration object before merging. */ export function mergeWithDefaultConfigCopy(config, defaultConfig = getDefaultConfig()) { // TODO deep copy // @ts-ignore const result = Object.assign({}, config); merge(result, defaultConfig, DEFAULT_MERGE_OPTIONS); return result; } /** * This function rewrites the properties of an object with the properties of another object. * @param {object} current - The object to be rewritten. * @param {object} newObj - The object containing the new properties. * @returns {void} */ export function rewrite(current, newObj) { Object.keys(current).forEach(key => delete current[key]); Object.keys(newObj).forEach(key => (current[key] = newObj[key])); } /** * Checks if a value is a non-null object. * @param {unknown} value - The value to check. * @returns {boolean} - True if the value is a non-null object, false otherwise. */ function isNonNullObject(value) { return typeof value === 'object' && value !== null; } /** * Creates a new array with the same elements as the provided array. * * @param {unknown[]} arr - The array to be copied. * @param {MergeOptions} options - The options to be used when cloning the elements of the array. * @returns {unknown[]} A new array with the same elements as the provided array. */ function copyArray(arr, options) { const arrWithoutHoles = [...arr]; // destructuring converts holes to undefined return arrWithoutHoles.map(item => clone(item, options)); } const isArray = (value) => Array.isArray(value); /** * Clones an object or array using the provided options. * * @param {unknown} value - The value to clone. * @param {MergeOptions} options - The options to use when cloning. * @returns {unknown} - The cloned object or array. */ function clone(value, options) { if (!isNonNullObject(value)) { return value; } if (isArray(value)) { return copyArray(value, options); } else { const copy = immutableMerge({}, value, options); Object.setPrototypeOf(copy, Object.getPrototypeOf(value)); return copy; } } /** * This function performs an immutable merge of two objects, `base` and `override`, and returns a new object with the merged properties. If `base` is not a non-null object, it returns either `override` or `base`, depending on the value of `options.overrideExisting`. If both `base` and `override` are arrays, it returns a new array with the merged elements. If `base` and `override` are objects, it creates a new object with the prototype of `base` and copies all properties of `base` to the new object. Then, it iterates over all properties of `override` and performs a recursive merge of the corresponding properties in `base` and `override`. If a property exists only in `override` and `options.addIfMissing` is true, it adds the property to the new object. The function uses the `clone` and `copyArray` functions to create copies of objects and arrays, respectively. * * @param {object} base - The base object to merge. * @param {object} override - The object to merge into `base`. * @param {object} options - An object with options for the merge operation. * @param {boolean} options.overrideExisting - If true, override existing properties in `base`. If false, keep the properties in `base`. * @param {boolean} options.addIfMissing - If true, add properties from `override` that do not exist in `base`. * @returns {object} - A new object with the merged properties. */ export function immutableMerge(base, override, options) { if (!isNonNullObject(base)) { return options.overrideExisting ? override : base; } if (Array.isArray(base) && Array.isArray(override)) { const arr = options.overrideExisting ? override : base; return copyArray(arr, options); } const result = Object.create(Object.getPrototypeOf(base)); Object.keys(base).forEach(key => (result[key] = clone(base[key], options))); Object.keys(override).forEach(key => { if (key in base) { result[key] = immutableMerge(base[key], override[key], options); } else if (options.addIfMissing) { result[key] = clone(override[key], options); } }); return result; } export const getFontFromConfig = (config) => `${config.fontSize}px ${config.fontFamily}`;