bn-lightweight-charts 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2472 @@
1
+ var Lib = (function (exports, lightweightCharts) {
2
+ 'use strict';
3
+
4
+ const paneStyleDefault = {
5
+ backgroundColor: 'rgb(18,24,38)',
6
+ hoverBackgroundColor: '#3c434c',
7
+ clickBackgroundColor: '#50565E',
8
+ activeBackgroundColor: 'rgba(0, 122, 255, 0.7)',
9
+ mutedBackgroundColor: 'rgba(0, 122, 255, 0.3)',
10
+ borderColor: '#3C434C',
11
+ color: '#d8d9db',
12
+ activeColor: '#ececed',
13
+ };
14
+ function globalParamInit() {
15
+ window.pane = {
16
+ ...paneStyleDefault,
17
+ };
18
+ window.containerDiv = document.getElementById("container") || document.createElement('div');
19
+ window.setCursor = (type) => {
20
+ if (type)
21
+ window.cursor = type;
22
+ document.body.style.cursor = window.cursor;
23
+ };
24
+ window.cursor = 'default';
25
+ window.textBoxFocused = false;
26
+ }
27
+ const setCursor = (type) => {
28
+ if (type)
29
+ window.cursor = type;
30
+ document.body.style.cursor = window.cursor;
31
+ };
32
+ function htmlToElement(html) {
33
+ let template = document.createElement('template');
34
+ html = html.trim(); // Never return a text node of whitespace as the result
35
+ template.innerHTML = html;
36
+ const el = template.content.firstChild;
37
+ if (!el)
38
+ throw new Error("Invalid HTML passed to htmlToElement");
39
+ return el;
40
+ }
41
+ // export interface SeriesHandler {
42
+ // type: string;
43
+ // series: ISeriesApi<SeriesType>;
44
+ // markers: SeriesMarker<"">[],
45
+ // horizontal_lines: HorizontalLine[],
46
+ // name?: string,
47
+ // precision: number,
48
+ // }
49
+
50
+ class Legend {
51
+ handler;
52
+ div;
53
+ seriesContainer;
54
+ ohlcEnabled = false;
55
+ percentEnabled = false;
56
+ linesEnabled = false;
57
+ colorBasedOnCandle = false;
58
+ text;
59
+ candle;
60
+ _lines = [];
61
+ _lines_grp = {};
62
+ constructor(handler) {
63
+ this.legendHandler = this.legendHandler.bind(this);
64
+ this.handler = handler;
65
+ this.ohlcEnabled = false;
66
+ this.percentEnabled = false;
67
+ this.linesEnabled = false;
68
+ this.colorBasedOnCandle = false;
69
+ this.div = document.createElement('div');
70
+ this.div.classList.add("legend");
71
+ this.div.style.maxWidth = `${(handler.scale.width * 100) - 8}vw`;
72
+ this.div.style.display = 'none';
73
+ const seriesWrapper = document.createElement('div');
74
+ seriesWrapper.style.display = 'flex';
75
+ seriesWrapper.style.flexDirection = 'row';
76
+ this.seriesContainer = document.createElement("div");
77
+ this.seriesContainer.classList.add("series-container");
78
+ this.text = document.createElement('span');
79
+ this.text.style.lineHeight = '1.8';
80
+ this.candle = document.createElement('div');
81
+ seriesWrapper.appendChild(this.seriesContainer);
82
+ this.div.appendChild(this.text);
83
+ this.div.appendChild(this.candle);
84
+ this.div.appendChild(seriesWrapper);
85
+ handler.div.appendChild(this.div);
86
+ // this.makeSeriesRows(handler);
87
+ handler.chart.subscribeCrosshairMove(this.legendHandler);
88
+ }
89
+ toJSON() {
90
+ // Exclude the chart attribute from serialization
91
+ const { _lines, handler, ...serialized } = this;
92
+ return serialized;
93
+ }
94
+ // makeSeriesRows(handler: Handler) {
95
+ // if (this.linesEnabled) handler._seriesList.forEach(s => this.makeSeriesRow(s))
96
+ // }
97
+ makeSeriesRow(name, series, paneIndex) {
98
+ const strokeColor = '#FFF';
99
+ let openEye = `
100
+ <path style="fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke:${strokeColor};stroke-opacity:1;stroke-miterlimit:4;" d="M 21.998437 12 C 21.998437 12 18.998437 18 12 18 C 5.001562 18 2.001562 12 2.001562 12 C 2.001562 12 5.001562 6 12 6 C 18.998437 6 21.998437 12 21.998437 12 Z M 21.998437 12 " transform="matrix(0.833333,0,0,0.833333,0,0)"/>
101
+ <path style="fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke:${strokeColor};stroke-opacity:1;stroke-miterlimit:4;" d="M 15 12 C 15 13.654687 13.654687 15 12 15 C 10.345312 15 9 13.654687 9 12 C 9 10.345312 10.345312 9 12 9 C 13.654687 9 15 10.345312 15 12 Z M 15 12 " transform="matrix(0.833333,0,0,0.833333,0,0)"/>\`
102
+ `;
103
+ let closedEye = `
104
+ <path style="fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke:${strokeColor};stroke-opacity:1;stroke-miterlimit:4;" d="M 20.001562 9 C 20.001562 9 19.678125 9.665625 18.998437 10.514062 M 12 14.001562 C 10.392187 14.001562 9.046875 13.589062 7.95 12.998437 M 12 14.001562 C 13.607812 14.001562 14.953125 13.589062 16.05 12.998437 M 12 14.001562 L 12 17.498437 M 3.998437 9 C 3.998437 9 4.354687 9.735937 5.104687 10.645312 M 7.95 12.998437 L 5.001562 15.998437 M 7.95 12.998437 C 6.689062 12.328125 5.751562 11.423437 5.104687 10.645312 M 16.05 12.998437 L 18.501562 15.998437 M 16.05 12.998437 C 17.38125 12.290625 18.351562 11.320312 18.998437 10.514062 M 5.104687 10.645312 L 2.001562 12 M 18.998437 10.514062 L 21.998437 12 " transform="matrix(0.833333,0,0,0.833333,0,0)"/>
105
+ `;
106
+ let row = document.createElement('div');
107
+ row.style.display = 'flex';
108
+ row.style.alignItems = 'center';
109
+ let div = document.createElement('div');
110
+ let toggle = document.createElement('div');
111
+ toggle.classList.add('legend-toggle-switch');
112
+ let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
113
+ svg.setAttribute("width", "22");
114
+ svg.setAttribute("height", "16");
115
+ let group = document.createElementNS("http://www.w3.org/2000/svg", "g");
116
+ group.innerHTML = openEye;
117
+ let on = true;
118
+ toggle.addEventListener('click', () => {
119
+ if (on) {
120
+ on = false;
121
+ group.innerHTML = closedEye;
122
+ series.applyOptions({
123
+ visible: false
124
+ });
125
+ }
126
+ else {
127
+ on = true;
128
+ series.applyOptions({
129
+ visible: true
130
+ });
131
+ group.innerHTML = openEye;
132
+ }
133
+ });
134
+ svg.appendChild(group);
135
+ toggle.appendChild(svg);
136
+ row.appendChild(div);
137
+ row.appendChild(toggle);
138
+ this.seriesContainer.appendChild(row);
139
+ const color = series.options().baseLineColor;
140
+ this._lines.push({
141
+ name: name,
142
+ paneIndex: paneIndex,
143
+ div: div,
144
+ row: row,
145
+ toggle: toggle,
146
+ series: series,
147
+ solid: color.startsWith('rgba') ? color.replace(/[^,]+(?=\))/, '1') : color
148
+ });
149
+ this._lines.sort((a, b) => a.paneIndex - b.paneIndex);
150
+ this._lines_grp = this._lines.reduce((acc, item) => {
151
+ if (!acc[item.paneIndex]) {
152
+ acc[item.paneIndex] = [];
153
+ }
154
+ acc[item.paneIndex].push(item);
155
+ return acc;
156
+ }, {});
157
+ this.seriesContainer.innerHTML = '';
158
+ for (const k in this._lines_grp) {
159
+ for (const l of this._lines_grp[k]) {
160
+ this.seriesContainer.appendChild(l.row);
161
+ }
162
+ this.seriesContainer.appendChild(htmlToElement("<br>"));
163
+ }
164
+ }
165
+ legendItemFormat(num, decimal) {
166
+ return num.toFixed(decimal).toString().padStart(8, ' ');
167
+ }
168
+ shorthandFormat(num) {
169
+ const absNum = Math.abs(num);
170
+ if (absNum >= 1000000) {
171
+ return (num / 1000000).toFixed(1) + 'M';
172
+ }
173
+ else if (absNum >= 1000) {
174
+ return (num / 1000).toFixed(1) + 'K';
175
+ }
176
+ return num.toString().padStart(8, ' ');
177
+ }
178
+ legendHandler(param, usingPoint = false) {
179
+ if (!this.ohlcEnabled && !this.linesEnabled && !this.percentEnabled)
180
+ return;
181
+ const options = this.handler.series.options();
182
+ if (!param.time) {
183
+ this.candle.style.color = 'transparent';
184
+ this.candle.innerHTML = this.candle.innerHTML.replace(options['upColor'], '').replace(options['downColor'], '');
185
+ return;
186
+ }
187
+ let data;
188
+ let logical = null;
189
+ if (usingPoint) {
190
+ const timeScale = this.handler.chart.timeScale();
191
+ let coordinate = timeScale.timeToCoordinate(param.time);
192
+ if (coordinate)
193
+ logical = timeScale.coordinateToLogical(coordinate.valueOf());
194
+ if (logical)
195
+ data = this.handler.series.dataByIndex(logical.valueOf());
196
+ }
197
+ else {
198
+ data = param.seriesData.get(this.handler.series);
199
+ }
200
+ this.candle.style.color = '';
201
+ let str = '<span style="line-height: 1.8;">';
202
+ if (data) {
203
+ if (this.ohlcEnabled) {
204
+ str += `O ${this.legendItemFormat(data.open, this.handler.precision)} `;
205
+ str += `| H ${this.legendItemFormat(data.high, this.handler.precision)} `;
206
+ str += `| L ${this.legendItemFormat(data.low, this.handler.precision)} `;
207
+ str += `| C ${this.legendItemFormat(data.close, this.handler.precision)} `;
208
+ }
209
+ if (this.handler.volumeSeries) {
210
+ let volumeData;
211
+ if (logical) {
212
+ volumeData = this.handler.volumeSeries.dataByIndex(logical);
213
+ }
214
+ else {
215
+ volumeData = param.seriesData.get(this.handler.volumeSeries);
216
+ }
217
+ if (volumeData) {
218
+ str += this.ohlcEnabled ? `| V ${this.shorthandFormat(volumeData.value)}` : '';
219
+ }
220
+ }
221
+ if (this.percentEnabled) {
222
+ let percentMove = ((data.close - data.open) / data.open) * 100;
223
+ let color = percentMove > 0 ? options["upColor"] : options["downColor"];
224
+ let percentStr = `${percentMove >= 0 ? "+" : ""}${percentMove.toFixed(2)} %`;
225
+ if (this.colorBasedOnCandle) {
226
+ str += `| <span style="color: ${color};">${percentStr}</span>`;
227
+ }
228
+ else {
229
+ str += "| " + percentStr;
230
+ }
231
+ }
232
+ }
233
+ this.candle.innerHTML = str + '</span>';
234
+ this._lines.forEach((e) => {
235
+ if (!this.linesEnabled) {
236
+ e.row.style.display = 'none';
237
+ return;
238
+ }
239
+ e.row.style.display = 'flex';
240
+ let data;
241
+ if (usingPoint && logical) {
242
+ data = e.series.dataByIndex(logical);
243
+ }
244
+ else {
245
+ data = param.seriesData.get(e.series);
246
+ }
247
+ if (!data?.value)
248
+ return;
249
+ let price;
250
+ if (e.series.seriesType() == 'Histogram') {
251
+ price = this.shorthandFormat(data.value);
252
+ }
253
+ else {
254
+ const format = e.series.options().priceFormat;
255
+ price = this.legendItemFormat(data.value, format.precision); // couldn't this just be line.options().precision?
256
+ }
257
+ e.div.innerHTML = `<span style="color: ${e.solid};">▨</span> ${e.name} : ${price}`;
258
+ });
259
+ }
260
+ }
261
+
262
+ function ensureDefined(value) {
263
+ if (value === undefined) {
264
+ throw new Error('Value is undefined');
265
+ }
266
+ return value;
267
+ }
268
+
269
+ //* PluginBase is a useful base to build a plugin upon which
270
+ //* already handles creating getters for the chart and series,
271
+ //* and provides a requestUpdate method.
272
+ class PluginBase {
273
+ _chart = undefined;
274
+ _series = undefined;
275
+ requestUpdate() {
276
+ if (this._requestUpdate)
277
+ this._requestUpdate();
278
+ }
279
+ _requestUpdate;
280
+ attached({ chart, series, requestUpdate, }) {
281
+ this._chart = chart;
282
+ this._series = series;
283
+ this._series.subscribeDataChanged(this._fireDataUpdated);
284
+ this._requestUpdate = requestUpdate;
285
+ this.requestUpdate();
286
+ }
287
+ detached() {
288
+ this._chart = undefined;
289
+ this._series = undefined;
290
+ this._requestUpdate = undefined;
291
+ }
292
+ get chart() {
293
+ return ensureDefined(this._chart);
294
+ }
295
+ get series() {
296
+ return ensureDefined(this._series);
297
+ }
298
+ _fireDataUpdated(scope) {
299
+ if (this.dataUpdated) {
300
+ this.dataUpdated(scope);
301
+ }
302
+ }
303
+ }
304
+
305
+ const defaultOptions = {
306
+ lineColor: '#1E80F0',
307
+ lineStyle: lightweightCharts.LineStyle.Solid,
308
+ width: 4,
309
+ };
310
+
311
+ var InteractionState;
312
+ (function (InteractionState) {
313
+ InteractionState[InteractionState["NONE"] = 0] = "NONE";
314
+ InteractionState[InteractionState["HOVERING"] = 1] = "HOVERING";
315
+ InteractionState[InteractionState["DRAGGING"] = 2] = "DRAGGING";
316
+ InteractionState[InteractionState["DRAGGINGP1"] = 3] = "DRAGGINGP1";
317
+ InteractionState[InteractionState["DRAGGINGP2"] = 4] = "DRAGGINGP2";
318
+ InteractionState[InteractionState["DRAGGINGP3"] = 5] = "DRAGGINGP3";
319
+ InteractionState[InteractionState["DRAGGINGP4"] = 6] = "DRAGGINGP4";
320
+ })(InteractionState || (InteractionState = {}));
321
+ class Drawing extends PluginBase {
322
+ _paneViews = [];
323
+ _options;
324
+ _points = [];
325
+ _state = InteractionState.NONE;
326
+ _startDragPoint = null;
327
+ _latestHoverPoint = null;
328
+ static _mouseIsDown = false;
329
+ static hoveredObject = null;
330
+ static lastHoveredObject = null;
331
+ _listeners = [];
332
+ constructor(options) {
333
+ super();
334
+ this._options = {
335
+ ...defaultOptions,
336
+ ...options,
337
+ };
338
+ }
339
+ updateAllViews() {
340
+ this._paneViews.forEach(pw => pw.update());
341
+ }
342
+ paneViews() {
343
+ return this._paneViews;
344
+ }
345
+ applyOptions(options) {
346
+ this._options = {
347
+ ...this._options,
348
+ ...options,
349
+ };
350
+ this.requestUpdate();
351
+ }
352
+ updatePoints(...points) {
353
+ for (let i = 0; i < this.points.length; i++) {
354
+ if (points[i] == null)
355
+ continue;
356
+ this.points[i] = points[i];
357
+ }
358
+ this.requestUpdate();
359
+ }
360
+ detach() {
361
+ this._options.lineColor = 'transparent';
362
+ this.requestUpdate();
363
+ this.series.detachPrimitive(this);
364
+ for (const s of this._listeners) {
365
+ document.body.removeEventListener(s.name, s.listener);
366
+ }
367
+ }
368
+ get points() {
369
+ return this._points;
370
+ }
371
+ _subscribe(name, listener) {
372
+ document.body.addEventListener(name, listener);
373
+ this._listeners.push({ name: name, listener: listener });
374
+ }
375
+ _unsubscribe(name, callback) {
376
+ document.body.removeEventListener(name, callback);
377
+ const toRemove = this._listeners.find((x) => x.name === name && x.listener === callback);
378
+ this._listeners.splice(this._listeners.indexOf(toRemove), 1);
379
+ }
380
+ _handleHoverInteraction(param) {
381
+ this._latestHoverPoint = param.point;
382
+ if (Drawing._mouseIsDown) {
383
+ this._handleDragInteraction(param);
384
+ }
385
+ else {
386
+ if (this._mouseIsOverDrawing(param)) {
387
+ if (this._state != InteractionState.NONE)
388
+ return;
389
+ this._moveToState(InteractionState.HOVERING);
390
+ Drawing.hoveredObject = Drawing.lastHoveredObject = this;
391
+ }
392
+ else {
393
+ if (this._state == InteractionState.NONE)
394
+ return;
395
+ this._moveToState(InteractionState.NONE);
396
+ if (Drawing.hoveredObject === this)
397
+ Drawing.hoveredObject = null;
398
+ }
399
+ }
400
+ }
401
+ static _eventToPoint(param, series) {
402
+ if (!series || !param.point || !param.logical)
403
+ return null;
404
+ const barPrice = series.coordinateToPrice(param.point.y);
405
+ if (barPrice == null)
406
+ return null;
407
+ return {
408
+ time: param.time || null,
409
+ logical: param.logical,
410
+ price: barPrice.valueOf(),
411
+ };
412
+ }
413
+ static _getDiff(p1, p2) {
414
+ const diff = {
415
+ logical: p1.logical - p2.logical,
416
+ price: p1.price - p2.price,
417
+ };
418
+ return diff;
419
+ }
420
+ _addDiffToPoint(point, logicalDiff, priceDiff) {
421
+ if (!point)
422
+ return;
423
+ point.logical = point.logical + logicalDiff;
424
+ point.price = point.price + priceDiff;
425
+ point.time = this.series.dataByIndex(point.logical)?.time || null;
426
+ }
427
+ _handleMouseDownInteraction = () => {
428
+ // if (Drawing._mouseIsDown) return;
429
+ Drawing._mouseIsDown = true;
430
+ this._onMouseDown();
431
+ };
432
+ _handleMouseUpInteraction = () => {
433
+ // if (!Drawing._mouseIsDown) return;
434
+ Drawing._mouseIsDown = false;
435
+ this._moveToState(InteractionState.HOVERING);
436
+ };
437
+ _handleDragInteraction(param) {
438
+ if (this._state != InteractionState.DRAGGING &&
439
+ this._state != InteractionState.DRAGGINGP1 &&
440
+ this._state != InteractionState.DRAGGINGP2 &&
441
+ this._state != InteractionState.DRAGGINGP3 &&
442
+ this._state != InteractionState.DRAGGINGP4) {
443
+ return;
444
+ }
445
+ const mousePoint = Drawing._eventToPoint(param, this.series);
446
+ if (!mousePoint)
447
+ return;
448
+ this._startDragPoint = this._startDragPoint || mousePoint;
449
+ const diff = Drawing._getDiff(mousePoint, this._startDragPoint);
450
+ this._onDrag(diff);
451
+ this.requestUpdate();
452
+ this._startDragPoint = mousePoint;
453
+ }
454
+ }
455
+
456
+ class DrawingPaneRenderer {
457
+ _options;
458
+ constructor(options) {
459
+ this._options = options;
460
+ }
461
+ }
462
+ class TwoPointDrawingPaneRenderer extends DrawingPaneRenderer {
463
+ _p1;
464
+ _p2;
465
+ _hovered;
466
+ constructor(p1, p2, options, hovered) {
467
+ super(options);
468
+ this._p1 = p1;
469
+ this._p2 = p2;
470
+ this._hovered = hovered;
471
+ }
472
+ _getScaledCoordinates(scope) {
473
+ if (this._p1.x === null || this._p1.y === null ||
474
+ this._p2.x === null || this._p2.y === null)
475
+ return null;
476
+ return {
477
+ x1: Math.round(this._p1.x * scope.horizontalPixelRatio),
478
+ y1: Math.round(this._p1.y * scope.verticalPixelRatio),
479
+ x2: Math.round(this._p2.x * scope.horizontalPixelRatio),
480
+ y2: Math.round(this._p2.y * scope.verticalPixelRatio),
481
+ };
482
+ }
483
+ // _drawTextLabel(scope: BitmapCoordinatesRenderingScope, text: string, x: number, y: number, left: boolean) {
484
+ // scope.context.font = '24px Arial';
485
+ // scope.context.beginPath();
486
+ // const offset = 5 * scope.horizontalPixelRatio;
487
+ // const textWidth = scope.context.measureText(text);
488
+ // const leftAdjustment = left ? textWidth.width + offset * 4 : 0;
489
+ // scope.context.fillStyle = this._options.labelBackgroundColor;
490
+ // scope.context.roundRect(x + offset - leftAdjustment, y - 24, textWidth.width + offset * 2, 24 + offset, 5);
491
+ // scope.context.fill();
492
+ // scope.context.beginPath();
493
+ // scope.context.fillStyle = this._options.labelTextColor;
494
+ // scope.context.fillText(text, x + offset * 2 - leftAdjustment, y);
495
+ // }
496
+ _drawEndCircle(scope, x, y) {
497
+ const radius = 9;
498
+ scope.context.fillStyle = '#000';
499
+ scope.context.beginPath();
500
+ scope.context.arc(x, y, radius, 0, 2 * Math.PI);
501
+ scope.context.stroke();
502
+ scope.context.fill();
503
+ // scope.context.strokeStyle = this._options.lineColor;
504
+ }
505
+ }
506
+
507
+ function setLineStyle(ctx, style) {
508
+ const dashPatterns = {
509
+ [lightweightCharts.LineStyle.Solid]: [],
510
+ [lightweightCharts.LineStyle.Dotted]: [ctx.lineWidth, ctx.lineWidth],
511
+ [lightweightCharts.LineStyle.Dashed]: [2 * ctx.lineWidth, 2 * ctx.lineWidth],
512
+ [lightweightCharts.LineStyle.LargeDashed]: [6 * ctx.lineWidth, 6 * ctx.lineWidth],
513
+ [lightweightCharts.LineStyle.SparseDotted]: [ctx.lineWidth, 4 * ctx.lineWidth],
514
+ };
515
+ const dashPattern = dashPatterns[style];
516
+ ctx.setLineDash(dashPattern);
517
+ }
518
+
519
+ class HorizontalLinePaneRenderer extends DrawingPaneRenderer {
520
+ _point = { x: null, y: null };
521
+ constructor(point, options) {
522
+ super(options);
523
+ this._point = point;
524
+ }
525
+ draw(target) {
526
+ target.useBitmapCoordinateSpace(scope => {
527
+ if (this._point.y == null)
528
+ return;
529
+ const ctx = scope.context;
530
+ const scaledY = Math.round(this._point.y * scope.verticalPixelRatio);
531
+ const scaledX = this._point.x ? this._point.x * scope.horizontalPixelRatio : 0;
532
+ ctx.lineWidth = this._options.width;
533
+ ctx.strokeStyle = this._options.lineColor;
534
+ setLineStyle(ctx, this._options.lineStyle);
535
+ ctx.beginPath();
536
+ ctx.moveTo(scaledX, scaledY);
537
+ ctx.lineTo(scope.bitmapSize.width, scaledY);
538
+ ctx.stroke();
539
+ });
540
+ }
541
+ }
542
+
543
+ class DrawingPaneView {
544
+ _source;
545
+ constructor(source) {
546
+ this._source = source;
547
+ }
548
+ }
549
+ class TwoPointDrawingPaneView extends DrawingPaneView {
550
+ _p1 = { x: null, y: null };
551
+ _p2 = { x: null, y: null };
552
+ _source;
553
+ constructor(source) {
554
+ super(source);
555
+ this._source = source;
556
+ }
557
+ update() {
558
+ if (!this._source.p1 || !this._source.p2)
559
+ return;
560
+ const series = this._source.series;
561
+ const y1 = series.priceToCoordinate(this._source.p1.price);
562
+ const y2 = series.priceToCoordinate(this._source.p2.price);
563
+ const x1 = this._getX(this._source.p1);
564
+ const x2 = this._getX(this._source.p2);
565
+ this._p1 = { x: x1, y: y1 };
566
+ this._p2 = { x: x2, y: y2 };
567
+ if (!x1 || !x2 || !y1 || !y2)
568
+ return;
569
+ }
570
+ _getX(p) {
571
+ const timeScale = this._source.chart.timeScale();
572
+ return timeScale.logicalToCoordinate(p.logical);
573
+ }
574
+ }
575
+
576
+ class HorizontalLinePaneView extends DrawingPaneView {
577
+ _source;
578
+ _point = { x: null, y: null };
579
+ constructor(source) {
580
+ super(source);
581
+ this._source = source;
582
+ }
583
+ update() {
584
+ const point = this._source._point;
585
+ const timeScale = this._source.chart.timeScale();
586
+ const series = this._source.series;
587
+ if (this._source._type == "RayLine") {
588
+ this._point.x = point.time ? timeScale.timeToCoordinate(point.time) : timeScale.logicalToCoordinate(point.logical);
589
+ }
590
+ this._point.y = series.priceToCoordinate(point.price);
591
+ }
592
+ renderer() {
593
+ return new HorizontalLinePaneRenderer(this._point, this._source._options);
594
+ }
595
+ }
596
+
597
+ class HorizontalLineAxisView {
598
+ _source;
599
+ _y = null;
600
+ _price = null;
601
+ constructor(source) {
602
+ this._source = source;
603
+ }
604
+ update() {
605
+ if (!this._source.series || !this._source._point)
606
+ return;
607
+ this._y = this._source.series.priceToCoordinate(this._source._point.price);
608
+ const priceFormat = this._source.series.options().priceFormat;
609
+ const precision = priceFormat.precision;
610
+ this._price = this._source._point.price.toFixed(precision).toString();
611
+ }
612
+ visible() {
613
+ return true;
614
+ }
615
+ tickVisible() {
616
+ return true;
617
+ }
618
+ coordinate() {
619
+ return this._y ?? 0;
620
+ }
621
+ text() {
622
+ return this._price || '';
623
+ }
624
+ textColor() {
625
+ return 'white';
626
+ }
627
+ backColor() {
628
+ return this._source._options.lineColor;
629
+ }
630
+ }
631
+
632
+ class HorizontalLine extends Drawing {
633
+ _type = 'HorizontalLine';
634
+ _paneViews;
635
+ _point;
636
+ _callbackName;
637
+ _priceAxisViews;
638
+ _startDragPoint = null;
639
+ constructor(point, options, callbackName = null) {
640
+ super(options);
641
+ this._point = point;
642
+ this._point.time = null; // time is null for horizontal lines
643
+ this._paneViews = [new HorizontalLinePaneView(this)];
644
+ this._priceAxisViews = [new HorizontalLineAxisView(this)];
645
+ this._callbackName = callbackName;
646
+ }
647
+ get points() {
648
+ return [this._point];
649
+ }
650
+ updatePoints(...points) {
651
+ for (const p of points)
652
+ if (p)
653
+ this._point.price = p.price;
654
+ this.requestUpdate();
655
+ }
656
+ updateAllViews() {
657
+ this._paneViews.forEach((pw) => pw.update());
658
+ this._priceAxisViews.forEach((tw) => tw.update());
659
+ }
660
+ priceAxisViews() {
661
+ return this._priceAxisViews;
662
+ }
663
+ _moveToState(state) {
664
+ switch (state) {
665
+ case InteractionState.NONE:
666
+ document.body.style.cursor = "default";
667
+ this._unsubscribe("mousedown", this._handleMouseDownInteraction);
668
+ break;
669
+ case InteractionState.HOVERING:
670
+ document.body.style.cursor = "pointer";
671
+ this._unsubscribe("mouseup", this._childHandleMouseUpInteraction);
672
+ this._subscribe("mousedown", this._handleMouseDownInteraction);
673
+ this.chart.applyOptions({ handleScroll: true });
674
+ break;
675
+ case InteractionState.DRAGGING:
676
+ document.body.style.cursor = "grabbing";
677
+ this._subscribe("mouseup", this._childHandleMouseUpInteraction);
678
+ this.chart.applyOptions({ handleScroll: false });
679
+ break;
680
+ }
681
+ this._state = state;
682
+ }
683
+ _onDrag(diff) {
684
+ this._addDiffToPoint(this._point, 0, diff.price);
685
+ this.requestUpdate();
686
+ }
687
+ _mouseIsOverDrawing(param, tolerance = 4) {
688
+ if (!param.point)
689
+ return false;
690
+ const y = this.series.priceToCoordinate(this._point.price);
691
+ if (!y)
692
+ return false;
693
+ return (Math.abs(y - param.point.y) < tolerance);
694
+ }
695
+ _onMouseDown() {
696
+ this._startDragPoint = null;
697
+ const hoverPoint = this._latestHoverPoint;
698
+ if (!hoverPoint)
699
+ return;
700
+ return this._moveToState(InteractionState.DRAGGING);
701
+ }
702
+ _childHandleMouseUpInteraction = () => {
703
+ this._handleMouseUpInteraction();
704
+ if (!this._callbackName)
705
+ return;
706
+ window.callbackFunction(`${this._callbackName}_~_${this._point.price.toFixed(8)}`);
707
+ };
708
+ }
709
+
710
+ class DrawingTool {
711
+ _chart;
712
+ _series;
713
+ _finishDrawingCallback = null;
714
+ _drawings = [];
715
+ _activeDrawing = null;
716
+ _isDrawing = false;
717
+ _drawingType = null;
718
+ constructor(chart, series, finishDrawingCallback = null) {
719
+ this._chart = chart;
720
+ this._series = series;
721
+ this._finishDrawingCallback = finishDrawingCallback;
722
+ this._chart.subscribeClick(this._clickHandler);
723
+ this._chart.subscribeCrosshairMove(this._moveHandler);
724
+ }
725
+ _clickHandler = (param) => this._onClick(param);
726
+ _moveHandler = (param) => this._onMouseMove(param);
727
+ beginDrawing(DrawingType) {
728
+ this._drawingType = DrawingType;
729
+ this._isDrawing = true;
730
+ }
731
+ stopDrawing() {
732
+ this._isDrawing = false;
733
+ this._activeDrawing = null;
734
+ }
735
+ get drawings() {
736
+ return this._drawings;
737
+ }
738
+ addNewDrawing(drawing) {
739
+ this._series.attachPrimitive(drawing);
740
+ this._drawings.push(drawing);
741
+ }
742
+ delete(d) {
743
+ if (d == null)
744
+ return;
745
+ const idx = this._drawings.indexOf(d);
746
+ if (idx == -1)
747
+ return;
748
+ this._drawings.splice(idx, 1);
749
+ d.detach();
750
+ }
751
+ clearDrawings() {
752
+ for (const d of this._drawings)
753
+ d.detach();
754
+ this._drawings = [];
755
+ }
756
+ repositionOnTime() {
757
+ for (const drawing of this.drawings) {
758
+ const newPoints = [];
759
+ for (const point of drawing.points) {
760
+ if (!point) {
761
+ newPoints.push(point);
762
+ continue;
763
+ }
764
+ const logical = point.time ? this._chart.timeScale()
765
+ .coordinateToLogical(this._chart.timeScale().timeToCoordinate(point.time) || 0) : point.logical;
766
+ newPoints.push({
767
+ time: point.time,
768
+ logical: logical,
769
+ price: point.price,
770
+ });
771
+ }
772
+ drawing.updatePoints(...newPoints);
773
+ }
774
+ }
775
+ _onClick(param) {
776
+ if (!this._isDrawing)
777
+ return;
778
+ const point = Drawing._eventToPoint(param, this._series);
779
+ if (!point)
780
+ return;
781
+ if (this._activeDrawing == null) {
782
+ if (this._drawingType == null)
783
+ return;
784
+ this._activeDrawing = new this._drawingType(point, point);
785
+ this._series.attachPrimitive(this._activeDrawing);
786
+ if (this._drawingType == HorizontalLine)
787
+ this._onClick(param);
788
+ }
789
+ else {
790
+ this._drawings.push(this._activeDrawing);
791
+ this.stopDrawing();
792
+ if (!this._finishDrawingCallback)
793
+ return;
794
+ this._finishDrawingCallback();
795
+ }
796
+ }
797
+ _onMouseMove(param) {
798
+ if (!param)
799
+ return;
800
+ for (const t of this._drawings)
801
+ t._handleHoverInteraction(param);
802
+ if (!this._isDrawing || !this._activeDrawing)
803
+ return;
804
+ const point = Drawing._eventToPoint(param, this._series);
805
+ if (!point)
806
+ return;
807
+ this._activeDrawing.updatePoints(null, point);
808
+ // this._activeDrawing.setSecondPoint(point);
809
+ }
810
+ }
811
+
812
+ class TrendLinePaneRenderer extends TwoPointDrawingPaneRenderer {
813
+ constructor(p1, p2, options, hovered) {
814
+ super(p1, p2, options, hovered);
815
+ }
816
+ draw(target) {
817
+ target.useBitmapCoordinateSpace(scope => {
818
+ if (this._p1.x === null ||
819
+ this._p1.y === null ||
820
+ this._p2.x === null ||
821
+ this._p2.y === null)
822
+ return;
823
+ const ctx = scope.context;
824
+ const scaled = this._getScaledCoordinates(scope);
825
+ if (!scaled)
826
+ return;
827
+ ctx.lineWidth = this._options.width;
828
+ ctx.strokeStyle = this._options.lineColor;
829
+ setLineStyle(ctx, this._options.lineStyle);
830
+ ctx.beginPath();
831
+ ctx.moveTo(scaled.x1, scaled.y1);
832
+ ctx.lineTo(scaled.x2, scaled.y2);
833
+ ctx.stroke();
834
+ // this._drawTextLabel(scope, this._text1, x1Scaled, y1Scaled, true);
835
+ // this._drawTextLabel(scope, this._text2, x2Scaled, y2Scaled, false);
836
+ if (!this._hovered)
837
+ return;
838
+ this._drawEndCircle(scope, scaled.x1, scaled.y1);
839
+ this._drawEndCircle(scope, scaled.x2, scaled.y2);
840
+ });
841
+ }
842
+ }
843
+
844
+ class TrendLinePaneView extends TwoPointDrawingPaneView {
845
+ constructor(source) {
846
+ super(source);
847
+ }
848
+ renderer() {
849
+ return new TrendLinePaneRenderer(this._p1, this._p2, this._source._options, this._source.hovered);
850
+ }
851
+ }
852
+
853
+ class TwoPointDrawing extends Drawing {
854
+ _paneViews = [];
855
+ _hovered = false;
856
+ constructor(p1, p2, options) {
857
+ super();
858
+ this.points.push(p1);
859
+ this.points.push(p2);
860
+ this._options = {
861
+ ...defaultOptions,
862
+ ...options,
863
+ };
864
+ }
865
+ setFirstPoint(point) {
866
+ this.updatePoints(point);
867
+ }
868
+ setSecondPoint(point) {
869
+ this.updatePoints(null, point);
870
+ }
871
+ get p1() { return this.points[0]; }
872
+ get p2() { return this.points[1]; }
873
+ get hovered() { return this._hovered; }
874
+ }
875
+
876
+ class TrendLine extends TwoPointDrawing {
877
+ _type = "TrendLine";
878
+ constructor(p1, p2, options) {
879
+ super(p1, p2, options);
880
+ this._paneViews = [new TrendLinePaneView(this)];
881
+ }
882
+ _moveToState(state) {
883
+ switch (state) {
884
+ case InteractionState.NONE:
885
+ document.body.style.cursor = "default";
886
+ this._hovered = false;
887
+ this.requestUpdate();
888
+ this._unsubscribe("mousedown", this._handleMouseDownInteraction);
889
+ break;
890
+ case InteractionState.HOVERING:
891
+ document.body.style.cursor = "pointer";
892
+ this._hovered = true;
893
+ this.requestUpdate();
894
+ this._subscribe("mousedown", this._handleMouseDownInteraction);
895
+ this._unsubscribe("mouseup", this._handleMouseDownInteraction);
896
+ this.chart.applyOptions({ handleScroll: true });
897
+ break;
898
+ case InteractionState.DRAGGINGP1:
899
+ case InteractionState.DRAGGINGP2:
900
+ case InteractionState.DRAGGING:
901
+ document.body.style.cursor = "grabbing";
902
+ this._subscribe("mouseup", this._handleMouseUpInteraction);
903
+ this.chart.applyOptions({ handleScroll: false });
904
+ break;
905
+ }
906
+ this._state = state;
907
+ }
908
+ _onDrag(diff) {
909
+ if (this._state == InteractionState.DRAGGING || this._state == InteractionState.DRAGGINGP1) {
910
+ this._addDiffToPoint(this.p1, diff.logical, diff.price);
911
+ }
912
+ if (this._state == InteractionState.DRAGGING || this._state == InteractionState.DRAGGINGP2) {
913
+ this._addDiffToPoint(this.p2, diff.logical, diff.price);
914
+ }
915
+ }
916
+ _onMouseDown() {
917
+ this._startDragPoint = null;
918
+ const hoverPoint = this._latestHoverPoint;
919
+ if (!hoverPoint)
920
+ return;
921
+ const p1 = this._paneViews[0]._p1;
922
+ const p2 = this._paneViews[0]._p2;
923
+ if (!p1.x || !p2.x || !p1.y || !p2.y)
924
+ return this._moveToState(InteractionState.DRAGGING);
925
+ const tolerance = 10;
926
+ if (Math.abs(hoverPoint.x - p1.x) < tolerance && Math.abs(hoverPoint.y - p1.y) < tolerance) {
927
+ this._moveToState(InteractionState.DRAGGINGP1);
928
+ }
929
+ else if (Math.abs(hoverPoint.x - p2.x) < tolerance && Math.abs(hoverPoint.y - p2.y) < tolerance) {
930
+ this._moveToState(InteractionState.DRAGGINGP2);
931
+ }
932
+ else {
933
+ this._moveToState(InteractionState.DRAGGING);
934
+ }
935
+ }
936
+ _mouseIsOverDrawing(param, tolerance = 4) {
937
+ if (!param.point)
938
+ return false;
939
+ const x1 = this._paneViews[0]._p1.x;
940
+ const y1 = this._paneViews[0]._p1.y;
941
+ const x2 = this._paneViews[0]._p2.x;
942
+ const y2 = this._paneViews[0]._p2.y;
943
+ if (!x1 || !x2 || !y1 || !y2)
944
+ return false;
945
+ const mouseX = param.point.x;
946
+ const mouseY = param.point.y;
947
+ if (mouseX <= Math.min(x1, x2) - tolerance ||
948
+ mouseX >= Math.max(x1, x2) + tolerance) {
949
+ return false;
950
+ }
951
+ const distance = Math.abs((y2 - y1) * mouseX - (x2 - x1) * mouseY + x2 * y1 - y2 * x1) / Math.sqrt((y2 - y1) ** 2 + (x2 - x1) ** 2);
952
+ return distance <= tolerance;
953
+ }
954
+ }
955
+
956
+ class BoxPaneRenderer extends TwoPointDrawingPaneRenderer {
957
+ constructor(p1, p2, options, showCircles) {
958
+ super(p1, p2, options, showCircles);
959
+ }
960
+ draw(target) {
961
+ target.useBitmapCoordinateSpace(scope => {
962
+ const ctx = scope.context;
963
+ const scaled = this._getScaledCoordinates(scope);
964
+ if (!scaled)
965
+ return;
966
+ ctx.lineWidth = this._options.width;
967
+ ctx.strokeStyle = this._options.lineColor;
968
+ setLineStyle(ctx, this._options.lineStyle);
969
+ ctx.fillStyle = this._options.fillColor;
970
+ const mainX = Math.min(scaled.x1, scaled.x2);
971
+ const mainY = Math.min(scaled.y1, scaled.y2);
972
+ const width = Math.abs(scaled.x1 - scaled.x2);
973
+ const height = Math.abs(scaled.y1 - scaled.y2);
974
+ ctx.strokeRect(mainX, mainY, width, height);
975
+ ctx.fillRect(mainX, mainY, width, height);
976
+ if (!this._hovered)
977
+ return;
978
+ this._drawEndCircle(scope, mainX, mainY);
979
+ this._drawEndCircle(scope, mainX + width, mainY);
980
+ this._drawEndCircle(scope, mainX + width, mainY + height);
981
+ this._drawEndCircle(scope, mainX, mainY + height);
982
+ });
983
+ }
984
+ }
985
+
986
+ class BoxPaneView extends TwoPointDrawingPaneView {
987
+ constructor(source) {
988
+ super(source);
989
+ }
990
+ renderer() {
991
+ return new BoxPaneRenderer(this._p1, this._p2, this._source._options, this._source.hovered);
992
+ }
993
+ }
994
+
995
+ const defaultBoxOptions = {
996
+ fillEnabled: true,
997
+ fillColor: 'rgba(255, 255, 255, 0.2)',
998
+ ...defaultOptions
999
+ };
1000
+ class Box extends TwoPointDrawing {
1001
+ _type = "Box";
1002
+ constructor(p1, p2, options) {
1003
+ super(p1, p2, options);
1004
+ this._options = {
1005
+ ...defaultBoxOptions,
1006
+ ...options,
1007
+ };
1008
+ this._paneViews = [new BoxPaneView(this)];
1009
+ }
1010
+ // autoscaleInfo(startTimePoint: Logical, endTimePoint: Logical): AutoscaleInfo | null {
1011
+ // const p1Index = this._pointIndex(this._p1);
1012
+ // const p2Index = this._pointIndex(this._p2);
1013
+ // if (p1Index === null || p2Index === null) return null;
1014
+ // if (endTimePoint < p1Index || startTimePoint > p2Index) return null;
1015
+ // return {
1016
+ // priceRange: {
1017
+ // minValue: this._minPrice,
1018
+ // maxValue: this._maxPrice,
1019
+ // },
1020
+ // };
1021
+ // }
1022
+ _moveToState(state) {
1023
+ switch (state) {
1024
+ case InteractionState.NONE:
1025
+ document.body.style.cursor = "default";
1026
+ this._hovered = false;
1027
+ this._unsubscribe("mousedown", this._handleMouseDownInteraction);
1028
+ break;
1029
+ case InteractionState.HOVERING:
1030
+ document.body.style.cursor = "pointer";
1031
+ this._hovered = true;
1032
+ this._unsubscribe("mouseup", this._handleMouseUpInteraction);
1033
+ this._subscribe("mousedown", this._handleMouseDownInteraction);
1034
+ this.chart.applyOptions({ handleScroll: true });
1035
+ break;
1036
+ case InteractionState.DRAGGINGP1:
1037
+ case InteractionState.DRAGGINGP2:
1038
+ case InteractionState.DRAGGINGP3:
1039
+ case InteractionState.DRAGGINGP4:
1040
+ case InteractionState.DRAGGING:
1041
+ document.body.style.cursor = "grabbing";
1042
+ document.body.addEventListener("mouseup", this._handleMouseUpInteraction);
1043
+ this._subscribe("mouseup", this._handleMouseUpInteraction);
1044
+ this.chart.applyOptions({ handleScroll: false });
1045
+ break;
1046
+ }
1047
+ this._state = state;
1048
+ }
1049
+ _onDrag(diff) {
1050
+ if (this._state == InteractionState.DRAGGING || this._state == InteractionState.DRAGGINGP1) {
1051
+ this._addDiffToPoint(this.p1, diff.logical, diff.price);
1052
+ }
1053
+ if (this._state == InteractionState.DRAGGING || this._state == InteractionState.DRAGGINGP2) {
1054
+ this._addDiffToPoint(this.p2, diff.logical, diff.price);
1055
+ }
1056
+ if (this._state != InteractionState.DRAGGING) {
1057
+ if (this._state == InteractionState.DRAGGINGP3) {
1058
+ this._addDiffToPoint(this.p1, diff.logical, 0);
1059
+ this._addDiffToPoint(this.p2, 0, diff.price);
1060
+ }
1061
+ if (this._state == InteractionState.DRAGGINGP4) {
1062
+ this._addDiffToPoint(this.p1, 0, diff.price);
1063
+ this._addDiffToPoint(this.p2, diff.logical, 0);
1064
+ }
1065
+ }
1066
+ }
1067
+ _onMouseDown() {
1068
+ this._startDragPoint = null;
1069
+ const hoverPoint = this._latestHoverPoint;
1070
+ const p1 = this._paneViews[0]._p1;
1071
+ const p2 = this._paneViews[0]._p2;
1072
+ if (!p1.x || !p2.x || !p1.y || !p2.y)
1073
+ return this._moveToState(InteractionState.DRAGGING);
1074
+ const tolerance = 10;
1075
+ if (Math.abs(hoverPoint.x - p1.x) < tolerance && Math.abs(hoverPoint.y - p1.y) < tolerance) {
1076
+ this._moveToState(InteractionState.DRAGGINGP1);
1077
+ }
1078
+ else if (Math.abs(hoverPoint.x - p2.x) < tolerance && Math.abs(hoverPoint.y - p2.y) < tolerance) {
1079
+ this._moveToState(InteractionState.DRAGGINGP2);
1080
+ }
1081
+ else if (Math.abs(hoverPoint.x - p1.x) < tolerance && Math.abs(hoverPoint.y - p2.y) < tolerance) {
1082
+ this._moveToState(InteractionState.DRAGGINGP3);
1083
+ }
1084
+ else if (Math.abs(hoverPoint.x - p2.x) < tolerance && Math.abs(hoverPoint.y - p1.y) < tolerance) {
1085
+ this._moveToState(InteractionState.DRAGGINGP4);
1086
+ }
1087
+ else {
1088
+ this._moveToState(InteractionState.DRAGGING);
1089
+ }
1090
+ }
1091
+ _mouseIsOverDrawing(param, tolerance = 4) {
1092
+ if (!param.point)
1093
+ return false;
1094
+ const x1 = this._paneViews[0]._p1.x;
1095
+ const y1 = this._paneViews[0]._p1.y;
1096
+ const x2 = this._paneViews[0]._p2.x;
1097
+ const y2 = this._paneViews[0]._p2.y;
1098
+ if (!x1 || !x2 || !y1 || !y2)
1099
+ return false;
1100
+ const mouseX = param.point.x;
1101
+ const mouseY = param.point.y;
1102
+ const mainX = Math.min(x1, x2);
1103
+ const mainY = Math.min(y1, y2);
1104
+ const width = Math.abs(x1 - x2);
1105
+ const height = Math.abs(y1 - y2);
1106
+ const halfTolerance = tolerance / 2;
1107
+ return mouseX > mainX - halfTolerance && mouseX < mainX + width + halfTolerance &&
1108
+ mouseY > mainY - halfTolerance && mouseY < mainY + height + halfTolerance;
1109
+ }
1110
+ }
1111
+
1112
+ class ColorPicker {
1113
+ colorOption;
1114
+ static colors = [
1115
+ '#EBB0B0', '#E9CEA1', '#E5DF80', '#ADEB97', '#A3C3EA', '#D8BDED',
1116
+ '#E15F5D', '#E1B45F', '#E2D947', '#4BE940', '#639AE1', '#D7A0E8',
1117
+ '#E42C2A', '#E49D30', '#E7D827', '#3CFF0A', '#3275E4', '#B06CE3',
1118
+ '#F3000D', '#EE9A14', '#F1DA13', '#2DFC0F', '#1562EE', '#BB00EF',
1119
+ '#B50911', '#E3860E', '#D2BD11', '#48DE0E', '#1455B4', '#6E009F',
1120
+ '#7C1713', '#B76B12', '#8D7A13', '#479C12', '#165579', '#51007E',
1121
+ ];
1122
+ _div;
1123
+ saveDrawings;
1124
+ opacity = 0;
1125
+ _opacitySlider;
1126
+ _opacityLabel;
1127
+ rgba;
1128
+ constructor(saveDrawings, colorOption) {
1129
+ this.colorOption = colorOption;
1130
+ this.saveDrawings = saveDrawings;
1131
+ this._div = document.createElement('div');
1132
+ this._div.classList.add('color-picker');
1133
+ let colorPicker = document.createElement('div');
1134
+ colorPicker.style.margin = '10px';
1135
+ colorPicker.style.display = 'flex';
1136
+ colorPicker.style.flexWrap = 'wrap';
1137
+ ColorPicker.colors.forEach((color) => colorPicker.appendChild(this.makeColorBox(color)));
1138
+ let separator = document.createElement('div');
1139
+ separator.style.backgroundColor = window.pane.borderColor;
1140
+ separator.style.height = '1px';
1141
+ separator.style.width = '130px';
1142
+ let opacity = document.createElement('div');
1143
+ opacity.style.margin = '10px';
1144
+ let opacityText = document.createElement('div');
1145
+ opacityText.style.color = 'lightgray';
1146
+ opacityText.style.fontSize = '12px';
1147
+ opacityText.innerText = 'Opacity';
1148
+ this._opacityLabel = document.createElement('div');
1149
+ this._opacityLabel.style.color = 'lightgray';
1150
+ this._opacityLabel.style.fontSize = '12px';
1151
+ this._opacitySlider = document.createElement('input');
1152
+ this._opacitySlider.type = 'range';
1153
+ this._opacitySlider.value = (this.opacity * 100).toString();
1154
+ this._opacityLabel.innerText = this._opacitySlider.value + '%';
1155
+ this._opacitySlider.oninput = () => {
1156
+ this._opacityLabel.innerText = this._opacitySlider.value + '%';
1157
+ this.opacity = parseInt(this._opacitySlider.value) / 100;
1158
+ this.updateColor();
1159
+ };
1160
+ opacity.appendChild(opacityText);
1161
+ opacity.appendChild(this._opacitySlider);
1162
+ opacity.appendChild(this._opacityLabel);
1163
+ this._div.appendChild(colorPicker);
1164
+ this._div.appendChild(separator);
1165
+ this._div.appendChild(opacity);
1166
+ window.containerDiv.appendChild(this._div);
1167
+ }
1168
+ _updateOpacitySlider() {
1169
+ this._opacitySlider.value = (this.opacity * 100).toString();
1170
+ this._opacityLabel.innerText = this._opacitySlider.value + '%';
1171
+ }
1172
+ makeColorBox(color) {
1173
+ const box = document.createElement('div');
1174
+ box.style.width = '18px';
1175
+ box.style.height = '18px';
1176
+ box.style.borderRadius = '3px';
1177
+ box.style.margin = '3px';
1178
+ box.style.boxSizing = 'border-box';
1179
+ box.style.backgroundColor = color;
1180
+ box.addEventListener('mouseover', () => box.style.border = '2px solid lightgray');
1181
+ box.addEventListener('mouseout', () => box.style.border = 'none');
1182
+ const rgba = ColorPicker.extractRGBA(color);
1183
+ box.addEventListener('click', () => {
1184
+ this.rgba = rgba;
1185
+ this.updateColor();
1186
+ });
1187
+ return box;
1188
+ }
1189
+ static extractRGBA(anyColor) {
1190
+ const dummyElem = document.createElement('div');
1191
+ dummyElem.style.color = anyColor;
1192
+ document.body.appendChild(dummyElem);
1193
+ const computedColor = getComputedStyle(dummyElem).color;
1194
+ document.body.removeChild(dummyElem);
1195
+ const rgb = computedColor.match(/\d+/g)?.map(Number);
1196
+ if (!rgb)
1197
+ return [];
1198
+ let isRgba = computedColor.includes('rgba');
1199
+ let opacity = isRgba ? parseFloat(computedColor.split(',')[3]) : 1;
1200
+ return [rgb[0], rgb[1], rgb[2], opacity];
1201
+ }
1202
+ updateColor() {
1203
+ if (!Drawing.lastHoveredObject || !this.rgba)
1204
+ return;
1205
+ const oColor = `rgba(${this.rgba[0]}, ${this.rgba[1]}, ${this.rgba[2]}, ${this.opacity})`;
1206
+ Drawing.lastHoveredObject.applyOptions({ [this.colorOption]: oColor });
1207
+ this.saveDrawings();
1208
+ }
1209
+ openMenu(rect) {
1210
+ if (!Drawing.lastHoveredObject)
1211
+ return;
1212
+ this.rgba = ColorPicker.extractRGBA(Drawing.lastHoveredObject._options[this.colorOption]);
1213
+ this.opacity = this.rgba[3];
1214
+ this._updateOpacitySlider();
1215
+ this._div.style.top = (rect.top - 30) + 'px';
1216
+ this._div.style.left = rect.right + 'px';
1217
+ this._div.style.display = 'flex';
1218
+ setTimeout(() => document.addEventListener('mousedown', (event) => {
1219
+ if (!this._div.contains(event.target)) {
1220
+ this.closeMenu();
1221
+ }
1222
+ }), 10);
1223
+ }
1224
+ closeMenu() {
1225
+ document.body.removeEventListener('click', this.closeMenu);
1226
+ this._div.style.display = 'none';
1227
+ }
1228
+ }
1229
+
1230
+ class StylePicker {
1231
+ static _styles = [
1232
+ { name: 'Solid', var: lightweightCharts.LineStyle.Solid },
1233
+ { name: 'Dotted', var: lightweightCharts.LineStyle.Dotted },
1234
+ { name: 'Dashed', var: lightweightCharts.LineStyle.Dashed },
1235
+ { name: 'Large Dashed', var: lightweightCharts.LineStyle.LargeDashed },
1236
+ { name: 'Sparse Dotted', var: lightweightCharts.LineStyle.SparseDotted },
1237
+ ];
1238
+ _div;
1239
+ _saveDrawings;
1240
+ constructor(saveDrawings) {
1241
+ this._saveDrawings = saveDrawings;
1242
+ this._div = document.createElement('div');
1243
+ this._div.classList.add('context-menu');
1244
+ StylePicker._styles.forEach((style) => {
1245
+ this._div.appendChild(this._makeTextBox(style.name, style.var));
1246
+ });
1247
+ window.containerDiv.appendChild(this._div);
1248
+ }
1249
+ _makeTextBox(text, style) {
1250
+ const item = document.createElement('span');
1251
+ item.classList.add('context-menu-item');
1252
+ item.innerText = text;
1253
+ item.addEventListener('click', () => {
1254
+ Drawing.lastHoveredObject?.applyOptions({ lineStyle: style });
1255
+ this._saveDrawings();
1256
+ });
1257
+ return item;
1258
+ }
1259
+ openMenu(rect) {
1260
+ this._div.style.top = (rect.top - 30) + 'px';
1261
+ this._div.style.left = rect.right + 'px';
1262
+ this._div.style.display = 'block';
1263
+ setTimeout(() => document.addEventListener('mousedown', (event) => {
1264
+ if (!this._div.contains(event.target)) {
1265
+ this.closeMenu();
1266
+ }
1267
+ }), 10);
1268
+ }
1269
+ closeMenu() {
1270
+ document.removeEventListener('click', this.closeMenu);
1271
+ this._div.style.display = 'none';
1272
+ }
1273
+ }
1274
+
1275
+ function camelToTitle(inputString) {
1276
+ const result = [];
1277
+ for (const c of inputString) {
1278
+ if (result.length == 0) {
1279
+ result.push(c.toUpperCase());
1280
+ }
1281
+ else if (c == c.toUpperCase()) {
1282
+ result.push(' ' + c);
1283
+ }
1284
+ else
1285
+ result.push(c);
1286
+ }
1287
+ return result.join('');
1288
+ }
1289
+ class ContextMenu {
1290
+ saveDrawings;
1291
+ drawingTool;
1292
+ div;
1293
+ hoverItem;
1294
+ items = [];
1295
+ constructor(saveDrawings, drawingTool) {
1296
+ this.saveDrawings = saveDrawings;
1297
+ this.drawingTool = drawingTool;
1298
+ this._onRightClick = this._onRightClick.bind(this);
1299
+ this.div = document.createElement('div');
1300
+ this.div.classList.add('context-menu');
1301
+ document.body.appendChild(this.div);
1302
+ this.hoverItem = null;
1303
+ document.body.addEventListener('contextmenu', this._onRightClick);
1304
+ }
1305
+ _handleClick = (ev) => this._onClick(ev);
1306
+ _onClick(ev) {
1307
+ if (!ev.target)
1308
+ return;
1309
+ if (!this.div.contains(ev.target)) {
1310
+ this.div.style.display = 'none';
1311
+ document.body.removeEventListener('click', this._handleClick);
1312
+ }
1313
+ }
1314
+ _onRightClick(ev) {
1315
+ if (!Drawing.hoveredObject)
1316
+ return;
1317
+ for (const item of this.items) {
1318
+ this.div.removeChild(item);
1319
+ }
1320
+ this.items = [];
1321
+ for (const optionName of Object.keys(Drawing.hoveredObject._options)) {
1322
+ let subMenu;
1323
+ if (optionName.toLowerCase().includes('color')) {
1324
+ subMenu = new ColorPicker(this.saveDrawings, optionName);
1325
+ }
1326
+ else if (optionName === 'lineStyle') {
1327
+ subMenu = new StylePicker(this.saveDrawings);
1328
+ }
1329
+ else
1330
+ continue;
1331
+ let onClick = (rect) => subMenu.openMenu(rect);
1332
+ this.menuItem(camelToTitle(optionName), onClick, () => {
1333
+ document.removeEventListener('click', subMenu.closeMenu);
1334
+ subMenu._div.style.display = 'none';
1335
+ });
1336
+ }
1337
+ let onClickDelete = () => this.drawingTool.delete(Drawing.lastHoveredObject);
1338
+ this.separator();
1339
+ this.menuItem('Delete Drawing', onClickDelete);
1340
+ // const colorPicker = new ColorPicker(this.saveDrawings)
1341
+ // const stylePicker = new StylePicker(this.saveDrawings)
1342
+ // let onClickDelete = () => this._drawingTool.delete(Drawing.lastHoveredObject);
1343
+ // let onClickColor = (rect: DOMRect) => colorPicker.openMenu(rect)
1344
+ // let onClickStyle = (rect: DOMRect) => stylePicker.openMenu(rect)
1345
+ // contextMenu.menuItem('Color Picker', onClickColor, () => {
1346
+ // document.removeEventListener('click', colorPicker.closeMenu)
1347
+ // colorPicker._div.style.display = 'none'
1348
+ // })
1349
+ // contextMenu.menuItem('Style', onClickStyle, () => {
1350
+ // document.removeEventListener('click', stylePicker.closeMenu)
1351
+ // stylePicker._div.style.display = 'none'
1352
+ // })
1353
+ // contextMenu.separator()
1354
+ // contextMenu.menuItem('Delete Drawing', onClickDelete)
1355
+ ev.preventDefault();
1356
+ this.div.style.left = ev.clientX + 'px';
1357
+ this.div.style.top = ev.clientY + 'px';
1358
+ this.div.style.display = 'block';
1359
+ document.body.addEventListener('click', this._handleClick);
1360
+ }
1361
+ menuItem(text, action, hover = null) {
1362
+ const item = document.createElement('span');
1363
+ item.classList.add('context-menu-item');
1364
+ this.div.appendChild(item);
1365
+ const elem = document.createElement('span');
1366
+ elem.innerText = text;
1367
+ elem.style.pointerEvents = 'none';
1368
+ item.appendChild(elem);
1369
+ if (hover) {
1370
+ let arrow = document.createElement('span');
1371
+ arrow.innerText = `►`;
1372
+ arrow.style.fontSize = '8px';
1373
+ arrow.style.pointerEvents = 'none';
1374
+ item.appendChild(arrow);
1375
+ }
1376
+ item.addEventListener('mouseover', () => {
1377
+ if (this.hoverItem && this.hoverItem.closeAction)
1378
+ this.hoverItem.closeAction();
1379
+ this.hoverItem = { elem: elem, action: action, closeAction: hover };
1380
+ });
1381
+ if (!hover)
1382
+ item.addEventListener('click', (event) => { action(event); this.div.style.display = 'none'; });
1383
+ else {
1384
+ let timeout;
1385
+ item.addEventListener('mouseover', () => timeout = setTimeout(() => action(item.getBoundingClientRect()), 100));
1386
+ item.addEventListener('mouseout', () => clearTimeout(timeout));
1387
+ }
1388
+ this.items.push(item);
1389
+ }
1390
+ separator() {
1391
+ const separator = document.createElement('div');
1392
+ separator.style.width = '90%';
1393
+ separator.style.height = '1px';
1394
+ separator.style.margin = '3px 0px';
1395
+ separator.style.backgroundColor = window.pane.borderColor;
1396
+ this.div.appendChild(separator);
1397
+ this.items.push(separator);
1398
+ }
1399
+ }
1400
+
1401
+ class RayLine extends HorizontalLine {
1402
+ _type = 'RayLine';
1403
+ constructor(point, options) {
1404
+ super({ ...point }, options);
1405
+ this._point.time = point.time;
1406
+ }
1407
+ updatePoints(...points) {
1408
+ for (const p of points)
1409
+ if (p)
1410
+ this._point = p;
1411
+ this.requestUpdate();
1412
+ }
1413
+ _onDrag(diff) {
1414
+ this._addDiffToPoint(this._point, diff.logical, diff.price);
1415
+ this.requestUpdate();
1416
+ }
1417
+ _mouseIsOverDrawing(param, tolerance = 4) {
1418
+ if (!param.point)
1419
+ return false;
1420
+ const y = this.series.priceToCoordinate(this._point.price);
1421
+ const x = this._point.time ? this.chart.timeScale().timeToCoordinate(this._point.time) : null;
1422
+ if (!y || !x)
1423
+ return false;
1424
+ return (Math.abs(y - param.point.y) < tolerance && param.point.x > x - tolerance);
1425
+ }
1426
+ }
1427
+
1428
+ class VerticalLinePaneRenderer extends DrawingPaneRenderer {
1429
+ _point = { x: null, y: null };
1430
+ constructor(point, options) {
1431
+ super(options);
1432
+ this._point = point;
1433
+ }
1434
+ draw(target) {
1435
+ target.useBitmapCoordinateSpace(scope => {
1436
+ if (this._point.x == null)
1437
+ return;
1438
+ const ctx = scope.context;
1439
+ const scaledX = this._point.x * scope.horizontalPixelRatio;
1440
+ ctx.lineWidth = this._options.width;
1441
+ ctx.strokeStyle = this._options.lineColor;
1442
+ setLineStyle(ctx, this._options.lineStyle);
1443
+ ctx.beginPath();
1444
+ ctx.moveTo(scaledX, 0);
1445
+ ctx.lineTo(scaledX, scope.bitmapSize.height);
1446
+ ctx.stroke();
1447
+ });
1448
+ }
1449
+ }
1450
+
1451
+ class VerticalLinePaneView extends DrawingPaneView {
1452
+ _source;
1453
+ _point = { x: null, y: null };
1454
+ constructor(source) {
1455
+ super(source);
1456
+ this._source = source;
1457
+ }
1458
+ update() {
1459
+ const point = this._source._point;
1460
+ const timeScale = this._source.chart.timeScale();
1461
+ const series = this._source.series;
1462
+ this._point.x = point.time ? timeScale.timeToCoordinate(point.time) : timeScale.logicalToCoordinate(point.logical);
1463
+ this._point.y = series.priceToCoordinate(point.price);
1464
+ }
1465
+ renderer() {
1466
+ return new VerticalLinePaneRenderer(this._point, this._source._options);
1467
+ }
1468
+ }
1469
+
1470
+ class VerticalLineTimeAxisView {
1471
+ _source;
1472
+ _x = null;
1473
+ constructor(source) {
1474
+ this._source = source;
1475
+ }
1476
+ update() {
1477
+ if (!this._source.chart || !this._source._point)
1478
+ return;
1479
+ const point = this._source._point;
1480
+ const timeScale = this._source.chart.timeScale();
1481
+ this._x = point.time ? timeScale.timeToCoordinate(point.time) : timeScale.logicalToCoordinate(point.logical);
1482
+ }
1483
+ visible() {
1484
+ return true;
1485
+ }
1486
+ tickVisible() {
1487
+ return true;
1488
+ }
1489
+ coordinate() {
1490
+ return this._x ?? 0;
1491
+ }
1492
+ text() {
1493
+ return '';
1494
+ }
1495
+ textColor() {
1496
+ return "white";
1497
+ }
1498
+ backColor() {
1499
+ return this._source._options.lineColor;
1500
+ }
1501
+ }
1502
+
1503
+ class VerticalLine extends Drawing {
1504
+ _type = 'VerticalLine';
1505
+ _paneViews;
1506
+ _timeAxisViews;
1507
+ _point;
1508
+ _callbackName;
1509
+ _startDragPoint = null;
1510
+ constructor(point, options, callbackName = null) {
1511
+ super(options);
1512
+ this._point = point;
1513
+ this._paneViews = [new VerticalLinePaneView(this)];
1514
+ this._callbackName = callbackName;
1515
+ this._timeAxisViews = [new VerticalLineTimeAxisView(this)];
1516
+ }
1517
+ updateAllViews() {
1518
+ this._paneViews.forEach(pw => pw.update());
1519
+ this._timeAxisViews.forEach(tw => tw.update());
1520
+ }
1521
+ timeAxisViews() {
1522
+ return this._timeAxisViews;
1523
+ }
1524
+ updatePoints(...points) {
1525
+ for (const p of points) {
1526
+ if (!p)
1527
+ continue;
1528
+ if (!p.time && p.logical) {
1529
+ p.time = this.series.dataByIndex(p.logical)?.time || null;
1530
+ }
1531
+ this._point = p;
1532
+ }
1533
+ this.requestUpdate();
1534
+ }
1535
+ get points() {
1536
+ return [this._point];
1537
+ }
1538
+ _moveToState(state) {
1539
+ switch (state) {
1540
+ case InteractionState.NONE:
1541
+ document.body.style.cursor = "default";
1542
+ this._unsubscribe("mousedown", this._handleMouseDownInteraction);
1543
+ break;
1544
+ case InteractionState.HOVERING:
1545
+ document.body.style.cursor = "pointer";
1546
+ this._unsubscribe("mouseup", this._childHandleMouseUpInteraction);
1547
+ this._subscribe("mousedown", this._handleMouseDownInteraction);
1548
+ this.chart.applyOptions({ handleScroll: true });
1549
+ break;
1550
+ case InteractionState.DRAGGING:
1551
+ document.body.style.cursor = "grabbing";
1552
+ this._subscribe("mouseup", this._childHandleMouseUpInteraction);
1553
+ this.chart.applyOptions({ handleScroll: false });
1554
+ break;
1555
+ }
1556
+ this._state = state;
1557
+ }
1558
+ _onDrag(diff) {
1559
+ this._addDiffToPoint(this._point, diff.logical, 0);
1560
+ this.requestUpdate();
1561
+ }
1562
+ _mouseIsOverDrawing(param, tolerance = 4) {
1563
+ if (!param.point)
1564
+ return false;
1565
+ const timeScale = this.chart.timeScale();
1566
+ let x;
1567
+ if (this._point.time) {
1568
+ x = timeScale.timeToCoordinate(this._point.time);
1569
+ }
1570
+ else {
1571
+ x = timeScale.logicalToCoordinate(this._point.logical);
1572
+ }
1573
+ if (!x)
1574
+ return false;
1575
+ return (Math.abs(x - param.point.x) < tolerance);
1576
+ }
1577
+ _onMouseDown() {
1578
+ this._startDragPoint = null;
1579
+ const hoverPoint = this._latestHoverPoint;
1580
+ if (!hoverPoint)
1581
+ return;
1582
+ return this._moveToState(InteractionState.DRAGGING);
1583
+ }
1584
+ _childHandleMouseUpInteraction = () => {
1585
+ this._handleMouseUpInteraction();
1586
+ if (!this._callbackName)
1587
+ return;
1588
+ window.callbackFunction(`${this._callbackName}_~_${this._point.price.toFixed(8)}`);
1589
+ };
1590
+ }
1591
+
1592
+ class ToolBox {
1593
+ static TREND_SVG = '<rect x="3.84" y="13.67" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -5.9847 14.4482)" width="21.21" height="1.56"/><path d="M23,3.17L20.17,6L23,8.83L25.83,6L23,3.17z M23,7.41L21.59,6L23,4.59L24.41,6L23,7.41z"/><path d="M6,20.17L3.17,23L6,25.83L8.83,23L6,20.17z M6,24.41L4.59,23L6,21.59L7.41,23L6,24.41z"/>';
1594
+ static HORZ_SVG = '<rect x="4" y="14" width="9" height="1"/><rect x="16" y="14" width="9" height="1"/><path d="M11.67,14.5l2.83,2.83l2.83-2.83l-2.83-2.83L11.67,14.5z M15.91,14.5l-1.41,1.41l-1.41-1.41l1.41-1.41L15.91,14.5z"/>';
1595
+ static RAY_SVG = '<rect x="8" y="14" width="17" height="1"/><path d="M3.67,14.5l2.83,2.83l2.83-2.83L6.5,11.67L3.67,14.5z M7.91,14.5L6.5,15.91L5.09,14.5l1.41-1.41L7.91,14.5z"/>';
1596
+ static BOX_SVG = '<rect x="8" y="6" width="12" height="1"/><rect x="9" y="22" width="11" height="1"/><path d="M3.67,6.5L6.5,9.33L9.33,6.5L6.5,3.67L3.67,6.5z M7.91,6.5L6.5,7.91L5.09,6.5L6.5,5.09L7.91,6.5z"/><path d="M19.67,6.5l2.83,2.83l2.83-2.83L22.5,3.67L19.67,6.5z M23.91,6.5L22.5,7.91L21.09,6.5l1.41-1.41L23.91,6.5z"/><path d="M19.67,22.5l2.83,2.83l2.83-2.83l-2.83-2.83L19.67,22.5z M23.91,22.5l-1.41,1.41l-1.41-1.41l1.41-1.41L23.91,22.5z"/><path d="M3.67,22.5l2.83,2.83l2.83-2.83L6.5,19.67L3.67,22.5z M7.91,22.5L6.5,23.91L5.09,22.5l1.41-1.41L7.91,22.5z"/><rect x="22" y="9" width="1" height="11"/><rect x="6" y="9" width="1" height="11"/>';
1597
+ static VERT_SVG = ToolBox.RAY_SVG;
1598
+ div;
1599
+ activeIcon = null;
1600
+ buttons = [];
1601
+ _commandFunctions;
1602
+ _handlerID;
1603
+ _drawingTool;
1604
+ constructor(handlerID, chart, series, commandFunctions) {
1605
+ this._handlerID = handlerID;
1606
+ this._commandFunctions = commandFunctions;
1607
+ this._drawingTool = new DrawingTool(chart, series, () => this.removeActiveAndSave());
1608
+ this.div = this._makeToolBox();
1609
+ new ContextMenu(this.saveDrawings, this._drawingTool);
1610
+ commandFunctions.push((event) => {
1611
+ if ((event.metaKey || event.ctrlKey) && event.code === 'KeyZ') {
1612
+ const drawingToDelete = this._drawingTool.drawings.pop();
1613
+ if (drawingToDelete)
1614
+ this._drawingTool.delete(drawingToDelete);
1615
+ return true;
1616
+ }
1617
+ return false;
1618
+ });
1619
+ }
1620
+ toJSON() {
1621
+ // Exclude the chart attribute from serialization
1622
+ const { ...serialized } = this;
1623
+ return serialized;
1624
+ }
1625
+ _makeToolBox() {
1626
+ let div = document.createElement('div');
1627
+ div.classList.add('toolbox');
1628
+ this.buttons.push(this._makeToolBoxElement(TrendLine, 'KeyT', ToolBox.TREND_SVG));
1629
+ this.buttons.push(this._makeToolBoxElement(HorizontalLine, 'KeyH', ToolBox.HORZ_SVG));
1630
+ this.buttons.push(this._makeToolBoxElement(RayLine, 'KeyR', ToolBox.RAY_SVG));
1631
+ this.buttons.push(this._makeToolBoxElement(Box, 'KeyB', ToolBox.BOX_SVG));
1632
+ this.buttons.push(this._makeToolBoxElement(VerticalLine, 'KeyV', ToolBox.VERT_SVG, true));
1633
+ for (const button of this.buttons) {
1634
+ div.appendChild(button);
1635
+ }
1636
+ return div;
1637
+ }
1638
+ _makeToolBoxElement(DrawingType, keyCmd, paths, rotate = false) {
1639
+ const elem = document.createElement('div');
1640
+ elem.classList.add("toolbox-button");
1641
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
1642
+ svg.setAttribute("width", "29");
1643
+ svg.setAttribute("height", "29");
1644
+ const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
1645
+ group.innerHTML = paths;
1646
+ group.setAttribute("fill", window.pane.color);
1647
+ svg.appendChild(group);
1648
+ elem.appendChild(svg);
1649
+ const icon = { div: elem, group: group, type: DrawingType };
1650
+ elem.addEventListener('click', () => this._onIconClick(icon));
1651
+ this._commandFunctions.push((event) => {
1652
+ if (this._handlerID !== window.handlerInFocus)
1653
+ return false;
1654
+ if (event.altKey && event.code === keyCmd) {
1655
+ event.preventDefault();
1656
+ this._onIconClick(icon);
1657
+ return true;
1658
+ }
1659
+ return false;
1660
+ });
1661
+ if (rotate == true) {
1662
+ svg.style.transform = 'rotate(90deg)';
1663
+ svg.style.transformBox = 'fill-box';
1664
+ svg.style.transformOrigin = 'center';
1665
+ }
1666
+ return elem;
1667
+ }
1668
+ _onIconClick(icon) {
1669
+ if (this.activeIcon) {
1670
+ this.activeIcon.div.classList.remove('active-toolbox-button');
1671
+ window.setCursor('crosshair');
1672
+ this._drawingTool?.stopDrawing();
1673
+ if (this.activeIcon === icon) {
1674
+ this.activeIcon = null;
1675
+ return;
1676
+ }
1677
+ }
1678
+ this.activeIcon = icon;
1679
+ this.activeIcon.div.classList.add('active-toolbox-button');
1680
+ window.setCursor('crosshair');
1681
+ this._drawingTool?.beginDrawing(this.activeIcon.type);
1682
+ }
1683
+ removeActiveAndSave = () => {
1684
+ window.setCursor('default');
1685
+ if (this.activeIcon)
1686
+ this.activeIcon.div.classList.remove('active-toolbox-button');
1687
+ this.activeIcon = null;
1688
+ this.saveDrawings();
1689
+ };
1690
+ addNewDrawing(d) {
1691
+ this._drawingTool.addNewDrawing(d);
1692
+ }
1693
+ clearDrawings() {
1694
+ this._drawingTool.clearDrawings();
1695
+ }
1696
+ saveDrawings = () => {
1697
+ const drawingMeta = [];
1698
+ for (const d of this._drawingTool.drawings) {
1699
+ drawingMeta.push({
1700
+ type: d._type,
1701
+ points: d.points,
1702
+ options: d._options
1703
+ });
1704
+ }
1705
+ const string = JSON.stringify(drawingMeta);
1706
+ window.callbackFunction(`save_drawings${this._handlerID}_~_${string}`);
1707
+ };
1708
+ loadDrawings(drawings) {
1709
+ drawings.forEach((d) => {
1710
+ switch (d.type) {
1711
+ case "Box":
1712
+ this._drawingTool.addNewDrawing(new Box(d.points[0], d.points[1], d.options));
1713
+ break;
1714
+ case "TrendLine":
1715
+ this._drawingTool.addNewDrawing(new TrendLine(d.points[0], d.points[1], d.options));
1716
+ break;
1717
+ case "HorizontalLine":
1718
+ this._drawingTool.addNewDrawing(new HorizontalLine(d.points[0], d.options));
1719
+ break;
1720
+ case "RayLine":
1721
+ this._drawingTool.addNewDrawing(new RayLine(d.points[0], d.options));
1722
+ break;
1723
+ case "VerticalLine":
1724
+ this._drawingTool.addNewDrawing(new VerticalLine(d.points[0], d.options));
1725
+ break;
1726
+ }
1727
+ });
1728
+ }
1729
+ }
1730
+
1731
+ class Menu {
1732
+ makeButton;
1733
+ callbackName;
1734
+ div;
1735
+ isOpen = false;
1736
+ widget;
1737
+ constructor(makeButton, callbackName, items, activeItem, separator, align) {
1738
+ this.makeButton = makeButton;
1739
+ this.callbackName = callbackName;
1740
+ this.div = document.createElement('div');
1741
+ this.div.classList.add('topbar-menu');
1742
+ this.widget = this.makeButton(activeItem + ' ↓', null, separator, true, align);
1743
+ this.updateMenuItems(items);
1744
+ this.widget.elem.addEventListener('click', () => {
1745
+ this.isOpen = !this.isOpen;
1746
+ if (!this.isOpen) {
1747
+ this.div.style.display = 'none';
1748
+ return;
1749
+ }
1750
+ let rect = this.widget.elem.getBoundingClientRect();
1751
+ this.div.style.display = 'flex';
1752
+ this.div.style.flexDirection = 'column';
1753
+ let center = rect.x + (rect.width / 2);
1754
+ this.div.style.left = center - (this.div.clientWidth / 2) + 'px';
1755
+ this.div.style.top = rect.y + rect.height + 'px';
1756
+ });
1757
+ document.body.appendChild(this.div);
1758
+ }
1759
+ updateMenuItems(items) {
1760
+ this.div.innerHTML = '';
1761
+ items.forEach(text => {
1762
+ let button = this.makeButton(text, null, false, false);
1763
+ button.elem.addEventListener('click', () => {
1764
+ this._clickHandler(button.elem.innerText);
1765
+ });
1766
+ button.elem.style.margin = '4px 4px';
1767
+ button.elem.style.padding = '2px 2px';
1768
+ this.div.appendChild(button.elem);
1769
+ });
1770
+ this.widget.elem.innerText = items[0] + ' ↓';
1771
+ }
1772
+ _clickHandler(name) {
1773
+ this.widget.elem.innerText = name + ' ↓';
1774
+ window.callbackFunction(`${this.callbackName}_~_${name}`);
1775
+ this.div.style.display = 'none';
1776
+ this.isOpen = false;
1777
+ }
1778
+ }
1779
+
1780
+ class TopBar {
1781
+ _handler;
1782
+ _div;
1783
+ left;
1784
+ right;
1785
+ constructor(handler) {
1786
+ this._handler = handler;
1787
+ this._div = document.createElement('div');
1788
+ this._div.classList.add('topbar');
1789
+ const createTopBarContainer = (justification) => {
1790
+ const div = document.createElement('div');
1791
+ div.classList.add('topbar-container');
1792
+ div.style.justifyContent = justification;
1793
+ this._div.appendChild(div);
1794
+ return div;
1795
+ };
1796
+ this.left = createTopBarContainer('flex-start');
1797
+ this.right = createTopBarContainer('flex-end');
1798
+ }
1799
+ makeSwitcher(items, defaultItem, callbackName, align = 'left') {
1800
+ const switcherElement = document.createElement('div');
1801
+ switcherElement.style.margin = '4px 12px';
1802
+ let activeItemEl;
1803
+ const createAndReturnSwitcherButton = (itemName) => {
1804
+ const button = document.createElement('button');
1805
+ button.classList.add('topbar-button');
1806
+ button.classList.add('switcher-button');
1807
+ button.style.margin = '0px 2px';
1808
+ button.innerText = itemName;
1809
+ if (itemName == defaultItem) {
1810
+ activeItemEl = button;
1811
+ button.classList.add('active-switcher-button');
1812
+ }
1813
+ const buttonWidth = TopBar.getClientWidth(button);
1814
+ button.style.minWidth = buttonWidth + 1 + 'px';
1815
+ button.addEventListener('click', () => widget.onItemClicked(button));
1816
+ switcherElement.appendChild(button);
1817
+ return button;
1818
+ };
1819
+ const widget = {
1820
+ elem: switcherElement,
1821
+ callbackName: callbackName,
1822
+ intervalElements: items.map(createAndReturnSwitcherButton),
1823
+ onItemClicked: (item) => {
1824
+ if (item == activeItemEl)
1825
+ return;
1826
+ activeItemEl.classList.remove('active-switcher-button');
1827
+ item.classList.add('active-switcher-button');
1828
+ activeItemEl = item;
1829
+ window.callbackFunction(`${widget.callbackName}_~_${item.innerText}`);
1830
+ }
1831
+ };
1832
+ this.appendWidget(switcherElement, align, true);
1833
+ return widget;
1834
+ }
1835
+ makeTextBoxWidget(text, align = 'left', callbackName = null) {
1836
+ if (callbackName) {
1837
+ const textBox = document.createElement('input');
1838
+ textBox.classList.add('topbar-textbox-input');
1839
+ textBox.value = text;
1840
+ textBox.style.width = `${(textBox.value.length + 2)}ch`;
1841
+ textBox.addEventListener('focus', () => {
1842
+ window.textBoxFocused = true;
1843
+ });
1844
+ textBox.addEventListener('input', (e) => {
1845
+ e.preventDefault();
1846
+ textBox.style.width = `${(textBox.value.length + 2)}ch`;
1847
+ });
1848
+ textBox.addEventListener('keydown', (e) => {
1849
+ if (e.key == 'Enter') {
1850
+ e.preventDefault();
1851
+ textBox.blur();
1852
+ }
1853
+ });
1854
+ textBox.addEventListener('blur', () => {
1855
+ window.callbackFunction(`${callbackName}_~_${textBox.value}`);
1856
+ window.textBoxFocused = false;
1857
+ });
1858
+ this.appendWidget(textBox, align, true);
1859
+ return textBox;
1860
+ }
1861
+ else {
1862
+ const textBox = document.createElement('div');
1863
+ textBox.classList.add('topbar-textbox');
1864
+ textBox.innerText = text;
1865
+ this.appendWidget(textBox, align, true);
1866
+ return textBox;
1867
+ }
1868
+ }
1869
+ makeMenu(items, activeItem, separator, callbackName, align) {
1870
+ return new Menu(this.makeButton.bind(this), callbackName, items, activeItem, separator, align);
1871
+ }
1872
+ makeButton(defaultText, callbackName, separator, append = true, align = 'left', toggle = false) {
1873
+ let button = document.createElement('button');
1874
+ button.classList.add('topbar-button');
1875
+ // button.style.color = window.pane.color
1876
+ button.innerText = defaultText;
1877
+ document.body.appendChild(button);
1878
+ button.style.minWidth = button.clientWidth + 1 + 'px';
1879
+ document.body.removeChild(button);
1880
+ let widget = {
1881
+ elem: button,
1882
+ callbackName: callbackName
1883
+ };
1884
+ if (callbackName) {
1885
+ let handler;
1886
+ if (toggle) {
1887
+ let state = false;
1888
+ handler = () => {
1889
+ state = !state;
1890
+ window.callbackFunction(`${widget.callbackName}_~_${state}`);
1891
+ button.style.backgroundColor = state ? 'var(--active-bg-color)' : '';
1892
+ button.style.color = state ? 'var(--active-color)' : '';
1893
+ };
1894
+ }
1895
+ else {
1896
+ handler = () => window.callbackFunction(`${widget.callbackName}_~_${button.innerText}`);
1897
+ }
1898
+ button.addEventListener('click', handler);
1899
+ }
1900
+ if (append)
1901
+ this.appendWidget(button, align, separator);
1902
+ return widget;
1903
+ }
1904
+ makeSeparator(align = 'left') {
1905
+ const separator = document.createElement('div');
1906
+ separator.classList.add('topbar-seperator');
1907
+ const div = align == 'left' ? this.left : this.right;
1908
+ div.appendChild(separator);
1909
+ }
1910
+ appendWidget(widget, align, separator) {
1911
+ const div = align == 'left' ? this.left : this.right;
1912
+ if (separator) {
1913
+ if (align == 'left')
1914
+ div.appendChild(widget);
1915
+ this.makeSeparator(align);
1916
+ if (align == 'right')
1917
+ div.appendChild(widget);
1918
+ }
1919
+ else
1920
+ div.appendChild(widget);
1921
+ this._handler.reSize();
1922
+ }
1923
+ static getClientWidth(element) {
1924
+ document.body.appendChild(element);
1925
+ const width = element.clientWidth;
1926
+ document.body.removeChild(element);
1927
+ return width;
1928
+ }
1929
+ }
1930
+
1931
+ globalParamInit();
1932
+ class Handler {
1933
+ id;
1934
+ commandFunctions = [];
1935
+ wrapper;
1936
+ div;
1937
+ chart;
1938
+ scale;
1939
+ precision = 2;
1940
+ series;
1941
+ volumeSeries;
1942
+ legend;
1943
+ _topBar;
1944
+ toolBox;
1945
+ spinner;
1946
+ _seriesList = [];
1947
+ resize_hdr_height = 8;
1948
+ watermark;
1949
+ seriesMarkers;
1950
+ // TODO find a better solution rather than the 'position' parameter
1951
+ constructor(chartId, innerWidth, innerHeight, position, autoSize, paneIndex = 0) {
1952
+ this.reSize = this.reSize.bind(this);
1953
+ this.id = chartId;
1954
+ this.scale = {
1955
+ width: innerWidth,
1956
+ height: innerHeight,
1957
+ };
1958
+ this.wrapper = document.createElement('div');
1959
+ this.wrapper.classList.add("handler");
1960
+ this.wrapper.style.float = position;
1961
+ this.div = document.createElement('div');
1962
+ this.div.style.position = 'relative';
1963
+ this.wrapper.appendChild(this.div);
1964
+ window.containerDiv.append(this.wrapper);
1965
+ // --- add this block to enable mouse‐drag height resizing ---
1966
+ const handle = document.createElement('div');
1967
+ handle.classList.add('resize-handle');
1968
+ this.wrapper.appendChild(handle);
1969
+ let startY, startHeight;
1970
+ const onMouseMove = (e) => {
1971
+ const delta = e.clientY - startY;
1972
+ const newH = Math.max(50, startHeight + delta); // min height 50px
1973
+ this.wrapper.style.height = `${newH}px`;
1974
+ // Resize the chart canvas accordingly:
1975
+ this.chart.resize(this.wrapper.offsetWidth, newH - this.resize_hdr_height);
1976
+ };
1977
+ const onMouseUp = () => {
1978
+ document.removeEventListener('mousemove', onMouseMove);
1979
+ document.removeEventListener('mouseup', onMouseUp);
1980
+ };
1981
+ handle.addEventListener('mousedown', (e) => {
1982
+ // prevent selecting text, etc.
1983
+ e.preventDefault();
1984
+ startY = e.clientY;
1985
+ startHeight = this.wrapper.getBoundingClientRect().height;
1986
+ document.addEventListener('mousemove', onMouseMove);
1987
+ document.addEventListener('mouseup', onMouseUp);
1988
+ });
1989
+ this.chart = this._createChart();
1990
+ this.series = this.createCandlestickSeries(paneIndex);
1991
+ this.volumeSeries = this.createVolumeSeries(paneIndex);
1992
+ this.seriesMarkers = lightweightCharts.createSeriesMarkers(this.series, []);
1993
+ this.legend = new Legend(this);
1994
+ document.addEventListener('keydown', (event) => {
1995
+ for (let i = 0; i < this.commandFunctions.length; i++) {
1996
+ if (this.commandFunctions[i](event))
1997
+ break;
1998
+ }
1999
+ });
2000
+ window.handlerInFocus = this.id;
2001
+ this.wrapper.addEventListener('mouseover', () => window.handlerInFocus = this.id);
2002
+ this.reSize();
2003
+ if (!autoSize)
2004
+ return;
2005
+ window.addEventListener('resize', () => this.reSize());
2006
+ }
2007
+ reSize() {
2008
+ let topBarOffset = this.scale.height !== 0 ? this._topBar?._div.offsetHeight || 0 : 0;
2009
+ if (this.scale.height >= 0) {
2010
+ this.chart.resize(window.innerWidth * this.scale.width, (window.innerHeight * this.scale.height) - topBarOffset - this.resize_hdr_height);
2011
+ this.wrapper.style.width = `${100 * this.scale.width}%`;
2012
+ this.wrapper.style.height = `${100 * this.scale.height}%`;
2013
+ }
2014
+ else {
2015
+ var chart_height = Math.ceil(Math.abs(this.scale.height));
2016
+ this.chart.resize(window.containerDiv.offsetWidth * this.scale.width, chart_height - topBarOffset - this.resize_hdr_height);
2017
+ this.wrapper.style.width = `${100 * this.scale.width}%`;
2018
+ this.wrapper.style.height = `${chart_height}px`;
2019
+ }
2020
+ // TODO definitely a better way to do this
2021
+ if (this.scale.height === 0 || this.scale.width === 0) {
2022
+ // if (this.legend.div.style.display == 'flex') this.legend.div.style.display = 'none'
2023
+ if (this.toolBox) {
2024
+ this.toolBox.div.style.display = 'none';
2025
+ }
2026
+ }
2027
+ else {
2028
+ // this.legend.div.style.display = 'flex'
2029
+ if (this.toolBox) {
2030
+ this.toolBox.div.style.display = 'flex';
2031
+ }
2032
+ }
2033
+ }
2034
+ _createChart() {
2035
+ return lightweightCharts.createChart(this.div, {
2036
+ width: window.containerDiv.offsetWidth * this.scale.width,
2037
+ height: this.scale.height < 0 ? Math.ceil(Math.abs(this.scale.height)) : window.innerHeight * this.scale.height,
2038
+ layout: {
2039
+ textColor: window.pane.color,
2040
+ background: {
2041
+ color: 'rgb(18,24,38)',
2042
+ type: lightweightCharts.ColorType.Solid,
2043
+ },
2044
+ fontSize: 12,
2045
+ panes: {
2046
+ separatorColor: 'lightgrey',
2047
+ separatorHoverColor: "rgba(255, 0, 0, 0.4)",
2048
+ enableResize: true,
2049
+ },
2050
+ },
2051
+ rightPriceScale: {
2052
+ scaleMargins: { top: 0.3, bottom: 0.25 },
2053
+ },
2054
+ timeScale: { timeVisible: true, secondsVisible: false },
2055
+ crosshair: {
2056
+ mode: lightweightCharts.CrosshairMode.Normal,
2057
+ vertLine: {
2058
+ labelBackgroundColor: 'rgb(46, 46, 46)',
2059
+ },
2060
+ horzLine: {
2061
+ labelBackgroundColor: 'rgb(55, 55, 55)',
2062
+ },
2063
+ },
2064
+ grid: {
2065
+ vertLines: { color: '#444', style: 1 },
2066
+ horzLines: { color: '#444', style: 1 },
2067
+ },
2068
+ handleScroll: { vertTouchDrag: true },
2069
+ });
2070
+ }
2071
+ createCandlestickSeries(paneIndex) {
2072
+ const up = 'rgba(39, 157, 130, 100)';
2073
+ const down = 'rgba(200, 97, 100, 100)';
2074
+ const candleSeries = this.chart.addSeries(lightweightCharts.CandlestickSeries, {
2075
+ upColor: up, borderUpColor: up, wickUpColor: up,
2076
+ downColor: down, borderDownColor: down, wickDownColor: down
2077
+ }, paneIndex);
2078
+ candleSeries.priceScale().applyOptions({
2079
+ scaleMargins: { top: 0.2, bottom: 0.2 },
2080
+ });
2081
+ return candleSeries;
2082
+ }
2083
+ createVolumeSeries(paneIndex) {
2084
+ const volumeSeries = this.chart.addSeries(lightweightCharts.HistogramSeries, {
2085
+ color: '#26a69a',
2086
+ priceFormat: { type: 'volume' },
2087
+ priceScaleId: 'volume_scale',
2088
+ }, paneIndex);
2089
+ volumeSeries.priceScale().applyOptions({
2090
+ scaleMargins: { top: 0.8, bottom: 0 },
2091
+ });
2092
+ return volumeSeries;
2093
+ }
2094
+ createLineSeries(name, options, paneIndex = 0) {
2095
+ const line = this.chart.addSeries(lightweightCharts.LineSeries, { ...options }, paneIndex);
2096
+ this._seriesList.push(line);
2097
+ this.legend.makeSeriesRow(name, line, paneIndex);
2098
+ return {
2099
+ name: name,
2100
+ series: line,
2101
+ };
2102
+ }
2103
+ createHistogramSeries(name, options, paneIndex = 0) {
2104
+ const line = this.chart.addSeries(lightweightCharts.HistogramSeries, { ...options }, paneIndex);
2105
+ this._seriesList.push(line);
2106
+ this.legend.makeSeriesRow(name, line, paneIndex);
2107
+ return {
2108
+ name: name,
2109
+ series: line,
2110
+ };
2111
+ }
2112
+ createToolBox() {
2113
+ this.toolBox = new ToolBox(this.id, this.chart, this.series, this.commandFunctions);
2114
+ this.div.appendChild(this.toolBox.div);
2115
+ }
2116
+ createTopBar() {
2117
+ this._topBar = new TopBar(this);
2118
+ this.wrapper.prepend(this._topBar._div);
2119
+ return this._topBar;
2120
+ }
2121
+ toJSON() {
2122
+ // Exclude the chart attribute from serialization
2123
+ const { chart, ...serialized } = this;
2124
+ return serialized;
2125
+ }
2126
+ static syncChartsAll(handlers, crosshairOnly = false) {
2127
+ // 1) Crosshair
2128
+ handlers.forEach((source) => {
2129
+ source.chart.subscribeCrosshairMove((param) => {
2130
+ handlers.forEach((target) => {
2131
+ if (target === source)
2132
+ return;
2133
+ if (!param.time) {
2134
+ target.chart.clearCrosshairPosition();
2135
+ return;
2136
+ }
2137
+ // get the point from the source series (for legend update)
2138
+ const point = param.seriesData.get(source.series) || null;
2139
+ // set the crosshair on the target chart
2140
+ target.chart.setCrosshairPosition(0, param.time, target.series);
2141
+ // update the legend on the target
2142
+ if (point) {
2143
+ target.legend.legendHandler(point, true);
2144
+ }
2145
+ });
2146
+ });
2147
+ });
2148
+ if (crosshairOnly)
2149
+ return;
2150
+ // 2) Visible range synchronization
2151
+ handlers.forEach((source) => {
2152
+ source.chart.timeScale().subscribeVisibleLogicalRangeChange((range) => {
2153
+ handlers.forEach((target) => {
2154
+ if (target === source || !range)
2155
+ return;
2156
+ target.chart.timeScale().setVisibleLogicalRange(range);
2157
+ });
2158
+ });
2159
+ });
2160
+ }
2161
+ static syncCharts(childChart, parentChart, crosshairOnly = false) {
2162
+ function crosshairHandler(chart, point, param) {
2163
+ if (!param.time) {
2164
+ chart.chart.clearCrosshairPosition();
2165
+ return;
2166
+ }
2167
+ chart.chart.setCrosshairPosition(0, param.time, chart.series);
2168
+ if (point)
2169
+ chart.legend.legendHandler(point, true);
2170
+ }
2171
+ function getPoint(series, param) {
2172
+ if (!param.time)
2173
+ return null;
2174
+ return param.seriesData.get(series) || null;
2175
+ }
2176
+ const childTimeScale = childChart.chart.timeScale();
2177
+ const parentTimeScale = parentChart.chart.timeScale();
2178
+ const setChildRange = (timeRange) => {
2179
+ if (timeRange)
2180
+ childTimeScale.setVisibleLogicalRange(timeRange);
2181
+ };
2182
+ const setParentRange = (timeRange) => {
2183
+ if (timeRange)
2184
+ parentTimeScale.setVisibleLogicalRange(timeRange);
2185
+ };
2186
+ const setParentCrosshair = (param) => {
2187
+ crosshairHandler(parentChart, getPoint(childChart.series, param), param);
2188
+ };
2189
+ const setChildCrosshair = (param) => {
2190
+ crosshairHandler(childChart, getPoint(parentChart.series, param), param);
2191
+ };
2192
+ parentChart.chart.subscribeCrosshairMove(setChildCrosshair);
2193
+ childChart.chart.subscribeCrosshairMove(setParentCrosshair);
2194
+ if (crosshairOnly)
2195
+ return;
2196
+ childChart.chart.timeScale().subscribeVisibleLogicalRangeChange(setParentRange);
2197
+ parentChart.chart.timeScale().subscribeVisibleLogicalRangeChange(setChildRange);
2198
+ }
2199
+ static makeSearchBox(chart) {
2200
+ const searchWindow = document.createElement('div');
2201
+ searchWindow.classList.add('searchbox');
2202
+ searchWindow.style.display = 'none';
2203
+ const magnifyingGlass = document.createElement('div');
2204
+ magnifyingGlass.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1"><path style="fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke:lightgray;stroke-opacity:1;stroke-miterlimit:4;" d="M 15 15 L 21 21 M 10 17 C 6.132812 17 3 13.867188 3 10 C 3 6.132812 6.132812 3 10 3 C 13.867188 3 17 6.132812 17 10 C 17 13.867188 13.867188 17 10 17 Z M 10 17 "/></svg>`;
2205
+ const sBox = document.createElement('input');
2206
+ sBox.type = 'text';
2207
+ searchWindow.appendChild(magnifyingGlass);
2208
+ searchWindow.appendChild(sBox);
2209
+ chart.div.appendChild(searchWindow);
2210
+ chart.commandFunctions.push((event) => {
2211
+ if (window.handlerInFocus !== chart.id || window.textBoxFocused)
2212
+ return false;
2213
+ if (searchWindow.style.display === 'none') {
2214
+ if (/^[a-zA-Z0-9]$/.test(event.key)) {
2215
+ searchWindow.style.display = 'flex';
2216
+ sBox.focus();
2217
+ return true;
2218
+ }
2219
+ else
2220
+ return false;
2221
+ }
2222
+ else if (event.key === 'Enter' || event.key === 'Escape') {
2223
+ if (event.key === 'Enter')
2224
+ window.callbackFunction(`search${chart.id}_~_${sBox.value}`);
2225
+ searchWindow.style.display = 'none';
2226
+ sBox.value = '';
2227
+ return true;
2228
+ }
2229
+ else
2230
+ return false;
2231
+ });
2232
+ sBox.addEventListener('input', () => sBox.value = sBox.value.toUpperCase());
2233
+ return {
2234
+ window: searchWindow,
2235
+ box: sBox,
2236
+ };
2237
+ }
2238
+ static makeSpinner(chart) {
2239
+ chart.spinner = document.createElement('div');
2240
+ chart.spinner.classList.add('spinner');
2241
+ chart.wrapper.appendChild(chart.spinner);
2242
+ // TODO below can be css (animate)
2243
+ let rotation = 0;
2244
+ const speed = 10;
2245
+ function animateSpinner() {
2246
+ if (!chart.spinner)
2247
+ return;
2248
+ rotation += speed;
2249
+ chart.spinner.style.transform = `translate(-50%, -50%) rotate(${rotation}deg)`;
2250
+ requestAnimationFrame(animateSpinner);
2251
+ }
2252
+ animateSpinner();
2253
+ }
2254
+ static _styleMap = {
2255
+ '--bg-color': 'backgroundColor',
2256
+ '--hover-bg-color': 'hoverBackgroundColor',
2257
+ '--click-bg-color': 'clickBackgroundColor',
2258
+ '--active-bg-color': 'activeBackgroundColor',
2259
+ '--muted-bg-color': 'mutedBackgroundColor',
2260
+ '--border-color': 'borderColor',
2261
+ '--color': 'color',
2262
+ '--active-color': 'activeColor',
2263
+ };
2264
+ static setRootStyles(styles) {
2265
+ const rootStyle = document.documentElement.style;
2266
+ for (const [property, valueKey] of Object.entries(this._styleMap)) {
2267
+ rootStyle.setProperty(property, styles[valueKey]);
2268
+ }
2269
+ }
2270
+ createWatermark(text, fontSize, color) {
2271
+ if (!this.watermark) {
2272
+ this.watermark = lightweightCharts.createTextWatermark(this.chart.panes()[0], {
2273
+ horzAlign: 'center',
2274
+ vertAlign: 'center',
2275
+ lines: [{
2276
+ text: text,
2277
+ color: color,
2278
+ fontSize: fontSize,
2279
+ }],
2280
+ });
2281
+ return;
2282
+ }
2283
+ this.watermark.applyOptions({
2284
+ lines: [{
2285
+ text: text,
2286
+ color: color,
2287
+ fontSize: fontSize,
2288
+ }]
2289
+ });
2290
+ }
2291
+ }
2292
+
2293
+ class Table {
2294
+ _div;
2295
+ callbackName;
2296
+ borderColor;
2297
+ borderWidth;
2298
+ table;
2299
+ rows = {};
2300
+ headings;
2301
+ widths;
2302
+ alignments;
2303
+ footer;
2304
+ header;
2305
+ constructor(width, height, headings, widths, alignments, position, draggable = false, tableBackgroundColor, borderColor, borderWidth, textColors, backgroundColors) {
2306
+ this._div = document.createElement('div');
2307
+ this.callbackName = null;
2308
+ this.borderColor = borderColor;
2309
+ this.borderWidth = borderWidth;
2310
+ if (draggable) {
2311
+ this._div.style.position = 'absolute';
2312
+ this._div.style.cursor = 'move';
2313
+ }
2314
+ else {
2315
+ this._div.style.position = 'relative';
2316
+ this._div.style.float = position;
2317
+ }
2318
+ this._div.style.zIndex = '2000';
2319
+ this.reSize(width, height);
2320
+ this._div.style.display = 'flex';
2321
+ this._div.style.flexDirection = 'column';
2322
+ // this._div.style.justifyContent = 'space-between'
2323
+ this._div.style.borderRadius = '5px';
2324
+ this._div.style.color = 'white';
2325
+ this._div.style.fontSize = '12px';
2326
+ this._div.style.fontVariantNumeric = 'tabular-nums';
2327
+ this.table = document.createElement('table');
2328
+ this.table.style.width = '100%';
2329
+ this.table.style.borderCollapse = 'collapse';
2330
+ this._div.style.overflow = 'hidden';
2331
+ this.headings = headings;
2332
+ this.widths = widths.map((width) => `${width * 100}%`);
2333
+ this.alignments = alignments;
2334
+ let head = this.table.createTHead();
2335
+ let row = head.insertRow();
2336
+ for (let i = 0; i < this.headings.length; i++) {
2337
+ let th = document.createElement('th');
2338
+ th.textContent = this.headings[i];
2339
+ th.style.width = this.widths[i];
2340
+ th.style.letterSpacing = '0.03rem';
2341
+ th.style.padding = '0.2rem 0px';
2342
+ th.style.fontWeight = '500';
2343
+ th.style.textAlign = 'center';
2344
+ if (i !== 0)
2345
+ th.style.borderLeft = borderWidth + 'px solid ' + borderColor;
2346
+ th.style.position = 'sticky';
2347
+ th.style.top = '0';
2348
+ th.style.backgroundColor = backgroundColors.length > 0 ? backgroundColors[i] : tableBackgroundColor;
2349
+ th.style.color = textColors[i];
2350
+ row.appendChild(th);
2351
+ }
2352
+ let overflowWrapper = document.createElement('div');
2353
+ overflowWrapper.style.overflowY = 'auto';
2354
+ overflowWrapper.style.overflowX = 'hidden';
2355
+ overflowWrapper.style.backgroundColor = tableBackgroundColor;
2356
+ overflowWrapper.appendChild(this.table);
2357
+ this._div.appendChild(overflowWrapper);
2358
+ window.containerDiv.appendChild(this._div);
2359
+ if (!draggable)
2360
+ return;
2361
+ let offsetX, offsetY;
2362
+ let onMouseDown = (event) => {
2363
+ offsetX = event.clientX - this._div.offsetLeft;
2364
+ offsetY = event.clientY - this._div.offsetTop;
2365
+ document.addEventListener('mousemove', onMouseMove);
2366
+ document.addEventListener('mouseup', onMouseUp);
2367
+ };
2368
+ let onMouseMove = (event) => {
2369
+ this._div.style.left = (event.clientX - offsetX) + 'px';
2370
+ this._div.style.top = (event.clientY - offsetY) + 'px';
2371
+ };
2372
+ let onMouseUp = () => {
2373
+ // Remove the event listeners for dragging
2374
+ document.removeEventListener('mousemove', onMouseMove);
2375
+ document.removeEventListener('mouseup', onMouseUp);
2376
+ };
2377
+ this._div.addEventListener('mousedown', onMouseDown);
2378
+ }
2379
+ divToButton(div, callbackString) {
2380
+ div.addEventListener('mouseover', () => div.style.backgroundColor = 'rgba(60, 60, 60, 0.6)');
2381
+ div.addEventListener('mouseout', () => div.style.backgroundColor = 'transparent');
2382
+ div.addEventListener('mousedown', () => div.style.backgroundColor = 'rgba(60, 60, 60)');
2383
+ div.addEventListener('click', () => window.callbackFunction(callbackString));
2384
+ div.addEventListener('mouseup', () => div.style.backgroundColor = 'rgba(60, 60, 60, 0.6)');
2385
+ }
2386
+ newRow(id, returnClickedCell = false) {
2387
+ let row = this.table.insertRow();
2388
+ row.style.cursor = 'default';
2389
+ for (let i = 0; i < this.headings.length; i++) {
2390
+ let cell = row.insertCell();
2391
+ cell.style.width = this.widths[i];
2392
+ cell.style.textAlign = this.alignments[i];
2393
+ cell.style.border = this.borderWidth + 'px solid ' + this.borderColor;
2394
+ if (returnClickedCell) {
2395
+ this.divToButton(cell, `${this.callbackName}_~_${id};;;${this.headings[i]}`);
2396
+ }
2397
+ }
2398
+ if (!returnClickedCell) {
2399
+ this.divToButton(row, `${this.callbackName}_~_${id}`);
2400
+ }
2401
+ this.rows[id] = row;
2402
+ }
2403
+ deleteRow(id) {
2404
+ this.table.deleteRow(this.rows[id].rowIndex);
2405
+ delete this.rows[id];
2406
+ }
2407
+ clearRows() {
2408
+ let numRows = Object.keys(this.rows).length;
2409
+ for (let i = 0; i < numRows; i++)
2410
+ this.table.deleteRow(-1);
2411
+ this.rows = {};
2412
+ }
2413
+ _getCell(rowId, column) {
2414
+ return this.rows[rowId].cells[this.headings.indexOf(column)];
2415
+ }
2416
+ updateCell(rowId, column, val) {
2417
+ this._getCell(rowId, column).textContent = val;
2418
+ }
2419
+ styleCell(rowId, column, styleAttribute, value) {
2420
+ const style = this._getCell(rowId, column).style;
2421
+ style[styleAttribute] = value;
2422
+ }
2423
+ makeSection(id, type, numBoxes, func = false) {
2424
+ let section = document.createElement('div');
2425
+ section.style.display = 'flex';
2426
+ section.style.width = '100%';
2427
+ section.style.padding = '3px 0px';
2428
+ section.style.backgroundColor = 'rgb(30, 30, 30)';
2429
+ type === 'footer' ? this._div.appendChild(section) : this._div.prepend(section);
2430
+ const textBoxes = [];
2431
+ for (let i = 0; i < numBoxes; i++) {
2432
+ let textBox = document.createElement('div');
2433
+ section.appendChild(textBox);
2434
+ textBox.style.flex = '1';
2435
+ textBox.style.textAlign = 'center';
2436
+ if (func) {
2437
+ this.divToButton(textBox, `${id}_~_${i}`);
2438
+ textBox.style.borderRadius = '2px';
2439
+ }
2440
+ textBoxes.push(textBox);
2441
+ }
2442
+ if (type === 'footer') {
2443
+ this.footer = textBoxes;
2444
+ }
2445
+ else {
2446
+ this.header = textBoxes;
2447
+ }
2448
+ }
2449
+ reSize(width, height) {
2450
+ this._div.style.width = width <= 1 ? width * 100 + '%' : width + 'px';
2451
+ this._div.style.height = height <= 1 ? height * 100 + '%' : height + 'px';
2452
+ }
2453
+ }
2454
+
2455
+ exports.Box = Box;
2456
+ exports.Handler = Handler;
2457
+ exports.HorizontalLine = HorizontalLine;
2458
+ exports.Legend = Legend;
2459
+ exports.RayLine = RayLine;
2460
+ exports.Table = Table;
2461
+ exports.ToolBox = ToolBox;
2462
+ exports.TopBar = TopBar;
2463
+ exports.TrendLine = TrendLine;
2464
+ exports.VerticalLine = VerticalLine;
2465
+ exports.globalParamInit = globalParamInit;
2466
+ exports.htmlToElement = htmlToElement;
2467
+ exports.paneStyleDefault = paneStyleDefault;
2468
+ exports.setCursor = setCursor;
2469
+
2470
+ return exports;
2471
+
2472
+ })({}, LightweightCharts);