@devexperts/dxcharts-lite
Version:
686 lines (685 loc) • 25.7 kB
JavaScript
/*
* 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}`;