/*!
* @license
* TradingView Lightweight Charts™ v5.2.0
* Copyright (c) 2026 TradingView, Inc.
* Licensed under Apache License 2.0 https://www.apache.org/licenses/LICENSE-2.0
*/
(function () {
'use strict';
const customStyleDefaults$1 = {
color: '#2196f3',
};
const seriesOptionsDefaults = {
title: '',
visible: true,
hitTestTolerance: 3,
lastValueVisible: true,
priceLineVisible: true,
priceLineSource: 0 /* PriceLineSource.LastBar */,
priceLineWidth: 1,
priceLineColor: '',
priceLineStyle: 2 /* LineStyle.Dashed */,
baseLineVisible: true,
baseLineWidth: 1,
baseLineColor: '#B2B5BE',
baseLineStyle: 0 /* LineStyle.Solid */,
priceFormat: {
type: 'price',
precision: 2,
minMove: 0.01,
},
};
/**
* Represents the possible line types.
*/
var LineType;
(function (LineType) {
/**
* A line.
*/
LineType[LineType["Simple"] = 0] = "Simple";
/**
* A stepped line.
*/
LineType[LineType["WithSteps"] = 1] = "WithSteps";
/**
* A curved line.
*/
LineType[LineType["Curved"] = 2] = "Curved";
})(LineType || (LineType = {}));
/**
* Represents the possible line styles.
*/
var LineStyle;
(function (LineStyle) {
/**
* A solid line.
*/
LineStyle[LineStyle["Solid"] = 0] = "Solid";
/**
* A dotted line.
*/
LineStyle[LineStyle["Dotted"] = 1] = "Dotted";
/**
* A dashed line.
*/
LineStyle[LineStyle["Dashed"] = 2] = "Dashed";
/**
* A dashed line with bigger dashes.
*/
LineStyle[LineStyle["LargeDashed"] = 3] = "LargeDashed";
/**
* A dotted line with more space between dots.
*/
LineStyle[LineStyle["SparseDotted"] = 4] = "SparseDotted";
})(LineStyle || (LineStyle = {}));
function getDashPattern(style, lineWidth) {
switch (style) {
case 0 /* LineStyle.Solid */: return [];
case 1 /* LineStyle.Dotted */: return [lineWidth, lineWidth];
case 2 /* LineStyle.Dashed */: return [2 * lineWidth, 2 * lineWidth];
case 3 /* LineStyle.LargeDashed */: return [6 * lineWidth, 6 * lineWidth];
case 4 /* LineStyle.SparseDotted */: return [lineWidth, 4 * lineWidth];
default: return [];
}
}
function getDashPatternLength(dashPattern) {
return dashPattern.reduce((sum, val) => sum + val, 0);
}
function setLineStyle(ctx, style) {
const dashPattern = getDashPattern(style, ctx.lineWidth);
ctx.setLineDash(dashPattern);
return dashPattern;
}
function drawHorizontalLine(ctx, y, left, right) {
ctx.beginPath();
const correction = (ctx.lineWidth % 2) ? 0.5 : 0;
ctx.moveTo(left, y + correction);
ctx.lineTo(right, y + correction);
ctx.stroke();
}
function drawVerticalLine(ctx, x, top, bottom) {
ctx.beginPath();
const correction = (ctx.lineWidth % 2) ? 0.5 : 0;
ctx.moveTo(x + correction, top);
ctx.lineTo(x + correction, bottom);
ctx.stroke();
}
function strokeInPixel(ctx, drawFunction) {
ctx.save();
if (ctx.lineWidth % 2) {
ctx.translate(0.5, 0.5);
}
drawFunction();
ctx.restore();
}
/**
* Checks an assertion. Throws if the assertion is failed.
*
* @param condition - Result of the assertion evaluation
* @param message - Text to include in the exception message
*/
function assert(condition, message) {
if (!condition) {
throw new Error('Assertion failed' + (message ? ': ' + message : ''));
}
}
function ensureDefined(value) {
if (value === undefined) {
throw new Error('Value is undefined');
}
return value;
}
function ensureNotNull(value) {
if (value === null) {
throw new Error('Value is null');
}
return value;
}
function ensure(value) {
return ensureNotNull(ensureDefined(value));
}
/**
* Compile time check for never
*/
function ensureNever(value) { }
class Delegate {
constructor() {
this._private__listeners = [];
}
_internal_subscribe(callback, linkedObject, singleshot) {
const listener = {
_internal_callback: callback,
_internal_linkedObject: linkedObject,
_internal_singleshot: singleshot === true,
};
this._private__listeners.push(listener);
}
_internal_unsubscribe(callback) {
const index = this._private__listeners.findIndex((listener) => callback === listener._internal_callback);
if (index > -1) {
this._private__listeners.splice(index, 1);
}
}
_internal_unsubscribeAll(linkedObject) {
this._private__listeners = this._private__listeners.filter((listener) => listener._internal_linkedObject !== linkedObject);
}
_internal_fire(param1, param2, param3) {
const listenersSnapshot = [...this._private__listeners];
this._private__listeners = this._private__listeners.filter((listener) => !listener._internal_singleshot);
listenersSnapshot.forEach((listener) => listener._internal_callback(param1, param2, param3));
}
_internal_hasListeners() {
return this._private__listeners.length > 0;
}
_internal_destroy() {
this._private__listeners = [];
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function merge(dst, ...sources) {
for (const src of sources) {
// eslint-disable-next-line no-restricted-syntax
for (const i in src) {
if (src[i] === undefined ||
!Object.prototype.hasOwnProperty.call(src, i) ||
['__proto__', 'constructor', 'prototype'].includes(i)) {
continue;
}
if ('object' !== typeof src[i] || dst[i] === undefined || Array.isArray(src[i])) {
dst[i] = src[i];
}
else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
merge(dst[i], src[i]);
}
}
}
return dst;
}
function isNumber(value) {
return (typeof value === 'number') && (isFinite(value));
}
function isInteger(value) {
return (typeof value === 'number') && ((value % 1) === 0);
}
function isString(value) {
return typeof value === 'string';
}
function isBoolean(value) {
return typeof value === 'boolean';
}
function clone(object) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const o = object;
if (!o || 'object' !== typeof o) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return o;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let c;
if (Array.isArray(o)) {
c = [];
}
else {
c = {};
}
let p;
let v;
// eslint-disable-next-line no-restricted-syntax
for (p in o) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call,no-prototype-builtins
if (o.hasOwnProperty(p)) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
v = o[p];
if (v && 'object' === typeof v) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
c[p] = clone(v);
}
else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
c[p] = v;
}
}
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return c;
}
function notNull(t) {
return t !== null;
}
function undefinedIfNull(t) {
return (t === null) ? undefined : t;
}
/**
* Default font family.
* Must be used to generate font string when font is not specified.
*/
const defaultFontFamily = `-apple-system, BlinkMacSystemFont, 'Trebuchet MS', Roboto, Ubuntu, sans-serif`;
/**
* Generates a font string, which can be used to set in canvas' font property.
* If no family provided, {@link defaultFontFamily} will be used.
*
* @param size - Font size in pixels.
* @param family - Optional font family.
* @param style - Optional font style.
* @returns The font string.
*/
function makeFont(size, family, style) {
if (style !== undefined) {
style = `${style} `;
}
else {
style = '';
}
if (family === undefined) {
family = defaultFontFamily;
}
return `${style}${size}px ${family}`;
}
class PriceAxisRendererOptionsProvider {
constructor(chartModel) {
this._private__rendererOptions = {
_internal_borderSize: 1 /* RendererConstants.BorderSize */,
_internal_tickLength: 5 /* RendererConstants.TickLength */,
_internal_fontSize: NaN,
_internal_font: '',
_internal_fontFamily: '',
_internal_color: '',
_internal_paneBackgroundColor: '',
_internal_paddingBottom: 0,
_internal_paddingInner: 0,
_internal_paddingOuter: 0,
_internal_paddingTop: 0,
_internal_baselineOffset: 0,
};
this._private__chartModel = chartModel;
}
_internal_options() {
const rendererOptions = this._private__rendererOptions;
const currentFontSize = this._private__fontSize();
const currentFontFamily = this._private__fontFamily();
if (rendererOptions._internal_fontSize !== currentFontSize || rendererOptions._internal_fontFamily !== currentFontFamily) {
rendererOptions._internal_fontSize = currentFontSize;
rendererOptions._internal_fontFamily = currentFontFamily;
rendererOptions._internal_font = makeFont(currentFontSize, currentFontFamily);
rendererOptions._internal_paddingTop = 2.5 / 12 * currentFontSize; // 2.5 px for 12px font
rendererOptions._internal_paddingBottom = rendererOptions._internal_paddingTop;
rendererOptions._internal_paddingInner = currentFontSize / 12 * rendererOptions._internal_tickLength;
rendererOptions._internal_paddingOuter = currentFontSize / 12 * rendererOptions._internal_tickLength;
rendererOptions._internal_baselineOffset = 0;
}
rendererOptions._internal_color = this._private__textColor();
rendererOptions._internal_paneBackgroundColor = this._private__paneBackgroundColor();
return this._private__rendererOptions;
}
_private__textColor() {
return this._private__chartModel._internal_options()['layout'].textColor;
}
_private__paneBackgroundColor() {
return this._private__chartModel._internal_backgroundTopColor();
}
_private__fontSize() {
return this._private__chartModel._internal_options()['layout'].fontSize;
}
_private__fontFamily() {
return this._private__chartModel._internal_options()['layout'].fontFamily;
}
}
function normalizeRgbComponent(component) {
if (component < 0) {
return 0;
}
if (component > 255) {
return 255;
}
// NaN values are treated as 0
return (Math.round(component) || 0);
}
function normalizeAlphaComponent(component) {
if (component <= 0 || component > 1) {
return Math.min(Math.max(component, 0), 1);
}
// limit the precision of all numbers to at most 4 digits in fractional part
return (Math.round(component * 10000) / 10000);
}
function rgbaToGrayscale(rgbValue) {
// Originally, the NTSC RGB to YUV formula
// perfected by @eugene-korobko's black magic
const redComponentGrayscaleWeight = 0.199;
const greenComponentGrayscaleWeight = 0.687;
const blueComponentGrayscaleWeight = 0.114;
return (redComponentGrayscaleWeight * rgbValue[0] +
greenComponentGrayscaleWeight * rgbValue[1] +
blueComponentGrayscaleWeight * rgbValue[2]);
}
/**
* For colors which fall within the sRGB space, the browser can
* be used to convert the color string into a rgb /rgba string.
*
* For other colors, it will be returned as specified (i.e. for
* newer formats like display-p3)
*
* See: https://www.w3.org/TR/css-color-4/#serializing-sRGB-values
*/
function getRgbStringViaBrowser(color) {
const element = document.createElement('div');
element.style.display = 'none';
// We append to the body as it is the most reliable way to get a color reading
// appending to the chart container or similar element can result in the following
// getComputedStyle returning empty strings on each check.
document.body.appendChild(element);
element.style.color = color;
const computed = window.getComputedStyle(element).color;
document.body.removeChild(element);
return computed;
}
class ColorParser {
constructor(customParsers, initialCache) {
this._private__rgbaCache = new Map();
this._private__customParsers = customParsers;
if (initialCache) {
this._private__rgbaCache = initialCache;
}
}
/**
* We fallback to RGBA here since supporting alpha transformations
* on wider color gamuts would currently be a lot of extra code
* for very little benefit due to actual usage.
*/
_internal_applyAlpha(color, alpha) {
// special case optimization
if (color === 'transparent') {
return color;
}
const originRgba = this._private__parseColor(color);
const originAlpha = originRgba[3];
return `rgba(${originRgba[0]}, ${originRgba[1]}, ${originRgba[2]}, ${alpha * originAlpha})`;
}
_internal_generateContrastColors(background) {
const rgba = this._private__parseColor(background);
return {
_internal_background: `rgb(${rgba[0]}, ${rgba[1]}, ${rgba[2]})`, // no alpha
_internal_foreground: rgbaToGrayscale(rgba) > 160 ? 'black' : 'white',
};
}
_internal_colorStringToGrayscale(background) {
return rgbaToGrayscale(this._private__parseColor(background));
}
_internal_gradientColorAtPercent(topColor, bottomColor, percent) {
const [topR, topG, topB, topA] = this._private__parseColor(topColor);
const [bottomR, bottomG, bottomB, bottomA] = this._private__parseColor(bottomColor);
const resultRgba = [
normalizeRgbComponent((topR + percent * (bottomR - topR))),
normalizeRgbComponent((topG + percent * (bottomG - topG))),
normalizeRgbComponent((topB + percent * (bottomB - topB))),
normalizeAlphaComponent((topA + percent * (bottomA - topA))),
];
return `rgba(${resultRgba[0]}, ${resultRgba[1]}, ${resultRgba[2]}, ${resultRgba[3]})`;
}
_private__parseColor(color) {
const cached = this._private__rgbaCache.get(color);
if (cached) {
return cached;
}
const computed = getRgbStringViaBrowser(color);
const match = computed.match(/^rgba?\s*\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d*\.?\d+))?\)$/);
if (!match) {
if (this._private__customParsers.length) {
for (const parser of this._private__customParsers) {
const result = parser(color);
if (result) {
this._private__rgbaCache.set(color, result);
return result;
}
}
}
throw new Error(`Failed to parse color: ${color}`);
}
const rgba = [
parseInt(match[1], 10),
parseInt(match[2], 10),
parseInt(match[3], 10),
(match[4] ? parseFloat(match[4]) : 1),
];
this._private__rgbaCache.set(color, rgba);
return rgba;
}
}
class CompositeRenderer {
constructor() {
this._private__renderers = [];
}
_internal_setRenderers(renderers) {
this._private__renderers = renderers;
}
_internal_draw(target, isHovered, hitTestData) {
this._private__renderers.forEach((r) => {
r._internal_draw(target, isHovered, hitTestData);
});
}
}
class BitmapCoordinatesPaneRenderer {
_internal_draw(target, isHovered, hitTestData) {
target.useBitmapCoordinateSpace((scope) => this._internal__drawImpl(scope, isHovered, hitTestData));
}
}
class PaneRendererMarks extends BitmapCoordinatesPaneRenderer {
constructor() {
super(...arguments);
this._internal__data = null;
}
_internal_setData(data) {
this._internal__data = data;
}
_internal__drawImpl({ context: ctx, horizontalPixelRatio, verticalPixelRatio }) {
if (this._internal__data === null || this._internal__data._internal_visibleRange === null) {
return;
}
const visibleRange = this._internal__data._internal_visibleRange;
const data = this._internal__data;
const tickWidth = Math.max(1, Math.floor(horizontalPixelRatio));
const correction = (tickWidth % 2) / 2;
const draw = (radiusMedia) => {
ctx.beginPath();
for (let i = visibleRange.to - 1; i >= visibleRange.from; --i) {
const point = data._internal_items[i];
const centerX = Math.round(point._internal_x * horizontalPixelRatio) + correction; // correct x coordinate only
const centerY = point._internal_y * verticalPixelRatio;
const radius = radiusMedia * verticalPixelRatio + correction;
ctx.moveTo(centerX, centerY);
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
}
ctx.fill();
};
if (data._internal_lineWidth > 0) {
ctx.fillStyle = data._internal_backColor;
draw(data._internal_radius + data._internal_lineWidth);
}
ctx.fillStyle = data._internal_lineColor;
draw(data._internal_radius);
}
}
function createEmptyMarkerData() {
return {
_internal_items: [{
_internal_x: 0,
_internal_y: 0,
_internal_time: 0,
_internal_price: 0,
}],
_internal_lineColor: '',
_internal_backColor: '',
_internal_radius: 0,
_internal_lineWidth: 0,
_internal_visibleRange: null,
};
}
const rangeForSinglePoint = { from: 0, to: 1 };
class CrosshairMarksPaneView {
constructor(chartModel, crosshair, pane) {
this._private__compositeRenderer = new CompositeRenderer();
this._private__markersRenderers = [];
this._private__markersData = [];
this._private__invalidated = true;
this._private__chartModel = chartModel;
this._private__crosshair = crosshair;
this._private__pane = pane;
this._private__compositeRenderer._internal_setRenderers(this._private__markersRenderers);
}
_internal_update(updateType) {
this._private__createMarkerRenderersIfNeeded();
this._private__invalidated = true;
}
_internal_renderer() {
if (this._private__invalidated) {
this._private__updateImpl();
this._private__invalidated = false;
}
return this._private__compositeRenderer;
}
_private__createMarkerRenderersIfNeeded() {
const serieses = this._private__pane._internal_orderedSources();
if (serieses.length !== this._private__markersRenderers.length) {
this._private__markersData = serieses.map(createEmptyMarkerData);
this._private__markersRenderers = this._private__markersData.map((data) => {
const res = new PaneRendererMarks();
res._internal_setData(data);
return res;
});
this._private__compositeRenderer._internal_setRenderers(this._private__markersRenderers);
}
}
_private__updateImpl() {
const forceHidden = this._private__crosshair._internal_options().mode === 2 /* CrosshairMode.Hidden */ || !this._private__crosshair._internal_visible();
const serieses = this._private__pane._internal_orderedSeries();
const timePointIndex = this._private__crosshair._internal_appliedIndex();
const timeScale = this._private__chartModel._internal_timeScale();
this._private__createMarkerRenderersIfNeeded();
serieses.forEach((s, index) => {
const data = this._private__markersData[index];
const seriesData = s._internal_markerDataAtIndex(timePointIndex);
const firstValue = s._internal_firstValue();
if (forceHidden || seriesData === null || !s._internal_visible() || firstValue === null) {
data._internal_visibleRange = null;
return;
}
data._internal_lineColor = seriesData._internal_backgroundColor;
data._internal_radius = seriesData._internal_radius;
data._internal_lineWidth = seriesData._internal_borderWidth;
data._internal_items[0]._internal_price = seriesData._internal_price;
data._internal_items[0]._internal_y = s._internal_priceScale()._internal_priceToCoordinate(seriesData._internal_price, firstValue._internal_value);
data._internal_backColor = seriesData._internal_borderColor ?? this._private__chartModel._internal_backgroundColorAtYPercentFromTop(data._internal_items[0]._internal_y / s._internal_priceScale()._internal_height());
data._internal_items[0]._internal_time = timePointIndex;
data._internal_items[0]._internal_x = timeScale._internal_indexToCoordinate(timePointIndex);
data._internal_visibleRange = rangeForSinglePoint;
});
}
}
class CrosshairRenderer extends BitmapCoordinatesPaneRenderer {
constructor(data) {
super();
this._private__data = data;
}
_internal__drawImpl({ context: ctx, bitmapSize, horizontalPixelRatio, verticalPixelRatio }) {
if (this._private__data === null) {
return;
}
const vertLinesVisible = this._private__data._internal_vertLine._internal_visible;
const horzLinesVisible = this._private__data._internal_horzLine._internal_visible;
if (!vertLinesVisible && !horzLinesVisible) {
return;
}
const x = Math.round(this._private__data._internal_x * horizontalPixelRatio);
const y = Math.round(this._private__data._internal_y * verticalPixelRatio);
ctx.lineCap = 'butt';
if (vertLinesVisible && x >= 0) {
ctx.lineWidth = Math.floor(this._private__data._internal_vertLine._internal_lineWidth * horizontalPixelRatio);
ctx.strokeStyle = this._private__data._internal_vertLine._internal_color;
ctx.fillStyle = this._private__data._internal_vertLine._internal_color;
setLineStyle(ctx, this._private__data._internal_vertLine._internal_lineStyle);
drawVerticalLine(ctx, x, 0, bitmapSize.height);
}
if (horzLinesVisible && y >= 0) {
ctx.lineWidth = Math.floor(this._private__data._internal_horzLine._internal_lineWidth * verticalPixelRatio);
ctx.strokeStyle = this._private__data._internal_horzLine._internal_color;
ctx.fillStyle = this._private__data._internal_horzLine._internal_color;
setLineStyle(ctx, this._private__data._internal_horzLine._internal_lineStyle);
drawHorizontalLine(ctx, y, 0, bitmapSize.width);
}
}
}
class CrosshairPaneView {
constructor(source, pane) {
this._private__invalidated = true;
this._private__rendererData = {
_internal_vertLine: {
_internal_lineWidth: 1,
_internal_lineStyle: 0,
_internal_color: '',
_internal_visible: false,
},
_internal_horzLine: {
_internal_lineWidth: 1,
_internal_lineStyle: 0,
_internal_color: '',
_internal_visible: false,
},
_internal_x: 0,
_internal_y: 0,
};
this._private__renderer = new CrosshairRenderer(this._private__rendererData);
this._private__source = source;
this._private__pane = pane;
}
_internal_update() {
this._private__invalidated = true;
}
_internal_renderer(pane) {
if (this._private__invalidated) {
this._private__updateImpl();
this._private__invalidated = false;
}
return this._private__renderer;
}
_private__updateImpl() {
const visible = this._private__source._internal_visible();
const crosshairOptions = this._private__pane._internal_model()._internal_options().crosshair;
const data = this._private__rendererData;
if (crosshairOptions.mode === 2 /* CrosshairMode.Hidden */) {
data._internal_horzLine._internal_visible = false;
data._internal_vertLine._internal_visible = false;
return;
}
data._internal_horzLine._internal_visible = visible && this._private__source._internal_horzLineVisible(this._private__pane);
data._internal_vertLine._internal_visible = visible && this._private__source._internal_vertLineVisible();
data._internal_horzLine._internal_lineWidth = crosshairOptions.horzLine.width;
data._internal_horzLine._internal_lineStyle = crosshairOptions.horzLine.style;
data._internal_horzLine._internal_color = crosshairOptions.horzLine.color;
data._internal_vertLine._internal_lineWidth = crosshairOptions.vertLine.width;
data._internal_vertLine._internal_lineStyle = crosshairOptions.vertLine.style;
data._internal_vertLine._internal_color = crosshairOptions.vertLine.color;
data._internal_x = this._private__source._internal_appliedX();
data._internal_y = this._private__source._internal_appliedY();
}
}
/**
* Fills rectangle's inner border (so, all the filled area is limited by the [x, x + width]*[y, y + height] region)
* ```
* (x, y)
* O***********************|*****
* | border | ^
* | ***************** | |
* | | | | |
* | b | | b | h
* | o | | o | e
* | r | | r | i
* | d | | d | g
* | e | | e | h
* | r | | r | t
* | | | | |
* | ***************** | |
* | border | v
* |***********************|*****
* | |
* |<------- width ------->|
* ```
*
* @param ctx - Context to draw on
* @param x - Left side of the target rectangle
* @param y - Top side of the target rectangle
* @param width - Width of the target rectangle
* @param height - Height of the target rectangle
* @param borderWidth - Width of border to fill, must be less than width and height of the target rectangle
*/
function fillRectInnerBorder(ctx, x, y, width, height, borderWidth) {
// horizontal (top and bottom) edges
ctx.fillRect(x + borderWidth, y, width - borderWidth * 2, borderWidth);
ctx.fillRect(x + borderWidth, y + height - borderWidth, width - borderWidth * 2, borderWidth);
// vertical (left and right) edges
ctx.fillRect(x, y, borderWidth, height);
ctx.fillRect(x + width - borderWidth, y, borderWidth, height);
}
function clearRect(ctx, x, y, w, h, clearColor) {
ctx.save();
ctx.globalCompositeOperation = 'copy';
ctx.fillStyle = clearColor;
ctx.fillRect(x, y, w, h);
ctx.restore();
}
function changeBorderRadius(borderRadius, offset) {
return borderRadius.map((x) => x === 0 ? x : x + offset);
}
function drawRoundRect(
// eslint:disable-next-line:max-params
ctx, x, y, w, h, radii) {
/**
* As of May 2023, all of the major browsers now support ctx.roundRect() so we should
* be able to switch to the native version soon.
*/
ctx.beginPath();
if (ctx.roundRect) {
ctx.roundRect(x, y, w, h, radii);
return;
}
/*
* Deprecate the rest in v5.
*/
ctx.lineTo(x + w - radii[1], y);
if (radii[1] !== 0) {
ctx.arcTo(x + w, y, x + w, y + radii[1], radii[1]);
}
ctx.lineTo(x + w, y + h - radii[2]);
if (radii[2] !== 0) {
ctx.arcTo(x + w, y + h, x + w - radii[2], y + h, radii[2]);
}
ctx.lineTo(x + radii[3], y + h);
if (radii[3] !== 0) {
ctx.arcTo(x, y + h, x, y + h - radii[3], radii[3]);
}
ctx.lineTo(x, y + radii[0]);
if (radii[0] !== 0) {
ctx.arcTo(x, y, x + radii[0], y, radii[0]);
}
}
/**
* Draws a rounded rect with a border.
*
* This function assumes that the colors will be solid, without
* any alpha. (This allows us to fix a rendering artefact.)
*
* @param outerBorderRadius - The radius of the border (outer edge)
*/
// eslint-disable-next-line max-params
function drawRoundRectWithBorder(ctx, left, top, width, height, backgroundColor, borderWidth = 0, outerBorderRadius = [0, 0, 0, 0], borderColor = '') {
ctx.save();
if (!borderWidth || !borderColor || borderColor === backgroundColor) {
drawRoundRect(ctx, left, top, width, height, outerBorderRadius);
ctx.fillStyle = backgroundColor;
ctx.fill();
ctx.restore();
return;
}
const halfBorderWidth = borderWidth / 2;
const radii = changeBorderRadius(outerBorderRadius, -halfBorderWidth);
drawRoundRect(ctx, left + halfBorderWidth, top + halfBorderWidth, width - borderWidth, height - borderWidth, radii);
if (backgroundColor !== 'transparent') {
ctx.fillStyle = backgroundColor;
ctx.fill();
}
if (borderColor !== 'transparent') {
ctx.lineWidth = borderWidth;
ctx.strokeStyle = borderColor;
ctx.closePath();
ctx.stroke();
}
ctx.restore();
}
// eslint-disable-next-line max-params
function clearRectWithGradient(ctx, x, y, w, h, topColor, bottomColor) {
ctx.save();
ctx.globalCompositeOperation = 'copy';
const gradient = ctx.createLinearGradient(0, 0, 0, h);
gradient.addColorStop(0, topColor);
gradient.addColorStop(1, bottomColor);
ctx.fillStyle = gradient;
ctx.fillRect(x, y, w, h);
ctx.restore();
}
class PriceAxisViewRenderer {
constructor(data, commonData) {
this._internal_setData(data, commonData);
}
_internal_setData(data, commonData) {
this._private__data = data;
this._private__commonData = commonData;
}
_internal_height(rendererOptions, useSecondLine) {
if (!this._private__data._internal_visible) {
return 0;
}
return rendererOptions._internal_fontSize + rendererOptions._internal_paddingTop + rendererOptions._internal_paddingBottom;
}
_internal_draw(target, rendererOptions, textWidthCache, align) {
if (!this._private__data._internal_visible || this._private__data._internal_text.length === 0) {
return;
}
const textColor = this._private__data._internal_color;
const backgroundColor = this._private__commonData._internal_background;
const geometry = target.useBitmapCoordinateSpace((scope) => {
const ctx = scope.context;
ctx.font = rendererOptions._internal_font;
const geom = this._private__calculateGeometry(scope, rendererOptions, textWidthCache, align);
const gb = geom._internal_bitmap;
/*
draw label. backgroundColor will always be a solid color (no alpha) [see generateContrastColors in color.ts].
Therefore we can draw the rounded label using simplified code (drawRoundRectWithBorder) that doesn't need to ensure the background and the border don't overlap.
*/
if (geom._internal_alignRight) {
drawRoundRectWithBorder(ctx, gb._internal_xOutside, gb._internal_yTop, gb._internal_totalWidth, gb._internal_totalHeight, backgroundColor, gb._internal_horzBorder, [gb._internal_radius, 0, 0, gb._internal_radius], backgroundColor);
}
else {
drawRoundRectWithBorder(ctx, gb._internal_xInside, gb._internal_yTop, gb._internal_totalWidth, gb._internal_totalHeight, backgroundColor, gb._internal_horzBorder, [0, gb._internal_radius, gb._internal_radius, 0], backgroundColor);
}
// draw tick
if (this._private__data._internal_tickVisible) {
ctx.fillStyle = textColor;
ctx.fillRect(gb._internal_xInside, gb._internal_yMid, gb._internal_xTick - gb._internal_xInside, gb._internal_tickHeight);
}
// draw separator
if (this._private__data._internal_borderVisible) {
ctx.fillStyle = rendererOptions._internal_paneBackgroundColor;
ctx.fillRect(geom._internal_alignRight ? gb._internal_right - gb._internal_horzBorder : 0, gb._internal_yTop, gb._internal_horzBorder, gb._internal_yBottom - gb._internal_yTop);
}
return geom;
});
target.useMediaCoordinateSpace(({ context: ctx }) => {
const gm = geometry._internal_media;
ctx.font = rendererOptions._internal_font;
ctx.textAlign = geometry._internal_alignRight ? 'right' : 'left';
ctx.textBaseline = 'middle';
ctx.fillStyle = textColor;
ctx.fillText(this._private__data._internal_text, gm._internal_xText, (gm._internal_yTop + gm._internal_yBottom) / 2 + gm._internal_textMidCorrection);
});
}
_private__calculateGeometry(scope, rendererOptions, textWidthCache, align) {
const { context: ctx, bitmapSize, mediaSize, horizontalPixelRatio, verticalPixelRatio } = scope;
const tickSize = (this._private__data._internal_tickVisible || !this._private__data._internal_moveTextToInvisibleTick) ? rendererOptions._internal_tickLength : 0;
const horzBorder = this._private__data._internal_separatorVisible ? rendererOptions._internal_borderSize : 0;
const paddingTop = rendererOptions._internal_paddingTop + this._private__commonData._internal_additionalPaddingTop;
const paddingBottom = rendererOptions._internal_paddingBottom + this._private__commonData._internal_additionalPaddingBottom;
const paddingInner = rendererOptions._internal_paddingInner;
const paddingOuter = rendererOptions._internal_paddingOuter;
const text = this._private__data._internal_text;
const actualTextHeight = rendererOptions._internal_fontSize;
const textMidCorrection = textWidthCache._internal_yMidCorrection(ctx, text);
const textWidth = Math.ceil(textWidthCache._internal_measureText(ctx, text));
const totalHeight = actualTextHeight + paddingTop + paddingBottom;
const totalWidth = rendererOptions._internal_borderSize + paddingInner + paddingOuter + textWidth + tickSize;
const tickHeightBitmap = Math.max(1, Math.floor(verticalPixelRatio));
let totalHeightBitmap = Math.round(totalHeight * verticalPixelRatio);
if (totalHeightBitmap % 2 !== tickHeightBitmap % 2) {
totalHeightBitmap += 1;
}
const horzBorderBitmap = horzBorder > 0 ? Math.max(1, Math.floor(horzBorder * horizontalPixelRatio)) : 0;
const totalWidthBitmap = Math.round(totalWidth * horizontalPixelRatio);
// tick overlaps scale border
const tickSizeBitmap = Math.round(tickSize * horizontalPixelRatio);
const yMid = this._private__commonData._internal_fixedCoordinate ?? this._private__commonData._internal_renderCoordinate ?? this._private__commonData._internal_coordinate;
const yMidBitmap = Math.round(yMid * verticalPixelRatio) - Math.floor(verticalPixelRatio * 0.5);
const yTopBitmap = Math.floor(yMidBitmap + tickHeightBitmap / 2 - totalHeightBitmap / 2);
const yBottomBitmap = yTopBitmap + totalHeightBitmap;
const alignRight = align === 'right';
const xInside = alignRight ? mediaSize.width - horzBorder : horzBorder;
const xInsideBitmap = alignRight ? bitmapSize.width - horzBorderBitmap : horzBorderBitmap;
let xOutsideBitmap;
let xTickBitmap;
let xText;
if (alignRight) {
// 2 1
//
// 6 5
//
// 3 4
xOutsideBitmap = xInsideBitmap - totalWidthBitmap;
xTickBitmap = xInsideBitmap - tickSizeBitmap;
xText = xInside - tickSize - paddingInner - horzBorder;
}
else {
// 1 2
//
// 6 5
//
// 4 3
xOutsideBitmap = xInsideBitmap + totalWidthBitmap;
xTickBitmap = xInsideBitmap + tickSizeBitmap;
xText = xInside + tickSize + paddingInner;
}
return {
_internal_alignRight: alignRight,
_internal_bitmap: {
_internal_yTop: yTopBitmap,
_internal_yMid: yMidBitmap,
_internal_yBottom: yBottomBitmap,
_internal_totalWidth: totalWidthBitmap,
_internal_totalHeight: totalHeightBitmap,
// TODO: it is better to have different horizontal and vertical radii
_internal_radius: 2 * horizontalPixelRatio,
_internal_horzBorder: horzBorderBitmap,
_internal_xOutside: xOutsideBitmap,
_internal_xInside: xInsideBitmap,
_internal_xTick: xTickBitmap,
_internal_tickHeight: tickHeightBitmap,
_internal_right: bitmapSize.width,
},
_internal_media: {
_internal_yTop: yTopBitmap / verticalPixelRatio,
_internal_yBottom: yBottomBitmap / verticalPixelRatio,
_internal_xText: xText,
_internal_textMidCorrection: textMidCorrection,
},
};
}
}
class PriceAxisView {
constructor(ctor) {
this._private__commonRendererData = {
_internal_coordinate: 0,
_internal_background: '#000',
_internal_additionalPaddingBottom: 0,
_internal_additionalPaddingTop: 0,
};
this._private__axisRendererData = {
_internal_text: '',
_internal_visible: false,
_internal_tickVisible: true,
_internal_moveTextToInvisibleTick: false,
_internal_borderColor: '',
_internal_color: '#FFF',
_internal_borderVisible: false,
_internal_separatorVisible: false,
};
this._private__paneRendererData = {
_internal_text: '',
_internal_visible: false,
_internal_tickVisible: false,
_internal_moveTextToInvisibleTick: true,
_internal_borderColor: '',
_internal_color: '#FFF',
_internal_borderVisible: true,
_internal_separatorVisible: true,
};
this._private__invalidated = true;
this._private__axisRenderer = new (ctor || PriceAxisViewRenderer)(this._private__axisRendererData, this._private__commonRendererData);
this._private__paneRenderer = new (ctor || PriceAxisViewRenderer)(this._private__paneRendererData, this._private__commonRendererData);
}
_internal_text() {
this._private__updateRendererDataIfNeeded();
return this._private__axisRendererData._internal_text;
}
_internal_coordinate() {
this._private__updateRendererDataIfNeeded();
return this._private__commonRendererData._internal_coordinate;
}
_internal_update() {
this._private__invalidated = true;
}
_internal_height(rendererOptions, useSecondLine = false) {
return Math.max(this._private__axisRenderer._internal_height(rendererOptions, useSecondLine), this._private__paneRenderer._internal_height(rendererOptions, useSecondLine));
}
_internal_getFixedCoordinate() {
return this._private__commonRendererData._internal_fixedCoordinate ?? null;
}
_internal_getRenderCoordinate() {
return this._private__commonRendererData._internal_fixedCoordinate ?? this._private__commonRendererData._internal_renderCoordinate ?? this._internal_coordinate();
}
_internal_setRenderCoordinate(value) {
this._private__commonRendererData._internal_renderCoordinate = value ?? undefined;
}
_internal_isVisible() {
this._private__updateRendererDataIfNeeded();
return this._private__axisRendererData._internal_visible || this._private__paneRendererData._internal_visible;
}
_internal_isAxisLabelVisible() {
this._private__updateRendererDataIfNeeded();
return this._private__axisRendererData._internal_visible;
}
_internal_renderer(priceScale) {
this._private__updateRendererDataIfNeeded();
// force update tickVisible state from price scale options
// because we don't have and we can't have price axis in other methods
// (like paneRenderer or any other who call _updateRendererDataIfNeeded)
this._private__axisRendererData._internal_tickVisible = this._private__axisRendererData._internal_tickVisible && priceScale._internal_options().ticksVisible;
this._private__paneRendererData._internal_tickVisible = this._private__paneRendererData._internal_tickVisible && priceScale._internal_options().ticksVisible;
this._private__axisRenderer._internal_setData(this._private__axisRendererData, this._private__commonRendererData);
this._private__paneRenderer._internal_setData(this._private__paneRendererData, this._private__commonRendererData);
return this._private__axisRenderer;
}
_internal_paneRenderer() {
this._private__updateRendererDataIfNeeded();
this._private__axisRenderer._internal_setData(this._private__axisRendererData, this._private__commonRendererData);
this._private__paneRenderer._internal_setData(this._private__paneRendererData, this._private__commonRendererData);
return this._private__paneRenderer;
}
_private__updateRendererDataIfNeeded() {
if (this._private__invalidated) {
this._private__axisRendererData._internal_tickVisible = true;
this._private__paneRendererData._internal_tickVisible = false;
this._internal__updateRendererData(this._private__axisRendererData, this._private__paneRendererData, this._private__commonRendererData);
}
}
}
class CrosshairPriceAxisView extends PriceAxisView {
constructor(source, priceScale, valueProvider) {
super();
this._private__source = source;
this._private__priceScale = priceScale;
this._private__valueProvider = valueProvider;
}
_internal__updateRendererData(axisRendererData, paneRendererData, commonRendererData) {
axisRendererData._internal_visible = false;
if (this._private__source._internal_options().mode === 2 /* CrosshairMode.Hidden */) {
return;
}
const options = this._private__source._internal_options().horzLine;
if (!options.labelVisible) {
return;
}
const firstValue = this._private__priceScale._internal_firstValue();
if (!this._private__source._internal_visible() || this._private__priceScale._internal_isEmpty() || (firstValue === null)) {
return;
}
const colors = this._private__priceScale._internal_colorParser()._internal_generateContrastColors(options.labelBackgroundColor);
commonRendererData._internal_background = colors._internal_background;
axisRendererData._internal_color = colors._internal_foreground;
const additionalPadding = 2 / 12 * this._private__priceScale._internal_fontSize();
commonRendererData._internal_additionalPaddingTop = additionalPadding;
commonRendererData._internal_additionalPaddingBottom = additionalPadding;
const value = this._private__valueProvider(this._private__priceScale);
commonRendererData._internal_coordinate = value._internal_coordinate;
axisRendererData._internal_text = this._private__priceScale._internal_formatPrice(value._internal_price, firstValue);
axisRendererData._internal_visible = true;
}
}
const optimizationReplacementRe = /[1-9]/g;
const radius$1 = 2;
class TimeAxisViewRenderer {
constructor() {
this._private__data = null;
}
_internal_setData(data) {
this._private__data = data;
}
_internal_draw(target, rendererOptions) {
if (this._private__data === null || this._private__data._internal_visible === false || this._private__data._internal_text.length === 0) {
return;
}
const textWidth = target.useMediaCoordinateSpace(({ context: ctx }) => {
ctx.font = rendererOptions._internal_font;
return Math.round(rendererOptions._internal_widthCache._internal_measureText(ctx, ensureNotNull(this._private__data)._internal_text, optimizationReplacementRe));
});
if (textWidth <= 0) {
return;
}
const horzMargin = rendererOptions._internal_paddingHorizontal;
const labelWidth = textWidth + 2 * horzMargin;
const labelWidthHalf = labelWidth / 2;
const timeScaleWidth = this._private__data._internal_width;
let coordinate = this._private__data._internal_coordinate;
let x1 = Math.floor(coordinate - labelWidthHalf) + 0.5;
if (x1 < 0) {
coordinate = coordinate + Math.abs(0 - x1);
x1 = Math.floor(coordinate - labelWidthHalf) + 0.5;
}
else if (x1 + labelWidth > timeScaleWidth) {
coordinate = coordinate - Math.abs(timeScaleWidth - (x1 + labelWidth));
x1 = Math.floor(coordinate - labelWidthHalf) + 0.5;
}
const x2 = x1 + labelWidth;
const y1 = 0;
const y2 = Math.ceil(y1 +
rendererOptions._internal_borderSize +
rendererOptions._internal_tickLength +
rendererOptions._internal_paddingTop +
rendererOptions._internal_fontSize +
rendererOptions._internal_paddingBottom);
target.useBitmapCoordinateSpace(({ context: ctx, horizontalPixelRatio, verticalPixelRatio }) => {
const data = ensureNotNull(this._private__data);
ctx.fillStyle = data._internal_background;
const x1scaled = Math.round(x1 * horizontalPixelRatio);
const y1scaled = Math.round(y1 * verticalPixelRatio);
const x2scaled = Math.round(x2 * horizontalPixelRatio);
const y2scaled = Math.round(y2 * verticalPixelRatio);
const radiusScaled = Math.round(radius$1 * horizontalPixelRatio);
ctx.beginPath();
ctx.moveTo(x1scaled, y1scaled);
ctx.lineTo(x1scaled, y2scaled - radiusScaled);
ctx.arcTo(x1scaled, y2scaled, x1scaled + radiusScaled, y2scaled, radiusScaled);
ctx.lineTo(x2scaled - radiusScaled, y2scaled);
ctx.arcTo(x2scaled, y2scaled, x2scaled, y2scaled - radiusScaled, radiusScaled);
ctx.lineTo(x2scaled, y1scaled);
ctx.fill();
if (data._internal_tickVisible) {
const tickX = Math.round(data._internal_coordinate * horizontalPixelRatio);
const tickTop = y1scaled;
const tickBottom = Math.round((tickTop + rendererOptions._internal_tickLength) * verticalPixelRatio);
ctx.fillStyle = data._internal_color;
const tickWidth = Math.max(1, Math.floor(horizontalPixelRatio));
const tickOffset = Math.floor(horizontalPixelRatio * 0.5);
ctx.fillRect(tickX - tickOffset, tickTop, tickWidth, tickBottom - tickTop);
}
});
target.useMediaCoordinateSpace(({ context: ctx }) => {
const data = ensureNotNull(this._private__data);
const yText = y1 +
rendererOptions._internal_borderSize +
rendererOptions._internal_tickLength +
rendererOptions._internal_paddingTop +
rendererOptions._internal_fontSize / 2;
ctx.font = rendererOptions._internal_font;
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
ctx.fillStyle = data._internal_color;
const textYCorrection = rendererOptions._internal_widthCache._internal_yMidCorrection(ctx, 'Apr0');
ctx.translate(x1 + horzMargin, yText + textYCorrection);
ctx.fillText(data._internal_text, 0, 0);
});
}
}
class CrosshairTimeAxisView {
constructor(crosshair, model, valueProvider) {
this._private__invalidated = true;
this._private__renderer = new TimeAxisViewRenderer();
this._private__rendererData = {
_internal_visible: false,
_internal_background: '#4c525e',
_internal_color: 'white',
_internal_text: '',
_internal_width: 0,
_internal_coordinate: NaN,
_internal_tickVisible: true,
};
this._private__crosshair = crosshair;
this._private__model = model;
this._private__valueProvider = valueProvider;
}
_internal_update() {
this._private__invalidated = true;
}
_internal_renderer() {
if (this._private__invalidated) {
this._private__updateImpl();
this._private__invalidated = false;
}
this._private__renderer._internal_setData(this._private__rendererData);
return this._private__renderer;
}
_private__updateImpl() {
const data = this._private__rendererData;
data._internal_visible = false;
if (this._private__crosshair._internal_options().mode === 2 /* CrosshairMode.Hidden */) {
return;
}
const options = this._private__crosshair._internal_options().vertLine;
if (!options.labelVisible) {
return;
}
const timeScale = this._private__model._internal_timeScale();
if (timeScale._internal_isEmpty()) {
return;
}
data._internal_width = timeScale._internal_width();
const value = this._private__valueProvider();
if (value === null) {
return;
}
data._internal_coordinate = value._internal_coordinate;
const currentTime = timeScale._internal_indexToTimeScalePoint(this._private__crosshair._internal_appliedIndex());
data._internal_text = timeScale._internal_formatDateTime(ensureNotNull(currentTime));
data._internal_visible = true;
const colors = this._private__model._internal_colorParser()._internal_generateContrastColors(options.labelBackgroundColor);
data._internal_background = colors._internal_background;
data._internal_color = colors._internal_foreground;
data._internal_tickVisible = timeScale._internal_options().ticksVisible;
}
}
class DataSource {
constructor() {
this._internal__priceScale = null;
this._private__zorder = 0;
}
_internal_zorder() {
return this._private__zorder;
}
_internal_setZorder(zorder) {
this._private__zorder = zorder;
}
_internal_priceScale() {
return this._internal__priceScale;
}
_internal_setPriceScale(priceScale) {
this._internal__priceScale = priceScale;
}
_internal_labelPaneViews(pane) {
return [];
}
_internal_timeAxisViews() {
return [];
}
_internal_visible() {
return true;
}
}
/**
* Represents the crosshair mode.
*/
var CrosshairMode;
(function (CrosshairMode) {
/**
* This mode allows crosshair to move freely on the chart.
*/
CrosshairMode[CrosshairMode["Normal"] = 0] = "Normal";
/**
* This mode sticks crosshair's horizontal line to the price value of a single-value series or to the close price of OHLC-based series.
*/
CrosshairMode[CrosshairMode["Magnet"] = 1] = "Magnet";
/**
* This mode disables rendering of the crosshair.
*/
CrosshairMode[CrosshairMode["Hidden"] = 2] = "Hidden";
/**
* This mode sticks crosshair's horizontal line to the price value of a single-value series or to the open/high/low/close price of OHLC-based series.
*/
CrosshairMode[CrosshairMode["MagnetOHLC"] = 3] = "MagnetOHLC";
})(CrosshairMode || (CrosshairMode = {}));
class Crosshair extends DataSource {
constructor(model, options) {
super();
this._private__pane = null;
this._private__price = NaN;
this._private__index = 0;
this._private__visible = false; // initially the crosshair should not be visible, until the user interacts.
this._private__priceAxisViews = new Map();
this._private__subscribed = false;
this._private__crosshairPaneViewCache = new WeakMap();
this._private__markersPaneViewCache = new WeakMap();
this._private__x = NaN;
this._private__y = NaN;
this._private__originX = NaN;
this._private__originY = NaN;
this._private__model = model;
this._private__options = options;
const valuePriceProvider = (rawPriceProvider, rawCoordinateProvider) => {
return (priceScale) => {
const coordinate = rawCoordinateProvider();
const rawPrice = rawPriceProvider();
if (priceScale === ensureNotNull(this._private__pane)._internal_defaultPriceScale()) {
// price must be defined
return { _internal_price: rawPrice, _internal_coordinate: coordinate };
}
else {
// always convert from coordinate
const firstValue = ensureNotNull(priceScale._internal_firstValue());
const price = priceScale._internal_coordinateToPrice(coordinate, firstValue);
return { _internal_price: price, _internal_coordinate: coordinate };
}
};
};
const valueTimeProvider = (rawIndexProvider, rawCoordinateProvider) => {
return () => {
const time = this._private__model._internal_timeScale()._internal_indexToTime(rawIndexProvider());
const coordinate = rawCoordinateProvider();
if (!time || !Number.isFinite(coordinate)) {
return null;
}
return {
_internal_time: time,
_internal_coordinate: coordinate,
};
};
};
// for current position always return both price and coordinate
this._private__currentPosPriceProvider = valuePriceProvider(() => this._private__price, () => this._private__y);
const currentPosTimeProvider = valueTimeProvider(() => this._private__index, () => this._internal_appliedX());
this._private__timeAxisView = new CrosshairTimeAxisView(this, model, currentPosTimeProvider);
}
_internal_options() {
return this._private__options;
}
_internal_saveOriginCoord(x, y) {
this._private__originX = x;
this._private__originY = y;
}
_internal_clearOriginCoord() {
this._private__originX = NaN;
this._private__originY = NaN;
}
_internal_originCoordX() {
return this._private__originX;
}
_internal_originCoordY() {
return this._private__originY;
}
_internal_setPosition(index, price, pane) {
if (!this._private__subscribed) {
this._private__subscribed = true;
}
this._private__visible = true;
this._private__tryToUpdateViews(index, price, pane);
}
_internal_appliedIndex() {
return this._private__index;
}
_internal_appliedX() {
return this._private__x;
}
_internal_appliedY() {
return this._private__y;
}
_internal_visible() {
return this._private__visible;
}
_internal_clearPosition() {
this._private__visible = false;
this._private__setIndexToLastSeriesBarIndex();
this._private__price = NaN;
this._private__x = NaN;
this._private__y = NaN;
this._private__pane = null;
this._internal_clearOriginCoord();
this._internal_updateAllViews();
}
_internal_snapToVisibleSeriesIfNeeded(index) {
if (!this._private__options.doNotSnapToHiddenSeriesIndices) {
return index;
}
const model = this._private__model;
const timeScale = model._internal_timeScale();
let closestLeftIndex = null;
let closestRightIndex = null;
for (const series of model._internal_visibleSerieses()) {
const leftResult = series._internal_bars()._internal_search(index, -1 /* MismatchDirection.NearestLeft */);
if (leftResult) {
if (leftResult._internal_index === index) {
return index; // already snapped
}
if (closestLeftIndex === null || leftResult._internal_index > closestLeftIndex) {
closestLeftIndex = leftResult._internal_index;
}
}
const rightResult = series._internal_bars()._internal_search(index, 1 /* MismatchDirection.NearestRight */);
if (rightResult) {
if (rightResult._internal_index === index) {
return index; // already snapped
}
if (closestRightIndex === null || rightResult._internal_index < closestRightIndex) {
closestRightIndex = rightResult._internal_index;
}
}
}
const candidates = [closestLeftIndex, closestRightIndex].filter(notNull);
if (candidates.length === 0) {
return index;
}
const x = timeScale._internal_indexToCoordinate(index);
const distances = candidates.map((i) => Math.abs(x - timeScale._internal_indexToCoordinate(i)));
const minDistanceIndex = distances.indexOf(Math.min(...distances));
return candidates[minDistanceIndex];
}
_internal_paneViews(pane) {
let crosshairPaneView = this._private__crosshairPaneViewCache.get(pane);
if (!crosshairPaneView) {
crosshairPaneView = new CrosshairPaneView(this, pane);
this._private__crosshairPaneViewCache.set(pane, crosshairPaneView);
}
let markersPaneView = this._private__markersPaneViewCache.get(pane);
if (!markersPaneView) {
markersPaneView = new CrosshairMarksPaneView(this._private__model, this, pane);
this._private__markersPaneViewCache.set(pane, markersPaneView);
}
return [crosshairPaneView, markersPaneView];
}
_internal_horzLineVisible(pane) {
return pane === this._private__pane && this._private__options.horzLine.visible;
}
_internal_vertLineVisible() {
return this._private__options.vertLine.visible;
}
_internal_priceAxisViews(pane, priceScale) {
if (!this._private__visible || this._private__pane !== pane) {
this._private__priceAxisViews.clear();
}
const views = [];
if (this._private__pane === pane) {
views.push(this._private__createPriceAxisViewOnDemand(this._private__priceAxisViews, priceScale, this._private__currentPosPriceProvider));
}
return views;
}
_internal_timeAxisViews() {
return this._private__visible ? [this._private__timeAxisView] : [];
}
_internal_pane() {
return this._private__pane;
}
_internal_updateAllViews() {
this._private__model._internal_panes().forEach((pane) => {
this._private__crosshairPaneViewCache.get(pane)?._internal_update();
this._private__markersPaneViewCache.get(pane)?._internal_update();
});
this._private__priceAxisViews.forEach((value) => value._internal_update());
this._private__timeAxisView._internal_update();
}
_private__priceScaleByPane(pane) {
if (pane && !pane._internal_defaultPriceScale()._internal_isEmpty()) {
return pane._internal_defaultPriceScale();
}
return null;
}
_private__tryToUpdateViews(index, price, pane) {
if (this._private__tryToUpdateData(index, price, pane)) {
this._internal_updateAllViews();
}
}
_private__tryToUpdateData(newIndex, newPrice, newPane) {
const oldX = this._private__x;
const oldY = this._private__y;
const oldPrice = this._private__price;
const oldIndex = this._private__index;
const oldPane = this._private__pane;
const priceScale = this._private__priceScaleByPane(newPane);
this._private__index = newIndex;
this._private__x = isNaN(newIndex) ? NaN : this._private__model._internal_timeScale()._internal_indexToCoordinate(newIndex);
this._private__pane = newPane;
const firstValue = priceScale !== null ? priceScale._internal_firstValue() : null;
if (priceScale !== null && firstValue !== null) {
this._private__price = newPrice;
this._private__y = priceScale._internal_priceToCoordinate(newPrice, firstValue);
}
else {
this._private__price = NaN;
this._private__y = NaN;
}
return (oldX !== this._private__x || oldY !== this._private__y || oldIndex !== this._private__index ||
oldPrice !== this._private__price || oldPane !== this._private__pane);
}
_private__setIndexToLastSeriesBarIndex() {
const lastIndexes = this._private__model._internal_serieses()
.map((s) => s._internal_bars()._internal_lastIndex())
.filter(notNull);
const lastBarIndex = (lastIndexes.length === 0) ? null : Math.max(...lastIndexes);
this._private__index = lastBarIndex !== null ? lastBarIndex : NaN;
}
_private__createPriceAxisViewOnDemand(map, priceScale, valueProvider) {
let view = map.get(priceScale);
if (view === undefined) {
view = new CrosshairPriceAxisView(this, priceScale, valueProvider);
map.set(priceScale, view);
}
return view;
}
}
function isDefaultPriceScale(priceScaleId) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
return priceScaleId === "left" /* DefaultPriceScaleId.Left */ || priceScaleId === "right" /* DefaultPriceScaleId.Right */;
}
function mergePaneInvalidation(beforeValue, newValue) {
if (beforeValue === undefined) {
return newValue;
}
const level = Math.max(beforeValue._internal_level, newValue._internal_level);
const autoScale = beforeValue._internal_autoScale || newValue._internal_autoScale;
return { _internal_level: level, _internal_autoScale: autoScale };
}
class InvalidateMask {
constructor(globalLevel) {
this._private__invalidatedPanes = new Map();
this._private__timeScaleInvalidations = [];
this._private__globalLevel = globalLevel;
}
_internal_invalidatePane(paneIndex, invalidation) {
const prevValue = this._private__invalidatedPanes.get(paneIndex);
const newValue = mergePaneInvalidation(prevValue, invalidation);
this._private__invalidatedPanes.set(paneIndex, newValue);
}
_internal_fullInvalidation() {
return this._private__globalLevel;
}
_internal_invalidateForPane(paneIndex) {
const paneInvalidation = this._private__invalidatedPanes.get(paneIndex);
if (paneInvalidation === undefined) {
return {
_internal_level: this._private__globalLevel,
};
}
return {
_internal_level: Math.max(this._private__globalLevel, paneInvalidation._internal_level),
_internal_autoScale: paneInvalidation._internal_autoScale,
};
}
_internal_setFitContent() {
this._internal_stopTimeScaleAnimation();
// modifies both bar spacing and right offset
this._private__timeScaleInvalidations = [{ _internal_type: 0 /* TimeScaleInvalidationType.FitContent */ }];
}
_internal_applyRange(range) {
this._internal_stopTimeScaleAnimation();
// modifies both bar spacing and right offset
this._private__timeScaleInvalidations = [{ _internal_type: 1 /* TimeScaleInvalidationType.ApplyRange */, _internal_value: range }];
}
_internal_setTimeScaleAnimation(animation) {
this._private__removeTimeScaleAnimation();
this._private__timeScaleInvalidations.push({ _internal_type: 5 /* TimeScaleInvalidationType.Animation */, _internal_value: animation });
}
_internal_stopTimeScaleAnimation() {
this._private__removeTimeScaleAnimation();
this._private__timeScaleInvalidations.push({ _internal_type: 6 /* TimeScaleInvalidationType.StopAnimation */ });
}
_internal_resetTimeScale() {
this._internal_stopTimeScaleAnimation();
// modifies both bar spacing and right offset
this._private__timeScaleInvalidations = [{ _internal_type: 4 /* TimeScaleInvalidationType.Reset */ }];
}
_internal_setBarSpacing(barSpacing) {
this._internal_stopTimeScaleAnimation();
this._private__timeScaleInvalidations.push({ _internal_type: 2 /* TimeScaleInvalidationType.ApplyBarSpacing */, _internal_value: barSpacing });
}
_internal_setRightOffset(offset) {
this._internal_stopTimeScaleAnimation();
this._private__timeScaleInvalidations.push({ _internal_type: 3 /* TimeScaleInvalidationType.ApplyRightOffset */, _internal_value: offset });
}
_internal_timeScaleInvalidations() {
return this._private__timeScaleInvalidations;
}
_internal_merge(other) {
for (const tsInvalidation of other._private__timeScaleInvalidations) {
this._private__applyTimeScaleInvalidation(tsInvalidation);
}
this._private__globalLevel = Math.max(this._private__globalLevel, other._private__globalLevel);
other._private__invalidatedPanes.forEach((invalidation, index) => {
this._internal_invalidatePane(index, invalidation);
});
}
static _internal_light() {
return new InvalidateMask(2 /* InvalidationLevel.Light */);
}
static _internal_full() {
return new InvalidateMask(3 /* InvalidationLevel.Full */);
}
_private__applyTimeScaleInvalidation(invalidation) {
switch (invalidation._internal_type) {
case 0 /* TimeScaleInvalidationType.FitContent */:
this._internal_setFitContent();
break;
case 1 /* TimeScaleInvalidationType.ApplyRange */:
this._internal_applyRange(invalidation._internal_value);
break;
case 2 /* TimeScaleInvalidationType.ApplyBarSpacing */:
this._internal_setBarSpacing(invalidation._internal_value);
break;
case 3 /* TimeScaleInvalidationType.ApplyRightOffset */:
this._internal_setRightOffset(invalidation._internal_value);
break;
case 4 /* TimeScaleInvalidationType.Reset */:
this._internal_resetTimeScale();
break;
case 5 /* TimeScaleInvalidationType.Animation */:
this._internal_setTimeScaleAnimation(invalidation._internal_value);
break;
case 6 /* TimeScaleInvalidationType.StopAnimation */:
this._private__removeTimeScaleAnimation();
}
}
_private__removeTimeScaleAnimation() {
const index = this._private__timeScaleInvalidations.findIndex((inv) => inv._internal_type === 5 /* TimeScaleInvalidationType.Animation */);
if (index !== -1) {
this._private__timeScaleInvalidations.splice(index, 1);
}
}
}
class FormatterBase {
formatTickmarks(prices) {
return prices.map((price) => this.format(price));
}
}
const formatterOptions = {
_internal_decimalSign: '.'};
/**
* @param value - The number of convert.
* @param length - The length. Must be between 0 and 16 inclusive.
*/
function numberToStringWithLeadingZero(value, length) {
if (!isNumber(value)) {
return 'n/a';
}
if (!isInteger(length)) {
throw new TypeError('invalid length');
}
if (length < 0 || length > 16) {
throw new TypeError('invalid length');
}
if (length === 0) {
return value.toString();
}
const dummyString = '0000000000000000';
return (dummyString + value.toString()).slice(-length);
}
class PriceFormatter extends FormatterBase {
constructor(priceScale, minMove) {
super();
if (!minMove) {
minMove = 1;
}
if (!isNumber(priceScale) || !isInteger(priceScale)) {
priceScale = 100;
}
if (priceScale < 0) {
throw new TypeError('invalid base');
}
this._private__priceScale = priceScale;
this._private__minMove = minMove;
this._private__calculateDecimal();
}
format(price) {
// \u2212 is unicode's minus sign https://www.fileformat.info/info/unicode/char/2212/index.htm
// we should use it because it has the same width as plus sign +
const sign = price < 0 ? '\u2212' : '';
price = Math.abs(price);
return sign + this._private__formatAsDecimal(price);
}
_private__calculateDecimal() {
// check if this._base is power of 10
// for double fractional _fractionalLength if for the main fractional only
this._internal__fractionalLength = 0;
if (this._private__priceScale > 0 && this._private__minMove > 0) {
let base = this._private__priceScale;
while (base > 1) {
base /= 10;
this._internal__fractionalLength++;
}
}
}
_private__formatAsDecimal(price) {
const base = this._private__priceScale / this._private__minMove;
let intPart = Math.floor(price);
let fracString = '';
const fracLength = this._internal__fractionalLength !== undefined ? this._internal__fractionalLength : NaN;
if (base > 1) {
let fracPart = +(Math.round(price * base) - intPart * base).toFixed(this._internal__fractionalLength);
if (fracPart >= base) {
fracPart -= base;
intPart += 1;
}
fracString = formatterOptions._internal_decimalSign + numberToStringWithLeadingZero(+fracPart.toFixed(this._internal__fractionalLength) * this._private__minMove, fracLength);
}
else {
// should round int part to min move
intPart = Math.round(intPart * base) / base;
// if min move > 1, fractional part is always = 0
if (fracLength > 0) {
fracString = formatterOptions._internal_decimalSign + numberToStringWithLeadingZero(0, fracLength);
}
}
return intPart.toFixed(0) + fracString;
}
}
class PercentageFormatter extends PriceFormatter {
constructor(priceScale = 100) {
super(priceScale);
}
format(price) {
return `${super.format(price)}%`;
}
}
class VolumeFormatter extends FormatterBase {
constructor(precision) {
super();
this._private__precision = precision;
}
format(vol) {
let sign = '';
if (vol < 0) {
sign = '-';
vol = -vol;
}
if (vol < 995) {
return sign + this._private__formatNumber(vol);
}
else if (vol < 999995) {
return sign + this._private__formatNumber(vol / 1000) + 'K';
}
else if (vol < 999999995) {
vol = 1000 * Math.round(vol / 1000);
return sign + this._private__formatNumber(vol / 1000000) + 'M';
}
else {
vol = 1000000 * Math.round(vol / 1000000);
return sign + this._private__formatNumber(vol / 1000000000) + 'B';
}
}
_private__formatNumber(value) {
let res;
const priceScale = Math.pow(10, this._private__precision);
value = Math.round(value * priceScale) / priceScale;
if (value >= 1e-15 && value < 1) {
res = value.toFixed(this._private__precision).replace(/\.?0+$/, ''); // regex removes trailing zeroes
}
else {
res = String(value);
}
return res.replace(/(\.[1-9]*)0+$/, (e, p1) => p1);
}
}
const defaultReplacementRe = /[2-9]/g;
class TextWidthCache {
constructor(size = 50) {
this._private__actualSize = 0;
this._private__usageTick = 1;
this._private__oldestTick = 1;
this._private__tick2Labels = {};
this._private__cache = new Map();
this._private__maxSize = size;
}
_internal_reset() {
this._private__actualSize = 0;
this._private__cache.clear();
this._private__usageTick = 1;
this._private__oldestTick = 1;
this._private__tick2Labels = {};
}
_internal_measureText(ctx, text, optimizationReplacementRe) {
return this._private__getMetrics(ctx, text, optimizationReplacementRe).width;
}
_internal_yMidCorrection(ctx, text, optimizationReplacementRe) {
const metrics = this._private__getMetrics(ctx, text, optimizationReplacementRe);
// if actualBoundingBoxAscent/actualBoundingBoxDescent are not supported we use 0 as a fallback
return ((metrics.actualBoundingBoxAscent || 0) - (metrics.actualBoundingBoxDescent || 0)) / 2;
}
_private__getMetrics(ctx, text, optimizationReplacementRe) {
const re = optimizationReplacementRe || defaultReplacementRe;
const cacheString = String(text).replace(re, '0');
if (this._private__cache.has(cacheString)) {
return ensureDefined(this._private__cache.get(cacheString))._internal_metrics;
}
if (this._private__actualSize === this._private__maxSize) {
const oldestValue = this._private__tick2Labels[this._private__oldestTick];
delete this._private__tick2Labels[this._private__oldestTick];
this._private__cache.delete(oldestValue);
this._private__oldestTick++;
this._private__actualSize--;
}
ctx.save();
ctx.textBaseline = 'middle';
const metrics = ctx.measureText(cacheString);
ctx.restore();
if (metrics.width === 0 && !!text.length) {
// measureText can return 0 in FF depending on a canvas size, don't cache it
return metrics;
}
this._private__cache.set(cacheString, { _internal_metrics: metrics, _internal_tick: this._private__usageTick });
this._private__tick2Labels[this._private__usageTick] = cacheString;
this._private__actualSize++;
this._private__usageTick++;
return metrics;
}
}
class PanePriceAxisViewRenderer {
constructor(textWidthCache) {
this._private__priceAxisViewRenderer = null;
this._private__rendererOptions = null;
this._private__align = 'right';
this._private__textWidthCache = textWidthCache;
}
_internal_setParams(priceAxisViewRenderer, rendererOptions, align) {
this._private__priceAxisViewRenderer = priceAxisViewRenderer;
this._private__rendererOptions = rendererOptions;
this._private__align = align;
}
_internal_draw(target) {
if (this._private__rendererOptions === null || this._private__priceAxisViewRenderer === null) {
return;
}
this._private__priceAxisViewRenderer._internal_draw(target, this._private__rendererOptions, this._private__textWidthCache, this._private__align);
}
}
class PanePriceAxisView {
constructor(priceAxisView, dataSource, chartModel) {
this._private__priceAxisView = priceAxisView;
this._private__textWidthCache = new TextWidthCache(50); // when should we clear cache?
this._private__dataSource = dataSource;
this._private__chartModel = chartModel;
this._private__fontSize = -1;
this._private__renderer = new PanePriceAxisViewRenderer(this._private__textWidthCache);
}
_internal_renderer() {
const pane = this._private__chartModel._internal_paneForSource(this._private__dataSource);
if (pane === null) {
return null;
}
// this price scale will be used to find label placement only (left, right, none)
const priceScale = pane._internal_isOverlay(this._private__dataSource) ? pane._internal_defaultVisiblePriceScale() : this._private__dataSource._internal_priceScale();
if (priceScale === null) {
return null;
}
const position = pane._internal_priceScalePosition(priceScale);
if (position === 'overlay') {
return null;
}
const options = this._private__chartModel._internal_priceAxisRendererOptions();
if (options._internal_fontSize !== this._private__fontSize) {
this._private__fontSize = options._internal_fontSize;
this._private__textWidthCache._internal_reset();
}
this._private__renderer._internal_setParams(this._private__priceAxisView._internal_paneRenderer(), options, position);
return this._private__renderer;
}
}
class HorizontalLineRenderer extends BitmapCoordinatesPaneRenderer {
constructor() {
super(...arguments);
this._private__data = null;
}
_internal_setData(data) {
this._private__data = data;
}
_internal_hitTest(x, y) {
if (!this._private__data?._internal_visible) {
return null;
}
const { _internal_y: itemY, _internal_lineWidth: lineWidth, _internal_externalId: externalId } = this._private__data;
// add a fixed area threshold around line (Y + width) for hit test
if (y >= itemY - lineWidth - 7 /* Constants.HitTestThreshold */ && y <= itemY + lineWidth + 7 /* Constants.HitTestThreshold */) {
return {
_internal_hitTestData: this._private__data,
_internal_distance: Math.abs(y - itemY),
_internal_priority: 2 /* HitTestPriority.Point */,
_internal_itemType: 'price-line',
_internal_externalId: externalId,
};
}
return null;
}
_internal__drawImpl({ context: ctx, bitmapSize, horizontalPixelRatio, verticalPixelRatio }) {
if (this._private__data === null) {
return;
}
if (this._private__data._internal_visible === false) {
return;
}
const y = Math.round(this._private__data._internal_y * verticalPixelRatio);
if (y < 0 || y > bitmapSize.height) {
return;
}
ctx.lineCap = 'butt';
ctx.strokeStyle = this._private__data._internal_color;
ctx.lineWidth = Math.floor(this._private__data._internal_lineWidth * horizontalPixelRatio);
setLineStyle(ctx, this._private__data._internal_lineStyle);
drawHorizontalLine(ctx, y, 0, bitmapSize.width);
}
}
class SeriesHorizontalLinePaneView {
constructor(series) {
this._internal__lineRendererData = {
_internal_y: 0,
_internal_color: 'rgba(0, 0, 0, 0)',
_internal_lineWidth: 1,
_internal_lineStyle: 0 /* LineStyle.Solid */,
_internal_visible: false,
};
this._internal__lineRenderer = new HorizontalLineRenderer();
this._private__invalidated = true;
this._internal__series = series;
this._internal__model = series._internal_model();
this._internal__lineRenderer._internal_setData(this._internal__lineRendererData);
}
_internal_update() {
this._private__invalidated = true;
}
_internal_renderer() {
if (!this._internal__series._internal_visible()) {
return null;
}
if (this._private__invalidated) {
this._internal__updateImpl();
this._private__invalidated = false;
}
return this._internal__lineRenderer;
}
}
class SeriesHorizontalBaseLinePaneView extends SeriesHorizontalLinePaneView {
// eslint-disable-next-line no-useless-constructor
constructor(series) {
super(series);
}
_internal__updateImpl() {
this._internal__lineRendererData._internal_visible = false;
const priceScale = this._internal__series._internal_priceScale();
const mode = priceScale._internal_mode()._internal_mode;
if (mode !== 2 /* PriceScaleMode.Percentage */ && mode !== 3 /* PriceScaleMode.IndexedTo100 */) {
return;
}
const seriesOptions = this._internal__series._internal_options();
if (!seriesOptions.baseLineVisible || !this._internal__series._internal_visible()) {
return;
}
const firstValue = this._internal__series._internal_firstValue();
if (firstValue === null) {
return;
}
this._internal__lineRendererData._internal_visible = true;
this._internal__lineRendererData._internal_y = priceScale._internal_priceToCoordinate(firstValue._internal_value, firstValue._internal_value);
this._internal__lineRendererData._internal_color = seriesOptions.baseLineColor;
this._internal__lineRendererData._internal_lineWidth = seriesOptions.baseLineWidth;
this._internal__lineRendererData._internal_lineStyle = seriesOptions.baseLineStyle;
}
}
class SeriesLastPriceAnimationRenderer extends BitmapCoordinatesPaneRenderer {
constructor() {
super(...arguments);
this._private__data = null;
}
_internal_setData(data) {
this._private__data = data;
}
_internal_data() {
return this._private__data;
}
_internal__drawImpl({ context: ctx, horizontalPixelRatio, verticalPixelRatio }) {
const data = this._private__data;
if (data === null) {
return;
}
const tickWidth = Math.max(1, Math.floor(horizontalPixelRatio));
const correction = (tickWidth % 2) / 2;
const centerX = Math.round(data._internal_center.x * horizontalPixelRatio) + correction; // correct x coordinate only
const centerY = data._internal_center.y * verticalPixelRatio;
ctx.fillStyle = data._internal_seriesLineColor;
ctx.beginPath();
// TODO: it is better to have different horizontal and vertical radii
const centerPointRadius = Math.max(2, data._internal_seriesLineWidth * 1.5) * horizontalPixelRatio;
ctx.arc(centerX, centerY, centerPointRadius, 0, 2 * Math.PI, false);
ctx.fill();
ctx.fillStyle = data._internal_fillColor;
ctx.beginPath();
ctx.arc(centerX, centerY, data._internal_radius * horizontalPixelRatio, 0, 2 * Math.PI, false);
ctx.fill();
ctx.lineWidth = tickWidth;
ctx.strokeStyle = data._internal_strokeColor;
ctx.beginPath();
ctx.arc(centerX, centerY, data._internal_radius * horizontalPixelRatio + tickWidth / 2, 0, 2 * Math.PI, false);
ctx.stroke();
}
}
const animationStagesData = [
{
_internal_start: 0,
_internal_end: 0.25 /* Constants.Stage1Period */,
_internal_startRadius: 4 /* Constants.Stage1StartCircleRadius */,
_internal_endRadius: 10 /* Constants.Stage1EndCircleRadius */,
_internal_startFillAlpha: 0.25 /* Constants.Stage1StartFillAlpha */,
_internal_endFillAlpha: 0 /* Constants.Stage1EndFillAlpha */,
_internal_startStrokeAlpha: 0.4 /* Constants.Stage1StartStrokeAlpha */,
_internal_endStrokeAlpha: 0.8 /* Constants.Stage1EndStrokeAlpha */,
},
{
_internal_start: 0.25 /* Constants.Stage1Period */,
_internal_end: 0.25 /* Constants.Stage1Period */ + 0.275 /* Constants.Stage2Period */,
_internal_startRadius: 10 /* Constants.Stage2StartCircleRadius */,
_internal_endRadius: 14 /* Constants.Stage2EndCircleRadius */,
_internal_startFillAlpha: 0 /* Constants.Stage2StartFillAlpha */,
_internal_endFillAlpha: 0 /* Constants.Stage2EndFillAlpha */,
_internal_startStrokeAlpha: 0.8 /* Constants.Stage2StartStrokeAlpha */,
_internal_endStrokeAlpha: 0 /* Constants.Stage2EndStrokeAlpha */,
},
{
_internal_start: 0.25 /* Constants.Stage1Period */ + 0.275 /* Constants.Stage2Period */,
_internal_end: 0.25 /* Constants.Stage1Period */ + 0.275 /* Constants.Stage2Period */ + 0.475 /* Constants.Stage3Period */,
_internal_startRadius: 14 /* Constants.Stage3StartCircleRadius */,
_internal_endRadius: 14 /* Constants.Stage3EndCircleRadius */,
_internal_startFillAlpha: 0 /* Constants.Stage3StartFillAlpha */,
_internal_endFillAlpha: 0 /* Constants.Stage3EndFillAlpha */,
_internal_startStrokeAlpha: 0 /* Constants.Stage3StartStrokeAlpha */,
_internal_endStrokeAlpha: 0 /* Constants.Stage3EndStrokeAlpha */,
},
];
function radius(stage, startRadius, endRadius) {
return startRadius + (endRadius - startRadius) * stage;
}
class SeriesLastPriceAnimationPaneView {
constructor(series) {
this._private__renderer = new SeriesLastPriceAnimationRenderer();
this._private__invalidated = true;
this._private__stageInvalidated = true;
this._private__startTime = performance.now();
this._private__endTime = this._private__startTime - 1;
this._private__series = series;
}
_internal_onDataCleared() {
this._private__endTime = this._private__startTime - 1;
this._internal_update();
}
_internal_onNewRealtimeDataReceived() {
this._internal_update();
if (this._private__series._internal_options().lastPriceAnimation === 2 /* LastPriceAnimationMode.OnDataUpdate */) {
const now = performance.now();
const timeToAnimationEnd = this._private__endTime - now;
if (timeToAnimationEnd > 0) {
if (timeToAnimationEnd < 2600 /* Constants.AnimationPeriod */ / 4) {
this._private__endTime += 2600 /* Constants.AnimationPeriod */;
}
return;
}
this._private__startTime = now;
this._private__endTime = now + 2600 /* Constants.AnimationPeriod */;
}
}
_internal_update() {
this._private__invalidated = true;
}
_internal_invalidateStage() {
this._private__stageInvalidated = true;
}
_internal_visible() {
// center point is always visible if lastPriceAnimation is not LastPriceAnimationMode.Disabled
return this._private__series._internal_options().lastPriceAnimation !== 0 /* LastPriceAnimationMode.Disabled */;
}
_internal_animationActive() {
switch (this._private__series._internal_options().lastPriceAnimation) {
case 0 /* LastPriceAnimationMode.Disabled */:
return false;
case 1 /* LastPriceAnimationMode.Continuous */:
return true;
case 2 /* LastPriceAnimationMode.OnDataUpdate */:
return performance.now() <= this._private__endTime;
}
}
_internal_renderer() {
if (this._private__invalidated) {
this._private__updateImpl();
this._private__invalidated = false;
this._private__stageInvalidated = false;
}
else if (this._private__stageInvalidated) {
this._private__updateRendererDataStage();
this._private__stageInvalidated = false;
}
return this._private__renderer;
}
_private__updateImpl() {
this._private__renderer._internal_setData(null);
const timeScale = this._private__series._internal_model()._internal_timeScale();
const visibleRange = timeScale._internal_visibleStrictRange();
const firstValue = this._private__series._internal_firstValue();
if (visibleRange === null || firstValue === null) {
return;
}
const lastValue = this._private__series._internal_lastValueData(true);
if (lastValue._internal_noData || !visibleRange._internal_contains(lastValue._internal_index)) {
return;
}
const lastValuePoint = {
x: timeScale._internal_indexToCoordinate(lastValue._internal_index),
y: this._private__series._internal_priceScale()._internal_priceToCoordinate(lastValue._internal_price, firstValue._internal_value),
};
const seriesLineColor = lastValue._internal_color;
const seriesLineWidth = this._private__series._internal_options().lineWidth;
const data = this._private__animationData(this._private__duration(), seriesLineColor);
this._private__renderer._internal_setData({
_internal_seriesLineColor: seriesLineColor,
_internal_seriesLineWidth: seriesLineWidth,
_internal_fillColor: data._internal_fillColor,
_internal_strokeColor: data._internal_strokeColor,
_internal_radius: data._internal_radius,
_internal_center: lastValuePoint,
});
}
_private__updateRendererDataStage() {
const rendererData = this._private__renderer._internal_data();
if (rendererData !== null) {
const data = this._private__animationData(this._private__duration(), rendererData._internal_seriesLineColor);
rendererData._internal_fillColor = data._internal_fillColor;
rendererData._internal_strokeColor = data._internal_strokeColor;
rendererData._internal_radius = data._internal_radius;
}
}
_private__duration() {
return this._internal_animationActive() ? performance.now() - this._private__startTime : 2600 /* Constants.AnimationPeriod */ - 1;
}
_private__color(seriesLineColor, stage, startAlpha, endAlpha) {
const alpha = startAlpha + (endAlpha - startAlpha) * stage;
return this._private__series._internal_model()._internal_colorParser()._internal_applyAlpha(seriesLineColor, alpha);
}
_private__animationData(durationSinceStart, lineColor) {
const globalStage = (durationSinceStart % 2600 /* Constants.AnimationPeriod */) / 2600 /* Constants.AnimationPeriod */;
let currentStageData;
for (const stageData of animationStagesData) {
if (globalStage >= stageData._internal_start && globalStage <= stageData._internal_end) {
currentStageData = stageData;
break;
}
}
assert(currentStageData !== undefined, 'Last price animation internal logic error');
const subStage = (globalStage - currentStageData._internal_start) / (currentStageData._internal_end - currentStageData._internal_start);
return {
_internal_fillColor: this._private__color(lineColor, subStage, currentStageData._internal_startFillAlpha, currentStageData._internal_endFillAlpha),
_internal_strokeColor: this._private__color(lineColor, subStage, currentStageData._internal_startStrokeAlpha, currentStageData._internal_endStrokeAlpha),
_internal_radius: radius(subStage, currentStageData._internal_startRadius, currentStageData._internal_endRadius),
};
}
}
class SeriesPriceLinePaneView extends SeriesHorizontalLinePaneView {
// eslint-disable-next-line no-useless-constructor
constructor(series) {
super(series);
}
_internal__updateImpl() {
const data = this._internal__lineRendererData;
data._internal_visible = false;
const seriesOptions = this._internal__series._internal_options();
if (!seriesOptions.priceLineVisible || !this._internal__series._internal_visible()) {
return;
}
const lastValueData = this._internal__series._internal_lastValueData(seriesOptions.priceLineSource === 0 /* PriceLineSource.LastBar */);
if (lastValueData._internal_noData) {
return;
}
data._internal_visible = true;
data._internal_y = lastValueData._internal_coordinate;
data._internal_color = this._internal__series._internal_priceLineColor(lastValueData._internal_color);
data._internal_lineWidth = seriesOptions.priceLineWidth;
data._internal_lineStyle = seriesOptions.priceLineStyle;
}
}
class SeriesPriceAxisView extends PriceAxisView {
constructor(source) {
super();
this._private__source = source;
}
_internal__updateRendererData(axisRendererData, paneRendererData, commonRendererData) {
axisRendererData._internal_visible = false;
paneRendererData._internal_visible = false;
const source = this._private__source;
if (!source._internal_visible()) {
return;
}
const seriesOptions = source._internal_options();
const showSeriesLastValue = seriesOptions.lastValueVisible;
const showSymbolLabel = source._internal_title() !== '';
const showPriceAndPercentage = seriesOptions.seriesLastValueMode === 0 /* PriceAxisLastValueMode.LastPriceAndPercentageValue */;
const lastValueData = source._internal_lastValueData(false);
if (lastValueData._internal_noData) {
return;
}
if (showSeriesLastValue) {
axisRendererData._internal_text = this._internal__axisText(lastValueData, showSeriesLastValue, showPriceAndPercentage);
axisRendererData._internal_visible = axisRendererData._internal_text.length !== 0;
}
if (showSymbolLabel || showPriceAndPercentage) {
paneRendererData._internal_text = this._internal__paneText(lastValueData, showSeriesLastValue, showSymbolLabel, showPriceAndPercentage);
paneRendererData._internal_visible = paneRendererData._internal_text.length > 0;
}
const lastValueColor = source._internal_priceLineColor(lastValueData._internal_color);
const colors = this._private__source._internal_model()._internal_colorParser()._internal_generateContrastColors(lastValueColor);
commonRendererData._internal_background = colors._internal_background;
commonRendererData._internal_coordinate = lastValueData._internal_coordinate;
paneRendererData._internal_borderColor = source._internal_model()._internal_backgroundColorAtYPercentFromTop(lastValueData._internal_coordinate / source._internal_priceScale()._internal_height());
axisRendererData._internal_borderColor = lastValueColor;
axisRendererData._internal_color = colors._internal_foreground;
paneRendererData._internal_color = colors._internal_foreground;
}
_internal__paneText(lastValue, showSeriesLastValue, showSymbolLabel, showPriceAndPercentage) {
let result = '';
const title = this._private__source._internal_title();
if (showSymbolLabel && title.length !== 0) {
result += `${title} `;
}
if (showSeriesLastValue && showPriceAndPercentage) {
result += this._private__source._internal_priceScale()._internal_isPercentage() ?
lastValue._internal_formattedPriceAbsolute : lastValue._internal_formattedPricePercentage;
}
return result.trim();
}
_internal__axisText(lastValueData, showSeriesLastValue, showPriceAndPercentage) {
if (!showSeriesLastValue) {
return '';
}
if (!showPriceAndPercentage) {
return lastValueData._internal_text;
}
return this._private__source._internal_priceScale()._internal_isPercentage() ?
lastValueData._internal_formattedPricePercentage : lastValueData._internal_formattedPriceAbsolute;
}
}
function computeFiniteResult(method, valueOne, valueTwo, fallback) {
const firstFinite = Number.isFinite(valueOne);
const secondFinite = Number.isFinite(valueTwo);
if (firstFinite && secondFinite) {
return method(valueOne, valueTwo);
}
return !firstFinite && !secondFinite ? fallback : (firstFinite ? valueOne : valueTwo);
}
class PriceRangeImpl {
constructor(minValue, maxValue) {
this._private__minValue = minValue;
this._private__maxValue = maxValue;
}
_internal_equals(pr) {
if (pr === null) {
return false;
}
return this._private__minValue === pr._private__minValue && this._private__maxValue === pr._private__maxValue;
}
_internal_clone() {
return new PriceRangeImpl(this._private__minValue, this._private__maxValue);
}
_internal_minValue() {
return this._private__minValue;
}
_internal_maxValue() {
return this._private__maxValue;
}
_internal_length() {
return this._private__maxValue - this._private__minValue;
}
_internal_isEmpty() {
return this._private__maxValue === this._private__minValue || Number.isNaN(this._private__maxValue) || Number.isNaN(this._private__minValue);
}
_internal_merge(anotherRange) {
if (anotherRange === null) {
return this;
}
return new PriceRangeImpl(computeFiniteResult(Math.min, this._internal_minValue(), anotherRange._internal_minValue(), -Infinity), computeFiniteResult(Math.max, this._internal_maxValue(), anotherRange._internal_maxValue(), Infinity));
}
_internal_scaleAroundCenter(coeff) {
if (!isNumber(coeff)) {
return;
}
const delta = this._private__maxValue - this._private__minValue;
if (delta === 0) {
return;
}
const center = (this._private__maxValue + this._private__minValue) * 0.5;
let maxDelta = this._private__maxValue - center;
let minDelta = this._private__minValue - center;
maxDelta *= coeff;
minDelta *= coeff;
this._private__maxValue = center + maxDelta;
this._private__minValue = center + minDelta;
}
_internal_shift(delta) {
if (!isNumber(delta)) {
return;
}
this._private__maxValue += delta;
this._private__minValue += delta;
}
_internal_toRaw() {
return {
minValue: this._private__minValue,
maxValue: this._private__maxValue,
};
}
static _internal_fromRaw(raw) {
return (raw === null) ? null : new PriceRangeImpl(raw.minValue, raw.maxValue);
}
}
class AutoscaleInfoImpl {
constructor(priceRange, margins) {
this._private__priceRange = priceRange;
this._private__margins = margins || null;
}
_internal_priceRange() {
return this._private__priceRange;
}
_internal_margins() {
return this._private__margins;
}
_internal_toRaw() {
return {
priceRange: this._private__priceRange === null ? null : this._private__priceRange._internal_toRaw(),
margins: this._private__margins || undefined,
};
}
static _internal_fromRaw(raw) {
return (raw === null) ? null : new AutoscaleInfoImpl(PriceRangeImpl._internal_fromRaw(raw.priceRange), raw.margins);
}
}
/**
* Power-of-2 conflation levels supported by the system.
* These represent the number of original data points that get merged into one conflated point.
*/
const CONFLATION_LEVELS = [2, 4, 8, 16, 32, 64, 128, 256, 512];
/**
* Maximum conflation level supported.
*/
const MAX_CONFLATION_LEVEL = 512;
/**
* Device pixel ratio threshold for conflation.
* Conflation happens when barSpacing is less than 1.0 / devicePixelRatio.
* This ensures we only conflate when we can't physically display the detail.
*/
const DPR_CONFLATION_THRESHOLD = 1;
const CONFLATION_ERROR_MESSAGES = {
_internal_missingPriceValueBuilder: 'Custom series with conflation reducer must have a priceValueBuilder method',
};
class CustomPriceLinePaneView extends SeriesHorizontalLinePaneView {
constructor(series, priceLine) {
super(series);
this._private__priceLine = priceLine;
}
_internal__updateImpl() {
const data = this._internal__lineRendererData;
data._internal_visible = false;
const lineOptions = this._private__priceLine._internal_options();
if (!this._internal__series._internal_visible() || !lineOptions.lineVisible) {
return;
}
const y = this._private__priceLine._internal_yCoord();
if (y === null) {
return;
}
data._internal_visible = true;
data._internal_y = y;
data._internal_color = lineOptions.color;
data._internal_lineWidth = lineOptions.lineWidth;
data._internal_lineStyle = lineOptions.lineStyle;
data._internal_externalId = this._private__priceLine._internal_options().id;
}
}
class CustomPriceLinePriceAxisView extends PriceAxisView {
constructor(series, priceLine) {
super();
this._private__series = series;
this._private__priceLine = priceLine;
}
_internal__updateRendererData(axisRendererData, paneRendererData, commonData) {
axisRendererData._internal_visible = false;
paneRendererData._internal_visible = false;
const options = this._private__priceLine._internal_options();
const labelVisible = options.axisLabelVisible;
const showPaneLabel = options.title !== '';
const series = this._private__series;
if (!labelVisible || !series._internal_visible()) {
return;
}
const y = this._private__priceLine._internal_yCoord();
if (y === null) {
return;
}
if (showPaneLabel) {
paneRendererData._internal_text = options.title;
paneRendererData._internal_visible = true;
}
paneRendererData._internal_borderColor = series._internal_model()._internal_backgroundColorAtYPercentFromTop(y / series._internal_priceScale()._internal_height());
axisRendererData._internal_text = this._private__formatPrice(options.price);
axisRendererData._internal_visible = true;
const colors = this._private__series._internal_model()._internal_colorParser()._internal_generateContrastColors(options.axisLabelColor || options.color);
commonData._internal_background = colors._internal_background;
const textColor = options.axisLabelTextColor || colors._internal_foreground;
axisRendererData._internal_color = textColor; // price text
paneRendererData._internal_color = textColor; // title text
commonData._internal_coordinate = y;
}
_private__formatPrice(price) {
const firstValue = this._private__series._internal_firstValue();
if (firstValue === null) {
return '';
}
return this._private__series._internal_priceScale()._internal_formatPrice(price, firstValue._internal_value);
}
}
class CustomPriceLine {
constructor(series, options) {
this._private__series = series;
this._private__options = options;
this._private__priceLineView = new CustomPriceLinePaneView(series, this);
this._private__priceAxisView = new CustomPriceLinePriceAxisView(series, this);
this._private__panePriceAxisView = new PanePriceAxisView(this._private__priceAxisView, series, series._internal_model());
}
_internal_applyOptions(options) {
merge(this._private__options, options);
this._internal_update();
this._private__series._internal_model()._internal_lightUpdate();
}
_internal_options() {
return this._private__options;
}
_internal_paneView() {
return this._private__priceLineView;
}
_internal_labelPaneView() {
return this._private__panePriceAxisView;
}
_internal_priceAxisView() {
return this._private__priceAxisView;
}
_internal_update() {
this._private__priceLineView._internal_update();
this._private__priceAxisView._internal_update();
}
_internal_yCoord() {
const series = this._private__series;
const priceScale = series._internal_priceScale();
const timeScale = series._internal_model()._internal_timeScale();
if (timeScale._internal_isEmpty() || priceScale._internal_isEmpty()) {
return null;
}
const firstValue = series._internal_firstValue();
if (firstValue === null) {
return null;
}
return priceScale._internal_priceToCoordinate(this._private__options.price, firstValue._internal_value);
}
}
class DataConflater {
constructor() {
this._private__dataCache = new WeakMap();
}
_internal_calculateConflationLevelWithSmoothing(barSpacing, devicePixelRatio, smoothingFactor) {
const conflationThreshold = (DPR_CONFLATION_THRESHOLD / devicePixelRatio) * smoothingFactor;
if (barSpacing >= conflationThreshold) {
return 1;
}
// calculate conflation level as power of 2
const ratio = conflationThreshold / barSpacing;
const conflationLevel = Math.pow(2, Math.floor(Math.log2(ratio)));
// ensure we don't exceed maximum conflation level
return Math.min(conflationLevel, MAX_CONFLATION_LEVEL);
}
_internal_conflateByFactor(data, barsToMerge, customReducer, isCustomSeries = false, priceValueBuilder) {
if (data.length === 0 || barsToMerge <= 1) {
return data;
}
const conflationLevel = this._private__normalizeConflationLevel(barsToMerge);
if (conflationLevel <= 1) {
return data;
}
const entry = this._private__getValidatedCacheEntry(data);
let cachedRows = entry._internal_levelResults.get(conflationLevel);
if (cachedRows !== undefined) {
return cachedRows;
}
cachedRows = this._private__buildRecursively(data, conflationLevel, customReducer, isCustomSeries, priceValueBuilder, entry._internal_levelResults);
entry._internal_levelResults.set(conflationLevel, cachedRows);
return cachedRows;
}
/**
* Efficiently update the last conflated chunk when new data arrives.
* This avoids rebuilding all chunks when just the last data point changes.
*/
_internal_updateLastConflatedChunk(originalData, newLastRow, conflationLevel, customReducer, isCustomSeries = false, priceValueBuilder) {
if (conflationLevel < 1 || originalData.length === 0) {
return originalData;
}
const entry = this._private__getValidatedCacheEntry(originalData);
const cachedRows = entry._internal_levelResults.get(conflationLevel);
if (!cachedRows) {
return this._internal_conflateByFactor(originalData, conflationLevel, customReducer, isCustomSeries, priceValueBuilder);
}
const updatedRows = this._private__updateLastChunkInCache(originalData, newLastRow, conflationLevel, cachedRows, isCustomSeries, customReducer, priceValueBuilder);
entry._internal_levelResults.set(conflationLevel, updatedRows);
return updatedRows;
}
_private__normalizeConflationLevel(barsToMerge) {
if (barsToMerge <= 2) {
return 2;
}
for (const level of CONFLATION_LEVELS) {
if (barsToMerge <= level) {
return level;
}
}
return MAX_CONFLATION_LEVEL;
}
_private__getDataVersion(data) {
if (data.length === 0) {
return 0;
}
// Simple hash based on data length and first/last items
const first = data[0];
const last = data[data.length - 1];
return data.length * 31 + first._internal_index * 17 + last._internal_index * 13;
}
/**
* Build conflation recursively, reusing previous level results.
*/
_private__buildRecursively(data, targetLevel, customReducer, isCustomSeries = false, priceValueBuilder, levelResults = new Map()) {
if (targetLevel === 2) {
return this._private__buildLevelFromOriginal(data, 2, customReducer, isCustomSeries, priceValueBuilder);
}
const prevLevel = targetLevel / 2;
let prevData = levelResults.get(prevLevel);
if (!prevData) {
prevData = this._private__buildRecursively(data, prevLevel, customReducer, isCustomSeries, priceValueBuilder, levelResults);
levelResults.set(prevLevel, prevData);
}
return this._private__buildLevelFromPrevious(prevData, customReducer, isCustomSeries, priceValueBuilder);
}
/**
* Build a conflation level directly from original data (used for level 2).
*/
_private__buildLevelFromOriginal(data, level, customReducer, isCustomSeries = false, priceValueBuilder) {
const chunks = this._private__buildChunksFromData(data, level, customReducer, isCustomSeries, priceValueBuilder);
return this._private__chunksToSeriesPlotRows(chunks, isCustomSeries);
}
/**
* Build a conflation level from the previous level's result.
*/
_private__buildLevelFromPrevious(prevData, customReducer, isCustomSeries = false, priceValueBuilder) {
// Always merge 2 chunks from the previous level
const chunks = this._private__buildChunksFromData(prevData, 2, customReducer, isCustomSeries, priceValueBuilder);
return this._private__chunksToSeriesPlotRows(chunks, isCustomSeries);
}
_private__buildChunksFromData(data, mergeFactor, customReducer, isCustomSeries = false, priceValueBuilder) {
const chunks = [];
for (let i = 0; i < data.length; i += mergeFactor) {
const remaining = data.length - i;
if (remaining >= mergeFactor) {
const merged = this._private__mergeTwoRows(data[i], data[i + 1], customReducer, isCustomSeries, priceValueBuilder);
merged._internal_isRemainder = false;
chunks.push(merged);
}
else {
// remainder of 1 -> fold into previous chunk if possible
if (chunks.length === 0) {
chunks.push(this._private__plotRowToChunk(data[i], true));
}
else {
const prev = chunks[chunks.length - 1];
chunks[chunks.length - 1] = this._private__mergeChunkAndRow(prev, data[i], customReducer, isCustomSeries, priceValueBuilder);
}
}
}
return chunks;
}
_private__sumCount(a, b) {
return (a ?? 1) + (b ?? 1);
}
_private__mergeTwoRows(a, b, customReducer, isCustomSeries = false, priceValueBuilder) {
if (!isCustomSeries || !customReducer || !priceValueBuilder) {
const high = a._internal_value[1 /* PlotRowValueIndex.High */] > b._internal_value[1 /* PlotRowValueIndex.High */] ? a._internal_value[1 /* PlotRowValueIndex.High */] : b._internal_value[1 /* PlotRowValueIndex.High */];
const low = a._internal_value[2 /* PlotRowValueIndex.Low */] < b._internal_value[2 /* PlotRowValueIndex.Low */] ? a._internal_value[2 /* PlotRowValueIndex.Low */] : b._internal_value[2 /* PlotRowValueIndex.Low */];
return {
_internal_startIndex: a._internal_index,
_internal_endIndex: b._internal_index,
_internal_startTime: a._internal_time,
_internal_endTime: b._internal_time,
_internal_open: a._internal_value[0 /* PlotRowValueIndex.Open */],
_internal_high: high,
_internal_low: low,
_internal_close: b._internal_value[3 /* PlotRowValueIndex.Close */],
_internal_originalDataCount: this._private__sumCount(a._internal_originalDataCount, b._internal_originalDataCount),
_internal_conflatedData: undefined,
_internal_isRemainder: false,
};
}
const c1 = this._private__convertToContext(a, priceValueBuilder);
const c2 = this._private__convertToContext(b, priceValueBuilder);
const aggregated = customReducer(c1, c2);
const prices = priceValueBuilder(aggregated);
const p = prices.length ? prices[prices.length - 1] : 0;
return {
_internal_startIndex: a._internal_index,
_internal_endIndex: b._internal_index,
_internal_startTime: a._internal_time,
_internal_endTime: b._internal_time,
_internal_open: a._internal_value[0 /* PlotRowValueIndex.Open */],
_internal_high: Math.max(a._internal_value[1 /* PlotRowValueIndex.High */], p),
_internal_low: Math.min(a._internal_value[2 /* PlotRowValueIndex.Low */], p),
_internal_close: p,
_internal_originalDataCount: this._private__sumCount(a._internal_originalDataCount, b._internal_originalDataCount),
_internal_conflatedData: aggregated,
_internal_isRemainder: false,
};
}
_private__mergeChunkAndRow(chunk, row, customReducer, isCustomSeries = false, priceValueBuilder) {
if (!isCustomSeries || !customReducer || !priceValueBuilder) {
return {
_internal_startIndex: chunk._internal_startIndex,
_internal_endIndex: row._internal_index,
_internal_startTime: chunk._internal_startTime,
_internal_endTime: row._internal_time,
_internal_open: chunk._internal_open,
_internal_high: chunk._internal_high > row._internal_value[1 /* PlotRowValueIndex.High */] ? chunk._internal_high : row._internal_value[1 /* PlotRowValueIndex.High */],
_internal_low: chunk._internal_low < row._internal_value[2 /* PlotRowValueIndex.Low */] ? chunk._internal_low : row._internal_value[2 /* PlotRowValueIndex.Low */],
_internal_close: row._internal_value[3 /* PlotRowValueIndex.Close */],
_internal_originalDataCount: chunk._internal_originalDataCount + (row._internal_originalDataCount ?? 1),
_internal_conflatedData: chunk._internal_conflatedData,
_internal_isRemainder: false,
};
}
const prevAgg = chunk._internal_conflatedData;
const ctx = this._private__convertToContext(row, priceValueBuilder);
// if prevAgg is missing (e.g single-item remainder chunk)
// treat the row as the first aggregate seed to avoid calling builder on undefined.
const prevCtx = prevAgg
? {
data: prevAgg,
index: chunk._internal_startIndex,
originalTime: chunk._internal_startTime,
time: chunk._internal_startTime,
priceValues: priceValueBuilder(prevAgg),
}
: null;
const aggregated = prevCtx ? customReducer(prevCtx, ctx) : (ctx.data);
const prices = prevCtx ? priceValueBuilder(aggregated) : ctx.priceValues;
const p = prices.length ? prices[prices.length - 1] : 0;
return {
_internal_startIndex: chunk._internal_startIndex,
_internal_endIndex: row._internal_index,
_internal_startTime: chunk._internal_startTime,
_internal_endTime: row._internal_time,
_internal_open: chunk._internal_open,
_internal_high: Math.max(chunk._internal_high, p),
_internal_low: Math.min(chunk._internal_low, p),
_internal_close: p,
_internal_originalDataCount: chunk._internal_originalDataCount + (row._internal_originalDataCount ?? 1),
_internal_conflatedData: aggregated,
_internal_isRemainder: false,
};
}
// fold [start, end) with override at overrideIndex
// eslint-disable-next-line max-params
_private__mergeRangeWithOverride(data, start, end, overrideIndex, overrideRow, customReducer, isCustomSeries = false, priceValueBuilder) {
const first = (start === overrideIndex) ? overrideRow : data[start];
if (end - start === 1) {
return this._private__plotRowToChunk(first, true);
}
const second = (start + 1 === overrideIndex) ? overrideRow : data[start + 1];
let chunk = this._private__mergeTwoRows(first, second, customReducer, isCustomSeries, priceValueBuilder);
for (let i = start + 2; i < end; i++) {
const row = (i === overrideIndex) ? overrideRow : data[i];
chunk = this._private__mergeChunkAndRow(chunk, row, customReducer, isCustomSeries, priceValueBuilder);
}
return chunk;
}
_private__convertToContext(item, priceValueBuilder) {
const itemData = item._internal_data ?? {};
return {
data: item._internal_data,
index: item._internal_index,
originalTime: item._internal_originalTime,
time: item._internal_time,
priceValues: priceValueBuilder(itemData),
};
}
_private__chunkToSeriesPlotRow(chunk, isCustomSeries = false) {
const isCustom = isCustomSeries === true;
const hasCustomData = !!chunk._internal_conflatedData;
const base = {
_internal_index: chunk._internal_startIndex,
_internal_time: chunk._internal_startTime,
_internal_originalTime: chunk._internal_startTime,
_internal_value: [
isCustom ? chunk._internal_close : chunk._internal_open,
chunk._internal_high,
chunk._internal_low,
chunk._internal_close,
],
_internal_originalDataCount: chunk._internal_originalDataCount,
};
const data = isCustom
? (hasCustomData ? chunk._internal_conflatedData : { _internal_time: chunk._internal_startTime })
: undefined;
return {
...base,
_internal_data: data,
};
}
_private__chunksToSeriesPlotRows(chunks, isCustomSeries = false) {
return chunks.map((chunk) => this._private__chunkToSeriesPlotRow(chunk, isCustomSeries));
}
/**
* Update only the last chunk in cached conflated data efficiently.
*/
// eslint-disable-next-line max-params
_private__updateLastChunkInCache(originalData, newLastRow, conflationLevel, cachedRows, isCustomSeries = false, customReducer, priceValueBuilder) {
if (cachedRows.length === 0) {
return cachedRows;
}
const lastOriginalIndex = originalData.length - 1;
const chunkStartIndex = Math.floor(lastOriginalIndex / conflationLevel) * conflationLevel;
const chunkEndIndex = Math.min(chunkStartIndex + conflationLevel, originalData.length);
if (chunkEndIndex - chunkStartIndex < conflationLevel && originalData.length > conflationLevel) {
// we must allocate a new array here to do a full rebuild.
const newOriginalData = originalData.slice();
newOriginalData[newOriginalData.length - 1] = newLastRow;
return this._internal_conflateByFactor(newOriginalData, conflationLevel, customReducer, isCustomSeries, priceValueBuilder);
}
const lastChunkIndex = Math.floor((lastOriginalIndex - 1) / conflationLevel);
const newChunkIndex = Math.floor(lastOriginalIndex / conflationLevel);
if (lastChunkIndex === newChunkIndex || cachedRows.length === 1) {
// Data length is within the same chunk OR it's the only chunk
const actualEndIndex = Math.min(chunkStartIndex + conflationLevel, originalData.length);
const count = actualEndIndex - chunkStartIndex;
if (count <= 0) {
// This can happen if originalData.length was 0, though we guard at the top.
return cachedRows;
}
const mergedChunk = count === 1
? this._private__plotRowToChunk((chunkStartIndex === lastOriginalIndex) ? newLastRow : originalData[chunkStartIndex], /* isRemainder*/ true)
: this._private__mergeRangeWithOverride(originalData, chunkStartIndex, actualEndIndex, lastOriginalIndex, newLastRow, customReducer, isCustomSeries, priceValueBuilder);
// in-place update of the cached result: avoid allocating a new array
cachedRows[cachedRows.length - 1] = this._private__chunkToSeriesPlotRow(mergedChunk, isCustomSeries);
return cachedRows;
}
else {
// update affects chunk structure
// we must allocate a new array here to do a full rebuild.
const newOriginalData = originalData.slice();
newOriginalData[newOriginalData.length - 1] = newLastRow;
return this._internal_conflateByFactor(newOriginalData, conflationLevel, customReducer, isCustomSeries, priceValueBuilder);
}
}
_private__plotRowToChunk(item, isRemainder = false) {
const chunk = {
_internal_startIndex: item._internal_index,
_internal_endIndex: item._internal_index,
_internal_startTime: item._internal_time,
_internal_endTime: item._internal_time,
_internal_open: item._internal_value[0 /* PlotRowValueIndex.Open */],
_internal_high: item._internal_value[1 /* PlotRowValueIndex.High */],
_internal_low: item._internal_value[2 /* PlotRowValueIndex.Low */],
_internal_close: item._internal_value[3 /* PlotRowValueIndex.Close */],
_internal_originalDataCount: item._internal_originalDataCount ?? 1,
_internal_conflatedData: item._internal_data,
_internal_isRemainder: isRemainder,
};
return chunk;
}
_private__getValidatedCacheEntry(data) {
const entry = this._private__ensureCacheEntry(data);
const dataVersion = this._private__getDataVersion(data);
if (entry._internal_version !== dataVersion) {
entry._internal_levelResults.clear();
entry._internal_version = dataVersion;
}
return entry;
}
_private__ensureCacheEntry(data) {
let entry = this._private__dataCache.get(data);
if (entry === undefined) {
entry = {
_internal_version: this._private__getDataVersion(data),
_internal_levelResults: new Map(),
};
this._private__dataCache.set(data, entry);
}
return entry;
}
}
class PriceDataSource extends DataSource {
constructor(model) {
super();
this._private__model = model;
}
_internal_model() {
return this._private__model;
}
}
const barStyleFnMap = {
// eslint-disable-next-line @typescript-eslint/naming-convention
Bar: (findBar, barStyle, barIndex, precomputedBars) => {
const upColor = barStyle.upColor;
const downColor = barStyle.downColor;
const currentBar = ensureNotNull(findBar(barIndex, precomputedBars));
const isUp = ensure(currentBar._internal_value[0 /* PlotRowValueIndex.Open */]) <= ensure(currentBar._internal_value[3 /* PlotRowValueIndex.Close */]);
return {
_internal_barColor: currentBar._internal_color ?? (isUp ? upColor : downColor),
};
},
// eslint-disable-next-line @typescript-eslint/naming-convention
Candlestick: (findBar, candlestickStyle, barIndex, precomputedBars) => {
const upColor = candlestickStyle.upColor;
const downColor = candlestickStyle.downColor;
const borderUpColor = candlestickStyle.borderUpColor;
const borderDownColor = candlestickStyle.borderDownColor;
const wickUpColor = candlestickStyle.wickUpColor;
const wickDownColor = candlestickStyle.wickDownColor;
const currentBar = ensureNotNull(findBar(barIndex, precomputedBars));
const isUp = ensure(currentBar._internal_value[0 /* PlotRowValueIndex.Open */]) <= ensure(currentBar._internal_value[3 /* PlotRowValueIndex.Close */]);
return {
_internal_barColor: currentBar._internal_color ?? (isUp ? upColor : downColor),
_internal_barBorderColor: currentBar._internal_borderColor ?? (isUp ? borderUpColor : borderDownColor),
_internal_barWickColor: currentBar._internal_wickColor ?? (isUp ? wickUpColor : wickDownColor),
};
},
// eslint-disable-next-line @typescript-eslint/naming-convention
Custom: (findBar, customStyle, barIndex, precomputedBars) => {
const currentBar = ensureNotNull(findBar(barIndex, precomputedBars));
return {
_internal_barColor: currentBar._internal_color ?? customStyle.color,
};
},
// eslint-disable-next-line @typescript-eslint/naming-convention
Area: (findBar, areaStyle, barIndex, precomputedBars) => {
const currentBar = ensureNotNull(findBar(barIndex, precomputedBars));
return {
_internal_barColor: currentBar._internal_lineColor ?? areaStyle.lineColor,
_internal_lineColor: currentBar._internal_lineColor ?? areaStyle.lineColor,
_internal_topColor: currentBar._internal_topColor ?? areaStyle.topColor,
_internal_bottomColor: currentBar._internal_bottomColor ?? areaStyle.bottomColor,
};
},
// eslint-disable-next-line @typescript-eslint/naming-convention
Baseline: (findBar, baselineStyle, barIndex, precomputedBars) => {
const currentBar = ensureNotNull(findBar(barIndex, precomputedBars));
const isAboveBaseline = currentBar._internal_value[3 /* PlotRowValueIndex.Close */] >= baselineStyle.baseValue.price;
return {
_internal_barColor: isAboveBaseline ? baselineStyle.topLineColor : baselineStyle.bottomLineColor,
_internal_topLineColor: currentBar._internal_topLineColor ?? baselineStyle.topLineColor,
_internal_bottomLineColor: currentBar._internal_bottomLineColor ?? baselineStyle.bottomLineColor,
_internal_topFillColor1: currentBar._internal_topFillColor1 ?? baselineStyle.topFillColor1,
_internal_topFillColor2: currentBar._internal_topFillColor2 ?? baselineStyle.topFillColor2,
_internal_bottomFillColor1: currentBar._internal_bottomFillColor1 ?? baselineStyle.bottomFillColor1,
_internal_bottomFillColor2: currentBar._internal_bottomFillColor2 ?? baselineStyle.bottomFillColor2,
};
},
// eslint-disable-next-line @typescript-eslint/naming-convention
Line: (findBar, lineStyle, barIndex, precomputedBars) => {
const currentBar = ensureNotNull(findBar(barIndex, precomputedBars));
return {
_internal_barColor: currentBar._internal_color ?? lineStyle.color,
_internal_lineColor: currentBar._internal_color ?? lineStyle.color,
};
},
// eslint-disable-next-line @typescript-eslint/naming-convention
Histogram: (findBar, histogramStyle, barIndex, precomputedBars) => {
const currentBar = ensureNotNull(findBar(barIndex, precomputedBars));
return {
_internal_barColor: currentBar._internal_color ?? histogramStyle.color,
};
},
};
class SeriesBarColorer {
constructor(series) {
this._private__findBar = (barIndex, precomputedBars) => {
if (precomputedBars !== undefined) {
return precomputedBars._internal_value;
}
return this._private__series._internal_bars()._internal_valueAt(barIndex);
};
this._private__series = series;
this._private__styleGetter = barStyleFnMap[series._internal_seriesType()];
}
_internal_barStyle(barIndex, precomputedBars) {
// precomputedBars: {value: [Array BarValues], previousValue: [Array BarValues] | undefined}
// Used to avoid binary search if bars are already known
return this._private__styleGetter(this._private__findBar, this._private__series._internal_options(), barIndex, precomputedBars);
}
}
/**
* Binary function that accepts two arguments (the first of the type of array elements, and the second is always val), and returns a value convertible to bool.
* The value returned indicates whether the first argument is considered to go before the second.
* The function shall not modify any of its arguments.
*/
function boundCompare(lower, arr, value, compare, start = 0, to = arr.length) {
let count = to - start;
while (0 < count) {
const count2 = (count >> 1);
const mid = start + count2;
if (compare(arr[mid], value) === lower) {
start = mid + 1;
count -= count2 + 1;
}
else {
count = count2;
}
}
return start;
}
const lowerBound = boundCompare.bind(null, true);
const upperBound = boundCompare.bind(null, false);
/**
* Search direction if no data found at provided index
*/
var MismatchDirection;
(function (MismatchDirection) {
/**
* Search the nearest left item
*/
MismatchDirection[MismatchDirection["NearestLeft"] = -1] = "NearestLeft";
/**
* Do not search
*/
MismatchDirection[MismatchDirection["None"] = 0] = "None";
/**
* Search the nearest right item
*/
MismatchDirection[MismatchDirection["NearestRight"] = 1] = "NearestRight";
})(MismatchDirection || (MismatchDirection = {}));
// TODO: think about changing it dynamically
const CHUNK_SIZE = 30;
/**
* PlotList is an array of plot rows
* each plot row consists of key (index in timescale) and plot value map
*/
class PlotList {
constructor() {
this._private__items = [];
this._private__minMaxCache = new Map();
this._private__rowSearchCache = new Map();
this._private__indices = [];
}
// @returns Last row
_internal_last() {
return this._internal_size() > 0 ? this._private__items[this._private__items.length - 1] : null;
}
_internal_firstIndex() {
return this._internal_size() > 0 ? this._private__indexAt(0) : null;
}
_internal_lastIndex() {
return this._internal_size() > 0 ? this._private__indexAt((this._private__items.length - 1)) : null;
}
_internal_size() {
return this._private__items.length;
}
_internal_isEmpty() {
return this._internal_size() === 0;
}
_internal_contains(index) {
return this._private__search(index, 0 /* MismatchDirection.None */) !== null;
}
_internal_valueAt(index) {
return this._internal_search(index);
}
_internal_search(index, searchMode = 0 /* MismatchDirection.None */) {
const pos = this._private__search(index, searchMode);
if (pos === null) {
return null;
}
return {
...this._private__valueAt(pos),
_internal_index: this._private__indexAt(pos),
};
}
_internal_rows() {
return this._private__items;
}
_internal_minMaxOnRangeCached(start, end, plots) {
// this code works for single series only
// could fail after whitespaces implementation
if (this._internal_isEmpty()) {
return null;
}
let result = null;
for (const plot of plots) {
const plotMinMax = this._private__minMaxOnRangeCachedImpl(start, end, plot);
result = mergeMinMax(result, plotMinMax);
}
return result;
}
_internal_setData(plotRows) {
this._private__rowSearchCache.clear();
this._private__minMaxCache.clear();
this._private__items = plotRows;
this._private__indices = plotRows.map((plotRow) => plotRow._internal_index);
}
// TimePointIndex values for fulfilled data points
_internal_indices() {
return this._private__indices;
}
_private__indexAt(offset) {
return this._private__items[offset]._internal_index;
}
_private__valueAt(offset) {
return this._private__items[offset];
}
_private__search(index, searchMode) {
const exactPos = this._private__bsearch(index);
if (exactPos === null && searchMode !== 0 /* MismatchDirection.None */) {
switch (searchMode) {
case -1 /* MismatchDirection.NearestLeft */:
return this._private__searchNearestLeft(index);
case 1 /* MismatchDirection.NearestRight */:
return this._private__searchNearestRight(index);
default:
throw new TypeError('Unknown search mode');
}
}
return exactPos;
}
_private__searchNearestLeft(index) {
let nearestLeftPos = this._private__lowerbound(index);
if (nearestLeftPos > 0) {
nearestLeftPos = nearestLeftPos - 1;
}
return (nearestLeftPos !== this._private__items.length && this._private__indexAt(nearestLeftPos) < index) ? nearestLeftPos : null;
}
_private__searchNearestRight(index) {
const nearestRightPos = this._private__upperbound(index);
return (nearestRightPos !== this._private__items.length && index < this._private__indexAt(nearestRightPos)) ? nearestRightPos : null;
}
_private__bsearch(index) {
const start = this._private__lowerbound(index);
if (start !== this._private__items.length && !(index < this._private__items[start]._internal_index)) {
return start;
}
return null;
}
_private__lowerbound(index) {
return lowerBound(this._private__items, index, (a, b) => a._internal_index < b);
}
_private__upperbound(index) {
return upperBound(this._private__items, index, (a, b) => a._internal_index > b);
}
_private__plotMinMax(startIndex, endIndexExclusive, plotIndex) {
let result = null;
for (let i = startIndex; i < endIndexExclusive; i++) {
const values = this._private__items[i]._internal_value;
const v = values[plotIndex];
if (Number.isNaN(v)) {
continue;
}
if (result === null) {
result = { _internal_min: v, _internal_max: v };
}
else {
if (v < result._internal_min) {
result._internal_min = v;
}
if (v > result._internal_max) {
result._internal_max = v;
}
}
}
return result;
}
_private__minMaxOnRangeCachedImpl(start, end, plotIndex) {
// this code works for single series only
// could fail after whitespaces implementation
if (this._internal_isEmpty()) {
return null;
}
let result = null;
// assume that bar indexes only increase
const firstIndex = ensureNotNull(this._internal_firstIndex());
const lastIndex = ensureNotNull(this._internal_lastIndex());
const s = Math.max(start, firstIndex);
const e = Math.min(end, lastIndex);
const cachedLow = Math.ceil(s / CHUNK_SIZE) * CHUNK_SIZE;
const cachedHigh = Math.max(cachedLow, Math.floor(e / CHUNK_SIZE) * CHUNK_SIZE);
{
const startIndex = this._private__lowerbound(s);
const endIndex = this._private__upperbound(Math.min(e, cachedLow, end)); // non-inclusive end
const plotMinMax = this._private__plotMinMax(startIndex, endIndex, plotIndex);
result = mergeMinMax(result, plotMinMax);
}
let minMaxCache = this._private__minMaxCache.get(plotIndex);
if (minMaxCache === undefined) {
minMaxCache = new Map();
this._private__minMaxCache.set(plotIndex, minMaxCache);
}
// now go cached
for (let c = Math.max(cachedLow + 1, s); c < cachedHigh; c += CHUNK_SIZE) {
const chunkIndex = Math.floor(c / CHUNK_SIZE);
let chunkMinMax = minMaxCache.get(chunkIndex);
if (chunkMinMax === undefined) {
const chunkStart = this._private__lowerbound(chunkIndex * CHUNK_SIZE);
const chunkEnd = this._private__upperbound((chunkIndex + 1) * CHUNK_SIZE - 1);
chunkMinMax = this._private__plotMinMax(chunkStart, chunkEnd, plotIndex);
minMaxCache.set(chunkIndex, chunkMinMax);
}
result = mergeMinMax(result, chunkMinMax);
}
// tail
{
const startIndex = this._private__lowerbound(cachedHigh);
const endIndex = this._private__upperbound(e); // non-inclusive end
const plotMinMax = this._private__plotMinMax(startIndex, endIndex, plotIndex);
result = mergeMinMax(result, plotMinMax);
}
return result;
}
}
function mergeMinMax(first, second) {
if (first === null) {
return second;
}
else {
if (second === null) {
return first;
}
else {
// merge MinMax values
const min = Math.min(first._internal_min, second._internal_min);
const max = Math.max(first._internal_max, second._internal_max);
return { _internal_min: min, _internal_max: max };
}
}
}
function createSeriesPlotList() {
return new PlotList();
}
const drawingUtils = {
setLineStyle: setLineStyle,
};
class PrimitiveRendererWrapper {
constructor(baseRenderer) {
this._private__baseRenderer = baseRenderer;
}
_internal_draw(target, isHovered, hitTestData) {
this._private__baseRenderer.draw(target, drawingUtils);
}
_internal_drawBackground(target, isHovered, hitTestData) {
this._private__baseRenderer.drawBackground?.(target, drawingUtils);
}
}
class PrimitivePaneViewWrapper {
constructor(paneView) {
this._private__cache = null;
this._private__paneView = paneView;
}
_internal_renderer() {
const baseRenderer = this._private__paneView.renderer();
if (baseRenderer === null) {
return null;
}
if (this._private__cache?._internal_base === baseRenderer) {
return this._private__cache._internal_wrapper;
}
const wrapper = new PrimitiveRendererWrapper(baseRenderer);
this._private__cache = {
_internal_base: baseRenderer,
_internal_wrapper: wrapper,
};
return wrapper;
}
_internal_zOrder() {
return this._private__paneView.zOrder?.() ?? 'normal';
}
}
class PrimitiveWrapper {
constructor(primitive) {
this._private__paneViewsCache = null;
this._internal__primitive = primitive;
}
_internal_primitive() {
return this._internal__primitive;
}
_internal_updateAllViews() {
this._internal__primitive.updateAllViews?.();
}
_internal_paneViews() {
const base = this._internal__primitive.paneViews?.() ?? [];
if (this._private__paneViewsCache?._internal_base === base) {
return this._private__paneViewsCache._internal_wrapper;
}
const wrapper = base.map((pw) => new PrimitivePaneViewWrapper(pw));
this._private__paneViewsCache = {
_internal_base: base,
_internal_wrapper: wrapper,
};
return wrapper;
}
_internal_hitTest(x, y) {
return this._internal__primitive.hitTest?.(x, y) ?? null;
}
}
let PanePrimitiveWrapper$1 = class PanePrimitiveWrapper extends PrimitiveWrapper {
_internal_labelPaneViews() {
return [];
}
};
class SeriesPrimitiveRendererWrapper {
constructor(baseRenderer) {
this._private__baseRenderer = baseRenderer;
}
_internal_draw(target, isHovered, hitTestData) {
this._private__baseRenderer.draw(target, drawingUtils);
}
_internal_drawBackground(target, isHovered, hitTestData) {
this._private__baseRenderer.drawBackground?.(target, drawingUtils);
}
}
class SeriesPrimitivePaneViewWrapper {
constructor(paneView) {
this._private__cache = null;
this._private__paneView = paneView;
}
_internal_renderer() {
const baseRenderer = this._private__paneView.renderer();
if (baseRenderer === null) {
return null;
}
if (this._private__cache?._internal_base === baseRenderer) {
return this._private__cache._internal_wrapper;
}
const wrapper = new SeriesPrimitiveRendererWrapper(baseRenderer);
this._private__cache = {
_internal_base: baseRenderer,
_internal_wrapper: wrapper,
};
return wrapper;
}
_internal_zOrder() {
return this._private__paneView.zOrder?.() ?? 'normal';
}
}
function getAxisViewData(baseView) {
return {
_internal_text: baseView.text(),
_internal_coordinate: baseView.coordinate(),
_internal_fixedCoordinate: baseView.fixedCoordinate?.(),
_internal_color: baseView.textColor(),
_internal_background: baseView.backColor(),
_internal_visible: baseView.visible?.() ?? true,
_internal_tickVisible: baseView.tickVisible?.() ?? true,
};
}
class SeriesPrimitiveTimeAxisViewWrapper {
constructor(baseView, timeScale) {
this._private__renderer = new TimeAxisViewRenderer();
this._private__baseView = baseView;
this._private__timeScale = timeScale;
}
_internal_renderer() {
this._private__renderer._internal_setData({
_internal_width: this._private__timeScale._internal_width(),
...getAxisViewData(this._private__baseView),
});
return this._private__renderer;
}
}
class SeriesPrimitivePriceAxisViewWrapper extends PriceAxisView {
constructor(baseView, priceScale) {
super();
this._private__baseView = baseView;
this._private__priceScale = priceScale;
}
_internal__updateRendererData(axisRendererData, paneRendererData, commonRendererData) {
const data = getAxisViewData(this._private__baseView);
commonRendererData._internal_background = data._internal_background;
axisRendererData._internal_color = data._internal_color;
const additionalPadding = 2 / 12 * this._private__priceScale._internal_fontSize();
commonRendererData._internal_additionalPaddingTop = additionalPadding;
commonRendererData._internal_additionalPaddingBottom = additionalPadding;
commonRendererData._internal_coordinate = data._internal_coordinate;
commonRendererData._internal_fixedCoordinate = data._internal_fixedCoordinate;
axisRendererData._internal_text = data._internal_text;
axisRendererData._internal_visible = data._internal_visible;
axisRendererData._internal_tickVisible = data._internal_tickVisible;
}
}
class SeriesPrimitiveWrapper extends PrimitiveWrapper {
constructor(primitive, series) {
super(primitive);
this._private__timeAxisViewsCache = null;
this._private__priceAxisViewsCache = null;
this._private__priceAxisPaneViewsCache = null;
this._private__timeAxisPaneViewsCache = null;
this._private__series = series;
}
_internal_timeAxisViews() {
const base = this._internal__primitive.timeAxisViews?.() ?? [];
if (this._private__timeAxisViewsCache?._internal_base === base) {
return this._private__timeAxisViewsCache._internal_wrapper;
}
const timeScale = this._private__series._internal_model()._internal_timeScale();
const wrapper = base.map((aw) => new SeriesPrimitiveTimeAxisViewWrapper(aw, timeScale));
this._private__timeAxisViewsCache = {
_internal_base: base,
_internal_wrapper: wrapper,
};
return wrapper;
}
_internal_priceAxisViews() {
const base = this._internal__primitive.priceAxisViews?.() ?? [];
if (this._private__priceAxisViewsCache?._internal_base === base) {
return this._private__priceAxisViewsCache._internal_wrapper;
}
const priceScale = this._private__series._internal_priceScale();
const wrapper = base.map((aw) => new SeriesPrimitivePriceAxisViewWrapper(aw, priceScale));
this._private__priceAxisViewsCache = {
_internal_base: base,
_internal_wrapper: wrapper,
};
return wrapper;
}
_internal_priceAxisPaneViews() {
const base = this._internal__primitive.priceAxisPaneViews?.() ?? [];
if (this._private__priceAxisPaneViewsCache?._internal_base === base) {
return this._private__priceAxisPaneViewsCache._internal_wrapper;
}
const wrapper = base.map((pw) => new SeriesPrimitivePaneViewWrapper(pw));
this._private__priceAxisPaneViewsCache = {
_internal_base: base,
_internal_wrapper: wrapper,
};
return wrapper;
}
_internal_timeAxisPaneViews() {
const base = this._internal__primitive.timeAxisPaneViews?.() ?? [];
if (this._private__timeAxisPaneViewsCache?._internal_base === base) {
return this._private__timeAxisPaneViewsCache._internal_wrapper;
}
const wrapper = base.map((pw) => new SeriesPrimitivePaneViewWrapper(pw));
this._private__timeAxisPaneViewsCache = {
_internal_base: base,
_internal_wrapper: wrapper,
};
return wrapper;
}
_internal_autoscaleInfo(startTimePoint, endTimePoint) {
return (this._internal__primitive.autoscaleInfo?.(startTimePoint, endTimePoint) ?? null);
}
}
function extractPrimitivePaneViews(primitives, extractor, zOrder, destination) {
primitives.forEach((wrapper) => {
extractor(wrapper).forEach((paneView) => {
if (paneView._internal_zOrder() !== zOrder) {
return;
}
destination.push(paneView);
});
});
}
function primitivePaneViewsExtractor(wrapper) {
return wrapper._internal_paneViews();
}
function primitivePricePaneViewsExtractor(wrapper) {
return wrapper._internal_priceAxisPaneViews();
}
function primitiveTimePaneViewsExtractor(wrapper) {
return wrapper._internal_timeAxisPaneViews();
}
const lineBasedSeries = ['Area', 'Line', 'Baseline'];
class Series extends PriceDataSource {
constructor(model, seriesType, options, createPaneView, customPaneView) {
super(model);
this._private__data = createSeriesPlotList();
this._private__priceLineView = new SeriesPriceLinePaneView(this);
this._private__customPriceLines = [];
this._private__baseHorizontalLineView = new SeriesHorizontalBaseLinePaneView(this);
this._private__lastPriceAnimationPaneView = null;
this._private__barColorerCache = null;
this._private__animationTimeoutId = null;
this._private__primitives = [];
this._private__dataConflater = new DataConflater();
this._private__conflationByFactorCache = new Map();
this._private__customConflationReducer = null;
this._private__options = options;
this._private__seriesType = seriesType;
const priceAxisView = new SeriesPriceAxisView(this);
this._private__priceAxisViews = [priceAxisView];
this._private__panePriceAxisView = new PanePriceAxisView(priceAxisView, this, model);
if (lineBasedSeries.includes(this._private__seriesType)) {
this._private__lastPriceAnimationPaneView = new SeriesLastPriceAnimationPaneView(this);
}
this._private__recreateFormatter();
this._private__paneView = createPaneView(this, this._internal_model(), customPaneView);
if (this._private__seriesType === 'Custom') {
const paneView = this._private__paneView;
if (paneView._internal_conflationReducer) {
this._internal_setCustomConflationReducer(paneView._internal_conflationReducer);
}
}
}
_internal_destroy() {
if (this._private__animationTimeoutId !== null) {
clearTimeout(this._private__animationTimeoutId);
}
}
_internal_priceLineColor(lastBarColor) {
return this._private__options.priceLineColor || lastBarColor;
}
_internal_lastValueData(globalLast) {
const noDataRes = { _internal_noData: true };
const priceScale = this._internal_priceScale();
if (this._internal_model()._internal_timeScale()._internal_isEmpty() || priceScale._internal_isEmpty() || this._private__data._internal_isEmpty()) {
return noDataRes;
}
const visibleBars = this._internal_model()._internal_timeScale()._internal_visibleStrictRange();
const firstValue = this._internal_firstValue();
if (visibleBars === null || firstValue === null) {
return noDataRes;
}
// find range of bars inside range
// TODO: make it more optimal
let bar;
let lastIndex;
if (globalLast) {
const lastBar = this._private__data._internal_last();
if (lastBar === null) {
return noDataRes;
}
bar = lastBar;
lastIndex = lastBar._internal_index;
}
else {
const endBar = this._private__data._internal_search(visibleBars._internal_right(), -1 /* MismatchDirection.NearestLeft */);
if (endBar === null) {
return noDataRes;
}
bar = this._private__data._internal_valueAt(endBar._internal_index);
if (bar === null) {
return noDataRes;
}
lastIndex = endBar._internal_index;
}
const price = bar._internal_value[3 /* PlotRowValueIndex.Close */];
const barColorer = this._internal_barColorer();
const style = barColorer._internal_barStyle(lastIndex, { _internal_value: bar });
const coordinate = priceScale._internal_priceToCoordinate(price, firstValue._internal_value);
return {
_internal_noData: false,
_internal_price: price,
_internal_text: priceScale._internal_formatPrice(price, firstValue._internal_value),
_internal_formattedPriceAbsolute: priceScale._internal_formatPriceAbsolute(price),
_internal_formattedPricePercentage: priceScale._internal_formatPricePercentage(price, firstValue._internal_value),
_internal_color: style._internal_barColor,
_internal_coordinate: coordinate,
_internal_index: lastIndex,
};
}
_internal_barColorer() {
if (this._private__barColorerCache !== null) {
return this._private__barColorerCache;
}
this._private__barColorerCache = new SeriesBarColorer(this);
return this._private__barColorerCache;
}
_internal_options() {
return this._private__options;
}
_internal_applyOptions(options) {
const model = this._internal_model();
const { priceScaleId, visible, priceFormat } = options;
if (priceScaleId !== undefined && priceScaleId !== this._private__options.priceScaleId) {
// series cannot do it itself, ask model
model._internal_moveSeriesToScale(this, priceScaleId);
}
if (visible !== undefined && visible !== this._private__options.visible) {
model._internal_invalidateVisibleSeries();
}
// Check if conflation-related options are changing
const conflationOptionsChanged = options.conflationThresholdFactor !== undefined;
merge(this._private__options, options);
if (conflationOptionsChanged) {
this._private__conflationByFactorCache.clear();
this._internal_model()._internal_lightUpdate();
}
if (priceFormat !== undefined) {
this._private__recreateFormatter();
// updated formatter might affect rendering and as a consequence of this the width of price axis might be changed
// thus we need to force the chart to do a full update to apply changes correctly
// full update is quite heavy operation in terms of performance
// but updating formatter looks like quite rare so forcing a full update here shouldn't affect the performance a lot
model._internal_fullUpdate();
}
model._internal_updateSource(this);
// a series might affect crosshair by some options (like crosshair markers)
// that's why we need to update crosshair as well
model._internal_updateCrosshair();
this._private__paneView._internal_update('options');
}
_internal_setData(data, updateInfo) {
this._private__data._internal_setData(data);
this._private__conflationByFactorCache.clear();
const ts = this._internal_model()._internal_timeScale();
const tsOptions = ts._internal_options();
if (tsOptions.enableConflation && tsOptions.precomputeConflationOnInit) {
this._private__precomputeConflationLevels(tsOptions.precomputeConflationPriority);
}
this._private__paneView._internal_update('data');
if (this._private__lastPriceAnimationPaneView !== null) {
if (updateInfo && updateInfo._internal_lastBarUpdatedOrNewBarsAddedToTheRight) {
this._private__lastPriceAnimationPaneView._internal_onNewRealtimeDataReceived();
}
else if (data.length === 0) {
this._private__lastPriceAnimationPaneView._internal_onDataCleared();
}
}
const sourcePane = this._internal_model()._internal_paneForSource(this);
this._internal_model()._internal_recalculatePane(sourcePane);
this._internal_model()._internal_updateSource(this);
this._internal_model()._internal_updateCrosshair();
this._internal_model()._internal_lightUpdate();
}
_internal_createPriceLine(options) {
const result = new CustomPriceLine(this, options);
this._private__customPriceLines.push(result);
this._internal_model()._internal_updateSource(this);
return result;
}
_internal_removePriceLine(line) {
const index = this._private__customPriceLines.indexOf(line);
if (index !== -1) {
this._private__customPriceLines.splice(index, 1);
}
this._internal_model()._internal_updateSource(this);
}
_internal_priceLines() {
return this._private__customPriceLines;
}
_internal_seriesType() {
return this._private__seriesType;
}
_internal_firstValue() {
const bar = this._internal_firstBar();
if (bar === null) {
return null;
}
return {
_internal_value: bar._internal_value[3 /* PlotRowValueIndex.Close */],
_internal_timePoint: bar._internal_time,
};
}
_internal_firstBar() {
const visibleBars = this._internal_model()._internal_timeScale()._internal_visibleStrictRange();
if (visibleBars === null) {
return null;
}
const startTimePoint = visibleBars._internal_left();
return this._private__data._internal_search(startTimePoint, 1 /* MismatchDirection.NearestRight */);
}
_internal_bars() {
return this._private__data;
}
_internal_setCustomConflationReducer(reducer) {
this._private__customConflationReducer = reducer;
// reset cache to respect new reducer
this._private__conflationByFactorCache.clear();
}
/**
* Check if conflation is currently enabled for this series.
*/
_internal_isConflationEnabled() {
const timeScale = this._internal_model()._internal_timeScale();
if (!timeScale._internal_options().enableConflation) {
return false;
}
return this._private__calculateConflationFactor() > 1;
}
/**
* Efficiently update conflation when only the last data point changes.
* This avoids rebuilding all conflated chunks.
*/
_internal_updateLastConflatedChunk(newLastRow) {
if (!this._internal_isConflationEnabled()) {
return;
}
const conflationFactor = this._private__calculateConflationFactor();
if (!this._private__conflationByFactorCache.has(conflationFactor)) {
return;
}
const isCustomSeries = this._private__seriesType === 'Custom';
const customReducer = isCustomSeries ? this._private__customConflationReducer || undefined : undefined;
const priceValueBuilder = isCustomSeries && this._private__paneView._internal_priceValueBuilder
? (item) => {
const customPaneView = this._private__paneView;
const plotRow = item;
const result = customPaneView._internal_priceValueBuilder(plotRow);
return Array.isArray(result) ? result : [typeof result === 'number' ? result : 0];
}
: undefined;
const updatedConflatedRows = this._private__dataConflater._internal_updateLastConflatedChunk(this._private__data._internal_rows(), newLastRow, conflationFactor, customReducer, isCustomSeries, priceValueBuilder);
const conflatedList = createSeriesPlotList();
conflatedList._internal_setData(updatedConflatedRows);
this._private__conflationByFactorCache.set(conflationFactor, conflatedList);
}
_internal_conflatedBars() {
const timeScale = this._internal_model()._internal_timeScale();
const conflationEnabled = timeScale._internal_options().enableConflation;
if (this._private__seriesType === 'Custom' && this._private__customConflationReducer === null) {
return this._private__data;
}
if (!conflationEnabled) {
return this._private__data;
}
const factor = this._private__calculateConflationFactor();
const cached = this._private__conflationByFactorCache.get(factor);
if (cached) {
return cached;
}
this._private__regenerateConflatedDataByFactor(factor);
const built = this._private__conflationByFactorCache.get(factor);
return built ?? this._private__data;
}
_internal_dataAt(time) {
const prices = this._private__data._internal_valueAt(time);
if (prices === null) {
return null;
}
if (this._private__seriesType === 'Bar' || this._private__seriesType === 'Candlestick' || this._private__seriesType === 'Custom') {
return {
_internal_open: prices._internal_value[0 /* PlotRowValueIndex.Open */],
_internal_high: prices._internal_value[1 /* PlotRowValueIndex.High */],
_internal_low: prices._internal_value[2 /* PlotRowValueIndex.Low */],
_internal_close: prices._internal_value[3 /* PlotRowValueIndex.Close */],
};
}
else {
return prices._internal_value[3 /* PlotRowValueIndex.Close */];
}
}
_internal_topPaneViews(pane) {
const res = [];
extractPrimitivePaneViews(this._private__primitives, primitivePaneViewsExtractor, 'top', res);
const animationPaneView = this._private__lastPriceAnimationPaneView;
if (animationPaneView === null || !animationPaneView._internal_visible()) {
return res;
}
if (this._private__animationTimeoutId === null && animationPaneView._internal_animationActive()) {
this._private__animationTimeoutId = setTimeout(() => {
this._private__animationTimeoutId = null;
this._internal_model()._internal_cursorUpdate();
}, 0);
}
animationPaneView._internal_invalidateStage();
res.unshift(animationPaneView);
return res;
}
_internal_paneViews() {
const res = [];
if (!this._private__isOverlay()) {
res.push(this._private__baseHorizontalLineView);
}
res.push(this._private__paneView, this._private__priceLineView);
const priceLineViews = this._private__customPriceLines.map((line) => line._internal_paneView());
res.push(...priceLineViews);
extractPrimitivePaneViews(this._private__primitives, primitivePaneViewsExtractor, 'normal', res);
return res;
}
_internal_bottomPaneViews() {
return this._private__extractPaneViews(primitivePaneViewsExtractor, 'bottom');
}
_internal_pricePaneViews(zOrder) {
return this._private__extractPaneViews(primitivePricePaneViewsExtractor, zOrder);
}
_internal_timePaneViews(zOrder) {
return this._private__extractPaneViews(primitiveTimePaneViewsExtractor, zOrder);
}
_internal_primitiveHitTest(x, y) {
return this._private__primitives
.map((primitive) => primitive._internal_hitTest(x, y))
.filter((result) => result !== null);
}
_internal_labelPaneViews() {
return [
this._private__panePriceAxisView,
...this._private__customPriceLines.map((line) => line._internal_labelPaneView()),
];
}
_internal_priceAxisViews(pane, priceScale) {
if (priceScale !== this._internal__priceScale && !this._private__isOverlay()) {
return [];
}
const result = [...this._private__priceAxisViews];
for (const customPriceLine of this._private__customPriceLines) {
result.push(customPriceLine._internal_priceAxisView());
}
this._private__primitives.forEach((wrapper) => {
result.push(...wrapper._internal_priceAxisViews());
});
return result;
}
_internal_timeAxisViews() {
const res = [];
this._private__primitives.forEach((wrapper) => {
res.push(...wrapper._internal_timeAxisViews());
});
return res;
}
_internal_autoscaleInfo(startTimePoint, endTimePoint) {
if (this._private__options.autoscaleInfoProvider !== undefined) {
const autoscaleInfo = this._private__options.autoscaleInfoProvider(() => {
const res = this._private__autoscaleInfoImpl(startTimePoint, endTimePoint);
return (res === null) ? null : res._internal_toRaw();
});
return AutoscaleInfoImpl._internal_fromRaw(autoscaleInfo);
}
return this._private__autoscaleInfoImpl(startTimePoint, endTimePoint);
}
_internal_base() {
const priceFormat = this._private__options.priceFormat;
return priceFormat.base ?? (1 / priceFormat.minMove);
}
_internal_formatter() {
return this._private__formatter;
}
_internal_updateAllViews() {
this._private__paneView._internal_update();
for (const priceAxisView of this._private__priceAxisViews) {
priceAxisView._internal_update();
}
for (const customPriceLine of this._private__customPriceLines) {
customPriceLine._internal_update();
}
this._private__priceLineView._internal_update();
this._private__baseHorizontalLineView._internal_update();
this._private__lastPriceAnimationPaneView?._internal_update();
this._private__primitives.forEach((wrapper) => wrapper._internal_updateAllViews());
}
_internal_priceScale() {
return ensureNotNull(super._internal_priceScale());
}
_internal_markerDataAtIndex(index) {
const getValue = (this._private__seriesType === 'Line' || this._private__seriesType === 'Area' || this._private__seriesType === 'Baseline') &&
this._private__options.crosshairMarkerVisible;
if (!getValue) {
return null;
}
const bar = this._private__data._internal_valueAt(index);
if (bar === null) {
return null;
}
const price = bar._internal_value[3 /* PlotRowValueIndex.Close */];
const radius = this._private__markerRadius();
const borderColor = this._private__markerBorderColor();
const borderWidth = this._private__markerBorderWidth();
const backgroundColor = this._private__markerBackgroundColor(index);
return { _internal_price: price, _internal_radius: radius, _internal_borderColor: borderColor, _internal_borderWidth: borderWidth, _internal_backgroundColor: backgroundColor };
}
_internal_title() {
return this._private__options.title;
}
_internal_visible() {
return this._private__options.visible;
}
_internal_attachPrimitive(primitive) {
this._private__primitives.push(new SeriesPrimitiveWrapper(primitive, this));
}
_internal_detachPrimitive(source) {
this._private__primitives = this._private__primitives.filter((wrapper) => wrapper._internal_primitive() !== source);
}
_internal_customSeriesPlotValuesBuilder() {
if (this._private__seriesType !== 'Custom') {
return undefined;
}
return (data) => {
return this._private__paneView._internal_priceValueBuilder(data);
};
}
_internal_customSeriesWhitespaceCheck() {
if (this._private__seriesType !== 'Custom') {
return undefined;
}
return (data) => {
return this._private__paneView._internal_isWhitespace(data);
};
}
_internal_fulfilledIndices() {
return this._private__data._internal_indices();
}
_private__isOverlay() {
const priceScale = this._internal_priceScale();
return !isDefaultPriceScale(priceScale._internal_id());
}
_private__autoscaleInfoImpl(startTimePoint, endTimePoint) {
if (!isInteger(startTimePoint) || !isInteger(endTimePoint) || this._private__data._internal_isEmpty()) {
return null;
}
// TODO: refactor this
// series data is strongly hardcoded to keep bars
const plots = this._private__seriesType === 'Line' || this._private__seriesType === 'Area' || this._private__seriesType === 'Baseline' || this._private__seriesType === 'Histogram'
? [3 /* PlotRowValueIndex.Close */]
: [2 /* PlotRowValueIndex.Low */, 1 /* PlotRowValueIndex.High */];
const barsMinMax = this._private__data._internal_minMaxOnRangeCached(startTimePoint, endTimePoint, plots);
let range = barsMinMax !== null ? new PriceRangeImpl(barsMinMax._internal_min, barsMinMax._internal_max) : null;
let margins = null;
if (this._internal_seriesType() === 'Histogram') {
const base = this._private__options.base;
const rangeWithBase = new PriceRangeImpl(base, base);
range = range !== null ? range._internal_merge(rangeWithBase) : rangeWithBase;
}
this._private__primitives.forEach((primitive) => {
const primitiveAutoscale = primitive._internal_autoscaleInfo(startTimePoint, endTimePoint);
if (primitiveAutoscale?.priceRange) {
const primitiveRange = new PriceRangeImpl(primitiveAutoscale.priceRange.minValue, primitiveAutoscale.priceRange.maxValue);
range = range !== null ? range._internal_merge(primitiveRange) : primitiveRange;
}
if (primitiveAutoscale?.margins) {
margins = primitiveAutoscale.margins;
}
});
return new AutoscaleInfoImpl(range, margins);
}
_private__markerRadius() {
switch (this._private__seriesType) {
case 'Line':
case 'Area':
case 'Baseline':
return this._private__options.crosshairMarkerRadius;
}
return 0;
}
_private__markerBorderColor() {
switch (this._private__seriesType) {
case 'Line':
case 'Area':
case 'Baseline': {
const crosshairMarkerBorderColor = this._private__options.crosshairMarkerBorderColor;
if (crosshairMarkerBorderColor.length !== 0) {
return crosshairMarkerBorderColor;
}
}
}
return null;
}
_private__markerBorderWidth() {
switch (this._private__seriesType) {
case 'Line':
case 'Area':
case 'Baseline':
return this._private__options.crosshairMarkerBorderWidth;
}
return 0;
}
_private__markerBackgroundColor(index) {
switch (this._private__seriesType) {
case 'Line':
case 'Area':
case 'Baseline': {
const crosshairMarkerBackgroundColor = this._private__options.crosshairMarkerBackgroundColor;
if (crosshairMarkerBackgroundColor.length !== 0) {
return crosshairMarkerBackgroundColor;
}
}
}
return this._internal_barColorer()._internal_barStyle(index)._internal_barColor;
}
_private__recreateFormatter() {
switch (this._private__options.priceFormat.type) {
case 'custom': {
const formatter = this._private__options.priceFormat.formatter;
this._private__formatter = {
format: formatter,
formatTickmarks: this._private__options.priceFormat.tickmarksFormatter ?? ((prices) => prices.map(formatter)),
};
break;
}
case 'volume': {
this._private__formatter = new VolumeFormatter(this._private__options.priceFormat.precision);
break;
}
case 'percent': {
this._private__formatter = new PercentageFormatter(this._private__options.priceFormat.precision);
break;
}
default: {
const priceScale = Math.pow(10, this._private__options.priceFormat.precision);
this._private__formatter = new PriceFormatter(priceScale, this._private__options.priceFormat.minMove * priceScale);
}
}
if (this._internal__priceScale !== null) {
this._internal__priceScale._internal_updateFormatter();
}
}
_private__extractPaneViews(extractor, zOrder) {
const res = [];
extractPrimitivePaneViews(this._private__primitives, extractor, zOrder, res);
return res;
}
_private__calculateConflationFactor() {
const { _internal_barSpacing: barSpacing, _internal_devicePixelRatio: devicePixelRatio, _internal_effectiveSmoothing: effectiveSmoothing } = this._private__getConflationParams();
return this._private__dataConflater._internal_calculateConflationLevelWithSmoothing(barSpacing, devicePixelRatio, effectiveSmoothing);
}
_private__getConflationParams() {
const timeScale = this._internal_model()._internal_timeScale();
const barSpacing = timeScale._internal_barSpacing();
const devicePixelRatio = window.devicePixelRatio || 1;
const globalSmoothing = timeScale._internal_options().conflationThresholdFactor;
const seriesSmoothing = this._private__options.conflationThresholdFactor;
const effectiveSmoothing = seriesSmoothing ?? globalSmoothing ?? 1.0;
return { _internal_barSpacing: barSpacing, _internal_devicePixelRatio: devicePixelRatio, _internal_effectiveSmoothing: effectiveSmoothing };
}
_private__buildConflatedListByFactor(factor) {
const originalRows = this._private__data._internal_rows();
let conflatedRows;
if (this._private__seriesType === 'Custom' && this._private__customConflationReducer !== null) {
const priceValueBuilder = this._internal_customSeriesPlotValuesBuilder();
if (!priceValueBuilder) {
throw new Error(CONFLATION_ERROR_MESSAGES._internal_missingPriceValueBuilder);
}
conflatedRows = this._private__dataConflater._internal_conflateByFactor(originalRows, factor, this._private__customConflationReducer, true, (item) => priceValueBuilder(item));
}
else {
conflatedRows = this._private__dataConflater._internal_conflateByFactor(originalRows, factor);
}
const list = createSeriesPlotList();
list._internal_setData(conflatedRows);
return list;
}
_private__regenerateConflatedDataByFactor(factor) {
const list = this._private__buildConflatedListByFactor(factor);
this._private__conflationByFactorCache.set(factor, list);
}
_private__precomputeConflationLevels(priority) {
if (this._private__seriesType === 'Custom' &&
(this._private__customConflationReducer === null ||
!this._internal_customSeriesPlotValuesBuilder())) {
return;
}
// Clear precomputed cache when data changes
this._private__conflationByFactorCache.clear();
const conflateFactors = this._internal_model()._internal_timeScale()._internal_possibleConflationFactors();
for (const lvl of conflateFactors) {
const task = () => {
this._private__precomputeConflationLevel(lvl);
};
// Use Prioritized Task Scheduling API if available
const globalObj = ((typeof window === 'object' && window) || (typeof self === 'object' && self));
if (globalObj?._internal_scheduler?._internal_postTask) {
void globalObj._internal_scheduler._internal_postTask(() => { task(); }, { _internal_priority: priority });
}
else {
void Promise.resolve().then(() => task());
}
}
}
_private__precomputeConflationLevel(factor) {
// Check if already cached
if (this._private__conflationByFactorCache.has(factor)) {
return;
}
const originalRows = this._private__data._internal_rows();
if (originalRows.length === 0) {
return;
}
const list = this._private__buildConflatedListByFactor(factor);
this._private__conflationByFactorCache.set(factor, list);
}
}
const magnetPlotRowKeys = [
3 /* PlotRowValueIndex.Close */,
];
const magnetOHLCPlotRowKeys = [
0 /* PlotRowValueIndex.Open */,
1 /* PlotRowValueIndex.High */,
2 /* PlotRowValueIndex.Low */,
3 /* PlotRowValueIndex.Close */,
];
class Magnet {
constructor(options) {
this._private__options = options;
}
_internal_align(price, index, pane) {
let res = price;
if (this._private__options.mode === 0 /* CrosshairMode.Normal */) {
return res;
}
const defaultPriceScale = pane._internal_defaultPriceScale();
const firstValue = defaultPriceScale._internal_firstValue();
if (firstValue === null) {
return res;
}
const y = defaultPriceScale._internal_priceToCoordinate(price, firstValue);
// get all serieses from the pane
const serieses = pane._internal_dataSources().filter(((ds) => (ds instanceof Series)));
const candidates = serieses.reduce((acc, series) => {
if (pane._internal_isOverlay(series) || !series._internal_visible()) {
return acc;
}
const ps = series._internal_priceScale();
const bars = series._internal_bars();
if (ps._internal_isEmpty() || !bars._internal_contains(index)) {
return acc;
}
const bar = bars._internal_valueAt(index);
if (bar === null) {
return acc;
}
// convert bar to pixels
const firstPrice = ensure(series._internal_firstValue());
const plotRowKeys = this._private__options.mode === 3 /* CrosshairMode.MagnetOHLC */
? magnetOHLCPlotRowKeys
: magnetPlotRowKeys;
return acc.concat(plotRowKeys.map((key) => ps._internal_priceToCoordinate(bar._internal_value[key], firstPrice._internal_value)));
}, []);
if (candidates.length === 0) {
return res;
}
candidates.sort((y1, y2) => Math.abs(y1 - y) - Math.abs(y2 - y));
const nearest = candidates[0];
res = defaultPriceScale._internal_coordinateToPrice(nearest, firstValue);
return res;
}
}
function clamp(value, minVal, maxVal) {
return Math.min(Math.max(value, minVal), maxVal);
}
function isBaseDecimal(value) {
if (value < 0) {
return false;
}
// cannot calculate exactly due to rounding error
if (value > 1e18) {
return true;
}
for (let current = value; current > 1; current /= 10) {
if ((current % 10) !== 0) {
return false;
}
}
return true;
}
function greaterOrEqual(x1, x2, epsilon) {
return (x2 - x1) <= epsilon;
}
function equal(x1, x2, epsilon) {
return Math.abs(x1 - x2) < epsilon;
}
// We can't use Math.min(...arr) because that would only support arrays shorter than 65536 items.
function min(arr) {
if (arr.length < 1) {
throw Error('array is empty');
}
let minVal = arr[0];
for (let i = 1; i < arr.length; ++i) {
if (arr[i] < minVal) {
minVal = arr[i];
}
}
return minVal;
}
function ceiledEven(x) {
const ceiled = Math.ceil(x);
return (ceiled % 2 !== 0) ? ceiled - 1 : ceiled;
}
function ceiledOdd(x) {
const ceiled = Math.ceil(x);
return (ceiled % 2 === 0) ? ceiled - 1 : ceiled;
}
class GridRenderer extends BitmapCoordinatesPaneRenderer {
constructor() {
super(...arguments);
this._private__data = null;
}
_internal_setData(data) {
this._private__data = data;
}
_internal__drawImpl({ context: ctx, bitmapSize, horizontalPixelRatio, verticalPixelRatio }) {
if (this._private__data === null) {
return;
}
const lineWidth = Math.max(1, Math.floor(horizontalPixelRatio));
ctx.lineWidth = lineWidth;
strokeInPixel(ctx, () => {
const data = ensureNotNull(this._private__data);
if (data._internal_vertLinesVisible) {
ctx.strokeStyle = data._internal_vertLinesColor;
setLineStyle(ctx, data._internal_vertLineStyle);
ctx.beginPath();
for (const timeMark of data._internal_timeMarks) {
const x = Math.round(timeMark._internal_coord * horizontalPixelRatio);
ctx.moveTo(x, -lineWidth);
ctx.lineTo(x, bitmapSize.height + lineWidth);
}
ctx.stroke();
}
if (data._internal_horzLinesVisible) {
ctx.strokeStyle = data._internal_horzLinesColor;
setLineStyle(ctx, data._internal_horzLineStyle);
ctx.beginPath();
for (const priceMark of data._internal_priceMarks) {
const y = Math.round(priceMark._internal_coord * verticalPixelRatio);
ctx.moveTo(-lineWidth, y);
ctx.lineTo(bitmapSize.width + lineWidth, y);
}
ctx.stroke();
}
});
}
}
class GridPaneView {
constructor(pane) {
this._private__renderer = new GridRenderer();
this._private__invalidated = true;
this._private__pane = pane;
}
_internal_update() {
this._private__invalidated = true;
}
_internal_renderer() {
if (this._private__invalidated) {
const gridOptions = this._private__pane._internal_model()._internal_options().grid;
const data = {
_internal_horzLinesVisible: gridOptions.horzLines.visible,
_internal_vertLinesVisible: gridOptions.vertLines.visible,
_internal_horzLinesColor: gridOptions.horzLines.color,
_internal_vertLinesColor: gridOptions.vertLines.color,
_internal_horzLineStyle: gridOptions.horzLines.style,
_internal_vertLineStyle: gridOptions.vertLines.style,
_internal_priceMarks: this._private__pane._internal_defaultPriceScale()._internal_marks(),
// need this conversiom because TimeMark is a part of external interface
// and fields inside TimeMark are not minified
_internal_timeMarks: (this._private__pane._internal_model()._internal_timeScale()._internal_marks() || []).map((tm) => {
return { _internal_coord: tm.coord };
}),
};
this._private__renderer._internal_setData(data);
this._private__invalidated = false;
}
return this._private__renderer;
}
}
class Grid {
constructor(pane) {
this._private__paneView = new GridPaneView(pane);
}
_internal_paneView() {
return this._private__paneView;
}
}
function hoveredSourceOnTopOrder(sources, hoveredSource, enabled) {
if (!enabled) {
return sources;
}
const hoveredIndex = sources.indexOf(hoveredSource);
if (hoveredIndex === -1 || hoveredIndex === sources.length - 1) {
return sources;
}
const reorderedSources = [];
for (let i = 0; i < sources.length; i++) {
if (i !== hoveredIndex) {
reorderedSources.push(sources[i]);
}
}
reorderedSources.push(sources[hoveredIndex]);
return reorderedSources;
}
const defLogFormula = {
_internal_logicalOffset: 4,
_internal_coordOffset: 0.0001,
};
function fromPercent(value, baseValue) {
if (baseValue < 0) {
value = -value;
}
return (value / 100) * baseValue + baseValue;
}
function toPercent(value, baseValue) {
const result = 100 * (value - baseValue) / baseValue;
return (baseValue < 0 ? -result : result);
}
function toPercentRange(priceRange, baseValue) {
const minPercent = toPercent(priceRange._internal_minValue(), baseValue);
const maxPercent = toPercent(priceRange._internal_maxValue(), baseValue);
return new PriceRangeImpl(minPercent, maxPercent);
}
function fromIndexedTo100(value, baseValue) {
value -= 100;
if (baseValue < 0) {
value = -value;
}
return (value / 100) * baseValue + baseValue;
}
function toIndexedTo100(value, baseValue) {
const result = 100 * (value - baseValue) / baseValue + 100;
return (baseValue < 0 ? -result : result);
}
function toIndexedTo100Range(priceRange, baseValue) {
const minPercent = toIndexedTo100(priceRange._internal_minValue(), baseValue);
const maxPercent = toIndexedTo100(priceRange._internal_maxValue(), baseValue);
return new PriceRangeImpl(minPercent, maxPercent);
}
function toLog(price, logFormula) {
const m = Math.abs(price);
if (m < 1e-15) {
return 0;
}
const res = Math.log10(m + logFormula._internal_coordOffset) + logFormula._internal_logicalOffset;
return ((price < 0) ? -res : res);
}
function fromLog(logical, logFormula) {
const m = Math.abs(logical);
if (m < 1e-15) {
return 0;
}
const res = Math.pow(10, m - logFormula._internal_logicalOffset) - logFormula._internal_coordOffset;
return (logical < 0) ? -res : res;
}
function convertPriceRangeToLog(priceRange, logFormula) {
if (priceRange === null) {
return null;
}
const min = toLog(priceRange._internal_minValue(), logFormula);
const max = toLog(priceRange._internal_maxValue(), logFormula);
return new PriceRangeImpl(min, max);
}
function canConvertPriceRangeFromLog(priceRange, logFormula) {
if (priceRange === null) {
return false;
}
const min = fromLog(priceRange._internal_minValue(), logFormula);
const max = fromLog(priceRange._internal_maxValue(), logFormula);
return isFinite(min) && isFinite(max);
}
function convertPriceRangeFromLog(priceRange, logFormula) {
if (priceRange === null) {
return null;
}
const min = fromLog(priceRange._internal_minValue(), logFormula);
const max = fromLog(priceRange._internal_maxValue(), logFormula);
return new PriceRangeImpl(min, max);
}
function logFormulaForPriceRange(range) {
if (range === null) {
return defLogFormula;
}
const diff = Math.abs(range._internal_maxValue() - range._internal_minValue());
if (diff >= 1 || diff < 1e-15) {
return defLogFormula;
}
const digits = Math.ceil(Math.abs(Math.log10(diff)));
const logicalOffset = defLogFormula._internal_logicalOffset + digits;
const coordOffset = 1 / Math.pow(10, logicalOffset);
return {
_internal_logicalOffset: logicalOffset,
_internal_coordOffset: coordOffset,
};
}
function logFormulasAreSame(f1, f2) {
return f1._internal_logicalOffset === f2._internal_logicalOffset && f1._internal_coordOffset === f2._internal_coordOffset;
}
class PriceTickSpanCalculator {
constructor(base, integralDividers) {
this._private__base = base;
this._private__integralDividers = integralDividers;
if (isBaseDecimal(this._private__base)) {
this._private__fractionalDividers = [2, 2.5, 2];
}
else {
this._private__fractionalDividers = [];
for (let baseRest = this._private__base; baseRest !== 1;) {
if ((baseRest % 2) === 0) {
this._private__fractionalDividers.push(2);
baseRest /= 2;
}
else if ((baseRest % 5) === 0) {
this._private__fractionalDividers.push(2, 2.5);
baseRest /= 5;
}
else {
throw new Error('unexpected base');
}
if (this._private__fractionalDividers.length > 100) {
throw new Error('something wrong with base');
}
}
}
}
_internal_tickSpan(high, low, maxTickSpan) {
const minMovement = (this._private__base === 0) ? (0) : (1 / this._private__base);
let resultTickSpan = Math.pow(10, Math.max(0, Math.ceil(Math.log10(high - low))));
let index = 0;
let c = this._private__integralDividers[0];
// eslint-disable-next-line no-constant-condition
while (true) {
// the second part is actual for small with very small values like 1e-10
// greaterOrEqual fails for such values
const resultTickSpanLargerMinMovement = greaterOrEqual(resultTickSpan, minMovement, 1e-14 /* Constants.TickSpanEpsilon */) && resultTickSpan > (minMovement + 1e-14 /* Constants.TickSpanEpsilon */);
const resultTickSpanLargerMaxTickSpan = greaterOrEqual(resultTickSpan, maxTickSpan * c, 1e-14 /* Constants.TickSpanEpsilon */);
const resultTickSpanLarger1 = greaterOrEqual(resultTickSpan, 1, 1e-14 /* Constants.TickSpanEpsilon */);
const haveToContinue = resultTickSpanLargerMinMovement && resultTickSpanLargerMaxTickSpan && resultTickSpanLarger1;
if (!haveToContinue) {
break;
}
resultTickSpan /= c;
c = this._private__integralDividers[++index % this._private__integralDividers.length];
}
if (resultTickSpan <= (minMovement + 1e-14 /* Constants.TickSpanEpsilon */)) {
resultTickSpan = minMovement;
}
resultTickSpan = Math.max(1, resultTickSpan);
if ((this._private__fractionalDividers.length > 0) && equal(resultTickSpan, 1, 1e-14 /* Constants.TickSpanEpsilon */)) {
index = 0;
c = this._private__fractionalDividers[0];
while (greaterOrEqual(resultTickSpan, maxTickSpan * c, 1e-14 /* Constants.TickSpanEpsilon */) && resultTickSpan > (minMovement + 1e-14 /* Constants.TickSpanEpsilon */)) {
resultTickSpan /= c;
c = this._private__fractionalDividers[++index % this._private__fractionalDividers.length];
}
}
return resultTickSpan;
}
}
class PriceTickMarkBuilder {
constructor(priceScale, base, coordinateToLogicalFunc, logicalToCoordinateFunc) {
this._private__marks = [];
this._private__priceScale = priceScale;
this._private__base = base;
this._private__coordinateToLogicalFunc = coordinateToLogicalFunc;
this._private__logicalToCoordinateFunc = logicalToCoordinateFunc;
}
_internal_tickSpan(high, low) {
if (high < low) {
throw new Error('high < low');
}
const scaleHeight = this._private__priceScale._internal_height();
const markHeight = this._private__tickMarkHeight();
const maxTickSpan = (high - low) * markHeight / scaleHeight;
const spanCalculator1 = new PriceTickSpanCalculator(this._private__base, [2, 2.5, 2]);
const spanCalculator2 = new PriceTickSpanCalculator(this._private__base, [2, 2, 2.5]);
const spanCalculator3 = new PriceTickSpanCalculator(this._private__base, [2.5, 2, 2]);
const spans = [];
spans.push(spanCalculator1._internal_tickSpan(high, low, maxTickSpan), spanCalculator2._internal_tickSpan(high, low, maxTickSpan), spanCalculator3._internal_tickSpan(high, low, maxTickSpan));
return min(spans);
}
_internal_rebuildTickMarks() {
const priceScale = this._private__priceScale;
const firstValue = priceScale._internal_firstValue();
if (firstValue === null) {
this._private__marks = [];
return;
}
const scaleHeight = priceScale._internal_height();
const bottom = this._private__coordinateToLogicalFunc(scaleHeight - 1, firstValue);
const top = this._private__coordinateToLogicalFunc(0, firstValue);
const extraTopBottomMargin = this._private__priceScale._internal_options().entireTextOnly ? this._private__fontHeight() / 2 : 0;
const minCoord = extraTopBottomMargin;
const maxCoord = scaleHeight - 1 - extraTopBottomMargin;
const high = Math.max(bottom, top);
const low = Math.min(bottom, top);
if (high === low) {
this._private__marks = [];
return;
}
const span = this._internal_tickSpan(high, low);
this._private__updateMarks(firstValue, span, high, low, minCoord, maxCoord);
if (priceScale._internal_hasVisibleEdgeMarks() && this._private__shouldApplyEdgeMarks(span, low, high)) {
const padding = this._private__priceScale._internal_getEdgeMarksPadding();
this._private__applyEdgeMarks(firstValue, span, minCoord, maxCoord, padding, padding * 2);
}
const logicals = this._private__marks.map((mark) => mark._internal_logical);
const labels = this._private__priceScale._internal_formatLogicalTickmarks(logicals);
for (let i = 0; i < this._private__marks.length; i++) {
this._private__marks[i]._internal_label = labels[i];
}
}
_internal_marks() {
return this._private__marks;
}
_private__fontHeight() {
return this._private__priceScale._internal_fontSize();
}
_private__tickMarkHeight() {
return Math.ceil(this._private__fontHeight() * this._private__priceScale._internal_options().tickMarkDensity);
}
_private__updateMarks(firstValue, span, high, low, minCoord, maxCoord) {
const marks = this._private__marks;
const priceScale = this._private__priceScale;
let mod = high % span;
mod += mod < 0 ? span : 0;
const sign = (high >= low) ? 1 : -1;
let prevCoord = null;
let targetIndex = 0;
for (let logical = high - mod; logical > low; logical -= span) {
const coord = this._private__logicalToCoordinateFunc(logical, firstValue, true);
// check if there is place for it
// this is required for log scale
if (prevCoord !== null && Math.abs(coord - prevCoord) < this._private__tickMarkHeight()) {
continue;
}
// check if a tick mark is partially visible and skip it if entireTextOnly is true
if (coord < minCoord || coord > maxCoord) {
continue;
}
if (targetIndex < marks.length) {
marks[targetIndex]._internal_coord = coord;
marks[targetIndex]._internal_label = priceScale._internal_formatLogical(logical);
marks[targetIndex]._internal_logical = logical;
}
else {
marks.push({
_internal_coord: coord,
_internal_label: priceScale._internal_formatLogical(logical),
_internal_logical: logical,
});
}
targetIndex++;
prevCoord = coord;
if (priceScale._internal_isLog()) {
// recalc span
span = this._internal_tickSpan(logical * sign, low);
}
}
marks.length = targetIndex;
}
_private__applyEdgeMarks(firstValue, span, minCoord, maxCoord, minPadding, maxPadding) {
const marks = this._private__marks;
// top boundary
const topMark = this._private__computeBoundaryPriceMark(firstValue, minCoord, minPadding, maxPadding);
// bottom boundary
const bottomMark = this._private__computeBoundaryPriceMark(firstValue, maxCoord, -maxPadding, -minPadding);
const spanPx = this._private__logicalToCoordinateFunc(0, firstValue, true)
- this._private__logicalToCoordinateFunc(span, firstValue, true);
if (marks.length > 0 && marks[0]._internal_coord - topMark._internal_coord < spanPx / 2) {
marks.shift();
}
if (marks.length > 0 && bottomMark._internal_coord - marks[marks.length - 1]._internal_coord < spanPx / 2) {
marks.pop();
}
marks.unshift(topMark);
marks.push(bottomMark);
}
_private__computeBoundaryPriceMark(firstValue, coord, minPadding, maxPadding) {
const avgPadding = (minPadding + maxPadding) / 2;
const value1 = this._private__coordinateToLogicalFunc(coord + minPadding, firstValue);
const value2 = this._private__coordinateToLogicalFunc(coord + maxPadding, firstValue);
const minValue = Math.min(value1, value2);
const maxValue = Math.max(value1, value2);
const valueSpan = Math.max(0.1, this._internal_tickSpan(maxValue, minValue));
const value = this._private__coordinateToLogicalFunc(coord + avgPadding, firstValue);
const roundedValue = value - (value % valueSpan);
const roundedCoord = this._private__logicalToCoordinateFunc(roundedValue, firstValue, true);
return { _internal_label: this._private__priceScale._internal_formatLogical(roundedValue), _internal_coord: roundedCoord, _internal_logical: roundedValue };
}
_private__shouldApplyEdgeMarks(span, low, high) {
let range = ensure(this._private__priceScale._internal_priceRange());
if (this._private__priceScale._internal_isLog()) {
range = convertPriceRangeFromLog(range, this._private__priceScale._internal_getLogFormula());
}
return (range._internal_minValue() - low < span) && (high - range._internal_maxValue() < span);
}
}
function sortSources(sources) {
return sources.slice().sort((s1, s2) => {
return (ensureNotNull(s1._internal_zorder()) - ensureNotNull(s2._internal_zorder()));
});
}
/**
* Represents the price scale mode.
*/
var PriceScaleMode;
(function (PriceScaleMode) {
/**
* Price scale shows prices. Price range changes linearly.
*/
PriceScaleMode[PriceScaleMode["Normal"] = 0] = "Normal";
/**
* Price scale shows prices. Price range changes logarithmically.
*/
PriceScaleMode[PriceScaleMode["Logarithmic"] = 1] = "Logarithmic";
/**
* Price scale shows percentage values according the first visible value of the price scale.
* The first visible value is 0% in this mode.
*/
PriceScaleMode[PriceScaleMode["Percentage"] = 2] = "Percentage";
/**
* The same as percentage mode, but the first value is moved to 100.
*/
PriceScaleMode[PriceScaleMode["IndexedTo100"] = 3] = "IndexedTo100";
})(PriceScaleMode || (PriceScaleMode = {}));
const percentageFormatter = new PercentageFormatter();
const defaultPriceFormatter = new PriceFormatter(100, 1);
class PriceScale {
constructor(id, options, layoutOptions, localizationOptions, colorParser) {
this._private__height = 0;
this._private__internalHeightCache = null;
this._private__priceRange = null;
this._private__priceRangeSnapshot = null;
this._private__invalidatedForRange = { _internal_isValid: false, _internal_visibleBars: null };
this._private__isCustomPriceRange = false;
this._private__marginAbove = 0;
this._private__marginBelow = 0;
this._private__onMarksChanged = new Delegate();
this._private__modeChanged = new Delegate();
this._private__dataSources = [];
this._private__formatterSource = null;
this._private__cachedOrderedSources = null;
this._private__marksCache = null;
this._private__scaleStartPoint = null;
this._private__scrollStartPoint = null;
this._private__formatter = defaultPriceFormatter;
this._private__logFormula = logFormulaForPriceRange(null);
this._private__id = id;
this._private__options = options;
this._private__layoutOptions = layoutOptions;
this._private__localizationOptions = localizationOptions;
this._private__colorParser = colorParser;
this._private__markBuilder = new PriceTickMarkBuilder(this, 100, this._private__coordinateToLogical.bind(this), this._private__logicalToCoordinate.bind(this));
}
_internal_id() {
return this._private__id;
}
_internal_options() {
return this._private__options;
}
_internal_applyOptions(options) {
merge(this._private__options, options);
this._internal_updateFormatter();
if (options.mode !== undefined) {
this._internal_setMode({ _internal_mode: options.mode });
}
if (options.scaleMargins !== undefined) {
const top = ensureDefined(options.scaleMargins.top);
const bottom = ensureDefined(options.scaleMargins.bottom);
if (top < 0 || top > 1) {
throw new Error(`Invalid top margin - expect value between 0 and 1, given=${top}`);
}
if (bottom < 0 || bottom > 1) {
throw new Error(`Invalid bottom margin - expect value between 0 and 1, given=${bottom}`);
}
if (top + bottom > 1) {
throw new Error(`Invalid margins - sum of margins must be less than 1, given=${top + bottom}`);
}
this._private__invalidateInternalHeightCache();
this._private__marksCache = null;
}
}
_internal_isAutoScale() {
return this._private__options.autoScale;
}
_internal_isCustomPriceRange() {
return this._private__isCustomPriceRange;
}
_internal_isLog() {
return this._private__options.mode === 1 /* PriceScaleMode.Logarithmic */;
}
_internal_isPercentage() {
return this._private__options.mode === 2 /* PriceScaleMode.Percentage */;
}
_internal_isIndexedTo100() {
return this._private__options.mode === 3 /* PriceScaleMode.IndexedTo100 */;
}
_internal_getLogFormula() {
return this._private__logFormula;
}
_internal_mode() {
return {
_internal_autoScale: this._private__options.autoScale,
_internal_isInverted: this._private__options.invertScale,
_internal_mode: this._private__options.mode,
};
}
// eslint-disable-next-line complexity
_internal_setMode(newMode) {
const oldMode = this._internal_mode();
let priceRange = null;
if (newMode._internal_autoScale !== undefined) {
this._private__options.autoScale = newMode._internal_autoScale;
}
if (newMode._internal_mode !== undefined) {
this._private__options.mode = newMode._internal_mode;
if (newMode._internal_mode === 2 /* PriceScaleMode.Percentage */ || newMode._internal_mode === 3 /* PriceScaleMode.IndexedTo100 */) {
this._private__options.autoScale = true;
}
// TODO: Remove after making rebuildTickMarks lazy
this._private__invalidatedForRange._internal_isValid = false;
}
// define which scale converted from
if (oldMode._internal_mode === 1 /* PriceScaleMode.Logarithmic */ && newMode._internal_mode !== oldMode._internal_mode) {
if (canConvertPriceRangeFromLog(this._private__priceRange, this._private__logFormula)) {
priceRange = convertPriceRangeFromLog(this._private__priceRange, this._private__logFormula);
if (priceRange !== null) {
this._internal_setPriceRange(priceRange);
}
}
else {
this._private__options.autoScale = true;
}
}
// define which scale converted to
if (newMode._internal_mode === 1 /* PriceScaleMode.Logarithmic */ && newMode._internal_mode !== oldMode._internal_mode) {
priceRange = convertPriceRangeToLog(this._private__priceRange, this._private__logFormula);
if (priceRange !== null) {
this._internal_setPriceRange(priceRange);
}
}
const modeChanged = oldMode._internal_mode !== this._private__options.mode;
if (modeChanged && (oldMode._internal_mode === 2 /* PriceScaleMode.Percentage */ || this._internal_isPercentage())) {
this._internal_updateFormatter();
}
if (modeChanged && (oldMode._internal_mode === 3 /* PriceScaleMode.IndexedTo100 */ || this._internal_isIndexedTo100())) {
this._internal_updateFormatter();
}
if (newMode._internal_isInverted !== undefined && oldMode._internal_isInverted !== newMode._internal_isInverted) {
this._private__options.invertScale = newMode._internal_isInverted;
this._private__onIsInvertedChanged();
}
this._private__modeChanged._internal_fire(oldMode, this._internal_mode());
}
_internal_modeChanged() {
return this._private__modeChanged;
}
_internal_fontSize() {
return this._private__layoutOptions.fontSize;
}
_internal_height() {
return this._private__height;
}
_internal_setHeight(value) {
if (this._private__height === value) {
return;
}
this._private__height = value;
this._private__invalidateInternalHeightCache();
this._private__marksCache = null;
}
_internal_internalHeight() {
if (this._private__internalHeightCache) {
return this._private__internalHeightCache;
}
const res = this._internal_height() - this._private__topMarginPx() - this._private__bottomMarginPx();
this._private__internalHeightCache = res;
return res;
}
_internal_priceRange() {
this._private__makeSureItIsValid();
return this._private__priceRange;
}
_internal_setPriceRange(newPriceRange, isForceSetValue) {
const oldPriceRange = this._private__priceRange;
if (!isForceSetValue &&
!(oldPriceRange === null && newPriceRange !== null) &&
(oldPriceRange === null || oldPriceRange._internal_equals(newPriceRange))) {
return;
}
this._private__marksCache = null;
this._private__priceRange = newPriceRange;
}
_internal_setCustomPriceRange(newPriceRange) {
this._internal_setPriceRange(newPriceRange);
this._private__toggleCustomPriceRange(newPriceRange !== null);
}
_internal_isEmpty() {
this._private__makeSureItIsValid();
return this._private__height === 0 || !this._private__priceRange || this._private__priceRange._internal_isEmpty();
}
_internal_invertedCoordinate(coordinate) {
return this._internal_isInverted() ? coordinate : this._internal_height() - 1 - coordinate;
}
_internal_priceToCoordinate(price, baseValue) {
if (this._internal_isPercentage()) {
price = toPercent(price, baseValue);
}
else if (this._internal_isIndexedTo100()) {
price = toIndexedTo100(price, baseValue);
}
return this._private__logicalToCoordinate(price, baseValue);
}
_internal_pointsArrayToCoordinates(points, baseValue, visibleRange) {
this._private__makeSureItIsValid();
const bh = this._private__bottomMarginPx();
const range = ensureNotNull(this._internal_priceRange());
const min = range._internal_minValue();
const max = range._internal_maxValue();
const ih = (this._internal_internalHeight() - 1);
const isInverted = this._internal_isInverted();
const hmm = ih / (max - min);
const fromIndex = (visibleRange === undefined) ? 0 : visibleRange.from;
const toIndex = (visibleRange === undefined) ? points.length : visibleRange.to;
const transformFn = this._private__getCoordinateTransformer();
for (let i = fromIndex; i < toIndex; i++) {
const point = points[i];
const price = point._internal_price;
if (isNaN(price)) {
continue;
}
let logical = price;
if (transformFn !== null) {
logical = transformFn(point._internal_price, baseValue);
}
const invCoordinate = bh + hmm * (logical - min);
const coordinate = isInverted ? invCoordinate : this._private__height - 1 - invCoordinate;
point._internal_y = coordinate;
}
}
_internal_barPricesToCoordinates(pricesList, baseValue, visibleRange) {
this._private__makeSureItIsValid();
const bh = this._private__bottomMarginPx();
const range = ensureNotNull(this._internal_priceRange());
const min = range._internal_minValue();
const max = range._internal_maxValue();
const ih = (this._internal_internalHeight() - 1);
const isInverted = this._internal_isInverted();
const hmm = ih / (max - min);
const fromIndex = (visibleRange === undefined) ? 0 : visibleRange.from;
const toIndex = (visibleRange === undefined) ? pricesList.length : visibleRange.to;
const transformFn = this._private__getCoordinateTransformer();
for (let i = fromIndex; i < toIndex; i++) {
const bar = pricesList[i];
let openLogical = bar._internal_open;
let highLogical = bar._internal_high;
let lowLogical = bar._internal_low;
let closeLogical = bar._internal_close;
if (transformFn !== null) {
openLogical = transformFn(bar._internal_open, baseValue);
highLogical = transformFn(bar._internal_high, baseValue);
lowLogical = transformFn(bar._internal_low, baseValue);
closeLogical = transformFn(bar._internal_close, baseValue);
}
let invCoordinate = bh + hmm * (openLogical - min);
let coordinate = isInverted ? invCoordinate : this._private__height - 1 - invCoordinate;
bar._internal_openY = coordinate;
invCoordinate = bh + hmm * (highLogical - min);
coordinate = isInverted ? invCoordinate : this._private__height - 1 - invCoordinate;
bar._internal_highY = coordinate;
invCoordinate = bh + hmm * (lowLogical - min);
coordinate = isInverted ? invCoordinate : this._private__height - 1 - invCoordinate;
bar._internal_lowY = coordinate;
invCoordinate = bh + hmm * (closeLogical - min);
coordinate = isInverted ? invCoordinate : this._private__height - 1 - invCoordinate;
bar._internal_closeY = coordinate;
}
}
_internal_coordinateToPrice(coordinate, baseValue) {
const logical = this._private__coordinateToLogical(coordinate, baseValue);
return this._internal_logicalToPrice(logical, baseValue);
}
_internal_logicalToPrice(logical, baseValue) {
let value = logical;
if (this._internal_isPercentage()) {
value = fromPercent(value, baseValue);
}
else if (this._internal_isIndexedTo100()) {
value = fromIndexedTo100(value, baseValue);
}
return value;
}
_internal_dataSources() {
return this._private__dataSources;
}
_internal_orderedSources() {
if (!this._private__cachedOrderedSources) {
this._private__cachedOrderedSources = sortSources(this._private__dataSources);
}
return this._private__cachedOrderedSources;
}
_internal_addDataSource(source) {
if (this._private__dataSources.indexOf(source) !== -1) {
return;
}
this._private__dataSources.push(source);
this._internal_updateFormatter();
this._internal_invalidateSourcesCache();
}
_internal_removeDataSource(source) {
const index = this._private__dataSources.indexOf(source);
if (index === -1) {
throw new Error('source is not attached to scale');
}
this._private__dataSources.splice(index, 1);
if (this._private__dataSources.length === 0) {
this._internal_setMode({
_internal_autoScale: true,
});
// if no sources on price scale let's clear price range cache as well as enabling auto scale
this._internal_setPriceRange(null);
}
this._internal_updateFormatter();
this._internal_invalidateSourcesCache();
}
_internal_firstValue() {
// TODO: cache the result
let result = null;
for (const source of this._private__dataSources) {
const firstValue = source._internal_firstValue();
if (firstValue === null) {
continue;
}
if (result === null || firstValue._internal_timePoint < result._internal_timePoint) {
result = firstValue;
}
}
return result === null ? null : result._internal_value;
}
_internal_isInverted() {
return this._private__options.invertScale;
}
_internal_marks() {
const firstValueIsNull = this._internal_firstValue() === null;
// do not recalculate marks if firstValueIsNull is true because in this case we'll always get empty result
// this could happen in case when a series had some data and then you set empty data to it (in a simplified case)
// we could display an empty price scale, but this is not good from UX
// so in this case we need to keep an previous marks to display them on the scale
// as one of possible examples for this situation could be the following:
// let's say you have a study/indicator attached to a price scale and then you decide to stop it, i.e. remove its data because of its visibility
// a user will see the previous marks on the scale until you turn on your study back or remove it from the chart completely
if (this._private__marksCache !== null && (firstValueIsNull || this._private__marksCache._internal_firstValueIsNull === firstValueIsNull)) {
return this._private__marksCache._internal_marks;
}
this._private__markBuilder._internal_rebuildTickMarks();
const marks = this._private__markBuilder._internal_marks();
this._private__marksCache = { _internal_marks: marks, _internal_firstValueIsNull: firstValueIsNull };
this._private__onMarksChanged._internal_fire();
return marks;
}
_internal_onMarksChanged() {
return this._private__onMarksChanged;
}
_internal_startScale(x) {
if (this._internal_isPercentage() || this._internal_isIndexedTo100()) {
return;
}
if (this._private__scaleStartPoint !== null || this._private__priceRangeSnapshot !== null) {
return;
}
if (this._internal_isEmpty()) {
return;
}
// invert x
this._private__scaleStartPoint = this._private__height - x;
this._private__priceRangeSnapshot = ensureNotNull(this._internal_priceRange())._internal_clone();
}
_internal_scaleTo(x) {
if (this._internal_isPercentage() || this._internal_isIndexedTo100()) {
return;
}
if (this._private__scaleStartPoint === null) {
return;
}
this._internal_setMode({
_internal_autoScale: false,
});
// invert x
x = this._private__height - x;
if (x < 0) {
x = 0;
}
let scaleCoeff = (this._private__scaleStartPoint + (this._private__height - 1) * 0.2) / (x + (this._private__height - 1) * 0.2);
const newPriceRange = ensureNotNull(this._private__priceRangeSnapshot)._internal_clone();
scaleCoeff = Math.max(scaleCoeff, 0.1);
newPriceRange._internal_scaleAroundCenter(scaleCoeff);
this._internal_setPriceRange(newPriceRange);
}
_internal_endScale() {
if (this._internal_isPercentage() || this._internal_isIndexedTo100()) {
return;
}
this._private__scaleStartPoint = null;
this._private__priceRangeSnapshot = null;
}
_internal_startScroll(x) {
if (this._internal_isAutoScale()) {
return;
}
if (this._private__scrollStartPoint !== null || this._private__priceRangeSnapshot !== null) {
return;
}
if (this._internal_isEmpty()) {
return;
}
this._private__scrollStartPoint = x;
this._private__priceRangeSnapshot = ensureNotNull(this._internal_priceRange())._internal_clone();
}
_internal_scrollTo(x) {
if (this._internal_isAutoScale()) {
return;
}
if (this._private__scrollStartPoint === null) {
return;
}
const priceUnitsPerPixel = ensureNotNull(this._internal_priceRange())._internal_length() / (this._internal_internalHeight() - 1);
let pixelDelta = x - this._private__scrollStartPoint;
if (this._internal_isInverted()) {
pixelDelta *= -1;
}
const priceDelta = pixelDelta * priceUnitsPerPixel;
const newPriceRange = ensureNotNull(this._private__priceRangeSnapshot)._internal_clone();
newPriceRange._internal_shift(priceDelta);
this._internal_setPriceRange(newPriceRange, true);
this._private__marksCache = null;
}
_internal_endScroll() {
if (this._internal_isAutoScale()) {
return;
}
if (this._private__scrollStartPoint === null) {
return;
}
this._private__scrollStartPoint = null;
this._private__priceRangeSnapshot = null;
}
_internal_formatter() {
if (!this._private__formatter) {
this._internal_updateFormatter();
}
return this._private__formatter;
}
_internal_formatPrice(price, firstValue) {
switch (this._private__options.mode) {
case 2 /* PriceScaleMode.Percentage */:
return this._private__formatPercentage(toPercent(price, firstValue));
case 3 /* PriceScaleMode.IndexedTo100 */:
return this._internal_formatter().format(toIndexedTo100(price, firstValue));
default:
return this._private__formatPrice(price);
}
}
_internal_formatLogical(logical) {
switch (this._private__options.mode) {
case 2 /* PriceScaleMode.Percentage */:
return this._private__formatPercentage(logical);
case 3 /* PriceScaleMode.IndexedTo100 */:
return this._internal_formatter().format(logical);
default:
return this._private__formatPrice(logical);
}
}
_internal_formatLogicalTickmarks(logicals) {
switch (this._private__options.mode) {
case 2 /* PriceScaleMode.Percentage */:
return this._private__formatPercentageTickmarks(logicals);
case 3 /* PriceScaleMode.IndexedTo100 */:
return this._internal_formatter().formatTickmarks(logicals);
default:
return this._private__formatTickmarks(logicals);
}
}
_internal_formatPriceAbsolute(price) {
return this._private__formatPrice(price, ensureNotNull(this._private__formatterSource)._internal_formatter());
}
_internal_formatPricePercentage(price, baseValue) {
price = toPercent(price, baseValue);
return this._private__formatPercentage(price, percentageFormatter);
}
_internal_sourcesForAutoScale() {
return this._private__dataSources;
}
_internal_recalculatePriceRange(visibleBars) {
this._private__invalidatedForRange = {
_internal_visibleBars: visibleBars,
_internal_isValid: false,
};
}
_internal_updateAllViews() {
this._private__dataSources.forEach((s) => s._internal_updateAllViews());
}
_internal_hasVisibleEdgeMarks() {
return this._private__options.ensureEdgeTickMarksVisible && this._internal_isAutoScale();
}
_internal_getEdgeMarksPadding() {
return this._internal_fontSize() / 2;
}
_internal_updateFormatter() {
this._private__marksCache = null;
let zOrder = Infinity;
this._private__formatterSource = null;
// choose source with the lowest zorder
for (const source of this._private__dataSources) {
if (source._internal_zorder() < zOrder) {
zOrder = source._internal_zorder();
this._private__formatterSource = source;
}
}
let base = 100;
if (this._private__formatterSource !== null) {
base = Math.round(this._private__formatterSource._internal_base());
}
this._private__formatter = defaultPriceFormatter;
if (this._internal_isPercentage()) {
this._private__formatter = percentageFormatter;
base = 100;
}
else if (this._internal_isIndexedTo100()) {
this._private__formatter = new PriceFormatter(100, 1);
base = 100;
}
else {
if (this._private__formatterSource !== null) {
// user
this._private__formatter = this._private__formatterSource._internal_formatter();
}
}
this._private__markBuilder = new PriceTickMarkBuilder(this, base, this._private__coordinateToLogical.bind(this), this._private__logicalToCoordinate.bind(this));
this._private__markBuilder._internal_rebuildTickMarks();
}
_internal_invalidateSourcesCache() {
this._private__cachedOrderedSources = null;
}
_internal_minMove() {
return this._private__formatterSource === null || this._internal_isPercentage() || this._internal_isIndexedTo100() ? 1 : 1 / this._private__formatterSource._internal_base();
}
_internal_colorParser() {
return this._private__colorParser;
}
_private__toggleCustomPriceRange(v) {
this._private__isCustomPriceRange = v;
}
_private__topMarginPx() {
return this._internal_isInverted()
? this._private__options.scaleMargins.bottom * this._internal_height() + this._private__marginBelow
: this._private__options.scaleMargins.top * this._internal_height() + this._private__marginAbove;
}
_private__bottomMarginPx() {
return this._internal_isInverted()
? this._private__options.scaleMargins.top * this._internal_height() + this._private__marginAbove
: this._private__options.scaleMargins.bottom * this._internal_height() + this._private__marginBelow;
}
_private__makeSureItIsValid() {
if (!this._private__invalidatedForRange._internal_isValid) {
this._private__invalidatedForRange._internal_isValid = true;
this._private__recalculatePriceRangeImpl();
}
}
_private__invalidateInternalHeightCache() {
this._private__internalHeightCache = null;
}
_private__logicalToCoordinate(logical, baseValue) {
this._private__makeSureItIsValid();
if (this._internal_isEmpty()) {
return 0;
}
logical = this._internal_isLog() && logical ? toLog(logical, this._private__logFormula) : logical;
const range = ensureNotNull(this._internal_priceRange());
const invCoordinate = this._private__bottomMarginPx() +
(this._internal_internalHeight() - 1) * (logical - range._internal_minValue()) / range._internal_length();
const coordinate = this._internal_invertedCoordinate(invCoordinate);
return coordinate;
}
_private__coordinateToLogical(coordinate, baseValue) {
this._private__makeSureItIsValid();
if (this._internal_isEmpty()) {
return 0;
}
const invCoordinate = this._internal_invertedCoordinate(coordinate);
const range = ensureNotNull(this._internal_priceRange());
const logical = range._internal_minValue() + range._internal_length() *
((invCoordinate - this._private__bottomMarginPx()) / (this._internal_internalHeight() - 1));
return this._internal_isLog() ? fromLog(logical, this._private__logFormula) : logical;
}
_private__onIsInvertedChanged() {
this._private__marksCache = null;
this._private__markBuilder._internal_rebuildTickMarks();
}
// eslint-disable-next-line complexity
_private__recalculatePriceRangeImpl() {
if (this._internal_isCustomPriceRange() && !this._internal_isAutoScale()) {
return;
}
const visibleBars = this._private__invalidatedForRange._internal_visibleBars;
if (visibleBars === null) {
return;
}
let priceRange = null;
const sources = this._internal_sourcesForAutoScale();
let marginAbove = 0;
let marginBelow = 0;
for (const source of sources) {
if (!source._internal_visible()) {
continue;
}
const firstValue = source._internal_firstValue();
if (firstValue === null) {
continue;
}
const autoScaleInfo = source._internal_autoscaleInfo(visibleBars._internal_left(), visibleBars._internal_right());
let sourceRange = autoScaleInfo && autoScaleInfo._internal_priceRange();
if (sourceRange !== null) {
switch (this._private__options.mode) {
case 1 /* PriceScaleMode.Logarithmic */:
sourceRange = convertPriceRangeToLog(sourceRange, this._private__logFormula);
break;
case 2 /* PriceScaleMode.Percentage */:
sourceRange = toPercentRange(sourceRange, firstValue._internal_value);
break;
case 3 /* PriceScaleMode.IndexedTo100 */:
sourceRange = toIndexedTo100Range(sourceRange, firstValue._internal_value);
break;
}
if (priceRange === null) {
priceRange = sourceRange;
}
else {
priceRange = priceRange._internal_merge(ensureNotNull(sourceRange));
}
if (autoScaleInfo !== null) {
const margins = autoScaleInfo._internal_margins();
if (margins !== null) {
marginAbove = Math.max(marginAbove, margins.above);
marginBelow = Math.max(marginBelow, margins.below);
}
}
}
}
if (this._internal_hasVisibleEdgeMarks()) {
marginAbove = Math.max(marginAbove, this._internal_getEdgeMarksPadding());
marginBelow = Math.max(marginBelow, this._internal_getEdgeMarksPadding());
}
if (marginAbove !== this._private__marginAbove || marginBelow !== this._private__marginBelow) {
this._private__marginAbove = marginAbove;
this._private__marginBelow = marginBelow;
this._private__marksCache = null;
this._private__invalidateInternalHeightCache();
}
if (priceRange !== null) {
// keep current range is new is empty
if (priceRange._internal_minValue() === priceRange._internal_maxValue()) {
// if price range is degenerated to 1 point let's extend it by 10 min move values
// to avoid incorrect range and empty (blank) scale (in case of min tick much greater than 1)
const extendValue = 5 * this._internal_minMove();
if (this._internal_isLog()) {
priceRange = convertPriceRangeFromLog(priceRange, this._private__logFormula);
}
priceRange = new PriceRangeImpl(priceRange._internal_minValue() - extendValue, priceRange._internal_maxValue() + extendValue);
if (this._internal_isLog()) {
priceRange = convertPriceRangeToLog(priceRange, this._private__logFormula);
}
}
if (this._internal_isLog()) {
const rawRange = convertPriceRangeFromLog(priceRange, this._private__logFormula);
const newLogFormula = logFormulaForPriceRange(rawRange);
if (!logFormulasAreSame(newLogFormula, this._private__logFormula)) {
const rawSnapshot = this._private__priceRangeSnapshot !== null ? convertPriceRangeFromLog(this._private__priceRangeSnapshot, this._private__logFormula) : null;
this._private__logFormula = newLogFormula;
priceRange = convertPriceRangeToLog(rawRange, newLogFormula);
if (rawSnapshot !== null) {
this._private__priceRangeSnapshot = convertPriceRangeToLog(rawSnapshot, newLogFormula);
}
}
}
this._internal_setPriceRange(priceRange);
}
else {
// reset empty to default
if (this._private__priceRange === null) {
this._internal_setPriceRange(new PriceRangeImpl(-0.5, 0.5));
this._private__logFormula = logFormulaForPriceRange(null);
}
}
}
_private__getCoordinateTransformer() {
if (this._internal_isPercentage()) {
return toPercent;
}
else if (this._internal_isIndexedTo100()) {
return toIndexedTo100;
}
else if (this._internal_isLog()) {
return (price) => toLog(price, this._private__logFormula);
}
return null;
}
_private__formatValue(value, formatter, fallbackFormatter) {
if (formatter === undefined) {
if (fallbackFormatter === undefined) {
fallbackFormatter = this._internal_formatter();
}
return fallbackFormatter.format(value);
}
return formatter(value);
}
_private__formatValues(values, formatter, fallbackFormatter) {
if (formatter === undefined) {
if (fallbackFormatter === undefined) {
fallbackFormatter = this._internal_formatter();
}
return fallbackFormatter.formatTickmarks(values);
}
return formatter(values);
}
_private__formatPrice(price, fallbackFormatter) {
return this._private__formatValue(price, this._private__localizationOptions.priceFormatter, fallbackFormatter);
}
_private__formatTickmarks(prices, fallbackFormatter) {
const priceFormatter = this._private__localizationOptions.priceFormatter;
return this._private__formatValues(prices, this._private__localizationOptions.tickmarksPriceFormatter ?? (priceFormatter ? (values) => values.map(priceFormatter) : undefined), fallbackFormatter);
}
_private__formatPercentage(percentage, fallbackFormatter) {
return this._private__formatValue(percentage, this._private__localizationOptions.percentageFormatter, fallbackFormatter);
}
_private__formatPercentageTickmarks(percentages, fallbackFormatter) {
const tickmarksPercentageFormatter = this._private__localizationOptions.percentageFormatter;
return this._private__formatValues(percentages, this._private__localizationOptions.tickmarksPercentageFormatter ?? (tickmarksPercentageFormatter ? (values) => values.map(tickmarksPercentageFormatter) : undefined), fallbackFormatter);
}
}
function isSeries(source) {
return source instanceof Series;
}
const DEFAULT_STRETCH_FACTOR = 1;
const MIN_PANE_HEIGHT = 30;
class Pane {
constructor(timeScale, model) {
this._private__dataSources = [];
this._private__overlaySourcesByScaleId = new Map();
this._private__height = 0;
this._private__width = 0;
this._private__stretchFactor = DEFAULT_STRETCH_FACTOR;
this._private__cachedOrderedSources = null;
this._private__cachedOrderedSourcesForRendering = null;
this._private__preserveEmptyPane = false;
this._private__destroyed = new Delegate();
this._private__primitives = [];
this._private__timeScale = timeScale;
this._private__model = model;
this._private__grid = new Grid(this);
const options = model._internal_options();
this._private__leftPriceScale = this._private__createPriceScale("left" /* DefaultPriceScaleId.Left */, options.leftPriceScale);
this._private__rightPriceScale = this._private__createPriceScale("right" /* DefaultPriceScaleId.Right */, options.rightPriceScale);
this._private__leftPriceScale._internal_modeChanged()._internal_subscribe(this._private__onPriceScaleModeChanged.bind(this, this._private__leftPriceScale), this);
this._private__rightPriceScale._internal_modeChanged()._internal_subscribe(this._private__onPriceScaleModeChanged.bind(this, this._private__rightPriceScale), this);
this._internal_applyScaleOptions(options);
}
_internal_applyScaleOptions(options) {
if (options.leftPriceScale) {
this._private__leftPriceScale._internal_applyOptions(options.leftPriceScale);
}
if (options.rightPriceScale) {
this._private__rightPriceScale._internal_applyOptions(options.rightPriceScale);
}
if (options.localization) {
this._private__leftPriceScale._internal_updateFormatter();
this._private__rightPriceScale._internal_updateFormatter();
}
if (options.overlayPriceScales) {
const sourceArrays = Array.from(this._private__overlaySourcesByScaleId.values());
for (const arr of sourceArrays) {
const priceScale = ensureNotNull(arr[0]._internal_priceScale());
priceScale._internal_applyOptions(options.overlayPriceScales);
if (options.localization) {
priceScale._internal_updateFormatter();
}
}
}
}
_internal_priceScaleById(id) {
switch (id) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
case "left" /* DefaultPriceScaleId.Left */: {
return this._private__leftPriceScale;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
case "right" /* DefaultPriceScaleId.Right */: {
return this._private__rightPriceScale;
}
}
if (this._private__overlaySourcesByScaleId.has(id)) {
return ensureDefined(this._private__overlaySourcesByScaleId.get(id))[0]._internal_priceScale();
}
return null;
}
_internal_destroy() {
this._internal_model()._internal_priceScalesOptionsChanged()._internal_unsubscribeAll(this);
this._private__leftPriceScale._internal_modeChanged()._internal_unsubscribeAll(this);
this._private__rightPriceScale._internal_modeChanged()._internal_unsubscribeAll(this);
this._private__dataSources.forEach((source) => {
if (source._internal_destroy) {
source._internal_destroy();
}
});
this._private__primitives = this._private__primitives.filter((primitive) => {
const p = primitive._internal_primitive();
if (p.detached) {
p.detached();
}
return false;
});
this._private__destroyed._internal_fire();
}
_internal_stretchFactor() {
return this._private__stretchFactor;
}
_internal_setStretchFactor(factor) {
this._private__stretchFactor = factor;
}
_internal_model() {
return this._private__model;
}
_internal_width() {
return this._private__width;
}
_internal_height() {
return this._private__height;
}
_internal_setWidth(width) {
this._private__width = width;
this._internal_updateAllSources();
}
_internal_setHeight(height) {
this._private__height = height;
this._private__leftPriceScale._internal_setHeight(height);
this._private__rightPriceScale._internal_setHeight(height);
// process overlays
this._private__dataSources.forEach((ds) => {
if (this._internal_isOverlay(ds)) {
const priceScale = ds._internal_priceScale();
if (priceScale !== null) {
priceScale._internal_setHeight(height);
}
}
});
this._internal_updateAllSources();
}
_internal_setPreserveEmptyPane(preserve) {
this._private__preserveEmptyPane = preserve;
}
_internal_preserveEmptyPane() {
return this._private__preserveEmptyPane;
}
_internal_series() {
return this._private__dataSources.filter(isSeries);
}
_internal_dataSources() {
return this._private__dataSources;
}
_internal_isOverlay(source) {
const priceScale = source._internal_priceScale();
if (priceScale === null) {
return true;
}
return this._private__leftPriceScale !== priceScale && this._private__rightPriceScale !== priceScale;
}
_internal_addDataSource(source, targetScaleId, keepSourcesOrder) {
this._private__insertDataSource(source, targetScaleId, keepSourcesOrder ? source._internal_zorder() : this._private__dataSources.length);
}
_internal_removeDataSource(source, keepSourceOrder) {
const index = this._private__dataSources.indexOf(source);
assert(index !== -1, 'removeDataSource: invalid data source');
this._private__dataSources.splice(index, 1);
if (!keepSourceOrder) {
this._private__dataSources.forEach((ds, i) => ds._internal_setZorder(i));
}
const priceScaleId = ensureNotNull(source._internal_priceScale())._internal_id();
if (this._private__overlaySourcesByScaleId.has(priceScaleId)) {
const overlaySources = ensureDefined(this._private__overlaySourcesByScaleId.get(priceScaleId));
const overlayIndex = overlaySources.indexOf(source);
if (overlayIndex !== -1) {
overlaySources.splice(overlayIndex, 1);
if (overlaySources.length === 0) {
this._private__overlaySourcesByScaleId.delete(priceScaleId);
}
}
}
const priceScale = source._internal_priceScale();
// if source has owner, it returns owner's price scale
// and it does not have source in their list
if (priceScale && priceScale._internal_dataSources().indexOf(source) >= 0) {
priceScale._internal_removeDataSource(source);
this._internal_recalculatePriceScale(priceScale);
}
this._private__invalidateSourceCaches();
}
_internal_priceScalePosition(priceScale) {
if (priceScale === this._private__leftPriceScale) {
return 'left';
}
if (priceScale === this._private__rightPriceScale) {
return 'right';
}
return 'overlay';
}
_internal_leftPriceScale() {
return this._private__leftPriceScale;
}
_internal_rightPriceScale() {
return this._private__rightPriceScale;
}
_internal_startScalePrice(priceScale, x) {
priceScale._internal_startScale(x);
}
_internal_scalePriceTo(priceScale, x) {
priceScale._internal_scaleTo(x);
// TODO: be more smart and update only affected views
this._internal_updateAllSources();
}
_internal_endScalePrice(priceScale) {
priceScale._internal_endScale();
}
_internal_startScrollPrice(priceScale, x) {
priceScale._internal_startScroll(x);
}
_internal_scrollPriceTo(priceScale, x) {
priceScale._internal_scrollTo(x);
this._internal_updateAllSources();
}
_internal_endScrollPrice(priceScale) {
priceScale._internal_endScroll();
}
_internal_updateAllSources() {
this._private__dataSources.forEach((source) => {
source._internal_updateAllViews();
});
}
_internal_defaultPriceScale() {
const [primaryPriceScale, secondaryPriceScale] = this._private__defaultPriceScalePair();
let priceScale = null;
if (primaryPriceScale._internal_options().visible && primaryPriceScale._internal_dataSources().length !== 0) {
priceScale = primaryPriceScale;
}
else if (secondaryPriceScale._internal_options().visible && secondaryPriceScale._internal_dataSources().length !== 0) {
priceScale = secondaryPriceScale;
}
else if (this._private__dataSources.length !== 0) {
priceScale = this._private__dataSources[0]._internal_priceScale();
}
if (priceScale === null) {
priceScale = this._internal_defaultVisiblePriceScale() ?? primaryPriceScale;
}
return priceScale;
}
_internal_defaultVisiblePriceScale() {
const [primaryPriceScale, secondaryPriceScale] = this._private__defaultPriceScalePair();
if (primaryPriceScale._internal_options().visible) {
return primaryPriceScale;
}
if (secondaryPriceScale._internal_options().visible) {
return secondaryPriceScale;
}
return null;
}
_internal_recalculatePriceScale(priceScale) {
if (priceScale === null || !priceScale._internal_isAutoScale()) {
return;
}
this._private__recalculatePriceScaleImpl(priceScale);
}
_internal_resetPriceScale(priceScale) {
const visibleBars = this._private__timeScale._internal_visibleStrictRange();
priceScale._internal_setMode({ _internal_autoScale: true });
if (visibleBars !== null) {
priceScale._internal_recalculatePriceRange(visibleBars);
}
this._internal_updateAllSources();
}
_internal_momentaryAutoScale() {
this._private__recalculatePriceScaleImpl(this._private__leftPriceScale);
this._private__recalculatePriceScaleImpl(this._private__rightPriceScale);
}
_internal_recalculate() {
this._internal_recalculatePriceScale(this._private__leftPriceScale);
this._internal_recalculatePriceScale(this._private__rightPriceScale);
this._private__dataSources.forEach((ds) => {
if (this._internal_isOverlay(ds)) {
this._internal_recalculatePriceScale(ds._internal_priceScale());
}
});
this._internal_updateAllSources();
this._private__model._internal_lightUpdate();
}
_internal_orderedSources() {
if (this._private__cachedOrderedSources === null) {
this._private__cachedOrderedSources = sortSources(this._private__dataSources);
}
return this._private__cachedOrderedSources;
}
_internal_orderedSourcesForRendering() {
const base = this._internal_orderedSources();
const hovered = this._private__model._internal_hoveredSource()?._internal_source;
const enabled = this._private__model._internal_options().hoveredSeriesOnTop;
const cached = this._private__cachedOrderedSourcesForRendering;
if (cached !== null && cached._internal_base === base && cached._internal_hovered === hovered && cached._internal_enabled === enabled) {
return cached._internal_result;
}
const result = hoveredSourceOnTopOrder(base, hovered, enabled);
this._private__cachedOrderedSourcesForRendering = {
_internal_base: base,
_internal_hovered: hovered,
_internal_enabled: enabled,
_internal_result: result,
};
return result;
}
_internal_setSeriesOrder(series, order) {
order = clamp(order, 0, this._private__dataSources.length - 1);
const index = this._private__dataSources.indexOf(series);
assert(index !== -1, 'setSeriesOrder: invalid data source');
this._private__dataSources.splice(index, 1);
this._private__dataSources.splice(order, 0, series);
this._private__dataSources.forEach((ps, i) => ps._internal_setZorder(i));
this._private__invalidateSourceCaches();
for (const ps of [this._private__leftPriceScale, this._private__rightPriceScale]) {
ps._internal_invalidateSourcesCache();
ps._internal_updateFormatter();
}
this._private__model._internal_lightUpdate();
}
_internal_orderedSeries() {
return this._internal_orderedSources().filter(isSeries);
}
_internal_onDestroyed() {
return this._private__destroyed;
}
_internal_grid() {
return this._private__grid;
}
_internal_attachPrimitive(primitive) {
this._private__primitives.push(new PanePrimitiveWrapper$1(primitive));
}
_internal_detachPrimitive(source) {
this._private__primitives = this._private__primitives.filter((wrapper) => wrapper._internal_primitive() !== source);
if (source.detached) {
source.detached();
}
this._private__model._internal_lightUpdate();
}
_internal_primitives() {
return this._private__primitives;
}
_internal_primitiveHitTest(x, y) {
return this._private__primitives
.map((primitive) => primitive._internal_hitTest(x, y))
.filter((result) => result !== null);
}
_private__recalculatePriceScaleImpl(priceScale) {
// TODO: can use this checks
const sourceForAutoScale = priceScale._internal_sourcesForAutoScale();
if (sourceForAutoScale && sourceForAutoScale.length > 0 && !this._private__timeScale._internal_isEmpty()) {
const visibleBars = this._private__timeScale._internal_visibleStrictRange();
if (visibleBars !== null) {
priceScale._internal_recalculatePriceRange(visibleBars);
}
}
priceScale._internal_updateAllViews();
}
_private__insertDataSource(source, priceScaleId, order) {
let priceScale = this._internal_priceScaleById(priceScaleId);
if (priceScale === null) {
priceScale = this._private__createPriceScale(priceScaleId, this._private__model._internal_options().overlayPriceScales);
}
this._private__dataSources.splice(order, 0, source);
if (!isDefaultPriceScale(priceScaleId)) {
const overlaySources = this._private__overlaySourcesByScaleId.get(priceScaleId) || [];
overlaySources.push(source);
this._private__overlaySourcesByScaleId.set(priceScaleId, overlaySources);
}
source._internal_setZorder(order);
priceScale._internal_addDataSource(source);
source._internal_setPriceScale(priceScale);
this._internal_recalculatePriceScale(priceScale);
this._private__invalidateSourceCaches();
}
_private__invalidateSourceCaches() {
this._private__cachedOrderedSources = null;
this._private__cachedOrderedSourcesForRendering = null;
}
_private__defaultPriceScalePair() {
return this._private__model._internal_options().defaultVisiblePriceScaleId === "left" /* DefaultPriceScaleId.Left */
? [this._private__leftPriceScale, this._private__rightPriceScale]
: [this._private__rightPriceScale, this._private__leftPriceScale];
}
_private__onPriceScaleModeChanged(priceScale, oldMode, newMode) {
if (oldMode._internal_mode === newMode._internal_mode) {
return;
}
// momentary auto scale if we toggle percentage/indexedTo100 mode
this._private__recalculatePriceScaleImpl(priceScale);
}
_private__createPriceScale(id, options) {
const actualOptions = { visible: true, autoScale: true, ...clone(options) };
const priceScale = new PriceScale(id, actualOptions, this._private__model._internal_options()['layout'], this._private__model._internal_options().localization, this._private__model._internal_colorParser());
priceScale._internal_setHeight(this._internal_height());
return priceScale;
}
}
function isBetterHit(candidate, currentBest) {
if (currentBest === null) {
return true;
}
if (candidate._internal_priority === 2 /* HitTestPriority.Point */ && currentBest._internal_priority !== 2 /* HitTestPriority.Point */) {
return true;
}
if (currentBest._internal_priority === 2 /* HitTestPriority.Point */ && candidate._internal_priority !== 2 /* HitTestPriority.Point */) {
return false;
}
if (candidate._internal_distance !== currentBest._internal_distance) {
return candidate._internal_distance < currentBest._internal_distance;
}
// Preserve the existing draw/source order for equal-distance non-point ties.
// This prevents hidden strokes from overtaking visually covering range hits.
return false;
}
function hoveredObjectFromCandidate(candidate) {
return {
_internal_externalId: candidate._internal_externalId,
_internal_hitTestData: candidate._internal_hitTestData,
};
}
// returns true if item is above reference
function comparePrimitiveZOrder(item, reference) {
return (!reference ||
(item === 'top' && reference !== 'top') ||
(item === 'normal' && reference === 'bottom'));
}
function primitiveHitCandidate(hitResult) {
return {
_internal_distance: hitResult.distance ?? 0,
_internal_priority: hitResult.hitTestPriority ?? (hitResult.itemType === 'marker' ? 2 /* HitTestPriority.Point */ : 0 /* HitTestPriority.Range */),
_internal_itemType: hitResult.itemType ?? 'primitive',
_internal_cursorStyle: hitResult.cursorStyle,
_internal_externalId: hitResult.externalId,
};
}
function findBestPrimitiveHitTest(sources, x, y) {
let bestPrimitiveHit;
let bestPrimitiveCandidate;
let bestHitSource;
for (const source of sources) {
const primitiveHitResults = source._internal_primitiveHitTest?.(x, y) ?? [];
for (const hitResult of primitiveHitResults) {
const candidate = primitiveHitCandidate(hitResult);
if (comparePrimitiveZOrder(hitResult.zOrder, bestPrimitiveHit?.zOrder) ||
(hitResult.zOrder === bestPrimitiveHit?.zOrder && bestPrimitiveCandidate !== undefined && isBetterHit(candidate, bestPrimitiveCandidate)) ||
(hitResult.zOrder === bestPrimitiveHit?.zOrder && bestPrimitiveCandidate === undefined)) {
bestPrimitiveHit = hitResult;
bestPrimitiveCandidate = candidate;
bestHitSource = source;
}
}
}
if (!bestPrimitiveHit || !bestHitSource || !bestPrimitiveCandidate) {
return null;
}
return {
_internal_candidate: bestPrimitiveCandidate,
_internal_hit: bestPrimitiveHit,
_internal_source: bestHitSource,
};
}
function convertPrimitiveHitResult(primitiveHit) {
return {
_internal_source: primitiveHit._internal_source,
_internal_object: hoveredObjectFromCandidate(primitiveHit._internal_candidate),
_internal_cursorStyle: primitiveHit._internal_candidate._internal_cursorStyle,
_internal_itemType: primitiveHit._internal_candidate._internal_itemType ?? 'primitive',
};
}
/**
* Performs a hit test on a collection of pane views to determine which view and object
* is located at a given coordinate (x, y) and returns the matching pane view and
* hit-tested result object, or null if no match is found.
*/
function hitTestPaneView(paneViews, x, y, pane) {
let bestResult = null;
for (const paneView of paneViews) {
// Pane-view hit tests are an internal contract, so we can trust the typed
// InternalHitTestCandidate directly instead of probing build-mangled fields.
let candidate = paneView._internal_hitTest?.(x, y, pane) ?? null;
if (candidate === null) {
const renderer = paneView._internal_renderer(pane);
candidate = renderer !== null && renderer._internal_hitTest ? renderer._internal_hitTest(x, y) : null;
}
if (candidate !== null) {
const candidateResult = {
_internal_view: paneView,
_internal_candidate: candidate,
};
if (bestResult === null || isBetterHit(candidateResult._internal_candidate, bestResult._internal_candidate)) {
bestResult = candidateResult;
}
}
}
return bestResult;
}
function isDataSource(source) {
return source._internal_paneViews !== undefined;
}
// eslint-disable-next-line complexity
function hitTestPane(pane, x, y) {
// Hover arbitration should use the pane's stable source order, not the temporary
// "hovered series on top" render order, otherwise the current hovered source can
// become sticky and keep winning equal-distance overlaps.
const sources = [pane, ...pane._internal_orderedSources()].reverse();
const bestPrimitiveHit = findBestPrimitiveHitTest(sources, x, y);
if (bestPrimitiveHit?._internal_hit.zOrder === 'top') {
// a primitive hit on the 'top' layer will always beat the built-in hit tests
// (on normal layer) so we can return early here.
return convertPrimitiveHitResult(bestPrimitiveHit);
}
let bestSourceHit = null;
let bestSourceCandidate = null;
for (const source of sources) {
if (bestPrimitiveHit && bestPrimitiveHit._internal_source === source && bestPrimitiveHit._internal_hit.zOrder !== 'bottom' && !bestPrimitiveHit._internal_hit.isBackground) {
// A foreground primitive sits above its source's built-in views and blocks all lower sources,
// but hits from higher sources should still keep precedence.
return bestSourceHit ?? convertPrimitiveHitResult(bestPrimitiveHit);
}
if (isDataSource(source)) {
const sourceResult = hitTestPaneView(source._internal_paneViews(pane), x, y, pane);
if (sourceResult !== null) {
const candidateHit = {
_internal_source: source,
_internal_view: sourceResult._internal_view,
_internal_object: hoveredObjectFromCandidate(sourceResult._internal_candidate),
_internal_cursorStyle: sourceResult._internal_candidate._internal_cursorStyle,
_internal_itemType: sourceResult._internal_candidate._internal_itemType ?? 'primitive',
};
if (bestSourceHit === null || isBetterHit(sourceResult._internal_candidate, bestSourceCandidate)) {
bestSourceHit = candidateHit;
bestSourceCandidate = sourceResult._internal_candidate;
}
}
}
if (bestPrimitiveHit && bestPrimitiveHit._internal_source === source && bestPrimitiveHit._internal_hit.zOrder !== 'bottom' && bestPrimitiveHit._internal_hit.isBackground) {
return bestSourceHit ?? convertPrimitiveHitResult(bestPrimitiveHit);
}
}
if (bestSourceHit !== null) {
return bestSourceHit;
}
if (bestPrimitiveHit?._internal_hit) {
// return primitive hits for the 'bottom' layer
return convertPrimitiveHitResult(bestPrimitiveHit);
}
return null;
}
class FormattedLabelsCache {
constructor(format, horzScaleBehavior, size = 50) {
this._private__actualSize = 0;
this._private__usageTick = 1;
this._private__oldestTick = 1;
this._private__cache = new Map();
this._private__tick2Labels = new Map();
this._private__format = format;
this._private__horzScaleBehavior = horzScaleBehavior;
this._private__maxSize = size;
}
_internal_format(tickMark) {
const time = tickMark.time;
const cacheKey = this._private__horzScaleBehavior.cacheKey(time);
const tick = this._private__cache.get(cacheKey);
if (tick !== undefined) {
return tick._internal_string;
}
if (this._private__actualSize === this._private__maxSize) {
const oldestValue = this._private__tick2Labels.get(this._private__oldestTick);
this._private__tick2Labels.delete(this._private__oldestTick);
this._private__cache.delete(ensureDefined(oldestValue));
this._private__oldestTick++;
this._private__actualSize--;
}
const str = this._private__format(tickMark);
this._private__cache.set(cacheKey, { _internal_string: str, _internal_tick: this._private__usageTick });
this._private__tick2Labels.set(this._private__usageTick, cacheKey);
this._private__actualSize++;
this._private__usageTick++;
return str;
}
}
class RangeImpl {
constructor(left, right) {
assert(left <= right, 'right should be >= left');
this._private__left = left;
this._private__right = right;
}
_internal_left() {
return this._private__left;
}
_internal_right() {
return this._private__right;
}
_internal_count() {
return this._private__right - this._private__left + 1;
}
_internal_contains(index) {
return this._private__left <= index && index <= this._private__right;
}
_internal_equals(other) {
return this._private__left === other._internal_left() && this._private__right === other._internal_right();
}
}
function areRangesEqual(first, second) {
if (first === null || second === null) {
return first === second;
}
return first._internal_equals(second);
}
class TickMarks {
constructor() {
this._private__marksByWeight = new Map();
this._private__cache = null;
this._private__uniformDistribution = false;
}
_internal_setUniformDistribution(val) {
this._private__uniformDistribution = val;
this._private__cache = null;
}
_internal_setTimeScalePoints(newPoints, firstChangedPointIndex) {
this._private__removeMarksSinceIndex(firstChangedPointIndex);
this._private__cache = null;
for (let index = firstChangedPointIndex; index < newPoints.length; ++index) {
const point = newPoints[index];
let marksForWeight = this._private__marksByWeight.get(point.timeWeight);
if (marksForWeight === undefined) {
marksForWeight = [];
this._private__marksByWeight.set(point.timeWeight, marksForWeight);
}
marksForWeight.push({
index: index,
time: point.time,
weight: point.timeWeight,
originalTime: point.originalTime,
});
}
}
_internal_build(spacing, maxWidth, checkIndicesForData, indicesWithDataMap, indicesWithDataId) {
const maxIndexesPerMark = Math.ceil(maxWidth / spacing);
if (this._private__cache === null ||
this._private__cache._internal_maxIndexesPerMark !== maxIndexesPerMark ||
indicesWithDataId !== this._private__cache._internal_indicesWithDataId ||
checkIndicesForData !== this._private__cache._internal_checkIndicesForData) {
this._private__cache = {
_internal_indicesWithDataId: indicesWithDataId,
_internal_checkIndicesForData: checkIndicesForData,
_internal_marks: this._private__buildMarksImpl(maxIndexesPerMark, checkIndicesForData, indicesWithDataMap),
_internal_maxIndexesPerMark: maxIndexesPerMark,
};
}
return this._private__cache._internal_marks;
}
_private__removeMarksSinceIndex(sinceIndex) {
if (sinceIndex === 0) {
this._private__marksByWeight.clear();
return;
}
const weightsToClear = [];
this._private__marksByWeight.forEach((marks, timeWeight) => {
if (sinceIndex <= marks[0].index) {
weightsToClear.push(timeWeight);
}
else {
marks.splice(lowerBound(marks, sinceIndex, (tm) => tm.index < sinceIndex), Infinity);
}
});
for (const weight of weightsToClear) {
this._private__marksByWeight.delete(weight);
}
}
_private__buildMarksImpl(maxIndexesPerMark, checkIndicesForData, indicesWithDataMap) {
let marks = [];
const canBeIncluded = (mark) => !checkIndicesForData || indicesWithDataMap.has(mark.index);
for (const weight of Array.from(this._private__marksByWeight.keys()).sort((a, b) => b - a)) {
if (!this._private__marksByWeight.get(weight)) {
continue;
}
// Built tickMarks are now prevMarks, and marks it as new array
const prevMarks = marks;
marks = [];
const prevMarksLength = prevMarks.length;
let prevMarksPointer = 0;
const currentWeight = ensureDefined(this._private__marksByWeight.get(weight));
const currentWeightLength = currentWeight.length;
let rightIndex = Infinity;
let leftIndex = -Infinity;
for (let i = 0; i < currentWeightLength; i++) {
const mark = currentWeight[i];
const currentIndex = mark.index;
// Determine indexes with which current index will be compared
// All marks to the right is moved to new array
while (prevMarksPointer < prevMarksLength) {
const lastMark = prevMarks[prevMarksPointer];
const lastIndex = lastMark.index;
if (lastIndex < currentIndex && canBeIncluded(lastMark)) {
prevMarksPointer++;
marks.push(lastMark);
leftIndex = lastIndex;
rightIndex = Infinity;
}
else {
rightIndex = lastIndex;
break;
}
}
if (rightIndex - currentIndex >= maxIndexesPerMark &&
currentIndex - leftIndex >= maxIndexesPerMark &&
canBeIncluded(mark)) {
// TickMark fits. Place it into new array
marks.push(mark);
leftIndex = currentIndex;
}
else {
if (this._private__uniformDistribution) {
return prevMarks;
}
}
}
// Place all unused tickMarks into new array;
for (; prevMarksPointer < prevMarksLength; prevMarksPointer++) {
if (canBeIncluded(prevMarks[prevMarksPointer])) {
marks.push(prevMarks[prevMarksPointer]);
}
}
}
return marks;
}
}
class TimeScaleVisibleRange {
constructor(logicalRange) {
this._private__logicalRange = logicalRange;
}
_internal_strictRange() {
if (this._private__logicalRange === null) {
return null;
}
return new RangeImpl(Math.floor(this._private__logicalRange._internal_left()), Math.ceil(this._private__logicalRange._internal_right()));
}
_internal_logicalRange() {
return this._private__logicalRange;
}
static _internal_invalid() {
return new TimeScaleVisibleRange(null);
}
}
const defaultTickMarkMaxCharacterLength = 8;
function markWithGreaterWeight$2(a, b) {
return a.weight > b.weight ? a : b;
}
class TimeScale {
constructor(model, options, localizationOptions, horzScaleBehavior) {
this._private__width = 0;
this._private__baseIndexOrNull = null;
this._private__points = [];
this._private__scrollStartPoint = null;
this._private__scaleStartPoint = null;
this._private__tickMarks = new TickMarks();
this._private__formattedByWeight = new Map();
this._private__visibleRange = TimeScaleVisibleRange._internal_invalid();
this._private__visibleRangeInvalidated = true;
this._private__visibleBarsChanged = new Delegate();
this._private__logicalRangeChanged = new Delegate();
this._private__optionsApplied = new Delegate();
this._private__commonTransitionStartState = null;
this._private__timeMarksCache = null;
this._private__indicesWithData = new Map();
this._private__indicesWithDataUpdateId = -1;
this._private__labels = [];
this._private__conflationFactor = 1;
this._private__options = options;
this._private__localizationOptions = localizationOptions;
this._private__rightOffset = options.rightOffset;
this._private__barSpacing = options.barSpacing;
this._private__model = model;
this._private__checkRightOffsetPixels(options);
this._private__horzScaleBehavior = horzScaleBehavior;
this._private__updateDateTimeFormatter();
this._private__tickMarks._internal_setUniformDistribution(options.uniformDistribution);
this._private__updateConflationFactor();
this._internal_recalculateIndicesWithData();
}
_internal_options() {
return this._private__options;
}
_internal_applyLocalizationOptions(localizationOptions) {
merge(this._private__localizationOptions, localizationOptions);
this._private__invalidateTickMarks();
this._private__updateDateTimeFormatter();
}
_internal_applyOptions(options, localizationOptions) {
merge(this._private__options, options);
if (this._private__options.fixLeftEdge) {
this._private__doFixLeftEdge();
}
if (this._private__options.fixRightEdge) {
this._private__doFixRightEdge();
}
// note that bar spacing should be applied before right offset
// because right offset depends on bar spacing
if (options.barSpacing !== undefined) {
this._private__model._internal_setBarSpacing(options.barSpacing);
}
if (options.rightOffset !== undefined) {
this._private__model._internal_setRightOffset(options.rightOffset);
}
this._private__checkRightOffsetPixels(options);
if (options.minBarSpacing !== undefined || options.maxBarSpacing !== undefined) {
// yes, if we apply bar spacing constrains then we need to correct bar spacing
// the easiest way is to apply it once again
this._private__model._internal_setBarSpacing(options.barSpacing ?? this._private__barSpacing);
}
if (options.ignoreWhitespaceIndices !== undefined && options.ignoreWhitespaceIndices !== this._private__options.ignoreWhitespaceIndices) {
this._internal_recalculateIndicesWithData();
}
this._private__invalidateTickMarks();
this._private__updateDateTimeFormatter();
// Recompute conflation factor when options that may affect it change
if (options.enableConflation !== undefined || options.conflationThresholdFactor !== undefined) {
this._private__updateConflationFactor();
}
this._private__optionsApplied._internal_fire();
}
_internal_indexToTime(index) {
return this._private__points[index]?.time ?? null;
}
_internal_indexToTimeScalePoint(index) {
return this._private__points[index] ?? null;
}
_internal_timeToIndex(time, findNearest) {
if (this._private__points.length < 1) {
// no time points available
return null;
}
if (this._private__horzScaleBehavior.key(time) > this._private__horzScaleBehavior.key(this._private__points[this._private__points.length - 1].time)) {
// special case
return findNearest ? this._private__points.length - 1 : null;
}
const index = lowerBound(this._private__points, this._private__horzScaleBehavior.key(time), (a, b) => this._private__horzScaleBehavior.key(a.time) < b);
if (this._private__horzScaleBehavior.key(time) < this._private__horzScaleBehavior.key(this._private__points[index].time)) {
return findNearest ? index : null;
}
return index;
}
_internal_isEmpty() {
return this._private__width === 0 || this._private__points.length === 0 || this._private__baseIndexOrNull === null;
}
_internal_hasPoints() {
return this._private__points.length > 0;
}
// strict range: integer indices of the bars in the visible range rounded in more wide direction
_internal_visibleStrictRange() {
this._private__updateVisibleRange();
return this._private__visibleRange._internal_strictRange();
}
_internal_visibleLogicalRange() {
this._private__updateVisibleRange();
return this._private__visibleRange._internal_logicalRange();
}
_internal_visibleTimeRange() {
const visibleBars = this._internal_visibleStrictRange();
if (visibleBars === null) {
return null;
}
const range = {
from: visibleBars._internal_left(),
to: visibleBars._internal_right(),
};
return this._internal_timeRangeForLogicalRange(range);
}
_internal_timeRangeForLogicalRange(range) {
const from = Math.round(range.from);
const to = Math.round(range.to);
const firstIndex = ensureNotNull(this._private__firstIndex());
const lastIndex = ensureNotNull(this._private__lastIndex());
return {
from: ensureNotNull(this._internal_indexToTimeScalePoint(Math.max(firstIndex, from))),
to: ensureNotNull(this._internal_indexToTimeScalePoint(Math.min(lastIndex, to))),
};
}
_internal_logicalRangeForTimeRange(range) {
return {
from: ensureNotNull(this._internal_timeToIndex(range.from, true)),
to: ensureNotNull(this._internal_timeToIndex(range.to, true)),
};
}
_internal_width() {
return this._private__width;
}
_internal_setWidth(newWidth) {
if (!isFinite(newWidth) || newWidth <= 0) {
return;
}
if (this._private__width === newWidth) {
return;
}
// when we change the width and we need to correct visible range because of fixing left edge
// we need to check the previous visible range rather than the new one
// because it might be updated by changing width, bar spacing, etc
// but we need to try to keep the same range
const previousVisibleRange = this._internal_visibleLogicalRange();
const oldWidth = this._private__width;
this._private__width = newWidth;
this._private__visibleRangeInvalidated = true;
if (this._private__options.lockVisibleTimeRangeOnResize && oldWidth !== 0) {
// recalculate bar spacing
const newBarSpacing = this._private__barSpacing * newWidth / oldWidth;
this._private__barSpacing = newBarSpacing;
}
// if time scale is scrolled to the end of data and we have fixed right edge
// keep left edge instead of right
// we need it to avoid "shaking" if the last bar visibility affects time scale width
if (this._private__options.fixLeftEdge) {
// note that logical left range means not the middle of a bar (it's the left border)
if (previousVisibleRange !== null && previousVisibleRange._internal_left() <= 0) {
const delta = oldWidth - newWidth;
// reduce _rightOffset means move right
// we could move more than required - this will be fixed by _correctOffset()
this._private__rightOffset -= Math.round(delta / this._private__barSpacing) + 1;
this._private__visibleRangeInvalidated = true;
}
}
// updating bar spacing should be first because right offset depends on it
this._private__correctBarSpacing();
this._private__correctOffset();
}
_internal_indexToCoordinate(index) {
if (this._internal_isEmpty() || !isInteger(index)) {
return 0;
}
const baseIndex = this._internal_baseIndex();
const deltaFromRight = baseIndex + this._private__rightOffset - index;
const coordinate = this._private__width - (deltaFromRight + 0.5) * this._private__barSpacing - 1;
return coordinate;
}
_internal_indexesToCoordinates(points, visibleRange) {
const baseIndex = this._internal_baseIndex();
const indexFrom = (visibleRange === undefined) ? 0 : visibleRange.from;
const indexTo = (visibleRange === undefined) ? points.length : visibleRange.to;
for (let i = indexFrom; i < indexTo; i++) {
const index = points[i]._internal_time;
const deltaFromRight = baseIndex + this._private__rightOffset - index;
const coordinate = this._private__width - (deltaFromRight + 0.5) * this._private__barSpacing - 1;
points[i]._internal_x = coordinate;
}
}
_internal_coordinateToIndex(x, considerIgnoreWhitespace) {
const index = Math.ceil(this._private__coordinateToFloatIndex(x));
if (!considerIgnoreWhitespace ||
!this._private__options.ignoreWhitespaceIndices ||
this._private__shouldConsiderIndex(index)) {
return index;
}
return this._private__findNearestIndexWithData(index);
}
_internal_setRightOffset(offset) {
this._private__visibleRangeInvalidated = true;
this._private__rightOffset = offset;
this._private__correctOffset();
this._private__model._internal_recalculateAllPanes();
this._private__model._internal_lightUpdate();
}
_internal_barSpacing() {
return this._private__barSpacing;
}
_internal_setBarSpacing(newBarSpacing) {
const oldBarSpacing = this._private__barSpacing;
this._private__setBarSpacing(newBarSpacing);
if (this._private__options.rightOffsetPixels !== undefined && oldBarSpacing !== 0) {
// when in pixel mode, zooming should keep the pixel offset, so we need to
// recalculate the bar offset.
const newRightOffset = this._private__rightOffset * oldBarSpacing / this._private__barSpacing;
this._private__rightOffset = newRightOffset;
}
// do not allow scroll out of visible bars
this._private__correctOffset();
this._private__model._internal_recalculateAllPanes();
this._private__model._internal_lightUpdate();
}
_internal_rightOffset() {
return this._private__rightOffset;
}
// eslint-disable-next-line complexity
_internal_marks() {
if (this._internal_isEmpty()) {
return null;
}
if (this._private__timeMarksCache !== null) {
return this._private__timeMarksCache;
}
const spacing = this._private__barSpacing;
const fontSize = this._private__model._internal_options()['layout'].fontSize;
const pixelsPer8Characters = (fontSize + 4) * 5;
const pixelsPerCharacter = pixelsPer8Characters / defaultTickMarkMaxCharacterLength;
const maxLabelWidth = pixelsPerCharacter * (this._private__options.tickMarkMaxCharacterLength || defaultTickMarkMaxCharacterLength);
const indexPerLabel = Math.round(maxLabelWidth / spacing);
const visibleBars = ensureNotNull(this._internal_visibleStrictRange());
const firstBar = Math.max(visibleBars._internal_left(), visibleBars._internal_left() - indexPerLabel);
const lastBar = Math.max(visibleBars._internal_right(), visibleBars._internal_right() - indexPerLabel);
const items = this._private__tickMarks._internal_build(spacing, maxLabelWidth, this._private__options.ignoreWhitespaceIndices, this._private__indicesWithData, this._private__indicesWithDataUpdateId);
// according to indexPerLabel value this value means "earliest index which _might be_ used as the second label on time scale"
const earliestIndexOfSecondLabel = this._private__firstIndex() + indexPerLabel;
// according to indexPerLabel value this value means "earliest index which _might be_ used as the second last label on time scale"
const indexOfSecondLastLabel = this._private__lastIndex() - indexPerLabel;
const isAllScalingAndScrollingDisabled = this._private__isAllScalingAndScrollingDisabled();
const isLeftEdgeFixed = this._private__options.fixLeftEdge || isAllScalingAndScrollingDisabled;
const isRightEdgeFixed = this._private__options.fixRightEdge || isAllScalingAndScrollingDisabled;
let targetIndex = 0;
for (const tm of items) {
if (!(firstBar <= tm.index && tm.index <= lastBar)) {
continue;
}
let label;
if (targetIndex < this._private__labels.length) {
label = this._private__labels[targetIndex];
label.coord = this._internal_indexToCoordinate(tm.index);
label.label = this._private__formatLabel(tm);
label.weight = tm.weight;
}
else {
label = {
needAlignCoordinate: false,
coord: this._internal_indexToCoordinate(tm.index),
label: this._private__formatLabel(tm),
weight: tm.weight,
};
this._private__labels.push(label);
}
if (this._private__barSpacing > (maxLabelWidth / 2) && !isAllScalingAndScrollingDisabled) {
// if there is enough space then let's show all tick marks as usual
label.needAlignCoordinate = false;
}
else {
// if a user is able to scroll after a tick mark then show it as usual, otherwise the coordinate might be aligned
// if the index is for the second (last) label or later (earlier) then most likely this label might be displayed without correcting the coordinate
label.needAlignCoordinate = (isLeftEdgeFixed && tm.index <= earliestIndexOfSecondLabel) || (isRightEdgeFixed && tm.index >= indexOfSecondLastLabel);
}
targetIndex++;
}
this._private__labels.length = targetIndex;
this._private__timeMarksCache = this._private__labels;
return this._private__labels;
}
_internal_restoreDefault() {
this._private__visibleRangeInvalidated = true;
this._internal_setBarSpacing(this._private__options.barSpacing);
let newOffset;
if (this._private__options.rightOffsetPixels !== undefined) {
newOffset = this._private__options.rightOffsetPixels / this._internal_barSpacing();
}
else {
newOffset = this._private__options.rightOffset;
}
this._internal_setRightOffset(newOffset);
}
_internal_setBaseIndex(baseIndex) {
this._private__visibleRangeInvalidated = true;
this._private__baseIndexOrNull = baseIndex;
this._private__correctOffset();
this._private__doFixLeftEdge();
}
/**
* Zoom in/out the scale around a `zoomPoint` on `scale` value.
*
* @param zoomPoint - X coordinate of the point to apply the zoom.
* If `rightBarStaysOnScroll` option is disabled, then will be used to restore right offset.
* @param scale - Zoom value (in 1/10 parts of current bar spacing).
* Negative value means zoom out, positive - zoom in.
*/
_internal_zoom(zoomPoint, scale) {
const floatIndexAtZoomPoint = this._private__coordinateToFloatIndex(zoomPoint);
const barSpacing = this._internal_barSpacing();
const newBarSpacing = barSpacing + scale * (barSpacing / 10);
// zoom in/out bar spacing
this._internal_setBarSpacing(newBarSpacing);
if (!this._private__options.rightBarStaysOnScroll) {
// and then correct right offset to move index under zoomPoint back to its coordinate
this._internal_setRightOffset(this._internal_rightOffset() + (floatIndexAtZoomPoint - this._private__coordinateToFloatIndex(zoomPoint)));
}
}
_internal_startScale(x) {
if (this._private__scrollStartPoint) {
this._internal_endScroll();
}
if (this._private__scaleStartPoint !== null || this._private__commonTransitionStartState !== null) {
return;
}
if (this._internal_isEmpty()) {
return;
}
this._private__scaleStartPoint = x;
this._private__saveCommonTransitionsStartState();
}
_internal_scaleTo(x) {
if (this._private__commonTransitionStartState === null) {
return;
}
const startLengthFromRight = clamp(this._private__width - x, 0, this._private__width);
const currentLengthFromRight = clamp(this._private__width - ensureNotNull(this._private__scaleStartPoint), 0, this._private__width);
if (startLengthFromRight === 0 || currentLengthFromRight === 0) {
return;
}
this._internal_setBarSpacing(this._private__commonTransitionStartState._internal_barSpacing * startLengthFromRight / currentLengthFromRight);
}
_internal_endScale() {
if (this._private__scaleStartPoint === null) {
return;
}
this._private__scaleStartPoint = null;
this._private__clearCommonTransitionsStartState();
}
_internal_startScroll(x) {
if (this._private__scrollStartPoint !== null || this._private__commonTransitionStartState !== null) {
return;
}
if (this._internal_isEmpty()) {
return;
}
this._private__scrollStartPoint = x;
this._private__saveCommonTransitionsStartState();
}
_internal_scrollTo(x) {
if (this._private__scrollStartPoint === null) {
return;
}
const shiftInLogical = (this._private__scrollStartPoint - x) / this._internal_barSpacing();
this._private__rightOffset = ensureNotNull(this._private__commonTransitionStartState)._internal_rightOffset + shiftInLogical;
this._private__visibleRangeInvalidated = true;
// do not allow scroll out of visible bars
this._private__correctOffset();
}
_internal_endScroll() {
if (this._private__scrollStartPoint === null) {
return;
}
this._private__scrollStartPoint = null;
this._private__clearCommonTransitionsStartState();
}
_internal_scrollToRealTime() {
this._internal_scrollToOffsetAnimated(this._private__options.rightOffset);
}
_internal_scrollToOffsetAnimated(offset, animationDuration = 400 /* Constants.DefaultAnimationDuration */) {
if (!isFinite(offset)) {
throw new RangeError('offset is required and must be finite number');
}
if (!isFinite(animationDuration) || animationDuration <= 0) {
throw new RangeError('animationDuration (optional) must be finite positive number');
}
const source = this._private__rightOffset;
const animationStart = performance.now();
this._private__model._internal_setTimeScaleAnimation({
_internal_finished: (time) => (time - animationStart) / animationDuration >= 1,
_internal_getPosition: (time) => {
const animationProgress = (time - animationStart) / animationDuration;
const finishAnimation = animationProgress >= 1;
return finishAnimation ? offset : source + (offset - source) * animationProgress;
},
});
}
_internal_update(newPoints, firstChangedPointIndex) {
this._private__visibleRangeInvalidated = true;
this._private__points = newPoints;
this._private__tickMarks._internal_setTimeScalePoints(newPoints, firstChangedPointIndex);
this._private__correctOffset();
}
_internal_visibleBarsChanged() {
return this._private__visibleBarsChanged;
}
_internal_logicalRangeChanged() {
return this._private__logicalRangeChanged;
}
_internal_optionsApplied() {
return this._private__optionsApplied;
}
_internal_baseIndex() {
// null is used to known that baseIndex is not set yet
// so in methods which should known whether it is set or not
// we should check field `_baseIndexOrNull` instead of getter `baseIndex()`
// see minRightOffset for example
return this._private__baseIndexOrNull || 0;
}
_internal_setVisibleRange(range, applyDefaultOffset) {
const length = range._internal_count();
const pixelOffset = (applyDefaultOffset && this._private__options.rightOffsetPixels) || 0;
this._private__setBarSpacing((this._private__width - pixelOffset) / length);
this._private__rightOffset = range._internal_right() - this._internal_baseIndex();
if (applyDefaultOffset) {
this._private__rightOffset = pixelOffset
? pixelOffset / this._internal_barSpacing()
: this._private__options.rightOffset;
}
this._private__correctOffset();
this._private__visibleRangeInvalidated = true;
this._private__model._internal_recalculateAllPanes();
this._private__model._internal_lightUpdate();
}
_internal_fitContent() {
const first = this._private__firstIndex();
const last = this._private__lastIndex();
if (first === null || last === null) {
return;
}
// If we are not using rightOffsetPixels then we should include the user defined rightOffset
// in the Range so that the bar scaling considers the space required for the offset.
const rightOffsetBars = (!this._private__options.rightOffsetPixels && this._private__options.rightOffset) || 0;
this._internal_setVisibleRange(new RangeImpl(first, last + rightOffsetBars), true);
}
_internal_setLogicalRange(range) {
const barRange = new RangeImpl(range.from, range.to);
this._internal_setVisibleRange(barRange);
}
_internal_formatDateTime(timeScalePoint) {
if (this._private__localizationOptions.timeFormatter !== undefined) {
return this._private__localizationOptions.timeFormatter(timeScalePoint.originalTime);
}
return this._private__horzScaleBehavior.formatHorzItem(timeScalePoint.time);
}
_internal_recalculateIndicesWithData() {
if (!this._private__options.ignoreWhitespaceIndices) {
return;
}
this._private__indicesWithData.clear();
const series = this._private__model._internal_serieses();
for (const s of series) {
for (const index of s._internal_fulfilledIndices()) {
this._private__indicesWithData.set(index, true);
}
}
this._private__indicesWithDataUpdateId++;
}
/**
* Returns the current data conflation factor.
* Factor \> 1 means data points should be conflated for performance.
*/
_internal_conflationFactor() {
return this._private__conflationFactor;
}
/**
* Provides an array of possible conflations factors based on the current
* minBarSpacing setting for the chart.
* @returns Arrays of conflation factors (number of bars to merge)
*/
_internal_possibleConflationFactors() {
const devicePixelRatio = window.devicePixelRatio || 1;
const conflationThreshold = 1.0 / devicePixelRatio;
const minBarSpacing = this._private__options.minBarSpacing;
if (minBarSpacing >= conflationThreshold) {
return [1];
}
// Return all power-of-2 conflation levels that might be used
const factors = [1];
let currentLevel = 2;
const maxLevel = 512;
while (currentLevel <= maxLevel) {
const levelThreshold = conflationThreshold / currentLevel;
if (minBarSpacing < levelThreshold) {
factors.push(currentLevel);
}
currentLevel *= 2;
}
return factors;
}
_private__isAllScalingAndScrollingDisabled() {
const handleScroll = this._private__model._internal_options()['handleScroll'];
const handleScale = this._private__model._internal_options()['handleScale'];
return !handleScroll.horzTouchDrag
&& !handleScroll.mouseWheel
&& !handleScroll.pressedMouseMove
&& !handleScroll.vertTouchDrag
&& !handleScale.axisDoubleClickReset.time
&& !handleScale.axisPressedMouseMove.time
&& !handleScale.mouseWheel
&& !handleScale.pinch;
}
_private__firstIndex() {
return this._private__points.length === 0 ? null : 0;
}
_private__lastIndex() {
return this._private__points.length === 0 ? null : (this._private__points.length - 1);
}
_private__rightOffsetForCoordinate(x) {
return (this._private__width - 1 - x) / this._private__barSpacing;
}
_private__coordinateToFloatIndex(x) {
const deltaFromRight = this._private__rightOffsetForCoordinate(x);
const baseIndex = this._internal_baseIndex();
const index = baseIndex + this._private__rightOffset - deltaFromRight;
// JavaScript uses very strange rounding
// we need rounding to avoid problems with calculation errors
return Math.round(index * 1000000) / 1000000;
}
_private__setBarSpacing(newBarSpacing) {
const oldBarSpacing = this._private__barSpacing;
this._private__barSpacing = newBarSpacing;
this._private__correctBarSpacing();
// this._barSpacing might be changed in _correctBarSpacing
if (oldBarSpacing !== this._private__barSpacing) {
this._private__visibleRangeInvalidated = true;
this._private__resetTimeMarksCache();
this._private__updateConflationFactor();
}
}
_private__updateVisibleRange() {
if (!this._private__visibleRangeInvalidated) {
return;
}
this._private__visibleRangeInvalidated = false;
if (this._internal_isEmpty()) {
this._private__setVisibleRange(TimeScaleVisibleRange._internal_invalid());
return;
}
const baseIndex = this._internal_baseIndex();
const newBarsLength = this._private__width / this._private__barSpacing;
const rightBorder = this._private__rightOffset + baseIndex;
const leftBorder = rightBorder - newBarsLength + 1;
const logicalRange = new RangeImpl(leftBorder, rightBorder);
this._private__setVisibleRange(new TimeScaleVisibleRange(logicalRange));
}
_private__correctBarSpacing() {
const barSpacing = clamp(this._private__barSpacing, this._private__minBarSpacing(), this._private__maxBarSpacing());
if (this._private__barSpacing !== barSpacing) {
this._private__barSpacing = barSpacing;
this._private__visibleRangeInvalidated = true;
}
}
_private__maxBarSpacing() {
if (this._private__options.maxBarSpacing > 0) {
// option takes precedance
return this._private__options.maxBarSpacing;
}
else {
// half of the width is default value for maximum bar spacing
return this._private__width * 0.5;
}
}
_private__minBarSpacing() {
// if both options are enabled then limit bar spacing so that zooming-out is not possible
// if it would cause either the first or last points to move too far from an edge
if (this._private__options.fixLeftEdge && this._private__options.fixRightEdge && this._private__points.length !== 0) {
return this._private__width / this._private__points.length;
}
return this._private__options.minBarSpacing;
}
/**
* Updates the conflation factor based on current bar spacing using DPR-aware power-of-2 calculation with optional smoothing factor.
* The smoothing factor allows intentional over-conflation for smoother appearance in small charts and sparklines.
*/
_private__updateConflationFactor() {
if (!this._private__options.enableConflation) {
this._private__conflationFactor = 1;
return;
}
// Use DPR-aware threshold calculation with smoothing factor
const devicePixelRatio = window.devicePixelRatio || 1;
const smoothingFactor = this._private__options.conflationThresholdFactor ?? 1;
const adjustedThreshold = (1.0 / devicePixelRatio) * smoothingFactor;
if (this._private__barSpacing >= adjustedThreshold) {
this._private__conflationFactor = 1;
return;
}
// Calculate conflation level as power of 2
const ratio = adjustedThreshold / this._private__barSpacing;
const conflationLevel = Math.pow(2, Math.floor(Math.log2(ratio)));
// Ensure we don't exceed maximum conflation level (512)
this._private__conflationFactor = Math.min(conflationLevel, 512);
}
_private__correctOffset() {
// block scrolling of to past
const minRightOffset = this._private__minRightOffset();
if (minRightOffset !== null && this._private__rightOffset < minRightOffset) {
this._private__rightOffset = minRightOffset;
this._private__visibleRangeInvalidated = true;
}
// block scrolling of to future
const maxRightOffset = this._private__maxRightOffset();
if (this._private__rightOffset > maxRightOffset) {
this._private__rightOffset = maxRightOffset;
this._private__visibleRangeInvalidated = true;
}
}
_private__minRightOffset() {
const firstIndex = this._private__firstIndex();
const baseIndex = this._private__baseIndexOrNull;
if (firstIndex === null || baseIndex === null) {
return null;
}
const barsEstimation = this._private__options.fixLeftEdge
? this._private__width / this._private__barSpacing
: Math.min(2 /* Constants.MinVisibleBarsCount */, this._private__points.length);
return firstIndex - baseIndex - 1 + barsEstimation;
}
_private__maxRightOffset() {
return this._private__options.fixRightEdge
? 0
: (this._private__width / this._private__barSpacing) - Math.min(2 /* Constants.MinVisibleBarsCount */, this._private__points.length);
}
_private__saveCommonTransitionsStartState() {
this._private__commonTransitionStartState = {
_internal_barSpacing: this._internal_barSpacing(),
_internal_rightOffset: this._internal_rightOffset(),
};
}
_private__clearCommonTransitionsStartState() {
this._private__commonTransitionStartState = null;
}
_private__formatLabel(tickMark) {
let formatter = this._private__formattedByWeight.get(tickMark.weight);
if (formatter === undefined) {
formatter = new FormattedLabelsCache((mark) => {
return this._private__formatLabelImpl(mark);
}, this._private__horzScaleBehavior);
this._private__formattedByWeight.set(tickMark.weight, formatter);
}
return formatter._internal_format(tickMark);
}
_private__formatLabelImpl(tickMark) {
return this._private__horzScaleBehavior.formatTickmark(tickMark, this._private__localizationOptions);
}
_private__setVisibleRange(newVisibleRange) {
const oldVisibleRange = this._private__visibleRange;
this._private__visibleRange = newVisibleRange;
if (!areRangesEqual(oldVisibleRange._internal_strictRange(), this._private__visibleRange._internal_strictRange())) {
this._private__visibleBarsChanged._internal_fire();
}
if (!areRangesEqual(oldVisibleRange._internal_logicalRange(), this._private__visibleRange._internal_logicalRange())) {
this._private__logicalRangeChanged._internal_fire();
}
// TODO: reset only coords in case when this._visibleBars has not been changed
this._private__resetTimeMarksCache();
}
_private__resetTimeMarksCache() {
this._private__timeMarksCache = null;
}
_private__invalidateTickMarks() {
this._private__resetTimeMarksCache();
this._private__formattedByWeight.clear();
}
_private__updateDateTimeFormatter() {
this._private__horzScaleBehavior.updateFormatter(this._private__localizationOptions);
}
_private__doFixLeftEdge() {
if (!this._private__options.fixLeftEdge) {
return;
}
const firstIndex = this._private__firstIndex();
if (firstIndex === null) {
return;
}
const visibleRange = this._internal_visibleStrictRange();
if (visibleRange === null) {
return;
}
const delta = visibleRange._internal_left() - firstIndex;
if (delta < 0) {
const leftEdgeOffset = this._private__rightOffset - delta - 1;
this._internal_setRightOffset(leftEdgeOffset);
}
this._private__correctBarSpacing();
}
_private__doFixRightEdge() {
this._private__correctOffset();
this._private__correctBarSpacing();
}
_private__shouldConsiderIndex(index) {
if (!this._private__options.ignoreWhitespaceIndices) {
return true;
}
return this._private__indicesWithData.get(index) || false;
}
_private__findNearestIndexWithData(x) {
const gen = testNearestIntegers(x);
const maxIndex = this._private__lastIndex();
while (maxIndex) {
const index = gen.next().value;
if (this._private__indicesWithData.get(index)) {
return index;
}
if (index < 0 || index > maxIndex) {
break;
}
}
return x; // fallback to original index
}
_private__checkRightOffsetPixels(options) {
if (options.rightOffsetPixels !== undefined) {
const newOffset = options.rightOffsetPixels / (options.barSpacing || this._private__barSpacing);
this._private__model._internal_setRightOffset(newOffset);
}
}
}
function* testNearestIntegers(num) {
const rounded = Math.round(num);
const isRoundedDown = rounded < num;
let offset = 1;
while (true) {
if (isRoundedDown) {
yield rounded + offset;
yield rounded - offset;
}
else {
yield rounded - offset;
yield rounded + offset;
}
offset++;
}
}
///
/**
* Determine how to exit the tracking mode.
*
* By default, mobile users will long press to deactivate the scroll and have the ability to check values and dates.
* Another press is required to activate the scroll, be able to move left/right, zoom, etc.
*/
var TrackingModeExitMode;
(function (TrackingModeExitMode) {
/**
* Tracking Mode will be deactivated on touch end event.
*/
TrackingModeExitMode[TrackingModeExitMode["OnTouchEnd"] = 0] = "OnTouchEnd";
/**
* Tracking Mode will be deactivated on the next tap event.
*/
TrackingModeExitMode[TrackingModeExitMode["OnNextTap"] = 1] = "OnNextTap";
})(TrackingModeExitMode || (TrackingModeExitMode = {}));
function isPanePrimitive(source) {
return source instanceof Pane;
}
class ChartModel {
constructor(invalidateHandler, options, horzScaleBehavior) {
this._private__panes = [];
this._private__serieses = [];
this._private__visibleSerieses = null;
this._private__width = 0;
this._private__hoveredSource = null;
this._private__priceScalesOptionsChanged = new Delegate();
this._private__crosshairMoved = new Delegate();
this._private__gradientColorsCache = null;
this._private__invalidateHandler = invalidateHandler;
this._private__options = options;
this._private__horzScaleBehavior = horzScaleBehavior;
this._private__colorParser = new ColorParser(this._private__options.layout.colorParsers);
this._private__rendererOptionsProvider = new PriceAxisRendererOptionsProvider(this);
this._private__timeScale = new TimeScale(this, options.timeScale, this._private__options.localization, horzScaleBehavior);
this._private__crosshair = new Crosshair(this, options.crosshair);
this._private__magnet = new Magnet(options.crosshair);
if (options.addDefaultPane) {
this._private__getOrCreatePane(0);
this._private__panes[0]._internal_setStretchFactor(DEFAULT_STRETCH_FACTOR * 2);
}
this._private__backgroundTopColor = this._private__getBackgroundColor(0 /* BackgroundColorSide.Top */);
this._private__backgroundBottomColor = this._private__getBackgroundColor(1 /* BackgroundColorSide.Bottom */);
}
_internal_fullUpdate() {
this._private__invalidate(InvalidateMask._internal_full());
}
_internal_lightUpdate() {
this._private__invalidate(InvalidateMask._internal_light());
}
_internal_cursorUpdate() {
this._private__invalidate(new InvalidateMask(1 /* InvalidationLevel.Cursor */));
}
_internal_updateSource(source) {
const inv = this._private__invalidationMaskForSource(source);
this._private__invalidate(inv);
}
_internal_hoveredSource() {
return this._private__hoveredSource;
}
_internal_setHoveredSource(source) {
if (this._private__hoveredSource?._internal_source === source?._internal_source &&
this._private__hoveredSource?._internal_object?._internal_externalId === source?._internal_object?._internal_externalId &&
this._private__hoveredSource?._internal_object?._internal_hitTestData === source?._internal_object?._internal_hitTestData &&
this._private__hoveredSource?._internal_cursorStyle === source?._internal_cursorStyle &&
this._private__hoveredSource?._internal_itemType === source?._internal_itemType) {
return;
}
const prevSource = this._private__hoveredSource;
this._private__hoveredSource = source;
if (prevSource !== null) {
this._internal_updateSource(prevSource._internal_source);
}
// additional check to prevent unnecessary updates of same source
if (source !== null && source._internal_source !== prevSource?._internal_source) {
this._internal_updateSource(source._internal_source);
}
}
_internal_options() {
return this._private__options;
}
_internal_applyOptions(options) {
merge(this._private__options, options);
this._private__panes.forEach((p) => p._internal_applyScaleOptions(options));
if (options.timeScale !== undefined) {
this._private__timeScale._internal_applyOptions(options.timeScale);
}
if (options.localization !== undefined) {
this._private__timeScale._internal_applyLocalizationOptions(options.localization);
}
if (options.leftPriceScale || options.rightPriceScale) {
this._private__priceScalesOptionsChanged._internal_fire();
}
this._private__backgroundTopColor = this._private__getBackgroundColor(0 /* BackgroundColorSide.Top */);
this._private__backgroundBottomColor = this._private__getBackgroundColor(1 /* BackgroundColorSide.Bottom */);
this._internal_fullUpdate();
}
_internal_applyPriceScaleOptions(priceScaleId, options, paneIndex = 0) {
const pane = this._private__panes[paneIndex];
if (pane === undefined) {
{
throw new Error(`Trying to apply price scale options with incorrect pane index: ${paneIndex}`);
}
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
if (priceScaleId === "left" /* DefaultPriceScaleId.Left */) {
merge(this._private__options, {
leftPriceScale: options,
});
pane._internal_applyScaleOptions({
leftPriceScale: options,
});
this._private__priceScalesOptionsChanged._internal_fire();
this._internal_fullUpdate();
return;
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
}
else if (priceScaleId === "right" /* DefaultPriceScaleId.Right */) {
merge(this._private__options, {
rightPriceScale: options,
});
pane._internal_applyScaleOptions({
rightPriceScale: options,
});
this._private__priceScalesOptionsChanged._internal_fire();
this._internal_fullUpdate();
return;
}
const res = this._internal_findPriceScale(priceScaleId, paneIndex);
if (res === null) {
{
throw new Error(`Trying to apply price scale options with incorrect ID: ${priceScaleId}`);
}
}
res._internal_priceScale._internal_applyOptions(options);
this._private__priceScalesOptionsChanged._internal_fire();
}
_internal_findPriceScale(priceScaleId, paneIndex) {
const pane = this._private__panes[paneIndex];
if (pane === undefined) {
return null;
}
const priceScale = pane._internal_priceScaleById(priceScaleId);
if (priceScale !== null) {
return {
_internal_pane: pane,
_internal_priceScale: priceScale,
};
}
return null;
}
_internal_timeScale() {
return this._private__timeScale;
}
_internal_panes() {
return this._private__panes;
}
_internal_crosshairSource() {
return this._private__crosshair;
}
_internal_crosshairMoved() {
return this._private__crosshairMoved;
}
_internal_setPaneHeight(pane, height) {
pane._internal_setHeight(height);
this._internal_recalculateAllPanes();
}
_internal_setWidth(width) {
this._private__width = width;
this._private__timeScale._internal_setWidth(this._private__width);
this._private__panes.forEach((pane) => pane._internal_setWidth(width));
this._internal_recalculateAllPanes();
}
_internal_removePane(index) {
if (this._private__panes.length === 1) {
return;
}
assert(index >= 0 && index < this._private__panes.length, 'Invalid pane index');
this._private__panes.splice(index, 1);
this._internal_fullUpdate();
}
_internal_changePanesHeight(paneIndex, height) {
if (this._private__panes.length < 2) {
return;
}
assert(paneIndex >= 0 && paneIndex < this._private__panes.length, 'Invalid pane index');
const targetPane = this._private__panes[paneIndex];
const totalStretch = this._private__panes.reduce((prevValue, pane) => prevValue + pane._internal_stretchFactor(), 0);
const totalHeight = this._private__panes.reduce((prevValue, pane) => prevValue + pane._internal_height(), 0);
const maxPaneHeight = totalHeight - MIN_PANE_HEIGHT * (this._private__panes.length - 1);
height = Math.min(maxPaneHeight, Math.max(MIN_PANE_HEIGHT, height));
const pixelStretchFactor = totalStretch / totalHeight;
const oldHeight = targetPane._internal_height();
targetPane._internal_setStretchFactor(height * pixelStretchFactor);
let otherPanesChange = height - oldHeight;
let panesCount = this._private__panes.length - 1;
for (const pane of this._private__panes) {
if (pane !== targetPane) {
const newPaneHeight = Math.min(maxPaneHeight, Math.max(30, pane._internal_height() - otherPanesChange / panesCount));
otherPanesChange -= (pane._internal_height() - newPaneHeight);
panesCount -= 1;
const newStretchFactor = newPaneHeight * pixelStretchFactor;
pane._internal_setStretchFactor(newStretchFactor);
}
}
this._internal_fullUpdate();
}
_internal_swapPanes(first, second) {
assert(first >= 0 && first < this._private__panes.length && second >= 0 && second < this._private__panes.length, 'Invalid pane index');
const firstPane = this._private__panes[first];
const secondPane = this._private__panes[second];
this._private__panes[first] = secondPane;
this._private__panes[second] = firstPane;
this._internal_fullUpdate();
}
_internal_movePane(from, to) {
assert(from >= 0 && from < this._private__panes.length && to >= 0 && to < this._private__panes.length, 'Invalid pane index');
if (from === to) {
return;
}
const [paneToMove] = this._private__panes.splice(from, 1);
this._private__panes.splice(to, 0, paneToMove);
this._internal_fullUpdate();
}
_internal_startScalePrice(pane, priceScale, x) {
pane._internal_startScalePrice(priceScale, x);
}
_internal_scalePriceTo(pane, priceScale, x) {
pane._internal_scalePriceTo(priceScale, x);
this._internal_updateCrosshair();
this._private__invalidate(this._private__paneInvalidationMask(pane, 2 /* InvalidationLevel.Light */));
}
_internal_endScalePrice(pane, priceScale) {
pane._internal_endScalePrice(priceScale);
this._private__invalidate(this._private__paneInvalidationMask(pane, 2 /* InvalidationLevel.Light */));
}
_internal_startScrollPrice(pane, priceScale, x) {
if (priceScale._internal_isAutoScale()) {
return;
}
pane._internal_startScrollPrice(priceScale, x);
}
_internal_scrollPriceTo(pane, priceScale, x) {
if (priceScale._internal_isAutoScale()) {
return;
}
pane._internal_scrollPriceTo(priceScale, x);
this._internal_updateCrosshair();
this._private__invalidate(this._private__paneInvalidationMask(pane, 2 /* InvalidationLevel.Light */));
}
_internal_endScrollPrice(pane, priceScale) {
if (priceScale._internal_isAutoScale()) {
return;
}
pane._internal_endScrollPrice(priceScale);
this._private__invalidate(this._private__paneInvalidationMask(pane, 2 /* InvalidationLevel.Light */));
}
_internal_resetPriceScale(pane, priceScale) {
pane._internal_resetPriceScale(priceScale);
this._private__invalidate(this._private__paneInvalidationMask(pane, 2 /* InvalidationLevel.Light */));
}
_internal_startScaleTime(position) {
this._private__timeScale._internal_startScale(position);
}
/**
* Zoom in/out the chart (depends on scale value).
*
* @param pointX - X coordinate of the point to apply the zoom (the point which should stay on its place)
* @param scale - Zoom value. Negative value means zoom out, positive - zoom in.
*/
_internal_zoomTime(pointX, scale) {
const timeScale = this._internal_timeScale();
if (timeScale._internal_isEmpty() || scale === 0) {
return;
}
const timeScaleWidth = timeScale._internal_width();
pointX = Math.max(1, Math.min(pointX, timeScaleWidth));
timeScale._internal_zoom(pointX, scale);
this._internal_recalculateAllPanes();
}
_internal_scrollChart(x) {
this._internal_startScrollTime(0);
this._internal_scrollTimeTo(x);
this._internal_endScrollTime();
}
_internal_scaleTimeTo(x) {
this._private__timeScale._internal_scaleTo(x);
this._internal_recalculateAllPanes();
}
_internal_endScaleTime() {
this._private__timeScale._internal_endScale();
this._internal_lightUpdate();
}
_internal_startScrollTime(x) {
this._private__timeScale._internal_startScroll(x);
}
_internal_scrollTimeTo(x) {
this._private__timeScale._internal_scrollTo(x);
this._internal_recalculateAllPanes();
}
_internal_endScrollTime() {
this._private__timeScale._internal_endScroll();
this._internal_lightUpdate();
}
_internal_serieses() {
return this._private__serieses;
}
_internal_visibleSerieses() {
if (this._private__visibleSerieses === null) {
this._private__visibleSerieses = this._private__serieses.filter((s) => s._internal_visible());
}
return this._private__visibleSerieses;
}
_internal_invalidateVisibleSeries() {
this._private__visibleSerieses = null;
}
_internal_setAndSaveCurrentPosition(x, y, event, pane, skipEvent) {
this._private__crosshair._internal_saveOriginCoord(x, y);
let price = NaN;
let index = this._private__timeScale._internal_coordinateToIndex(x, true);
const visibleBars = this._private__timeScale._internal_visibleStrictRange();
if (visibleBars !== null) {
index = Math.min(Math.max(visibleBars._internal_left(), index), visibleBars._internal_right());
}
index = this._private__crosshair._internal_snapToVisibleSeriesIfNeeded(index);
const priceScale = pane._internal_defaultPriceScale();
const firstValue = priceScale._internal_firstValue();
if (firstValue !== null) {
price = priceScale._internal_coordinateToPrice(y, firstValue);
}
price = this._private__magnet._internal_align(price, index, pane);
this._private__crosshair._internal_setPosition(index, price, pane);
this._internal_cursorUpdate();
if (!skipEvent) {
const hitTest = hitTestPane(pane, x, y);
this._internal_setHoveredSource(hitTest && {
_internal_source: hitTest._internal_source,
_internal_object: hitTest._internal_object,
_internal_cursorStyle: hitTest._internal_cursorStyle || null,
_internal_itemType: hitTest._internal_itemType,
});
this._private__crosshairMoved._internal_fire(this._private__crosshair._internal_appliedIndex(), { x, y }, event);
}
}
// A position provided external (not from an internal event listener)
_internal_setAndSaveSyntheticPosition(price, horizontalPosition, pane) {
const priceScale = pane._internal_defaultPriceScale();
const firstValue = priceScale._internal_firstValue();
const y = priceScale._internal_priceToCoordinate(price, ensureNotNull(firstValue));
const index = this._private__timeScale._internal_timeToIndex(horizontalPosition, true);
const x = this._private__timeScale._internal_indexToCoordinate(ensureNotNull(index));
this._internal_setAndSaveCurrentPosition(x, y, null, pane, true);
}
_internal_clearCurrentPosition(skipEvent) {
const crosshair = this._internal_crosshairSource();
crosshair._internal_clearPosition();
this._internal_cursorUpdate();
if (!skipEvent) {
this._private__crosshairMoved._internal_fire(null, null, null);
}
}
_internal_updateCrosshair() {
// apply magnet
const pane = this._private__crosshair._internal_pane();
if (pane !== null) {
const x = this._private__crosshair._internal_originCoordX();
const y = this._private__crosshair._internal_originCoordY();
this._internal_setAndSaveCurrentPosition(x, y, null, pane);
}
this._private__crosshair._internal_updateAllViews();
}
_internal_updateTimeScale(newBaseIndex, newPoints, firstChangedPointIndex) {
const oldFirstTime = this._private__timeScale._internal_indexToTime(0);
if (newPoints !== undefined && firstChangedPointIndex !== undefined) {
this._private__timeScale._internal_update(newPoints, firstChangedPointIndex);
}
const newFirstTime = this._private__timeScale._internal_indexToTime(0);
const currentBaseIndex = this._private__timeScale._internal_baseIndex();
const visibleBars = this._private__timeScale._internal_visibleStrictRange();
// if time scale cannot return current visible bars range (e.g. time scale has zero-width)
// then we do not need to update right offset to shift visible bars range to have the same right offset as we have before new bar
// (and actually we cannot)
if (visibleBars !== null && oldFirstTime !== null && newFirstTime !== null) {
const isLastSeriesBarVisible = visibleBars._internal_contains(currentBaseIndex);
const isLeftBarShiftToLeft = this._private__horzScaleBehavior.key(oldFirstTime) > this._private__horzScaleBehavior.key(newFirstTime);
const isSeriesPointsAdded = newBaseIndex !== null && newBaseIndex > currentBaseIndex;
const isSeriesPointsAddedToRight = isSeriesPointsAdded && !isLeftBarShiftToLeft;
const allowShiftWhenReplacingWhitespace = this._private__timeScale._internal_options().allowShiftVisibleRangeOnWhitespaceReplacement;
const replacedExistingWhitespace = firstChangedPointIndex === undefined;
const needShiftVisibleRangeOnNewBar = isLastSeriesBarVisible && (!replacedExistingWhitespace || allowShiftWhenReplacingWhitespace) && this._private__timeScale._internal_options().shiftVisibleRangeOnNewBar;
if (isSeriesPointsAddedToRight && !needShiftVisibleRangeOnNewBar) {
const compensationShift = newBaseIndex - currentBaseIndex;
this._private__timeScale._internal_setRightOffset(this._private__timeScale._internal_rightOffset() - compensationShift);
}
}
this._private__timeScale._internal_setBaseIndex(newBaseIndex);
}
_internal_recalculatePane(pane) {
if (pane !== null) {
pane._internal_recalculate();
}
}
_internal_paneForSource(source) {
if (isPanePrimitive(source)) {
return source;
}
const pane = this._private__panes.find((p) => p._internal_orderedSources().includes(source));
return pane === undefined ? null : pane;
}
_internal_recalculateAllPanes() {
this._private__panes.forEach((p) => p._internal_recalculate());
this._internal_updateCrosshair();
}
_internal_destroy() {
this._private__panes.forEach((p) => p._internal_destroy());
this._private__panes.length = 0;
// to avoid memleaks
this._private__options.localization.priceFormatter = undefined;
this._private__options.localization.percentageFormatter = undefined;
this._private__options.localization.timeFormatter = undefined;
}
_internal_rendererOptionsProvider() {
return this._private__rendererOptionsProvider;
}
_internal_priceAxisRendererOptions() {
return this._private__rendererOptionsProvider._internal_options();
}
_internal_priceScalesOptionsChanged() {
return this._private__priceScalesOptionsChanged;
}
_internal_addSeriesToPane(series, paneIndex) {
const pane = this._private__getOrCreatePane(paneIndex);
this._private__addSeriesToPane(series, pane);
this._private__serieses.push(series);
this._internal_invalidateVisibleSeries();
if (this._private__serieses.length === 1) {
// call fullUpdate to recalculate chart's parts geometry
this._internal_fullUpdate();
}
else {
this._internal_lightUpdate();
}
}
_internal_removeSeries(series) {
const pane = this._internal_paneForSource(series);
const seriesIndex = this._private__serieses.indexOf(series);
assert(seriesIndex !== -1, 'Series not found');
const paneImpl = ensureNotNull(pane);
this._private__serieses.splice(seriesIndex, 1);
paneImpl._internal_removeDataSource(series);
if (series._internal_destroy) {
series._internal_destroy();
}
this._internal_invalidateVisibleSeries();
this._private__timeScale._internal_recalculateIndicesWithData();
this._private__cleanupIfPaneIsEmpty(paneImpl);
}
_internal_moveSeriesToScale(series, targetScaleId) {
const pane = ensureNotNull(this._internal_paneForSource(series));
pane._internal_removeDataSource(series, true);
pane._internal_addDataSource(series, targetScaleId, true);
}
_internal_fitContent() {
const mask = InvalidateMask._internal_light();
mask._internal_setFitContent();
this._private__invalidate(mask);
}
_internal_setTargetLogicalRange(range) {
const mask = InvalidateMask._internal_light();
mask._internal_applyRange(range);
this._private__invalidate(mask);
}
_internal_resetTimeScale() {
const mask = InvalidateMask._internal_light();
mask._internal_resetTimeScale();
this._private__invalidate(mask);
}
_internal_setBarSpacing(spacing) {
const mask = InvalidateMask._internal_light();
mask._internal_setBarSpacing(spacing);
this._private__invalidate(mask);
}
_internal_setRightOffset(offset) {
const mask = InvalidateMask._internal_light();
mask._internal_setRightOffset(offset);
this._private__invalidate(mask);
}
_internal_setTimeScaleAnimation(animation) {
const mask = InvalidateMask._internal_light();
mask._internal_setTimeScaleAnimation(animation);
this._private__invalidate(mask);
}
_internal_stopTimeScaleAnimation() {
const mask = InvalidateMask._internal_light();
mask._internal_stopTimeScaleAnimation();
this._private__invalidate(mask);
}
_internal_defaultVisiblePriceScaleId() {
const preferredPriceScaleId = this._private__options.defaultVisiblePriceScaleId;
const leftVisible = this._private__options.leftPriceScale.visible;
const rightVisible = this._private__options.rightPriceScale.visible;
if (leftVisible !== rightVisible) {
return leftVisible ? "left" /* DefaultPriceScaleId.Left */ : "right" /* DefaultPriceScaleId.Right */;
}
return preferredPriceScaleId;
}
_internal_moveSeriesToPane(series, newPaneIndex) {
assert(newPaneIndex >= 0, 'Index should be greater or equal to 0');
const fromPaneIndex = this._private__seriesPaneIndex(series);
if (newPaneIndex === fromPaneIndex) {
return;
}
const previousPane = ensureNotNull(this._internal_paneForSource(series));
previousPane._internal_removeDataSource(series);
const newPane = this._private__getOrCreatePane(newPaneIndex);
this._private__addSeriesToPane(series, newPane);
let paneWasRemoved = false;
if (previousPane._internal_dataSources().length === 0) {
paneWasRemoved = this._private__cleanupIfPaneIsEmpty(previousPane);
}
if (!paneWasRemoved) {
this._internal_fullUpdate();
}
}
_internal_backgroundBottomColor() {
return this._private__backgroundBottomColor;
}
_internal_backgroundTopColor() {
return this._private__backgroundTopColor;
}
_internal_backgroundColorAtYPercentFromTop(percent) {
const bottomColor = this._private__backgroundBottomColor;
const topColor = this._private__backgroundTopColor;
if (bottomColor === topColor) {
// solid background
return bottomColor;
}
// gradient background
// percent should be from 0 to 100 (we're using only integer values to make cache more efficient)
percent = Math.max(0, Math.min(100, Math.round(percent * 100)));
if (this._private__gradientColorsCache === null ||
this._private__gradientColorsCache._internal_topColor !== topColor || this._private__gradientColorsCache._internal_bottomColor !== bottomColor) {
this._private__gradientColorsCache = {
_internal_topColor: topColor,
_internal_bottomColor: bottomColor,
_internal_colors: new Map(),
};
}
else {
const cachedValue = this._private__gradientColorsCache._internal_colors.get(percent);
if (cachedValue !== undefined) {
return cachedValue;
}
}
const result = this._private__colorParser._internal_gradientColorAtPercent(topColor, bottomColor, percent / 100);
this._private__gradientColorsCache._internal_colors.set(percent, result);
return result;
}
_internal_getPaneIndex(pane) {
return this._private__panes.indexOf(pane);
}
_internal_colorParser() {
return this._private__colorParser;
}
_internal_addPane() {
return this._private__addPane();
}
_private__addPane(index) {
const pane = new Pane(this._private__timeScale, this);
this._private__panes.push(pane);
const idx = index ?? this._private__panes.length - 1;
// we always do autoscaling on the creation
// if autoscale option is true, it is ok, just recalculate by invalidation mask
// if autoscale option is false, autoscale anyway on the first draw
// also there is a scenario when autoscale is true in constructor and false later on applyOptions
const mask = InvalidateMask._internal_full();
mask._internal_invalidatePane(idx, {
_internal_level: 0 /* InvalidationLevel.None */,
_internal_autoScale: true,
});
this._private__invalidate(mask);
return pane;
}
_private__getOrCreatePane(index) {
assert(index >= 0, 'Index should be greater or equal to 0');
index = Math.min(this._private__panes.length, index);
if (index < this._private__panes.length) {
return this._private__panes[index];
}
return this._private__addPane(index);
}
_private__seriesPaneIndex(series) {
return this._private__panes.findIndex((pane) => pane._internal_series().includes(series));
}
_private__paneInvalidationMask(pane, level) {
const inv = new InvalidateMask(level);
if (pane !== null) {
const index = this._private__panes.indexOf(pane);
inv._internal_invalidatePane(index, {
_internal_level: level,
});
}
return inv;
}
_private__invalidationMaskForSource(source, invalidateType) {
if (invalidateType === undefined) {
invalidateType = 2 /* InvalidationLevel.Light */;
}
return this._private__paneInvalidationMask(this._internal_paneForSource(source), invalidateType);
}
_private__invalidate(mask) {
if (this._private__invalidateHandler) {
this._private__invalidateHandler(mask);
}
this._private__panes.forEach((pane) => pane._internal_grid()._internal_paneView()._internal_update());
}
_private__addSeriesToPane(series, pane) {
const priceScaleId = series._internal_options().priceScaleId;
const targetScaleId = priceScaleId !== undefined ? priceScaleId : this._internal_defaultVisiblePriceScaleId();
pane._internal_addDataSource(series, targetScaleId);
if (!isDefaultPriceScale(targetScaleId)) {
// let's apply that options again to apply margins
series._internal_applyOptions(series._internal_options());
}
}
_private__getBackgroundColor(side) {
const layoutOptions = this._private__options['layout'];
if (layoutOptions.background.type === "gradient" /* ColorType.VerticalGradient */) {
return side === 0 /* BackgroundColorSide.Top */ ?
layoutOptions.background.topColor :
layoutOptions.background.bottomColor;
}
return layoutOptions.background.color;
}
_private__cleanupIfPaneIsEmpty(pane) {
if (!pane._internal_preserveEmptyPane() && (pane._internal_dataSources().length === 0 && this._private__panes.length > 1)) {
this._private__panes.splice(this._internal_getPaneIndex(pane), 1);
this._internal_fullUpdate();
return true;
}
return false;
}
}
function fillUpDownCandlesticksColors(options) {
if (options.borderColor !== undefined) {
options.borderUpColor = options.borderColor;
options.borderDownColor = options.borderColor;
}
if (options.wickColor !== undefined) {
options.wickUpColor = options.wickColor;
options.wickDownColor = options.wickColor;
}
}
/**
* Represents the type of the last price animation for series such as area or line.
*/
var LastPriceAnimationMode;
(function (LastPriceAnimationMode) {
/**
* Animation is always disabled
*/
LastPriceAnimationMode[LastPriceAnimationMode["Disabled"] = 0] = "Disabled";
/**
* Animation is always enabled.
*/
LastPriceAnimationMode[LastPriceAnimationMode["Continuous"] = 1] = "Continuous";
/**
* Animation is active after new data.
*/
LastPriceAnimationMode[LastPriceAnimationMode["OnDataUpdate"] = 2] = "OnDataUpdate";
})(LastPriceAnimationMode || (LastPriceAnimationMode = {}));
function precisionByMinMove(minMove) {
if (minMove >= 1) {
return 0;
}
let i = 0;
for (; i < 8; i++) {
const intPart = Math.round(minMove);
const fractPart = Math.abs(intPart - minMove);
if (fractPart < 1e-8) {
return i;
}
minMove = minMove * 10;
}
return i;
}
/**
* Represents the source of data to be used for the horizontal price line.
*/
var PriceLineSource;
(function (PriceLineSource) {
/**
* Use the last bar data.
*/
PriceLineSource[PriceLineSource["LastBar"] = 0] = "LastBar";
/**
* Use the last visible data of the chart viewport.
*/
PriceLineSource[PriceLineSource["LastVisible"] = 1] = "LastVisible";
})(PriceLineSource || (PriceLineSource = {}));
/**
* Represents a type of color.
*/
var ColorType;
(function (ColorType) {
/** Solid color */
ColorType["Solid"] = "solid";
/** Vertical gradient color */
ColorType["VerticalGradient"] = "gradient";
})(ColorType || (ColorType = {}));
/**
* Check if a time value is a business day object.
*
* @param time - The time to check.
* @returns `true` if `time` is a {@link BusinessDay} object, false otherwise.
*/
function isBusinessDay(time) {
return !isNumber(time) && !isString(time);
}
/**
* Check if a time value is a UTC timestamp number.
*
* @param time - The time to check.
* @returns `true` if `time` is a {@link UTCTimestamp} number, false otherwise.
*/
function isUTCTimestamp(time) {
return isNumber(time);
}
/**
* Represents the type of a tick mark on the time axis.
*/
var TickMarkType;
(function (TickMarkType) {
/**
* The start of the year (e.g. it's the first tick mark in a year).
*/
TickMarkType[TickMarkType["Year"] = 0] = "Year";
/**
* The start of the month (e.g. it's the first tick mark in a month).
*/
TickMarkType[TickMarkType["Month"] = 1] = "Month";
/**
* A day of the month.
*/
TickMarkType[TickMarkType["DayOfMonth"] = 2] = "DayOfMonth";
/**
* A time without seconds.
*/
TickMarkType[TickMarkType["Time"] = 3] = "Time";
/**
* A time with seconds.
*/
TickMarkType[TickMarkType["TimeWithSeconds"] = 4] = "TimeWithSeconds";
})(TickMarkType || (TickMarkType = {}));
const getMonth = (date) => date.getUTCMonth() + 1;
const getDay = (date) => date.getUTCDate();
const getYear = (date) => date.getUTCFullYear();
const dd = (date) => numberToStringWithLeadingZero(getDay(date), 2);
const MMMM = (date, locale) => new Date(date.getUTCFullYear(), date.getUTCMonth(), 1)
.toLocaleString(locale, { month: 'long' });
const MMM = (date, locale) => new Date(date.getUTCFullYear(), date.getUTCMonth(), 1)
.toLocaleString(locale, { month: 'short' });
const MM = (date) => numberToStringWithLeadingZero(getMonth(date), 2);
const yy = (date) => numberToStringWithLeadingZero(getYear(date) % 100, 2);
const yyyy = (date) => numberToStringWithLeadingZero(getYear(date), 4);
function formatDate(date, format, locale) {
return format
.replace(/yyyy/g, yyyy(date))
.replace(/yy/g, yy(date))
.replace(/MMMM/g, MMMM(date, locale))
.replace(/MMM/g, MMM(date, locale))
.replace(/MM/g, MM(date))
.replace(/dd/g, dd(date));
}
class DateFormatter {
constructor(dateFormat = 'yyyy-MM-dd', locale = 'default') {
this._private__dateFormat = dateFormat;
this._private__locale = locale;
}
_internal_format(date) {
return formatDate(date, this._private__dateFormat, this._private__locale);
}
}
class TimeFormatter {
constructor(format) {
this._private__formatStr = format || '%h:%m:%s';
}
_internal_format(date) {
return this._private__formatStr.replace('%h', numberToStringWithLeadingZero(date.getUTCHours(), 2)).
replace('%m', numberToStringWithLeadingZero(date.getUTCMinutes(), 2)).
replace('%s', numberToStringWithLeadingZero(date.getUTCSeconds(), 2));
}
}
const defaultParams = {
_internal_dateFormat: 'yyyy-MM-dd',
_internal_timeFormat: '%h:%m:%s',
_internal_dateTimeSeparator: ' ',
_internal_locale: 'default',
};
class DateTimeFormatter {
constructor(params = {}) {
const formatterParams = { ...defaultParams, ...params };
this._private__dateFormatter = new DateFormatter(formatterParams._internal_dateFormat, formatterParams._internal_locale);
this._private__timeFormatter = new TimeFormatter(formatterParams._internal_timeFormat);
this._private__separator = formatterParams._internal_dateTimeSeparator;
}
_internal_format(dateTime) {
return `${this._private__dateFormatter._internal_format(dateTime)}${this._private__separator}${this._private__timeFormatter._internal_format(dateTime)}`;
}
}
function defaultTickMarkFormatter(timePoint, tickMarkType, locale) {
const formatOptions = {};
switch (tickMarkType) {
case 0 /* TickMarkType.Year */:
formatOptions.year = 'numeric';
break;
case 1 /* TickMarkType.Month */:
formatOptions.month = 'short';
break;
case 2 /* TickMarkType.DayOfMonth */:
formatOptions.day = 'numeric';
break;
case 3 /* TickMarkType.Time */:
formatOptions.hour12 = false;
formatOptions.hour = '2-digit';
formatOptions.minute = '2-digit';
break;
case 4 /* TickMarkType.TimeWithSeconds */:
formatOptions.hour12 = false;
formatOptions.hour = '2-digit';
formatOptions.minute = '2-digit';
formatOptions.second = '2-digit';
break;
}
const date = timePoint._internal_businessDay === undefined
? new Date(timePoint._internal_timestamp * 1000)
: new Date(Date.UTC(timePoint._internal_businessDay.year, timePoint._internal_businessDay.month - 1, timePoint._internal_businessDay.day));
// from given date we should use only as UTC date or timestamp
// but to format as locale date we can convert UTC date to local date
const localDateFromUtc = new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), date.getUTCMilliseconds());
return localDateFromUtc.toLocaleString(locale, formatOptions);
}
function hours(count) {
return count * 60 * 60 * 1000;
}
function minutes(count) {
return count * 60 * 1000;
}
function seconds(count) {
return count * 1000;
}
const intradayWeightDivisors = [
{ _internal_divisor: seconds(1), _internal_weight: 10 /* TickMarkWeight.Second */ },
{ _internal_divisor: minutes(1), _internal_weight: 20 /* TickMarkWeight.Minute1 */ },
{ _internal_divisor: minutes(5), _internal_weight: 21 /* TickMarkWeight.Minute5 */ },
{ _internal_divisor: minutes(30), _internal_weight: 22 /* TickMarkWeight.Minute30 */ },
{ _internal_divisor: hours(1), _internal_weight: 30 /* TickMarkWeight.Hour1 */ },
{ _internal_divisor: hours(3), _internal_weight: 31 /* TickMarkWeight.Hour3 */ },
{ _internal_divisor: hours(6), _internal_weight: 32 /* TickMarkWeight.Hour6 */ },
{ _internal_divisor: hours(12), _internal_weight: 33 /* TickMarkWeight.Hour12 */ },
];
function weightByTime(currentDate, prevDate) {
if (currentDate.getUTCFullYear() !== prevDate.getUTCFullYear()) {
return 70 /* TickMarkWeight.Year */;
}
else if (currentDate.getUTCMonth() !== prevDate.getUTCMonth()) {
return 60 /* TickMarkWeight.Month */;
}
else if (currentDate.getUTCDate() !== prevDate.getUTCDate()) {
return 50 /* TickMarkWeight.Day */;
}
for (let i = intradayWeightDivisors.length - 1; i >= 0; --i) {
if (Math.floor(prevDate.getTime() / intradayWeightDivisors[i]._internal_divisor) !== Math.floor(currentDate.getTime() / intradayWeightDivisors[i]._internal_divisor)) {
return intradayWeightDivisors[i]._internal_weight;
}
}
return 0 /* TickMarkWeight.LessThanSecond */;
}
function cast(t) {
return t;
}
function fillWeightsForPoints(sortedTimePoints, startIndex = 0) {
if (sortedTimePoints.length === 0) {
return;
}
let prevTime = startIndex === 0 ? null : cast(sortedTimePoints[startIndex - 1].time)._internal_timestamp;
let prevDate = prevTime !== null ? new Date(prevTime * 1000) : null;
let totalTimeDiff = 0;
for (let index = startIndex; index < sortedTimePoints.length; ++index) {
const currentPoint = sortedTimePoints[index];
const currentDate = new Date(cast(currentPoint.time)._internal_timestamp * 1000);
if (prevDate !== null) {
currentPoint.timeWeight = weightByTime(currentDate, prevDate);
}
totalTimeDiff += cast(currentPoint.time)._internal_timestamp - (prevTime || cast(currentPoint.time)._internal_timestamp);
prevTime = cast(currentPoint.time)._internal_timestamp;
prevDate = currentDate;
}
if (startIndex === 0 && sortedTimePoints.length > 1) {
// let's guess a weight for the first point
// let's say the previous point was average time back in the history
const averageTimeDiff = Math.ceil(totalTimeDiff / (sortedTimePoints.length - 1));
const approxPrevDate = new Date((cast(sortedTimePoints[0].time)._internal_timestamp - averageTimeDiff) * 1000);
sortedTimePoints[0].timeWeight = weightByTime(new Date(cast(sortedTimePoints[0].time)._internal_timestamp * 1000), approxPrevDate);
}
}
function businessDayConverter(time) {
let businessDay = time;
if (isString(time)) {
businessDay = stringToBusinessDay(time);
}
if (!isBusinessDay(businessDay)) {
throw new Error('time must be of type BusinessDay');
}
const date = new Date(Date.UTC(businessDay.year, businessDay.month - 1, businessDay.day, 0, 0, 0, 0));
return {
_internal_timestamp: Math.round(date.getTime() / 1000),
_internal_businessDay: businessDay,
};
}
function timestampConverter(time) {
if (!isUTCTimestamp(time)) {
throw new Error('time must be of type isUTCTimestamp');
}
return {
_internal_timestamp: time,
};
}
function selectTimeConverter(data) {
if (data.length === 0) {
return null;
}
if (isBusinessDay(data[0].time) || isString(data[0].time)) {
return businessDayConverter;
}
return timestampConverter;
}
const validDateRegex = /^\d\d\d\d-\d\d-\d\d$/;
function convertTime(time) {
if (isUTCTimestamp(time)) {
return timestampConverter(time);
}
if (!isBusinessDay(time)) {
return businessDayConverter(stringToBusinessDay(time));
}
return businessDayConverter(time);
}
function stringToBusinessDay(value) {
{
// in some browsers (I look at your Chrome) the Date constructor may accept invalid date string
// but parses them in 'implementation specific' way
// for example 2019-1-1 isn't the same as 2019-01-01 (for Chrome both are 'valid' date strings)
// see https://bugs.chromium.org/p/chromium/issues/detail?id=968939
// so, we need to be sure that date has valid format to avoid strange behavior and hours of debugging
// but let's do this in development build only because of perf
if (!validDateRegex.test(value)) {
throw new Error(`Invalid date string=${value}, expected format=yyyy-mm-dd`);
}
}
const d = new Date(value);
if (isNaN(d.getTime())) {
throw new Error(`Invalid date string=${value}, expected format=yyyy-mm-dd`);
}
return {
day: d.getUTCDate(),
month: d.getUTCMonth() + 1,
year: d.getUTCFullYear(),
};
}
function convertStringToBusinessDay(value) {
if (isString(value.time)) {
value.time = stringToBusinessDay(value.time);
}
}
function convertStringsToBusinessDays(data) {
return data.forEach(convertStringToBusinessDay);
}
// eslint-disable-next-line complexity
function weightToTickMarkType(weight, timeVisible, secondsVisible) {
switch (weight) {
case 0 /* TickMarkWeight.LessThanSecond */:
case 10 /* TickMarkWeight.Second */:
return timeVisible
? (secondsVisible ? 4 /* TickMarkType.TimeWithSeconds */ : 3 /* TickMarkType.Time */)
: 2 /* TickMarkType.DayOfMonth */;
case 20 /* TickMarkWeight.Minute1 */:
case 21 /* TickMarkWeight.Minute5 */:
case 22 /* TickMarkWeight.Minute30 */:
case 30 /* TickMarkWeight.Hour1 */:
case 31 /* TickMarkWeight.Hour3 */:
case 32 /* TickMarkWeight.Hour6 */:
case 33 /* TickMarkWeight.Hour12 */:
return timeVisible ? 3 /* TickMarkType.Time */ : 2 /* TickMarkType.DayOfMonth */;
case 50 /* TickMarkWeight.Day */:
return 2 /* TickMarkType.DayOfMonth */;
case 60 /* TickMarkWeight.Month */:
return 1 /* TickMarkType.Month */;
case 70 /* TickMarkWeight.Year */:
return 0 /* TickMarkType.Year */;
}
}
class HorzScaleBehaviorTime {
options() {
return this._private__options;
}
setOptions(options) {
this._private__options = options;
this.updateFormatter(options.localization);
}
preprocessData(data) {
if (Array.isArray(data)) {
convertStringsToBusinessDays(data);
}
else {
convertStringToBusinessDay(data);
}
}
createConverterToInternalObj(data) {
return ensureNotNull(selectTimeConverter(data));
}
key(item) {
// eslint-disable-next-line no-restricted-syntax
if (typeof item === 'object' && "_internal_timestamp" in item) {
return item._internal_timestamp;
}
else {
return this.key(this.convertHorzItemToInternal(item));
}
}
cacheKey(item) {
const time = item;
return time._internal_businessDay === undefined
? new Date(time._internal_timestamp * 1000).getTime()
: new Date(Date.UTC(time._internal_businessDay.year, time._internal_businessDay.month - 1, time._internal_businessDay.day)).getTime();
}
convertHorzItemToInternal(item) {
return convertTime(item);
}
updateFormatter(options) {
if (!this._private__options) {
return;
}
const dateFormat = options.dateFormat;
if (this._private__options.timeScale.timeVisible) {
this._private__dateTimeFormatter = new DateTimeFormatter({
_internal_dateFormat: dateFormat,
_internal_timeFormat: this._private__options.timeScale.secondsVisible ? '%h:%m:%s' : '%h:%m',
_internal_dateTimeSeparator: ' ',
_internal_locale: options.locale,
});
}
else {
this._private__dateTimeFormatter = new DateFormatter(dateFormat, options.locale);
}
}
formatHorzItem(item) {
const tp = item;
return this._private__dateTimeFormatter._internal_format(new Date(tp._internal_timestamp * 1000));
}
formatTickmark(tickMark, localizationOptions) {
const tickMarkType = weightToTickMarkType(tickMark.weight, this._private__options.timeScale.timeVisible, this._private__options.timeScale.secondsVisible);
const options = this._private__options.timeScale;
if (options.tickMarkFormatter !== undefined) {
const tickMarkString = options.tickMarkFormatter(tickMark.originalTime, tickMarkType, localizationOptions.locale);
if (tickMarkString !== null) {
return tickMarkString;
}
}
return defaultTickMarkFormatter(tickMark.time, tickMarkType, localizationOptions.locale);
}
maxTickMarkWeight(tickMarks) {
let maxWeight = tickMarks.reduce(markWithGreaterWeight$2, tickMarks[0]).weight;
// special case: it looks strange if 15:00 is bold but 14:00 is not
// so if maxWeight > TickMarkWeight.Hour1 and < TickMarkWeight.Day reduce it to TickMarkWeight.Hour1
if (maxWeight > 30 /* TickMarkWeight.Hour1 */ && maxWeight < 50 /* TickMarkWeight.Day */) {
maxWeight = 30 /* TickMarkWeight.Hour1 */;
}
return maxWeight;
}
fillWeightsForPoints(sortedTimePoints, startIndex) {
fillWeightsForPoints(sortedTimePoints, startIndex);
}
static _internal_applyDefaults(options) {
return merge({ localization: { dateFormat: 'dd MMM \'yy' } }, options ?? {});
}
}
function size$1(_a) {
var width = _a.width, height = _a.height;
if (width < 0) {
throw new Error('Negative width is not allowed for Size');
}
if (height < 0) {
throw new Error('Negative height is not allowed for Size');
}
return {
width: width,
height: height,
};
}
function equalSizes(first, second) {
return (first.width === second.width) &&
(first.height === second.height);
}
var Observable = /** @class */ (function () {
function Observable(win) {
var _this = this;
this._resolutionListener = function () { return _this._onResolutionChanged(); };
this._resolutionMediaQueryList = null;
this._observers = [];
this._window = win;
this._installResolutionListener();
}
Observable.prototype.dispose = function () {
this._uninstallResolutionListener();
this._window = null;
};
Object.defineProperty(Observable.prototype, "value", {
get: function () {
return this._window.devicePixelRatio;
},
enumerable: false,
configurable: true
});
Observable.prototype.subscribe = function (next) {
var _this = this;
var observer = { next: next };
this._observers.push(observer);
return {
unsubscribe: function () {
_this._observers = _this._observers.filter(function (o) { return o !== observer; });
},
};
};
Observable.prototype._installResolutionListener = function () {
if (this._resolutionMediaQueryList !== null) {
throw new Error('Resolution listener is already installed');
}
var dppx = this._window.devicePixelRatio;
this._resolutionMediaQueryList = this._window.matchMedia("all and (resolution: ".concat(dppx, "dppx)"));
// IE and some versions of Edge do not support addEventListener/removeEventListener, and we are going to use the deprecated addListener/removeListener
this._resolutionMediaQueryList.addListener(this._resolutionListener);
};
Observable.prototype._uninstallResolutionListener = function () {
if (this._resolutionMediaQueryList !== null) {
// IE and some versions of Edge do not support addEventListener/removeEventListener, and we are going to use the deprecated addListener/removeListener
this._resolutionMediaQueryList.removeListener(this._resolutionListener);
this._resolutionMediaQueryList = null;
}
};
Observable.prototype._reinstallResolutionListener = function () {
this._uninstallResolutionListener();
this._installResolutionListener();
};
Observable.prototype._onResolutionChanged = function () {
var _this = this;
this._observers.forEach(function (observer) { return observer.next(_this._window.devicePixelRatio); });
this._reinstallResolutionListener();
};
return Observable;
}());
function createObservable(win) {
return new Observable(win);
}
var DevicePixelContentBoxBinding = /** @class */ (function () {
function DevicePixelContentBoxBinding(canvasElement, transformBitmapSize, options) {
var _a;
this._canvasElement = null;
this._bitmapSizeChangedListeners = [];
this._suggestedBitmapSize = null;
this._suggestedBitmapSizeChangedListeners = [];
// devicePixelRatio approach
this._devicePixelRatioObservable = null;
// ResizeObserver approach
this._canvasElementResizeObserver = null;
this._canvasElement = canvasElement;
this._canvasElementClientSize = size$1({
width: this._canvasElement.clientWidth,
height: this._canvasElement.clientHeight,
});
this._transformBitmapSize = transformBitmapSize !== null && transformBitmapSize !== void 0 ? transformBitmapSize : (function (size) { return size; });
this._allowResizeObserver = (_a = options === null || options === void 0 ? void 0 : options.allowResizeObserver) !== null && _a !== void 0 ? _a : true;
this._chooseAndInitObserver();
// we MAY leave the constuctor without any bitmap size observation mechanics initialized
}
DevicePixelContentBoxBinding.prototype.dispose = function () {
var _a, _b;
if (this._canvasElement === null) {
throw new Error('Object is disposed');
}
(_a = this._canvasElementResizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
this._canvasElementResizeObserver = null;
(_b = this._devicePixelRatioObservable) === null || _b === void 0 ? void 0 : _b.dispose();
this._devicePixelRatioObservable = null;
this._suggestedBitmapSizeChangedListeners.length = 0;
this._bitmapSizeChangedListeners.length = 0;
this._canvasElement = null;
};
Object.defineProperty(DevicePixelContentBoxBinding.prototype, "canvasElement", {
get: function () {
if (this._canvasElement === null) {
throw new Error('Object is disposed');
}
return this._canvasElement;
},
enumerable: false,
configurable: true
});
Object.defineProperty(DevicePixelContentBoxBinding.prototype, "canvasElementClientSize", {
get: function () {
return this._canvasElementClientSize;
},
enumerable: false,
configurable: true
});
Object.defineProperty(DevicePixelContentBoxBinding.prototype, "bitmapSize", {
get: function () {
return size$1({
width: this.canvasElement.width,
height: this.canvasElement.height,
});
},
enumerable: false,
configurable: true
});
/**
* Use this function to change canvas element client size until binding is disposed
* @param clientSize New client size for bound HTMLCanvasElement
*/
DevicePixelContentBoxBinding.prototype.resizeCanvasElement = function (clientSize) {
this._canvasElementClientSize = size$1(clientSize);
this.canvasElement.style.width = "".concat(this._canvasElementClientSize.width, "px");
this.canvasElement.style.height = "".concat(this._canvasElementClientSize.height, "px");
this._invalidateBitmapSize();
};
DevicePixelContentBoxBinding.prototype.subscribeBitmapSizeChanged = function (listener) {
this._bitmapSizeChangedListeners.push(listener);
};
DevicePixelContentBoxBinding.prototype.unsubscribeBitmapSizeChanged = function (listener) {
this._bitmapSizeChangedListeners = this._bitmapSizeChangedListeners.filter(function (l) { return l !== listener; });
};
Object.defineProperty(DevicePixelContentBoxBinding.prototype, "suggestedBitmapSize", {
get: function () {
return this._suggestedBitmapSize;
},
enumerable: false,
configurable: true
});
DevicePixelContentBoxBinding.prototype.subscribeSuggestedBitmapSizeChanged = function (listener) {
this._suggestedBitmapSizeChangedListeners.push(listener);
};
DevicePixelContentBoxBinding.prototype.unsubscribeSuggestedBitmapSizeChanged = function (listener) {
this._suggestedBitmapSizeChangedListeners = this._suggestedBitmapSizeChangedListeners.filter(function (l) { return l !== listener; });
};
DevicePixelContentBoxBinding.prototype.applySuggestedBitmapSize = function () {
if (this._suggestedBitmapSize === null) {
// nothing to apply
return;
}
var oldSuggestedSize = this._suggestedBitmapSize;
this._suggestedBitmapSize = null;
this._resizeBitmap(oldSuggestedSize);
this._emitSuggestedBitmapSizeChanged(oldSuggestedSize, this._suggestedBitmapSize);
};
DevicePixelContentBoxBinding.prototype._resizeBitmap = function (newSize) {
var oldSize = this.bitmapSize;
if (equalSizes(oldSize, newSize)) {
return;
}
this.canvasElement.width = newSize.width;
this.canvasElement.height = newSize.height;
this._emitBitmapSizeChanged(oldSize, newSize);
};
DevicePixelContentBoxBinding.prototype._emitBitmapSizeChanged = function (oldSize, newSize) {
var _this = this;
this._bitmapSizeChangedListeners.forEach(function (listener) { return listener.call(_this, oldSize, newSize); });
};
DevicePixelContentBoxBinding.prototype._suggestNewBitmapSize = function (newSize) {
var oldSuggestedSize = this._suggestedBitmapSize;
var finalNewSize = size$1(this._transformBitmapSize(newSize, this._canvasElementClientSize));
var newSuggestedSize = equalSizes(this.bitmapSize, finalNewSize) ? null : finalNewSize;
if (oldSuggestedSize === null && newSuggestedSize === null) {
return;
}
if (oldSuggestedSize !== null && newSuggestedSize !== null
&& equalSizes(oldSuggestedSize, newSuggestedSize)) {
return;
}
this._suggestedBitmapSize = newSuggestedSize;
this._emitSuggestedBitmapSizeChanged(oldSuggestedSize, newSuggestedSize);
};
DevicePixelContentBoxBinding.prototype._emitSuggestedBitmapSizeChanged = function (oldSize, newSize) {
var _this = this;
this._suggestedBitmapSizeChangedListeners.forEach(function (listener) { return listener.call(_this, oldSize, newSize); });
};
DevicePixelContentBoxBinding.prototype._chooseAndInitObserver = function () {
var _this = this;
if (!this._allowResizeObserver) {
this._initDevicePixelRatioObservable();
return;
}
isDevicePixelContentBoxSupported()
.then(function (isSupported) {
return isSupported ?
_this._initResizeObserver() :
_this._initDevicePixelRatioObservable();
});
};
// devicePixelRatio approach
DevicePixelContentBoxBinding.prototype._initDevicePixelRatioObservable = function () {
var _this = this;
if (this._canvasElement === null) {
// it looks like we are already dead
return;
}
var win = canvasElementWindow(this._canvasElement);
if (win === null) {
throw new Error('No window is associated with the canvas');
}
this._devicePixelRatioObservable = createObservable(win);
this._devicePixelRatioObservable.subscribe(function () { return _this._invalidateBitmapSize(); });
this._invalidateBitmapSize();
};
DevicePixelContentBoxBinding.prototype._invalidateBitmapSize = function () {
var _a, _b;
if (this._canvasElement === null) {
// it looks like we are already dead
return;
}
var win = canvasElementWindow(this._canvasElement);
if (win === null) {
return;
}
var ratio = (_b = (_a = this._devicePixelRatioObservable) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : win.devicePixelRatio;
var canvasRects = this._canvasElement.getClientRects();
var newSize =
// eslint-disable-next-line no-negated-condition
canvasRects[0] !== undefined ?
predictedBitmapSize(canvasRects[0], ratio) :
size$1({
width: this._canvasElementClientSize.width * ratio,
height: this._canvasElementClientSize.height * ratio,
});
this._suggestNewBitmapSize(newSize);
};
// ResizeObserver approach
DevicePixelContentBoxBinding.prototype._initResizeObserver = function () {
var _this = this;
if (this._canvasElement === null) {
// it looks like we are already dead
return;
}
this._canvasElementResizeObserver = new ResizeObserver(function (entries) {
var entry = entries.find(function (entry) { return entry.target === _this._canvasElement; });
if (!entry || !entry.devicePixelContentBoxSize || !entry.devicePixelContentBoxSize[0]) {
return;
}
var entrySize = entry.devicePixelContentBoxSize[0];
var newSize = size$1({
width: entrySize.inlineSize,
height: entrySize.blockSize,
});
_this._suggestNewBitmapSize(newSize);
});
this._canvasElementResizeObserver.observe(this._canvasElement, { box: 'device-pixel-content-box' });
};
return DevicePixelContentBoxBinding;
}());
function bindTo(canvasElement, target) {
{
return new DevicePixelContentBoxBinding(canvasElement, target.transform, target.options);
}
}
function canvasElementWindow(canvasElement) {
// According to DOM Level 2 Core specification, ownerDocument should never be null for HTMLCanvasElement
// see https://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#node-ownerDoc
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return canvasElement.ownerDocument.defaultView;
}
function isDevicePixelContentBoxSupported() {
return new Promise(function (resolve) {
var ro = new ResizeObserver(function (entries) {
resolve(entries.every(function (entry) { return 'devicePixelContentBoxSize' in entry; }));
ro.disconnect();
});
ro.observe(document.body, { box: 'device-pixel-content-box' });
})
.catch(function () { return false; });
}
function predictedBitmapSize(canvasRect, ratio) {
return size$1({
width: Math.round(canvasRect.left * ratio + canvasRect.width * ratio) -
Math.round(canvasRect.left * ratio),
height: Math.round(canvasRect.top * ratio + canvasRect.height * ratio) -
Math.round(canvasRect.top * ratio),
});
}
/**
* @experimental
*/
var CanvasRenderingTarget2D = /** @class */ (function () {
function CanvasRenderingTarget2D(context, mediaSize, bitmapSize) {
if (mediaSize.width === 0 || mediaSize.height === 0) {
throw new TypeError('Rendering target could only be created on a media with positive width and height');
}
this._mediaSize = mediaSize;
// !Number.isInteger(bitmapSize.width) || !Number.isInteger(bitmapSize.height)
if (bitmapSize.width === 0 || bitmapSize.height === 0) {
throw new TypeError('Rendering target could only be created using a bitmap with positive integer width and height');
}
this._bitmapSize = bitmapSize;
this._context = context;
}
CanvasRenderingTarget2D.prototype.useMediaCoordinateSpace = function (f) {
try {
this._context.save();
// do not use resetTransform to support old versions of Edge
this._context.setTransform(1, 0, 0, 1, 0, 0);
this._context.scale(this._horizontalPixelRatio, this._verticalPixelRatio);
return f({
context: this._context,
mediaSize: this._mediaSize,
});
}
finally {
this._context.restore();
}
};
CanvasRenderingTarget2D.prototype.useBitmapCoordinateSpace = function (f) {
try {
this._context.save();
// do not use resetTransform to support old versions of Edge
this._context.setTransform(1, 0, 0, 1, 0, 0);
return f({
context: this._context,
mediaSize: this._mediaSize,
bitmapSize: this._bitmapSize,
horizontalPixelRatio: this._horizontalPixelRatio,
verticalPixelRatio: this._verticalPixelRatio,
});
}
finally {
this._context.restore();
}
};
Object.defineProperty(CanvasRenderingTarget2D.prototype, "_horizontalPixelRatio", {
get: function () {
return this._bitmapSize.width / this._mediaSize.width;
},
enumerable: false,
configurable: true
});
Object.defineProperty(CanvasRenderingTarget2D.prototype, "_verticalPixelRatio", {
get: function () {
return this._bitmapSize.height / this._mediaSize.height;
},
enumerable: false,
configurable: true
});
return CanvasRenderingTarget2D;
}());
/**
* @experimental
*/
function tryCreateCanvasRenderingTarget2D(binding, contextOptions) {
var mediaSize = binding.canvasElementClientSize;
if (mediaSize.width === 0 || mediaSize.height === 0) {
return null;
}
var bitmapSize = binding.bitmapSize;
if (bitmapSize.width === 0 || bitmapSize.height === 0) {
return null;
}
var context = binding.canvasElement.getContext('2d', contextOptions);
if (context === null) {
return null;
}
return new CanvasRenderingTarget2D(context, mediaSize, bitmapSize);
}
/**
* When you're trying to use the library in server-side context (for instance in SSR)
* you don't have some browser-specific variables like navigator or window
* and if the library will use them on the top level of the library
* the import will fail due ReferenceError
* thus, this allows use the navigator on the top level and being imported in server-side context as well
* See issue #446
*/
// eslint-disable-next-line @typescript-eslint/tslint/config
const isRunningOnClientSide = typeof window !== 'undefined';
function isFF() {
if (!isRunningOnClientSide) {
return false;
}
return window.navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
}
function isIOS() {
if (!isRunningOnClientSide) {
return false;
}
// eslint-disable-next-line deprecation/deprecation
return /iPhone|iPad|iPod/.test(window.navigator.platform);
}
function isChrome() {
if (!isRunningOnClientSide) {
return false;
}
return window.chrome !== undefined;
}
// Determine whether the browser is running on windows.
function isWindows() {
if (!isRunningOnClientSide) {
return false;
}
// more accurate if available
if (navigator?.userAgentData?.platform) {
return navigator.userAgentData.platform === 'Windows';
}
return navigator.userAgent.toLowerCase().indexOf('win') >= 0;
}
// Determine whether the browser is Chromium based.
function isChromiumBased() {
if (!isRunningOnClientSide) {
return false;
}
if (!navigator.userAgentData) {
return false;
}
return navigator.userAgentData.brands.some((brand) => {
return brand.brand.includes('Chromium');
});
}
///
function warn(msg) {
{
// eslint-disable-next-line no-console
console.warn(msg);
}
}
function hoveredTargetSourceKind(source, itemType) {
if (source instanceof Pane) {
return 'pane-primitive';
}
if (itemType === 'marker' || itemType === 'primitive') {
return 'series-primitive';
}
return 'series';
}
function hoveredTargetObjectKind(itemType, objectId) {
switch (itemType) {
case 'custom':
return objectId !== undefined ? 'custom-object' : 'series';
case 'price-line':
return 'custom-price-line';
case 'marker':
return 'series-marker';
case 'primitive':
return 'primitive';
case 'series-point':
case 'series-line':
case 'series-range':
default:
return 'series';
}
}
function buildHoveredEventInfo(hoveredSource, paneIndex) {
const hoveredSeries = hoveredSource !== null && hoveredSource._internal_source instanceof Series
? hoveredSource._internal_source
: undefined;
const hoveredObject = hoveredSource?._internal_object?._internal_externalId;
const normalizedPaneIndex = paneIndex !== undefined && paneIndex !== -1 ? paneIndex : undefined;
if (hoveredSource === null || hoveredSource._internal_itemType === undefined) {
return {
_internal_hoveredSeries: hoveredSeries,
_internal_hoveredObject: hoveredObject,
};
}
const hoveredInfo = {
_internal_type: hoveredSource._internal_itemType,
_internal_sourceKind: hoveredTargetSourceKind(hoveredSource._internal_source, hoveredSource._internal_itemType),
_internal_objectKind: hoveredTargetObjectKind(hoveredSource._internal_itemType, hoveredObject),
_internal_series: hoveredSeries,
_internal_objectId: hoveredObject,
_internal_paneIndex: normalizedPaneIndex,
};
return {
_internal_hoveredSeries: hoveredSeries,
_internal_hoveredObject: hoveredObject,
_internal_hoveredInfo: hoveredInfo,
};
}
// on Hi-DPI CSS size * Device Pixel Ratio should be integer to avoid smoothing
// For chart widget we decrease the size because we must be inside container.
// For time axis this is not important, since it just affects space for pane widgets
function suggestChartSize(originalSize) {
const integerWidth = Math.floor(originalSize.width);
const integerHeight = Math.floor(originalSize.height);
const width = integerWidth - (integerWidth % 2);
const height = integerHeight - (integerHeight % 2);
return size$1({ width, height });
}
function suggestTimeScaleHeight(originalHeight) {
return originalHeight + (originalHeight % 2);
}
function suggestPriceScaleWidth(originalWidth) {
return originalWidth + (originalWidth % 2);
}
function preventScrollByWheelClick(el) {
if (!isChrome()) {
return;
}
el.addEventListener('mousedown', (e) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
if (e.button === 1 /* MouseEventButton.Middle */) {
// prevent incorrect scrolling event
e.preventDefault();
return false;
}
return undefined;
});
}
// TODO: get rid of a lot of boolean flags, probably we should replace it with some enum
class MouseEventHandler {
constructor(target, handler, options) {
this._private__clickCount = 0;
this._private__clickTimeoutId = null;
this._private__clickPosition = { _internal_x: Number.NEGATIVE_INFINITY, _internal_y: Number.POSITIVE_INFINITY };
this._private__tapCount = 0;
this._private__tapTimeoutId = null;
this._private__tapPosition = { _internal_x: Number.NEGATIVE_INFINITY, _internal_y: Number.POSITIVE_INFINITY };
this._private__longTapTimeoutId = null;
this._private__longTapActive = false;
this._private__mouseMoveStartPosition = null;
this._private__touchMoveStartPosition = null;
this._private__touchMoveExceededManhattanDistance = false;
this._private__cancelClick = false;
this._private__cancelTap = false;
this._private__unsubscribeOutsideMouseEvents = null;
this._private__unsubscribeOutsideTouchEvents = null;
this._private__unsubscribeMobileSafariEvents = null;
this._private__unsubscribeMousemove = null;
this._private__unsubscribeRootMouseEvents = null;
this._private__unsubscribeRootTouchEvents = null;
this._private__startPinchMiddlePoint = null;
this._private__startPinchDistance = 0;
this._private__pinchPrevented = false;
this._private__preventTouchDragProcess = false;
this._private__mousePressed = false;
this._private__lastTouchEventTimeStamp = 0;
// for touchstart/touchmove/touchend events we handle only first touch
// i.e. we don't support several active touches at the same time (except pinch event)
this._private__activeTouchId = null;
// accept all mouse leave events if it's not an iOS device
// see _mouseEnterHandler, _mouseMoveHandler, _mouseLeaveHandler
this._private__acceptMouseLeave = !isIOS();
/**
* In Firefox mouse events dont't fire if the mouse position is outside of the browser's border.
* To prevent the mouse from hanging while pressed we're subscribing on the mouseleave event of the document element.
* We're subscribing on mouseleave, but this event is actually fired on mouseup outside of the browser's border.
*/
this._private__onFirefoxOutsideMouseUp = (mouseUpEvent) => {
this._private__mouseUpHandler(mouseUpEvent);
};
/**
* Safari doesn't fire touchstart/mousedown events on double tap since iOS 13.
* There are two possible solutions:
* 1) Call preventDefault in touchEnd handler. But it also prevents click event from firing.
* 2) Add listener on dblclick event that fires with the preceding mousedown/mouseup.
* https://developer.apple.com/forums/thread/125073
*/
this._private__onMobileSafariDoubleClick = (dblClickEvent) => {
if (this._private__firesTouchEvents(dblClickEvent)) {
const compatEvent = this._private__makeCompatEvent(dblClickEvent);
++this._private__tapCount;
if (this._private__tapTimeoutId && this._private__tapCount > 1) {
const { _internal_manhattanDistance: manhattanDistance } = this._private__touchMouseMoveWithDownInfo(getPosition(dblClickEvent), this._private__tapPosition);
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
if (manhattanDistance < 30 /* Constants.DoubleTapManhattanDistance */ && !this._private__cancelTap) {
this._private__processTouchEvent(compatEvent, this._private__handler._internal_doubleTapEvent);
}
this._private__resetTapTimeout();
}
}
else {
const compatEvent = this._private__makeCompatEvent(dblClickEvent);
++this._private__clickCount;
if (this._private__clickTimeoutId && this._private__clickCount > 1) {
const { _internal_manhattanDistance: manhattanDistance } = this._private__touchMouseMoveWithDownInfo(getPosition(dblClickEvent), this._private__clickPosition);
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
if (manhattanDistance < 5 /* Constants.DoubleClickManhattanDistance */ && !this._private__cancelClick) {
this._private__processMouseEvent(compatEvent, this._private__handler._internal_mouseDoubleClickEvent);
}
this._private__resetClickTimeout();
}
}
};
this._private__target = target;
this._private__handler = handler;
this._private__options = options;
this._private__init();
}
_internal_destroy() {
if (this._private__unsubscribeOutsideMouseEvents !== null) {
this._private__unsubscribeOutsideMouseEvents();
this._private__unsubscribeOutsideMouseEvents = null;
}
if (this._private__unsubscribeOutsideTouchEvents !== null) {
this._private__unsubscribeOutsideTouchEvents();
this._private__unsubscribeOutsideTouchEvents = null;
}
if (this._private__unsubscribeMousemove !== null) {
this._private__unsubscribeMousemove();
this._private__unsubscribeMousemove = null;
}
if (this._private__unsubscribeRootMouseEvents !== null) {
this._private__unsubscribeRootMouseEvents();
this._private__unsubscribeRootMouseEvents = null;
}
if (this._private__unsubscribeRootTouchEvents !== null) {
this._private__unsubscribeRootTouchEvents();
this._private__unsubscribeRootTouchEvents = null;
}
if (this._private__unsubscribeMobileSafariEvents !== null) {
this._private__unsubscribeMobileSafariEvents();
this._private__unsubscribeMobileSafariEvents = null;
}
this._private__clearLongTapTimeout();
this._private__resetClickTimeout();
}
_private__mouseEnterHandler(enterEvent) {
if (this._private__unsubscribeMousemove) {
this._private__unsubscribeMousemove();
}
const boundMouseMoveHandler = this._private__mouseMoveHandler.bind(this);
this._private__unsubscribeMousemove = () => {
this._private__target.removeEventListener('mousemove', boundMouseMoveHandler);
};
this._private__target.addEventListener('mousemove', boundMouseMoveHandler);
if (this._private__firesTouchEvents(enterEvent)) {
return;
}
const compatEvent = this._private__makeCompatEvent(enterEvent);
this._private__processMouseEvent(compatEvent, this._private__handler._internal_mouseEnterEvent);
this._private__acceptMouseLeave = true;
}
_private__resetClickTimeout() {
if (this._private__clickTimeoutId !== null) {
clearTimeout(this._private__clickTimeoutId);
}
this._private__clickCount = 0;
this._private__clickTimeoutId = null;
this._private__clickPosition = { _internal_x: Number.NEGATIVE_INFINITY, _internal_y: Number.POSITIVE_INFINITY };
}
_private__resetTapTimeout() {
if (this._private__tapTimeoutId !== null) {
clearTimeout(this._private__tapTimeoutId);
}
this._private__tapCount = 0;
this._private__tapTimeoutId = null;
this._private__tapPosition = { _internal_x: Number.NEGATIVE_INFINITY, _internal_y: Number.POSITIVE_INFINITY };
}
_private__mouseMoveHandler(moveEvent) {
if (this._private__mousePressed || this._private__touchMoveStartPosition !== null) {
return;
}
if (this._private__firesTouchEvents(moveEvent)) {
return;
}
const compatEvent = this._private__makeCompatEvent(moveEvent);
this._private__processMouseEvent(compatEvent, this._private__handler._internal_mouseMoveEvent);
this._private__acceptMouseLeave = true;
}
_private__touchMoveHandler(moveEvent) {
const touch = touchWithId(moveEvent.changedTouches, ensureNotNull(this._private__activeTouchId));
if (touch === null) {
return;
}
this._private__lastTouchEventTimeStamp = eventTimeStamp(moveEvent);
if (this._private__startPinchMiddlePoint !== null) {
return;
}
if (this._private__preventTouchDragProcess) {
return;
}
// prevent pinch if move event comes faster than the second touch
this._private__pinchPrevented = true;
const moveInfo = this._private__touchMouseMoveWithDownInfo(getPosition(touch), ensureNotNull(this._private__touchMoveStartPosition));
const { _internal_xOffset: xOffset, _internal_yOffset: yOffset, _internal_manhattanDistance: manhattanDistance } = moveInfo;
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
if (!this._private__touchMoveExceededManhattanDistance && manhattanDistance < 5 /* Constants.CancelTapManhattanDistance */) {
return;
}
if (!this._private__touchMoveExceededManhattanDistance) {
// first time when current position exceeded manhattan distance
// vertical drag is more important than horizontal drag
// because we scroll the page vertically often than horizontally
const correctedXOffset = xOffset * 0.5;
// a drag can be only if touch page scroll isn't allowed
const isVertDrag = yOffset >= correctedXOffset && !this._private__options._internal_treatVertTouchDragAsPageScroll();
const isHorzDrag = correctedXOffset > yOffset && !this._private__options._internal_treatHorzTouchDragAsPageScroll();
// if drag event happened then we should revert preventDefault state to original one
// and try to process the drag event
// else we shouldn't prevent default of the event and ignore processing the drag event
if (!isVertDrag && !isHorzDrag) {
this._private__preventTouchDragProcess = true;
}
this._private__touchMoveExceededManhattanDistance = true;
// if manhattan distance is more that 5 - we should cancel tap event
this._private__cancelTap = true;
this._private__clearLongTapTimeout();
this._private__resetTapTimeout();
}
if (!this._private__preventTouchDragProcess) {
const compatEvent = this._private__makeCompatEvent(moveEvent, touch);
this._private__processTouchEvent(compatEvent, this._private__handler._internal_touchMoveEvent);
// we should prevent default in case of touch only
// to prevent scroll of the page
preventDefault(moveEvent);
}
}
_private__mouseMoveWithDownHandler(moveEvent) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
if (moveEvent.button !== 0 /* MouseEventButton.Left */) {
return;
}
const moveInfo = this._private__touchMouseMoveWithDownInfo(getPosition(moveEvent), ensureNotNull(this._private__mouseMoveStartPosition));
const { _internal_manhattanDistance: manhattanDistance } = moveInfo;
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
if (manhattanDistance >= 5 /* Constants.CancelClickManhattanDistance */) {
// if manhattan distance is more that 5 - we should cancel click event
this._private__cancelClick = true;
this._private__resetClickTimeout();
}
if (this._private__cancelClick) {
// if this._cancelClick is true, that means that minimum manhattan distance is already exceeded
const compatEvent = this._private__makeCompatEvent(moveEvent);
this._private__processMouseEvent(compatEvent, this._private__handler._internal_pressedMouseMoveEvent);
}
}
_private__touchMouseMoveWithDownInfo(currentPosition, startPosition) {
const xOffset = Math.abs(startPosition._internal_x - currentPosition._internal_x);
const yOffset = Math.abs(startPosition._internal_y - currentPosition._internal_y);
const manhattanDistance = xOffset + yOffset;
return {
_internal_xOffset: xOffset,
_internal_yOffset: yOffset,
_internal_manhattanDistance: manhattanDistance,
};
}
// eslint-disable-next-line complexity
_private__touchEndHandler(touchEndEvent) {
let touch = touchWithId(touchEndEvent.changedTouches, ensureNotNull(this._private__activeTouchId));
if (touch === null && touchEndEvent.touches.length === 0) {
// something went wrong, somehow we missed the required touchend event
// probably the browser has not sent this event
touch = touchEndEvent.changedTouches[0];
}
if (touch === null) {
return;
}
this._private__activeTouchId = null;
this._private__lastTouchEventTimeStamp = eventTimeStamp(touchEndEvent);
this._private__clearLongTapTimeout();
this._private__touchMoveStartPosition = null;
if (this._private__unsubscribeRootTouchEvents) {
this._private__unsubscribeRootTouchEvents();
this._private__unsubscribeRootTouchEvents = null;
}
const compatEvent = this._private__makeCompatEvent(touchEndEvent, touch);
this._private__processTouchEvent(compatEvent, this._private__handler._internal_touchEndEvent);
++this._private__tapCount;
if (this._private__tapTimeoutId && this._private__tapCount > 1) {
// check that both clicks are near enough
const { _internal_manhattanDistance: manhattanDistance } = this._private__touchMouseMoveWithDownInfo(getPosition(touch), this._private__tapPosition);
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
if (manhattanDistance < 30 /* Constants.DoubleTapManhattanDistance */ && !this._private__cancelTap) {
this._private__processTouchEvent(compatEvent, this._private__handler._internal_doubleTapEvent);
}
this._private__resetTapTimeout();
}
else {
if (!this._private__cancelTap) {
this._private__processTouchEvent(compatEvent, this._private__handler._internal_tapEvent);
// do not fire mouse events if tap handler was executed
// prevent click event on new dom element (who appeared after tap)
if (this._private__handler._internal_tapEvent) {
preventDefault(touchEndEvent);
}
}
}
// prevent, for example, safari's dblclick-to-zoom or fast-click after long-tap
// we handle mouseDoubleClickEvent here ourselves
if (this._private__tapCount === 0) {
preventDefault(touchEndEvent);
}
if (touchEndEvent.touches.length === 0) {
if (this._private__longTapActive) {
this._private__longTapActive = false;
// prevent native click event
preventDefault(touchEndEvent);
}
}
}
_private__mouseUpHandler(mouseUpEvent) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
if (mouseUpEvent.button !== 0 /* MouseEventButton.Left */) {
return;
}
const compatEvent = this._private__makeCompatEvent(mouseUpEvent);
this._private__mouseMoveStartPosition = null;
this._private__mousePressed = false;
if (this._private__unsubscribeRootMouseEvents) {
this._private__unsubscribeRootMouseEvents();
this._private__unsubscribeRootMouseEvents = null;
}
if (isFF()) {
const rootElement = this._private__target.ownerDocument.documentElement;
rootElement.removeEventListener('mouseleave', this._private__onFirefoxOutsideMouseUp);
}
if (this._private__firesTouchEvents(mouseUpEvent)) {
return;
}
this._private__processMouseEvent(compatEvent, this._private__handler._internal_mouseUpEvent);
++this._private__clickCount;
if (this._private__clickTimeoutId && this._private__clickCount > 1) {
// check that both clicks are near enough
const { _internal_manhattanDistance: manhattanDistance } = this._private__touchMouseMoveWithDownInfo(getPosition(mouseUpEvent), this._private__clickPosition);
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
if (manhattanDistance < 5 /* Constants.DoubleClickManhattanDistance */ && !this._private__cancelClick) {
this._private__processMouseEvent(compatEvent, this._private__handler._internal_mouseDoubleClickEvent);
}
this._private__resetClickTimeout();
}
else {
if (!this._private__cancelClick) {
this._private__processMouseEvent(compatEvent, this._private__handler._internal_mouseClickEvent);
}
}
}
_private__clearLongTapTimeout() {
if (this._private__longTapTimeoutId === null) {
return;
}
clearTimeout(this._private__longTapTimeoutId);
this._private__longTapTimeoutId = null;
}
_private__touchStartHandler(downEvent) {
if (this._private__activeTouchId !== null) {
return;
}
const touch = downEvent.changedTouches[0];
this._private__activeTouchId = touch.identifier;
this._private__lastTouchEventTimeStamp = eventTimeStamp(downEvent);
const rootElement = this._private__target.ownerDocument.documentElement;
this._private__cancelTap = false;
this._private__touchMoveExceededManhattanDistance = false;
this._private__preventTouchDragProcess = false;
this._private__touchMoveStartPosition = getPosition(touch);
if (this._private__unsubscribeRootTouchEvents) {
this._private__unsubscribeRootTouchEvents();
this._private__unsubscribeRootTouchEvents = null;
}
{
const boundTouchMoveWithDownHandler = this._private__touchMoveHandler.bind(this);
const boundTouchEndHandler = this._private__touchEndHandler.bind(this);
this._private__unsubscribeRootTouchEvents = () => {
rootElement.removeEventListener('touchmove', boundTouchMoveWithDownHandler);
rootElement.removeEventListener('touchend', boundTouchEndHandler);
};
rootElement.addEventListener('touchmove', boundTouchMoveWithDownHandler, { passive: false });
rootElement.addEventListener('touchend', boundTouchEndHandler, { passive: false });
this._private__clearLongTapTimeout();
this._private__longTapTimeoutId = setTimeout(this._private__longTapHandler.bind(this, downEvent), 240 /* Delay.LongTap */);
}
const compatEvent = this._private__makeCompatEvent(downEvent, touch);
this._private__processTouchEvent(compatEvent, this._private__handler._internal_touchStartEvent);
if (!this._private__tapTimeoutId) {
this._private__tapCount = 0;
this._private__tapTimeoutId = setTimeout(this._private__resetTapTimeout.bind(this), 500 /* Delay.ResetClick */);
this._private__tapPosition = getPosition(touch);
}
}
_private__mouseDownHandler(downEvent) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
if (downEvent.button !== 0 /* MouseEventButton.Left */) {
return;
}
const rootElement = this._private__target.ownerDocument.documentElement;
if (isFF()) {
rootElement.addEventListener('mouseleave', this._private__onFirefoxOutsideMouseUp);
}
this._private__cancelClick = false;
this._private__mouseMoveStartPosition = getPosition(downEvent);
if (this._private__unsubscribeRootMouseEvents) {
this._private__unsubscribeRootMouseEvents();
this._private__unsubscribeRootMouseEvents = null;
}
{
const boundMouseMoveWithDownHandler = this._private__mouseMoveWithDownHandler.bind(this);
const boundMouseUpHandler = this._private__mouseUpHandler.bind(this);
this._private__unsubscribeRootMouseEvents = () => {
rootElement.removeEventListener('mousemove', boundMouseMoveWithDownHandler);
rootElement.removeEventListener('mouseup', boundMouseUpHandler);
};
rootElement.addEventListener('mousemove', boundMouseMoveWithDownHandler);
rootElement.addEventListener('mouseup', boundMouseUpHandler);
}
this._private__mousePressed = true;
if (this._private__firesTouchEvents(downEvent)) {
return;
}
const compatEvent = this._private__makeCompatEvent(downEvent);
this._private__processMouseEvent(compatEvent, this._private__handler._internal_mouseDownEvent);
if (!this._private__clickTimeoutId) {
this._private__clickCount = 0;
this._private__clickTimeoutId = setTimeout(this._private__resetClickTimeout.bind(this), 500 /* Delay.ResetClick */);
this._private__clickPosition = getPosition(downEvent);
}
}
_private__init() {
this._private__target.addEventListener('mouseenter', this._private__mouseEnterHandler.bind(this));
// Do not show context menu when something went wrong
this._private__target.addEventListener('touchcancel', this._private__clearLongTapTimeout.bind(this));
{
const doc = this._private__target.ownerDocument;
const outsideHandler = (event) => {
if (!this._private__handler._internal_mouseDownOutsideEvent) {
return;
}
if (event.composed && this._private__target.contains(event.composedPath()[0])) {
return;
}
if (event.target && this._private__target.contains(event.target)) {
return;
}
this._private__handler._internal_mouseDownOutsideEvent();
};
this._private__unsubscribeOutsideTouchEvents = () => {
doc.removeEventListener('touchstart', outsideHandler);
};
this._private__unsubscribeOutsideMouseEvents = () => {
doc.removeEventListener('mousedown', outsideHandler);
};
doc.addEventListener('mousedown', outsideHandler);
doc.addEventListener('touchstart', outsideHandler, { passive: true });
}
if (isIOS()) {
this._private__unsubscribeMobileSafariEvents = () => {
this._private__target.removeEventListener('dblclick', this._private__onMobileSafariDoubleClick);
};
this._private__target.addEventListener('dblclick', this._private__onMobileSafariDoubleClick);
}
this._private__target.addEventListener('mouseleave', this._private__mouseLeaveHandler.bind(this));
this._private__target.addEventListener('touchstart', this._private__touchStartHandler.bind(this), { passive: true });
preventScrollByWheelClick(this._private__target);
this._private__target.addEventListener('mousedown', this._private__mouseDownHandler.bind(this));
this._private__initPinch();
// Hey mobile Safari, what's up?
// If mobile Safari doesn't have any touchmove handler with passive=false
// it treats a touchstart and the following touchmove events as cancelable=false,
// so we can't prevent them (as soon we subscribe on touchmove inside touchstart's handler).
// And we'll get scroll of the page along with chart's one instead of only chart's scroll.
this._private__target.addEventListener('touchmove', () => { }, { passive: false });
}
_private__initPinch() {
if (this._private__handler._internal_pinchStartEvent === undefined &&
this._private__handler._internal_pinchEvent === undefined &&
this._private__handler._internal_pinchEndEvent === undefined) {
return;
}
this._private__target.addEventListener('touchstart', (event) => this._private__checkPinchState(event.touches), { passive: true });
this._private__target.addEventListener('touchmove', (event) => {
if (event.touches.length !== 2 || this._private__startPinchMiddlePoint === null) {
return;
}
if (this._private__handler._internal_pinchEvent !== undefined) {
const currentDistance = getDistance(event.touches[0], event.touches[1]);
const scale = currentDistance / this._private__startPinchDistance;
this._private__handler._internal_pinchEvent(this._private__startPinchMiddlePoint, scale);
preventDefault(event);
}
}, { passive: false });
this._private__target.addEventListener('touchend', (event) => {
this._private__checkPinchState(event.touches);
});
}
_private__checkPinchState(touches) {
if (touches.length === 1) {
this._private__pinchPrevented = false;
}
if (touches.length !== 2 || this._private__pinchPrevented || this._private__longTapActive) {
this._private__stopPinch();
}
else {
this._private__startPinch(touches);
}
}
_private__startPinch(touches) {
const box = getBoundingClientRect(this._private__target);
this._private__startPinchMiddlePoint = {
_internal_x: ((touches[0].clientX - box.left) + (touches[1].clientX - box.left)) / 2,
_internal_y: ((touches[0].clientY - box.top) + (touches[1].clientY - box.top)) / 2,
};
this._private__startPinchDistance = getDistance(touches[0], touches[1]);
if (this._private__handler._internal_pinchStartEvent !== undefined) {
this._private__handler._internal_pinchStartEvent();
}
this._private__clearLongTapTimeout();
}
_private__stopPinch() {
if (this._private__startPinchMiddlePoint === null) {
return;
}
this._private__startPinchMiddlePoint = null;
if (this._private__handler._internal_pinchEndEvent !== undefined) {
this._private__handler._internal_pinchEndEvent();
}
}
_private__mouseLeaveHandler(event) {
if (this._private__unsubscribeMousemove) {
this._private__unsubscribeMousemove();
}
if (this._private__firesTouchEvents(event)) {
return;
}
if (!this._private__acceptMouseLeave) {
// mobile Safari sometimes emits mouse leave event for no reason, there is no way to handle it in other way
// just ignore this event if there was no mouse move or mouse enter events
return;
}
const compatEvent = this._private__makeCompatEvent(event);
this._private__processMouseEvent(compatEvent, this._private__handler._internal_mouseLeaveEvent);
// accept all mouse leave events if it's not an iOS device
this._private__acceptMouseLeave = !isIOS();
}
_private__longTapHandler(event) {
const touch = touchWithId(event.touches, ensureNotNull(this._private__activeTouchId));
if (touch === null) {
return;
}
const compatEvent = this._private__makeCompatEvent(event, touch);
this._private__processTouchEvent(compatEvent, this._private__handler._internal_longTapEvent);
this._private__cancelTap = true;
// long tap is active until touchend event with 0 touches occurred
this._private__longTapActive = true;
}
_private__firesTouchEvents(e) {
if (e.sourceCapabilities && e.sourceCapabilities.firesTouchEvents !== undefined) {
return e.sourceCapabilities.firesTouchEvents;
}
return eventTimeStamp(e) < this._private__lastTouchEventTimeStamp + 500 /* Delay.PreventFiresTouchEvents */;
}
_private__processTouchEvent(event, callback) {
if (callback) {
callback.call(this._private__handler, event);
}
}
_private__processMouseEvent(event, callback) {
if (!callback) {
return;
}
callback.call(this._private__handler, event);
}
_private__makeCompatEvent(event, touch) {
// TouchEvent has no clientX/Y coordinates:
// We have to use the last Touch instead
const eventLike = touch || event;
const box = this._private__target.getBoundingClientRect() || { left: 0, top: 0 };
return {
clientX: eventLike.clientX,
clientY: eventLike.clientY,
pageX: eventLike.pageX,
pageY: eventLike.pageY,
screenX: eventLike.screenX,
screenY: eventLike.screenY,
localX: (eventLike.clientX - box.left),
localY: (eventLike.clientY - box.top),
ctrlKey: event.ctrlKey,
altKey: event.altKey,
shiftKey: event.shiftKey,
metaKey: event.metaKey,
_internal_isTouch: !event.type.startsWith('mouse') && event.type !== 'contextmenu' && event.type !== 'click',
_internal_srcType: event.type,
_internal_target: eventLike.target,
_internal_view: event.view,
_internal_preventDefault: () => {
if (event.type !== 'touchstart') {
// touchstart is passive and cannot be prevented
preventDefault(event);
}
},
};
}
}
function getBoundingClientRect(element) {
return element.getBoundingClientRect() || { left: 0, top: 0 };
}
function getDistance(p1, p2) {
const xDiff = p1.clientX - p2.clientX;
const yDiff = p1.clientY - p2.clientY;
return Math.sqrt(xDiff * xDiff + yDiff * yDiff);
}
function preventDefault(event) {
if (event.cancelable) {
event.preventDefault();
}
}
function getPosition(eventLike) {
return {
_internal_x: eventLike.pageX,
_internal_y: eventLike.pageY,
};
}
function eventTimeStamp(e) {
// for some reason e.timestamp is always 0 on iPad with magic mouse, so we use performance.now() as a fallback
return e.timeStamp || performance.now();
}
function touchWithId(touches, id) {
for (let i = 0; i < touches.length; ++i) {
if (touches[i].identifier === id) {
return touches[i];
}
}
return null;
}
class PaneSeparator {
constructor(chartWidget, topPaneIndex, bottomPaneIndex) {
this._private__handle = null;
this._private__mouseEventHandler = null;
this._private__resizeEnabled = true;
this._private__resizeInfo = null;
this._private__chartWidget = chartWidget;
this._private__topPane = chartWidget._internal_paneWidgets()[topPaneIndex];
this._private__bottomPane = chartWidget._internal_paneWidgets()[bottomPaneIndex];
this._private__rowElement = document.createElement('tr');
this._private__rowElement.style.height = 1 /* SeparatorConstants.SeparatorHeight */ + 'px';
this._private__cell = document.createElement('td');
this._private__cell.style.position = 'relative';
this._private__cell.style.padding = '0';
this._private__cell.style.margin = '0';
this._private__cell.setAttribute('colspan', '3');
this._private__updateBorderColor();
this._private__rowElement.appendChild(this._private__cell);
this._private__resizeEnabled = this._private__chartWidget._internal_options()['layout'].panes.enableResize;
if (!this._private__resizeEnabled) {
this._private__handle = null;
this._private__mouseEventHandler = null;
}
else {
this._private__addResizableHandle();
}
}
_internal_destroy() {
if (this._private__mouseEventHandler !== null) {
this._private__mouseEventHandler._internal_destroy();
}
}
_internal_getElement() {
return this._private__rowElement;
}
_internal_getSize() {
return size$1({
width: this._private__topPane._internal_getSize().width,
height: 1 /* SeparatorConstants.SeparatorHeight */,
});
}
_internal_getBitmapSize() {
return size$1({
width: this._private__topPane._internal_getBitmapSize().width,
height: 1 /* SeparatorConstants.SeparatorHeight */ * window.devicePixelRatio,
});
}
_internal_drawBitmap(ctx, x, y) {
const bitmapSize = this._internal_getBitmapSize();
ctx.fillStyle = this._private__chartWidget._internal_options()['layout'].panes.separatorColor;
ctx.fillRect(x, y, bitmapSize.width, bitmapSize.height);
}
_internal_update() {
this._private__updateBorderColor();
if (this._private__chartWidget._internal_options()['layout'].panes.enableResize !== this._private__resizeEnabled) {
this._private__resizeEnabled = this._private__chartWidget._internal_options()['layout'].panes.enableResize;
if (this._private__resizeEnabled) {
this._private__addResizableHandle();
}
else {
if (this._private__handle !== null) {
this._private__cell.removeChild(this._private__handle._internal_backgroundElement);
this._private__cell.removeChild(this._private__handle._internal_element);
this._private__handle = null;
}
if (this._private__mouseEventHandler !== null) {
this._private__mouseEventHandler._internal_destroy();
this._private__mouseEventHandler = null;
}
}
}
}
_private__addResizableHandle() {
const backgroundElement = document.createElement('div');
const bgStyle = backgroundElement.style;
bgStyle.position = 'fixed';
bgStyle.display = 'none';
bgStyle.zIndex = '49';
bgStyle.top = '0';
bgStyle.left = '0';
bgStyle.width = '100%';
bgStyle.height = '100%';
bgStyle.cursor = 'row-resize';
this._private__cell.appendChild(backgroundElement);
const element = document.createElement('div');
const style = element.style;
style.position = 'absolute';
style.zIndex = '50';
style.top = '-4px';
style.height = '9px';
style.width = '100%';
style.backgroundColor = '';
style.cursor = 'row-resize';
this._private__cell.appendChild(element);
const handlers = {
_internal_mouseEnterEvent: this._private__mouseOverEvent.bind(this),
_internal_mouseLeaveEvent: this._private__mouseLeaveEvent.bind(this),
_internal_mouseDownEvent: this._private__mouseDownEvent.bind(this),
_internal_touchStartEvent: this._private__mouseDownEvent.bind(this),
_internal_pressedMouseMoveEvent: this._private__pressedMouseMoveEvent.bind(this),
_internal_touchMoveEvent: this._private__pressedMouseMoveEvent.bind(this),
_internal_mouseUpEvent: this._private__mouseUpEvent.bind(this),
_internal_touchEndEvent: this._private__mouseUpEvent.bind(this),
};
this._private__mouseEventHandler = new MouseEventHandler(element, handlers, {
_internal_treatVertTouchDragAsPageScroll: () => false,
_internal_treatHorzTouchDragAsPageScroll: () => true,
});
this._private__handle = { _internal_element: element, _internal_backgroundElement: backgroundElement };
}
_private__updateBorderColor() {
this._private__cell.style.background = this._private__chartWidget._internal_options()['layout'].panes.separatorColor;
}
_private__mouseOverEvent(event) {
if (this._private__handle !== null) {
this._private__handle._internal_element.style.backgroundColor = this._private__chartWidget._internal_options()['layout'].panes.separatorHoverColor;
}
}
_private__mouseLeaveEvent(event) {
if (this._private__handle !== null && this._private__resizeInfo === null) {
this._private__handle._internal_element.style.backgroundColor = '';
}
}
_private__mouseDownEvent(event) {
if (this._private__handle === null) {
return;
}
const totalStretch = this._private__topPane._internal_state()._internal_stretchFactor() + this._private__bottomPane._internal_state()._internal_stretchFactor();
const totalHeight = this._private__topPane._internal_getSize().height + this._private__bottomPane._internal_getSize().height;
const pixelStretchFactor = totalStretch / totalHeight;
const minPaneStretch = 30 /* SeparatorConstants.MinPaneHeight */ * pixelStretchFactor;
if (totalStretch <= minPaneStretch * 2) {
// cannot resize panes that already have less than minimal height
// that's possible if there are many panes on the chart
return;
}
this._private__resizeInfo = {
_internal_startY: event.pageY,
_internal_prevStretchTopPane: this._private__topPane._internal_state()._internal_stretchFactor(),
_internal_maxPaneStretch: totalStretch - minPaneStretch,
_internal_totalStretch: totalStretch,
_internal_pixelStretchFactor: pixelStretchFactor,
_internal_minPaneStretch: minPaneStretch,
};
this._private__handle._internal_backgroundElement.style.display = 'block';
}
_private__pressedMouseMoveEvent(event) {
const resizeInfo = this._private__resizeInfo;
if (resizeInfo === null) {
return;
}
const deltaY = event.pageY - resizeInfo._internal_startY;
const deltaStretchFactor = deltaY * resizeInfo._internal_pixelStretchFactor;
const upperPaneNewStretch = clamp(resizeInfo._internal_prevStretchTopPane + deltaStretchFactor, resizeInfo._internal_minPaneStretch, resizeInfo._internal_maxPaneStretch);
this._private__topPane._internal_state()._internal_setStretchFactor(upperPaneNewStretch);
this._private__bottomPane._internal_state()._internal_setStretchFactor(resizeInfo._internal_totalStretch - upperPaneNewStretch);
this._private__chartWidget._internal_model()._internal_fullUpdate();
}
_private__mouseUpEvent(event) {
if (this._private__resizeInfo === null || this._private__handle === null) {
return;
}
this._private__resizeInfo = null;
this._private__handle._internal_backgroundElement.style.display = 'none';
}
}
function distanceBetweenPoints(pos1, pos2) {
return pos1._internal_position - pos2._internal_position;
}
function speedPxPerMSec(pos1, pos2, maxSpeed) {
const speed = (pos1._internal_position - pos2._internal_position) / (pos1._internal_time - pos2._internal_time);
return Math.sign(speed) * Math.min(Math.abs(speed), maxSpeed);
}
function durationMSec(speed, dumpingCoeff) {
const lnDumpingCoeff = Math.log(dumpingCoeff);
return Math.log((1 /* Constants.EpsilonDistance */ * lnDumpingCoeff) / -speed) / (lnDumpingCoeff);
}
class KineticAnimation {
constructor(minSpeed, maxSpeed, dumpingCoeff, minMove) {
this._private__position1 = null;
this._private__position2 = null;
this._private__position3 = null;
this._private__position4 = null;
this._private__animationStartPosition = null;
this._private__durationMsecs = 0;
this._private__speedPxPerMsec = 0;
this._private__minSpeed = minSpeed;
this._private__maxSpeed = maxSpeed;
this._private__dumpingCoeff = dumpingCoeff;
this._private__minMove = minMove;
}
_internal_addPosition(position, time) {
if (this._private__position1 !== null) {
if (this._private__position1._internal_time === time) {
this._private__position1._internal_position = position;
return;
}
if (Math.abs(this._private__position1._internal_position - position) < this._private__minMove) {
return;
}
}
this._private__position4 = this._private__position3;
this._private__position3 = this._private__position2;
this._private__position2 = this._private__position1;
this._private__position1 = { _internal_time: time, _internal_position: position };
}
_internal_start(position, time) {
if (this._private__position1 === null || this._private__position2 === null) {
return;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
if (time - this._private__position1._internal_time > 50 /* Constants.MaxStartDelay */) {
return;
}
// To calculate all the rest parameters we should calculate the speed af first
let totalDistance = 0;
const speed1 = speedPxPerMSec(this._private__position1, this._private__position2, this._private__maxSpeed);
const distance1 = distanceBetweenPoints(this._private__position1, this._private__position2);
// We're calculating weighted average speed
// Than more distance for a segment, than more its weight
const speedItems = [speed1];
const distanceItems = [distance1];
totalDistance += distance1;
if (this._private__position3 !== null) {
const speed2 = speedPxPerMSec(this._private__position2, this._private__position3, this._private__maxSpeed);
// stop at this moment if direction of the segment is opposite
if (Math.sign(speed2) === Math.sign(speed1)) {
const distance2 = distanceBetweenPoints(this._private__position2, this._private__position3);
speedItems.push(speed2);
distanceItems.push(distance2);
totalDistance += distance2;
if (this._private__position4 !== null) {
const speed3 = speedPxPerMSec(this._private__position3, this._private__position4, this._private__maxSpeed);
if (Math.sign(speed3) === Math.sign(speed1)) {
const distance3 = distanceBetweenPoints(this._private__position3, this._private__position4);
speedItems.push(speed3);
distanceItems.push(distance3);
totalDistance += distance3;
}
}
}
}
let resultSpeed = 0;
for (let i = 0; i < speedItems.length; ++i) {
resultSpeed += distanceItems[i] / totalDistance * speedItems[i];
}
if (Math.abs(resultSpeed) < this._private__minSpeed) {
return;
}
this._private__animationStartPosition = { _internal_position: position, _internal_time: time };
this._private__speedPxPerMsec = resultSpeed;
this._private__durationMsecs = durationMSec(Math.abs(resultSpeed), this._private__dumpingCoeff);
}
_internal_getPosition(time) {
const startPosition = ensureNotNull(this._private__animationStartPosition);
const durationMsecs = time - startPosition._internal_time;
return startPosition._internal_position + this._private__speedPxPerMsec * (Math.pow(this._private__dumpingCoeff, durationMsecs) - 1) / (Math.log(this._private__dumpingCoeff));
}
_internal_finished(time) {
return this._private__animationStartPosition === null || this._private__progressDuration(time) === this._private__durationMsecs;
}
_private__progressDuration(time) {
const startPosition = ensureNotNull(this._private__animationStartPosition);
const progress = time - startPosition._internal_time;
return Math.min(progress, this._private__durationMsecs);
}
}
const svg = ``;
const css = `a#tv-attr-logo{--fill:#131722;--stroke:#fff;position:absolute;left:10px;bottom:10px;height:19px;width:35px;margin:0;padding:0;border:0;z-index:3;}a#tv-attr-logo[data-dark]{--fill:#D1D4DC;--stroke:#131722;}`;
// This widget doesn't support dynamically responding to options changes
// because it is expected that the `attributionLogo` option won't be changed
// and this saves some bundle size.
class AttributionLogoWidget {
constructor(container, chart) {
this._private__element = undefined;
this._private__cssElement = undefined;
this._private__theme = undefined;
this._private__visible = false;
this._private__container = container;
this._private__chart = chart;
this._private__render();
}
_internal_update() {
this._private__render();
}
_internal_removeElement() {
if (this._private__element) {
this._private__container.removeChild(this._private__element);
}
if (this._private__cssElement) {
this._private__container.removeChild(this._private__cssElement);
}
this._private__element = undefined;
this._private__cssElement = undefined;
}
_private__shouldUpdate() {
return this._private__visible !== this._private__shouldBeVisible() || this._private__theme !== this._private__themeToUse();
}
_private__themeToUse() {
return this._private__chart._internal_model()._internal_colorParser()._internal_colorStringToGrayscale(this._private__chart._internal_options()['layout'].textColor) > 160
? 'dark'
: 'light';
}
_private__shouldBeVisible() {
return this._private__chart._internal_options()['layout'].attributionLogo;
}
_private__getUTMSource() {
const url = new URL(location.href);
if (!url.hostname) {
// ignore local testing
return '';
}
return '&utm_source=' + url.hostname + url.pathname;
}
_private__render() {
if (!this._private__shouldUpdate()) {
return;
}
this._internal_removeElement();
this._private__visible = this._private__shouldBeVisible();
if (this._private__visible) {
this._private__theme = this._private__themeToUse();
this._private__cssElement = document.createElement('style');
this._private__cssElement.innerText = css;
this._private__element = document.createElement('a');
this._private__element.href = `https://www.tradingview.com/?utm_medium=lwc-link&utm_campaign=lwc-chart${this._private__getUTMSource()}`;
this._private__element.title = 'Charting by TradingView';
this._private__element.id = 'tv-attr-logo';
this._private__element.target = '_blank';
this._private__element.innerHTML = svg;
this._private__element.toggleAttribute('data-dark', this._private__theme === 'dark');
this._private__container.appendChild(this._private__cssElement);
this._private__container.appendChild(this._private__element);
}
}
}
function createBoundCanvas(parentElement, size) {
const doc = ensureNotNull(parentElement.ownerDocument);
const canvas = doc.createElement('canvas');
parentElement.appendChild(canvas);
const binding = bindTo(canvas, {
options: {
allowResizeObserver: true,
},
transform: (bitmapSize, canvasElementClientSize) => ({
width: Math.max(bitmapSize.width, canvasElementClientSize.width),
height: Math.max(bitmapSize.height, canvasElementClientSize.height),
}),
});
binding.resizeCanvasElement(size);
return binding;
}
function releaseCanvas(canvas) {
// This function fixes the iOS Safari error "Total canvas memory use exceeds the maximum limit".
// Seems that iOS Safari stores canvas elements for some additional time internally.
// So if we create/destroy a lot of canvas elements in a short period of time we can get this error.
// We resize the canvas to 1x1 pixels to force it to release memmory resources.
canvas.width = 1;
canvas.height = 1;
canvas.getContext('2d')?.clearRect(0, 0, 1, 1);
}
function drawBackground(renderer, target, isHovered, hitTestData) {
if (renderer._internal_drawBackground) {
renderer._internal_drawBackground(target, isHovered, hitTestData);
}
}
function drawForeground(renderer, target, isHovered, hitTestData) {
renderer._internal_draw(target, isHovered, hitTestData);
}
function drawSourceViews(paneViewsGetter, drawRendererFn, source, pane) {
const views = paneViewsGetter(source, pane);
for (const view of views) {
const renderer = view._internal_renderer(pane);
if (renderer !== null) {
drawRendererFn(renderer);
}
}
}
function hasPriceScale(source) {
return source._internal_priceScale !== undefined;
}
function buildPriceAxisViewsGetter(zOrder, priceScaleId) {
return (source) => {
if (!hasPriceScale(source)) {
return [];
}
const psId = source._internal_priceScale()?._internal_id() ?? '';
if (psId !== priceScaleId) {
// exclude if source is using a different price scale.
return [];
}
return source._internal_pricePaneViews?.(zOrder) ?? [];
};
}
function recalculateOverlapping(views, direction, scaleHeight, rendererOptions) {
if (!views.length) {
return;
}
let currentGroupStart = 0;
const initLabelHeight = views[0]._internal_height(rendererOptions, true);
let spaceBeforeCurrentGroup = direction === 1
? scaleHeight / 2 - (views[0]._internal_getRenderCoordinate() - initLabelHeight / 2)
: views[0]._internal_getRenderCoordinate() - initLabelHeight / 2 - scaleHeight / 2;
spaceBeforeCurrentGroup = Math.max(0, spaceBeforeCurrentGroup);
for (let i = 1; i < views.length; i++) {
const view = views[i];
const prev = views[i - 1];
const height = prev._internal_height(rendererOptions, false);
const coordinate = view._internal_getRenderCoordinate();
const prevCoordinate = prev._internal_getRenderCoordinate();
const overlap = direction === 1
? coordinate > prevCoordinate - height
: coordinate < prevCoordinate + height;
if (overlap) {
const renderCoordinate = prevCoordinate - height * direction;
view._internal_setRenderCoordinate(renderCoordinate);
const edgePoint = renderCoordinate - direction * height / 2;
const outOfViewport = direction === 1 ? edgePoint < 0 : edgePoint > scaleHeight;
if (outOfViewport && spaceBeforeCurrentGroup > 0) {
// shift the whole group up or down
const desiredGroupShift = direction === 1 ? -1 - edgePoint : edgePoint - scaleHeight;
const possibleShift = Math.min(desiredGroupShift, spaceBeforeCurrentGroup);
for (let k = currentGroupStart; k < views.length; k++) {
views[k]._internal_setRenderCoordinate(views[k]._internal_getRenderCoordinate() + direction * possibleShift);
}
spaceBeforeCurrentGroup -= possibleShift;
}
}
else {
currentGroupStart = i;
spaceBeforeCurrentGroup = direction === 1
? prevCoordinate - height - coordinate
: coordinate - (prevCoordinate + height);
}
}
}
function priceScaleCrosshairLabelVisible(crosshair) {
return crosshair.mode !== 2 /* CrosshairMode.Hidden */ && crosshair.horzLine.visible && crosshair.horzLine.labelVisible;
}
class PriceAxisWidget {
constructor(pane, options, rendererOptionsProvider, side) {
this._private__priceScale = null;
this._private__size = null;
this._private__mousedown = false;
this._private__widthCache = new TextWidthCache(200);
this._private__font = null;
this._private__prevOptimalWidth = 0;
this._private__isSettingSize = false;
this._private__canvasSuggestedBitmapSizeChangedHandler = () => {
if (this._private__isSettingSize) {
return;
}
this._private__pane._internal_chart()._internal_model()._internal_lightUpdate();
};
this._private__topCanvasSuggestedBitmapSizeChangedHandler = () => {
if (this._private__isSettingSize) {
return;
}
this._private__pane._internal_chart()._internal_model()._internal_lightUpdate();
};
this._private__pane = pane;
this._private__options = options;
this._private__layoutOptions = options['layout'];
this._private__rendererOptionsProvider = rendererOptionsProvider;
this._private__isLeft = side === 'left';
this._private__sourcePaneViews = buildPriceAxisViewsGetter('normal', side);
this._private__sourceTopPaneViews = buildPriceAxisViewsGetter('top', side);
this._private__sourceBottomPaneViews = buildPriceAxisViewsGetter('bottom', side);
this._private__cell = document.createElement('div');
this._private__cell.style.height = '100%';
this._private__cell.style.overflow = 'hidden';
this._private__cell.style.width = '25px';
this._private__cell.style.left = '0';
this._private__cell.style.position = 'relative';
this._private__canvasBinding = createBoundCanvas(this._private__cell, size$1({ width: 16, height: 16 }));
this._private__canvasBinding.subscribeSuggestedBitmapSizeChanged(this._private__canvasSuggestedBitmapSizeChangedHandler);
const canvas = this._private__canvasBinding.canvasElement;
canvas.style.position = 'absolute';
canvas.style.zIndex = '1';
canvas.style.left = '0';
canvas.style.top = '0';
this._private__topCanvasBinding = createBoundCanvas(this._private__cell, size$1({ width: 16, height: 16 }));
this._private__topCanvasBinding.subscribeSuggestedBitmapSizeChanged(this._private__topCanvasSuggestedBitmapSizeChangedHandler);
const topCanvas = this._private__topCanvasBinding.canvasElement;
topCanvas.style.position = 'absolute';
topCanvas.style.zIndex = '2';
topCanvas.style.left = '0';
topCanvas.style.top = '0';
const handler = {
_internal_mouseDownEvent: this._private__mouseDownEvent.bind(this),
_internal_touchStartEvent: this._private__mouseDownEvent.bind(this),
_internal_pressedMouseMoveEvent: this._private__pressedMouseMoveEvent.bind(this),
_internal_touchMoveEvent: this._private__pressedMouseMoveEvent.bind(this),
_internal_mouseDownOutsideEvent: this._private__mouseDownOutsideEvent.bind(this),
_internal_mouseUpEvent: this._private__mouseUpEvent.bind(this),
_internal_touchEndEvent: this._private__mouseUpEvent.bind(this),
_internal_mouseDoubleClickEvent: this._private__mouseDoubleClickEvent.bind(this),
_internal_doubleTapEvent: this._private__mouseDoubleClickEvent.bind(this),
_internal_mouseEnterEvent: this._private__mouseEnterEvent.bind(this),
_internal_mouseLeaveEvent: this._private__mouseLeaveEvent.bind(this),
};
this._private__mouseEventHandler = new MouseEventHandler(this._private__topCanvasBinding.canvasElement, handler, {
_internal_treatVertTouchDragAsPageScroll: () => !this._private__options['handleScroll'].vertTouchDrag,
_internal_treatHorzTouchDragAsPageScroll: () => true,
});
}
_internal_destroy() {
this._private__mouseEventHandler._internal_destroy();
this._private__topCanvasBinding.unsubscribeSuggestedBitmapSizeChanged(this._private__topCanvasSuggestedBitmapSizeChangedHandler);
releaseCanvas(this._private__topCanvasBinding.canvasElement);
this._private__topCanvasBinding.dispose();
this._private__canvasBinding.unsubscribeSuggestedBitmapSizeChanged(this._private__canvasSuggestedBitmapSizeChangedHandler);
releaseCanvas(this._private__canvasBinding.canvasElement);
this._private__canvasBinding.dispose();
if (this._private__priceScale !== null) {
this._private__priceScale._internal_onMarksChanged()._internal_unsubscribeAll(this);
}
this._private__priceScale = null;
}
_internal_getElement() {
return this._private__cell;
}
_internal_fontSize() {
return this._private__layoutOptions.fontSize;
}
_internal_rendererOptions() {
const options = this._private__rendererOptionsProvider._internal_options();
const isFontChanged = this._private__font !== options._internal_font;
if (isFontChanged) {
this._private__widthCache._internal_reset();
this._private__font = options._internal_font;
}
return options;
}
_internal_optimalWidth() {
if (this._private__priceScale === null) {
return 0;
}
let tickMarkMaxWidth = 0;
const rendererOptions = this._internal_rendererOptions();
const ctx = ensureNotNull(this._private__canvasBinding.canvasElement.getContext('2d', {
colorSpace: this._private__pane._internal_chart()._internal_options().layout.colorSpace,
}));
ctx.save();
const tickMarks = this._private__priceScale._internal_marks();
ctx.font = this._private__baseFont();
if (tickMarks.length > 0) {
tickMarkMaxWidth = Math.max(this._private__widthCache._internal_measureText(ctx, tickMarks[0]._internal_label), this._private__widthCache._internal_measureText(ctx, tickMarks[tickMarks.length - 1]._internal_label));
}
const views = this._private__backLabels();
for (let j = views.length; j--;) {
const width = this._private__widthCache._internal_measureText(ctx, views[j]._internal_text());
if (width > tickMarkMaxWidth) {
tickMarkMaxWidth = width;
}
}
const firstValue = this._private__priceScale._internal_firstValue();
if (firstValue !== null &&
this._private__size !== null &&
priceScaleCrosshairLabelVisible(this._private__options.crosshair)) {
const topValue = this._private__priceScale._internal_coordinateToPrice(1, firstValue);
const bottomValue = this._private__priceScale._internal_coordinateToPrice(this._private__size.height - 2, firstValue);
tickMarkMaxWidth = Math.max(tickMarkMaxWidth, this._private__widthCache._internal_measureText(ctx, this._private__priceScale._internal_formatPrice(Math.floor(Math.min(topValue, bottomValue)) + 0.11111111111111, firstValue)), this._private__widthCache._internal_measureText(ctx, this._private__priceScale._internal_formatPrice(Math.ceil(Math.max(topValue, bottomValue)) - 0.11111111111111, firstValue)));
}
ctx.restore();
const resultTickMarksMaxWidth = tickMarkMaxWidth || 34 /* Constants.DefaultOptimalWidth */;
const res = Math.ceil(rendererOptions._internal_borderSize +
rendererOptions._internal_tickLength +
rendererOptions._internal_paddingInner +
rendererOptions._internal_paddingOuter +
5 /* Constants.LabelOffset */ +
resultTickMarksMaxWidth);
// make it even, remove this after migration to perfect fancy canvas
return suggestPriceScaleWidth(res);
}
_internal_setSize(newSize) {
if (this._private__size === null || !equalSizes(this._private__size, newSize)) {
this._private__size = newSize;
this._private__isSettingSize = true;
this._private__canvasBinding.resizeCanvasElement(newSize);
this._private__topCanvasBinding.resizeCanvasElement(newSize);
this._private__isSettingSize = false;
this._private__cell.style.width = `${newSize.width}px`;
this._private__cell.style.height = `${newSize.height}px`;
}
}
_internal_getWidth() {
return ensureNotNull(this._private__size).width;
}
_internal_setPriceScale(priceScale) {
if (this._private__priceScale === priceScale) {
return;
}
if (this._private__priceScale !== null) {
this._private__priceScale._internal_onMarksChanged()._internal_unsubscribeAll(this);
}
this._private__priceScale = priceScale;
priceScale._internal_onMarksChanged()._internal_subscribe(this._private__onMarksChanged.bind(this), this);
}
_internal_priceScale() {
return this._private__priceScale;
}
_internal_reset() {
const pane = this._private__pane._internal_state();
const model = this._private__pane._internal_chart()._internal_model();
model._internal_resetPriceScale(pane, ensureNotNull(this._internal_priceScale()));
}
_internal_paint(type) {
if (this._private__size === null) {
return;
}
const canvasOptions = {
colorSpace: this._private__pane._internal_chart()._internal_options().layout.colorSpace,
};
if (type !== 1 /* InvalidationLevel.Cursor */) {
this._private__alignLabels();
this._private__canvasBinding.applySuggestedBitmapSize();
const target = tryCreateCanvasRenderingTarget2D(this._private__canvasBinding, canvasOptions);
if (target !== null) {
target.useBitmapCoordinateSpace((scope) => {
this._private__drawBackground(scope);
this._private__drawBorder(scope);
});
this._private__pane._internal_drawAdditionalSources(target, this._private__sourceBottomPaneViews);
this._private__drawTickMarks(target);
this._private__pane._internal_drawAdditionalSources(target, this._private__sourcePaneViews);
this._private__drawBackLabels(target);
}
}
this._private__topCanvasBinding.applySuggestedBitmapSize();
const topTarget = tryCreateCanvasRenderingTarget2D(this._private__topCanvasBinding, canvasOptions);
if (topTarget !== null) {
topTarget.useBitmapCoordinateSpace(({ context: ctx, bitmapSize }) => {
ctx.clearRect(0, 0, bitmapSize.width, bitmapSize.height);
});
this._private__drawCrosshairLabel(topTarget);
this._private__pane._internal_drawAdditionalSources(topTarget, this._private__sourceTopPaneViews);
}
}
_internal_getBitmapSize() {
return this._private__canvasBinding.bitmapSize;
}
_internal_drawBitmap(ctx, x, y, addTopLayer) {
const bitmapSize = this._internal_getBitmapSize();
if (bitmapSize.width > 0 && bitmapSize.height > 0) {
ctx.drawImage(this._private__canvasBinding.canvasElement, x, y);
if (addTopLayer) {
const topLayer = this._private__topCanvasBinding.canvasElement;
ctx.drawImage(topLayer, x, y);
}
}
}
_internal_update() {
// this call has side-effect - it regenerates marks on the price scale
this._private__priceScale?._internal_marks();
}
_private__mouseDownEvent(e) {
if (this._private__priceScale === null || this._private__priceScale._internal_isEmpty() || !this._private__options['handleScale'].axisPressedMouseMove.price) {
return;
}
const model = this._private__pane._internal_chart()._internal_model();
const pane = this._private__pane._internal_state();
this._private__mousedown = true;
model._internal_startScalePrice(pane, this._private__priceScale, e.localY);
}
_private__pressedMouseMoveEvent(e) {
if (this._private__priceScale === null || !this._private__options['handleScale'].axisPressedMouseMove.price) {
return;
}
const model = this._private__pane._internal_chart()._internal_model();
const pane = this._private__pane._internal_state();
const priceScale = this._private__priceScale;
model._internal_scalePriceTo(pane, priceScale, e.localY);
}
_private__mouseDownOutsideEvent() {
if (this._private__priceScale === null || !this._private__options['handleScale'].axisPressedMouseMove.price) {
return;
}
const model = this._private__pane._internal_chart()._internal_model();
const pane = this._private__pane._internal_state();
const priceScale = this._private__priceScale;
if (this._private__mousedown) {
this._private__mousedown = false;
model._internal_endScalePrice(pane, priceScale);
}
}
_private__mouseUpEvent(e) {
if (this._private__priceScale === null || !this._private__options['handleScale'].axisPressedMouseMove.price) {
return;
}
const model = this._private__pane._internal_chart()._internal_model();
const pane = this._private__pane._internal_state();
this._private__mousedown = false;
model._internal_endScalePrice(pane, this._private__priceScale);
}
_private__mouseDoubleClickEvent(e) {
if (this._private__options['handleScale'].axisDoubleClickReset.price) {
this._internal_reset();
}
}
_private__mouseEnterEvent(e) {
if (this._private__priceScale === null) {
return;
}
const model = this._private__pane._internal_chart()._internal_model();
if (model._internal_options()['handleScale'].axisPressedMouseMove.price && !this._private__priceScale._internal_isPercentage() && !this._private__priceScale._internal_isIndexedTo100()) {
this._private__setCursor(1 /* CursorType.NsResize */);
}
}
_private__mouseLeaveEvent(e) {
this._private__setCursor(0 /* CursorType.Default */);
}
_private__backLabels() {
const res = [];
const priceScale = (this._private__priceScale === null) ? undefined : this._private__priceScale;
const addViewsForSources = (sources) => {
for (let i = 0; i < sources.length; ++i) {
const source = sources[i];
const views = source._internal_priceAxisViews(this._private__pane._internal_state(), priceScale);
for (let j = 0; j < views.length; j++) {
res.push(views[j]);
}
}
};
// calculate max and min coordinates for views on selection
// crosshair individually
addViewsForSources(this._private__pane._internal_state()._internal_orderedSources());
return res;
}
_private__drawBackground({ context: ctx, bitmapSize }) {
const { width, height } = bitmapSize;
const model = this._private__pane._internal_state()._internal_model();
const topColor = model._internal_backgroundTopColor();
const bottomColor = model._internal_backgroundBottomColor();
if (topColor === bottomColor) {
clearRect(ctx, 0, 0, width, height, topColor);
}
else {
clearRectWithGradient(ctx, 0, 0, width, height, topColor, bottomColor);
}
}
_private__drawBorder({ context: ctx, bitmapSize, horizontalPixelRatio }) {
if (this._private__size === null || this._private__priceScale === null || !this._private__priceScale._internal_options().borderVisible) {
return;
}
ctx.fillStyle = this._private__priceScale._internal_options().borderColor;
const borderSize = Math.max(1, Math.floor(this._internal_rendererOptions()._internal_borderSize * horizontalPixelRatio));
let left;
if (this._private__isLeft) {
left = bitmapSize.width - borderSize;
}
else {
left = 0;
}
ctx.fillRect(left, 0, borderSize, bitmapSize.height);
}
_private__drawTickMarks(target) {
if (this._private__size === null || this._private__priceScale === null) {
return;
}
const tickMarks = this._private__priceScale._internal_marks();
const priceScaleOptions = this._private__priceScale._internal_options();
const rendererOptions = this._internal_rendererOptions();
const tickMarkLeftX = this._private__isLeft ?
(this._private__size.width - rendererOptions._internal_tickLength) :
0;
if (priceScaleOptions.borderVisible && priceScaleOptions.ticksVisible) {
target.useBitmapCoordinateSpace(({ context: ctx, horizontalPixelRatio, verticalPixelRatio }) => {
ctx.fillStyle = priceScaleOptions.borderColor;
const tickHeight = Math.max(1, Math.floor(verticalPixelRatio));
const tickOffset = Math.floor(verticalPixelRatio * 0.5);
const tickLength = Math.round(rendererOptions._internal_tickLength * horizontalPixelRatio);
ctx.beginPath();
for (const tickMark of tickMarks) {
ctx.rect(Math.floor(tickMarkLeftX * horizontalPixelRatio), Math.round(tickMark._internal_coord * verticalPixelRatio) - tickOffset, tickLength, tickHeight);
}
ctx.fill();
});
}
target.useMediaCoordinateSpace(({ context: ctx }) => {
ctx.font = this._private__baseFont();
ctx.fillStyle = priceScaleOptions.textColor ?? this._private__layoutOptions.textColor;
ctx.textAlign = this._private__isLeft ? 'right' : 'left';
ctx.textBaseline = 'middle';
const textLeftX = this._private__isLeft ?
Math.round(tickMarkLeftX - rendererOptions._internal_paddingInner) :
Math.round(tickMarkLeftX + rendererOptions._internal_tickLength + rendererOptions._internal_paddingInner);
const yMidCorrections = tickMarks.map((mark) => this._private__widthCache._internal_yMidCorrection(ctx, mark._internal_label));
for (let i = tickMarks.length; i--;) {
const tickMark = tickMarks[i];
ctx.fillText(tickMark._internal_label, textLeftX, tickMark._internal_coord + yMidCorrections[i]);
}
});
}
_private__alignLabels() {
if (this._private__size === null || this._private__priceScale === null) {
return;
}
let center = this._private__size.height / 2;
const views = [];
const orderedSources = this._private__priceScale._internal_orderedSources().slice(); // Copy of array
const pane = this._private__pane;
const paneState = pane._internal_state();
const rendererOptions = this._internal_rendererOptions();
// if we are default price scale, append labels from no-scale
const isDefault = this._private__priceScale === paneState._internal_defaultVisiblePriceScale();
if (isDefault) {
this._private__pane._internal_state()._internal_orderedSources().forEach((source) => {
if (paneState._internal_isOverlay(source)) {
orderedSources.push(source);
}
});
}
// we can use any, but let's use the first source as "center" one
const centerSource = this._private__priceScale._internal_dataSources()[0];
const priceScale = this._private__priceScale;
const updateForSources = (sources) => {
sources.forEach((source) => {
const sourceViews = source._internal_priceAxisViews(paneState, priceScale);
// never align selected sources
sourceViews.forEach((view) => {
if (view._internal_isVisible() && view._internal_getFixedCoordinate() === null) {
view._internal_setRenderCoordinate(null); // Clear the previous render coordinate
views.push(view);
}
});
if (centerSource === source && sourceViews.length > 0) {
center = sourceViews[0]._internal_coordinate();
}
});
};
// crosshair individually
updateForSources(orderedSources);
const options = this._private__priceScale._internal_options();
if (!options.alignLabels) {
return;
}
this._private__fixLabelOverlap(views, rendererOptions, center);
}
_private__fixLabelOverlap(views, rendererOptions, center) {
if (this._private__size === null) {
return;
}
// split into two parts
const top = views.filter((view) => view._internal_coordinate() <= center);
const bottom = views.filter((view) => view._internal_coordinate() > center);
// sort top from center to top
top.sort((l, r) => r._internal_coordinate() - l._internal_coordinate());
// share center label
if (top.length && bottom.length) {
bottom.push(top[0]);
}
bottom.sort((l, r) => l._internal_coordinate() - r._internal_coordinate());
for (const view of views) {
const halfHeight = Math.floor(view._internal_height(rendererOptions) / 2);
const coordinate = view._internal_coordinate();
if (coordinate > -halfHeight && coordinate < halfHeight) {
view._internal_setRenderCoordinate(halfHeight);
}
if (coordinate > (this._private__size.height - halfHeight) && coordinate < this._private__size.height + halfHeight) {
view._internal_setRenderCoordinate(this._private__size.height - halfHeight);
}
}
recalculateOverlapping(top, 1, this._private__size.height, rendererOptions);
recalculateOverlapping(bottom, -1, this._private__size.height, rendererOptions);
}
_private__drawBackLabels(target) {
if (this._private__size === null) {
return;
}
const views = this._private__backLabels();
const rendererOptions = this._internal_rendererOptions();
const align = this._private__isLeft ? 'right' : 'left';
views.forEach((view) => {
if (view._internal_isAxisLabelVisible()) {
const renderer = view._internal_renderer(ensureNotNull(this._private__priceScale));
renderer._internal_draw(target, rendererOptions, this._private__widthCache, align);
}
});
}
_private__drawCrosshairLabel(target) {
if (this._private__size === null || this._private__priceScale === null) {
return;
}
const model = this._private__pane._internal_chart()._internal_model();
const views = []; // array of arrays
const pane = this._private__pane._internal_state();
const v = model._internal_crosshairSource()._internal_priceAxisViews(pane, this._private__priceScale);
if (v.length) {
views.push(v);
}
const ro = this._internal_rendererOptions();
const align = this._private__isLeft ? 'right' : 'left';
views.forEach((arr) => {
arr.forEach((view) => {
view._internal_renderer(ensureNotNull(this._private__priceScale))._internal_draw(target, ro, this._private__widthCache, align);
});
});
}
_private__setCursor(type) {
this._private__cell.style.cursor = type === 1 /* CursorType.NsResize */ ? 'ns-resize' : 'default';
}
_private__onMarksChanged() {
const width = this._internal_optimalWidth();
// avoid price scale is shrunk
// using < instead !== to avoid infinite changes
if (this._private__prevOptimalWidth < width) {
this._private__pane._internal_chart()._internal_model()._internal_fullUpdate();
}
this._private__prevOptimalWidth = width;
}
_private__baseFont() {
return makeFont(this._private__layoutOptions.fontSize, this._private__layoutOptions.fontFamily);
}
}
function sourceBottomPaneViews$1(source, pane) {
return source._internal_bottomPaneViews?.(pane) ?? [];
}
function sourcePaneViews$1(source, pane) {
return source._internal_paneViews?.(pane) ?? [];
}
function sourceLabelPaneViews(source, pane) {
return source._internal_labelPaneViews?.(pane) ?? [];
}
function sourceTopPaneViews$1(source, pane) {
return source._internal_topPaneViews?.(pane) ?? [];
}
class PaneWidget {
constructor(chart, state) {
this._private__size = size$1({ width: 0, height: 0 });
this._private__leftPriceAxisWidget = null;
this._private__rightPriceAxisWidget = null;
this._private__attributionLogoWidget = null;
this._private__startScrollingPos = null;
this._private__isScrolling = false;
this._private__clicked = new Delegate();
this._private__dblClicked = new Delegate();
this._private__prevPinchScale = 0;
this._private__longTap = false;
this._private__startTrackPoint = null;
this._private__exitTrackingModeOnNextTry = false;
this._private__initCrosshairPosition = null;
this._private__scrollXAnimation = null;
this._private__isSettingSize = false;
this._private__canvasSuggestedBitmapSizeChangedHandler = () => {
if (this._private__isSettingSize || this._private__state === null) {
return;
}
this._private__model()._internal_lightUpdate();
};
this._private__topCanvasSuggestedBitmapSizeChangedHandler = () => {
if (this._private__isSettingSize || this._private__state === null) {
return;
}
this._private__model()._internal_lightUpdate();
};
this._private__chart = chart;
this._private__state = state;
this._private__state._internal_onDestroyed()._internal_subscribe(this._private__onStateDestroyed.bind(this), this, true);
this._private__paneCell = document.createElement('td');
this._private__paneCell.style.padding = '0';
this._private__paneCell.style.position = 'relative';
const paneWrapper = document.createElement('div');
paneWrapper.style.width = '100%';
paneWrapper.style.height = '100%';
paneWrapper.style.position = 'relative';
paneWrapper.style.overflow = 'hidden';
this._private__leftAxisCell = document.createElement('td');
this._private__leftAxisCell.style.padding = '0';
this._private__rightAxisCell = document.createElement('td');
this._private__rightAxisCell.style.padding = '0';
this._private__paneCell.appendChild(paneWrapper);
this._private__canvasBinding = createBoundCanvas(paneWrapper, size$1({ width: 16, height: 16 }));
this._private__canvasBinding.subscribeSuggestedBitmapSizeChanged(this._private__canvasSuggestedBitmapSizeChangedHandler);
const canvas = this._private__canvasBinding.canvasElement;
canvas.style.position = 'absolute';
canvas.style.zIndex = '1';
canvas.style.left = '0';
canvas.style.top = '0';
this._private__topCanvasBinding = createBoundCanvas(paneWrapper, size$1({ width: 16, height: 16 }));
this._private__topCanvasBinding.subscribeSuggestedBitmapSizeChanged(this._private__topCanvasSuggestedBitmapSizeChangedHandler);
const topCanvas = this._private__topCanvasBinding.canvasElement;
topCanvas.style.position = 'absolute';
topCanvas.style.zIndex = '2';
topCanvas.style.left = '0';
topCanvas.style.top = '0';
this._private__rowElement = document.createElement('tr');
this._private__rowElement.appendChild(this._private__leftAxisCell);
this._private__rowElement.appendChild(this._private__paneCell);
this._private__rowElement.appendChild(this._private__rightAxisCell);
this._internal_updatePriceAxisWidgetsStates();
this._private__mouseEventHandler = new MouseEventHandler(this._private__topCanvasBinding.canvasElement, this, {
_internal_treatVertTouchDragAsPageScroll: () => this._private__startTrackPoint === null && !this._private__chart._internal_options()['handleScroll'].vertTouchDrag,
_internal_treatHorzTouchDragAsPageScroll: () => this._private__startTrackPoint === null && !this._private__chart._internal_options()['handleScroll'].horzTouchDrag,
});
}
_internal_destroy() {
if (this._private__leftPriceAxisWidget !== null) {
this._private__leftPriceAxisWidget._internal_destroy();
}
if (this._private__rightPriceAxisWidget !== null) {
this._private__rightPriceAxisWidget._internal_destroy();
}
this._private__attributionLogoWidget = null;
this._private__topCanvasBinding.unsubscribeSuggestedBitmapSizeChanged(this._private__topCanvasSuggestedBitmapSizeChangedHandler);
releaseCanvas(this._private__topCanvasBinding.canvasElement);
this._private__topCanvasBinding.dispose();
this._private__canvasBinding.unsubscribeSuggestedBitmapSizeChanged(this._private__canvasSuggestedBitmapSizeChangedHandler);
releaseCanvas(this._private__canvasBinding.canvasElement);
this._private__canvasBinding.dispose();
if (this._private__state !== null) {
this._private__state._internal_onDestroyed()._internal_unsubscribeAll(this);
this._private__state._internal_destroy();
}
this._private__mouseEventHandler._internal_destroy();
}
_internal_state() {
return ensureNotNull(this._private__state);
}
_internal_setState(pane) {
if (this._private__state !== null) {
this._private__state._internal_onDestroyed()._internal_unsubscribeAll(this);
}
this._private__state = pane;
if (this._private__state !== null) {
this._private__state._internal_onDestroyed()._internal_subscribe(PaneWidget.prototype._private__onStateDestroyed.bind(this), this, true);
}
this._internal_updatePriceAxisWidgetsStates();
if (this._private__chart._internal_paneWidgets().indexOf(this) === this._private__chart._internal_paneWidgets().length - 1) {
this._private__attributionLogoWidget = this._private__attributionLogoWidget ?? new AttributionLogoWidget(this._private__paneCell, this._private__chart);
this._private__attributionLogoWidget._internal_update();
}
else {
this._private__attributionLogoWidget?._internal_removeElement();
this._private__attributionLogoWidget = null;
}
}
_internal_chart() {
return this._private__chart;
}
_internal_getElement() {
return this._private__rowElement;
}
_internal_updatePriceAxisWidgetsStates() {
if (this._private__state === null) {
return;
}
this._private__recreatePriceAxisWidgets();
if (this._private__model()._internal_serieses().length === 0) {
return;
}
if (this._private__leftPriceAxisWidget !== null) {
const leftPriceScale = this._private__state._internal_leftPriceScale();
this._private__leftPriceAxisWidget._internal_setPriceScale(ensureNotNull(leftPriceScale));
}
if (this._private__rightPriceAxisWidget !== null) {
const rightPriceScale = this._private__state._internal_rightPriceScale();
this._private__rightPriceAxisWidget._internal_setPriceScale(ensureNotNull(rightPriceScale));
}
}
_internal_updatePriceAxisWidgets() {
if (this._private__leftPriceAxisWidget !== null) {
this._private__leftPriceAxisWidget._internal_update();
}
if (this._private__rightPriceAxisWidget !== null) {
this._private__rightPriceAxisWidget._internal_update();
}
}
_internal_stretchFactor() {
return this._private__state !== null ? this._private__state._internal_stretchFactor() : 0;
}
_internal_setStretchFactor(stretchFactor) {
if (this._private__state) {
this._private__state._internal_setStretchFactor(stretchFactor);
}
}
_internal_mouseEnterEvent(event) {
if (!this._private__state) {
return;
}
this._private__onMouseEvent();
const x = event.localX;
const y = event.localY;
this._private__setCrosshairPosition(x, y, event);
}
_internal_mouseDownEvent(event) {
this._private__onMouseEvent();
this._private__mouseTouchDownEvent();
this._private__setCrosshairPosition(event.localX, event.localY, event);
}
_internal_mouseMoveEvent(event) {
if (!this._private__state) {
return;
}
this._private__onMouseEvent();
const x = event.localX;
const y = event.localY;
this._private__setCrosshairPosition(x, y, event);
}
_internal_mouseClickEvent(event) {
if (this._private__state === null) {
return;
}
this._private__onMouseEvent();
this._private__setCrosshairPosition(event.localX, event.localY, event);
this._private__fireClickedDelegate(event);
}
_internal_mouseDoubleClickEvent(event) {
if (this._private__state === null) {
return;
}
this._private__fireMouseClickDelegate(this._private__dblClicked, event);
}
_internal_doubleTapEvent(event) {
this._internal_mouseDoubleClickEvent(event);
}
_internal_pressedMouseMoveEvent(event) {
this._private__onMouseEvent();
this._private__pressedMouseTouchMoveEvent(event);
this._private__setCrosshairPosition(event.localX, event.localY, event);
}
_internal_mouseUpEvent(event) {
if (this._private__state === null) {
return;
}
this._private__onMouseEvent();
this._private__longTap = false;
this._private__endScroll(event);
}
_internal_tapEvent(event) {
if (this._private__state === null) {
return;
}
this._private__fireClickedDelegate(event);
}
_internal_longTapEvent(event) {
this._private__longTap = true;
if (this._private__startTrackPoint === null) {
const point = { x: event.localX, y: event.localY };
this._private__startTrackingMode(point, point, event);
}
}
_internal_mouseLeaveEvent(event) {
if (this._private__state === null) {
return;
}
this._private__onMouseEvent();
this._private__state._internal_model()._internal_setHoveredSource(null);
this._private__clearCrosshairPosition();
}
_internal_clicked() {
return this._private__clicked;
}
_internal_dblClicked() {
return this._private__dblClicked;
}
_internal_pinchStartEvent() {
this._private__prevPinchScale = 1;
this._private__model()._internal_stopTimeScaleAnimation();
}
_internal_pinchEvent(middlePoint, scale) {
if (!this._private__chart._internal_options()['handleScale'].pinch) {
return;
}
const zoomScale = (scale - this._private__prevPinchScale) * 5;
this._private__prevPinchScale = scale;
this._private__model()._internal_zoomTime(middlePoint._internal_x, zoomScale);
}
_internal_touchStartEvent(event) {
this._private__longTap = false;
this._private__exitTrackingModeOnNextTry = this._private__startTrackPoint !== null;
this._private__mouseTouchDownEvent();
const crosshair = this._private__model()._internal_crosshairSource();
if (this._private__startTrackPoint !== null && crosshair._internal_visible()) {
this._private__initCrosshairPosition = { x: crosshair._internal_appliedX(), y: crosshair._internal_appliedY() };
this._private__startTrackPoint = { x: event.localX, y: event.localY };
}
}
_internal_touchMoveEvent(event) {
if (this._private__state === null) {
return;
}
const x = event.localX;
const y = event.localY;
if (this._private__startTrackPoint !== null) {
// tracking mode: move crosshair
this._private__exitTrackingModeOnNextTry = false;
const origPoint = ensureNotNull(this._private__initCrosshairPosition);
const newX = origPoint.x + (x - this._private__startTrackPoint.x);
const newY = origPoint.y + (y - this._private__startTrackPoint.y);
this._private__setCrosshairPosition(newX, newY, event);
return;
}
this._private__pressedMouseTouchMoveEvent(event);
}
_internal_touchEndEvent(event) {
if (this._internal_chart()._internal_options().trackingMode.exitMode === 0 /* TrackingModeExitMode.OnTouchEnd */) {
this._private__exitTrackingModeOnNextTry = true;
}
this._private__tryExitTrackingMode();
this._private__endScroll(event);
}
_internal_hitTest(x, y) {
const state = this._private__state;
if (state === null) {
return null;
}
return hitTestPane(state, x, y);
}
_internal_setPriceAxisSize(width, position) {
const priceAxisWidget = position === 'left' ? this._private__leftPriceAxisWidget : this._private__rightPriceAxisWidget;
ensureNotNull(priceAxisWidget)._internal_setSize(size$1({ width, height: this._private__size.height }));
}
_internal_getSize() {
return this._private__size;
}
_internal_setSize(newSize) {
if (equalSizes(this._private__size, newSize)) {
return;
}
this._private__size = newSize;
this._private__isSettingSize = true;
this._private__canvasBinding.resizeCanvasElement(newSize);
this._private__topCanvasBinding.resizeCanvasElement(newSize);
this._private__isSettingSize = false;
this._private__paneCell.style.width = newSize.width + 'px';
this._private__paneCell.style.height = newSize.height + 'px';
}
_internal_recalculatePriceScales() {
const pane = ensureNotNull(this._private__state);
pane._internal_recalculatePriceScale(pane._internal_leftPriceScale());
pane._internal_recalculatePriceScale(pane._internal_rightPriceScale());
for (const source of pane._internal_dataSources()) {
if (pane._internal_isOverlay(source)) {
const priceScale = source._internal_priceScale();
if (priceScale !== null) {
pane._internal_recalculatePriceScale(priceScale);
}
// for overlay drawings price scale is owner's price scale
// however owner's price scale could not contain ds
source._internal_updateAllViews();
}
}
for (const primitive of pane._internal_primitives()) {
primitive._internal_updateAllViews();
}
}
_internal_getBitmapSize() {
return this._private__canvasBinding.bitmapSize;
}
_internal_drawBitmap(ctx, x, y, addTopLayer) {
const bitmapSize = this._internal_getBitmapSize();
if (bitmapSize.width > 0 && bitmapSize.height > 0) {
ctx.drawImage(this._private__canvasBinding.canvasElement, x, y);
if (addTopLayer) {
const topLayer = this._private__topCanvasBinding.canvasElement;
if (ctx !== null) {
ctx.drawImage(topLayer, x, y);
}
}
}
}
_internal_paint(type) {
if (type === 0 /* InvalidationLevel.None */) {
return;
}
if (this._private__state === null) {
return;
}
if (type > 1 /* InvalidationLevel.Cursor */) {
this._internal_recalculatePriceScales();
}
if (this._private__leftPriceAxisWidget !== null) {
this._private__leftPriceAxisWidget._internal_paint(type);
}
if (this._private__rightPriceAxisWidget !== null) {
this._private__rightPriceAxisWidget._internal_paint(type);
}
const canvasOptions = {
colorSpace: this._private__chart._internal_options().layout.colorSpace,
};
if (type !== 1 /* InvalidationLevel.Cursor */) {
this._private__canvasBinding.applySuggestedBitmapSize();
const target = tryCreateCanvasRenderingTarget2D(this._private__canvasBinding, canvasOptions);
if (target !== null) {
target.useBitmapCoordinateSpace((scope) => {
this._private__drawBackground(scope);
});
if (this._private__state) {
this._private__drawSources(target, sourceBottomPaneViews$1);
this._private__drawGrid(target);
this._private__drawSources(target, sourcePaneViews$1);
this._private__drawSources(target, sourceLabelPaneViews);
}
}
}
this._private__topCanvasBinding.applySuggestedBitmapSize();
const topTarget = tryCreateCanvasRenderingTarget2D(this._private__topCanvasBinding, canvasOptions);
if (topTarget !== null) {
topTarget.useBitmapCoordinateSpace(({ context: ctx, bitmapSize }) => {
ctx.clearRect(0, 0, bitmapSize.width, bitmapSize.height);
});
this._private__drawCrosshair(topTarget);
this._private__drawSources(topTarget, sourceTopPaneViews$1);
this._private__drawSources(topTarget, sourceLabelPaneViews);
}
}
_internal_leftPriceAxisWidget() {
return this._private__leftPriceAxisWidget;
}
_internal_rightPriceAxisWidget() {
return this._private__rightPriceAxisWidget;
}
_internal_drawAdditionalSources(target, paneViewsGetter) {
this._private__drawSources(target, paneViewsGetter);
}
_private__onStateDestroyed() {
if (this._private__state !== null) {
this._private__state._internal_onDestroyed()._internal_unsubscribeAll(this);
}
this._private__state = null;
}
_private__fireClickedDelegate(event) {
this._private__fireMouseClickDelegate(this._private__clicked, event);
}
_private__fireMouseClickDelegate(delegate, event) {
const x = event.localX;
const y = event.localY;
if (delegate._internal_hasListeners()) {
delegate._internal_fire(this._private__model()._internal_timeScale()._internal_coordinateToIndex(x), { x, y }, event);
}
}
_private__drawBackground({ context: ctx, bitmapSize }) {
const { width, height } = bitmapSize;
const model = this._private__model();
const topColor = model._internal_backgroundTopColor();
const bottomColor = model._internal_backgroundBottomColor();
if (topColor === bottomColor) {
clearRect(ctx, 0, 0, width, height, bottomColor);
}
else {
clearRectWithGradient(ctx, 0, 0, width, height, topColor, bottomColor);
}
}
_private__drawGrid(target) {
const state = ensureNotNull(this._private__state);
const paneView = state._internal_grid()._internal_paneView();
const renderer = paneView._internal_renderer(state);
if (renderer !== null) {
renderer._internal_draw(target, false);
}
}
_private__drawCrosshair(target) {
this._private__drawSourceImpl(target, sourcePaneViews$1, drawForeground, this._private__model()._internal_crosshairSource());
}
_private__drawSources(target, paneViewsGetter) {
const state = ensureNotNull(this._private__state);
const sources = state._internal_orderedSourcesForRendering();
const panePrimitives = state._internal_primitives();
for (const panePrimitive of panePrimitives) {
this._private__drawSourceImpl(target, paneViewsGetter, drawBackground, panePrimitive);
}
for (const source of sources) {
this._private__drawSourceImpl(target, paneViewsGetter, drawBackground, source);
}
for (const panePrimitive of panePrimitives) {
this._private__drawSourceImpl(target, paneViewsGetter, drawForeground, panePrimitive);
}
for (const source of sources) {
this._private__drawSourceImpl(target, paneViewsGetter, drawForeground, source);
}
}
_private__drawSourceImpl(target, paneViewsGetter, drawFn, source) {
const state = ensureNotNull(this._private__state);
const hoveredSource = state._internal_model()._internal_hoveredSource();
const isHovered = hoveredSource !== null && hoveredSource._internal_source === source;
const objecId = hoveredSource !== null && isHovered && hoveredSource._internal_object !== undefined
? hoveredSource._internal_object._internal_hitTestData
: undefined;
const drawRendererFn = (renderer) => drawFn(renderer, target, isHovered, objecId);
drawSourceViews(paneViewsGetter, drawRendererFn, source, state);
}
_private__recreatePriceAxisWidgets() {
if (this._private__state === null) {
return;
}
const chart = this._private__chart;
const leftAxisVisible = this._private__state._internal_leftPriceScale()._internal_options().visible;
const rightAxisVisible = this._private__state._internal_rightPriceScale()._internal_options().visible;
if (!leftAxisVisible && this._private__leftPriceAxisWidget !== null) {
this._private__leftAxisCell.removeChild(this._private__leftPriceAxisWidget._internal_getElement());
this._private__leftPriceAxisWidget._internal_destroy();
this._private__leftPriceAxisWidget = null;
}
if (!rightAxisVisible && this._private__rightPriceAxisWidget !== null) {
this._private__rightAxisCell.removeChild(this._private__rightPriceAxisWidget._internal_getElement());
this._private__rightPriceAxisWidget._internal_destroy();
this._private__rightPriceAxisWidget = null;
}
const rendererOptionsProvider = chart._internal_model()._internal_rendererOptionsProvider();
if (leftAxisVisible && this._private__leftPriceAxisWidget === null) {
this._private__leftPriceAxisWidget = new PriceAxisWidget(this, chart._internal_options(), rendererOptionsProvider, 'left');
this._private__leftAxisCell.appendChild(this._private__leftPriceAxisWidget._internal_getElement());
}
if (rightAxisVisible && this._private__rightPriceAxisWidget === null) {
this._private__rightPriceAxisWidget = new PriceAxisWidget(this, chart._internal_options(), rendererOptionsProvider, 'right');
this._private__rightAxisCell.appendChild(this._private__rightPriceAxisWidget._internal_getElement());
}
}
_private__preventScroll(event) {
return event._internal_isTouch && this._private__longTap || this._private__startTrackPoint !== null;
}
_private__setCrosshairPosition(x, y, event) {
x = Math.max(0, Math.min(x, this._private__size.width - 1));
y = Math.max(0, Math.min(y, this._private__size.height - 1));
this._private__model()._internal_setAndSaveCurrentPosition(x, y, event, ensureNotNull(this._private__state));
}
_private__clearCrosshairPosition() {
this._private__model()._internal_clearCurrentPosition();
}
_private__tryExitTrackingMode() {
if (this._private__exitTrackingModeOnNextTry) {
this._private__startTrackPoint = null;
this._private__clearCrosshairPosition();
}
}
_private__startTrackingMode(startTrackPoint, crossHairPosition, event) {
this._private__startTrackPoint = startTrackPoint;
this._private__exitTrackingModeOnNextTry = false;
this._private__setCrosshairPosition(crossHairPosition.x, crossHairPosition.y, event);
const crosshair = this._private__model()._internal_crosshairSource();
this._private__initCrosshairPosition = { x: crosshair._internal_appliedX(), y: crosshair._internal_appliedY() };
}
_private__model() {
return this._private__chart._internal_model();
}
_private__endScroll(event) {
if (!this._private__isScrolling) {
return;
}
const model = this._private__model();
const state = this._internal_state();
model._internal_endScrollPrice(state, state._internal_defaultPriceScale());
this._private__startScrollingPos = null;
this._private__isScrolling = false;
model._internal_endScrollTime();
if (this._private__scrollXAnimation !== null) {
const startAnimationTime = performance.now();
const timeScale = model._internal_timeScale();
this._private__scrollXAnimation._internal_start(timeScale._internal_rightOffset(), startAnimationTime);
if (!this._private__scrollXAnimation._internal_finished(startAnimationTime)) {
model._internal_setTimeScaleAnimation(this._private__scrollXAnimation);
}
}
}
_private__onMouseEvent() {
this._private__startTrackPoint = null;
}
_private__mouseTouchDownEvent() {
if (!this._private__state) {
return;
}
this._private__model()._internal_stopTimeScaleAnimation();
if (document.activeElement !== document.body && document.activeElement !== document.documentElement) {
// If any focusable element except the page itself is focused, remove the focus
ensureNotNull(document.activeElement).blur();
}
else {
// Clear selection
const selection = document.getSelection();
if (selection !== null) {
selection.removeAllRanges();
}
}
const priceScale = this._private__state._internal_defaultPriceScale();
if (priceScale._internal_isEmpty() || this._private__model()._internal_timeScale()._internal_isEmpty()) {
return;
}
}
// eslint-disable-next-line complexity
_private__pressedMouseTouchMoveEvent(event) {
if (this._private__state === null) {
return;
}
const model = this._private__model();
const timeScale = model._internal_timeScale();
if (timeScale._internal_isEmpty()) {
return;
}
const chartOptions = this._private__chart._internal_options();
const scrollOptions = chartOptions['handleScroll'];
const kineticScrollOptions = chartOptions.kineticScroll;
if ((!scrollOptions.pressedMouseMove || event._internal_isTouch) &&
(!scrollOptions.horzTouchDrag && !scrollOptions.vertTouchDrag || !event._internal_isTouch)) {
return;
}
const priceScale = this._private__state._internal_defaultPriceScale();
const now = performance.now();
if (this._private__startScrollingPos === null && !this._private__preventScroll(event)) {
this._private__startScrollingPos = {
x: event.clientX,
y: event.clientY,
_internal_timestamp: now,
_internal_localX: event.localX,
_internal_localY: event.localY,
};
}
if (this._private__startScrollingPos !== null &&
!this._private__isScrolling &&
(this._private__startScrollingPos.x !== event.clientX || this._private__startScrollingPos.y !== event.clientY)) {
if (event._internal_isTouch && kineticScrollOptions.touch || !event._internal_isTouch && kineticScrollOptions.mouse) {
const barSpacing = timeScale._internal_barSpacing();
this._private__scrollXAnimation = new KineticAnimation(0.2 /* KineticScrollConstants.MinScrollSpeed */ / barSpacing, 7 /* KineticScrollConstants.MaxScrollSpeed */ / barSpacing, 0.997 /* KineticScrollConstants.DumpingCoeff */, 15 /* KineticScrollConstants.ScrollMinMove */ / barSpacing);
this._private__scrollXAnimation._internal_addPosition(timeScale._internal_rightOffset(), this._private__startScrollingPos._internal_timestamp);
}
else {
this._private__scrollXAnimation = null;
}
if (!priceScale._internal_isEmpty()) {
model._internal_startScrollPrice(this._private__state, priceScale, event.localY);
}
model._internal_startScrollTime(event.localX);
this._private__isScrolling = true;
}
if (this._private__isScrolling) {
// this allows scrolling not default price scales
if (!priceScale._internal_isEmpty()) {
model._internal_scrollPriceTo(this._private__state, priceScale, event.localY);
}
model._internal_scrollTimeTo(event.localX);
if (this._private__scrollXAnimation !== null) {
this._private__scrollXAnimation._internal_addPosition(timeScale._internal_rightOffset(), now);
}
}
}
}
class PriceAxisStub {
constructor(side, options, params, borderVisible, bottomColor) {
this._private__invalidated = true;
this._private__size = size$1({ width: 0, height: 0 });
this._private__canvasSuggestedBitmapSizeChangedHandler = () => this._internal_paint(3 /* InvalidationLevel.Full */);
this._private__isLeft = side === 'left';
this._private__rendererOptionsProvider = params._internal_rendererOptionsProvider;
this._private__options = options;
this._private__borderVisible = borderVisible;
this._private__bottomColor = bottomColor;
this._private__cell = document.createElement('div');
this._private__cell.style.width = '25px';
this._private__cell.style.height = '100%';
this._private__cell.style.overflow = 'hidden';
this._private__canvasBinding = createBoundCanvas(this._private__cell, size$1({ width: 16, height: 16 }));
this._private__canvasBinding.subscribeSuggestedBitmapSizeChanged(this._private__canvasSuggestedBitmapSizeChangedHandler);
}
_internal_destroy() {
this._private__canvasBinding.unsubscribeSuggestedBitmapSizeChanged(this._private__canvasSuggestedBitmapSizeChangedHandler);
releaseCanvas(this._private__canvasBinding.canvasElement);
this._private__canvasBinding.dispose();
}
_internal_getElement() {
return this._private__cell;
}
_internal_getSize() {
return this._private__size;
}
_internal_setSize(newSize) {
if (!equalSizes(this._private__size, newSize)) {
this._private__size = newSize;
this._private__canvasBinding.resizeCanvasElement(newSize);
this._private__cell.style.width = `${newSize.width}px`;
this._private__cell.style.height = `${newSize.height}px`;
this._private__invalidated = true;
}
}
_internal_paint(type) {
if (type < 3 /* InvalidationLevel.Full */ && !this._private__invalidated) {
return;
}
if (this._private__size.width === 0 || this._private__size.height === 0) {
return;
}
this._private__invalidated = false;
this._private__canvasBinding.applySuggestedBitmapSize();
const target = tryCreateCanvasRenderingTarget2D(this._private__canvasBinding, {
colorSpace: this._private__options.layout.colorSpace,
});
if (target !== null) {
target.useBitmapCoordinateSpace((scope) => {
this._private__drawBackground(scope);
this._private__drawBorder(scope);
});
}
}
_internal_getBitmapSize() {
return this._private__canvasBinding.bitmapSize;
}
_internal_drawBitmap(ctx, x, y) {
const bitmapSize = this._internal_getBitmapSize();
if (bitmapSize.width > 0 && bitmapSize.height > 0) {
ctx.drawImage(this._private__canvasBinding.canvasElement, x, y);
}
}
_private__drawBorder({ context: ctx, bitmapSize, horizontalPixelRatio, verticalPixelRatio }) {
if (!this._private__borderVisible()) {
return;
}
ctx.fillStyle = this._private__options.timeScale.borderColor;
const horzBorderSize = Math.floor(this._private__rendererOptionsProvider._internal_options()._internal_borderSize * horizontalPixelRatio);
const vertBorderSize = Math.floor(this._private__rendererOptionsProvider._internal_options()._internal_borderSize * verticalPixelRatio);
const left = (this._private__isLeft) ? bitmapSize.width - horzBorderSize : 0;
ctx.fillRect(left, 0, horzBorderSize, vertBorderSize);
}
_private__drawBackground({ context: ctx, bitmapSize }) {
clearRect(ctx, 0, 0, bitmapSize.width, bitmapSize.height, this._private__bottomColor());
}
}
function buildTimeAxisViewsGetter(zOrder) {
return (source) => source._internal_timePaneViews?.(zOrder) ?? [];
}
const sourcePaneViews = buildTimeAxisViewsGetter('normal');
const sourceTopPaneViews = buildTimeAxisViewsGetter('top');
const sourceBottomPaneViews = buildTimeAxisViewsGetter('bottom');
class TimeAxisWidget {
constructor(chartWidget, horzScaleBehavior) {
this._private__leftStub = null;
this._private__rightStub = null;
this._private__rendererOptions = null;
this._private__mouseDown = false;
this._private__size = size$1({ width: 0, height: 0 });
this._private__sizeChanged = new Delegate();
this._private__widthCache = new TextWidthCache(5);
this._private__isSettingSize = false;
this._private__canvasSuggestedBitmapSizeChangedHandler = () => {
if (!this._private__isSettingSize) {
this._private__chart._internal_model()._internal_lightUpdate();
}
};
this._private__topCanvasSuggestedBitmapSizeChangedHandler = () => {
if (!this._private__isSettingSize) {
this._private__chart._internal_model()._internal_lightUpdate();
}
};
this._private__chart = chartWidget;
this._private__horzScaleBehavior = horzScaleBehavior;
this._private__options = chartWidget._internal_options()['layout'];
this._private__element = document.createElement('tr');
this._private__leftStubCell = document.createElement('td');
this._private__leftStubCell.style.padding = '0';
this._private__rightStubCell = document.createElement('td');
this._private__rightStubCell.style.padding = '0';
this._private__cell = document.createElement('td');
this._private__cell.style.height = '25px';
this._private__cell.style.padding = '0';
this._private__dv = document.createElement('div');
this._private__dv.style.width = '100%';
this._private__dv.style.height = '100%';
this._private__dv.style.position = 'relative';
this._private__dv.style.overflow = 'hidden';
this._private__cell.appendChild(this._private__dv);
this._private__canvasBinding = createBoundCanvas(this._private__dv, size$1({ width: 16, height: 16 }));
this._private__canvasBinding.subscribeSuggestedBitmapSizeChanged(this._private__canvasSuggestedBitmapSizeChangedHandler);
const canvas = this._private__canvasBinding.canvasElement;
canvas.style.position = 'absolute';
canvas.style.zIndex = '1';
canvas.style.left = '0';
canvas.style.top = '0';
this._private__topCanvasBinding = createBoundCanvas(this._private__dv, size$1({ width: 16, height: 16 }));
this._private__topCanvasBinding.subscribeSuggestedBitmapSizeChanged(this._private__topCanvasSuggestedBitmapSizeChangedHandler);
const topCanvas = this._private__topCanvasBinding.canvasElement;
topCanvas.style.position = 'absolute';
topCanvas.style.zIndex = '2';
topCanvas.style.left = '0';
topCanvas.style.top = '0';
this._private__element.appendChild(this._private__leftStubCell);
this._private__element.appendChild(this._private__cell);
this._private__element.appendChild(this._private__rightStubCell);
this._private__recreateStubs();
this._private__chart._internal_model()._internal_priceScalesOptionsChanged()._internal_subscribe(this._private__recreateStubs.bind(this), this);
this._private__mouseEventHandler = new MouseEventHandler(this._private__topCanvasBinding.canvasElement, this, {
_internal_treatVertTouchDragAsPageScroll: () => true,
_internal_treatHorzTouchDragAsPageScroll: () => !this._private__chart._internal_options()['handleScroll'].horzTouchDrag,
});
}
_internal_destroy() {
this._private__mouseEventHandler._internal_destroy();
if (this._private__leftStub !== null) {
this._private__leftStub._internal_destroy();
}
if (this._private__rightStub !== null) {
this._private__rightStub._internal_destroy();
}
this._private__topCanvasBinding.unsubscribeSuggestedBitmapSizeChanged(this._private__topCanvasSuggestedBitmapSizeChangedHandler);
releaseCanvas(this._private__topCanvasBinding.canvasElement);
this._private__topCanvasBinding.dispose();
this._private__canvasBinding.unsubscribeSuggestedBitmapSizeChanged(this._private__canvasSuggestedBitmapSizeChangedHandler);
releaseCanvas(this._private__canvasBinding.canvasElement);
this._private__canvasBinding.dispose();
}
_internal_getElement() {
return this._private__element;
}
_internal_leftStub() {
return this._private__leftStub;
}
_internal_rightStub() {
return this._private__rightStub;
}
_internal_mouseDownEvent(event) {
if (this._private__mouseDown) {
return;
}
this._private__mouseDown = true;
const model = this._private__chart._internal_model();
if (model._internal_timeScale()._internal_isEmpty() || !this._private__chart._internal_options()['handleScale'].axisPressedMouseMove.time) {
return;
}
model._internal_startScaleTime(event.localX);
}
_internal_touchStartEvent(event) {
this._internal_mouseDownEvent(event);
}
_internal_mouseDownOutsideEvent() {
const model = this._private__chart._internal_model();
if (!model._internal_timeScale()._internal_isEmpty() && this._private__mouseDown) {
this._private__mouseDown = false;
if (this._private__chart._internal_options()['handleScale'].axisPressedMouseMove.time) {
model._internal_endScaleTime();
}
}
}
_internal_pressedMouseMoveEvent(event) {
const model = this._private__chart._internal_model();
if (model._internal_timeScale()._internal_isEmpty() || !this._private__chart._internal_options()['handleScale'].axisPressedMouseMove.time) {
return;
}
model._internal_scaleTimeTo(event.localX);
}
_internal_touchMoveEvent(event) {
this._internal_pressedMouseMoveEvent(event);
}
_internal_mouseUpEvent() {
this._private__mouseDown = false;
const model = this._private__chart._internal_model();
if (model._internal_timeScale()._internal_isEmpty() && !this._private__chart._internal_options()['handleScale'].axisPressedMouseMove.time) {
return;
}
model._internal_endScaleTime();
}
_internal_touchEndEvent() {
this._internal_mouseUpEvent();
}
_internal_mouseDoubleClickEvent() {
if (this._private__chart._internal_options()['handleScale'].axisDoubleClickReset.time) {
this._private__chart._internal_model()._internal_resetTimeScale();
}
}
_internal_doubleTapEvent() {
this._internal_mouseDoubleClickEvent();
}
_internal_mouseEnterEvent() {
if (this._private__chart._internal_model()._internal_options()['handleScale'].axisPressedMouseMove.time) {
this._private__setCursor(1 /* CursorType.EwResize */);
}
}
_internal_mouseLeaveEvent() {
this._private__setCursor(0 /* CursorType.Default */);
}
_internal_getSize() {
return this._private__size;
}
_internal_sizeChanged() {
return this._private__sizeChanged;
}
_internal_setSizes(timeAxisSize, leftStubWidth, rightStubWidth) {
if (!equalSizes(this._private__size, timeAxisSize)) {
this._private__size = timeAxisSize;
this._private__isSettingSize = true;
this._private__canvasBinding.resizeCanvasElement(timeAxisSize);
this._private__topCanvasBinding.resizeCanvasElement(timeAxisSize);
this._private__isSettingSize = false;
this._private__cell.style.width = `${timeAxisSize.width}px`;
this._private__cell.style.height = `${timeAxisSize.height}px`;
this._private__sizeChanged._internal_fire(timeAxisSize);
}
if (this._private__leftStub !== null) {
this._private__leftStub._internal_setSize(size$1({ width: leftStubWidth, height: timeAxisSize.height }));
}
if (this._private__rightStub !== null) {
this._private__rightStub._internal_setSize(size$1({ width: rightStubWidth, height: timeAxisSize.height }));
}
}
_internal_optimalHeight() {
const rendererOptions = this._private__getRendererOptions();
return Math.ceil(
// rendererOptions.offsetSize +
rendererOptions._internal_borderSize +
rendererOptions._internal_tickLength +
rendererOptions._internal_fontSize +
rendererOptions._internal_paddingTop +
rendererOptions._internal_paddingBottom +
rendererOptions._internal_labelBottomOffset);
}
_internal_update() {
// this call has side-effect - it regenerates marks on the time scale
this._private__chart._internal_model()._internal_timeScale()._internal_marks();
}
_internal_getBitmapSize() {
return this._private__canvasBinding.bitmapSize;
}
_internal_drawBitmap(ctx, x, y, addTopLayer) {
const bitmapSize = this._internal_getBitmapSize();
if (bitmapSize.width > 0 && bitmapSize.height > 0) {
ctx.drawImage(this._private__canvasBinding.canvasElement, x, y);
if (addTopLayer) {
const topLayer = this._private__topCanvasBinding.canvasElement;
ctx.drawImage(topLayer, x, y);
}
}
}
_internal_paint(type) {
if (type === 0 /* InvalidationLevel.None */) {
return;
}
const canvasOptions = {
colorSpace: this._private__options.colorSpace,
};
if (type !== 1 /* InvalidationLevel.Cursor */) {
this._private__canvasBinding.applySuggestedBitmapSize();
const target = tryCreateCanvasRenderingTarget2D(this._private__canvasBinding, canvasOptions);
if (target !== null) {
target.useBitmapCoordinateSpace((scope) => {
this._private__drawBackground(scope);
this._private__drawBorder(scope);
this._private__drawAdditionalSources(target, sourceBottomPaneViews);
});
this._private__drawTickMarks(target);
this._private__drawAdditionalSources(target, sourcePaneViews);
// atm we don't have sources to be drawn on time axis except crosshair which is rendered on top level canvas
// so let's don't call this code at all for now
// this._drawLabels(this._chart.model().dataSources(), target);
}
if (this._private__leftStub !== null) {
this._private__leftStub._internal_paint(type);
}
if (this._private__rightStub !== null) {
this._private__rightStub._internal_paint(type);
}
}
this._private__topCanvasBinding.applySuggestedBitmapSize();
const topTarget = tryCreateCanvasRenderingTarget2D(this._private__topCanvasBinding, canvasOptions);
if (topTarget !== null) {
topTarget.useBitmapCoordinateSpace(({ context: ctx, bitmapSize }) => {
ctx.clearRect(0, 0, bitmapSize.width, bitmapSize.height);
});
this._private__drawLabels([...this._private__chart._internal_model()._internal_serieses(), this._private__chart._internal_model()._internal_crosshairSource()], topTarget);
this._private__drawAdditionalSources(topTarget, sourceTopPaneViews);
}
}
_private__drawAdditionalSources(target, axisViewsGetter) {
const sources = this._private__chart._internal_model()._internal_serieses();
for (const source of sources) {
drawSourceViews(axisViewsGetter, (renderer) => drawBackground(renderer, target, false, undefined), source, undefined);
}
for (const source of sources) {
drawSourceViews(axisViewsGetter, (renderer) => drawForeground(renderer, target, false, undefined), source, undefined);
}
}
_private__drawBackground({ context: ctx, bitmapSize }) {
clearRect(ctx, 0, 0, bitmapSize.width, bitmapSize.height, this._private__chart._internal_model()._internal_backgroundBottomColor());
}
_private__drawBorder({ context: ctx, bitmapSize, verticalPixelRatio }) {
if (this._private__chart._internal_options().timeScale.borderVisible) {
ctx.fillStyle = this._private__lineColor();
const borderSize = Math.max(1, Math.floor(this._private__getRendererOptions()._internal_borderSize * verticalPixelRatio));
ctx.fillRect(0, 0, bitmapSize.width, borderSize);
}
}
_private__drawTickMarks(target) {
const timeScale = this._private__chart._internal_model()._internal_timeScale();
const tickMarks = timeScale._internal_marks();
if (!tickMarks || tickMarks.length === 0) {
return;
}
const maxWeight = this._private__horzScaleBehavior.maxTickMarkWeight(tickMarks);
const rendererOptions = this._private__getRendererOptions();
const options = timeScale._internal_options();
if (options.borderVisible && options.ticksVisible) {
target.useBitmapCoordinateSpace(({ context: ctx, horizontalPixelRatio, verticalPixelRatio }) => {
ctx.strokeStyle = this._private__lineColor();
ctx.fillStyle = this._private__lineColor();
const tickWidth = Math.max(1, Math.floor(horizontalPixelRatio));
const tickOffset = Math.floor(horizontalPixelRatio * 0.5);
ctx.beginPath();
const tickLen = Math.round(rendererOptions._internal_tickLength * verticalPixelRatio);
for (let index = tickMarks.length; index--;) {
const x = Math.round(tickMarks[index].coord * horizontalPixelRatio);
ctx.rect(x - tickOffset, 0, tickWidth, tickLen);
}
ctx.fill();
});
}
target.useMediaCoordinateSpace(({ context: ctx }) => {
const yText = (rendererOptions._internal_borderSize +
rendererOptions._internal_tickLength +
rendererOptions._internal_paddingTop +
rendererOptions._internal_fontSize / 2);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = this._private__textColor();
// draw base marks
ctx.font = this._private__baseFont();
for (const tickMark of tickMarks) {
if (tickMark.weight < maxWeight) {
const coordinate = tickMark.needAlignCoordinate ? this._private__alignTickMarkLabelCoordinate(ctx, tickMark.coord, tickMark.label) : tickMark.coord;
ctx.fillText(tickMark.label, coordinate, yText);
}
}
if (this._private__chart._internal_options().timeScale.allowBoldLabels) {
ctx.font = this._private__baseBoldFont();
}
for (const tickMark of tickMarks) {
if (tickMark.weight >= maxWeight) {
const coordinate = tickMark.needAlignCoordinate ? this._private__alignTickMarkLabelCoordinate(ctx, tickMark.coord, tickMark.label) : tickMark.coord;
ctx.fillText(tickMark.label, coordinate, yText);
}
}
});
}
_private__alignTickMarkLabelCoordinate(ctx, coordinate, labelText) {
const labelWidth = this._private__widthCache._internal_measureText(ctx, labelText);
const labelWidthHalf = labelWidth / 2;
const leftTextCoordinate = Math.floor(coordinate - labelWidthHalf) + 0.5;
if (leftTextCoordinate < 0) {
coordinate = coordinate + Math.abs(0 - leftTextCoordinate);
}
else if (leftTextCoordinate + labelWidth > this._private__size.width) {
coordinate = coordinate - Math.abs(this._private__size.width - (leftTextCoordinate + labelWidth));
}
return coordinate;
}
_private__drawLabels(sources, target) {
const rendererOptions = this._private__getRendererOptions();
for (const source of sources) {
for (const view of source._internal_timeAxisViews()) {
view._internal_renderer()._internal_draw(target, rendererOptions);
}
}
}
_private__lineColor() {
return this._private__chart._internal_options().timeScale.borderColor;
}
_private__textColor() {
return this._private__options.textColor;
}
_private__fontSize() {
return this._private__options.fontSize;
}
_private__baseFont() {
return makeFont(this._private__fontSize(), this._private__options.fontFamily);
}
_private__baseBoldFont() {
return makeFont(this._private__fontSize(), this._private__options.fontFamily, 'bold');
}
_private__getRendererOptions() {
if (this._private__rendererOptions === null) {
this._private__rendererOptions = {
_internal_borderSize: 1 /* Constants.BorderSize */,
_internal_baselineOffset: NaN,
_internal_paddingTop: NaN,
_internal_paddingBottom: NaN,
_internal_paddingHorizontal: NaN,
_internal_tickLength: 5 /* Constants.TickLength */,
_internal_fontSize: NaN,
_internal_font: '',
_internal_widthCache: new TextWidthCache(),
_internal_labelBottomOffset: 0,
};
}
const rendererOptions = this._private__rendererOptions;
const newFont = this._private__baseFont();
if (rendererOptions._internal_font !== newFont) {
const fontSize = this._private__fontSize();
rendererOptions._internal_fontSize = fontSize;
rendererOptions._internal_font = newFont;
rendererOptions._internal_paddingTop = 3 * fontSize / 12;
rendererOptions._internal_paddingBottom = 3 * fontSize / 12;
rendererOptions._internal_paddingHorizontal = 9 * fontSize / 12;
rendererOptions._internal_baselineOffset = 0;
rendererOptions._internal_labelBottomOffset = 4 * fontSize / 12;
rendererOptions._internal_widthCache._internal_reset();
}
return this._private__rendererOptions;
}
_private__setCursor(type) {
this._private__cell.style.cursor = type === 1 /* CursorType.EwResize */ ? 'ew-resize' : 'default';
}
_private__recreateStubs() {
const model = this._private__chart._internal_model();
const options = model._internal_options();
if (!options.leftPriceScale.visible && this._private__leftStub !== null) {
this._private__leftStubCell.removeChild(this._private__leftStub._internal_getElement());
this._private__leftStub._internal_destroy();
this._private__leftStub = null;
}
if (!options.rightPriceScale.visible && this._private__rightStub !== null) {
this._private__rightStubCell.removeChild(this._private__rightStub._internal_getElement());
this._private__rightStub._internal_destroy();
this._private__rightStub = null;
}
const rendererOptionsProvider = this._private__chart._internal_model()._internal_rendererOptionsProvider();
const params = {
_internal_rendererOptionsProvider: rendererOptionsProvider,
};
const borderVisibleGetter = () => {
return options.leftPriceScale.borderVisible && model._internal_timeScale()._internal_options().borderVisible;
};
const bottomColorGetter = () => model._internal_backgroundBottomColor();
if (options.leftPriceScale.visible && this._private__leftStub === null) {
this._private__leftStub = new PriceAxisStub('left', options, params, borderVisibleGetter, bottomColorGetter);
this._private__leftStubCell.appendChild(this._private__leftStub._internal_getElement());
}
if (options.rightPriceScale.visible && this._private__rightStub === null) {
this._private__rightStub = new PriceAxisStub('right', options, params, borderVisibleGetter, bottomColorGetter);
this._private__rightStubCell.appendChild(this._private__rightStub._internal_getElement());
}
}
}
const windowsChrome = isChromiumBased() && isWindows();
class ChartWidget {
constructor(container, options, horzScaleBehavior) {
this._private__paneWidgets = [];
this._private__paneSeparators = [];
this._private__drawRafId = 0;
this._private__height = 0;
this._private__width = 0;
this._private__leftPriceAxisWidth = 0;
this._private__rightPriceAxisWidth = 0;
this._private__invalidateMask = null;
this._private__drawPlanned = false;
this._private__clicked = new Delegate();
this._private__dblClicked = new Delegate();
this._private__crosshairMoved = new Delegate();
this._private__observer = null;
this._private__cursorStyleOverride = null;
this._private__container = container;
this._private__options = options;
this._private__horzScaleBehavior = horzScaleBehavior;
this._private__element = document.createElement('div');
this._private__element.classList.add('tv-lightweight-charts');
this._private__element.style.overflow = 'hidden';
this._private__element.style.direction = 'ltr';
this._private__element.style.width = '100%';
this._private__element.style.height = '100%';
disableSelection(this._private__element);
this._private__tableElement = document.createElement('table');
this._private__tableElement.setAttribute('cellspacing', '0');
this._private__element.appendChild(this._private__tableElement);
this._private__onWheelBound = this._private__onMousewheel.bind(this);
if (shouldSubscribeMouseWheel(this._private__options)) {
this._private__setMouseWheelEventListener(true);
}
this._private__model = new ChartModel(this._private__invalidateHandler.bind(this), this._private__options, horzScaleBehavior);
this._internal_model()._internal_crosshairMoved()._internal_subscribe(this._private__onPaneWidgetCrosshairMoved.bind(this), this);
this._private__timeAxisWidget = new TimeAxisWidget(this, this._private__horzScaleBehavior);
this._private__tableElement.appendChild(this._private__timeAxisWidget._internal_getElement());
const usedObserver = options.autoSize && this._private__installObserver();
// observer could not fire event immediately for some cases
// so we have to set initial size manually
let width = this._private__options.width;
let height = this._private__options.height;
// ignore width/height options if observer has actually been used
// however respect options if installing resize observer failed
if (usedObserver || width === 0 || height === 0) {
const containerRect = container.getBoundingClientRect();
width = width || containerRect.width;
height = height || containerRect.height;
}
// BEWARE: resize must be called BEFORE _syncGuiWithModel (in constructor only)
// or after but with adjustSize to properly update time scale
this._internal_resize(width, height);
this._private__syncGuiWithModel();
container.appendChild(this._private__element);
this._private__updateTimeAxisVisibility();
this._private__model._internal_timeScale()._internal_optionsApplied()._internal_subscribe(this._private__model._internal_fullUpdate.bind(this._private__model), this);
this._private__model._internal_priceScalesOptionsChanged()._internal_subscribe(this._private__model._internal_fullUpdate.bind(this._private__model), this);
}
_internal_model() {
return this._private__model;
}
_internal_options() {
return this._private__options;
}
_internal_paneWidgets() {
return this._private__paneWidgets;
}
_internal_timeAxisWidget() {
return this._private__timeAxisWidget;
}
_internal_destroy() {
this._private__setMouseWheelEventListener(false);
if (this._private__drawRafId !== 0) {
window.cancelAnimationFrame(this._private__drawRafId);
}
this._private__model._internal_crosshairMoved()._internal_unsubscribeAll(this);
this._private__model._internal_timeScale()._internal_optionsApplied()._internal_unsubscribeAll(this);
this._private__model._internal_priceScalesOptionsChanged()._internal_unsubscribeAll(this);
this._private__model._internal_destroy();
for (const paneWidget of this._private__paneWidgets) {
this._private__tableElement.removeChild(paneWidget._internal_getElement());
paneWidget._internal_clicked()._internal_unsubscribeAll(this);
paneWidget._internal_dblClicked()._internal_unsubscribeAll(this);
paneWidget._internal_destroy();
}
this._private__paneWidgets = [];
for (const paneSeparator of this._private__paneSeparators) {
this._private__destroySeparator(paneSeparator);
}
this._private__paneSeparators = [];
ensureNotNull(this._private__timeAxisWidget)._internal_destroy();
if (this._private__element.parentElement !== null) {
this._private__element.parentElement.removeChild(this._private__element);
}
this._private__crosshairMoved._internal_destroy();
this._private__clicked._internal_destroy();
this._private__dblClicked._internal_destroy();
this._private__uninstallObserver();
}
_internal_resize(width, height, forceRepaint = false) {
if (this._private__height === height && this._private__width === width) {
return;
}
const sizeHint = suggestChartSize(size$1({ width, height }));
this._private__height = sizeHint.height;
this._private__width = sizeHint.width;
const heightStr = this._private__height + 'px';
const widthStr = this._private__width + 'px';
// When autoSize is enabled, keep outer element at 100%/100% to avoid jitter
// from pixel rounding differences between container and chart size
if (!this._internal_autoSizeActive()) {
ensureNotNull(this._private__element).style.height = heightStr;
ensureNotNull(this._private__element).style.width = widthStr;
}
this._private__tableElement.style.height = heightStr;
this._private__tableElement.style.width = widthStr;
if (forceRepaint) {
// Cancel any pending animation frame since we're doing a synchronous paint
if (this._private__drawRafId !== 0) {
window.cancelAnimationFrame(this._private__drawRafId);
this._private__drawRafId = 0;
}
this._private__drawPlanned = false;
// Merge any pending invalidations and clear them
const mask = InvalidateMask._internal_full();
if (this._private__invalidateMask !== null) {
mask._internal_merge(this._private__invalidateMask);
this._private__invalidateMask = null;
}
this._private__drawImpl(mask, performance.now());
}
else {
this._private__model._internal_fullUpdate();
}
}
_internal_paint(invalidateMask) {
if (invalidateMask === undefined) {
invalidateMask = InvalidateMask._internal_full();
}
for (let i = 0; i < this._private__paneWidgets.length; i++) {
this._private__paneWidgets[i]._internal_paint(invalidateMask._internal_invalidateForPane(i)._internal_level);
}
if (this._private__options.timeScale.visible) {
this._private__timeAxisWidget._internal_paint(invalidateMask._internal_fullInvalidation());
}
}
_internal_applyOptions(options) {
const currentlyHasMouseWheelListener = shouldSubscribeMouseWheel(this._private__options);
// we don't need to merge options here because it's done in chart model
// and since both model and widget share the same object it will be done automatically for widget as well
// not ideal solution for sure, but it work's for now ¯\_(ツ)_/¯
this._private__model._internal_applyOptions(options);
const shouldHaveMouseWheelListener = shouldSubscribeMouseWheel(this._private__options);
if (shouldHaveMouseWheelListener !== currentlyHasMouseWheelListener) {
this._private__setMouseWheelEventListener(shouldHaveMouseWheelListener);
}
if (options['layout']?.panes) {
this._private__applyPanesOptions();
}
this._private__updateTimeAxisVisibility();
this._private__applyAutoSizeOptions(options);
}
_internal_clicked() {
return this._private__clicked;
}
_internal_dblClicked() {
return this._private__dblClicked;
}
_internal_crosshairMoved() {
return this._private__crosshairMoved;
}
_internal_takeScreenshot(addTopLayer = false) {
if (this._private__invalidateMask !== null) {
this._private__drawImpl(this._private__invalidateMask, performance.now());
this._private__invalidateMask = null;
}
const screeshotBitmapSize = this._private__traverseLayout(null);
const screenshotCanvas = document.createElement('canvas');
screenshotCanvas.width = screeshotBitmapSize.width;
screenshotCanvas.height = screeshotBitmapSize.height;
const ctx = ensureNotNull(screenshotCanvas.getContext('2d'));
this._private__traverseLayout(ctx, addTopLayer);
return screenshotCanvas;
}
_internal_getPriceAxisWidth(position) {
if (position === "left" /* DefaultPriceScaleId.Left */ && !this._private__isLeftAxisVisible()) {
return 0;
}
if (position === "right" /* DefaultPriceScaleId.Right */ && !this._private__isRightAxisVisible()) {
return 0;
}
if (this._private__paneWidgets.length === 0) {
return 0;
}
// we don't need to worry about exactly pane widget here
// because all pane widgets have the same width of price axis widget
// see _adjustSizeImpl
const priceAxisWidget = position === "left" /* DefaultPriceScaleId.Left */
? this._private__paneWidgets[0]._internal_leftPriceAxisWidget()
: this._private__paneWidgets[0]._internal_rightPriceAxisWidget();
return ensureNotNull(priceAxisWidget)._internal_getWidth();
}
_internal_autoSizeActive() {
return this._private__options.autoSize && this._private__observer !== null;
}
_internal_element() {
return this._private__element;
}
_internal_setCursorStyle(style) {
this._private__cursorStyleOverride = style;
if (this._private__cursorStyleOverride) {
this._internal_element().style.setProperty('cursor', style);
}
else {
this._internal_element().style.removeProperty('cursor');
}
}
_internal_getCursorOverrideStyle() {
return this._private__cursorStyleOverride;
}
_internal_paneSize(paneIndex) {
return ensureDefined(this._private__paneWidgets[paneIndex])._internal_getSize();
}
_private__applyPanesOptions() {
this._private__paneSeparators.forEach((separator) => {
separator._internal_update();
});
}
// eslint-disable-next-line complexity
_private__applyAutoSizeOptions(options) {
if (options.autoSize === undefined && this._private__observer && (options.width !== undefined || options.height !== undefined)) {
warn(`You should turn autoSize off explicitly before specifying sizes; try adding options.autoSize: false to new options`);
return;
}
if (options.autoSize && !this._private__observer) {
// installing observer will override resize if successful
this._private__installObserver();
}
if (options.autoSize === false && this._private__observer !== null) {
this._private__uninstallObserver();
}
if (!options.autoSize && (options.width !== undefined || options.height !== undefined)) {
this._internal_resize(options.width || this._private__width, options.height || this._private__height);
}
}
/**
* Traverses the widget's layout (pane and axis child widgets),
* draws the screenshot (if rendering context is passed) and returns the screenshot bitmap size
*
* @param ctx - if passed, used to draw the screenshot of widget
* @param addTopLayer - if true, the top layer with crosshair and primitives will be drawn
* @returns screenshot bitmap size
*/
_private__traverseLayout(ctx, addTopLayer) {
let totalWidth = 0;
let totalHeight = 0;
const firstPane = this._private__paneWidgets[0];
const drawPriceAxises = (position, targetX) => {
let targetY = 0;
for (let paneIndex = 0; paneIndex < this._private__paneWidgets.length; paneIndex++) {
const paneWidget = this._private__paneWidgets[paneIndex];
const priceAxisWidget = ensureNotNull(position === 'left' ? paneWidget._internal_leftPriceAxisWidget() : paneWidget._internal_rightPriceAxisWidget());
const bitmapSize = priceAxisWidget._internal_getBitmapSize();
if (ctx !== null) {
priceAxisWidget._internal_drawBitmap(ctx, targetX, targetY, addTopLayer);
}
targetY += bitmapSize.height;
if (paneIndex < this._private__paneWidgets.length - 1) {
const separator = this._private__paneSeparators[paneIndex];
const separatorBitmapSize = separator._internal_getBitmapSize();
if (ctx !== null) {
separator._internal_drawBitmap(ctx, targetX, targetY);
}
targetY += separatorBitmapSize.height;
}
}
};
// draw left price scale if exists
if (this._private__isLeftAxisVisible()) {
drawPriceAxises('left', 0);
const leftAxisBitmapWidth = ensureNotNull(firstPane._internal_leftPriceAxisWidget())._internal_getBitmapSize().width;
totalWidth += leftAxisBitmapWidth;
}
for (let paneIndex = 0; paneIndex < this._private__paneWidgets.length; paneIndex++) {
const paneWidget = this._private__paneWidgets[paneIndex];
const bitmapSize = paneWidget._internal_getBitmapSize();
if (ctx !== null) {
paneWidget._internal_drawBitmap(ctx, totalWidth, totalHeight, addTopLayer);
}
totalHeight += bitmapSize.height;
if (paneIndex < this._private__paneWidgets.length - 1) {
const separator = this._private__paneSeparators[paneIndex];
const separatorBitmapSize = separator._internal_getBitmapSize();
if (ctx !== null) {
separator._internal_drawBitmap(ctx, totalWidth, totalHeight);
}
totalHeight += separatorBitmapSize.height;
}
}
const firstPaneBitmapWidth = firstPane._internal_getBitmapSize().width;
totalWidth += firstPaneBitmapWidth;
// draw right price scale if exists
if (this._private__isRightAxisVisible()) {
drawPriceAxises('right', totalWidth);
const rightAxisBitmapWidth = ensureNotNull(firstPane._internal_rightPriceAxisWidget())._internal_getBitmapSize().width;
totalWidth += rightAxisBitmapWidth;
}
const drawStub = (position, targetX, targetY) => {
const stub = ensureNotNull(position === 'left' ? this._private__timeAxisWidget._internal_leftStub() : this._private__timeAxisWidget._internal_rightStub());
stub._internal_drawBitmap(ensureNotNull(ctx), targetX, targetY);
};
// draw time scale and stubs
if (this._private__options.timeScale.visible) {
const timeAxisBitmapSize = this._private__timeAxisWidget._internal_getBitmapSize();
if (ctx !== null) {
let targetX = 0;
if (this._private__isLeftAxisVisible()) {
drawStub('left', targetX, totalHeight);
targetX = ensureNotNull(firstPane._internal_leftPriceAxisWidget())._internal_getBitmapSize().width;
}
this._private__timeAxisWidget._internal_drawBitmap(ctx, targetX, totalHeight, addTopLayer);
targetX += timeAxisBitmapSize.width;
if (this._private__isRightAxisVisible()) {
drawStub('right', targetX, totalHeight);
}
}
totalHeight += timeAxisBitmapSize.height;
}
return size$1({
width: totalWidth,
height: totalHeight,
});
}
// eslint-disable-next-line complexity
_private__adjustSizeImpl() {
let totalStretch = 0;
let leftPriceAxisWidth = 0;
let rightPriceAxisWidth = 0;
for (const paneWidget of this._private__paneWidgets) {
if (this._private__isLeftAxisVisible()) {
leftPriceAxisWidth = Math.max(leftPriceAxisWidth, ensureNotNull(paneWidget._internal_leftPriceAxisWidget())._internal_optimalWidth(), this._private__options.leftPriceScale.minimumWidth);
}
if (this._private__isRightAxisVisible()) {
rightPriceAxisWidth = Math.max(rightPriceAxisWidth, ensureNotNull(paneWidget._internal_rightPriceAxisWidget())._internal_optimalWidth(), this._private__options.rightPriceScale.minimumWidth);
}
totalStretch += paneWidget._internal_stretchFactor();
}
leftPriceAxisWidth = suggestPriceScaleWidth(leftPriceAxisWidth);
rightPriceAxisWidth = suggestPriceScaleWidth(rightPriceAxisWidth);
const width = this._private__width;
const height = this._private__height;
const paneWidth = Math.max(width - leftPriceAxisWidth - rightPriceAxisWidth, 0);
const separatorCount = this._private__paneSeparators.length;
const separatorHeight = 1 /* SeparatorConstants.SeparatorHeight */;
const separatorsHeight = separatorHeight * separatorCount;
const timeAxisVisible = this._private__options.timeScale.visible;
let timeAxisHeight = timeAxisVisible ? Math.max(this._private__timeAxisWidget._internal_optimalHeight(), this._private__options.timeScale.minimumHeight) : 0;
timeAxisHeight = suggestTimeScaleHeight(timeAxisHeight);
const otherWidgetHeight = separatorsHeight + timeAxisHeight;
const totalPaneHeight = height < otherWidgetHeight ? 0 : height - otherWidgetHeight;
const stretchPixels = totalPaneHeight / totalStretch;
let accumulatedHeight = 0;
const pixelRatio = window.devicePixelRatio || 1;
for (let paneIndex = 0; paneIndex < this._private__paneWidgets.length; ++paneIndex) {
const paneWidget = this._private__paneWidgets[paneIndex];
paneWidget._internal_setState(this._private__model._internal_panes()[paneIndex]);
let paneHeight = 0;
let calculatePaneHeight = 0;
if (paneIndex === this._private__paneWidgets.length - 1) {
calculatePaneHeight = Math.ceil((totalPaneHeight - accumulatedHeight) * pixelRatio) / pixelRatio;
}
else {
calculatePaneHeight = Math.round(paneWidget._internal_stretchFactor() * stretchPixels * pixelRatio) / pixelRatio;
}
paneHeight = Math.max(calculatePaneHeight, 2);
accumulatedHeight += paneHeight;
paneWidget._internal_setSize(size$1({ width: paneWidth, height: paneHeight }));
if (this._private__isLeftAxisVisible()) {
paneWidget._internal_setPriceAxisSize(leftPriceAxisWidth, 'left');
}
if (this._private__isRightAxisVisible()) {
paneWidget._internal_setPriceAxisSize(rightPriceAxisWidth, 'right');
}
if (paneWidget._internal_state()) {
this._private__model._internal_setPaneHeight(paneWidget._internal_state(), paneHeight);
}
}
this._private__timeAxisWidget._internal_setSizes(size$1({ width: timeAxisVisible ? paneWidth : 0, height: timeAxisHeight }), timeAxisVisible ? leftPriceAxisWidth : 0, timeAxisVisible ? rightPriceAxisWidth : 0);
this._private__model._internal_setWidth(paneWidth);
if (this._private__leftPriceAxisWidth !== leftPriceAxisWidth) {
this._private__leftPriceAxisWidth = leftPriceAxisWidth;
}
if (this._private__rightPriceAxisWidth !== rightPriceAxisWidth) {
this._private__rightPriceAxisWidth = rightPriceAxisWidth;
}
}
_private__setMouseWheelEventListener(add) {
if (add) {
this._private__element.addEventListener('wheel', this._private__onWheelBound, { passive: false });
return;
}
this._private__element.removeEventListener('wheel', this._private__onWheelBound);
}
_private__determineWheelSpeedAdjustment(event) {
switch (event.deltaMode) {
case event.DOM_DELTA_PAGE:
// one screen at time scroll mode
return 120;
case event.DOM_DELTA_LINE:
// one line at time scroll mode
return 32;
}
if (!windowsChrome) {
return 1;
}
// Chromium on Windows has a bug where the scroll speed isn't correctly
// adjusted for high density displays. We need to correct for this so that
// scroll speed is consistent between browsers.
// https://bugs.chromium.org/p/chromium/issues/detail?id=1001735
// https://bugs.chromium.org/p/chromium/issues/detail?id=1207308
return (1 / window.devicePixelRatio);
}
_private__onMousewheel(event) {
if ((event.deltaX === 0 || !this._private__options['handleScroll'].mouseWheel) &&
(event.deltaY === 0 || !this._private__options['handleScale'].mouseWheel)) {
return;
}
const scrollSpeedAdjustment = this._private__determineWheelSpeedAdjustment(event);
const deltaX = scrollSpeedAdjustment * event.deltaX / 100;
const deltaY = -(scrollSpeedAdjustment * event.deltaY / 100);
if (event.cancelable) {
event.preventDefault();
}
if (deltaY !== 0 && this._private__options['handleScale'].mouseWheel) {
const zoomScale = Math.sign(deltaY) * Math.min(1, Math.abs(deltaY));
const scrollPosition = event.clientX - this._private__element.getBoundingClientRect().left;
this._internal_model()._internal_zoomTime(scrollPosition, zoomScale);
}
if (deltaX !== 0 && this._private__options['handleScroll'].mouseWheel) {
this._internal_model()._internal_scrollChart(deltaX * -80); // 80 is a made up coefficient, and minus is for the "natural" scroll
}
}
_private__drawImpl(invalidateMask, time) {
const invalidationType = invalidateMask._internal_fullInvalidation();
// actions for full invalidation ONLY (not shared with light)
if (invalidationType === 3 /* InvalidationLevel.Full */) {
this._private__updateGui();
}
// light or full invalidate actions
if (invalidationType === 3 /* InvalidationLevel.Full */ ||
invalidationType === 2 /* InvalidationLevel.Light */) {
this._private__applyMomentaryAutoScale(invalidateMask);
this._private__applyTimeScaleInvalidations(invalidateMask, time);
this._private__timeAxisWidget._internal_update();
this._private__paneWidgets.forEach((pane) => {
pane._internal_updatePriceAxisWidgets();
});
// In the case a full invalidation has been postponed during the draw, reapply
// the timescale invalidations. A full invalidation would mean there is a change
// in the timescale width (caused by price scale changes) that needs to be drawn
// right away to avoid flickering.
if (this._private__invalidateMask?._internal_fullInvalidation() === 3 /* InvalidationLevel.Full */) {
this._private__invalidateMask._internal_merge(invalidateMask);
this._private__updateGui();
this._private__applyMomentaryAutoScale(this._private__invalidateMask);
this._private__applyTimeScaleInvalidations(this._private__invalidateMask, time);
invalidateMask = this._private__invalidateMask;
this._private__invalidateMask = null;
}
}
this._internal_paint(invalidateMask);
}
_private__applyTimeScaleInvalidations(invalidateMask, time) {
for (const tsInvalidation of invalidateMask._internal_timeScaleInvalidations()) {
this._private__applyTimeScaleInvalidation(tsInvalidation, time);
}
}
_private__applyMomentaryAutoScale(invalidateMask) {
const panes = this._private__model._internal_panes();
for (let i = 0; i < panes.length; i++) {
if (invalidateMask._internal_invalidateForPane(i)._internal_autoScale) {
panes[i]._internal_momentaryAutoScale();
}
}
}
_private__applyTimeScaleInvalidation(invalidation, time) {
const timeScale = this._private__model._internal_timeScale();
switch (invalidation._internal_type) {
case 0 /* TimeScaleInvalidationType.FitContent */:
timeScale._internal_fitContent();
break;
case 1 /* TimeScaleInvalidationType.ApplyRange */:
timeScale._internal_setLogicalRange(invalidation._internal_value);
break;
case 2 /* TimeScaleInvalidationType.ApplyBarSpacing */:
timeScale._internal_setBarSpacing(invalidation._internal_value);
break;
case 3 /* TimeScaleInvalidationType.ApplyRightOffset */:
timeScale._internal_setRightOffset(invalidation._internal_value);
break;
case 4 /* TimeScaleInvalidationType.Reset */:
timeScale._internal_restoreDefault();
break;
case 5 /* TimeScaleInvalidationType.Animation */:
if (!invalidation._internal_value._internal_finished(time)) {
timeScale._internal_setRightOffset(invalidation._internal_value._internal_getPosition(time));
}
break;
}
}
_private__invalidateHandler(invalidateMask) {
if (this._private__invalidateMask !== null) {
this._private__invalidateMask._internal_merge(invalidateMask);
}
else {
this._private__invalidateMask = invalidateMask;
}
if (!this._private__drawPlanned) {
this._private__drawPlanned = true;
this._private__drawRafId = window.requestAnimationFrame((time) => {
this._private__drawPlanned = false;
this._private__drawRafId = 0;
if (this._private__invalidateMask !== null) {
const mask = this._private__invalidateMask;
this._private__invalidateMask = null;
this._private__drawImpl(mask, time);
for (const tsInvalidation of mask._internal_timeScaleInvalidations()) {
if (tsInvalidation._internal_type === 5 /* TimeScaleInvalidationType.Animation */ && !tsInvalidation._internal_value._internal_finished(time)) {
this._internal_model()._internal_setTimeScaleAnimation(tsInvalidation._internal_value);
break;
}
}
}
});
}
}
_private__updateGui() {
this._private__syncGuiWithModel();
}
_private__destroySeparator(separator) {
this._private__tableElement.removeChild(separator._internal_getElement());
separator._internal_destroy();
}
_private__syncGuiWithModel() {
const panes = this._private__model._internal_panes();
const targetPaneWidgetsCount = panes.length;
const actualPaneWidgetsCount = this._private__paneWidgets.length;
// Remove (if needed) pane widgets and separators
for (let i = targetPaneWidgetsCount; i < actualPaneWidgetsCount; i++) {
const paneWidget = ensureDefined(this._private__paneWidgets.pop());
this._private__tableElement.removeChild(paneWidget._internal_getElement());
paneWidget._internal_clicked()._internal_unsubscribeAll(this);
paneWidget._internal_dblClicked()._internal_unsubscribeAll(this);
paneWidget._internal_destroy();
const paneSeparator = this._private__paneSeparators.pop();
if (paneSeparator !== undefined) {
this._private__destroySeparator(paneSeparator);
}
}
// Create (if needed) new pane widgets and separators
for (let i = actualPaneWidgetsCount; i < targetPaneWidgetsCount; i++) {
const paneWidget = new PaneWidget(this, panes[i]);
paneWidget._internal_clicked()._internal_subscribe(this._private__onPaneWidgetClicked.bind(this, paneWidget), this);
paneWidget._internal_dblClicked()._internal_subscribe(this._private__onPaneWidgetDblClicked.bind(this, paneWidget), this);
this._private__paneWidgets.push(paneWidget);
// create and insert separator
if (i > 0) {
const paneSeparator = new PaneSeparator(this, i - 1, i);
this._private__paneSeparators.push(paneSeparator);
this._private__tableElement.insertBefore(paneSeparator._internal_getElement(), this._private__timeAxisWidget._internal_getElement());
}
// insert paneWidget
this._private__tableElement.insertBefore(paneWidget._internal_getElement(), this._private__timeAxisWidget._internal_getElement());
}
for (let i = 0; i < targetPaneWidgetsCount; i++) {
const state = panes[i];
const paneWidget = this._private__paneWidgets[i];
if (paneWidget._internal_state() !== state) {
paneWidget._internal_setState(state);
}
else {
paneWidget._internal_updatePriceAxisWidgetsStates();
}
}
this._private__updateTimeAxisVisibility();
this._private__adjustSizeImpl();
}
_private__getMouseEventParamsImpl(index, point, event, pane) {
const seriesData = new Map();
if (index !== null) {
const serieses = this._private__model._internal_serieses();
serieses.forEach((s) => {
// TODO: replace with search left
const data = s._internal_bars()._internal_search(index);
if (data !== null) {
seriesData.set(s, data);
}
});
}
let clientTime;
if (index !== null) {
const timePoint = this._private__model._internal_timeScale()._internal_indexToTimeScalePoint(index)?.originalTime;
if (timePoint !== undefined) {
clientTime = timePoint;
}
}
const hoveredSource = this._internal_model()._internal_hoveredSource();
const paneIndex = this._private__getPaneIndex(pane);
const hoveredInfo = buildHoveredEventInfo(hoveredSource, paneIndex);
return {
_internal_originalTime: clientTime,
_internal_index: index ?? undefined,
_internal_point: point ?? undefined,
_internal_paneIndex: paneIndex !== -1 ? paneIndex : undefined,
_internal_hoveredSeries: hoveredInfo._internal_hoveredSeries,
_internal_seriesData: seriesData,
_internal_hoveredObject: hoveredInfo._internal_hoveredObject,
_internal_hoveredInfo: hoveredInfo._internal_hoveredInfo,
_internal_touchMouseEventData: event ?? undefined,
};
}
_private__getPaneIndex(pane) {
let paneIndex = -1;
if (pane) {
paneIndex = this._private__paneWidgets.indexOf(pane);
}
else {
const crosshairPane = this._internal_model()._internal_crosshairSource()._internal_pane();
if (crosshairPane !== null) {
paneIndex = this._internal_model()._internal_panes().indexOf(crosshairPane);
}
}
return paneIndex;
}
_private__onPaneWidgetClicked(pane, time, point, event) {
this._private__clicked._internal_fire(() => this._private__getMouseEventParamsImpl(time, point, event, pane));
}
_private__onPaneWidgetDblClicked(pane, time, point, event) {
this._private__dblClicked._internal_fire(() => this._private__getMouseEventParamsImpl(time, point, event, pane));
}
_private__onPaneWidgetCrosshairMoved(time, point, event) {
this._internal_setCursorStyle(this._internal_model()._internal_hoveredSource()?._internal_cursorStyle ?? null);
this._private__crosshairMoved._internal_fire(() => this._private__getMouseEventParamsImpl(time, point, event));
}
_private__updateTimeAxisVisibility() {
const display = this._private__options.timeScale.visible ? '' : 'none';
this._private__timeAxisWidget._internal_getElement().style.display = display;
}
_private__isLeftAxisVisible() {
return this._private__paneWidgets[0]._internal_state()._internal_leftPriceScale()._internal_options().visible;
}
_private__isRightAxisVisible() {
return this._private__paneWidgets[0]._internal_state()._internal_rightPriceScale()._internal_options().visible;
}
_private__installObserver() {
// eslint-disable-next-line no-restricted-syntax
if (!('ResizeObserver' in window)) {
warn('Options contains "autoSize" flag, but the browser does not support ResizeObserver feature. Please provide polyfill.');
return false;
}
else {
this._private__observer = new ResizeObserver((entries) => {
// There is no need to check if entry.target === this._container since there is only
// a single element being observed.
// and we want to use the last entry (if multiple) because it would be most up to date
// (since the browser may batch multiple updates).
const containerEntry = entries[entries.length - 1];
if (!containerEntry) {
// this may be undefined if the entries array was empty.
return;
}
const newW = containerEntry.contentRect.width;
const newH = containerEntry.contentRect.height;
// Use forceRepaint=true to paint synchronously within this callback,
// rather than scheduling to the next animation frame. This prevents
// the visual "jitter" that occurs when the container has resized but
// the canvas hasn't repainted yet.
this._internal_resize(newW, newH, true);
});
this._private__observer.observe(this._private__container, { box: 'border-box' });
return true;
}
}
_private__uninstallObserver() {
if (this._private__observer !== null) {
this._private__observer.disconnect();
}
this._private__observer = null;
}
}
function disableSelection(element) {
element.style.userSelect = 'none';
// eslint-disable-next-line deprecation/deprecation
element.style.webkitUserSelect = 'none';
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-member-access
element.style.msUserSelect = 'none';
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-member-access
element.style.MozUserSelect = 'none';
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-member-access
element.style.webkitTapHighlightColor = 'transparent';
}
function shouldSubscribeMouseWheel(options) {
return Boolean(options['handleScroll'].mouseWheel || options['handleScale'].mouseWheel);
}
function isWhitespaceData(data) {
return data.open === undefined && data.value === undefined;
}
function isFulfilledData(data) {
return isFulfilledBarData(data) || isFulfilledLineData(data);
}
function isFulfilledBarData(data) {
return data.open !== undefined;
}
function isFulfilledLineData(data) {
return data.value !== undefined;
}
function getColoredLineBasedSeriesPlotRow(time, index, item, originalTime) {
const val = item.value;
const res = { _internal_index: index, _internal_time: time, _internal_value: [val, val, val, val], _internal_originalTime: originalTime };
if (item.color !== undefined) {
res._internal_color = item.color;
}
return res;
}
function getAreaSeriesPlotRow(time, index, item, originalTime) {
const val = item.value;
const res = { _internal_index: index, _internal_time: time, _internal_value: [val, val, val, val], _internal_originalTime: originalTime };
if (item.lineColor !== undefined) {
res._internal_lineColor = item.lineColor;
}
if (item.topColor !== undefined) {
res._internal_topColor = item.topColor;
}
if (item.bottomColor !== undefined) {
res._internal_bottomColor = item.bottomColor;
}
return res;
}
function getBaselineSeriesPlotRow(time, index, item, originalTime) {
const val = item.value;
const res = { _internal_index: index, _internal_time: time, _internal_value: [val, val, val, val], _internal_originalTime: originalTime };
if (item.topLineColor !== undefined) {
res._internal_topLineColor = item.topLineColor;
}
if (item.bottomLineColor !== undefined) {
res._internal_bottomLineColor = item.bottomLineColor;
}
if (item.topFillColor1 !== undefined) {
res._internal_topFillColor1 = item.topFillColor1;
}
if (item.topFillColor2 !== undefined) {
res._internal_topFillColor2 = item.topFillColor2;
}
if (item.bottomFillColor1 !== undefined) {
res._internal_bottomFillColor1 = item.bottomFillColor1;
}
if (item.bottomFillColor2 !== undefined) {
res._internal_bottomFillColor2 = item.bottomFillColor2;
}
return res;
}
function getBarSeriesPlotRow(time, index, item, originalTime) {
const res = { _internal_index: index, _internal_time: time, _internal_value: [item.open, item.high, item.low, item.close], _internal_originalTime: originalTime };
if (item.color !== undefined) {
res._internal_color = item.color;
}
return res;
}
function getCandlestickSeriesPlotRow(time, index, item, originalTime) {
const res = { _internal_index: index, _internal_time: time, _internal_value: [item.open, item.high, item.low, item.close], _internal_originalTime: originalTime };
if (item.color !== undefined) {
res._internal_color = item.color;
}
if (item.borderColor !== undefined) {
res._internal_borderColor = item.borderColor;
}
if (item.wickColor !== undefined) {
res._internal_wickColor = item.wickColor;
}
return res;
}
function getCustomSeriesPlotRow(time, index, item, originalTime, dataToPlotRow) {
const values = ensureDefined(dataToPlotRow)(item);
const max = Math.max(...values);
const min = Math.min(...values);
const last = values[values.length - 1];
const value = [last, max, min, last];
const { time: excludedTime, color, ...data } = item;
return { _internal_index: index, _internal_time: time, _internal_value: value, _internal_originalTime: originalTime, _internal_data: data, _internal_color: color };
}
function isSeriesPlotRow(row) {
return row._internal_value !== undefined;
}
function wrapCustomValues(plotRow, bar) {
if (bar.customValues !== undefined) {
plotRow._internal_customValues = bar.customValues;
}
return plotRow;
}
function isWhitespaceDataWithCustomCheck(bar, customIsWhitespace) {
if (customIsWhitespace) {
return customIsWhitespace(bar);
}
return isWhitespaceData(bar);
}
function wrapWhitespaceData(createPlotRowFn) {
return (time, index, bar, originalTime, dataToPlotRow, customIsWhitespace) => {
if (isWhitespaceDataWithCustomCheck(bar, customIsWhitespace)) {
return wrapCustomValues({ _internal_time: time, _internal_index: index, _internal_originalTime: originalTime }, bar);
}
return wrapCustomValues(createPlotRowFn(time, index, bar, originalTime, dataToPlotRow), bar);
};
}
function getSeriesPlotRowCreator(seriesType) {
const seriesPlotRowFnMap = {
Candlestick: wrapWhitespaceData(getCandlestickSeriesPlotRow),
Bar: wrapWhitespaceData(getBarSeriesPlotRow),
Area: wrapWhitespaceData(getAreaSeriesPlotRow),
Baseline: wrapWhitespaceData(getBaselineSeriesPlotRow),
Histogram: wrapWhitespaceData(getColoredLineBasedSeriesPlotRow),
Line: wrapWhitespaceData(getColoredLineBasedSeriesPlotRow),
Custom: wrapWhitespaceData(getCustomSeriesPlotRow),
};
return seriesPlotRowFnMap[seriesType];
}
///
function createEmptyTimePointData(timePoint) {
return { _internal_index: 0, _internal_mapping: new Map(), _internal_timePoint: timePoint };
}
function seriesRowsFirstAndLastTime(seriesRows, bh) {
if (seriesRows === undefined || seriesRows.length === 0) {
return undefined;
}
return {
_internal_firstTime: bh.key(seriesRows[0]._internal_time),
_internal_lastTime: bh.key(seriesRows[seriesRows.length - 1]._internal_time),
};
}
function seriesUpdateInfo(seriesRows, prevSeriesRows, bh) {
const firstAndLastTime = seriesRowsFirstAndLastTime(seriesRows, bh);
const prevFirstAndLastTime = seriesRowsFirstAndLastTime(prevSeriesRows, bh);
if (firstAndLastTime !== undefined && prevFirstAndLastTime !== undefined) {
return {
_internal_historicalUpdate: false,
_internal_lastBarUpdatedOrNewBarsAddedToTheRight: firstAndLastTime._internal_lastTime >= prevFirstAndLastTime._internal_lastTime &&
firstAndLastTime._internal_firstTime >= prevFirstAndLastTime._internal_firstTime,
};
}
return undefined;
}
function timeScalePointTime(mergedPointData) {
let result;
mergedPointData.forEach((v) => {
if (result === undefined) {
result = v._internal_originalTime;
}
});
return ensureDefined(result);
}
function saveOriginalTime(data) {
if (data._internal_originalTime === undefined) {
data._internal_originalTime = data.time;
}
}
class DataLayer {
constructor(horzScaleBehavior) {
// note that _pointDataByTimePoint and _seriesRowsBySeries shares THE SAME objects in their values between each other
// it's just different kind of maps to make usages/perf better
this._private__pointDataByTimePoint = new Map();
this._private__seriesRowsBySeries = new Map();
this._private__seriesLastTimePoint = new Map();
// this is kind of "dest" values (in opposite to "source" ones) - we don't need to modify it manually, the only by calling _updateTimeScalePoints or updateSeriesData methods
this._private__sortedTimePoints = [];
this._private__horzScaleBehavior = horzScaleBehavior;
}
_internal_destroy() {
this._private__pointDataByTimePoint.clear();
this._private__seriesRowsBySeries.clear();
this._private__seriesLastTimePoint.clear();
this._private__sortedTimePoints = [];
}
_internal_setSeriesData(series, data) {
let needCleanupPoints = this._private__pointDataByTimePoint.size !== 0;
let isTimeScaleAffected = false;
// save previous series rows data before it's replaced inside this._setRowsToSeries
const prevSeriesRows = this._private__seriesRowsBySeries.get(series);
if (prevSeriesRows !== undefined) {
if (this._private__seriesRowsBySeries.size === 1) {
needCleanupPoints = false;
isTimeScaleAffected = true;
// perf optimization - if there is only 1 series, then we can just clear and fill everything from scratch
this._private__pointDataByTimePoint.clear();
}
else {
// perf optimization - actually we have to use this._pointDataByTimePoint for going through here
// but as soon as this._sortedTimePoints is just a different form of _pointDataByTimePoint we can use it as well
for (const point of this._private__sortedTimePoints) {
if (point.pointData._internal_mapping.delete(series)) {
isTimeScaleAffected = true;
}
}
}
}
let seriesRows = [];
if (data.length !== 0) {
const originalTimes = data.map((d) => d.time);
const timeConverter = this._private__horzScaleBehavior.createConverterToInternalObj(data);
const createPlotRow = getSeriesPlotRowCreator(series._internal_seriesType());
const dataToPlotRow = series._internal_customSeriesPlotValuesBuilder();
const customWhitespaceChecker = series._internal_customSeriesWhitespaceCheck();
seriesRows = data.map((item, index) => {
const time = timeConverter(item.time);
const horzItemKey = this._private__horzScaleBehavior.key(time);
let timePointData = this._private__pointDataByTimePoint.get(horzItemKey);
if (timePointData === undefined) {
// the indexes will be sync later
timePointData = createEmptyTimePointData(time);
this._private__pointDataByTimePoint.set(horzItemKey, timePointData);
isTimeScaleAffected = true;
}
const row = createPlotRow(time, timePointData._internal_index, item, originalTimes[index], dataToPlotRow, customWhitespaceChecker);
timePointData._internal_mapping.set(series, row);
return row;
});
}
if (needCleanupPoints) {
// we deleted the old data from mapping and added the new ones
// so there might be empty points now, let's remove them first
this._private__cleanupPointsData();
}
this._private__setRowsToSeries(series, seriesRows);
let firstChangedPointIndex = -1;
if (isTimeScaleAffected) {
// then generate the time scale points
// timeWeight will be updates in _updateTimeScalePoints later
const newTimeScalePoints = [];
this._private__pointDataByTimePoint.forEach((pointData) => {
newTimeScalePoints.push({
timeWeight: 0,
time: pointData._internal_timePoint,
pointData,
originalTime: timeScalePointTime(pointData._internal_mapping),
});
});
newTimeScalePoints.sort((t1, t2) => this._private__horzScaleBehavior.key(t1.time) - this._private__horzScaleBehavior.key(t2.time));
firstChangedPointIndex = this._private__replaceTimeScalePoints(newTimeScalePoints);
}
return this._private__getUpdateResponse(series, firstChangedPointIndex, seriesUpdateInfo(this._private__seriesRowsBySeries.get(series), prevSeriesRows, this._private__horzScaleBehavior));
}
_internal_removeSeries(series) {
return this._internal_setSeriesData(series, []);
}
// eslint-disable-next-line complexity
_internal_updateSeriesData(series, data, historicalUpdate) {
// check if conflation is enabled and this is a historical update
if (historicalUpdate && series._internal_isConflationEnabled()) {
throw new Error('Historical updates are not supported when conflation is enabled. Conflation requires data to be processed in order.');
}
const extendedData = data;
saveOriginalTime(extendedData);
// convertStringToBusinessDay(data);
this._private__horzScaleBehavior.preprocessData(data);
const timeConverter = this._private__horzScaleBehavior.createConverterToInternalObj([data]);
const time = timeConverter(data.time);
const lastSeriesTime = this._private__seriesLastTimePoint.get(series);
if (!historicalUpdate && lastSeriesTime !== undefined && this._private__horzScaleBehavior.key(time) < this._private__horzScaleBehavior.key(lastSeriesTime)) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
throw new Error(`Cannot update oldest data, last time=${lastSeriesTime}, new time=${time}`);
}
let pointDataAtTime = this._private__pointDataByTimePoint.get(this._private__horzScaleBehavior.key(time));
if (historicalUpdate && pointDataAtTime === undefined) {
throw new Error('Cannot update non-existing data point when historicalUpdate is true');
}
// if no point data found for the new data item
// that means that we need to update scale
const affectsTimeScale = pointDataAtTime === undefined;
if (pointDataAtTime === undefined) {
// the indexes will be sync later
pointDataAtTime = createEmptyTimePointData(time);
this._private__pointDataByTimePoint.set(this._private__horzScaleBehavior.key(time), pointDataAtTime);
}
const createPlotRow = getSeriesPlotRowCreator(series._internal_seriesType());
const dataToPlotRow = series._internal_customSeriesPlotValuesBuilder();
const customWhitespaceChecker = series._internal_customSeriesWhitespaceCheck();
const plotRow = createPlotRow(time, pointDataAtTime._internal_index, data, extendedData._internal_originalTime, dataToPlotRow, customWhitespaceChecker);
// For efficient conflation updates, check if this is just updating the last bar
const isLastBarUpdate = !historicalUpdate &&
!affectsTimeScale &&
lastSeriesTime !== undefined &&
this._private__horzScaleBehavior.key(time) === this._private__horzScaleBehavior.key(lastSeriesTime);
pointDataAtTime._internal_mapping.set(series, plotRow);
if (historicalUpdate) {
this._private__updateHistoricalSeriesRow(series, plotRow, pointDataAtTime._internal_index);
}
else if (isLastBarUpdate && series._internal_isConflationEnabled() && isSeriesPlotRow(plotRow)) {
series._internal_updateLastConflatedChunk(plotRow);
this._private__updateLastSeriesRow(series, plotRow);
}
else {
this._private__updateLastSeriesRow(series, plotRow);
}
const info = {
_internal_lastBarUpdatedOrNewBarsAddedToTheRight: isSeriesPlotRow(plotRow),
_internal_historicalUpdate: historicalUpdate,
};
// if point already exist on the time scale - we don't need to make a full update and just make an incremental one
if (!affectsTimeScale) {
return this._private__getUpdateResponse(series, -1, info);
}
const newPoint = {
timeWeight: 0,
time: pointDataAtTime._internal_timePoint,
pointData: pointDataAtTime,
originalTime: timeScalePointTime(pointDataAtTime._internal_mapping),
};
const insertIndex = lowerBound(this._private__sortedTimePoints, this._private__horzScaleBehavior.key(newPoint.time), (a, b) => this._private__horzScaleBehavior.key(a.time) < b);
// yes, I know that this array is readonly and this change is intended to make it performative
// we marked _sortedTimePoints array as readonly to avoid modifying this array anywhere else
// but this place is exceptional case due performance reasons, sorry
this._private__sortedTimePoints.splice(insertIndex, 0, newPoint);
for (let index = insertIndex; index < this._private__sortedTimePoints.length; ++index) {
assignIndexToPointData(this._private__sortedTimePoints[index].pointData, index);
}
this._private__horzScaleBehavior.fillWeightsForPoints(this._private__sortedTimePoints, insertIndex);
return this._private__getUpdateResponse(series, insertIndex, info);
}
_internal_popSeriesData(series, count) {
const seriesData = this._private__seriesRowsBySeries.get(series);
if (seriesData === undefined || count <= 0) {
return [[], this._private__emptyUpdateResponse()];
}
count = Math.min(count, seriesData.length);
const poppedData = seriesData.splice(-count).reverse();
if (seriesData.length === 0) {
this._private__seriesLastTimePoint.delete(series);
}
else {
this._private__seriesLastTimePoint.set(series, seriesData[seriesData.length - 1]._internal_time);
}
for (const data of poppedData) {
const pointData = this._private__pointDataByTimePoint.get(this._private__horzScaleBehavior.key(data._internal_time));
if (!pointData) {
continue;
}
pointData._internal_mapping.delete(series);
if (pointData._internal_mapping.size !== 0) {
continue;
}
// No remaining data at this timepoint therefore we need to remove the internal
// timescale point and update the indices.
this._private__pointDataByTimePoint.delete(this._private__horzScaleBehavior.key(pointData._internal_timePoint));
this._private__sortedTimePoints.splice(pointData._internal_index, 1);
for (let index = pointData._internal_index; index < this._private__sortedTimePoints.length; ++index) {
assignIndexToPointData(this._private__sortedTimePoints[index].pointData, index);
}
}
const info = {
_internal_historicalUpdate: false,
_internal_lastBarUpdatedOrNewBarsAddedToTheRight: false,
};
return [poppedData, this._private__getUpdateResponse(series, this._private__sortedTimePoints.length - 1, info)];
}
_private__updateLastSeriesRow(series, plotRow) {
let seriesData = this._private__seriesRowsBySeries.get(series);
if (seriesData === undefined) {
seriesData = [];
this._private__seriesRowsBySeries.set(series, seriesData);
}
const lastSeriesRow = seriesData.length !== 0 ? seriesData[seriesData.length - 1] : null;
if (lastSeriesRow === null || this._private__horzScaleBehavior.key(plotRow._internal_time) > this._private__horzScaleBehavior.key(lastSeriesRow._internal_time)) {
if (isSeriesPlotRow(plotRow)) {
seriesData.push(plotRow);
}
}
else {
if (isSeriesPlotRow(plotRow)) {
seriesData[seriesData.length - 1] = plotRow;
}
else {
seriesData.splice(-1, 1);
}
}
this._private__seriesLastTimePoint.set(series, plotRow._internal_time);
}
_private__updateHistoricalSeriesRow(series, plotRow, pointDataIndex) {
const seriesData = this._private__seriesRowsBySeries.get(series);
if (seriesData === undefined) {
return;
}
// binary search for actual index in array.
const index = lowerBound(seriesData, pointDataIndex, (row, currentIndex) => row._internal_index < currentIndex);
if (isSeriesPlotRow(plotRow)) {
seriesData[index] = plotRow;
}
else {
seriesData.splice(index, 1);
}
}
_private__setRowsToSeries(series, seriesRows) {
if (seriesRows.length !== 0) {
this._private__seriesRowsBySeries.set(series, seriesRows.filter(isSeriesPlotRow));
this._private__seriesLastTimePoint.set(series, seriesRows[seriesRows.length - 1]._internal_time);
}
else {
this._private__seriesRowsBySeries.delete(series);
this._private__seriesLastTimePoint.delete(series);
}
}
_private__cleanupPointsData() {
// let's treat all current points as "potentially removed"
// we could create an array with actually potentially removed points
// but most likely this array will be similar to _sortedTimePoints so let's avoid using additional memory
// note that we can use _sortedTimePoints here since a point might be removed only it was here previously
for (const point of this._private__sortedTimePoints) {
if (point.pointData._internal_mapping.size === 0) {
this._private__pointDataByTimePoint.delete(this._private__horzScaleBehavior.key(point.time));
}
}
}
/**
* Sets new time scale and make indexes valid for all series
*
* @returns The index of the first changed point or `-1` if there is no change.
*/
_private__replaceTimeScalePoints(newTimePoints) {
let firstChangedPointIndex = -1;
// search the first different point and "syncing" time weight by the way
for (let index = 0; index < this._private__sortedTimePoints.length && index < newTimePoints.length; ++index) {
const oldPoint = this._private__sortedTimePoints[index];
const newPoint = newTimePoints[index];
if (this._private__horzScaleBehavior.key(oldPoint.time) !== this._private__horzScaleBehavior.key(newPoint.time)) {
firstChangedPointIndex = index;
break;
}
// re-assign point's time weight for points if time is the same (and all prior times was the same)
newPoint.timeWeight = oldPoint.timeWeight;
assignIndexToPointData(newPoint.pointData, index);
}
if (firstChangedPointIndex === -1 && this._private__sortedTimePoints.length !== newTimePoints.length) {
// the common part of the prev and the new points are the same
// so the first changed point is the next after the common part
firstChangedPointIndex = Math.min(this._private__sortedTimePoints.length, newTimePoints.length);
}
if (firstChangedPointIndex === -1) {
// if no time scale changed, then do nothing
return -1;
}
// if time scale points are changed that means that we need to make full update to all series (with clearing points)
// but first we need to synchronize indexes and re-fill time weights
for (let index = firstChangedPointIndex; index < newTimePoints.length; ++index) {
assignIndexToPointData(newTimePoints[index].pointData, index);
}
// re-fill time weights for point after the first changed one
this._private__horzScaleBehavior.fillWeightsForPoints(newTimePoints, firstChangedPointIndex);
this._private__sortedTimePoints = newTimePoints;
return firstChangedPointIndex;
}
_private__getBaseIndex() {
if (this._private__seriesRowsBySeries.size === 0) {
// if we have no data then 'reset' the base index to null
return null;
}
let baseIndex = 0;
this._private__seriesRowsBySeries.forEach((data) => {
if (data.length !== 0) {
baseIndex = Math.max(baseIndex, data[data.length - 1]._internal_index);
}
});
return baseIndex;
}
_private__getUpdateResponse(updatedSeries, firstChangedPointIndex, info) {
const dataUpdateResponse = this._private__emptyUpdateResponse();
if (firstChangedPointIndex !== -1) {
// TODO: it's possible to make perf improvements by checking what series has data after firstChangedPointIndex
// but let's skip for now
this._private__seriesRowsBySeries.forEach((data, s) => {
dataUpdateResponse._internal_series.set(s, {
_internal_data: data,
_internal_info: s === updatedSeries ? info : undefined,
});
});
// if the series data was set to [] it will have already been removed from _seriesRowBySeries
// meaning the forEach above won't add the series to the data update response
// so we handle that case here
if (!this._private__seriesRowsBySeries.has(updatedSeries)) {
dataUpdateResponse._internal_series.set(updatedSeries, { _internal_data: [], _internal_info: info });
}
dataUpdateResponse._internal_timeScale._internal_points = this._private__sortedTimePoints;
dataUpdateResponse._internal_timeScale._internal_firstChangedPointIndex = firstChangedPointIndex;
}
else {
const seriesData = this._private__seriesRowsBySeries.get(updatedSeries);
// if no seriesData found that means that we just removed the series
dataUpdateResponse._internal_series.set(updatedSeries, { _internal_data: seriesData || [], _internal_info: info });
}
return dataUpdateResponse;
}
_private__emptyUpdateResponse() {
return {
_internal_series: new Map(),
_internal_timeScale: {
_internal_baseIndex: this._private__getBaseIndex(),
},
};
}
}
function assignIndexToPointData(pointData, index) {
// first, nevertheless update index of point data ("make it valid")
pointData._internal_index = index;
// and then we need to sync indexes for all series
pointData._internal_mapping.forEach((seriesRow) => {
seriesRow._internal_index = index;
});
}
function lowerBoundByCoordinate(item, value) {
return item._internal_x < value;
}
function upperBoundByCoordinate(item, value) {
return value < item._internal_x;
}
function lowerBoundByX(items, value, from, to) {
return lowerBound(items, value, lowerBoundByCoordinate, from, to);
}
function upperBoundByX(items, value, from, to) {
return upperBound(items, value, upperBoundByCoordinate, from, to);
}
function hoveredSeriesHitTestResult(distance, priority, itemType) {
return { _internal_distance: distance, _internal_priority: priority, _internal_itemType: itemType };
}
function isWithinHorizontalSweep(x, left, right, radius) {
return x >= left - radius && x <= right + radius;
}
function distanceToSegment(x, y, x1, y1, x2, y2) {
const deltaX = x2 - x1;
const deltaY = y2 - y1;
if (deltaX === 0 && deltaY === 0) {
return Math.hypot(x - x1, y - y1);
}
const projection = ((x - x1) * deltaX + (y - y1) * deltaY) / (deltaX * deltaX + deltaY * deltaY);
const clampedProjection = Math.max(0, Math.min(1, projection));
const closestX = x1 + deltaX * clampedProjection;
const closestY = y1 + deltaY * clampedProjection;
return Math.hypot(x - closestX, y - closestY);
}
const rangePair = [0, 0];
function slotStart(item, previousItem, barSpacing) {
if (previousItem === undefined || previousItem._internal_time !== item._internal_time - 1) {
return item._internal_x - barSpacing / 2;
}
return (previousItem._internal_x + item._internal_x) / 2;
}
function slotEnd(item, nextItem, barSpacing) {
if (nextItem === undefined || nextItem._internal_time !== item._internal_time + 1) {
return item._internal_x + barSpacing / 2;
}
return (item._internal_x + nextItem._internal_x) / 2;
}
// eslint-disable-next-line max-params, complexity
function hitTestSeriesRange(items, visibleRange, x, y, barSpacing, hitTestTolerance, rangeProvider) {
if (visibleRange === null || visibleRange.from >= visibleRange.to || items.length === 0) {
return null;
}
const horizontalRadius = barSpacing / 2 + hitTestTolerance;
const candidateFrom = lowerBoundByX(items, x - horizontalRadius, visibleRange.from, visibleRange.to);
const candidateTo = upperBoundByX(items, x + horizontalRadius, candidateFrom, visibleRange.to);
if (candidateFrom >= candidateTo) {
return null;
}
let minDistance = Number.POSITIVE_INFINITY;
for (let itemIndex = candidateFrom; itemIndex < candidateTo; itemIndex++) {
const item = items[itemIndex];
const previousItem = itemIndex > visibleRange.from ? items[itemIndex - 1] : undefined;
const nextItem = itemIndex < visibleRange.to - 1 ? items[itemIndex + 1] : undefined;
const leftBoundary = slotStart(item, previousItem, barSpacing) - hitTestTolerance;
const rightBoundary = slotEnd(item, nextItem, barSpacing) + hitTestTolerance;
if (x < leftBoundary || x > rightBoundary) {
continue;
}
rangeProvider(item, rangePair);
const rangeStart = rangePair[0];
const rangeEnd = rangePair[1];
const actualTop = Math.min(rangeStart, rangeEnd);
const actualBottom = Math.max(rangeStart, rangeEnd);
const top = actualTop - hitTestTolerance;
const bottom = actualBottom + hitTestTolerance;
if (y >= actualTop && y <= actualBottom) {
minDistance = Math.min(minDistance, 0);
continue;
}
if (y >= top && y <= bottom) {
const distance = Math.min(Math.abs(y - actualTop), Math.abs(actualBottom - y));
minDistance = Math.min(minDistance, distance);
}
}
return Number.isFinite(minDistance) ? hoveredSeriesHitTestResult(minDistance, 0 /* HitTestPriority.Range */, 'series-range') : null;
}
function lowerBoundItemsCompare(item, time) {
return item._internal_time < time;
}
function upperBoundItemsCompare(item, time) {
return time < item._internal_time;
}
function visibleTimedValues(items, range, extendedRange) {
const firstBar = range._internal_left();
const lastBar = range._internal_right();
const from = lowerBound(items, firstBar, lowerBoundItemsCompare);
const to = upperBound(items, lastBar, upperBoundItemsCompare);
if (!extendedRange) {
return { from, to };
}
let extendedFrom = from;
let extendedTo = to;
if (from > 0 && from < items.length && items[from]._internal_time >= firstBar) {
extendedFrom = from - 1;
}
if (to > 0 && to < items.length && items[to - 1]._internal_time <= lastBar) {
extendedTo = to + 1;
}
return { from: extendedFrom, to: extendedTo };
}
class SeriesPaneViewBase {
constructor(series, model, extendedVisibleRange) {
this._internal__invalidated = true;
this._internal__dataInvalidated = true;
this._internal__optionsInvalidated = true;
this._internal__items = [];
this._internal__itemsVisibleRange = null;
this._private__lastConflationKey = -1;
this._internal__series = series;
this._internal__model = model;
this._private__extendedVisibleRange = extendedVisibleRange;
}
_internal_update(updateType) {
this._internal__invalidated = true;
if (updateType === 'data') {
this._internal__dataInvalidated = true;
}
if (updateType === 'options') {
this._internal__optionsInvalidated = true;
}
}
_internal_renderer() {
if (!this._internal__series._internal_visible()) {
return null;
}
this._internal__makeValid();
return this._internal__itemsVisibleRange === null ? null : this._internal__renderer;
}
_internal_hitTest(x, y) {
if (!this._internal__series._internal_visible()) {
return null;
}
this._internal__makeValid();
if (this._internal__itemsVisibleRange === null) {
return null;
}
return this._internal__hitTestImpl(x, y);
}
_internal__hitTestImpl(x, y) {
return null;
}
_internal__updateOptions() {
this._internal__items = this._internal__items.map((item) => ({
...item,
...this._internal__series._internal_barColorer()._internal_barStyle(item._internal_time),
}));
}
_internal__clearVisibleRange() {
this._internal__itemsVisibleRange = null;
}
_internal__makeValid() {
// If the conflation setting or factor changed (due to zoom/barSpacing),
// we must rebuild raw items from series data.
const timeScale = this._internal__model._internal_timeScale();
const conflationEnabled = timeScale._internal_options().enableConflation;
const currentConflationKey = conflationEnabled ? timeScale._internal_conflationFactor() : 0;
if (currentConflationKey !== this._private__lastConflationKey) {
this._internal__dataInvalidated = true;
this._private__lastConflationKey = currentConflationKey;
}
if (this._internal__dataInvalidated) {
this._internal__fillRawPoints();
this._internal__dataInvalidated = false;
}
if (this._internal__optionsInvalidated) {
this._internal__updateOptions();
this._internal__optionsInvalidated = false;
}
if (this._internal__invalidated) {
this._private__makeValidImpl();
this._internal__invalidated = false;
}
}
_private__makeValidImpl() {
const priceScale = this._internal__series._internal_priceScale();
const timeScale = this._internal__model._internal_timeScale();
this._internal__clearVisibleRange();
if (timeScale._internal_isEmpty() || priceScale._internal_isEmpty()) {
return;
}
const visibleBars = timeScale._internal_visibleStrictRange();
if (visibleBars === null) {
return;
}
if (this._internal__series._internal_bars()._internal_size() === 0) {
return;
}
const firstValue = this._internal__series._internal_firstValue();
if (firstValue === null) {
return;
}
this._internal__itemsVisibleRange = visibleTimedValues(this._internal__items, visibleBars, this._private__extendedVisibleRange);
this._internal__convertToCoordinates(priceScale, timeScale, firstValue._internal_value);
this._internal__prepareRendererData();
}
}
class CustomSeriesPaneRendererWrapper {
constructor(sourceRenderer, priceScale) {
this._private__sourceRenderer = sourceRenderer;
this._private__priceScale = priceScale;
}
_internal_draw(target, isHovered, hitTestData) {
this._private__sourceRenderer.draw(target, this._private__priceScale, isHovered, hitTestData);
}
}
function customHitPriority(type) {
switch (type) {
case 'point':
return 2 /* HitTestPriority.Point */;
case 'range':
return 0 /* HitTestPriority.Range */;
case 'line':
case 'custom':
default:
return 1 /* HitTestPriority.Line */;
}
}
function normalizeCustomHit(result) {
return {
_internal_distance: result.distance,
_internal_priority: customHitPriority(result.type),
_internal_itemType: 'custom',
_internal_cursorStyle: result.cursorStyle,
_internal_externalId: result.objectId,
_internal_hitTestData: result.hitTestData,
};
}
class SeriesCustomPaneView extends SeriesPaneViewBase {
constructor(series, model, paneView) {
super(series, model, false);
this._private__paneView = paneView;
this._private__sourceRenderer = this._private__paneView.renderer();
this._internal__renderer = new CustomSeriesPaneRendererWrapper(this._private__sourceRenderer, (price) => this._private__rendererPriceCoordinate(price));
}
get _internal_conflationReducer() {
// eslint-disable-next-line @typescript-eslint/unbound-method
return this._private__paneView.conflationReducer;
}
_internal_priceValueBuilder(plotRow) {
return this._private__paneView.priceValueBuilder(plotRow);
}
_internal_isWhitespace(data) {
return this._private__paneView.isWhitespace(data);
}
_internal__hitTestImpl(x, y) {
const customHit = this._private__sourceRenderer.hitTest?.(x, y, (price) => this._private__rendererPriceCoordinate(price));
if (customHit !== null && customHit !== undefined) {
return normalizeCustomHit(customHit);
}
const fallbackHit = hitTestSeriesRange(this._internal__items, this._internal__itemsVisibleRange, x, y, this._internal__model._internal_timeScale()._internal_barSpacing(), this._internal__series._internal_options().hitTestTolerance, (bar, out) => {
const originalData = bar._internal_originalData;
let top = NaN;
let bottom = NaN;
if (originalData !== undefined && !this._private__paneView.isWhitespace(originalData)) {
for (const price of this._private__paneView.priceValueBuilder(originalData)) {
const coordinate = this._private__rendererPriceCoordinate(price);
if (coordinate === null) {
continue;
}
top = Number.isNaN(top) ? coordinate : Math.min(top, coordinate);
bottom = Number.isNaN(bottom) ? coordinate : Math.max(bottom, coordinate);
}
}
out[0] = top;
out[1] = bottom;
});
return fallbackHit === null ? null : {
...fallbackHit,
_internal_itemType: 'custom',
};
}
_internal__fillRawPoints() {
const colorer = this._internal__series._internal_barColorer();
this._internal__items = this._internal__series._internal_conflatedBars()._internal_rows()
.map((row) => {
return {
_internal_time: row._internal_index,
_internal_x: NaN,
...colorer._internal_barStyle(row._internal_index),
_internal_originalData: row._internal_data,
};
});
}
_internal__convertToCoordinates(priceScale, timeScale) {
timeScale._internal_indexesToCoordinates(this._internal__items, undefinedIfNull(this._internal__itemsVisibleRange));
}
_internal__prepareRendererData() {
this._private__paneView.update({
bars: this._internal__items.map(unwrapItemData),
barSpacing: this._internal__model._internal_timeScale()._internal_barSpacing(),
visibleRange: this._internal__itemsVisibleRange,
conflationFactor: this._internal__model._internal_timeScale()._internal_conflationFactor(),
}, this._internal__series._internal_options());
}
_private__rendererPriceCoordinate(price) {
const firstValue = this._internal__series._internal_firstValue();
if (firstValue === null) {
return null;
}
return this._internal__series._internal_priceScale()._internal_priceToCoordinate(price, firstValue._internal_value);
}
}
function unwrapItemData(item) {
return {
x: item._internal_x,
time: item._internal_time,
originalData: item._internal_originalData,
barColor: item._internal_barColor,
};
}
const customStyleDefaults = {
color: '#2196f3',
};
const createPaneView$6 = (series, model, customPaneView) => {
const paneView = ensure(customPaneView);
return new SeriesCustomPaneView(series, model, paneView);
};
const createCustomSeriesDefinition = (paneView) => {
const definition = {
type: 'Custom',
isBuiltIn: false,
defaultOptions: { ...customStyleDefaults, ...paneView.defaultOptions() },
/**
* @internal
*/
_internal_createPaneView: createPaneView$6,
_internal_customPaneView: paneView,
};
return definition;
};
const isSeriesDefinition = (value) => {
return value._internal_createPaneView !== undefined;
};
function singleValueData(plotRow) {
const data = {
value: plotRow._internal_value[3 /* PlotRowValueIndex.Close */],
time: plotRow._internal_originalTime,
};
if (plotRow._internal_customValues !== undefined) {
data.customValues = plotRow._internal_customValues;
}
return data;
}
function lineData(plotRow) {
const result = singleValueData(plotRow);
if (plotRow._internal_color !== undefined) {
result.color = plotRow._internal_color;
}
return result;
}
function areaData(plotRow) {
const result = singleValueData(plotRow);
if (plotRow._internal_lineColor !== undefined) {
result.lineColor = plotRow._internal_lineColor;
}
if (plotRow._internal_topColor !== undefined) {
result.topColor = plotRow._internal_topColor;
}
if (plotRow._internal_bottomColor !== undefined) {
result.bottomColor = plotRow._internal_bottomColor;
}
return result;
}
function baselineData(plotRow) {
const result = singleValueData(plotRow);
if (plotRow._internal_topLineColor !== undefined) {
result.topLineColor = plotRow._internal_topLineColor;
}
if (plotRow._internal_bottomLineColor !== undefined) {
result.bottomLineColor = plotRow._internal_bottomLineColor;
}
if (plotRow._internal_topFillColor1 !== undefined) {
result.topFillColor1 = plotRow._internal_topFillColor1;
}
if (plotRow._internal_topFillColor2 !== undefined) {
result.topFillColor2 = plotRow._internal_topFillColor2;
}
if (plotRow._internal_bottomFillColor1 !== undefined) {
result.bottomFillColor1 = plotRow._internal_bottomFillColor1;
}
if (plotRow._internal_bottomFillColor2 !== undefined) {
result.bottomFillColor2 = plotRow._internal_bottomFillColor2;
}
return result;
}
function ohlcData(plotRow) {
const data = {
open: plotRow._internal_value[0 /* PlotRowValueIndex.Open */],
high: plotRow._internal_value[1 /* PlotRowValueIndex.High */],
low: plotRow._internal_value[2 /* PlotRowValueIndex.Low */],
close: plotRow._internal_value[3 /* PlotRowValueIndex.Close */],
time: plotRow._internal_originalTime,
};
if (plotRow._internal_customValues !== undefined) {
data.customValues = plotRow._internal_customValues;
}
return data;
}
function barData(plotRow) {
const result = ohlcData(plotRow);
if (plotRow._internal_color !== undefined) {
result.color = plotRow._internal_color;
}
return result;
}
function candlestickData(plotRow) {
const result = ohlcData(plotRow);
const { _internal_color: color, _internal_borderColor: borderColor, _internal_wickColor: wickColor } = plotRow;
if (color !== undefined) {
result.color = color;
}
if (borderColor !== undefined) {
result.borderColor = borderColor;
}
if (wickColor !== undefined) {
result.wickColor = wickColor;
}
return result;
}
function getSeriesDataCreator(seriesType) {
const seriesPlotRowToDataMap = {
Area: (areaData),
Line: (lineData),
Baseline: (baselineData),
Histogram: (lineData),
Bar: (barData),
Candlestick: (candlestickData),
Custom: (customData),
};
return seriesPlotRowToDataMap[seriesType];
}
function customData(plotRow) {
const time = plotRow._internal_originalTime;
return {
...plotRow._internal_data,
time,
};
}
const crosshairOptionsDefaults = {
vertLine: {
color: '#9598A1',
width: 1,
style: 3 /* LineStyle.LargeDashed */,
visible: true,
labelVisible: true,
labelBackgroundColor: '#131722',
},
horzLine: {
color: '#9598A1',
width: 1,
style: 3 /* LineStyle.LargeDashed */,
visible: true,
labelVisible: true,
labelBackgroundColor: '#131722',
},
mode: 1 /* CrosshairMode.Magnet */,
doNotSnapToHiddenSeriesIndices: false,
};
const gridOptionsDefaults = {
vertLines: {
color: '#D6DCDE',
style: 0 /* LineStyle.Solid */,
visible: true,
},
horzLines: {
color: '#D6DCDE',
style: 0 /* LineStyle.Solid */,
visible: true,
},
};
const layoutOptionsDefaults = {
background: {
type: "solid" /* ColorType.Solid */,
color: '#FFFFFF',
},
textColor: '#191919',
fontSize: 12,
fontFamily: defaultFontFamily,
panes: {
enableResize: true,
separatorColor: '#E0E3EB',
separatorHoverColor: 'rgba(178, 181, 189, 0.2)',
},
attributionLogo: true,
colorSpace: 'srgb',
colorParsers: [],
};
const priceScaleOptionsDefaults = {
autoScale: true,
mode: 0 /* PriceScaleMode.Normal */,
invertScale: false,
alignLabels: true,
borderVisible: true,
borderColor: '#2B2B43',
entireTextOnly: false,
visible: false,
ticksVisible: false,
scaleMargins: {
bottom: 0.1,
top: 0.2,
},
minimumWidth: 0,
ensureEdgeTickMarksVisible: false,
tickMarkDensity: 2.5,
};
const timeScaleOptionsDefaults = {
rightOffset: 0,
barSpacing: 6,
minBarSpacing: 0.5,
maxBarSpacing: 0,
fixLeftEdge: false,
fixRightEdge: false,
lockVisibleTimeRangeOnResize: false,
rightBarStaysOnScroll: false,
borderVisible: true,
borderColor: '#2B2B43',
visible: true,
timeVisible: false,
secondsVisible: true,
shiftVisibleRangeOnNewBar: true,
allowShiftVisibleRangeOnWhitespaceReplacement: false,
ticksVisible: false,
uniformDistribution: false,
minimumHeight: 0,
allowBoldLabels: true,
ignoreWhitespaceIndices: false,
enableConflation: false,
conflationThresholdFactor: 1,
precomputeConflationOnInit: false,
precomputeConflationPriority: 'background',
};
function chartOptionsDefaults() {
return {
addDefaultPane: true,
hoveredSeriesOnTop: true,
width: 0,
height: 0,
autoSize: false,
layout: layoutOptionsDefaults,
crosshair: crosshairOptionsDefaults,
grid: gridOptionsDefaults,
overlayPriceScales: {
...priceScaleOptionsDefaults,
},
leftPriceScale: {
...priceScaleOptionsDefaults,
visible: false,
},
rightPriceScale: {
...priceScaleOptionsDefaults,
visible: true,
},
defaultVisiblePriceScaleId: 'right',
timeScale: timeScaleOptionsDefaults,
localization: {
locale: isRunningOnClientSide ? navigator.language : '',
dateFormat: 'dd MMM \'yy',
},
handleScroll: {
mouseWheel: true,
pressedMouseMove: true,
horzTouchDrag: true,
vertTouchDrag: true,
},
handleScale: {
axisPressedMouseMove: {
time: true,
price: true,
},
axisDoubleClickReset: {
time: true,
price: true,
},
mouseWheel: true,
pinch: true,
},
kineticScroll: {
mouse: false,
touch: true,
},
trackingMode: {
exitMode: 1 /* TrackingModeExitMode.OnNextTap */,
},
};
}
class PriceScaleApi {
constructor(chartWidget, priceScaleId, paneIndex) {
this._private__chartWidget = chartWidget;
this._private__priceScaleId = priceScaleId;
this._private__paneIndex = paneIndex ?? 0;
}
applyOptions(options) {
this._private__chartWidget._internal_model()._internal_applyPriceScaleOptions(this._private__priceScaleId, options, this._private__paneIndex);
}
options() {
return this._private__priceScale()._internal_options();
}
width() {
if (!isDefaultPriceScale(this._private__priceScaleId)) {
return 0;
}
return this._private__chartWidget._internal_getPriceAxisWidth(this._private__priceScaleId);
}
setVisibleRange(range) {
this.setAutoScale(false);
this._private__priceScale()._internal_setCustomPriceRange(new PriceRangeImpl(range.from, range.to));
}
getVisibleRange() {
let range = this._private__priceScale()._internal_priceRange();
if (range === null) {
return null;
}
let from;
let to;
if (this._private__priceScale()._internal_isLog()) {
const minMove = this._private__priceScale()._internal_minMove();
const minMovePrecision = precisionByMinMove(minMove);
range = convertPriceRangeFromLog(range, this._private__priceScale()._internal_getLogFormula());
from = Number((Math.round(range._internal_minValue() / minMove) * minMove).toFixed(minMovePrecision));
to = Number((Math.round(range._internal_maxValue() / minMove) * minMove).toFixed(minMovePrecision));
}
else {
from = range._internal_minValue();
to = range._internal_maxValue();
}
return {
from,
to,
};
}
setAutoScale(on) {
this.applyOptions({ autoScale: on });
}
_private__priceScale() {
return ensureNotNull(this._private__chartWidget._internal_model()._internal_findPriceScale(this._private__priceScaleId, this._private__paneIndex))._internal_priceScale;
}
}
class PaneApi {
constructor(chartWidget, seriesApiGetter, pane, chartApi) {
this._private__chartWidget = chartWidget;
this._private__pane = pane;
this._private__seriesApiGetter = seriesApiGetter;
this._internal__chartApi = chartApi;
}
getHeight() {
return this._private__pane._internal_height();
}
setHeight(height) {
const chartModel = this._private__chartWidget._internal_model();
const paneIndex = chartModel._internal_getPaneIndex(this._private__pane);
chartModel._internal_changePanesHeight(paneIndex, height);
}
getStretchFactor() {
return this._private__pane._internal_stretchFactor();
}
setStretchFactor(stretchFactor) {
this._private__pane._internal_setStretchFactor(stretchFactor);
this._private__chartWidget._internal_model()._internal_fullUpdate();
}
paneIndex() {
return this._private__chartWidget._internal_model()._internal_getPaneIndex(this._private__pane);
}
moveTo(paneIndex) {
const currentIndex = this.paneIndex();
if (currentIndex === paneIndex) {
return;
}
assert(paneIndex >= 0 && paneIndex < this._private__chartWidget._internal_paneWidgets().length, 'Invalid pane index');
this._private__chartWidget._internal_model()._internal_movePane(currentIndex, paneIndex);
}
getSeries() {
return this._private__pane._internal_series().map((source) => this._private__seriesApiGetter(source)) ?? [];
}
getHTMLElement() {
const widgets = this._private__chartWidget._internal_paneWidgets();
if (!widgets || widgets.length === 0 || !widgets[this.paneIndex()]) {
return null;
}
return widgets[this.paneIndex()]._internal_getElement();
}
attachPrimitive(primitive) {
this._private__pane._internal_attachPrimitive(primitive);
if (primitive.attached) {
primitive.attached({
chart: this._internal__chartApi,
requestUpdate: () => this._private__pane._internal_model()._internal_fullUpdate(),
});
}
}
detachPrimitive(primitive) {
this._private__pane._internal_detachPrimitive(primitive);
}
priceScale(priceScaleId) {
const priceScale = this._private__pane._internal_priceScaleById(priceScaleId);
if (priceScale === null) {
throw new Error(`Cannot find price scale with id: ${priceScaleId}`);
}
return new PriceScaleApi(this._private__chartWidget, priceScaleId, this.paneIndex());
}
setPreserveEmptyPane(preserve) {
this._private__pane._internal_setPreserveEmptyPane(preserve);
}
preserveEmptyPane() {
return this._private__pane._internal_preserveEmptyPane();
}
addCustomSeries(customPaneView, options = {}, paneIndex = 0) {
return this._internal__chartApi.addCustomSeries(customPaneView, options, paneIndex);
}
addSeries(definition, options = {}) {
return this._internal__chartApi.addSeries(definition, options, this.paneIndex());
}
}
///
function checkPriceLineOptions(options) {
assert(typeof options.price === 'number', `the type of 'price' price line's property must be a number, got '${typeof options.price}'`);
}
function checkItemsAreOrdered(data, bh, allowDuplicates = false) {
if (data.length === 0) {
return;
}
let prevTime = bh.key(data[0].time);
for (let i = 1; i < data.length; ++i) {
const currentTime = bh.key(data[i].time);
const checkResult = allowDuplicates ? prevTime <= currentTime : prevTime < currentTime;
assert(checkResult, `data must be asc ordered by time, index=${i}, time=${currentTime}, prev time=${prevTime}`);
prevTime = currentTime;
}
}
function checkSeriesValuesType(type, data) {
data.forEach(getChecker(type));
}
function getChecker(type) {
switch (type) {
case 'Bar':
case 'Candlestick':
return checkBarItem.bind(null, type);
case 'Area':
case 'Baseline':
case 'Line':
case 'Histogram':
return checkLineItem.bind(null, type);
case 'Custom':
return checkCustomItem.bind(null);
}
}
function checkBarItem(type, barItem) {
if (!isFulfilledData(barItem)) {
return;
}
['open', 'high', 'low', 'close'].forEach((key) => {
assert(typeof barItem[key] === 'number', `${type} series item data value of ${key} must be a number, got=${typeof barItem[key]}, value=${barItem[key]}`);
assert(isSafeValue(barItem[key]), `${type} series item data value of ${key} must be between ${MIN_SAFE_VALUE.toPrecision(16)} and ${MAX_SAFE_VALUE.toPrecision(16)}, got=${typeof barItem[key]}, value=${barItem[key]}`);
});
}
function checkLineItem(type, lineItem) {
if (!isFulfilledData(lineItem)) {
return;
}
assert(typeof lineItem.value === 'number', `${type} series item data value must be a number, got=${typeof lineItem.value}, value=${lineItem.value}`);
assert(isSafeValue(lineItem.value), `${type} series item data value must be between ${MIN_SAFE_VALUE.toPrecision(16)} and ${MAX_SAFE_VALUE.toPrecision(16)}, got=${typeof lineItem.value}, value=${lineItem.value}`);
}
function checkCustomItem(
// type: 'Custom',
// customItem: SeriesDataItemTypeMap[typeof type]
) {
// Nothing to check yet...
return;
}
const MIN_SAFE_VALUE = Number.MIN_SAFE_INTEGER / 100;
const MAX_SAFE_VALUE = Number.MAX_SAFE_INTEGER / 100;
function isSafeValue(value) {
return value >= MIN_SAFE_VALUE && value <= MAX_SAFE_VALUE;
}
const priceLineOptionsDefaults = {
color: '#FF0000',
price: 0,
lineStyle: 2 /* LineStyle.Dashed */,
lineWidth: 1,
lineVisible: true,
axisLabelVisible: true,
title: '',
axisLabelColor: '',
axisLabelTextColor: '',
};
class PriceLine {
constructor(priceLine) {
this._private__priceLine = priceLine;
}
applyOptions(options) {
this._private__priceLine._internal_applyOptions(options);
}
options() {
return this._private__priceLine._internal_options();
}
_internal_priceLine() {
return this._private__priceLine;
}
}
class SeriesApi {
constructor(series, dataUpdatesConsumer, priceScaleApiProvider, chartApi, horzScaleBehavior, paneApiGetter) {
this._private__dataChangedDelegate = new Delegate();
this._internal__series = series;
this._internal__dataUpdatesConsumer = dataUpdatesConsumer;
this._private__priceScaleApiProvider = priceScaleApiProvider;
this._private__horzScaleBehavior = horzScaleBehavior;
this._internal__chartApi = chartApi;
this._private__paneApiGetter = paneApiGetter;
}
_internal_destroy() {
this._private__dataChangedDelegate._internal_destroy();
}
priceFormatter() {
return this._internal__series._internal_formatter();
}
priceToCoordinate(price) {
const firstValue = this._internal__series._internal_firstValue();
if (firstValue === null) {
return null;
}
return this._internal__series._internal_priceScale()._internal_priceToCoordinate(price, firstValue._internal_value);
}
coordinateToPrice(coordinate) {
const firstValue = this._internal__series._internal_firstValue();
if (firstValue === null) {
return null;
}
return this._internal__series._internal_priceScale()._internal_coordinateToPrice(coordinate, firstValue._internal_value);
}
barsInLogicalRange(range) {
if (range === null) {
return null;
}
// we use TimeScaleVisibleRange here to convert LogicalRange to strict range properly
const correctedRange = new TimeScaleVisibleRange(new RangeImpl(range.from, range.to))._internal_strictRange();
const bars = this._internal__series._internal_bars();
if (bars._internal_isEmpty()) {
return null;
}
const dataFirstBarInRange = bars._internal_search(correctedRange._internal_left(), 1 /* MismatchDirection.NearestRight */);
const dataLastBarInRange = bars._internal_search(correctedRange._internal_right(), -1 /* MismatchDirection.NearestLeft */);
const dataFirstIndex = ensureNotNull(bars._internal_firstIndex());
const dataLastIndex = ensureNotNull(bars._internal_lastIndex());
// this means that we request data in the data gap
// e.g. let's say we have series with data [0..10, 30..60]
// and we request bars info in range [15, 25]
// thus, dataFirstBarInRange will be with index 30 and dataLastBarInRange with 10
if (dataFirstBarInRange !== null && dataLastBarInRange !== null && dataFirstBarInRange._internal_index > dataLastBarInRange._internal_index) {
return {
barsBefore: range.from - dataFirstIndex,
barsAfter: dataLastIndex - range.to,
};
}
const barsBefore = (dataFirstBarInRange === null || dataFirstBarInRange._internal_index === dataFirstIndex)
? range.from - dataFirstIndex
: dataFirstBarInRange._internal_index - dataFirstIndex;
const barsAfter = (dataLastBarInRange === null || dataLastBarInRange._internal_index === dataLastIndex)
? dataLastIndex - range.to
: dataLastIndex - dataLastBarInRange._internal_index;
const result = { barsBefore, barsAfter };
// actually they can't exist separately
if (dataFirstBarInRange !== null && dataLastBarInRange !== null) {
result.from = dataFirstBarInRange._internal_originalTime;
result.to = dataLastBarInRange._internal_originalTime;
}
return result;
}
setData(data) {
checkItemsAreOrdered(data, this._private__horzScaleBehavior);
checkSeriesValuesType(this._internal__series._internal_seriesType(), data);
this._internal__dataUpdatesConsumer._internal_applyNewData(this._internal__series, data);
this._private__onDataChanged('full');
}
update(bar, historicalUpdate = false) {
checkSeriesValuesType(this._internal__series._internal_seriesType(), [bar]);
this._internal__dataUpdatesConsumer._internal_updateData(this._internal__series, bar, historicalUpdate);
this._private__onDataChanged('update');
}
pop(count = 1) {
const poppedRows = this._internal__dataUpdatesConsumer._internal_popData(this._internal__series, count);
if (poppedRows.length !== 0) {
this._private__onDataChanged('update');
}
const creator = getSeriesDataCreator(this.seriesType());
return poppedRows.map((row) => creator(row));
}
dataByIndex(logicalIndex, mismatchDirection) {
const data = this._internal__series._internal_bars()._internal_search(logicalIndex, mismatchDirection);
if (data === null) {
// actually it can be a whitespace
return null;
}
const creator = getSeriesDataCreator(this.seriesType());
return creator(data);
}
data() {
const seriesCreator = getSeriesDataCreator(this.seriesType());
const rows = this._internal__series._internal_bars()._internal_rows();
return rows.map((row) => seriesCreator(row));
}
subscribeDataChanged(handler) {
this._private__dataChangedDelegate._internal_subscribe(handler);
}
unsubscribeDataChanged(handler) {
this._private__dataChangedDelegate._internal_unsubscribe(handler);
}
applyOptions(options) {
this._internal__series._internal_applyOptions(options);
}
options() {
return clone(this._internal__series._internal_options());
}
priceScale() {
return this._private__priceScaleApiProvider.priceScale(this._internal__series._internal_priceScale()._internal_id(), this.getPane().paneIndex());
}
createPriceLine(options) {
checkPriceLineOptions(options);
const strictOptions = merge(clone(priceLineOptionsDefaults), options);
const priceLine = this._internal__series._internal_createPriceLine(strictOptions);
return new PriceLine(priceLine);
}
removePriceLine(line) {
this._internal__series._internal_removePriceLine(line._internal_priceLine());
}
priceLines() {
return this._internal__series._internal_priceLines().map((priceLine) => new PriceLine(priceLine));
}
seriesType() {
return this._internal__series._internal_seriesType();
}
lastValueData(globalLast) {
const result = this._internal__series._internal_lastValueData(globalLast);
if (result._internal_noData) {
return {
noData: true,
};
}
return {
noData: false,
price: result._internal_price,
color: result._internal_color,
};
}
attachPrimitive(primitive) {
// at this point we cast the generic to unknown because we
// don't want the model to know the types of the API (◑_◑)
this._internal__series._internal_attachPrimitive(primitive);
if (primitive.attached) {
primitive.attached({
chart: this._internal__chartApi,
series: this,
requestUpdate: () => this._internal__series._internal_model()._internal_fullUpdate(),
horzScaleBehavior: this._private__horzScaleBehavior,
});
}
}
detachPrimitive(primitive) {
this._internal__series._internal_detachPrimitive(primitive);
if (primitive.detached) {
primitive.detached();
}
this._internal__series._internal_model()._internal_fullUpdate();
}
getPane() {
const series = this._internal__series;
const pane = ensureNotNull(this._internal__series._internal_model()._internal_paneForSource(series));
return this._private__paneApiGetter(pane);
}
moveToPane(paneIndex) {
this._internal__series._internal_model()._internal_moveSeriesToPane(this._internal__series, paneIndex);
}
seriesOrder() {
const pane = this._internal__series._internal_model()._internal_paneForSource(this._internal__series);
if (pane === null) {
return -1;
}
return pane._internal_series().indexOf(this._internal__series);
}
setSeriesOrder(order) {
const pane = this._internal__series._internal_model()._internal_paneForSource(this._internal__series);
if (pane === null) {
return;
}
pane._internal_setSeriesOrder(this._internal__series, order);
}
_private__onDataChanged(scope) {
if (this._private__dataChangedDelegate._internal_hasListeners()) {
this._private__dataChangedDelegate._internal_fire(scope);
}
}
}
class TimeScaleApi {
constructor(model, timeAxisWidget, horzScaleBehavior) {
this._private__timeRangeChanged = new Delegate();
this._private__logicalRangeChanged = new Delegate();
this._private__sizeChanged = new Delegate();
this._private__model = model;
this._private__timeScale = model._internal_timeScale();
this._private__timeAxisWidget = timeAxisWidget;
this._private__timeScale._internal_visibleBarsChanged()._internal_subscribe(this._private__onVisibleBarsChanged.bind(this));
this._private__timeScale._internal_logicalRangeChanged()._internal_subscribe(this._private__onVisibleLogicalRangeChanged.bind(this));
this._private__timeAxisWidget._internal_sizeChanged()._internal_subscribe(this._private__onSizeChanged.bind(this));
this._private__horzScaleBehavior = horzScaleBehavior;
}
_internal_destroy() {
this._private__timeScale._internal_visibleBarsChanged()._internal_unsubscribeAll(this);
this._private__timeScale._internal_logicalRangeChanged()._internal_unsubscribeAll(this);
this._private__timeAxisWidget._internal_sizeChanged()._internal_unsubscribeAll(this);
this._private__timeRangeChanged._internal_destroy();
this._private__logicalRangeChanged._internal_destroy();
this._private__sizeChanged._internal_destroy();
}
scrollPosition() {
return this._private__timeScale._internal_rightOffset();
}
scrollToPosition(position, animated) {
if (!animated) {
this._private__model._internal_setRightOffset(position);
return;
}
this._private__timeScale._internal_scrollToOffsetAnimated(position, 1000 /* Constants.AnimationDurationMs */);
}
scrollToRealTime() {
this._private__timeScale._internal_scrollToRealTime();
}
getVisibleRange() {
const timeRange = this._private__timeScale._internal_visibleTimeRange();
if (timeRange === null) {
return null;
}
return {
from: timeRange.from.originalTime,
to: timeRange.to.originalTime,
};
}
setVisibleRange(range) {
const convertedRange = {
from: this._private__horzScaleBehavior.convertHorzItemToInternal(range.from),
to: this._private__horzScaleBehavior.convertHorzItemToInternal(range.to),
};
const logicalRange = this._private__timeScale._internal_logicalRangeForTimeRange(convertedRange);
this._private__model._internal_setTargetLogicalRange(logicalRange);
}
getVisibleLogicalRange() {
const logicalRange = this._private__timeScale._internal_visibleLogicalRange();
if (logicalRange === null) {
return null;
}
return {
from: logicalRange._internal_left(),
to: logicalRange._internal_right(),
};
}
setVisibleLogicalRange(range) {
assert(range.from <= range.to, 'The from index cannot be after the to index.');
this._private__model._internal_setTargetLogicalRange(range);
}
resetTimeScale() {
this._private__model._internal_resetTimeScale();
}
fitContent() {
this._private__model._internal_fitContent();
}
logicalToCoordinate(logical) {
const timeScale = this._private__model._internal_timeScale();
if (timeScale._internal_isEmpty()) {
return null;
}
else {
return timeScale._internal_indexToCoordinate(logical);
}
}
coordinateToLogical(x) {
if (this._private__timeScale._internal_isEmpty()) {
return null;
}
else {
return this._private__timeScale._internal_coordinateToIndex(x);
}
}
timeToIndex(time, findNearest) {
const timePoint = this._private__horzScaleBehavior.convertHorzItemToInternal(time);
return this._private__timeScale._internal_timeToIndex(timePoint, findNearest);
}
timeToCoordinate(time) {
const timePointIndex = this.timeToIndex(time, false);
if (timePointIndex === null) {
return null;
}
return this._private__timeScale._internal_indexToCoordinate(timePointIndex);
}
coordinateToTime(x) {
const timeScale = this._private__model._internal_timeScale();
const timePointIndex = timeScale._internal_coordinateToIndex(x);
const timePoint = timeScale._internal_indexToTimeScalePoint(timePointIndex);
if (timePoint === null) {
return null;
}
return timePoint.originalTime;
}
width() {
return this._private__timeAxisWidget._internal_getSize().width;
}
height() {
return this._private__timeAxisWidget._internal_getSize().height;
}
subscribeVisibleTimeRangeChange(handler) {
this._private__timeRangeChanged._internal_subscribe(handler);
}
unsubscribeVisibleTimeRangeChange(handler) {
this._private__timeRangeChanged._internal_unsubscribe(handler);
}
subscribeVisibleLogicalRangeChange(handler) {
this._private__logicalRangeChanged._internal_subscribe(handler);
}
unsubscribeVisibleLogicalRangeChange(handler) {
this._private__logicalRangeChanged._internal_unsubscribe(handler);
}
subscribeSizeChange(handler) {
this._private__sizeChanged._internal_subscribe(handler);
}
unsubscribeSizeChange(handler) {
this._private__sizeChanged._internal_unsubscribe(handler);
}
applyOptions(options) {
this._private__timeScale._internal_applyOptions(options);
}
options() {
return {
...clone(this._private__timeScale._internal_options()),
barSpacing: this._private__timeScale._internal_barSpacing(),
};
}
_private__onVisibleBarsChanged() {
if (this._private__timeRangeChanged._internal_hasListeners()) {
this._private__timeRangeChanged._internal_fire(this.getVisibleRange());
}
}
_private__onVisibleLogicalRangeChanged() {
if (this._private__logicalRangeChanged._internal_hasListeners()) {
this._private__logicalRangeChanged._internal_fire(this.getVisibleLogicalRange());
}
}
_private__onSizeChanged(size) {
this._private__sizeChanged._internal_fire(size.width, size.height);
}
}
///
function patchPriceFormat(priceFormat) {
if (priceFormat === undefined || priceFormat.type === 'custom') {
return;
}
const priceFormatBuiltIn = priceFormat;
if (priceFormatBuiltIn.minMove !== undefined && priceFormatBuiltIn.precision === undefined) {
priceFormatBuiltIn.precision = precisionByMinMove(priceFormatBuiltIn.minMove);
}
}
function migrateHandleScaleScrollOptions(options) {
if (isBoolean(options['handleScale'])) {
const handleScale = options['handleScale'];
options['handleScale'] = {
axisDoubleClickReset: {
time: handleScale,
price: handleScale,
},
axisPressedMouseMove: {
time: handleScale,
price: handleScale,
},
mouseWheel: handleScale,
pinch: handleScale,
};
}
else if (options['handleScale'] !== undefined) {
const { axisPressedMouseMove, axisDoubleClickReset } = options['handleScale'];
if (isBoolean(axisPressedMouseMove)) {
options['handleScale'].axisPressedMouseMove = {
time: axisPressedMouseMove,
price: axisPressedMouseMove,
};
}
if (isBoolean(axisDoubleClickReset)) {
options['handleScale'].axisDoubleClickReset = {
time: axisDoubleClickReset,
price: axisDoubleClickReset,
};
}
}
const handleScroll = options['handleScroll'];
if (isBoolean(handleScroll)) {
options['handleScroll'] = {
horzTouchDrag: handleScroll,
vertTouchDrag: handleScroll,
mouseWheel: handleScroll,
pressedMouseMove: handleScroll,
};
}
}
function toInternalOptions(options) {
migrateHandleScaleScrollOptions(options);
return options;
}
class ChartApi {
constructor(container, horzScaleBehavior, options) {
this._private__seriesMap = new Map();
this._private__seriesMapReversed = new Map();
this._private__clickedDelegate = new Delegate();
this._private__dblClickedDelegate = new Delegate();
this._private__crosshairMovedDelegate = new Delegate();
this._private__panes = new WeakMap();
this._private__dataLayer = new DataLayer(horzScaleBehavior);
const internalOptions = (options === undefined) ?
clone(chartOptionsDefaults()) :
merge(clone(chartOptionsDefaults()), toInternalOptions(options));
this._internal__horzScaleBehavior = horzScaleBehavior;
this._private__chartWidget = new ChartWidget(container, internalOptions, horzScaleBehavior);
this._private__chartWidget._internal_clicked()._internal_subscribe((paramSupplier) => {
if (this._private__clickedDelegate._internal_hasListeners()) {
this._private__clickedDelegate._internal_fire(this._private__convertMouseParams(paramSupplier()));
}
}, this);
this._private__chartWidget._internal_dblClicked()._internal_subscribe((paramSupplier) => {
if (this._private__dblClickedDelegate._internal_hasListeners()) {
this._private__dblClickedDelegate._internal_fire(this._private__convertMouseParams(paramSupplier()));
}
}, this);
this._private__chartWidget._internal_crosshairMoved()._internal_subscribe((paramSupplier) => {
if (this._private__crosshairMovedDelegate._internal_hasListeners()) {
this._private__crosshairMovedDelegate._internal_fire(this._private__convertMouseParams(paramSupplier()));
}
}, this);
const model = this._private__chartWidget._internal_model();
this._private__timeScaleApi = new TimeScaleApi(model, this._private__chartWidget._internal_timeAxisWidget(), this._internal__horzScaleBehavior);
}
remove() {
this._private__chartWidget._internal_clicked()._internal_unsubscribeAll(this);
this._private__chartWidget._internal_dblClicked()._internal_unsubscribeAll(this);
this._private__chartWidget._internal_crosshairMoved()._internal_unsubscribeAll(this);
this._private__timeScaleApi._internal_destroy();
this._private__chartWidget._internal_destroy();
this._private__seriesMap.clear();
this._private__seriesMapReversed.clear();
this._private__clickedDelegate._internal_destroy();
this._private__dblClickedDelegate._internal_destroy();
this._private__crosshairMovedDelegate._internal_destroy();
this._private__dataLayer._internal_destroy();
}
resize(width, height, forceRepaint) {
if (this.autoSizeActive()) {
// We return early here instead of checking this within the actual _chartWidget.resize method
// because this should only apply to external resize requests.
warn(`Height and width values ignored because 'autoSize' option is enabled.`);
return;
}
this._private__chartWidget._internal_resize(width, height, forceRepaint);
}
addCustomSeries(customPaneView, options = {}, paneIndex = 0) {
const paneView = ensure(customPaneView);
const definition = createCustomSeriesDefinition(paneView);
return this._private__addSeriesImpl(definition, options, paneIndex);
}
addSeries(definition, options = {}, paneIndex = 0) {
return this._private__addSeriesImpl(definition, options, paneIndex);
}
removeSeries(seriesApi) {
const series = ensureDefined(this._private__seriesMap.get(seriesApi));
const update = this._private__dataLayer._internal_removeSeries(series);
const model = this._private__chartWidget._internal_model();
model._internal_removeSeries(series);
this._private__sendUpdateToChart(update);
this._private__seriesMap.delete(seriesApi);
this._private__seriesMapReversed.delete(series);
}
_internal_applyNewData(series, data) {
this._private__sendUpdateToChart(this._private__dataLayer._internal_setSeriesData(series, data));
}
_internal_updateData(series, data, historicalUpdate) {
this._private__sendUpdateToChart(this._private__dataLayer._internal_updateSeriesData(series, data, historicalUpdate));
}
_internal_popData(series, count) {
const [poppedData, update] = this._private__dataLayer._internal_popSeriesData(series, count);
if (poppedData.length !== 0) {
this._private__sendUpdateToChart(update);
}
return poppedData;
}
subscribeClick(handler) {
this._private__clickedDelegate._internal_subscribe(handler);
}
unsubscribeClick(handler) {
this._private__clickedDelegate._internal_unsubscribe(handler);
}
subscribeCrosshairMove(handler) {
this._private__crosshairMovedDelegate._internal_subscribe(handler);
}
unsubscribeCrosshairMove(handler) {
this._private__crosshairMovedDelegate._internal_unsubscribe(handler);
}
subscribeDblClick(handler) {
this._private__dblClickedDelegate._internal_subscribe(handler);
}
unsubscribeDblClick(handler) {
this._private__dblClickedDelegate._internal_unsubscribe(handler);
}
priceScale(priceScaleId, paneIndex = 0) {
return new PriceScaleApi(this._private__chartWidget, priceScaleId, paneIndex);
}
timeScale() {
return this._private__timeScaleApi;
}
applyOptions(options) {
{
const colorSpace = options.layout?.colorSpace;
if (colorSpace !== undefined && colorSpace !== this.options().layout.colorSpace) {
throw new Error(`colorSpace option should not be changed once the chart has been created.`);
}
const colorParsers = options.layout?.colorParsers;
if (colorParsers !== undefined && colorParsers !== this.options().layout.colorParsers) {
throw new Error(`colorParsers option should not be changed once the chart has been created.`);
}
}
this._private__chartWidget._internal_applyOptions(toInternalOptions(options));
}
options() {
return this._private__chartWidget._internal_options();
}
takeScreenshot(addTopLayer = false, includeCrosshair = false) {
let crosshairMode;
let screenshotCanvas;
try {
if (!includeCrosshair) {
crosshairMode = this._private__chartWidget._internal_model()._internal_options().crosshair.mode;
this._private__chartWidget._internal_applyOptions({
crosshair: {
mode: 2 /* CrosshairMode.Hidden */,
},
});
}
screenshotCanvas = this._private__chartWidget._internal_takeScreenshot(addTopLayer);
}
finally {
if (!includeCrosshair && crosshairMode !== undefined) {
this._private__chartWidget._internal_model()._internal_applyOptions({
crosshair: {
mode: crosshairMode,
},
});
}
}
return screenshotCanvas;
}
addPane(preserveEmptyPane = false) {
const pane = this._private__chartWidget._internal_model()._internal_addPane();
pane._internal_setPreserveEmptyPane(preserveEmptyPane);
return this._private__getPaneApi(pane);
}
removePane(index) {
this._private__chartWidget._internal_model()._internal_removePane(index);
}
swapPanes(first, second) {
this._private__chartWidget._internal_model()._internal_swapPanes(first, second);
}
autoSizeActive() {
return this._private__chartWidget._internal_autoSizeActive();
}
chartElement() {
return this._private__chartWidget._internal_element();
}
panes() {
return this._private__chartWidget._internal_model()._internal_panes().map((pane) => this._private__getPaneApi(pane));
}
paneSize(paneIndex = 0) {
const size = this._private__chartWidget._internal_paneSize(paneIndex);
return {
height: size.height,
width: size.width,
};
}
setCrosshairPosition(price, horizontalPosition, seriesApi) {
const series = this._private__seriesMap.get(seriesApi);
if (series === undefined) {
return;
}
const pane = this._private__chartWidget._internal_model()._internal_paneForSource(series);
if (pane === null) {
return;
}
this._private__chartWidget._internal_model()._internal_setAndSaveSyntheticPosition(price, horizontalPosition, pane);
}
clearCrosshairPosition() {
this._private__chartWidget._internal_model()._internal_clearCurrentPosition(true);
}
horzBehaviour() {
return this._internal__horzScaleBehavior;
}
_private__addSeriesImpl(definition, options = {}, paneIndex = 0) {
assert(isSeriesDefinition(definition));
patchPriceFormat(options.priceFormat);
if (definition.type === 'Candlestick') {
fillUpDownCandlesticksColors(options);
}
const strictOptions = merge(clone(seriesOptionsDefaults), clone(definition.defaultOptions), options);
const createPaneView = definition._internal_createPaneView;
const series = new Series(this._private__chartWidget._internal_model(), definition.type, strictOptions, createPaneView, definition._internal_customPaneView);
this._private__chartWidget._internal_model()._internal_addSeriesToPane(series, paneIndex);
const res = new SeriesApi(series, this, this, this, this._internal__horzScaleBehavior, (pane) => this._private__getPaneApi(pane));
this._private__seriesMap.set(res, series);
this._private__seriesMapReversed.set(series, res);
return res;
}
_private__sendUpdateToChart(update) {
const model = this._private__chartWidget._internal_model();
model._internal_updateTimeScale(update._internal_timeScale._internal_baseIndex, update._internal_timeScale._internal_points, update._internal_timeScale._internal_firstChangedPointIndex);
update._internal_series.forEach((value, series) => series._internal_setData(value._internal_data, value._internal_info));
model._internal_timeScale()._internal_recalculateIndicesWithData();
model._internal_recalculateAllPanes();
}
_private__mapSeriesToApi(series) {
return ensureDefined(this._private__seriesMapReversed.get(series));
}
_private__resolveSeriesApi(series) {
return series !== undefined && this._private__seriesMapReversed.has(series) ? this._private__mapSeriesToApi(series) : undefined;
}
_private__convertMouseParams(param) {
const seriesData = new Map();
param._internal_seriesData.forEach((plotRow, series) => {
const seriesType = series._internal_seriesType();
const data = getSeriesDataCreator(seriesType)(plotRow);
if (seriesType !== 'Custom') {
assert(isFulfilledData(data));
}
else {
const customWhitespaceChecker = series._internal_customSeriesWhitespaceCheck();
assert(!customWhitespaceChecker || customWhitespaceChecker(data) === false);
}
seriesData.set(this._private__mapSeriesToApi(series), data);
});
const hoveredSeries = this._private__resolveSeriesApi(param._internal_hoveredSeries);
const hoveredInfo = param._internal_hoveredInfo === undefined
? undefined
: {
type: param._internal_hoveredInfo._internal_type,
sourceKind: param._internal_hoveredInfo._internal_sourceKind,
objectKind: param._internal_hoveredInfo._internal_objectKind,
series: this._private__resolveSeriesApi(param._internal_hoveredInfo._internal_series),
objectId: param._internal_hoveredInfo._internal_objectId,
paneIndex: param._internal_hoveredInfo._internal_paneIndex,
};
return {
time: param._internal_originalTime,
logical: param._internal_index,
point: param._internal_point,
paneIndex: param._internal_paneIndex,
hoveredInfo,
hoveredSeries,
hoveredObjectId: param._internal_hoveredObject,
seriesData,
sourceEvent: param._internal_touchMouseEventData,
};
}
_private__getPaneApi(pane) {
let result = this._private__panes.get(pane);
if (!result) {
result = new PaneApi(this._private__chartWidget, (series) => this._private__mapSeriesToApi(series), pane, this);
this._private__panes.set(pane, result);
}
return result;
}
}
function fetchHtmlElement(container) {
if (isString(container)) {
const element = document.getElementById(container);
assert(element !== null, `Cannot find element in DOM with id=${container}`);
return element;
}
return container;
}
/**
* This function is the main entry point of the Lightweight Charting Library. If you are using time values
* for the horizontal scale then it is recommended that you rather use the {@link createChart} function.
*
* @template HorzScaleItem - type of points on the horizontal scale
* @template THorzScaleBehavior - type of horizontal axis strategy that encapsulate all the specific behaviors of the horizontal scale type
*
* @param container - ID of HTML element or element itself
* @param horzScaleBehavior - Horizontal scale behavior
* @param options - Any subset of options to be applied at start.
* @returns An interface to the created chart
*/
function createChartEx(container, horzScaleBehavior, options) {
const htmlElement = fetchHtmlElement(container);
const res = new ChartApi(htmlElement, horzScaleBehavior, options);
horzScaleBehavior.setOptions(res.options());
return res;
}
/**
* This function is the simplified main entry point of the Lightweight Charting Library with time points for the horizontal scale.
*
* @param container - ID of HTML element or element itself
* @param options - Any subset of options to be applied at start.
* @returns An interface to the created chart
*/
function createChart(container, options) {
return createChartEx(container, new HorzScaleBehaviorTime(), HorzScaleBehaviorTime._internal_applyDefaults(options));
}
/**
* Provides the default implementation of the horizontal scale (time-based) that can be used as a base for extending the horizontal scale with custom behavior.
* This allows for the introduction of custom functionality without re-implementing the entire {@link IHorzScaleBehavior}<{@link Time}> interface.
*
* For further details, refer to the {@link createChartEx} chart constructor method.
*
* @returns An uninitialized class implementing the {@link IHorzScaleBehavior}<{@link Time}> interface
*/
function defaultHorzScaleBehavior() {
return HorzScaleBehaviorTime;
}
function drawSeriesPointMarkers(renderingScope, items, pointMarkersRadius, visibleRange,
// the values returned by styleGetter are compared using the operator !==,
// so if styleGetter returns objects, then styleGetter should return the same object for equal styles
styleGetter) {
if (visibleRange.to - visibleRange.from <= 0) {
return;
}
const { horizontalPixelRatio, verticalPixelRatio, context } = renderingScope;
let prevStyle = null;
const tickWidth = Math.max(1, Math.floor(horizontalPixelRatio));
const correction = (tickWidth % 2) / 2;
const radius = pointMarkersRadius * verticalPixelRatio + correction;
for (let i = visibleRange.to - 1; i >= visibleRange.from; --i) {
const point = items[i];
if (point) {
const style = styleGetter(renderingScope, point);
if (style !== prevStyle) {
if (prevStyle !== null) {
context.fill();
}
context.beginPath();
context.fillStyle = style;
prevStyle = style;
}
const centerX = Math.round(point._internal_x * horizontalPixelRatio) + correction; // correct x coordinate only
const centerY = point._internal_y * verticalPixelRatio;
context.moveTo(centerX, centerY);
context.arc(centerX, centerY, radius, 0, Math.PI * 2);
}
}
context.fill();
}
function distanceByCoordinates(p1x, p1y, p2x, p2y) {
return Math.hypot(p2x - p1x, p2y - p1y);
}
// eslint-disable-next-line max-params, complexity
function walkLine(renderingScope, items, lineType, visibleRange, barWidth,
// the values returned by styleGetter are compared using the operator !==,
// so if styleGetter returns objects, then styleGetter should return the same object for equal styles
styleGetter, finishStyledArea, dashPatternLength = 0) {
if (items.length === 0 || visibleRange.from >= items.length || visibleRange.to <= 0) {
return;
}
const { context: ctx, horizontalPixelRatio, verticalPixelRatio } = renderingScope;
const firstItem = items[visibleRange.from];
let currentStyle = styleGetter(renderingScope, firstItem);
let currentStyleFirstItem = firstItem;
if (visibleRange.to - visibleRange.from < 2) {
const halfBarWidth = barWidth / 2;
ctx.beginPath();
const item1 = { _internal_x: firstItem._internal_x - halfBarWidth, _internal_y: firstItem._internal_y };
const item2 = { _internal_x: firstItem._internal_x + halfBarWidth, _internal_y: firstItem._internal_y };
ctx.moveTo(item1._internal_x * horizontalPixelRatio, item1._internal_y * verticalPixelRatio);
ctx.lineTo(item2._internal_x * horizontalPixelRatio, item2._internal_y * verticalPixelRatio);
finishStyledArea(renderingScope, currentStyle, item1, item2);
}
else {
const shouldTrackDashOffset = dashPatternLength > 0;
let accumulatedDistance = 0;
const changeStyle = (newStyle, currentItem) => {
finishStyledArea(renderingScope, currentStyle, currentStyleFirstItem, currentItem);
ctx.beginPath();
currentStyle = newStyle;
currentStyleFirstItem = currentItem;
if (shouldTrackDashOffset) {
const offset = accumulatedDistance % dashPatternLength;
ctx.lineDashOffset = offset;
// reset to the remainder to avoid floating-point precision drift over very long series.
accumulatedDistance = offset;
}
};
let currentItem = currentStyleFirstItem;
ctx.beginPath();
ctx.moveTo(firstItem._internal_x * horizontalPixelRatio, firstItem._internal_y * verticalPixelRatio);
for (let i = visibleRange.from + 1; i < visibleRange.to; ++i) {
currentItem = items[i];
const currentX = currentItem._internal_x * horizontalPixelRatio;
const currentY = currentItem._internal_y * verticalPixelRatio;
const itemStyle = styleGetter(renderingScope, currentItem);
switch (lineType) {
case 0 /* LineType.Simple */: {
ctx.lineTo(currentX, currentY);
if (shouldTrackDashOffset) {
const prevItem = items[i - 1];
const prevX = prevItem._internal_x * horizontalPixelRatio;
const prevY = prevItem._internal_y * verticalPixelRatio;
accumulatedDistance += distanceByCoordinates(prevX, prevY, currentX, currentY);
}
break;
}
case 1 /* LineType.WithSteps */: {
const prevItem = items[i - 1];
const prevY = prevItem._internal_y * verticalPixelRatio;
ctx.lineTo(currentX, prevY);
if (shouldTrackDashOffset) {
accumulatedDistance += Math.abs(currentItem._internal_x - prevItem._internal_x) * horizontalPixelRatio;
}
if (itemStyle !== currentStyle) {
changeStyle(itemStyle, currentItem);
ctx.lineTo(currentX, prevY);
}
ctx.lineTo(currentX, currentY);
if (shouldTrackDashOffset) {
accumulatedDistance += Math.abs(currentItem._internal_y - prevItem._internal_y) * verticalPixelRatio;
}
break;
}
case 2 /* LineType.Curved */: {
const [cp1, cp2] = getControlPoints(items, i - 1, i);
const cp1x = cp1._internal_x * horizontalPixelRatio;
const cp1y = cp1._internal_y * verticalPixelRatio;
const cp2x = cp2._internal_x * horizontalPixelRatio;
const cp2y = cp2._internal_y * verticalPixelRatio;
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, currentX, currentY);
if (shouldTrackDashOffset) {
const prevItem = items[i - 1];
const prevX = prevItem._internal_x * horizontalPixelRatio;
const prevY = prevItem._internal_y * verticalPixelRatio;
const chord = distanceByCoordinates(prevX, prevY, currentX, currentY);
const controlPolygon = distanceByCoordinates(prevX, prevY, cp1x, cp1y) +
distanceByCoordinates(cp1x, cp1y, cp2x, cp2y) +
distanceByCoordinates(cp2x, cp2y, currentX, currentY);
accumulatedDistance += (chord + controlPolygon) / 2;
}
break;
}
}
if (lineType !== 1 /* LineType.WithSteps */ && itemStyle !== currentStyle) {
changeStyle(itemStyle, currentItem);
ctx.moveTo(currentX, currentY);
}
}
if (currentStyleFirstItem !== currentItem || currentStyleFirstItem === currentItem && lineType === 1 /* LineType.WithSteps */) {
finishStyledArea(renderingScope, currentStyle, currentStyleFirstItem, currentItem);
}
if (shouldTrackDashOffset) {
ctx.lineDashOffset = 0;
}
}
}
const curveTension = 6;
function subtract(p1, p2) {
return { _internal_x: p1._internal_x - p2._internal_x, _internal_y: p1._internal_y - p2._internal_y };
}
function add(p1, p2) {
return { _internal_x: p1._internal_x + p2._internal_x, _internal_y: p1._internal_y + p2._internal_y };
}
function divide(p1, n) {
return { _internal_x: p1._internal_x / n, _internal_y: p1._internal_y / n };
}
/**
* @returns Two control points that can be used as arguments to {@link CanvasRenderingContext2D.bezierCurveTo} to draw a curved line between `points[fromPointIndex]` and `points[toPointIndex]`.
*/
function getControlPoints(points, fromPointIndex, toPointIndex) {
const beforeFromPointIndex = Math.max(0, fromPointIndex - 1);
const afterToPointIndex = Math.min(points.length - 1, toPointIndex + 1);
const cp1 = add(points[fromPointIndex], divide(subtract(points[toPointIndex], points[beforeFromPointIndex]), curveTension));
const cp2 = subtract(points[toPointIndex], divide(subtract(points[afterToPointIndex], points[fromPointIndex]), curveTension));
return [cp1, cp2];
}
function finishStyledArea$1(scope, style) {
const ctx = scope.context;
ctx.strokeStyle = style;
ctx.stroke();
}
class PaneRendererLineBase extends BitmapCoordinatesPaneRenderer {
constructor() {
super(...arguments);
this._internal__data = null;
}
_internal_setData(data) {
this._internal__data = data;
}
_internal__drawImpl(renderingScope) {
if (this._internal__data === null) {
return;
}
const { _internal_items: items, _internal_visibleRange: visibleRange, _internal_barWidth: barWidth, _internal_lineType: lineType, _internal_lineWidth: lineWidth, _internal_lineStyle: lineStyle, _internal_pointMarkersRadius: pointMarkersRadius } = this._internal__data;
if (visibleRange === null) {
return;
}
const ctx = renderingScope.context;
ctx.lineCap = 'butt';
ctx.lineWidth = lineWidth * renderingScope.verticalPixelRatio;
const dashPattern = setLineStyle(ctx, lineStyle);
ctx.lineJoin = 'round';
const styleGetter = this._internal__strokeStyle.bind(this);
const dashPatternLength = getDashPatternLength(dashPattern);
if (lineType !== undefined) {
walkLine(renderingScope, items, lineType, visibleRange, barWidth, styleGetter, finishStyledArea$1, dashPatternLength);
}
if (pointMarkersRadius) {
drawSeriesPointMarkers(renderingScope, items, pointMarkersRadius, visibleRange, styleGetter);
}
}
}
class PaneRendererLine extends PaneRendererLineBase {
_internal__strokeStyle(renderingScope, item) {
return item._internal_lineColor;
}
}
const BEZIER_APPROXIMATION_STEPS = 12;
function cubicBezierPoint(p0, p1, p2, p3, t) {
const u = 1 - t;
return u * u * u * p0
+ 3 * u * u * t * p1
+ 3 * u * t * t * p2
+ t * t * t * p3;
}
function distanceToBezierCurve(x, y, points) {
let minDistance = Number.POSITIVE_INFINITY;
let previousPoint = points[0];
for (let step = 1; step <= BEZIER_APPROXIMATION_STEPS; step++) {
const t = step / BEZIER_APPROXIMATION_STEPS;
const currentPoint = {
_internal_x: cubicBezierPoint(points[0]._internal_x, points[1]._internal_x, points[2]._internal_x, points[3]._internal_x, t),
_internal_y: cubicBezierPoint(points[0]._internal_y, points[1]._internal_y, points[2]._internal_y, points[3]._internal_y, t),
};
minDistance = Math.min(minDistance, distanceToSegment(x, y, previousPoint._internal_x, previousPoint._internal_y, currentPoint._internal_x, currentPoint._internal_y));
previousPoint = currentPoint;
}
return minDistance;
}
function lineSegmentHorizontalBounds(firstItem, secondItem, lineType, items, toItemIndex) {
switch (lineType) {
case 2 /* LineType.Curved */: {
const [firstControlPoint, secondControlPoint] = getControlPoints(items, toItemIndex - 1, toItemIndex);
const minX = Math.min(firstItem._internal_x, secondItem._internal_x, firstControlPoint._internal_x, secondControlPoint._internal_x);
const maxX = Math.max(firstItem._internal_x, secondItem._internal_x, firstControlPoint._internal_x, secondControlPoint._internal_x);
return [minX, maxX];
}
case 1 /* LineType.WithSteps */:
case 0 /* LineType.Simple */:
default: {
const minX = Math.min(firstItem._internal_x, secondItem._internal_x);
const maxX = Math.max(firstItem._internal_x, secondItem._internal_x);
return [minX, maxX];
}
}
}
// eslint-disable-next-line max-params
function hitTestLineSegment(x, y, firstItem, secondItem, lineType, items, toItemIndex, radius) {
switch (lineType) {
case 1 /* LineType.WithSteps */:
{
const horizontalDistance = distanceToSegment(x, y, firstItem._internal_x, firstItem._internal_y, secondItem._internal_x, firstItem._internal_y);
const verticalDistance = distanceToSegment(x, y, secondItem._internal_x, firstItem._internal_y, secondItem._internal_x, secondItem._internal_y);
const minDistance = Math.min(horizontalDistance, verticalDistance);
return minDistance <= radius ? minDistance : null;
}
case 2 /* LineType.Curved */: {
const [firstControlPoint, secondControlPoint] = getControlPoints(items, toItemIndex - 1, toItemIndex);
const distance = distanceToBezierCurve(x, y, [firstItem, firstControlPoint, secondControlPoint, secondItem]);
return distance <= radius ? distance : null;
}
case 0 /* LineType.Simple */:
default:
{
const distance = distanceToSegment(x, y, firstItem._internal_x, firstItem._internal_y, secondItem._internal_x, secondItem._internal_y);
return distance <= radius ? distance : null;
}
}
}
// eslint-disable-next-line max-params, complexity
function hitTestLineSeries(items, visibleRange, x, y, lineType, lineWidth, pointMarkersRadius, barSpacing = 0, hitTestTolerance = 0) {
if (visibleRange === null || visibleRange.from >= visibleRange.to || items.length === 0) {
return null;
}
const radius = Math.max(lineWidth / 2, pointMarkersRadius ?? 0) + hitTestTolerance;
let pointMinDistance = Number.POSITIVE_INFINITY;
if (pointMarkersRadius !== undefined) {
const pointRadius = pointMarkersRadius + hitTestTolerance;
const pointCandidateFrom = lowerBoundByX(items, x - pointRadius, visibleRange.from, visibleRange.to);
const pointCandidateTo = upperBoundByX(items, x + pointRadius, pointCandidateFrom, visibleRange.to);
for (let itemIndex = pointCandidateFrom; itemIndex < pointCandidateTo; itemIndex++) {
const item = items[itemIndex];
if (!isWithinHorizontalSweep(x, item._internal_x, item._internal_x, pointMarkersRadius + hitTestTolerance)) {
continue;
}
const distance = Math.hypot(x - item._internal_x, y - item._internal_y);
if (distance <= pointMarkersRadius + hitTestTolerance) {
pointMinDistance = Math.min(pointMinDistance, distance);
}
}
}
if (visibleRange.to - visibleRange.from < 2) {
const item = items[visibleRange.from];
const singlePointHalfWidth = Math.max(barSpacing / 2, radius);
const distance = distanceToSegment(x, y, item._internal_x - singlePointHalfWidth, item._internal_y, item._internal_x + singlePointHalfWidth, item._internal_y);
if (distance <= radius) {
pointMinDistance = Math.min(pointMinDistance, distance);
}
return Number.isFinite(pointMinDistance) ? hoveredSeriesHitTestResult(pointMinDistance, 2 /* HitTestPriority.Point */, 'series-point') : null;
}
let lineMinDistance = Number.POSITIVE_INFINITY;
const lineCandidateFrom = lowerBoundByX(items, x - radius, visibleRange.from, visibleRange.to);
const lineCandidateTo = upperBoundByX(items, x + radius, lineCandidateFrom, visibleRange.to);
const segmentFrom = Math.max(visibleRange.from + 1, lineCandidateFrom);
const segmentTo = Math.min(visibleRange.to, lineCandidateTo + 1);
for (let itemIndex = segmentFrom; itemIndex < segmentTo; itemIndex++) {
const previousItem = items[itemIndex - 1];
const currentItem = items[itemIndex];
const [leftX, rightX] = lineSegmentHorizontalBounds(previousItem, currentItem, lineType, items, itemIndex);
if (!isWithinHorizontalSweep(x, leftX, rightX, radius)) {
continue;
}
const distance = hitTestLineSegment(x, y, previousItem, currentItem, lineType, items, itemIndex, radius);
if (distance !== null) {
lineMinDistance = Math.min(lineMinDistance, distance);
}
}
if (Number.isFinite(pointMinDistance)) {
return hoveredSeriesHitTestResult(pointMinDistance, 2 /* HitTestPriority.Point */, 'series-point');
}
return Number.isFinite(lineMinDistance) ? hoveredSeriesHitTestResult(lineMinDistance, 1 /* HitTestPriority.Line */, 'series-line') : null;
}
class LinePaneViewBase extends SeriesPaneViewBase {
constructor(series, model) {
super(series, model, true);
}
_internal__convertToCoordinates(priceScale, timeScale, firstValue) {
timeScale._internal_indexesToCoordinates(this._internal__items, undefinedIfNull(this._internal__itemsVisibleRange));
priceScale._internal_pointsArrayToCoordinates(this._internal__items, firstValue, undefinedIfNull(this._internal__itemsVisibleRange));
}
_internal__createRawItemBase(time, price) {
return {
_internal_time: time,
_internal_price: price,
_internal_x: NaN,
_internal_y: NaN,
};
}
_internal__fillRawPoints() {
const colorer = this._internal__series._internal_barColorer();
this._internal__items = this._internal__series._internal_conflatedBars()._internal_rows().map((row) => {
const isConflated = (row._internal_originalDataCount ?? 1) > 1;
let value;
if (isConflated) {
const high = row._internal_value[1 /* PlotRowValueIndex.High */];
const low = row._internal_value[2 /* PlotRowValueIndex.Low */];
const close = row._internal_value[3 /* PlotRowValueIndex.Close */];
const highMove = Math.abs(high - close);
const lowMove = Math.abs(low - close);
// in case of conflation we want to show more extreme price to represent the range
// and we choose the one which is further from the close price
value = (highMove > lowMove) ? high : low;
}
else {
value = row._internal_value[3 /* PlotRowValueIndex.Close */];
}
return this._internal__createRawItem(row._internal_index, value, colorer);
});
}
}
class LineHitTestPaneViewBase extends LinePaneViewBase {
_internal__hitTestImpl(x, y) {
const options = this._internal__series._internal_options();
return hitTestLineSeries(this._internal__items, this._internal__itemsVisibleRange, x, y, options.lineType, options.lineVisible ? options.lineWidth : 1, options.pointMarkersVisible ? (options.pointMarkersRadius || options.lineWidth / 2 + 2) : undefined, this._internal__model._internal_timeScale()._internal_barSpacing(), options.hitTestTolerance);
}
}
class SeriesLinePaneView extends LineHitTestPaneViewBase {
constructor() {
super(...arguments);
this._internal__renderer = new PaneRendererLine();
}
_internal__createRawItem(time, price, colorer) {
return {
...this._internal__createRawItemBase(time, price),
...colorer._internal_barStyle(time),
};
}
_internal__prepareRendererData() {
const options = this._internal__series._internal_options();
const data = {
_internal_items: this._internal__items,
_internal_lineStyle: options.lineStyle,
_internal_lineType: options.lineVisible ? options.lineType : undefined,
_internal_lineWidth: options.lineWidth,
_internal_pointMarkersRadius: options.pointMarkersVisible ? (options.pointMarkersRadius || options.lineWidth / 2 + 2) : undefined,
_internal_visibleRange: this._internal__itemsVisibleRange,
_internal_barWidth: this._internal__model._internal_timeScale()._internal_barSpacing(),
};
this._internal__renderer._internal_setData(data);
}
}
const lineStyleDefaults = {
color: '#2196f3',
lineStyle: 0 /* LineStyle.Solid */,
lineWidth: 3,
lineType: 0 /* LineType.Simple */,
lineVisible: true,
crosshairMarkerVisible: true,
crosshairMarkerRadius: 4,
crosshairMarkerBorderColor: '',
crosshairMarkerBorderWidth: 2,
crosshairMarkerBackgroundColor: '',
lastPriceAnimation: 0 /* LastPriceAnimationMode.Disabled */,
pointMarkersVisible: false,
};
const createPaneView$5 = (series, model) => new SeriesLinePaneView(series, model);
const createLineSeries = () => {
const definition = {
type: 'Line',
isBuiltIn: true,
defaultOptions: lineStyleDefaults,
/**
* @internal
*/
_internal_createPaneView: createPaneView$5,
};
return definition;
};
const lineSeries = createLineSeries();
function createDebouncedMicroTaskHandler(callback) {
let scheduled = false;
return function (...args) {
if (!scheduled) {
scheduled = true;
queueMicrotask(() => {
callback(...args);
scheduled = false;
});
}
};
}
function markWithGreaterWeight$1(a, b) {
return a.weight > b.weight ? a : b;
}
function toInternalHorzScaleItem(item) {
return item;
}
function fromInternalHorzScaleItem(item) {
return item;
}
class YieldCurveHorzScaleBehavior {
constructor() {
this._private__pointsChangedDelegate = new Delegate();
this._private__invalidateWhitespace = createDebouncedMicroTaskHandler(() => this._private__pointsChangedDelegate._internal_fire(this._private__largestIndex));
this._private__largestIndex = 0;
}
/** Data changes might require that the whitespace be generated again */
_internal_whitespaceInvalidated() {
return this._private__pointsChangedDelegate;
}
_internal_destroy() {
this._private__pointsChangedDelegate._internal_destroy();
}
options() {
return this._private__options;
}
setOptions(options) {
this._private__options = options;
}
preprocessData(data) {
// No preprocessing needed for yield curve data
}
updateFormatter(options) {
if (!this._private__options) {
return;
}
this._private__options.localization = options;
}
createConverterToInternalObj(data) {
this._private__invalidateWhitespace();
return (time) => {
if (time > this._private__largestIndex) {
this._private__largestIndex = time;
}
return toInternalHorzScaleItem(time);
};
}
key(internalItem) {
return internalItem;
}
cacheKey(internalItem) {
return fromInternalHorzScaleItem(internalItem);
}
convertHorzItemToInternal(item) {
return toInternalHorzScaleItem(item);
}
formatHorzItem(item) {
return this._private__formatTime(item);
}
formatTickmark(item) {
return this._private__formatTime(item.time);
}
maxTickMarkWeight(marks) {
return marks.reduce(markWithGreaterWeight$1, marks[0]).weight;
}
fillWeightsForPoints(sortedTimePoints, startIndex) {
const timeWeight = (time) => {
if (time % 120 === 0) {
return 10;
}
if (time % 60 === 0) {
return 9;
}
if (time % 36 === 0) {
return 8;
}
if (time % 12 === 0) {
return 7;
}
if (time % 6 === 0) {
return 6;
}
if (time % 3 === 0) {
return 5;
}
if (time % 1 === 0) {
return 4;
}
return 0;
};
for (let index = startIndex; index < sortedTimePoints.length; ++index) {
sortedTimePoints[index].timeWeight = timeWeight(fromInternalHorzScaleItem(sortedTimePoints[index].time));
}
this._private__largestIndex = fromInternalHorzScaleItem(sortedTimePoints[sortedTimePoints.length - 1].time);
this._private__invalidateWhitespace();
}
_private__formatTime(months) {
if (this._private__options.localization?.timeFormatter) {
return this._private__options.localization.timeFormatter(months);
}
if (months < 12) {
return `${months}M`;
}
const years = Math.floor(months / 12);
const remainingMonths = months % 12;
if (remainingMonths === 0) {
return `${years}Y`;
}
return `${years}Y${remainingMonths}M`;
}
}
const yieldChartOptionsDefaults = {
baseResolution: 1,
minimumTimeRange: 120,
startTimeRange: 0,
};
function generateWhitespaceData({ _internal_start: start, _internal_end: end, _internal_resolution: resolution, }) {
return Array.from({ length: Math.floor((end - start) / resolution) + 1 },
// eslint-disable-next-line quote-props
(item, i) => ({ 'time': start + i * resolution }));
}
function buildWhitespaceState(options, lastIndex) {
return {
_internal_start: Math.max(0, options.startTimeRange),
_internal_end: Math.max(0, options.minimumTimeRange, lastIndex || 0),
_internal_resolution: Math.max(1, options.baseResolution),
};
}
const generateWhitespaceHash = ({ _internal_start: start, _internal_end: end, _internal_resolution: resolution, }) => `${start}~${end}~${resolution}`;
const defaultOptions = {
yieldCurve: yieldChartOptionsDefaults,
// and add sensible default options for yield charts which
// are different from the usual defaults.
timeScale: {
ignoreWhitespaceIndices: true,
},
leftPriceScale: {
visible: true,
},
rightPriceScale: {
visible: false,
},
localization: {
priceFormatter: (value) => {
return value.toFixed(3) + '%';
},
},
};
const lineStyleDefaultOptionOverrides = {
lastValueVisible: false,
priceLineVisible: false,
};
class YieldChartApi extends ChartApi {
constructor(container, options) {
const fullOptions = merge(defaultOptions, options || {});
const horzBehaviour = new YieldCurveHorzScaleBehavior();
super(container, horzBehaviour, fullOptions);
horzBehaviour.setOptions(this.options());
this._initWhitespaceSeries();
}
addSeries(definition, options = {}, paneIndex = 0) {
if (definition.isBuiltIn && ['Area', 'Line'].includes(definition.type) === false) {
throw new Error('Yield curve only support Area and Line series');
}
const optionOverrides = {
...lineStyleDefaultOptionOverrides,
...options,
};
return super.addSeries(definition, optionOverrides, paneIndex);
}
_initWhitespaceSeries() {
const horzBehaviour = this.horzBehaviour();
const whiteSpaceSeries = this.addSeries(lineSeries);
let currentWhitespaceHash;
function updateWhitespace(lastIndex) {
const newWhitespaceState = buildWhitespaceState(horzBehaviour.options().yieldCurve, lastIndex);
const newWhitespaceHash = generateWhitespaceHash(newWhitespaceState);
if (newWhitespaceHash !== currentWhitespaceHash) {
currentWhitespaceHash = newWhitespaceHash;
whiteSpaceSeries.setData(generateWhitespaceData(newWhitespaceState));
}
}
updateWhitespace(0);
horzBehaviour._internal_whitespaceInvalidated()._internal_subscribe(updateWhitespace);
}
}
/**
* Creates a yield curve chart with the specified options.
*
* A yield curve chart differs from the default chart type
* in the following ways:
* - Horizontal scale is linearly spaced, and defined in monthly
* time duration units
* - Whitespace is ignored for the crosshair and grid lines
*
* @param container - ID of HTML element or element itself
* @param options - The yield chart options.
* @returns An interface to the created chart
*/
function createYieldCurveChart(container, options) {
const htmlElement = fetchHtmlElement(container);
const chartApi = new YieldChartApi(htmlElement, options);
return chartApi;
}
function markWithGreaterWeight(a, b) {
return a.weight > b.weight ? a : b;
}
class HorzScaleBehaviorPrice {
options() {
return this._private__options;
}
setOptions(options) {
this._private__options = options;
}
preprocessData(data) { }
updateFormatter(options) {
if (!this._private__options) {
return;
}
this._private__options.localization = options;
}
createConverterToInternalObj(data) {
return (price) => price;
}
key(internalItem) {
return internalItem;
}
cacheKey(internalItem) {
return internalItem;
}
convertHorzItemToInternal(item) {
return item;
}
formatHorzItem(item) {
return item.toFixed(this._private__precision());
}
formatTickmark(item, localizationOptions) {
return item.time.toFixed(this._private__precision());
}
maxTickMarkWeight(marks) {
return marks.reduce(markWithGreaterWeight, marks[0]).weight;
}
fillWeightsForPoints(sortedTimePoints, startIndex) {
const priceWeight = (price) => {
if (price === Math.ceil(price / 100) * 100) {
return 8;
}
if (price === Math.ceil(price / 50) * 50) {
return 7;
}
if (price === Math.ceil(price / 25) * 25) {
return 6;
}
if (price === Math.ceil(price / 10) * 10) {
return 5;
}
if (price === Math.ceil(price / 5) * 5) {
return 4;
}
if (price === Math.ceil(price)) {
return 3;
}
if (price * 2 === Math.ceil(price * 2)) {
return 1;
}
return 0;
};
for (let index = startIndex; index < sortedTimePoints.length; ++index) {
sortedTimePoints[index].timeWeight = priceWeight(sortedTimePoints[index].time);
}
}
_private__precision() {
return this._private__options.localization
.precision;
}
}
/**
* Creates an 'options' chart with price values on the horizontal scale.
*
* This function is used to create a specialized chart type where the horizontal scale
* represents price values instead of time. It's particularly useful for visualizing
* option chains, price distributions, or any data where price is the primary x-axis metric.
*
* @param container - The DOM element or its id where the chart will be rendered.
* @param options - Optional configuration options for the price chart.
* @returns An instance of IChartApiBase configured for price-based horizontal scaling.
*/
function createOptionsChart(container, options) {
return createChartEx(container, new HorzScaleBehaviorPrice(), options);
}
function finishStyledArea(baseLevelCoordinate, scope, style, areaFirstItem, newAreaFirstItem) {
const { context, horizontalPixelRatio, verticalPixelRatio } = scope;
context.lineTo(newAreaFirstItem._internal_x * horizontalPixelRatio, baseLevelCoordinate * verticalPixelRatio);
context.lineTo(areaFirstItem._internal_x * horizontalPixelRatio, baseLevelCoordinate * verticalPixelRatio);
context.closePath();
context.fillStyle = style;
context.fill();
}
class PaneRendererAreaBase extends BitmapCoordinatesPaneRenderer {
constructor() {
super(...arguments);
this._internal__data = null;
}
_internal_setData(data) {
this._internal__data = data;
}
_internal__drawImpl(renderingScope) {
if (this._internal__data === null) {
return;
}
const { _internal_items: items, _internal_visibleRange: visibleRange, _internal_barWidth: barWidth, _internal_lineWidth: lineWidth, _internal_lineStyle: lineStyle, _internal_lineType: lineType } = this._internal__data;
const baseLevelCoordinate = this._internal__data._internal_baseLevelCoordinate ??
(this._internal__data._internal_invertFilledArea ? 0 : renderingScope.mediaSize.height);
if (visibleRange === null) {
return;
}
const ctx = renderingScope.context;
ctx.lineCap = 'butt';
ctx.lineJoin = 'round';
ctx.lineWidth = lineWidth;
setLineStyle(ctx, lineStyle);
// walk lines with width=1 to have more accurate gradient's filling
ctx.lineWidth = 1;
walkLine(renderingScope, items, lineType, visibleRange, barWidth, this._internal__fillStyle.bind(this), finishStyledArea.bind(null, baseLevelCoordinate));
}
}
class GradientStyleCache {
// eslint-disable-next-line complexity
_internal_get(scope, params) {
const cachedParams = this._private__params;
const { _internal_topColor1: topColor1, _internal_topColor2: topColor2, _internal_bottomColor1: bottomColor1, _internal_bottomColor2: bottomColor2, _internal_baseLevelCoordinate: baseLevelCoordinate, _internal_topCoordinate: topCoordinate, _internal_bottomCoordinate: bottomCoordinate, } = params;
if (this._private__cachedValue === undefined ||
cachedParams === undefined ||
cachedParams._internal_topColor1 !== topColor1 ||
cachedParams._internal_topColor2 !== topColor2 ||
cachedParams._internal_bottomColor1 !== bottomColor1 ||
cachedParams._internal_bottomColor2 !== bottomColor2 ||
cachedParams._internal_baseLevelCoordinate !== baseLevelCoordinate ||
cachedParams._internal_topCoordinate !== topCoordinate ||
cachedParams._internal_bottomCoordinate !== bottomCoordinate) {
const { verticalPixelRatio } = scope;
const multiplier = baseLevelCoordinate || topCoordinate > 0 ? verticalPixelRatio : 1;
const top = topCoordinate * multiplier;
const bottom = bottomCoordinate === scope.bitmapSize.height ? bottomCoordinate : bottomCoordinate * multiplier;
const baseline = (baseLevelCoordinate ?? 0) * multiplier;
const gradient = scope.context.createLinearGradient(0, top, 0, bottom);
gradient.addColorStop(0, topColor1);
if (baseLevelCoordinate !== null && baseLevelCoordinate !== undefined) {
const range = bottom - top;
const baselineRatio = clamp(((baseline - top) / range), 0, 1);
gradient.addColorStop(baselineRatio, topColor2);
gradient.addColorStop(baselineRatio, bottomColor1);
}
gradient.addColorStop(1, bottomColor2);
this._private__cachedValue = gradient;
this._private__params = params;
}
return this._private__cachedValue;
}
}
class PaneRendererBaselineArea extends PaneRendererAreaBase {
constructor() {
super(...arguments);
this._private__fillCache = new GradientStyleCache();
}
_internal__fillStyle(renderingScope, item) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const data = this._internal__data;
return this._private__fillCache._internal_get(renderingScope, {
_internal_topColor1: item._internal_topFillColor1,
_internal_topColor2: item._internal_topFillColor2,
_internal_bottomColor1: item._internal_bottomFillColor1,
_internal_bottomColor2: item._internal_bottomFillColor2,
_internal_baseLevelCoordinate: data._internal_baseLevelCoordinate,
_internal_topCoordinate: data._internal_topCoordinate ?? 0,
_internal_bottomCoordinate: data._internal_bottomCoordinate ?? renderingScope.bitmapSize.height,
});
}
}
class PaneRendererBaselineLine extends PaneRendererLineBase {
constructor() {
super(...arguments);
this._private__strokeCache = new GradientStyleCache();
}
_internal__strokeStyle(renderingScope, item) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const data = this._internal__data;
return this._private__strokeCache._internal_get(renderingScope, {
_internal_topColor1: item._internal_topLineColor,
_internal_topColor2: item._internal_topLineColor,
_internal_bottomColor1: item._internal_bottomLineColor,
_internal_bottomColor2: item._internal_bottomLineColor,
_internal_baseLevelCoordinate: data._internal_baseLevelCoordinate,
_internal_topCoordinate: data._internal_topCoordinate ?? 0,
_internal_bottomCoordinate: data._internal_bottomCoordinate ?? renderingScope.bitmapSize.height,
});
}
}
class SeriesBaselinePaneView extends LineHitTestPaneViewBase {
constructor(series, model) {
super(series, model);
this._internal__renderer = new CompositeRenderer();
this._private__baselineAreaRenderer = new PaneRendererBaselineArea();
this._private__baselineLineRenderer = new PaneRendererBaselineLine();
this._internal__renderer._internal_setRenderers([this._private__baselineAreaRenderer, this._private__baselineLineRenderer]);
}
_internal__createRawItem(time, price, colorer) {
return {
...this._internal__createRawItemBase(time, price),
...colorer._internal_barStyle(time),
};
}
_internal__prepareRendererData() {
const firstValue = this._internal__series._internal_firstValue();
if (firstValue === null) {
return;
}
const options = this._internal__series._internal_options();
const baseLevelCoordinate = this._internal__series._internal_priceScale()._internal_priceToCoordinate(options.baseValue.price, firstValue._internal_value);
const barWidth = this._internal__model._internal_timeScale()._internal_barSpacing();
if (this._internal__itemsVisibleRange === null || this._internal__items.length === 0) {
return;
}
let topCoordinate;
let bottomCoordinate;
if (options.relativeGradient) {
topCoordinate = this._internal__items[this._internal__itemsVisibleRange.from]._internal_y;
bottomCoordinate = this._internal__items[this._internal__itemsVisibleRange.from]._internal_y;
for (let i = this._internal__itemsVisibleRange.from; i < this._internal__itemsVisibleRange.to; i++) {
const item = this._internal__items[i];
if (item._internal_y < topCoordinate) {
topCoordinate = item._internal_y;
}
if (item._internal_y > bottomCoordinate) {
bottomCoordinate = item._internal_y;
}
}
}
this._private__baselineAreaRenderer._internal_setData({
_internal_items: this._internal__items,
_internal_lineWidth: options.lineWidth,
_internal_lineStyle: options.lineStyle,
_internal_lineType: options.lineType,
_internal_baseLevelCoordinate: baseLevelCoordinate,
_internal_topCoordinate: topCoordinate,
_internal_bottomCoordinate: bottomCoordinate,
_internal_invertFilledArea: false,
_internal_visibleRange: this._internal__itemsVisibleRange,
_internal_barWidth: barWidth,
});
this._private__baselineLineRenderer._internal_setData({
_internal_items: this._internal__items,
_internal_lineWidth: options.lineWidth,
_internal_lineStyle: options.lineStyle,
_internal_lineType: options.lineVisible ? options.lineType : undefined,
_internal_pointMarkersRadius: options.pointMarkersVisible ? (options.pointMarkersRadius || options.lineWidth / 2 + 2) : undefined,
_internal_baseLevelCoordinate: baseLevelCoordinate,
_internal_topCoordinate: topCoordinate,
_internal_bottomCoordinate: bottomCoordinate,
_internal_visibleRange: this._internal__itemsVisibleRange,
_internal_barWidth: barWidth,
});
}
}
const baselineStyleDefaults = {
baseValue: {
type: 'price',
price: 0,
},
relativeGradient: false,
topFillColor1: 'rgba(38, 166, 154, 0.28)',
topFillColor2: 'rgba(38, 166, 154, 0.05)',
topLineColor: 'rgba(38, 166, 154, 1)',
bottomFillColor1: 'rgba(239, 83, 80, 0.05)',
bottomFillColor2: 'rgba(239, 83, 80, 0.28)',
bottomLineColor: 'rgba(239, 83, 80, 1)',
lineWidth: 3,
lineStyle: 0 /* LineStyle.Solid */,
lineType: 0 /* LineType.Simple */,
lineVisible: true,
crosshairMarkerVisible: true,
crosshairMarkerRadius: 4,
crosshairMarkerBorderColor: '',
crosshairMarkerBorderWidth: 2,
crosshairMarkerBackgroundColor: '',
lastPriceAnimation: 0 /* LastPriceAnimationMode.Disabled */,
pointMarkersVisible: false,
};
const createPaneView$4 = (series, model) => new SeriesBaselinePaneView(series, model);
const createSeries$4 = () => {
const definition = {
type: 'Baseline',
isBuiltIn: true,
defaultOptions: baselineStyleDefaults,
/**
* @internal
*/
_internal_createPaneView: createPaneView$4,
};
return definition;
};
const baselineSeries = createSeries$4();
class PaneRendererArea extends PaneRendererAreaBase {
constructor() {
super(...arguments);
this._private__fillCache = new GradientStyleCache();
}
_internal__fillStyle(renderingScope, item) {
return this._private__fillCache._internal_get(renderingScope, {
_internal_topColor1: item._internal_topColor,
_internal_topColor2: '',
_internal_bottomColor1: '',
_internal_bottomColor2: item._internal_bottomColor,
_internal_topCoordinate: this._internal__data?._internal_topCoordinate ?? 0,
_internal_bottomCoordinate: renderingScope.bitmapSize.height,
});
}
}
class SeriesAreaPaneView extends LineHitTestPaneViewBase {
constructor(series, model) {
super(series, model);
this._internal__renderer = new CompositeRenderer();
this._private__areaRenderer = new PaneRendererArea();
this._private__lineRenderer = new PaneRendererLine();
this._internal__renderer._internal_setRenderers([this._private__areaRenderer, this._private__lineRenderer]);
}
_internal__createRawItem(time, price, colorer) {
return {
...this._internal__createRawItemBase(time, price),
...colorer._internal_barStyle(time),
};
}
_internal__prepareRendererData() {
const options = this._internal__series._internal_options();
if (this._internal__itemsVisibleRange === null || this._internal__items.length === 0) {
return;
}
let topCoordinate;
if (options.relativeGradient) {
topCoordinate = this._internal__items[this._internal__itemsVisibleRange.from]._internal_y;
for (let i = this._internal__itemsVisibleRange.from; i < this._internal__itemsVisibleRange.to; i++) {
const item = this._internal__items[i];
if (item._internal_y < topCoordinate) {
topCoordinate = item._internal_y;
}
}
}
this._private__areaRenderer._internal_setData({
_internal_lineType: options.lineType,
_internal_items: this._internal__items,
_internal_lineStyle: options.lineStyle,
_internal_lineWidth: options.lineWidth,
_internal_baseLevelCoordinate: null,
_internal_topCoordinate: topCoordinate,
_internal_invertFilledArea: options.invertFilledArea,
_internal_visibleRange: this._internal__itemsVisibleRange,
_internal_barWidth: this._internal__model._internal_timeScale()._internal_barSpacing(),
});
this._private__lineRenderer._internal_setData({
_internal_lineType: options.lineVisible ? options.lineType : undefined,
_internal_items: this._internal__items,
_internal_lineStyle: options.lineStyle,
_internal_lineWidth: options.lineWidth,
_internal_visibleRange: this._internal__itemsVisibleRange,
_internal_barWidth: this._internal__model._internal_timeScale()._internal_barSpacing(),
_internal_pointMarkersRadius: options.pointMarkersVisible ? (options.pointMarkersRadius || options.lineWidth / 2 + 2) : undefined,
});
}
}
const areaStyleDefaults = {
topColor: 'rgba( 46, 220, 135, 0.4)',
bottomColor: 'rgba( 40, 221, 100, 0)',
invertFilledArea: false,
relativeGradient: false,
lineColor: '#33D778',
lineStyle: 0 /* LineStyle.Solid */,
lineWidth: 3,
lineType: 0 /* LineType.Simple */,
lineVisible: true,
crosshairMarkerVisible: true,
crosshairMarkerRadius: 4,
crosshairMarkerBorderColor: '',
crosshairMarkerBorderWidth: 2,
crosshairMarkerBackgroundColor: '',
lastPriceAnimation: 0 /* LastPriceAnimationMode.Disabled */,
pointMarkersVisible: false,
};
const createPaneView$3 = (series, model) => new SeriesAreaPaneView(series, model);
const createSeries$3 = () => {
const definition = {
type: 'Area',
isBuiltIn: true,
defaultOptions: areaStyleDefaults,
/**
* @internal
*/
_internal_createPaneView: createPaneView$3,
};
return definition;
};
const areaSeries = createSeries$3();
function optimalBarWidth(barSpacing, pixelRatio) {
return Math.floor(barSpacing * 0.3 * pixelRatio);
}
function optimalCandlestickWidth(barSpacing, pixelRatio) {
const barSpacingSpecialCaseFrom = 2.5;
const barSpacingSpecialCaseTo = 4;
const barSpacingSpecialCaseCoeff = 3;
if (barSpacing >= barSpacingSpecialCaseFrom && barSpacing <= barSpacingSpecialCaseTo) {
return Math.floor(barSpacingSpecialCaseCoeff * pixelRatio);
}
// coeff should be 1 on small barspacing and go to 0.8 while groing bar spacing
const barSpacingReducingCoeff = 0.2;
const coeff = 1 - barSpacingReducingCoeff * Math.atan(Math.max(barSpacingSpecialCaseTo, barSpacing) - barSpacingSpecialCaseTo) / (Math.PI * 0.5);
const res = Math.floor(barSpacing * coeff * pixelRatio);
const scaledBarSpacing = Math.floor(barSpacing * pixelRatio);
const optimal = Math.min(res, scaledBarSpacing);
return Math.max(Math.floor(pixelRatio), optimal);
}
class PaneRendererBars extends BitmapCoordinatesPaneRenderer {
constructor() {
super(...arguments);
this._private__data = null;
this._private__barWidth = 0;
this._private__barLineWidth = 0;
}
_internal_setData(data) {
this._private__data = data;
}
// eslint-disable-next-line complexity
_internal__drawImpl({ context: ctx, horizontalPixelRatio, verticalPixelRatio }) {
if (this._private__data === null || this._private__data._internal_bars.length === 0 || this._private__data._internal_visibleRange === null) {
return;
}
this._private__barWidth = this._private__calcBarWidth(horizontalPixelRatio);
// grid and crosshair have line width = Math.floor(pixelRatio)
// if this value is odd, we have to make bars' width odd
// if this value is even, we have to make bars' width even
// in order of keeping crosshair-over-bar drawing symmetric
if (this._private__barWidth >= 2) {
const lineWidth = Math.max(1, Math.floor(horizontalPixelRatio));
if ((lineWidth % 2) !== (this._private__barWidth % 2)) {
this._private__barWidth--;
}
}
// if scale is compressed, bar could become less than 1 CSS pixel
this._private__barLineWidth = this._private__data._internal_thinBars ? Math.min(this._private__barWidth, Math.floor(horizontalPixelRatio)) : this._private__barWidth;
let prevColor = null;
const drawOpenClose = this._private__barLineWidth <= this._private__barWidth && this._private__data._internal_barSpacing >= Math.floor(1.5 * horizontalPixelRatio);
for (let i = this._private__data._internal_visibleRange.from; i < this._private__data._internal_visibleRange.to; ++i) {
const bar = this._private__data._internal_bars[i];
if (prevColor !== bar._internal_barColor) {
ctx.fillStyle = bar._internal_barColor;
prevColor = bar._internal_barColor;
}
const bodyWidthHalf = Math.floor(this._private__barLineWidth * 0.5);
const bodyCenter = Math.round(bar._internal_x * horizontalPixelRatio);
const bodyLeft = bodyCenter - bodyWidthHalf;
const bodyWidth = this._private__barLineWidth;
const bodyRight = bodyLeft + bodyWidth - 1;
const high = Math.min(bar._internal_highY, bar._internal_lowY);
const low = Math.max(bar._internal_highY, bar._internal_lowY);
const bodyTop = Math.round(high * verticalPixelRatio) - bodyWidthHalf;
const bodyBottom = Math.round(low * verticalPixelRatio) + bodyWidthHalf;
const bodyHeight = Math.max((bodyBottom - bodyTop), this._private__barLineWidth);
ctx.fillRect(bodyLeft, bodyTop, bodyWidth, bodyHeight);
const sideWidth = Math.ceil(this._private__barWidth * 1.5);
if (drawOpenClose) {
if (this._private__data._internal_openVisible) {
const openLeft = bodyCenter - sideWidth;
let openTop = Math.max(bodyTop, Math.round(bar._internal_openY * verticalPixelRatio) - bodyWidthHalf);
let openBottom = openTop + bodyWidth - 1;
if (openBottom > bodyTop + bodyHeight - 1) {
openBottom = bodyTop + bodyHeight - 1;
openTop = openBottom - bodyWidth + 1;
}
ctx.fillRect(openLeft, openTop, bodyLeft - openLeft, openBottom - openTop + 1);
}
const closeRight = bodyCenter + sideWidth;
let closeTop = Math.max(bodyTop, Math.round(bar._internal_closeY * verticalPixelRatio) - bodyWidthHalf);
let closeBottom = closeTop + bodyWidth - 1;
if (closeBottom > bodyTop + bodyHeight - 1) {
closeBottom = bodyTop + bodyHeight - 1;
closeTop = closeBottom - bodyWidth + 1;
}
ctx.fillRect(bodyRight + 1, closeTop, closeRight - bodyRight, closeBottom - closeTop + 1);
}
}
}
_private__calcBarWidth(pixelRatio) {
const limit = Math.floor(pixelRatio);
return Math.max(limit, Math.floor(optimalBarWidth(ensureNotNull(this._private__data)._internal_barSpacing, pixelRatio)));
}
}
class BarsPaneViewBase extends SeriesPaneViewBase {
constructor(series, model) {
super(series, model, false);
}
_internal__hitTestImpl(x, y) {
return hitTestSeriesRange(this._internal__items, this._internal__itemsVisibleRange, x, y, this._internal__model._internal_timeScale()._internal_barSpacing(), this._internal__series._internal_options().hitTestTolerance, (bar, out) => {
out[0] = bar._internal_highY;
out[1] = bar._internal_lowY;
});
}
_internal__convertToCoordinates(priceScale, timeScale, firstValue) {
timeScale._internal_indexesToCoordinates(this._internal__items, undefinedIfNull(this._internal__itemsVisibleRange));
priceScale._internal_barPricesToCoordinates(this._internal__items, firstValue, undefinedIfNull(this._internal__itemsVisibleRange));
}
_internal__createDefaultItem(time, bar, colorer) {
return {
_internal_time: time,
_internal_open: bar._internal_value[0 /* PlotRowValueIndex.Open */],
_internal_high: bar._internal_value[1 /* PlotRowValueIndex.High */],
_internal_low: bar._internal_value[2 /* PlotRowValueIndex.Low */],
_internal_close: bar._internal_value[3 /* PlotRowValueIndex.Close */],
_internal_x: NaN,
_internal_openY: NaN,
_internal_highY: NaN,
_internal_lowY: NaN,
_internal_closeY: NaN,
};
}
_internal__fillRawPoints() {
const colorer = this._internal__series._internal_barColorer();
this._internal__items = this._internal__series._internal_conflatedBars()._internal_rows().map((row) => this._internal__createRawItem(row._internal_index, row, colorer));
}
}
class SeriesBarsPaneView extends BarsPaneViewBase {
constructor() {
super(...arguments);
this._internal__renderer = new PaneRendererBars();
}
_internal__createRawItem(time, bar, colorer) {
return {
...this._internal__createDefaultItem(time, bar, colorer),
...colorer._internal_barStyle(time),
};
}
_internal__prepareRendererData() {
const barStyleProps = this._internal__series._internal_options();
this._internal__renderer._internal_setData({
_internal_bars: this._internal__items,
_internal_barSpacing: this._internal__model._internal_timeScale()._internal_barSpacing(),
_internal_openVisible: barStyleProps.openVisible,
_internal_thinBars: barStyleProps.thinBars,
_internal_visibleRange: this._internal__itemsVisibleRange,
});
}
}
const barStyleDefaults = {
upColor: '#26a69a',
downColor: '#ef5350',
openVisible: true,
thinBars: true,
};
const createPaneView$2 = (series, model) => new SeriesBarsPaneView(series, model);
const createSeries$2 = () => {
const definition = {
type: 'Bar',
isBuiltIn: true,
defaultOptions: barStyleDefaults,
/**
* @internal
*/
_internal_createPaneView: createPaneView$2,
};
return definition;
};
const barSeries = createSeries$2();
class PaneRendererCandlesticks extends BitmapCoordinatesPaneRenderer {
constructor() {
super(...arguments);
this._private__data = null;
// scaled with pixelRatio
this._private__barWidth = 0;
}
_internal_setData(data) {
this._private__data = data;
}
_internal__drawImpl(renderingScope) {
if (this._private__data === null || this._private__data._internal_bars.length === 0 || this._private__data._internal_visibleRange === null) {
return;
}
const { horizontalPixelRatio } = renderingScope;
// now we know pixelRatio and we could calculate barWidth effectively
this._private__barWidth = optimalCandlestickWidth(this._private__data._internal_barSpacing, horizontalPixelRatio);
// grid and crosshair have line width = Math.floor(pixelRatio)
// if this value is odd, we have to make candlesticks' width odd
// if this value is even, we have to make candlesticks' width even
// in order of keeping crosshair-over-candlesticks drawing symmetric
if (this._private__barWidth >= 2) {
const wickWidth = Math.floor(horizontalPixelRatio);
if ((wickWidth % 2) !== (this._private__barWidth % 2)) {
this._private__barWidth--;
}
}
const bars = this._private__data._internal_bars;
if (this._private__data._internal_wickVisible) {
this._private__drawWicks(renderingScope, bars, this._private__data._internal_visibleRange);
}
if (this._private__data._internal_borderVisible) {
this._private__drawBorder(renderingScope, bars, this._private__data._internal_visibleRange);
}
const borderWidth = this._private__calculateBorderWidth(horizontalPixelRatio);
if (!this._private__data._internal_borderVisible || this._private__barWidth > borderWidth * 2) {
this._private__drawCandles(renderingScope, bars, this._private__data._internal_visibleRange);
}
}
_private__drawWicks(renderingScope, bars, visibleRange) {
if (this._private__data === null) {
return;
}
const { context: ctx, horizontalPixelRatio, verticalPixelRatio } = renderingScope;
let prevWickColor = '';
let wickWidth = Math.min(Math.floor(horizontalPixelRatio), Math.floor(this._private__data._internal_barSpacing * horizontalPixelRatio));
wickWidth = Math.max(Math.floor(horizontalPixelRatio), Math.min(wickWidth, this._private__barWidth));
const wickOffset = Math.floor(wickWidth * 0.5);
let prevEdge = null;
for (let i = visibleRange.from; i < visibleRange.to; i++) {
const bar = bars[i];
if (bar._internal_barWickColor !== prevWickColor) {
ctx.fillStyle = bar._internal_barWickColor;
prevWickColor = bar._internal_barWickColor;
}
const top = Math.round(Math.min(bar._internal_openY, bar._internal_closeY) * verticalPixelRatio);
const bottom = Math.round(Math.max(bar._internal_openY, bar._internal_closeY) * verticalPixelRatio);
const high = Math.round(bar._internal_highY * verticalPixelRatio);
const low = Math.round(bar._internal_lowY * verticalPixelRatio);
const scaledX = Math.round(horizontalPixelRatio * bar._internal_x);
let left = scaledX - wickOffset;
const right = left + wickWidth - 1;
if (prevEdge !== null) {
left = Math.max(prevEdge + 1, left);
left = Math.min(left, right);
}
const width = right - left + 1;
ctx.fillRect(left, high, width, top - high);
ctx.fillRect(left, bottom + 1, width, low - bottom);
prevEdge = right;
}
}
_private__calculateBorderWidth(pixelRatio) {
let borderWidth = Math.floor(1 /* Constants.BarBorderWidth */ * pixelRatio);
if (this._private__barWidth <= 2 * borderWidth) {
borderWidth = Math.floor((this._private__barWidth - 1) * 0.5);
}
const res = Math.max(Math.floor(pixelRatio), borderWidth);
if (this._private__barWidth <= res * 2) {
// do not draw bodies, restore original value
return Math.max(Math.floor(pixelRatio), Math.floor(1 /* Constants.BarBorderWidth */ * pixelRatio));
}
return res;
}
_private__drawBorder(renderingScope, bars, visibleRange) {
if (this._private__data === null) {
return;
}
const { context: ctx, horizontalPixelRatio, verticalPixelRatio } = renderingScope;
let prevBorderColor = '';
const borderWidth = this._private__calculateBorderWidth(horizontalPixelRatio);
let prevEdge = null;
for (let i = visibleRange.from; i < visibleRange.to; i++) {
const bar = bars[i];
if (bar._internal_barBorderColor !== prevBorderColor) {
ctx.fillStyle = bar._internal_barBorderColor;
prevBorderColor = bar._internal_barBorderColor;
}
let left = Math.round(bar._internal_x * horizontalPixelRatio) - Math.floor(this._private__barWidth * 0.5);
// this is important to calculate right before patching left
const right = left + this._private__barWidth - 1;
const top = Math.round(Math.min(bar._internal_openY, bar._internal_closeY) * verticalPixelRatio);
const bottom = Math.round(Math.max(bar._internal_openY, bar._internal_closeY) * verticalPixelRatio);
if (prevEdge !== null) {
left = Math.max(prevEdge + 1, left);
left = Math.min(left, right);
}
if (this._private__data._internal_barSpacing * horizontalPixelRatio > 2 * borderWidth) {
fillRectInnerBorder(ctx, left, top, right - left + 1, bottom - top + 1, borderWidth);
}
else {
const width = right - left + 1;
ctx.fillRect(left, top, width, bottom - top + 1);
}
prevEdge = right;
}
}
_private__drawCandles(renderingScope, bars, visibleRange) {
if (this._private__data === null) {
return;
}
const { context: ctx, horizontalPixelRatio, verticalPixelRatio } = renderingScope;
let prevBarColor = '';
const borderWidth = this._private__calculateBorderWidth(horizontalPixelRatio);
for (let i = visibleRange.from; i < visibleRange.to; i++) {
const bar = bars[i];
let top = Math.round(Math.min(bar._internal_openY, bar._internal_closeY) * verticalPixelRatio);
let bottom = Math.round(Math.max(bar._internal_openY, bar._internal_closeY) * verticalPixelRatio);
let left = Math.round(bar._internal_x * horizontalPixelRatio) - Math.floor(this._private__barWidth * 0.5);
let right = left + this._private__barWidth - 1;
if (bar._internal_barColor !== prevBarColor) {
const barColor = bar._internal_barColor;
ctx.fillStyle = barColor;
prevBarColor = barColor;
}
if (this._private__data._internal_borderVisible) {
left += borderWidth;
top += borderWidth;
right -= borderWidth;
bottom -= borderWidth;
}
if (top > bottom) {
continue;
}
ctx.fillRect(left, top, right - left + 1, bottom - top + 1);
}
}
}
class SeriesCandlesticksPaneView extends BarsPaneViewBase {
constructor() {
super(...arguments);
this._internal__renderer = new PaneRendererCandlesticks();
}
_internal__createRawItem(time, bar, colorer) {
return {
...this._internal__createDefaultItem(time, bar, colorer),
...colorer._internal_barStyle(time),
};
}
_internal__prepareRendererData() {
const candlestickStyleProps = this._internal__series._internal_options();
this._internal__renderer._internal_setData({
_internal_bars: this._internal__items,
_internal_barSpacing: this._internal__model._internal_timeScale()._internal_barSpacing(),
_internal_wickVisible: candlestickStyleProps.wickVisible,
_internal_borderVisible: candlestickStyleProps.borderVisible,
_internal_visibleRange: this._internal__itemsVisibleRange,
});
}
}
const candlestickStyleDefaults = {
upColor: '#26a69a',
downColor: '#ef5350',
wickVisible: true,
borderVisible: true,
borderColor: '#378658',
borderUpColor: '#26a69a',
borderDownColor: '#ef5350',
wickColor: '#737375',
wickUpColor: '#26a69a',
wickDownColor: '#ef5350',
};
const createPaneView$1 = (series, model) => new SeriesCandlesticksPaneView(series, model);
const createSeries$1 = () => {
const definition = {
type: 'Candlestick',
isBuiltIn: true,
defaultOptions: candlestickStyleDefaults,
/**
* @internal
*/
_internal_createPaneView: createPaneView$1,
};
return definition;
};
const candlestickSeries = createSeries$1();
const showSpacingMinimalBarWidth = 1;
const alignToMinimalWidthLimit = 4;
class PaneRendererHistogram extends BitmapCoordinatesPaneRenderer {
constructor() {
super(...arguments);
this._private__data = null;
this._private__precalculatedCache = [];
}
_internal_setData(data) {
this._private__data = data;
this._private__precalculatedCache = [];
}
_internal__drawImpl({ context: ctx, horizontalPixelRatio, verticalPixelRatio }) {
if (this._private__data === null || this._private__data._internal_items.length === 0 || this._private__data._internal_visibleRange === null) {
return;
}
if (!this._private__precalculatedCache.length) {
this._private__fillPrecalculatedCache(horizontalPixelRatio);
}
const tickWidth = Math.max(1, Math.floor(verticalPixelRatio));
const histogramBase = Math.round((this._private__data._internal_histogramBase) * verticalPixelRatio);
const topHistogramBase = histogramBase - Math.floor(tickWidth / 2);
const bottomHistogramBase = topHistogramBase + tickWidth;
for (let i = this._private__data._internal_visibleRange.from; i < this._private__data._internal_visibleRange.to; i++) {
const item = this._private__data._internal_items[i];
const current = this._private__precalculatedCache[i - this._private__data._internal_visibleRange.from];
const y = Math.round(item._internal_y * verticalPixelRatio);
ctx.fillStyle = item._internal_barColor;
let top;
let bottom;
if (y <= topHistogramBase) {
top = y;
bottom = bottomHistogramBase;
}
else {
top = topHistogramBase;
bottom = y - Math.floor(tickWidth / 2) + tickWidth;
}
ctx.fillRect(current._internal_left, top, current._internal_right - current._internal_left + 1, bottom - top);
}
}
// eslint-disable-next-line complexity
_private__fillPrecalculatedCache(pixelRatio) {
if (this._private__data === null || this._private__data._internal_items.length === 0 || this._private__data._internal_visibleRange === null) {
this._private__precalculatedCache = [];
return;
}
const spacing = Math.ceil(this._private__data._internal_barSpacing * pixelRatio) <= showSpacingMinimalBarWidth ? 0 : Math.max(1, Math.floor(pixelRatio));
const columnWidth = Math.round(this._private__data._internal_barSpacing * pixelRatio) - spacing;
this._private__precalculatedCache = new Array(this._private__data._internal_visibleRange.to - this._private__data._internal_visibleRange.from);
for (let i = this._private__data._internal_visibleRange.from; i < this._private__data._internal_visibleRange.to; i++) {
const item = this._private__data._internal_items[i];
// force cast to avoid ensureDefined call
const x = Math.round(item._internal_x * pixelRatio);
let left;
let right;
if (columnWidth % 2) {
const halfWidth = (columnWidth - 1) / 2;
left = x - halfWidth;
right = x + halfWidth;
}
else {
// shift pixel to left
const halfWidth = columnWidth / 2;
left = x - halfWidth;
right = x + halfWidth - 1;
}
this._private__precalculatedCache[i - this._private__data._internal_visibleRange.from] = {
_internal_left: left,
_internal_right: right,
_internal_roundedCenter: x,
_internal_center: (item._internal_x * pixelRatio),
_internal_time: item._internal_time,
};
}
// correct positions
for (let i = this._private__data._internal_visibleRange.from + 1; i < this._private__data._internal_visibleRange.to; i++) {
const current = this._private__precalculatedCache[i - this._private__data._internal_visibleRange.from];
const prev = this._private__precalculatedCache[i - this._private__data._internal_visibleRange.from - 1];
if (current._internal_time !== prev._internal_time + 1) {
continue;
}
if (current._internal_left - prev._internal_right !== (spacing + 1)) {
// have to align
if (prev._internal_roundedCenter > prev._internal_center) {
// prev wasshifted to left, so add pixel to right
prev._internal_right = current._internal_left - spacing - 1;
}
else {
// extend current to left
current._internal_left = prev._internal_right + spacing + 1;
}
}
}
let minWidth = Math.ceil(this._private__data._internal_barSpacing * pixelRatio);
for (let i = this._private__data._internal_visibleRange.from; i < this._private__data._internal_visibleRange.to; i++) {
const current = this._private__precalculatedCache[i - this._private__data._internal_visibleRange.from];
// this could happen if barspacing < 1
if (current._internal_right < current._internal_left) {
current._internal_right = current._internal_left;
}
const width = current._internal_right - current._internal_left + 1;
minWidth = Math.min(width, minWidth);
}
if (spacing > 0 && minWidth < alignToMinimalWidthLimit) {
for (let i = this._private__data._internal_visibleRange.from; i < this._private__data._internal_visibleRange.to; i++) {
const current = this._private__precalculatedCache[i - this._private__data._internal_visibleRange.from];
const width = current._internal_right - current._internal_left + 1;
if (width > minWidth) {
if (current._internal_roundedCenter > current._internal_center) {
current._internal_right -= 1;
}
else {
current._internal_left += 1;
}
}
}
}
}
}
class SeriesHistogramPaneView extends LinePaneViewBase {
constructor() {
super(...arguments);
this._internal__renderer = new PaneRendererHistogram();
}
_internal__hitTestImpl(x, y) {
const histogramBase = this._internal__series._internal_priceScale()._internal_priceToCoordinate(this._internal__series._internal_options().base, ensureNotNull(this._internal__series._internal_firstValue())._internal_value);
if (histogramBase === null) {
return null;
}
return hitTestSeriesRange(this._internal__items, this._internal__itemsVisibleRange, x, y, this._internal__model._internal_timeScale()._internal_barSpacing(), this._internal__series._internal_options().hitTestTolerance, (item, out) => {
out[0] = item._internal_y;
out[1] = histogramBase;
});
}
_internal__createRawItem(time, price, colorer) {
return {
...this._internal__createRawItemBase(time, price),
...colorer._internal_barStyle(time),
};
}
_internal__prepareRendererData() {
const data = {
_internal_items: this._internal__items,
_internal_barSpacing: this._internal__model._internal_timeScale()._internal_barSpacing(),
_internal_visibleRange: this._internal__itemsVisibleRange,
_internal_histogramBase: this._internal__series._internal_priceScale()._internal_priceToCoordinate(this._internal__series._internal_options().base, ensureNotNull(this._internal__series._internal_firstValue())._internal_value),
};
this._internal__renderer._internal_setData(data);
}
}
const histogramStyleDefaults = {
color: '#26a69a',
base: 0,
};
const createPaneView = (series, model) => new SeriesHistogramPaneView(series, model);
const createSeries = () => {
const definition = {
type: 'Histogram',
isBuiltIn: true,
defaultOptions: histogramStyleDefaults,
/**
* @internal
*/
_internal_createPaneView: createPaneView,
};
return definition;
};
const histogramSeries = createSeries();
class PanePrimitiveWrapper {
constructor(pane, primitive) {
this._private__pane = pane;
this._private__primitive = primitive;
this._private__attach();
}
detach() {
this._private__pane.detachPrimitive(this._private__primitive);
}
getPane() {
return this._private__pane;
}
applyOptions(options) {
this._private__primitive._internal_applyOptions?.(options);
}
_private__attach() {
this._private__pane.attachPrimitive(this._private__primitive);
}
}
const textWatermarkOptionsDefaults = {
visible: true,
horzAlign: 'center',
vertAlign: 'center',
lines: [],
};
const textWatermarkLineOptionsDefaults = {
color: 'rgba(0, 0, 0, 0.5)',
fontSize: 48,
fontFamily: defaultFontFamily,
fontStyle: '',
text: '',
};
class TextWatermarkRenderer {
constructor(options) {
this._private__metricsCache = new Map();
this._private__data = options;
}
draw(target) {
target.useMediaCoordinateSpace((scope) => {
if (!this._private__data.visible) {
return;
}
const { context: ctx, mediaSize } = scope;
let textHeight = 0;
for (const line of this._private__data.lines) {
if (line.text.length === 0) {
continue;
}
ctx.font = line._internal_font;
const textWidth = this._private__metrics(ctx, line.text);
if (textWidth > mediaSize.width) {
line._internal_zoom = mediaSize.width / textWidth;
}
else {
line._internal_zoom = 1;
}
textHeight += line.lineHeight * line._internal_zoom;
}
let vertOffset = 0;
switch (this._private__data.vertAlign) {
case 'top':
vertOffset = 0;
break;
case 'center':
vertOffset = Math.max((mediaSize.height - textHeight) / 2, 0);
break;
case 'bottom':
vertOffset = Math.max(mediaSize.height - textHeight, 0);
break;
}
for (const line of this._private__data.lines) {
ctx.save();
ctx.fillStyle = line.color;
let horzOffset = 0;
switch (this._private__data.horzAlign) {
case 'left':
ctx.textAlign = 'left';
horzOffset = line.lineHeight / 2;
break;
case 'center':
ctx.textAlign = 'center';
horzOffset = mediaSize.width / 2;
break;
case 'right':
ctx.textAlign = 'right';
horzOffset = mediaSize.width - 1 - line.lineHeight / 2;
break;
}
ctx.translate(horzOffset, vertOffset);
ctx.textBaseline = 'top';
ctx.font = line._internal_font;
ctx.scale(line._internal_zoom, line._internal_zoom);
ctx.fillText(line.text, 0, line._internal_vertOffset);
ctx.restore();
vertOffset += line.lineHeight * line._internal_zoom;
}
});
}
_private__metrics(ctx, text) {
const fontCache = this._private__fontCache(ctx.font);
let result = fontCache.get(text);
if (result === undefined) {
result = ctx.measureText(text).width;
fontCache.set(text, result);
}
return result;
}
_private__fontCache(font) {
let fontCache = this._private__metricsCache.get(font);
if (fontCache === undefined) {
fontCache = new Map();
this._private__metricsCache.set(font, fontCache);
}
return fontCache;
}
}
class TextWatermarkPaneView {
constructor(options) {
this._private__options = buildRendererOptions$1(options);
}
_internal_update(options) {
this._private__options = buildRendererOptions$1(options);
}
renderer() {
return new TextWatermarkRenderer(this._private__options);
}
}
function buildRendererLineOptions(lineOption) {
return {
...lineOption,
_internal_font: makeFont(lineOption.fontSize, lineOption.fontFamily, lineOption.fontStyle),
lineHeight: lineOption.lineHeight || lineOption.fontSize * 1.2,
_internal_vertOffset: 0,
_internal_zoom: 0,
};
}
function buildRendererOptions$1(options) {
return {
...options,
lines: options.lines.map(buildRendererLineOptions),
};
}
function mergeLineOptionsWithDefaults(options) {
return {
...textWatermarkLineOptionsDefaults,
...options,
};
}
function mergeOptionsWithDefaults$2(options) {
return {
...textWatermarkOptionsDefaults,
...options,
lines: options.lines?.map(mergeLineOptionsWithDefaults) ?? [],
};
}
class TextWatermark {
constructor(options) {
this._private__options = mergeOptionsWithDefaults$2(options);
this._private__paneViews = [new TextWatermarkPaneView(this._private__options)];
}
updateAllViews() {
this._private__paneViews.forEach((pw) => pw._internal_update(this._private__options));
}
paneViews() {
return this._private__paneViews;
}
attached({ requestUpdate }) {
this._internal_requestUpdate = requestUpdate;
}
detached() {
this._internal_requestUpdate = undefined;
}
_internal_applyOptions(options) {
this._private__options = mergeOptionsWithDefaults$2({ ...this._private__options, ...options });
if (this._internal_requestUpdate) {
this._internal_requestUpdate();
}
}
}
/**
* Creates an image watermark.
*
* @param pane - Target pane.
* @param options - Watermark options.
*
* @returns Image watermark wrapper.
*
* @example
* ```js
* import { createTextWatermark } from 'lightweight-charts';
*
* const firstPane = chart.panes()[0];
* const textWatermark = createTextWatermark(firstPane, {
* horzAlign: 'center',
* vertAlign: 'center',
* lines: [
* {
* text: 'Hello',
* color: 'rgba(255,0,0,0.5)',
* fontSize: 100,
* fontStyle: 'bold',
* },
* {
* text: 'This is a text watermark',
* color: 'rgba(0,0,255,0.5)',
* fontSize: 50,
* fontStyle: 'italic',
* fontFamily: 'monospace',
* },
* ],
* });
* // to change options
* textWatermark.applyOptions({ horzAlign: 'left' });
* // to remove watermark from the pane
* textWatermark.detach();
* ```
*/
function createTextWatermark(pane, options) {
return new PanePrimitiveWrapper(pane, new TextWatermark(options));
}
const imageWatermarkOptionsDefaults = {
alpha: 1,
padding: 0,
};
class ImageWatermarkRenderer {
constructor(data) {
this._private__data = data;
}
draw(target) {
target.useMediaCoordinateSpace((scope) => {
const ctx = scope.context;
const pos = this._private__determinePlacement(this._private__data, scope.mediaSize);
if (!pos || !this._private__data._internal_imgElement) {
return;
}
ctx.globalAlpha = this._private__data.alpha ?? 1;
ctx.drawImage(this._private__data._internal_imgElement, pos._internal_x, pos._internal_y, pos._internal_width, pos._internal_height);
});
}
_private__determinePlacement(data, paneSize) {
const { maxHeight, maxWidth, _internal_imgHeight: imgHeight, _internal_imgWidth: imgWidth, padding } = data;
const plotCentreX = Math.round(paneSize.width / 2);
const plotCentreY = Math.round(paneSize.height / 2);
const paddingSize = padding ?? 0;
let availableWidth = paneSize.width - 2 * paddingSize;
let availableHeight = paneSize.height - 2 * paddingSize;
if (maxHeight) {
availableHeight = Math.min(availableHeight, maxHeight);
}
if (maxWidth) {
availableWidth = Math.min(availableWidth, maxWidth);
}
const scaleX = availableWidth / imgWidth;
const scaleY = availableHeight / imgHeight;
const scaleToUse = Math.min(scaleX, scaleY);
const drawWidth = imgWidth * scaleToUse;
const drawHeight = imgHeight * scaleToUse;
const x = plotCentreX - 0.5 * drawWidth;
const y = plotCentreY - 0.5 * drawHeight;
return {
_internal_x: x,
_internal_y: y,
_internal_height: drawHeight,
_internal_width: drawWidth,
};
}
}
class ImageWatermarkPaneView {
constructor(options) {
this._private__image = null;
this._private__imageWidth = 0; // don't draw until loaded
this._private__imageHeight = 0;
this._private__options = options;
this._private__rendererOptions = buildRendererOptions(this._private__options, this._private__image, this._private__imageWidth, this._private__imageHeight);
}
_internal_stateUpdate(state) {
if (state._internal_imageWidth !== undefined) {
this._private__imageWidth = state._internal_imageWidth;
}
if (state._internal_imageHeight !== undefined) {
this._private__imageHeight = state._internal_imageHeight;
}
if (state._internal_image !== undefined) {
this._private__image = state._internal_image;
}
this._internal_update();
}
_internal_optionsUpdate(options) {
this._private__options = options;
this._internal_update();
}
zOrder() {
return 'bottom';
}
_internal_update() {
this._private__rendererOptions = buildRendererOptions(this._private__options, this._private__image, this._private__imageWidth, this._private__imageHeight);
}
renderer() {
return new ImageWatermarkRenderer(this._private__rendererOptions);
}
}
function buildRendererOptions(options, imgElement, imgWidth, imgHeight) {
return {
...options,
_internal_imgElement: imgElement,
_internal_imgWidth: imgWidth,
_internal_imgHeight: imgHeight,
};
}
function mergeOptionsWithDefaults$1(options) {
return {
...imageWatermarkOptionsDefaults,
...options,
};
}
class ImageWatermark {
constructor(imageUrl, options) {
this._private__imgElement = null;
this._private__imageUrl = imageUrl;
this._private__options = mergeOptionsWithDefaults$1(options);
this._private__paneViews = [new ImageWatermarkPaneView(this._private__options)];
}
updateAllViews() {
this._private__paneViews.forEach((pw) => pw._internal_update());
}
paneViews() {
return this._private__paneViews;
}
attached(attachedParams) {
const { requestUpdate } = attachedParams;
this._private__requestUpdate = requestUpdate;
this._private__imgElement = new Image();
this._private__imgElement.onload = () => {
const imageHeight = this._private__imgElement?.naturalHeight ?? 1;
const imageWidth = this._private__imgElement?.naturalWidth ?? 1;
this._private__paneViews.forEach((pv) => pv._internal_stateUpdate({
_internal_imageHeight: imageHeight,
_internal_imageWidth: imageWidth,
_internal_image: this._private__imgElement,
}));
if (this._private__requestUpdate) {
this._private__requestUpdate();
}
};
this._private__imgElement.src = this._private__imageUrl;
}
detached() {
this._private__requestUpdate = undefined;
this._private__imgElement = null;
}
_internal_applyOptions(options) {
this._private__options = mergeOptionsWithDefaults$1({ ...this._private__options, ...options });
this._private__updateOptions();
if (this._internal_requestUpdate) {
this._internal_requestUpdate();
}
}
_internal_requestUpdate() {
if (this._private__requestUpdate) {
this._private__requestUpdate();
}
}
_private__updateOptions() {
this._private__paneViews.forEach((pw) => pw._internal_optionsUpdate(this._private__options));
}
}
/**
* Creates an image watermark.
*
* @param pane - Target pane.
* @param imageUrl - Image URL.
* @param options - Watermark options.
*
* @returns Image watermark wrapper.
*
* @example
* ```js
* import { createImageWatermark } from 'lightweight-charts';
*
* const firstPane = chart.panes()[0];
* const imageWatermark = createImageWatermark(firstPane, '/images/my-image.png', {
* alpha: 0.5,
* padding: 20,
* });
* // to change options
* imageWatermark.applyOptions({ padding: 10 });
* // to remove watermark from the pane
* imageWatermark.detach();
* ```
*/
function createImageWatermark(pane, imageUrl, options) {
return new PanePrimitiveWrapper(pane, new ImageWatermark(imageUrl, options));
}
class SeriesPrimitiveAdapter {
constructor(series, primitive) {
this._internal__series = series;
this._internal__primitive = primitive;
this._private__attach();
}
detach() {
this._internal__series.detachPrimitive(this._internal__primitive);
}
getSeries() {
return this._internal__series;
}
applyOptions(options) {
if (this._internal__primitive && this._internal__primitive._internal_applyOptions) {
this._internal__primitive._internal_applyOptions(options);
}
}
_private__attach() {
this._internal__series.attachPrimitive(this._internal__primitive);
}
}
const seriesMarkerOptionsDefaults = {
autoScale: true,
zOrder: 'normal',
};
function size(barSpacing, coeff) {
const result = Math.min(Math.max(barSpacing, 12 /* Constants.MinShapeSize */), 30 /* Constants.MaxShapeSize */) * coeff;
return ceiledOdd(result);
}
function shapeSize(shape, originalSize) {
switch (shape) {
case 'arrowDown':
case 'arrowUp':
return size(originalSize, 1);
case 'circle':
return size(originalSize, 0.8);
case 'square':
return size(originalSize, 0.7);
}
}
function calculateShapeHeight(barSpacing) {
return ceiledEven(size(barSpacing, 1));
}
function shapeMargin(barSpacing) {
return Math.max(size(barSpacing, 0.1), 3 /* Constants.MinShapeMargin */);
}
function calculateAdjustedMargin(margin, hasSide, hasInBar) {
if (hasSide) {
return margin;
}
else if (hasInBar) {
return Math.ceil(margin / 2);
}
return 0;
}
function drawArrow(up, ctx, coords, size) {
const arrowSize = shapeSize('arrowUp', size);
const halfArrowSize = ((arrowSize - 1) / 2) * coords._internal_pixelRatio;
const baseSize = ceiledOdd(size / 2);
const halfBaseSize = ((baseSize - 1) / 2) * coords._internal_pixelRatio;
ctx.beginPath();
if (up) {
ctx.moveTo(coords._internal_x - halfArrowSize, coords._internal_y);
ctx.lineTo(coords._internal_x, coords._internal_y - halfArrowSize);
ctx.lineTo(coords._internal_x + halfArrowSize, coords._internal_y);
ctx.lineTo(coords._internal_x + halfBaseSize, coords._internal_y);
ctx.lineTo(coords._internal_x + halfBaseSize, coords._internal_y + halfArrowSize);
ctx.lineTo(coords._internal_x - halfBaseSize, coords._internal_y + halfArrowSize);
ctx.lineTo(coords._internal_x - halfBaseSize, coords._internal_y);
}
else {
ctx.moveTo(coords._internal_x - halfArrowSize, coords._internal_y);
ctx.lineTo(coords._internal_x, coords._internal_y + halfArrowSize);
ctx.lineTo(coords._internal_x + halfArrowSize, coords._internal_y);
ctx.lineTo(coords._internal_x + halfBaseSize, coords._internal_y);
ctx.lineTo(coords._internal_x + halfBaseSize, coords._internal_y - halfArrowSize);
ctx.lineTo(coords._internal_x - halfBaseSize, coords._internal_y - halfArrowSize);
ctx.lineTo(coords._internal_x - halfBaseSize, coords._internal_y);
}
ctx.fill();
}
function hitTestArrow(up, centerX, centerY, size, x, y) {
const arrowSize = shapeSize('arrowUp', size);
const halfArrowSize = (arrowSize - 1) / 2;
const baseSize = ceiledOdd(size / 2);
const halfBaseSize = (baseSize - 1) / 2;
const triangleTolerance = 3;
const rectTolerance = 2;
const baseLeft = centerX - halfBaseSize - rectTolerance;
const baseRight = centerX + halfBaseSize + rectTolerance;
const baseTop = up ? centerY : centerY - halfArrowSize;
const baseBottom = up ? centerY + halfArrowSize : centerY;
if (x >= baseLeft && x <= baseRight &&
y >= baseTop - rectTolerance && y <= baseBottom + rectTolerance) {
return true;
}
const isInTriangleBounds = () => {
const headLeft = centerX - halfArrowSize - triangleTolerance;
const headRight = centerX + halfArrowSize + triangleTolerance;
const headTop = up ? centerY - halfArrowSize - triangleTolerance : centerY;
const headBottom = up ? centerY : centerY + halfArrowSize + triangleTolerance;
if (x < headLeft || x > headRight ||
y < headTop || y > headBottom) {
return false;
}
const dx = Math.abs(x - centerX);
const dy = up
? Math.abs(y - centerY) // up arrow
: Math.abs(y - centerY); // down arrow
return dy + triangleTolerance >= dx / 2;
};
return isInTriangleBounds();
}
function drawCircle(ctx, coords, size) {
const circleSize = shapeSize('circle', size);
const halfSize = (circleSize - 1) / 2;
ctx.beginPath();
ctx.arc(coords._internal_x, coords._internal_y, halfSize * coords._internal_pixelRatio, 0, 2 * Math.PI, false);
ctx.fill();
}
function hitTestCircle(centerX, centerY, size, x, y) {
const circleSize = shapeSize('circle', size);
const tolerance = 2 + circleSize / 2;
const xOffset = centerX - x;
const yOffset = centerY - y;
const dist = Math.sqrt(xOffset * xOffset + yOffset * yOffset);
return dist <= tolerance;
}
function drawSquare(ctx, coords, size) {
const squareSize = shapeSize('square', size);
const halfSize = ((squareSize - 1) * coords._internal_pixelRatio) / 2;
const left = coords._internal_x - halfSize;
const top = coords._internal_y - halfSize;
ctx.fillRect(left, top, squareSize * coords._internal_pixelRatio, squareSize * coords._internal_pixelRatio);
}
function hitTestSquare(centerX, centerY, size, x, y) {
const squareSize = shapeSize('square', size);
const halfSize = (squareSize - 1) / 2;
const left = centerX - halfSize;
const top = centerY - halfSize;
return x >= left && x <= left + squareSize &&
y >= top && y <= top + squareSize;
}
function drawText(ctx, text, x, y, horizontalPixelRatio, verticalPixelRatio) {
ctx.save();
ctx.scale(horizontalPixelRatio, verticalPixelRatio);
ctx.fillText(text, x, y);
ctx.restore();
}
function hitTestText(textX, textY, textWidth, textHeight, x, y) {
const halfHeight = textHeight / 2;
return x >= textX && x <= textX + textWidth &&
y >= textY - halfHeight && y <= textY + halfHeight;
}
class SeriesMarkersRenderer {
constructor() {
this._private__data = null;
this._private__textWidthCache = new TextWidthCache();
this._private__fontSize = -1;
this._private__fontFamily = '';
this._private__font = '';
this._private__zOrder = 'normal';
}
_internal_setData(data) {
this._private__data = data;
}
_internal_setParams(fontSize, fontFamily, zOrder) {
if (this._private__fontSize !== fontSize || this._private__fontFamily !== fontFamily) {
this._private__fontSize = fontSize;
this._private__fontFamily = fontFamily;
this._private__font = makeFont(fontSize, fontFamily);
this._private__textWidthCache._internal_reset();
}
this._private__zOrder = zOrder;
}
_internal_hitTest(x, y) {
if (this._private__data === null || this._private__data._internal_visibleRange === null) {
return null;
}
for (let i = this._private__data._internal_visibleRange.from; i < this._private__data._internal_visibleRange.to; i++) {
const item = this._private__data._internal_items[i];
if (item && hitTestItem(item, x, y)) {
return {
zOrder: 'normal',
externalId: item._internal_externalId ?? '',
itemType: 'marker',
};
}
}
return null;
}
draw(target) {
if (this._private__zOrder === 'aboveSeries') {
return;
}
target.useBitmapCoordinateSpace((scope) => {
this._internal__drawImpl(scope);
});
}
drawBackground(target) {
if (this._private__zOrder !== 'aboveSeries') {
return;
}
target.useBitmapCoordinateSpace((scope) => {
this._internal__drawImpl(scope);
});
}
_internal__drawImpl({ context: ctx, horizontalPixelRatio, verticalPixelRatio }) {
if (this._private__data === null || this._private__data._internal_visibleRange === null) {
return;
}
ctx.textBaseline = 'middle';
ctx.font = this._private__font;
for (let index = this._private__data._internal_visibleRange.from; index < this._private__data._internal_visibleRange.to; index++) {
const item = this._private__data._internal_items[index];
if (item._internal_text !== undefined) {
item._internal_text._internal_width = this._private__textWidthCache._internal_measureText(ctx, item._internal_text._internal_content);
item._internal_text._internal_height = this._private__fontSize;
item._internal_text._internal_x = item._internal_x - item._internal_text._internal_width / 2;
}
drawItem(item, ctx, horizontalPixelRatio, verticalPixelRatio);
}
}
}
function bitmapShapeItemCoordinates(item, horizontalPixelRatio, verticalPixelRatio) {
const tickWidth = Math.max(1, Math.floor(horizontalPixelRatio));
const correction = (tickWidth % 2) / 2;
return {
_internal_x: Math.round(item._internal_x * horizontalPixelRatio) + correction,
_internal_y: item._internal_y * verticalPixelRatio,
_internal_pixelRatio: horizontalPixelRatio,
};
}
function drawItem(item, ctx, horizontalPixelRatio, verticalPixelRatio) {
ctx.fillStyle = item._internal_color;
if (item._internal_text !== undefined) {
drawText(ctx, item._internal_text._internal_content, item._internal_text._internal_x, item._internal_text._internal_y, horizontalPixelRatio, verticalPixelRatio);
}
drawShape(item, ctx, bitmapShapeItemCoordinates(item, horizontalPixelRatio, verticalPixelRatio));
}
function drawShape(item, ctx, coordinates) {
if (item._internal_size === 0) {
return;
}
switch (item._internal_shape) {
case 'arrowDown':
drawArrow(false, ctx, coordinates, item._internal_size);
return;
case 'arrowUp':
drawArrow(true, ctx, coordinates, item._internal_size);
return;
case 'circle':
drawCircle(ctx, coordinates, item._internal_size);
return;
case 'square':
drawSquare(ctx, coordinates, item._internal_size);
return;
}
ensureNever(item._internal_shape);
}
function hitTestItem(item, x, y) {
if (item._internal_text !== undefined && hitTestText(item._internal_text._internal_x, item._internal_text._internal_y, item._internal_text._internal_width, item._internal_text._internal_height, x, y)) {
return true;
}
return hitTestShape(item, x, y);
}
function hitTestShape(item, x, y) {
if (item._internal_size === 0) {
return false;
}
switch (item._internal_shape) {
case 'arrowDown':
return hitTestArrow(true, item._internal_x, item._internal_y, item._internal_size, x, y);
case 'arrowUp':
return hitTestArrow(false, item._internal_x, item._internal_y, item._internal_size, x, y);
case 'circle':
return hitTestCircle(item._internal_x, item._internal_y, item._internal_size, x, y);
case 'square':
return hitTestSquare(item._internal_x, item._internal_y, item._internal_size, x, y);
}
}
function isPriceMarker(position) {
return position === 'atPriceTop' || position === 'atPriceBottom' || position === 'atPriceMiddle';
}
function getPrice(seriesData, marker, isInverted) {
if (isPriceMarker(marker.position) && marker.price !== undefined) {
return marker.price;
}
if (isValueData(seriesData)) {
return seriesData.value;
}
if (isOhlcData(seriesData)) {
if (marker.position === 'inBar') {
return seriesData.close;
}
if (marker.position === 'aboveBar') {
if (!isInverted) {
return seriesData.high;
}
return seriesData.low;
}
if (marker.position === 'belowBar') {
if (!isInverted) {
return seriesData.low;
}
return seriesData.high;
}
}
return;
}
// eslint-disable-next-line max-params, complexity
function fillSizeAndY(rendererItem, marker, seriesData, offsets, textHeight, shapeMargin, series, chart) {
const price = getPrice(seriesData, marker, series.priceScale().options().invertScale);
if (price === undefined) {
return;
}
const ignoreOffset = isPriceMarker(marker.position);
const timeScale = chart.timeScale();
const sizeMultiplier = isNumber(marker.size) ? Math.max(marker.size, 0) : 1;
const shapeSize = calculateShapeHeight(timeScale.options().barSpacing) * sizeMultiplier;
const halfSize = shapeSize / 2;
rendererItem._internal_size = shapeSize;
const position = marker.position;
switch (position) {
case 'inBar':
case 'atPriceMiddle': {
rendererItem._internal_y = ensureNotNull(series.priceToCoordinate(price));
if (rendererItem._internal_text !== undefined) {
rendererItem._internal_text._internal_y = rendererItem._internal_y + halfSize + shapeMargin + textHeight * (0.5 + 0.1 /* Constants.TextMargin */);
}
return;
}
case 'aboveBar':
case 'atPriceTop': {
const offset = ignoreOffset ? 0 : offsets._internal_aboveBar;
rendererItem._internal_y = (ensureNotNull(series.priceToCoordinate(price)) - halfSize - offset);
if (rendererItem._internal_text !== undefined) {
rendererItem._internal_text._internal_y = rendererItem._internal_y - halfSize - textHeight * (0.5 + 0.1 /* Constants.TextMargin */);
offsets._internal_aboveBar += textHeight * (1 + 2 * 0.1 /* Constants.TextMargin */);
}
if (!ignoreOffset) {
offsets._internal_aboveBar += shapeSize + shapeMargin;
}
return;
}
case 'belowBar':
case 'atPriceBottom': {
const offset = ignoreOffset ? 0 : offsets._internal_belowBar;
rendererItem._internal_y = (ensureNotNull(series.priceToCoordinate(price)) + halfSize + offset);
if (rendererItem._internal_text !== undefined) {
rendererItem._internal_text._internal_y = (rendererItem._internal_y + halfSize + shapeMargin + textHeight * (0.5 + 0.1 /* Constants.TextMargin */));
offsets._internal_belowBar += textHeight * (1 + 2 * 0.1 /* Constants.TextMargin */);
}
if (!ignoreOffset) {
offsets._internal_belowBar += shapeSize + shapeMargin;
}
return;
}
}
}
function isValueData(data) {
// eslint-disable-next-line no-restricted-syntax
return 'value' in data && typeof data.value === 'number';
}
function isOhlcData(data) {
// eslint-disable-next-line no-restricted-syntax
return 'open' in data && 'high' in data && 'low' in data && 'close' in data;
}
class SeriesMarkersPaneView {
constructor(series, chart, options) {
this._private__markers = [];
this._private__invalidated = true;
this._private__dataInvalidated = true;
this._private__renderer = new SeriesMarkersRenderer();
this._private__series = series;
this._private__chart = chart;
this._private__data = {
_internal_items: [],
_internal_visibleRange: null,
};
this._private__options = options;
}
renderer() {
if (!this._private__series.options().visible) {
return null;
}
if (this._private__invalidated) {
this._internal__makeValid();
}
const layout = this._private__chart.options()['layout'];
this._private__renderer._internal_setParams(layout.fontSize, layout.fontFamily, this._private__options.zOrder);
this._private__renderer._internal_setData(this._private__data);
return this._private__renderer;
}
_internal_setMarkers(markers) {
this._private__markers = markers;
this._internal_update('data');
}
_internal_update(updateType) {
this._private__invalidated = true;
if (updateType === 'data') {
this._private__dataInvalidated = true;
}
}
_internal_updateOptions(options) {
this._private__invalidated = true;
this._private__options = options;
}
zOrder() {
return this._private__options.zOrder === 'aboveSeries' ? 'top' : this._private__options.zOrder;
}
_internal__makeValid() {
const timeScale = this._private__chart.timeScale();
const seriesMarkers = this._private__markers;
if (this._private__dataInvalidated) {
this._private__data._internal_items = seriesMarkers.map((marker) => ({
_internal_time: marker.time,
_internal_x: 0,
_internal_y: 0,
_internal_size: 0,
_internal_shape: marker.shape,
_internal_color: marker.color,
_internal_externalId: marker.id,
_internal_internalId: marker._internal_internalId,
_internal_text: undefined,
}));
this._private__dataInvalidated = false;
}
const layoutOptions = this._private__chart.options()['layout'];
this._private__data._internal_visibleRange = null;
const visibleBars = timeScale.getVisibleLogicalRange();
if (visibleBars === null) {
return;
}
const visibleBarsRange = new RangeImpl(Math.floor(visibleBars.from), Math.ceil(visibleBars.to));
const firstValue = this._private__series.data()[0];
if (firstValue === null) {
return;
}
if (this._private__data._internal_items.length === 0) {
return;
}
let prevTimeIndex = NaN;
const shapeMargin$1 = shapeMargin(timeScale.options().barSpacing);
const offsets = {
_internal_aboveBar: shapeMargin$1,
_internal_belowBar: shapeMargin$1,
};
this._private__data._internal_visibleRange = visibleTimedValues(this._private__data._internal_items, visibleBarsRange, true);
for (let index = this._private__data._internal_visibleRange.from; index < this._private__data._internal_visibleRange.to; index++) {
const marker = seriesMarkers[index];
if (marker.time !== prevTimeIndex) {
// new bar, reset stack counter
offsets._internal_aboveBar = shapeMargin$1;
offsets._internal_belowBar = shapeMargin$1;
prevTimeIndex = marker.time;
}
const rendererItem = this._private__data._internal_items[index];
rendererItem._internal_x = ensureNotNull(timeScale.logicalToCoordinate(marker.time));
if (marker.text !== undefined && marker.text.length > 0) {
rendererItem._internal_text = {
_internal_content: marker.text,
_internal_x: 0,
_internal_y: 0,
_internal_width: 0,
_internal_height: 0,
};
}
const dataAt = this._private__series.dataByIndex(marker.time, 0 /* MismatchDirection.None */);
if (dataAt === null) {
continue;
}
fillSizeAndY(rendererItem, marker, dataAt, offsets, layoutOptions.fontSize, shapeMargin$1, this._private__series, this._private__chart);
}
this._private__invalidated = false;
}
}
function mergeOptionsWithDefaults(options) {
return {
...seriesMarkerOptionsDefaults,
...options,
};
}
class SeriesMarkersPrimitive {
constructor(options) {
this._private__paneView = null;
this._private__markers = [];
this._private__indexedMarkers = [];
this._private__dataChangedHandler = null;
this._private__series = null;
this._private__chart = null;
this._private__autoScaleMarginsInvalidated = true;
this._private__autoScaleMargins = null;
this._private__markersPositions = null;
this._private__cachedBarSpacing = null;
this._private__recalculationRequired = true;
this._private__options = mergeOptionsWithDefaults(options);
}
attached(param) {
this._private__recalculateMarkers();
this._private__chart = param.chart;
this._private__series = param.series;
this._private__paneView = new SeriesMarkersPaneView(this._private__series, ensureNotNull(this._private__chart), this._private__options);
this._private__requestUpdate = param.requestUpdate;
this._private__series.subscribeDataChanged((scope) => this._private__onDataChanged(scope));
this._private__recalculationRequired = true;
this._internal_requestUpdate();
}
_internal_requestUpdate() {
if (this._private__requestUpdate) {
this._private__requestUpdate();
}
}
detached() {
if (this._private__series && this._private__dataChangedHandler) {
this._private__series.unsubscribeDataChanged(this._private__dataChangedHandler);
}
this._private__chart = null;
this._private__series = null;
this._private__paneView = null;
this._private__dataChangedHandler = null;
}
_internal_setMarkers(markers) {
this._private__recalculationRequired = true;
this._private__markers = markers;
this._private__recalculateMarkers();
this._private__autoScaleMarginsInvalidated = true;
this._private__markersPositions = null;
this._internal_requestUpdate();
}
_internal_markers() {
return this._private__markers;
}
paneViews() {
return this._private__paneView ? [this._private__paneView] : [];
}
updateAllViews() {
this._private__updateAllViews();
}
hitTest(x, y) {
if (this._private__paneView) {
return this._private__paneView.renderer()?._internal_hitTest(x, y) ?? null;
}
return null;
}
autoscaleInfo(startTimePoint, endTimePoint) {
if (this._private__options.autoScale && this._private__paneView) {
const margins = this._private__getAutoScaleMargins();
if (margins) {
return {
priceRange: null,
margins: margins,
};
}
}
return null;
}
_internal_applyOptions(options) {
this._private__options = mergeOptionsWithDefaults({ ...this._private__options, ...options });
if (this._internal_requestUpdate) {
this._internal_requestUpdate();
}
}
_private__getAutoScaleMargins() {
const chart = ensureNotNull(this._private__chart);
const barSpacing = chart.timeScale().options().barSpacing;
if (this._private__autoScaleMarginsInvalidated || barSpacing !== this._private__cachedBarSpacing) {
this._private__cachedBarSpacing = barSpacing;
if (this._private__markers.length > 0) {
const shapeMargin$1 = shapeMargin(barSpacing);
const marginValue = calculateShapeHeight(barSpacing) * 1.5 + shapeMargin$1 * 2;
const positions = this._private__getMarkerPositions();
this._private__autoScaleMargins = {
above: calculateAdjustedMargin(marginValue, positions.aboveBar, positions.inBar),
below: calculateAdjustedMargin(marginValue, positions.belowBar, positions.inBar),
};
}
else {
this._private__autoScaleMargins = null;
}
this._private__autoScaleMarginsInvalidated = false;
}
return this._private__autoScaleMargins;
}
_private__getMarkerPositions() {
if (this._private__markersPositions === null) {
this._private__markersPositions = this._private__markers.reduce((acc, marker) => {
if (!acc[marker.position]) {
acc[marker.position] = true;
}
return acc;
}, {
inBar: false,
aboveBar: false,
belowBar: false,
atPriceTop: false,
atPriceBottom: false,
atPriceMiddle: false,
});
}
return this._private__markersPositions;
}
_private__recalculateMarkers() {
if (!this._private__recalculationRequired || !this._private__chart || !this._private__series) {
return;
}
const timeScale = this._private__chart.timeScale();
const seriesData = this._private__series?.data();
if (timeScale.getVisibleLogicalRange() == null || !this._private__series || seriesData.length === 0) {
this._private__indexedMarkers = [];
return;
}
const firstDataIndex = timeScale.timeToIndex(ensureNotNull(seriesData[0].time), true);
this._private__indexedMarkers = this._private__markers.map((marker, index) => {
const timePointIndex = timeScale.timeToIndex(marker.time, true);
const searchMode = timePointIndex < firstDataIndex ? 1 /* MismatchDirection.NearestRight */ : -1 /* MismatchDirection.NearestLeft */;
const seriesDataByIndex = ensureNotNull(this._private__series).dataByIndex(timePointIndex, searchMode);
const finalIndex = timeScale.timeToIndex(ensureNotNull(seriesDataByIndex).time, false);
// You must explicitly define the types so that the minification build processes the field names correctly
const baseMarker = {
time: finalIndex,
position: marker.position,
shape: marker.shape,
color: marker.color,
id: marker.id,
_internal_internalId: index,
text: marker.text,
size: marker.size,
price: marker.price,
_internal_originalTime: marker.time,
};
if (marker.position === 'atPriceTop' ||
marker.position === 'atPriceBottom' ||
marker.position === 'atPriceMiddle') {
if (marker.price === undefined) {
throw new Error(`Price is required for position ${marker.position}`);
}
return {
...baseMarker,
position: marker.position, // TypeScript knows this is SeriesMarkerPricePosition
price: marker.price,
};
}
else {
return {
...baseMarker,
position: marker.position, // TypeScript knows this is SeriesMarkerBarPosition
price: marker.price, // Optional for bar positions
};
}
});
this._private__recalculationRequired = false;
}
_private__updateAllViews(updateType) {
if (this._private__paneView) {
this._private__recalculateMarkers();
this._private__paneView._internal_setMarkers(this._private__indexedMarkers);
this._private__paneView._internal_updateOptions(this._private__options);
this._private__paneView._internal_update(updateType);
}
}
_private__onDataChanged(scope) {
this._private__recalculationRequired = true;
this._internal_requestUpdate();
}
}
class SeriesMarkersPrimitiveWrapper extends SeriesPrimitiveAdapter {
constructor(series, primitive, markers) {
super(series, primitive);
if (markers) {
this.setMarkers(markers);
}
}
setMarkers(markers) {
this._internal__primitive._internal_setMarkers(markers);
}
markers() {
return this._internal__primitive._internal_markers();
}
}
/**
* A function to create a series markers primitive.
*
* @param series - The series to which the primitive will be attached.
*
* @param markers - An array of markers to be displayed on the series.
*
* @param options - Options for the series markers plugin.
*
* @example
* ```js
* import { createSeriesMarkers } from 'lightweight-charts';
*
* const seriesMarkers = createSeriesMarkers(
* series,
* [
* {
* color: 'green',
* position: 'inBar',
* shape: 'arrowDown',
* time: 1556880900,
* },
* ]
* );
* // and then you can modify the markers
* // set it to empty array to remove all markers
* seriesMarkers.setMarkers([]);
*
* // `seriesMarkers.markers()` returns current markers
* ```
*/
function createSeriesMarkers(series, markers, options) {
const wrapper = new SeriesMarkersPrimitiveWrapper(series, new SeriesMarkersPrimitive(options ?? {}));
if (markers) {
wrapper.setMarkers(markers);
}
return wrapper;
}
class ExpiringMarkerManager {
constructor(updateCallback) {
this._private__markers = new Map();
this._private__updateCallback = updateCallback;
}
_internal_setMarker(marker, key, timeout) {
this._internal_clearMarker(key);
if (timeout !== undefined) {
const timeoutId = window.setTimeout(() => {
this._private__markers.delete(key);
this._private__triggerUpdate();
}, timeout);
const markerWithTimeout = {
...marker,
_internal_timeoutId: timeoutId,
_internal_expiresAt: Date.now() + timeout,
};
this._private__markers.set(key, markerWithTimeout);
}
else {
// For markers without timeout, we set timeoutId and expiresAt to undefined
this._private__markers.set(key, {
...marker,
_internal_timeoutId: undefined,
_internal_expiresAt: undefined,
});
}
this._private__triggerUpdate();
}
_internal_clearMarker(key) {
const marker = this._private__markers.get(key);
if (marker && marker._internal_timeoutId !== undefined) {
window.clearTimeout(marker._internal_timeoutId);
}
this._private__markers.delete(key);
this._private__triggerUpdate();
}
_internal_clearAllMarkers() {
for (const [point] of this._private__markers) {
this._internal_clearMarker(point);
}
}
_internal_getMarkers() {
const now = Date.now();
const activeMarkers = [];
for (const [time, marker] of this._private__markers) {
if (!marker._internal_expiresAt || marker._internal_expiresAt > now) {
activeMarkers.push({ time: marker.time, sign: marker.sign, value: marker.value });
}
else {
this._internal_clearMarker(time);
}
}
return activeMarkers;
}
_internal_setUpdateCallback(callback) {
this._private__updateCallback = callback;
}
_private__triggerUpdate() {
if (this._private__updateCallback) {
this._private__updateCallback();
}
}
}
const upDownMarkersPluginOptionDefaults = {
positiveColor: '#22AB94',
negativeColor: '#F7525F',
updateVisibilityDuration: 5000,
};
class MarkersPrimitiveRenderer {
constructor(data, neutralColor, negativeColor, positiveColor) {
this._private__data = data;
this._private__neutralColor = neutralColor;
this._private__negativeColor = negativeColor;
this._private__positiveColor = positiveColor;
}
draw(target) {
target.useBitmapCoordinateSpace((scope) => {
const ctx = scope.context;
const tickWidth = Math.max(1, Math.floor(scope.horizontalPixelRatio));
const correction = (tickWidth % 2) / 2;
const rad = 4 /* Constants.Radius */ * scope.verticalPixelRatio + correction;
this._private__data.forEach((item) => {
const centreX = Math.round(item._internal_x * scope.horizontalPixelRatio) + correction;
ctx.beginPath();
const color = this._private__getColor(item._internal_sign);
ctx.fillStyle = color;
ctx.arc(centreX, item._internal_y * scope.verticalPixelRatio, rad, 0, 2 * Math.PI, false);
ctx.fill();
if (item._internal_sign) {
ctx.strokeStyle = color;
ctx.lineWidth = Math.floor(2 /* Constants.ArrowLineWidth */ * scope.horizontalPixelRatio);
ctx.beginPath();
ctx.moveTo((item._internal_x - 4.7 /* Constants.ArrowSize */) * scope.horizontalPixelRatio + correction, (item._internal_y - 7 /* Constants.ArrowOffset */ * item._internal_sign) *
scope.verticalPixelRatio);
ctx.lineTo(item._internal_x * scope.horizontalPixelRatio + correction, (item._internal_y -
7 /* Constants.ArrowOffset */ * item._internal_sign -
7 /* Constants.ArrowOffset */ * item._internal_sign * 0.5 /* Constants.VerticalScale */) *
scope.verticalPixelRatio);
ctx.lineTo((item._internal_x + 4.7 /* Constants.ArrowSize */) * scope.horizontalPixelRatio + correction, (item._internal_y - 7 /* Constants.ArrowOffset */ * item._internal_sign) *
scope.verticalPixelRatio);
ctx.stroke();
}
});
});
}
_private__getColor(sign) {
if (sign === 0) {
return this._private__neutralColor;
}
return sign > 0 ? this._private__positiveColor : this._private__negativeColor;
}
}
function isAreaStyleOptions(opts, seriesType) {
return seriesType === 'Area';
}
function getNeutralColor(opts, seriesType) {
if (isAreaStyleOptions(opts, seriesType)) {
return opts.lineColor;
}
return opts.color;
}
class MarkersPrimitivePaneView {
constructor(series, timeScale, options) {
this._private__data = [];
this._private__series = series;
this._private__timeScale = timeScale;
this._private__options = options;
}
_internal_update(markers) {
this._private__data = markers.map((marker) => {
const y = this._private__series.priceToCoordinate(marker.value);
if (y === null) {
return null;
}
const x = ensureNotNull(this._private__timeScale.timeToCoordinate(marker.time));
return {
_internal_x: x,
_internal_y: y,
_internal_sign: marker.sign,
};
})
.filter(notNull);
}
renderer() {
const options = this._private__series.options();
const seriesType = this._private__series.seriesType();
const neutralColor = getNeutralColor(options, seriesType);
return new MarkersPrimitiveRenderer(this._private__data, neutralColor, this._private__options.negativeColor, this._private__options.positiveColor);
}
}
function isLineData(item, type) {
return type === 'Line' || type === 'Area';
}
class UpDownMarkersPrimitive {
constructor(options) {
this._private__chart = undefined;
this._private__series = undefined;
this._private__paneViews = [];
this._private__horzScaleBehavior = null;
this._private__managedDataPoints = new Map();
this._private__markersManager = new ExpiringMarkerManager(() => this._internal_requestUpdate());
this._private__options = {
...upDownMarkersPluginOptionDefaults,
...options,
};
}
_internal_applyOptions(options) {
this._private__options = {
...this._private__options,
...options,
};
this._internal_requestUpdate();
}
_internal_setMarkers(markers) {
this._private__markersManager._internal_clearAllMarkers();
const horzBehaviour = this._private__horzScaleBehavior;
if (!horzBehaviour) {
return;
}
markers.forEach((marker) => {
this._private__markersManager._internal_setMarker(marker, horzBehaviour.key(marker.time));
});
}
_internal_markers() {
return this._private__markersManager._internal_getMarkers();
}
_internal_requestUpdate() {
this._private__requestUpdate?.();
}
attached(params) {
const { chart, series, requestUpdate, horzScaleBehavior, } = params;
this._private__chart = chart;
this._private__series = series;
this._private__horzScaleBehavior = horzScaleBehavior;
const seriesType = this._private__series.seriesType();
if (seriesType !== 'Area' && seriesType !== 'Line') {
throw new Error('UpDownMarkersPrimitive is only supported for Area and Line series types');
}
this._private__paneViews = [
new MarkersPrimitivePaneView(this._private__series, this._private__chart.timeScale(), this._private__options),
];
this._private__requestUpdate = requestUpdate;
this._internal_requestUpdate();
}
detached() {
this._private__chart = undefined;
this._private__series = undefined;
this._private__requestUpdate = undefined;
}
_internal_chart() {
return ensureDefined(this._private__chart);
}
_internal_series() {
return ensureDefined(this._private__series);
}
updateAllViews() {
this._private__paneViews.forEach((pw) => pw._internal_update(this._internal_markers()));
}
paneViews() {
return this._private__paneViews;
}
_internal_setData(data) {
if (!this._private__series) {
throw new Error('Primitive not attached to series');
}
const seriesType = this._private__series.seriesType();
this._private__managedDataPoints.clear();
const horzBehaviour = this._private__horzScaleBehavior;
if (horzBehaviour) {
data.forEach((d) => {
if (isFulfilledData(d) && isLineData(d, seriesType)) {
this._private__managedDataPoints.set(horzBehaviour.key(d.time), d.value);
}
});
}
ensureDefined(this._private__series).setData(data);
}
_internal_update(data, historicalUpdate) {
if (!this._private__series || !this._private__horzScaleBehavior) {
throw new Error('Primitive not attached to series');
}
const seriesType = this._private__series.seriesType();
const horzKey = this._private__horzScaleBehavior.key(data.time);
if (isWhitespaceData(data)) {
this._private__managedDataPoints.delete(horzKey);
}
if (isFulfilledData(data) && isLineData(data, seriesType)) {
const existingPrice = this._private__managedDataPoints.get(horzKey);
if (existingPrice) {
this._private__markersManager._internal_setMarker({
time: data.time,
value: data.value,
sign: getSign(data.value, existingPrice),
}, horzKey, this._private__options.updateVisibilityDuration);
}
}
ensureDefined(this._private__series).update(data, historicalUpdate);
}
_internal_clearMarkers() {
this._private__markersManager._internal_clearAllMarkers();
}
}
function getSign(newValue, oldValue) {
if (newValue === oldValue) {
return 0;
}
return newValue - oldValue > 0 ? 1 : -1;
}
class SeriesUpDownMarkerPrimitiveWrapper extends SeriesPrimitiveAdapter {
setData(data) {
return this._internal__primitive._internal_setData(data);
}
update(data, historicalUpdate) {
return this._internal__primitive._internal_update(data, historicalUpdate);
}
markers() {
return this._internal__primitive._internal_markers();
}
setMarkers(markers) {
return this._internal__primitive._internal_setMarkers(markers);
}
clearMarkers() {
return this._internal__primitive._internal_clearMarkers();
}
}
/**
* Creates and attaches the Series Up Down Markers Plugin.
*
* @param series - Series to which attach the Up Down Markers Plugin
* @param options - options for the Up Down Markers Plugin
*
* @returns Api for Series Up Down Marker Plugin. {@link ISeriesUpDownMarkerPluginApi}
*
* @example
* ```js
* import { createUpDownMarkers, createChart, LineSeries } from 'lightweight-charts';
*
* const chart = createChart('container');
* const lineSeries = chart.addSeries(LineSeries);
* const upDownMarkers = createUpDownMarkers(lineSeries, {
* positiveColor: '#22AB94',
* negativeColor: '#F7525F',
* updateVisibilityDuration: 5000,
* });
* // to add some data
* upDownMarkers.setData(
* [
* { time: '2020-02-02', value: 12.34 },
* //... more line series data
* ]
* );
* // ... Update some values
* upDownMarkers.update({ time: '2020-02-02', value: 13.54 }, true);
* // to remove plugin from the series
* upDownMarkers.detach();
* ```
*/
function createUpDownMarkers(series, options = {}) {
const wrapper = new SeriesUpDownMarkerPrimitiveWrapper(series, new UpDownMarkersPrimitive(options));
return wrapper;
}
///
const customSeriesDefaultOptions = {
...seriesOptionsDefaults,
...customStyleDefaults$1,
};
/**
* Returns the current version as a string. For example `'3.3.0'`.
*/
function version() {
return "5.2.0";
}
var LightweightChartsModule = /*#__PURE__*/Object.freeze({
__proto__: null,
AreaSeries: areaSeries,
BarSeries: barSeries,
BaselineSeries: baselineSeries,
CandlestickSeries: candlestickSeries,
get ColorType () { return ColorType; },
get CrosshairMode () { return CrosshairMode; },
HistogramSeries: histogramSeries,
get LastPriceAnimationMode () { return LastPriceAnimationMode; },
LineSeries: lineSeries,
get LineStyle () { return LineStyle; },
get LineType () { return LineType; },
get MismatchDirection () { return MismatchDirection; },
get PriceLineSource () { return PriceLineSource; },
get PriceScaleMode () { return PriceScaleMode; },
get TickMarkType () { return TickMarkType; },
get TrackingModeExitMode () { return TrackingModeExitMode; },
createChart: createChart,
createChartEx: createChartEx,
createImageWatermark: createImageWatermark,
createOptionsChart: createOptionsChart,
createSeriesMarkers: createSeriesMarkers,
createTextWatermark: createTextWatermark,
createUpDownMarkers: createUpDownMarkers,
createYieldCurveChart: createYieldCurveChart,
customSeriesDefaultOptions: customSeriesDefaultOptions,
defaultHorzScaleBehavior: defaultHorzScaleBehavior,
isBusinessDay: isBusinessDay,
isUTCTimestamp: isUTCTimestamp,
version: version
});
// eslint-disable-next-line @typescript-eslint/naming-convention
// put all exports from package to window.LightweightCharts object
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-member-access
window.LightweightCharts = LightweightChartsModule;
})();