UNPKG

lightweight-charts

Version:

Performant financial charts built with HTML5 canvas

1,257 lines (1,239 loc) 558 kB
/*! * @license * TradingView Lightweight Charts™ v4.0.1 * Copyright (c) 2023 TradingView, Inc. * Licensed under Apache License 2.0 https://www.apache.org/licenses/LICENSE-2.0 */ /** * 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 dottled line with more space between dots. */ LineStyle[LineStyle["SparseDotted"] = 4] = "SparseDotted"; })(LineStyle || (LineStyle = {})); function setLineStyle(ctx, style) { const dashPatterns = { [0 /* LineStyle.Solid */]: [], [1 /* LineStyle.Dotted */]: [ctx.lineWidth, ctx.lineWidth], [2 /* LineStyle.Dashed */]: [2 * ctx.lineWidth, 2 * ctx.lineWidth], [3 /* LineStyle.LargeDashed */]: [6 * ctx.lineWidth, 6 * ctx.lineWidth], [4 /* LineStyle.SparseDotted */]: [ctx.lineWidth, 4 * ctx.lineWidth], }; const dashPattern = dashPatterns[style]; ctx.setLineDash(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) { } /** * Note this object should be explicitly marked as public so that dts-bundle-generator does not mangle the property names. * * @public * @see https://developer.mozilla.org/en-US/docs/Web/CSS/color_value */ const namedColorRgbHexStrings = { // The order of properties in this Record is not important for the internal logic. // It's just GZIPped better when props follows this order. // Please add new colors to the end of the record. khaki: '#f0e68c', azure: '#f0ffff', aliceblue: '#f0f8ff', ghostwhite: '#f8f8ff', gold: '#ffd700', goldenrod: '#daa520', gainsboro: '#dcdcdc', gray: '#808080', green: '#008000', honeydew: '#f0fff0', floralwhite: '#fffaf0', lightblue: '#add8e6', lightcoral: '#f08080', lemonchiffon: '#fffacd', hotpink: '#ff69b4', lightyellow: '#ffffe0', greenyellow: '#adff2f', lightgoldenrodyellow: '#fafad2', limegreen: '#32cd32', linen: '#faf0e6', lightcyan: '#e0ffff', magenta: '#f0f', maroon: '#800000', olive: '#808000', orange: '#ffa500', oldlace: '#fdf5e6', mediumblue: '#0000cd', transparent: '#0000', lime: '#0f0', lightpink: '#ffb6c1', mistyrose: '#ffe4e1', moccasin: '#ffe4b5', midnightblue: '#191970', orchid: '#da70d6', mediumorchid: '#ba55d3', mediumturquoise: '#48d1cc', orangered: '#ff4500', royalblue: '#4169e1', powderblue: '#b0e0e6', red: '#f00', coral: '#ff7f50', turquoise: '#40e0d0', white: '#fff', whitesmoke: '#f5f5f5', wheat: '#f5deb3', teal: '#008080', steelblue: '#4682b4', bisque: '#ffe4c4', aquamarine: '#7fffd4', aqua: '#0ff', sienna: '#a0522d', silver: '#c0c0c0', springgreen: '#00ff7f', antiquewhite: '#faebd7', burlywood: '#deb887', brown: '#a52a2a', beige: '#f5f5dc', chocolate: '#d2691e', chartreuse: '#7fff00', cornflowerblue: '#6495ed', cornsilk: '#fff8dc', crimson: '#dc143c', cadetblue: '#5f9ea0', tomato: '#ff6347', fuchsia: '#f0f', blue: '#00f', salmon: '#fa8072', blanchedalmond: '#ffebcd', slateblue: '#6a5acd', slategray: '#708090', thistle: '#d8bfd8', tan: '#d2b48c', cyan: '#0ff', darkblue: '#00008b', darkcyan: '#008b8b', darkgoldenrod: '#b8860b', darkgray: '#a9a9a9', blueviolet: '#8a2be2', black: '#000', darkmagenta: '#8b008b', darkslateblue: '#483d8b', darkkhaki: '#bdb76b', darkorchid: '#9932cc', darkorange: '#ff8c00', darkgreen: '#006400', darkred: '#8b0000', dodgerblue: '#1e90ff', darkslategray: '#2f4f4f', dimgray: '#696969', deepskyblue: '#00bfff', firebrick: '#b22222', forestgreen: '#228b22', indigo: '#4b0082', ivory: '#fffff0', lavenderblush: '#fff0f5', feldspar: '#d19275', indianred: '#cd5c5c', lightgreen: '#90ee90', lightgrey: '#d3d3d3', lightskyblue: '#87cefa', lightslategray: '#789', lightslateblue: '#8470ff', snow: '#fffafa', lightseagreen: '#20b2aa', lightsalmon: '#ffa07a', darksalmon: '#e9967a', darkviolet: '#9400d3', mediumpurple: '#9370d8', mediumaquamarine: '#66cdaa', skyblue: '#87ceeb', lavender: '#e6e6fa', lightsteelblue: '#b0c4de', mediumvioletred: '#c71585', mintcream: '#f5fffa', navajowhite: '#ffdead', navy: '#000080', olivedrab: '#6b8e23', palevioletred: '#d87093', violetred: '#d02090', yellow: '#ff0', yellowgreen: '#9acd32', lawngreen: '#7cfc00', pink: '#ffc0cb', paleturquoise: '#afeeee', palegoldenrod: '#eee8aa', darkolivegreen: '#556b2f', darkseagreen: '#8fbc8f', darkturquoise: '#00ced1', peachpuff: '#ffdab9', deeppink: '#ff1493', violet: '#ee82ee', palegreen: '#98fb98', mediumseagreen: '#3cb371', peru: '#cd853f', saddlebrown: '#8b4513', sandybrown: '#f4a460', rosybrown: '#bc8f8f', purple: '#800080', seagreen: '#2e8b57', seashell: '#fff5ee', papayawhip: '#ffefd5', mediumslateblue: '#7b68ee', plum: '#dda0dd', mediumspringgreen: '#00fa9a', }; 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) { return (!(component <= 0) && !(component > 0) ? 0 : component < 0 ? 0 : component > 1 ? 1 : // limit the precision of all numbers to at most 4 digits in fractional part Math.round(component * 10000) / 10000); } /** * @example * #fb0 * @example * #f0f * @example * #f0fa */ const shortHexRe = /^#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])?$/i; /** * @example * #00ff00 * @example * #336699 * @example * #336699FA */ const hexRe = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?$/i; /** * @example * rgb(123, 234, 45) * @example * rgb(255,234,245) */ const rgbRe = /^rgb\(\s*(-?\d{1,10})\s*,\s*(-?\d{1,10})\s*,\s*(-?\d{1,10})\s*\)$/; /** * @example * rgba(123, 234, 45, 1) * @example * rgba(255,234,245,0.1) */ const rgbaRe = /^rgba\(\s*(-?\d{1,10})\s*,\s*(-?\d{1,10})\s*,\s*(-?\d{1,10})\s*,\s*(-?[\d]{0,10}(?:\.\d+)?)\s*\)$/; function colorStringToRgba(colorString) { colorString = colorString.toLowerCase(); // eslint-disable-next-line no-restricted-syntax if (colorString in namedColorRgbHexStrings) { colorString = namedColorRgbHexStrings[colorString]; } { const matches = rgbaRe.exec(colorString) || rgbRe.exec(colorString); if (matches) { return [ normalizeRgbComponent(parseInt(matches[1], 10)), normalizeRgbComponent(parseInt(matches[2], 10)), normalizeRgbComponent(parseInt(matches[3], 10)), normalizeAlphaComponent((matches.length < 5 ? 1 : parseFloat(matches[4]))), ]; } } { const matches = hexRe.exec(colorString); if (matches) { return [ normalizeRgbComponent(parseInt(matches[1], 16)), normalizeRgbComponent(parseInt(matches[2], 16)), normalizeRgbComponent(parseInt(matches[3], 16)), 1, ]; } } { const matches = shortHexRe.exec(colorString); if (matches) { return [ normalizeRgbComponent(parseInt(matches[1], 16) * 0x11), normalizeRgbComponent(parseInt(matches[2], 16) * 0x11), normalizeRgbComponent(parseInt(matches[3], 16) * 0x11), 1, ]; } } throw new Error(`Cannot parse color: ${colorString}`); } 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]); } function applyAlpha(color, alpha) { // special case optimization if (color === 'transparent') { return color; } const originRgba = colorStringToRgba(color); const originAlpha = originRgba[3]; return `rgba(${originRgba[0]}, ${originRgba[1]}, ${originRgba[2]}, ${alpha * originAlpha})`; } function generateContrastColors(backgroundColor) { const rgb = colorStringToRgba(backgroundColor); return { _internal_background: `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`, _internal_foreground: rgbaToGrayscale(rgb) > 160 ? 'black' : 'white', }; } function gradientColorAtPercent(topColor, bottomColor, percent) { const [topR, topG, topB, topA] = colorStringToRgba(topColor); const [bottomR, bottomG, bottomB, bottomA] = colorStringToRgba(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]})`; } 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) { continue; } if ('object' !== typeof src[i] || dst[i] === undefined) { 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; } } 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 MediaCoordinatesPaneRenderer { _internal_draw(target, isHovered, hitTestData) { target.useMediaCoordinateSpace((scope) => this._internal__drawImpl(scope, isHovered, hitTestData)); } _internal_drawBackground(target, isHovered, hitTestData) { target.useMediaCoordinateSpace((scope) => this._internal__drawBackgroundImpl(scope, isHovered, hitTestData)); } _internal__drawBackgroundImpl(renderingScope, isHovered, hitTestData) { } } class PaneRendererMarks extends MediaCoordinatesPaneRenderer { constructor() { super(...arguments); this._internal__data = null; } _internal_setData(data) { this._internal__data = data; } _internal__drawImpl({ context: ctx }) { 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 draw = (radius) => { ctx.beginPath(); for (let i = visibleRange.to - 1; i >= visibleRange.from; --i) { const point = data._internal_items[i]; ctx.moveTo(point._internal_x, point._internal_y); ctx.arc(point._internal_x, point._internal_y, 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) { 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__compositeRenderer._internal_setRenderers(this._private__markersRenderers); } _internal_update(updateType) { const serieses = this._private__chartModel._internal_serieses(); 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); } this._private__invalidated = true; } _internal_renderer() { if (this._private__invalidated) { this._private__updateImpl(); this._private__invalidated = false; } return this._private__compositeRenderer; } _private__updateImpl() { const serieses = this._private__chartModel._internal_serieses(); const timePointIndex = this._private__crosshair._internal_appliedIndex(); const timeScale = this._private__chartModel._internal_timeScale(); serieses.forEach((s, index) => { var _a; const data = this._private__markersData[index]; const seriesData = s._internal_markerDataAtIndex(timePointIndex); if (seriesData === null || !s._internal_visible()) { data._internal_visibleRange = null; return; } const firstValue = ensureNotNull(s._internal_firstValue()); 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 = (_a = seriesData._internal_borderColor) !== null && _a !== void 0 ? _a : 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 BitmapCoordinatesPaneRenderer { _internal_draw(target, isHovered, hitTestData) { target.useBitmapCoordinateSpace((scope) => this._internal__drawImpl(scope, isHovered, hitTestData)); } } 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) { 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; } _internal_update() { this._private__invalidated = true; } _internal_renderer() { 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 pane = ensureNotNull(this._private__source._internal_pane()); const crosshairOptions = pane._internal_model()._internal_options().crosshair; const data = this._private__rendererData; data._internal_horzLine._internal_visible = visible && this._private__source._internal_horzLineVisible(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) { if (Array.isArray(borderRadius)) { return borderRadius.map((x) => x === 0 ? x : x + offset); } return borderRadius + offset; } function drawRoundRect( // eslint:disable-next-line:max-params ctx, x, y, w, h, radii) { let radiusLeftTop; let radiusRightTop; let radiusRightBottom; let radiusLeftBottom; if (!Array.isArray(radii)) { const oneRadius = Math.max(0, radii); radiusLeftTop = oneRadius; radiusRightTop = oneRadius; radiusRightBottom = oneRadius; radiusLeftBottom = oneRadius; } else if (radii.length === 2) { const cornerRadius1 = Math.max(0, radii[0]); const cornerRadius2 = Math.max(0, radii[1]); radiusLeftTop = cornerRadius1; radiusRightTop = cornerRadius1; radiusRightBottom = cornerRadius2; radiusLeftBottom = cornerRadius2; } else if (radii.length === 4) { radiusLeftTop = Math.max(0, radii[0]); radiusRightTop = Math.max(0, radii[1]); radiusRightBottom = Math.max(0, radii[2]); radiusLeftBottom = Math.max(0, radii[3]); } else { throw new Error(`Wrong border radius - it should be like css border radius`); } ctx.beginPath(); ctx.moveTo(x + radiusLeftTop, y); ctx.lineTo(x + w - radiusRightTop, y); if (radiusRightTop !== 0) { ctx.arcTo(x + w, y, x + w, y + radiusRightTop, radiusRightTop); } ctx.lineTo(x + w, y + h - radiusRightBottom); if (radiusRightBottom !== 0) { ctx.arcTo(x + w, y + h, x + w - radiusRightBottom, y + h, radiusRightBottom); } ctx.lineTo(x + radiusLeftBottom, y + h); if (radiusLeftBottom !== 0) { ctx.arcTo(x, y + h, x, y + h - radiusLeftBottom, radiusLeftBottom); } ctx.lineTo(x, y + radiusLeftTop); if (radiusLeftTop !== 0) { ctx.arcTo(x, y, x + radiusLeftTop, y, radiusLeftTop); } } // eslint-disable-next-line max-params function drawRoundRectWithInnerBorder(ctx, left, top, width, height, backgroundColor, borderWidth = 0, borderRadius = 0, borderColor = '') { ctx.save(); if (!borderWidth || !borderColor || borderColor === backgroundColor) { drawRoundRect(ctx, left, top, width, height, borderRadius); ctx.fillStyle = backgroundColor; ctx.fill(); ctx.restore(); return; } const halfBorderWidth = borderWidth / 2; // Draw body if (backgroundColor !== 'transparent') { const innerRadii = changeBorderRadius(borderRadius, -borderWidth); drawRoundRect(ctx, left + borderWidth, top + borderWidth, width - borderWidth * 2, height - borderWidth * 2, innerRadii); ctx.fillStyle = backgroundColor; ctx.fill(); } // Draw border if (borderColor !== 'transparent') { const outerRadii = changeBorderRadius(borderRadius, -halfBorderWidth); drawRoundRect(ctx, left + halfBorderWidth, top + halfBorderWidth, width - borderWidth, height - borderWidth, outerRadii); 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; const drawLabelBody = (labelBackgroundColor, labelBorderColor) => { if (geom._internal_alignRight) { drawRoundRectWithInnerBorder(ctx, gb._internal_xOutside, gb._internal_yTop, gb._internal_totalWidth, gb._internal_totalHeight, labelBackgroundColor, gb._internal_horzBorder, [gb._internal_radius, 0, 0, gb._internal_radius], labelBorderColor); } else { drawRoundRectWithInnerBorder(ctx, gb._internal_xInside, gb._internal_yTop, gb._internal_totalWidth, gb._internal_totalHeight, labelBackgroundColor, gb._internal_horzBorder, [0, gb._internal_radius, gb._internal_radius, 0], labelBorderColor); } }; // draw border // draw label background drawLabelBody(backgroundColor, 'transparent'); // 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 label border above the tick drawLabelBody('transparent', backgroundColor); // 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) { var _a; 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 = (_a = this._private__commonData._internal_fixedCoordinate) !== null && _a !== void 0 ? _a : 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 || 0; } _internal_setFixedCoordinate(value) { this._private__commonRendererData._internal_fixedCoordinate = value; } _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; 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 = 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_tickV